font,font/sfnt: expose font x-Height and capHeight

Change-Id: I6e3e6e51c7e270e16413c23990f6df5e22cbfeb6
Reviewed-on: https://go-review.googlesource.com/135555
Run-TryBot: Elias Naur <elias.naur@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
This commit is contained in:
Elias Naur 2018-09-15 11:34:11 +02:00 committed by Nigel Tao
parent c73c2afc3b
commit e1a1ede689
8 changed files with 116 additions and 15 deletions

View File

@ -80,6 +80,8 @@ func (f *Face) Metrics() font.Metrics {
Height: fixed.I(f.Height),
Ascent: fixed.I(f.Ascent),
Descent: fixed.I(f.Descent),
XHeight: fixed.I(f.Ascent),
CapHeight: fixed.I(f.Ascent),
}
}

View File

@ -0,0 +1,18 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package basicfont
import (
"testing"
"golang.org/x/image/font"
)
func TestMetrics(t *testing.T) {
want := font.Metrics{Height: 832, Ascent: 704, Descent: 128, XHeight: 704, CapHeight: 704}
if got := Face7x13.Metrics(); got != want {
t.Errorf("Face7x13: Metrics: got %v want %v", got, want)
}
}

View File

@ -86,6 +86,14 @@ type Metrics struct {
// value is typically positive, even though a descender goes below the
// baseline.
Descent fixed.Int26_6
// XHeight is the distance from the top of non-ascending lowercase letters
// to the baseline.
XHeight fixed.Int26_6
// CapHeight is the distance from the top of uppercase letters to the
// baseline.
CapHeight fixed.Int26_6
}
// Drawer draws text on a destination image.

View File

@ -82,7 +82,7 @@ func TestFaceKern(t *testing.T) {
}
func TestFaceMetrics(t *testing.T) {
want := font.Metrics{Height: 888, Ascent: 726, Descent: 162}
want := font.Metrics{Height: 888, Ascent: 726, Descent: 162, XHeight: 407, CapHeight: 555}
got := regular.Metrics()
if got != want {
t.Fatalf("metrics failed. got=%#v. want=%#v", got, want)

View File

@ -62,10 +62,16 @@ func (f *subface) Close() error { return nil }
func (f *subface) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }
func (f *subface) Metrics() font.Metrics {
// Approximate XHeight with the ascent of lowercase 'x'.
xbounds, _, _ := f.GlyphBounds('x')
// The same applies to CapHeight, using the uppercase 'H'.
hbounds, _, _ := f.GlyphBounds('H')
return font.Metrics{
Height: fixed.I(f.height),
Ascent: fixed.I(f.ascent),
Descent: fixed.I(f.height - f.ascent),
XHeight: -xbounds.Min.Y,
CapHeight: -hbounds.Min.Y,
}
}
@ -144,10 +150,14 @@ func (f *face) Close() error { return nil }
func (f *face) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }
func (f *face) Metrics() font.Metrics {
xbounds, _, _ := f.GlyphBounds('x')
hbounds, _, _ := f.GlyphBounds('H')
return font.Metrics{
Height: fixed.I(f.height),
Ascent: fixed.I(f.ascent),
Descent: fixed.I(f.height - f.ascent),
XHeight: -xbounds.Min.Y,
CapHeight: -hbounds.Min.Y,
}
}

View File

@ -6,10 +6,42 @@ package plan9font
import (
"io/ioutil"
"path"
"path/filepath"
"testing"
"golang.org/x/image/font"
)
func TestMetrics(t *testing.T) {
readFile := func(name string) ([]byte, error) {
return ioutil.ReadFile(filepath.FromSlash(path.Join("../testdata/fixed", name)))
}
data, err := readFile("unicode.7x13.font")
if err != nil {
t.Fatal(err)
}
face, err := ParseFont(data, readFile)
if err != nil {
t.Fatal(err)
}
want := font.Metrics{Height: 832, Ascent: 704, Descent: 128, XHeight: 704, CapHeight: 704}
if got := face.Metrics(); got != want {
t.Errorf("unicode.7x13.font: Metrics: got %v, want %v", got, want)
}
subData, err := readFile("7x13.0000")
if err != nil {
t.Fatal(err)
}
subFace, err := ParseSubfont(subData, 0)
if err != nil {
t.Fatal(err)
}
if got := subFace.Metrics(); got != want {
t.Errorf("7x13.0000: Metrics: got %v, want %v", got, want)
}
}
func BenchmarkParseSubfont(b *testing.B) {
subfontData, err := ioutil.ReadFile(filepath.FromSlash("../testdata/fixed/7x13.0000"))
if err != nil {

View File

@ -86,6 +86,7 @@ var (
errInvalidLocationData = errors.New("sfnt: invalid location data")
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
errInvalidNameTable = errors.New("sfnt: invalid name table")
errInvalidOS2Table = errors.New("sfnt: invalid OS/2 table")
errInvalidPostTable = errors.New("sfnt: invalid post table")
errInvalidSingleFont = errors.New("sfnt: invalid single font (data is a font collection)")
errInvalidSourceData = errors.New("sfnt: invalid source data")
@ -565,6 +566,7 @@ type Font struct {
cached struct {
ascent int32
capHeight int32
glyphData glyphData
glyphIndex glyphIndexFunc
bounds [4]int16
@ -578,6 +580,7 @@ type Font struct {
numHMetrics int32
postTableVersion uint32
unitsPerEm Units
xHeight int32
}
}
@ -632,12 +635,17 @@ func (f *Font) initialize(offset int, isDfont bool) error {
if err != nil {
return err
}
buf, xHeight, capHeight, err := f.parseOS2(buf)
if err != nil {
return err
}
buf, postTableVersion, err := f.parsePost(buf, numGlyphs)
if err != nil {
return err
}
f.cached.ascent = ascent
f.cached.capHeight = capHeight
f.cached.glyphData = glyphData
f.cached.glyphIndex = glyphIndex
f.cached.bounds = bounds
@ -651,6 +659,7 @@ func (f *Font) initialize(offset int, isDfont bool) error {
f.cached.numHMetrics = numHMetrics
f.cached.postTableVersion = postTableVersion
f.cached.unitsPerEm = unitsPerEm
f.cached.xHeight = xHeight
return nil
}
@ -1045,6 +1054,24 @@ func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isP
return buf, ret, isColorBitmap, nil
}
func (f *Font) parseOS2(buf []byte) (buf1 []byte, xHeight, capHeight int32, err error) {
// https://docs.microsoft.com/da-dk/typography/opentype/spec/os2
const headerSize = 96
if f.os2.length < headerSize {
return nil, 0, 0, errInvalidOS2Table
}
xh, err := f.src.u16(buf, f.os2, 86)
if err != nil {
return nil, 0, 0, err
}
ch, err := f.src.u16(buf, f.os2, 88)
if err != nil {
return nil, 0, 0, err
}
return buf, int32(int16(xh)), int32(int16(ch)), nil
}
func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, postTableVersion uint32, err error) {
// https://www.microsoft.com/typography/otspec/post.htm
@ -1360,12 +1387,16 @@ func (f *Font) Metrics(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (font.Metr
Height: scale(fixed.Int26_6(f.cached.ascent-f.cached.descent+f.cached.lineGap)*ppem, f.cached.unitsPerEm),
Ascent: +scale(fixed.Int26_6(f.cached.ascent)*ppem, f.cached.unitsPerEm),
Descent: -scale(fixed.Int26_6(f.cached.descent)*ppem, f.cached.unitsPerEm),
XHeight: scale(fixed.Int26_6(f.cached.xHeight)*ppem, f.cached.unitsPerEm),
CapHeight: scale(fixed.Int26_6(f.cached.capHeight)*ppem, f.cached.unitsPerEm),
}
if h == font.HintingFull {
// Quantize up to a whole pixel.
m.Height = (m.Height + 63) &^ 63
m.Ascent = (m.Ascent + 63) &^ 63
m.Descent = (m.Descent + 63) &^ 63
m.XHeight = (m.XHeight + 63) &^ 63
m.CapHeight = (m.CapHeight + 63) &^ 63
}
return m, nil
}

View File

@ -223,9 +223,9 @@ func TestMetrics(t *testing.T) {
font []byte
want font.Metrics
}{
"goregular": {goregular.TTF, font.Metrics{Height: 2367, Ascent: 1935, Descent: 432}},
"goregular": {goregular.TTF, font.Metrics{Height: 2367, Ascent: 1935, Descent: 432, XHeight: 1086, CapHeight: 1480}},
// cmapTest.ttf has a non-zero lineGap.
"cmapTest": {cmapFont, font.Metrics{Height: 1549, Ascent: 1365, Descent: 0}},
"cmapTest": {cmapFont, font.Metrics{Height: 1549, Ascent: 1365, Descent: 0, XHeight: 800, CapHeight: 800}},
}
var b Buffer
for name, tc := range testCases {