#21 start constructing font objects for truetype embedding

This commit is contained in:
Eliot Jones
2018-12-04 20:14:24 +00:00
parent d6a896dcb0
commit 77311aa6c6
11 changed files with 182 additions and 38 deletions

View File

@@ -56,7 +56,7 @@
public static FontDescriptor GetFontDescriptor(IPdfTokenScanner pdfScanner, FontDescriptorFactory fontDescriptorFactory, DictionaryToken dictionary, public static FontDescriptor GetFontDescriptor(IPdfTokenScanner pdfScanner, FontDescriptorFactory fontDescriptorFactory, DictionaryToken dictionary,
bool isLenientParsing) 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}."); throw new InvalidFontFormatException($"No font descriptor indirect reference found in the TrueType font: {dictionary}.");
} }

View File

@@ -73,7 +73,7 @@
{ {
descriptorDictionary = null; descriptorDictionary = null;
if (!dictionary.TryGet(NameToken.FontDesc, out var baseValue)) if (!dictionary.TryGet(NameToken.FontDescriptor, out var baseValue))
{ {
return false; return false;
} }

View File

@@ -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<T>(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<Os2Table>
{
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<out T> where T : ITable
{
T Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register);
}
}

View File

@@ -59,6 +59,8 @@
public KerningTable KerningTable { get; } public KerningTable KerningTable { get; }
public Os2Table Os2Table { get; }
/// <summary> /// <summary>
/// Create a new <see cref="TableRegister"/>. /// Create a new <see cref="TableRegister"/>.
/// </summary> /// </summary>
@@ -80,6 +82,7 @@
PostScriptTable = builder.PostScriptTable; PostScriptTable = builder.PostScriptTable;
CMapTable = builder.CMapTable; CMapTable = builder.CMapTable;
KerningTable = builder.KerningTable; KerningTable = builder.KerningTable;
Os2Table = builder.Os2Table;
} }
/// <summary> /// <summary>
@@ -112,6 +115,8 @@
public NameTable NameTable { get; set; } public NameTable NameTable { get; set; }
public Os2Table Os2Table { get; set; }
public TableRegister Build() public TableRegister Build()
{ {
return new TableRegister(this); return new TableRegister(this);

View File

@@ -95,6 +95,11 @@
builder.NameTable = NameTable.Load(data, nameTable); builder.NameTable = NameTable.Load(data, nameTable);
} }
if (tables.TryGetValue(TrueTypeHeaderTable.Os2, out var os2Table))
{
builder.Os2Table = TableParser.Parse<Os2Table>(os2Table, data, builder);
}
if (!isPostScript) if (!isPostScript)
{ {
if (!tables.TryGetValue(TrueTypeHeaderTable.Loca, out var indexToLocationHeaderTable)) if (!tables.TryGetValue(TrueTypeHeaderTable.Loca, out var indexToLocationHeaderTable))

View File

@@ -0,0 +1,9 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
internal class Os2Table : ITable
{
public string Tag => TrueTypeHeaderTable.Os2;
public TrueTypeHeaderTable DirectoryTable { get; }
}
}

View File

@@ -214,7 +214,7 @@
public static readonly NameToken FlateDecodeAbbreviation = new NameToken("Fl"); public static readonly NameToken FlateDecodeAbbreviation = new NameToken("Fl");
public static readonly NameToken Font = new NameToken("Font"); public static readonly NameToken Font = new NameToken("Font");
public static readonly NameToken FontBbox = new NameToken("FontBBox"); 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 FontFamily = new NameToken("FontFamily");
public static readonly NameToken FontFile = new NameToken("FontFile"); public static readonly NameToken FontFile = new NameToken("FontFile");
public static readonly NameToken FontFile2 = new NameToken("FontFile2"); public static readonly NameToken FontFile2 = new NameToken("FontFile2");

View File

@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Writer namespace UglyToad.PdfPig.Writer
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Geometry; using Geometry;
using Tokens; using Tokens;
@@ -10,6 +11,6 @@
bool TryGetBoundingBox(char character, out PdfRectangle boundingBox); bool TryGetBoundingBox(char character, out PdfRectangle boundingBox);
IReadOnlyDictionary<IToken, IToken> GetDictionary(NameToken fontKeyName); ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context);
} }
} }

View File

@@ -34,7 +34,7 @@
var id = Guid.NewGuid(); var id = Guid.NewGuid();
var i = fonts.Count; var i = fonts.Count;
var added = new AddedFont(id, NameToken.Create($"F{i}")); 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; return added;
} }
@@ -99,9 +99,8 @@
public byte[] Build() public byte[] Build()
{ {
var objectLocations = new Dictionary<IndirectReference, long>(); var context = new BuilderContext();
var fontsWritten = new Dictionary<Guid, ObjectToken>(); var fontsWritten = new Dictionary<Guid, ObjectToken>();
var number = 1;
using (var memory = new MemoryStream()) using (var memory = new MemoryStream())
{ {
// Header // Header
@@ -110,14 +109,7 @@
// Body // Body
foreach (var font in fonts) foreach (var font in fonts)
{ {
var fontDictionary = font.Value.FontProgram.GetDictionary(font.Value.FontKey.Name); var fontObj = font.Value.FontProgram.WriteFont(font.Value.FontKey.Name, memory, context);
// TODO
// var descriptorRef = new IndirectReference(number++, 0);
var dictionary = new DictionaryToken(fontDictionary);
var fontObj = WriteObject(dictionary, memory, objectLocations, ref number);
fontsWritten.Add(font.Key, fontObj); 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))) 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)); .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)); resources.Add(NameToken.Font, new IndirectReferenceToken(fontsDictionaryRef.Number));
} }
@@ -153,12 +145,12 @@
{ {
var contentStream = WriteContentStream(page.Value.Operations); 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); 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)); pageReferences.Add(new IndirectReferenceToken(pageRef.Number));
} }
@@ -170,7 +162,7 @@
{ NameToken.Count, new NumericToken(1) } { 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<IToken, IToken> var catalog = new DictionaryToken(new Dictionary<IToken, IToken>
{ {
@@ -178,9 +170,9 @@
{ NameToken.Pages, new IndirectReferenceToken(pagesRef.Number) } { 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(); return memory.ToArray();
} }
@@ -219,16 +211,6 @@
}); });
} }
private static ObjectToken WriteObject(IToken content, Stream stream, Dictionary<IndirectReference, long> 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) private static void WriteString(string text, MemoryStream stream, bool appendBreak = true)
{ {
var bytes = OtherEncodings.StringAsLatin1Bytes(text); var bytes = OtherEncodings.StringAsLatin1Bytes(text);

View File

@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Writer namespace UglyToad.PdfPig.Writer
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Fonts; using Fonts;
using Fonts.Encodings; using Fonts.Encodings;
using Geometry; using Geometry;
@@ -32,9 +33,9 @@
return true; return true;
} }
public IReadOnlyDictionary<IToken, IToken> GetDictionary(NameToken fontKeyName) public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context)
{ {
return new Dictionary<IToken, IToken> var dictionary = new Dictionary<IToken, IToken>
{ {
{ NameToken.Type, NameToken.Font }, { NameToken.Type, NameToken.Font },
{ NameToken.Subtype, NameToken.Type1 }, { NameToken.Subtype, NameToken.Type1 },
@@ -42,6 +43,29 @@
{ NameToken.Encoding, NameToken.MacRomanEncoding }, { NameToken.Encoding, NameToken.MacRomanEncoding },
{ NameToken.Name, fontKeyName } { 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<IndirectReference, long> objectOffsets = new Dictionary<IndirectReference, long>();
public IReadOnlyDictionary<IndirectReference, long> 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;
} }
} }
} }

View File

@@ -1,6 +1,8 @@
namespace UglyToad.PdfPig.Writer namespace UglyToad.PdfPig.Writer
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Fonts;
using Fonts.TrueType; using Fonts.TrueType;
using Geometry; using Geometry;
using Tokens; using Tokens;
@@ -8,10 +10,12 @@
internal class TrueTypeWritingFont : IWritingFont internal class TrueTypeWritingFont : IWritingFont
{ {
private readonly TrueTypeFontProgram font; private readonly TrueTypeFontProgram font;
private readonly IReadOnlyList<byte> fontFileBytes;
public TrueTypeWritingFont(TrueTypeFontProgram font) public TrueTypeWritingFont(TrueTypeFontProgram font, IReadOnlyList<byte> fontFileBytes)
{ {
this.font = font; this.font = font;
this.fontFileBytes = fontFileBytes;
} }
public bool HasWidths { get; } = true; public bool HasWidths { get; } = true;
@@ -21,13 +25,64 @@
return font.TryGetBoundingBox(character, out boundingBox); return font.TryGetBoundingBox(character, out boundingBox);
} }
public IReadOnlyDictionary<IToken, IToken> GetDictionary(NameToken fontKeyName) public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context)
{ {
return new Dictionary<IToken, IToken> var bytes = fontFileBytes;
var embeddedFile = new StreamToken(new DictionaryToken(new Dictionary<IToken, IToken>
{
{ 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, IToken>
{
{ 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<IToken, IToken>
{ {
{ NameToken.Type, NameToken.Font }, { 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)
});
} }
} }
} }