From 370f87ea011e6ad5056fba649eb5674b9d9b1d0b Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Sun, 21 Jun 2026 02:14:42 -0400 Subject: [PATCH 01/27] feat: add Spacing Modifier Letters range to font presets (#2194) --- lib/EpdFont/scripts/fontconvert_sdcard.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/EpdFont/scripts/fontconvert_sdcard.py b/lib/EpdFont/scripts/fontconvert_sdcard.py index e2125be4..28373b58 100755 --- a/lib/EpdFont/scripts/fontconvert_sdcard.py +++ b/lib/EpdFont/scripts/fontconvert_sdcard.py @@ -40,7 +40,8 @@ INTERVAL_PRESETS = { "ascii": [(0x0020, 0x007E)], "latin1": [(0x0080, 0x00FF)], "latin-ext": [(0x0020, 0x007E), (0x0080, 0x00FF), (0x0100, 0x024F), - (0x1E00, 0x1EFF), (0x2000, 0x206F), (0xFB00, 0xFB06)], + (0x02B0, 0x02FF), (0x1E00, 0x1EFF), (0x2000, 0x206F), + (0xFB00, 0xFB06)], "greek": [(0x0370, 0x03FF), (0x1F00, 0x1FFF)], "cyrillic": [(0x0400, 0x04FF), (0x0500, 0x052F)], "hebrew": [(0x0590, 0x05FF), (0xFB1D, 0xFB4F)], @@ -62,7 +63,7 @@ INTERVAL_PRESETS = { # Composite preset for English-language literary fiction including scifi/popsci. # Greek for physics terms, math operators, geometric shapes, uncommon # dialogue punctuation, CJK quote marks, miscellaneous symbols (♪♫♬), dingbats. - "reading": [(0x0020, 0x024F), (0x0300, 0x036F), (0x0370, 0x03FF), + "reading": [(0x0020, 0x024F), (0x02B0, 0x02FF), (0x0300, 0x036F), (0x0370, 0x03FF), (0x0400, 0x04FF), (0x1E00, 0x1EFF), (0x2000, 0x206F), (0x2070, 0x209F), (0x20A0, 0x20CF), (0x2150, 0x218F), (0x2190, 0x21FF), (0x2200, 0x22FF), (0x2500, 0x257F), From d1e4650e19638fb74b745df9e09b90f3f29271c5 Mon Sep 17 00:00:00 2001 From: SurprisedDuck Date: Sun, 21 Jun 2026 15:12:14 +0200 Subject: [PATCH 02/27] fix: don't justify-stretch a leading no-break space (#2185) (#2298) --- lib/Epub/Epub/ParsedText.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/Epub/Epub/ParsedText.cpp b/lib/Epub/Epub/ParsedText.cpp index e57d1743..ebe758db 100644 --- a/lib/Epub/Epub/ParsedText.cpp +++ b/lib/Epub/Epub/ParsedText.cpp @@ -1006,7 +1006,10 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const int advance = renderer.getKerning(fontId, lastCodepoint(reorderedWordsScratch[wordIdx]), firstCodepoint(reorderedWordsScratch[wordIdx + 1]), reorderedStylesScratch[wordIdx]); - if (reorderedWordsScratch[wordIdx] == " " && reorderedContinuesScratch[wordIdx] && + // wordIdx > 0 mirrors the gap accounting above (which skips index 0): a leading + // no-break space must not receive justifyExtra, or the line over-stretches by one + // gap and the last word is pushed past the right margin (issue #2185). + if (wordIdx > 0 && reorderedWordsScratch[wordIdx] == " " && reorderedContinuesScratch[wordIdx] && effectiveAlignment == CssTextAlign::Justify && !isLastLine) { advance += reorderedJustifyExtra; } @@ -1048,7 +1051,8 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const // Cross-boundary kerning for continuation words int advance = renderer.getKerning(fontId, lastCodepoint(lineWords[wordIdx]), firstCodepoint(lineWords[wordIdx + 1]), lineWordStyles[wordIdx]); - if (lineWords[wordIdx] == " " && continuesVec[lastBreakAt + wordIdx] && + // wordIdx > 0: see the LTR branch — a leading no-break space is not a justifiable gap. + if (wordIdx > 0 && lineWords[wordIdx] == " " && continuesVec[lastBreakAt + wordIdx] && effectiveAlignment == CssTextAlign::Justify && !isLastLine) { advance += justifyExtra; } @@ -1086,7 +1090,10 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const int advance = wordWidths[lastBreakAt + wordIdx]; advance += renderer.getKerning(fontId, lastCodepoint(lineWords[wordIdx]), firstCodepoint(lineWords[wordIdx + 1]), lineWordStyles[wordIdx]); - if (lineWords[wordIdx] == " " && continuesVec[lastBreakAt + wordIdx] && + // wordIdx > 0 mirrors the gap accounting above (which skips index 0): a leading + // no-break space must not receive justifyExtra, or the line over-stretches by one + // gap and the last word is pushed past the right margin (issue #2185). + if (wordIdx > 0 && lineWords[wordIdx] == " " && continuesVec[lastBreakAt + wordIdx] && effectiveAlignment == CssTextAlign::Justify && !isLastLine) { advance += justifyExtra; } From 282514f755421d4b07676a922eaf1a934f39012d Mon Sep 17 00:00:00 2001 From: kygia Date: Sun, 21 Jun 2026 13:43:39 -0700 Subject: [PATCH 03/27] fix(epub): flush displaced anchor before overwrite (#2336) (#2382) ## Summary * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) Fixes footnote links landing in start section instead of the specific foot note target. * **What changes are included?** ChapterHtmlSlimParser.cpp: call flushPendingAnchor() before overwriting pendingAnchorId. First id got lost due to consecutive non-block elements carry ids, the first id was lost and the reader had no page to jump to, so it defaulted to page 0. ## Additional Context Tested with the epubs attached to to #2336. Need to clear .crosspoint/ cache after flashing so the anchor map gets rebuilt with the fix. * 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? Partially assisted with writing documentation. --- lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index c4b0ccd5..bd51f9d2 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -317,6 +317,14 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* const bool isTocAnchor = std::find(self->tocAnchors.begin(), self->tocAnchors.end(), idValue) != self->tocAnchors.end(); if (isTocAnchor || (!isNonNavigableInlineElement(name) && self->anchorData.size() < MAX_ANCHORS_PER_CHAPTER)) { + // Flush a displaced anchor before overwriting. Consecutive non-block elements + // (e.g.