Add SvgTextExporter

Modify WriteSvg to account for height
This commit is contained in:
BobLd
2020-04-02 17:29:03 +01:00
committed by Eliot Jones
parent 983cfcb2f6
commit a7fe39fc32
5 changed files with 225 additions and 17 deletions

View File

@@ -311,7 +311,7 @@
/// <summary> /// <summary>
/// Converts from the path command to an SVG string representing the path operation. /// Converts from the path command to an SVG string representing the path operation.
/// </summary> /// </summary>
void WriteSvg(StringBuilder builder); void WriteSvg(StringBuilder builder, double height);
} }
/// <summary> /// <summary>
@@ -326,7 +326,7 @@
} }
/// <inheritdoc /> /// <inheritdoc />
public void WriteSvg(StringBuilder builder) public void WriteSvg(StringBuilder builder, double height)
{ {
builder.Append("Z "); builder.Append("Z ");
} }
@@ -373,9 +373,9 @@
} }
/// <inheritdoc /> /// <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 /> /// <inheritdoc />
@@ -439,9 +439,9 @@
} }
/// <inheritdoc /> /// <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 /> /// <inheritdoc />
@@ -543,10 +543,9 @@
} }
/// <inheritdoc /> /// <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, builder.Append($"C {FirstControlPoint.X} { height - FirstControlPoint.Y}, { SecondControlPoint.X} {height - SecondControlPoint.Y}, {EndPoint.X} {height - EndPoint.Y} ");
EndPoint.X, EndPoint.Y);
} }
private bool TrySolveQuadratic(bool isX, double currentMin, double currentMax, out (double min, double max) solutions) private bool TrySolveQuadratic(bool isX, double currentMin, double currentMax, out (double min, double max) solutions)

View File

@@ -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;
}
}
}

View File

@@ -53,9 +53,9 @@
new PdfPoint(140, 160)); new PdfPoint(140, 160));
var builder = new StringBuilder(); 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());
} }
} }
} }

View File

@@ -63,7 +63,7 @@
foreach (var charString in result.CharStrings.CharStrings) foreach (var charString in result.CharStrings.CharStrings)
{ {
Assert.True(result.CharStrings.TryGenerate(charString.Key, out var path)); 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>"); builder.Append("</body></html>");

View File

@@ -888,12 +888,12 @@
return new[] {x1, x2, x3}; 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(); var builder = new StringBuilder();
foreach (var pathCommand in p.Commands) foreach (var pathCommand in p.Commands)
{ {
pathCommand.WriteSvg(builder); pathCommand.WriteSvg(builder, height);
} }
if (builder.Length == 0) if (builder.Length == 0)
@@ -909,15 +909,15 @@
return builder.ToString(); 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>"; 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; return overallBbox;
} }
var glyph = p.ToSvg(); var glyph = p.ToSvg(height);
var bbox = p.GetBoundingRectangle(); var bbox = p.GetBoundingRectangle();
var bboxes = new List<PdfRectangle>(); var bboxes = new List<PdfRectangle>();