Implement TryGetPath and TryGetNormalisedPath for fonts

This commit is contained in:
BobLD
2023-01-31 17:07:07 +00:00
parent 5eebe9d0f9
commit cc6e2d302f
47 changed files with 1884 additions and 1256 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

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

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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()

View File

@@ -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
{ {

View File

@@ -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 =>
{ {

View File

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

View File

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

View File

@@ -63,7 +63,7 @@
foreach (var charString in result.CharStrings.CharStrings) foreach (var charString in result.CharStrings.CharStrings)
{ {
Assert.True(result.CharStrings.TryGenerate(charString.Key, out var path)); Assert.True(result.CharStrings.TryGenerate(charString.Key, out var path));
builder.AppendLine(path.ToFullSvg(0)); //builder.AppendLine(path.ToFullSvg(0)); // TODO - to restore
} }
builder.Append("</body></html>"); builder.Append("</body></html>");

View File

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

View File

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

View File

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

View File

@@ -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))
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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:

View File

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

View File

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

View File

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

View File

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

View File

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