This repository has been archived by the owner on Dec 17, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
shaper.go
114 lines (102 loc) · 3.57 KB
/
shaper.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
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
package shaping
import (
"fmt"
"github.com/benoitkugler/textlayout/fonts"
"github.com/benoitkugler/textlayout/harfbuzz"
"github.com/go-text/di"
"golang.org/x/image/math/fixed"
)
type Shaper interface {
// Shape takes an Input and shapes it into the Output.
Shape(Input) Output
}
// MissingGlyphError indicates that the font used in shaping did not
// have a glyph needed to complete the shaping.
type MissingGlyphError struct {
fonts.GID
}
func (m MissingGlyphError) Error() string {
return fmt.Sprintf("missing glyph with id %d", m.GID)
}
// InvalidRunError represents an invalid run of text, either because
// the end is before the start or because start or end is greater
// than the length.
type InvalidRunError struct {
RunStart, RunEnd, TextLength int
}
func (i InvalidRunError) Error() string {
return fmt.Sprintf("run from %d to %d is not valid for text len %d", i.RunStart, i.RunEnd, i.TextLength)
}
const (
// scaleShift is the power of 2 with which to automatically scale
// up the input coordinate space of the shaper. This factor will
// be removed prior to returning dimensions. This ensures that the
// returned glyph dimensions take advantage of all of the precision
// that a fixed.Int26_6 can provide.
scaleShift = 6
)
// Shape turns an input into an output.
func Shape(input Input) (Output, error) {
// Prepare to shape the text.
// TODO: maybe reuse these buffers for performance?
buf := harfbuzz.NewBuffer()
runes, start, end := input.Text, input.RunStart, input.RunEnd
if end < start {
return Output{}, InvalidRunError{RunStart: start, RunEnd: end, TextLength: len(input.Text)}
}
buf.AddRunes(runes, start, end-start)
// TODO: handle vertical text?
switch input.Direction {
case di.DirectionLTR:
buf.Props.Direction = harfbuzz.LeftToRight
case di.DirectionRTL:
buf.Props.Direction = harfbuzz.RightToLeft
default:
return Output{}, UnimplementedDirectionError{
Direction: input.Direction,
}
}
buf.Props.Language = input.Language
buf.Props.Script = input.Script
// TODO: figure out what (if anything) to do if this type assertion fails.
font := harfbuzz.NewFont(input.Face.(harfbuzz.Face))
font.XScale = int32(input.Size.Ceil()) << scaleShift
font.YScale = font.XScale
// Actually use harfbuzz to shape the text.
buf.Shape(font, nil)
// Convert the shaped text into an Output.
glyphs := make([]Glyph, len(buf.Info))
for i := range glyphs {
g := buf.Info[i].Glyph
extents, ok := font.GlyphExtents(g)
if !ok {
// TODO: can this error happen? Will harfbuzz return a
// GID for a glyph that isn't in the font?
return Output{}, MissingGlyphError{GID: g}
}
glyphs[i] = Glyph{
Width: fixed.I(int(extents.Width)) >> scaleShift,
Height: fixed.I(int(extents.Height)) >> scaleShift,
XBearing: fixed.I(int(extents.XBearing)) >> scaleShift,
YBearing: fixed.I(int(extents.YBearing)) >> scaleShift,
XAdvance: fixed.I(int(buf.Pos[i].XAdvance)) >> scaleShift,
YAdvance: fixed.I(int(buf.Pos[i].YAdvance)) >> scaleShift,
XOffset: fixed.I(int(buf.Pos[i].XOffset)) >> scaleShift,
YOffset: fixed.I(int(buf.Pos[i].YOffset)) >> scaleShift,
Cluster: buf.Info[i].Cluster,
Glyph: g,
Mask: buf.Info[i].Mask,
}
}
out := Output{
Glyphs: glyphs,
}
fontExtents := font.ExtentsForDirection(buf.Props.Direction)
out.LineBounds = Bounds{
Ascent: fixed.I(int(fontExtents.Ascender)) >> scaleShift,
Descent: fixed.I(int(fontExtents.Descender)) >> scaleShift,
Gap: fixed.I(int(fontExtents.LineGap)) >> scaleShift,
}
return out, out.RecalculateAll(input.Direction)
}