Pico Headers
Loading...
Searching...
No Matches
pico_font.h
Go to the documentation of this file.
1
87#ifndef PICO_FONT_H
88#define PICO_FONT_H
89
90#include <stdbool.h>
91#include <stdint.h>
92#include <stdlib.h>
93#include <string.h>
94
95// ---- Types ------------------------------------------------------------------
96
97// Opaque handles
98typedef struct pf_atlas_t pf_atlas_t;
99typedef struct pf_face_t pf_face_t;
100
104typedef struct
105{
106 uint32_t codepoint; // Unicode codepoint
107 float size; // Font pixel height used when rasterizing
108 int glyph_index; // stbtt glyph index (0 = missing glyph)
109
110 // Position inside page (pixels)
111 int page_x, page_y;
112 int page_w, page_h;
113
114 // Atlas page index
115 size_t page;
116
117 // Offset from cursor to top-left of bitmap
118 int offset_x, offset_y;
119
120 // Horizontal advance in pixels (scaled)
122
123 // UV corners (computed after placement)
124 float u0, v0, u1, v1;
125} pf_glyph_t;
126
130typedef struct
131{
132 float x0, y0, x1, y1; // screen-space rectangle
133 float u0, v0, u1, v1; // atlas UVs
134 size_t page; // atlas page index
135} pf_quad_t;
136
140typedef struct
141{
142 float ascent;
143 float descent;
144 float line_gap;
147
153typedef bool (*pf_draw_callback_fn)(const pf_quad_t* quad, void* user);
154
164typedef bool (*pf_upload_callback_fn)(size_t page, const unsigned char* pixels,
165 int width, int height, void* user);
166
167// ---- API --------------------------------------------------------------------
168
169#ifdef __cplusplus
170extern "C" {
171#endif
172
185pf_atlas_t* pf_create_atlas(int page_width, int max_page_height);
186
193
207 const unsigned char* ttf_data,
208 float pixel_height);
209
218
229const pf_glyph_t* pf_get_glyph(pf_face_t* face, uint32_t codepoint);
230
246void pf_draw_text(pf_face_t* face, const char* text,
247 float* x, float* y,
248 pf_draw_callback_fn cb, void* user);
249
262
273void pf_measure_text(pf_face_t* face, const char* text,
274 float* out_width, float* out_height);
275
282void pf_get_metrics(const pf_face_t* face, pf_metrics_t* metrics);
283
292float pf_get_kerning(const pf_face_t* face, uint32_t cp1, uint32_t cp2);
293
294#ifdef __cplusplus
295}
296#endif
297
298#endif // PICO_FONT_H
299
300// ---- Implementation ---------------------------------------------------------
301
302#ifdef PICO_FONT_IMPLEMENTATION
303
304// Padding around each glyph in the atlas (pixels).
305#ifndef PICO_FONT_GLYPH_PADDING
306#define PICO_FONT_GLYPH_PADDING 1
307#endif
308
309// Initial hash table size for the glyph cache. Must be power of two.
310#ifndef PICO_FONT_CACHE_INIT_SIZE
311#define PICO_FONT_CACHE_INIT_SIZE 256
312#endif
313
314// Initial height for newly created atlas pages. Set to 0 to defer allocation.
315#ifndef PICO_FONT_INIT_PAGE_HEIGHT
316#define PICO_FONT_INIT_PAGE_HEIGHT 64
317#endif
318
319#ifdef NDEBUG
320 #define PICO_FONT_ASSERT(expr) ((void)0)
321#else
322 #ifndef PICO_FONT_ASSERT
323 #include <assert.h>
324 #define PICO_FONT_ASSERT(expr) (assert(expr))
325 #endif
326#endif
327
328#if !defined(PICO_FONT_CALLOC) || \
329 !defined(PICO_FONT_REALLOC) || \
330 !defined(PICO_FONT_FREE)
331 #include <stdlib.h>
332 #define PICO_FONT_CALLOC(num, size) (calloc(num, size))
333 #define PICO_FONT_REALLOC(ptr, size) (realloc(ptr, size))
334 #define PICO_FONT_FREE(ptr) (free(ptr))
335#endif
336
337#ifndef PICO_FONT_MEMSET
338 #include <string.h>
339 #define PICO_FONT_MEMSET memset
340#endif
341
342// Sentinel value returned by internal functions to signal failure.
343#define PICO_FONT_ERROR ((size_t)-1)
344
345#include "stb_truetype.h"
346
347// ---- Internal types ---------------------------------------------------------
348
349// Hash-table entry for glyph cache (open addressing, linear probe).
350typedef struct
351{
352 uint32_t key; // hash key; 0 = empty slot
353 size_t glyph_index; // index into pf_atlas_t.glyphs[]
354} pf_cache_entry_t;
355
356// Shelf-based packing state.
357typedef struct
358{
359 int cursor_x; // next free x in current shelf
360 int cursor_y; // top of current shelf
361 int shelf_height; // height of current shelf row
362} pf_shelf_t;
363
364// A single page in the atlas. Each page has its own bitmap.
365typedef struct
366{
367 unsigned char* pixels;
368 int width, height;
369 bool dirty; // set to true whenever pixels are modified
370 pf_shelf_t shelf;
371} pf_atlas_page_t;
372
373// Full atlas definition (opaque to users).
374struct pf_atlas_t
375{
376 // page storage (dynamic array of pages)
377 pf_atlas_page_t* pages;
378 size_t page_count;
379 size_t page_capacity;
380 int page_width; // fixed width for all pages
381 int max_page_height; // maximum height a page may grow to
382
383 // glyph storage (dynamic array)
384 pf_glyph_t* glyphs;
385 size_t glyph_count;
386 size_t glyph_capacity;
387
388 // hash table for fast lookup
389 pf_cache_entry_t* cache;
390 size_t cache_size; // always power of two
391};
392
393// Full face definition (opaque to users).
394struct pf_face_t
395{
396 stbtt_fontinfo info;
397 float size; // requested pixel height
398 float scale; // stbtt scale factor
399 int ascent; // scaled ascent in pixels
400 int descent;
401 int line_gap;
402 pf_atlas_t* atlas;
403};
404
405// Measure callback state.
406typedef struct
407{
408 float max_x;
409 float max_y;
410} pf_measure_state_t;
411
412// ---- Forward declarations ---------------------------------------------------
413
414// Compute a hash key from a codepoint and font size.
415static uint32_t pf_hash_key(uint32_t cp, float size);
416
417// Look up a glyph index in the cache by hash key.
418static size_t pf_cache_lookup(const pf_atlas_t* atlas, uint32_t key);
419
420// Insert a key/glyph-index pair into a cache table without growing.
421static void pf_cache_insert_raw(pf_cache_entry_t* cache, size_t cache_size,
422 uint32_t key, size_t glyph_index);
423
424// Insert a key/glyph-index pair, growing the cache if needed.
425static int pf_cache_insert(pf_atlas_t* atlas, uint32_t key,
426 size_t glyph_index);
427
428// Append a new page to the atlas and return its index.
429static size_t pf_atlas_add_page(pf_atlas_t* atlas);
430
431// Try to allocate a rectangle on a single atlas page using shelf packing.
432static int pf_page_alloc(pf_atlas_page_t* page, int w, int h,
433 int* out_x, int* out_y);
434
435// Grow a page's pixel buffer vertically up to max_height.
436static int pf_page_grow(pf_atlas_page_t* page, int needed_height,
437 int max_height);
438
439// Recompute v0/v1 for all glyphs on a page after it grows.
440static void pf_page_recompute_uvs(pf_atlas_t* atlas, size_t page_index);
441
442// Allocate a rectangle in the atlas, adding a new page if necessary.
443static int pf_atlas_alloc(pf_atlas_t* atlas, int w, int h,
444 int* out_x, int* out_y, size_t* out_page);
445
446// Append a glyph to the atlas glyph array and return its index.
447static size_t pf_glyph_push(pf_atlas_t* atlas, const pf_glyph_t* g);
448
449// Decode one UTF-8 codepoint from a string, advancing the pointer.
450static uint32_t pf_utf8_decode(const char** str);
451
452// Iterate over a UTF-8 string, resolving glyphs and emitting quads.
453static void pf_walk_text(pf_face_t* face, const char* text,
454 float* x, float* y,
455 pf_draw_callback_fn cb, void* user);
456
457// Quad callback used by pf_measure_text to track bounding box extents.
458static bool pf_measure_cb(const pf_quad_t* quad, void* user);
459
460// ---- Public API -------------------------------------------------------------
461
462pf_atlas_t* pf_create_atlas(int page_width, int max_page_height)
463{
464 PICO_FONT_ASSERT(page_width > 0);
465 PICO_FONT_ASSERT(max_page_height > 0);
466
467 pf_atlas_t* atlas = (pf_atlas_t*)PICO_FONT_CALLOC(1, sizeof(pf_atlas_t));
468
469 if (!atlas)
470 return NULL;
471
472 atlas->page_width = page_width;
473 atlas->max_page_height = max_page_height;
474
475 atlas->cache_size = PICO_FONT_CACHE_INIT_SIZE;
476 atlas->cache = (pf_cache_entry_t*)PICO_FONT_CALLOC(atlas->cache_size,
477 sizeof(pf_cache_entry_t));
478
479 if (!atlas->cache)
480 {
481 PICO_FONT_FREE(atlas);
482 return NULL;
483 }
484
485 // Create the first page.
486 if (pf_atlas_add_page(atlas) == PICO_FONT_ERROR)
487 {
488 PICO_FONT_FREE(atlas->cache);
489 PICO_FONT_FREE(atlas);
490 return NULL;
491 }
492
493 return atlas;
494}
495
496void pf_destroy_atlas(pf_atlas_t* atlas)
497{
498 if (!atlas)
499 return;
500
501 for (size_t i = 0; i < atlas->page_count; i++)
502 {
503 PICO_FONT_FREE(atlas->pages[i].pixels);
504 }
505
506 PICO_FONT_FREE(atlas->pages);
507 PICO_FONT_FREE(atlas->glyphs);
508 PICO_FONT_FREE(atlas->cache);
509 PICO_FONT_FREE(atlas);
510}
511
513 const unsigned char* ttf_data,
514 float pixel_height)
515{
516 PICO_FONT_ASSERT(atlas != NULL);
517 PICO_FONT_ASSERT(ttf_data != NULL);
518 PICO_FONT_ASSERT(pixel_height > 0.0f);
519
520 pf_face_t* face = (pf_face_t*)PICO_FONT_CALLOC(1, sizeof(pf_face_t));
521
522 if (!face)
523 return NULL;
524
525 face->atlas = atlas;
526 face->size = pixel_height;
527
528 int offset = stbtt_GetFontOffsetForIndex(ttf_data, 0);
529 if (offset < 0)
530 {
531 PICO_FONT_FREE(face);
532 return NULL;
533 }
534
535 if (!stbtt_InitFont(&face->info, ttf_data, offset))
536 {
537 PICO_FONT_FREE(face);
538 return NULL;
539 }
540
541 face->scale = stbtt_ScaleForPixelHeight(&face->info, pixel_height);
542
543 int ascent, descent, gap;
544 stbtt_GetFontVMetrics(&face->info, &ascent, &descent, &gap);
545 face->ascent = (int)(ascent * face->scale + 0.5f);
546 face->descent = (int)(descent * face->scale - 0.5f);
547 face->line_gap = (int)(gap * face->scale + 0.5f);
548 return face;
549}
550
551void pf_destroy_face(pf_face_t* face)
552{
553 if (!face)
554 return;
555
556 PICO_FONT_FREE(face);
557}
558
559const pf_glyph_t* pf_get_glyph(pf_face_t* face, uint32_t codepoint)
560{
561 PICO_FONT_ASSERT(face != NULL);
562
563 pf_atlas_t* atlas = face->atlas;
564 uint32_t key = pf_hash_key(codepoint, face->size);
565
566 // Check cache first.
567 size_t cached = pf_cache_lookup(atlas, key);
568 if (cached != PICO_FONT_ERROR)
569 {
570 // Verify it's actually the right glyph (hash collision check).
571 pf_glyph_t* glyph = &atlas->glyphs[cached];
572
573 if (glyph->codepoint == codepoint && glyph->size == face->size)
574 {
575 return glyph;
576 }
577 /*
578 * Collision: fall through to rasterize (rare). For simplicity we
579 * linear-probe for a matching glyph in the table. A full collision
580 * resolution would use a secondary key; this is acceptable for the
581 * typical glyph counts involved.
582 */
583 }
584
585 // Rasterize the glyph.
586 int glyph_index = stbtt_FindGlyphIndex(&face->info, (int)codepoint);
587
588 int advance_raw, lsb;
589 stbtt_GetGlyphHMetrics(&face->info, glyph_index, &advance_raw, &lsb);
590
591 pf_glyph_t glyph = { 0 };
592 glyph.codepoint = codepoint;
593 glyph.size = face->size;
594 glyph.glyph_index = glyph_index;
595 glyph.advance_x = advance_raw * face->scale;
596
597 // Empty glyphs (space, control chars).
598 if (stbtt_IsGlyphEmpty(&face->info, glyph_index))
599 {
600 glyph.page_w = 0;
601 glyph.page_h = 0;
602
603 size_t index = pf_glyph_push(atlas, &glyph);
604 if (index == PICO_FONT_ERROR)
605 return NULL;
606
607 pf_cache_insert(atlas, key, index);
608 return &atlas->glyphs[index];
609 }
610
611 int x0, y0, x1, y1;
612 stbtt_GetGlyphBitmapBox(&face->info, glyph_index,
613 face->scale, face->scale,
614 &x0, &y0, &x1, &y1);
615 int bw = x1 - x0;
616 int bh = y1 - y0;
617
618 if (bw <= 0 || bh <= 0)
619 {
620 size_t index = pf_glyph_push(atlas, &glyph);
621
622 if (index == PICO_FONT_ERROR)
623 return NULL;
624
625 pf_cache_insert(atlas, key, index);
626 return &atlas->glyphs[index];
627 }
628
629 // Allocate space in atlas.
630 int ax, ay;
631 size_t ap;
632 if (pf_atlas_alloc(atlas, bw, bh, &ax, &ay, &ap) != 0)
633 {
634 return NULL; // atlas is completely full
635 }
636
637 // Render into the page's bitmap.
638 pf_atlas_page_t* page = &atlas->pages[ap];
639 stbtt_MakeGlyphBitmap(&face->info,
640 page->pixels + ay * page->width + ax,
641 bw, bh, page->width,
642 face->scale, face->scale,
643 glyph_index);
644 page->dirty = true;
645
646 glyph.page_x = ax;
647 glyph.page_y = ay;
648 glyph.page_w = bw;
649 glyph.page_h = bh;
650 glyph.page = ap;
651 glyph.offset_x = x0;
652 glyph.offset_y = y0;
653
654 float inv_w = 1.0f / (float)page->width;
655 float inv_h = 1.0f / (float)page->height;
656
657 glyph.u0 = (float)ax * inv_w;
658 glyph.v0 = (float)ay * inv_h;
659 glyph.u1 = (float)(ax + bw) * inv_w;
660 glyph.v1 = (float)(ay + bh) * inv_h;
661
662 size_t index = pf_glyph_push(atlas, &glyph);
663
664 if (index == PICO_FONT_ERROR)
665 return NULL;
666
667 pf_cache_insert(atlas, key, index);
668 return &atlas->glyphs[index];
669}
670
671void pf_draw_text(pf_face_t* face, const char* text,
672 float* x, float* y,
673 pf_draw_callback_fn cb, void* user)
674{
675 PICO_FONT_ASSERT(face != NULL);
676 PICO_FONT_ASSERT(x != NULL);
677 PICO_FONT_ASSERT(y != NULL);
678
679 if (!text)
680 return;
681
682 pf_walk_text(face, text, x, y, cb, user);
683}
684
685void pf_upload_atlas(pf_atlas_t* atlas, pf_upload_callback_fn cb, void* user)
686{
687 if (!atlas || !cb)
688 return;
689
690 for (size_t i = 0; i < atlas->page_count; i++)
691 {
692 pf_atlas_page_t* page = &atlas->pages[i];
693
694 if (!page->dirty)
695 continue;
696
697 if (!cb(i, page->pixels, page->width, page->height, user))
698 {
699 return;
700 }
701
702 page->dirty = false;
703 }
704}
705
706void pf_measure_text(pf_face_t* face, const char* text,
707 float* out_width, float* out_height)
708{
709 PICO_FONT_ASSERT(face != NULL);
710
711 if (!text)
712 {
713 if (out_width)
714 *out_width = 0;
715
716 if (out_height)
717 *out_height = 0;
718
719 return;
720 }
721
722 float x = 0, y = 0;
723 pf_measure_state_t state = { 0, 0 };
724 pf_walk_text(face, text, &x, &y, pf_measure_cb, &state);
725
726 // Account for trailing spaces by checking cursor.
727 if (x > state.max_x)
728 state.max_x = x;
729
730 float line_height = (float)(face->ascent - face->descent + face->line_gap);
731
732 if (out_width)
733 *out_width = state.max_x;
734
735 if (out_height)
736 *out_height = (state.max_x > 0) ? line_height : 0;
737}
738
739void pf_get_metrics(const pf_face_t* face, pf_metrics_t* metrics)
740{
741 PICO_FONT_ASSERT(face != NULL);
742 PICO_FONT_ASSERT(metrics != NULL);
743
744 metrics->ascent = (float)face->ascent;
745 metrics->descent = (float)face->descent;
746 metrics->line_gap = (float)face->line_gap;
747 metrics->line_height = (float)(face->ascent - face->descent + face->line_gap);
748}
749
750float pf_get_kerning(const pf_face_t* face, uint32_t cp1, uint32_t cp2)
751{
752 PICO_FONT_ASSERT(face != NULL);
753
754 int g1 = stbtt_FindGlyphIndex(&face->info, (int)cp1);
755 int g2 = stbtt_FindGlyphIndex(&face->info, (int)cp2);
756
757 return stbtt_GetGlyphKernAdvance(&face->info, g1, g2) * face->scale;
758}
759
760// ---- Internal helpers -------------------------------------------------------
761
762static uint32_t pf_hash_key(uint32_t cp, float size)
763{
764 uint32_t x = cp ^ (uint32_t)(size * 100.f);
765 x = ((x >> 16) ^ x) * 0x45d9f3b;
766 x = ((x >> 16) ^ x) * 0x45d9f3b;
767 x = (x >> 16) ^ x;
768 return x;
769}
770
771static size_t pf_cache_lookup(const pf_atlas_t* atlas, uint32_t key)
772{
773 size_t mask = atlas->cache_size - 1;
774 size_t index = (size_t)(key) & mask;
775
776 for (size_t i = 0; i < atlas->cache_size; i++)
777 {
778 size_t slot = (index + i) & mask;
779
780 if (atlas->cache[slot].key == key)
781 return atlas->cache[slot].glyph_index;
782
783 if (atlas->cache[slot].key == 0)
784 return PICO_FONT_ERROR; // empty slot → not found
785 }
786
787 return PICO_FONT_ERROR;
788}
789
790static void pf_cache_insert_raw(pf_cache_entry_t* cache, size_t cache_size,
791 uint32_t key, size_t glyph_index)
792{
793 PICO_FONT_ASSERT(key != 0);
794
795 size_t mask = cache_size - 1;
796 size_t index = (size_t)(key) & mask;
797
798 for (size_t i = 0; i < cache_size; i++)
799 {
800 size_t slot = (index + i) & mask;
801
802 if (cache[slot].key == 0)
803 {
804 cache[slot].key = key;
805 cache[slot].glyph_index = glyph_index;
806 return;
807 }
808 }
809 // Should never happen if load factor is kept in check.
810 PICO_FONT_ASSERT(false);
811}
812
813static int pf_cache_insert(pf_atlas_t* atlas, uint32_t key, size_t glyph_index)
814{
815 // Grow if load factor > 0.7
816 size_t used = atlas->glyph_count; // approximate
817
818 if (used * 10 > atlas->cache_size * 7)
819 {
820 size_t new_size = atlas->cache_size * 2;
821
822 pf_cache_entry_t* new_cache = (pf_cache_entry_t*)PICO_FONT_CALLOC(new_size,
823 sizeof(pf_cache_entry_t));
824
825 if (!new_cache)
826 return -1;
827
828 // rehash
829 for (size_t i = 0; i < atlas->cache_size; i++)
830 {
831 if (atlas->cache[i].key != 0)
832 {
833 pf_cache_insert_raw(new_cache, new_size,
834 atlas->cache[i].key,
835 atlas->cache[i].glyph_index);
836 }
837 }
838
839 PICO_FONT_FREE(atlas->cache);
840
841 atlas->cache = new_cache;
842 atlas->cache_size = new_size;
843 }
844
845 pf_cache_insert_raw(atlas->cache, atlas->cache_size, key, glyph_index);
846
847 return 0;
848}
849
850// Add a new page to the atlas. Returns the page index or PICO_FONT_ERROR on failure.
851static size_t pf_atlas_add_page(pf_atlas_t* atlas)
852{
853 if (atlas->page_count >= atlas->page_capacity)
854 {
855 size_t new_capacity = atlas->page_capacity ? atlas->page_capacity * 2 : 4;
856
857 pf_atlas_page_t* new_array = (pf_atlas_page_t*)PICO_FONT_REALLOC(atlas->pages,
858 new_capacity * sizeof(pf_atlas_page_t));
859
860 if (!new_array)
861 return PICO_FONT_ERROR;
862
863 atlas->pages = new_array;
864 atlas->page_capacity = new_capacity;
865 }
866
867 size_t index = atlas->page_count;
868
869 pf_atlas_page_t* page = &atlas->pages[index];
870 PICO_FONT_MEMSET(page, 0, sizeof(*page));
871 page->width = atlas->page_width;
872 page->height = 0;
873
874 if (pf_page_grow(page, PICO_FONT_INIT_PAGE_HEIGHT,
875 atlas->max_page_height) != 0)
876 {
877 return PICO_FONT_ERROR;
878 }
879
880 atlas->page_count++;
881
882 return index;
883}
884
885// Try to allocate a rectangle on a specific page. Returns 0 on success.
886static int pf_page_alloc(pf_atlas_page_t* page, int w, int h,
887 int* out_x, int* out_y)
888{
889 int pad = PICO_FONT_GLYPH_PADDING;
890 int pw = w + pad;
891 int ph = h + pad;
892
893 // Try current shelf.
894 if (page->shelf.cursor_x + pw <= page->width &&
895 page->shelf.cursor_y + ph <= page->height)
896 {
897 if (ph > page->shelf.shelf_height)
898 page->shelf.shelf_height = ph;
899
900 *out_x = page->shelf.cursor_x;
901 *out_y = page->shelf.cursor_y;
902 page->shelf.cursor_x += pw;
903
904 return 0;
905 }
906
907 // Start a new shelf.
908 page->shelf.cursor_x = 0;
909 page->shelf.cursor_y += page->shelf.shelf_height;
910 page->shelf.shelf_height = 0;
911
912 if (page->shelf.cursor_x + pw <= page->width &&
913 page->shelf.cursor_y + ph <= page->height)
914 {
915 page->shelf.shelf_height = ph;
916
917 *out_x = page->shelf.cursor_x;
918 *out_y = page->shelf.cursor_y;
919 page->shelf.cursor_x += pw;
920
921 return 0;
922 }
923
924 return -1; // page is full
925}
926
927/*
928 * Try to grow a page's pixel buffer vertically. Doubles the current height
929 * (starting from 1 when height == 0) until it reaches at least needed_height,
930 * capped at max_height. Returns 0 on success.
931 */
932static int pf_page_grow(pf_atlas_page_t* page, int needed_height, int max_height)
933{
934 PICO_FONT_ASSERT(page != NULL);
935 PICO_FONT_ASSERT(max_height > 0);
936
937 if (needed_height <= page->height)
938 return 0;
939
940 if (needed_height > max_height)
941 return -1;
942
943 int new_height = page->height > 0 ? page->height : 1;
944 while (new_height < needed_height)
945 {
946 new_height *= 2;
947 }
948
949 if (new_height > max_height)
950 new_height = max_height;
951
952 unsigned char* new_pixels = (unsigned char*)PICO_FONT_REALLOC(page->pixels,
953 (size_t)page->width * (size_t)new_height);
954
955 if (!new_pixels)
956 return -1;
957
958 // Zero the newly added rows.
959 PICO_FONT_MEMSET(new_pixels + (size_t)page->width * (size_t)page->height, 0,
960 (size_t)page->width *
961 (size_t)(new_height - page->height));
962
963 page->pixels = new_pixels;
964 page->height = new_height;
965 page->dirty = true;
966
967 return 0;
968}
969
970// Recompute v0/v1 for every glyph on the given page using its current height.
971static void pf_page_recompute_uvs(pf_atlas_t* atlas, size_t page_index)
972{
973 PICO_FONT_ASSERT(page_index < atlas->page_count);
974 PICO_FONT_ASSERT(atlas->pages[page_index].height > 0);
975
976 float inv_h = 1.0f / (float)atlas->pages[page_index].height;
977
978 for (size_t i = 0; i < atlas->glyph_count; i++)
979 {
980 pf_glyph_t* g = &atlas->glyphs[i];
981
982 if (g->page != page_index)
983 continue;
984
985 g->v0 = (float)g->page_y * inv_h;
986 g->v1 = (float)(g->page_y + g->page_h) * inv_h;
987 }
988}
989
990/*
991 * Allocate a rectangle in the atlas. Tries the current page, growing it
992 * vertically if needed, and only adds a new page as a last resort.
993 * Returns 0 on success.
994 */
995static int pf_atlas_alloc(pf_atlas_t* atlas, int w, int h,
996 int* out_x, int* out_y, size_t* out_page)
997{
998 int pad = PICO_FONT_GLYPH_PADDING;
999 int ph = h + pad;
1000
1001 // Try the last (current) page.
1002 if (atlas->page_count > 0)
1003 {
1004 size_t page_index = atlas->page_count - 1;
1005 pf_atlas_page_t* page = &atlas->pages[page_index];
1006
1007 if (pf_page_alloc(page, w, h, out_x, out_y) == 0)
1008 {
1009 *out_page = page_index;
1010 return 0;
1011 }
1012
1013 // Shelf packing failed -- try growing the page height.
1014 int needed = page->shelf.cursor_y + page->shelf.shelf_height + ph;
1015
1016 if (pf_page_grow(page, needed, atlas->max_page_height) == 0)
1017 {
1018 pf_page_recompute_uvs(atlas, page_index);
1019
1020 if (pf_page_alloc(page, w, h, out_x, out_y) == 0)
1021 {
1022 *out_page = page_index;
1023 return 0;
1024 }
1025 }
1026 }
1027
1028 // Current page is at max height -- add a new one.
1029 size_t page_index = pf_atlas_add_page(atlas);
1030
1031 if (page_index == PICO_FONT_ERROR)
1032 return -1;
1033
1034 // Grow the fresh page to fit the glyph.
1035 pf_atlas_page_t* page = &atlas->pages[page_index];
1036 if (pf_page_grow(page, ph, atlas->max_page_height) != 0)
1037 {
1038 return -1;
1039 }
1040
1041 if (pf_page_alloc(page, w, h, out_x, out_y) == 0)
1042 {
1043 *out_page = page_index;
1044 return 0;
1045 }
1046
1047 return -1; // glyph too large for a single page
1048}
1049
1050// Append a glyph to the dynamic array. Returns index or PICO_FONT_ERROR.
1051static size_t pf_glyph_push(pf_atlas_t* atlas, const pf_glyph_t* glyph)
1052{
1053 if (atlas->glyph_count >= atlas->glyph_capacity)
1054 {
1055 size_t new_capacity = atlas->glyph_capacity ? atlas->glyph_capacity * 2 : 64;
1056
1057 pf_glyph_t* new_array = (pf_glyph_t*)PICO_FONT_REALLOC(atlas->glyphs,
1058 new_capacity * sizeof(pf_glyph_t));
1059
1060 if (!new_array)
1061 return PICO_FONT_ERROR;
1062
1063 atlas->glyphs = new_array;
1064 atlas->glyph_capacity = new_capacity;
1065 }
1066
1067 size_t index = atlas->glyph_count++;
1068 atlas->glyphs[index] = *glyph;
1069
1070 return index;
1071}
1072
1073// Decode one UTF-8 codepoint. Advances *str. Returns 0xFFFD on error.
1074static uint32_t pf_utf8_decode(const char** str)
1075{
1076 const unsigned char* s = (const unsigned char*)*str;
1077 uint32_t cp;
1078 int n;
1079
1080 if (s[0] < 0x80) { cp = s[0]; n = 1; }
1081 else if (s[0] < 0xC0) { cp = 0xFFFD; n = 1; }
1082 else if (s[0] < 0xE0) { cp = s[0] & 0x1F; n = 2; }
1083 else if (s[0] < 0xF0) { cp = s[0] & 0x0F; n = 3; }
1084 else if (s[0] < 0xF8) { cp = s[0] & 0x07; n = 4; }
1085 else { cp = 0xFFFD; n = 1; }
1086
1087 for (int i = 1; i < n; i++)
1088 {
1089 if ((s[i] & 0xC0) != 0x80)
1090 {
1091 *str = (const char*)(s + i);
1092 return 0xFFFD;
1093 }
1094 cp = (cp << 6) | (s[i] & 0x3F);
1095 }
1096
1097 // Reject overlong encodings and surrogates.
1098 if ((n == 2 && cp < 0x80) ||
1099 (n == 3 && cp < 0x800) ||
1100 (n == 4 && cp < 0x10000) ||
1101 (cp >= 0xD800 && cp <= 0xDFFF) ||
1102 cp > 0x10FFFF)
1103 {
1104 cp = 0xFFFD;
1105 }
1106
1107 *str = (const char*)(s + n);
1108 return cp;
1109}
1110
1111// Internal: iterate over a UTF-8 string, resolve glyphs, and optionally
1112// emit quads via callback. Used by both pf_draw_text and pf_measure_text.
1113static void pf_walk_text(pf_face_t* face, const char* text,
1114 float* x, float* y,
1115 pf_draw_callback_fn cb, void* user)
1116{
1117 const char* s = text;
1118 uint32_t prev_cp = 0;
1119
1120 while (*s)
1121 {
1122 uint32_t cp = pf_utf8_decode(&s);
1123
1124 if (cp == 0)
1125 break;
1126
1127 if (prev_cp)
1128 *x += pf_get_kerning(face, prev_cp, cp);
1129
1130 const pf_glyph_t* g = pf_get_glyph(face, cp);
1131
1132 if (!g)
1133 {
1134 prev_cp = cp;
1135 continue;
1136 }
1137
1138 if (g->page_w > 0 && g->page_h > 0 && cb)
1139 {
1140 pf_quad_t q = { 0 };
1141
1142 q.x0 = *x + (float)g->offset_x;
1143 q.y0 = *y + (float)g->offset_y + (float)face->ascent;
1144 q.x1 = q.x0 + (float)g->page_w;
1145 q.y1 = q.y0 + (float)g->page_h;
1146 q.u0 = g->u0;
1147 q.v0 = g->v0;
1148 q.u1 = g->u1;
1149 q.v1 = g->v1;
1150 q.page = g->page;
1151
1152 if (!cb(&q, user))
1153 return;
1154 }
1155
1156 *x += g->advance_x;
1157 prev_cp = cp;
1158 }
1159}
1160
1161static bool pf_measure_cb(const pf_quad_t* quad, void* user)
1162{
1163 pf_measure_state_t* st = (pf_measure_state_t*)user;
1164
1165 if (quad->x1 > st->max_x)
1166 st->max_x = quad->x1;
1167
1168 if (quad->y1 > st->max_y)
1169 st->max_y = quad->y1;
1170
1171 return true;
1172}
1173
1174#endif // PICO_FONT_IMPLEMENTATION
1175
1176/*
1177 ----------------------------------------------------------------------------
1178 This software is available under two licenses (A) or (B). You may choose
1179 either one as you wish:
1180 ----------------------------------------------------------------------------
1181
1182 (A) The zlib License
1183
1184 Copyright (c) 2026 James McLean
1185
1186 This software is provided 'as-is', without any express or implied warranty.
1187 In no event will the authors be held liable for any damages arising from the
1188 use of this software.
1189
1190 Permission is granted to anyone to use this software for any purpose,
1191 including commercial applications, and to alter it and redistribute it
1192 freely, subject to the following restrictions:
1193
1194 1. The origin of this software must not be misrepresented; you must not
1195 claim that you wrote the original software. If you use this software in a
1196 product, an acknowledgment in the product documentation would be appreciated
1197 but is not required.
1198
1199 2. Altered source versions must be plainly marked as such, and must not be
1200 misrepresented as being the original software.
1201
1202 3. This notice may not be removed or altered from any source distribution.
1203
1204 ----------------------------------------------------------------------------
1205
1206 (B) Public Domain (www.unlicense.org)
1207
1208 This is free and unencumbered software released into the public domain.
1209
1210 Anyone is free to copy, modify, publish, use, compile, sell, or distribute
1211 this software, either in source code form or as a compiled binary, for any
1212 purpose, commercial or non-commercial, and by any means.
1213
1214 In jurisdictions that recognize copyright laws, the author or authors of
1215 this software dedicate any and all copyright interest in the software to the
1216 public domain. We make this dedication for the benefit of the public at
1217 large and to the detriment of our heirs and successors. We intend this
1218 dedication to be an overt act of relinquishment in perpetuity of all present
1219 and future rights to this software under copyright law.
1220
1221 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1222 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1223 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1224 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1225 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1226 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1227*/
1228
1229// EoF
void pf_upload_atlas(pf_atlas_t *atlas, pf_upload_callback_fn cb, void *user)
Iterate over dirty pages and invoke the callback for each one.
struct pf_atlas_t pf_atlas_t
Definition pico_font.h:98
bool(* pf_upload_callback_fn)(size_t page, const unsigned char *pixels, int width, int height, void *user)
Callback invoked for each dirty atlas page during pf_upload_atlas.
Definition pico_font.h:164
struct pf_face_t pf_face_t
Definition pico_font.h:99
float pf_get_kerning(const pf_face_t *face, uint32_t cp1, uint32_t cp2)
Get the horizontal kerning adjustment between two codepoints.
void pf_destroy_face(pf_face_t *face)
Destroys a font face.
void pf_destroy_atlas(pf_atlas_t *atlas)
Destroys a font atlas and frees all associated pages and glyphs.
void pf_get_metrics(const pf_face_t *face, pf_metrics_t *metrics)
Retrieve vertical font metrics for a face.
bool(* pf_draw_callback_fn)(const pf_quad_t *quad, void *user)
Callback invoked for each glyph quad during text drawing.
Definition pico_font.h:153
void pf_draw_text(pf_face_t *face, const char *text, float *x, float *y, pf_draw_callback_fn cb, void *user)
Lay out and emit quads for a UTF-8 string.
const pf_glyph_t * pf_get_glyph(pf_face_t *face, uint32_t codepoint)
Get (or rasterize) a single glyph.
pf_atlas_t * pf_create_atlas(int page_width, int max_page_height)
Allocates and initializes a font atlas.
pf_face_t * pf_create_face(pf_atlas_t *atlas, const unsigned char *ttf_data, float pixel_height)
Create a font face at a given pixel height.
void pf_measure_text(pf_face_t *face, const char *text, float *out_width, float *out_height)
Measure a UTF-8 string without drawing.
UV coordinates and metrics for a cached glyph.
Definition pico_font.h:105
float advance_x
Definition pico_font.h:121
int offset_x
Definition pico_font.h:118
int glyph_index
Definition pico_font.h:108
uint32_t codepoint
Definition pico_font.h:106
float v1
Definition pico_font.h:124
int page_h
Definition pico_font.h:112
int page_x
Definition pico_font.h:111
size_t page
Definition pico_font.h:115
float size
Definition pico_font.h:107
int page_w
Definition pico_font.h:112
int offset_y
Definition pico_font.h:118
float v0
Definition pico_font.h:124
float u1
Definition pico_font.h:124
float u0
Definition pico_font.h:124
int page_y
Definition pico_font.h:111
Vertical font metrics for a face.
Definition pico_font.h:141
float ascent
Distance from baseline to top of tallest glyph.
Definition pico_font.h:142
float line_gap
Extra spacing between lines.
Definition pico_font.h:144
float descent
Distance from baseline to bottom (typically negative).
Definition pico_font.h:143
float line_height
Recommended line advance (ascent - descent + line_gap).
Definition pico_font.h:145
Quad emitted by pf_draw_text.
Definition pico_font.h:131
float u0
Definition pico_font.h:133
size_t page
Definition pico_font.h:134
float x1
Definition pico_font.h:132
float y1
Definition pico_font.h:132
float x0
Definition pico_font.h:132