Files
Crosspoint/lib/Epub/Epub/blocks/TextBlock.h
T

94 lines
4.6 KiB
C++

#pragma once
#include <EpdFontFamily.h>
#include <HalStorage.h>
#include <memory>
#include <string>
#include <vector>
#include "Block.h"
#include "BlockStyle.h"
// Represents a line of text on a page.
//
// All per-word data lives in ONE flat heap allocation (the arena) instead of
// six parallel vectors: a resident page holds ~25-30 of these blocks, and the
// vector-of-string layout cost ~250 throwing allocations per page load, which
// was the primary driver of heap fragmentation on the ESP32-C3.
//
// Arena layout, in order (2-byte alignment holds by construction: all 16-bit
// arrays come first and the arena base is allocator-aligned; RISC-V faults on
// unaligned multi-byte access):
// uint16_t textOff[wordCount] byte offset of word i's text in text[]
// int16_t xpos[wordCount]
// uint16_t focusSuffixX[wordCount] present only when focusPresent
// uint8_t styles[wordCount]
// uint8_t focusBoundary[wordCount] present only when focusPresent
// char text[textBytes] all words back to back, NUL-terminated
//
// Each word is stored NUL-terminated so render() can hand `text + textOff[i]`
// straight to C APIs (drawText) with no std::string materialization.
//
// Focus split semantics (unchanged from the vector layout): boundary N > 0
// means the first N bytes of word i render bold, the remainder in the base
// style. N is bounded to 9 codepoints (<= 36 UTF-8 bytes) by the clamp in
// ParsedText::addWord. focusSuffixX is the pre-computed pixel offset from the
// word start to the regular suffix. Both arrays are omitted from the arena
// entirely when no word on the line has a split (zero per-word RAM cost when
// focus reading is disabled).
class TextBlock final : public Block {
private:
BlockStyle blockStyle;
uint16_t numWords = 0;
uint16_t textBytes = 0; // total size of the text region, including NULs
bool focusPresent = false;
bool isValid = true;
// The ONLY allocation: makeUniqueNoThrow, so OOM yields an invalid block
// instead of abort() (bare new is not nothrow with -fno-exceptions).
std::unique_ptr<uint8_t[]> arena;
// Typed views into the arena, bound once after the arena is filled. All
// 16-bit bases sit at even offsets, so direct dereference is alignment-safe.
const uint16_t* textOffArr = nullptr;
const int16_t* xposArr = nullptr;
const uint16_t* focusSuffixXArr = nullptr; // null when !focusPresent
const uint8_t* stylesArr = nullptr;
const uint8_t* focusBoundaryArr = nullptr; // null when !focusPresent
const char* textArr = nullptr;
TextBlock() = default; // deserialize() fills the fields directly
static size_t arenaSize(uint16_t wordCount, bool hasFocus, uint16_t textBytes);
void bindArenaPointers();
public:
// Flatten-on-construct: copies the layout-time vectors into the arena; the
// vectors die with the caller. On arena OOM the block is empty and valid()
// is false -- callers must check and fail the line instead of using it.
explicit TextBlock(const std::vector<std::string>& words, const std::vector<int16_t>& wordXpos,
const std::vector<EpdFontFamily::Style>& wordStyles, const std::vector<uint8_t>& focusBoundary,
const std::vector<uint16_t>& focusSuffixX, const BlockStyle& blockStyle = BlockStyle());
~TextBlock() override = default;
TextBlock(const TextBlock&) = delete;
TextBlock& operator=(const TextBlock&) = delete;
void setBlockStyle(const BlockStyle& blockStyle) { this->blockStyle = blockStyle; }
const BlockStyle& getBlockStyle() const { return blockStyle; }
bool isEmpty() override { return numWords == 0; }
bool valid() const { return isValid; }
uint16_t wordCount() const { return numWords; }
// NUL-terminated by construction; safe to pass to C APIs directly.
const char* wordText(const uint16_t i) const { return textArr + textOffArr[i]; }
uint16_t wordTextLen(const uint16_t i) const {
const uint16_t end = (i + 1 < numWords) ? textOffArr[i + 1] : textBytes;
return end - textOffArr[i] - 1; // exclude the NUL
}
int16_t wordXpos(const uint16_t i) const { return xposArr[i]; }
EpdFontFamily::Style wordStyle(const uint16_t i) const { return static_cast<EpdFontFamily::Style>(stylesArr[i]); }
uint8_t focusBoundary(const uint16_t i) const { return focusPresent ? focusBoundaryArr[i] : 0; }
uint16_t focusSuffixX(const uint16_t i) const { return focusPresent ? focusSuffixXArr[i] : 0; }
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
BlockType getType() override { return TEXT_BLOCK; }
bool serialize(HalFile& file) const;
static std::unique_ptr<TextBlock> deserialize(HalFile& file);
};