font/sfnt: add ErrColoredGlyph.

Also add tests for the Noto proprietary fonts. Prior to this commit,
NotoColorEmoji.ttf was unsupported. It's still not well supported, but
the error message returned is now more informative.

Change-Id: I61a3301d7f2458a4b838eb1de7a73d6472e3486f
Reviewed-on: https://go-review.googlesource.com/43694
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go
index dcafdd4..d98961b 100644
--- a/font/sfnt/proprietary_test.go
+++ b/font/sfnt/proprietary_test.go
@@ -23,15 +23,14 @@
 	-adobeDir=$HOME/fonts/adobe \
 	-appleDir=$HOME/fonts/apple \
 	-dejavuDir=$HOME/fonts/dejavu \
-	-microsoftDir=$HOME/fonts/microsoft
+	-microsoftDir=$HOME/fonts/microsoft \
+	-notoDir=$HOME/fonts/noto
 
 To only run those tests for the Microsoft fonts:
 
 go test golang.org/x/image/font/sfnt -test.run=ProprietaryMicrosoft -args -proprietary etc
 */
 
-// TODO: add Google fonts (Droid? Noto?)? Emoji fonts?
-
 // TODO: enable Apple/Microsoft tests by default on Darwin/Windows?
 
 import (
@@ -88,6 +87,13 @@
 		"/usr/share/fonts/truetype/msttcorefonts",
 		"directory name for the Microsoft proprietary fonts",
 	)
+
+	notoDir = flag.String(
+		"notoDir",
+		// Get the fonts from https://www.google.com/get/noto/
+		"",
+		"directory name for the Noto proprietary fonts",
+	)
 )
 
 func TestProprietaryAdobeSourceCodeProOTF(t *testing.T) {
@@ -186,6 +192,14 @@
 	testProprietary(t, "microsoft", "Webdings.ttf", 200, -1)
 }
 
+func TestProprietaryNotoColorEmoji(t *testing.T) {
+	testProprietary(t, "noto", "NotoColorEmoji.ttf", 2300, -1)
+}
+
+func TestProprietaryNotoSansRegular(t *testing.T) {
+	testProprietary(t, "noto", "NotoSans-Regular.ttf", 2400, -1)
+}
+
 // testProprietary tests that we can load every glyph in the named font.
 //
 // The exact number of glyphs in the font can differ across its various
@@ -219,6 +233,8 @@
 		dir = *dejavuDir
 	case "microsoft":
 		dir = *microsoftDir
+	case "noto":
+		dir = *notoDir
 	default:
 		panic("unreachable")
 	}
@@ -287,7 +303,7 @@
 		iMax = firstUnsupportedGlyph
 	}
 	for i, numErrors := 0, 0; i < iMax; i++ {
-		if _, err := f.LoadGlyph(&buf, GlyphIndex(i), ppem, nil); err != nil {
+		if _, err := f.LoadGlyph(&buf, GlyphIndex(i), ppem, nil); err != nil && err != ErrColoredGlyph {
 			t.Errorf("LoadGlyph(%d): %v", i, err)
 			numErrors++
 		}
@@ -409,6 +425,9 @@
 	"microsoft/Comic_Sans_MS.ttf":   "Version 2.10",
 	"microsoft/Times_New_Roman.ttf": "Version 2.82",
 	"microsoft/Webdings.ttf":        "Version 1.03",
+
+	"noto/NotoColorEmoji.ttf":   "Version 1.33",
+	"noto/NotoSans-Regular.ttf": "Version 1.06",
 }
 
 // proprietaryFullNames holds the expected full name of each proprietary font
@@ -441,6 +460,9 @@
 	"microsoft/Comic_Sans_MS.ttf":   "Comic Sans MS",
 	"microsoft/Times_New_Roman.ttf": "Times New Roman",
 	"microsoft/Webdings.ttf":        "Webdings",
+
+	"noto/NotoColorEmoji.ttf":   "Noto Color Emoji",
+	"noto/NotoSans-Regular.ttf": "Noto Sans",
 }
 
 // proprietaryGlyphIndexTestCases hold a sample of each font's rune to glyph
@@ -1152,6 +1174,27 @@
 			transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(482, -217, 560, -384)),
 		},
 	},
+
+	"noto/NotoSans-Regular.ttf": {
+		'i': {
+			// - contour #0
+			moveTo(354, 0),
+			lineTo(174, 0),
+			lineTo(174, 1098),
+			lineTo(354, 1098),
+			lineTo(354, 0),
+			// - contour #1
+			moveTo(160, 1395),
+			quadTo(160, 1455, 190, 1482),
+			quadTo(221, 1509, 266, 1509),
+			quadTo(308, 1509, 339, 1482),
+			quadTo(371, 1455, 371, 1395),
+			quadTo(371, 1336, 339, 1308),
+			quadTo(308, 1280, 266, 1280),
+			quadTo(221, 1280, 190, 1308),
+			quadTo(160, 1336, 160, 1395),
+		},
+	},
 }
 
 type kernTestCase struct {
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 34587da..53ac94b 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -64,6 +64,9 @@
 )
 
 var (
+	// ErrColoredGlyph indicates that the requested glyph is not a monochrome
+	// vector glyph, such as a colored (bitmap or vector) emoji glyph.
+	ErrColoredGlyph = errors.New("sfnt: colored glyph")
 	// ErrNotFound indicates that the requested value was not found.
 	ErrNotFound = errors.New("sfnt: not found")
 
@@ -544,6 +547,12 @@
 	cff table
 
 	// https://www.microsoft.com/typography/otspec/otff.htm#otttables
+	// "Tables Related to Bitmap Glyphs".
+	//
+	// TODO: Others?
+	cblc table
+
+	// https://www.microsoft.com/typography/otspec/otff.htm#otttables
 	// "Advanced Typographic Tables".
 	//
 	// TODO: base, gdef, gpos, gsub, jstf, math?
@@ -559,6 +568,7 @@
 		glyphIndex       glyphIndexFunc
 		bounds           [4]int16
 		indexToLocFormat bool // false means short, true means long.
+		isColorBitmap    bool
 		isPostScript     bool
 		kernNumPairs     int32
 		kernOffset       int32
@@ -599,7 +609,7 @@
 	if err != nil {
 		return err
 	}
-	buf, glyphData, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript)
+	buf, glyphData, isColorBitmap, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript)
 	if err != nil {
 		return err
 	}
@@ -628,6 +638,7 @@
 	f.cached.glyphIndex = glyphIndex
 	f.cached.bounds = bounds
 	f.cached.indexToLocFormat = indexToLocFormat
+	f.cached.isColorBitmap = isColorBitmap
 	f.cached.isPostScript = isPostScript
 	f.cached.kernNumPairs = kernNumPairs
 	f.cached.kernOffset = kernOffset
@@ -701,6 +712,8 @@
 
 		// Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32.
 		switch tag {
+		case 0x43424c43:
+			f.cblc = table{o, n}
 		case 0x43464620:
 			f.cff = table{o, n}
 		case 0x4f532f32:
@@ -983,7 +996,7 @@
 	fdSelect fdSelect
 }
 
-func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, err error) {
+func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, isColorBitmap bool, err error) {
 	if isPostScript {
 		p := cffParser{
 			src:    &f.src,
@@ -993,19 +1006,25 @@
 		}
 		ret, err = p.parse(numGlyphs)
 		if err != nil {
-			return nil, glyphData{}, err
+			return nil, glyphData{}, false, err
 		}
-	} else {
+	} else if f.loca.length != 0 {
 		ret.locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
 		if err != nil {
-			return nil, glyphData{}, err
+			return nil, glyphData{}, false, err
 		}
-	}
-	if len(ret.locations) != int(numGlyphs+1) {
-		return nil, glyphData{}, errInvalidLocationData
+	} else if f.cblc.length != 0 {
+		isColorBitmap = true
+		// TODO: parse the CBLC (and CBDT) tables. For now, we return a font
+		// with empty glyphs.
+		ret.locations = make([]uint32, numGlyphs+1)
 	}
 
-	return buf, ret, nil
+	if len(ret.locations) != int(numGlyphs+1) {
+		return nil, glyphData{}, false, errInvalidLocationData
+	}
+
+	return buf, ret, isColorBitmap, nil
 }
 
 func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, postTableVersion uint32, err error) {
@@ -1105,13 +1124,18 @@
 //
 // In the returned Segments' (x, y) coordinates, the Y axis increases down.
 //
-// It returns ErrNotFound if the glyph index is out of range.
+// It returns ErrNotFound if the glyph index is out of range. It returns
+// ErrColoredGlyph if the glyph is not a monochrome vector glyph, such as a
+// colored (bitmap or vector) emoji glyph.
 func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) ([]Segment, error) {
 	if b == nil {
 		b = &Buffer{}
 	}
 
 	b.segments = b.segments[:0]
+	if f.cached.isColorBitmap {
+		return nil, ErrColoredGlyph
+	}
 	if f.cached.isPostScript {
 		buf, offset, length, err := f.viewGlyphData(b, x)
 		if err != nil {
@@ -1124,10 +1148,8 @@
 		if !b.psi.type2Charstrings.ended {
 			return nil, errInvalidCFFTable
 		}
-	} else {
-		if err := loadGlyf(f, b, x, 0, 0); err != nil {
-			return nil, err
-		}
+	} else if err := loadGlyf(f, b, x, 0, 0); err != nil {
+		return nil, err
 	}
 
 	// Scale the segments. If we want to support hinting, we'll have to push