## Summary _Revision to @blindbat's #802. Description comes from the original PR._ - Replace `std::list` with `std::vector` for word storage in `TextBlock` and `ParsedText` - Use index-based access (`words[i]`) instead of iterator advancement (`std::advance(it, n)`) - Remove the separate `continuesVec` copy that was built from `wordContinues` for O(1) access — now unnecessary since `std::vector<bool>` already provides O(1) indexing ## Why `std::list` allocates each node individually on the heap with 16 bytes of prev/next pointer overhead per node. For text layout with many small words, this means: - Scattered heap allocations instead of contiguous memory - Poor cache locality during iteration (each node can be anywhere in memory) - Per-node malloc/free overhead during construction and destruction `std::vector` stores elements contiguously, giving better cache performance during the tight rendering and layout loops. The `extractLine` function also benefits: list splice was O(1) but required maintaining three parallel iterators, while vector range construction with move iterators is simpler and still efficient for the small line-sized chunks involved. ## Files changed - `lib/Epub/Epub/blocks/TextBlock.h` / `.cpp` - `lib/Epub/Epub/ParsedText.h` / `.cpp` ## AI Usage YES ## Test plan - [ ] Open an EPUB with mixed formatting (bold, italic, underline) — verify text renders correctly - [ ] Open a book with justified text — verify word spacing is correct - [ ] Open a book with hyphenation enabled — verify words break correctly at hyphens - [ ] Navigate through pages rapidly — verify no rendering glitches or crashes - [ ] Open a book with long paragraphs — verify text layout matches pre-change behavior --------- Co-authored-by: Kuanysh Bekkulov <kbekkulov@gmail.com>
115 lines
4.7 KiB
C++
115 lines
4.7 KiB
C++
#include "TextBlock.h"
|
|
|
|
#include <GfxRenderer.h>
|
|
#include <Logging.h>
|
|
#include <Serialization.h>
|
|
|
|
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
|
// Validate iterator bounds before rendering
|
|
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
|
|
LOG_ERR("TXB", "Render skipped: size mismatch (words=%u, xpos=%u, styles=%u)\n", (uint32_t)words.size(),
|
|
(uint32_t)wordXpos.size(), (uint32_t)wordStyles.size());
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < words.size(); i++) {
|
|
const int wordX = wordXpos[i] + x;
|
|
const EpdFontFamily::Style currentStyle = wordStyles[i];
|
|
renderer.drawText(fontId, wordX, y, words[i].c_str(), true, currentStyle);
|
|
|
|
if ((currentStyle & EpdFontFamily::UNDERLINE) != 0) {
|
|
const std::string& w = words[i];
|
|
const int fullWordWidth = renderer.getTextWidth(fontId, w.c_str(), currentStyle);
|
|
// y is the top of the text line; add ascender to reach baseline, then offset 2px below
|
|
const int underlineY = y + renderer.getFontAscenderSize(fontId) + 2;
|
|
|
|
int startX = wordX;
|
|
int underlineWidth = fullWordWidth;
|
|
|
|
// if word starts with em-space ("\xe2\x80\x83"), account for the additional indent before drawing the line
|
|
if (w.size() >= 3 && static_cast<uint8_t>(w[0]) == 0xE2 && static_cast<uint8_t>(w[1]) == 0x80 &&
|
|
static_cast<uint8_t>(w[2]) == 0x83) {
|
|
const char* visiblePtr = w.c_str() + 3;
|
|
const int prefixWidth = renderer.getTextAdvanceX(fontId, "\xe2\x80\x83", currentStyle);
|
|
const int visibleWidth = renderer.getTextWidth(fontId, visiblePtr, currentStyle);
|
|
startX = wordX + prefixWidth;
|
|
underlineWidth = visibleWidth;
|
|
}
|
|
|
|
renderer.drawLine(startX, underlineY, startX + underlineWidth, underlineY, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TextBlock::serialize(FsFile& file) const {
|
|
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
|
|
LOG_ERR("TXB", "Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", words.size(),
|
|
wordXpos.size(), wordStyles.size());
|
|
return false;
|
|
}
|
|
|
|
// Word data
|
|
serialization::writePod(file, static_cast<uint16_t>(words.size()));
|
|
for (const auto& w : words) serialization::writeString(file, w);
|
|
for (auto x : wordXpos) serialization::writePod(file, x);
|
|
for (auto s : wordStyles) serialization::writePod(file, s);
|
|
|
|
// Style (alignment + margins/padding/indent)
|
|
serialization::writePod(file, blockStyle.alignment);
|
|
serialization::writePod(file, blockStyle.textAlignDefined);
|
|
serialization::writePod(file, blockStyle.marginTop);
|
|
serialization::writePod(file, blockStyle.marginBottom);
|
|
serialization::writePod(file, blockStyle.marginLeft);
|
|
serialization::writePod(file, blockStyle.marginRight);
|
|
serialization::writePod(file, blockStyle.paddingTop);
|
|
serialization::writePod(file, blockStyle.paddingBottom);
|
|
serialization::writePod(file, blockStyle.paddingLeft);
|
|
serialization::writePod(file, blockStyle.paddingRight);
|
|
serialization::writePod(file, blockStyle.textIndent);
|
|
serialization::writePod(file, blockStyle.textIndentDefined);
|
|
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<TextBlock> TextBlock::deserialize(FsFile& file) {
|
|
uint16_t wc;
|
|
std::vector<std::string> words;
|
|
std::vector<uint16_t> wordXpos;
|
|
std::vector<EpdFontFamily::Style> wordStyles;
|
|
BlockStyle blockStyle;
|
|
|
|
// Word count
|
|
serialization::readPod(file, wc);
|
|
|
|
// Sanity check: prevent allocation of unreasonably large vectors (max 10000 words per block)
|
|
if (wc > 10000) {
|
|
LOG_ERR("TXB", "Deserialization failed: word count %u exceeds maximum", wc);
|
|
return nullptr;
|
|
}
|
|
|
|
// Word data
|
|
words.resize(wc);
|
|
wordXpos.resize(wc);
|
|
wordStyles.resize(wc);
|
|
for (auto& w : words) serialization::readString(file, w);
|
|
for (auto& x : wordXpos) serialization::readPod(file, x);
|
|
for (auto& s : wordStyles) serialization::readPod(file, s);
|
|
|
|
// Style (alignment + margins/padding/indent)
|
|
serialization::readPod(file, blockStyle.alignment);
|
|
serialization::readPod(file, blockStyle.textAlignDefined);
|
|
serialization::readPod(file, blockStyle.marginTop);
|
|
serialization::readPod(file, blockStyle.marginBottom);
|
|
serialization::readPod(file, blockStyle.marginLeft);
|
|
serialization::readPod(file, blockStyle.marginRight);
|
|
serialization::readPod(file, blockStyle.paddingTop);
|
|
serialization::readPod(file, blockStyle.paddingBottom);
|
|
serialization::readPod(file, blockStyle.paddingLeft);
|
|
serialization::readPod(file, blockStyle.paddingRight);
|
|
serialization::readPod(file, blockStyle.textIndent);
|
|
serialization::readPod(file, blockStyle.textIndentDefined);
|
|
|
|
return std::unique_ptr<TextBlock>(
|
|
new TextBlock(std::move(words), std::move(wordXpos), std::move(wordStyles), blockStyle));
|
|
}
|