diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index cd8a055ffb..c0ba7eac15 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1217,6 +1217,10 @@ #define POWER_LOSS_MIN_Z_CHANGE 0.05 // (mm) Minimum Z change before saving power-loss data #endif + #define SD_FILENAMES_MULTILINGUAL // Allow national symbols in filenames. To display correctly + // font must contains it - check your language selected + // This needs ~78 bytes more RAM and ~130 bytes Flash + /** * Sort SD file listings in alphabetical order. * diff --git a/Marlin/src/sd/SdBaseFile.cpp b/Marlin/src/sd/SdBaseFile.cpp index 46ed9372ab..ae4942e0f1 100644 --- a/Marlin/src/sd/SdBaseFile.cpp +++ b/Marlin/src/sd/SdBaseFile.cpp @@ -1103,15 +1103,68 @@ int8_t SdBaseFile::readDir(dir_t* dir, char* longFilename) { if (WITHIN(seq, 1, MAX_VFAT_ENTRIES)) { // TODO: Store the filename checksum to verify if a long-filename-unaware system modified the file table. n = (seq - 1) * (FILENAME_LENGTH); - LOOP_L_N(i, FILENAME_LENGTH) - longFilename[n + i] = (i < 5) ? VFAT->name1[i] : (i < 11) ? VFAT->name2[i - 5] : VFAT->name3[i - 11]; + LOOP_L_N(i, FILENAME_LENGTH) { + uint16_t utf16_ch = (i < 5) ? VFAT->name1[i] : (i < 11) ? VFAT->name2[i - 5] : VFAT->name3[i - 11]; + #if ENABLED(SD_FILENAMES_MULTILINGUAL) + // We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks + // needs static bytes addressing. So here just store full UTF-16LE words to re-convert later. + uint16_t idx = (n + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding + longFilename[idx] = utf16_ch & 0xFF; + longFilename[idx+1] = (utf16_ch >> 8) & 0xFF; + #else + // Replace all multibyte characters to '_' + longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF); + #endif + } // If this VFAT entry is the last one, add a NUL terminator at the end of the string - if (VFAT->sequenceNumber & 0x40) longFilename[n + FILENAME_LENGTH] = '\0'; + if (VFAT->sequenceNumber & 0x40) longFilename[(n + FILENAME_LENGTH) * LONG_FILENAME_CHARSIZE] = '\0'; } } } + // Return if normal file or subdirectory - if (DIR_IS_FILE_OR_SUBDIR(dir)) return n; + if (DIR_IS_FILE_OR_SUBDIR(dir)) { + #if ENABLED(SD_FILENAMES_MULTILINGUAL) + // Convert filename from utf-16 to utf-8 as Marlin expects + #if LONG_FILENAME_CHARSIZE > 2 + // Add warning for developers for currently not supported 3-byte cases (Conversion series of 2-byte + // codepoints to 3-byte in-place will broke rest of filename) + #error "Currently filename re-encoding is done in-place, this may broke rest of chars if we use 3-byte codepoints" + #endif + uint16_t currentPos = 0; + LOOP_L_N(i, (LONG_FILENAME_LENGTH / 2)) { + uint16_t idx = i * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding + + uint16_t utf16_ch = longFilename[idx] | (longFilename[idx+1] << 8); + if (0xD800 == (utf16_ch & 0xF800)) { + // Surrogate pair - encode as '_' + longFilename[currentPos++] = '_'; + } else if (0 == (utf16_ch & 0xFF80)) { + // Encode as 1-byte utf-8 char + longFilename[currentPos++] = utf16_ch & 0x007F; + } else if (0 == (utf16_ch & 0xF800)) { + // Encode as 2-byte utf-8 char + longFilename[currentPos++] = 0xC0 | ((utf16_ch >> 6) & 0x1F); + longFilename[currentPos++] = 0x80 | (utf16_ch & 0x3F); + } else { + #if LONG_FILENAME_CHARSIZE > 2 + // Encode as 3-byte utf-8 char + longFilename[currentPos++] = 0xE0 | ((utf16_ch >> 12) & 0x0F); + longFilename[currentPos++] = 0xC0 | ((utf16_ch >> 6) & 0x3F); + longFilename[currentPos++] = 0xC0 | (utf16_ch & 0x3F); + #else + // Encode as '_' + longFilename[currentPos++] = '_'; + #endif + } + + if (0 == utf16_ch) break; // End of filename + } + return currentPos; + #else + return n; + #endif + } } } diff --git a/Marlin/src/sd/SdFatConfig.h b/Marlin/src/sd/SdFatConfig.h index 8f0596c5dd..c0fa9d42f2 100644 --- a/Marlin/src/sd/SdFatConfig.h +++ b/Marlin/src/sd/SdFatConfig.h @@ -103,5 +103,14 @@ #define FILENAME_LENGTH 13 // Number of UTF-16 characters per entry +#if ENABLED(SD_FILENAMES_MULTILINGUAL) + // UTF-8 may use up to 3 bytes to represent single UTF-16 code point. + // We discard 3-byte characters allowing only 2-bytes + // or 1-byte if SD_FILENAMES_MULTILINGUAL disabled. + #define LONG_FILENAME_CHARSIZE 2 +#else + #define LONG_FILENAME_CHARSIZE 1 +#endif + // Total bytes needed to store a single long filename -#define LONG_FILENAME_LENGTH (FILENAME_LENGTH * MAX_VFAT_ENTRIES + 1) +#define LONG_FILENAME_LENGTH (FILENAME_LENGTH * LONG_FILENAME_CHARSIZE * MAX_VFAT_ENTRIES + 1)