| 129 | // --- getBitmap: page buffer → hot group → decompress --- |
| 130 | |
| 131 | const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint32_t glyphIndex) { |
| 132 | const uint32_t tStart = micros(); |
| 133 | stats.getBitmapCalls++; |
| 134 | |
| 135 | if (!fontData->groups || fontData->groupCount == 0) { |
| 136 | stats.getBitmapTimeUs += micros() - tStart; |
| 137 | return &fontData->bitmap[glyph->dataOffset]; |
| 138 | } |
| 139 | |
| 140 | // Check page buffer slots (populated by prewarmCache — one slot per font style) |
| 141 | for (uint8_t s = 0; s < pageSlotCount; s++) { |
| 142 | const auto& slot = pageSlots[s]; |
| 143 | if (slot.fontData != fontData || slot.glyphCount == 0) continue; |
| 144 | |
| 145 | int left = 0, right = slot.glyphCount - 1; |
| 146 | while (left <= right) { |
| 147 | int mid = left + (right - left) / 2; |
| 148 | if (slot.glyphs[mid].glyphIndex == glyphIndex) { |
| 149 | if (slot.glyphs[mid].bufferOffset != UINT32_MAX) { |
| 150 | stats.cacheHits++; |
| 151 | stats.getBitmapTimeUs += micros() - tStart; |
| 152 | return &slot.buffer[slot.glyphs[mid].bufferOffset]; |
| 153 | } |
| 154 | break; // Not extracted during prewarm; fall through to hot-group path |
| 155 | } |
| 156 | if (slot.glyphs[mid].glyphIndex < glyphIndex) |
| 157 | left = mid + 1; |
| 158 | else |
| 159 | right = mid - 1; |
| 160 | } |
| 161 | break; // Found the right slot but glyph wasn't in it; don't check other slots |
| 162 | } |
| 163 | |
| 164 | // Fallback: hot group slot |
| 165 | uint16_t groupIndex = getGroupIndex(fontData, glyphIndex); |
| 166 | if (groupIndex >= fontData->groupCount) { |
| 167 | LOG_ERR("FDC", "Glyph %u not found in any group", glyphIndex); |
| 168 | stats.getBitmapTimeUs += micros() - tStart; |
| 169 | return nullptr; |
| 170 | } |
| 171 | |
| 172 | // Check if hot group already has this group decompressed — if not, decompress it |
| 173 | if (!(!hotGroup.empty() && hotGroupFont == fontData && hotGroupIndex == groupIndex)) { |
| 174 | stats.cacheMisses++; |
| 175 | const EpdFontGroup& group = fontData->groups[groupIndex]; |
| 176 | |
| 177 | hotGroup.resize(group.uncompressedSize); |
| 178 | if (hotGroup.empty()) { |
| 179 | LOG_ERR("FDC", "Failed to allocate %u bytes for hot group %u", group.uncompressedSize, groupIndex); |
| 180 | hotGroupFont = nullptr; |
| 181 | hotGroupIndex = UINT16_MAX; |
| 182 | stats.getBitmapTimeUs += micros() - tStart; |
| 183 | return nullptr; |
| 184 | } |
| 185 | |
| 186 | if (!decompressGroup(fontData, groupIndex, hotGroup.data(), group.uncompressedSize)) { |
| 187 | hotGroup.clear(); |
| 188 | hotGroup.shrink_to_fit(); |
no test coverage detected