Skip to content

feat: Add text wrapping to Lyra Extended theme#1074

Closed
iandchasse wants to merge 6 commits intocrosspoint-reader:masterfrom
iandchasse:feature/lyra-extended-text-wrap
Closed

feat: Add text wrapping to Lyra Extended theme#1074
iandchasse wants to merge 6 commits intocrosspoint-reader:masterfrom
iandchasse:feature/lyra-extended-text-wrap

Conversation

@iandchasse
Copy link
Contributor

Summary

Additional Context

image
  • Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks,
    specific areas to focus on).

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? < YES >

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

The changes implement UTF-8 aware word-wrapping for book titles in the Lyra theme component, replacing single-line truncation with multi-line support (up to 3 lines). A dynamic height calculation adjusts the selection box based on the number of wrapped title lines, and the home cover tile height metric is increased from 287 to 300 pixels.

Changes

Cohort / File(s) Summary
Title Wrapping & Dynamic Height
src/components/themes/lyra/Lyra3CoversTheme.cpp
Implements UTF-8 aware word-wrapping that splits titles into multiple lines (max 3), truncates with ellipses when exceeding max width, and calculates dynamic title box height based on line count. Adjusts selection box height accordingly. Adds vector and Utf8 includes.
Layout Metric Update
src/components/themes/lyra/Lyra3CoversTheme.h
Updates homeCoverTileHeight metric from 287 to 300 pixels.

Possibly related PRs

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding text wrapping functionality to the Lyra theme, specifically the 3-cover variant, which is the core purpose of the changeset.
Description check ✅ Passed The description is directly related to the changeset, explaining the goal of wrapping text in the 3-cover Lyra Theme and detailing the changes made, including font size adjustments and multi-line text support.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/components/themes/lyra/Lyra3CoversTheme.cpp (2)

92-146: Consider extracting text wrapping logic into a helper function.

This 55-line block handles word parsing, line wrapping, and ellipsis truncation all inline. Extracting it into a separate helper (e.g., wrapTitleToLines(renderer, title, maxLineWidth, maxLines)) would improve readability and make it reusable/testable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/themes/lyra/Lyra3CoversTheme.cpp` around lines 92 - 146, The
inline 55-line text-wrapping block should be extracted into a reusable helper to
improve readability and testability: create a function (e.g., wrapTitleToLines
or wrapTextToLines) that accepts Renderer& renderer, const std::string& title,
int maxLineWidth, int maxLines, font identifiers (SMALL_FONT_ID,
EpdFontFamily::REGULAR) and returns vector<string> titleLines; move all word
splitting, truncation logic (including utf8RemoveLastChar usage and ellipsis
handling) into that helper, replace the block in Lyra3CoversTheme.cpp with a
call to the new helper, and ensure behavior (spaceWidth calculation, per-word
truncation, and final line ellipsizing) is preserved.

20-20: Remove unused global variable coverWidth.

This variable is never used in the file. Line 54 declares a local float coverWidth that shadows it, making this global completely unreachable.

🧹 Proposed fix
 constexpr int hPaddingInSelection = 8;
 constexpr int cornerRadius = 6;
-int coverWidth = 0;
 }  // namespace
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/themes/lyra/Lyra3CoversTheme.cpp` at line 20, Remove the
unused global variable coverWidth declared at the top of Lyra3CoversTheme.cpp;
it is shadowed by a local float coverWidth later in the file and never
referenced elsewhere, so delete the global declaration to avoid shadowing and
unused-symbol warnings.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e4d0e5 and 7ae705b.

📒 Files selected for processing (2)
  • src/components/themes/lyra/Lyra3CoversTheme.cpp
  • src/components/themes/lyra/Lyra3CoversTheme.h
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
src/components/themes/lyra/Lyra3CoversTheme.h (1)

28-28: LGTM!

The increased tile height appropriately accommodates the new 3-line text wrapping layout introduced in the corresponding implementation file.

src/components/themes/lyra/Lyra3CoversTheme.cpp (1)

148-170: LGTM!

The dynamic title box height calculation correctly adapts to the actual number of wrapped lines, and the selection box/text rendering logic properly uses these computed values.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/themes/lyra/Lyra3CoversTheme.cpp`:
- Around line 92-146: The inline 55-line text-wrapping block should be extracted
into a reusable helper to improve readability and testability: create a function
(e.g., wrapTitleToLines or wrapTextToLines) that accepts Renderer& renderer,
const std::string& title, int maxLineWidth, int maxLines, font identifiers
(SMALL_FONT_ID, EpdFontFamily::REGULAR) and returns vector<string> titleLines;
move all word splitting, truncation logic (including utf8RemoveLastChar usage
and ellipsis handling) into that helper, replace the block in
Lyra3CoversTheme.cpp with a call to the new helper, and ensure behavior
(spaceWidth calculation, per-word truncation, and final line ellipsizing) is
preserved.
- Line 20: Remove the unused global variable coverWidth declared at the top of
Lyra3CoversTheme.cpp; it is shadowed by a local float coverWidth later in the
file and never referenced elsewhere, so delete the global declaration to avoid
shadowing and unused-symbol warnings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ae705b and 2be12bf.

📒 Files selected for processing (2)
  • open-x4-sdk
  • src/components/themes/lyra/Lyra3CoversTheme.cpp
✅ Files skipped from review due to trivial changes (1)
  • open-x4-sdk
🔇 Additional comments (1)
src/components/themes/lyra/Lyra3CoversTheme.cpp (1)

8-11: LGTM — required headers for wrapping logic.

The added includes look appropriate for the new word-wrapping behavior.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/themes/lyra/Lyra3CoversTheme.cpp`:
- Around line 110-119: The truncation logic in the for-loop that builds
titleLines (in Lyra3CoversTheme.cpp) can append "..." twice to the third line;
before appending "..." to titleLines.back() check whether it already ends with
"..." (using a UTF-8 safe check of the last 3 bytes/characters) and only append
if it does not; then run the existing width-adjust loop (which calls
renderer.getTextWidth(SMALL_FONT_ID, ..., EpdFontFamily::REGULAR) and uses
utf8RemoveLastChar) as-is to shrink the line until it fits. Ensure the guard
uses the same string used by utf8RemoveLastChar to avoid double-ellipsis while
preserving UTF-8 safety.

iandchasse and others added 3 commits February 22, 2026 02:11
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/components/themes/lyra/Lyra3CoversTheme.cpp (1)

152-152: Consider extracting the magic number 5 to a named constant.

The value 5 appears here and again at line 167, representing vertical padding above the title text. Extracting it to a named constant (e.g., vPaddingInSelection or titleTextTopPadding) would improve maintainability.

♻️ Suggested refactor

Add to the anonymous namespace at the top:

constexpr int vPaddingInSelection = 5;

Then update both usages:

-      const int dynamicTitleBoxHeight = dynamicBlockHeight + hPaddingInSelection + 5;
+      const int dynamicTitleBoxHeight = dynamicBlockHeight + hPaddingInSelection + vPaddingInSelection;
-      int currentY = tileY + Lyra3CoversMetrics::values.homeCoverHeight + hPaddingInSelection + 5;
+      int currentY = tileY + Lyra3CoversMetrics::values.homeCoverHeight + hPaddingInSelection + vPaddingInSelection;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/themes/lyra/Lyra3CoversTheme.cpp` at line 152, Extract the
magic number 5 into a named constexpr and replace both usages so padding is
clear and maintainable: add a constexpr int vPaddingInSelection = 5 to the
anonymous namespace in Lyra3CoversTheme.cpp, then update the expressions that
compute dynamicTitleBoxHeight (which currently uses dynamicBlockHeight +
hPaddingInSelection + 5) and the other occurrence at the later line (line 167)
to use vPaddingInSelection instead of the literal 5.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ba2c28 and b3fa5fd.

📒 Files selected for processing (1)
  • src/components/themes/lyra/Lyra3CoversTheme.cpp
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-02-19T12:17:05.421Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 988
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:649-661
Timestamp: 2026-02-19T12:17:05.421Z
Learning: In ChapterHtmlSlimParser.cpp, when computing footnote word indices in endElement() for footnote links, the wordIndex must be cumulative across the 750-word mid-paragraph flush boundary. The correct calculation is: `wordIndex = wordsExtractedInBlock + currentTextBlock->size()`, not just `currentTextBlock->size()`. This ensures footnotes attach to the page containing their actual anchor word, even after layoutAndExtractLines(false) has extracted and removed earlier words from the block.

Applied to files:

  • src/components/themes/lyra/Lyra3CoversTheme.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
src/components/themes/lyra/Lyra3CoversTheme.cpp (5)

8-11: LGTM!

The new includes are appropriate for the UTF-8 aware word-wrapping functionality.


92-103: LGTM!

The word-splitting logic correctly handles edge cases like leading spaces, consecutive spaces, and uses reserve(8) as a reasonable optimization for typical title lengths.


110-133: The double-ellipsis concern from previous review has been addressed.

The guard at lines 112-114 correctly prevents appending "..." when the line already ends with it, using the suggested fix. The UTF-8 truncation logic using utf8RemoveLastChar is appropriate for multi-byte character handling.
[duplicate_comment, approve_code_changes]


135-147: LGTM!

The line-building logic correctly uses getTextAdvanceX for cumulative width measurement and properly handles word wrapping with space width calculations.


154-171: LGTM!

The selection box rendering correctly adapts its height based on the number of title lines, and the text rendering loop properly positions each line with consistent spacing.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/themes/lyra/Lyra3CoversTheme.cpp`:
- Line 152: Extract the magic number 5 into a named constexpr and replace both
usages so padding is clear and maintainable: add a constexpr int
vPaddingInSelection = 5 to the anonymous namespace in Lyra3CoversTheme.cpp, then
update the expressions that compute dynamicTitleBoxHeight (which currently uses
dynamicBlockHeight + hPaddingInSelection + 5) and the other occurrence at the
later line (line 167) to use vPaddingInSelection instead of the literal 5.


for (auto& w : words) {
if (titleLines.size() >= 3) {
if (titleLines.back().size() < 3 || titleLines.back().compare(titleLines.back().size() - 3, 3, "...") != 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be nice to use a UTF-8 ellipsis character here instead of three periods

Comment on lines +124 to +129
int wordW = renderer.getTextWidth(SMALL_FONT_ID, w.c_str(), EpdFontFamily::REGULAR);
while (wordW > maxLineWidth && !w.empty()) {
utf8RemoveLastChar(w);
std::string withE = w + "...";
wordW = renderer.getTextWidth(SMALL_FONT_ID, withE.c_str(), EpdFontFamily::REGULAR);
if (wordW <= maxLineWidth) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We have almost the same logic in GfxRenderer::truncatedText, could this be simplified to use that implementation?

Comment on lines +92 to +93
// Wrap title to up to 3 lines (word-wrap by advance width)
const std::string& lastBookTitle = recentBooks[i].title;
Copy link
Contributor

Choose a reason for hiding this comment

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

Overall, this algorithm overlaps a lot with what's already in BaseTheme::drawRecentBookCover. I wonder if we could add a new utility for doing this type of multiline layout, like:

/// Word-wrap `text` into at most `maxLines` lines, each no wider than
/// `maxWidth` pixels. Overflowing words and excess lines are UTF-8-safely
/// truncated with an ellipsis (U+2026).
std::vector<std::string> wrapText(
    const GfxRenderer& renderer,
    int fontId,
    const std::string& text,
    int maxWidth,
    int maxLines,
    EpdFontFamily::Style style = EpdFontFamily::REGULAR);

@iandchasse iandchasse closed this Feb 23, 2026
ngxson pushed a commit that referenced this pull request Feb 25, 2026
## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)


* **What changes are included?**
Conrgegate the changes of #1074 , #1013 , and extended upon #911 by
@lkristensen
New function implemented in GfxRenderer.cpp
```C++
std::vector<std::string> GfxRenderer::wrappedText(const int fontId, const char* text, const int maxWidth,
                                                  const int maxLines, const EpdFontFamily::Style style) const
```
Applied logic to all uses in Lyra, Lyra Extended, and base theme
(continue reading card as pointed out by @znelson


## Additional Context





![IMG_8604](https://github.com/user-attachments/assets/49da71c9-a44f-4cde-b3bf-6773d71601b6)

![IMG_8605](https://github.com/user-attachments/assets/5eab4293-65c1-47fb-b422-8ab53a6b50a2)

![IMG_8606](https://github.com/user-attachments/assets/e0f98d19-0e3f-4294-83a1-e49264378dca)


---

### 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? _**< YES >**_

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
el pushed a commit to el/crosspoint-reader that referenced this pull request Feb 26, 2026
…oint-reader#1141)

## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)


* **What changes are included?**
Conrgegate the changes of crosspoint-reader#1074 , crosspoint-reader#1013 , and extended upon crosspoint-reader#911 by
@lkristensen
New function implemented in GfxRenderer.cpp
```C++
std::vector<std::string> GfxRenderer::wrappedText(const int fontId, const char* text, const int maxWidth,
                                                  const int maxLines, const EpdFontFamily::Style style) const
```
Applied logic to all uses in Lyra, Lyra Extended, and base theme
(continue reading card as pointed out by @znelson


## Additional Context





![IMG_8604](https://github.com/user-attachments/assets/49da71c9-a44f-4cde-b3bf-6773d71601b6)

![IMG_8605](https://github.com/user-attachments/assets/5eab4293-65c1-47fb-b422-8ab53a6b50a2)

![IMG_8606](https://github.com/user-attachments/assets/e0f98d19-0e3f-4294-83a1-e49264378dca)


---

### 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? _**< YES >**_

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants