Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix M23 select long filename for print #25540

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 51 additions & 36 deletions Marlin/src/sd/SdBaseFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -703,14 +703,18 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11]
// Get VFat dir entry
pvFat = (vfat_t *) p;
// Get checksum from the last entry of the sequence
if (pvFat->sequenceNumber & 0x40) lfnChecksum = pvFat->checksum;
if (pvFat->sequenceNumber & 0x40) {
lfnChecksum = pvFat->checksum;
ZERO(lfnName);
}
// Get LFN sequence number
lfnSequenceNumber = pvFat->sequenceNumber & 0x1F;
if WITHIN(lfnSequenceNumber, 1, reqEntriesNum) {
// Check checksum for all other entries with the starting checksum fetched before
if (lfnChecksum == pvFat->checksum) {
// Set chunk of LFN from VFAT entry into lfnName
getLFNName(pvFat, (char *)lfnName, lfnSequenceNumber);
TERN_(UTF_FILENAME_SUPPORT, convertUtf16ToUtf8((char *)lfnName));
// LFN found?
if (!strncasecmp((char*)dlname, (char*)lfnName, lfnNameLength)) lfnFileFound = true;
}
Expand Down Expand Up @@ -1132,13 +1136,13 @@ bool SdBaseFile::openNext(SdBaseFile *dirFile, uint8_t oflag) {
* Get the LFN filename block from a dir. Get the block in lname at startOffset
*/
void SdBaseFile::getLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) {
uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH;
const uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH;
LOOP_L_N(i, FILENAME_LENGTH) {
const uint16_t utf16_ch = (i >= 11) ? pFatDir->name3[i - 11] : (i >= 5) ? pFatDir->name2[i - 5] : pFatDir->name1[i];
#if ENABLED(UTF_FILENAME_SUPPORT)
// 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 = (startOffset + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding
const uint16_t idx = (startOffset + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding
lname[idx] = utf16_ch & 0xFF;
lname[idx + 1] = (utf16_ch >> 8) & 0xFF;
#else
Expand All @@ -1152,8 +1156,8 @@ bool SdBaseFile::openNext(SdBaseFile *dirFile, uint8_t oflag) {
* Set the LFN filename block lname to a dir. Put the block based on sequence number
*/
void SdBaseFile::setLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) {
uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH;
uint8_t nameLength = strlen(lname);
const uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH,
nameLength = strlen(lname);
LOOP_L_N(i, FILENAME_LENGTH) {
uint16_t ch = 0;
if ((startOffset + i) < nameLength)
Expand Down Expand Up @@ -1424,7 +1428,7 @@ int16_t SdBaseFile::read(void *buf, uint16_t nbyte) {
* readDir() called before a directory has been opened, this is not
* a directory file or an I/O error occurred.
*/
int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) {
int8_t SdBaseFile::readDir(dir_t *dir, char * const longFilename) {
int16_t n;
// if not a directory file or miss-positioned return an error
if (!isDir() || (0x1F & curPosition_)) return -1;
Expand Down Expand Up @@ -1506,44 +1510,55 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) {
// Post-process normal file or subdirectory longname, if any
if (DIR_IS_FILE_OR_SUBDIR(dir)) {
#if ENABLED(UTF_FILENAME_SUPPORT)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this is the original behavior, I guess UCS-2 to UTF-8 conversion should be done always, since VFat always stores long names as UCS-2 (UTF-16) in LFN sequences, while we always handle 8bit string names.

#if LONG_FILENAME_CHARSIZE > 2
// Add warning for developers for unsupported 3-byte cases.
// (Converting 2-byte codepoints to 3-byte in-place would break the rest of filename.)
#error "Currently filename re-encoding is done in-place. It may break the remaining chars to use 3-byte codepoints."
#endif

// Is there a long filename to decode?
if (longFilename) {
// Reset n to the start of the long name
n = 0;
for (uint16_t idx = 0; idx < (LONG_FILENAME_LENGTH); idx += 2) { // idx is fixed since FAT LFN always contains UTF-16LE encoding
const uint16_t utf16_ch = longFilename[idx] | (longFilename[idx + 1] << 8);
if (0xD800 == (utf16_ch & 0xF800)) // Surrogate pair - encode as '_'
longFilename[n++] = '_';
else if (0 == (utf16_ch & 0xFF80)) // Encode as 1-byte UTF-8 char
longFilename[n++] = utf16_ch & 0x007F;
else if (0 == (utf16_ch & 0xF800)) { // Encode as 2-byte UTF-8 char
longFilename[n++] = 0xC0 | ((utf16_ch >> 6) & 0x1F);
longFilename[n++] = 0x80 | ( utf16_ch & 0x3F);
}
else {
#if LONG_FILENAME_CHARSIZE > 2 // Encode as 3-byte UTF-8 char
longFilename[n++] = 0xE0 | ((utf16_ch >> 12) & 0x0F);
longFilename[n++] = 0xC0 | ((utf16_ch >> 6) & 0x3F);
longFilename[n++] = 0xC0 | ( utf16_ch & 0x3F);
#else // Encode as '_'
longFilename[n++] = '_';
#endif
}
if (0 == utf16_ch) break; // End of filename
} // idx
} // longFilename
n = convertUtf16ToUtf8(longFilename);
}
#endif
return n;
} // DIR_IS_FILE_OR_SUBDIR
}
}

#if ENABLED(UTF_FILENAME_SUPPORT)

uint8_t SdBaseFile::convertUtf16ToUtf8(char * const longFilename) {
#if LONG_FILENAME_CHARSIZE > 2
// Add warning for developers for unsupported 3-byte cases.
// (Converting 2-byte codepoints to 3-byte in-place would break the rest of filename.)
#error "Currently filename re-encoding is done in-place. It may break the remaining chars to use 3-byte codepoints."
#endif

int16_t n;
// Reset n to the start of the long name
n = 0;
for (uint16_t idx = 0; idx < (LONG_FILENAME_LENGTH); idx += 2) { // idx is fixed since FAT LFN always contains UTF-16LE encoding
const uint16_t utf16_ch = longFilename[idx] | (longFilename[idx + 1] << 8);
if (0xD800 == (utf16_ch & 0xF800)) // Surrogate pair - encode as '_'
longFilename[n++] = '_';
else if (0 == (utf16_ch & 0xFF80)) // Encode as 1-byte UTF-8 char
longFilename[n++] = utf16_ch & 0x007F;
else if (0 == (utf16_ch & 0xF800)) { // Encode as 2-byte UTF-8 char
longFilename[n++] = 0xC0 | ((utf16_ch >> 6) & 0x1F);
longFilename[n++] = 0x80 | ( utf16_ch & 0x3F);
}
else {
#if LONG_FILENAME_CHARSIZE > 2 // Encode as 3-byte UTF-8 char
longFilename[n++] = 0xE0 | ((utf16_ch >> 12) & 0x0F);
longFilename[n++] = 0xC0 | ((utf16_ch >> 6) & 0x3F);
longFilename[n++] = 0xC0 | ( utf16_ch & 0x3F);
#else // Encode as '_'
longFilename[n++] = '_';
#endif
}
if (0 == utf16_ch) break; // End of filename
} // idx

return n;
}

#endif // UTF_FILENAME_SUPPORT

// Read next directory entry into the cache
// Assumes file is correctly positioned
dir_t* SdBaseFile::readDirCache() {
Expand Down
10 changes: 6 additions & 4 deletions Marlin/src/sd/SdBaseFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ class SdBaseFile {
bool printName();
int16_t read();
int16_t read(void *buf, uint16_t nbyte);
int8_t readDir(dir_t *dir, char *longFilename);
int8_t readDir(dir_t *dir, char * const longFilename);
static bool remove(SdBaseFile *dirFile, const char *path);
bool remove();

Expand Down Expand Up @@ -392,14 +392,16 @@ class SdBaseFile {
bool openCachedEntry(uint8_t cacheIndex, uint8_t oflags);
dir_t* readDirCache();

#if ENABLED(UTF_FILENAME_SUPPORT)
uint8_t convertUtf16ToUtf8(char * const longFilename);
#endif

// Long Filename create/write support
#if ENABLED(LONG_FILENAME_WRITE_SUPPORT)
static bool isDirLFN(const dir_t* dir);
static bool isDirNameLFN(const char *dirname);
static bool parsePath(const char *str, uint8_t *name, uint8_t *lname, const char **ptr);
/**
* Return the number of entries needed in the FAT for this LFN
*/
// Return the number of entries needed in the FAT for this LFN
static inline uint8_t getLFNEntriesNum(const char *lname) { return (strlen(lname) + 12) / 13; }
static void getLFNName(vfat_t *vFatDir, char *lname, uint8_t startOffset);
static void setLFNName(vfat_t *vFatDir, char *lname, uint8_t lfnSequenceNumber);
Expand Down