8000 font/opentype: implement Glyph and GlyphBounds · golang/image@e59bae6 · GitHub
[go: up one dir, main page]

Skip to content

Commit e59bae6

Browse files
committed
font/opentype: implement Glyph and GlyphBounds
This CL is based on Joe Blubaugh's work (golang.org/cl/240897). Thank you. Fixes golang/go#22451 Change-Id: I02e194b9e0a227128ff111cf9f40d6a569dfbd2c Reviewed-on: https://go-review.googlesource.com/c/image/+/255237 Run-TryBot: Hajime Hoshi <hajimehoshi@gmail.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: 8000 Nigel Tao <nigeltao@golang.org> Trust: David Symonds <dsymonds@golang.org>
1 parent 3a743ba commit e59bae6

File tree

2 files changed

+203
-26
lines changed

2 files changed

+203
-26
lines changed

font/opentype/face.go

Lines changed: 122 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ package opentype
66

77
import (
88
"image"
9+
"image/draw"
910

1011
"golang.org/x/image/font"
1112
"golang.org/x/image/font/sfnt"
1213
"golang.org/x/image/math/fixed"
14+
"golang.org/x/image/vector"
1315
)
1416

1517
// FaceOptions describes the possible options given to NewFace when
@@ -34,7 +36,12 @@ type Face struct {
3436
hinting font.Hinting
3537
scale fixed.Int26_6
3638

37-
buf sfnt.Buffer
39+
metrics font.Metrics
40+
metricsSet bool
41+
42+
buf sfnt.Buffer
43+
rast vector.Rasterizer
44+
mask image.Alpha
3845
}
3946

4047
// NewFace returns a new font.Face for the given sfnt.Font.
@@ -58,11 +65,15 @@ func (f *Face) Close() error {
5865

5966
// Metrics satisfies the font.Face interface.
6067
func (f *Face) Metrics() font.Metrics {
61-
m, err := f.f.Metrics(&f.buf, f.scale, f.hinting)
62-
if err != nil {
63-
return font.Metrics{}
68+
if !f.metricsSet {
69+
var err error
70+
f.metrics, err = f.f.Metrics(&f.buf, f.scale, f.hinting)
71+
if err != nil {
72+
f.metrics = font.Metrics{}
73+
}
74+
f.metricsSet = true
6475
}
65-
return m
76+
return f.metrics
6677
}
6778

6879
// Kern satisfies the font.Face interface.
@@ -78,22 +89,120 @@ func (f *Face) Kern(r0, r1 rune) fixed.Int26_6 {
7889

7990
// Glyph satisfies the font.Face interface.
8091
func (f *Face) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
81-
panic("not implemented")
92+
x, err := f.f.GlyphIndex(&f.buf, r)
93+
if err != nil {
94+
return image.Rectangle{}, nil, image.Point{}, 0, false
95+
}
96+
97+
segments, err := f.f.LoadGlyph(&f.buf, x, f.scale, nil)
98+
if err != nil {
99+
return image.Rectangle{}, nil, image.Point{}, 0, false
100+
}
101+
102+
bounds, advance, err := f.f.GlyphBounds(&f.buf, x, f.scale, f.hinting)
103+
if err != nil {
104+
return image.Rectangle{}, nil, image.Point{}, 0, false
105+
}
106+
107+
// Numerical notation used below:
108+
// - 2 is an integer, "two"
109+
// - 2:16 is a 26.6 fixed point number, "two and a quarter"
110+
// - 2.5 is a float32 number, "two and a half"
111+
// Using 26.6 fixed point numbers means that there are 64 sub-pixel units
112+
// in 1 integer pixel unit.
113+
// Translate the sub-pixel bounding box from glyph space (where the glyph
114+
// origin is at (0:00, 0:00)) to dst space (where the glyph origin is at
115+
// the dot). dst space is the coordinate space that contains both the dot
116+
// (a sub-pixel position) and dr (a pixel rectangle).
117+
dBounds := bounds.Add(dot)
118+
119+
// Quantize the sub-pixel bounds (dBounds) to integer-pixel bounds (dr).
120+
dr.Min.X = dBounds.Min.X.Floor()
121+
dr.Min.Y = dBounds.Min.Y.Floor()
122+
dr.Max.X = dBounds.Max.X.Ceil()
123+
dr.Max.Y = dBounds.Max.Y.Ceil()
124+
width := dr.Dx()
125+
height := dr.Dy()
126+
if width < 0 || height < 0 {
127+
return image.Rectangle{}, nil, image.Point{}, 0, false
128+
}
129+
130+
// Calculate the sub-pixel bias to convert from glyph space to rasterizer
131+
// space. In glyph space, the segments may be to the left or right and
132+
// above or below the glyph origin. In rasterizer space, the segments
133+
// should only be right and below (or equal to) the top-left corner (0.0,
134+
// 0.0). They should also be left and above (or equal to) the bottom-right
135+
// corner (width, height), as the rasterizer should enclose the glyph
136+
// bounding box.
137+
//
138+
// For example, suppose that dot.X was at the sub-pixel position 25:48,
139+
// three quarters of the way into the 26th pixel, and that bounds.Min.X was
140+
// 1:20. We then have dBounds.Min.X = 1:20 + 25:48 = 27:04, dr.Min.X = 27
141+
// and biasX = 25:48 - 27:00 = -1:16. A vertical stroke at 1:20 in glyph
142+
// space becomes (1:20 + -1:16) = 0:04 in rasterizer space. 0:04 as a
143+
// fixed.Int26_6 value is float32(4)/64.0 = 0.0625 as a float32 value.
144+
biasX := dot.X - fixed.Int26_6(dr.Min.X<<6)
145+
biasY := dot.Y - fixed.Int26_6(dr.Min.Y<<6)
146+
147+
// Configure the mask image, re-allocating its buffer if necessary.
148+
nPixels := width * height
149+
if cap(f.mask.Pix) < nPixels {
150+
f.mask.Pix = make([]uint8, 2*nPixels)
151+
}
152+
f.mask.Pix = f.mask.Pix[:nPixels]
153+
f.mask.Stride = width
154+
f.mask.Rect.Min.X = 0
155+
f.mask.Rect.Min.Y = 0
156+
f.mask.Rect.Max.X = width
157+
f.mask.Rect.Max.Y = height
158+
159+
// Rasterize the biased segments, converting from fixed.Int26_6 to float32.
160+
f.rast.Reset(width, height)
161+
f.rast.DrawOp = draw.Src
162+
for _, seg := range segments {
163+
switch seg.Op {
164+
case sfnt.SegmentOpMoveTo:
165+
f.rast.MoveTo(
166+
float32(seg.Args[0].X+biasX)/64,
167+
float32(seg.Args[0].Y+biasY)/64,
168+
)
169+
case sfnt.SegmentOpLineTo:
170+
f.rast.LineTo(
171+
float32(seg.Args[0].X+biasX)/64,
172+
float32(seg.Args[0].Y+biasY)/64,
173+
)
174+
case sfnt.SegmentOpQuadTo:
175+
f.rast.QuadTo(
176+
float32(seg.Args[0].X+biasX)/64,
177+
float32(seg.Args[0].Y+biasY)/64,
178+
float32(seg.Args[1].X+biasX)/64,
179+
float32(seg.Args[1].Y+biasY)/64,
180+
)
181+
case sfnt.SegmentOpCubeTo:
182+
f.rast.CubeTo(
183+
float32(seg.Args[0].X+biasX)/64,
184+
float32(seg.Args[0].Y+biasY)/64,
185+
float32(seg.Args[1].X+biasX)/64,
186+
float32(seg.Args[1].Y+biasY)/64,
187+
float32(seg.Args[2].X+biasX)/64,
188+
float32(seg.Args[2].Y+biasY)/64,
189+
)
190+
}
191+
}
192+
f.rast.Draw(&f.mask, f.mask.Bounds(), image.Opaque, image.Point{})
193+
194+
return dr, &f.mask, f.mask.Rect.Min, advance, true
82195
}
83196

84197
// GlyphBounds satisfies the font.Face interface.
85198
func (f *Face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
86-
advance, ok = f.GlyphAdvance(r)
87-
if !ok {
88-
return bounds, advance, ok
89-
}
90-
panic("not implemented")
199+
bounds, advance, err := f.f.GlyphBounds(&f.buf, f.index(r), f.scale, f.hinting)
200+
return bounds, advance, err == nil
91201
}
92202

93203
// GlyphAdvance satisfies the font.Face interface.
94204
func (f *Face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
95-
idx := f.index(r)
96-
advance, err := f.f.GlyphAdvance(&f.buf, idx, f.scale, f.hinting)
205+
advance, err := f.f.GlyphAdvance(&f.buf, f.index(r), f.scale, f.hinting)
97206
return advance, err == nil
98207
}
99208

font/opentype/face_test.go

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,96 @@ func init() {
3030
}
3131
}
3232

33+
var runeTests = []struct {
34+
r rune
35+
advance fixed.Int26_6
36+
dr image.Rectangle
37+
}{
38+
{' ', 213, image.Rect(0, 0, 0, 0)},
39+
{'A', 512, image.Rect(0, -9, 8, 0)},
40+
{'Á', 512, image.Rect(0, -12, 8, 0)},
41+
{'Æ', 768, image.Rect(0, -9, 12, 0)},
42+
{'i', 189, image.Rect(0, -9, 3, 0)},
43+
{'x', 384, image.Rect(0, -7, 6, 0)},
44+
}
45+
3346
func TestFaceGlyphAdvance(t *testing.T) {
34-
for _, test := range []struct {
35-
r rune
36-
want fixed.Int26_6
37-
}{
38-
{' ', 213},
39-
{'A', 512},
40-
{'Á', 512},
41-
{'Æ', 768},
42-
{'i', 189},
43-
{'x', 384},
44-
} {
47+
for _, test := range runeTests {
4548
got, ok := regular.GlyphAdvance(test.r)
4649
if !ok {
4750
t.Errorf("could not get glyph advance width for %q", test.r)
4851
continue
4952
}
5053

51-
if got != test.want {
52-
t.Errorf("%q: glyph advance width=%d. want=%d", test.r, got, test.want)
54+
if got != test.advance {
55+
t.Errorf("%q: glyph advance width=%d. want=%d", test.r, got, test.advance)
56+
continue
57+
}
58+
}
59+
}
60+
61+
func TestFaceGlyphBounds(t *testing.T) {
62+
for _, test := range runeTests {
63+
bounds, advance, ok := regular.GlyphBounds(test.r)
64+
if !ok {
65+
t.Errorf("could not get glyph bounds for %q", test.r)
66+
continue
67+
}
68+
69+
// bounds must fit inside the draw rect.
70+
testFixedBounds := fixed.R(test.dr.Min.X, test.dr.Min.Y,
71+
test.dr.Max.X, test.dr.Max.Y)
72+
if !bounds.In(testFixedBounds) {
73+
t.Errorf("%q: glyph bounds %v must be inside %v", test.r, bounds, testFixedBounds)
74+
continue
75+
}
76+
if advance != test.advance {
77+
t.Errorf("%q: glyph advance width=%d. want=%d", test.r, advance, test.advance)
78+
continue
79+
}
80+
}
81+
}
82+
83+
func TestFaceGlyph(t *testing.T) {
84+
dot := image.Pt(200, 500)
85+
fixedDot := fixed.P(dot.X, dot.Y)
86+
87+
for _, test := range runeTests {
88+
dr, mask, maskp, advance, ok := regular.Glyph(fixedDot, test.r)
89+
if !ok {
90+
t.Errorf("could not get glyph for %q", test.r)
5391
continue
5492
}
93+
if got, want := dr, test.dr.Add(dot); got != want {
94+
t.Errorf("%q: glyph draw rectangle=%d. want=%d", test.r, got, want)
95+
continue
96+
}
97+
if got, want := mask.Bounds(), image.Rect(0, 0, dr.Dx(), dr.Dy()); got != want {
98+
t.Errorf("%q: glyph mask rectangle=%d. want=%d", test.r, got, want)
99+
continue
100+
}
101+
if maskp != (image.Point{}) {
102+
t.Errorf("%q: glyph maskp=%d. want=%d", test.r, maskp, image.Point{})
103+
continue
104+
}
105+
if advance != test.advance {
106+
t.Errorf("%q: glyph advance width=%d. want=%d", test.r, advance, test.advance)
107+
continue
108+
}
109+
}
110+
}
111+
112+
func BenchmarkFaceGlyph(b *testing.B) {
113+
fixedDot := fixed.P(200, 500)
114+
r := 'A'
115+
116+
b.ReportAllocs()
117+
b.ResetTimer()
118+
for i := 0; i < b.N; i++ {
119+
_, _, _, _, ok := regular.Glyph(fixedDot, r)
120+
if !ok {
121+
b.Fatalf("could not get glyph for %q", r)
122+
}
55123
}
56124
}
57125

0 commit comments

Comments
 (0)
0