503 lines16 KBC
1/*
2font.c: TrueType text with stb_truetype and a glyph cache.
3
4Poppins for Latin text, DejaVu Sans (and its bold) for what Poppins lacks
5(Cyrillic, Greek, symbols), then the console's own fonts in /dev_flash for Japanese, Korean
6and Chinese (loaded the first time a title needs them).
7*/
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <math.h>
12#include "plat.h"
13#include "gfx.h"
14#include "font.h"
15#include "lang.h"
16
17#define STB_TRUETYPE_IMPLEMENTATION
18#define STBTT_STATIC
19#include "thirdparty/stb_truetype.h"
20
21typedef struct {
22 unsigned char *data;
23 stbtt_fontinfo info;
24 int ok;
25 const char *path;
26 int tried;
27} face_t;
28
29static face_t faces[FONT_COUNT];
30static const char *face_files[FONT_COUNT] = {"Poppins-Regular.ttf", "Poppins-Medium.ttf", "Poppins-Bold.ttf"};
31
32// the bold weight's own fallback for Cyrillic and Greek, so headings stay bold
33static face_t fallback_bold = { .path = "DejaVuSans-Bold.ttf" };
34
35// fallbacks, in order; the dev_flash ones only exist on a PS3
36static face_t fallbacks[] = {
37 { .path = "DejaVuSans.ttf" },
38 { .path = "/dev_flash/data/font/SCE-PS3-NR-R-JPN.TTF" },
39 { .path = "/dev_flash/data/font/SCE-PS3-YG-R-KOR.TTF" },
40 { .path = "/dev_flash/data/font/SCE-PS3-DH-R-CGB.TTF" },
41 { .path = "/dev_flash/data/font/SCE-PS3-CP-R-KANA.TTF" },
42 { .path = NULL }, // the simulator's stand-in for those (SIM_CJK_FONT)
43};
44#define NUM_FALLBACKS (int)(sizeof(fallbacks) / sizeof(fallbacks[0]))
45
46typedef struct {
47 uint32_t key; // font(4) | size(8) | codepoint(20)
48 uint32_t cp_hi; // codepoints past 20 bits
49 int used;
50 int16_t w, h, xoff, yoff;
51 int16_t advance;
52 unsigned char *bmp;
53} glyph_t;
54
55#define CACHE_SIZE 8192
56static glyph_t cache[CACHE_SIZE];
57
58// the size asked for -> the em size drawn (the small ones a little bigger:
59// they're read from across a room)
60static float Em(int px)
61{
62 return (px < 20 ? px + 2 : px) * 0.80f;
63}
64
65// coverage lifted a touch: light text on a dark background reads thin and
66// broken at the edges with linear blending on a TV
67static unsigned char gamma_lut[256];
68static int cache_count;
69
70static int Face_Load(face_t *f, const char *path)
71{
72 char full[512];
73 size_t len;
74 if (path[0] == '/')
75 snprintf(full, sizeof(full), "%s", path);
76 else
77 snprintf(full, sizeof(full), "%s/fonts/%s", plat_data_dir(), path);
78 f->tried = 1;
79 f->data = plat_read_file(full, &len);
80 if (!f->data)
81 return 0;
82 if (!stbtt_InitFont(&f->info, f->data, stbtt_GetFontOffsetForIndex(f->data, 0))) {
83 free(f->data);
84 f->data = NULL;
85 plat_log("font: %s isn't a font stb_truetype reads", full);
86 return 0;
87 }
88 f->ok = 1;
89 plat_log("font: %s (%u KB)", full, (unsigned)(len / 1024));
90 return 1;
91}
92
93int font_init(void)
94{
95 for (int i = 0; i < 256; i++)
96 gamma_lut[i] = (unsigned char)(255.0f * powf(i / 255.0f, 0.82f) + 0.5f);
97 int ok = 1;
98 for (int i = 0; i < FONT_COUNT; i++)
99 if (!Face_Load(&faces[i], face_files[i]))
100 ok = 0;
101 Face_Load(&fallbacks[0], fallbacks[0].path);
102 if (getenv("SIM_CJK_FONT")) fallbacks[NUM_FALLBACKS - 1].path = getenv("SIM_CJK_FONT"); // NULL on a PS3
103 return ok;
104}
105
106static void Cache_Clear(void);
107void font_flush(void) { Cache_Clear(); } // after a language change (another font may draw the same characters)
108
109static void Cache_Clear(void)
110{
111 gfx_masks_reset(); // (the GPU's copies are keyed by these bitmaps)
112 for (int i = 0; i < CACHE_SIZE; i++) {
113 free(cache[i].bmp);
114 cache[i].bmp = NULL;
115 cache[i].used = 0;
116 }
117 cache_count = 0;
118}
119
120// the face that has this codepoint
121static face_t *Face_For(int font, uint32_t cp, int *glyph)
122{
123 face_t *f = &faces[font];
124 int g;
125 if (f->ok && (g = stbtt_FindGlyphIndex(&f->info, cp)) != 0) {
126 *glyph = g;
127 return f;
128 }
129 if (font == FONT_BOLD) {
130 if (!fallback_bold.tried)
131 Face_Load(&fallback_bold, fallback_bold.path);
132 if (fallback_bold.ok && (g = stbtt_FindGlyphIndex(&fallback_bold.info, cp)) != 0) {
133 *glyph = g;
134 return &fallback_bold;
135 }
136 }
137 // Chinese and Korean text from their own fonts first: the Japanese font has many of the
138 // same characters, drawn the Japanese way (and mixing typefaces within a word)
139 static const int order_default[] = {0, 1, 2, 3, 4, 5}, order_zh[] = {0, 3, 1, 2, 4, 5}, order_ko[] = {0, 2, 1, 3, 4, 5};
140 const char *lc = lang_code(lang_current());
141 const int *order = !strncmp(lc, "zh", 2) ? order_zh : !strcmp(lc, "ko") ? order_ko : order_default;
142 for (int k = 0; k < NUM_FALLBACKS; k++) {
143 face_t *fb = &fallbacks[order[k]];
144 if (!fb->path) continue;
145 if (!fb->tried)
146 Face_Load(fb, fb->path);
147 if (fb->ok && (g = stbtt_FindGlyphIndex(&fb->info, cp)) != 0) {
148 *glyph = g;
149 return fb;
150 }
151 }
152 *glyph = 0;
153 return f->ok ? f : NULL;
154}
155
156static glyph_t *Glyph(int font, int px, uint32_t cp)
157{
158 uint32_t key = ((uint32_t)font << 28) | ((uint32_t)(px & 255) << 20) | (cp & 0xfffff);
159 uint32_t hi = cp >> 20;
160 uint32_t h = (key * 2654435761u) % CACHE_SIZE;
161 for (int probe = 0; probe < CACHE_SIZE; probe++) {
162 glyph_t *g = &cache[(h + probe) % CACHE_SIZE];
163 if (!g->used) {
164 if (cache_count > CACHE_SIZE * 3 / 4) {
165 Cache_Clear();
166 return Glyph(font, px, cp);
167 }
168 int gi, adv, lsb, x0, y0, x1, y1;
169 face_t *f = Face_For(font, cp, &gi);
170 memset(g, 0, sizeof(*g));
171 g->used = 1;
172 g->key = key;
173 g->cp_hi = hi;
174 cache_count++;
175 if (!f)
176 return g;
177 float sc = stbtt_ScaleForMappingEmToPixels(&f->info, Em(px));
178 stbtt_GetGlyphHMetrics(&f->info, gi, &adv, &lsb);
179 g->advance = (int16_t)(adv * sc + 0.5f);
180 // drawn at twice the size and averaged down: smoother edges than
181 // stb_truetype's own antialiasing at small sizes
182 stbtt_GetGlyphBitmapBox(&f->info, gi, sc * 2, sc * 2, &x0, &y0, &x1, &y1);
183 if (x1 > x0 && y1 > y0) {
184 int ex0 = x0 - (((x0 % 2) + 2) % 2), ey0 = y0 - (((y0 % 2) + 2) % 2); // down to even
185 int W2 = x1 - ex0, H2 = y1 - ey0;
186 W2 += W2 & 1;
187 H2 += H2 & 1;
188 unsigned char *big = calloc(W2 * H2, 1);
189 g->w = W2 / 2;
190 g->h = H2 / 2;
191 g->xoff = ex0 / 2;
192 g->yoff = ey0 / 2;
193 g->bmp = malloc(g->w * g->h);
194 if (big && g->bmp) {
195 stbtt_MakeGlyphBitmap(&f->info, big + (y0 - ey0) * W2 + (x0 - ex0), x1 - x0, y1 - y0, W2, sc * 2, sc * 2, gi);
196 for (int j = 0; j < g->h; j++)
197 for (int i = 0; i < g->w; i++) {
198 const unsigned char *b = big + (j * 2) * W2 + i * 2;
199 g->bmp[j * g->w + i] = gamma_lut[(b[0] + b[1] + b[W2] + b[W2 + 1] + 2) >> 2];
200 }
201 } else {
202 free(g->bmp);
203 g->bmp = NULL;
204 g->w = g->h = 0;
205 }
206 free(big);
207 }
208 return g;
209 }
210 if (g->key == key && g->cp_hi == hi)
211 return g;
212 }
213 return NULL;
214}
215
216static uint32_t Utf8_Next(const char **ps)
217{
218 const unsigned char *s = (const unsigned char *)*ps;
219 uint32_t c = *s++;
220 int n = 0;
221 if (c < 0x80) n = 0;
222 else if ((c & 0xe0) == 0xc0) { c &= 0x1f; n = 1; }
223 else if ((c & 0xf0) == 0xe0) { c &= 0x0f; n = 2; }
224 else if ((c & 0xf8) == 0xf0) { c &= 0x07; n = 3; }
225 else { *ps = (const char *)s; return 0xfffd; }
226 while (n--) {
227 if ((*s & 0xc0) != 0x80) { *ps = (const char *)s; return 0xfffd; }
228 c = (c << 6) | (*s++ & 0x3f);
229 }
230 *ps = (const char *)s;
231 return c;
232}
233
234/*
235==================
236Arabic: each letter takes the form its neighbours join it in (DejaVu Sans has the
237presentation forms), and a line with Arabic in it reads right to left; a run of
238other text in it (numbers, Latin) keeps its own order.
239==================
240*/
241#define MAX_CPS 384 // a line's characters (on the stack, a few times over)
242
243// U+0621..U+064A: the isolated presentation form; joins 2: both sides, 1: only to the letter before, 0: neither
244static const uint16_t ar_iso[] = {
245 0xFE80, 0xFE81, 0xFE83, 0xFE85, 0xFE87, 0xFE89, 0xFE8D, 0xFE8F, 0xFE93, 0xFE95, 0xFE99, 0xFE9D, 0xFEA1, 0xFEA5,
246 0xFEA9, 0xFEAB, 0xFEAD, 0xFEAF, 0xFEB1, 0xFEB5, 0xFEB9, 0xFEBD, 0xFEC1, 0xFEC5, 0xFEC9, 0xFECD,
247 0, 0, 0, 0, 0, // 063B..063F
248 0x0640, // tatweel: joins, no forms of its own
249 0xFED1, 0xFED5, 0xFED9, 0xFEDD, 0xFEE1, 0xFEE5, 0xFEE9, 0xFEED, 0xFEEF, 0xFEF1,
250};
251static const uint8_t ar_joins[] = {
252 0, 1, 1, 1, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2,
253 0, 0, 0, 0, 0,
254 2,
255 2, 2, 2, 2, 2, 2, 2, 1, 1, 2,
256};
257
258static int Ar_Letter(uint32_t c) { return c >= 0x621 && c <= 0x64a; }
259static int Ar_Joins(uint32_t c) { return Ar_Letter(c) ? ar_joins[c - 0x621] : 0; }
260static int Ar_Mark(uint32_t c) { return (c >= 0x64b && c <= 0x65f) || c == 0x670; } // harakat: don't break a join
261static int Rtl(uint32_t c) { return (c >= 0x600 && c <= 0x6ff) || (c >= 0xfb50 && c <= 0xfdff) || (c >= 0xfe70 && c <= 0xfefc); }
262
263static void Shape(const uint32_t *in, int n, uint32_t *out, int *nout)
264{
265 int k = 0;
266 for (int i = 0; i < n; i++) {
267 uint32_t c = in[i];
268 if (!Ar_Letter(c) || !ar_iso[c - 0x621] || c == 0x640) { out[k++] = c; continue; }
269 int p = i - 1, q = i + 1;
270 while (p >= 0 && Ar_Mark(in[p])) p--;
271 while (q < n && Ar_Mark(in[q])) q++;
272 int before = p >= 0 && Ar_Joins(in[p]) == 2;
273 uint32_t iso = ar_iso[c - 0x621];
274 // lam then alef: one ligature
275 if (c == 0x644 && q < n && (in[q] == 0x622 || in[q] == 0x623 || in[q] == 0x625 || in[q] == 0x627)) {
276 uint32_t lig = in[q] == 0x622 ? 0xFEF5 : in[q] == 0x623 ? 0xFEF7 : in[q] == 0x625 ? 0xFEF9 : 0xFEFB;
277 out[k++] = lig + before;
278 for (int m = i + 1; m < q; m++) out[k++] = in[m]; // (marks between them)
279 i = q;
280 continue;
281 }
282 int after = Ar_Joins(c) == 2 && q < n && Ar_Joins(in[q]) >= 1;
283 if (Ar_Joins(c) == 2) out[k++] = iso + (before && after ? 3 : before ? 1 : after ? 2 : 0);
284 else out[k++] = iso + (before ? 1 : 0);
285 }
286 *nout = k;
287}
288
289static uint32_t Mirror(uint32_t c)
290{
291 switch (c) {
292 case '(': return ')'; case ')': return '(';
293 case '[': return ']'; case ']': return '[';
294 case '<': return '>'; case '>': return '<';
295 case '{': return '}'; case '}': return '{';
296 }
297 return c;
298}
299
300// a left-to-right character: letters other than Arabic, and digits
301static int Ltr(uint32_t c)
302{
303 if (c >= '0' && c <= '9') return 1;
304 if ((c | 32) >= 'a' && (c | 32) <= 'z') return 1;
305 return c >= 0xc0 && !Rtl(c) && !(c >= 0x2000 && c <= 0x206f);
306}
307
308// a line into the codepoints drawn, left to right (shaped and reordered if it has Arabic)
309static int Visual(const char *s, uint32_t *out, int max)
310{
311 uint32_t in[MAX_CPS];
312 int n = 0, rtl = 0;
313 while (*s && n < MAX_CPS) {
314 uint32_t c = Utf8_Next(&s);
315 rtl |= Rtl(c);
316 in[n++] = c;
317 }
318 if (!rtl) {
319 if (n > max) n = max;
320 memcpy(out, in, n * sizeof(uint32_t));
321 return n;
322 }
323 uint32_t sh[MAX_CPS];
324 int m;
325 Shape(in, n, sh, &m);
326 // what reads left to right: Latin and digits, and punctuation stuck to them ("/dev_hdd0", "(12)")
327 unsigned char ltr[MAX_CPS];
328 for (int i = 0; i < m; i++) ltr[i] = Ltr(sh[i]);
329 for (int changed = 1; changed; ) {
330 changed = 0;
331 for (int i = 0; i < m; i++)
332 if (!ltr[i] && !Rtl(sh[i]) && sh[i] != ' ' && ((i > 0 && ltr[i - 1]) || (i + 1 < m && ltr[i + 1]))) {
333 ltr[i] = 1;
334 changed = 1;
335 }
336 }
337 // right to left, but each run of left-to-right text (with the spaces between its words) kept in order
338 int k = 0;
339 for (int i = m - 1; i >= 0 && k < max; ) {
340 if (!ltr[i]) { out[k++] = Mirror(sh[i]); i--; continue; }
341 int e = i, b = i;
342 while (b > 0 && !Rtl(sh[b - 1])) b--;
343 while (!ltr[b]) b++; // (spaces before the run stay right to left)
344 for (int j = b; j <= e && k < max; j++) out[k++] = sh[j];
345 i = b - 1;
346 }
347 return k;
348}
349
350// the baseline below the top of the line, for this size
351static int Ascent(int font, int px)
352{
353 face_t *f = &faces[font];
354 int asc, desc, gap;
355 if (!f->ok) return px;
356 stbtt_GetFontVMetrics(&f->info, &asc, &desc, &gap);
357 float sc = 0;
358 // Poppins' ascent is generous: centre the cap height in the line instead
359 int cap = (int)(Em(px) * 0.70f);
360 (void)asc; (void)desc; (void)gap; (void)sc;
361 return (px + cap) / 2;
362}
363
364int font_line_height(int font, int px) { (void)font; return px; }
365
366int font_width(int font, int px, const char *s)
367{
368 int w = 0;
369 if (!s) return 0;
370 uint32_t cps[MAX_CPS];
371 int n = Visual(s, cps, MAX_CPS);
372 for (int i = 0; i < n; i++) {
373 glyph_t *g = Glyph(font, px, cps[i]);
374 if (g) w += g->advance;
375 }
376 return w;
377}
378
379/*
380A glyph at pen, base. When the GPU draws at a higher resolution (1080p),
381the bitmap is made at that size and drawn back down to this one, so text
382is as sharp as the screen; the layout (pen, advances) stays the canvas's,
383so nothing moves.
384*/
385static void Put(int font, int px, uint32_t cp, const glyph_t *g, int pen, int base, uint32_t c)
386{
387 float sc = gfx_scale();
388 if (sc != 1.0f) {
389 int spx = (int)(px * sc + 0.5f);
390 if (spx > 1 && spx < 256) {
391 glyph_t *gs = Glyph(font, spx, cp);
392 if (gs && gs->bmp) {
393 gfx_mask_scaled(gs->bmp, gs->w, gs->h, gs->w, pen + gs->xoff / sc, base + gs->yoff / sc, c);
394 return;
395 }
396 }
397 }
398 if (g && g->bmp)
399 gfx_mask(g->bmp, g->w, g->h, g->w, pen + g->xoff, base + g->yoff, c);
400}
401
402static int Draw_At(int font, int px, int x, int y, const char *s, uint32_t c, int maxw);
403
404// right to left (gfx_mirrored): the line ends where it would start, measured first (no alpha: nothing drawn)
405static int Draw(int font, int px, int x, int y, const char *s, uint32_t c, int maxw)
406{
407 if (gfx_mirrored() && s) x = SCREEN_W - x - Draw_At(font, px, x, y, s, c & 0x00ffffff, maxw);
408 return Draw_At(font, px, x, y, s, c, maxw);
409}
410
411static int Draw_At(int font, int px, int x, int y, const char *s, uint32_t c, int maxw)
412{
413 int base = y + Ascent(font, px);
414 int pen = x;
415 const char *ell = "\xe2\x80\xa6";
416 int ellw = 0, cut = 0;
417
418 if (!s) return 0;
419 uint32_t cps[MAX_CPS];
420 int n = Visual(s, cps, MAX_CPS), first = 0, total = 0;
421 int rtl = 0;
422 for (int i = 0; i < n; i++) rtl |= Rtl(cps[i]);
423 if (maxw > 0 && (total = font_width(font, px, s)) > maxw) {
424 ellw = font_width(font, px, ell);
425 cut = 1;
426 }
427 // right to left, the end of the text is on the left: that's what goes, the "…" first
428 if (cut && rtl) {
429 for (int w = total; first < n && w > maxw - ellw; first++) {
430 glyph_t *g = Glyph(font, px, cps[first]);
431 if (g) w -= g->advance;
432 }
433 const char *e = ell;
434 uint32_t ecp = Utf8_Next(&e);
435 glyph_t *g = Glyph(font, px, ecp);
436 int adv = g ? g->advance : 0;
437 if (g && g->bmp && (c >> 24))
438 Put(font, px, ecp, g, pen, base, c);
439 pen += adv;
440 cut = 0;
441 }
442 for (int i = first; i < n; i++) {
443 uint32_t cp = cps[i];
444 glyph_t *g = Glyph(font, px, cp);
445 if (!g) continue;
446 if (cut && pen - x + g->advance > maxw - ellw)
447 break;
448 int adv = g->advance; // (Put may refill the cache under g)
449 if (g->bmp && (c >> 24))
450 Put(font, px, cp, g, pen, base, c);
451 pen += adv;
452 }
453 if (cut) {
454 const char *e = ell;
455 uint32_t ecp = Utf8_Next(&e);
456 glyph_t *g = Glyph(font, px, ecp);
457 int adv = g ? g->advance : 0;
458 if (g && g->bmp && (c >> 24))
459 Put(font, px, ecp, g, pen, base, c);
460 pen += adv;
461 }
462 return pen - x;
463}
464
465int font_draw(int font, int px, int x, int y, const char *s, uint32_t c, int maxw)
466{
467 return Draw(font, px, x, y, s, c, maxw);
468}
469
470int font_draw_center(int font, int px, int cx, int y, const char *s, uint32_t c, int maxw)
471{
472 int w = font_width(font, px, s);
473 if (maxw > 0 && w > maxw) w = maxw;
474 return Draw(font, px, cx - w / 2, y, s, c, maxw);
475}
476
477int font_draw_right(int font, int px, int rx, int y, const char *s, uint32_t c)
478{
479 int w = font_width(font, px, s);
480 return Draw(font, px, rx - w, y, s, c, 0);
481}
482
483// one character with its drawn shape (not its line box) centred on (cx, cy)
484void font_draw_glyph_centered(int font, int px, int cx, int cy, const char *ch, uint32_t c)
485{
486 if (gfx_mirrored()) cx = SCREEN_W - cx;
487 const char *p = ch;
488 uint32_t cp = Utf8_Next(&p);
489 glyph_t *g = Glyph(font, px, cp);
490 if (!g || !g->bmp) return;
491 float sc = gfx_scale();
492 if (sc != 1.0f) {
493 int spx = (int)(px * sc + 0.5f);
494 glyph_t *gs = spx > 1 && spx < 256 ? Glyph(font, spx, cp) : NULL;
495 if (gs && gs->bmp) {
496 gfx_mask_scaled(gs->bmp, gs->w, gs->h, gs->w, cx - gs->w / sc / 2, cy - gs->h / sc / 2, c);
497 return;
498 }
499 g = Glyph(font, px, cp); // (the cache may have been refilled)
500 if (!g || !g->bmp) return;
501 }
502 gfx_mask(g->bmp, g->w, g->h, g->w, cx - g->w / 2, cy - g->h / 2, c);
503}