font/sfnt: implement Font.Bounds.
Change-Id: I24ab4cfa74a791ebb8223b38e5d6624c74caa9f8 Reviewed-on: https://go-review.googlesource.com/39670 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
84a6511894
commit
1de9a5bb2a
|
@ -445,6 +445,7 @@ type Font struct {
|
||||||
cached struct {
|
cached struct {
|
||||||
glyphData glyphData
|
glyphData glyphData
|
||||||
glyphIndex glyphIndexFunc
|
glyphIndex glyphIndexFunc
|
||||||
|
bounds [4]int16
|
||||||
indexToLocFormat bool // false means short, true means long.
|
indexToLocFormat bool // false means short, true means long.
|
||||||
isPostScript bool
|
isPostScript bool
|
||||||
kernNumPairs int32
|
kernNumPairs int32
|
||||||
|
@ -478,7 +479,7 @@ func (f *Font) initialize(offset int) error {
|
||||||
// When implementing new parseXxx methods, take care not to call methods
|
// When implementing new parseXxx methods, take care not to call methods
|
||||||
// such as Font.NumGlyphs that implicitly depend on f.cached fields.
|
// such as Font.NumGlyphs that implicitly depend on f.cached fields.
|
||||||
|
|
||||||
buf, indexToLocFormat, unitsPerEm, err := f.parseHead(buf)
|
buf, bounds, indexToLocFormat, unitsPerEm, err := f.parseHead(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -513,6 +514,7 @@ func (f *Font) initialize(offset int) error {
|
||||||
|
|
||||||
f.cached.glyphData = glyphData
|
f.cached.glyphData = glyphData
|
||||||
f.cached.glyphIndex = glyphIndex
|
f.cached.glyphIndex = glyphIndex
|
||||||
|
f.cached.bounds = bounds
|
||||||
f.cached.indexToLocFormat = indexToLocFormat
|
f.cached.indexToLocFormat = indexToLocFormat
|
||||||
f.cached.isPostScript = isPostScript
|
f.cached.isPostScript = isPostScript
|
||||||
f.cached.kernNumPairs = kernNumPairs
|
f.cached.kernNumPairs = kernNumPairs
|
||||||
|
@ -668,26 +670,36 @@ func (f *Font) parseCmap(buf []byte) (buf1 []byte, glyphIndex glyphIndexFunc, er
|
||||||
return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
|
return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) parseHead(buf []byte) (buf1 []byte, indexToLocFormat bool, unitsPerEm Units, err error) {
|
func (f *Font) parseHead(buf []byte) (buf1 []byte, bounds [4]int16, indexToLocFormat bool, unitsPerEm Units, err error) {
|
||||||
// https://www.microsoft.com/typography/otspec/head.htm
|
// https://www.microsoft.com/typography/otspec/head.htm
|
||||||
|
|
||||||
if f.head.length != 54 {
|
if f.head.length != 54 {
|
||||||
return nil, false, 0, errInvalidHeadTable
|
return nil, [4]int16{}, false, 0, errInvalidHeadTable
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := f.src.u16(buf, f.head, 18)
|
u, err := f.src.u16(buf, f.head, 18)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, 0, err
|
return nil, [4]int16{}, false, 0, err
|
||||||
}
|
}
|
||||||
if u == 0 {
|
if u == 0 {
|
||||||
return nil, false, 0, errInvalidHeadTable
|
return nil, [4]int16{}, false, 0, errInvalidHeadTable
|
||||||
}
|
}
|
||||||
unitsPerEm = Units(u)
|
unitsPerEm = Units(u)
|
||||||
|
|
||||||
|
for i := range bounds {
|
||||||
|
u, err := f.src.u16(buf, f.head, 36+2*i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, [4]int16{}, false, 0, err
|
||||||
|
}
|
||||||
|
bounds[i] = int16(u)
|
||||||
|
}
|
||||||
|
|
||||||
u, err = f.src.u16(buf, f.head, 50)
|
u, err = f.src.u16(buf, f.head, 50)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, 0, err
|
return nil, [4]int16{}, false, 0, err
|
||||||
}
|
}
|
||||||
indexToLocFormat = u != 0
|
indexToLocFormat = u != 0
|
||||||
return buf, indexToLocFormat, unitsPerEm, nil
|
return buf, bounds, indexToLocFormat, unitsPerEm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, numHMetrics int32, err error) {
|
func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, numHMetrics int32, err error) {
|
||||||
|
@ -895,6 +907,33 @@ func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, postTableVer
|
||||||
return buf, u, nil
|
return buf, u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bounds returns the union of a Font's glyphs' bounds.
|
||||||
|
//
|
||||||
|
// In the returned Rectangle26_6's (x, y) coordinates, the Y axis increases
|
||||||
|
// down.
|
||||||
|
func (f *Font) Bounds(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (fixed.Rectangle26_6, error) {
|
||||||
|
// The 0, 3, 2, 1 indices are to flip the Y coordinates. OpenType's Y axis
|
||||||
|
// increases up. Go's standard graphics libraries' Y axis increases down.
|
||||||
|
r := fixed.Rectangle26_6{
|
||||||
|
Min: fixed.Point26_6{
|
||||||
|
X: +scale(fixed.Int26_6(f.cached.bounds[0])*ppem, f.cached.unitsPerEm),
|
||||||
|
Y: -scale(fixed.Int26_6(f.cached.bounds[3])*ppem, f.cached.unitsPerEm),
|
||||||
|
},
|
||||||
|
Max: fixed.Point26_6{
|
||||||
|
X: +scale(fixed.Int26_6(f.cached.bounds[2])*ppem, f.cached.unitsPerEm),
|
||||||
|
Y: -scale(fixed.Int26_6(f.cached.bounds[1])*ppem, f.cached.unitsPerEm),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if h == font.HintingFull {
|
||||||
|
// Quantize the Min down and Max up to a whole pixel.
|
||||||
|
r.Min.X = (r.Min.X + 0) &^ 63
|
||||||
|
r.Min.Y = (r.Min.Y + 0) &^ 63
|
||||||
|
r.Max.X = (r.Max.X + 63) &^ 63
|
||||||
|
r.Max.Y = (r.Max.Y + 63) &^ 63
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: API for looking up glyph variants?? For example, some fonts may
|
// TODO: API for looking up glyph variants?? For example, some fonts may
|
||||||
// provide both slashed and dotted zero glyphs ('0'), or regular and 'old
|
// provide both slashed and dotted zero glyphs ('0'), or regular and 'old
|
||||||
// style' numerals, and users can direct software to choose a variant.
|
// style' numerals, and users can direct software to choose a variant.
|
||||||
|
|
|
@ -112,6 +112,73 @@ func testTrueType(t *testing.T, f *Font) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fontData(name string) []byte {
|
||||||
|
switch name {
|
||||||
|
case "gobold":
|
||||||
|
return gobold.TTF
|
||||||
|
case "gomono":
|
||||||
|
return gomono.TTF
|
||||||
|
case "goregular":
|
||||||
|
return goregular.TTF
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBounds(t *testing.T) {
|
||||||
|
testCases := map[string]fixed.Rectangle26_6{
|
||||||
|
"gobold": {
|
||||||
|
Min: fixed.Point26_6{
|
||||||
|
X: -452,
|
||||||
|
Y: -2193,
|
||||||
|
},
|
||||||
|
Max: fixed.Point26_6{
|
||||||
|
X: 2190,
|
||||||
|
Y: 432,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"gomono": {
|
||||||
|
Min: fixed.Point26_6{
|
||||||
|
X: 0,
|
||||||
|
Y: -2227,
|
||||||
|
},
|
||||||
|
Max: fixed.Point26_6{
|
||||||
|
X: 1229,
|
||||||
|
Y: 432,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"goregular": {
|
||||||
|
Min: fixed.Point26_6{
|
||||||
|
X: -440,
|
||||||
|
Y: -2118,
|
||||||
|
},
|
||||||
|
Max: fixed.Point26_6{
|
||||||
|
X: 2160,
|
||||||
|
Y: 543,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var b Buffer
|
||||||
|
for name, want := range testCases {
|
||||||
|
f, err := Parse(fontData(name))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Parse(%q): %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ppem := fixed.Int26_6(f.UnitsPerEm())
|
||||||
|
|
||||||
|
got, err := f.Bounds(&b, ppem, font.HintingNone)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("name=%q: Bounds: %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("name=%q: Bounds: got %v, want %v", name, got, want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGlyphAdvance(t *testing.T) {
|
func TestGlyphAdvance(t *testing.T) {
|
||||||
testCases := map[string][]struct {
|
testCases := map[string][]struct {
|
||||||
r rune
|
r rune
|
||||||
|
@ -145,16 +212,7 @@ func TestGlyphAdvance(t *testing.T) {
|
||||||
|
|
||||||
var b Buffer
|
var b Buffer
|
||||||
for name, testCases1 := range testCases {
|
for name, testCases1 := range testCases {
|
||||||
data := []byte(nil)
|
f, err := Parse(fontData(name))
|
||||||
switch name {
|
|
||||||
case "gobold":
|
|
||||||
data = gobold.TTF
|
|
||||||
case "gomono":
|
|
||||||
data = gomono.TTF
|
|
||||||
case "goregular":
|
|
||||||
data = goregular.TTF
|
|
||||||
}
|
|
||||||
f, err := Parse(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Parse(%q): %v", name, err)
|
t.Errorf("Parse(%q): %v", name, err)
|
||||||
continue
|
continue
|
||||||
|
|
Loading…
Reference in New Issue
Block a user