diff --git a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs
index 2b223f2e..5cbc0e00 100644
--- a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs
+++ b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs
@@ -63,11 +63,11 @@
Assert.Equal(13, font.HeaderTable.Modified.Minute);
Assert.Equal(10, font.HeaderTable.Modified.Second);
- Assert.Equal(-980, font.HeaderTable.XMin);
- Assert.Equal(-555, font.HeaderTable.YMin);
+ Assert.Equal(-980, font.HeaderTable.Bounds.Left);
+ Assert.Equal(-555, font.HeaderTable.Bounds.Bottom);
- Assert.Equal(2396, font.HeaderTable.XMax);
- Assert.Equal(2163, font.HeaderTable.YMax);
+ Assert.Equal(2396, font.HeaderTable.Bounds.Right);
+ Assert.Equal(2163, font.HeaderTable.Bounds.Top);
Assert.Equal(HeaderTable.HeaderMacStyle.None, font.HeaderTable.MacStyle);
Assert.Equal(9, font.HeaderTable.LowestRecommendedPpem);
diff --git a/src/UglyToad.PdfPig/Content/CropBox.cs b/src/UglyToad.PdfPig/Content/CropBox.cs
index e3962202..38947432 100644
--- a/src/UglyToad.PdfPig/Content/CropBox.cs
+++ b/src/UglyToad.PdfPig/Content/CropBox.cs
@@ -12,7 +12,7 @@
[NotNull]
public PdfRectangle Bounds { get; }
- public CropBox(PdfRectangle bounds)
+ public CropBox(PdfRectangle? bounds)
{
Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds));
}
diff --git a/src/UglyToad.PdfPig/Content/MediaBox.cs b/src/UglyToad.PdfPig/Content/MediaBox.cs
index 63e3babb..070341a9 100644
--- a/src/UglyToad.PdfPig/Content/MediaBox.cs
+++ b/src/UglyToad.PdfPig/Content/MediaBox.cs
@@ -68,7 +68,7 @@
public PdfRectangle Bounds { get; }
- public MediaBox(PdfRectangle bounds)
+ public MediaBox(PdfRectangle? bounds)
{
Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds));
}
diff --git a/src/UglyToad.PdfPig/Fonts/Composite/Type0Font.cs b/src/UglyToad.PdfPig/Fonts/Composite/Type0Font.cs
index ea486a6b..512b19b3 100644
--- a/src/UglyToad.PdfPig/Fonts/Composite/Type0Font.cs
+++ b/src/UglyToad.PdfPig/Fonts/Composite/Type0Font.cs
@@ -82,7 +82,9 @@
var cid = CMap.ConvertToCid(characterCode);
var fromFont = CidFont.GetWidthFromDictionary(cid);
-
+
+ var box = GetBoundingBox(characterCode);
+
return fromFont;
}
diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CompositeGlyphFlags.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CompositeGlyphFlags.cs
new file mode 100644
index 00000000..0561293d
--- /dev/null
+++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CompositeGlyphFlags.cs
@@ -0,0 +1,49 @@
+namespace UglyToad.PdfPig.Fonts.TrueType.Tables
+{
+ using System;
+
+ [Flags]
+ internal enum CompositeGlyphFlags : ushort
+ {
+ ///
+ /// If set arguments are words, otherwise they are bytes.
+ ///
+ Args1And2AreWords = 1,
+ ///
+ /// If set arguments are x y offset values, otherwise they are points.
+ ///
+ ArgsAreXAndYValues = 1 << 1,
+ ///
+ /// If arguments are x y offset values and this is set then the values are rounded to the closest grid lines before addition to the glyph.
+ ///
+ RoundXAndYToGrid = 1 << 2,
+ ///
+ /// If set the scale value is read in 2.14 format (between -2 to < 2) and the glyph is scaled before grid-fitting. Otherwise scale is 1.
+ ///
+ WeHaveAScale = 1 << 3,
+ ///
+ /// Reserved for future use, should be set to 0.
+ ///
+ Reserved = 1 << 4,
+ ///
+ /// Indicates that there is a glyph following the current one.
+ ///
+ MoreComponents = 1 << 5,
+ ///
+ /// Indicates that X is scaled differently to Y.
+ ///
+ WeHaveAnXAndYScale = 1 << 6,
+ ///
+ /// Indicates that there is a 2 by 2 transformation used to scale the component.
+ ///
+ WeHaveATwoByTwo = 1 << 7,
+ ///
+ /// Indicates that there are instructions for the composite character following the last component.
+ ///
+ WeHaveInstructions = 1 << 8,
+ ///
+ /// If set this forces advance width and left side bearing for the composite to be equal to those from the original glyph.
+ ///
+ UseMyMetrics = 1 << 9
+ }
+}
\ No newline at end of file
diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/GlyphDataTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/GlyphDataTable.cs
index e20ead76..ebd4a80f 100644
--- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/GlyphDataTable.cs
+++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/GlyphDataTable.cs
@@ -28,7 +28,6 @@
{
data.Seek(table.Offset);
- var headerTable = tableRegister.HeaderTable;
var indexToLocationTable = tableRegister.IndexToLocationTable;
var offsets = indexToLocationTable.GlyphOffsets;
@@ -39,11 +38,16 @@
var glyphs = new IGlyphDescription[glyphCount];
+ var emptyGlyph = new EmptyGlyph(tableRegister.HeaderTable.Bounds);
+
+ var compositeLocations = new Dictionary();
+
for (var i = 0; i < glyphCount; i++)
{
if (offsets[i] == offsets[i + 1])
{
// empty glyph
+ glyphs[i] = emptyGlyph;
continue;
}
@@ -55,9 +59,9 @@
var minY = data.ReadSignedShort();
var maxX = data.ReadSignedShort();
var maxY = data.ReadSignedShort();
-
+
var bounds = new PdfRectangle(minX, minY, maxX, maxY);
-
+
// If the number of contours is greater than or equal zero it's a simple glyph.
if (contourCount >= 0)
{
@@ -65,10 +69,16 @@
}
else
{
-
+ compositeLocations.Add(i , new TemporaryCompositeLocation(data.Position, bounds, contourCount));
}
}
+ // Build composite glyphs by combining simple and other composite glyphs.
+ foreach (var compositeLocation in compositeLocations)
+ {
+ glyphs[compositeLocation.Key] = ReadCompositeGlyph(data, compositeLocation.Value, compositeLocations, glyphs);
+ }
+
return new GlyphDataTable(table, glyphs);
}
@@ -97,6 +107,103 @@
return new SimpleGlyphDescription(instructionLength, endPointsOfContours, flags, xCoordinates, yCoordinates, bounds);
}
+ private static CompositeGlyphDescription ReadCompositeGlyph(TrueTypeDataBytes data, TemporaryCompositeLocation compositeLocation, Dictionary compositeLocations, IGlyphDescription[] glyphs)
+ {
+ bool HasFlag(CompositeGlyphFlags value, CompositeGlyphFlags target)
+ {
+ return (value & target) == target;
+ }
+
+ data.Seek(compositeLocation.Position);
+
+ CompositeGlyphFlags flags;
+ do
+ {
+ flags = (CompositeGlyphFlags) data.ReadUnsignedShort();
+ var glyphIndex = data.ReadUnsignedShort();
+
+ var childGlyph = glyphs[glyphIndex];
+
+ if (childGlyph == null)
+ {
+ if (!compositeLocations.TryGetValue(glyphIndex, out var missingComposite))
+ {
+ throw new InvalidOperationException($"The composite glyph required a contour at index {glyphIndex} but there was no simple or composite glyph at this location.");
+ }
+
+ childGlyph = ReadCompositeGlyph(data, missingComposite, compositeLocations, glyphs);
+
+ glyphs[glyphIndex] = childGlyph;
+ }
+
+ var cloned = childGlyph.DeepClone();
+
+ short arg1, arg2;
+ if (HasFlag(flags, CompositeGlyphFlags.Args1And2AreWords))
+ {
+ arg1 = data.ReadSignedShort();
+ arg2 = data.ReadSignedShort();
+ }
+ else
+ {
+ arg1 = data.ReadByte();
+ arg2 = data.ReadByte();
+ }
+
+ float xscale = 1;
+ float scale01 = 0;
+ float scale10 = 0;
+ float yscale = 1;
+
+ bool hasScale, hasMatrix = false;
+ if (HasFlag(flags, CompositeGlyphFlags.WeHaveAScale))
+ {
+ xscale = ReadTwoFourteenFormat(data);
+ yscale = xscale;
+ hasScale = true;
+ }
+ else if (HasFlag(flags, CompositeGlyphFlags.WeHaveAnXAndYScale))
+ {
+ xscale = ReadTwoFourteenFormat(data);
+ yscale = ReadTwoFourteenFormat(data);
+ hasScale = true;
+ }
+ else if (HasFlag(flags, CompositeGlyphFlags.WeHaveATwoByTwo))
+ {
+ /*
+ * We build the 2 by 2 matrix:
+ * x 0
+ * 0 y
+ */
+ xscale = ReadTwoFourteenFormat(data);
+ scale01 = ReadTwoFourteenFormat(data);
+ scale10 = ReadTwoFourteenFormat(data);
+ yscale = ReadTwoFourteenFormat(data);
+ hasScale = true;
+ hasMatrix = true;
+ }
+
+ if (HasFlag(flags, CompositeGlyphFlags.ArgsAreXAndYValues))
+ {
+
+ }
+ else
+ {
+ // TODO: Not implemented, it is unclear how to do this.
+ }
+
+ } while (HasFlag(flags, CompositeGlyphFlags.MoreComponents));
+
+ return new CompositeGlyphDescription();
+ }
+
+ private static float ReadTwoFourteenFormat(TrueTypeDataBytes data)
+ {
+ const float divisor = 1 << 14;
+
+ return data.ReadSignedShort() / divisor;
+ }
+
private static SimpleGlyphFlags[] ReadFlags(TrueTypeDataBytes data, int pointCount)
{
var result = new SimpleGlyphFlags[pointCount];
@@ -140,36 +247,39 @@
return xs;
}
- }
- [Flags]
- internal enum SimpleGlyphFlags : byte
- {
///
- /// The point is on the curve.
+ /// Stores the composite glyph information we read when initially scanning the glyph table.
+ /// Once we have all composite glyphs we can start building them from simple glyphs.
///
- OnCurve = 1,
- ///
- /// The x-coordinate is 1 byte long instead of 2.
- ///
- XShortVector = 1 << 1,
- ///
- /// The y-coordinate is 1 byte long instead of 2.
- ///
- YShortVector = 1 << 2,
- ///
- /// The next byte specifies the number of times to repeat this set of flags.
- ///
- Repeat = 1 << 3,
- ///
- /// If is set this means the sign of the x-coordinate is positive.
- /// If is not set then the current x-coordinate is the same as the previous.
- ///
- XSignOrSame = 1 << 4,
- ///
- /// If is set this means the sign of the y-coordinate is positive.
- /// If is not set then the current y-coordinate is the same as the previous.
- ///
- YSignOrSame = 1 << 5
+ private struct TemporaryCompositeLocation
+ {
+ ///
+ /// Stores the position after reading the contour count and bounds.
+ ///
+ public long Position { get; }
+
+ ///
+ /// The bounds we read.
+ ///
+ public PdfRectangle Bounds { get; }
+
+ ///
+ /// The number of contours in this composite glyph. Should be less than zero.
+ ///
+ public short ContourCount { get; }
+
+ public TemporaryCompositeLocation(long position, PdfRectangle bounds, short contourCount)
+ {
+ Position = position;
+ Bounds = bounds;
+ ContourCount = contourCount;
+
+ if (ContourCount >= 0 )
+ {
+ throw new ArgumentException($"A composite glyph should not have a positive contour count. Got: {contourCount}.", nameof(contourCount));
+ }
+ }
+ }
}
}
diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HeaderTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HeaderTable.cs
index de0d626d..b0ff44cc 100644
--- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HeaderTable.cs
+++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HeaderTable.cs
@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
using System;
+ using Geometry;
///
/// Gives global information about the font.
@@ -27,13 +28,7 @@
public DateTime Modified { get; }
- public short XMin { get; }
-
- public short YMin { get; }
-
- public short XMax { get; }
-
- public short YMax { get; }
+ public PdfRectangle Bounds { get; }
public HeaderMacStyle MacStyle { get; }
@@ -74,10 +69,7 @@
UnitsPerEm = unitsPerEm;
Created = created;
Modified = modified;
- XMin = xMin;
- YMin = yMin;
- XMax = xMax;
- YMax = yMax;
+ Bounds = new PdfRectangle(xMin, yMin, xMax, yMax);
MacStyle = (HeaderMacStyle)macStyle;
LowestRecommendedPpem = lowestRecommendedPpem;
FontDirectionHint = (FontDirection)fontDirectionHint;
diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/IGlyphDescription.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/IGlyphDescription.cs
index 06e0670b..f2449935 100644
--- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/IGlyphDescription.cs
+++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/IGlyphDescription.cs
@@ -1,5 +1,6 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
+ using System;
using Geometry;
internal interface IGlyphDescription
@@ -8,8 +9,10 @@
SimpleGlyphDescription SimpleGlyph { get; }
- object CompositeGlyph { get; }
+ CompositeGlyphDescription CompositeGlyph { get; }
PdfRectangle GlyphBounds { get; }
+
+ IGlyphDescription DeepClone();
}
}
\ No newline at end of file
diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphDescription.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphDescription.cs
index 5e7ec499..2a06a386 100644
--- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphDescription.cs
+++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphDescription.cs
@@ -1,5 +1,6 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
+ using System;
using Geometry;
internal class SimpleGlyphDescription : IGlyphDescription
@@ -13,7 +14,7 @@
/// The total number of bytes for instructions.
///
public int InstructionLength { get; }
-
+
///
/// An array of the last points of each contour.
///
@@ -29,7 +30,7 @@
/// the rest are relative to the previous point.
///
public short[] XCoordinates { get; }
-
+
///
/// The y-coordinates of the points in this glyph. The first coordinates are relative to the origin (0, 0)
/// the rest are relative to the previous point.
@@ -51,6 +52,59 @@
public SimpleGlyphDescription SimpleGlyph => this;
- public object CompositeGlyph { get; } = null;
+ public CompositeGlyphDescription CompositeGlyph { get; } = null;
+
+ public IGlyphDescription DeepClone()
+ {
+ var clonedEndPoints = new int[EndPointsOfContours.Length];
+ Array.Copy(EndPointsOfContours, clonedEndPoints, EndPointsOfContours.Length);
+
+ var clonedFlags = new SimpleGlyphFlags[Flags.Length];
+ Array.Copy(Flags, clonedFlags, Flags.Length);
+
+ var clonedXCoordinates = new short[XCoordinates.Length];
+ Array.Copy(XCoordinates, clonedXCoordinates, XCoordinates.Length);
+
+ var clonedYCoordinates = new short[YCoordinates.Length];
+ Array.Copy(YCoordinates, clonedYCoordinates, YCoordinates.Length);
+
+ return new SimpleGlyphDescription(InstructionLength, clonedEndPoints, clonedFlags, clonedXCoordinates, clonedYCoordinates, GlyphBounds);
+ }
+ }
+
+ internal class CompositeGlyphDescription : IGlyphDescription
+ {
+ public bool IsSimple { get; } = false;
+
+ public SimpleGlyphDescription SimpleGlyph { get; } = null;
+
+ public CompositeGlyphDescription CompositeGlyph => this;
+
+ public PdfRectangle GlyphBounds { get; }
+ public IGlyphDescription DeepClone()
+ {
+ return new CompositeGlyphDescription();
+ }
+ }
+
+ internal class EmptyGlyph : IGlyphDescription
+ {
+ public bool IsSimple { get; } = true;
+
+ public SimpleGlyphDescription SimpleGlyph => new SimpleGlyphDescription(0, new int[0], new SimpleGlyphFlags[0], new short[0], new short[0], GlyphBounds);
+
+ public CompositeGlyphDescription CompositeGlyph { get; } = null;
+
+ public PdfRectangle GlyphBounds { get; }
+
+ public EmptyGlyph(PdfRectangle glyphBounds)
+ {
+ GlyphBounds = glyphBounds;
+ }
+
+ public IGlyphDescription DeepClone()
+ {
+ return new EmptyGlyph(GlyphBounds);
+ }
}
}
\ No newline at end of file
diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphFlags.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphFlags.cs
new file mode 100644
index 00000000..b7c772b9
--- /dev/null
+++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphFlags.cs
@@ -0,0 +1,35 @@
+namespace UglyToad.PdfPig.Fonts.TrueType.Tables
+{
+ using System;
+
+ [Flags]
+ internal enum SimpleGlyphFlags : byte
+ {
+ ///
+ /// The point is on the curve.
+ ///
+ OnCurve = 1,
+ ///
+ /// The x-coordinate is 1 byte long instead of 2.
+ ///
+ XShortVector = 1 << 1,
+ ///
+ /// The y-coordinate is 1 byte long instead of 2.
+ ///
+ YShortVector = 1 << 2,
+ ///
+ /// The next byte specifies the number of times to repeat this set of flags.
+ ///
+ Repeat = 1 << 3,
+ ///
+ /// If is set this means the sign of the x-coordinate is positive.
+ /// If is not set then the current x-coordinate is the same as the previous.
+ ///
+ XSignOrSame = 1 << 4,
+ ///
+ /// If is set this means the sign of the y-coordinate is positive.
+ /// If is not set then the current y-coordinate is the same as the previous.
+ ///
+ YSignOrSame = 1 << 5
+ }
+}
\ No newline at end of file
diff --git a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1FontParser.cs b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1FontParser.cs
index 10298b6c..6edc69af 100644
--- a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1FontParser.cs
+++ b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1FontParser.cs
@@ -148,7 +148,7 @@
encryptedPortionParser.Parse(eexecPortion);
- return new Type1Font(name, encoding, matrix, boundingBox);
+ return new Type1Font(name, encoding, matrix, boundingBox ?? new PdfRectangle());
}
///
@@ -386,7 +386,7 @@
return null;
}
- private static PdfRectangle GetBoundingBox(IReadOnlyList dictionaries)
+ private static PdfRectangle? GetBoundingBox(IReadOnlyList dictionaries)
{
foreach (var dictionary in dictionaries)
{
diff --git a/src/UglyToad.PdfPig/Geometry/PdfRectangle.cs b/src/UglyToad.PdfPig/Geometry/PdfRectangle.cs
index 48ee98e3..c9ddd7e7 100644
--- a/src/UglyToad.PdfPig/Geometry/PdfRectangle.cs
+++ b/src/UglyToad.PdfPig/Geometry/PdfRectangle.cs
@@ -2,7 +2,7 @@
{
using System;
- internal class PdfRectangle
+ internal struct PdfRectangle
{
public PdfPoint TopLeft { get; }
@@ -18,6 +18,11 @@
public decimal Area { get; }
+ public decimal Left => TopLeft.X;
+ public decimal Top => TopLeft.Y;
+ public decimal Right => BottomRight.X;
+ public decimal Bottom => BottomRight.Y;
+
public PdfRectangle(PdfPoint point1, PdfPoint point2) : this(point1.X, point1.Y, point2.X, point2.Y) { }
public PdfRectangle(short x1, short y1, short x2, short y2) : this((decimal) x1, y1, x2, y2) { }
public PdfRectangle(decimal x1, decimal y1, decimal x2, decimal y2)