Compare commits

..

28 Commits

Author SHA1 Message Date
Dave Allie de453fed1d Stream inflated EPUB HTMLs down to disk instead of inflating in memory (#4)
* Downgrade miniz for stability

* Stream HTML from ZIP down to disk instead of loading all in mem
2025-12-08 00:39:17 +11:00
Dave Allie c715c18bf7 Sort directories first in file picker 2025-12-07 19:17:43 +11:00
Dave Allie 9fa697ae0c Fix 1-bit font pixel alignment of last byte 2025-12-07 12:25:10 +11:00
Dave Allie 79294f6b8f Build and use 1-bit font, saves a good amount of space 2025-12-07 01:26:49 +11:00
Dave Allie eceffaa289 Reduce number of full screen refreshes to once every 20 pages 2025-12-06 22:12:01 +11:00
Dave Allie bb151caee7 Version section bin files 2025-12-06 22:09:25 +11:00
Dave Allie dd6e649d74 Add expat and swap out EPUB HTML parser (#2)
* Add expat and swap out ERB HTML parser

* Increase EpubHtmlParserSlim file buffer to 1024 bytes

* Cleanup TextBlock functions

* Do not break words when leaving spans
2025-12-06 20:57:24 +11:00
Dave Allie ad8cee12ab Small cleanup 2025-12-06 20:24:24 +11:00
Dave Allie 4ecfdea1a1 More pass by reference changes 2025-12-06 15:56:00 +11:00
Dave Allie 5ed2fe391d Add image to README 2025-12-06 13:32:09 +11:00
Dave Allie 021f77eab3 Sort items on FileSelectionScreen 2025-12-06 13:01:16 +11:00
Dave Allie 6d3d25a288 Fix bug with selectin epubs inside of folders 2025-12-06 12:57:17 +11:00
Dave Allie 9a33030623 Use reference passing for EpdRenderer 2025-12-06 12:56:39 +11:00
Dave Allie 6414f85257 Use InputManager from community-sdk 2025-12-06 12:35:41 +11:00
Dave Allie f0d92da8f2 Update README.md with checkout instructions
Fixes https://github.com/daveallie/crosspoint-reader/issues/1
2025-12-06 04:53:58 +11:00
Dave Allie 8679c8f57c Update sleep screen 2025-12-06 04:20:41 +11:00
Dave Allie 899caab70c Avoid leaving screens mid-display update
Was leave the EPD in a bad state, blocking further actions
2025-12-06 03:02:52 +11:00
Dave Allie 98c8e7e77c Fix memory leak with Epub object getting orphaned 2025-12-06 02:49:10 +11:00
Dave Allie 7198d943b0 Add UI font 2025-12-06 01:44:14 +11:00
Dave Allie 248af4b8fb Add new boot logo screen 2025-12-06 01:44:14 +11:00
Dave Allie 05a027e2bf Wrap up multiple font styles into EpdFontFamily 2025-12-06 01:44:14 +11:00
Dave Allie fa0f27df6a Full screen refresh of EpubReaderScreen every 10 renders
This is done in an effort to minimise ghosting
2025-12-05 22:19:44 +11:00
Dave Allie 2631613b8d Add directory picking to home screen 2025-12-05 22:12:28 +11:00
Dave Allie 72aa7ba3f6 Upgrade open-x4-sdk 2025-12-05 21:14:08 +11:00
Dave Allie e08bac2e10 Show indexing text when indexing 2025-12-05 21:12:15 +11:00
Dave Allie 12d28e2148 Avoid ghosting on sleep screen by doing a full screen update 2025-12-05 17:55:17 +11:00
Dave Allie 85502b417e Speedup boot by not waiting for Serial 2025-12-05 17:47:23 +11:00
Dave Allie ddec7f78dd Fix hold to wake logic
esp_sleep_get_wakeup_cause does not seem to be set when not connected to USB power
2025-12-04 00:57:32 +11:00
79 changed files with 28649 additions and 22865 deletions
+16 -5
View File
@@ -6,10 +6,7 @@ Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller.
CrossPoint Reader is a purpose-built firmware designed to be a drop-in, fully open-source replacement for the official
Xteink firmware. It aims to match or improve upon the standard EPUB reading experience.
// TODO include some images
I look at the [**diy-esp32-epub-reader** by atomic14](https://github.com/atomic14/diy-esp32-epub-reader) project a lot
when making CrossPoint and a handful of lessons and some direct source code comes directly from that repo.
![](./docs/cover.jpg)
## Motivation
@@ -32,7 +29,7 @@ This project is **not affiliated with Xteink**; it's built as a community projec
- [x] Saved reading position
- [ ] File explorer with file picker
- [x] Basic EPUB picker from root directory
- [ ] Support nested folders
- [x] Support nested folders
- [ ] EPUB picker with cover art
- [ ] Image support within EPUB
- [ ] Configurable font, layout, and display options
@@ -48,6 +45,17 @@ This project is **not affiliated with Xteink**; it's built as a community projec
* USB-C cable for flashing the ESP32-C3
* Xteink X4
### Checking out the code
CrossPoint uses PlatformIO for building and flashing the firmware. To get started, clone the repository:
```
git clone --recursive https://github.com/daveallie/crosspoint-reader
# Or, if you've already cloned without --recursive:
git submodule update --init --recursive
```
### Flashing your device
#### Command line
@@ -118,3 +126,6 @@ Contributions are very welcome!
---
CrossPoint Reader is **not affiliated with Xteink or any manufacturer of the X4 hardware**.
Huge shoutout to [**diy-esp32-epub-reader** by atomic14](https://github.com/atomic14/diy-esp32-epub-reader), which was a project I took a lot of inspiration from as I
was making CrossPoint.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

-3
View File
@@ -2,12 +2,9 @@
#include <Utf8.h>
EpdFont::EpdFont(const EpdFontData* data) { this->data = data; }
inline int min(const int a, const int b) { return a < b ? a : b; }
inline int max(const int a, const int b) { return a < b ? b : a; }
// TODO: Text properties??
void EpdFont::getTextBounds(const char* string, const int startX, const int startY, int* minX, int* minY, int* maxX,
int* maxY) const {
*minX = startX;
+1 -1
View File
@@ -6,7 +6,7 @@ class EpdFont {
public:
const EpdFontData* data;
explicit EpdFont(const EpdFontData* data);
explicit EpdFont(const EpdFontData* data) : data(data) {}
~EpdFont() = default;
void getTextDimensions(const char* string, int* w, int* h) const;
bool hasPrintableChars(const char* string) const;
+7 -8
View File
@@ -6,13 +6,13 @@
/// Font data stored PER GLYPH
typedef struct {
uint8_t width; ///< Bitmap dimensions in pixels
uint8_t height; ///< Bitmap dimensions in pixels
uint8_t advanceX; ///< Distance to advance cursor (x axis)
int16_t left; ///< X dist from cursor pos to UL corner
int16_t top; ///< Y dist from cursor pos to UL corner
uint16_t compressedSize; ///< Size of the zlib-compressed font data.
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap
uint8_t width; ///< Bitmap dimensions in pixels
uint8_t height; ///< Bitmap dimensions in pixels
uint8_t advanceX; ///< Distance to advance cursor (x axis)
int16_t left; ///< X dist from cursor pos to UL corner
int16_t top; ///< Y dist from cursor pos to UL corner
uint16_t dataLength; ///< Size of the font data.
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap
} EpdGlyph;
/// Glyph interval structure
@@ -28,7 +28,6 @@ typedef struct {
const EpdGlyph* glyph; ///< Glyph array
const EpdUnicodeInterval* intervals; ///< Valid unicode intervals for this font
uint32_t intervalCount; ///< Number of unicode intervals.
bool compressed; ///< Does this font use compressed glyph bitmaps?
uint8_t advanceY; ///< Newline distance (y axis)
int ascender; ///< Maximal height of a glyph above the base line
int descender; ///< Maximal height of a glyph below the base line
+37
View File
@@ -0,0 +1,37 @@
#include "EpdFontFamily.h"
const EpdFont* EpdFontFamily::getFont(const EpdFontStyle style) const {
if (style == BOLD && bold) {
return bold;
}
if (style == ITALIC && italic) {
return italic;
}
if (style == BOLD_ITALIC) {
if (boldItalic) {
return boldItalic;
}
if (bold) {
return bold;
}
if (italic) {
return italic;
}
}
return regular;
}
void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const EpdFontStyle style) const {
getFont(style)->getTextDimensions(string, w, h);
}
bool EpdFontFamily::hasPrintableChars(const char* string, const EpdFontStyle style) const {
return getFont(style)->hasPrintableChars(string);
}
const EpdFontData* EpdFontFamily::getData(const EpdFontStyle style) const { return getFont(style)->data; }
const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const EpdFontStyle style) const {
return getFont(style)->getGlyph(cp);
};
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "EpdFont.h"
enum EpdFontStyle { REGULAR, BOLD, ITALIC, BOLD_ITALIC };
class EpdFontFamily {
const EpdFont* regular;
const EpdFont* bold;
const EpdFont* italic;
const EpdFont* boldItalic;
const EpdFont* getFont(EpdFontStyle style) const;
public:
explicit EpdFontFamily(const EpdFont* regular, const EpdFont* bold = nullptr, const EpdFont* italic = nullptr,
const EpdFont* boldItalic = nullptr)
: regular(regular), bold(bold), italic(italic), boldItalic(boldItalic) {}
~EpdFontFamily() = default;
void getTextDimensions(const char* string, int* w, int* h, EpdFontStyle style = REGULAR) const;
bool hasPrintableChars(const char* string, EpdFontStyle style = REGULAR) const;
const EpdFontData* getData(EpdFontStyle style = REGULAR) const;
const EpdGlyph* getGlyph(uint32_t cp, EpdFontStyle style = REGULAR) const;
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+32 -22
View File
@@ -7,20 +7,18 @@ import math
import argparse
from collections import namedtuple
# From: https://github.com/vroland/epdiy
# Originally from https://github.com/vroland/epdiy
parser = argparse.ArgumentParser(description="Generate a header file from a font to be used with epdiy.")
parser.add_argument("name", action="store", help="name of the font.")
parser.add_argument("size", type=int, help="font size to use.")
parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority.")
parser.add_argument("--compress", dest="compress", action="store_true", help="compress glyph bitmaps.")
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
args = parser.parse_args()
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "compressed_size", "data_offset", "code_point"])
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "code_point"])
font_stack = [freetype.Face(f) for f in args.fontstack]
compress = args.compress
size = args.size
font_name = args.name
@@ -148,19 +146,18 @@ for i_start, i_end in unvalidated_intervals:
intervals.append((start, i_end))
for face in font_stack:
# shift by 6 bytes, because sizes are given as 6-bit fractions
# the display has about 150 dpi.
face.set_char_size(size << 6, size << 6, 150, 150)
total_size = 0
total_packed = 0
all_glyphs = []
for i_start, i_end in intervals:
for code_point in range(i_start, i_end + 1):
face = load_glyph(code_point)
bitmap = face.glyph.bitmap
pixels = []
# Build out 4-bit greyscale bitmap
pixels4g = []
px = 0
for i, v in enumerate(bitmap.buffer):
y = i / bitmap.width
@@ -169,31 +166,45 @@ for i_start, i_end in intervals:
px = (v >> 4)
else:
px = px | (v & 0xF0)
pixels.append(px);
pixels4g.append(px);
px = 0
# eol
if x == bitmap.width - 1 and bitmap.width % 2 > 0:
pixels.append(px)
pixels4g.append(px)
px = 0
packed = bytes(pixels);
total_packed += len(packed)
compressed = packed
if compress:
compressed = zlib.compress(packed)
# Downsample to 1-bit bitmap - treat any non-zero as black
pixelsbw = []
px = 0
pitch = (bitmap.width // 2) + (bitmap.width % 2)
for y in range(bitmap.rows):
for x in range(bitmap.width):
px = px << 1
bm = pixels4g[y * pitch + (x // 2)]
px += 1 if ((x & 1) == 0 and bm & 0xF > 0) or ((x & 1) == 1 and bm & 0xF0 > 0) else 0
if (y * bitmap.width + x) % 8 == 7:
pixelsbw.append(px)
px = 0
if (bitmap.width * bitmap.rows) % 8 != 0:
px = px << (8 - (bitmap.width * bitmap.rows) % 8)
pixelsbw.append(px)
# Build output data
packed = bytes(pixelsbw)
glyph = GlyphProps(
width = bitmap.width,
height = bitmap.rows,
advance_x = norm_floor(face.glyph.advance.x),
left = face.glyph.bitmap_left,
top = face.glyph.bitmap_top,
compressed_size = len(compressed),
data_length = len(packed),
data_offset = total_size,
code_point = code_point,
)
total_size += len(compressed)
all_glyphs.append((glyph, compressed))
total_size += len(packed)
all_glyphs.append((glyph, packed))
# pipe seems to be a good heuristic for the "real" descender
face = load_glyph(ord('|'))
@@ -201,11 +212,11 @@ face = load_glyph(ord('|'))
glyph_data = []
glyph_props = []
for index, glyph in enumerate(all_glyphs):
props, compressed = glyph
glyph_data.extend([b for b in compressed])
props, packed = glyph
glyph_data.extend([b for b in packed])
glyph_props.append(props)
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n * compressed: {compress}\n */")
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n */")
print("#pragma once")
print("#include \"EpdFontData.h\"\n")
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
@@ -230,7 +241,6 @@ print(f" {font_name}Bitmaps,")
print(f" {font_name}Glyphs,")
print(f" {font_name}Intervals,")
print(f" {len(intervals)},")
print(f" {1 if compress else 0},")
print(f" {norm_ceil(face.size.height)},")
print(f" {norm_ceil(face.size.ascender)},")
print(f" {norm_floor(face.size.descender)},")
+26 -74
View File
@@ -1,74 +1,52 @@
#pragma once
#include <EpdFont.h>
#include <EpdFontFamily.h>
#include <HardwareSerial.h>
#include <Utf8.h>
#include <miniz.h>
inline int min(const int a, const int b) { return a < b ? a : b; }
inline int max(const int a, const int b) { return a > b ? a : b; }
static tinfl_decompressor decomp;
template <typename Renderable>
class EpdFontRenderer {
Renderable* renderer;
void renderChar(uint32_t cp, int* x, const int* y, uint16_t color);
Renderable& renderer;
void renderChar(uint32_t cp, int* x, const int* y, uint16_t color, EpdFontStyle style = REGULAR);
public:
const EpdFont* font;
explicit EpdFontRenderer(const EpdFont* font, Renderable* renderer);
const EpdFontFamily* fontFamily;
explicit EpdFontRenderer(const EpdFontFamily* fontFamily, Renderable& renderer)
: fontFamily(fontFamily), renderer(renderer) {}
~EpdFontRenderer() = default;
void renderString(const char* string, int* x, int* y, uint16_t color);
void renderString(const char* string, int* x, int* y, uint16_t color, EpdFontStyle style = REGULAR);
};
inline int uncompress(uint8_t* dest, size_t uncompressedSize, const uint8_t* source, size_t sourceSize) {
if (uncompressedSize == 0 || dest == nullptr || sourceSize == 0 || source == nullptr) {
return -1;
}
tinfl_init(&decomp);
// we know everything will fit into the buffer.
const tinfl_status decomp_status =
tinfl_decompress(&decomp, source, &sourceSize, dest, dest, &uncompressedSize,
TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
if (decomp_status != TINFL_STATUS_DONE) {
return decomp_status;
}
return 0;
}
template <typename Renderable>
EpdFontRenderer<Renderable>::EpdFontRenderer(const EpdFont* font, Renderable* renderer) {
this->font = font;
this->renderer = renderer;
}
template <typename Renderable>
void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const uint16_t color) {
void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const uint16_t color,
const EpdFontStyle style) {
// cannot draw a NULL / empty string
if (string == nullptr || *string == '\0') {
return;
}
// no printable characters
if (!font->hasPrintableChars(string)) {
if (!fontFamily->hasPrintableChars(string, style)) {
return;
}
uint32_t cp;
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
renderChar(cp, x, y, color);
renderChar(cp, x, y, color, style);
}
*y += font->data->advanceY;
*y += fontFamily->getData(style)->advanceY;
}
template <typename Renderable>
void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, uint16_t color) {
const EpdGlyph* glyph = font->getGlyph(cp);
void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, uint16_t color,
const EpdFontStyle style) {
const EpdGlyph* glyph = fontFamily->getGlyph(cp, style);
if (!glyph) {
// TODO: Replace with fallback glyph property?
glyph = font->getGlyph('?');
glyph = fontFamily->getGlyph('?', style);
}
// no glyph?
@@ -82,50 +60,24 @@ void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const in
const uint8_t height = glyph->height;
const int left = glyph->left;
const int byteWidth = width / 2 + width % 2;
const unsigned long bitmapSize = byteWidth * height;
const uint8_t* bitmap = nullptr;
if (font->data->compressed) {
auto* tmpBitmap = static_cast<uint8_t*>(malloc(bitmapSize));
if (tmpBitmap == nullptr && bitmapSize) {
// ESP_LOGE("font", "malloc failed.");
return;
}
uncompress(tmpBitmap, bitmapSize, &font->data->bitmap[offset], glyph->compressedSize);
bitmap = tmpBitmap;
} else {
bitmap = &font->data->bitmap[offset];
}
bitmap = &fontFamily->getData(style)->bitmap[offset];
if (bitmap != nullptr) {
for (int localY = 0; localY < height; localY++) {
int yy = *y - glyph->top + localY;
const int startPos = *x + left;
bool byteComplete = startPos % 2;
int localX = max(0, -startPos);
const int maxX = startPos + width;
for (int glyphY = 0; glyphY < height; glyphY++) {
int screenY = *y - glyph->top + glyphY;
for (int glyphX = 0; glyphX < width; glyphX++) {
const int pixelPosition = glyphY * width + glyphX;
int screenX = *x + left + glyphX;
for (int xx = startPos; xx < maxX; xx++) {
uint8_t bm = bitmap[localY * byteWidth + localX / 2];
if ((localX & 1) == 0) {
bm = bm & 0xF;
} else {
bm = bm >> 4;
}
const uint8_t byte = bitmap[pixelPosition / 8];
const uint8_t bit_index = 7 - (pixelPosition % 8);
if (bm) {
renderer->drawPixel(xx, yy, color);
if ((byte >> bit_index) & 1) {
renderer.drawPixel(screenX, screenY, color);
}
byteComplete = !byteComplete;
localX++;
}
}
if (font->data->compressed) {
free(const_cast<uint8_t*>(bitmap));
}
}
*x += glyph->advanceX;
+87 -70
View File
@@ -5,66 +5,82 @@
#include "builtinFonts/bookerly_bold.h"
#include "builtinFonts/bookerly_bold_italic.h"
#include "builtinFonts/bookerly_italic.h"
#include "builtinFonts/ubuntu_10.h"
#include "builtinFonts/ubuntu_bold_10.h"
EpdRenderer::EpdRenderer(XteinkDisplay* display) {
this->display = display;
this->regularFont = new EpdFontRenderer<XteinkDisplay>(new EpdFont(&bookerly), display);
this->boldFont = new EpdFontRenderer<XteinkDisplay>(new EpdFont(&bookerly_bold), display);
this->italicFont = new EpdFontRenderer<XteinkDisplay>(new EpdFont(&bookerly_italic), display);
this->bold_italicFont = new EpdFontRenderer<XteinkDisplay>(new EpdFont(&bookerly_bold_italic), display);
this->smallFont = new EpdFontRenderer<XteinkDisplay>(new EpdFont(&babyblue), display);
EpdFont bookerlyFont(&bookerly);
EpdFont bookerlyBoldFont(&bookerly_bold);
EpdFont bookerlyItalicFont(&bookerly_italic);
EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic);
EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont);
this->marginTop = 11;
this->marginBottom = 30;
this->marginLeft = 10;
this->marginRight = 10;
this->lineCompression = 0.95f;
EpdFont smallFont(&babyblue);
EpdFontFamily smallFontFamily(&smallFont);
EpdFont ubuntu10Font(&ubuntu_10);
EpdFont ununtuBold10Font(&ubuntu_bold_10);
EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ununtuBold10Font);
EpdRenderer::EpdRenderer(XteinkDisplay& display)
: display(display), marginTop(11), marginBottom(30), marginLeft(10), marginRight(10), lineCompression(0.95f) {
this->regularFontRenderer = new EpdFontRenderer<XteinkDisplay>(&bookerlyFontFamily, display);
this->smallFontRenderer = new EpdFontRenderer<XteinkDisplay>(&smallFontFamily, display);
this->uiFontRenderer = new EpdFontRenderer<XteinkDisplay>(&ubuntuFontFamily, display);
}
EpdFontRenderer<XteinkDisplay>* EpdRenderer::getFontRenderer(const bool bold, const bool italic) const {
if (bold && italic) {
return bold_italicFont;
}
if (bold) {
return boldFont;
}
if (italic) {
return italicFont;
}
return regularFont;
EpdRenderer::~EpdRenderer() {
delete regularFontRenderer;
delete smallFontRenderer;
delete uiFontRenderer;
}
int EpdRenderer::getTextWidth(const char* text, const bool bold, const bool italic) const {
int EpdRenderer::getTextWidth(const char* text, const EpdFontStyle style) const {
int w = 0, h = 0;
getFontRenderer(bold, italic)->font->getTextDimensions(text, &w, &h);
regularFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
return w;
}
int EpdRenderer::getSmallTextWidth(const char* text) const {
int EpdRenderer::getUiTextWidth(const char* text, const EpdFontStyle style) const {
int w = 0, h = 0;
smallFont->font->getTextDimensions(text, &w, &h);
uiFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
return w;
}
void EpdRenderer::drawText(const int x, const int y, const char* text, const bool bold, const bool italic,
const uint16_t color) const {
int EpdRenderer::getSmallTextWidth(const char* text, const EpdFontStyle style) const {
int w = 0, h = 0;
smallFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
return w;
}
void EpdRenderer::drawText(const int x, const int y, const char* text, const uint16_t color,
const EpdFontStyle style) const {
int ypos = y + getLineHeight() + marginTop;
int xpos = x + marginLeft;
getFontRenderer(bold, italic)->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
regularFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
}
void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const uint16_t color) const {
int ypos = y + smallFont->font->data->advanceY + marginTop;
void EpdRenderer::drawUiText(const int x, const int y, const char* text, const uint16_t color,
const EpdFontStyle style) const {
int ypos = y + uiFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
int xpos = x + marginLeft;
smallFont->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
uiFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
}
void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const uint16_t color,
const EpdFontStyle style) const {
int ypos = y + smallFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
int xpos = x + marginLeft;
smallFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
}
void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text, const int width, const int height,
const bool bold, const bool italic) const {
const EpdFontStyle style) const {
const size_t length = text.length();
// fit the text into the box
int start = 0;
@@ -72,7 +88,7 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
int ypos = 0;
while (true) {
if (end >= length) {
drawText(x, y + ypos, text.substr(start, length - start).c_str(), bold, italic);
drawText(x, y + ypos, text.substr(start, length - start).c_str(), 1, style);
break;
}
@@ -81,15 +97,15 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
}
if (text[end - 1] == '\n') {
drawText(x, y + ypos, text.substr(start, end - start).c_str(), bold, italic);
drawText(x, y + ypos, text.substr(start, end - start).c_str(), 1, style);
ypos += getLineHeight();
start = end;
end = start + 1;
continue;
}
if (getTextWidth(text.substr(start, end - start).c_str(), bold, italic) > width) {
drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), bold, italic);
if (getTextWidth(text.substr(start, end - start).c_str(), style) > width) {
drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), 1, style);
ypos += getLineHeight();
start = end - 1;
continue;
@@ -100,52 +116,53 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
}
void EpdRenderer::drawLine(int x1, int y1, int x2, int y2, uint16_t color) const {
display->drawLine(x1 + marginLeft, y1 + marginTop, x2 + marginLeft, y2 + marginTop,
color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
display.drawLine(x1 + marginLeft, y1 + marginTop, x2 + marginLeft, y2 + marginTop,
color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::drawRect(const int x, const int y, const int width, const int height, const uint16_t color) const {
display->drawRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
display.drawRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::fillRect(const int x, const int y, const int width, const int height,
const uint16_t color = 0) const {
display->fillRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
void EpdRenderer::fillRect(const int x, const int y, const int width, const int height, const uint16_t color) const {
display.fillRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::drawCircle(const int x, const int y, const int radius, const uint16_t color) const {
display.drawCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::fillCircle(const int x, const int y, const int radius, const uint16_t color) const {
display.fillCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height,
const bool invert, const bool mirrorY) const {
drawImageNoMargin(bitmap, x + marginLeft, y + marginTop, width, height, invert, mirrorY);
}
void EpdRenderer::drawImageNoMargin(const uint8_t bitmap[], const int x, const int y, const int width, const int height,
const bool invert, const bool mirrorY) const {
display.drawImage(bitmap, x, y, width, height, invert, mirrorY);
}
void EpdRenderer::clearScreen(const bool black) const {
Serial.println("Clearing screen");
display->fillScreen(black ? GxEPD_BLACK : GxEPD_WHITE);
display.fillScreen(black ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::flushDisplay() const { display->display(true); }
void EpdRenderer::flushDisplay(const bool partialUpdate) const { display.display(partialUpdate); }
void EpdRenderer::flushArea(int x, int y, int width, int height) const {
// TODO: Fix
display->display(true);
void EpdRenderer::flushArea(const int x, const int y, const int width, const int height) const {
display.displayWindow(x + marginLeft, y + marginTop, width, height);
}
int EpdRenderer::getPageWidth() const { return display->width() - marginLeft - marginRight; }
int EpdRenderer::getPageWidth() const { return display.width() - marginLeft - marginRight; }
int EpdRenderer::getPageHeight() const { return display->height() - marginTop - marginBottom; }
int EpdRenderer::getPageHeight() const { return display.height() - marginTop - marginBottom; }
int EpdRenderer::getSpaceWidth() const { return regularFont->font->getGlyph(' ')->advanceX; }
int EpdRenderer::getSpaceWidth() const { return regularFontRenderer->fontFamily->getGlyph(' ', REGULAR)->advanceX; }
int EpdRenderer::getLineHeight() const { return regularFont->font->data->advanceY * lineCompression; }
// deep sleep helper - persist any state to disk that may be needed on wake
bool EpdRenderer::dehydrate() {
// TODO: Implement
return false;
};
// deep sleep helper - retrieve any state from disk after wake
bool EpdRenderer::hydrate() {
// TODO: Implement
return false;
};
// really really clear the screen
void EpdRenderer::reset() {
// TODO: Implement
};
int EpdRenderer::getLineHeight() const {
return regularFontRenderer->fontFamily->getData(REGULAR)->advanceY * lineCompression;
}
+23 -27
View File
@@ -7,33 +7,37 @@
#define XteinkDisplay GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT>
class EpdRenderer {
XteinkDisplay* display;
EpdFontRenderer<XteinkDisplay>* regularFont;
EpdFontRenderer<XteinkDisplay>* boldFont;
EpdFontRenderer<XteinkDisplay>* italicFont;
EpdFontRenderer<XteinkDisplay>* bold_italicFont;
EpdFontRenderer<XteinkDisplay>* smallFont;
XteinkDisplay& display;
EpdFontRenderer<XteinkDisplay>* regularFontRenderer;
EpdFontRenderer<XteinkDisplay>* smallFontRenderer;
EpdFontRenderer<XteinkDisplay>* uiFontRenderer;
int marginTop;
int marginBottom;
int marginLeft;
int marginRight;
float lineCompression;
EpdFontRenderer<XteinkDisplay>* getFontRenderer(bool bold, bool italic) const;
public:
explicit EpdRenderer(XteinkDisplay* display);
~EpdRenderer() = default;
int getTextWidth(const char* text, bool bold = false, bool italic = false) const;
int getSmallTextWidth(const char* text) const;
void drawText(int x, int y, const char* text, bool bold = false, bool italic = false, uint16_t color = 1) const;
void drawSmallText(int x, int y, const char* text, uint16_t color = 1) const;
void drawTextBox(int x, int y, const std::string& text, int width, int height, bool bold = false,
bool italic = false) const;
void drawLine(int x1, int y1, int x2, int y2, uint16_t color) const;
void drawRect(int x, int y, int width, int height, uint16_t color) const;
void fillRect(int x, int y, int width, int height, uint16_t color) const;
explicit EpdRenderer(XteinkDisplay& display);
~EpdRenderer();
int getTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
int getUiTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
int getSmallTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
void drawText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
void drawUiText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
void drawSmallText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
void drawTextBox(int x, int y, const std::string& text, int width, int height, EpdFontStyle style = REGULAR) const;
void drawLine(int x1, int y1, int x2, int y2, uint16_t color = 1) const;
void drawRect(int x, int y, int width, int height, uint16_t color = 1) const;
void fillRect(int x, int y, int width, int height, uint16_t color = 1) const;
void drawCircle(int x, int y, int radius, uint16_t color = 1) const;
void fillCircle(int x, int y, int radius, uint16_t color = 1) const;
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false,
bool mirrorY = false) const;
void drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false,
bool mirrorY = false) const;
void clearScreen(bool black = false) const;
void flushDisplay() const;
void flushDisplay(bool partialUpdate = true) const;
void flushArea(int x, int y, int width, int height) const;
int getPageWidth() const;
@@ -45,12 +49,4 @@ class EpdRenderer {
void setMarginBottom(const int newMarginBottom) { this->marginBottom = newMarginBottom; }
void setMarginLeft(const int newMarginLeft) { this->marginLeft = newMarginLeft; }
void setMarginRight(const int newMarginRight) { this->marginRight = newMarginRight; }
// deep sleep helper - persist any state to disk that may be needed on wake
bool dehydrate();
// deep sleep helper - retrieve any state from disk after wake
bool hydrate();
// really really clear the screen
void reset();
uint8_t temperature = 20;
};
+7 -13
View File
@@ -9,7 +9,7 @@
bool Epub::findContentOpfFile(const ZipFile& zip, std::string& contentOpfFile) {
// open up the meta data to find where the content.opf file lives
size_t s;
const auto metaInfo = zip.readTextFileToMemory("META-INF/container.xml", &s);
const auto metaInfo = reinterpret_cast<char*>(zip.readFileToMemory("META-INF/container.xml", &s, true));
if (!metaInfo) {
Serial.println("Could not find META-INF/container.xml");
return false;
@@ -57,7 +57,7 @@ bool Epub::findContentOpfFile(const ZipFile& zip, std::string& contentOpfFile) {
bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
// read in the content.opf file and parse it
auto contents = zip.readTextFileToMemory(content_opf_file.c_str());
auto contents = reinterpret_cast<char*>(zip.readFileToMemory(content_opf_file.c_str(), nullptr, true));
// parse the contents
tinyxml2::XMLDocument doc;
@@ -168,7 +168,7 @@ bool Epub::parseTocNcxFile(const ZipFile& zip) {
return false;
}
const auto ncxData = zip.readTextFileToMemory(tocNcxItem.c_str());
const auto ncxData = reinterpret_cast<char*>(zip.readFileToMemory(tocNcxItem.c_str(), nullptr, true));
if (!ncxData) {
Serial.printf("Could not find %s\n", tocNcxItem.c_str());
return false;
@@ -308,11 +308,11 @@ std::string normalisePath(const std::string& path) {
return result;
}
uint8_t* Epub::getItemContents(const std::string& itemHref, size_t* size) const {
uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, bool trailingNullByte) const {
const ZipFile zip("/sd" + filepath);
const std::string path = normalisePath(itemHref);
const auto content = zip.readFileToMemory(path.c_str(), size);
const auto content = zip.readFileToMemory(path.c_str(), size, trailingNullByte);
if (!content) {
Serial.printf("Failed to read item %s\n", path.c_str());
return nullptr;
@@ -321,17 +321,11 @@ uint8_t* Epub::getItemContents(const std::string& itemHref, size_t* size) const
return content;
}
char* Epub::getTextItemContents(const std::string& itemHref, size_t* size) const {
bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, const size_t chunkSize) const {
const ZipFile zip("/sd" + filepath);
const std::string path = normalisePath(itemHref);
const auto content = zip.readTextFileToMemory(path.c_str(), size);
if (!content) {
Serial.printf("Failed to read item %s\n", path.c_str());
return nullptr;
}
return content;
return zip.readFileToStream(path.c_str(), out, chunkSize);
}
int Epub::getSpineItemsCount() const { return spine.size(); }
+3 -2
View File
@@ -56,8 +56,9 @@ class Epub {
const std::string& getPath() const;
const std::string& getTitle() const;
const std::string& getCoverImageItem() const;
uint8_t* getItemContents(const std::string& itemHref, size_t* size = nullptr) const;
char* getTextItemContents(const std::string& itemHref, size_t* size = nullptr) const;
uint8_t* readItemContentsToBytes(const std::string& itemHref, size_t* size = nullptr,
bool trailingNullByte = false) const;
bool readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize) const;
std::string& getSpineItem(int spineIndex);
int getSpineItemsCount() const;
EpubTocEntry& getTocItem(int tocTndex);

Some files were not shown because too many files have changed in this diff Show More