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>
diff --git a/font/basicfont/basicfont.go b/font/basicfont/basicfont.go
index 1acc79f..acd1179 100644
--- a/font/basicfont/basicfont.go
+++ b/font/basicfont/basicfont.go
@@ -77,9 +77,11 @@
func (f *Face) Metrics() font.Metrics {
return font.Metrics{
- Height: fixed.I(f.Height),
- Ascent: fixed.I(f.Ascent),
- Descent: fixed.I(f.Descent),
+ 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),
}
}
diff --git a/font/basicfont/basicfont_test.go b/font/basicfont/basicfont_test.go
new file mode 100644
index 0000000..f3409d7
--- /dev/null
+++ b/font/basicfont/basicfont_test.go
@@ -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)
+ }
+}
diff --git a/font/font.go b/font/font.go
index 05f4357..56a3310 100644
--- a/font/font.go
+++ b/font/font.go
@@ -86,6 +86,14 @@
// 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.
diff --git a/font/opentype/face_test.go b/font/opentype/face_test.go
index 524a7f6..8389bd8 100644
--- a/font/opentype/face_test.go
+++ b/font/opentype/face_test.go
@@ -82,7 +82,7 @@
}
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)
diff --git a/font/plan9font/plan9font.go b/font/plan9font/plan9font.go
index 315e793..82cd882 100644
--- a/font/plan9font/plan9font.go
+++ b/font/plan9font/plan9font.go
@@ -62,10 +62,16 @@
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),
+ 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) 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),
+ 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,
}
}
diff --git a/font/plan9font/plan9font_test.go b/font/plan9font/plan9font_test.go
index 23393a1..04a701d 100644
--- a/font/plan9font/plan9font_test.go
+++ b/font/plan9font/plan9font_test.go
@@ -6,10 +6,42 @@
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 {
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 21a7927..3f952e9 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -86,6 +86,7 @@
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 @@
cached struct {
ascent int32
+ capHeight int32
glyphData glyphData
glyphIndex glyphIndexFunc
bounds [4]int16
@@ -578,6 +580,7 @@
numHMetrics int32
postTableVersion uint32
unitsPerEm Units
+ xHeight int32
}
}
@@ -632,12 +635,17 @@
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 @@
f.cached.numHMetrics = numHMetrics
f.cached.postTableVersion = postTableVersion
f.cached.unitsPerEm = unitsPerEm
+ f.cached.xHeight = xHeight
return nil
}
@@ -1045,6 +1054,24 @@
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
@@ -1357,15 +1384,19 @@
// Metrics returns the metrics of this font.
func (f *Font) Metrics(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (font.Metrics, error) {
m := font.Metrics{
- 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),
+ 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
}
diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go
index ca52fba..f5ddd2b 100644
--- a/font/sfnt/sfnt_test.go
+++ b/font/sfnt/sfnt_test.go
@@ -223,9 +223,9 @@
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 {