| 1 | /* |
| 2 | font.c: TrueType text with stb_truetype and a glyph cache. |
| 3 |
|
| 4 | Poppins 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 |
| 6 | and 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 |
|
| 21 | typedef struct { |
| 22 | unsigned char *data; |
| 23 | stbtt_fontinfo info; |
| 24 | int ok; |
| 25 | const char *path; |
| 26 | int tried; |
| 27 | } face_t; |
| 28 |
|
| 29 | static face_t faces[FONT_COUNT]; |
| 30 | static 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 |
| 33 | static face_t fallback_bold = { .path = "DejaVuSans-Bold.ttf" }; |
| 34 |
|
| 35 | // fallbacks, in order; the dev_flash ones only exist on a PS3 |
| 36 | static 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 |
|
| 46 | typedef 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 |
| 56 | static 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) |
| 60 | static 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 |
| 67 | static unsigned char gamma_lut[256]; |
| 68 | static int cache_count; |
| 69 |
|
| 70 | static 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 |
|
| 93 | int 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 |
|
| 106 | static void Cache_Clear(void); |
| 107 | void font_flush(void) { Cache_Clear(); } // after a language change (another font may draw the same characters) |
| 108 |
|
| 109 | static 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 |
| 121 | static 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 |
|
| 156 | static 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 |
|
| 216 | static 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 | ================== |
| 236 | Arabic: each letter takes the form its neighbours join it in (DejaVu Sans has the |
| 237 | presentation forms), and a line with Arabic in it reads right to left; a run of |
| 238 | other 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 |
| 244 | static 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 | }; |
| 251 | static 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 |
|
| 258 | static int Ar_Letter(uint32_t c) { return c >= 0x621 && c <= 0x64a; } |
| 259 | static int Ar_Joins(uint32_t c) { return Ar_Letter(c) ? ar_joins[c - 0x621] : 0; } |
| 260 | static int Ar_Mark(uint32_t c) { return (c >= 0x64b && c <= 0x65f) || c == 0x670; } // harakat: don't break a join |
| 261 | static int Rtl(uint32_t c) { return (c >= 0x600 && c <= 0x6ff) || (c >= 0xfb50 && c <= 0xfdff) || (c >= 0xfe70 && c <= 0xfefc); } |
| 262 |
|
| 263 | static 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 |
|
| 289 | static 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 |
| 301 | static 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) |
| 309 | static 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 |
| 351 | static 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 |
|
| 364 | int font_line_height(int font, int px) { (void)font; return px; } |
| 365 |
|
| 366 | int 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 | /* |
| 380 | A glyph at pen, base. When the GPU draws at a higher resolution (1080p), |
| 381 | the bitmap is made at that size and drawn back down to this one, so text |
| 382 | is as sharp as the screen; the layout (pen, advances) stays the canvas's, |
| 383 | so nothing moves. |
| 384 | */ |
| 385 | static 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 |
|
| 402 | static 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) |
| 405 | static 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 |
|
| 411 | static 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 |
|
| 465 | int 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 |
|
| 470 | int 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 |
|
| 477 | int 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) |
| 484 | void 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 | } |