Cherry-pick merge from pablohc/crosspoint-reader@2d8cbcf, based on upstream PR #556 by martinbrook with pablohc's refresh optimization. - Add JPEG decoder (picojpeg) and PNG decoder (PNGdec) with 4-level grayscale Bayer dithering for e-ink display - Add pixel caching system (.pxc files) for fast image re-rendering - Integrate image extraction from EPUB HTML parser (<img> tag support) - Add ImageBlock/PageImage types with serialization support - Add image-aware refresh optimization (double FAST_REFRESH technique) - Add experimental displayWindow() partial refresh support - Bump section cache version 12->13 to invalidate stale caches - Resolve TAG_PageImage=3 to avoid conflict with mod's TAG_PageTableRow=2 Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
4.1 KiB
C++
108 lines
4.1 KiB
C++
#pragma once
|
|
#include <HalStorage.h>
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "blocks/ImageBlock.h"
|
|
#include "blocks/TextBlock.h"
|
|
|
|
enum PageElementTag : uint8_t {
|
|
TAG_PageLine = 1,
|
|
TAG_PageTableRow = 2,
|
|
TAG_PageImage = 3,
|
|
};
|
|
|
|
// 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 PageElementTag getTag() const = 0;
|
|
virtual void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) = 0;
|
|
virtual bool serialize(FsFile& file) = 0;
|
|
};
|
|
|
|
// 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; }
|
|
PageElementTag getTag() const override { return TAG_PageLine; }
|
|
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
|
bool serialize(FsFile& file) override;
|
|
static std::unique_ptr<PageLine> deserialize(FsFile& file);
|
|
};
|
|
|
|
/// Data for a single cell within a PageTableRow.
|
|
struct PageTableCellData {
|
|
std::vector<std::shared_ptr<TextBlock>> lines; // Laid-out text lines for this cell
|
|
uint16_t columnWidth = 0; // Width of this column in pixels
|
|
uint16_t xOffset = 0; // X offset of this cell within the row
|
|
};
|
|
|
|
/// A table row element that renders cells in a column-aligned grid with borders.
|
|
class PageTableRow final : public PageElement {
|
|
std::vector<PageTableCellData> cells;
|
|
int16_t rowHeight; // Total row height in pixels
|
|
int16_t totalWidth; // Total table width in pixels
|
|
int16_t lineHeight; // Height of one text line (for vertical positioning of cell lines)
|
|
|
|
public:
|
|
PageTableRow(std::vector<PageTableCellData> cells, int16_t rowHeight, int16_t totalWidth, int16_t lineHeight,
|
|
int16_t xPos, int16_t yPos)
|
|
: PageElement(xPos, yPos),
|
|
cells(std::move(cells)),
|
|
rowHeight(rowHeight),
|
|
totalWidth(totalWidth),
|
|
lineHeight(lineHeight) {}
|
|
|
|
int16_t getHeight() const { return rowHeight; }
|
|
PageElementTag getTag() const override { return TAG_PageTableRow; }
|
|
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
|
bool serialize(FsFile& file) override;
|
|
static std::unique_ptr<PageTableRow> deserialize(FsFile& file);
|
|
};
|
|
|
|
// An image element on a page
|
|
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);
|
|
|
|
// Helper to get image block dimensions (needed for bounding box calculation)
|
|
ImageBlock* getImageBlock() const { return imageBlock.get(); }
|
|
};
|
|
|
|
class Page {
|
|
public:
|
|
// the list of block index and line numbers on this page
|
|
std::vector<std::shared_ptr<PageElement>> elements;
|
|
void render(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 the bounding box of all images on this page.
|
|
// Returns true if page has images and fills out the bounding box coordinates.
|
|
// If no images, returns false.
|
|
bool getImageBoundingBox(int& outX, int& outY, int& outWidth, int& outHeight) const;
|
|
};
|