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)