mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-01-18 19:51:24 +08:00
Implement TryGetPath and TryGetNormalisedPath for fonts
This commit is contained in:
@@ -196,7 +196,7 @@
|
|||||||
throw new ArgumentNullException("BezierCurveTo(): currentPosition is null.");
|
throw new ArgumentNullException("BezierCurveTo(): currentPosition is null.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Close the path.
|
/// Close the path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -212,7 +212,7 @@
|
|||||||
}
|
}
|
||||||
commands.Add(new Close());
|
commands.Add(new Close());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if the path is currently closed.
|
/// Determines if the path is currently closed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -350,6 +350,30 @@
|
|||||||
return new PdfRectangle(mv.Location, new PdfPoint(mv.Location.X + width, mv.Location.Y + height));
|
return new PdfRectangle(mv.Location, new PdfPoint(mv.Location.X + width, mv.Location.Y + height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="PdfRectangle"/> which entirely contains the geometry of the defined path.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>For paths which don't define any geometry this returns <see langword="null"/>.</returns>
|
||||||
|
public static PdfRectangle? GetBoundingRectangle(IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
if (path == null || path.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bboxes = path.Select(x => x.GetBoundingRectangle()).Where(x => x.HasValue).Select(x => x.Value).ToList();
|
||||||
|
if (bboxes.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minX = bboxes.Min(x => x.Left);
|
||||||
|
var minY = bboxes.Min(x => x.Bottom);
|
||||||
|
var maxX = bboxes.Max(x => x.Right);
|
||||||
|
var maxY = bboxes.Max(x => x.Top);
|
||||||
|
return new PdfRectangle(minX, minY, maxX, maxY);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A command in a <see cref="PdfSubpath"/>.
|
/// A command in a <see cref="PdfSubpath"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
namespace UglyToad.PdfPig.Core
|
namespace UglyToad.PdfPig.Core
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.Contracts;
|
using System.Diagnostics.Contracts;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using static UglyToad.PdfPig.Core.PdfSubpath;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies the conversion from the transformed coordinate space to the original untransformed coordinate space.
|
/// Specifies the conversion from the transformed coordinate space to the original untransformed coordinate space.
|
||||||
@@ -256,6 +259,59 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transform a subpath using this transformation matrix.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subpath">The original subpath.</param>
|
||||||
|
/// <returns>A new subpath which is the result of applying this transformation matrix.</returns>
|
||||||
|
public PdfSubpath Transform(PdfSubpath subpath)
|
||||||
|
{
|
||||||
|
var trSubpath = new PdfSubpath();
|
||||||
|
foreach (var c in subpath.Commands)
|
||||||
|
{
|
||||||
|
if (c is Move move)
|
||||||
|
{
|
||||||
|
var loc = Transform(move.Location);
|
||||||
|
trSubpath.MoveTo(loc.X, loc.Y);
|
||||||
|
}
|
||||||
|
else if (c is Line line)
|
||||||
|
{
|
||||||
|
//var from = Transform(line.From);
|
||||||
|
var to = Transform(line.To);
|
||||||
|
trSubpath.LineTo(to.X, to.Y);
|
||||||
|
}
|
||||||
|
else if (c is BezierCurve curve)
|
||||||
|
{
|
||||||
|
var first = Transform(curve.FirstControlPoint);
|
||||||
|
var second = Transform(curve.SecondControlPoint);
|
||||||
|
var end = Transform(curve.EndPoint);
|
||||||
|
trSubpath.BezierCurveTo(first.X, first.Y, second.X, second.Y, end.X, end.Y);
|
||||||
|
}
|
||||||
|
else if (c is Close)
|
||||||
|
{
|
||||||
|
trSubpath.CloseSubpath();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Unknown PdfSubpath type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trSubpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transform a path using this transformation matrix.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The original path.</param>
|
||||||
|
/// <returns>A new path which is the result of applying this transformation matrix.</returns>
|
||||||
|
public IEnumerable<PdfSubpath> Transform(IEnumerable<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
foreach (var subpath in path)
|
||||||
|
{
|
||||||
|
yield return Transform(subpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate a <see cref="TransformationMatrix"/> translated by the specified amount.
|
/// Generate a <see cref="TransformationMatrix"/> translated by the specified amount.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
|
||||||
using Core;
|
using Core;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The context used and updated when interpreting the commands for a charstring.
|
/// The context used and updated when interpreting the commands for a charstring.
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current path.
|
/// The current path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PdfSubpath Path { get; } = new PdfSubpath();
|
public List<PdfSubpath> Path { get; } = new List<PdfSubpath>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current location of the active point.
|
/// The current location of the active point.
|
||||||
@@ -41,6 +41,28 @@
|
|||||||
AddRelativeLine(0, dy);
|
AddRelativeLine(0, dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddRelativeMoveTo(double dx, double dy)
|
||||||
|
{
|
||||||
|
BeforeMoveTo();
|
||||||
|
var newLocation = new PdfPoint(CurrentLocation.X + dx, CurrentLocation.Y + dy);
|
||||||
|
Path[Path.Count - 1].MoveTo(newLocation.X, newLocation.Y);
|
||||||
|
CurrentLocation = newLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddHorizontalMoveTo(double dx)
|
||||||
|
{
|
||||||
|
BeforeMoveTo();
|
||||||
|
Path[Path.Count - 1].MoveTo(CurrentLocation.X + dx, CurrentLocation.Y);
|
||||||
|
CurrentLocation = CurrentLocation.MoveX(dx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddVerticallMoveTo(double dy)
|
||||||
|
{
|
||||||
|
BeforeMoveTo();
|
||||||
|
Path[Path.Count - 1].MoveTo(CurrentLocation.X, CurrentLocation.Y + dy);
|
||||||
|
CurrentLocation = CurrentLocation.MoveY(dy);
|
||||||
|
}
|
||||||
|
|
||||||
public void AddRelativeBezierCurve(double dx1, double dy1, double dx2, double dy2, double dx3, double dy3)
|
public void AddRelativeBezierCurve(double dx1, double dy1, double dx2, double dy2, double dx3, double dy3)
|
||||||
{
|
{
|
||||||
var x1 = CurrentLocation.X + dx1;
|
var x1 = CurrentLocation.X + dx1;
|
||||||
@@ -52,7 +74,7 @@
|
|||||||
var x3 = x2 + dx3;
|
var x3 = x2 + dx3;
|
||||||
var y3 = y2 + dy3;
|
var y3 = y2 + dy3;
|
||||||
|
|
||||||
Path.BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
Path[Path.Count - 1].BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||||
CurrentLocation = new PdfPoint(x3, y3);
|
CurrentLocation = new PdfPoint(x3, y3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +82,7 @@
|
|||||||
{
|
{
|
||||||
var dest = new PdfPoint(CurrentLocation.X + dx, CurrentLocation.Y + dy);
|
var dest = new PdfPoint(CurrentLocation.X + dx, CurrentLocation.Y + dy);
|
||||||
|
|
||||||
Path.LineTo(dest.X, dest.Y);
|
Path[Path.Count - 1].LineTo(dest.X, dest.Y);
|
||||||
CurrentLocation = dest;
|
CurrentLocation = dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +99,23 @@
|
|||||||
transientArray[location] = value;
|
transientArray[location] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Every character path and subpath must begin with one of the
|
||||||
|
/// moveto operators. If the current path is open when a moveto
|
||||||
|
/// operator is encountered, the path is closed before performing
|
||||||
|
/// the moveto operation.
|
||||||
|
/// <para>See 4.1 Path Construction Operators in 'The Type 2 Charstring Format, Technical Note #5177', 16 March 2000</para>
|
||||||
|
/// <see href="https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf"/>
|
||||||
|
/// </summary>
|
||||||
|
private void BeforeMoveTo()
|
||||||
|
{
|
||||||
|
if (Path.Count > 0)
|
||||||
|
{
|
||||||
|
Path[Path.Count - 1].CloseSubpath();
|
||||||
|
}
|
||||||
|
Path.Add(new PdfSubpath());
|
||||||
|
}
|
||||||
|
|
||||||
public double GetFromTransientArray(int location)
|
public double GetFromTransientArray(int location)
|
||||||
{
|
{
|
||||||
var result = transientArray[location];
|
var result = transientArray[location];
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||||
{
|
{
|
||||||
|
using Core;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Core;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stores the decoded command sequences for Type 2 CharStrings from a Compact Font Format font as well
|
/// Stores the decoded command sequences for Type 2 CharStrings from a Compact Font Format font as well
|
||||||
@@ -21,7 +21,6 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyDictionary<string, CommandSequence> CharStrings { get; }
|
public IReadOnlyDictionary<string, CommandSequence> CharStrings { get; }
|
||||||
|
|
||||||
|
|
||||||
public Type2CharStrings(IReadOnlyDictionary<string, CommandSequence> charStrings)
|
public Type2CharStrings(IReadOnlyDictionary<string, CommandSequence> charStrings)
|
||||||
{
|
{
|
||||||
CharStrings = charStrings ?? throw new ArgumentNullException(nameof(charStrings));
|
CharStrings = charStrings ?? throw new ArgumentNullException(nameof(charStrings));
|
||||||
@@ -70,7 +69,7 @@
|
|||||||
private static Type2Glyph Run(CommandSequence sequence, double defaultWidthX, double nominalWidthX)
|
private static Type2Glyph Run(CommandSequence sequence, double defaultWidthX, double nominalWidthX)
|
||||||
{
|
{
|
||||||
var context = new Type2BuildCharContext();
|
var context = new Type2BuildCharContext();
|
||||||
|
|
||||||
var hasRunStackClearingCommand = false;
|
var hasRunStackClearingCommand = false;
|
||||||
for (var i = -1; i < sequence.Values.Count; i++)
|
for (var i = -1; i < sequence.Values.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -224,7 +223,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path of the glyph.
|
/// The path of the glyph.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PdfSubpath Path { get; }
|
public IReadOnlyList<PdfSubpath> Path { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The width of the glyph as a difference from the nominal width X for the font. Optional.
|
/// The width of the glyph as a difference from the nominal width X for the font. Optional.
|
||||||
@@ -234,7 +233,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="Type2Glyph"/>.
|
/// Create a new <see cref="Type2Glyph"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Type2Glyph(PdfSubpath path, double? width)
|
public Type2Glyph(IReadOnlyList<PdfSubpath> path, double? width)
|
||||||
{
|
{
|
||||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
Path = path ?? throw new ArgumentNullException(nameof(path));
|
||||||
Width = width;
|
Width = width;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
|
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Charsets;
|
using Charsets;
|
||||||
using CharStrings;
|
using CharStrings;
|
||||||
using Core;
|
using Core;
|
||||||
using Dictionaries;
|
using Dictionaries;
|
||||||
using Encodings;
|
using Encodings;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Type1.CharStrings;
|
using Type1.CharStrings;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var glyph = type2CharStrings.Generate(characterName, (double)defaultWidthX, (double)nominalWidthX);
|
var glyph = type2CharStrings.Generate(characterName, (double)defaultWidthX, (double)nominalWidthX);
|
||||||
var rectangle = glyph.Path.GetBoundingRectangle();
|
var rectangle = PdfSubpath.GetBoundingRectangle(glyph.Path);
|
||||||
if (rectangle.HasValue)
|
if (rectangle.HasValue)
|
||||||
{
|
{
|
||||||
return rectangle;
|
return rectangle;
|
||||||
@@ -77,7 +77,55 @@
|
|||||||
|
|
||||||
var defaultBoundingBox = TopDictionary.FontBoundingBox;
|
var defaultBoundingBox = TopDictionary.FontBoundingBox;
|
||||||
return new PdfRectangle(0, 0, glyph.Width.GetValueOrDefault(), defaultBoundingBox.Height);
|
return new PdfRectangle(0, 0, glyph.Width.GetValueOrDefault(), defaultBoundingBox.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the pdfpath for the character with the given name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="characterName"></param>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool TryGetPath(string characterName, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
var defaultWidthX = GetDefaultWidthX(characterName);
|
||||||
|
var nominalWidthX = GetNominalWidthX(characterName);
|
||||||
|
|
||||||
|
if (CharStrings.TryGetFirst(out var _))
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Type 1 CharStrings in a CFF font are currently unsupported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CharStrings.TryGetSecond(out var type2CharStrings))
|
||||||
|
{
|
||||||
|
path = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = type2CharStrings.Generate(characterName, (double)defaultWidthX, (double)nominalWidthX).Path;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GetCharacterPath
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="characterName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IReadOnlyList<PdfSubpath> GetCharacterPath(string characterName)
|
||||||
|
{
|
||||||
|
var defaultWidthX = GetDefaultWidthX(characterName);
|
||||||
|
var nominalWidthX = GetNominalWidthX(characterName);
|
||||||
|
|
||||||
|
if (CharStrings.TryGetFirst(out var _))
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Type 1 CharStrings in a CFF font are currently unsupported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CharStrings.TryGetSecond(out var type2CharStrings))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type2CharStrings.Generate(characterName, (double)defaultWidthX, (double)nominalWidthX).Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
private readonly IReadOnlyDictionary<string, string> nameToUnicode;
|
private readonly IReadOnlyDictionary<string, string> nameToUnicode;
|
||||||
private readonly IReadOnlyDictionary<string, string> unicodeToName;
|
private readonly IReadOnlyDictionary<string, string> unicodeToName;
|
||||||
|
|
||||||
private readonly Dictionary<string, string> oddNameToUnicodeCache = new Dictionary<string, string>();
|
private readonly Dictionary<string, string> oddNameToUnicodeCache = new Dictionary<string, string>();
|
||||||
|
|
||||||
private static readonly Lazy<GlyphList> LazyAdobeGlyphList = new Lazy<GlyphList>(() => GlyphListFactory.Get("glyphlist"));
|
private static readonly Lazy<GlyphList> LazyAdobeGlyphList = new Lazy<GlyphList>(() => GlyphListFactory.Get("glyphlist"));
|
||||||
@@ -138,9 +138,9 @@
|
|||||||
else if (name.StartsWith("u") && name.Length == 5)
|
else if (name.StartsWith("u") && name.Length == 5)
|
||||||
{
|
{
|
||||||
// test for an alternate Unicode name representation uXXXX
|
// test for an alternate Unicode name representation uXXXX
|
||||||
var codePoint = int.Parse(name.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
var codePoint = int.Parse(name.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
if (codePoint > 0xD7FF && codePoint < 0xE000)
|
if (codePoint > 0xD7FF && codePoint < 0xE000)
|
||||||
{
|
{
|
||||||
throw new InvalidFontFormatException(
|
throw new InvalidFontFormatException(
|
||||||
$"Unicode character name with disallowed code area: {name}");
|
$"Unicode character name with disallowed code area: {name}");
|
||||||
@@ -159,4 +159,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using Core;
|
using Core;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
internal class Glyph : IGlyphDescription
|
internal class Glyph : IGlyphDescription
|
||||||
{
|
{
|
||||||
@@ -111,12 +112,112 @@
|
|||||||
|
|
||||||
scaled = matrix.Translate(scaled);
|
scaled = matrix.Translate(scaled);
|
||||||
|
|
||||||
newPoints[i] = new GlyphPoint((short)scaled.X, (short)scaled.Y, point.IsOnCurve);
|
newPoints[i] = new GlyphPoint((short)scaled.X, (short)scaled.Y, point.IsOnCurve, point.IsEndOfContour);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Glyph(IsSimple, Instructions, EndPointsOfContours, newPoints, Bounds);
|
return new Glyph(IsSimple, Instructions, EndPointsOfContours, newPoints, Bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Subpaths
|
||||||
|
public bool TryGetGlyphPath(out IReadOnlyList<PdfSubpath> subpaths)
|
||||||
|
{
|
||||||
|
subpaths = EmptyArray<PdfSubpath>.Instance;
|
||||||
|
if (Points == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Points.Length > 0)
|
||||||
|
{
|
||||||
|
subpaths = CalculatePath(Points);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<PdfSubpath> CalculatePath(GlyphPoint[] points)
|
||||||
|
{
|
||||||
|
// https://github.com/apache/pdfbox/blob/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphRenderer.java
|
||||||
|
var path = new List<PdfSubpath>();
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
for (int p = 0; p < points.Length; ++p)
|
||||||
|
{
|
||||||
|
if (points[p].IsEndOfContour)
|
||||||
|
{
|
||||||
|
PdfSubpath subpath = new PdfSubpath();
|
||||||
|
GlyphPoint firstPoint = points[start];
|
||||||
|
GlyphPoint lastPoint = points[p];
|
||||||
|
var contour = new List<GlyphPoint>();
|
||||||
|
|
||||||
|
for (int q = start; q <= p; ++q)
|
||||||
|
{
|
||||||
|
contour.Add(points[q]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (points[start].IsOnCurve)
|
||||||
|
{
|
||||||
|
// using start point at the contour end
|
||||||
|
contour.Add(firstPoint);
|
||||||
|
}
|
||||||
|
else if (points[p].IsOnCurve)
|
||||||
|
{
|
||||||
|
// first is off-curve point, trying to use one from the end
|
||||||
|
contour.Insert(0, lastPoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// start and end are off-curve points, creating implicit one
|
||||||
|
var pmid = midValue(firstPoint, lastPoint);
|
||||||
|
contour.Insert(0, pmid);
|
||||||
|
contour.Add(pmid);
|
||||||
|
}
|
||||||
|
|
||||||
|
subpath.MoveTo(contour[0].X, contour[0].Y);
|
||||||
|
for (int j = 1; j < contour.Count; j++)
|
||||||
|
{
|
||||||
|
GlyphPoint pNow = contour[j];
|
||||||
|
if (pNow.IsOnCurve)
|
||||||
|
{
|
||||||
|
subpath.LineTo(pNow.X, pNow.Y);
|
||||||
|
}
|
||||||
|
else if (contour[j + 1].IsOnCurve)
|
||||||
|
{
|
||||||
|
var pPrevious = contour[j - 1];
|
||||||
|
var pNext = contour[j + 1];
|
||||||
|
subpath.BezierCurveTo(pPrevious.X, pPrevious.Y, pNow.X, pNow.Y, pNext.X, pNext.Y);
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pPrevious = contour[j - 1];
|
||||||
|
var pmid = midValue(pNow, contour[j + 1]);
|
||||||
|
subpath.BezierCurveTo(pPrevious.X, pPrevious.Y, pNow.X, pNow.Y, pmid.X, pmid.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subpath.CloseSubpath();
|
||||||
|
path.Add(subpath);
|
||||||
|
start = p + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short midValue(short a, short b)
|
||||||
|
{
|
||||||
|
return (short)(a + (b - a) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This creates an onCurve point that is between point1 and point2.
|
||||||
|
/// </summary>
|
||||||
|
private static GlyphPoint midValue(GlyphPoint point1, GlyphPoint point2)
|
||||||
|
{
|
||||||
|
// this constructs an on-curve, non-endofcountour point
|
||||||
|
return new GlyphPoint(midValue(point1.X, point2.X), midValue(point1.Y, point2.Y), true, false);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var type = IsSimple ? "S" : "C";
|
var type = IsSimple ? "S" : "C";
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||||
{
|
{
|
||||||
|
using UglyToad.PdfPig.Core;
|
||||||
|
|
||||||
internal struct GlyphPoint
|
internal struct GlyphPoint
|
||||||
{
|
{
|
||||||
public short X { get; }
|
public short X { get; }
|
||||||
@@ -8,16 +10,19 @@
|
|||||||
|
|
||||||
public bool IsOnCurve { get; }
|
public bool IsOnCurve { get; }
|
||||||
|
|
||||||
public GlyphPoint(short x, short y, bool isOnCurve)
|
public bool IsEndOfContour { get; }
|
||||||
|
|
||||||
|
public GlyphPoint(short x, short y, bool isOnCurve, bool isEndOfContour)
|
||||||
{
|
{
|
||||||
X = x;
|
X = x;
|
||||||
Y = y;
|
Y = y;
|
||||||
IsOnCurve = isOnCurve;
|
IsOnCurve = isOnCurve;
|
||||||
|
IsEndOfContour = isEndOfContour;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"({X}, {Y}) | {IsOnCurve}";
|
return $"({X}, {Y}) | {IsOnCurve} | {IsEndOfContour}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||||
{
|
{
|
||||||
using Core;
|
using Core;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
internal interface IGlyphDescription : IMergeableGlyph, ITransformableGlyph
|
internal interface IGlyphDescription : IMergeableGlyph, ITransformableGlyph
|
||||||
{
|
{
|
||||||
@@ -16,6 +17,8 @@
|
|||||||
|
|
||||||
bool IsEmpty { get; }
|
bool IsEmpty { get; }
|
||||||
|
|
||||||
|
bool TryGetGlyphPath(out IReadOnlyList<PdfSubpath> subpaths);
|
||||||
|
|
||||||
IGlyphDescription DeepClone();
|
IGlyphDescription DeepClone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
public class TableRegister
|
public class TableRegister
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This table contains global information about the font.
|
/// This table contains global information about the font.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HeaderTable HeaderTable { get; }
|
public HeaderTable HeaderTable { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Core;
|
using Core;
|
||||||
using Glyphs;
|
using Glyphs;
|
||||||
using Parser;
|
using Parser;
|
||||||
@@ -25,9 +26,9 @@
|
|||||||
|
|
||||||
private readonly Lazy<IReadOnlyList<IGlyphDescription>> glyphs;
|
private readonly Lazy<IReadOnlyList<IGlyphDescription>> glyphs;
|
||||||
public IReadOnlyList<IGlyphDescription> Glyphs => glyphs.Value;
|
public IReadOnlyList<IGlyphDescription> Glyphs => glyphs.Value;
|
||||||
|
|
||||||
public GlyphDataTable(TrueTypeHeaderTable directoryTable, IReadOnlyList<uint> glyphOffsets,
|
public GlyphDataTable(TrueTypeHeaderTable directoryTable, IReadOnlyList<uint> glyphOffsets,
|
||||||
PdfRectangle maxGlyphBounds,
|
PdfRectangle maxGlyphBounds,
|
||||||
TrueTypeDataBytes tableBytes)
|
TrueTypeDataBytes tableBytes)
|
||||||
{
|
{
|
||||||
this.glyphOffsets = glyphOffsets;
|
this.glyphOffsets = glyphOffsets;
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
bounds = new PdfRectangle(0, 0, 0, 0);
|
bounds = new PdfRectangle(0, 0, 0, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
tableBytes.Seek(offset);
|
tableBytes.Seek(offset);
|
||||||
|
|
||||||
// ReSharper disable once UnusedVariable
|
// ReSharper disable once UnusedVariable
|
||||||
@@ -91,7 +92,7 @@
|
|||||||
|
|
||||||
var bytes = data.ReadByteArray((int)table.Length);
|
var bytes = data.ReadByteArray((int)table.Length);
|
||||||
|
|
||||||
return new GlyphDataTable(table, tableRegister.IndexToLocationTable.GlyphOffsets,
|
return new GlyphDataTable(table, tableRegister.IndexToLocationTable.GlyphOffsets,
|
||||||
tableRegister.HeaderTable.Bounds,
|
tableRegister.HeaderTable.Bounds,
|
||||||
new TrueTypeDataBytes(bytes));
|
new TrueTypeDataBytes(bytes));
|
||||||
}
|
}
|
||||||
@@ -164,7 +165,7 @@
|
|||||||
if (contourCount == 0)
|
if (contourCount == 0)
|
||||||
{
|
{
|
||||||
return new Glyph(true, EmptyArray<byte>.Instance, EmptyArray<ushort>.Instance,
|
return new Glyph(true, EmptyArray<byte>.Instance, EmptyArray<ushort>.Instance,
|
||||||
EmptyArray<GlyphPoint>.Instance,
|
EmptyArray<GlyphPoint>.Instance,
|
||||||
new PdfRectangle(0, 0, 0, 0));
|
new PdfRectangle(0, 0, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,11 +189,25 @@
|
|||||||
var yCoordinates = ReadCoordinates(data, pointCount, flags, SimpleGlyphFlags.YSingleByte,
|
var yCoordinates = ReadCoordinates(data, pointCount, flags, SimpleGlyphFlags.YSingleByte,
|
||||||
SimpleGlyphFlags.ThisYIsTheSame);
|
SimpleGlyphFlags.ThisYIsTheSame);
|
||||||
|
|
||||||
|
int endPtIndex = endPointsOfContours.Length - 1;
|
||||||
|
int endPtOfContourIndex = -1;
|
||||||
var points = new GlyphPoint[xCoordinates.Length];
|
var points = new GlyphPoint[xCoordinates.Length];
|
||||||
|
|
||||||
for (var i = xCoordinates.Length - 1; i >= 0; i--)
|
for (var i = xCoordinates.Length - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
|
if (endPtOfContourIndex == -1)
|
||||||
|
{
|
||||||
|
endPtOfContourIndex = endPointsOfContours[endPtIndex];
|
||||||
|
}
|
||||||
|
bool endPt = endPtOfContourIndex == i;
|
||||||
|
if (endPt && endPtIndex > 0)
|
||||||
|
{
|
||||||
|
endPtIndex--;
|
||||||
|
endPtOfContourIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
var isOnCurve = (flags[i] & SimpleGlyphFlags.OnCurve) == SimpleGlyphFlags.OnCurve;
|
var isOnCurve = (flags[i] & SimpleGlyphFlags.OnCurve) == SimpleGlyphFlags.OnCurve;
|
||||||
points[i] = new GlyphPoint(xCoordinates[i], yCoordinates[i], isOnCurve);
|
points[i] = new GlyphPoint(xCoordinates[i], yCoordinates[i], isOnCurve, endPt);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Glyph(true, instructions, endPointsOfContours, points, bounds);
|
return new Glyph(true, instructions, endPointsOfContours, points, bounds);
|
||||||
@@ -209,12 +224,12 @@
|
|||||||
data.Seek(compositeLocation.Position);
|
data.Seek(compositeLocation.Position);
|
||||||
|
|
||||||
var components = new List<CompositeComponent>();
|
var components = new List<CompositeComponent>();
|
||||||
|
|
||||||
// First recursively find all components and ensure they are available.
|
// First recursively find all components and ensure they are available.
|
||||||
CompositeGlyphFlags flags;
|
CompositeGlyphFlags flags;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
flags = (CompositeGlyphFlags) data.ReadUnsignedShort();
|
flags = (CompositeGlyphFlags)data.ReadUnsignedShort();
|
||||||
var glyphIndex = data.ReadUnsignedShort();
|
var glyphIndex = data.ReadUnsignedShort();
|
||||||
|
|
||||||
var childGlyph = glyphs[glyphIndex];
|
var childGlyph = glyphs[glyphIndex];
|
||||||
@@ -232,7 +247,7 @@
|
|||||||
|
|
||||||
glyphs[glyphIndex] = childGlyph;
|
glyphs[glyphIndex] = childGlyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
short arg1, arg2;
|
short arg1, arg2;
|
||||||
if (HasFlag(flags, CompositeGlyphFlags.Args1And2AreWords))
|
if (HasFlag(flags, CompositeGlyphFlags.Args1And2AreWords))
|
||||||
{
|
{
|
||||||
@@ -276,7 +291,6 @@
|
|||||||
{
|
{
|
||||||
// TODO: Not implemented, it is unclear how to do this.
|
// TODO: Not implemented, it is unclear how to do this.
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (HasFlag(flags, CompositeGlyphFlags.MoreComponents));
|
} while (HasFlag(flags, CompositeGlyphFlags.MoreComponents));
|
||||||
|
|
||||||
// Now build the final glyph from the components.
|
// Now build the final glyph from the components.
|
||||||
@@ -381,12 +395,12 @@
|
|||||||
/// Stores the position after reading the contour count and bounds.
|
/// Stores the position after reading the contour count and bounds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long Position { get; }
|
public long Position { get; }
|
||||||
|
|
||||||
public PdfRectangle Bounds { get; }
|
public PdfRectangle Bounds { get; }
|
||||||
|
|
||||||
public TemporaryCompositeLocation(long position, PdfRectangle bounds, short contourCount)
|
public TemporaryCompositeLocation(long position, PdfRectangle bounds, short contourCount)
|
||||||
{
|
{
|
||||||
if (contourCount >= 0 )
|
if (contourCount >= 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"A composite glyph should not have a positive contour count. Got: {contourCount}.", nameof(contourCount));
|
throw new ArgumentException($"A composite glyph should not have a positive contour count. Got: {contourCount}.", nameof(contourCount));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TableRegister.GlyphTable.TryGetGlyphBounds(index, out boundingBox))
|
if (!TableRegister.GlyphTable.TryGetGlyphBounds(index, out boundingBox))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -126,6 +126,28 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to get the bounding box for a glyph representing the specified character code if present.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path) => TryGetPath(characterCode, null, out path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to get the path for a glyph representing the specified character code if present.
|
||||||
|
/// Uses a custom mapping of character code to glyph index.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
path = EmptyArray<PdfSubpath>.Instance;
|
||||||
|
|
||||||
|
if (!TryGetGlyphIndex(characterCode, characterCodeToGlyphId, out var index)
|
||||||
|
|| TableRegister.GlyphTable == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TableRegister.GlyphTable.Glyphs[index].TryGetGlyphPath(out path);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try to get the advance width for a glyph representing the specified character code if present.
|
/// Try to get the advance width for a glyph representing the specified character code if present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
public static void Run(Type1BuildCharContext context)
|
public static void Run(Type1BuildCharContext context)
|
||||||
{
|
{
|
||||||
context.Path.CloseSubpath();
|
context.Path[context.Path.Count - 1].CloseSubpath();
|
||||||
context.Stack.Clear();
|
context.Stack.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
var deltaX = context.Stack.PopBottom();
|
var deltaX = context.Stack.PopBottom();
|
||||||
var x = context.CurrentPosition.X + deltaX;
|
var x = context.CurrentPosition.X + deltaX;
|
||||||
|
|
||||||
context.Path.LineTo(x, context.CurrentPosition.Y);
|
context.Path[context.Path.Count - 1].LineTo(x, context.CurrentPosition.Y);
|
||||||
context.CurrentPosition = new PdfPoint(x, context.CurrentPosition.Y);
|
context.CurrentPosition = new PdfPoint(x, context.CurrentPosition.Y);
|
||||||
|
|
||||||
context.Stack.Clear();
|
context.Stack.Clear();
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
var x = context.CurrentPosition.X + deltaX;
|
var x = context.CurrentPosition.X + deltaX;
|
||||||
var y = context.CurrentPosition.Y;
|
var y = context.CurrentPosition.Y;
|
||||||
context.CurrentPosition = new PdfPoint(x, y);
|
context.CurrentPosition = new PdfPoint(x, y);
|
||||||
context.Path.MoveTo(x, y);
|
|
||||||
|
context.Path.Add(new PdfSubpath());
|
||||||
|
context.Path[context.Path.Count - 1].MoveTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Stack.Clear();
|
context.Stack.Clear();
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
var x3 = x2;
|
var x3 = x2;
|
||||||
var y3 = y2 + dy3;
|
var y3 = y2 + dy3;
|
||||||
|
|
||||||
context.Path.BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
context.Path[context.Path.Count - 1].BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||||
context.CurrentPosition = new PdfPoint(x3, y3);
|
context.CurrentPosition = new PdfPoint(x3, y3);
|
||||||
|
|
||||||
context.Stack.Clear();
|
context.Stack.Clear();
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
var x = context.CurrentPosition.X + deltaX;
|
var x = context.CurrentPosition.X + deltaX;
|
||||||
var y = context.CurrentPosition.Y + deltaY;
|
var y = context.CurrentPosition.Y + deltaY;
|
||||||
|
|
||||||
context.Path.LineTo(x, y);
|
context.Path[context.Path.Count - 1].LineTo(x, y);
|
||||||
context.CurrentPosition = new PdfPoint(x, y);
|
context.CurrentPosition = new PdfPoint(x, y);
|
||||||
|
|
||||||
context.Stack.Clear();
|
context.Stack.Clear();
|
||||||
|
|||||||
@@ -37,7 +37,9 @@
|
|||||||
var x = context.CurrentPosition.X + deltaX;
|
var x = context.CurrentPosition.X + deltaX;
|
||||||
var y = context.CurrentPosition.Y + deltaY;
|
var y = context.CurrentPosition.Y + deltaY;
|
||||||
context.CurrentPosition = new PdfPoint(x, y);
|
context.CurrentPosition = new PdfPoint(x, y);
|
||||||
context.Path.MoveTo(x, y);
|
|
||||||
|
context.Path.Add(new PdfSubpath());
|
||||||
|
context.Path[context.Path.Count - 1].MoveTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Stack.Clear();
|
context.Stack.Clear();
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
var x3 = x2 + dx3;
|
var x3 = x2 + dx3;
|
||||||
var y3 = y2 + dy3;
|
var y3 = y2 + dy3;
|
||||||
|
|
||||||
context.Path.BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
context.Path[context.Path.Count - 1].BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||||
|
|
||||||
context.CurrentPosition = new PdfPoint(x3, y3);
|
context.CurrentPosition = new PdfPoint(x3, y3);
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
var deltaY = context.Stack.PopBottom();
|
var deltaY = context.Stack.PopBottom();
|
||||||
var y = context.CurrentPosition.Y + deltaY;
|
var y = context.CurrentPosition.Y + deltaY;
|
||||||
|
|
||||||
context.Path.LineTo(context.CurrentPosition.X, y);
|
context.Path[context.Path.Count - 1].LineTo(context.CurrentPosition.X, y);
|
||||||
context.CurrentPosition = new PdfPoint(context.CurrentPosition.X, y);
|
context.CurrentPosition = new PdfPoint(context.CurrentPosition.X, y);
|
||||||
|
|
||||||
context.Stack.Clear();
|
context.Stack.Clear();
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
var x = context.CurrentPosition.X;
|
var x = context.CurrentPosition.X;
|
||||||
|
|
||||||
context.CurrentPosition = new PdfPoint(x, y);
|
context.CurrentPosition = new PdfPoint(x, y);
|
||||||
context.Path.MoveTo(x, y);
|
|
||||||
|
context.Path.Add(new PdfSubpath());
|
||||||
|
context.Path[context.Path.Count - 1].MoveTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Stack.Clear();
|
context.Stack.Clear();
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
var x3 = x2 + dx3;
|
var x3 = x2 + dx3;
|
||||||
var y3 = y2;
|
var y3 = y2;
|
||||||
|
|
||||||
context.Path.BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
context.Path[context.Path.Count - 1].BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||||
context.CurrentPosition = new PdfPoint(x3, y3);
|
context.CurrentPosition = new PdfPoint(x3, y3);
|
||||||
|
|
||||||
context.Stack.Clear();
|
context.Stack.Clear();
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Core;
|
using Core;
|
||||||
|
|
||||||
internal class Type1BuildCharContext
|
internal class Type1BuildCharContext
|
||||||
{
|
{
|
||||||
private readonly Func<int, PdfSubpath> characterByIndexFactory;
|
private readonly Func<int, IReadOnlyList<PdfSubpath>> characterByIndexFactory;
|
||||||
private readonly Func<string, PdfSubpath> characterByNameFactory;
|
private readonly Func<string, IReadOnlyList<PdfSubpath>> characterByNameFactory;
|
||||||
public IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> Subroutines { get; }
|
public IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> Subroutines { get; }
|
||||||
|
|
||||||
public double WidthX { get; set; }
|
public double WidthX { get; set; }
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
|
|
||||||
public bool IsFlexing { get; set; }
|
public bool IsFlexing { get; set; }
|
||||||
|
|
||||||
public PdfSubpath Path { get; private set; } = new PdfSubpath();
|
public List<PdfSubpath> Path { get; private set; } = new List<PdfSubpath>();
|
||||||
|
|
||||||
public PdfPoint CurrentPosition { get; set; }
|
public PdfPoint CurrentPosition { get; set; }
|
||||||
|
|
||||||
@@ -31,8 +32,8 @@
|
|||||||
public List<PdfPoint> FlexPoints { get; } = new List<PdfPoint>();
|
public List<PdfPoint> FlexPoints { get; } = new List<PdfPoint>();
|
||||||
|
|
||||||
public Type1BuildCharContext(IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> subroutines,
|
public Type1BuildCharContext(IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> subroutines,
|
||||||
Func<int, PdfSubpath> characterByIndexFactory,
|
Func<int, IReadOnlyList<PdfSubpath>> characterByIndexFactory,
|
||||||
Func<string, PdfSubpath> characterByNameFactory)
|
Func<string, IReadOnlyList<PdfSubpath>> characterByNameFactory)
|
||||||
{
|
{
|
||||||
this.characterByIndexFactory = characterByIndexFactory ?? throw new ArgumentNullException(nameof(characterByIndexFactory));
|
this.characterByIndexFactory = characterByIndexFactory ?? throw new ArgumentNullException(nameof(characterByIndexFactory));
|
||||||
this.characterByNameFactory = characterByNameFactory ?? throw new ArgumentNullException(nameof(characterByNameFactory));
|
this.characterByNameFactory = characterByNameFactory ?? throw new ArgumentNullException(nameof(characterByNameFactory));
|
||||||
@@ -44,19 +45,19 @@
|
|||||||
FlexPoints.Add(point);
|
FlexPoints.Add(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PdfSubpath GetCharacter(int characterCode)
|
public IReadOnlyList<PdfSubpath> GetCharacter(int characterCode)
|
||||||
{
|
{
|
||||||
return characterByIndexFactory(characterCode);
|
return characterByIndexFactory(characterCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PdfSubpath GetCharacter(string characterName)
|
public IReadOnlyList<PdfSubpath> GetCharacter(string characterName)
|
||||||
{
|
{
|
||||||
return characterByNameFactory(characterName);
|
return characterByNameFactory(characterName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPath(PdfSubpath path)
|
public void SetPath(IReadOnlyList<PdfSubpath> path)
|
||||||
{
|
{
|
||||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
Path = path.ToList() ?? throw new ArgumentNullException(nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearFlexPoints()
|
public void ClearFlexPoints()
|
||||||
|
|||||||
@@ -1,35 +1,39 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
|
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Commands;
|
using Commands;
|
||||||
using Commands.Arithmetic;
|
using Commands.Arithmetic;
|
||||||
using Commands.Hint;
|
using Commands.Hint;
|
||||||
using Commands.PathConstruction;
|
using Commands.PathConstruction;
|
||||||
using Commands.StartFinishOutline;
|
using Commands.StartFinishOutline;
|
||||||
using Core;
|
using Core;
|
||||||
using Parser;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Decodes a set of CharStrings to their corresponding Type 1 BuildChar operations.
|
/// Decodes a set of CharStrings to their corresponding Type 1 BuildChar operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// A charstring is an encrypted sequence of unsigned 8-bit bytes that encode integers and commands.
|
/// <para>
|
||||||
|
/// A charstring is an encrypted sequence of unsigned 8-bit bytes that encode integers and commands.
|
||||||
/// Type 1 BuildChar, when interpreting a charstring, will first decrypt it and then will decode
|
/// Type 1 BuildChar, when interpreting a charstring, will first decrypt it and then will decode
|
||||||
/// its bytes one at a time in sequence.
|
/// its bytes one at a time in sequence.
|
||||||
///
|
/// </para>
|
||||||
|
/// <para>
|
||||||
/// The value in a byte indicates a command, a number, or subsequent bytes that are to be interpreted
|
/// The value in a byte indicates a command, a number, or subsequent bytes that are to be interpreted
|
||||||
/// in a special way.
|
/// in a special way.
|
||||||
///
|
/// </para>
|
||||||
|
/// <para>
|
||||||
/// Once the bytes are decoded into numbers and commands, the execution of these numbers and commands proceeds in a
|
/// Once the bytes are decoded into numbers and commands, the execution of these numbers and commands proceeds in a
|
||||||
/// manner similar to the operation of the PostScript language. Type 1 BuildChar uses its own operand stack,
|
/// manner similar to the operation of the PostScript language. Type 1 BuildChar uses its own operand stack,
|
||||||
/// called the Type 1 BuildChar operand stack, that is distinct from the PostScript interpreter operand stack.
|
/// called the Type 1 BuildChar operand stack, that is distinct from the PostScript interpreter operand stack.
|
||||||
///
|
/// </para>
|
||||||
|
/// <para>
|
||||||
/// This stack holds up to 24 numeric entries. A number, decoded from a charstring, is pushed onto the Type 1
|
/// This stack holds up to 24 numeric entries. A number, decoded from a charstring, is pushed onto the Type 1
|
||||||
/// BuildChar operand stack. A command expects its arguments in order on this operand stack with all arguments generally taken
|
/// BuildChar operand stack. A command expects its arguments in order on this operand stack with all arguments generally taken
|
||||||
/// from the bottom of the stack (first argument bottom-most);
|
/// from the bottom of the stack (first argument bottom-most);
|
||||||
/// however, some commands, particularly the subroutine commands, normally work from the top of the stack. If a command returns
|
/// however, some commands, particularly the subroutine commands, normally work from the top of the stack. If a command returns
|
||||||
/// results, they are pushed onto the Type 1 BuildChar operand stack (last result topmost).
|
/// results, they are pushed onto the Type 1 BuildChar operand stack (last result topmost).
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
internal static class Type1CharStringParser
|
internal static class Type1CharStringParser
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
{
|
{
|
||||||
private readonly IReadOnlyDictionary<int, string> charStringIndexToName;
|
private readonly IReadOnlyDictionary<int, string> charStringIndexToName;
|
||||||
private readonly object locker = new object();
|
private readonly object locker = new object();
|
||||||
private readonly Dictionary<string, PdfSubpath> glyphs = new Dictionary<string, PdfSubpath>();
|
private readonly Dictionary<string, IReadOnlyList<PdfSubpath>> glyphs = new Dictionary<string, IReadOnlyList<PdfSubpath>>();
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, CommandSequence> CharStrings { get; }
|
public IReadOnlyDictionary<string, CommandSequence> CharStrings { get; }
|
||||||
|
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
Subroutines = subroutines ?? throw new ArgumentNullException(nameof(subroutines));
|
Subroutines = subroutines ?? throw new ArgumentNullException(nameof(subroutines));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGenerate(string name, out PdfSubpath path)
|
public bool TryGenerate(string name, out IReadOnlyList<PdfSubpath> path)
|
||||||
{
|
{
|
||||||
path = default(PdfSubpath);
|
path = new List<PdfSubpath>();
|
||||||
lock (locker)
|
lock (locker)
|
||||||
{
|
{
|
||||||
if (glyphs.TryGetValue(name, out path))
|
if (glyphs.TryGetValue(name, out path))
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PdfSubpath Run(CommandSequence sequence)
|
private IReadOnlyList<PdfSubpath> Run(CommandSequence sequence)
|
||||||
{
|
{
|
||||||
var context = new Type1BuildCharContext(Subroutines, i =>
|
var context = new Type1BuildCharContext(Subroutines, i =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.Type1
|
namespace UglyToad.PdfPig.Fonts.Type1
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using CharStrings;
|
using CharStrings;
|
||||||
using Core;
|
using Core;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -62,14 +62,8 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public PdfRectangle? GetCharacterBoundingBox(string characterName)
|
public PdfRectangle? GetCharacterBoundingBox(string characterName)
|
||||||
{
|
{
|
||||||
if (!CharStrings.TryGenerate(characterName, out var glyph))
|
var glyph = GetCharacterPath(characterName);
|
||||||
{
|
return PdfSubpath.GetBoundingRectangle(glyph);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bbox = glyph.GetBoundingRectangle();
|
|
||||||
|
|
||||||
return bbox;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -96,5 +90,17 @@
|
|||||||
|
|
||||||
return TransformationMatrix.FromValues(a, b, c, d, e, f);
|
return TransformationMatrix.FromValues(a, b, c, d, e, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the pdfpath for the character with the given name.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<PdfSubpath> GetCharacterPath(string characterName)
|
||||||
|
{
|
||||||
|
if (!CharStrings.TryGenerate(characterName, out var glyph))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,18 @@
|
|||||||
namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using PdfPig.Core;
|
using PdfPig.Core;
|
||||||
using PdfPig.Fonts.TrueType;
|
using PdfPig.Fonts.TrueType;
|
||||||
using PdfPig.Fonts.TrueType.Parser;
|
using PdfPig.Fonts.TrueType.Parser;
|
||||||
using PdfPig.Fonts.TrueType.Tables;
|
using PdfPig.Fonts.TrueType.Tables;
|
||||||
|
using UglyToad.PdfPig.Fonts.TrueType.Glyphs;
|
||||||
|
using UglyToad.PdfPig.Graphics;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
public class TrueTypeFontParserTests
|
public class TrueTypeFontParserTests
|
||||||
@@ -183,7 +188,7 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
|||||||
var font = TrueTypeFontParser.Parse(input);
|
var font = TrueTypeFontParser.Parse(input);
|
||||||
|
|
||||||
var robotoGlyphs = Encoding.ASCII.GetString(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.GlyphData.txt"));
|
var robotoGlyphs = Encoding.ASCII.GetString(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.GlyphData.txt"));
|
||||||
var lines = robotoGlyphs.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.RemoveEmptyEntries);
|
var lines = robotoGlyphs.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
for (var i = 0; i < lines.Length; i++)
|
for (var i = 0; i < lines.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -205,9 +210,16 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
|||||||
{
|
{
|
||||||
Assert.Equal(width, glyph.Bounds.Width);
|
Assert.Equal(width, glyph.Bounds.Width);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(height, glyph.Bounds.Height);
|
Assert.Equal(height, glyph.Bounds.Height);
|
||||||
Assert.Equal(points, glyph.Points.Length);
|
Assert.Equal(points, glyph.Points.Length);
|
||||||
|
if (points > 0)
|
||||||
|
{
|
||||||
|
Assert.True(glyph.Points[glyph.Points.Length - 1].IsEndOfContour);
|
||||||
|
Assert.True(glyph.TryGetGlyphPath(out var subpaths));
|
||||||
|
|
||||||
|
// TODO - more tests on path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,4 +238,3 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(0));
|
//builder.AppendLine(path.ToFullSvg(0)); // TODO - to restore
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append("</body></html>");
|
builder.Append("</body></html>");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using UglyToad.PdfPig.Tests.Integration;
|
using System.IO;
|
||||||
|
using UglyToad.PdfPig.Tests.Integration;
|
||||||
using UglyToad.PdfPig.Writer;
|
using UglyToad.PdfPig.Writer;
|
||||||
using System.IO;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace UglyToad.PdfPig.Tests.Writer
|
namespace UglyToad.PdfPig.Tests.Writer
|
||||||
@@ -18,7 +18,8 @@ namespace UglyToad.PdfPig.Tests.Writer
|
|||||||
using (var document = PdfDocument.Open(filePath))
|
using (var document = PdfDocument.Open(filePath))
|
||||||
{
|
{
|
||||||
var withoutText = PdfTextRemover.RemoveText(filePath);
|
var withoutText = PdfTextRemover.RemoveText(filePath);
|
||||||
File.WriteAllBytes(@"C:\temp\_tx.pdf", withoutText);
|
WriteFile($"{nameof(TextRemoverRemovesText)}_{file}", withoutText);
|
||||||
|
|
||||||
using (var documentWithoutText = PdfDocument.Open(withoutText))
|
using (var documentWithoutText = PdfDocument.Open(withoutText))
|
||||||
{
|
{
|
||||||
Assert.Equal(document.NumberOfPages, documentWithoutText.NumberOfPages);
|
Assert.Equal(document.NumberOfPages, documentWithoutText.NumberOfPages);
|
||||||
@@ -27,9 +28,27 @@ namespace UglyToad.PdfPig.Tests.Writer
|
|||||||
Assert.NotEqual(document.GetPage(i).Text, string.Empty);
|
Assert.NotEqual(document.GetPage(i).Text, string.Empty);
|
||||||
Assert.Equal(documentWithoutText.GetPage(i).Text, string.Empty);
|
Assert.Equal(documentWithoutText.GetPage(i).Text, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void WriteFile(string name, byte[] bytes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists("Writer"))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory("Writer");
|
||||||
|
}
|
||||||
|
|
||||||
|
var output = Path.Combine("Writer", $"{name}");
|
||||||
|
|
||||||
|
File.WriteAllBytes(output, bytes);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored.
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 15.0.27130.2010
|
VisualStudioVersion = 17.3.32819.101
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig", "UglyToad.PdfPig\UglyToad.PdfPig.csproj", "{57D0610C-87D3-4E0B-B7C2-EC8B765A8288}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig", "UglyToad.PdfPig\UglyToad.PdfPig.csproj", "{57D0610C-87D3-4E0B-B7C2-EC8B765A8288}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -18,9 +18,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.Core", "Ugl
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.DocumentLayoutAnalysis", "UglyToad.PdfPig.DocumentLayoutAnalysis\UglyToad.PdfPig.DocumentLayoutAnalysis.csproj", "{60126BCA-6C52-48A9-A0A6-51796C8B0BE7}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.DocumentLayoutAnalysis", "UglyToad.PdfPig.DocumentLayoutAnalysis\UglyToad.PdfPig.DocumentLayoutAnalysis.csproj", "{60126BCA-6C52-48A9-A0A6-51796C8B0BE7}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UglyToad.PdfPig.Tokens", "UglyToad.PdfPig.Tokens\UglyToad.PdfPig.Tokens.csproj", "{D840FF69-4250-4B05-9829-5ABEC43EC82C}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.Tokens", "UglyToad.PdfPig.Tokens\UglyToad.PdfPig.Tokens.csproj", "{D840FF69-4250-4B05-9829-5ABEC43EC82C}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UglyToad.PdfPig.Tokenization", "UglyToad.PdfPig.Tokenization\UglyToad.PdfPig.Tokenization.csproj", "{FD005C50-CD2C-497E-8F7E-6D791091E9B0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.Tokenization", "UglyToad.PdfPig.Tokenization\UglyToad.PdfPig.Tokenization.csproj", "{FD005C50-CD2C-497E-8F7E-6D791091E9B0}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
namespace UglyToad.PdfPig.Graphics
|
namespace UglyToad.PdfPig.Graphics
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using UglyToad.PdfPig.Core;
|
using UglyToad.PdfPig.Core;
|
||||||
using UglyToad.PdfPig.Graphics.Colors;
|
using UglyToad.PdfPig.Graphics.Colors;
|
||||||
using UglyToad.PdfPig.Graphics.Core;
|
using UglyToad.PdfPig.Graphics.Core;
|
||||||
@@ -128,22 +127,7 @@
|
|||||||
/// <returns>For paths which don't define any geometry this returns <see langword="null"/>.</returns>
|
/// <returns>For paths which don't define any geometry this returns <see langword="null"/>.</returns>
|
||||||
public PdfRectangle? GetBoundingRectangle()
|
public PdfRectangle? GetBoundingRectangle()
|
||||||
{
|
{
|
||||||
if (this.Count == 0)
|
return PdfSubpath.GetBoundingRectangle(this);
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bboxes = this.Select(x => x.GetBoundingRectangle()).Where(x => x.HasValue).Select(x => x.Value).ToList();
|
|
||||||
if (bboxes.Count == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var minX = bboxes.Min(x => x.Left);
|
|
||||||
var minY = bboxes.Min(x => x.Bottom);
|
|
||||||
var maxX = bboxes.Max(x => x.Right);
|
|
||||||
var maxY = bboxes.Max(x => x.Top);
|
|
||||||
return new PdfRectangle(minX, minY, maxX, maxY);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
pdfScanner,
|
pdfScanner,
|
||||||
filterProvider,
|
filterProvider,
|
||||||
resourceStore);
|
resourceStore);
|
||||||
// ignored for now, is it possible? check the spec...
|
// ignored for now, is it possible? check the spec...
|
||||||
}
|
}
|
||||||
else if (DirectObjectFinder.TryGet<ArrayToken>(contents, pdfScanner, out var array))
|
else if (DirectObjectFinder.TryGet<ArrayToken>(contents, pdfScanner, out var array))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
{
|
{
|
||||||
using Core;
|
using Core;
|
||||||
using Geometry;
|
using Geometry;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A CID font contains glyph descriptions accessed by
|
/// A CID font contains glyph descriptions accessed by
|
||||||
/// CID (character identifier) as character selectors.
|
/// CID (character identifier) as character selectors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
@@ -51,5 +53,35 @@
|
|||||||
PdfVector GetPositionVector(int characterIdentifier);
|
PdfVector GetPositionVector(int characterIdentifier);
|
||||||
|
|
||||||
PdfVector GetDisplacementVector(int characterIdentifier);
|
PdfVector GetDisplacementVector(int characterIdentifier);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the glyph path for the given character code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||||
|
/// <param name="path">The glyph path for the given character code.</param>
|
||||||
|
bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the glyph path for the given character code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||||
|
/// <param name="characterCodeToGlyphId"></param>
|
||||||
|
/// <param name="path">The glyph path for the given character code.</param>
|
||||||
|
bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the normalised glyph path for the given character code in a PDF.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||||
|
/// <param name="path">The normalized glyph path for the given character code.</param>
|
||||||
|
bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the normalised glyph path for the given character code in a PDF.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||||
|
/// <param name="characterCodeToGlyphId"></param>
|
||||||
|
/// <param name="path">The normalized glyph path for the given character code.</param>
|
||||||
|
bool TryGetNormalisedPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Core;
|
using Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -18,6 +19,10 @@
|
|||||||
|
|
||||||
bool TryGetBoundingAdvancedWidth(int characterIdentifier, out double width);
|
bool TryGetBoundingAdvancedWidth(int characterIdentifier, out double width);
|
||||||
|
|
||||||
|
bool TryGetPath(int characterName, out IReadOnlyList<PdfSubpath> path);
|
||||||
|
|
||||||
|
bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path);
|
||||||
|
|
||||||
int GetFontMatrixMultiplier();
|
int GetFontMatrixMultiplier();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Core;
|
using Core;
|
||||||
using Fonts.CompactFontFormat;
|
using Fonts.CompactFontFormat;
|
||||||
|
|
||||||
@@ -62,7 +63,6 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool TryGetBoundingBox(int characterIdentifier, Func<int, int?> characterCodeToGlyphId, out PdfRectangle boundingBox)
|
public bool TryGetBoundingBox(int characterIdentifier, Func<int, int?> characterCodeToGlyphId, out PdfRectangle boundingBox)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
@@ -106,5 +106,31 @@
|
|||||||
#endif
|
#endif
|
||||||
return fontCollection.FirstFont;
|
return fontCollection.FirstFont;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
path = EmptyArray<PdfSubpath>.Instance;
|
||||||
|
|
||||||
|
var font = GetFont();
|
||||||
|
|
||||||
|
if (font.Encoding == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var characterName = GetCharacterName(characterCode);
|
||||||
|
|
||||||
|
if (font.TryGetPath(characterName, out path))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Core;
|
using Core;
|
||||||
using Fonts.TrueType;
|
using Fonts.TrueType;
|
||||||
using Fonts.TrueType.Tables;
|
using Fonts.TrueType.Tables;
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
private readonly TrueTypeFont font;
|
private readonly TrueTypeFont font;
|
||||||
|
|
||||||
public FontDetails Details { get; }
|
public FontDetails Details { get; }
|
||||||
|
|
||||||
public PdfCidTrueTypeFont(TrueTypeFont font)
|
public PdfCidTrueTypeFont(TrueTypeFont font)
|
||||||
{
|
{
|
||||||
this.font = font ?? throw new ArgumentNullException(nameof(font));
|
this.font = font ?? throw new ArgumentNullException(nameof(font));
|
||||||
@@ -33,5 +34,10 @@
|
|||||||
=> font.TryGetAdvanceWidth(characterIdentifier, characterCodeToGlyphId, out width);
|
=> font.TryGetAdvanceWidth(characterIdentifier, characterCodeToGlyphId, out width);
|
||||||
|
|
||||||
public int GetFontMatrixMultiplier() => font.GetUnitsPerEm();
|
public int GetFontMatrixMultiplier() => font.GetUnitsPerEm();
|
||||||
|
|
||||||
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path) => font.TryGetPath(characterCode, out path);
|
||||||
|
|
||||||
|
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
=> font.TryGetPath(characterCode, characterCodeToGlyphId, out path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,5 +131,37 @@
|
|||||||
{
|
{
|
||||||
return verticalWritingMetrics.GetDisplacementVector(characterIdentifier);
|
return verticalWritingMetrics.GetDisplacementVector(characterIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
path = null;
|
||||||
|
if (fontProgram == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fontProgram.TryGetPath(characterCode, out path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
path = null;
|
||||||
|
if (fontProgram == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fontProgram.TryGetPath(characterCode, characterCodeToGlyphId, out path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
return TryGetPath(characterCode, out path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetNormalisedPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
return TryGetPath(characterCode, characterCodeToGlyphId, out path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
||||||
{
|
{
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Core;
|
using Core;
|
||||||
using Geometry;
|
using Geometry;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
@@ -114,5 +116,34 @@
|
|||||||
{
|
{
|
||||||
return verticalWritingMetrics.GetDisplacementVector(characterIdentifier);
|
return verticalWritingMetrics.GetDisplacementVector(characterIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path) => TryGetPath(characterCode, cidToGid.GetGlyphIndex, out path);
|
||||||
|
|
||||||
|
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
path = null;
|
||||||
|
if (fontProgram == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fontProgram.TryGetPath(characterCode, characterCodeToGlyphId, out path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
if (!TryGetPath(characterCode, out path))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = FontMatrix.Transform(path).ToList();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetNormalisedPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,165 +1,177 @@
|
|||||||
namespace UglyToad.PdfPig.PdfFonts.Composite
|
namespace UglyToad.PdfPig.PdfFonts.Composite
|
||||||
{
|
{
|
||||||
using System;
|
using CidFonts;
|
||||||
using System.Collections.Generic;
|
using Cmap;
|
||||||
using CidFonts;
|
using Core;
|
||||||
using Cmap;
|
using Geometry;
|
||||||
using Core;
|
using System;
|
||||||
using Geometry;
|
using System.Collections.Generic;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
using Util.JetBrains.Annotations;
|
using Util.JetBrains.Annotations;
|
||||||
using Debug = System.Diagnostics.Debug;
|
using Debug = System.Diagnostics.Debug;
|
||||||
/// <summary>
|
|
||||||
/// Defines glyphs using a CIDFont
|
/// <summary>
|
||||||
/// </summary>
|
/// Defines glyphs using a CIDFont
|
||||||
internal class Type0Font : IFont, IVerticalWritingSupported
|
/// </summary>
|
||||||
{
|
internal class Type0Font : IFont, IVerticalWritingSupported
|
||||||
private readonly CMap ucs2CMap;
|
{
|
||||||
// ReSharper disable once NotAccessedField.Local
|
private readonly CMap ucs2CMap;
|
||||||
private readonly bool isChineseJapaneseOrKorean;
|
// ReSharper disable once NotAccessedField.Local
|
||||||
private readonly Dictionary<int, CharacterBoundingBox> boundingBoxCache
|
private readonly bool isChineseJapaneseOrKorean;
|
||||||
= new Dictionary<int, CharacterBoundingBox>();
|
private readonly Dictionary<int, CharacterBoundingBox> boundingBoxCache
|
||||||
|
= new Dictionary<int, CharacterBoundingBox>();
|
||||||
public NameToken Name => BaseFont;
|
|
||||||
|
public NameToken Name => BaseFont;
|
||||||
[NotNull]
|
|
||||||
public NameToken BaseFont { get; }
|
[NotNull]
|
||||||
|
public NameToken BaseFont { get; }
|
||||||
[NotNull]
|
|
||||||
public ICidFont CidFont { get; }
|
[NotNull]
|
||||||
|
public ICidFont CidFont { get; }
|
||||||
[NotNull]
|
|
||||||
public CMap CMap { get; }
|
[NotNull]
|
||||||
|
public CMap CMap { get; }
|
||||||
[NotNull]
|
|
||||||
public ToUnicodeCMap ToUnicode { get; }
|
[NotNull]
|
||||||
|
public ToUnicodeCMap ToUnicode { get; }
|
||||||
public bool IsVertical => CMap.WritingMode == WritingMode.Vertical;
|
|
||||||
|
public bool IsVertical => CMap.WritingMode == WritingMode.Vertical;
|
||||||
public FontDetails Details { get; }
|
|
||||||
|
public FontDetails Details { get; }
|
||||||
public Type0Font(NameToken baseFont, ICidFont cidFont, CMap cmap, CMap toUnicodeCMap,
|
|
||||||
CMap ucs2CMap,
|
public Type0Font(NameToken baseFont, ICidFont cidFont, CMap cmap, CMap toUnicodeCMap,
|
||||||
bool isChineseJapaneseOrKorean)
|
CMap ucs2CMap,
|
||||||
{
|
bool isChineseJapaneseOrKorean)
|
||||||
this.ucs2CMap = ucs2CMap;
|
{
|
||||||
this.isChineseJapaneseOrKorean = isChineseJapaneseOrKorean;
|
this.ucs2CMap = ucs2CMap;
|
||||||
|
this.isChineseJapaneseOrKorean = isChineseJapaneseOrKorean;
|
||||||
BaseFont = baseFont ?? throw new ArgumentNullException(nameof(baseFont));
|
|
||||||
CidFont = cidFont ?? throw new ArgumentNullException(nameof(cidFont));
|
BaseFont = baseFont ?? throw new ArgumentNullException(nameof(baseFont));
|
||||||
CMap = cmap ?? throw new ArgumentNullException(nameof(cmap));
|
CidFont = cidFont ?? throw new ArgumentNullException(nameof(cidFont));
|
||||||
ToUnicode = new ToUnicodeCMap(toUnicodeCMap);
|
CMap = cmap ?? throw new ArgumentNullException(nameof(cmap));
|
||||||
Details = cidFont.Details?.WithName(Name.Data)
|
ToUnicode = new ToUnicodeCMap(toUnicodeCMap);
|
||||||
?? FontDetails.GetDefault(Name.Data);
|
Details = cidFont.Details?.WithName(Name.Data)
|
||||||
}
|
?? FontDetails.GetDefault(Name.Data);
|
||||||
|
}
|
||||||
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)
|
|
||||||
{
|
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)
|
||||||
var current = bytes.CurrentOffset;
|
{
|
||||||
|
var current = bytes.CurrentOffset;
|
||||||
var code = CMap.ReadCode(bytes);
|
|
||||||
|
var code = CMap.ReadCode(bytes);
|
||||||
codeLength = (int)(bytes.CurrentOffset - current);
|
|
||||||
|
codeLength = (int)(bytes.CurrentOffset - current);
|
||||||
return code;
|
|
||||||
}
|
return code;
|
||||||
|
}
|
||||||
public bool TryGetUnicode(int characterCode, out string value)
|
|
||||||
{
|
public bool TryGetUnicode(int characterCode, out string value)
|
||||||
|
{
|
||||||
value = null;
|
value = null;
|
||||||
|
|
||||||
var HaveCMap = ToUnicode.CanMapToUnicode;
|
var HaveCMap = ToUnicode.CanMapToUnicode;
|
||||||
if (HaveCMap == false)
|
if (HaveCMap == false)
|
||||||
{
|
{
|
||||||
var HaveUnicode2CMap = (ucs2CMap is null == false);
|
var HaveUnicode2CMap = (ucs2CMap is null == false);
|
||||||
if (HaveUnicode2CMap)
|
if (HaveUnicode2CMap)
|
||||||
{
|
{
|
||||||
// Have both ucs2Map and CMap convert to unicode by
|
// Have both ucs2Map and CMap convert to unicode by
|
||||||
// characterCode ----by CMAP---> CID ---ucs2Map---> Unicode
|
// characterCode ----by CMAP---> CID ---ucs2Map---> Unicode
|
||||||
var CID = CMap.ConvertToCid(characterCode);
|
var CID = CMap.ConvertToCid(characterCode);
|
||||||
if (CID == 0)
|
if (CID == 0)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Warning: No mapping from characterCode (0x{characterCode:X} to CID by ucs2Map.");
|
Debug.WriteLine($"Warning: No mapping from characterCode (0x{characterCode:X} to CID by ucs2Map.");
|
||||||
return false; // No mapping from characterCode to CID.
|
return false; // No mapping from characterCode to CID.
|
||||||
}
|
}
|
||||||
// CID ---ucs2Map---> Unicode
|
// CID ---ucs2Map---> Unicode
|
||||||
if (ucs2CMap.TryConvertToUnicode(CID, out value))
|
if (ucs2CMap.TryConvertToUnicode(CID, out value))
|
||||||
{
|
{
|
||||||
return value != null;
|
return value != null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
if (HaveUnicode2CMap) // 2022-12-24 @fnatzke left as fall-back. Possible?
|
||||||
if (HaveUnicode2CMap) // 2022-12-24 @fnatzke left as fall-back. Possible?
|
{
|
||||||
{
|
// characterCode ---ucs2Map---> Unicode (?) @fnatzke possible?
|
||||||
// characterCode ---ucs2Map---> Unicode (?) @fnatzke possible?
|
if (ucs2CMap.TryConvertToUnicode(characterCode, out value))
|
||||||
if (ucs2CMap.TryConvertToUnicode(characterCode, out value))
|
{
|
||||||
{
|
return value != null;
|
||||||
return value != null;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// According to PdfBox certain providers incorrectly using Identity CMaps as ToUnicode.
|
||||||
// According to PdfBox certain providers incorrectly using Identity CMaps as ToUnicode.
|
if (ToUnicode.IsUsingIdentityAsUnicodeMap)
|
||||||
if (ToUnicode.IsUsingIdentityAsUnicodeMap)
|
{
|
||||||
{
|
value = new string((char)characterCode, 1);
|
||||||
value = new string((char)characterCode, 1);
|
|
||||||
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
return ToUnicode.TryGet(characterCode, out value);
|
||||||
return ToUnicode.TryGet(characterCode, out value);
|
}
|
||||||
}
|
|
||||||
|
public CharacterBoundingBox GetBoundingBox(int characterCode)
|
||||||
public CharacterBoundingBox GetBoundingBox(int characterCode)
|
{
|
||||||
{
|
if (boundingBoxCache.TryGetValue(characterCode, out var cached))
|
||||||
if (boundingBoxCache.TryGetValue(characterCode, out var cached))
|
{
|
||||||
{
|
return cached;
|
||||||
return cached;
|
}
|
||||||
}
|
|
||||||
|
var matrix = GetFontMatrix();
|
||||||
var matrix = GetFontMatrix();
|
|
||||||
|
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
|
||||||
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
|
|
||||||
|
boundingBox = matrix.Transform(boundingBox);
|
||||||
boundingBox = matrix.Transform(boundingBox);
|
|
||||||
|
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
|
||||||
|
var width = CidFont.GetWidthFromFont(characterIdentifier);
|
||||||
var width = CidFont.GetWidthFromFont(characterIdentifier);
|
|
||||||
|
var advanceWidth = matrix.TransformX(width);
|
||||||
var advanceWidth = matrix.TransformX(width);
|
|
||||||
|
var result = new CharacterBoundingBox(boundingBox, advanceWidth);
|
||||||
var result = new CharacterBoundingBox(boundingBox, advanceWidth);
|
|
||||||
|
boundingBoxCache[characterCode] = result;
|
||||||
boundingBoxCache[characterCode] = result;
|
|
||||||
|
return result;
|
||||||
return result;
|
}
|
||||||
}
|
|
||||||
|
public PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
|
||||||
public PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
|
{
|
||||||
{
|
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
|
||||||
|
return CidFont.GetBoundingBox(characterIdentifier);
|
||||||
return CidFont.GetBoundingBox(characterIdentifier);
|
}
|
||||||
}
|
|
||||||
|
public TransformationMatrix GetFontMatrix()
|
||||||
public TransformationMatrix GetFontMatrix()
|
{
|
||||||
{
|
return CidFont.FontMatrix;
|
||||||
return CidFont.FontMatrix;
|
}
|
||||||
}
|
|
||||||
|
public PdfVector GetPositionVector(int characterCode)
|
||||||
public PdfVector GetPositionVector(int characterCode)
|
{
|
||||||
{
|
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
|
||||||
|
return CidFont.GetPositionVector(characterIdentifier).Scale(-1 / 1000.0);
|
||||||
return CidFont.GetPositionVector(characterIdentifier).Scale(-1 / 1000.0);
|
}
|
||||||
}
|
|
||||||
|
public PdfVector GetDisplacementVector(int characterCode)
|
||||||
public PdfVector GetDisplacementVector(int characterCode)
|
{
|
||||||
{
|
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
|
||||||
|
return CidFont.GetDisplacementVector(characterIdentifier).Scale(1 / 1000.0);
|
||||||
return CidFont.GetDisplacementVector(characterIdentifier).Scale(1 / 1000.0);
|
}
|
||||||
}
|
|
||||||
}
|
/// <inheritdoc/>
|
||||||
}
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
return CidFont.TryGetPath(characterCode, out path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
return CidFont.TryGetNormalisedPath(characterCode, out path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
namespace UglyToad.PdfPig.PdfFonts
|
namespace UglyToad.PdfPig.PdfFonts
|
||||||
{
|
{
|
||||||
using Core;
|
using Core;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
|
||||||
internal interface IFont
|
internal interface IFont
|
||||||
{
|
{
|
||||||
NameToken Name { get; }
|
NameToken Name { get; }
|
||||||
|
|
||||||
bool IsVertical { get; }
|
bool IsVertical { get; }
|
||||||
|
|
||||||
FontDetails Details { get; }
|
FontDetails Details { get; }
|
||||||
@@ -18,5 +19,19 @@
|
|||||||
CharacterBoundingBox GetBoundingBox(int characterCode);
|
CharacterBoundingBox GetBoundingBox(int characterCode);
|
||||||
|
|
||||||
TransformationMatrix GetFontMatrix();
|
TransformationMatrix GetFontMatrix();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the glyph path for the given character code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||||
|
/// <param name="path">The glyph path for the given character code.</param>
|
||||||
|
bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the normalised glyph path for the given character code in a PDF.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||||
|
/// <param name="path">The normalized glyph path for the given character code.</param>
|
||||||
|
bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,11 +114,11 @@
|
|||||||
switch (descriptor.FontFile.FileType)
|
switch (descriptor.FontFile.FileType)
|
||||||
{
|
{
|
||||||
case DescriptorFontFile.FontFileType.TrueType:
|
case DescriptorFontFile.FontFileType.TrueType:
|
||||||
{
|
{
|
||||||
var input = new TrueTypeDataBytes(new ByteArrayInputBytes(fontFile));
|
var input = new TrueTypeDataBytes(new ByteArrayInputBytes(fontFile));
|
||||||
var ttf = TrueTypeFontParser.Parse(input);
|
var ttf = TrueTypeFontParser.Parse(input);
|
||||||
return new PdfCidTrueTypeFont(ttf);
|
return new PdfCidTrueTypeFont(ttf);
|
||||||
}
|
}
|
||||||
case DescriptorFontFile.FontFileType.FromSubtype:
|
case DescriptorFontFile.FontFileType.FromSubtype:
|
||||||
{
|
{
|
||||||
if (!DirectObjectFinder.TryGet(descriptor.FontFile.ObjectKey, pdfScanner, out StreamToken str))
|
if (!DirectObjectFinder.TryGet(descriptor.FontFile.ObjectKey, pdfScanner, out StreamToken str))
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
var ttf = TrueTypeFontParser.Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(bytes)));
|
var ttf = TrueTypeFontParser.Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(bytes)));
|
||||||
return new PdfCidTrueTypeFont(ttf);
|
return new PdfCidTrueTypeFont(ttf);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new PdfDocumentFormatException($"Unexpected subtype for CID font: {subtypeName}.");
|
throw new PdfDocumentFormatException($"Unexpected subtype for CID font: {subtypeName}.");
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
namespace UglyToad.PdfPig.PdfFonts.Simple
|
namespace UglyToad.PdfPig.PdfFonts.Simple
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Cmap;
|
using Cmap;
|
||||||
using Composite;
|
using Composite;
|
||||||
using Core;
|
using Core;
|
||||||
using Fonts;
|
using Fonts;
|
||||||
using Fonts.Encodings;
|
using Fonts.Encodings;
|
||||||
using Fonts.TrueType;
|
using Fonts.TrueType;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
using Util.JetBrains.Annotations;
|
using Util.JetBrains.Annotations;
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
Name = name;
|
Name = name;
|
||||||
IsVertical = false;
|
IsVertical = false;
|
||||||
ToUnicode = new ToUnicodeCMap(toUnicodeCMap);
|
ToUnicode = new ToUnicodeCMap(toUnicodeCMap);
|
||||||
Details = descriptor?.ToDetails(Name?.Data)
|
Details = descriptor?.ToDetails(Name?.Data)
|
||||||
?? FontDetails.GetDefault(Name?.Data);
|
?? FontDetails.GetDefault(Name?.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +322,29 @@
|
|||||||
|
|
||||||
return widths[index];
|
return widths[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
if (font == null)
|
||||||
|
{
|
||||||
|
path = EmptyArray<PdfSubpath>.Instance;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return font.TryGetPath(characterCode, CharacterCodeToGlyphId, out path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
if (!TryGetPath(characterCode, out path))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = GetFontMatrix().Transform(path).ToList();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Core;
|
using Core;
|
||||||
using Fonts;
|
using Fonts;
|
||||||
using Fonts.AdobeFontMetrics;
|
using Fonts.AdobeFontMetrics;
|
||||||
@@ -132,6 +133,29 @@
|
|||||||
return DefaultTransformation;
|
return DefaultTransformation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
path = null;
|
||||||
|
if (font == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return font.TryGetPath(characterCode, out path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
if (!TryGetPath(characterCode, out path))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = GetFontMatrix().Transform(path).ToList();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public class MetricOverrides
|
public class MetricOverrides
|
||||||
{
|
{
|
||||||
public int? FirstCharacterCode { get; }
|
public int? FirstCharacterCode { get; }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace UglyToad.PdfPig.PdfFonts.Simple
|
namespace UglyToad.PdfPig.PdfFonts.Simple
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Cmap;
|
using Cmap;
|
||||||
using Composite;
|
using Composite;
|
||||||
using Core;
|
using Core;
|
||||||
@@ -43,7 +44,7 @@
|
|||||||
|
|
||||||
public FontDetails Details { get; }
|
public FontDetails Details { get; }
|
||||||
|
|
||||||
public Type1FontSimple(NameToken name, int firstChar, int lastChar, double[] widths, FontDescriptor fontDescriptor, Encoding encoding,
|
public Type1FontSimple(NameToken name, int firstChar, int lastChar, double[] widths, FontDescriptor fontDescriptor, Encoding encoding,
|
||||||
CMap toUnicodeCMap,
|
CMap toUnicodeCMap,
|
||||||
Union<Type1Font, CompactFontFormatFontCollection> fontProgram)
|
Union<Type1Font, CompactFontFormatFontCollection> fontProgram)
|
||||||
{
|
{
|
||||||
@@ -72,7 +73,7 @@
|
|||||||
fontMatrix = matrix;
|
fontMatrix = matrix;
|
||||||
|
|
||||||
Name = name;
|
Name = name;
|
||||||
Details = fontDescriptor?.ToDetails(name?.Data)
|
Details = fontDescriptor?.ToDetails(name?.Data)
|
||||||
?? FontDetails.GetDefault(name?.Data);
|
?? FontDetails.GetDefault(name?.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var name = encoding.GetName(characterCode);
|
var name = encoding.GetName(characterCode);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
value = GlyphList.AdobeGlyphList.NameToUnicode(name);
|
value = GlyphList.AdobeGlyphList.NameToUnicode(name);
|
||||||
@@ -135,7 +136,7 @@
|
|||||||
{
|
{
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
|
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
|
||||||
|
|
||||||
var matrix = fontMatrix;
|
var matrix = fontMatrix;
|
||||||
@@ -144,7 +145,7 @@
|
|||||||
|
|
||||||
var width = GetWidth(characterCode, boundingBox);
|
var width = GetWidth(characterCode, boundingBox);
|
||||||
|
|
||||||
var result = new CharacterBoundingBox(boundingBox, width/1000.0);
|
var result = new CharacterBoundingBox(boundingBox, width / 1000.0);
|
||||||
|
|
||||||
cachedBoundingBoxes[characterCode] = result;
|
cachedBoundingBoxes[characterCode] = result;
|
||||||
|
|
||||||
@@ -167,7 +168,7 @@
|
|||||||
|
|
||||||
return boundingBox.Width;
|
return boundingBox.Width;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
|
private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
|
||||||
{
|
{
|
||||||
if (characterCode < firstChar || characterCode > lastChar)
|
if (characterCode < firstChar || characterCode > lastChar)
|
||||||
@@ -183,26 +184,25 @@
|
|||||||
PdfRectangle? rect = null;
|
PdfRectangle? rect = null;
|
||||||
if (fontProgram.TryGetFirst(out var t1Font))
|
if (fontProgram.TryGetFirst(out var t1Font))
|
||||||
{
|
{
|
||||||
var name = encoding.GetName(characterCode);
|
var name = encoding.GetName(characterCode);
|
||||||
rect = t1Font.GetCharacterBoundingBox(name);
|
rect = t1Font.GetCharacterBoundingBox(name);
|
||||||
}
|
}
|
||||||
else if (fontProgram.TryGetSecond(out var cffFont))
|
else if (fontProgram.TryGetSecond(out var cffFont))
|
||||||
{
|
{
|
||||||
var first = cffFont.FirstFont;
|
var first = cffFont.FirstFont;
|
||||||
string characterName;
|
string characterName;
|
||||||
if (encoding != null)
|
if (encoding != null)
|
||||||
{
|
{
|
||||||
characterName = encoding.GetName(characterCode);
|
characterName = encoding.GetName(characterCode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
characterName = cffFont.GetCharacterName(characterCode);
|
characterName = cffFont.GetCharacterName(characterCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
rect = first.GetCharacterBoundingBox(characterName);
|
rect = first.GetCharacterBoundingBox(characterName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!rect.HasValue)
|
if (!rect.HasValue)
|
||||||
{
|
{
|
||||||
return new PdfRectangle(0, 0, widths[characterCode - firstChar], 0);
|
return new PdfRectangle(0, 0, widths[characterCode - firstChar], 0);
|
||||||
@@ -216,5 +216,61 @@
|
|||||||
{
|
{
|
||||||
return fontMatrix;
|
return fontMatrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
path = null;
|
||||||
|
IReadOnlyList<PdfSubpath> tempPath = null;
|
||||||
|
if (characterCode < firstChar || characterCode > lastChar)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontProgram == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontProgram.TryGetFirst(out var t1Font))
|
||||||
|
{
|
||||||
|
var name = encoding.GetName(characterCode);
|
||||||
|
tempPath = t1Font.GetCharacterPath(name);
|
||||||
|
}
|
||||||
|
else if (fontProgram.TryGetSecond(out var cffFont))
|
||||||
|
{
|
||||||
|
var first = cffFont.FirstFont;
|
||||||
|
string characterName;
|
||||||
|
if (encoding != null)
|
||||||
|
{
|
||||||
|
characterName = encoding.GetName(characterCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
characterName = cffFont.GetCharacterName(characterCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
tempPath = first.GetCharacterPath(characterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempPath != null)
|
||||||
|
{
|
||||||
|
path = tempPath;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
if (TryGetPath(characterCode, out path))
|
||||||
|
{
|
||||||
|
path = fontMatrix.Transform(path).ToList();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
//// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||||
namespace UglyToad.PdfPig.PdfFonts.Simple
|
namespace UglyToad.PdfPig.PdfFonts.Simple
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Core;
|
using Core;
|
||||||
using Fonts;
|
using Fonts;
|
||||||
using Fonts.AdobeFontMetrics;
|
using Fonts.AdobeFontMetrics;
|
||||||
using Fonts.Encodings;
|
using Fonts.Encodings;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -120,5 +121,25 @@ namespace UglyToad.PdfPig.PdfFonts.Simple
|
|||||||
{
|
{
|
||||||
return fontMatrix;
|
return fontMatrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <para>Not implemeted.</para>
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
// https://github.com/apache/pdfbox/blob/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/Standard14Fonts.java
|
||||||
|
path = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <para>Not implemeted.</para>
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
return TryGetPath(characterCode, out path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
using Core;
|
using Core;
|
||||||
using Fonts;
|
using Fonts;
|
||||||
using Fonts.Encodings;
|
using Fonts.Encodings;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
|
||||||
internal class Type3Font : IFont
|
internal class Type3Font : IFont
|
||||||
@@ -57,9 +58,7 @@
|
|||||||
|
|
||||||
var name = encoding.GetName(characterCode);
|
var name = encoding.GetName(characterCode);
|
||||||
|
|
||||||
var listed = GlyphList.AdobeGlyphList.NameToUnicode(name);
|
value = GlyphList.AdobeGlyphList.NameToUnicode(name);
|
||||||
|
|
||||||
value = listed;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -89,5 +88,24 @@
|
|||||||
{
|
{
|
||||||
return fontMatrix;
|
return fontMatrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <para>Type 3 fonts do not use vector paths. Always returns <c>false</c>.</para>
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
path = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <inheritdoc/>
|
||||||
|
/// <para>Type 3 fonts do not use vector paths. Always returns <c>false</c>.</para>
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||||
|
{
|
||||||
|
return TryGetPath(characterCode, out path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user