rgame 0.1.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 +26 -0
- data/README.md +406 -0
- data/docs/api/README.md +167 -0
- data/docs/api/app.md +192 -0
- data/docs/api/assets.md +426 -0
- data/docs/api/audio.md +208 -0
- data/docs/api/components.md +321 -0
- data/docs/api/drawing.md +330 -0
- data/docs/api/game.md +99 -0
- data/docs/api/images.md +118 -0
- data/docs/api/input.md +179 -0
- data/docs/api/internals.md +110 -0
- data/docs/api/scene_graph.md +159 -0
- data/docs/api/signals.md +142 -0
- data/docs/api/systems.md +98 -0
- data/docs/api/text.md +116 -0
- data/docs/api/toolbox.md +240 -0
- data/docs/api/values.md +101 -0
- data/ext/README.md +225 -0
- data/ext/rgame_core/app/app.c +721 -0
- data/ext/rgame_core/app/app_gl.h +64 -0
- data/ext/rgame_core/app/frame_loop.c +42 -0
- data/ext/rgame_core/app/frame_loop.h +54 -0
- data/ext/rgame_core/audio/audio.c +466 -0
- data/ext/rgame_core/audio/audio_internal.h +45 -0
- data/ext/rgame_core/audio/vorbis_decoder.c +282 -0
- data/ext/rgame_core/audio/vorbis_decoder.h +45 -0
- data/ext/rgame_core/example.rb +188 -0
- data/ext/rgame_core/extconf.rb +167 -0
- data/ext/rgame_core/graphics/backend.c +52 -0
- data/ext/rgame_core/graphics/backend.h +64 -0
- data/ext/rgame_core/graphics/canvas.c +247 -0
- data/ext/rgame_core/graphics/canvas.h +143 -0
- data/ext/rgame_core/graphics/clip.c +87 -0
- data/ext/rgame_core/graphics/clip.h +89 -0
- data/ext/rgame_core/graphics/draw_queue.c +216 -0
- data/ext/rgame_core/graphics/draw_queue.h +174 -0
- data/ext/rgame_core/graphics/gl_backend.c +122 -0
- data/ext/rgame_core/graphics/gl_backend.h +43 -0
- data/ext/rgame_core/graphics/image.c +304 -0
- data/ext/rgame_core/graphics/image_internal.h +30 -0
- data/ext/rgame_core/graphics/primitives.c +189 -0
- data/ext/rgame_core/graphics/primitives.h +111 -0
- data/ext/rgame_core/graphics/recording.c +119 -0
- data/ext/rgame_core/graphics/recording.h +88 -0
- data/ext/rgame_core/graphics/texture.c +181 -0
- data/ext/rgame_core/graphics/texture.h +165 -0
- data/ext/rgame_core/graphics/transform.c +128 -0
- data/ext/rgame_core/graphics/transform.h +106 -0
- data/ext/rgame_core/include/rgame/core.h +577 -0
- data/ext/rgame_core/input/device_slots.c +103 -0
- data/ext/rgame_core/input/device_slots.h +93 -0
- data/ext/rgame_core/input/gamepad.c +145 -0
- data/ext/rgame_core/input/gamepad.h +63 -0
- data/ext/rgame_core/input/input.c +109 -0
- data/ext/rgame_core/input/input.h +99 -0
- data/ext/rgame_core/ruby/audio_ext.c +321 -0
- data/ext/rgame_core/ruby/core_ext.c +513 -0
- data/ext/rgame_core/ruby/core_ext.h +51 -0
- data/ext/rgame_core/ruby/font_ext.c +168 -0
- data/ext/rgame_core/ruby/image_ext.c +230 -0
- data/ext/rgame_core/ruby/recording_ext.c +186 -0
- data/ext/rgame_core/ruby/renderer_ext.c +376 -0
- data/ext/rgame_core/text/atlas.c +59 -0
- data/ext/rgame_core/text/atlas.h +85 -0
- data/ext/rgame_core/text/font.c +281 -0
- data/ext/rgame_core/text/font.h +139 -0
- data/ext/rgame_core/text/font_atlas.c +385 -0
- data/ext/rgame_core/text/font_internal.h +47 -0
- data/ext/rgame_core/text/glyph_cache.c +142 -0
- data/ext/rgame_core/text/glyph_cache.h +89 -0
- data/ext/rgame_core/vendor/README.md +159 -0
- data/ext/rgame_core/vendor/miniaudio.h +95864 -0
- data/ext/rgame_core/vendor/miniaudio_impl.c +62 -0
- data/ext/rgame_core/vendor/stb_image.h +7988 -0
- data/ext/rgame_core/vendor/stb_image_impl.c +31 -0
- data/ext/rgame_core/vendor/stb_truetype.h +5079 -0
- data/ext/rgame_core/vendor/stb_truetype_impl.c +23 -0
- data/ext/rgame_core/vendor/stb_vorbis.c +5584 -0
- data/ext/rgame_core/vendor/stb_vorbis_impl.c +29 -0
- data/ext/rgame_util/color.c +19 -0
- data/ext/rgame_util/color.h +60 -0
- data/ext/rgame_util/color_ext.c +156 -0
- data/ext/rgame_util/extconf.rb +27 -0
- data/ext/rgame_util/tensor.c +186 -0
- data/ext/rgame_util/util_ext.c +27 -0
- data/ext/rgame_util/util_ext.h +16 -0
- data/lib/rgame/boot.rb +13 -0
- data/lib/rgame/core/app.rb +82 -0
- data/lib/rgame/core/asset_manager.rb +224 -0
- data/lib/rgame/core/audio.rb +124 -0
- data/lib/rgame/core/font.rb +49 -0
- data/lib/rgame/core/gamepad.rb +55 -0
- data/lib/rgame/core/image.rb +55 -0
- data/lib/rgame/core/input.rb +77 -0
- data/lib/rgame/core/nine_slice.rb +163 -0
- data/lib/rgame/core/recording.rb +52 -0
- data/lib/rgame/core/renderer.rb +363 -0
- data/lib/rgame/core/sprite_sheet.rb +108 -0
- data/lib/rgame/core/tile_map_renderer.rb +160 -0
- data/lib/rgame/core/ui_atlas.rb +86 -0
- data/lib/rgame/core.rb +24 -0
- data/lib/rgame/engine/actor.rb +53 -0
- data/lib/rgame/engine/animation_set.rb +49 -0
- data/lib/rgame/engine/animator.rb +44 -0
- data/lib/rgame/engine/audio_bus.rb +24 -0
- data/lib/rgame/engine/audio_director.rb +29 -0
- data/lib/rgame/engine/body.rb +49 -0
- data/lib/rgame/engine/cached_label.rb +33 -0
- data/lib/rgame/engine/camera.rb +33 -0
- data/lib/rgame/engine/camera_view.rb +28 -0
- data/lib/rgame/engine/circle_collider.rb +32 -0
- data/lib/rgame/engine/collision_box.rb +34 -0
- data/lib/rgame/engine/collision_system.rb +44 -0
- data/lib/rgame/engine/component.rb +30 -0
- data/lib/rgame/engine/components/action_trigger.rb +41 -0
- data/lib/rgame/engine/components/animated_sprite.rb +63 -0
- data/lib/rgame/engine/components/character_body.rb +70 -0
- data/lib/rgame/engine/components/circle_collider.rb +44 -0
- data/lib/rgame/engine/components/collision_world.rb +103 -0
- data/lib/rgame/engine/components/despawn_offscreen.rb +26 -0
- data/lib/rgame/engine/components/path_follow.rb +84 -0
- data/lib/rgame/engine/components/player_controller.rb +24 -0
- data/lib/rgame/engine/components/pool.rb +53 -0
- data/lib/rgame/engine/components/screen_wrap.rb +27 -0
- data/lib/rgame/engine/components/sprite.rb +31 -0
- data/lib/rgame/engine/components/targeting.rb +54 -0
- data/lib/rgame/engine/components/thrust_controller.rb +65 -0
- data/lib/rgame/engine/components/tile_world.rb +68 -0
- data/lib/rgame/engine/components/timer.rb +75 -0
- data/lib/rgame/engine/components/velocity.rb +27 -0
- data/lib/rgame/engine/components/wander_controller.rb +60 -0
- data/lib/rgame/engine/debug_overlay.rb +106 -0
- data/lib/rgame/engine/i18n.rb +97 -0
- data/lib/rgame/engine/input/action_mapper.rb +46 -0
- data/lib/rgame/engine/input/actions.rb +41 -0
- data/lib/rgame/engine/input/player_controller.rb +14 -0
- data/lib/rgame/engine/matrix.rb +32 -0
- data/lib/rgame/engine/node2d.rb +271 -0
- data/lib/rgame/engine/path.rb +78 -0
- data/lib/rgame/engine/pool.rb +51 -0
- data/lib/rgame/engine/resettable.rb +67 -0
- data/lib/rgame/engine/scene/scene_stack.rb +65 -0
- data/lib/rgame/engine/signal.rb +75 -0
- data/lib/rgame/engine/spatial_hash.rb +71 -0
- data/lib/rgame/engine/tile_collision.rb +78 -0
- data/lib/rgame/engine/tile_map.rb +149 -0
- data/lib/rgame/engine/tileset.rb +101 -0
- data/lib/rgame/engine/timer.rb +51 -0
- data/lib/rgame/engine.rb +68 -0
- data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
- data/lib/rgame/fonts/OFL.txt +102 -0
- data/lib/rgame/game.rb +129 -0
- data/lib/rgame/util/color.rb +27 -0
- data/lib/rgame/util/controls.rb +107 -0
- data/lib/rgame/util/tensor.rb +12 -0
- data/lib/rgame/util.rb +8 -0
- data/lib/rgame/version.rb +12 -0
- data/lib/rgame.rb +20 -0
- metadata +215 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* font.c — metrics and rasterisation over stb_truetype. See font.h.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "text/font.h"
|
|
6
|
+
|
|
7
|
+
#include <stdlib.h>
|
|
8
|
+
#include <string.h>
|
|
9
|
+
|
|
10
|
+
#include "vendor/stb_truetype.h"
|
|
11
|
+
|
|
12
|
+
struct rgame_typeface {
|
|
13
|
+
stbtt_fontinfo info;
|
|
14
|
+
/* Our own copy: stb keeps pointers into this for the life of the face, and
|
|
15
|
+
* a borrowed buffer would be a lifetime rule callers have to remember. */
|
|
16
|
+
unsigned char *ttf;
|
|
17
|
+
|
|
18
|
+
int pixel_height;
|
|
19
|
+
float scale; /* font units -> pixels at that height */
|
|
20
|
+
float ascent; /* pixels from the top of the line box to the baseline */
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
rgame_typeface *rgame_typeface_open(const unsigned char *ttf, size_t length, int pixel_height) {
|
|
24
|
+
if (!ttf || length == 0 || pixel_height <= 0) {
|
|
25
|
+
return NULL;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
rgame_typeface *typeface = calloc(1, sizeof(rgame_typeface));
|
|
29
|
+
if (!typeface) {
|
|
30
|
+
return NULL;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
typeface->ttf = malloc(length);
|
|
34
|
+
if (!typeface->ttf) {
|
|
35
|
+
free(typeface);
|
|
36
|
+
return NULL;
|
|
37
|
+
}
|
|
38
|
+
memcpy(typeface->ttf, ttf, length);
|
|
39
|
+
|
|
40
|
+
/* Index 0 of a font collection. A .ttc with several faces in it is a thing
|
|
41
|
+
* that exists; picking anything but the first would need a way to say
|
|
42
|
+
* which, and nothing has asked for one. */
|
|
43
|
+
int offset = stbtt_GetFontOffsetForIndex(typeface->ttf, 0);
|
|
44
|
+
if (offset < 0 || !stbtt_InitFont(&typeface->info, typeface->ttf, offset)) {
|
|
45
|
+
free(typeface->ttf);
|
|
46
|
+
free(typeface);
|
|
47
|
+
return NULL;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
typeface->pixel_height = pixel_height;
|
|
51
|
+
typeface->scale = stbtt_ScaleForPixelHeight(&typeface->info, (float)pixel_height);
|
|
52
|
+
|
|
53
|
+
int ascent = 0, descent = 0, line_gap = 0;
|
|
54
|
+
stbtt_GetFontVMetrics(&typeface->info, &ascent, &descent, &line_gap);
|
|
55
|
+
typeface->ascent = (float)ascent * typeface->scale;
|
|
56
|
+
|
|
57
|
+
return typeface;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
void rgame_typeface_close(rgame_typeface *typeface) {
|
|
61
|
+
if (!typeface) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
free(typeface->ttf);
|
|
65
|
+
free(typeface);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
int rgame_typeface_height(const rgame_typeface *typeface) {
|
|
69
|
+
return typeface ? typeface->pixel_height : 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
float rgame_typeface_ascent(const rgame_typeface *typeface) {
|
|
73
|
+
return typeface ? typeface->ascent : 0.0f;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
int rgame_typeface_glyph(const rgame_typeface *typeface, int codepoint, rgame_glyph *out) {
|
|
77
|
+
if (!typeface || !out) {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
int advance = 0, left_bearing = 0;
|
|
82
|
+
stbtt_GetCodepointHMetrics(&typeface->info, codepoint, &advance, &left_bearing);
|
|
83
|
+
|
|
84
|
+
/* The ink's bounding box relative to the *baseline*, so y0 is negative for
|
|
85
|
+
* anything reaching above it — which is nearly everything. */
|
|
86
|
+
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
|
87
|
+
stbtt_GetCodepointBitmapBox(&typeface->info, codepoint, typeface->scale, typeface->scale,
|
|
88
|
+
&x0, &y0, &x1, &y1);
|
|
89
|
+
|
|
90
|
+
out->codepoint = codepoint;
|
|
91
|
+
out->page = 0;
|
|
92
|
+
/* Size only. Where it lands on a page is the atlas's decision. */
|
|
93
|
+
out->rect = rgame_rect_make(0, 0, x1 - x0, y1 - y0);
|
|
94
|
+
out->advance = (float)advance * typeface->scale;
|
|
95
|
+
out->bearing_x = (float)x0;
|
|
96
|
+
/* Baseline-relative to top-of-line-box, converted once and never again. */
|
|
97
|
+
out->bearing_y = typeface->ascent + (float)y0;
|
|
98
|
+
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
void rgame_typeface_render(const rgame_typeface *typeface, int codepoint, unsigned char *out,
|
|
103
|
+
int stride, int width, int height) {
|
|
104
|
+
/* stb copes with a zero-sized box on its own, so mutating this guard away
|
|
105
|
+
* survives the suite. It stays for the *negative* case, which stb does not
|
|
106
|
+
* promise anything about, and a negative size here would come from an
|
|
107
|
+
* atlas rectangle that had already gone wrong. */
|
|
108
|
+
if (!typeface || !out || width <= 0 || height <= 0) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
stbtt_MakeCodepointBitmap(&typeface->info, out, width, height, stride, typeface->scale,
|
|
113
|
+
typeface->scale, codepoint);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
float rgame_typeface_kern(const rgame_typeface *typeface, int previous, int codepoint) {
|
|
117
|
+
/*
|
|
118
|
+
* No previous glyph means nothing to kern against — the first letter of a
|
|
119
|
+
* string is where it is.
|
|
120
|
+
*
|
|
121
|
+
* Removing this guard survives the suite, because the shipped font has no
|
|
122
|
+
* kern pair involving codepoint 0 and so answers zero anyway. It stays
|
|
123
|
+
* because that is a fact about one font, not about kerning: a font with
|
|
124
|
+
* such a pair would shift the first letter of every string it drew.
|
|
125
|
+
*/
|
|
126
|
+
if (!typeface || previous == 0) {
|
|
127
|
+
return 0.0f;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
int kern = stbtt_GetCodepointKernAdvance(&typeface->info, previous, codepoint);
|
|
131
|
+
return (float)kern * typeface->scale;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ------------------------------------------------------------------------- *
|
|
135
|
+
* UTF-8
|
|
136
|
+
* ------------------------------------------------------------------------- */
|
|
137
|
+
|
|
138
|
+
/* A continuation byte is 10xxxxxx and nothing else. */
|
|
139
|
+
static int is_continuation(unsigned char byte) {
|
|
140
|
+
return (byte & 0xC0) == 0x80;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/*
|
|
144
|
+
* One malformed byte costs one replacement character, not the rest of the
|
|
145
|
+
* string: `*offset` always moves by at least one, so a caller looping on this
|
|
146
|
+
* always terminates however bad the input is.
|
|
147
|
+
*/
|
|
148
|
+
static int reject(size_t *offset, int *codepoint) {
|
|
149
|
+
*offset += 1;
|
|
150
|
+
*codepoint = RGAME_UTF8_REPLACEMENT;
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
int rgame_utf8_next(const char *text, size_t length, size_t *offset, int *codepoint) {
|
|
155
|
+
if (!text || !offset || !codepoint || *offset >= length) {
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const unsigned char *bytes = (const unsigned char *)text;
|
|
160
|
+
unsigned char lead = bytes[*offset];
|
|
161
|
+
|
|
162
|
+
if (lead < 0x80) {
|
|
163
|
+
*codepoint = lead;
|
|
164
|
+
*offset += 1;
|
|
165
|
+
return 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* How many bytes the lead announces, and the value its low bits carry. A
|
|
169
|
+
* lead of 10xxxxxx is a continuation with nothing in front of it, and
|
|
170
|
+
* 11111xxx is not a lead byte at all — both land in the else. */
|
|
171
|
+
int extra;
|
|
172
|
+
int value;
|
|
173
|
+
if ((lead & 0xE0) == 0xC0) {
|
|
174
|
+
extra = 1;
|
|
175
|
+
value = lead & 0x1F;
|
|
176
|
+
} else if ((lead & 0xF0) == 0xE0) {
|
|
177
|
+
extra = 2;
|
|
178
|
+
/* 0x0F, not 0x1F: a three-byte lead is 1110xxxx, so the fifth bit is
|
|
179
|
+
* always clear and the two masks happen to be equivalent here. Written
|
|
180
|
+
* as the four bits the format actually defines — a mutation to 0x1F is
|
|
181
|
+
* undetectable, and that is a property of the lead range rather than a
|
|
182
|
+
* gap in the tests. */
|
|
183
|
+
value = lead & 0x0F;
|
|
184
|
+
} else if ((lead & 0xF8) == 0xF0) {
|
|
185
|
+
extra = 3;
|
|
186
|
+
value = lead & 0x07;
|
|
187
|
+
} else {
|
|
188
|
+
return reject(offset, codepoint);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Truncated: the sequence runs off the end of the buffer. Checked before
|
|
192
|
+
* reading any of it, which is the whole point. */
|
|
193
|
+
if (*offset + (size_t)extra >= length) {
|
|
194
|
+
return reject(offset, codepoint);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (int i = 1; i <= extra; i++) {
|
|
198
|
+
unsigned char byte = bytes[*offset + (size_t)i];
|
|
199
|
+
if (!is_continuation(byte)) {
|
|
200
|
+
return reject(offset, codepoint);
|
|
201
|
+
}
|
|
202
|
+
value = (value << 6) | (byte & 0x3F);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/*
|
|
206
|
+
* Overlong encodings — a codepoint written in more bytes than it needs —
|
|
207
|
+
* are rejected rather than accepted. They are the classic way to smuggle a
|
|
208
|
+
* character past a check that looked at the bytes, and they have no
|
|
209
|
+
* legitimate use.
|
|
210
|
+
*/
|
|
211
|
+
static const int minimum[4] = { 0, 0x80, 0x800, 0x10000 };
|
|
212
|
+
if (value < minimum[extra]) {
|
|
213
|
+
return reject(offset, codepoint);
|
|
214
|
+
}
|
|
215
|
+
/* Surrogate halves are not characters, and nothing above U+10FFFF exists. */
|
|
216
|
+
if ((value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF) {
|
|
217
|
+
return reject(offset, codepoint);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
*offset += (size_t)(extra + 1);
|
|
221
|
+
*codepoint = value;
|
|
222
|
+
return 1;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* ------------------------------------------------------------------------- *
|
|
226
|
+
* Walking a string
|
|
227
|
+
* ------------------------------------------------------------------------- */
|
|
228
|
+
|
|
229
|
+
void rgame_text_cursor_init(rgame_text_cursor *cursor, const char *text, size_t length) {
|
|
230
|
+
cursor->text = text;
|
|
231
|
+
cursor->length = text ? length : 0;
|
|
232
|
+
cursor->offset = 0;
|
|
233
|
+
cursor->previous = 0;
|
|
234
|
+
cursor->pen_x = 0.0f;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
int rgame_text_cursor_next(rgame_text_cursor *cursor, const rgame_typeface *typeface,
|
|
238
|
+
int *codepoint, float *pen_x) {
|
|
239
|
+
if (!cursor || !typeface || !codepoint) {
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
int next = 0;
|
|
244
|
+
if (!rgame_utf8_next(cursor->text, cursor->length, &cursor->offset, &next)) {
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* Kerning belongs to the gap *before* this glyph, so it moves the pen
|
|
249
|
+
* before the position is handed out. */
|
|
250
|
+
cursor->pen_x += rgame_typeface_kern(typeface, cursor->previous, next);
|
|
251
|
+
|
|
252
|
+
*codepoint = next;
|
|
253
|
+
if (pen_x) {
|
|
254
|
+
*pen_x = cursor->pen_x;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
rgame_glyph glyph;
|
|
258
|
+
rgame_typeface_glyph(typeface, next, &glyph);
|
|
259
|
+
cursor->pen_x += glyph.advance;
|
|
260
|
+
cursor->previous = next;
|
|
261
|
+
|
|
262
|
+
return 1;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
float rgame_typeface_measure(const rgame_typeface *typeface, const char *text, size_t length) {
|
|
266
|
+
if (!typeface || !text) {
|
|
267
|
+
return 0.0f;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* The same walk drawing uses, run to the end and asked where it got to.
|
|
271
|
+
* Not "the same arithmetic" — the same code. */
|
|
272
|
+
rgame_text_cursor cursor;
|
|
273
|
+
rgame_text_cursor_init(&cursor, text, length);
|
|
274
|
+
|
|
275
|
+
int codepoint = 0;
|
|
276
|
+
while (rgame_text_cursor_next(&cursor, typeface, &codepoint, NULL)) {
|
|
277
|
+
/* nothing to do; the cursor is doing the summing */
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return cursor.pen_x;
|
|
281
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#ifndef RGAME_FONT_H
|
|
2
|
+
#define RGAME_FONT_H
|
|
3
|
+
|
|
4
|
+
#include <stddef.h>
|
|
5
|
+
|
|
6
|
+
#include "text/glyph_cache.h"
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* A typeface at one pixel size: what each glyph measures, and what it looks
|
|
10
|
+
* like. Wraps `stb_truetype` and nothing else — no atlas, no cache, no GL, no
|
|
11
|
+
* file I/O.
|
|
12
|
+
*
|
|
13
|
+
* This is the layer that can be tested exactly, and it is testable *because*
|
|
14
|
+
* the engine ships its own font: `test/test_font.c` opens
|
|
15
|
+
* `lib/rgame/fonts/LiberationSans-Regular.ttf` and asserts on real advances and
|
|
16
|
+
* real ink. Nothing here needs a fixture, a mock, or a display.
|
|
17
|
+
*
|
|
18
|
+
* "Typeface" rather than "font" because the public `rgame_font` (core.h) is the
|
|
19
|
+
* composed thing — this plus an atlas, a glyph cache and GL textures. This is
|
|
20
|
+
* only the part that knows what letters are shaped like.
|
|
21
|
+
*
|
|
22
|
+
* ---------------------------------------------------------------------------
|
|
23
|
+
* One walk, used by measuring and by drawing
|
|
24
|
+
* ---------------------------------------------------------------------------
|
|
25
|
+
*
|
|
26
|
+
* `rgame_text_cursor` below is not a convenience. Measuring a string and
|
|
27
|
+
* drawing it must agree to the last fraction of a pixel, or every centred
|
|
28
|
+
* label in the game sits slightly off and nothing points at why. Two loops that
|
|
29
|
+
* both "sum the advances" drift the moment one of them gains a rounding rule or
|
|
30
|
+
* forgets kerning — so there is one loop, and both callers turn its crank.
|
|
31
|
+
*
|
|
32
|
+
* ---------------------------------------------------------------------------
|
|
33
|
+
* Coordinates
|
|
34
|
+
* ---------------------------------------------------------------------------
|
|
35
|
+
*
|
|
36
|
+
* Everything is in pixels at the typeface's size, and vertical measurements are
|
|
37
|
+
* from the **top of the line box** rather than the baseline. Typography works
|
|
38
|
+
* from the baseline; a caller placing a label works from a corner. Converting
|
|
39
|
+
* once, here, keeps the baseline out of every drawing calculation downstream.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
typedef struct rgame_typeface rgame_typeface;
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* Opens a TrueType font at `pixel_height`, or NULL if the data is not a font
|
|
46
|
+
* this can read, or the size is not positive.
|
|
47
|
+
*
|
|
48
|
+
* **The bytes are copied**, so the caller may free its buffer immediately.
|
|
49
|
+
* stb keeps pointers into the font data for the life of the face, and a
|
|
50
|
+
* borrowed buffer would make that a lifetime rule someone has to remember; a
|
|
51
|
+
* few hundred kilobytes per open font is the cheaper answer.
|
|
52
|
+
*/
|
|
53
|
+
rgame_typeface *rgame_typeface_open(const unsigned char *ttf, size_t length, int pixel_height);
|
|
54
|
+
void rgame_typeface_close(rgame_typeface *typeface);
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* The size the face was opened at, which is also the line height a caller
|
|
58
|
+
* should step by for a second line. stb scales a font so that ascent minus
|
|
59
|
+
* descent is exactly the requested pixel height, so this is the em box, not an
|
|
60
|
+
* approximation of it.
|
|
61
|
+
*/
|
|
62
|
+
int rgame_typeface_height(const rgame_typeface *typeface);
|
|
63
|
+
|
|
64
|
+
/* Distance from the top of the line box down to the baseline. */
|
|
65
|
+
float rgame_typeface_ascent(const rgame_typeface *typeface);
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* Metrics for one glyph: its advance, its bearings, and the *size* of the
|
|
69
|
+
* bitmap it would rasterise to, written into `out->rect` as `w` and `h` with
|
|
70
|
+
* `x` and `y` left at zero — where on a page it goes is the atlas's decision,
|
|
71
|
+
* not this module's.
|
|
72
|
+
*
|
|
73
|
+
* Returns 1 always for a face that is open; a codepoint the font has no glyph
|
|
74
|
+
* for still yields the `.notdef` box, which is deliberately something visible
|
|
75
|
+
* rather than a zero-width nothing that swallows characters silently.
|
|
76
|
+
*/
|
|
77
|
+
int rgame_typeface_glyph(const rgame_typeface *typeface, int codepoint, rgame_glyph *out);
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* Rasterises a glyph into a caller-provided 8-bit coverage buffer — 0 is
|
|
81
|
+
* transparent, 255 is solid ink. `stride` is the distance between rows, so a
|
|
82
|
+
* glyph can be written straight into the middle of a bigger buffer.
|
|
83
|
+
*
|
|
84
|
+
* Writes nothing outside the `width` x `height` box it is given.
|
|
85
|
+
*/
|
|
86
|
+
void rgame_typeface_render(const rgame_typeface *typeface, int codepoint, unsigned char *out,
|
|
87
|
+
int stride, int width, int height);
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
* The kerning adjustment between two adjacent glyphs, in pixels — usually zero,
|
|
91
|
+
* usually negative when not. Without it "AV" reads as two letters that happen
|
|
92
|
+
* to be near each other.
|
|
93
|
+
*/
|
|
94
|
+
float rgame_typeface_kern(const rgame_typeface *typeface, int previous, int codepoint);
|
|
95
|
+
|
|
96
|
+
/* ------------------------------------------------------------------------- *
|
|
97
|
+
* Walking a string
|
|
98
|
+
* ------------------------------------------------------------------------- */
|
|
99
|
+
|
|
100
|
+
typedef struct {
|
|
101
|
+
const char *text;
|
|
102
|
+
size_t length;
|
|
103
|
+
size_t offset;
|
|
104
|
+
int previous; /* the glyph before, for kerning; 0 at the start */
|
|
105
|
+
float pen_x; /* how far along the line the pen has travelled */
|
|
106
|
+
} rgame_text_cursor;
|
|
107
|
+
|
|
108
|
+
void rgame_text_cursor_init(rgame_text_cursor *cursor, const char *text, size_t length);
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
* Steps to the next glyph. Returns 1 and writes the codepoint plus the pen
|
|
112
|
+
* position it should be drawn at; returns 0 at the end of the string.
|
|
113
|
+
*
|
|
114
|
+
* Kerning against the previous glyph is applied *before* the position is
|
|
115
|
+
* reported, and the advance afterwards — so `cursor->pen_x` once this returns 0
|
|
116
|
+
* is the width of the whole string.
|
|
117
|
+
*/
|
|
118
|
+
int rgame_text_cursor_next(rgame_text_cursor *cursor, const rgame_typeface *typeface,
|
|
119
|
+
int *codepoint, float *pen_x);
|
|
120
|
+
|
|
121
|
+
/* The width of a UTF-8 string in pixels: the cursor above, run to the end. */
|
|
122
|
+
float rgame_typeface_measure(const rgame_typeface *typeface, const char *text, size_t length);
|
|
123
|
+
|
|
124
|
+
/*
|
|
125
|
+
* Decodes one UTF-8 codepoint starting at `*offset` and advances it. Returns 1
|
|
126
|
+
* on success and 0 at the end of the string.
|
|
127
|
+
*
|
|
128
|
+
* Malformed input yields U+FFFD and advances exactly one byte, rather than
|
|
129
|
+
* stopping: a string can come from a data file, and one bad byte should cost
|
|
130
|
+
* one visible replacement character, not the rest of the label. Never reads
|
|
131
|
+
* past `length` — this is the only place in the engine that walks bytes it did
|
|
132
|
+
* not produce.
|
|
133
|
+
*/
|
|
134
|
+
int rgame_utf8_next(const char *text, size_t length, size_t *offset, int *codepoint);
|
|
135
|
+
|
|
136
|
+
/* What a malformed byte decodes to: U+FFFD REPLACEMENT CHARACTER. */
|
|
137
|
+
#define RGAME_UTF8_REPLACEMENT 0xFFFD
|
|
138
|
+
|
|
139
|
+
#endif /* RGAME_FONT_H */
|