A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: truncating chapter titles using UTF-8 safe function (#599)

## Summary

* Truncating chapter titles using utf8 safe functions (Cyrillic titles
were split mid codepoint)
* refactoring of lib/Utf8

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< PARTIALLY >**_

authored by

Arthur Tazhitdinov and committed by
GitHub
b1dcb773 0d82b039

+45 -40
+13 -5
lib/GfxRenderer/GfxRenderer.cpp
··· 415 415 416 416 std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth, 417 417 const EpdFontFamily::Style style) const { 418 + if (!text || maxWidth <= 0) return ""; 419 + 418 420 std::string item = text; 419 - int itemWidth = getTextWidth(fontId, item.c_str(), style); 420 - while (itemWidth > maxWidth && item.length() > 8) { 421 - item.replace(item.length() - 5, 5, "..."); 422 - itemWidth = getTextWidth(fontId, item.c_str(), style); 421 + const char* ellipsis = "..."; 422 + int textWidth = getTextWidth(fontId, item.c_str(), style); 423 + if (textWidth <= maxWidth) { 424 + // Text fits, return as is 425 + return item; 423 426 } 424 - return item; 427 + 428 + while (!item.empty() && getTextWidth(fontId, (item + ellipsis).c_str(), style) >= maxWidth) { 429 + utf8RemoveLastChar(item); 430 + } 431 + 432 + return item.empty() ? ellipsis : item + ellipsis; 425 433 } 426 434 427 435 // Note: Internal driver treats screen in command orientation; this library exposes a logical orientation
+17
lib/Utf8/Utf8.cpp
··· 29 29 30 30 return cp; 31 31 } 32 + 33 + size_t utf8RemoveLastChar(std::string& str) { 34 + if (str.empty()) return 0; 35 + size_t pos = str.size() - 1; 36 + while (pos > 0 && (static_cast<unsigned char>(str[pos]) & 0xC0) == 0x80) { 37 + --pos; 38 + } 39 + str.resize(pos); 40 + return pos; 41 + } 42 + 43 + // Truncate string by removing N UTF-8 characters from the end 44 + void utf8TruncateChars(std::string& str, const size_t numChars) { 45 + for (size_t i = 0; i < numChars && !str.empty(); ++i) { 46 + utf8RemoveLastChar(str); 47 + } 48 + }
+5 -1
lib/Utf8/Utf8.h
··· 1 1 #pragma once 2 2 3 3 #include <cstdint> 4 - 4 + #include <string> 5 5 #define REPLACEMENT_GLYPH 0xFFFD 6 6 7 7 uint32_t utf8NextCodepoint(const unsigned char** string); 8 + // Remove the last UTF-8 codepoint from a std::string and return the new size. 9 + size_t utf8RemoveLastChar(std::string& str); 10 + // Truncate string by removing N UTF-8 codepoints from the end. 11 + void utf8TruncateChars(std::string& str, size_t numChars);
+6 -5
src/activities/home/HomeActivity.cpp
··· 4 4 #include <Epub.h> 5 5 #include <GfxRenderer.h> 6 6 #include <SDCardManager.h> 7 + #include <Utf8.h> 7 8 #include <Xtc.h> 8 9 9 10 #include <cstring> ··· 366 367 while (!lines.back().empty() && renderer.getTextWidth(UI_12_FONT_ID, lines.back().c_str()) > maxLineWidth) { 367 368 // Remove "..." first, then remove one UTF-8 char, then add "..." back 368 369 lines.back().resize(lines.back().size() - 3); // Remove "..." 369 - StringUtils::utf8RemoveLastChar(lines.back()); 370 + utf8RemoveLastChar(lines.back()); 370 371 lines.back().append("..."); 371 372 } 372 373 break; ··· 375 376 int wordWidth = renderer.getTextWidth(UI_12_FONT_ID, i.c_str()); 376 377 while (wordWidth > maxLineWidth && !i.empty()) { 377 378 // Word itself is too long, trim it (UTF-8 safe) 378 - StringUtils::utf8RemoveLastChar(i); 379 + utf8RemoveLastChar(i); 379 380 // Check if we have room for ellipsis 380 381 std::string withEllipsis = i + "..."; 381 382 wordWidth = renderer.getTextWidth(UI_12_FONT_ID, withEllipsis.c_str()); ··· 428 429 if (!lastBookAuthor.empty()) { 429 430 std::string trimmedAuthor = lastBookAuthor; 430 431 while (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) > maxLineWidth && !trimmedAuthor.empty()) { 431 - StringUtils::utf8RemoveLastChar(trimmedAuthor); 432 + utf8RemoveLastChar(trimmedAuthor); 432 433 } 433 434 if (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) < 434 435 renderer.getTextWidth(UI_10_FONT_ID, lastBookAuthor.c_str())) { ··· 462 463 // Trim author if too long (UTF-8 safe) 463 464 bool wasTrimmed = false; 464 465 while (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) > maxLineWidth && !trimmedAuthor.empty()) { 465 - StringUtils::utf8RemoveLastChar(trimmedAuthor); 466 + utf8RemoveLastChar(trimmedAuthor); 466 467 wasTrimmed = true; 467 468 } 468 469 if (wasTrimmed && !trimmedAuthor.empty()) { 469 470 // Make room for ellipsis 470 471 while (renderer.getTextWidth(UI_10_FONT_ID, (trimmedAuthor + "...").c_str()) > maxLineWidth && 471 472 !trimmedAuthor.empty()) { 472 - StringUtils::utf8RemoveLastChar(trimmedAuthor); 473 + utf8RemoveLastChar(trimmedAuthor); 473 474 } 474 475 trimmedAuthor.append("..."); 475 476 }
+2 -2
src/activities/reader/EpubReaderActivity.cpp
··· 570 570 availableTitleSpace = rendererableScreenWidth - titleMarginLeft - titleMarginRight; 571 571 titleMarginLeftAdjusted = titleMarginLeft; 572 572 } 573 - while (titleWidth > availableTitleSpace && title.length() > 11) { 574 - title.replace(title.length() - 8, 8, "..."); 573 + if (titleWidth > availableTitleSpace) { 574 + title = renderer.truncatedText(SMALL_FONT_ID, title.c_str(), availableTitleSpace); 575 575 titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 576 576 } 577 577 }
+2 -2
src/activities/reader/TxtReaderActivity.cpp
··· 533 533 534 534 std::string title = txt->getTitle(); 535 535 int titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 536 - while (titleWidth > availableTextWidth && title.length() > 11) { 537 - title.replace(title.length() - 8, 8, "..."); 536 + if (titleWidth > availableTextWidth) { 537 + title = renderer.truncatedText(SMALL_FONT_ID, title.c_str(), availableTextWidth); 538 538 titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 539 539 } 540 540
-19
src/util/StringUtils.cpp
··· 61 61 return localFile.endsWith(localExtension); 62 62 } 63 63 64 - size_t utf8RemoveLastChar(std::string& str) { 65 - if (str.empty()) return 0; 66 - size_t pos = str.size() - 1; 67 - // Walk back to find the start of the last UTF-8 character 68 - // UTF-8 continuation bytes start with 10xxxxxx (0x80-0xBF) 69 - while (pos > 0 && (static_cast<unsigned char>(str[pos]) & 0xC0) == 0x80) { 70 - --pos; 71 - } 72 - str.resize(pos); 73 - return pos; 74 - } 75 - 76 - // Truncate string by removing N UTF-8 characters from the end 77 - void utf8TruncateChars(std::string& str, const size_t numChars) { 78 - for (size_t i = 0; i < numChars && !str.empty(); ++i) { 79 - utf8RemoveLastChar(str); 80 - } 81 - } 82 - 83 64 } // namespace StringUtils
-6
src/util/StringUtils.h
··· 19 19 bool checkFileExtension(const std::string& fileName, const char* extension); 20 20 bool checkFileExtension(const String& fileName, const char* extension); 21 21 22 - // UTF-8 safe string truncation - removes one character from the end 23 - // Returns the new size after removing one UTF-8 character 24 - size_t utf8RemoveLastChar(std::string& str); 25 - 26 - // Truncate string by removing N UTF-8 characters from the end 27 - void utf8TruncateChars(std::string& str, size_t numChars); 28 22 } // namespace StringUtils