diff --git a/truetype/truetype.go b/truetype/truetype.go index fb39d52..abca124 100644 --- a/truetype/truetype.go +++ b/truetype/truetype.go @@ -26,6 +26,41 @@ import ( // An Index is a Font's index of a rune. type Index uint16 +// A NameID represents the Name Identifiers in the name table. +type NameID uint16 + +const ( + NameIDCopyright NameID = 0 + NameIDFontFamily = 1 + NameIDFontSubfamily = 2 + NameIDUniqueSubfamilyID = 3 + NameIDFontFullName = 4 + NameIDNameTableVersion = 5 + NameIDPostscriptName = 6 + NameIDTrademarkNotice = 7 + NameIDManufacturerName = 8 + NameIDDesignerName = 9 + NameIDFontDescription = 10 + NameIDFontVendorURL = 11 + NameIDFontDesignerURL = 12 + NameIDFontLicense = 13 + NameIDFontLicenseURL = 14 + NameIDPreferredFamily = 16 + NameIDPreferredSubfamily = 17 + NameIDCompatibleName = 18 + NameIDSampleText = 19 +) + +const ( + // A 32-bit encoding consists of a most-significant 16-bit Platform ID and a + // least-significant 16-bit Platform Specific ID. The magic numbers are + // specified at https://www.microsoft.com/typography/otspec/name.htm + unicodeEncoding = 0x00000003 // PID = 0 (Unicode), PSID = 3 (Unicode 2.0) + microsoftSymbolEncoding = 0x00030000 // PID = 3 (Microsoft), PSID = 0 (Symbol) + microsoftUCS2Encoding = 0x00030001 // PID = 3 (Microsoft), PSID = 1 (UCS-2) + microsoftUCS4Encoding = 0x0003000a // PID = 3 (Microsoft), PSID = 10 (UCS-4) +) + // An HMetric holds the horizontal metrics of a single glyph. type HMetric struct { AdvanceWidth, LeftSideBearing fixed.Int26_6 @@ -78,6 +113,43 @@ func readTable(ttf []byte, offsetLength []byte) ([]byte, error) { return ttf[offset:end], nil } +// parseSubtables wraps the commonality in Name and parseCmap. +func parseSubtables(b []byte, name string, offset, size int, tableCheckPred func(b []byte) bool) (int, int, error) { + if len(b) < 4 { + return 0, 0, FormatError(name + " too short") + } + nsubtab := int(u16(b, 2)) + if len(b) < size*nsubtab+offset { + return 0, 0, FormatError(name + " too short") + } + pid, x := 0, offset + offset = 0 + for i := 0; i < nsubtab; i, x = i+1, x+size { + if tableCheckPred != nil && !tableCheckPred(b[x:]) { // When tableCheck is nil, examine the table. + continue + } + + // We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32. + // All values are big-endian. + pidPsid := u32(b, x) + // We prefer the Unicode cmap encoding. Failing to find that, we fall + // back onto the Microsoft cmap encoding. + if pidPsid == unicodeEncoding { + offset, pid = x, int(pidPsid>>16) + break + + } else if pidPsid == microsoftSymbolEncoding || + pidPsid == microsoftUCS2Encoding || + pidPsid == microsoftUCS4Encoding { + + offset, pid = x, int(pidPsid>>16) + // We don't break out of the for loop, so that Unicode can override Microsoft. + } + } + + return offset, pid, nil +} + const ( locaOffsetFormatUnknown int = iota locaOffsetFormatShort @@ -93,7 +165,7 @@ type cm struct { type Font struct { // Tables sliced from the TTF data. The different tables are documented // at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html - cmap, cvt, fpgm, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, os2, prep, vmtx []byte + cmap, cvt, fpgm, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, os2, prep, vmtx []byte cmapIndexes []byte @@ -112,45 +184,12 @@ func (f *Font) parseCmap() error { cmapFormat4 = 4 cmapFormat12 = 12 languageIndependent = 0 - - // A 32-bit encoding consists of a most-significant 16-bit Platform ID and a - // least-significant 16-bit Platform Specific ID. The magic numbers are - // specified at https://www.microsoft.com/typography/otspec/name.htm - unicodeEncoding = 0x00000003 // PID = 0 (Unicode), PSID = 3 (Unicode 2.0) - microsoftSymbolEncoding = 0x00030000 // PID = 3 (Microsoft), PSID = 0 (Symbol) - microsoftUCS2Encoding = 0x00030001 // PID = 3 (Microsoft), PSID = 1 (UCS-2) - microsoftUCS4Encoding = 0x0003000a // PID = 3 (Microsoft), PSID = 10 (UCS-4) ) - if len(f.cmap) < 4 { - return FormatError("cmap too short") - } - nsubtab := int(u16(f.cmap, 2)) - if len(f.cmap) < 8*nsubtab+4 { - return FormatError("cmap too short") - } - offset, found, x := 0, false, 4 - for i := 0; i < nsubtab; i++ { - // We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32. - // All values are big-endian. - pidPsid, o := u32(f.cmap, x), u32(f.cmap, x+4) - x += 8 - // We prefer the Unicode cmap encoding. Failing to find that, we fall - // back onto the Microsoft cmap encoding. - if pidPsid == unicodeEncoding { - offset, found = int(o), true - break - - } else if pidPsid == microsoftSymbolEncoding || - pidPsid == microsoftUCS2Encoding || - pidPsid == microsoftUCS4Encoding { - - offset, found = int(o), true - // We don't break out of the for loop, so that Unicode can override Microsoft. - } - } - if !found { - return UnsupportedError("cmap encoding") + x, _, err := parseSubtables(f.cmap, "cmap", 4, 8, nil) + offset := int(u32(f.cmap, x+4)) + if err != nil { + return err } if offset <= 0 || offset > len(f.cmap) { return FormatError("bad cmap offset") @@ -345,6 +384,48 @@ func (f *Font) Index(x rune) Index { return 0 } +// Name returns the NameID value of a Font. +// Returns "" on error or not found. +func (f *Font) Name(id NameID) string { + x, platformID, err := parseSubtables(f.name, "name", 6, 12, func(b []byte) bool { + return NameID(u16(b, 6)) == id + }) + if err != nil { + return "" + } + + offset, length := u16(f.name, x+10), u16(f.name, x+8) + + offset += u16(f.name, 4) + // Return the ASCII value of the encoded string. + // The string is encoded as UTF-16 on non-Apple platformIDs; Apple is platformID 1. + src := f.name[offset : offset+length] + var dst []byte + if platformID != 1 { // UTF-16. + if len(src)&1 != 0 { + return "" + } + dst = make([]byte, len(src)/2) + for i := range dst { + dst[i] = printable(u16(src, 2*i)) + } + } else { // ASCII. + dst = make([]byte, len(src)) + for i, c := range src { + dst[i] = printable(uint16(c)) + } + } + + return string(dst) +} + +func printable(r uint16) byte { + if 0x20 <= r && r <= 0x7f { + return byte(r) + } + return '?' +} + // unscaledHMetric returns the unscaled horizontal metrics for the glyph with // the given index. func (f *Font) unscaledHMetric(i Index) (h HMetric) { @@ -518,6 +599,8 @@ func parse(ttf []byte, offset int) (font *Font, err error) { f.loca, err = readTable(ttf, ttf[x+8:x+16]) case "maxp": f.maxp, err = readTable(ttf, ttf[x+8:x+16]) + case "name": + f.name, err = readTable(ttf, ttf[x+8:x+16]) case "OS/2": f.os2, err = readTable(ttf, ttf[x+8:x+16]) case "prep": diff --git a/truetype/truetype_test.go b/truetype/truetype_test.go index ce7f127..bd62d1d 100644 --- a/truetype/truetype_test.go +++ b/truetype/truetype_test.go @@ -211,6 +211,29 @@ func TestIndex(t *testing.T) { } } +func TestName(t *testing.T) { + testCases := map[string]string{ + "luximr": "Luxi Mono", + "luxirr": "Luxi Serif", + "luxisr": "Luxi Sans", + } + + for name, want := range testCases { + f, testdataIsOptional, err := parseTestdataFont(name) + if err != nil { + if testdataIsOptional { + t.Log(err) + } else { + t.Fatal(err) + } + continue + } + if got := f.Name(NameIDFontFamily); got != want { + t.Errorf("%s: got %q, want %q", name, got, want) + } + } +} + type scalingTestData struct { advanceWidth fixed.Int26_6 bounds fixed.Rectangle26_6