Add Font.Name method.

This commit is contained in:
Stephen Edwards 2015-09-21 16:46:35 +10:00 committed by Nigel Tao
parent fb514f71f0
commit 995030b974
2 changed files with 144 additions and 38 deletions

View file

@ -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":

View file

@ -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