Add Font.Name method.
This commit is contained in:
parent
fb514f71f0
commit
995030b974
2 changed files with 144 additions and 38 deletions
|
@ -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":
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue