222 lines
7.0 KiB
C++
222 lines
7.0 KiB
C++
#include "SdCardFontRegistry.h"
|
|
|
|
#include <HalStorage.h>
|
|
#include <Logging.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
// --- SdCardFontFamilyInfo helpers ---
|
|
|
|
const SdCardFontFileInfo* SdCardFontFamilyInfo::findFile(uint8_t size, uint8_t style) const {
|
|
for (const auto& f : files) {
|
|
if (f.pointSize == size && f.style == style) return &f;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool SdCardFontFamilyInfo::hasSize(uint8_t size) const {
|
|
for (const auto& f : files) {
|
|
if (f.pointSize == size) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<uint8_t> SdCardFontFamilyInfo::availableSizes() const {
|
|
std::vector<uint8_t> sizes;
|
|
for (const auto& f : files) {
|
|
bool found = false;
|
|
for (uint8_t s : sizes) {
|
|
if (s == f.pointSize) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) sizes.push_back(f.pointSize);
|
|
}
|
|
std::sort(sizes.begin(), sizes.end());
|
|
return sizes;
|
|
}
|
|
|
|
const SdCardFontFileInfo* SdCardFontFamilyInfo::pickClosestSize(uint8_t targetPtSize) const {
|
|
const SdCardFontFileInfo* selected = nullptr;
|
|
int bestDiff = INT32_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 ---
|
|
|
|
bool SdCardFontRegistry::parseFilename(const char* filename, uint8_t& size, uint8_t& style) {
|
|
// V4 naming: <name>_<size>.cpfont (e.g. Bookerly-SD_14.cpfont)
|
|
// Use an ends-with check rather than strstr() so that in-progress downloads
|
|
// like "Foo_14.cpfont.tmp" or backups like "Foo_14.cpfont~" aren't accepted.
|
|
static constexpr char kExt[] = ".cpfont";
|
|
static constexpr size_t kExtLen = sizeof(kExt) - 1;
|
|
const size_t nameLen = strlen(filename);
|
|
if (nameLen <= kExtLen) return false;
|
|
if (strcmp(filename + nameLen - kExtLen, kExt) != 0) return false;
|
|
const char* ext = filename + nameLen - kExtLen;
|
|
|
|
size_t baseLen = ext - filename;
|
|
if (baseLen == 0 || baseLen > 127) return false;
|
|
|
|
char base[128];
|
|
memcpy(base, filename, baseLen);
|
|
base[baseLen] = '\0';
|
|
|
|
char* lastUnderscore = strrchr(base, '_');
|
|
if (!lastUnderscore || lastUnderscore == base) return false;
|
|
|
|
const char* sizeStr = lastUnderscore + 1;
|
|
char* endPtr;
|
|
long sizeVal = strtol(sizeStr, &endPtr, 10);
|
|
if (endPtr == sizeStr || *endPtr != '\0' || sizeVal < 1 || sizeVal > 255) return false;
|
|
size = static_cast<uint8_t>(sizeVal);
|
|
style = 0;
|
|
return true;
|
|
}
|
|
|
|
void SdCardFontRegistry::scanDirectory(const char* dirPath, SdCardFontFamilyInfo& family) {
|
|
FsFile dir = Storage.open(dirPath);
|
|
if (!dir || !dir.isDirectory()) return;
|
|
|
|
char nameBuffer[128];
|
|
while (true) {
|
|
FsFile entry = dir.openNextFile();
|
|
if (!entry) break;
|
|
if (entry.isDirectory()) {
|
|
entry.close();
|
|
continue;
|
|
}
|
|
|
|
entry.getName(nameBuffer, sizeof(nameBuffer));
|
|
entry.close();
|
|
|
|
// Skip macOS resource fork files (._*) and other hidden files
|
|
if (nameBuffer[0] == '.' || nameBuffer[0] == '_') continue;
|
|
|
|
uint8_t size, style;
|
|
if (!parseFilename(nameBuffer, size, style)) continue;
|
|
|
|
SdCardFontFileInfo info;
|
|
info.path = std::string(dirPath) + "/" + nameBuffer;
|
|
info.pointSize = size;
|
|
info.style = style;
|
|
family.files.push_back(std::move(info));
|
|
}
|
|
dir.close();
|
|
}
|
|
|
|
void SdCardFontRegistry::scanRoot(const char* rootPath) {
|
|
FsFile root = Storage.open(rootPath);
|
|
if (!root) {
|
|
LOG_DBG("SDREG", "Fonts directory not found: %s", rootPath);
|
|
return;
|
|
}
|
|
if (!root.isDirectory()) {
|
|
LOG_ERR("SDREG", "Fonts path is not a directory: %s", rootPath);
|
|
root.close();
|
|
return;
|
|
}
|
|
|
|
char nameBuffer[128];
|
|
while (true) {
|
|
FsFile entry = root.openNextFile();
|
|
if (!entry) break;
|
|
if (entry.isDirectory()) {
|
|
// Subdirectory = font family
|
|
entry.getName(nameBuffer, sizeof(nameBuffer));
|
|
entry.close();
|
|
|
|
// Skip hidden/system directories (macOS ._*, .Trashes, etc.)
|
|
if (nameBuffer[0] == '.' || nameBuffer[0] == '_') continue;
|
|
|
|
// Skip in-flight install/rollback dirs (Bookerly__staging, Bookerly__backup, ...)
|
|
// — these may contain valid-looking .cpfont files during a resumable
|
|
// download but must not be exposed as installable families.
|
|
const size_t nameLen = strlen(nameBuffer);
|
|
static constexpr const char* kStagingSuffix = "__staging";
|
|
static constexpr const char* kBackupSuffix = "__backup";
|
|
const size_t stagingLen = strlen(kStagingSuffix);
|
|
const size_t backupLen = strlen(kBackupSuffix);
|
|
if ((nameLen > stagingLen && strcmp(nameBuffer + nameLen - stagingLen, kStagingSuffix) == 0) ||
|
|
(nameLen > backupLen && strcmp(nameBuffer + nameLen - backupLen, kBackupSuffix) == 0)) {
|
|
continue;
|
|
}
|
|
|
|
// Primary wins: skip if the family was already discovered in an earlier root.
|
|
bool alreadyKnown = false;
|
|
for (const auto& f : families_) {
|
|
if (f.name == nameBuffer) {
|
|
alreadyKnown = true;
|
|
break;
|
|
}
|
|
}
|
|
if (alreadyKnown) {
|
|
LOG_DBG("SDREG", "Skipping duplicate family %s in %s (primary wins)", nameBuffer, rootPath);
|
|
continue;
|
|
}
|
|
|
|
SdCardFontFamilyInfo family;
|
|
family.name = nameBuffer;
|
|
std::string subDirPath = std::string(rootPath) + "/" + nameBuffer;
|
|
scanDirectory(subDirPath.c_str(), family);
|
|
|
|
if (!family.files.empty()) {
|
|
families_.push_back(std::move(family));
|
|
LOG_DBG("SDREG", "Found family: %s (%d files) in %s", families_.back().name.c_str(),
|
|
static_cast<int>(families_.back().files.size()), rootPath);
|
|
}
|
|
} else {
|
|
entry.close();
|
|
}
|
|
}
|
|
root.close();
|
|
}
|
|
|
|
bool SdCardFontRegistry::discover() {
|
|
families_.clear();
|
|
families_.reserve(MAX_SD_FAMILIES);
|
|
|
|
// Primary first so it wins on family-name conflicts; the upstream roots are
|
|
// read-only fallbacks — installers/deleters always target only the primary.
|
|
scanRoot(FONTS_DIR);
|
|
scanRoot(FONTS_DIR_UPSTREAM_HIDDEN);
|
|
scanRoot(FONTS_DIR_UPSTREAM_VISIBLE);
|
|
|
|
// Sort families alphabetically
|
|
std::sort(families_.begin(), families_.end(),
|
|
[](const SdCardFontFamilyInfo& a, const SdCardFontFamilyInfo& b) { return a.name < b.name; });
|
|
|
|
// Cap at MAX_SD_FAMILIES
|
|
if (static_cast<int>(families_.size()) > MAX_SD_FAMILIES) {
|
|
families_.resize(MAX_SD_FAMILIES);
|
|
}
|
|
|
|
LOG_DBG("SDREG", "Discovery complete: %d families", static_cast<int>(families_.size()));
|
|
return !families_.empty();
|
|
}
|
|
|
|
const SdCardFontFamilyInfo* SdCardFontRegistry::findFamily(const std::string& name) const {
|
|
for (const auto& f : families_) {
|
|
if (f.name == name) return &f;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int SdCardFontRegistry::getFamilyIndex(const std::string& name) const {
|
|
for (int i = 0; i < static_cast<int>(families_.size()); i++) {
|
|
if (families_[i].name == name) return i;
|
|
}
|
|
return -1;
|
|
}
|