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>
This commit is contained in:
Nigel Tao 2017-05-20 18:22:12 +10:00
Родитель f483456c9f
Коммит d835a09709
2 изменённых файлов: 83 добавлений и 18 удалений

Просмотреть файл

@ -23,15 +23,14 @@ go test golang.org/x/image/font/sfnt -args -proprietary \
-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 @@ var (
"/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 @@ func TestProprietaryMicrosoftWebdings(t *testing.T) {
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 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi
dir = *dejavuDir
case "microsoft":
dir = *microsoftDir
case "noto":
dir = *notoDir
default:
panic("unreachable")
}
@ -287,7 +303,7 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi
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 @@ var proprietaryVersions = map[string]string{
"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 @@ var proprietaryFullNames = map[string]string{
"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 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
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 {

Просмотреть файл

@ -64,6 +64,9 @@ const (
)
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")
@ -543,6 +546,12 @@ type Font struct {
// TODO: cff2, vorg?
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".
//
@ -559,6 +568,7 @@ type Font struct {
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 @@ func (f *Font) initialize(offset int, isDfont bool) error {
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 @@ func (f *Font) initialize(offset int, isDfont bool) error {
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 @@ func (f *Font) initializeTables(offset int, isDfont bool) (buf1 []byte, isPostSc
// 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 @@ type glyphData struct {
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 @@ func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isP
}
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 @@ type LoadGlyphOptions struct {
//
// 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 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *Load
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