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.
Files changed (161) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +26 -0
  3. data/README.md +406 -0
  4. data/docs/api/README.md +167 -0
  5. data/docs/api/app.md +192 -0
  6. data/docs/api/assets.md +426 -0
  7. data/docs/api/audio.md +208 -0
  8. data/docs/api/components.md +321 -0
  9. data/docs/api/drawing.md +330 -0
  10. data/docs/api/game.md +99 -0
  11. data/docs/api/images.md +118 -0
  12. data/docs/api/input.md +179 -0
  13. data/docs/api/internals.md +110 -0
  14. data/docs/api/scene_graph.md +159 -0
  15. data/docs/api/signals.md +142 -0
  16. data/docs/api/systems.md +98 -0
  17. data/docs/api/text.md +116 -0
  18. data/docs/api/toolbox.md +240 -0
  19. data/docs/api/values.md +101 -0
  20. data/ext/README.md +225 -0
  21. data/ext/rgame_core/app/app.c +721 -0
  22. data/ext/rgame_core/app/app_gl.h +64 -0
  23. data/ext/rgame_core/app/frame_loop.c +42 -0
  24. data/ext/rgame_core/app/frame_loop.h +54 -0
  25. data/ext/rgame_core/audio/audio.c +466 -0
  26. data/ext/rgame_core/audio/audio_internal.h +45 -0
  27. data/ext/rgame_core/audio/vorbis_decoder.c +282 -0
  28. data/ext/rgame_core/audio/vorbis_decoder.h +45 -0
  29. data/ext/rgame_core/example.rb +188 -0
  30. data/ext/rgame_core/extconf.rb +167 -0
  31. data/ext/rgame_core/graphics/backend.c +52 -0
  32. data/ext/rgame_core/graphics/backend.h +64 -0
  33. data/ext/rgame_core/graphics/canvas.c +247 -0
  34. data/ext/rgame_core/graphics/canvas.h +143 -0
  35. data/ext/rgame_core/graphics/clip.c +87 -0
  36. data/ext/rgame_core/graphics/clip.h +89 -0
  37. data/ext/rgame_core/graphics/draw_queue.c +216 -0
  38. data/ext/rgame_core/graphics/draw_queue.h +174 -0
  39. data/ext/rgame_core/graphics/gl_backend.c +122 -0
  40. data/ext/rgame_core/graphics/gl_backend.h +43 -0
  41. data/ext/rgame_core/graphics/image.c +304 -0
  42. data/ext/rgame_core/graphics/image_internal.h +30 -0
  43. data/ext/rgame_core/graphics/primitives.c +189 -0
  44. data/ext/rgame_core/graphics/primitives.h +111 -0
  45. data/ext/rgame_core/graphics/recording.c +119 -0
  46. data/ext/rgame_core/graphics/recording.h +88 -0
  47. data/ext/rgame_core/graphics/texture.c +181 -0
  48. data/ext/rgame_core/graphics/texture.h +165 -0
  49. data/ext/rgame_core/graphics/transform.c +128 -0
  50. data/ext/rgame_core/graphics/transform.h +106 -0
  51. data/ext/rgame_core/include/rgame/core.h +577 -0
  52. data/ext/rgame_core/input/device_slots.c +103 -0
  53. data/ext/rgame_core/input/device_slots.h +93 -0
  54. data/ext/rgame_core/input/gamepad.c +145 -0
  55. data/ext/rgame_core/input/gamepad.h +63 -0
  56. data/ext/rgame_core/input/input.c +109 -0
  57. data/ext/rgame_core/input/input.h +99 -0
  58. data/ext/rgame_core/ruby/audio_ext.c +321 -0
  59. data/ext/rgame_core/ruby/core_ext.c +513 -0
  60. data/ext/rgame_core/ruby/core_ext.h +51 -0
  61. data/ext/rgame_core/ruby/font_ext.c +168 -0
  62. data/ext/rgame_core/ruby/image_ext.c +230 -0
  63. data/ext/rgame_core/ruby/recording_ext.c +186 -0
  64. data/ext/rgame_core/ruby/renderer_ext.c +376 -0
  65. data/ext/rgame_core/text/atlas.c +59 -0
  66. data/ext/rgame_core/text/atlas.h +85 -0
  67. data/ext/rgame_core/text/font.c +281 -0
  68. data/ext/rgame_core/text/font.h +139 -0
  69. data/ext/rgame_core/text/font_atlas.c +385 -0
  70. data/ext/rgame_core/text/font_internal.h +47 -0
  71. data/ext/rgame_core/text/glyph_cache.c +142 -0
  72. data/ext/rgame_core/text/glyph_cache.h +89 -0
  73. data/ext/rgame_core/vendor/README.md +159 -0
  74. data/ext/rgame_core/vendor/miniaudio.h +95864 -0
  75. data/ext/rgame_core/vendor/miniaudio_impl.c +62 -0
  76. data/ext/rgame_core/vendor/stb_image.h +7988 -0
  77. data/ext/rgame_core/vendor/stb_image_impl.c +31 -0
  78. data/ext/rgame_core/vendor/stb_truetype.h +5079 -0
  79. data/ext/rgame_core/vendor/stb_truetype_impl.c +23 -0
  80. data/ext/rgame_core/vendor/stb_vorbis.c +5584 -0
  81. data/ext/rgame_core/vendor/stb_vorbis_impl.c +29 -0
  82. data/ext/rgame_util/color.c +19 -0
  83. data/ext/rgame_util/color.h +60 -0
  84. data/ext/rgame_util/color_ext.c +156 -0
  85. data/ext/rgame_util/extconf.rb +27 -0
  86. data/ext/rgame_util/tensor.c +186 -0
  87. data/ext/rgame_util/util_ext.c +27 -0
  88. data/ext/rgame_util/util_ext.h +16 -0
  89. data/lib/rgame/boot.rb +13 -0
  90. data/lib/rgame/core/app.rb +82 -0
  91. data/lib/rgame/core/asset_manager.rb +224 -0
  92. data/lib/rgame/core/audio.rb +124 -0
  93. data/lib/rgame/core/font.rb +49 -0
  94. data/lib/rgame/core/gamepad.rb +55 -0
  95. data/lib/rgame/core/image.rb +55 -0
  96. data/lib/rgame/core/input.rb +77 -0
  97. data/lib/rgame/core/nine_slice.rb +163 -0
  98. data/lib/rgame/core/recording.rb +52 -0
  99. data/lib/rgame/core/renderer.rb +363 -0
  100. data/lib/rgame/core/sprite_sheet.rb +108 -0
  101. data/lib/rgame/core/tile_map_renderer.rb +160 -0
  102. data/lib/rgame/core/ui_atlas.rb +86 -0
  103. data/lib/rgame/core.rb +24 -0
  104. data/lib/rgame/engine/actor.rb +53 -0
  105. data/lib/rgame/engine/animation_set.rb +49 -0
  106. data/lib/rgame/engine/animator.rb +44 -0
  107. data/lib/rgame/engine/audio_bus.rb +24 -0
  108. data/lib/rgame/engine/audio_director.rb +29 -0
  109. data/lib/rgame/engine/body.rb +49 -0
  110. data/lib/rgame/engine/cached_label.rb +33 -0
  111. data/lib/rgame/engine/camera.rb +33 -0
  112. data/lib/rgame/engine/camera_view.rb +28 -0
  113. data/lib/rgame/engine/circle_collider.rb +32 -0
  114. data/lib/rgame/engine/collision_box.rb +34 -0
  115. data/lib/rgame/engine/collision_system.rb +44 -0
  116. data/lib/rgame/engine/component.rb +30 -0
  117. data/lib/rgame/engine/components/action_trigger.rb +41 -0
  118. data/lib/rgame/engine/components/animated_sprite.rb +63 -0
  119. data/lib/rgame/engine/components/character_body.rb +70 -0
  120. data/lib/rgame/engine/components/circle_collider.rb +44 -0
  121. data/lib/rgame/engine/components/collision_world.rb +103 -0
  122. data/lib/rgame/engine/components/despawn_offscreen.rb +26 -0
  123. data/lib/rgame/engine/components/path_follow.rb +84 -0
  124. data/lib/rgame/engine/components/player_controller.rb +24 -0
  125. data/lib/rgame/engine/components/pool.rb +53 -0
  126. data/lib/rgame/engine/components/screen_wrap.rb +27 -0
  127. data/lib/rgame/engine/components/sprite.rb +31 -0
  128. data/lib/rgame/engine/components/targeting.rb +54 -0
  129. data/lib/rgame/engine/components/thrust_controller.rb +65 -0
  130. data/lib/rgame/engine/components/tile_world.rb +68 -0
  131. data/lib/rgame/engine/components/timer.rb +75 -0
  132. data/lib/rgame/engine/components/velocity.rb +27 -0
  133. data/lib/rgame/engine/components/wander_controller.rb +60 -0
  134. data/lib/rgame/engine/debug_overlay.rb +106 -0
  135. data/lib/rgame/engine/i18n.rb +97 -0
  136. data/lib/rgame/engine/input/action_mapper.rb +46 -0
  137. data/lib/rgame/engine/input/actions.rb +41 -0
  138. data/lib/rgame/engine/input/player_controller.rb +14 -0
  139. data/lib/rgame/engine/matrix.rb +32 -0
  140. data/lib/rgame/engine/node2d.rb +271 -0
  141. data/lib/rgame/engine/path.rb +78 -0
  142. data/lib/rgame/engine/pool.rb +51 -0
  143. data/lib/rgame/engine/resettable.rb +67 -0
  144. data/lib/rgame/engine/scene/scene_stack.rb +65 -0
  145. data/lib/rgame/engine/signal.rb +75 -0
  146. data/lib/rgame/engine/spatial_hash.rb +71 -0
  147. data/lib/rgame/engine/tile_collision.rb +78 -0
  148. data/lib/rgame/engine/tile_map.rb +149 -0
  149. data/lib/rgame/engine/tileset.rb +101 -0
  150. data/lib/rgame/engine/timer.rb +51 -0
  151. data/lib/rgame/engine.rb +68 -0
  152. data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
  153. data/lib/rgame/fonts/OFL.txt +102 -0
  154. data/lib/rgame/game.rb +129 -0
  155. data/lib/rgame/util/color.rb +27 -0
  156. data/lib/rgame/util/controls.rb +107 -0
  157. data/lib/rgame/util/tensor.rb +12 -0
  158. data/lib/rgame/util.rb +8 -0
  159. data/lib/rgame/version.rb +12 -0
  160. data/lib/rgame.rb +20 -0
  161. 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 */