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