| 131 | } |
| 132 | |
| 133 | void SdCardFontRegistry::scanDirectory(const char* dirPath, SdCardFontFamilyInfo& family) { |
| 134 | HalFile dir = Storage.open(dirPath); |
| 135 | if (!dir || !dir.isDirectory()) return; |
| 136 | |
| 137 | char nameBuffer[128]; |
| 138 | while (true) { |
| 139 | HalFile entry = dir.openNextFile(); |
| 140 | if (!entry) break; |
| 141 | if (entry.isDirectory()) { |
| 142 | entry.close(); |
| 143 | continue; |
| 144 | } |
| 145 | |
| 146 | entry.getName(nameBuffer, sizeof(nameBuffer)); |
| 147 | entry.close(); |
| 148 | |
| 149 | // Skip macOS resource fork files (._*) and other hidden files |
| 150 | if (nameBuffer[0] == '.' || nameBuffer[0] == '_') continue; |
| 151 | |
| 152 | uint8_t size, style; |
| 153 | if (!parseFilename(nameBuffer, size, style)) continue; |
| 154 | |
| 155 | // Reject duplicate (pointSize, style) entries in the same family. With |
| 156 | // v4's bundle-everything design parseFilename always returns style=0, so |
| 157 | // two files at the same size in the same family would silently shadow |
| 158 | // each other in findFile(). Skip the duplicate and warn. |
| 159 | bool duplicate = false; |
| 160 | for (const auto& existing : family.files) { |
| 161 | if (existing.pointSize == size && existing.style == style) { |
| 162 | duplicate = true; |
| 163 | break; |
| 164 | } |
| 165 | } |
| 166 | if (duplicate) { |
| 167 | LOG_ERR("SDREG", "Duplicate font %s in %s — skipping", nameBuffer, dirPath); |
| 168 | continue; |
| 169 | } |
| 170 | |
| 171 | SdCardFontFileInfo info; |
| 172 | info.path = std::string(dirPath) + "/" + nameBuffer; |
| 173 | info.pointSize = size; |
| 174 | info.style = style; |
| 175 | family.files.push_back(std::move(info)); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | // Scan a single root (e.g. "/.fonts") and append its families to `out`. |
| 180 | // Skips families whose names already exist in `out` (de-duplicates between |
nothing calls this directly
no test coverage detected