opentype.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 // Package opentype implements text layout and shaping for OpenType
4 // files.
5 package opentype
6
7 import (
8 "bytes"
9 "io"
10 "unicode"
11 "unicode/utf8"
12
13 "golang.org/x/image/font"
14 "golang.org/x/image/font/sfnt"
15 "golang.org/x/image/math/fixed"
16
17 "github.com/p9c/p9/pkg/gel/gio/f32"
18 "github.com/p9c/p9/pkg/gel/gio/op"
19 "github.com/p9c/p9/pkg/gel/gio/op/clip"
20 "github.com/p9c/p9/pkg/gel/gio/text"
21 )
22
23 // Font implements text.Face. Its methods are safe to use
24 // concurrently.
25 type Font struct {
26 font *sfnt.Font
27 }
28
29 // Collection is a collection of one or more fonts. When used as a text.Face,
30 // each rune will be assigned a glyph from the first font in the collection
31 // that supports it.
32 type Collection struct {
33 fonts []*opentype
34 }
35
36 type opentype struct {
37 Font *sfnt.Font
38 Hinting font.Hinting
39 }
40
41 // a glyph represents a rune and its advance according to a Font.
42 // TODO: remove this type and work on io.Readers directly.
43 type glyph struct {
44 Rune rune
45 Advance fixed.Int26_6
46 }
47
48 // NewFont parses an SFNT font, such as TTF or OTF data, from a []byte
49 // data source.
50 func Parse(src []byte) (*Font, error) {
51 fnt, err := sfnt.Parse(src)
52 if err != nil {
53 return nil, err
54 }
55 return &Font{font: fnt}, nil
56 }
57
58 // ParseCollection parses an SFNT font collection, such as TTC or OTC data,
59 // from a []byte data source.
60 //
61 // If passed data for a single font, a TTF or OTF instead of a TTC or OTC,
62 // it will return a collection containing 1 font.
63 func ParseCollection(src []byte) (*Collection, error) {
64 c, err := sfnt.ParseCollection(src)
65 if err != nil {
66 return nil, err
67 }
68 return newCollectionFrom(c)
69 }
70
71 // ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data,
72 // from an io.ReaderAt data source.
73 //
74 // If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
75 // will return a collection containing 1 font.
76 func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
77 c, err := sfnt.ParseCollectionReaderAt(src)
78 if err != nil {
79 return nil, err
80 }
81 return newCollectionFrom(c)
82 }
83
84 func newCollectionFrom(coll *sfnt.Collection) (*Collection, error) {
85 fonts := make([]*opentype, coll.NumFonts())
86 for i := range fonts {
87 fnt, err := coll.Font(i)
88 if err != nil {
89 return nil, err
90 }
91 fonts[i] = &opentype{
92 Font: fnt,
93 Hinting: font.HintingFull,
94 }
95 }
96 return &Collection{fonts: fonts}, nil
97 }
98
99 // NumFonts returns the number of fonts in the collection.
100 func (c *Collection) NumFonts() int {
101 return len(c.fonts)
102 }
103
104 // Font returns the i'th font in the collection.
105 func (c *Collection) Font(i int) (*Font, error) {
106 if i < 0 || len(c.fonts) <= i {
107 return nil, sfnt.ErrNotFound
108 }
109 return &Font{font: c.fonts[i].Font}, nil
110 }
111
112 func (f *Font) Layout(ppem fixed.Int26_6, maxWidth int, txt io.Reader) ([]text.Line, error) {
113 glyphs, err := readGlyphs(txt)
114 if err != nil {
115 return nil, err
116 }
117 fonts := []*opentype{{Font: f.font, Hinting: font.HintingFull}}
118 var buf sfnt.Buffer
119 return layoutText(&buf, ppem, maxWidth, fonts, glyphs)
120 }
121
122 func (f *Font) Shape(ppem fixed.Int26_6, str text.Layout) op.CallOp {
123 var buf sfnt.Buffer
124 return textPath(&buf, ppem, []*opentype{{Font: f.font, Hinting: font.HintingFull}}, str)
125 }
126
127 func (f *Font) Metrics(ppem fixed.Int26_6) font.Metrics {
128 o := &opentype{Font: f.font, Hinting: font.HintingFull}
129 var buf sfnt.Buffer
130 return o.Metrics(&buf, ppem)
131 }
132
133 func (c *Collection) Layout(ppem fixed.Int26_6, maxWidth int, txt io.Reader) ([]text.Line, error) {
134 glyphs, err := readGlyphs(txt)
135 if err != nil {
136 return nil, err
137 }
138 var buf sfnt.Buffer
139 return layoutText(&buf, ppem, maxWidth, c.fonts, glyphs)
140 }
141
142 func (c *Collection) Shape(ppem fixed.Int26_6, str text.Layout) op.CallOp {
143 var buf sfnt.Buffer
144 return textPath(&buf, ppem, c.fonts, str)
145 }
146
147 func fontForGlyph(buf *sfnt.Buffer, fonts []*opentype, r rune) *opentype {
148 if len(fonts) < 1 {
149 return nil
150 }
151 for _, f := range fonts {
152 if f.HasGlyph(buf, r) {
153 return f
154 }
155 }
156 return fonts[0] // Use replacement character from the first font if necessary
157 }
158
159 func layoutText(sbuf *sfnt.Buffer, ppem fixed.Int26_6, maxWidth int, fonts []*opentype, glyphs []glyph) ([]text.Line, error) {
160 var lines []text.Line
161 var nextLine text.Line
162 updateBounds := func(f *opentype) {
163 m := f.Metrics(sbuf, ppem)
164 if m.Ascent > nextLine.Ascent {
165 nextLine.Ascent = m.Ascent
166 }
167 // m.Height is equal to m.Ascent + m.Descent + linegap.
168 // Compute the descent including the linegap.
169 descent := m.Height - m.Ascent
170 if descent > nextLine.Descent {
171 nextLine.Descent = descent
172 }
173 b := f.Bounds(sbuf, ppem)
174 nextLine.Bounds = nextLine.Bounds.Union(b)
175 }
176 maxDotX := fixed.I(maxWidth)
177 type state struct {
178 r rune
179 f *opentype
180 adv fixed.Int26_6
181 x fixed.Int26_6
182 idx int
183 len int
184 valid bool
185 }
186 var prev, word state
187 endLine := func() {
188 if prev.f == nil && len(fonts) > 0 {
189 prev.f = fonts[0]
190 }
191 updateBounds(prev.f)
192 nextLine.Layout = toLayout(glyphs[:prev.idx:prev.idx])
193 nextLine.Width = prev.x + prev.adv
194 nextLine.Bounds.Max.X += prev.x
195 lines = append(lines, nextLine)
196 glyphs = glyphs[prev.idx:]
197 nextLine = text.Line{}
198 prev = state{}
199 word = state{}
200 }
201 for prev.idx < len(glyphs) {
202 g := &glyphs[prev.idx]
203 next := state{
204 r: g.Rune,
205 f: fontForGlyph(sbuf, fonts, g.Rune),
206 idx: prev.idx + 1,
207 len: prev.len + utf8.RuneLen(g.Rune),
208 x: prev.x + prev.adv,
209 }
210 if next.f != nil {
211 if next.f != prev.f {
212 updateBounds(next.f)
213 }
214 next.adv, next.valid = next.f.GlyphAdvance(sbuf, ppem, g.Rune)
215 }
216 if g.Rune == '\n' {
217 // The newline is zero width; use the previous
218 // character for line measurements.
219 prev.idx = next.idx
220 prev.len = next.len
221 endLine()
222 continue
223 }
224 var k fixed.Int26_6
225 if prev.valid && next.f != nil {
226 k = next.f.Kern(sbuf, ppem, prev.r, next.r)
227 }
228 // Break the line if we're out of space.
229 if prev.idx > 0 && next.x+next.adv+k > maxDotX {
230 // If the line contains no word breaks, break off the last rune.
231 if word.idx == 0 {
232 word = prev
233 }
234 next.x -= word.x + word.adv
235 next.idx -= word.idx
236 next.len -= word.len
237 prev = word
238 endLine()
239 } else if k != 0 {
240 glyphs[prev.idx-1].Advance += k
241 next.x += k
242 }
243 g.Advance = next.adv
244 if unicode.IsSpace(g.Rune) {
245 word = next
246 }
247 prev = next
248 }
249 endLine()
250 return lines, nil
251 }
252
253 // toLayout converts a slice of glyphs to a text.Layout.
254 func toLayout(glyphs []glyph) text.Layout {
255 var buf bytes.Buffer
256 advs := make([]fixed.Int26_6, len(glyphs))
257 for i, g := range glyphs {
258 buf.WriteRune(g.Rune)
259 advs[i] = glyphs[i].Advance
260 }
261 return text.Layout{Text: buf.String(), Advances: advs}
262 }
263
264 func textPath(buf *sfnt.Buffer, ppem fixed.Int26_6, fonts []*opentype, str text.Layout) op.CallOp {
265 var lastPos f32.Point
266 var builder clip.Path
267 ops := new(op.Ops)
268 m := op.Record(ops)
269 var x fixed.Int26_6
270 builder.Begin(ops)
271 rune := 0
272 for _, r := range str.Text {
273 if !unicode.IsSpace(r) {
274 f := fontForGlyph(buf, fonts, r)
275 if f == nil {
276 continue
277 }
278 segs, ok := f.LoadGlyph(buf, ppem, r)
279 if !ok {
280 continue
281 }
282 // Move to glyph position.
283 pos := f32.Point{
284 X: float32(x) / 64,
285 }
286 builder.Move(pos.Sub(lastPos))
287 lastPos = pos
288 var lastArg f32.Point
289 // Convert sfnt.Segments to relative segments.
290 for _, fseg := range segs {
291 nargs := 1
292 switch fseg.Op {
293 case sfnt.SegmentOpQuadTo:
294 nargs = 2
295 case sfnt.SegmentOpCubeTo:
296 nargs = 3
297 }
298 var args [3]f32.Point
299 for i := 0; i < nargs; i++ {
300 a := f32.Point{
301 X: float32(fseg.Args[i].X) / 64,
302 Y: float32(fseg.Args[i].Y) / 64,
303 }
304 args[i] = a.Sub(lastArg)
305 if i == nargs-1 {
306 lastArg = a
307 }
308 }
309 switch fseg.Op {
310 case sfnt.SegmentOpMoveTo:
311 builder.Move(args[0])
312 case sfnt.SegmentOpLineTo:
313 builder.Line(args[0])
314 case sfnt.SegmentOpQuadTo:
315 builder.Quad(args[0], args[1])
316 case sfnt.SegmentOpCubeTo:
317 builder.Cube(args[0], args[1], args[2])
318 default:
319 panic("unsupported segment op")
320 }
321 }
322 lastPos = lastPos.Add(lastArg)
323 }
324 x += str.Advances[rune]
325 rune++
326 }
327 clip.Outline{
328 Path: builder.End(),
329 }.Op().Add(ops)
330 return m.Stop()
331 }
332
333 func readGlyphs(r io.Reader) ([]glyph, error) {
334 var glyphs []glyph
335 buf := make([]byte, 0, 1024)
336 for {
337 n, err := r.Read(buf[len(buf):cap(buf)])
338 buf = buf[:len(buf)+n]
339 lim := len(buf)
340 // Read full runes if possible.
341 if err != io.EOF {
342 lim -= utf8.UTFMax - 1
343 }
344 i := 0
345 for i < lim {
346 c, s := utf8.DecodeRune(buf[i:])
347 i += s
348 glyphs = append(glyphs, glyph{Rune: c})
349 }
350 n = copy(buf, buf[i:])
351 buf = buf[:n]
352 if err != nil {
353 if err == io.EOF {
354 break
355 }
356 return nil, err
357 }
358 }
359 return glyphs, nil
360 }
361
362 func (f *opentype) HasGlyph(buf *sfnt.Buffer, r rune) bool {
363 g, err := f.Font.GlyphIndex(buf, r)
364 return g != 0 && err == nil
365 }
366
367 func (f *opentype) GlyphAdvance(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) (advance fixed.Int26_6, ok bool) {
368 g, err := f.Font.GlyphIndex(buf, r)
369 if err != nil {
370 return 0, false
371 }
372 adv, err := f.Font.GlyphAdvance(buf, g, ppem, f.Hinting)
373 return adv, err == nil
374 }
375
376 func (f *opentype) Kern(buf *sfnt.Buffer, ppem fixed.Int26_6, r0, r1 rune) fixed.Int26_6 {
377 g0, err := f.Font.GlyphIndex(buf, r0)
378 if err != nil {
379 return 0
380 }
381 g1, err := f.Font.GlyphIndex(buf, r1)
382 if err != nil {
383 return 0
384 }
385 adv, err := f.Font.Kern(buf, g0, g1, ppem, f.Hinting)
386 if err != nil {
387 return 0
388 }
389 return adv
390 }
391
392 func (f *opentype) Metrics(buf *sfnt.Buffer, ppem fixed.Int26_6) font.Metrics {
393 m, _ := f.Font.Metrics(buf, ppem, f.Hinting)
394 return m
395 }
396
397 func (f *opentype) Bounds(buf *sfnt.Buffer, ppem fixed.Int26_6) fixed.Rectangle26_6 {
398 r, _ := f.Font.Bounds(buf, ppem, f.Hinting)
399 return r
400 }
401
402 func (f *opentype) LoadGlyph(buf *sfnt.Buffer, ppem fixed.Int26_6, r rune) ([]sfnt.Segment, bool) {
403 g, err := f.Font.GlyphIndex(buf, r)
404 if err != nil {
405 return nil, false
406 }
407 segs, err := f.Font.LoadGlyph(buf, g, ppem, nil)
408 if err != nil {
409 return nil, false
410 }
411 return segs, true
412 }
413