font/sfnt: implement Font.GlyphAdvance.

Change-Id: I3e15c6e634d858a87e73221bd9d5a9e3979d674a
Reviewed-on: https://go-review.googlesource.com/39250
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Nigel Tao 2017-04-01 15:09:28 +11:00
parent ce0faa1867
commit 10ed294205
4 changed files with 151 additions and 10 deletions

View File

@ -132,7 +132,7 @@ type cffParser struct {
psi psInterpreter psi psInterpreter
} }
func (p *cffParser) parse(numGlyphs int) (ret glyphData, err error) { func (p *cffParser) parse(numGlyphs int32) (ret glyphData, err error) {
// Parse the header. // Parse the header.
{ {
if !p.read(4) { if !p.read(4) {
@ -236,7 +236,7 @@ func (p *cffParser) parse(numGlyphs int) (ret glyphData, err error) {
if !ok { if !ok {
return glyphData{}, p.err return glyphData{}, p.err
} }
if count == 0 || int(count) != numGlyphs { if count == 0 || int32(count) != numGlyphs {
return glyphData{}, errInvalidCFFTable return glyphData{}, errInvalidCFFTable
} }
ret.locations = make([]uint32, count+1) ret.locations = make([]uint32, count+1)
@ -312,7 +312,7 @@ func (p *cffParser) parse(numGlyphs int) (ret glyphData, err error) {
// parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section // parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section
// 19 "FDSelect". // 19 "FDSelect".
func (p *cffParser) parseFDSelect(offset int32, numGlyphs int) (ret fdSelect, err error) { func (p *cffParser) parseFDSelect(offset int32, numGlyphs int32) (ret fdSelect, err error) {
if !p.seekFromBase(p.psi.topDict.fdSelect) { if !p.seekFromBase(p.psi.topDict.fdSelect) {
return fdSelect{}, errInvalidCFFTable return fdSelect{}, errInvalidCFFTable
} }
@ -322,7 +322,7 @@ func (p *cffParser) parseFDSelect(offset int32, numGlyphs int) (ret fdSelect, er
ret.format = p.buf[0] ret.format = p.buf[0]
switch ret.format { switch ret.format {
case 0: case 0:
if p.end-p.offset < numGlyphs { if p.end-p.offset < int(numGlyphs) {
return fdSelect{}, errInvalidCFFTable return fdSelect{}, errInvalidCFFTable
} }
ret.offset = int32(p.offset) ret.offset = int32(p.offset)

View File

@ -75,6 +75,8 @@ var (
errInvalidGlyphData = errors.New("sfnt: invalid glyph data") errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length") errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length")
errInvalidHeadTable = errors.New("sfnt: invalid head table") errInvalidHeadTable = errors.New("sfnt: invalid head table")
errInvalidHheaTable = errors.New("sfnt: invalid hhea table")
errInvalidHmtxTable = errors.New("sfnt: invalid hmtx table")
errInvalidKernTable = errors.New("sfnt: invalid kern table") errInvalidKernTable = errors.New("sfnt: invalid kern table")
errInvalidLocaTable = errors.New("sfnt: invalid loca table") errInvalidLocaTable = errors.New("sfnt: invalid loca table")
errInvalidLocationData = errors.New("sfnt: invalid location data") errInvalidLocationData = errors.New("sfnt: invalid location data")
@ -447,6 +449,7 @@ type Font struct {
isPostScript bool isPostScript bool
kernNumPairs int32 kernNumPairs int32
kernOffset int32 kernOffset int32
numHMetrics int32
postTableVersion uint32 postTableVersion uint32
unitsPerEm Units unitsPerEm Units
} }
@ -495,6 +498,14 @@ func (f *Font) initialize(offset int) error {
if err != nil { if err != nil {
return err return err
} }
buf, numHMetrics, err := f.parseHhea(buf, numGlyphs)
if err != nil {
return err
}
buf, err = f.parseHmtx(buf, numGlyphs, numHMetrics)
if err != nil {
return err
}
buf, postTableVersion, err := f.parsePost(buf, numGlyphs) buf, postTableVersion, err := f.parsePost(buf, numGlyphs)
if err != nil { if err != nil {
return err return err
@ -506,6 +517,7 @@ func (f *Font) initialize(offset int) error {
f.cached.isPostScript = isPostScript f.cached.isPostScript = isPostScript
f.cached.kernNumPairs = kernNumPairs f.cached.kernNumPairs = kernNumPairs
f.cached.kernOffset = kernOffset f.cached.kernOffset = kernOffset
f.cached.numHMetrics = numHMetrics
f.cached.postTableVersion = postTableVersion f.cached.postTableVersion = postTableVersion
f.cached.unitsPerEm = unitsPerEm f.cached.unitsPerEm = unitsPerEm
@ -678,6 +690,31 @@ func (f *Font) parseHead(buf []byte) (buf1 []byte, indexToLocFormat bool, unitsP
return buf, indexToLocFormat, unitsPerEm, nil return buf, indexToLocFormat, unitsPerEm, nil
} }
func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, numHMetrics int32, err error) {
// https://www.microsoft.com/typography/OTSPEC/hhea.htm
if f.hhea.length != 36 {
return nil, 0, errInvalidHheaTable
}
u, err := f.src.u16(buf, f.hhea, 34)
if err != nil {
return nil, 0, err
}
if int32(u) > numGlyphs || u == 0 {
return nil, 0, errInvalidHheaTable
}
return buf, int32(u), nil
}
func (f *Font) parseHmtx(buf []byte, numGlyphs, numHMetrics int32) (buf1 []byte, err error) {
// https://www.microsoft.com/typography/OTSPEC/hmtx.htm
if f.hmtx.length != uint32(2*numGlyphs+2*numHMetrics) {
return nil, errInvalidHmtxTable
}
return buf, nil
}
func (f *Font) parseKern(buf []byte) (buf1 []byte, kernNumPairs, kernOffset int32, err error) { func (f *Font) parseKern(buf []byte) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
// https://www.microsoft.com/typography/otspec/kern.htm // https://www.microsoft.com/typography/otspec/kern.htm
@ -772,7 +809,7 @@ func (f *Font) parseKernFormat0(buf []byte, offset, length int) (buf1 []byte, ke
return buf, kernNumPairs, int32(offset) + headerSize, nil return buf, kernNumPairs, int32(offset) + headerSize, nil
} }
func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs int, err error) { func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs int32, err error) {
// https://www.microsoft.com/typography/otspec/maxp.htm // https://www.microsoft.com/typography/otspec/maxp.htm
if isPostScript { if isPostScript {
@ -788,7 +825,7 @@ func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
return buf, int(u), nil return buf, int32(u), nil
} }
type glyphData struct { type glyphData struct {
@ -809,7 +846,7 @@ type glyphData struct {
fdSelect fdSelect fdSelect fdSelect
} }
func (f *Font) parseGlyphData(buf []byte, numGlyphs int, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, err error) { func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, err error) {
if isPostScript { if isPostScript {
p := cffParser{ p := cffParser{
src: &f.src, src: &f.src,
@ -827,14 +864,14 @@ func (f *Font) parseGlyphData(buf []byte, numGlyphs int, indexToLocFormat, isPos
return nil, glyphData{}, err return nil, glyphData{}, err
} }
} }
if len(ret.locations) != numGlyphs+1 { if len(ret.locations) != int(numGlyphs+1) {
return nil, glyphData{}, errInvalidLocationData return nil, glyphData{}, errInvalidLocationData
} }
return buf, ret, nil return buf, ret, nil
} }
func (f *Font) parsePost(buf []byte, numGlyphs int) (buf1 []byte, postTableVersion uint32, err error) { func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, postTableVersion uint32, err error) {
// https://www.microsoft.com/typography/otspec/post.htm // https://www.microsoft.com/typography/otspec/post.htm
const headerSize = 32 const headerSize = 32
@ -1022,6 +1059,39 @@ func (f *Font) GlyphName(b *Buffer, x GlyphIndex) (string, error) {
} }
} }
// GlyphAdvance returns the advance width for the x'th glyph. ppem is the
// number of pixels in 1 em.
//
// It returns ErrNotFound if the glyph index is out of range.
func (f *Font) GlyphAdvance(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) {
if int(x) >= f.NumGlyphs() {
return 0, ErrNotFound
}
if b == nil {
b = &Buffer{}
}
// https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an
// optimization, the number of records can be less than the number of
// glyphs, in which case the advance width value of the last record applies
// to all remaining glyph IDs."
if n := GlyphIndex(f.cached.numHMetrics - 1); x > n {
x = n
}
buf, err := b.view(&f.src, int(f.hmtx.offset)+int(4*x), 2)
if err != nil {
return 0, err
}
adv := fixed.Int26_6(u16(buf))
adv = scale(adv*ppem, f.cached.unitsPerEm)
if h == font.HintingFull {
// Quantize the fixed.Int26_6 value to the nearest pixel.
adv = (adv + 32) &^ 63
}
return adv, nil
}
// Kern returns the horizontal adjustment for the kerning pair (x0, x1). A // Kern returns the horizontal adjustment for the kerning pair (x0, x1). A
// positive kern means to move the glyphs further apart. ppem is the number of // positive kern means to move the glyphs further apart. ppem is the number of
// pixels in 1 em. // pixels in 1 em.

View File

@ -11,6 +11,9 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"golang.org/x/image/font"
"golang.org/x/image/font/gofont/gobold"
"golang.org/x/image/font/gofont/gomono"
"golang.org/x/image/font/gofont/goregular" "golang.org/x/image/font/gofont/goregular"
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
) )
@ -109,6 +112,74 @@ func testTrueType(t *testing.T, f *Font) {
} }
} }
func TestGlyphAdvance(t *testing.T) {
testCases := map[string][]struct {
r rune
want fixed.Int26_6
}{
"gobold": {
{' ', 569},
{'A', 1479},
{'Á', 1479},
{'Æ', 2048},
{'i', 592},
{'x', 1139},
},
"gomono": {
{' ', 1229},
{'A', 1229},
{'Á', 1229},
{'Æ', 1229},
{'i', 1229},
{'x', 1229},
},
"goregular": {
{' ', 569},
{'A', 1366},
{'Á', 1366},
{'Æ', 2048},
{'i', 505},
{'x', 1024},
},
}
var b Buffer
for name, testCases1 := range testCases {
data := []byte(nil)
switch name {
case "gobold":
data = gobold.TTF
case "gomono":
data = gomono.TTF
case "goregular":
data = goregular.TTF
}
f, err := Parse(data)
if err != nil {
t.Errorf("Parse(%q): %v", name, err)
continue
}
ppem := fixed.Int26_6(f.UnitsPerEm())
for _, tc := range testCases1 {
x, err := f.GlyphIndex(&b, tc.r)
if err != nil {
t.Errorf("name=%q, r=%q: GlyphIndex: %v", name, tc.r, err)
continue
}
got, err := f.GlyphAdvance(&b, x, ppem, font.HintingNone)
if err != nil {
t.Errorf("name=%q, r=%q: GlyphAdvance: %v", name, tc.r, err)
continue
}
if got != tc.want {
t.Errorf("name=%q, r=%q: GlyphAdvance: got %d, want %d", name, tc.r, got, tc.want)
continue
}
}
}
}
func TestGoRegularGlyphIndex(t *testing.T) { func TestGoRegularGlyphIndex(t *testing.T) {
f, err := Parse(goregular.TTF) f, err := Parse(goregular.TTF)
if err != nil { if err != nil {

View File

@ -51,7 +51,7 @@ func midPoint(p, q fixed.Point26_6) fixed.Point26_6 {
} }
} }
func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool, numGlyphs int) (locations []uint32, err error) { func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool, numGlyphs int32) (locations []uint32, err error) {
if indexToLocFormat { if indexToLocFormat {
if loca.length != 4*uint32(numGlyphs+1) { if loca.length != 4*uint32(numGlyphs+1) {
return nil, errInvalidLocaTable return nil, errInvalidLocaTable