forked from gioui/gio
-
Notifications
You must be signed in to change notification settings - Fork 0
/
opentype.go
138 lines (126 loc) · 3.62 KB
/
opentype.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// SPDX-License-Identifier: Unlicense OR MIT
// Package opentype implements text layout and shaping for OpenType
// files.
package opentype
import (
"bytes"
"fmt"
"image"
"io"
"github.com/benoitkugler/textlayout/fonts"
"github.com/benoitkugler/textlayout/fonts/truetype"
"github.com/benoitkugler/textlayout/harfbuzz"
"github.com/go-text/typesetting/shaping"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
"github.com/xiaoshengduan/gio-fly/f32"
"github.com/xiaoshengduan/gio-fly/font/opentype/internal"
"github.com/xiaoshengduan/gio-fly/io/system"
"github.com/xiaoshengduan/gio-fly/op"
"github.com/xiaoshengduan/gio-fly/op/clip"
"github.com/xiaoshengduan/gio-fly/text"
)
// Font implements the text.Shaper interface using a rich text
// shaping engine.
type Font struct {
font *truetype.Font
}
// Parse constructs a Font from source bytes.
func Parse(src []byte) (*Font, error) {
face, err := truetype.Parse(bytes.NewReader(src))
if err != nil {
return nil, fmt.Errorf("failed parsing truetype font: %w", err)
}
return &Font{
font: face,
}, nil
}
func (f *Font) Layout(ppem fixed.Int26_6, maxWidth int, lc system.Locale, txt io.RuneReader) ([]text.Line, error) {
return internal.Document(shaping.Shape, f.font, ppem, maxWidth, lc, txt), nil
}
func (f *Font) Shape(ppem fixed.Int26_6, str text.Layout) clip.PathSpec {
return textPath(ppem, f, str)
}
func (f *Font) Metrics(ppem fixed.Int26_6) font.Metrics {
metrics := font.Metrics{}
font := harfbuzz.NewFont(f.font)
font.XScale = int32(ppem.Ceil()) << 6
font.YScale = font.XScale
// Use any horizontal direction.
fontExtents := font.ExtentsForDirection(harfbuzz.LeftToRight)
ascender := fixed.I(int(fontExtents.Ascender * 64))
descender := fixed.I(int(fontExtents.Descender * 64))
gap := fixed.I(int(fontExtents.LineGap * 64))
metrics.Height = ascender + descender + gap
metrics.Ascent = ascender
metrics.Descent = descender
// These three are not readily available.
// TODO(whereswaldon): figure out how to get these values.
metrics.XHeight = ascender
metrics.CapHeight = ascender
metrics.CaretSlope = image.Pt(0, 1)
return metrics
}
func textPath(ppem fixed.Int26_6, font *Font, str text.Layout) clip.PathSpec {
var lastPos f32.Point
var builder clip.Path
ops := new(op.Ops)
var x fixed.Int26_6
builder.Begin(ops)
rune := 0
ppemInt := ppem.Round()
ppem16 := uint16(ppemInt)
scaleFactor := float32(ppemInt) / float32(font.font.Upem())
for _, g := range str.Glyphs {
advance := g.XAdvance
outline, ok := font.font.GlyphData(g.ID, ppem16, ppem16).(fonts.GlyphOutline)
if !ok {
continue
}
// Move to glyph position.
pos := f32.Point{
X: float32(x)/64 - float32(g.XOffset)/64,
Y: -float32(g.YOffset) / 64,
}
builder.Move(pos.Sub(lastPos))
lastPos = pos
var lastArg f32.Point
// Convert sfnt.Segments to relative segments.
for _, fseg := range outline.Segments {
nargs := 1
switch fseg.Op {
case fonts.SegmentOpQuadTo:
nargs = 2
case fonts.SegmentOpCubeTo:
nargs = 3
}
var args [3]f32.Point
for i := 0; i < nargs; i++ {
a := f32.Point{
X: fseg.Args[i].X * scaleFactor,
Y: -fseg.Args[i].Y * scaleFactor,
}
args[i] = a.Sub(lastArg)
if i == nargs-1 {
lastArg = a
}
}
switch fseg.Op {
case fonts.SegmentOpMoveTo:
builder.Move(args[0])
case fonts.SegmentOpLineTo:
builder.Line(args[0])
case fonts.SegmentOpQuadTo:
builder.Quad(args[0], args[1])
case fonts.SegmentOpCubeTo:
builder.Cube(args[0], args[1], args[2])
default:
panic("unsupported segment op")
}
}
lastPos = lastPos.Add(lastArg)
x += advance
rune++
}
return builder.End()
}