glance-widgets-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +17 -0
- data/README.md +47 -0
- data/lib/glance_widgets/box.rb +117 -0
- data/lib/glance_widgets/color.rb +48 -0
- data/lib/glance_widgets/components/bar_chart.rb +65 -0
- data/lib/glance_widgets/components/battery.rb +77 -0
- data/lib/glance_widgets/components/charts.rb +5 -0
- data/lib/glance_widgets/components/heatmap.rb +184 -0
- data/lib/glance_widgets/components/line_chart.rb +213 -0
- data/lib/glance_widgets/components/modern.rb +59 -0
- data/lib/glance_widgets/components/pixel_bar_chart.rb +106 -0
- data/lib/glance_widgets/components/pixel_progress.rb +151 -0
- data/lib/glance_widgets/components/pixel_text.rb +66 -0
- data/lib/glance_widgets/components/progress.rb +62 -0
- data/lib/glance_widgets/components/ring_progress.rb +89 -0
- data/lib/glance_widgets/components/shared.rb +131 -0
- data/lib/glance_widgets/components/signal.rb +41 -0
- data/lib/glance_widgets/components/stat.rb +171 -0
- data/lib/glance_widgets/components/status.rb +102 -0
- data/lib/glance_widgets/data/fonts/font-3x5.json +120 -0
- data/lib/glance_widgets/data/fonts/font-5x7.json +118 -0
- data/lib/glance_widgets/data/icons.json +5293 -0
- data/lib/glance_widgets/data/schemas.json +418 -0
- data/lib/glance_widgets/data/text-advances.json +120 -0
- data/lib/glance_widgets/font.rb +165 -0
- data/lib/glance_widgets/geometry.rb +108 -0
- data/lib/glance_widgets/layers.rb +152 -0
- data/lib/glance_widgets/matrix.rb +277 -0
- data/lib/glance_widgets/options.rb +31 -0
- data/lib/glance_widgets/output.rb +91 -0
- data/lib/glance_widgets/pixelate.rb +46 -0
- data/lib/glance_widgets/protocol.rb +217 -0
- data/lib/glance_widgets/raster.rb +130 -0
- data/lib/glance_widgets/signature.rb +30 -0
- data/lib/glance_widgets/support.rb +134 -0
- data/lib/glance_widgets/themes.rb +34 -0
- data/lib/glance_widgets.rb +38 -0
- metadata +79 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pixel": {
|
|
3
|
+
"color": "color",
|
|
4
|
+
"opacity": "number",
|
|
5
|
+
"cornerRadius": "number",
|
|
6
|
+
"rotation": "number",
|
|
7
|
+
"halo": "number",
|
|
8
|
+
"empty": "boolean"
|
|
9
|
+
},
|
|
10
|
+
"layout": {
|
|
11
|
+
"gapMode": {
|
|
12
|
+
"enum": [
|
|
13
|
+
"zero",
|
|
14
|
+
"near",
|
|
15
|
+
"default",
|
|
16
|
+
"far",
|
|
17
|
+
"extraFar"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"edgeInsetRatio": "number",
|
|
21
|
+
"cellAspectRatio": "number",
|
|
22
|
+
"columnGapRatio": "number",
|
|
23
|
+
"rowGapRatio": "number"
|
|
24
|
+
},
|
|
25
|
+
"layers": {
|
|
26
|
+
"text": {
|
|
27
|
+
"content": {
|
|
28
|
+
"text": "textValue"
|
|
29
|
+
},
|
|
30
|
+
"style": {
|
|
31
|
+
"effectType": {
|
|
32
|
+
"enum": [
|
|
33
|
+
"none",
|
|
34
|
+
"mask",
|
|
35
|
+
"vignette",
|
|
36
|
+
"readability"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"backgroundColor": "color",
|
|
40
|
+
"opacity": "number",
|
|
41
|
+
"cornerRadiusRatio": "number",
|
|
42
|
+
"borderColor": "color",
|
|
43
|
+
"borderOpacity": "number",
|
|
44
|
+
"borderWidth": "number",
|
|
45
|
+
"gradientEnabled": "boolean",
|
|
46
|
+
"gradientColors": {
|
|
47
|
+
"arrayOf": "color"
|
|
48
|
+
},
|
|
49
|
+
"gradientStops": {
|
|
50
|
+
"arrayOf": "number"
|
|
51
|
+
},
|
|
52
|
+
"gradientAngle": "number",
|
|
53
|
+
"maskColor": "color",
|
|
54
|
+
"maskOpacity": "number",
|
|
55
|
+
"vignetteIntensity": "number",
|
|
56
|
+
"readabilityGradient": {
|
|
57
|
+
"enum": [
|
|
58
|
+
"none",
|
|
59
|
+
"top",
|
|
60
|
+
"bottom",
|
|
61
|
+
"left",
|
|
62
|
+
"right",
|
|
63
|
+
"both",
|
|
64
|
+
"topLeftDiagonal",
|
|
65
|
+
"topRightDiagonal"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"color": "color",
|
|
69
|
+
"textOpacity": "number",
|
|
70
|
+
"fontSizeRatio": "number",
|
|
71
|
+
"fontWeight": {
|
|
72
|
+
"enum": [
|
|
73
|
+
"300",
|
|
74
|
+
"400",
|
|
75
|
+
"500",
|
|
76
|
+
"600",
|
|
77
|
+
"700"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"fontFamily": {
|
|
81
|
+
"enum": [
|
|
82
|
+
"system",
|
|
83
|
+
"systemMonospace",
|
|
84
|
+
"monospace"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"fontFamilyFallback": {
|
|
88
|
+
"arrayOf": "string"
|
|
89
|
+
},
|
|
90
|
+
"letterSpacing": "number",
|
|
91
|
+
"baselineRatio": "number",
|
|
92
|
+
"horizontalAlignment": {
|
|
93
|
+
"enum": [
|
|
94
|
+
"left",
|
|
95
|
+
"center",
|
|
96
|
+
"right"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"verticalAlignment": {
|
|
100
|
+
"enum": [
|
|
101
|
+
"top",
|
|
102
|
+
"center",
|
|
103
|
+
"bottom"
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
"autoWrap": "boolean",
|
|
107
|
+
"paddingRatio": "number"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"line": {
|
|
111
|
+
"content": {
|
|
112
|
+
"direction": {
|
|
113
|
+
"enum": [
|
|
114
|
+
"horizontal",
|
|
115
|
+
"vertical"
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"style": {
|
|
120
|
+
"color": "color",
|
|
121
|
+
"opacity": "number",
|
|
122
|
+
"strokeWidth": "number",
|
|
123
|
+
"pattern": {
|
|
124
|
+
"enum": [
|
|
125
|
+
"solid",
|
|
126
|
+
"dashed",
|
|
127
|
+
"dotted"
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
"cap": {
|
|
131
|
+
"enum": [
|
|
132
|
+
"round",
|
|
133
|
+
"butt"
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
"dashLength": "number",
|
|
137
|
+
"dashGap": "number",
|
|
138
|
+
"gradientEnabled": "boolean",
|
|
139
|
+
"gradientColors": {
|
|
140
|
+
"arrayOf": "color"
|
|
141
|
+
},
|
|
142
|
+
"gradientStops": {
|
|
143
|
+
"arrayOf": "number"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"shape": {
|
|
148
|
+
"content": {},
|
|
149
|
+
"style": {
|
|
150
|
+
"effectType": {
|
|
151
|
+
"enum": [
|
|
152
|
+
"none",
|
|
153
|
+
"mask",
|
|
154
|
+
"vignette",
|
|
155
|
+
"readability"
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
"backgroundColor": "color",
|
|
159
|
+
"opacity": "number",
|
|
160
|
+
"cornerRadiusRatio": "number",
|
|
161
|
+
"borderColor": "color",
|
|
162
|
+
"borderOpacity": "number",
|
|
163
|
+
"borderWidth": "number",
|
|
164
|
+
"gradientEnabled": "boolean",
|
|
165
|
+
"gradientColors": {
|
|
166
|
+
"arrayOf": "color"
|
|
167
|
+
},
|
|
168
|
+
"gradientStops": {
|
|
169
|
+
"arrayOf": "number"
|
|
170
|
+
},
|
|
171
|
+
"gradientAngle": "number",
|
|
172
|
+
"maskColor": "color",
|
|
173
|
+
"maskOpacity": "number",
|
|
174
|
+
"vignetteIntensity": "number",
|
|
175
|
+
"readabilityGradient": {
|
|
176
|
+
"enum": [
|
|
177
|
+
"none",
|
|
178
|
+
"top",
|
|
179
|
+
"bottom",
|
|
180
|
+
"left",
|
|
181
|
+
"right",
|
|
182
|
+
"both",
|
|
183
|
+
"topLeftDiagonal",
|
|
184
|
+
"topRightDiagonal"
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
"color": "color"
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"icon": {
|
|
191
|
+
"content": {
|
|
192
|
+
"icon": "string",
|
|
193
|
+
"iconCodePoint": "integer",
|
|
194
|
+
"iconFont": {
|
|
195
|
+
"enum": [
|
|
196
|
+
"material",
|
|
197
|
+
"phosphorBold",
|
|
198
|
+
"devicon",
|
|
199
|
+
"simpleIcons"
|
|
200
|
+
]
|
|
201
|
+
},
|
|
202
|
+
"url": "string"
|
|
203
|
+
},
|
|
204
|
+
"style": {
|
|
205
|
+
"color": "color",
|
|
206
|
+
"textOpacity": "number",
|
|
207
|
+
"iconGlowEnabled": "boolean",
|
|
208
|
+
"iconGlowColor": "color",
|
|
209
|
+
"iconGlowOpacity": "number",
|
|
210
|
+
"iconGlowRadius": "number"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"image": {
|
|
214
|
+
"content": {
|
|
215
|
+
"url": "string"
|
|
216
|
+
},
|
|
217
|
+
"style": {
|
|
218
|
+
"effectType": {
|
|
219
|
+
"enum": [
|
|
220
|
+
"none",
|
|
221
|
+
"mask",
|
|
222
|
+
"vignette",
|
|
223
|
+
"readability"
|
|
224
|
+
]
|
|
225
|
+
},
|
|
226
|
+
"backgroundColor": "color",
|
|
227
|
+
"opacity": "number",
|
|
228
|
+
"cornerRadiusRatio": "number",
|
|
229
|
+
"borderColor": "color",
|
|
230
|
+
"borderOpacity": "number",
|
|
231
|
+
"borderWidth": "number",
|
|
232
|
+
"gradientEnabled": "boolean",
|
|
233
|
+
"gradientColors": {
|
|
234
|
+
"arrayOf": "color"
|
|
235
|
+
},
|
|
236
|
+
"gradientStops": {
|
|
237
|
+
"arrayOf": "number"
|
|
238
|
+
},
|
|
239
|
+
"gradientAngle": "number",
|
|
240
|
+
"maskColor": "color",
|
|
241
|
+
"maskOpacity": "number",
|
|
242
|
+
"vignetteIntensity": "number",
|
|
243
|
+
"readabilityGradient": {
|
|
244
|
+
"enum": [
|
|
245
|
+
"none",
|
|
246
|
+
"top",
|
|
247
|
+
"bottom",
|
|
248
|
+
"left",
|
|
249
|
+
"right",
|
|
250
|
+
"both",
|
|
251
|
+
"topLeftDiagonal",
|
|
252
|
+
"topRightDiagonal"
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
"color": "color"
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
"matrix": {
|
|
259
|
+
"content": {
|
|
260
|
+
"pixels": {
|
|
261
|
+
"arrayOf": {
|
|
262
|
+
"arrayOf": {
|
|
263
|
+
"object": {
|
|
264
|
+
"color": "color",
|
|
265
|
+
"opacity": "number",
|
|
266
|
+
"cornerRadius": "number",
|
|
267
|
+
"rotation": "number",
|
|
268
|
+
"halo": "number",
|
|
269
|
+
"empty": "boolean"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
"default": {
|
|
275
|
+
"object": {
|
|
276
|
+
"color": "color",
|
|
277
|
+
"opacity": "number",
|
|
278
|
+
"cornerRadius": "number",
|
|
279
|
+
"rotation": "number",
|
|
280
|
+
"halo": "number",
|
|
281
|
+
"empty": "boolean"
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
"batch": {
|
|
285
|
+
"arrayOf": {
|
|
286
|
+
"object": {}
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
"layout": {
|
|
290
|
+
"object": {
|
|
291
|
+
"gapMode": {
|
|
292
|
+
"enum": [
|
|
293
|
+
"zero",
|
|
294
|
+
"near",
|
|
295
|
+
"default",
|
|
296
|
+
"far",
|
|
297
|
+
"extraFar"
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
"edgeInsetRatio": "number",
|
|
301
|
+
"cellAspectRatio": "number",
|
|
302
|
+
"columnGapRatio": "number",
|
|
303
|
+
"rowGapRatio": "number"
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"style": {}
|
|
308
|
+
},
|
|
309
|
+
"curve": {
|
|
310
|
+
"content": {
|
|
311
|
+
"mode": {
|
|
312
|
+
"enum": [
|
|
313
|
+
"samples",
|
|
314
|
+
"function"
|
|
315
|
+
]
|
|
316
|
+
},
|
|
317
|
+
"points": {
|
|
318
|
+
"arrayOf": {
|
|
319
|
+
"arrayOf": "number",
|
|
320
|
+
"length": 2
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
"expression": "string",
|
|
324
|
+
"parameters": {
|
|
325
|
+
"record": "number"
|
|
326
|
+
},
|
|
327
|
+
"domain": {
|
|
328
|
+
"arrayOf": "number",
|
|
329
|
+
"length": 2
|
|
330
|
+
},
|
|
331
|
+
"yDomain": {
|
|
332
|
+
"arrayOf": "number",
|
|
333
|
+
"length": 2
|
|
334
|
+
},
|
|
335
|
+
"interpolation": {
|
|
336
|
+
"enum": [
|
|
337
|
+
"linear",
|
|
338
|
+
"stepAfter",
|
|
339
|
+
"monotone"
|
|
340
|
+
]
|
|
341
|
+
},
|
|
342
|
+
"markers": {
|
|
343
|
+
"arrayOf": {
|
|
344
|
+
"object": {
|
|
345
|
+
"at": {
|
|
346
|
+
"object": {
|
|
347
|
+
"x": "number",
|
|
348
|
+
"sampleIndex": "integer",
|
|
349
|
+
"offsetPt": {
|
|
350
|
+
"arrayOf": "number",
|
|
351
|
+
"length": 2
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
"symbol": {
|
|
356
|
+
"object": {
|
|
357
|
+
"radiusPt": "number",
|
|
358
|
+
"fill": "color"
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
"label": "string",
|
|
362
|
+
"labelStyle": {
|
|
363
|
+
"object": {
|
|
364
|
+
"color": "color",
|
|
365
|
+
"fontSizePt": "number",
|
|
366
|
+
"position": {
|
|
367
|
+
"enum": [
|
|
368
|
+
"top",
|
|
369
|
+
"bottom",
|
|
370
|
+
"left",
|
|
371
|
+
"right"
|
|
372
|
+
]
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
"style": {
|
|
381
|
+
"color": "color",
|
|
382
|
+
"opacity": "number",
|
|
383
|
+
"strokeWidth": "number",
|
|
384
|
+
"lineCap": {
|
|
385
|
+
"enum": [
|
|
386
|
+
"round",
|
|
387
|
+
"butt",
|
|
388
|
+
"square"
|
|
389
|
+
]
|
|
390
|
+
},
|
|
391
|
+
"lineJoin": {
|
|
392
|
+
"enum": [
|
|
393
|
+
"round",
|
|
394
|
+
"bevel",
|
|
395
|
+
"miter"
|
|
396
|
+
]
|
|
397
|
+
},
|
|
398
|
+
"dashLength": "number",
|
|
399
|
+
"dashGap": "number",
|
|
400
|
+
"fillColor": "color",
|
|
401
|
+
"fillOpacity": "number",
|
|
402
|
+
"plotPadding": "number"
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
"frame": {
|
|
407
|
+
"left": "number",
|
|
408
|
+
"top": "number",
|
|
409
|
+
"right": "number",
|
|
410
|
+
"bottom": "number",
|
|
411
|
+
"width": "number",
|
|
412
|
+
"height": "number"
|
|
413
|
+
},
|
|
414
|
+
"transform": {
|
|
415
|
+
"translateX": "number",
|
|
416
|
+
"translateY": "number"
|
|
417
|
+
}
|
|
418
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ascii": [
|
|
3
|
+
282,
|
|
4
|
+
357,
|
|
5
|
+
569,
|
|
6
|
+
672,
|
|
7
|
+
672,
|
|
8
|
+
1036,
|
|
9
|
+
745,
|
|
10
|
+
348,
|
|
11
|
+
429,
|
|
12
|
+
429,
|
|
13
|
+
485,
|
|
14
|
+
672,
|
|
15
|
+
348,
|
|
16
|
+
485,
|
|
17
|
+
348,
|
|
18
|
+
335,
|
|
19
|
+
685,
|
|
20
|
+
512,
|
|
21
|
+
644,
|
|
22
|
+
669,
|
|
23
|
+
688,
|
|
24
|
+
664,
|
|
25
|
+
684,
|
|
26
|
+
605,
|
|
27
|
+
694,
|
|
28
|
+
684,
|
|
29
|
+
348,
|
|
30
|
+
348,
|
|
31
|
+
672,
|
|
32
|
+
672,
|
|
33
|
+
672,
|
|
34
|
+
558,
|
|
35
|
+
928,
|
|
36
|
+
733,
|
|
37
|
+
694,
|
|
38
|
+
742,
|
|
39
|
+
748,
|
|
40
|
+
623,
|
|
41
|
+
598,
|
|
42
|
+
761,
|
|
43
|
+
784,
|
|
44
|
+
319,
|
|
45
|
+
602,
|
|
46
|
+
710,
|
|
47
|
+
595,
|
|
48
|
+
907,
|
|
49
|
+
769,
|
|
50
|
+
787,
|
|
51
|
+
674,
|
|
52
|
+
787,
|
|
53
|
+
695,
|
|
54
|
+
676,
|
|
55
|
+
661,
|
|
56
|
+
761,
|
|
57
|
+
722,
|
|
58
|
+
1010,
|
|
59
|
+
730,
|
|
60
|
+
709,
|
|
61
|
+
676,
|
|
62
|
+
429,
|
|
63
|
+
335,
|
|
64
|
+
429,
|
|
65
|
+
672,
|
|
66
|
+
628,
|
|
67
|
+
500,
|
|
68
|
+
590,
|
|
69
|
+
652,
|
|
70
|
+
588,
|
|
71
|
+
652,
|
|
72
|
+
602,
|
|
73
|
+
409,
|
|
74
|
+
646,
|
|
75
|
+
633,
|
|
76
|
+
288,
|
|
77
|
+
288,
|
|
78
|
+
602,
|
|
79
|
+
295,
|
|
80
|
+
933,
|
|
81
|
+
628,
|
|
82
|
+
620,
|
|
83
|
+
647,
|
|
84
|
+
648,
|
|
85
|
+
436,
|
|
86
|
+
567,
|
|
87
|
+
413,
|
|
88
|
+
628,
|
|
89
|
+
584,
|
|
90
|
+
848,
|
|
91
|
+
581,
|
|
92
|
+
598,
|
|
93
|
+
567,
|
|
94
|
+
429,
|
|
95
|
+
300,
|
|
96
|
+
429,
|
|
97
|
+
672
|
|
98
|
+
],
|
|
99
|
+
"extra": {
|
|
100
|
+
"°": 485,
|
|
101
|
+
"±": 672,
|
|
102
|
+
"×": 672,
|
|
103
|
+
"÷": 672,
|
|
104
|
+
"·": 348,
|
|
105
|
+
"•": 485,
|
|
106
|
+
"●": 952,
|
|
107
|
+
"–": 628,
|
|
108
|
+
"—": 907,
|
|
109
|
+
"…": 955,
|
|
110
|
+
"€": 702,
|
|
111
|
+
"£": 672,
|
|
112
|
+
"¥": 672,
|
|
113
|
+
"←": 944,
|
|
114
|
+
"→": 944,
|
|
115
|
+
"↑": 816,
|
|
116
|
+
"↓": 816,
|
|
117
|
+
"▲": 779,
|
|
118
|
+
"▼": 779
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GlanceWidgets
|
|
4
|
+
DEFAULT_FONT_NAME = '3x5'
|
|
5
|
+
DEFAULT_LETTER_SPACING = 1
|
|
6
|
+
DEFAULT_LINE_SPACING = 1
|
|
7
|
+
ANCHOR_BOX_WIDTH = 1
|
|
8
|
+
|
|
9
|
+
class PixelFont
|
|
10
|
+
attr_reader :name, :height, :descent, :spacing, :line_height
|
|
11
|
+
|
|
12
|
+
def initialize(definition)
|
|
13
|
+
@name = definition.fetch('name')
|
|
14
|
+
@height = definition.fetch('height')
|
|
15
|
+
@descent, @spacing = DataValues.fetch_or(definition, 'descent', 0), DataValues.fetch_or(definition, 'spacing', 1)
|
|
16
|
+
Raster.integers('Font dimensions', [height, descent, spacing])
|
|
17
|
+
raise ArgumentError, 'Invalid font height/descent' unless height >= 1 && descent >= 0
|
|
18
|
+
@line_height = height + descent
|
|
19
|
+
@fallback, @glyphs = definition['fallback'], {}
|
|
20
|
+
fixed = definition['advance']
|
|
21
|
+
Raster.integers('Font advance', [fixed]) unless fixed.nil?
|
|
22
|
+
definition.fetch('glyphs').each { |character, glyph| compile(character, glyph, fixed) }
|
|
23
|
+
DataValues.fetch_or(definition, 'aliases', {}).each do |alias_name, target|
|
|
24
|
+
unless alias_name.each_char.count == 1 && @glyphs.key?(target)
|
|
25
|
+
raise ArgumentError, 'Invalid font alias'
|
|
26
|
+
end
|
|
27
|
+
@glyphs[alias_name] ||= @glyphs[target]
|
|
28
|
+
end
|
|
29
|
+
raise ArgumentError, 'Font fallback has no glyph' if @fallback && !@glyphs.key?(@fallback)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def monospace_advance
|
|
33
|
+
@glyphs.values.map { |g| [g['width'], g['advance']].max }.max || 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def has_glyph(character)
|
|
37
|
+
@glyphs.key?(character)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def glyph_for(character)
|
|
41
|
+
glyph = @glyphs[character] || @glyphs[@fallback]
|
|
42
|
+
raise ArgumentError, "Font #{name} has no glyph for #{character.inspect}" unless glyph
|
|
43
|
+
DataValues.copy(glyph)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def compile(character, definition, fixed)
|
|
49
|
+
raise ArgumentError, 'Glyph key must be one Unicode code point' unless character.each_char.count == 1
|
|
50
|
+
rows = DataValues.copy(definition.is_a?(Array) ? definition : definition.fetch('rows'))
|
|
51
|
+
width = rows.empty? ? 0 : rows[0].length
|
|
52
|
+
unless rows.length <= line_height && rows.all? { |row| row.length == width && /\A[#.]*\z/.match(row) }
|
|
53
|
+
raise ArgumentError, 'Invalid glyph rows'
|
|
54
|
+
end
|
|
55
|
+
advance = fixed || (definition.is_a?(Hash) ? DataValues.fetch_or(definition, 'advance', width) : width)
|
|
56
|
+
if rows.empty? && fixed.nil? && (!definition.is_a?(Hash) || !DataValues.set?(definition, 'advance'))
|
|
57
|
+
raise ArgumentError, 'Glyph without rows requires an advance'
|
|
58
|
+
end
|
|
59
|
+
Raster.integers('Glyph advance', [advance])
|
|
60
|
+
raise ArgumentError, 'Invalid glyph advance' if advance < 0 || (fixed && width > fixed)
|
|
61
|
+
rows += Array.new([0, height - rows.length].max, '.' * width)
|
|
62
|
+
ink_bottom = rows.each_index.select { |i| rows[i].include?('#') }.map { |i| i + 1 }.max || 0
|
|
63
|
+
@glyphs[character] = { 'rows' => rows, 'width' => width, 'advance' => advance, 'inkBottom' => ink_bottom }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
FONT_REGISTRY = {}
|
|
68
|
+
|
|
69
|
+
def define_font(definition)
|
|
70
|
+
raise ArgumentError, 'Font already defined' if FONT_REGISTRY.key?(definition.fetch('name'))
|
|
71
|
+
font = PixelFont.new(definition)
|
|
72
|
+
FONT_REGISTRY[font.name] = font
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def get_font(name)
|
|
76
|
+
FONT_REGISTRY.fetch(name) { raise ArgumentError, "Unknown font: #{name}" }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def resolve_font(font = nil)
|
|
80
|
+
font ||= DEFAULT_FONT_NAME
|
|
81
|
+
return get_font(font) if font.is_a?(String)
|
|
82
|
+
font.is_a?(PixelFont) ? font : PixelFont.new(font)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
module TextLayout
|
|
86
|
+
module_function
|
|
87
|
+
|
|
88
|
+
def width(text, font, spacing, fixed = nil)
|
|
89
|
+
characters = text.each_char.to_a
|
|
90
|
+
return 0 if characters.empty?
|
|
91
|
+
characters.sum { |char| fixed || font.glyph_for(char)['advance'] } + spacing * (characters.length - 1)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def wrap(paragraph, font, spacing, max_width, fixed = nil)
|
|
95
|
+
return [paragraph] unless max_width
|
|
96
|
+
result, current = [], nil
|
|
97
|
+
paragraph.split(/ /, -1).each do |word|
|
|
98
|
+
rest = word.each_char.to_a
|
|
99
|
+
candidate = current.nil? ? word : current + ' ' + word
|
|
100
|
+
if width(candidate, font, spacing, fixed) <= max_width
|
|
101
|
+
current = candidate
|
|
102
|
+
next
|
|
103
|
+
end
|
|
104
|
+
result << current unless current.nil?
|
|
105
|
+
while rest.length > 1 && width(rest.join, font, spacing, fixed) > max_width
|
|
106
|
+
length = 1
|
|
107
|
+
length += 1 while length < rest.length && width(rest[0..length].join, font, spacing, fixed) <= max_width
|
|
108
|
+
result << rest.shift(length).join
|
|
109
|
+
end
|
|
110
|
+
current = rest.join
|
|
111
|
+
end
|
|
112
|
+
result << (current || '')
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def layout_text(content, options = {})
|
|
117
|
+
font = resolve_font(options['font'])
|
|
118
|
+
fixed = options['monospace'] ? font.monospace_advance : nil
|
|
119
|
+
spacing, line_spacing = DataValues.fetch_or(options, 'letterSpacing', font.spacing), DataValues.fetch_or(options, 'lineSpacing', 1)
|
|
120
|
+
max_width = options['maxWidth']
|
|
121
|
+
box_width = DataValues.fetch_or(options, 'boxWidth', max_width || 1)
|
|
122
|
+
Raster.integers('Text layout spacing', [spacing, line_spacing, box_width])
|
|
123
|
+
if max_width
|
|
124
|
+
Raster.integers('maxWidth', [max_width])
|
|
125
|
+
raise ArgumentError, 'maxWidth must be positive' if max_width < 1
|
|
126
|
+
end
|
|
127
|
+
align = DataValues.fetch_or(options, 'align', 'left')
|
|
128
|
+
raise ArgumentError, 'Invalid text alignment' unless %w[left center right].include?(align)
|
|
129
|
+
lines = content.split(/\n/, -1).flat_map { |line| TextLayout.wrap(line, font, spacing, max_width, fixed) }
|
|
130
|
+
lines = [''] if lines.empty?
|
|
131
|
+
glyphs, widest = [], 0
|
|
132
|
+
pitch = font.line_height + line_spacing
|
|
133
|
+
lines.each_with_index do |line, index|
|
|
134
|
+
line_width = TextLayout.width(line, font, spacing, fixed)
|
|
135
|
+
widest = [widest, line_width].max
|
|
136
|
+
pen = case align
|
|
137
|
+
when 'left' then 0
|
|
138
|
+
when 'right' then box_width - line_width
|
|
139
|
+
else ((box_width - line_width) / 2.0).floor
|
|
140
|
+
end
|
|
141
|
+
line.each_char do |character|
|
|
142
|
+
glyph = font.glyph_for(character)
|
|
143
|
+
glyphs << { 'glyph' => glyph, 'x' => pen + (fixed ? (fixed - glyph['width']) / 2 : 0), 'y' => index * pitch }
|
|
144
|
+
pen += (fixed || glyph['advance']) + spacing
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
bottom = [font.height, *lines.last.each_char.map { |c| font.glyph_for(c)['inkBottom'] }].max
|
|
148
|
+
{ 'width' => widest, 'height' => (lines.length - 1) * pitch + bottom, 'lines' => lines, 'glyphs' => glyphs }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def measure_text(content, font = nil, options = {})
|
|
152
|
+
layout_text(content, options.merge('font' => font)).select { |key, _| %w[width height lines].include?(key) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def text_matrix(content, options = {})
|
|
156
|
+
metrics = measure_text(content, options['font'], options)
|
|
157
|
+
matrix = Matrix.new([1, metrics['width']].max, [1, metrics['height']].max, options)
|
|
158
|
+
drawing = options.merge('boxWidth' => metrics['width'])
|
|
159
|
+
drawing['style'] = options['color'] if DataValues.set?(options, 'color')
|
|
160
|
+
matrix.text(content, 0, 0, drawing)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
FONT_3X5 = define_font(DataValues.load('fonts/font-3x5.json'))
|
|
164
|
+
FONT_5X7 = define_font(DataValues.load('fonts/font-5x7.json'))
|
|
165
|
+
end
|