mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-14 19:05:01 +08:00
#21 start constructing font objects for truetype embedding
This commit is contained in:
@@ -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}.");
|
||||
}
|
||||
|
@@ -73,7 +73,7 @@
|
||||
{
|
||||
descriptorDictionary = null;
|
||||
|
||||
if (!dictionary.TryGet(NameToken.FontDesc, out var baseValue))
|
||||
if (!dictionary.TryGet(NameToken.FontDescriptor, out var baseValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
63
src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableParser.cs
Normal file
63
src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableParser.cs
Normal 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);
|
||||
}
|
||||
}
|
@@ -59,6 +59,8 @@
|
||||
|
||||
public KerningTable KerningTable { get; }
|
||||
|
||||
public Os2Table Os2Table { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TableRegister"/>.
|
||||
/// </summary>
|
||||
@@ -80,6 +82,7 @@
|
||||
PostScriptTable = builder.PostScriptTable;
|
||||
CMapTable = builder.CMapTable;
|
||||
KerningTable = builder.KerningTable;
|
||||
Os2Table = builder.Os2Table;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,6 +115,8 @@
|
||||
|
||||
public NameTable NameTable { get; set; }
|
||||
|
||||
public Os2Table Os2Table { get; set; }
|
||||
|
||||
public TableRegister Build()
|
||||
{
|
||||
return new TableRegister(this);
|
||||
|
@@ -94,6 +94,11 @@
|
||||
{
|
||||
builder.NameTable = NameTable.Load(data, nameTable);
|
||||
}
|
||||
|
||||
if (tables.TryGetValue(TrueTypeHeaderTable.Os2, out var os2Table))
|
||||
{
|
||||
builder.Os2Table = TableParser.Parse<Os2Table>(os2Table, data, builder);
|
||||
}
|
||||
|
||||
if (!isPostScript)
|
||||
{
|
||||
|
9
src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Table.cs
Normal file
9
src/UglyToad.PdfPig/Fonts/TrueType/Tables/Os2Table.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
|
||||
{
|
||||
internal class Os2Table : ITable
|
||||
{
|
||||
public string Tag => TrueTypeHeaderTable.Os2;
|
||||
|
||||
public TrueTypeHeaderTable DirectoryTable { get; }
|
||||
}
|
||||
}
|
@@ -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");
|
||||
|
@@ -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<IToken, IToken> GetDictionary(NameToken fontKeyName);
|
||||
ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context);
|
||||
}
|
||||
}
|
@@ -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<IndirectReference, long>();
|
||||
var context = new BuilderContext();
|
||||
var fontsWritten = new Dictionary<Guid, ObjectToken>();
|
||||
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<IToken, IToken>
|
||||
{
|
||||
@@ -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<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)
|
||||
{
|
||||
var bytes = OtherEncodings.StringAsLatin1Bytes(text);
|
||||
|
@@ -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<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.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<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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<byte> fontFileBytes;
|
||||
|
||||
public TrueTypeWritingFont(TrueTypeFontProgram font)
|
||||
public TrueTypeWritingFont(TrueTypeFontProgram font, IReadOnlyList<byte> fontFileBytes)
|
||||
{
|
||||
this.font = font;
|
||||
this.fontFileBytes = fontFileBytes;
|
||||
}
|
||||
|
||||
public bool HasWidths { get; } = true;
|
||||
@@ -21,13 +25,64 @@
|
||||
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.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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user