fix: flush long EPUB text blocks before large vector growth and reduce EPUB parser contiguous heap growth
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
// Minimum file size (in bytes) to show indexing popup - smaller chapters don't benefit from it
|
||||
constexpr size_t MIN_SIZE_FOR_POPUP = 10 * 1024; // 10KB
|
||||
constexpr size_t PARSE_BUFFER_SIZE = 1024;
|
||||
constexpr size_t MAX_BUFFERED_TEXT_WORDS = 300;
|
||||
|
||||
// Hard cap on the number of anchor IDs recorded per chapter. Legitimate navigation
|
||||
// anchors (TOC entries, footnotes, cross-references) rarely exceed a few hundred per
|
||||
@@ -201,6 +202,8 @@ void ChapterHtmlSlimParser::flushPendingAnchor() {
|
||||
|
||||
// flush the contents of partWordBuffer to currentTextBlock
|
||||
void ChapterHtmlSlimParser::flushPartWordBuffer() {
|
||||
flushLongTextBlockIfNeeded();
|
||||
|
||||
// Determine font style from depth-based tracking and CSS effective style
|
||||
const bool isBold = boldUntilDepth < depth || effectiveBold;
|
||||
const bool isItalic = italicUntilDepth < depth || effectiveItalic;
|
||||
@@ -228,6 +231,20 @@ void ChapterHtmlSlimParser::flushPartWordBuffer() {
|
||||
listItemBulletOnly = false;
|
||||
}
|
||||
|
||||
void ChapterHtmlSlimParser::flushLongTextBlockIfNeeded() {
|
||||
if (!currentTextBlock || currentTextBlock->size() <= MAX_BUFFERED_TEXT_WORDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DBG("EHP", "Text block too long, splitting into multiple pages");
|
||||
const int horizontalInset = currentTextBlock->getBlockStyle().totalHorizontalInset();
|
||||
const uint16_t effectiveWidth =
|
||||
(horizontalInset < viewportWidth) ? static_cast<uint16_t>(viewportWidth - horizontalInset) : viewportWidth;
|
||||
currentTextBlock->layoutAndExtractLines(
|
||||
renderer, fontId, effectiveWidth,
|
||||
[this](const std::shared_ptr<TextBlock>& textBlock) { this->addLineToPage(textBlock); }, false);
|
||||
}
|
||||
|
||||
// start a new text block if needed
|
||||
void ChapterHtmlSlimParser::startNewTextBlock(const BlockStyle& blockStyle) {
|
||||
nextWordContinues = false; // New block = new paragraph, no continuation
|
||||
@@ -1155,20 +1172,9 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
|
||||
self->partWordBuffer[self->partWordBufferIndex++] = s[i];
|
||||
}
|
||||
|
||||
// If we have > 750 words buffered up, perform the layout and consume out all but the last line
|
||||
// There should be enough here to build out 1-2 full pages and doing this will free up a lot of
|
||||
// memory.
|
||||
// If we have a large number of words buffered up, perform the layout and consume out all but the last line.
|
||||
// Spotted when reading Intermezzo, there are some really long text blocks in there.
|
||||
if (self->currentTextBlock->size() > 750) {
|
||||
LOG_DBG("EHP", "Text block too long, splitting into multiple pages");
|
||||
const int horizontalInset = self->currentTextBlock->getBlockStyle().totalHorizontalInset();
|
||||
const uint16_t effectiveWidth = (horizontalInset < self->viewportWidth)
|
||||
? static_cast<uint16_t>(self->viewportWidth - horizontalInset)
|
||||
: self->viewportWidth;
|
||||
self->currentTextBlock->layoutAndExtractLines(
|
||||
self->renderer, self->fontId, effectiveWidth,
|
||||
[self](const std::shared_ptr<TextBlock>& textBlock) { self->addLineToPage(textBlock); }, false);
|
||||
}
|
||||
self->flushLongTextBlockIfNeeded();
|
||||
}
|
||||
|
||||
void XMLCALL ChapterHtmlSlimParser::defaultHandlerExpand(void* userData, const XML_Char* s, const int len) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <expat.h>
|
||||
|
||||
#include <climits>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -84,7 +85,7 @@ class ChapterHtmlSlimParser {
|
||||
|
||||
// Anchor-to-page mapping: tracks which page each HTML id attribute lands on
|
||||
int completedPageCount = 0;
|
||||
std::vector<std::pair<std::string, uint16_t>> anchorData;
|
||||
std::deque<std::pair<std::string, uint16_t>> anchorData;
|
||||
std::string pendingAnchorId; // deferred until after previous text block is flushed
|
||||
std::vector<std::string> tocAnchors; // the list of anchors that are TOC chapter boundaries
|
||||
uint16_t xpathParagraphIndex = 0;
|
||||
@@ -112,6 +113,7 @@ class ChapterHtmlSlimParser {
|
||||
void startNewTextBlock(const BlockStyle& blockStyle);
|
||||
void flushPendingAnchor();
|
||||
void flushPartWordBuffer();
|
||||
void flushLongTextBlockIfNeeded();
|
||||
void makePages();
|
||||
static EpdFontFamily::Style fontStyleForTextDecoration(CssTextDecoration decoration);
|
||||
static void applyDirectionToEntry(StyleStackEntry& entry, const CssStyle& css);
|
||||
@@ -173,7 +175,7 @@ class ChapterHtmlSlimParser {
|
||||
void abortParse(); // tear down without flushing (error / abandon)
|
||||
|
||||
void addLineToPage(std::shared_ptr<TextBlock> line);
|
||||
const std::vector<std::pair<std::string, uint16_t>>& getAnchors() const { return anchorData; }
|
||||
const std::deque<std::pair<std::string, uint16_t>>& getAnchors() const { return anchorData; }
|
||||
|
||||
// Byte progress of the in-flight parse, used to estimate a still-building section's total page
|
||||
// count (a giant single-spine book never fully lays out, so its real count is unknown). Valid
|
||||
|
||||
Reference in New Issue
Block a user