font/sfnt: implement Font.GlyphName.

This is based on the post table in the sfnt file.

Change-Id: I11f7a9bd9024cfc8f92adc5abb4d5356521f0df7
Reviewed-on: https://go-review.googlesource.com/36972
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/data.go b/font/sfnt/data.go
new file mode 100644
index 0000000..ad0c139
--- /dev/null
+++ b/font/sfnt/data.go
@@ -0,0 +1,68 @@
+// generated by go run gen.go; DO NOT EDIT
+
+package sfnt
+
+const numBuiltInPostNames = 258
+
+const builtInPostNamesData = "" +
+	".notdef.nullnonmarkingreturnspaceexclamquotedblnumbersigndollarp" +
+	"ercentampersandquotesingleparenleftparenrightasteriskpluscommahy" +
+	"phenperiodslashzeroonetwothreefourfivesixseveneightninecolonsemi" +
+	"colonlessequalgreaterquestionatABCDEFGHIJKLMNOPQRSTUVWXYZbracket" +
+	"leftbackslashbracketrightasciicircumunderscoregraveabcdefghijklm" +
+	"nopqrstuvwxyzbraceleftbarbracerightasciitildeAdieresisAringCcedi" +
+	"llaEacuteNtildeOdieresisUdieresisaacuteagraveacircumflexadieresi" +
+	"satildearingccedillaeacuteegraveecircumflexedieresisiacuteigrave" +
+	"icircumflexidieresisntildeoacuteograveocircumflexodieresisotilde" +
+	"uacuteugraveucircumflexudieresisdaggerdegreecentsterlingsectionb" +
+	"ulletparagraphgermandblsregisteredcopyrighttrademarkacutedieresi" +
+	"snotequalAEOslashinfinityplusminuslessequalgreaterequalyenmupart" +
+	"ialdiffsummationproductpiintegralordfeminineordmasculineOmegaaeo" +
+	"slashquestiondownexclamdownlogicalnotradicalflorinapproxequalDel" +
+	"taguillemotleftguillemotrightellipsisnonbreakingspaceAgraveAtild" +
+	"eOtildeOEoeendashemdashquotedblleftquotedblrightquoteleftquoteri" +
+	"ghtdividelozengeydieresisYdieresisfractioncurrencyguilsinglleftg" +
+	"uilsinglrightfifldaggerdblperiodcenteredquotesinglbasequotedblba" +
+	"seperthousandAcircumflexEcircumflexAacuteEdieresisEgraveIacuteIc" +
+	"ircumflexIdieresisIgraveOacuteOcircumflexappleOgraveUacuteUcircu" +
+	"mflexUgravedotlessicircumflextildemacronbrevedotaccentringcedill" +
+	"ahungarumlautogonekcaronLslashlslashScaronscaronZcaronzcaronbrok" +
+	"enbarEthethYacuteyacuteThornthornminusmultiplyonesuperiortwosupe" +
+	"riorthreesuperioronehalfonequarterthreequartersfrancGbrevegbreve" +
+	"IdotaccentScedillascedillaCacutecacuteCcaronccarondcroat"
+
+var builtInPostNamesOffsets = [...]uint16{
+	0x0000, 0x0007, 0x000c, 0x001c, 0x0021, 0x0027, 0x002f, 0x0039,
+	0x003f, 0x0046, 0x004f, 0x005a, 0x0063, 0x006d, 0x0075, 0x0079,
+	0x007e, 0x0084, 0x008a, 0x008f, 0x0093, 0x0096, 0x0099, 0x009e,
+	0x00a2, 0x00a6, 0x00a9, 0x00ae, 0x00b3, 0x00b7, 0x00bc, 0x00c5,
+	0x00c9, 0x00ce, 0x00d5, 0x00dd, 0x00df, 0x00e0, 0x00e1, 0x00e2,
+	0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea,
+	0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00f2,
+	0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x0104,
+	0x010d, 0x0119, 0x0124, 0x012e, 0x0133, 0x0134, 0x0135, 0x0136,
+	0x0137, 0x0138, 0x0139, 0x013a, 0x013b, 0x013c, 0x013d, 0x013e,
+	0x013f, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146,
+	0x0147, 0x0148, 0x0149, 0x014a, 0x014b, 0x014c, 0x014d, 0x0156,
+	0x0159, 0x0163, 0x016d, 0x0176, 0x017b, 0x0183, 0x0189, 0x018f,
+	0x0198, 0x01a1, 0x01a7, 0x01ad, 0x01b8, 0x01c1, 0x01c7, 0x01cc,
+	0x01d4, 0x01da, 0x01e0, 0x01eb, 0x01f4, 0x01fa, 0x0200, 0x020b,
+	0x0214, 0x021a, 0x0220, 0x0226, 0x0231, 0x023a, 0x0240, 0x0246,
+	0x024c, 0x0257, 0x0260, 0x0266, 0x026c, 0x0270, 0x0278, 0x027f,
+	0x0285, 0x028e, 0x0298, 0x02a2, 0x02ab, 0x02b4, 0x02b9, 0x02c1,
+	0x02c9, 0x02cb, 0x02d1, 0x02d9, 0x02e2, 0x02eb, 0x02f7, 0x02fa,
+	0x02fc, 0x0307, 0x0310, 0x0317, 0x0319, 0x0321, 0x032c, 0x0338,
+	0x033d, 0x033f, 0x0345, 0x0351, 0x035b, 0x0365, 0x036c, 0x0372,
+	0x037d, 0x0382, 0x038f, 0x039d, 0x03a5, 0x03b5, 0x03bb, 0x03c1,
+	0x03c7, 0x03c9, 0x03cb, 0x03d1, 0x03d7, 0x03e3, 0x03f0, 0x03f9,
+	0x0403, 0x0409, 0x0410, 0x0419, 0x0422, 0x042a, 0x0432, 0x043f,
+	0x044d, 0x044f, 0x0451, 0x045a, 0x0468, 0x0476, 0x0482, 0x048d,
+	0x0498, 0x04a3, 0x04a9, 0x04b2, 0x04b8, 0x04be, 0x04c9, 0x04d2,
+	0x04d8, 0x04de, 0x04e9, 0x04ee, 0x04f4, 0x04fa, 0x0505, 0x050b,
+	0x0513, 0x051d, 0x0522, 0x0528, 0x052d, 0x0536, 0x053a, 0x0541,
+	0x054d, 0x0553, 0x0558, 0x055e, 0x0564, 0x056a, 0x0570, 0x0576,
+	0x057c, 0x0585, 0x0588, 0x058b, 0x0591, 0x0597, 0x059c, 0x05a1,
+	0x05a6, 0x05ae, 0x05b9, 0x05c4, 0x05d1, 0x05d8, 0x05e2, 0x05ef,
+	0x05f4, 0x05fa, 0x0600, 0x060a, 0x0612, 0x061a, 0x0620, 0x0626,
+	0x062c, 0x0632, 0x0638,
+}
diff --git a/font/sfnt/gen.go b/font/sfnt/gen.go
new file mode 100644
index 0000000..12587d4
--- /dev/null
+++ b/font/sfnt/gen.go
@@ -0,0 +1,321 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"go/format"
+	"io/ioutil"
+	"log"
+)
+
+func main() {
+	data, offsets := []byte(nil), []int{0}
+	for _, name := range names {
+		data = append(data, name...)
+		offsets = append(offsets, len(data))
+	}
+
+	b := new(bytes.Buffer)
+	fmt.Fprintf(b, "// generated by go run gen.go; DO NOT EDIT\n\n")
+	fmt.Fprintf(b, "package sfnt\n\n")
+
+	fmt.Fprintf(b, "const numBuiltInPostNames = %d\n\n", len(names))
+
+	fmt.Fprintf(b, "const builtInPostNamesData = \"\" +\n")
+	for s := data; ; {
+		if len(s) <= 64 {
+			fmt.Fprintf(b, "%q\n", s)
+			break
+		}
+		fmt.Fprintf(b, "%q +\n", s[:64])
+		s = s[64:]
+	}
+	fmt.Fprintf(b, "\n")
+
+	fmt.Fprintf(b, "var builtInPostNamesOffsets = [...]uint16{\n")
+	for i, o := range offsets {
+		fmt.Fprintf(b, "%#04x,", o)
+		if i%8 == 7 {
+			fmt.Fprintf(b, "\n")
+		}
+	}
+	fmt.Fprintf(b, "\n}\n")
+
+	dstUnformatted := b.Bytes()
+	dst, err := format.Source(dstUnformatted)
+	if err != nil {
+		log.Fatalf("format.Source: %v\n\n----\n%s\n----", err, dstUnformatted)
+	}
+	if err := ioutil.WriteFile("data.go", dst, 0666); err != nil {
+		log.Fatalf("ioutil.WriteFile: %v", err)
+	}
+}
+
+// names is the built-in post table names listed at
+// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
+var names = [258]string{
+	".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",
+}
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index b7f4bdc..995b287 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:generate go run gen.go
+
 // Package sfnt implements a decoder for SFNT font file formats, including
 // TrueType and OpenType.
 package sfnt // import "golang.org/x/image/font/sfnt"
@@ -65,6 +67,7 @@
 	errInvalidLocationData  = errors.New("sfnt: invalid location data")
 	errInvalidMaxpTable     = errors.New("sfnt: invalid maxp table")
 	errInvalidNameTable     = errors.New("sfnt: invalid name table")
+	errInvalidPostTable     = errors.New("sfnt: invalid post table")
 	errInvalidSourceData    = errors.New("sfnt: invalid source data")
 	errInvalidTableOffset   = errors.New("sfnt: invalid table offset")
 	errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
@@ -80,6 +83,7 @@
 	errUnsupportedNumberOfHints        = errors.New("sfnt: unsupported number of hints")
 	errUnsupportedNumberOfTables       = errors.New("sfnt: unsupported number of tables")
 	errUnsupportedPlatformEncoding     = errors.New("sfnt: unsupported platform encoding")
+	errUnsupportedPostTable            = errors.New("sfnt: unsupported post table")
 	errUnsupportedTableOffsetLength    = errors.New("sfnt: unsupported table offset or length")
 	errUnsupportedType2Charstring      = errors.New("sfnt: unsupported Type 2 Charstring")
 )
@@ -217,6 +221,20 @@
 	return u16(buf), nil
 }
 
+// u32 returns the uint32 in the table t at the relative offset i.
+//
+// buf is an optional scratch buffer as per the source.view method.
+func (s *source) u32(buf []byte, t table, i int) (uint32, error) {
+	if i < 0 || uint(t.length) < uint(i+4) {
+		return 0, errInvalidBounds
+	}
+	buf, err := s.view(buf, int(t.offset)+i, 4)
+	if err != nil {
+		return 0, err
+	}
+	return u32(buf), nil
+}
+
 // table is a section of the font data.
 type table struct {
 	offset, length uint32
@@ -298,6 +316,7 @@
 		glyphIndex       func(f *Font, b *Buffer, r rune) (GlyphIndex, error)
 		indexToLocFormat bool // false means short, true means long.
 		isPostScript     bool
+		postTableVersion uint32
 		unitsPerEm       Units
 
 		// The glyph data for the glyph index i is in
@@ -320,6 +339,13 @@
 	if err != nil {
 		return err
 	}
+
+	// The order of these parseXxx calls matters. Later calls may depend on
+	// f.cached state set up by earlier calls, such as the number of glyphs in
+	// the font being parsed by parseMaxp.
+
+	// TODO: make state dependencies explicit instead of implicit.
+
 	buf, err = f.parseHead(buf)
 	if err != nil {
 		return err
@@ -332,6 +358,10 @@
 	if err != nil {
 		return err
 	}
+	buf, err = f.parsePost(buf)
+	if err != nil {
+		return err
+	}
 	return nil
 }
 
@@ -538,6 +568,31 @@
 	return buf, nil
 }
 
+func (f *Font) parsePost(buf []byte) ([]byte, error) {
+	// https://www.microsoft.com/typography/otspec/post.htm
+
+	const headerSize = 32
+	if f.post.length < headerSize {
+		return nil, errInvalidPostTable
+	}
+	u, err := f.src.u32(buf, f.post, 0)
+	if err != nil {
+		return nil, err
+	}
+	switch u {
+	case 0x20000:
+		if f.post.length < headerSize+2+2*uint32(f.NumGlyphs()) {
+			return nil, errInvalidPostTable
+		}
+	case 0x30000:
+		// No-op.
+	default:
+		return nil, errUnsupportedPostTable
+	}
+	f.cached.postTableVersion = u
+	return buf, nil
+}
+
 // TODO: API for looking up glyph variants?? For example, some fonts may
 // provide both slashed and dotted zero glyphs ('0'), or regular and 'old
 // style' numerals, and users can direct software to choose a variant.
@@ -606,6 +661,78 @@
 	return b.segments, nil
 }
 
+// GlyphName returns the name of the x'th glyph.
+//
+// Not every font contains glyph names. If not present, GlyphName will return
+// ("", nil).
+//
+// If present, the glyph name, provided by the font, is assumed to follow the
+// Adobe Glyph List Specification:
+// https://github.com/adobe-type-tools/agl-specification/blob/master/README.md
+//
+// This is also known as the "Adobe Glyph Naming convention", the "Adobe
+// document [for] Unicode and Glyph Names" or "PostScript glyph names".
+//
+// It returns ErrNotFound if the glyph index is out of range.
+func (f *Font) GlyphName(b *Buffer, x GlyphIndex) (string, error) {
+	if int(x) >= f.NumGlyphs() {
+		return "", ErrNotFound
+	}
+	if f.cached.postTableVersion != 0x20000 {
+		return "", nil
+	}
+	if b == nil {
+		b = &Buffer{}
+	}
+
+	// The wire format for a Version 2 post table is documented at:
+	// https://www.microsoft.com/typography/otspec/post.htm
+	const glyphNameIndexOffset = 34
+
+	buf, err := b.view(&f.src, int(f.post.offset)+glyphNameIndexOffset+2*int(x), 2)
+	if err != nil {
+		return "", err
+	}
+	u := u16(buf)
+	if u < numBuiltInPostNames {
+		i := builtInPostNamesOffsets[u+0]
+		j := builtInPostNamesOffsets[u+1]
+		return builtInPostNamesData[i:j], nil
+	}
+	// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
+	// says that "32768 through 65535 are reserved for future use".
+	if u > 32767 {
+		return "", errUnsupportedPostTable
+	}
+	u -= numBuiltInPostNames
+
+	// Iterate through the list of Pascal-formatted strings. A linear scan is
+	// clearly O(u), which isn't great (as the obvious loop, calling
+	// Font.GlyphName, to get all of the glyph names in a font has quadratic
+	// complexity), but the wire format doesn't suggest a better alternative.
+
+	offset := glyphNameIndexOffset + 2*f.NumGlyphs()
+	buf, err = b.view(&f.src, int(f.post.offset)+offset, int(f.post.length)-offset)
+	if err != nil {
+		return "", err
+	}
+
+	for {
+		if len(buf) == 0 {
+			return "", errInvalidPostTable
+		}
+		n := 1 + int(buf[0])
+		if len(buf) < n {
+			return "", errInvalidPostTable
+		}
+		if u == 0 {
+			return string(buf[1:n]), nil
+		}
+		buf = buf[n:]
+		u--
+	}
+}
+
 // Name returns the name value keyed by the given NameID.
 //
 // It returns ErrNotFound if there is no value for that key.
diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go
index c6d6741..ca30321 100644
--- a/font/sfnt/sfnt_test.go
+++ b/font/sfnt/sfnt_test.go
@@ -431,3 +431,77 @@
 		t.Errorf("Name:\ngot  %q\nwant %q", name, want)
 	}
 }
+
+func TestGlyphName(t *testing.T) {
+	f, err := Parse(goregular.TTF)
+	if err != nil {
+		t.Fatalf("Parse: %v", err)
+	}
+
+	testCases := []struct {
+		r    rune
+		want string
+	}{
+		{'\x00', "NULL"},
+		{'!', "exclam"},
+		{'A', "A"},
+		{'{', "braceleft"},
+		{'\u00c4', "Adieresis"}, // U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS
+		{'\u2020', "dagger"},    // U+2020 DAGGER
+		{'\u2660', "spade"},     // U+2660 BLACK SPADE SUIT
+		{'\uf800', "gopher"},    // U+F800 <Private Use>
+		{'\ufffe', ".notdef"},   // Not in the Go Regular font, so GlyphIndex returns (0, nil).
+	}
+
+	var b Buffer
+	for _, tc := range testCases {
+		x, err := f.GlyphIndex(&b, tc.r)
+		if err != nil {
+			t.Errorf("r=%q: GlyphIndex: %v", tc.r, err)
+			continue
+		}
+		got, err := f.GlyphName(&b, x)
+		if err != nil {
+			t.Errorf("r=%q: GlyphName: %v", tc.r, err)
+			continue
+		}
+		if got != tc.want {
+			t.Errorf("r=%q: got %q, want %q", tc.r, got, tc.want)
+			continue
+		}
+	}
+}
+
+func TestBuiltInPostNames(t *testing.T) {
+	testCases := []struct {
+		x    GlyphIndex
+		want string
+	}{
+		{0, ".notdef"},
+		{1, ".null"},
+		{2, "nonmarkingreturn"},
+		{13, "asterisk"},
+		{36, "A"},
+		{93, "z"},
+		{123, "ocircumflex"},
+		{202, "Edieresis"},
+		{255, "Ccaron"},
+		{256, "ccaron"},
+		{257, "dcroat"},
+		{258, ""},
+		{999, ""},
+		{0xffff, ""},
+	}
+
+	for _, tc := range testCases {
+		if tc.x >= numBuiltInPostNames {
+			continue
+		}
+		i := builtInPostNamesOffsets[tc.x+0]
+		j := builtInPostNamesOffsets[tc.x+1]
+		got := builtInPostNamesData[i:j]
+		if got != tc.want {
+			t.Errorf("x=%d: got %q, want %q", tc.x, got, tc.want)
+		}
+	}
+}