feat: closest-pt size selection instead of ordinal slot (#1912)
Add SdCardFontFamilyInfo::pickClosestSize(targetPtSize) and have the
manager and SdCardFontSystem drive size selection from a target point
size derived from the user's font-size enum (SMALL=12, MEDIUM=14,
LARGE=16, EXTRA_LARGE=18) rather than indexing the family's sorted size
list by enum ordinal.
The ordinal-slot approach mis-selects whenever a family doesn't ship the
canonical {12,14,16,18} set: a family with only [10,14,18] would map
SMALL/MEDIUM/LARGE/EXTRA_LARGE to 10/14/18/18 — fine for SMALL but
arbitrary for the rest. Closest-pt always picks the on-disk file nearest
to the user-intended point size, with a deterministic smaller-pt
tie-break.
No change for canonical-sized families.
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
* **What changes are included?**
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_
This commit is contained in:
@@ -28,25 +28,21 @@ int SdCardFontManager::computeFontId(uint32_t contentHash, const char* familyNam
|
|||||||
return id != 0 ? id : 1; // 0 is reserved as "not found" sentinel
|
return id != 0 ? id : 1; // 0 is reserved as "not found" sentinel
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SdCardFontManager::loadFamily(const SdCardFontFamilyInfo& family, GfxRenderer& renderer, uint8_t fontSizeEnum) {
|
bool SdCardFontManager::loadFamily(const SdCardFontFamilyInfo& family, GfxRenderer& renderer, uint8_t targetPtSize) {
|
||||||
// Unload any previously loaded family first
|
// Unload any previously loaded family first
|
||||||
if (!loadedFamilyName_.empty()) {
|
if (!loadedFamilyName_.empty()) {
|
||||||
unloadAll(renderer);
|
unloadAll(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select by ordinal position: sort available sizes, then map the font size
|
// Pick the single file whose size is closest to targetPtSize. Loading
|
||||||
// enum (SMALL=0 .. EXTRA_LARGE=3) to the corresponding slot. When the
|
// only one size bounds resident memory (intervals + kern/ligature tables
|
||||||
// family has fewer sizes than 4, clamp to the last available size.
|
// per style) to one file's worth, vs. N_sizes × per-file overhead.
|
||||||
auto sizes = family.availableSizes();
|
const SdCardFontFileInfo* selected = family.pickClosestSize(targetPtSize);
|
||||||
if (sizes.empty()) {
|
if (!selected) {
|
||||||
LOG_ERR("SDMGR", "Family %s has no files to load", family.name.c_str());
|
LOG_ERR("SDMGR", "Family %s has no files to load", family.name.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t idx = fontSizeEnum;
|
|
||||||
if (idx >= sizes.size()) idx = sizes.size() - 1;
|
|
||||||
const SdCardFontFileInfo* selected = family.findFile(sizes[idx]);
|
|
||||||
|
|
||||||
auto* font = new (std::nothrow) SdCardFont();
|
auto* font = new (std::nothrow) SdCardFont();
|
||||||
if (!font) {
|
if (!font) {
|
||||||
LOG_ERR("SDMGR", "Failed to allocate SdCardFont for %s", selected->path.c_str());
|
LOG_ERR("SDMGR", "Failed to allocate SdCardFont for %s", selected->path.c_str());
|
||||||
@@ -70,8 +66,8 @@ bool SdCardFontManager::loadFamily(const SdCardFontFamilyInfo& family, GfxRender
|
|||||||
renderer.registerSdCardFont(fontId, font);
|
renderer.registerSdCardFont(fontId, font);
|
||||||
loaded_.push_back({font, fontId, selected->pointSize});
|
loaded_.push_back({font, fontId, selected->pointSize});
|
||||||
|
|
||||||
LOG_DBG("SDMGR", "Loaded %s size=%u id=%d styles=%u (sizeEnum=%u)", selected->path.c_str(), selected->pointSize,
|
LOG_DBG("SDMGR", "Loaded %s size=%u id=%d styles=%u (target=%u)", selected->path.c_str(), selected->pointSize, fontId,
|
||||||
fontId, font->styleCount(), fontSizeEnum);
|
font->styleCount(), targetPtSize);
|
||||||
|
|
||||||
EpdFontFamily fontFamily(font->getEpdFont(0), font->getEpdFont(1), font->getEpdFont(2), font->getEpdFont(3));
|
EpdFontFamily fontFamily(font->getEpdFont(0), font->getEpdFont(1), font->getEpdFont(2), font->getEpdFont(3));
|
||||||
renderer.insertFont(fontId, fontFamily);
|
renderer.insertFont(fontId, fontFamily);
|
||||||
|
|||||||
@@ -15,12 +15,16 @@ class SdCardFontManager {
|
|||||||
SdCardFontManager(const SdCardFontManager&) = delete;
|
SdCardFontManager(const SdCardFontManager&) = delete;
|
||||||
SdCardFontManager& operator=(const SdCardFontManager&) = delete;
|
SdCardFontManager& operator=(const SdCardFontManager&) = delete;
|
||||||
|
|
||||||
// Load the font file matching fontSizeEnum (SMALL=0 .. EXTRA_LARGE=3) by
|
// Load the single size whose pointSize is closest to targetPtSize. Only one
|
||||||
// ordinal position in the family's sorted size list. Only one .cpfont file
|
// .cpfont file is loaded; other sizes remain on disk. This keeps resident
|
||||||
// is loaded; other sizes remain on disk. This keeps resident interval +
|
// interval + kern/ligature tables to one size's worth of memory.
|
||||||
// kern/ligature tables to one size's worth of memory.
|
//
|
||||||
|
// Closest-pt selection is robust against families that don't ship the
|
||||||
|
// canonical {12,14,16,18}: a family with only [10,14,18] still resolves
|
||||||
|
// any reasonable target, where ordinal slot-mapping by SMALL..EXTRA_LARGE
|
||||||
|
// would mis-select.
|
||||||
// Returns true on success.
|
// Returns true on success.
|
||||||
bool loadFamily(const SdCardFontFamilyInfo& family, GfxRenderer& renderer, uint8_t fontSizeEnum);
|
bool loadFamily(const SdCardFontFamilyInfo& family, GfxRenderer& renderer, uint8_t targetPtSize);
|
||||||
|
|
||||||
// Unload everything, unregister from renderer.
|
// Unload everything, unregister from renderer.
|
||||||
void unloadAll(GfxRenderer& renderer);
|
void unloadAll(GfxRenderer& renderer);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <climits>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
// --- SdCardFontFamilyInfo helpers ---
|
// --- SdCardFontFamilyInfo helpers ---
|
||||||
@@ -38,6 +40,21 @@ std::vector<uint8_t> SdCardFontFamilyInfo::availableSizes() const {
|
|||||||
return sizes;
|
return sizes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SdCardFontFileInfo* SdCardFontFamilyInfo::pickClosestSize(uint8_t targetPtSize) const {
|
||||||
|
const SdCardFontFileInfo* selected = nullptr;
|
||||||
|
int bestDiff = INT_MAX;
|
||||||
|
for (const auto& f : files) {
|
||||||
|
int diff = std::abs(static_cast<int>(f.pointSize) - static_cast<int>(targetPtSize));
|
||||||
|
// Strict < ensures the first scan wins on ties; then tie-break by smaller
|
||||||
|
// pointSize to make the choice independent of filesystem enumeration order.
|
||||||
|
if (diff < bestDiff || (diff == bestDiff && selected && f.pointSize < selected->pointSize)) {
|
||||||
|
bestDiff = diff;
|
||||||
|
selected = &f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
// --- SdCardFontRegistry ---
|
// --- SdCardFontRegistry ---
|
||||||
|
|
||||||
bool SdCardFontRegistry::parseFilename(const char* filename, uint8_t& size, uint8_t& style) {
|
bool SdCardFontRegistry::parseFilename(const char* filename, uint8_t& size, uint8_t& style) {
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ struct SdCardFontFamilyInfo {
|
|||||||
const SdCardFontFileInfo* findFile(uint8_t size, uint8_t style = 0) const;
|
const SdCardFontFileInfo* findFile(uint8_t size, uint8_t style = 0) const;
|
||||||
bool hasSize(uint8_t size) const;
|
bool hasSize(uint8_t size) const;
|
||||||
std::vector<uint8_t> availableSizes() const;
|
std::vector<uint8_t> availableSizes() const;
|
||||||
|
|
||||||
|
// Pick the file whose pointSize is closest to targetPtSize. On ties (equal
|
||||||
|
// distance) prefers the smaller pointSize so behaviour is deterministic
|
||||||
|
// across SD card layouts. Returns nullptr when files is empty.
|
||||||
|
//
|
||||||
|
// Robust against families that don't ship the canonical {12,14,16,18} set:
|
||||||
|
// a family with only [10,14,18] resolves target 12 → 10 (or 14 on tie),
|
||||||
|
// target 16 → 14 or 18, etc., instead of mis-indexing by ordinal slot.
|
||||||
|
const SdCardFontFileInfo* pickClosestSize(uint8_t targetPtSize) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SdCardFontRegistry {
|
class SdCardFontRegistry {
|
||||||
|
|||||||
+17
-14
@@ -5,10 +5,15 @@
|
|||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
|
|
||||||
static uint8_t fontSizeEnumFromSettings() {
|
// Map fontSize enum (SMALL=0, MEDIUM=1, LARGE=2, EXTRA_LARGE=3) to the point
|
||||||
|
// sizes shipped with the built-in fonts. Used to drive closest-pt selection
|
||||||
|
// in the SD card font registry (see SdCardFontFamilyInfo::pickClosestSize).
|
||||||
|
static constexpr uint8_t FONT_SIZE_TO_PT[CrossPointSettings::FONT_SIZE_COUNT] = {12, 14, 16, 18};
|
||||||
|
|
||||||
|
static uint8_t targetPtSizeFromSettings() {
|
||||||
uint8_t e = SETTINGS.fontSize;
|
uint8_t e = SETTINGS.fontSize;
|
||||||
if (e >= CrossPointSettings::FONT_SIZE_COUNT) e = 1; // default to MEDIUM
|
if (e >= CrossPointSettings::FONT_SIZE_COUNT) e = 1; // default to MEDIUM
|
||||||
return e;
|
return FONT_SIZE_TO_PT[e];
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdCardFontSystem::begin(GfxRenderer& renderer) {
|
void SdCardFontSystem::begin(GfxRenderer& renderer) {
|
||||||
@@ -25,7 +30,7 @@ void SdCardFontSystem::begin(GfxRenderer& renderer) {
|
|||||||
if (SETTINGS.sdFontFamilyName[0] != '\0') {
|
if (SETTINGS.sdFontFamilyName[0] != '\0') {
|
||||||
const auto* family = registry_.findFamily(SETTINGS.sdFontFamilyName);
|
const auto* family = registry_.findFamily(SETTINGS.sdFontFamilyName);
|
||||||
if (family) {
|
if (family) {
|
||||||
if (manager_.loadFamily(*family, renderer, fontSizeEnumFromSettings())) {
|
if (manager_.loadFamily(*family, renderer, targetPtSizeFromSettings())) {
|
||||||
LOG_DBG("SDFS", "Loaded SD card font family: %s", SETTINGS.sdFontFamilyName);
|
LOG_DBG("SDFS", "Loaded SD card font family: %s", SETTINGS.sdFontFamilyName);
|
||||||
} else {
|
} else {
|
||||||
LOG_ERR("SDFS", "Failed to load SD font family: %s (clearing)", SETTINGS.sdFontFamilyName);
|
LOG_ERR("SDFS", "Failed to load SD font family: %s (clearing)", SETTINGS.sdFontFamilyName);
|
||||||
@@ -53,7 +58,7 @@ void SdCardFontSystem::ensureLoaded(GfxRenderer& renderer) {
|
|||||||
|
|
||||||
const char* wantedFamily = SETTINGS.sdFontFamilyName;
|
const char* wantedFamily = SETTINGS.sdFontFamilyName;
|
||||||
const std::string& currentFamily = manager_.currentFamilyName();
|
const std::string& currentFamily = manager_.currentFamilyName();
|
||||||
const uint8_t sizeEnum = fontSizeEnumFromSettings();
|
const uint8_t targetPt = targetPtSizeFromSettings();
|
||||||
|
|
||||||
if (wantedFamily[0] == '\0') {
|
if (wantedFamily[0] == '\0') {
|
||||||
if (!currentFamily.empty()) {
|
if (!currentFamily.empty()) {
|
||||||
@@ -62,8 +67,8 @@ void SdCardFontSystem::ensureLoaded(GfxRenderer& renderer) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload if family changed OR if the user-selected size maps to a
|
// Reload if family changed OR if the user-selected size now resolves to a
|
||||||
// different file than what's currently loaded OR if the registry was
|
// different on-disk file than what's currently loaded OR if the registry was
|
||||||
// just rediscovered (file may have been replaced on disk).
|
// just rediscovered (file may have been replaced on disk).
|
||||||
bool familyMatches = (currentFamily == wantedFamily);
|
bool familyMatches = (currentFamily == wantedFamily);
|
||||||
if (familyMatches) {
|
if (familyMatches) {
|
||||||
@@ -74,13 +79,11 @@ void SdCardFontSystem::ensureLoaded(GfxRenderer& renderer) {
|
|||||||
SETTINGS.sdFontFamilyName[0] = '\0';
|
SETTINGS.sdFontFamilyName[0] = '\0';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto sizes = family->availableSizes();
|
const auto* best = family->pickClosestSize(targetPt);
|
||||||
uint8_t idx = sizeEnum;
|
const uint8_t bestPt = best ? best->pointSize : 0;
|
||||||
if (idx >= sizes.size()) idx = sizes.size() - 1;
|
if (!registryWasDirty && bestPt == manager_.currentPointSize()) return;
|
||||||
uint8_t wantedPt = sizes.empty() ? 0 : sizes[idx];
|
LOG_DBG("SDFS", "Reloading %s: size %u -> %u (target %u)%s", wantedFamily, manager_.currentPointSize(), bestPt,
|
||||||
if (!registryWasDirty && wantedPt == manager_.currentPointSize()) return;
|
targetPt, registryWasDirty ? " [registry dirty]" : "");
|
||||||
LOG_DBG("SDFS", "Reloading %s: size %u -> %u (enum %u)%s", wantedFamily, manager_.currentPointSize(), wantedPt,
|
|
||||||
sizeEnum, registryWasDirty ? " [registry dirty]" : "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentFamily.empty()) {
|
if (!currentFamily.empty()) {
|
||||||
@@ -89,7 +92,7 @@ void SdCardFontSystem::ensureLoaded(GfxRenderer& renderer) {
|
|||||||
|
|
||||||
const auto* family = registry_.findFamily(wantedFamily);
|
const auto* family = registry_.findFamily(wantedFamily);
|
||||||
if (family) {
|
if (family) {
|
||||||
if (manager_.loadFamily(*family, renderer, sizeEnum)) {
|
if (manager_.loadFamily(*family, renderer, targetPt)) {
|
||||||
LOG_DBG("SDFS", "Loaded SD font family: %s", wantedFamily);
|
LOG_DBG("SDFS", "Loaded SD font family: %s", wantedFamily);
|
||||||
} else {
|
} else {
|
||||||
LOG_ERR("SDFS", "Failed to load SD font family: %s (clearing)", wantedFamily);
|
LOG_ERR("SDFS", "Failed to load SD font family: %s (clearing)", wantedFamily);
|
||||||
|
|||||||
Reference in New Issue
Block a user