font/sfnt: implement Font.GlyphAdvance.
Change-Id: I3e15c6e634d858a87e73221bd9d5a9e3979d674a
Reviewed-on: https://go-review.googlesource.com/39250
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/postscript.go b/font/sfnt/postscript.go
index 2f746c4..f166b6c 100644
--- a/font/sfnt/postscript.go
+++ b/font/sfnt/postscript.go
@@ -132,7 +132,7 @@
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.
{
if !p.read(4) {
@@ -236,7 +236,7 @@
if !ok {
return glyphData{}, p.err
}
- if count == 0 || int(count) != numGlyphs {
+ if count == 0 || int32(count) != numGlyphs {
return glyphData{}, errInvalidCFFTable
}
ret.locations = make([]uint32, count+1)
@@ -312,7 +312,7 @@
// parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section
// 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) {
return fdSelect{}, errInvalidCFFTable
}
@@ -322,7 +322,7 @@
ret.format = p.buf[0]
switch ret.format {
case 0:
- if p.end-p.offset < numGlyphs {
+ if p.end-p.offset < int(numGlyphs) {
return fdSelect{}, errInvalidCFFTable
}
ret.offset = int32(p.offset)
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 6668606..f177e82 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -75,6 +75,8 @@
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length")
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")
errInvalidLocaTable = errors.New("sfnt: invalid loca table")
errInvalidLocationData = errors.New("sfnt: invalid location data")
@@ -447,6 +449,7 @@
isPostScript bool
kernNumPairs int32
kernOffset int32
+ numHMetrics int32
postTableVersion uint32
unitsPerEm Units
}
@@ -495,6 +498,14 @@
if err != nil {
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)
if err != nil {
return err
@@ -506,6 +517,7 @@
f.cached.isPostScript = isPostScript
f.cached.kernNumPairs = kernNumPairs
f.cached.kernOffset = kernOffset
+ f.cached.numHMetrics = numHMetrics
f.cached.postTableVersion = postTableVersion
f.cached.unitsPerEm = unitsPerEm
@@ -678,6 +690,31 @@
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) {
// https://www.microsoft.com/typography/otspec/kern.htm
@@ -772,7 +809,7 @@
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
if isPostScript {
@@ -788,7 +825,7 @@
if err != nil {
return nil, 0, err
}
- return buf, int(u), nil
+ return buf, int32(u), nil
}
type glyphData struct {
@@ -809,7 +846,7 @@
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 {
p := cffParser{
src: &f.src,
@@ -827,14 +864,14 @@
return nil, glyphData{}, err
}
}
- if len(ret.locations) != numGlyphs+1 {
+ if len(ret.locations) != int(numGlyphs+1) {
return nil, glyphData{}, errInvalidLocationData
}
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
const headerSize = 32
@@ -1022,6 +1059,39 @@
}
}
+// 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
// positive kern means to move the glyphs further apart. ppem is the number of
// pixels in 1 em.
diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go
index 2bb508b..3f4fcd5 100644
--- a/font/sfnt/sfnt_test.go
+++ b/font/sfnt/sfnt_test.go
@@ -11,6 +11,9 @@
"path/filepath"
"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/math/fixed"
)
@@ -109,6 +112,74 @@
}
}
+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) {
f, err := Parse(goregular.TTF)
if err != nil {
diff --git a/font/sfnt/truetype.go b/font/sfnt/truetype.go
index 25455e5..ab27f5b 100644
--- a/font/sfnt/truetype.go
+++ b/font/sfnt/truetype.go
@@ -51,7 +51,7 @@
}
}
-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 loca.length != 4*uint32(numGlyphs+1) {
return nil, errInvalidLocaTable