Skip to content

Commit

Permalink
text/v2: add Metrics.XHeight and Metrics.CapHeight
Browse files Browse the repository at this point in the history
Closes #3082
  • Loading branch information
hajimehoshi committed Sep 10, 2024
1 parent 26feb26 commit 813e3b2
Show file tree
Hide file tree
Showing 8 changed files with 437 additions and 5 deletions.
15 changes: 15 additions & 0 deletions text/v2/gotext.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ func (g *GoTextFace) Metrics() Metrics {
m.VDescent = float64(-v.Descender) * scale
}

m.XHeight = float64(g.Source.f.LineMetric(font.XHeight)) * scale
m.CapHeight = float64(g.Source.f.LineMetric(font.CapHeight)) * scale

// XHeight and CapHeight might not be correct for some old fonts (go-text/typesetting#169).
if m.XHeight <= 0 {
if _, gs := g.Source.shape("x", g); len(gs) > 0 {
m.XHeight = fixed26_6ToFloat64(-gs[0].bounds.Min.Y)
}
}
if m.CapHeight <= 0 {
if _, gs := g.Source.shape("H", g); len(gs) > 0 {
m.CapHeight = fixed26_6ToFloat64(-gs[0].bounds.Min.Y)
}
}

return m
}

Expand Down
28 changes: 23 additions & 5 deletions text/v2/gox.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ type GoXFace struct {

glyphImageCache glyphImageCache[goXFaceGlyphImageCacheKey]

cachedMetrics Metrics

addr *GoXFace
}

Expand All @@ -69,12 +71,28 @@ func (s *GoXFace) copyCheck() {
func (s *GoXFace) Metrics() Metrics {
s.copyCheck()

m := s.f.Metrics()
return Metrics{
HLineGap: fixed26_6ToFloat64(m.Height - m.Ascent - m.Descent),
HAscent: fixed26_6ToFloat64(m.Ascent),
HDescent: fixed26_6ToFloat64(m.Descent),
if s.cachedMetrics != (Metrics{}) {
return s.cachedMetrics
}

fm := s.f.Metrics()
m := Metrics{
HLineGap: fixed26_6ToFloat64(fm.Height - fm.Ascent - fm.Descent),
HAscent: fixed26_6ToFloat64(fm.Ascent),
HDescent: fixed26_6ToFloat64(fm.Descent),
XHeight: fixed26_6ToFloat64(fm.XHeight),
CapHeight: fixed26_6ToFloat64(fm.CapHeight),
}

// There is an issue that XHeight is negative for some old fonts (golang/go#69378).
if fm.XHeight < 0 {
m.XHeight *= -1
}
if fm.CapHeight < 0 {
m.CapHeight *= -1
}
s.cachedMetrics = m
return m
}

// UnsafeInternal returns its internal font.Face.
Expand Down
6 changes: 6 additions & 0 deletions text/v2/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ func (m *MultiFace) Metrics() Metrics {
if mt1.VDescent > mt.VDescent {
mt.VDescent = mt1.VDescent
}
if mt1.XHeight > mt.XHeight {
mt.XHeight = mt1.XHeight
}
if mt1.CapHeight > mt.CapHeight {
mt.CapHeight = mt1.CapHeight
}
}
return mt
}
Expand Down
310 changes: 310 additions & 0 deletions text/v2/testdata/LICENSE.md

Large diffs are not rendered by default.

Binary file added text/v2/testdata/MPLUS1p-Regular.ttf
Binary file not shown.
Binary file added text/v2/testdata/Roboto-Regular.ttf
Binary file not shown.
6 changes: 6 additions & 0 deletions text/v2/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ type Metrics struct {
// VDescent is the distance in pixels from the top of a line to its baseline for vertical lines.
// If the face is GoXFace or the font doesn't support a vertical direction, VDescent is 0.
VDescent float64

// XHeight is the distance in pixels from the baseline to the top of the lower case letters.
XHeight float64

// CapHeight is the distance in pixels from the baseline to the top of the capital letters.
CapHeight float64
}

func fixed26_6ToFloat32(x fixed.Int26_6) float32 {
Expand Down
77 changes: 77 additions & 0 deletions text/v2/text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@
package text_test

import (
"bytes"
"image"
"image/color"
"math"
"os"
"path/filepath"
"regexp"
"strings"
"testing"

"github.com/hajimehoshi/bitmapfont/v3"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"golang.org/x/image/math/fixed"

"github.com/hajimehoshi/ebiten/v2"
Expand Down Expand Up @@ -371,3 +376,75 @@ func TestDrawOptionsNotModified(t *testing.T) {
t.Errorf("got: %v, want: %v", got, want)
}
}

func TestGoXFaceMetrics(t *testing.T) {
const size = 100

fontFiles := []string{
// MPLUS1p-Regular.ttf is an old version of M+ 1p font, and this doesn't have metadata.
"MPLUS1p-Regular.ttf",
"Roboto-Regular.ttf",
}

for _, fontFile := range fontFiles {
fontFile := fontFile
t.Run(fontFile, func(t *testing.T) {
fontdata, err := os.ReadFile(filepath.Join("testdata", fontFile))
if err != nil {
t.Fatal(err)
}

sfntFont, err := opentype.Parse(fontdata)
if err != nil {
t.Fatal(err)
}
opentypeFace, err := opentype.NewFace(sfntFont, &opentype.FaceOptions{
Size: size,
DPI: 72,
})
if err != nil {
t.Fatal(err)
}
goXFace := text.NewGoXFace(opentypeFace)
goXMetrics := goXFace.Metrics()
if goXMetrics.XHeight <= 0 {
t.Errorf("GoXFace's XHeight must be positive but not: %f", goXMetrics.XHeight)
}
if goXMetrics.CapHeight <= 0 {
t.Errorf("GoXFace's CapHeight must be positive but not: %f", goXMetrics.CapHeight)
}

goTextFaceSource, err := text.NewGoTextFaceSource(bytes.NewBuffer(fontdata))
if err != nil {
t.Fatal(err)
}
goTextFace := &text.GoTextFace{
Source: goTextFaceSource,
Size: size,
}
goTextMetrics := goTextFace.Metrics()
if goTextMetrics.XHeight <= 0 {
t.Errorf("GoTextFace's XHeight must be positive but not: %f", goTextMetrics.XHeight)
}
if goTextMetrics.CapHeight <= 0 {
t.Errorf("GoTextFace's CapHeight must be positive but not: %f", goTextMetrics.CapHeight)
}

if math.Abs(goXMetrics.XHeight-goTextMetrics.XHeight) >= 0.1 {
t.Errorf("XHeight values don't match: %f (GoXFace) vs %f (GoTextFace)", goXMetrics.XHeight, goTextMetrics.XHeight)
}
if math.Abs(goXMetrics.CapHeight-goTextMetrics.CapHeight) >= 0.1 {
t.Errorf("CapHeight values don't match: %f (GoXFace) vs %f (GoTextFace)", goXMetrics.CapHeight, goTextMetrics.CapHeight)
}

// Check that a MultiFace should have the same metrics.
multiFace, err := text.NewMultiFace(goTextFace)
if err != nil {
t.Fatal(err)
}
if got := multiFace.Metrics(); got != goTextMetrics {
t.Errorf("got: %v, want: %v", got, goTextMetrics)
}
})
}
}

0 comments on commit 813e3b2

Please sign in to comment.