diff --git a/src/UglyToad.Pdf/Fonts/TrueType/Parser/TrueTypeFontParser.cs b/src/UglyToad.Pdf/Fonts/TrueType/Parser/TrueTypeFontParser.cs
index 4e2ac89b..edcd512c 100644
--- a/src/UglyToad.Pdf/Fonts/TrueType/Parser/TrueTypeFontParser.cs
+++ b/src/UglyToad.Pdf/Fonts/TrueType/Parser/TrueTypeFontParser.cs
@@ -74,6 +74,20 @@
var maximumProfile = BasicMaximumProfileTable.Load(data, maxHeaderTable);
+ var postScriptTable = default(PostScriptTable);
+ if (tables.TryGetValue(TrueTypeHeaderTable.Post, out var postscriptHeaderTable))
+ {
+ postScriptTable = PostScriptTable.Load(data, table, maximumProfile);
+ }
+
+ if (!isPostScript)
+ {
+ if (!tables.TryGetValue(TrueTypeHeaderTable.Loca, out var indexToLocationHeaderTable))
+ {
+ throw new InvalidOperationException("The location to index table is required for non-PostScript fonts.");
+ }
+ }
+
return new TrueTypeFont(version, header);
}
}
diff --git a/src/UglyToad.Pdf/Fonts/TrueType/Tables/PostScriptTable.cs b/src/UglyToad.Pdf/Fonts/TrueType/Tables/PostScriptTable.cs
new file mode 100644
index 00000000..1eed6050
--- /dev/null
+++ b/src/UglyToad.Pdf/Fonts/TrueType/Tables/PostScriptTable.cs
@@ -0,0 +1,212 @@
+namespace UglyToad.Pdf.Fonts.TrueType.Tables
+{
+ using System;
+ using System.Text;
+
+ ///
+ /// This table contains information for TrueType fonts on PostScript printers.
+ /// This includes data for the FontInfo dictionary and the PostScript glyph names.
+ ///
+ internal class PostScriptTable : ITable
+ {
+ public string Tag => TrueTypeHeaderTable.Post;
+
+ public TrueTypeHeaderTable DirectoryTable { get; }
+
+ ///
+ /// Format 1 contains the 258 standard Mac TrueType font file.
+ /// Format 2 is the Microsoft font format.
+ /// Format 2.5 is a space optimised subset of the standard Mac glyph set.
+ /// Format 3 enables a special font type which provides no PostScript information.
+ ///
+ public decimal FormatType { get; }
+
+ ///
+ /// Angle in counter-clockwise degrees from vertical. 0 for upright text, negative for right-leaning text.
+ ///
+ public decimal ItalicAngle { get; }
+
+ ///
+ /// Suggested values for the underline position with negative values below the baseline.
+ ///
+ public short UnderlinePosition { get; }
+
+ ///
+ /// Suggested values for the underline thickness.
+ ///
+ public short UnderlineThickness { get; }
+
+ ///
+ /// 0 if the font is proportionally spaced, non-zero for monospace or other
+ /// non-proportional spacing.
+ ///
+ public long IsFixedPitch { get; }
+
+ ///
+ /// Minimum memory usage when the TrueType font is downloaded.
+ ///
+ public long MinimumMemoryType42 { get; }
+
+ ///
+ /// Maximum memory usage when the TrueType font is downloaded.
+ ///
+ public long MaximumMemoryType42 { get; }
+
+ ///
+ /// Minimum memory usage when the TrueType font is downloaded as a Type 1 font.
+ ///
+ public long MinimumMemoryType1 { get; }
+
+ ///
+ /// Maximum memory usage when the TrueType font is downloaded as a Type 1 font.
+ ///
+ public long MaximumMemoryType1 { get; }
+
+ public string[] GlyphNames { get; }
+
+ public PostScriptTable(TrueTypeHeaderTable directoryTable, decimal formatType, decimal italicAngle, short underlinePosition, short underlineThickness, long isFixedPitch, long minimumMemoryType42, long maximumMemoryType42, long minimumMemoryType1, long maximumMemoryType1, string[] glyphNames)
+ {
+ DirectoryTable = directoryTable;
+ FormatType = formatType;
+ ItalicAngle = italicAngle;
+ UnderlinePosition = underlinePosition;
+ UnderlineThickness = underlineThickness;
+ IsFixedPitch = isFixedPitch;
+ MinimumMemoryType42 = minimumMemoryType42;
+ MaximumMemoryType42 = maximumMemoryType42;
+ MinimumMemoryType1 = minimumMemoryType1;
+ MaximumMemoryType1 = maximumMemoryType1;
+ GlyphNames = glyphNames ?? throw new ArgumentNullException(nameof(glyphNames));
+ }
+
+ public static PostScriptTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, BasicMaximumProfileTable maximumProfileTable)
+ {
+ data.Seek(table.Offset - 1);
+ var formatType = data.Read32Fixed();
+ var italicAngle = data.Read32Fixed();
+ var underlinePosition = data.ReadSignedShort();
+ var underlineThickness = data.ReadSignedShort();
+ var isFixedPitch = data.ReadUnsignedInt();
+ var minMemType42 = data.ReadUnsignedInt();
+ var maxMemType42 = data.ReadUnsignedInt();
+ var mimMemType1 = data.ReadUnsignedInt();
+ var maxMemType1 = data.ReadUnsignedInt();
+
+ var glyphNames = GetGlyphNamesByFormat(data, maximumProfileTable, formatType);
+
+ return new PostScriptTable(table, (decimal)formatType, (decimal)italicAngle,
+ underlinePosition, underlineThickness, isFixedPitch,
+ minMemType42, maxMemType42, mimMemType1,
+ maxMemType1, glyphNames);
+ }
+
+ private static string[] GetGlyphNamesByFormat(TrueTypeDataBytes data, BasicMaximumProfileTable maximumProfileTable,
+ float formatType)
+ {
+ string[] glyphNames;
+ if (Math.Abs(formatType - 1) < float.Epsilon)
+ {
+ glyphNames = new string[WindowsGlyphList4.NumberOfMacGlyphs];
+ Array.Copy(WindowsGlyphList4.MacGlyphNames, glyphNames, WindowsGlyphList4.NumberOfMacGlyphs);
+ }
+ else if (Math.Abs(formatType - 2) < float.Epsilon)
+ {
+ glyphNames = GetFormat2GlyphNames(data);
+ }
+ else if (Math.Abs(formatType - 2.5) < float.Epsilon)
+ {
+ var glyphNameIndex = new int[maximumProfileTable?.NumberOfGlyphs ?? 0];
+
+ for (var i = 0; i < glyphNameIndex.Length; i++)
+ {
+ var offset = data.ReadSignedByte();
+ glyphNameIndex[i] = i + 1 + offset;
+ }
+
+ glyphNames = new string[glyphNameIndex.Length];
+
+ for (var i = 0; i < glyphNames.Length; i++)
+ {
+ var name = WindowsGlyphList4.MacGlyphNames[glyphNameIndex[i]];
+
+ if (name != null)
+ {
+ glyphNames[i] = name;
+ }
+ }
+ }
+ else if (Math.Abs(formatType - 3) < float.Epsilon)
+ {
+ glyphNames = new string[0];
+ }
+ else
+ {
+ throw new InvalidOperationException($"Format type {formatType} is not supported for the PostScript table.");
+ }
+
+ return glyphNames;
+ }
+
+ private static string[] GetFormat2GlyphNames(TrueTypeDataBytes data)
+ {
+ const int reservedIndexStart = 32768;
+
+ var numberOfGlyphs = data.ReadUnsignedShort();
+
+ var glyphNameIndex = new int[numberOfGlyphs];
+
+ var glyphNames = new string[numberOfGlyphs];
+
+ var maxIndex = int.MinValue;
+ for (var i = 0; i < numberOfGlyphs; i++)
+ {
+ var index = data.ReadUnsignedShort();
+
+ glyphNameIndex[i] = index;
+
+ if (index < reservedIndexStart)
+ {
+ maxIndex = Math.Max(maxIndex, index);
+ }
+ }
+
+ var nameArray = default(string[]);
+
+ if (maxIndex >= WindowsGlyphList4.NumberOfMacGlyphs)
+ {
+ var namesLength = maxIndex - WindowsGlyphList4.NumberOfMacGlyphs + 1;
+ nameArray = new string[namesLength];
+
+ for (var i = 0; i < namesLength; i++)
+ {
+ var numberOfCharacters = data.ReadUnsignedByte();
+ nameArray[i] = data.ReadString(numberOfCharacters, Encoding.UTF8);
+ }
+ }
+
+ for (int i = 0; i < numberOfGlyphs; i++)
+ {
+ var index = glyphNameIndex[i];
+ if (index < WindowsGlyphList4.NumberOfMacGlyphs)
+ {
+ glyphNames[i] = WindowsGlyphList4.MacGlyphNames[index];
+ }
+ else if (index >= WindowsGlyphList4.NumberOfMacGlyphs && index < reservedIndexStart)
+ {
+ if (nameArray == null)
+ {
+ throw new InvalidOperationException("The name array was null despite the number of glyphs exceeding the maximum Mac Glyphs.");
+ }
+
+ glyphNames[i] = nameArray[index - WindowsGlyphList4.NumberOfMacGlyphs];
+ }
+ else
+ {
+ glyphNames[i] = ".undefined";
+ }
+ }
+
+ return glyphNames;
+ }
+ }
+}
diff --git a/src/UglyToad.Pdf/Fonts/TrueType/TrueTypeDataBytes.cs b/src/UglyToad.Pdf/Fonts/TrueType/TrueTypeDataBytes.cs
index cab2ad7b..686ea03b 100644
--- a/src/UglyToad.Pdf/Fonts/TrueType/TrueTypeDataBytes.cs
+++ b/src/UglyToad.Pdf/Fonts/TrueType/TrueTypeDataBytes.cs
@@ -36,6 +36,14 @@
return (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;
@@ -69,7 +77,7 @@
public long ReadUnsignedInt()
{
ReadBuffered(internalBuffer, 4);
-
+
return (internalBuffer[0] << 24) + (internalBuffer[1] << 16) + (internalBuffer[2] << 8) + (internalBuffer[3] << 0);
}
@@ -93,7 +101,7 @@
{
// TODO: this returns the wrong value, investigate...
long secondsSince1904 = ReadLong();
-
+
var date = new DateTime(1904, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var result = date.AddSeconds(secondsSince1904);
@@ -118,5 +126,12 @@
return ret;
}
+
+ public int ReadSignedByte()
+ {
+ ReadBuffered(internalBuffer, 1);
+ var signedByte = internalBuffer[0];
+ return signedByte < 127 ? signedByte : signedByte - 256;
+ }
}
}
diff --git a/src/UglyToad.Pdf/Fonts/TrueType/WindowsGlyphList4.cs b/src/UglyToad.Pdf/Fonts/TrueType/WindowsGlyphList4.cs
new file mode 100644
index 00000000..f2bf33ed
--- /dev/null
+++ b/src/UglyToad.Pdf/Fonts/TrueType/WindowsGlyphList4.cs
@@ -0,0 +1,76 @@
+namespace UglyToad.Pdf.Fonts.TrueType
+{
+ using System.Collections.Generic;
+
+ internal static class WindowsGlyphList4
+ {
+ ///
+ /// The number of standard mac glyph names.
+ ///
+ public static readonly int NumberOfMacGlyphs = 258;
+
+ ///
+ /// The 258 standard mac glyph names used in 'post' format 1 and 2.
+ ///
+ public static readonly string[] MacGlyphNames = {
+ ".notdef",".null", "nonmarkingreturn", "space", "exclam", "quotedbl",
+ "numbersign", "dollar", "percent", "ampersand", "quotesingle",
+ "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen",
+ "period", "slash", "zero", "one", "two", "three", "four", "five",
+ "six", "seven", "eight", "nine", "colon", "semicolon", "less",
+ "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F",
+ "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S",
+ "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash",
+ "bracketright", "asciicircum", "underscore", "grave", "a", "b",
+ "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
+ "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft",
+ "bar", "braceright", "asciitilde", "Adieresis", "Aring",
+ "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute",
+ "agrave", "acircumflex", "adieresis", "atilde", "aring",
+ "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis",
+ "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute",
+ "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave",
+ "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling",
+ "section", "bullet", "paragraph", "germandbls", "registered",
+ "copyright", "trademark", "acute", "dieresis", "notequal", "AE",
+ "Oslash", "infinity", "plusminus", "lessequal", "greaterequal",
+ "yen", "mu", "partialdiff", "summation", "product", "pi",
+ "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash",
+ "questiondown", "exclamdown", "logicalnot", "radical", "florin",
+ "approxequal", "Delta", "guillemotleft", "guillemotright",
+ "ellipsis", "nonbreakingspace", "Agrave", "Atilde", "Otilde", "OE",
+ "oe", "endash", "emdash", "quotedblleft", "quotedblright",
+ "quoteleft", "quoteright", "divide", "lozenge", "ydieresis",
+ "Ydieresis", "fraction", "currency", "guilsinglleft",
+ "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered",
+ "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex",
+ "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute",
+ "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex",
+ "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi",
+ "circumflex", "tilde", "macron", "breve", "dotaccent", "ring",
+ "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash",
+ "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth",
+ "Yacute", "yacute", "Thorn", "thorn", "minus", "multiply",
+ "onesuperior", "twosuperior", "threesuperior", "onehalf",
+ "onequarter", "threequarters", "franc", "Gbreve", "gbreve",
+ "Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron",
+ "ccaron", "dcroat"
+};
+
+ ///
+ /// The indices of the standard mac glyph names.
+ ///
+ public static readonly IReadOnlyDictionary MacGlyphNamesIndices;
+
+ static WindowsGlyphList4()
+ {
+ var indices = new Dictionary(NumberOfMacGlyphs);
+ for (int i = 0; i < NumberOfMacGlyphs; ++i)
+ {
+ indices[MacGlyphNames[i]] = i;
+ }
+
+ MacGlyphNamesIndices = indices;
+ }
+ }
+}