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
|
// 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 MIN_SIZE_FOR_POPUP = 10 * 1024; // 10KB
|
||||||
constexpr size_t PARSE_BUFFER_SIZE = 1024;
|
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
|
// 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
|
// 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
|
// flush the contents of partWordBuffer to currentTextBlock
|
||||||
void ChapterHtmlSlimParser::flushPartWordBuffer() {
|
void ChapterHtmlSlimParser::flushPartWordBuffer() {
|
||||||
|
flushLongTextBlockIfNeeded();
|
||||||
|
|
||||||
// Determine font style from depth-based tracking and CSS effective style
|
// Determine font style from depth-based tracking and CSS effective style
|
||||||
const bool isBold = boldUntilDepth < depth || effectiveBold;
|
const bool isBold = boldUntilDepth < depth || effectiveBold;
|
||||||
const bool isItalic = italicUntilDepth < depth || effectiveItalic;
|
const bool isItalic = italicUntilDepth < depth || effectiveItalic;
|
||||||
@@ -228,6 +231,20 @@ void ChapterHtmlSlimParser::flushPartWordBuffer() {
|
|||||||
listItemBulletOnly = false;
|
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
|
// start a new text block if needed
|
||||||
void ChapterHtmlSlimParser::startNewTextBlock(const BlockStyle& blockStyle) {
|
void ChapterHtmlSlimParser::startNewTextBlock(const BlockStyle& blockStyle) {
|
||||||
nextWordContinues = false; // New block = new paragraph, no continuation
|
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];
|
self->partWordBuffer[self->partWordBufferIndex++] = s[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have > 750 words buffered up, perform the layout and consume out all but the last line
|
// If we have a large number of 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.
|
|
||||||
// Spotted when reading Intermezzo, there are some really long text blocks in there.
|
// Spotted when reading Intermezzo, there are some really long text blocks in there.
|
||||||
if (self->currentTextBlock->size() > 750) {
|
self->flushLongTextBlockIfNeeded();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLCALL ChapterHtmlSlimParser::defaultHandlerExpand(void* userData, const XML_Char* s, const int len) {
|
void XMLCALL ChapterHtmlSlimParser::defaultHandlerExpand(void* userData, const XML_Char* s, const int len) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <expat.h>
|
#include <expat.h>
|
||||||
|
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
#include <deque>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -84,7 +85,7 @@ class ChapterHtmlSlimParser {
|
|||||||
|
|
||||||
// Anchor-to-page mapping: tracks which page each HTML id attribute lands on
|
// Anchor-to-page mapping: tracks which page each HTML id attribute lands on
|
||||||
int completedPageCount = 0;
|
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::string pendingAnchorId; // deferred until after previous text block is flushed
|
||||||
std::vector<std::string> tocAnchors; // the list of anchors that are TOC chapter boundaries
|
std::vector<std::string> tocAnchors; // the list of anchors that are TOC chapter boundaries
|
||||||
uint16_t xpathParagraphIndex = 0;
|
uint16_t xpathParagraphIndex = 0;
|
||||||
@@ -112,6 +113,7 @@ class ChapterHtmlSlimParser {
|
|||||||
void startNewTextBlock(const BlockStyle& blockStyle);
|
void startNewTextBlock(const BlockStyle& blockStyle);
|
||||||
void flushPendingAnchor();
|
void flushPendingAnchor();
|
||||||
void flushPartWordBuffer();
|
void flushPartWordBuffer();
|
||||||
|
void flushLongTextBlockIfNeeded();
|
||||||
void makePages();
|
void makePages();
|
||||||
static EpdFontFamily::Style fontStyleForTextDecoration(CssTextDecoration decoration);
|
static EpdFontFamily::Style fontStyleForTextDecoration(CssTextDecoration decoration);
|
||||||
static void applyDirectionToEntry(StyleStackEntry& entry, const CssStyle& css);
|
static void applyDirectionToEntry(StyleStackEntry& entry, const CssStyle& css);
|
||||||
@@ -173,7 +175,7 @@ class ChapterHtmlSlimParser {
|
|||||||
void abortParse(); // tear down without flushing (error / abandon)
|
void abortParse(); // tear down without flushing (error / abandon)
|
||||||
|
|
||||||
void addLineToPage(std::shared_ptr<TextBlock> line);
|
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
|
// 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
|
// count (a giant single-spine book never fully lays out, so its real count is unknown). Valid
|
||||||
|
|||||||
Reference in New Issue
Block a user