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 {