| 658 | // --- Prewarm --- |
| 659 | |
| 660 | int SdCardFont::prewarm(const char* utf8Text, uint8_t styleMask, bool metadataOnly) { |
| 661 | if (!loaded_) return -1; |
| 662 | styleMask = resolveStyleMask(styleMask); |
| 663 | if (styleMask == 0) return 0; |
| 664 | |
| 665 | unsigned long startMs = millis(); |
| 666 | |
| 667 | // Step 1: Extract unique codepoints from UTF-8 text (shared across all styles). |
| 668 | // Dedup uses O(n^2) linear scan — worst case is MAX_PAGE_GLYPHS (512) unique codepoints |
| 669 | // = ~131K comparisons, but in practice pages contain far fewer unique codepoints so the |
| 670 | // actual cost is much lower. This is dwarfed by SD I/O that follows. Alternatives (hash |
| 671 | // set, bitmap) exceed the 256-byte stack limit or add template bloat. |
| 672 | // Heap-allocated: MAX_PAGE_GLYPHS * 4 = 2048 bytes, too large for stack (limit < 256 bytes) |
| 673 | std::unique_ptr<uint32_t[]> codepoints(new (std::nothrow) uint32_t[MAX_PAGE_GLYPHS]); |
| 674 | if (!codepoints) { |
| 675 | LOG_ERR("SDCF", "Failed to allocate codepoint buffer (%u bytes)", MAX_PAGE_GLYPHS * 4); |
| 676 | return -1; |
| 677 | } |
| 678 | uint32_t cpCount = 0; |
| 679 | |
| 680 | const unsigned char* p = reinterpret_cast<const unsigned char*>(utf8Text); |
| 681 | while (*p && cpCount < MAX_PAGE_GLYPHS) { |
| 682 | uint32_t cp = utf8NextCodepoint(&p); |
| 683 | if (cp == 0) break; |
| 684 | |
| 685 | bool found = false; |
| 686 | for (uint32_t i = 0; i < cpCount; i++) { |
| 687 | if (codepoints[i] == cp) { |
| 688 | found = true; |
| 689 | break; |
| 690 | } |
| 691 | } |
| 692 | if (!found) { |
| 693 | codepoints[cpCount++] = cp; |
| 694 | } |
| 695 | } |
| 696 | |
| 697 | // Always include the replacement character |
| 698 | { |
| 699 | bool hasReplacement = false; |
| 700 | for (uint32_t i = 0; i < cpCount; i++) { |
| 701 | if (codepoints[i] == REPLACEMENT_GLYPH) { |
| 702 | hasReplacement = true; |
| 703 | break; |
| 704 | } |
| 705 | } |
| 706 | if (!hasReplacement && cpCount < MAX_PAGE_GLYPHS) { |
| 707 | codepoints[cpCount++] = REPLACEMENT_GLYPH; |
| 708 | } |
| 709 | } |
| 710 | |
| 711 | // Add ligature output codepoints from all styles being prewarmed. |
| 712 | // Skip during metadata-only prewarm (layout measurement) to avoid loading |
| 713 | // kern/lig data for all styles upfront (~22KB per style). Kern/lig is |
| 714 | // loaded per-style in prewarmStyle() during the full render prewarm instead. |
| 715 | if (!metadataOnly) { |
| 716 | for (uint8_t si = 0; si < MAX_STYLES; si++) { |
| 717 | if (!(styleMask & (1 << si)) || !styles_[si].present) continue; |
no test coverage detected