Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
9.8 kB · 263 lines
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264#include "EpdFont.h"
#include <Utf8.h>
#include <algorithm>
void EpdFont::getTextBounds(const char* string, const int startX, const int startY, int* minX, int* minY, int* maxX, int* maxY) const { *minX = startX; *minY = startY; *maxX = startX; *maxY = startY;
if (*string == '\0') { return; }
int lastBaseX = startX; int lastBaseLeft = 0; int lastBaseWidth = 0; int lastBaseTop = 0; int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap uint32_t cp; uint32_t prevCp = 0; while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) { const bool isCombining = utf8IsCombiningMark(cp);
if (!isCombining) { cp = applyLigatures(cp, string); }
const EpdGlyph* glyph = getGlyph(cp); if (!glyph) { // Keep cursor movement stable when a base glyph is missing, but don't attach subsequent // combining marks to stale base metrics. if (!isCombining) { lastBaseX += fp4::toPixel(prevAdvanceFP); // flush pending advance before resetting prevCp = 0; prevAdvanceFP = 0; lastBaseLeft = 0; lastBaseWidth = 0; lastBaseTop = 0; } continue; }
const combiningMark::Anchor anchor = combiningMark::anchorFor(cp); const int raiseBy = isCombining ? combiningMark::raiseAboveBase(anchor, glyph->top, glyph->height, lastBaseTop) : 0;
if (!isCombining && prevCp != 0) { const auto kernFP = getKerning(prevCp, cp); // 4.4 fixed-point kern lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP); }
const int glyphBaseX = isCombining ? combiningMark::anchorOver(anchor, lastBaseX, lastBaseLeft, lastBaseWidth, glyph->left, glyph->width) : lastBaseX; const int glyphBaseY = startY - raiseBy;
*minX = std::min(*minX, glyphBaseX + glyph->left); *maxX = std::max(*maxX, glyphBaseX + glyph->left + glyph->width); *minY = std::min(*minY, glyphBaseY + glyph->top - glyph->height); *maxY = std::max(*maxY, glyphBaseY + glyph->top);
if (!isCombining) { lastBaseLeft = glyph->left; lastBaseWidth = glyph->width; lastBaseTop = glyph->top; prevAdvanceFP = glyph->advanceX; // 12.4 fixed-point prevCp = cp; } }}
void EpdFont::getTextDimensions(const char* string, int* w, int* h) const { int minX = 0, minY = 0, maxX = 0, maxY = 0;
getTextBounds(string, 0, 0, &minX, &minY, &maxX, &maxY);
*w = maxX - minX; *h = maxY - minY;}
// Split form: the search touches only the codepoint array. See EpdFontData::kernLeftCodepoints.static uint8_t lookupKernClassSplit(const uint16_t* codepoints, const uint8_t* classIds, const uint16_t count, const uint32_t cp) { if (!codepoints || count == 0 || cp > 0xFFFF) { return 0; } const auto target = static_cast<uint16_t>(cp); const uint16_t* end = codepoints + count; const auto it = std::lower_bound(codepoints, end, target); return (it != end && *it == target) ? classIds[it - codepoints] : 0;}
static uint8_t lookupKernClass(const EpdKernClassEntry* entries, const uint16_t count, const uint32_t cp) { if (!entries || count == 0 || cp > 0xFFFF) { return 0; }
const auto target = static_cast<uint16_t>(cp); const auto* end = entries + count;
// lower_bound: exact-key lookup. Finds the first entry with codepoint >= target, // then the equality check confirms an exact match exists. const auto it = std::lower_bound( entries, end, target, [](const EpdKernClassEntry& entry, uint16_t value) { return entry.codepoint < value; });
if (it != end && it->codepoint == target) { return it->classId; }
return 0;}
int8_t EpdFont::getKerning(const uint32_t leftCp, const uint32_t rightCp) const { if (utf8IsCjkBreakable(leftCp) || utf8IsCjkBreakable(rightCp)) { return 0; } if (data->kernHandler) { return data->kernHandler(data->glyphMissCtx, leftCp, rightCp); } if (!data->kernMatrix && !data->kernRowOffsets) { return 0; } if (!data->kernLeftClasses && !data->kernLeftCodepoints) { return 0; } // Built-in fonts carry the split arrays, SD-card fonts the packed ones; never both. const bool split = data->kernLeftCodepoints != nullptr; const uint8_t lc = split ? lookupKernClassSplit(data->kernLeftCodepoints, data->kernLeftClassIds, data->kernLeftEntryCount, leftCp) : lookupKernClass(data->kernLeftClasses, data->kernLeftEntryCount, leftCp); if (lc == 0) return 0; const uint8_t rc = split ? lookupKernClassSplit(data->kernRightCodepoints, data->kernRightClassIds, data->kernRightEntryCount, rightCp) : lookupKernClass(data->kernRightClasses, data->kernRightEntryCount, rightCp); if (rc == 0) return 0;
// Sparse (built-in fonts): scan the row. Linear rather than binary — rows hold ~15 entries on // average, short enough that the scan measured faster (worst-case mix +11% over dense against // +19% for std::lower_bound), and it should widen on hardware that reads these arrays through // a flash cache, since the scan walks forwards through a cache line. if (data->kernRowOffsets) { const uint16_t begin = data->kernRowOffsets[lc - 1]; const uint16_t end = data->kernRowOffsets[lc]; const auto target = static_cast<uint8_t>(rc - 1); const uint8_t* cols = data->kernSparseCols; for (uint16_t i = begin; i < end; i++) { if (cols[i] == target) return data->kernSparseValues[i]; if (cols[i] > target) break; // sorted ascending, so past the target means absent } return 0; }
// Dense (SD-card fonts, mapped straight out of the .cpfont). return data->kernMatrix[(lc - 1) * data->kernRightClassCount + (rc - 1)];}
// Arabic contextual joining (including Lam-Alef) is resolved earlier by// do_shape() in MiniBidi, which emits presentation forms in visual order.// Font GSUB ligatures must not run a second pass over that output: a shaped// Alef+Lam ("…ال…") is FEDF+FE8E, which the font's Lam-Alef pairs would// wrongly re-collapse into FEFB/FEFC — transposing the letters (e.g. کسالت →// کسلات). Latin ligatures (ff/fi/fl) key off ASCII and are unaffected.static inline bool isArabicPresentationForm(const uint32_t cp) { return (cp >= 0xFB50 && cp <= 0xFDFF) || (cp >= 0xFE70 && cp <= 0xFEFF);}
uint32_t EpdFont::getLigature(const uint32_t leftCp, const uint32_t rightCp) const { const auto* pairs = data->ligaturePairs; const auto count = data->ligaturePairCount; if (!pairs || count == 0 || leftCp > 0xFFFF || rightCp > 0xFFFF) { return 0; } if (isArabicPresentationForm(leftCp) || isArabicPresentationForm(rightCp)) { return 0; }
const uint32_t key = (leftCp << 16) | rightCp; const auto* end = pairs + count;
// lower_bound: exact-key lookup. Finds the first entry with pair >= key, // then the equality check confirms an exact match exists. const auto it = std::lower_bound(pairs, end, key, [](const EpdLigaturePair& pair, uint32_t value) { return pair.pair < value; });
if (it != end && it->pair == key) { return it->ligatureCp; }
return 0;}
uint32_t EpdFont::applyLigatures(uint32_t cp, const char*& text) const { if (!data->ligaturePairs || data->ligaturePairCount == 0) { return cp; } while (true) { const auto saved = reinterpret_cast<const uint8_t*>(text); const uint32_t nextCp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)); if (nextCp == 0) break; const uint32_t lig = getLigature(cp, nextCp); if (lig == 0) { text = reinterpret_cast<const char*>(saved); break; } cp = lig; } return cp;}
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const { const int count = data->intervalCount; if (count == 0 && !data->glyphMissHandler) return nullptr;
if (count > 0) { const EpdUnicodeInterval* intervals = data->intervals; const auto* end = intervals + count;
// upper_bound: range lookup. Finds the first interval with first > cp, so the // interval just before it is the last one with first <= cp. That's the only // candidate that could contain cp. Then we verify cp <= candidate.last. const auto it = std::upper_bound( intervals, end, cp, [](uint32_t value, const EpdUnicodeInterval& interval) { return value < interval.first; });
if (it != intervals) { const auto& interval = *(it - 1); if (cp <= interval.last) { return &data->glyph[interval.offset + (cp - interval.first)]; } } }
// Codepoint not in interval table — try on-demand loading (SD card fonts). if (data->glyphMissHandler) { const EpdGlyph* loaded = data->glyphMissHandler(data->glyphMissCtx, cp); if (loaded) return loaded; }
if (cp != REPLACEMENT_GLYPH) { return getGlyph(REPLACEMENT_GLYPH); } return nullptr;}
bool EpdFont::hasCodepoint(const uint32_t cp) const { const int count = data->intervalCount; if (count > 0) { const EpdUnicodeInterval* intervals = data->intervals; const auto* end = intervals + count; const auto it = std::upper_bound( intervals, end, cp, [](uint32_t value, const EpdUnicodeInterval& interval) { return value < interval.first; }); if (it != intervals && cp <= (it - 1)->last) return true; }
// Interval table miss. SD card fonts only keep the current page's glyphs in // their interval table — ask their full RAM-resident coverage index instead. if (data->coverageHandler) { return data->coverageHandler(data->glyphMissCtx, cp); } return false;}