-
Notifications
You must be signed in to change notification settings - Fork 650
/
drawtext.go
512 lines (464 loc) · 16.8 KB
/
drawtext.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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
// 12 august 2018
package ui
// #include "pkgui.h"
import "C"
// Attribute stores information about an attribute in an
// AttributedString.
//
// The following types can be used as Attributes:
//
// - TextFamily
// - TextSize
// - TextWeight
// - TextItalic
// - TextStretch
// - TextColor
// - TextBackground
// - Underline
// - UnderlineColor
// - UnderlineColorCustom
// - OpenTypeFeatures
//
// For every Unicode codepoint in the AttributedString, at most one
// value of each attribute type can be applied.
type Attribute interface {
toLibui() *C.uiAttribute
}
// TextFamily is an Attribute that changes the font family of the text
// it is applied to. Font family names are case-insensitive.
type TextFamily string
func (f TextFamily) toLibui() *C.uiAttribute {
fstr := C.CString(string(f))
defer freestr(fstr)
return C.uiNewFamilyAttribute(fstr)
}
// TextSize is an Attribute that changes the size of the text it is
// applied to, in typographical points.
type TextSize float64
func (s TextSize) toLibui() *C.uiAttribute {
return C.uiNewSizeAttribute(C.double(s))
}
// TextWeight is an Attribute that changes the weight of the text
// it is applied to. These roughly map to the OS/2 text weight field
// of TrueType and OpenType fonts, or to CSS weight numbers. The
// named constants are nominal values; the actual values may vary
// by font and by OS, though this isn't particularly likely. Any value
// between TextWeightMinimum and TextWeightMaximum,
// inclusive, is allowed.
//
// Note that due to restrictions in early versions of Windows, some
// fonts have "special" weights be exposed in many programs as
// separate font families. This is perhaps most notable with
// Arial Black. Package ui does not do this, even on Windows
// (because the DirectWrite API libui uses on Windows does not do
// this); to specify Arial Black, use family Arial and weight
// TextWeightBlack.
type TextWeight int
const (
TextWeightMinimum TextWeight = 0
TextWeightThin TextWeight = 100
TextWeightUltraLight TextWeight = 200
TextWeightLight TextWeight = 300
TextWeightBook TextWeight = 350
TextWeightNormal TextWeight = 400
TextWeightMedium TextWeight = 500
TextWeightSemiBold TextWeight = 600
TextWeightBold TextWeight = 700
TextWeightUltraBold TextWeight = 800
TextWeightHeavy TextWeight = 900
TextWeightUltraHeavy TextWeight = 950
TextWeightMaximum TextWeight = 1000
)
func (w TextWeight) toLibui() *C.uiAttribute {
return C.uiNewWeightAttribute(C.uiTextWeight(w))
}
// TextItalic is an Attribute that changes the italic mode of the text
// it is applied to. Italic represents "true" italics where the slanted
// glyphs have custom shapes, whereas oblique represents italics
// that are merely slanted versions of the normal glyphs. Most fonts
// usually have one or the other.
type TextItalic int
const (
TextItalicNormal TextItalic = iota
TextItalicOblique
TextItalicItalic
)
func (i TextItalic) toLibui() *C.uiAttribute {
return C.uiNewItalicAttribute(C.uiTextItalic(i))
}
// TextStretch is an Attribute that changes the stretch (also called
// "width") of the text it is applied to.
//
// Note that due to restrictions in early versions of Windows, some
// fonts have "special" stretches be exposed in many programs as
// separate font families. This is perhaps most notable with
// Arial Condensed. Package ui does not do this, even on Windows
// (because the DirectWrite API package ui uses on Windows does
// not do this); to specify Arial Condensed, use family Arial and
// stretch TextStretchCondensed.
type TextStretch int
const (
TextStretchUltraCondensed TextStretch = iota
TextStretchExtraCondensed
TextStretchCondensed
TextStretchSemiCondensed
TextStretchNormal
TextStretchSemiExpanded
TextStretchExpanded
TextStretchExtraExpanded
TextStretchUltraExpanded
)
func (s TextStretch) toLibui() *C.uiAttribute {
return C.uiNewStretchAttribute(C.uiTextStretch(s))
}
// TextColor is an Attribute that changes the color of the text it is
// applied to.
type TextColor struct {
R float64
G float64
B float64
A float64
}
func (c TextColor) toLibui() *C.uiAttribute {
return C.uiNewColorAttribute(C.double(c.R), C.double(c.G), C.double(c.B), C.double(c.A))
}
// TextBackground is an Attribute that changes the background
// color of the text it is applied to.
type TextBackground struct {
R float64
G float64
B float64
A float64
}
func (b TextBackground) toLibui() *C.uiAttribute {
return C.uiNewBackgroundAttribute(C.double(b.R), C.double(b.G), C.double(b.B), C.double(b.A))
}
// Underline is an Attribute that specifies a type of underline to use
// on text.
type Underline int
const (
UnderlineNone Underline = iota
UnderlineSingle
UnderlineDouble
UnderlineSuggestion // wavy or dotted underlines used for spelling/grammar checkers
)
func (u Underline) toLibui() *C.uiAttribute {
return C.uiNewUnderlineAttribute(C.uiUnderline(u))
}
// UnderlineColor is an Attribute that changes the color of any
// underline on the text it is applied to, regardless of the type of
// underline. In addition to being able to specify the
// platform-specific colors for suggestion underlines here, you can
// also use a custom color with UnderlineColorCustom.
//
// To use the constants here correctly, pair them with
// UnderlineSuggestion (though they can be used on other types of
// underline as well).
//
// If an underline type is applied but no underline color is
// specified, the text color is used instead. If an underline color
// is specified without an underline type, the underline color
// attribute is ignored, but not removed from the uiAttributedString.
type UnderlineColor int
const (
UnderlineColorSpelling UnderlineColor = iota + 1
UnderlineColorGrammar
UnderlineColorAuxiliary // for instance, the color used by smart replacements on macOS or in Microsoft Office
)
func (u UnderlineColor) toLibui() *C.uiAttribute {
return C.uiNewUnderlineColorAttribute(C.uiUnderlineColor(u), 0, 0, 0, 0)
}
// UnderlineColorCustom is an Attribute like UnderlineColor, except
// it allows specifying a custom color.
type UnderlineColorCustom struct {
R float64
G float64
B float64
A float64
}
func (u UnderlineColorCustom) toLibui() *C.uiAttribute {
return C.uiNewUnderlineColorAttribute(C.uiUnderlineColorCustom, C.double(u.R), C.double(u.G), C.double(u.B), C.double(u.A))
}
// OpenTypeFeatures is an Attribute that represents a set of
// OpenType feature tag-value pairs, for applying OpenType
// features to text. OpenType feature tags are four-character codes
// defined by OpenType that cover things from design features like
// small caps and swashes to language-specific glyph shapes and
// beyond. Each tag may only appear once in any given
// uiOpenTypeFeatures instance. Each value is a 32-bit integer,
// often used as a Boolean flag, but sometimes as an index to choose
// a glyph shape to use.
//
// If a font does not support a certain feature, that feature will be
// ignored. (TODO verify this on all OSs)
//
// See the OpenType specification at
// https://www.microsoft.com/typography/otspec/featuretags.htm
// for the complete list of available features, information on specific
// features, and how to use them.
// TODO invalid features
//
// Note that if a feature is not present in a OpenTypeFeatures,
// the feature is NOT treated as if its value was zero, unlike in Go.
// Script-specific font shaping rules and font-specific feature
// settings may use a different default value for a feature. You
// should likewise NOT treat a missing feature as having a value of
// zero either. Instead, a missing feature should be treated as
// having some unspecified default value.
//
// Note that despite OpenTypeFeatures being a map, its contents
// are copied by AttributedString. Modifying an OpenTypeFeatures
// after giving it to an AttributedString, or modifying one that comes
// out of an AttributedString, will have no effect.
type OpenTypeFeatures map[OpenTypeTag]uint32
func (o OpenTypeFeatures) toLibui() *C.uiAttribute {
otf := C.uiNewOpenTypeFeatures()
defer C.uiFreeOpenTypeFeatures(otf)
for tag, value := range o {
a := byte((tag >> 24) & 0xFF)
b := byte((tag >> 16) & 0xFF)
c := byte((tag >> 8) & 0xFF)
d := byte(tag & 0xFF)
C.uiOpenTypeFeaturesAdd(otf, C.char(a), C.char(b), C.char(c), C.char(d), C.uint32_t(value))
}
return C.uiNewFeaturesAttribute(otf)
}
// OpenTypeTag represents a four-byte OpenType feature tag.
type OpenTypeTag uint32
// ToOpenTypeTag converts the four characters a, b, c, and d into
// an OpenTypeTag.
func ToOpenTypeTag(a, b, c, d byte) OpenTypeTag {
return (OpenTypeTag(a) << 24) |
(OpenTypeTag(b) << 16) |
(OpenTypeTag(c) << 8) |
OpenTypeTag(d)
}
func attributeFromLibui(a *C.uiAttribute) Attribute {
switch C.uiAttributeGetType(a) {
case C.uiAttributeTypeFamily:
cf := C.uiAttributeFamily(a)
return TextFamily(C.GoString(cf))
case C.uiAttributeTypeSize:
return TextSize(C.uiAttributeSize(a))
case C.uiAttributeTypeWeight:
return TextWeight(C.uiAttributeWeight(a))
case C.uiAttributeTypeItalic:
return TextItalic(C.uiAttributeItalic(a))
case C.uiAttributeTypeStretch:
return TextStretch(C.uiAttributeStretch(a))
case C.uiAttributeTypeColor:
cc := C.pkguiAllocColorDoubles()
defer C.pkguiFreeColorDoubles(cc)
C.uiAttributeColor(a, cc.r, cc.g, cc.b, cc.a)
return TextColor{
R: float64(*(cc.r)),
G: float64(*(cc.g)),
B: float64(*(cc.b)),
A: float64(*(cc.a)),
}
case C.uiAttributeTypeBackground:
cc := C.pkguiAllocColorDoubles()
defer C.pkguiFreeColorDoubles(cc)
C.uiAttributeColor(a, cc.r, cc.g, cc.b, cc.a)
return TextBackground{
R: float64(*(cc.r)),
G: float64(*(cc.g)),
B: float64(*(cc.b)),
A: float64(*(cc.a)),
}
case C.uiAttributeTypeUnderline:
return Underline(C.uiAttributeUnderline(a))
case C.uiAttributeTypeUnderlineColor:
cu := C.pkguiNewUnderlineColor()
defer C.pkguiFreeUnderlineColor(cu)
cc := C.pkguiAllocColorDoubles()
defer C.pkguiFreeColorDoubles(cc)
C.uiAttributeUnderlineColor(a, cu, cc.r, cc.g, cc.b, cc.a)
if *cu == C.uiUnderlineColorCustom {
return UnderlineColorCustom{
R: float64(*(cc.r)),
G: float64(*(cc.g)),
B: float64(*(cc.b)),
A: float64(*(cc.a)),
}
}
return UnderlineColor(*cu)
case C.uiAttributeTypeFeatures:
// TODO
}
panic("unreachable")
}
// AttributedString represents a string of UTF-8 text that can
// optionally be embellished with formatting attributes. Package ui
// provides the list of formatting attributes, which cover common
// formatting traits like boldface and color as well as advanced
// typographical features provided by OpenType like superscripts
// and small caps. These attributes can be combined in a variety of
// ways.
//
// Attributes are applied to runs of Unicode codepoints in the string.
// Zero-length runs are elided. Consecutive runs that have the same
// attribute type and value are merged. Each attribute is independent
// of each other attribute; overlapping attributes of different types
// do not split each other apart, but different values of the same
// attribute type do.
//
// The empty string can also be represented by AttributedString,
// but because of the no-zero-length-attribute rule, it will not have
// attributes.
//
// Unlike Go strings, AttributedStrings are mutable.
//
// AttributedString allocates resources within libui, which package
// ui sits on top of. As such, when you are finished with an
// AttributedString, you must free it with Free. Like other things in
// package ui, AttributedString must only be used from the main
// goroutine.
//
// In addition, AttributedString provides facilities for moving
// between grapheme clusters, which represent a character
// from the point of view of the end user. The cursor of a text editor
// is always placed on a grapheme boundary, so you can use these
// features to move the cursor left or right by one "character".
// TODO does uiAttributedString itself need this
//
// AttributedString does not provide enough information to be able
// to draw itself onto a DrawContext or respond to user actions.
// In order to do that, you'll need to use a DrawTextLayout, which
// is built from the combination of an AttributedString and a set of
// layout-specific properties.
type AttributedString struct {
s *C.uiAttributedString
}
// NewAttributedString creates a new AttributedString from
// initialString. The string will be entirely unattributed.
func NewAttributedString(initialString string) *AttributedString {
cs := C.CString(initialString)
defer freestr(cs)
return &AttributedString{
s: C.uiNewAttributedString(cs),
}
}
// Free destroys s.
func (s *AttributedString) Free() {
C.uiFreeAttributedString(s.s)
}
// String returns the textual content of s.
func (s *AttributedString) String() string {
return C.GoString(C.uiAttributedStringString(s.s))
}
// AppendUnattributed adds str to the end of s. The new substring
// will be unattributed.
func (s *AttributedString) AppendUnattributed(str string) {
cs := C.CString(str)
defer freestr(cs)
C.uiAttributedStringAppendUnattributed(s.s, cs)
}
// InsertAtUnattributed adds str to s at the byte position specified by
// at. The new substring will be unattributed; existing attributes will
// be moved along with their text.
func (s *AttributedString) InsertAtUnattributed(str string, at int) {
cs := C.CString(str)
defer freestr(cs)
C.uiAttributedStringInsertAtUnattributed(s.s, cs, C.size_t(at))
}
// Delete deletes the characters and attributes of s in the byte range
// [start, end).
func (s *AttributedString) Delete(start, end int) {
C.uiAttributedStringDelete(s.s, C.size_t(start), C.size_t(end))
}
// SetAttribute sets a in the byte range [start, end) of s. Any existing
// attributes in that byte range of the same type are removed.
func (s *AttributedString) SetAttribute(a Attribute, start, end int) {
C.uiAttributedStringSetAttribute(s.s, a.toLibui(), C.size_t(start), C.size_t(end))
}
// TODO uiAttributedStringForEachAttribute
// TODO uiAttributedStringNumGraphemes
// TODO uiAttributedStringByteIndexToGrapheme
// TODO uiAttributedStringGraphemeToByteIndex
// FontDescriptor provides a complete description of a font where
// one is needed. Currently, this means as the default font of a
// DrawTextLayout and as the data returned by FontButton.
type FontDescriptor struct {
Family TextFamily
Size TextSize
Weight TextWeight
Italic TextItalic
Stretch TextStretch
}
func (d *FontDescriptor) fromLibui(fd *C.uiFontDescriptor) {
d.Family = TextFamily(C.GoString(fd.Family))
d.Size = TextSize(fd.Size)
d.Weight = TextWeight(fd.Weight)
d.Italic = TextItalic(fd.Italic)
d.Stretch = TextStretch(fd.Stretch)
}
func (d *FontDescriptor) toLibui() *C.uiFontDescriptor {
fd := C.pkguiNewFontDescriptor()
fd.Family = C.CString(string(d.Family))
fd.Size = C.double(d.Size)
fd.Weight = C.uiTextWeight(d.Weight)
fd.Italic = C.uiTextItalic(d.Italic)
fd.Stretch = C.uiTextStretch(d.Stretch)
return fd
}
func freeLibuiFontDescriptor(fd *C.uiFontDescriptor) {
freestr(fd.Family)
C.pkguiFreeFontDescriptor(fd)
}
// DrawTextLayout is a concrete representation of an
// AttributedString that can be displayed in a DrawContext.
// It includes information important for the drawing of a block of
// text, including the bounding box to wrap the text within, the
// alignment of lines of text within that box, areas to mark as
// being selected, and other things.
//
// Unlike AttributedString, the content of a DrawTextLayout is
// immutable once it has been created.
//
// TODO talk about OS-specific differences with text drawing that libui can't account for...
type DrawTextLayout struct {
tl *C.uiDrawTextLayout
}
// DrawTextAlign specifies the alignment of lines of text in a
// DrawTextLayout.
// TODO should this really have Draw in the name?
type DrawTextAlign int
const (
DrawTextAlignLeft DrawTextAlign = iota
DrawTextAlignCenter
DrawTextAlignRight
)
// DrawTextLayoutParams describes a DrawTextLayout.
// DefaultFont is used to render any text that is not attributed
// sufficiently in String. Width determines the width of the bounding
// box of the text; the height is determined automatically.
type DrawTextLayoutParams struct {
String *AttributedString
DefaultFont *FontDescriptor
Width float64
Align DrawTextAlign
}
// DrawNewTextLayout() creates a new DrawTextLayout from
// the given parameters.
func DrawNewTextLayout(p *DrawTextLayoutParams) *DrawTextLayout {
dp := C.pkguiNewDrawTextLayoutParams()
defer C.pkguiFreeDrawTextLayoutParams(dp)
dp.String = p.String.s
dp.DefaultFont = p.DefaultFont.toLibui()
defer freeLibuiFontDescriptor(dp.DefaultFont)
dp.Width = C.double(p.Width)
dp.Align = C.uiDrawTextAlign(p.Align)
return &DrawTextLayout{
tl: C.uiDrawNewTextLayout(dp),
}
}
// Free frees tl. The underlying AttributedString is not freed.
func (tl *DrawTextLayout) Free() {
C.uiDrawFreeTextLayout(tl.tl)
}
// Text draws tl in c with the top-left point of tl at (x, y).
func (c *DrawContext) Text(tl *DrawTextLayout, x, y float64) {
C.uiDrawText(c.c, tl.tl, C.double(x), C.double(y))
}
// TODO uiDrawTextLayoutExtents