diff --git a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs index 659f8809..93a528eb 100644 --- a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs +++ b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs @@ -3,7 +3,6 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser { using System; using System.Globalization; - using System.IO; using System.Text; using System.Text.RegularExpressions; using PdfPig.Fonts.TrueType; @@ -14,23 +13,12 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser public class TrueTypeFontParserTests { - private static byte[] GetFileBytes(string name) - { - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Fonts", "TrueType"); - - name = name.EndsWith(".ttf") || name.EndsWith(".txt") ? name : name + ".ttf"; - - var file = Path.Combine(path, name); - - return File.ReadAllBytes(file); - } - private readonly TrueTypeFontParser parser = new TrueTypeFontParser(); [Fact] public void ParseRegularRoboto() { - var bytes = GetFileBytes("Roboto-Regular"); + var bytes = TrueTypeTestHelper.GetFileBytes("Roboto-Regular"); var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes)); @@ -104,7 +92,7 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser "prep 158516 77 251381919" }; - var bytes = GetFileBytes("Roboto-Regular"); + var bytes = TrueTypeTestHelper.GetFileBytes("Roboto-Regular"); var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes)); @@ -136,7 +124,7 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser [Fact] public void ParseSimpleGoogleDocssGautmi() { - var bytes = GetFileBytes("google-simple-doc"); + var bytes = TrueTypeTestHelper.GetFileBytes("google-simple-doc"); var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes)); @@ -148,7 +136,7 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser [Fact] public void ParseAndadaRegular() { - var bytes = GetFileBytes("Andada-Regular"); + var bytes = TrueTypeTestHelper.GetFileBytes("Andada-Regular"); var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes)); @@ -178,7 +166,7 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser [Fact] public void ParsePMingLiU() { - var bytes = GetFileBytes("PMingLiU"); + var bytes = TrueTypeTestHelper.GetFileBytes("PMingLiU"); var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes)); @@ -192,13 +180,13 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser { var regex = new Regex(@"\?: Width (?\d+), Height: (?\d+), Points: (?\d+)"); - var bytes = GetFileBytes("Roboto-Regular"); + var bytes = TrueTypeTestHelper.GetFileBytes("Roboto-Regular"); var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes)); var font = parser.Parse(input); - var robotoGlyphs = Encoding.ASCII.GetString(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); for (var i = 0; i < lines.Length; i++) diff --git a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Tables/Os2TableTests.cs b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Tables/Os2TableTests.cs new file mode 100644 index 00000000..11c4259f --- /dev/null +++ b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Tables/Os2TableTests.cs @@ -0,0 +1,45 @@ +namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Tables +{ + using System.IO; + using System.Linq; + using PdfPig.Fonts.TrueType; + using PdfPig.Fonts.TrueType.Parser; + using PdfPig.IO; + using Xunit; + + public class Os2TableTests + { + [Theory] + [InlineData("Andada-Regular")] + [InlineData("Roboto-Regular")] + [InlineData("PMingLiU")] + public void WritesSameTableAsRead(string fontFile) + { + var fontBytes = TrueTypeTestHelper.GetFileBytes(fontFile); + + var parsed = new TrueTypeFontParser().Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(fontBytes))); + + var os2 = parsed.TableRegister.Os2Table; + var os2Header = parsed.TableHeaders.Single(x => x.Value.Tag == TrueTypeHeaderTable.Os2); + + var os2InputBytes = fontBytes.Skip((int) os2Header.Value.Offset).Take((int) os2Header.Value.Length).ToArray(); + + using (var stream = new MemoryStream()) + { + os2.Write(stream); + + var result = stream.ToArray(); + + Assert.Equal(os2InputBytes.Length, result.Length); + + for (var i = 0; i < os2InputBytes.Length; i++) + { + var expected = os2InputBytes[i]; + var actual = result[i]; + + Assert.Equal(expected, actual); + } + } + } + } +} diff --git a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/TrueTypeTestHelper.cs b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/TrueTypeTestHelper.cs new file mode 100644 index 00000000..cc13d1dc --- /dev/null +++ b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/TrueTypeTestHelper.cs @@ -0,0 +1,19 @@ +namespace UglyToad.PdfPig.Tests.Fonts.TrueType +{ + using System; + using System.IO; + + internal static class TrueTypeTestHelper + { + public static byte[] GetFileBytes(string name) + { + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Fonts", "TrueType"); + + name = name.EndsWith(".ttf") || name.EndsWith(".txt") ? name : name + ".ttf"; + + var file = Path.Combine(path, name); + + return File.ReadAllBytes(file); + } + } +} diff --git a/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs b/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs index 5e1d4802..0f67eec4 100644 --- a/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs +++ b/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs @@ -1,6 +1,5 @@ namespace UglyToad.PdfPig.Tests.Writer { - using System; using System.IO; using System.Linq; using Content; @@ -8,6 +7,7 @@ using PdfPig.Geometry; using PdfPig.Util; using PdfPig.Writer; + using Tests.Fonts.TrueType; using Xunit; public class PdfDocumentBuilderTests @@ -102,10 +102,9 @@ page.DrawRectangle(new PdfPoint(30, 200), 250, 100, 0.5m); page.DrawRectangle(new PdfPoint(30, 100), 250, 100, 0.5m); - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Fonts", "TrueType"); - var file = Path.Combine(path, "Andada-Regular.ttf"); + var file = TrueTypeTestHelper.GetFileBytes("Andada-Regular.ttf"); - var font = builder.AddTrueTypeFont(File.ReadAllBytes(file)); + var font = builder.AddTrueTypeFont(file); var letters = page.AddText("Hello World!", 12, new PdfPoint(30, 50), font); @@ -220,10 +219,9 @@ var builder = new PdfDocumentBuilder(); var page = builder.AddPage(PageSize.A4); - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Fonts", "TrueType"); - var file = Path.Combine(path, "Roboto-Regular.ttf"); + var file = TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf"); - var font = builder.AddTrueTypeFont(File.ReadAllBytes(file)); + var font = builder.AddTrueTypeFont(file); page.AddText("é (lower case, upper case É).", 9, new PdfPoint(30, page.PageSize.Height - 50), font); @@ -245,11 +243,8 @@ var builder = new PdfDocumentBuilder(); var page1 = builder.AddPage(PageSize.A4); var page2 = builder.AddPage(PageSize.A4); - - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Fonts", "TrueType"); - var file = Path.Combine(path, "Roboto-Regular.ttf"); - - var font = builder.AddTrueTypeFont(File.ReadAllBytes(file)); + + var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); var topLine = new PdfPoint(30, page1.PageSize.Height - 60); var letters = page1.AddText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor", 9, topLine, font); @@ -283,11 +278,8 @@ { var builder = new PdfDocumentBuilder(); var page = builder.AddPage(PageSize.A4); - - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Fonts", "TrueType"); - var file = Path.Combine(path, "Roboto-Regular.ttf"); - - var font = builder.AddTrueTypeFont(File.ReadAllBytes(file)); + + var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); page.AddText("Hello: řó", 9, new PdfPoint(30, page.PageSize.Height - 50), font); diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/Os2TableParser.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/Os2TableParser.cs index 5bd963bd..eb484047 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/Os2TableParser.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/Os2TableParser.cs @@ -31,12 +31,14 @@ var ulCharRange2 = (uint)data.ReadUnsignedInt(); var ulCharRange3 = (uint)data.ReadUnsignedInt(); var ulCharRange4 = (uint)data.ReadUnsignedInt(); - var vendorId = data.ReadByteArray(4); + var vendorIdBytes = data.ReadByteArray(4); var selectionFlags = data.ReadUnsignedShort(); var firstCharacterIndex = data.ReadUnsignedShort(); var lastCharacterIndex = data.ReadUnsignedShort(); var unicodeCharRange = new[] {ulCharRange1, ulCharRange2, ulCharRange3, ulCharRange4}; + var vendorId = Encoding.ASCII.GetString(vendorIdBytes); + /* * Documentation for OS/2 version 0 in Apple’s TrueType Reference Manual stops at the usLastCharIndex field * and does not include the last five fields of the table as it was defined by Microsoft. @@ -59,7 +61,7 @@ familyClass, panose, unicodeCharRange, - Encoding.Unicode.GetString(vendorId), + vendorId, selectionFlags, firstCharacterIndex, lastCharacterIndex); @@ -87,7 +89,7 @@ familyClass, panose, unicodeCharRange, - Encoding.Unicode.GetString(vendorId), + vendorId, selectionFlags, firstCharacterIndex, lastCharacterIndex, @@ -116,7 +118,7 @@ familyClass, panose, unicodeCharRange, - Encoding.Unicode.GetString(vendorId), + vendorId, selectionFlags, firstCharacterIndex, lastCharacterIndex, @@ -151,7 +153,7 @@ familyClass, panose, unicodeCharRange, - Encoding.Unicode.GetString(vendorId), + vendorId, selectionFlags, firstCharacterIndex, lastCharacterIndex, @@ -186,7 +188,7 @@ familyClass, panose, unicodeCharRange, - Encoding.Unicode.GetString(vendorId), + vendorId, selectionFlags, firstCharacterIndex, lastCharacterIndex, diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/TrimmedTableMappingCMapTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/TrimmedTableMappingCMapTable.cs index 165e56b7..5c6ecaec 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/TrimmedTableMappingCMapTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/TrimmedTableMappingCMapTable.cs @@ -2,13 +2,18 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables { using System; + using System.IO; + using IO; + using Util; - /// /// /// A format 6 CMap sub-table which uses 2 bytes to map a contiguous range of character codes to glyph indices. /// - internal class TrimmedTableMappingCMapTable : ICMapSubTable + internal class TrimmedTableMappingCMapTable : ICMapSubTable, IWriteable { + private const ushort Format = 6; + private const ushort DefaultLanguageId = 0; + private readonly int entryCount; private readonly ushort[] glyphIndices; @@ -69,5 +74,22 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables return new TrimmedTableMappingCMapTable(platformId, encodingId, firstCode, entryCount, glyphIndices); } + + public void Write(Stream stream) + { + stream.WriteUShort(Format); + + var length = (ushort)((5 * sizeof(short)) + (sizeof(short) * glyphIndices.Length)); + + stream.WriteUShort(length); + stream.WriteUShort(DefaultLanguageId); + stream.WriteUShort(FirstCharacterCode); + stream.WriteUShort(glyphIndices.Length); + + for (var j = 0; j < glyphIndices.Length; j++) + { + stream.WriteUShort(glyphIndices[j]); + } + } } } diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapTable.cs index 4a097d0d..4d708f7e 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapTable.cs @@ -1,9 +1,13 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables { + using System; using System.Collections.Generic; + using System.IO; using CMapSubTables; + using IO; + using Util; - internal class CMapTable : ITable + internal class CMapTable : ITable, IWriteable { public IReadOnlyList SubTables { get; } @@ -74,5 +78,53 @@ return false; } + + public void Write(Stream stream) + { + // Write cmap index. + stream.WriteUShort(Version); + stream.WriteUShort(SubTables.Count); + + // Write cmap encoding subtable, an index of all subtables and store the offsets to correct once written. + var subTableIndexOffsetPositions = new long[SubTables.Count]; + for (var i = 0; i < SubTables.Count; i++) + { + var subTable = SubTables[i]; + + stream.WriteUShort((ushort)subTable.PlatformId); + stream.WriteUShort(subTable.EncodingId); + + subTableIndexOffsetPositions[i] = stream.Position; + stream.WriteUInt(0); + } + + // Write the full tables and store their actual offsets. + var subTableActualPositions = new long[SubTables.Count]; + for (var i = 0; i < SubTables.Count; i++) + { + var subTable = SubTables[i]; + + if (!(subTable is IWriteable writeableSubTable)) + { + throw new InvalidOperationException($"Cannot write subtable of type: {subTable.GetType().Name}."); + } + + subTableActualPositions[i] = stream.Position; + + writeableSubTable.Write(stream); + } + + // Return to the index to fix the offset values. + var endAt = stream.Position; + + for (var i = 0; i < subTableIndexOffsetPositions.Length; i++) + { + var actual = subTableActualPositions[i]; + stream.Seek(subTableIndexOffsetPositions[i], SeekOrigin.Begin); + stream.WriteUInt(actual); + } + + stream.Seek(endAt, SeekOrigin.Begin); + } } } diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2RevisedVersion0Table.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2RevisedVersion0Table.cs index 83e1687d..3b8a484c 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2RevisedVersion0Table.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2RevisedVersion0Table.cs @@ -1,6 +1,8 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables { using System.Collections.Generic; + using System.IO; + using Util; /// /// Version 0 was defined in TrueType revision 1.5 and includes fields not in the Apple specification. @@ -68,5 +70,15 @@ WindowsAscent = windowsAscent; WindowsDescent = windowsDescent; } + + public override void Write(Stream stream) + { + base.Write(stream); + stream.WriteShort(TypographicAscender); + stream.WriteShort(TypographicDescender); + stream.WriteShort(TypographicLineGap); + stream.WriteUShort(WindowsAscent); + stream.WriteUShort(WindowsDescent); + } } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Table.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Table.cs index 10c5e955..f233f9b1 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Table.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Table.cs @@ -1,11 +1,15 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables { using System.Collections.Generic; + using System.IO; + using System.Linq; + using IO; + using Util; /// /// The most basic format of the OS/2 table, excluding the fields not included in the Apple version of the specification. /// - internal class Os2Table : ITable + internal class Os2Table : ITable, IWriteable { public string Tag => TrueTypeHeaderTable.Os2; @@ -97,7 +101,7 @@ /// The PANOSE definition of 10 bytes defines various information about the /// font enabling matching fonts based on requirements. The meaning of each /// byte in the PANOSE definition depends on the preceding bytes. The first byte - /// is the family type, Lating, Latin Hand Written, etc. + /// is the family type, Latin, Latin Hand Written, etc. /// public IReadOnlyList Panose { get; } @@ -174,5 +178,46 @@ FirstCharacterIndex = firstCharacterIndex; LastCharacterIndex = lastCharacterIndex; } + + public virtual void Write(Stream stream) + { + stream.WriteUShort(Version); + stream.WriteShort(XAverageCharacterWidth); + stream.WriteUShort(WeightClass); + stream.WriteUShort(WidthClass); + + stream.WriteShort(TypeFlags); + + stream.WriteShort(YSubscriptXSize); + stream.WriteShort(YSubscriptYSize); + stream.WriteShort(YSubscriptXOffset); + stream.WriteShort(YSubscriptYOffset); + + stream.WriteShort(YSuperscriptXSize); + stream.WriteShort(YSuperscriptYSize); + stream.WriteShort(YSuperscriptXOffset); + stream.WriteShort(YSuperscriptYOffset); + + stream.WriteShort(YStrikeoutSize); + stream.WriteShort(YStrikeoutPosition); + + stream.WriteShort(FamilyClass); + + stream.Write(Panose.ToArray(), 0, Panose.Count); + + for (var i = 0; i < UnicodeRanges.Count; i++) + { + stream.WriteUInt(UnicodeRanges[i]); + } + + for (var i = 0; i < VendorId.Length; i++) + { + stream.WriteByte((byte)VendorId[i]); + } + + stream.WriteUShort(FontSelectionFlags); + stream.WriteUShort(FirstCharacterIndex); + stream.WriteUShort(LastCharacterIndex); + } } } diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version1Table.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version1Table.cs index 755045a6..8fd9b971 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version1Table.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version1Table.cs @@ -1,6 +1,8 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables { using System.Collections.Generic; + using System.IO; + using Util; /// /// Version 1 was defined in TrueType revision 1.66. Version 1 has two additional fields beyond those in version 0. @@ -51,5 +53,12 @@ CodePage1 = codePage1; CodePage2 = codePage2; } + + public override void Write(Stream stream) + { + base.Write(stream); + stream.WriteUInt(CodePage1); + stream.WriteUInt(CodePage2); + } } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version2To4OpenTypeTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version2To4OpenTypeTable.cs index 3ddfabe7..067b213e 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version2To4OpenTypeTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version2To4OpenTypeTable.cs @@ -1,6 +1,8 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables { using System.Collections.Generic; + using System.IO; + using Util; /// /// Version 4 was defined in OpenType 1.5. Version 4 has the same fields as in version 2 and version 3. @@ -8,7 +10,6 @@ /// internal class Os2Version2To4OpenTypeTable : Os2Version1Table { - /// /// This metric specifies the distance between the baseline and the approximate height of non-ascending lowercase letters. /// @@ -88,5 +89,15 @@ BreakCharacter = breakCharacter; MaximumContext = maximumContext; } + + public override void Write(Stream stream) + { + base.Write(stream); + stream.WriteShort(XHeight); + stream.WriteShort(CapHeight); + stream.WriteUShort(DefaultCharacter); + stream.WriteUShort(BreakCharacter); + stream.WriteUShort(MaximumContext); + } } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version5OpenTypeTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version5OpenTypeTable.cs index 6fe8fb7e..dc5dfd79 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version5OpenTypeTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Version5OpenTypeTable.cs @@ -1,6 +1,8 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables { using System.Collections.Generic; + using System.IO; + using Util; /// /// Version 5 was defined in OpenType 1.7. @@ -71,5 +73,12 @@ LowerOpticalPointSize = lowerOpticalPointSize; UpperOpticalPointSize = upperOpticalPointSize; } + + public override void Write(Stream stream) + { + base.Write(stream); + stream.WriteUShort(LowerOpticalPointSize); + stream.WriteUShort(UpperOpticalPointSize); + } } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeHeaderTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeHeaderTable.cs index 15664535..e804efea 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeHeaderTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeHeaderTable.cs @@ -1,12 +1,15 @@ namespace UglyToad.PdfPig.Fonts.TrueType { using System; + using System.IO; + using IO; + using Util; using Util.JetBrains.Annotations; /// /// A table directory entry from the TrueType font file. /// - internal struct TrueTypeHeaderTable + internal struct TrueTypeHeaderTable : IWriteable { #region RequiredTableTags /// @@ -190,9 +193,29 @@ Length = length; } + /// + /// Gets an empty header table with the non-tag values set to zero. + /// + public static TrueTypeHeaderTable GetEmptyHeaderTable(string tag) + { + return new TrueTypeHeaderTable(tag, 0, 0, 0); + } + public override string ToString() { return $"{Tag} {Offset} {Length} {CheckSum}"; } + + public void Write(Stream stream) + { + for (var i = 0; i < Tag.Length; i++) + { + stream.WriteByte((byte)Tag[i]); + } + + stream.WriteUInt(CheckSum); + stream.WriteUInt(Offset); + stream.WriteUInt(Length); + } } } diff --git a/src/UglyToad.PdfPig/IO/IWriteable.cs b/src/UglyToad.PdfPig/IO/IWriteable.cs new file mode 100644 index 00000000..1066a81f --- /dev/null +++ b/src/UglyToad.PdfPig/IO/IWriteable.cs @@ -0,0 +1,15 @@ +namespace UglyToad.PdfPig.IO +{ + using System.IO; + + /// + /// Indicates that a data structure can be written to an output stream. + /// + internal interface IWriteable + { + /// + /// Write the data to the output stream. + /// + void Write(Stream stream); + } +} diff --git a/src/UglyToad.PdfPig/Util/WritingExtensions.cs b/src/UglyToad.PdfPig/Util/WritingExtensions.cs new file mode 100644 index 00000000..e276aa41 --- /dev/null +++ b/src/UglyToad.PdfPig/Util/WritingExtensions.cs @@ -0,0 +1,45 @@ +namespace UglyToad.PdfPig.Util +{ + using System.IO; + + internal static class WritingExtensions + { + public static void WriteUInt(this Stream stream, long value) => WriteUInt(stream, (uint) value); + public static void WriteUInt(this Stream stream, uint value) + { + var buffer = new[] + { + (byte) (value >> 24), + (byte) (value >> 16), + (byte) (value >> 8), + (byte) value + }; + + stream.Write(buffer, 0, 4); + } + + public static void WriteUShort(this Stream stream, int value) => WriteUShort(stream, (ushort) value); + public static void WriteUShort(this Stream stream, ushort value) + { + var buffer = new[] + { + (byte) (value >> 8), + (byte) value + }; + + stream.Write(buffer, 0, 2); + } + + public static void WriteShort(this Stream stream, ushort value) => WriteShort(stream, (short)value); + public static void WriteShort(this Stream stream, short value) + { + var buffer = new[] + { + (byte) (value >> 8), + (byte) value + }; + + stream.Write(buffer, 0, 2); + } + } +} diff --git a/src/UglyToad.PdfPig/Writer/Fonts/TrueTypeCMapReplacer.cs b/src/UglyToad.PdfPig/Writer/Fonts/TrueTypeCMapReplacer.cs index f6a38a98..26fc54c4 100644 --- a/src/UglyToad.PdfPig/Writer/Fonts/TrueTypeCMapReplacer.cs +++ b/src/UglyToad.PdfPig/Writer/Fonts/TrueTypeCMapReplacer.cs @@ -8,6 +8,9 @@ using IO; using PdfPig.Fonts.TrueType; using PdfPig.Fonts.TrueType.Parser; + using PdfPig.Fonts.TrueType.Tables; + using PdfPig.Fonts.TrueType.Tables.CMapSubTables; + using Util; internal static class TrueTypeCMapReplacer { @@ -35,7 +38,7 @@ var inputTableHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); var outputTableHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - + var fileChecksumOffset = SizeOfTag; byte[] result; @@ -138,12 +141,12 @@ $"did not match header length {inputLength} for table {inputHeader.Key}."); } - WriteUInt(stream, outputHeader.Offset); + stream.WriteUInt(outputHeader.Offset); if (isCmap) { // Also overwrite length. - WriteUInt(stream, outputHeader.Length); + stream.WriteUInt(outputHeader.Length); } } @@ -154,14 +157,14 @@ } var inputBytes = new ByteArrayInputBytes(result); - + // Overwrite checksum values per table. foreach (var inputHeader in inputTableHeaders) { var outputHeader = outputTableHeaders[inputHeader.Key]; var headerOffset = inputHeader.Value.OffsetInInput; - + var newChecksum = TrueTypeChecksumCalculator.Calculate(inputBytes, outputHeader); // Overwrite the checksum value. @@ -178,7 +181,7 @@ // Store the result in checksum adjustment. WriteUInt(result, checksumAdjustmentLocation, checksumAdjustment); - + var canParse = new TrueTypeFontParser().Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(result))); return result; @@ -197,30 +200,6 @@ + (buffer[location + 3] << 0)); } - private static void WriteUInt(Stream stream, uint value) - { - var buffer = new[] - { - (byte) (value >> 24), - (byte) (value >> 16), - (byte) (value >> 8), - (byte) value - }; - - stream.Write(buffer, 0, 4); - } - - private static void WriteUShort(Stream stream, ushort value) - { - var buffer = new[] - { - (byte) (value >> 8), - (byte) value - }; - - stream.Write(buffer, 0, 2); - } - private static void WriteUInt(byte[] array, uint offset, uint value) { array[offset] = (byte)(value >> 24); @@ -273,42 +252,23 @@ { // We generate a format 6 sub-table. const ushort cmapVersion = 0; - const ushort numberOfSubtables = 1; - const ushort platformId = 3; const ushort encodingId = 0; - const ushort format = 6; - const ushort languageId = 0; var glyphIndices = MapNewEncodingToGlyphIndexArray(font, newEncoding); - - using (var memoryStream = new MemoryStream()) + + var cmapTable = new CMapTable(cmapVersion, new TrueTypeHeaderTable(CMapTag, 0, 0, 0), new[] { - // Write cmap table header. - WriteUShort(memoryStream, cmapVersion); - WriteUShort(memoryStream, numberOfSubtables); + new TrimmedTableMappingCMapTable(TrueTypeCMapPlatform.Windows, encodingId, 0, glyphIndices.Length, glyphIndices), + new TrimmedTableMappingCMapTable(TrueTypeCMapPlatform.Macintosh, encodingId, 0, glyphIndices.Length, glyphIndices) + }); - // Write sub-table index. - WriteUShort(memoryStream, platformId); - WriteUShort(memoryStream, encodingId); - WriteUInt(memoryStream, (uint)(memoryStream.Position + SizeOfInt)); - - // Write format 6 sub-table. - WriteUShort(memoryStream, format); - var length = (ushort)((5 * SizeOfShort) + (SizeOfShort * glyphIndices.Length)); - WriteUShort(memoryStream, length); - WriteUShort(memoryStream, languageId); - WriteUShort(memoryStream, 0); - WriteUShort(memoryStream, (ushort)glyphIndices.Length); - - for (var j = 0; j < glyphIndices.Length; j++) - { - WriteUShort(memoryStream, glyphIndices[j]); - } - - return memoryStream.ToArray(); + using (var stream = new MemoryStream()) + { + cmapTable.Write(stream); + return stream.ToArray(); } } - + private static ushort[] MapNewEncodingToGlyphIndexArray(TrueTypeFontProgram font, IReadOnlyDictionary newEncoding) { var mappingTable = font.WindowsUnicodeCMap ?? font.WindowsSymbolCMap;