diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/CompositeGlyphDescription.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/CompositeGlyphDescription.cs new file mode 100644 index 00000000..cb64005a --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/CompositeGlyphDescription.cs @@ -0,0 +1,20 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs +{ + using Geometry; + + 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(); + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CompositeGlyphFlags.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/CompositeGlyphFlags.cs similarity index 97% rename from src/UglyToad.PdfPig/Fonts/TrueType/Tables/CompositeGlyphFlags.cs rename to src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/CompositeGlyphFlags.cs index 0561293d..3692c4f7 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CompositeGlyphFlags.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/CompositeGlyphFlags.cs @@ -1,4 +1,4 @@ -namespace UglyToad.PdfPig.Fonts.TrueType.Tables +namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs { using System; diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/EmptyGlyph.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/EmptyGlyph.cs new file mode 100644 index 00000000..9594745d --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/EmptyGlyph.cs @@ -0,0 +1,25 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs +{ + using Geometry; + + internal class EmptyGlyph : IGlyphDescription + { + public bool IsSimple { get; } = true; + + public SimpleGlyphDescription SimpleGlyph => new SimpleGlyphDescription(new byte[0], new int[0], new GlyphPoint[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/Glyphs/GlyphPoint.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/GlyphPoint.cs new file mode 100644 index 00000000..c393239b --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/GlyphPoint.cs @@ -0,0 +1,23 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs +{ + using Geometry; + + internal struct GlyphPoint + { + public PdfPoint Point { get; } + + public bool IsOnCurve { get; } + + public GlyphPoint(decimal x, decimal y, bool isOnCurve) : this(new PdfPoint(x, y), isOnCurve) { } + public GlyphPoint(PdfPoint point, bool isOnCurve) + { + Point = point; + IsOnCurve = isOnCurve; + } + + public override string ToString() + { + return $"{Point} | {IsOnCurve}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/IGlyphDescription.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/IGlyphDescription.cs similarity index 81% rename from src/UglyToad.PdfPig/Fonts/TrueType/Tables/IGlyphDescription.cs rename to src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/IGlyphDescription.cs index f2449935..095e27ff 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/IGlyphDescription.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/IGlyphDescription.cs @@ -1,6 +1,5 @@ -namespace UglyToad.PdfPig.Fonts.TrueType.Tables +namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs { - using System; using Geometry; internal interface IGlyphDescription diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/SimpleGlyphDescription.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/SimpleGlyphDescription.cs new file mode 100644 index 00000000..3c8417e4 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/SimpleGlyphDescription.cs @@ -0,0 +1,54 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs +{ + using System; + using Geometry; + + internal class SimpleGlyphDescription : IGlyphDescription + { + /// + /// The bounding rectangle for the character. + /// + public PdfRectangle GlyphBounds { get; } + + /// + /// The total number of bytes for instructions. + /// + public byte[] Instructions { get; } + + /// + /// An array of the last points of each contour. + /// + public int[] EndPointsOfContours { get; } + + public GlyphPoint[] Points { get; set; } + + public SimpleGlyphDescription(byte[] instructions, int[] endPointsOfContours, GlyphPoint[] points, + PdfRectangle bounds) + { + Instructions = instructions; + EndPointsOfContours = endPointsOfContours; + Points = points; + GlyphBounds = bounds; + } + + public bool IsSimple { get; } = true; + + public SimpleGlyphDescription SimpleGlyph => this; + + public CompositeGlyphDescription CompositeGlyph { get; } = null; + + public IGlyphDescription DeepClone() + { + var clonedInstructions = new byte[Instructions.Length]; + Array.Copy(Instructions, clonedInstructions, Instructions.Length); + + var clonedEndPoints = new int[EndPointsOfContours.Length]; + Array.Copy(EndPointsOfContours, clonedEndPoints, EndPointsOfContours.Length); + + var clonedPoints = new GlyphPoint[Points.Length]; + Array.Copy(Points, clonedPoints, Points.Length); + + return new SimpleGlyphDescription(clonedInstructions, clonedEndPoints, clonedPoints, GlyphBounds); + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphFlags.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/SimpleGlyphFlags.cs similarity index 95% rename from src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphFlags.cs rename to src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/SimpleGlyphFlags.cs index b7c772b9..269f4141 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphFlags.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Glyphs/SimpleGlyphFlags.cs @@ -1,4 +1,4 @@ -namespace UglyToad.PdfPig.Fonts.TrueType.Tables +namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs { using System; diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/GlyphDataTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/GlyphDataTable.cs index ebd4a80f..8cd6bf92 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/GlyphDataTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/GlyphDataTable.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using Geometry; + using Glyphs; using Parser; using Util.JetBrains.Annotations; @@ -88,7 +89,7 @@ var instructionLength = data.ReadUnsignedShort(); - data.ReadByteArray(instructionLength); + var instructions = data.ReadByteArray(instructionLength); var pointCount = 0; if (contourCount > 0) @@ -104,7 +105,14 @@ var yCoordinates = ReadCoordinates(data, pointCount, flags, SimpleGlyphFlags.YShortVector, SimpleGlyphFlags.YSignOrSame); - return new SimpleGlyphDescription(instructionLength, endPointsOfContours, flags, xCoordinates, yCoordinates, bounds); + var points = new GlyphPoint[xCoordinates.Length]; + for (var i = xCoordinates.Length - 1; i >= 0; i--) + { + var isOnCurve = (flags[i] & SimpleGlyphFlags.OnCurve) == SimpleGlyphFlags.OnCurve; + points[i] = new GlyphPoint(xCoordinates[i], yCoordinates[i], isOnCurve); + } + + return new SimpleGlyphDescription(instructions, endPointsOfContours, points, bounds); } private static CompositeGlyphDescription ReadCompositeGlyph(TrueTypeDataBytes data, TemporaryCompositeLocation compositeLocation, Dictionary compositeLocations, IGlyphDescription[] glyphs) @@ -116,6 +124,8 @@ data.Seek(compositeLocation.Position); + var components = new List(); + CompositeGlyphFlags flags; do { @@ -150,42 +160,32 @@ arg2 = data.ReadByte(); } - float xscale = 1; - float scale01 = 0; - float scale10 = 0; - float yscale = 1; + decimal xscale = 1; + decimal scale01 = 0; + decimal scale10 = 0; + decimal 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)) { - + components.Add(new CompositeComponent(glyphIndex, new PdfMatrix3By2(xscale, scale01, scale10, yscale, arg1, arg2))); } else { @@ -197,9 +197,9 @@ return new CompositeGlyphDescription(); } - private static float ReadTwoFourteenFormat(TrueTypeDataBytes data) + private static decimal ReadTwoFourteenFormat(TrueTypeDataBytes data) { - const float divisor = 1 << 14; + const decimal divisor = 1 << 14; return data.ReadSignedShort() / divisor; } @@ -281,5 +281,18 @@ } } } + + private class CompositeComponent + { + public int Index { get; } + + public PdfMatrix3By2 Transformation { get; } + + public CompositeComponent(int index, PdfMatrix3By2 transformation) + { + Index = index; + Transformation = transformation; + } + } } } diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphDescription.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphDescription.cs deleted file mode 100644 index 2a06a386..00000000 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/SimpleGlyphDescription.cs +++ /dev/null @@ -1,110 +0,0 @@ -namespace UglyToad.PdfPig.Fonts.TrueType.Tables -{ - using System; - using Geometry; - - internal class SimpleGlyphDescription : IGlyphDescription - { - /// - /// The bounding rectangle for the character. - /// - public PdfRectangle GlyphBounds { get; } - - /// - /// The total number of bytes for instructions. - /// - public int InstructionLength { get; } - - /// - /// An array of the last points of each contour. - /// - public int[] EndPointsOfContours { get; } - - /// - /// Array of flags for each coordinate in the outline. - /// - public SimpleGlyphFlags[] Flags { get; } - - /// - /// The x-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. - /// - 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. - /// - public short[] YCoordinates { get; } - - public SimpleGlyphDescription(int instructionLength, int[] endPointsOfContours, SimpleGlyphFlags[] flags, short[] xCoordinates, short[] yCoordinates, - PdfRectangle bounds) - { - InstructionLength = instructionLength; - EndPointsOfContours = endPointsOfContours; - Flags = flags; - XCoordinates = xCoordinates; - YCoordinates = yCoordinates; - GlyphBounds = bounds; - } - - public bool IsSimple { get; } = true; - - public SimpleGlyphDescription SimpleGlyph => this; - - 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/Geometry/PdfMatrix3By2.cs b/src/UglyToad.PdfPig/Geometry/PdfMatrix3By2.cs new file mode 100644 index 00000000..959aff78 --- /dev/null +++ b/src/UglyToad.PdfPig/Geometry/PdfMatrix3By2.cs @@ -0,0 +1,31 @@ +namespace UglyToad.PdfPig.Geometry +{ + internal struct PdfMatrix3By2 + { + private readonly decimal r0c0; + private readonly decimal r0c1; + private readonly decimal r1c0; + private readonly decimal r1c1; + private readonly decimal r2c0; + private readonly decimal r2c1; + + public PdfMatrix3By2(decimal r0C0, decimal r0C1, decimal r1C0, decimal r1C1, decimal r2C0, decimal r2C1) + { + r0c0 = r0C0; + r0c1 = r0C1; + r1c0 = r1C0; + r1c1 = r1C1; + r2c0 = r2C0; + r2c1 = r2C1; + } + + public static PdfMatrix3By2 Identity { get; } = new PdfMatrix3By2(1, 0, 0, 1, 0, 0); + public static PdfMatrix3By2 CreateTranslation(PdfVector vector) => new PdfMatrix3By2(1, 0, 0, 1, vector.X, vector.Y); + public static PdfMatrix3By2 CreateTranslation(decimal x, decimal y) => new PdfMatrix3By2(1, 0, 0, 1, x, y); + + public PdfMatrix3By2 WithTranslation(decimal x, decimal y) + { + return new PdfMatrix3By2(r0c0, r0c1, r1c0, r1c1, x, y); + } + } +}