mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-19 10:47:56 +08:00
Add SvgTextExporter
Modify WriteSvg to account for height
This commit is contained in:
@@ -311,7 +311,7 @@
|
||||
/// <summary>
|
||||
/// Converts from the path command to an SVG string representing the path operation.
|
||||
/// </summary>
|
||||
void WriteSvg(StringBuilder builder);
|
||||
void WriteSvg(StringBuilder builder, double height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -326,7 +326,7 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteSvg(StringBuilder builder)
|
||||
public void WriteSvg(StringBuilder builder, double height)
|
||||
{
|
||||
builder.Append("Z ");
|
||||
}
|
||||
@@ -373,9 +373,9 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteSvg(StringBuilder builder)
|
||||
public void WriteSvg(StringBuilder builder, double height)
|
||||
{
|
||||
builder.Append("M ").Append(Location.X).Append(' ').Append(Location.Y).Append(' ');
|
||||
builder.Append($"M {Location.X} {height - Location.Y} ");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -439,9 +439,9 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteSvg(StringBuilder builder)
|
||||
public void WriteSvg(StringBuilder builder, double height)
|
||||
{
|
||||
builder.AppendFormat("L {0} {1} ", To.X, To.Y);
|
||||
builder.Append($"L {To.X} {height - To.Y} ");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -543,10 +543,9 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteSvg(StringBuilder builder)
|
||||
public void WriteSvg(StringBuilder builder, double height)
|
||||
{
|
||||
builder.AppendFormat("C {0} {1}, {2} {3}, {4} {5} ", FirstControlPoint.X, FirstControlPoint.Y, SecondControlPoint.X, SecondControlPoint.Y,
|
||||
EndPoint.X, EndPoint.Y);
|
||||
builder.Append($"C {FirstControlPoint.X} { height - FirstControlPoint.Y}, { SecondControlPoint.X} {height - SecondControlPoint.Y}, {EndPoint.X} {height - EndPoint.Y} ");
|
||||
}
|
||||
|
||||
private bool TrySolveQuadratic(bool isX, double currentMin, double currentMax, out (double min, double max) solutions)
|
||||
|
@@ -0,0 +1,209 @@
|
||||
namespace UglyToad.PdfPig.DocumentLayoutAnalysis.Export
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UglyToad.PdfPig.Content;
|
||||
using UglyToad.PdfPig.Graphics;
|
||||
using UglyToad.PdfPig.Graphics.Colors;
|
||||
using UglyToad.PdfPig.Graphics.Core;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class SvgTextExporter : ITextExporter
|
||||
{
|
||||
static readonly int rounding = 4;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
/// <returns></returns>
|
||||
public string Get(Page page)
|
||||
{
|
||||
var builder = new StringBuilder($"<svg width='{page.Width}' height='{page.Height}'><g transform=\"scale(1, 1) translate(0, 0)\">");
|
||||
|
||||
var paths = page.ExperimentalAccess.Paths;
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (path.IsClipping)
|
||||
{
|
||||
//var svg = PathToSvg(path, page.Height);
|
||||
//svg = svg.Replace("stroke='black'", "stroke='yellow'");
|
||||
//builder.Append(svg);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(PathToSvg(path, page.Height));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var letter in page.Letters)
|
||||
{
|
||||
builder.Append(LetterToSvg(letter, page.Height));
|
||||
}
|
||||
|
||||
builder.Append("</g></svg>");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
static readonly Dictionary<string, string> _fonts = new Dictionary<string, string>()
|
||||
{
|
||||
{ "ArialMT", "Arial Rounded MT Bold" }
|
||||
};
|
||||
|
||||
private static string LetterToSvg(Letter l, double height)
|
||||
{
|
||||
string fontFamily = GetFontFamily(l.FontName, out string style, out string weight);
|
||||
string rotation = "";
|
||||
if (l.GlyphRectangle.Rotation != 0)
|
||||
{
|
||||
rotation = $" transform='rotate({Math.Round(-l.GlyphRectangle.Rotation, rounding)} {Math.Round(l.GlyphRectangle.BottomLeft.X, rounding)},{Math.Round(height - l.GlyphRectangle.TopLeft.Y, rounding)})'";
|
||||
}
|
||||
|
||||
string fontSize = l.FontSize != 1 ? $"font-size='{l.FontSize.ToString("0")}'" : $"style='font-size:{Math.Round(l.GlyphRectangle.Height, 2)}px'";
|
||||
|
||||
return $"<text x='{Math.Round(l.StartBaseLine.X, rounding)}' y='{Math.Round(height - l.StartBaseLine.Y, rounding)}'{rotation} font-family='{fontFamily}' font-style='{style}' font-weight='{weight}' {fontSize} fill='{ColorToSvg(l.Color)}'>{l.Value}</text>";
|
||||
}
|
||||
|
||||
private static string GetFontFamily(string fontName, out string style, out string weight)
|
||||
{
|
||||
style = "normal"; // normal | italic | oblique
|
||||
weight = "normal"; // normal | bold | bolder | lighter
|
||||
|
||||
// remove subset prefix
|
||||
if (fontName.Contains('+'))
|
||||
{
|
||||
if (fontName.Length > 7 && fontName[6] == '+')
|
||||
{
|
||||
var split = fontName.Split('+');
|
||||
if (split[0].All(c => char.IsUpper(c)))
|
||||
{
|
||||
fontName = split[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fontName.Contains('-'))
|
||||
{
|
||||
var infos = fontName.Split('-');
|
||||
fontName = infos[0];
|
||||
|
||||
for (int i = 1; i < infos.Length; i++)
|
||||
{
|
||||
string infoLower = infos[i].ToLowerInvariant();
|
||||
if (infoLower.Contains("light"))
|
||||
{
|
||||
weight = "lighter";
|
||||
}
|
||||
else if (infoLower.Contains("bolder"))
|
||||
{
|
||||
weight = "bolder";
|
||||
}
|
||||
else if (infoLower.Contains("bold"))
|
||||
{
|
||||
weight = "bold";
|
||||
}
|
||||
|
||||
if (infoLower.Contains("italic"))
|
||||
{
|
||||
style = "italic";
|
||||
}
|
||||
else if (infoLower.Contains("oblique"))
|
||||
{
|
||||
style = "oblique";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_fonts.ContainsKey(fontName)) fontName = _fonts[fontName];
|
||||
return fontName;
|
||||
}
|
||||
|
||||
private static string ColorToSvg(IColor color)
|
||||
{
|
||||
if (color == null) return "";
|
||||
var (r, g, b) = color.ToRGBValues();
|
||||
return $"rgb({Math.Ceiling(r * 255)},{Math.Ceiling(g * 255)},{Math.Ceiling(b * 255)})";
|
||||
}
|
||||
|
||||
private static string PathToSvg(PdfPath p, double height)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var subpath in p)
|
||||
{
|
||||
foreach (var command in subpath.Commands)
|
||||
{
|
||||
command.WriteSvg(builder, height);
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (builder[builder.Length - 1] == ' ')
|
||||
{
|
||||
builder.Remove(builder.Length - 1, 1);
|
||||
}
|
||||
|
||||
var glyph = builder.ToString();
|
||||
|
||||
string dashArray = "";
|
||||
string capStyle = "";
|
||||
string jointStyle = "";
|
||||
string strokeColor = " stroke='none'";
|
||||
string strokeWidth = "";
|
||||
|
||||
if (p.IsStroked)
|
||||
{
|
||||
strokeColor = $" stroke='{ColorToSvg(p.StrokeColor)}'";
|
||||
strokeWidth = $" stroke-width='{p.LineWidth}'";
|
||||
|
||||
if (p.LineDashPattern.HasValue && p.LineDashPattern.Value.Array.Count > 0)
|
||||
{
|
||||
dashArray = $" stroke-dasharray='{string.Join(" ", p.LineDashPattern.Value.Array)}'";
|
||||
}
|
||||
|
||||
if (p.LineCapStyle != LineCapStyle.Butt)
|
||||
{
|
||||
if (p.LineCapStyle == LineCapStyle.Round)
|
||||
{
|
||||
capStyle = " stroke-linecap='round'";
|
||||
}
|
||||
else
|
||||
{
|
||||
capStyle = " stroke-linecap='square'";
|
||||
}
|
||||
}
|
||||
|
||||
if (p.LineJoinStyle != LineJoinStyle.Miter)
|
||||
{
|
||||
if (p.LineJoinStyle == LineJoinStyle.Round)
|
||||
{
|
||||
jointStyle = " stroke-linejoin='round'";
|
||||
}
|
||||
else
|
||||
{
|
||||
jointStyle = " stroke-linejoin='bevel'";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string fillColor = " fill='none'";
|
||||
string fillRule = "";
|
||||
|
||||
if (p.IsFilled)
|
||||
{
|
||||
fillColor = $" fill='{ColorToSvg(p.FillColor)}'";
|
||||
//if (p.FillingRule == FillingRule.EvenOdd) fillRule = " fill-rule='evenodd'";
|
||||
}
|
||||
|
||||
var path = $"<path d='{glyph}'{fillColor}{fillRule}{strokeColor}{strokeWidth}{dashArray}{capStyle}{jointStyle}></path>";
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
@@ -53,9 +53,9 @@
|
||||
new PdfPoint(140, 160));
|
||||
|
||||
var builder = new StringBuilder();
|
||||
curve.WriteSvg(builder);
|
||||
curve.WriteSvg(builder, 0);
|
||||
|
||||
Assert.Equal("C 75 30, 215 115, 140 160 ", builder.ToString());
|
||||
Assert.Equal("C 75 -30, 215 -115, 140 -160 ", builder.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -63,7 +63,7 @@
|
||||
foreach (var charString in result.CharStrings.CharStrings)
|
||||
{
|
||||
Assert.True(result.CharStrings.TryGenerate(charString.Key, out var path));
|
||||
builder.AppendLine(path.ToFullSvg());
|
||||
builder.AppendLine(path.ToFullSvg(double.NaN)); // TODO
|
||||
}
|
||||
|
||||
builder.Append("</body></html>");
|
||||
|
@@ -888,12 +888,12 @@
|
||||
return new[] {x1, x2, x3};
|
||||
}
|
||||
|
||||
internal static string ToSvg(this PdfSubpath p)
|
||||
internal static string ToSvg(this PdfSubpath p, double height)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var pathCommand in p.Commands)
|
||||
{
|
||||
pathCommand.WriteSvg(builder);
|
||||
pathCommand.WriteSvg(builder, height);
|
||||
}
|
||||
|
||||
if (builder.Length == 0)
|
||||
@@ -909,15 +909,15 @@
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
internal static string ToFullSvg(this PdfSubpath p)
|
||||
internal static string ToFullSvg(this PdfSubpath p, double height)
|
||||
{
|
||||
string BboxToRect(PdfRectangle box, string stroke)
|
||||
static string BboxToRect(PdfRectangle box, string stroke)
|
||||
{
|
||||
var overallBbox = $"<rect x='{box.Left}' y='{box.Bottom}' width='{box.Width}' height='{box.Height}' stroke-width='2' fill='none' stroke='{stroke}'></rect>";
|
||||
return overallBbox;
|
||||
}
|
||||
|
||||
var glyph = p.ToSvg();
|
||||
var glyph = p.ToSvg(height);
|
||||
var bbox = p.GetBoundingRectangle();
|
||||
var bboxes = new List<PdfRectangle>();
|
||||
|
||||
|
Reference in New Issue
Block a user