mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-19 19:07:56 +08:00
Add SvgTextExporter
Modify WriteSvg to account for height
This commit is contained in:
@@ -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)
|
||||||
|
@@ -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));
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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>");
|
||||||
|
@@ -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>();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user