From 8d9960ef98bd962234cf6763804c14ddc7d87951 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Mon, 4 Dec 2017 23:00:57 +0000 Subject: [PATCH] add postscript table --- .../TrueType/Parser/TrueTypeFontParser.cs | 14 ++ .../Fonts/TrueType/Tables/PostScriptTable.cs | 212 ++++++++++++++++++ .../Fonts/TrueType/TrueTypeDataBytes.cs | 19 +- .../Fonts/TrueType/WindowsGlyphList4.cs | 76 +++++++ 4 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 src/UglyToad.Pdf/Fonts/TrueType/Tables/PostScriptTable.cs create mode 100644 src/UglyToad.Pdf/Fonts/TrueType/WindowsGlyphList4.cs 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; + } + } +}