Files
Crosspoint/lib/Epub/Epub/Page.h
T

170 lines
6.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <HalStorage.h>
#include <algorithm>
#include <array>
#include <string>
#include <utility>
#include <vector>
#include "FootnoteEntry.h"
#include "blocks/ImageBlock.h"
#include "blocks/TextBlock.h"
static constexpr uint8_t MAX_TABLE_COLS = 8;
static constexpr uint16_t MAX_TABLE_ROWS = 48;
static constexpr uint8_t TABLE_CELL_PADDING = 5;
static constexpr uint16_t MIN_COL_INNER_WIDTH = 24;
static constexpr uint8_t MAX_CELL_LINES = 64;
enum PageElementTag : uint8_t {
TAG_PageLine = 1,
TAG_PageImage = 2,
TAG_PageTable = 3,
TAG_PageHR = 4,
};
// represents something that has been added to a page
class PageElement {
public:
int16_t xPos;
int16_t yPos;
explicit PageElement(const int16_t xPos, const int16_t yPos) : xPos(xPos), yPos(yPos) {}
virtual ~PageElement() = default;
virtual void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) = 0;
virtual bool serialize(FsFile& file) = 0;
virtual PageElementTag getTag() const = 0; // Add type identification
};
// a line from a block element
class PageLine final : public PageElement {
std::shared_ptr<TextBlock> block;
public:
PageLine(std::shared_ptr<TextBlock> block, const int16_t xPos, const int16_t yPos)
: PageElement(xPos, yPos), block(std::move(block)) {}
const std::shared_ptr<TextBlock>& getBlock() const { return block; }
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
bool serialize(FsFile& file) override;
PageElementTag getTag() const override { return TAG_PageLine; }
static std::unique_ptr<PageLine> deserialize(FsFile& file);
};
// New PageImage class
class PageImage final : public PageElement {
std::shared_ptr<ImageBlock> imageBlock;
public:
PageImage(std::shared_ptr<ImageBlock> block, const int16_t xPos, const int16_t yPos)
: PageElement(xPos, yPos), imageBlock(std::move(block)) {}
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
bool serialize(FsFile& file) override;
PageElementTag getTag() const override { return TAG_PageImage; }
static std::unique_ptr<PageImage> deserialize(FsFile& file);
const ImageBlock& getImageBlock() const { return *imageBlock; }
};
class PageHR final : public PageElement {
int16_t width;
public:
PageHR(const int16_t xPos, const int16_t yPos, const int16_t width) : PageElement(xPos, yPos), width(width) {}
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
bool serialize(FsFile& file) override;
PageElementTag getTag() const override { return TAG_PageHR; }
static std::unique_ptr<PageHR> deserialize(FsFile& file);
};
struct TableCell {
std::vector<std::shared_ptr<TextBlock>> lines;
bool isHeader = false;
};
struct TableRow {
std::vector<TableCell> cells;
uint16_t height = 0; // pixel height including 2×CELL_PADDING
bool isHeaderRow = false; // drives 2px separator below this row
};
class PageTableFragment final : public PageElement {
uint8_t columnCount = 0;
uint16_t totalWidth = 0;
uint16_t totalHeight = 0;
std::array<uint16_t, MAX_TABLE_COLS> colWidths = {};
std::vector<TableRow> rows;
public:
PageTableFragment(uint8_t colCount, uint16_t totalWidth, uint16_t totalHeight,
std::array<uint16_t, MAX_TABLE_COLS> colWidths, std::vector<TableRow> rows, int16_t xPos,
int16_t yPos)
: PageElement(xPos, yPos),
columnCount(colCount),
totalWidth(totalWidth),
totalHeight(totalHeight),
colWidths(colWidths),
rows(std::move(rows)) {}
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
bool serialize(FsFile& file) override;
static std::unique_ptr<PageTableFragment> deserialize(FsFile& file);
PageElementTag getTag() const override { return TAG_PageTable; }
uint16_t getTotalHeight() const { return totalHeight; }
};
class Page {
public:
// the list of block index and line numbers on this page
std::vector<std::shared_ptr<PageElement>> elements;
std::vector<FootnoteEntry> footnotes;
static constexpr uint16_t MAX_FOOTNOTES_PER_PAGE = 16;
void addFootnote(const char* number, const char* href) {
if (footnotes.size() >= MAX_FOOTNOTES_PER_PAGE) return; // Cap per-page footnotes
FootnoteEntry entry;
strncpy(entry.number, number, sizeof(entry.number) - 1);
entry.number[sizeof(entry.number) - 1] = '\0';
strncpy(entry.href, href, sizeof(entry.href) - 1);
entry.href[sizeof(entry.href) - 1] = '\0';
footnotes.push_back(entry);
}
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
void renderTextOnly(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
bool serialize(FsFile& file) const;
static std::unique_ptr<Page> deserialize(FsFile& file);
// Check if page contains any images (used to force full refresh)
bool hasImages() const {
return std::any_of(elements.begin(), elements.end(),
[](const std::shared_ptr<PageElement>& el) { return el->getTag() == TAG_PageImage; });
}
// Get bounding box of all images on the page (union of image rects)
// Returns false if no images. Coordinates are relative to page origin.
bool getImageBoundingBox(int16_t& outX, int16_t& outY, int16_t& outW, int16_t& outH) const {
bool found = false;
int16_t minX = INT16_MAX, minY = INT16_MAX, maxX = INT16_MIN, maxY = INT16_MIN;
for (const auto& el : elements) {
if (el->getTag() == TAG_PageImage) {
const auto& img = static_cast<const PageImage&>(*el);
int16_t x = img.xPos;
int16_t y = img.yPos;
int16_t right = x + img.getImageBlock().getWidth();
int16_t bottom = y + img.getImageBlock().getHeight();
minX = std::min(minX, x);
minY = std::min(minY, y);
maxX = std::max(maxX, right);
maxY = std::max(maxY, bottom);
found = true;
}
}
if (found) {
outX = minX;
outY = minY;
outW = maxX - minX;
outH = maxY - minY;
}
return found;
}
};