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:
parent
ce0faa1867
commit
10ed294205
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user