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:
parent
f483456c9f
commit
d835a09709
|
@ -23,15 +23,14 @@ go test golang.org/x/image/font/sfnt -args -proprietary \
|
||||||
-adobeDir=$HOME/fonts/adobe \
|
-adobeDir=$HOME/fonts/adobe \
|
||||||
-appleDir=$HOME/fonts/apple \
|
-appleDir=$HOME/fonts/apple \
|
||||||
-dejavuDir=$HOME/fonts/dejavu \
|
-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:
|
To only run those tests for the Microsoft fonts:
|
||||||
|
|
||||||
go test golang.org/x/image/font/sfnt -test.run=ProprietaryMicrosoft -args -proprietary etc
|
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?
|
// TODO: enable Apple/Microsoft tests by default on Darwin/Windows?
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -88,6 +87,13 @@ var (
|
||||||
"/usr/share/fonts/truetype/msttcorefonts",
|
"/usr/share/fonts/truetype/msttcorefonts",
|
||||||
"directory name for the Microsoft proprietary fonts",
|
"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) {
|
func TestProprietaryAdobeSourceCodeProOTF(t *testing.T) {
|
||||||
|
@ -186,6 +192,14 @@ func TestProprietaryMicrosoftWebdings(t *testing.T) {
|
||||||
testProprietary(t, "microsoft", "Webdings.ttf", 200, -1)
|
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.
|
// 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
|
// 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
|
dir = *dejavuDir
|
||||||
case "microsoft":
|
case "microsoft":
|
||||||
dir = *microsoftDir
|
dir = *microsoftDir
|
||||||
|
case "noto":
|
||||||
|
dir = *notoDir
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
@ -287,7 +303,7 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi
|
||||||
iMax = firstUnsupportedGlyph
|
iMax = firstUnsupportedGlyph
|
||||||
}
|
}
|
||||||
for i, numErrors := 0, 0; i < iMax; i++ {
|
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)
|
t.Errorf("LoadGlyph(%d): %v", i, err)
|
||||||
numErrors++
|
numErrors++
|
||||||
}
|
}
|
||||||
|
@ -409,6 +425,9 @@ var proprietaryVersions = map[string]string{
|
||||||
"microsoft/Comic_Sans_MS.ttf": "Version 2.10",
|
"microsoft/Comic_Sans_MS.ttf": "Version 2.10",
|
||||||
"microsoft/Times_New_Roman.ttf": "Version 2.82",
|
"microsoft/Times_New_Roman.ttf": "Version 2.82",
|
||||||
"microsoft/Webdings.ttf": "Version 1.03",
|
"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
|
// 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/Comic_Sans_MS.ttf": "Comic Sans MS",
|
||||||
"microsoft/Times_New_Roman.ttf": "Times New Roman",
|
"microsoft/Times_New_Roman.ttf": "Times New Roman",
|
||||||
"microsoft/Webdings.ttf": "Webdings",
|
"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
|
// 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)),
|
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 {
|
type kernTestCase struct {
|
||||||
|
|
|
@ -64,6 +64,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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 indicates that the requested value was not found.
|
||||||
ErrNotFound = errors.New("sfnt: not found")
|
ErrNotFound = errors.New("sfnt: not found")
|
||||||
|
|
||||||
|
@ -543,6 +546,12 @@ type Font struct {
|
||||||
// TODO: cff2, vorg?
|
// TODO: cff2, vorg?
|
||||||
cff table
|
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
|
// https://www.microsoft.com/typography/otspec/otff.htm#otttables
|
||||||
// "Advanced Typographic Tables".
|
// "Advanced Typographic Tables".
|
||||||
//
|
//
|
||||||
|
@ -559,6 +568,7 @@ type Font struct {
|
||||||
glyphIndex glyphIndexFunc
|
glyphIndex glyphIndexFunc
|
||||||
bounds [4]int16
|
bounds [4]int16
|
||||||
indexToLocFormat bool // false means short, true means long.
|
indexToLocFormat bool // false means short, true means long.
|
||||||
|
isColorBitmap bool
|
||||||
isPostScript bool
|
isPostScript bool
|
||||||
kernNumPairs int32
|
kernNumPairs int32
|
||||||
kernOffset int32
|
kernOffset int32
|
||||||
|
@ -599,7 +609,7 @@ func (f *Font) initialize(offset int, isDfont bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
buf, glyphData, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript)
|
buf, glyphData, isColorBitmap, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -628,6 +638,7 @@ func (f *Font) initialize(offset int, isDfont bool) error {
|
||||||
f.cached.glyphIndex = glyphIndex
|
f.cached.glyphIndex = glyphIndex
|
||||||
f.cached.bounds = bounds
|
f.cached.bounds = bounds
|
||||||
f.cached.indexToLocFormat = indexToLocFormat
|
f.cached.indexToLocFormat = indexToLocFormat
|
||||||
|
f.cached.isColorBitmap = isColorBitmap
|
||||||
f.cached.isPostScript = isPostScript
|
f.cached.isPostScript = isPostScript
|
||||||
f.cached.kernNumPairs = kernNumPairs
|
f.cached.kernNumPairs = kernNumPairs
|
||||||
f.cached.kernOffset = kernOffset
|
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.
|
// Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32.
|
||||||
switch tag {
|
switch tag {
|
||||||
|
case 0x43424c43:
|
||||||
|
f.cblc = table{o, n}
|
||||||
case 0x43464620:
|
case 0x43464620:
|
||||||
f.cff = table{o, n}
|
f.cff = table{o, n}
|
||||||
case 0x4f532f32:
|
case 0x4f532f32:
|
||||||
|
@ -983,7 +996,7 @@ type glyphData struct {
|
||||||
fdSelect fdSelect
|
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 {
|
if isPostScript {
|
||||||
p := cffParser{
|
p := cffParser{
|
||||||
src: &f.src,
|
src: &f.src,
|
||||||
|
@ -993,19 +1006,25 @@ func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isP
|
||||||
}
|
}
|
||||||
ret, err = p.parse(numGlyphs)
|
ret, err = p.parse(numGlyphs)
|
||||||
if err != nil {
|
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)
|
ret.locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, glyphData{}, err
|
return nil, glyphData{}, false, err
|
||||||
}
|
}
|
||||||
}
|
} else if f.cblc.length != 0 {
|
||||||
if len(ret.locations) != int(numGlyphs+1) {
|
isColorBitmap = true
|
||||||
return nil, glyphData{}, errInvalidLocationData
|
// 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) {
|
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.
|
// 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) {
|
func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) ([]Segment, error) {
|
||||||
if b == nil {
|
if b == nil {
|
||||||
b = &Buffer{}
|
b = &Buffer{}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.segments = b.segments[:0]
|
b.segments = b.segments[:0]
|
||||||
|
if f.cached.isColorBitmap {
|
||||||
|
return nil, ErrColoredGlyph
|
||||||
|
}
|
||||||
if f.cached.isPostScript {
|
if f.cached.isPostScript {
|
||||||
buf, offset, length, err := f.viewGlyphData(b, x)
|
buf, offset, length, err := f.viewGlyphData(b, x)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1124,11 +1148,9 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *Load
|
||||||
if !b.psi.type2Charstrings.ended {
|
if !b.psi.type2Charstrings.ended {
|
||||||
return nil, errInvalidCFFTable
|
return nil, errInvalidCFFTable
|
||||||
}
|
}
|
||||||
} else {
|
} else if err := loadGlyf(f, b, x, 0, 0); err != nil {
|
||||||
if err := loadGlyf(f, b, x, 0, 0); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Scale the segments. If we want to support hinting, we'll have to push
|
// Scale the segments. If we want to support hinting, we'll have to push
|
||||||
// the scaling computation into the PostScript / TrueType specific glyph
|
// the scaling computation into the PostScript / TrueType specific glyph
|
||||||
|
|
Loading…
Reference in New Issue
Block a user