-
-
Notifications
You must be signed in to change notification settings - Fork 662
/
text.go
256 lines (213 loc) · 8.58 KB
/
text.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// Copyright 2023 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package text offers functions to draw texts on an Ebitengine's image.
//
// For the example using a TrueType font, see examples in the examples directory.
package text
import (
"strings"
"golang.org/x/image/math/fixed"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
)
// Face is an interface representing a font face.
// The implementations are only faces defined in this package, like GoTextFace and GoXFace.
type Face interface {
// Metrics returns the metrics for this Face.
Metrics() Metrics
advance(text string) float64
hasGlyph(r rune) bool
appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph
appendVectorPathForLine(path *vector.Path, line string, originX, originY float64)
direction() Direction
// private is an unexported function preventing being implemented by other packages.
private()
}
// Metrics holds the metrics for a Face.
// A visual depiction is at https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
type Metrics struct {
// HLineGap is the recommended amount of vertical space between two lines of text in pixels.
HLineGap float64
// HAscent is the distance in pixels from the top of a line to its baseline for horizontal lines.
HAscent float64
// HDescent is the distance in pixels from the bottom of a line to its baseline for horizontal lines.
// The value is typically positive, even though a descender goes below the baseline.
HDescent float64
// VLineGap is the recommended amount of horizontal space between two lines of text in pixels.
// If the face is GoXFace or the font doesn't support a vertical direction, VLineGap is 0.
VLineGap float64
// VAscent 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, VAscent is 0.
VAscent float64
// 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
}
func fixed26_6ToFloat32(x fixed.Int26_6) float32 {
return float32(x) / (1 << 6)
}
func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
return float64(x) / (1 << 6)
}
func float32ToFixed26_6(x float32) fixed.Int26_6 {
return fixed.Int26_6(x * (1 << 6))
}
func float64ToFixed26_6(x float64) fixed.Int26_6 {
return fixed.Int26_6(x * (1 << 6))
}
func glyphVariationCount(face Face) int {
var s float64
if m := face.Metrics(); face.direction().isHorizontal() {
s = m.HAscent + m.HDescent
} else {
s = m.VAscent + m.VDescent
}
// The threshold is decided based on the rendering result of the examples (e.g. examples/text, examples/ui).
if s < 20 {
return 8
}
if s < 40 {
return 4
}
if s < 80 {
return 2
}
return 1
}
func adjustGranularity(x fixed.Int26_6, face Face) fixed.Int26_6 {
c := glyphVariationCount(face)
factor := (1 << 6) / fixed.Int26_6(c)
return x / factor * factor
}
// Glyph represents one glyph to render.
type Glyph struct {
// StartIndexInBytes is the start index in bytes for the given string at AppendGlyphs.
StartIndexInBytes int
// EndIndexInBytes is the end index in bytes for the given string at AppendGlyphs.
EndIndexInBytes int
// GID is an ID for a glyph of TrueType or OpenType font. GID is valid when the face is GoTextFace.
GID uint32
// Image is a rasterized glyph image.
// Image is a grayscale image i.e. RGBA values are the same.
// Image should be used as a render source and should not be modified.
Image *ebiten.Image
// X is the X position to render this glyph.
// The position is determined in a sequence of characters given at AppendGlyphs.
// The position's origin is the first character's origin position.
X float64
// Y is the Y position to render this glyph.
// The position is determined in a sequence of characters given at AppendGlyphs.
// The position's origin is the first character's origin position.
Y float64
}
// Advance returns the advanced distance from the origin position when rendering the given text with the given face.
//
// Advance doesn't treat multiple lines.
//
// Advance is concurrent-safe.
func Advance(text string, face Face) float64 {
return face.advance(text)
}
// Direction represents a direction of text rendering.
// Direction indicates both the primary direction, in which a text in one line is rendered,
// and the secondary direction, in which multiple lines are rendered.
type Direction int
const (
// DirectionLeftToRight indicates that the primary direction is from left to right,
// and the secondary direction is from top to bottom.
DirectionLeftToRight Direction = iota
// DirectionRightToLeft indicates that the primary direction is from right to left,
// and the secondary direction is from top to bottom.
DirectionRightToLeft
// DirectionTopToBottomAndLeftToRight indicates that the primary direction is from top to bottom,
// and the secondary direction is from left to right.
// This is used e.g. for Mongolian.
DirectionTopToBottomAndLeftToRight
// DirectionTopToBottomAndRightToLeft indicates that the primary direction is from top to bottom,
// and the secondary direction is from right to left.
// This is used e.g. for Japanese.
DirectionTopToBottomAndRightToLeft
)
func (d Direction) isHorizontal() bool {
switch d {
case DirectionLeftToRight, DirectionRightToLeft:
return true
}
return false
}
// Measure measures the boundary size of the text.
// With a horizontal direction face, the width is the longest line's advance, and the height is the total of line heights.
// With a vertical direction face, the width and the height are calculated in an opposite manner.
//
// Measure is concurrent-safe.
func Measure(text string, face Face, lineSpacingInPixels float64) (width, height float64) {
if text == "" {
return 0, 0
}
var primary float64
var lineCount int
for t := text; ; {
lineCount++
line, rest, found := strings.Cut(t, "\n")
a := face.advance(line)
if primary < a {
primary = a
}
if !found {
break
}
t = rest
}
m := face.Metrics()
if face.direction().isHorizontal() {
secondary := float64(lineCount-1)*lineSpacingInPixels + m.HAscent + m.HDescent
return primary, secondary
}
secondary := float64(lineCount-1)*lineSpacingInPixels + m.VAscent + m.VDescent
return secondary, primary
}
// CacheGlyphs pre-caches the glyphs for the given text and the given font face into the cache.
//
// CacheGlyphs doesn't treat multiple lines.
//
// Glyphs used for rendering are cached in the least-recently-used way.
// Then old glyphs might be evicted from the cache.
// As the cache capacity has limitations, it is not guaranteed that all the glyphs for runes given at CacheGlyphs are cached.
// The cache is shared with Draw and AppendGlyphs.
//
// One rune can have multiple variations of glyphs due to sub-pixels in X or Y direction.
// CacheGlyphs creates all such variations for one rune, while Draw and AppendGlyphs create only necessary glyphs.
//
// Draw and AppendGlyphs automatically create and cache necessary glyphs, so usually you don't have to call CacheGlyphs explicitly.
// However, for example, when you call Draw for each rune of one big text, Draw tries to create the glyph cache and render it for each rune.
// This is very inefficient because creating a glyph image and rendering it are different operations
// (`(*ebiten.Image).WritePixels` and `(*ebiten.Image).DrawImage`) and can never be merged as one draw call.
// CacheGlyphs creates necessary glyphs without rendering them so that these operations are likely merged into one draw call regardless of the size of the text.
//
// CacheGlyphs is concurrent-safe.
func CacheGlyphs(text string, face Face) {
var x, y float64
c := glyphVariationCount(face)
var buf []Glyph
// Create all the possible variations (#2528).
for i := 0; i < c; i++ {
buf = appendGlyphs(buf, text, face, x, y, nil)
buf = buf[:0]
if face.direction().isHorizontal() {
x += 1.0 / float64(c)
} else {
y += 1.0 / float64(c)
}
}
}