#include "SdCardFontRegistry.h" #include #include #include #include // --- 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 SdCardFontFamilyInfo::availableSizes() const { std::vector 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(f.pointSize) - static_cast(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: _.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(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(); } bool SdCardFontRegistry::discover() { families_.clear(); families_.reserve(MAX_SD_FAMILIES); FsFile root = Storage.open(FONTS_DIR); if (!root) { LOG_DBG("SDREG", "Fonts directory not found: %s", FONTS_DIR); return false; } if (!root.isDirectory()) { LOG_ERR("SDREG", "Fonts path is not a directory: %s", FONTS_DIR); root.close(); return false; } 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; SdCardFontFamilyInfo family; family.name = nameBuffer; std::string subDirPath = std::string(FONTS_DIR) + "/" + nameBuffer; scanDirectory(subDirPath.c_str(), family); if (!family.files.empty()) { families_.push_back(std::move(family)); LOG_DBG("SDREG", "Found family: %s (%d files)", families_.back().name.c_str(), static_cast(families_.back().files.size())); } } else { entry.close(); } } root.close(); // 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(families_.size()) > MAX_SD_FAMILIES) { families_.resize(MAX_SD_FAMILIES); } LOG_DBG("SDREG", "Discovery complete: %d families", static_cast(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(families_.size()); i++) { if (families_[i].name == name) return i; } return -1; }