diff --git a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs index dc183987..659f8809 100644 --- a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs +++ b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs @@ -1,4 +1,5 @@ -namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser +// ReSharper disable CompareOfFloatsByEqualityOperator +namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser { using System; using System.Globalization; @@ -159,7 +160,7 @@ Assert.Equal("Andada Regular", name); - Assert.Equal(1.001999m, font.TableRegister.HeaderTable.Revision); + Assert.Equal(1.001999, font.TableRegister.HeaderTable.Revision, new DoubleComparer(5)); Assert.Equal(11, font.TableRegister.HeaderTable.Flags); @@ -182,6 +183,8 @@ var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes)); var font = parser.Parse(input); + + Assert.NotNull(font); } [Fact] diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/HorizontalHeaderTableParser.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/HorizontalHeaderTableParser.cs new file mode 100644 index 00000000..7eab7a59 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/HorizontalHeaderTableParser.cs @@ -0,0 +1,55 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Parser +{ + using System; + using Tables; + + internal class HorizontalHeaderTableParser : ITrueTypeTableParser + { + public HorizontalHeaderTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register) + { + data.Seek(header.Offset); + var majorVersion = data.ReadUnsignedShort(); + var minorVersion = data.ReadUnsignedShort(); + + var ascender = data.ReadSignedShort(); + var descender = data.ReadSignedShort(); + var lineGap = data.ReadSignedShort(); + + var advancedWidthMax = data.ReadUnsignedShort(); + + var minLeftSideBearing = data.ReadSignedShort(); + var minRightSideBearing = data.ReadSignedShort(); + var xMaxExtent = data.ReadSignedShort(); + + var caretSlopeRise = data.ReadSignedShort(); + var caretSlopeRun = data.ReadSignedShort(); + var caretOffset = data.ReadSignedShort(); + + // Reserved section + data.ReadSignedShort(); + data.ReadSignedShort(); + data.ReadSignedShort(); + data.ReadSignedShort(); + + var metricDataFormat = data.ReadSignedShort(); + + if (metricDataFormat != 0) + { + throw new NotSupportedException("The metric data format for a horizontal header table should be 0."); + } + + var numberOfHeaderMetrics = data.ReadUnsignedShort(); + + return new HorizontalHeaderTable(header, majorVersion, minorVersion, ascender, + descender, lineGap, advancedWidthMax, + minLeftSideBearing, + minRightSideBearing, + xMaxExtent, + caretSlopeRise, + caretSlopeRun, + caretOffset, + metricDataFormat, + numberOfHeaderMetrics); + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/HorizontalMetricsTableParser.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/HorizontalMetricsTableParser.cs index 8e712bf0..b5e09b4c 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/HorizontalMetricsTableParser.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/HorizontalMetricsTableParser.cs @@ -12,33 +12,36 @@ data.Seek(header.Offset); var bytesRead = 0; - // The number of entries in the left side bearing field per entry is number of glyphs - number of metrics - var additionalLeftSideBearingLength = glyphCount - metricCount; - var advancedWidths = new int[metricCount]; + var horizontalMetrics = new HorizontalMetricsTable.HorizontalMetric[metricCount]; - // For bearings over the metric count, the width is the same as the last width in advanced widths. - var leftSideBearings = new short[glyphCount]; for (var i = 0; i < metricCount; i++) { - advancedWidths[i] = data.ReadUnsignedShort(); - leftSideBearings[i] = data.ReadSignedShort(); + var width = data.ReadUnsignedShort(); + var lsb = data.ReadSignedShort(); + + horizontalMetrics[i] = new HorizontalMetricsTable.HorizontalMetric(width, lsb); + bytesRead += 4; } + + // The number of entries in the left side bearing field per entry is number of glyphs - number of metrics + // For bearings over the metric count, the width is the same as the last width in advanced widths. + var additionalLeftSideBearings = new short[glyphCount - metricCount]; - for (var i = 0; i < additionalLeftSideBearingLength; i++) + for (var i = 0; i < additionalLeftSideBearings.Length; i++) { if (bytesRead >= header.Length) { break; } - leftSideBearings[metricCount + i] = data.ReadSignedShort(); + additionalLeftSideBearings[i] = data.ReadSignedShort(); bytesRead += 2; } - return new HorizontalMetricsTable(header, advancedWidths, leftSideBearings, metricCount); + return new HorizontalMetricsTable(header, horizontalMetrics, additionalLeftSideBearings); } } } diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/NameTableParser.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/NameTableParser.cs index 91cbea65..ad039228 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/NameTableParser.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/NameTableParser.cs @@ -1,63 +1,11 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Parser { - using System; using System.Text; using Names; using Tables; using Util; using Util.JetBrains.Annotations; - internal class HorizontalHeaderTableParser : ITrueTypeTableParser - { - public HorizontalHeaderTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register) - { - data.Seek(header.Offset); - var majorVersion = data.ReadUnsignedShort(); - var minorVersion = data.ReadUnsignedShort(); - - var ascender = data.ReadSignedShort(); - var descender = data.ReadSignedShort(); - var lineGap = data.ReadSignedShort(); - - var advancedWidthMax = data.ReadUnsignedShort(); - - var minLeftSideBearing = data.ReadSignedShort(); - var minRightSideBearing = data.ReadSignedShort(); - var xMaxExtent = data.ReadSignedShort(); - - var caretSlopeRise = data.ReadSignedShort(); - var caretSlopeRun = data.ReadSignedShort(); - var caretOffset = data.ReadSignedShort(); - - // Reserved section - data.ReadSignedShort(); - data.ReadSignedShort(); - data.ReadSignedShort(); - data.ReadSignedShort(); - - var metricDataFormat = data.ReadSignedShort(); - - if (metricDataFormat != 0) - { - throw new NotSupportedException("The metric data format for a horizontal header table should be 0."); - } - - var numberOfHeaderMetrics = data.ReadUnsignedShort(); - - return new HorizontalHeaderTable(header, majorVersion, minorVersion, ascender, - descender, lineGap, advancedWidthMax, - minLeftSideBearing, - minRightSideBearing, - xMaxExtent, - caretSlopeRise, - caretSlopeRun, - caretOffset, - metricDataFormat, - numberOfHeaderMetrics); - - } - } - internal class NameTableParser : ITrueTypeTableParser { public NameTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register) diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs index ca85b404..7057659f 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs @@ -10,14 +10,14 @@ { public TrueTypeFontProgram Parse(TrueTypeDataBytes data) { - var version = (decimal)data.Read32Fixed(); + var version = data.Read32Fixed(); int numberOfTables = data.ReadUnsignedShort(); // Read these data points to move to the correct data location. // ReSharper disable UnusedVariable - int searchRange = data.ReadUnsignedShort(); - int entrySelector = data.ReadUnsignedShort(); - int rangeShift = data.ReadUnsignedShort(); + var searchRange = data.ReadUnsignedShort(); + var entrySelector = data.ReadUnsignedShort(); + var rangeShift = data.ReadUnsignedShort(); // ReSharper restore UnusedVariable var tables = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -54,7 +54,7 @@ return new TrueTypeHeaderTable(tag, checksum, offset, length); } - private static TrueTypeFontProgram ParseTables(decimal version, IReadOnlyDictionary tables, TrueTypeDataBytes data) + private static TrueTypeFontProgram ParseTables(float version, IReadOnlyDictionary tables, TrueTypeDataBytes data) { var isPostScript = tables.ContainsKey(TrueTypeHeaderTable.Cff); diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HeaderTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HeaderTable.cs index 1cb19e2a..a08954a6 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HeaderTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HeaderTable.cs @@ -14,17 +14,17 @@ public TrueTypeHeaderTable DirectoryTable { get; } - public decimal Version { get; } + public float Version { get; } - public decimal Revision { get; } + public float Revision { get; } public long CheckSumAdjustment { get; } public long MagicNumber { get; } - public int Flags { get; } + public ushort Flags { get; } - public int UnitsPerEm { get; } + public ushort UnitsPerEm { get; } public DateTime Created { get; } @@ -37,7 +37,7 @@ /// /// Smallest readable size in pixels. /// - public int LowestRecommendedPpem { get; } + public ushort LowestRecommendedPpem { get; } public FontDirection FontDirectionHint { get; } @@ -51,13 +51,13 @@ /// public short GlyphDataFormat { get; } - public HeaderTable(TrueTypeHeaderTable directoryTable, decimal version, decimal revision, long checkSumAdjustment, - long magicNumber, int flags, int unitsPerEm, + public HeaderTable(TrueTypeHeaderTable directoryTable, float version, float revision, long checkSumAdjustment, + long magicNumber, ushort flags, ushort unitsPerEm, DateTime created, DateTime modified, short xMin, short yMin, short xMax, short yMax, - int macStyle, - int lowestRecommendedPpem, + ushort macStyle, + ushort lowestRecommendedPpem, short fontDirectionHint, short indexToLocFormat, short glyphDataFormat) @@ -130,7 +130,7 @@ var indexToLocFormat = data.ReadSignedShort(); var glyphDataFormat = data.ReadSignedShort(); - return new HeaderTable(table, (decimal)version, (decimal)fontRevision, checkSumAdjustment, + return new HeaderTable(table, version, fontRevision, checkSumAdjustment, magicNumber, flags, unitsPerEm, created, modified, xMin, yMin, xMax, yMax, macStyle, lowestRecPpem, fontDirectionHint, indexToLocFormat, glyphDataFormat); diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HorizontalHeaderTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HorizontalHeaderTable.cs index a797c12e..c4b03b77 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HorizontalHeaderTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HorizontalHeaderTable.cs @@ -1,7 +1,5 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables { - using System; - /// /// The 'hhea' table contains information needed to layout fonts whose characters are written horizontally, that is, either left to right or right to left. /// This table contains information that is general to the font as a whole. @@ -25,12 +23,12 @@ /// /// Distance from baseline to highest ascender. /// - public short Ascender { get; } + public short Ascent { get; } /// /// Distance from baseline to lower descender. /// - public short Descender { get; } + public short Descent { get; } /// /// The typographic line gap. @@ -40,7 +38,7 @@ /// /// The maximum advance width value as given by the Horizontal Metrics table. /// - public int AdvanceWidthMaximum { get; } + public ushort AdvanceWidthMaximum { get; } /// /// The minimum left side bearing as given by the Horizontal Metrics table. @@ -80,15 +78,20 @@ /// /// Number of horizontal metrics in the Horizontal Metrics table. /// - public int NumberOfHeaderMetrics { get; } + public ushort NumberOfHeaderMetrics { get; } - public HorizontalHeaderTable(TrueTypeHeaderTable directoryTable, int majorVersion, int minorVersion, short ascender, short descender, short lineGap, int advanceWidthMaximum, short minimumLeftSideBearing, short minimumRightSideBearing, short xMaxExtent, short caretSlopeRise, short caretSlopeRun, short caretOffset, short metricDataFormat, int numberOfHeaderMetrics) + public HorizontalHeaderTable(TrueTypeHeaderTable directoryTable, int majorVersion, int minorVersion, short ascent, short descent, + short lineGap, ushort advanceWidthMaximum, + short minimumLeftSideBearing, short minimumRightSideBearing, + short xMaxExtent, short caretSlopeRise, + short caretSlopeRun, short caretOffset, + short metricDataFormat, ushort numberOfHeaderMetrics) { DirectoryTable = directoryTable; MajorVersion = majorVersion; MinorVersion = minorVersion; - Ascender = ascender; - Descender = descender; + Ascent = ascent; + Descent = descent; LineGap = lineGap; AdvanceWidthMaximum = advanceWidthMaximum; MinimumLeftSideBearing = minimumLeftSideBearing; diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HorizontalMetricsTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HorizontalMetricsTable.cs index 9eee58c0..e3bb7b52 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HorizontalMetricsTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/HorizontalMetricsTable.cs @@ -1,42 +1,66 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables { using System.Collections.Generic; - using Parser; /// /// The 'hmtx' table contains metric information for the horizontal layout each of the glyphs in the font. /// internal class HorizontalMetricsTable : ITable { - private readonly short[] leftSideBearings; - - private readonly int metricCount; - public string Tag => TrueTypeHeaderTable.Hmtx; public TrueTypeHeaderTable DirectoryTable { get; } - public IReadOnlyList AdvancedWidths { get; } + public IReadOnlyList HorizontalMetrics { get; } - public HorizontalMetricsTable(TrueTypeHeaderTable directoryTable, int[] advancedWidths, short[] leftSideBearings, int metricCount) + /// + /// Some fonts may have an array of left side bearings following the . + /// Generally, this array of left side bearings is used for a run of monospaced glyphs. + /// For example, it might be used for a Kanji font or for Courier. + /// The corresponding glyphs are assumed to have the same advance width as that found in the last entry in the . + /// + public IReadOnlyList AdditionalLeftSideBearings { get; } + + public HorizontalMetricsTable(TrueTypeHeaderTable directoryTable, + IReadOnlyList horizontalMetrics, + IReadOnlyList additionalLeftSideBearings) { - AdvancedWidths = advancedWidths; - this.leftSideBearings = leftSideBearings; - this.metricCount = metricCount; - DirectoryTable = directoryTable; + HorizontalMetrics = horizontalMetrics; + AdditionalLeftSideBearings = additionalLeftSideBearings; } - public int GetAdvanceWidth(int index) + public ushort GetAdvanceWidth(int index) { - if (index < metricCount) + if (index < HorizontalMetrics.Count) { - return AdvancedWidths[index]; + return HorizontalMetrics[index].AdvanceWidth; } - // monospaced fonts may not have a width for every glyph - // the last one is for subsequent glyphs - return AdvancedWidths[AdvancedWidths.Count - 1]; + // Monospaced fonts may not have a width for every glyph, the last metric is for subsequent glyphs. + return HorizontalMetrics[HorizontalMetrics.Count - 1].AdvanceWidth; + } + + /// + /// The pair of horizontal metrics for an individual glyph. + /// + public struct HorizontalMetric + { + /// + /// The advance width. + /// + public ushort AdvanceWidth { get; } + + /// + /// The left side bearing. + /// + public short LeftSideBearing { get; } + + internal HorizontalMetric(ushort advanceWidth, short leftSideBearing) + { + AdvanceWidth = advanceWidth; + LeftSideBearing = leftSideBearing; + } } } } diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/PostScriptTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/PostScriptTable.cs index 53476441..8305938e 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/PostScriptTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/PostScriptTable.cs @@ -179,7 +179,7 @@ for (var i = 0; i < namesLength; i++) { - var numberOfCharacters = data.ReadUnsignedByte(); + var numberOfCharacters = data.ReadByte(); nameArray[i] = data.ReadString(numberOfCharacters, Encoding.UTF8); } } diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeDataBytes.cs b/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeDataBytes.cs index 70ae6d99..7ec719e8 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeDataBytes.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeDataBytes.cs @@ -38,15 +38,7 @@ return (ushort)((internalBuffer[0] << 8) + (internalBuffer[1] << 0)); } - - public int ReadUnsignedByte() - { - ReadBuffered(internalBuffer, 1); - - // TODO: the cast from int -> byte -> int here suggest we are treating data incorrectly. - return internalBuffer[0]; - } - + private void ReadBuffered(byte[] buffer, int length) { var numberRead = 0; diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFontProgram.cs b/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFontProgram.cs index 2d02db58..35c03bf0 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFontProgram.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFontProgram.cs @@ -10,7 +10,7 @@ internal class TrueTypeFontProgram : ICidFontProgram { - public decimal Version { get; } + public float Version { get; } public IReadOnlyDictionary TableHeaders { get; } @@ -26,7 +26,7 @@ public ICMapSubTable WindowsSymbolCMap { get; } - public TrueTypeFontProgram(decimal version, IReadOnlyDictionary tableHeaders, TableRegister tableRegister) + public TrueTypeFontProgram(float version, IReadOnlyDictionary tableHeaders, TableRegister tableRegister) { Version = version; TableHeaders = tableHeaders; diff --git a/src/UglyToad.PdfPig/Writer/TrueTypeWritingFont.cs b/src/UglyToad.PdfPig/Writer/TrueTypeWritingFont.cs index f640fb71..11a7ff9d 100644 --- a/src/UglyToad.PdfPig/Writer/TrueTypeWritingFont.cs +++ b/src/UglyToad.PdfPig/Writer/TrueTypeWritingFont.cs @@ -76,8 +76,8 @@ { NameToken.Flags, new NumericToken((int)FontDescriptorFlags.Symbolic) }, { NameToken.FontBbox, GetBoundingBox(bbox, scaling) }, { NameToken.ItalicAngle, new NumericToken(postscript.ItalicAngle) }, - { NameToken.Ascent, new NumericToken(hhead.Ascender * scaling) }, - { NameToken.Descent, new NumericToken(hhead.Descender * scaling) }, + { NameToken.Ascent, new NumericToken(hhead.Ascent * scaling) }, + { NameToken.Descent, new NumericToken(hhead.Descent * scaling) }, { NameToken.CapHeight, new NumericToken(90) }, { NameToken.StemV, new NumericToken(90) }, { NameToken.FontFile2, new IndirectReferenceToken(fileRef.Number) } @@ -181,7 +181,7 @@ if (!font.TryGetBoundingAdvancedWidth(characterCode, out var width)) { - width = font.TableRegister.HorizontalMetricsTable.AdvancedWidths[0]; + width = font.TableRegister.HorizontalMetricsTable.HorizontalMetrics[0].AdvanceWidth; } widths[pair.Key - firstCharacter] = new NumericToken((decimal)width * scaling);