Merge remote-tracking branch 'origin/develop' into feat-touch

# Conflicts:
#	freeink-sdk
#	lib/I18n/translations/slovak.yaml
#	platformio.ini
#	src/components/icons/bookmark.h
This commit is contained in:
Justin Mitchell
2026-06-28 02:20:24 -04:00
60 changed files with 998 additions and 227 deletions
+20 -3
View File
@@ -6,6 +6,20 @@
#include <new>
namespace {
template <typename Predicate>
void renderFilteredPageElements(const std::vector<std::shared_ptr<PageElement>>& elements, GfxRenderer& renderer,
const int fontId, const int xOffset, const int yOffset, Predicate&& predicate) {
for (const auto& element : elements) {
if (predicate(*element)) {
element->render(renderer, fontId, xOffset, yOffset);
}
}
}
} // namespace
void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
block->render(renderer, fontId, xPos + xOffset, yPos + yOffset);
}
@@ -93,9 +107,12 @@ std::unique_ptr<PageHorizontalRule> PageHorizontalRule::deserialize(HalFile& fil
}
void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) const {
for (auto& element : elements) {
element->render(renderer, fontId, xOffset, yOffset);
}
renderFilteredPageElements(elements, renderer, fontId, xOffset, yOffset, [](const PageElement&) { return true; });
}
void Page::renderImages(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) const {
renderFilteredPageElements(elements, renderer, fontId, xOffset, yOffset,
[](const PageElement& element) { return element.getTag() == TAG_PageImage; });
}
bool Page::serialize(HalFile& file) const {
+1
View File
@@ -88,6 +88,7 @@ class Page {
}
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
void renderImages(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
bool serialize(HalFile& file) const;
static std::unique_ptr<Page> deserialize(HalFile& file);
+10 -3
View File
@@ -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;
}
@@ -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. <aside id="fn1">text</aside><aside id="fn2">) with no intervening block
// never trigger startNewTextBlock, so fn1 gets silently overwritten. That leaves
// fn1 missing from the anchor map -> getPageForAnchor returns nullopt -> reader
// lands at page 0 (section start) instead of the footnote.
if (!self->pendingAnchorId.empty()) {
self->flushPendingAnchor();
}
self->pendingAnchorId = idValue;
}
} else if (strcmp(atts[i], "dir") == 0) {