diff --git a/src/UglyToad.PdfPig/Fonts/Parser/FontDictionaryAccessHelper.cs b/src/UglyToad.PdfPig/Fonts/Parser/FontDictionaryAccessHelper.cs index a9b35c94..c97e700e 100644 --- a/src/UglyToad.PdfPig/Fonts/Parser/FontDictionaryAccessHelper.cs +++ b/src/UglyToad.PdfPig/Fonts/Parser/FontDictionaryAccessHelper.cs @@ -56,7 +56,7 @@ public static FontDescriptor GetFontDescriptor(IPdfTokenScanner pdfScanner, FontDescriptorFactory fontDescriptorFactory, DictionaryToken dictionary, bool isLenientParsing) { - if (!dictionary.TryGet(NameToken.FontDesc, out var obj)) + if (!dictionary.TryGet(NameToken.FontDescriptor, out var obj)) { throw new InvalidFontFormatException($"No font descriptor indirect reference found in the TrueType font: {dictionary}."); } diff --git a/src/UglyToad.PdfPig/Fonts/Parser/Parts/CidFontFactory.cs b/src/UglyToad.PdfPig/Fonts/Parser/Parts/CidFontFactory.cs index 2b8ae138..ab7ed413 100644 --- a/src/UglyToad.PdfPig/Fonts/Parser/Parts/CidFontFactory.cs +++ b/src/UglyToad.PdfPig/Fonts/Parser/Parts/CidFontFactory.cs @@ -73,7 +73,7 @@ { descriptorDictionary = null; - if (!dictionary.TryGet(NameToken.FontDesc, out var baseValue)) + if (!dictionary.TryGet(NameToken.FontDescriptor, out var baseValue)) { return false; } diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableParser.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableParser.cs new file mode 100644 index 00000000..4e3fa1ea --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableParser.cs @@ -0,0 +1,63 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Parser +{ + using System; + using Tables; + + internal static class TableParser + { + private static readonly Os2TableParser Os2TableParser = new Os2TableParser(); + public static T Parse(TrueTypeHeaderTable table, TrueTypeDataBytes data, TableRegister.Builder register) where T : ITable + { + if (typeof(T) == typeof(Os2Table)) + { + return (T)(object)Os2TableParser.Parse(table, data, register); + } + + throw new NotImplementedException(); + } + } + + internal class Os2TableParser : ITrueTypeTableParser + { + public Os2Table Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register) + { + data.Seek(header.Offset); + + var version = data.ReadUnsignedShort(); + + var xAvgCharWidth = data.ReadSignedShort(); + var usWeightClass = data.ReadUnsignedShort(); + var usWidthClass = data.ReadUnsignedShort(); + var fsType = data.ReadSignedShort(); + var ySubscriptXSize = data.ReadSignedShort(); + var ySubscriptYSize = data.ReadSignedShort(); + var ySubscriptXOffset = data.ReadSignedShort(); + var ySubscriptYOffset = data.ReadSignedShort(); + var ySuperscriptXSize = data.ReadSignedShort(); + var ySuperscriptYSize = data.ReadSignedShort(); + var ySuperscriptXOffset = data.ReadSignedShort(); + var ySuperscriptYOffset = data.ReadSignedShort(); + var yStrikeoutSize = data.ReadSignedShort(); + var yStrikeoutPosition = data.ReadSignedShort(); + var sFamilyClass = data.ReadSignedShort(); + var panose = data.ReadByteArray(10); + var ulCharRange1 = data.ReadUnsignedInt(); + var ulCharRange2 = data.ReadUnsignedInt(); + var ulCharRange3 = data.ReadUnsignedInt(); + var ulCharRange4 = data.ReadUnsignedInt(); + var vendorId = data.ReadByteArray(4); + var fsSelection = data.ReadUnsignedShort(); + var fsFirstCharIndex = data.ReadUnsignedShort(); + var fsLastCharIndex = data.ReadUnsignedShort(); + + var bytesRead = data.Position - header.Offset; + + return null; + } + } + + internal interface ITrueTypeTableParser where T : ITable + { + T Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register); + } +} diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableRegister.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableRegister.cs index 9af03cbc..d01f1531 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableRegister.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableRegister.cs @@ -59,6 +59,8 @@ public KerningTable KerningTable { get; } + public Os2Table Os2Table { get; } + /// /// Create a new . /// @@ -80,6 +82,7 @@ PostScriptTable = builder.PostScriptTable; CMapTable = builder.CMapTable; KerningTable = builder.KerningTable; + Os2Table = builder.Os2Table; } /// @@ -112,6 +115,8 @@ public NameTable NameTable { get; set; } + public Os2Table Os2Table { get; set; } + public TableRegister Build() { return new TableRegister(this); diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs index 5eb60110..214cfbda 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs @@ -94,6 +94,11 @@ { builder.NameTable = NameTable.Load(data, nameTable); } + + if (tables.TryGetValue(TrueTypeHeaderTable.Os2, out var os2Table)) + { + builder.Os2Table = TableParser.Parse(os2Table, data, builder); + } if (!isPostScript) { diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Table.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Table.cs new file mode 100644 index 00000000..99c75cae --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Table.cs @@ -0,0 +1,9 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Tables +{ + internal class Os2Table : ITable + { + public string Tag => TrueTypeHeaderTable.Os2; + + public TrueTypeHeaderTable DirectoryTable { get; } + } +} diff --git a/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs b/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs index f68aa8ac..36f59334 100644 --- a/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs +++ b/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs @@ -214,7 +214,7 @@ public static readonly NameToken FlateDecodeAbbreviation = new NameToken("Fl"); public static readonly NameToken Font = new NameToken("Font"); public static readonly NameToken FontBbox = new NameToken("FontBBox"); - public static readonly NameToken FontDesc = new NameToken("FontDescriptor"); + public static readonly NameToken FontDescriptor = new NameToken("FontDescriptor"); public static readonly NameToken FontFamily = new NameToken("FontFamily"); public static readonly NameToken FontFile = new NameToken("FontFile"); public static readonly NameToken FontFile2 = new NameToken("FontFile2"); diff --git a/src/UglyToad.PdfPig/Writer/IWritingFont.cs b/src/UglyToad.PdfPig/Writer/IWritingFont.cs index 9b0ad8fb..9d84a140 100644 --- a/src/UglyToad.PdfPig/Writer/IWritingFont.cs +++ b/src/UglyToad.PdfPig/Writer/IWritingFont.cs @@ -1,6 +1,7 @@ namespace UglyToad.PdfPig.Writer { using System.Collections.Generic; + using System.IO; using Geometry; using Tokens; @@ -10,6 +11,6 @@ bool TryGetBoundingBox(char character, out PdfRectangle boundingBox); - IReadOnlyDictionary GetDictionary(NameToken fontKeyName); + ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context); } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs index efddcbac..512c8199 100644 --- a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs @@ -34,7 +34,7 @@ var id = Guid.NewGuid(); var i = fonts.Count; var added = new AddedFont(id, NameToken.Create($"F{i}")); - fonts[id] = new FontStored(added, new TrueTypeWritingFont(font)); + fonts[id] = new FontStored(added, new TrueTypeWritingFont(font, fontFileBytes)); return added; } @@ -99,9 +99,8 @@ public byte[] Build() { - var objectLocations = new Dictionary(); + var context = new BuilderContext(); var fontsWritten = new Dictionary(); - var number = 1; using (var memory = new MemoryStream()) { // Header @@ -110,14 +109,7 @@ // Body foreach (var font in fonts) { - var fontDictionary = font.Value.FontProgram.GetDictionary(font.Value.FontKey.Name); - - // TODO - // var descriptorRef = new IndirectReference(number++, 0); - - var dictionary = new DictionaryToken(fontDictionary); - - var fontObj = WriteObject(dictionary, memory, objectLocations, ref number); + var fontObj = font.Value.FontProgram.WriteFont(font.Value.FontKey.Name, memory, context); fontsWritten.Add(font.Key, fontObj); } @@ -131,7 +123,7 @@ var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => ((IToken)fonts[x.Key].FontKey.Name, (IToken)new IndirectReferenceToken(x.Value.Number))) .ToDictionary(x => x.Item1, x => x.Item2)); - var fontsDictionaryRef = WriteObject(fontsDictionary, memory, objectLocations, ref number); + var fontsDictionaryRef = context.WriteObject(memory, fontsDictionary); resources.Add(NameToken.Font, new IndirectReferenceToken(fontsDictionaryRef.Number)); } @@ -153,12 +145,12 @@ { var contentStream = WriteContentStream(page.Value.Operations); - var contentStreamObj = WriteObject(contentStream, memory, objectLocations, ref number); + var contentStreamObj = context.WriteObject(memory, contentStream); pageDictionary[NameToken.Contents] = new IndirectReferenceToken(contentStreamObj.Number); } - var pageRef = WriteObject(new DictionaryToken(pageDictionary), memory, objectLocations, ref number); + var pageRef = context.WriteObject(memory, new DictionaryToken(pageDictionary)); pageReferences.Add(new IndirectReferenceToken(pageRef.Number)); } @@ -170,7 +162,7 @@ { NameToken.Count, new NumericToken(1) } }); - var pagesRef = WriteObject(pagesDictionary, memory, objectLocations, ref number); + var pagesRef = context.WriteObject(memory, pagesDictionary); var catalog = new DictionaryToken(new Dictionary { @@ -178,9 +170,9 @@ { NameToken.Pages, new IndirectReferenceToken(pagesRef.Number) } }); - var catalogRef = WriteObject(catalog, memory, objectLocations, ref number); + var catalogRef = context.WriteObject(memory, catalog); - TokenWriter.WriteCrossReferenceTable(objectLocations, catalogRef, memory); + TokenWriter.WriteCrossReferenceTable(context.ObjectOffsets, catalogRef, memory); return memory.ToArray(); } @@ -218,17 +210,7 @@ new NumericToken(rectangle.TopRight.Y) }); } - - private static ObjectToken WriteObject(IToken content, Stream stream, Dictionary objectOffsets, ref int number) - { - var reference = new IndirectReference(number++, 0); - var obj = new ObjectToken(stream.Position, reference, content); - objectOffsets.Add(reference, obj.Position); - // TODO: write - TokenWriter.WriteToken(obj, stream); - return obj; - } - + private static void WriteString(string text, MemoryStream stream, bool appendBreak = true) { var bytes = OtherEncodings.StringAsLatin1Bytes(text); diff --git a/src/UglyToad.PdfPig/Writer/Standard14WritingFont.cs b/src/UglyToad.PdfPig/Writer/Standard14WritingFont.cs index 23b623ea..99670563 100644 --- a/src/UglyToad.PdfPig/Writer/Standard14WritingFont.cs +++ b/src/UglyToad.PdfPig/Writer/Standard14WritingFont.cs @@ -1,6 +1,7 @@ namespace UglyToad.PdfPig.Writer { using System.Collections.Generic; + using System.IO; using Fonts; using Fonts.Encodings; using Geometry; @@ -32,9 +33,9 @@ return true; } - public IReadOnlyDictionary GetDictionary(NameToken fontKeyName) + public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context) { - return new Dictionary + var dictionary = new Dictionary { { NameToken.Type, NameToken.Font }, { NameToken.Subtype, NameToken.Type1 }, @@ -42,6 +43,29 @@ { NameToken.Encoding, NameToken.MacRomanEncoding }, { NameToken.Name, fontKeyName } }; + + var token = new DictionaryToken(dictionary); + + var result = context.WriteObject(outputStream, token); + + return result; + } + } + + internal class BuilderContext + { + public int CurrentNumber { get; private set; } = 1; + + private Dictionary objectOffsets = new Dictionary(); + public IReadOnlyDictionary ObjectOffsets => objectOffsets; + + public ObjectToken WriteObject(Stream stream, IToken token) + { + var reference = new IndirectReference(CurrentNumber++, 0); + var obj = new ObjectToken(stream.Position, reference, token); + objectOffsets.Add(reference, obj.Position); + TokenWriter.WriteToken(obj, stream); + return obj; } } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Writer/TrueTypeWritingFont.cs b/src/UglyToad.PdfPig/Writer/TrueTypeWritingFont.cs index 4b70429d..590a34f0 100644 --- a/src/UglyToad.PdfPig/Writer/TrueTypeWritingFont.cs +++ b/src/UglyToad.PdfPig/Writer/TrueTypeWritingFont.cs @@ -1,6 +1,8 @@ namespace UglyToad.PdfPig.Writer { using System.Collections.Generic; + using System.IO; + using Fonts; using Fonts.TrueType; using Geometry; using Tokens; @@ -8,10 +10,12 @@ internal class TrueTypeWritingFont : IWritingFont { private readonly TrueTypeFontProgram font; + private readonly IReadOnlyList fontFileBytes; - public TrueTypeWritingFont(TrueTypeFontProgram font) + public TrueTypeWritingFont(TrueTypeFontProgram font, IReadOnlyList fontFileBytes) { this.font = font; + this.fontFileBytes = fontFileBytes; } public bool HasWidths { get; } = true; @@ -21,13 +25,64 @@ return font.TryGetBoundingBox(character, out boundingBox); } - public IReadOnlyDictionary GetDictionary(NameToken fontKeyName) + public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context) { - return new Dictionary + var bytes = fontFileBytes; + var embeddedFile = new StreamToken(new DictionaryToken(new Dictionary + { + { NameToken.Length, new NumericToken(bytes.Count) } + }), bytes); + + var fileRef = context.WriteObject(outputStream, embeddedFile); + + var baseFont = NameToken.Create(font.TableRegister.NameTable.FontName); + + var postscript = font.TableRegister.PostScriptTable; + var hhead = font.TableRegister.HorizontalHeaderTable; + + var bbox = font.TableRegister.HeaderTable.Bounds; + + var scaling = 1000m / font.TableRegister.HeaderTable.UnitsPerEm; + var descriptorDictionary = new Dictionary + { + { NameToken.Type, NameToken.FontDescriptor }, + { NameToken.FontName, baseFont }, + // TODO: get flags TrueTypeEmbedder.java + { NameToken.Flags, new NumericToken((int)FontFlags.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) }, + // TODO: cap, x height, stem v + { NameToken.CapHeight, new NumericToken(90) }, + { NameToken.StemV, new NumericToken(90) }, + // TODO: font file 2 + { NameToken.FontFile2, new IndirectReferenceToken(fileRef.Number) } + }; + + var dictionary = new Dictionary { { NameToken.Type, NameToken.Font }, - { NameToken.Subtype, NameToken.TrueType } + { NameToken.Subtype, NameToken.TrueType }, + { NameToken.BaseFont, baseFont }, }; + + var token = new DictionaryToken(dictionary); + + var result = context.WriteObject(outputStream, token); + + return result; + } + + private static ArrayToken GetBoundingBox(PdfRectangle boundingBox, decimal scaling) + { + return new ArrayToken(new[] + { + new NumericToken(boundingBox.Left * scaling), + new NumericToken(boundingBox.Bottom * scaling), + new NumericToken(boundingBox.Right * scaling), + new NumericToken(boundingBox.Top * scaling) + }); } } } \ No newline at end of file