Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45af2d0e81 | ||
|
|
0926e9e6e4 | ||
|
|
02b157c02b | ||
|
|
07cc589e59 | ||
|
|
b743a1ca8e | ||
|
|
2ed8017aa2 |
@@ -31,4 +31,5 @@ typedef struct {
|
|||||||
uint8_t advanceY; ///< Newline distance (y axis)
|
uint8_t advanceY; ///< Newline distance (y axis)
|
||||||
int ascender; ///< Maximal height of a glyph above the base line
|
int ascender; ///< Maximal height of a glyph above the base line
|
||||||
int descender; ///< Maximal height of a glyph below the base line
|
int descender; ///< Maximal height of a glyph below the base line
|
||||||
|
bool is2Bit;
|
||||||
} EpdFontData;
|
} EpdFontData;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* generated by fontconvert.py
|
* generated by fontconvert.py
|
||||||
* name: babyblue
|
* name: babyblue
|
||||||
* size: 8
|
* size: 8
|
||||||
|
* mode: 1-bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
@@ -500,5 +501,5 @@ static const EpdUnicodeInterval babyblueIntervals[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const EpdFontData babyblue = {
|
static const EpdFontData babyblue = {
|
||||||
babyblueBitmaps, babyblueGlyphs, babyblueIntervals, 5, 17, 13, -4,
|
babyblueBitmaps, babyblueGlyphs, babyblueIntervals, 5, 17, 13, -4, false,
|
||||||
};
|
};
|
||||||
|
|||||||
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
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
|||||||
* generated by fontconvert.py
|
* generated by fontconvert.py
|
||||||
* name: ubuntu_10
|
* name: ubuntu_10
|
||||||
* size: 10
|
* size: 10
|
||||||
|
* mode: 1-bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
@@ -762,5 +763,5 @@ static const EpdUnicodeInterval ubuntu_10Intervals[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const EpdFontData ubuntu_10 = {
|
static const EpdFontData ubuntu_10 = {
|
||||||
ubuntu_10Bitmaps, ubuntu_10Glyphs, ubuntu_10Intervals, 31, 24, 20, -4,
|
ubuntu_10Bitmaps, ubuntu_10Glyphs, ubuntu_10Intervals, 31, 24, 20, -4, false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* generated by fontconvert.py
|
* generated by fontconvert.py
|
||||||
* name: ubuntu_bold_10
|
* name: ubuntu_bold_10
|
||||||
* size: 10
|
* size: 10
|
||||||
|
* mode: 1-bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
@@ -806,5 +807,5 @@ static const EpdUnicodeInterval ubuntu_bold_10Intervals[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const EpdFontData ubuntu_bold_10 = {
|
static const EpdFontData ubuntu_bold_10 = {
|
||||||
ubuntu_bold_10Bitmaps, ubuntu_bold_10Glyphs, ubuntu_bold_10Intervals, 31, 24, 20, -4,
|
ubuntu_bold_10Bitmaps, ubuntu_bold_10Glyphs, ubuntu_bold_10Intervals, 31, 24, 20, -4, false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ parser = argparse.ArgumentParser(description="Generate a header file from a font
|
|||||||
parser.add_argument("name", action="store", help="name of the font.")
|
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("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("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority.")
|
||||||
|
parser.add_argument("--2bit", dest="is2Bit", action="store_true", help="generate 2-bit greyscale bitmap instead of 1-bit black and white.")
|
||||||
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.")
|
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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "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]
|
font_stack = [freetype.Face(f) for f in args.fontstack]
|
||||||
|
is2Bit = args.is2Bit
|
||||||
size = args.size
|
size = args.size
|
||||||
font_name = args.name
|
font_name = args.name
|
||||||
|
|
||||||
@@ -173,6 +175,42 @@ for i_start, i_end in intervals:
|
|||||||
pixels4g.append(px)
|
pixels4g.append(px)
|
||||||
px = 0
|
px = 0
|
||||||
|
|
||||||
|
if is2Bit:
|
||||||
|
# 0 = white, 15 black, 8+ dark grey, 7- light grey
|
||||||
|
# Downsample to 2-bit bitmap
|
||||||
|
pixels2b = []
|
||||||
|
px = 0
|
||||||
|
pitch = (bitmap.width // 2) + (bitmap.width % 2)
|
||||||
|
for y in range(bitmap.rows):
|
||||||
|
for x in range(bitmap.width):
|
||||||
|
px = px << 2
|
||||||
|
bm = pixels4g[y * pitch + (x // 2)]
|
||||||
|
bm = (bm >> ((x % 2) * 4)) & 0xF
|
||||||
|
|
||||||
|
if bm == 15:
|
||||||
|
px += 3
|
||||||
|
elif bm >= 8:
|
||||||
|
px += 2
|
||||||
|
elif bm > 0:
|
||||||
|
px += 1
|
||||||
|
|
||||||
|
if (y * bitmap.width + x) % 4 == 3:
|
||||||
|
pixels2b.append(px)
|
||||||
|
px = 0
|
||||||
|
if (bitmap.width * bitmap.rows) % 4 != 0:
|
||||||
|
px = px << (4 - (bitmap.width * bitmap.rows) % 4) * 2
|
||||||
|
pixels2b.append(px)
|
||||||
|
|
||||||
|
# for y in range(bitmap.rows):
|
||||||
|
# line = ''
|
||||||
|
# for x in range(bitmap.width):
|
||||||
|
# pixelPosition = y * bitmap.width + x
|
||||||
|
# byte = pixels2b[pixelPosition // 4]
|
||||||
|
# bit_index = (3 - (pixelPosition % 4)) * 2
|
||||||
|
# line += '#' if ((byte >> bit_index) & 3) > 0 else '.'
|
||||||
|
# print(line)
|
||||||
|
# print('')
|
||||||
|
else:
|
||||||
# Downsample to 1-bit bitmap - treat any non-zero as black
|
# Downsample to 1-bit bitmap - treat any non-zero as black
|
||||||
pixelsbw = []
|
pixelsbw = []
|
||||||
px = 0
|
px = 0
|
||||||
@@ -190,9 +228,20 @@ for i_start, i_end in intervals:
|
|||||||
px = px << (8 - (bitmap.width * bitmap.rows) % 8)
|
px = px << (8 - (bitmap.width * bitmap.rows) % 8)
|
||||||
pixelsbw.append(px)
|
pixelsbw.append(px)
|
||||||
|
|
||||||
|
# for y in range(bitmap.rows):
|
||||||
|
# line = ''
|
||||||
|
# for x in range(bitmap.width):
|
||||||
|
# pixelPosition = y * bitmap.width + x
|
||||||
|
# byte = pixelsbw[pixelPosition // 8]
|
||||||
|
# bit_index = 7 - (pixelPosition % 8)
|
||||||
|
# line += '#' if (byte >> bit_index) & 1 else '.'
|
||||||
|
# print(line)
|
||||||
|
# print('')
|
||||||
|
|
||||||
|
pixels = pixels2b if is2Bit else pixelsbw
|
||||||
|
|
||||||
# Build output data
|
# Build output data
|
||||||
packed = bytes(pixelsbw)
|
packed = bytes(pixels)
|
||||||
glyph = GlyphProps(
|
glyph = GlyphProps(
|
||||||
width = bitmap.width,
|
width = bitmap.width,
|
||||||
height = bitmap.rows,
|
height = bitmap.rows,
|
||||||
@@ -216,7 +265,7 @@ for index, glyph in enumerate(all_glyphs):
|
|||||||
glyph_data.extend([b for b in packed])
|
glyph_data.extend([b for b in packed])
|
||||||
glyph_props.append(props)
|
glyph_props.append(props)
|
||||||
|
|
||||||
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n */")
|
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n * mode: {'2-bit' if is2Bit else '1-bit'}\n */")
|
||||||
print("#pragma once")
|
print("#pragma once")
|
||||||
print("#include \"EpdFontData.h\"\n")
|
print("#include \"EpdFontData.h\"\n")
|
||||||
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
|
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
|
||||||
@@ -244,4 +293,5 @@ print(f" {len(intervals)},")
|
|||||||
print(f" {norm_ceil(face.size.height)},")
|
print(f" {norm_ceil(face.size.height)},")
|
||||||
print(f" {norm_ceil(face.size.ascender)},")
|
print(f" {norm_ceil(face.size.ascender)},")
|
||||||
print(f" {norm_floor(face.size.descender)},")
|
print(f" {norm_floor(face.size.descender)},")
|
||||||
|
print(f" {'true' if is2Bit else 'false'},")
|
||||||
print("};")
|
print("};")
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <EpdFontFamily.h>
|
|
||||||
#include <HardwareSerial.h>
|
|
||||||
#include <Utf8.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; }
|
|
||||||
|
|
||||||
template <typename Renderable>
|
|
||||||
class EpdFontRenderer {
|
|
||||||
Renderable& renderer;
|
|
||||||
void renderChar(uint32_t cp, int* x, const int* y, uint16_t color, EpdFontStyle style = REGULAR);
|
|
||||||
|
|
||||||
public:
|
|
||||||
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, EpdFontStyle style = REGULAR);
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Renderable>
|
|
||||||
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 (!fontFamily->hasPrintableChars(string, style)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t cp;
|
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
|
|
||||||
renderChar(cp, x, y, color, style);
|
|
||||||
}
|
|
||||||
|
|
||||||
*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 EpdFontStyle style) {
|
|
||||||
const EpdGlyph* glyph = fontFamily->getGlyph(cp, style);
|
|
||||||
if (!glyph) {
|
|
||||||
// TODO: Replace with fallback glyph property?
|
|
||||||
glyph = fontFamily->getGlyph('?', style);
|
|
||||||
}
|
|
||||||
|
|
||||||
// no glyph?
|
|
||||||
if (!glyph) {
|
|
||||||
Serial.printf("No glyph for codepoint %d\n", cp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t offset = glyph->dataOffset;
|
|
||||||
const uint8_t width = glyph->width;
|
|
||||||
const uint8_t height = glyph->height;
|
|
||||||
const int left = glyph->left;
|
|
||||||
|
|
||||||
const uint8_t* bitmap = nullptr;
|
|
||||||
bitmap = &fontFamily->getData(style)->bitmap[offset];
|
|
||||||
|
|
||||||
if (bitmap != nullptr) {
|
|
||||||
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;
|
|
||||||
|
|
||||||
const uint8_t byte = bitmap[pixelPosition / 8];
|
|
||||||
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
|
||||||
|
|
||||||
if ((byte >> bit_index) & 1) {
|
|
||||||
renderer.drawPixel(screenX, screenY, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*x += glyph->advanceX;
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
#include "EpdRenderer.h"
|
|
||||||
|
|
||||||
#include "builtinFonts/babyblue.h"
|
|
||||||
#include "builtinFonts/bookerly.h"
|
|
||||||
#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"
|
|
||||||
|
|
||||||
EpdFont bookerlyFont(&bookerly);
|
|
||||||
EpdFont bookerlyBoldFont(&bookerly_bold);
|
|
||||||
EpdFont bookerlyItalicFont(&bookerly_italic);
|
|
||||||
EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic);
|
|
||||||
EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
EpdRenderer::~EpdRenderer() {
|
|
||||||
delete regularFontRenderer;
|
|
||||||
delete smallFontRenderer;
|
|
||||||
delete uiFontRenderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
int EpdRenderer::getTextWidth(const char* text, const EpdFontStyle style) const {
|
|
||||||
int w = 0, h = 0;
|
|
||||||
|
|
||||||
regularFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
|
|
||||||
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
int EpdRenderer::getUiTextWidth(const char* text, const EpdFontStyle style) const {
|
|
||||||
int w = 0, h = 0;
|
|
||||||
|
|
||||||
uiFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
|
|
||||||
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
regularFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
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 EpdFontStyle style) const {
|
|
||||||
const size_t length = text.length();
|
|
||||||
// fit the text into the box
|
|
||||||
int start = 0;
|
|
||||||
int end = 1;
|
|
||||||
int ypos = 0;
|
|
||||||
while (true) {
|
|
||||||
if (end >= length) {
|
|
||||||
drawText(x, y + ypos, text.substr(start, length - start).c_str(), 1, style);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ypos + getLineHeight() >= height) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text[end - 1] == '\n') {
|
|
||||||
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(), style) > width) {
|
|
||||||
drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), 1, style);
|
|
||||||
ypos += getLineHeight();
|
|
||||||
start = end - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
end++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::flushDisplay(const bool partialUpdate) const { display.display(partialUpdate); }
|
|
||||||
|
|
||||||
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::getPageHeight() const { return display.height() - marginTop - marginBottom; }
|
|
||||||
|
|
||||||
int EpdRenderer::getSpaceWidth() const { return regularFontRenderer->fontFamily->getGlyph(' ', REGULAR)->advanceX; }
|
|
||||||
|
|
||||||
int EpdRenderer::getLineHeight() const {
|
|
||||||
return regularFontRenderer->fontFamily->getData(REGULAR)->advanceY * lineCompression;
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <GxEPD2_BW.h>
|
|
||||||
|
|
||||||
#include <EpdFontRenderer.hpp>
|
|
||||||
|
|
||||||
#define XteinkDisplay GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT>
|
|
||||||
|
|
||||||
class EpdRenderer {
|
|
||||||
XteinkDisplay& display;
|
|
||||||
EpdFontRenderer<XteinkDisplay>* regularFontRenderer;
|
|
||||||
EpdFontRenderer<XteinkDisplay>* smallFontRenderer;
|
|
||||||
EpdFontRenderer<XteinkDisplay>* uiFontRenderer;
|
|
||||||
int marginTop;
|
|
||||||
int marginBottom;
|
|
||||||
int marginLeft;
|
|
||||||
int marginRight;
|
|
||||||
float lineCompression;
|
|
||||||
|
|
||||||
public:
|
|
||||||
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(bool partialUpdate = true) const;
|
|
||||||
void flushArea(int x, int y, int width, int height) const;
|
|
||||||
|
|
||||||
int getPageWidth() const;
|
|
||||||
int getPageHeight() const;
|
|
||||||
int getSpaceWidth() const;
|
|
||||||
int getLineHeight() const;
|
|
||||||
// set margins
|
|
||||||
void setMarginTop(const int newMarginTop) { this->marginTop = newMarginTop; }
|
|
||||||
void setMarginBottom(const int newMarginBottom) { this->marginBottom = newMarginBottom; }
|
|
||||||
void setMarginLeft(const int newMarginLeft) { this->marginLeft = newMarginLeft; }
|
|
||||||
void setMarginRight(const int newMarginRight) { this->marginRight = newMarginRight; }
|
|
||||||
};
|
|
||||||
+24
-23
@@ -11,7 +11,7 @@ bool Epub::findContentOpfFile(const ZipFile& zip, std::string& contentOpfFile) {
|
|||||||
size_t s;
|
size_t s;
|
||||||
const auto metaInfo = reinterpret_cast<char*>(zip.readFileToMemory("META-INF/container.xml", &s, true));
|
const auto metaInfo = reinterpret_cast<char*>(zip.readFileToMemory("META-INF/container.xml", &s, true));
|
||||||
if (!metaInfo) {
|
if (!metaInfo) {
|
||||||
Serial.println("Could not find META-INF/container.xml");
|
Serial.printf("[%lu] [EBP] Could not find META-INF/container.xml\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,19 +21,19 @@ bool Epub::findContentOpfFile(const ZipFile& zip, std::string& contentOpfFile) {
|
|||||||
free(metaInfo);
|
free(metaInfo);
|
||||||
|
|
||||||
if (result != tinyxml2::XML_SUCCESS) {
|
if (result != tinyxml2::XML_SUCCESS) {
|
||||||
Serial.printf("Could not parse META-INF/container.xml. Error: %d\n", result);
|
Serial.printf("[%lu] [EBP] Could not parse META-INF/container.xml. Error: %d\n", millis(), result);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto container = metaDataDoc.FirstChildElement("container");
|
const auto container = metaDataDoc.FirstChildElement("container");
|
||||||
if (!container) {
|
if (!container) {
|
||||||
Serial.println("Could not find container element in META-INF/container.xml");
|
Serial.printf("[%lu] [EBP] Could not find container element in META-INF/container.xml\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto rootfiles = container->FirstChildElement("rootfiles");
|
const auto rootfiles = container->FirstChildElement("rootfiles");
|
||||||
if (!rootfiles) {
|
if (!rootfiles) {
|
||||||
Serial.println("Could not find rootfiles element in META-INF/container.xml");
|
Serial.printf("[%lu] [EBP] Could not find rootfiles element in META-INF/container.xml\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ bool Epub::findContentOpfFile(const ZipFile& zip, std::string& contentOpfFile) {
|
|||||||
rootfile = rootfile->NextSiblingElement("rootfile");
|
rootfile = rootfile->NextSiblingElement("rootfile");
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.println("Could not get path to content.opf file");
|
Serial.printf("[%lu] [EBP] Could not get path to content.opf file\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,8 @@ bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
|
|||||||
free(contents);
|
free(contents);
|
||||||
|
|
||||||
if (result != tinyxml2::XML_SUCCESS) {
|
if (result != tinyxml2::XML_SUCCESS) {
|
||||||
Serial.printf("Error parsing content.opf - %s\n", tinyxml2::XMLDocument::ErrorIDToName(result));
|
Serial.printf("[%lu] [EBP] Error parsing content.opf - %s\n", millis(),
|
||||||
|
tinyxml2::XMLDocument::ErrorIDToName(result));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
|
|||||||
if (!package) package = doc.FirstChildElement("opf:package");
|
if (!package) package = doc.FirstChildElement("opf:package");
|
||||||
|
|
||||||
if (!package) {
|
if (!package) {
|
||||||
Serial.println("Could not find package element in content.opf");
|
Serial.printf("[%lu] [EBP] Could not find package element in content.opf\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,13 +82,13 @@ bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
|
|||||||
auto metadata = package->FirstChildElement("metadata");
|
auto metadata = package->FirstChildElement("metadata");
|
||||||
if (!metadata) metadata = package->FirstChildElement("opf:metadata");
|
if (!metadata) metadata = package->FirstChildElement("opf:metadata");
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
Serial.println("Missing metadata");
|
Serial.printf("[%lu] [EBP] Missing metadata\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto titleEl = metadata->FirstChildElement("dc:title");
|
auto titleEl = metadata->FirstChildElement("dc:title");
|
||||||
if (!titleEl) {
|
if (!titleEl) {
|
||||||
Serial.println("Missing title");
|
Serial.printf("[%lu] [EBP] Missing title\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this->title = titleEl->GetText();
|
this->title = titleEl->GetText();
|
||||||
@@ -98,7 +99,7 @@ bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
|
|||||||
cover = cover->NextSiblingElement("meta");
|
cover = cover->NextSiblingElement("meta");
|
||||||
}
|
}
|
||||||
if (!cover) {
|
if (!cover) {
|
||||||
Serial.println("Missing cover");
|
Serial.printf("[%lu] [EBP] Missing cover\n", millis());
|
||||||
}
|
}
|
||||||
auto coverItem = cover ? cover->Attribute("content") : nullptr;
|
auto coverItem = cover ? cover->Attribute("content") : nullptr;
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
|
|||||||
auto manifest = package->FirstChildElement("manifest");
|
auto manifest = package->FirstChildElement("manifest");
|
||||||
if (!manifest) manifest = package->FirstChildElement("opf:manifest");
|
if (!manifest) manifest = package->FirstChildElement("opf:manifest");
|
||||||
if (!manifest) {
|
if (!manifest) {
|
||||||
Serial.println("Missing manifest");
|
Serial.printf("[%lu] [EBP] Missing manifest\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
|
|||||||
auto spineEl = package->FirstChildElement("spine");
|
auto spineEl = package->FirstChildElement("spine");
|
||||||
if (!spineEl) spineEl = package->FirstChildElement("opf:spine");
|
if (!spineEl) spineEl = package->FirstChildElement("opf:spine");
|
||||||
if (!spineEl) {
|
if (!spineEl) {
|
||||||
Serial.println("Missing spine");
|
Serial.printf("[%lu] [EBP] Missing spine\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,13 +165,13 @@ bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
|
|||||||
bool Epub::parseTocNcxFile(const ZipFile& zip) {
|
bool Epub::parseTocNcxFile(const ZipFile& zip) {
|
||||||
// the ncx file should have been specified in the content.opf file
|
// the ncx file should have been specified in the content.opf file
|
||||||
if (tocNcxItem.empty()) {
|
if (tocNcxItem.empty()) {
|
||||||
Serial.println("No ncx file specified");
|
Serial.printf("[%lu] [EBP] No ncx file specified\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto ncxData = reinterpret_cast<char*>(zip.readFileToMemory(tocNcxItem.c_str(), nullptr, true));
|
const auto ncxData = reinterpret_cast<char*>(zip.readFileToMemory(tocNcxItem.c_str(), nullptr, true));
|
||||||
if (!ncxData) {
|
if (!ncxData) {
|
||||||
Serial.printf("Could not find %s\n", tocNcxItem.c_str());
|
Serial.printf("[%lu] [EBP] Could not find %s\n", millis(), tocNcxItem.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,19 +181,19 @@ bool Epub::parseTocNcxFile(const ZipFile& zip) {
|
|||||||
free(ncxData);
|
free(ncxData);
|
||||||
|
|
||||||
if (result != tinyxml2::XML_SUCCESS) {
|
if (result != tinyxml2::XML_SUCCESS) {
|
||||||
Serial.printf("Error parsing toc %s\n", tinyxml2::XMLDocument::ErrorIDToName(result));
|
Serial.printf("[%lu] [EBP] Error parsing toc %s\n", millis(), tinyxml2::XMLDocument::ErrorIDToName(result));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto ncx = doc.FirstChildElement("ncx");
|
const auto ncx = doc.FirstChildElement("ncx");
|
||||||
if (!ncx) {
|
if (!ncx) {
|
||||||
Serial.println("Could not find first child ncx in toc");
|
Serial.printf("[%lu] [EBP] Could not find first child ncx in toc\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto navMap = ncx->FirstChildElement("navMap");
|
const auto navMap = ncx->FirstChildElement("navMap");
|
||||||
if (!navMap) {
|
if (!navMap) {
|
||||||
Serial.println("Could not find navMap child in ncx");
|
Serial.printf("[%lu] [EBP] Could not find navMap child in ncx\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +232,7 @@ bool Epub::load() {
|
|||||||
|
|
||||||
std::string contentOpfFile;
|
std::string contentOpfFile;
|
||||||
if (!findContentOpfFile(zip, contentOpfFile)) {
|
if (!findContentOpfFile(zip, contentOpfFile)) {
|
||||||
Serial.println("Could not open ePub");
|
Serial.printf("[%lu] [EBP] Could not open ePub\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +315,7 @@ uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size
|
|||||||
|
|
||||||
const auto content = zip.readFileToMemory(path.c_str(), size, trailingNullByte);
|
const auto content = zip.readFileToMemory(path.c_str(), size, trailingNullByte);
|
||||||
if (!content) {
|
if (!content) {
|
||||||
Serial.printf("Failed to read item %s\n", path.c_str());
|
Serial.printf("[%lu] [EBP] Failed to read item %s\n", millis(), path.c_str());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +333,7 @@ int Epub::getSpineItemsCount() const { return spine.size(); }
|
|||||||
|
|
||||||
std::string& Epub::getSpineItem(const int spineIndex) {
|
std::string& Epub::getSpineItem(const int spineIndex) {
|
||||||
if (spineIndex < 0 || spineIndex >= spine.size()) {
|
if (spineIndex < 0 || spineIndex >= spine.size()) {
|
||||||
Serial.printf("getSpineItem index:%d is out of range\n", spineIndex);
|
Serial.printf("[%lu] [EBP] getSpineItem index:%d is out of range\n", millis(), spineIndex);
|
||||||
return spine.at(0).second;
|
return spine.at(0).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,7 +342,7 @@ std::string& Epub::getSpineItem(const int spineIndex) {
|
|||||||
|
|
||||||
EpubTocEntry& Epub::getTocItem(const int tocTndex) {
|
EpubTocEntry& Epub::getTocItem(const int tocTndex) {
|
||||||
if (tocTndex < 0 || tocTndex >= toc.size()) {
|
if (tocTndex < 0 || tocTndex >= toc.size()) {
|
||||||
Serial.printf("getTocItem index:%d is out of range\n", tocTndex);
|
Serial.printf("[%lu] [EBP] getTocItem index:%d is out of range\n", millis(), tocTndex);
|
||||||
return toc.at(0);
|
return toc.at(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,7 +361,7 @@ int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.println("Section not found");
|
Serial.printf("[%lu] [EBP] Section not found\n", millis());
|
||||||
// not found - default to the start of the book
|
// not found - default to the start of the book
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -374,7 +375,7 @@ int Epub::getTocIndexForSpineIndex(const int spineIndex) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.println("TOC item not found");
|
Serial.printf("[%lu] [EBP] TOC item not found\n", millis());
|
||||||
// not found - default to first item
|
// not found - default to first item
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <HardwareSerial.h>
|
#include <Print.h>
|
||||||
#include <tinyxml2.h>
|
#include <tinyxml2.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "EpubHtmlParserSlim.h"
|
#include "EpubHtmlParserSlim.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <expat.h>
|
#include <expat.h>
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ void XMLCALL EpubHtmlParserSlim::characterData(void* userData, const XML_Char* s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we're about to run out of space, then cut the word off and start a new one
|
// If we're about to run out of space, then cut the word off and start a new one
|
||||||
if (self->partWordBufferIndex >= PART_WORD_BUFFER_SIZE - 2) {
|
if (self->partWordBufferIndex >= MAX_WORD_SIZE) {
|
||||||
self->partWordBuffer[self->partWordBufferIndex] = '\0';
|
self->partWordBuffer[self->partWordBufferIndex] = '\0';
|
||||||
self->currentTextBlock->addWord(replaceHtmlEntities(self->partWordBuffer), self->boldUntilDepth < self->depth,
|
self->currentTextBlock->addWord(replaceHtmlEntities(self->partWordBuffer), self->boldUntilDepth < self->depth,
|
||||||
self->italicUntilDepth < self->depth);
|
self->italicUntilDepth < self->depth);
|
||||||
@@ -190,7 +190,7 @@ bool EpubHtmlParserSlim::parseAndBuildPages() {
|
|||||||
int done;
|
int done;
|
||||||
|
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
Serial.println("Couldn't allocate memory for parser");
|
Serial.printf("[%lu] [EHP] Couldn't allocate memory for parser\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ bool EpubHtmlParserSlim::parseAndBuildPages() {
|
|||||||
|
|
||||||
FILE* file = fopen(filepath, "r");
|
FILE* file = fopen(filepath, "r");
|
||||||
if (!file) {
|
if (!file) {
|
||||||
Serial.printf("Couldn't open file %s\n", filepath);
|
Serial.printf("[%lu] [EHP] Couldn't open file %s\n", millis(), filepath);
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ bool EpubHtmlParserSlim::parseAndBuildPages() {
|
|||||||
do {
|
do {
|
||||||
void* const buf = XML_GetBuffer(parser, 1024);
|
void* const buf = XML_GetBuffer(parser, 1024);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
Serial.println("Couldn't allocate memory for buffer");
|
Serial.printf("[%lu] [EHP] Couldn't allocate memory for buffer\n", millis());
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return false;
|
return false;
|
||||||
@@ -217,7 +217,7 @@ bool EpubHtmlParserSlim::parseAndBuildPages() {
|
|||||||
const size_t len = fread(buf, 1, 1024, file);
|
const size_t len = fread(buf, 1, 1024, file);
|
||||||
|
|
||||||
if (ferror(file)) {
|
if (ferror(file)) {
|
||||||
Serial.println("Read error");
|
Serial.printf("[%lu] [EHP] File read error\n", millis());
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return false;
|
return false;
|
||||||
@@ -226,7 +226,7 @@ bool EpubHtmlParserSlim::parseAndBuildPages() {
|
|||||||
done = feof(file);
|
done = feof(file);
|
||||||
|
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(len), done) == XML_STATUS_ERROR) {
|
if (XML_ParseBuffer(parser, static_cast<int>(len), done) == XML_STATUS_ERROR) {
|
||||||
Serial.printf("Parse error at line %lu:\n%s\n", XML_GetCurrentLineNumber(parser),
|
Serial.printf("[%lu] [EHP] Parse error at line %lu:\n%s\n", millis(), XML_GetCurrentLineNumber(parser),
|
||||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
@@ -251,34 +251,36 @@ bool EpubHtmlParserSlim::parseAndBuildPages() {
|
|||||||
|
|
||||||
void EpubHtmlParserSlim::makePages() {
|
void EpubHtmlParserSlim::makePages() {
|
||||||
if (!currentTextBlock) {
|
if (!currentTextBlock) {
|
||||||
Serial.println("!! No text block to make pages for !!");
|
Serial.printf("[%lu] [EHP] !! No text block to make pages for !!\n", millis());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentPage) {
|
if (!currentPage) {
|
||||||
currentPage = new Page();
|
currentPage = new Page();
|
||||||
|
currentPageNextY = marginTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int lineHeight = renderer.getLineHeight();
|
const int lineHeight = renderer.getLineHeight(fontId) * lineCompression;
|
||||||
const int pageHeight = renderer.getPageHeight();
|
const int pageHeight = GfxRenderer::getScreenHeight() - marginTop - marginBottom;
|
||||||
|
|
||||||
// Long running task, make sure to let other things happen
|
// Long running task, make sure to let other things happen
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
|
|
||||||
if (currentTextBlock->getType() == TEXT_BLOCK) {
|
if (currentTextBlock->getType() == TEXT_BLOCK) {
|
||||||
const auto lines = currentTextBlock->splitIntoLines(renderer);
|
const auto lines = currentTextBlock->splitIntoLines(renderer, fontId, marginLeft + marginRight);
|
||||||
|
|
||||||
for (const auto line : lines) {
|
for (const auto line : lines) {
|
||||||
if (currentPage->nextY + lineHeight > pageHeight) {
|
if (currentPageNextY + lineHeight > pageHeight) {
|
||||||
completePageFn(currentPage);
|
completePageFn(currentPage);
|
||||||
currentPage = new Page();
|
currentPage = new Page();
|
||||||
|
currentPageNextY = marginTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPage->elements.push_back(new PageLine(line, currentPage->nextY));
|
currentPage->elements.push_back(new PageLine(line, marginLeft, currentPageNextY));
|
||||||
currentPage->nextY += lineHeight;
|
currentPageNextY += lineHeight;
|
||||||
}
|
}
|
||||||
// add some extra line between blocks
|
// add some extra line between blocks
|
||||||
currentPage->nextY += lineHeight / 2;
|
currentPageNextY += lineHeight / 2;
|
||||||
}
|
}
|
||||||
// TODO: Image block support
|
// TODO: Image block support
|
||||||
// if (block->getType() == BlockType::IMAGE_BLOCK) {
|
// if (block->getType() == BlockType::IMAGE_BLOCK) {
|
||||||
|
|||||||
@@ -1,30 +1,38 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <expat.h>
|
#include <expat.h>
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "blocks/TextBlock.h"
|
#include "blocks/TextBlock.h"
|
||||||
|
|
||||||
class Page;
|
class Page;
|
||||||
class EpdRenderer;
|
class GfxRenderer;
|
||||||
|
|
||||||
#define PART_WORD_BUFFER_SIZE 200
|
#define MAX_WORD_SIZE 200
|
||||||
|
|
||||||
class EpubHtmlParserSlim {
|
class EpubHtmlParserSlim {
|
||||||
const char* filepath;
|
const char* filepath;
|
||||||
EpdRenderer& renderer;
|
GfxRenderer& renderer;
|
||||||
std::function<void(Page*)> completePageFn;
|
std::function<void(Page*)> completePageFn;
|
||||||
int depth = 0;
|
int depth = 0;
|
||||||
int skipUntilDepth = INT_MAX;
|
int skipUntilDepth = INT_MAX;
|
||||||
int boldUntilDepth = INT_MAX;
|
int boldUntilDepth = INT_MAX;
|
||||||
int italicUntilDepth = INT_MAX;
|
int italicUntilDepth = INT_MAX;
|
||||||
// If we encounter words longer than this, but this is pretty large
|
// buffer for building up words from characters, will auto break if longer than this
|
||||||
char partWordBuffer[PART_WORD_BUFFER_SIZE] = {};
|
// leave one char at end for null pointer
|
||||||
|
char partWordBuffer[MAX_WORD_SIZE + 1] = {};
|
||||||
int partWordBufferIndex = 0;
|
int partWordBufferIndex = 0;
|
||||||
TextBlock* currentTextBlock = nullptr;
|
TextBlock* currentTextBlock = nullptr;
|
||||||
Page* currentPage = nullptr;
|
Page* currentPage = nullptr;
|
||||||
|
int currentPageNextY = 0;
|
||||||
|
int fontId;
|
||||||
|
float lineCompression;
|
||||||
|
int marginTop;
|
||||||
|
int marginRight;
|
||||||
|
int marginBottom;
|
||||||
|
int marginLeft;
|
||||||
|
|
||||||
void startNewTextBlock(BLOCK_STYLE style);
|
void startNewTextBlock(BLOCK_STYLE style);
|
||||||
void makePages();
|
void makePages();
|
||||||
@@ -34,9 +42,19 @@ class EpubHtmlParserSlim {
|
|||||||
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubHtmlParserSlim(const char* filepath, EpdRenderer& renderer,
|
explicit EpubHtmlParserSlim(const char* filepath, GfxRenderer& renderer, const int fontId,
|
||||||
|
const float lineCompression, const int marginTop, const int marginRight,
|
||||||
|
const int marginBottom, const int marginLeft,
|
||||||
const std::function<void(Page*)>& completePageFn)
|
const std::function<void(Page*)>& completePageFn)
|
||||||
: filepath(filepath), renderer(renderer), completePageFn(completePageFn) {}
|
: filepath(filepath),
|
||||||
|
renderer(renderer),
|
||||||
|
fontId(fontId),
|
||||||
|
lineCompression(lineCompression),
|
||||||
|
marginTop(marginTop),
|
||||||
|
marginRight(marginRight),
|
||||||
|
marginBottom(marginBottom),
|
||||||
|
marginLeft(marginLeft),
|
||||||
|
completePageFn(completePageFn) {}
|
||||||
~EpubHtmlParserSlim() = default;
|
~EpubHtmlParserSlim() = default;
|
||||||
bool parseAndBuildPages();
|
bool parseAndBuildPages();
|
||||||
};
|
};
|
||||||
|
|||||||
+17
-9
@@ -3,9 +3,12 @@
|
|||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
void PageLine::render(EpdRenderer& renderer) { block->render(renderer, 0, yPos); }
|
constexpr uint8_t PAGE_FILE_VERSION = 1;
|
||||||
|
|
||||||
|
void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); }
|
||||||
|
|
||||||
void PageLine::serialize(std::ostream& os) {
|
void PageLine::serialize(std::ostream& os) {
|
||||||
|
serialization::writePod(os, xPos);
|
||||||
serialization::writePod(os, yPos);
|
serialization::writePod(os, yPos);
|
||||||
|
|
||||||
// serialize TextBlock pointed to by PageLine
|
// serialize TextBlock pointed to by PageLine
|
||||||
@@ -13,23 +16,23 @@ void PageLine::serialize(std::ostream& os) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PageLine* PageLine::deserialize(std::istream& is) {
|
PageLine* PageLine::deserialize(std::istream& is) {
|
||||||
|
int32_t xPos;
|
||||||
int32_t yPos;
|
int32_t yPos;
|
||||||
|
serialization::readPod(is, xPos);
|
||||||
serialization::readPod(is, yPos);
|
serialization::readPod(is, yPos);
|
||||||
|
|
||||||
const auto tb = TextBlock::deserialize(is);
|
const auto tb = TextBlock::deserialize(is);
|
||||||
return new PageLine(tb, yPos);
|
return new PageLine(tb, xPos, yPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Page::render(EpdRenderer& renderer) const {
|
void Page::render(GfxRenderer& renderer, const int fontId) const {
|
||||||
const auto start = millis();
|
|
||||||
for (const auto element : elements) {
|
for (const auto element : elements) {
|
||||||
element->render(renderer);
|
element->render(renderer, fontId);
|
||||||
}
|
}
|
||||||
Serial.printf("Rendered page elements (%u) in %dms\n", elements.size(), millis() - start);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Page::serialize(std::ostream& os) const {
|
void Page::serialize(std::ostream& os) const {
|
||||||
serialization::writePod(os, nextY);
|
serialization::writePod(os, PAGE_FILE_VERSION);
|
||||||
|
|
||||||
const uint32_t count = elements.size();
|
const uint32_t count = elements.size();
|
||||||
serialization::writePod(os, count);
|
serialization::writePod(os, count);
|
||||||
@@ -42,9 +45,14 @@ void Page::serialize(std::ostream& os) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Page* Page::deserialize(std::istream& is) {
|
Page* Page::deserialize(std::istream& is) {
|
||||||
auto* page = new Page();
|
uint8_t version;
|
||||||
|
serialization::readPod(is, version);
|
||||||
|
if (version != PAGE_FILE_VERSION) {
|
||||||
|
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
serialization::readPod(is, page->nextY);
|
auto* page = new Page();
|
||||||
|
|
||||||
uint32_t count;
|
uint32_t count;
|
||||||
serialization::readPod(is, count);
|
serialization::readPod(is, count);
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ enum PageElementTag : uint8_t {
|
|||||||
// represents something that has been added to a page
|
// represents something that has been added to a page
|
||||||
class PageElement {
|
class PageElement {
|
||||||
public:
|
public:
|
||||||
|
int xPos;
|
||||||
int yPos;
|
int yPos;
|
||||||
explicit PageElement(const int yPos) : yPos(yPos) {}
|
explicit PageElement(const int xPos, const int yPos) : xPos(xPos), yPos(yPos) {}
|
||||||
virtual ~PageElement() = default;
|
virtual ~PageElement() = default;
|
||||||
virtual void render(EpdRenderer& renderer) = 0;
|
virtual void render(GfxRenderer& renderer, int fontId) = 0;
|
||||||
virtual void serialize(std::ostream& os) = 0;
|
virtual void serialize(std::ostream& os) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,24 +21,24 @@ class PageLine final : public PageElement {
|
|||||||
const TextBlock* block;
|
const TextBlock* block;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PageLine(const TextBlock* block, const int yPos) : PageElement(yPos), block(block) {}
|
PageLine(const TextBlock* block, const int xPos, const int yPos) : PageElement(xPos, yPos), block(block) {}
|
||||||
~PageLine() override { delete block; }
|
~PageLine() override { delete block; }
|
||||||
void render(EpdRenderer& renderer) override;
|
void render(GfxRenderer& renderer, int fontId) override;
|
||||||
void serialize(std::ostream& os) override;
|
void serialize(std::ostream& os) override;
|
||||||
static PageLine* deserialize(std::istream& is);
|
static PageLine* deserialize(std::istream& is);
|
||||||
};
|
};
|
||||||
|
|
||||||
class Page {
|
class Page {
|
||||||
public:
|
public:
|
||||||
int nextY = 0;
|
|
||||||
// the list of block index and line numbers on this page
|
|
||||||
std::vector<PageElement*> elements;
|
|
||||||
void render(EpdRenderer& renderer) const;
|
|
||||||
~Page() {
|
~Page() {
|
||||||
for (const auto element : elements) {
|
for (const auto element : elements) {
|
||||||
delete element;
|
delete element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the list of block index and line numbers on this page
|
||||||
|
std::vector<PageElement*> elements;
|
||||||
|
void render(GfxRenderer& renderer, int fontId) const;
|
||||||
void serialize(std::ostream& os) const;
|
void serialize(std::ostream& os) const;
|
||||||
static Page* deserialize(std::istream& is);
|
static Page* deserialize(std::istream& is);
|
||||||
};
|
};
|
||||||
|
|||||||
+56
-31
@@ -1,6 +1,6 @@
|
|||||||
#include "Section.h"
|
#include "Section.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -9,29 +9,37 @@
|
|||||||
#include "Page.h"
|
#include "Page.h"
|
||||||
#include "Serialization.h"
|
#include "Serialization.h"
|
||||||
|
|
||||||
constexpr uint8_t SECTION_FILE_VERSION = 2;
|
constexpr uint8_t SECTION_FILE_VERSION = 3;
|
||||||
|
|
||||||
void Section::onPageComplete(const Page* page) {
|
void Section::onPageComplete(const Page* page) {
|
||||||
Serial.printf("Page %d complete - free mem: %lu\n", pageCount, ESP.getFreeHeap());
|
|
||||||
|
|
||||||
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
||||||
|
|
||||||
std::ofstream outputFile("/sd" + filePath);
|
std::ofstream outputFile("/sd" + filePath);
|
||||||
page->serialize(outputFile);
|
page->serialize(outputFile);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [SCT] Page %d processed\n", millis(), pageCount);
|
||||||
|
|
||||||
pageCount++;
|
pageCount++;
|
||||||
delete page;
|
delete page;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Section::writeCacheMetadata() const {
|
void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
||||||
|
const int marginRight, const int marginBottom, const int marginLeft) const {
|
||||||
std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str());
|
std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str());
|
||||||
serialization::writePod(outputFile, SECTION_FILE_VERSION);
|
serialization::writePod(outputFile, SECTION_FILE_VERSION);
|
||||||
|
serialization::writePod(outputFile, fontId);
|
||||||
|
serialization::writePod(outputFile, lineCompression);
|
||||||
|
serialization::writePod(outputFile, marginTop);
|
||||||
|
serialization::writePod(outputFile, marginRight);
|
||||||
|
serialization::writePod(outputFile, marginBottom);
|
||||||
|
serialization::writePod(outputFile, marginLeft);
|
||||||
serialization::writePod(outputFile, pageCount);
|
serialization::writePod(outputFile, pageCount);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Section::loadCacheMetadata() {
|
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
||||||
|
const int marginRight, const int marginBottom, const int marginLeft) {
|
||||||
if (!SD.exists(cachePath.c_str())) {
|
if (!SD.exists(cachePath.c_str())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -42,17 +50,39 @@ bool Section::loadCacheMetadata() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::ifstream inputFile(("/sd" + sectionFilePath).c_str());
|
std::ifstream inputFile(("/sd" + sectionFilePath).c_str());
|
||||||
|
|
||||||
|
// Match parameters
|
||||||
|
{
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(inputFile, version);
|
serialization::readPod(inputFile, version);
|
||||||
if (version != SECTION_FILE_VERSION) {
|
if (version != SECTION_FILE_VERSION) {
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
SD.remove(sectionFilePath.c_str());
|
clearCache();
|
||||||
Serial.printf("Section state file: Unknown version %u\n", version);
|
Serial.printf("[%lu] [SCT] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft;
|
||||||
|
float fileLineCompression;
|
||||||
|
serialization::readPod(inputFile, fileFontId);
|
||||||
|
serialization::readPod(inputFile, fileLineCompression);
|
||||||
|
serialization::readPod(inputFile, fileMarginTop);
|
||||||
|
serialization::readPod(inputFile, fileMarginRight);
|
||||||
|
serialization::readPod(inputFile, fileMarginBottom);
|
||||||
|
serialization::readPod(inputFile, fileMarginLeft);
|
||||||
|
|
||||||
|
if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop ||
|
||||||
|
marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft) {
|
||||||
|
inputFile.close();
|
||||||
|
clearCache();
|
||||||
|
Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serialization::readPod(inputFile, pageCount);
|
serialization::readPod(inputFile, pageCount);
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
Serial.printf("Loaded cache: %d pages\n", pageCount);
|
Serial.printf("[%lu] [SCT] Deserialization succeeded: %d pages\n", millis(), pageCount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +93,8 @@ void Section::setupCacheDir() const {
|
|||||||
|
|
||||||
void Section::clearCache() const { SD.rmdir(cachePath.c_str()); }
|
void Section::clearCache() const { SD.rmdir(cachePath.c_str()); }
|
||||||
|
|
||||||
bool Section::persistPageDataToSD() {
|
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop,
|
||||||
|
const int marginRight, const int marginBottom, const int marginLeft) {
|
||||||
const auto localPath = epub->getSpineItem(spineIndex);
|
const auto localPath = epub->getSpineItem(spineIndex);
|
||||||
|
|
||||||
// TODO: Should we get rid of this file all together?
|
// TODO: Should we get rid of this file all together?
|
||||||
@@ -75,44 +106,38 @@ bool Section::persistPageDataToSD() {
|
|||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.println("Failed to stream item contents");
|
Serial.printf("[%lu] [SCT] Failed to stream item contents to temp file\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("Streamed HTML to %s\n", tmpHtmlPath.c_str());
|
Serial.printf("[%lu] [SCT] Streamed temp HTML to %s\n", millis(), tmpHtmlPath.c_str());
|
||||||
|
|
||||||
const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath;
|
const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath;
|
||||||
|
|
||||||
auto visitor =
|
auto visitor = EpubHtmlParserSlim(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight,
|
||||||
EpubHtmlParserSlim(sdTmpHtmlPath.c_str(), renderer, [this](const Page* page) { this->onPageComplete(page); });
|
marginBottom, marginLeft, [this](const Page* page) { this->onPageComplete(page); });
|
||||||
success = visitor.parseAndBuildPages();
|
success = visitor.parseAndBuildPages();
|
||||||
|
|
||||||
SD.remove(tmpHtmlPath.c_str());
|
SD.remove(tmpHtmlPath.c_str());
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.println("Failed to parse and build pages");
|
Serial.printf("[%lu] [SCT] Failed to parse XML and build pages\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeCacheMetadata();
|
writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Section::renderPage() const {
|
Page* Section::loadPageFromSD() const {
|
||||||
if (0 <= currentPage && currentPage < pageCount) {
|
|
||||||
const auto filePath = "/sd" + cachePath + "/page_" + std::to_string(currentPage) + ".bin";
|
const auto filePath = "/sd" + cachePath + "/page_" + std::to_string(currentPage) + ".bin";
|
||||||
std::ifstream inputFile(filePath);
|
if (!SD.exists(filePath.c_str() + 3)) {
|
||||||
const Page* p = Page::deserialize(inputFile);
|
Serial.printf("[%lu] [SCT] Page file does not exist: %s\n", millis(), filePath.c_str());
|
||||||
inputFile.close();
|
return nullptr;
|
||||||
p->render(renderer);
|
|
||||||
delete p;
|
|
||||||
} else if (pageCount == 0) {
|
|
||||||
Serial.println("No pages to render");
|
|
||||||
const int width = renderer.getTextWidth("Empty chapter", BOLD);
|
|
||||||
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Empty chapter", 1, BOLD);
|
|
||||||
} else {
|
|
||||||
Serial.printf("Page out of bounds: %d (max %d)\n", currentPage, pageCount);
|
|
||||||
const int width = renderer.getTextWidth("Out of bounds", BOLD);
|
|
||||||
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Out of bounds", 1, BOLD);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::ifstream inputFile(filePath);
|
||||||
|
Page* p = Page::deserialize(inputFile);
|
||||||
|
inputFile.close();
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-7
@@ -2,29 +2,32 @@
|
|||||||
#include "Epub.h"
|
#include "Epub.h"
|
||||||
|
|
||||||
class Page;
|
class Page;
|
||||||
class EpdRenderer;
|
class GfxRenderer;
|
||||||
|
|
||||||
class Section {
|
class Section {
|
||||||
Epub* epub;
|
Epub* epub;
|
||||||
const int spineIndex;
|
const int spineIndex;
|
||||||
EpdRenderer& renderer;
|
GfxRenderer& renderer;
|
||||||
std::string cachePath;
|
std::string cachePath;
|
||||||
|
|
||||||
|
void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||||
|
int marginLeft) const;
|
||||||
void onPageComplete(const Page* page);
|
void onPageComplete(const Page* page);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
int pageCount = 0;
|
int pageCount = 0;
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
|
|
||||||
explicit Section(Epub* epub, const int spineIndex, EpdRenderer& renderer)
|
explicit Section(Epub* epub, const int spineIndex, GfxRenderer& renderer)
|
||||||
: epub(epub), spineIndex(spineIndex), renderer(renderer) {
|
: epub(epub), spineIndex(spineIndex), renderer(renderer) {
|
||||||
cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex);
|
cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex);
|
||||||
}
|
}
|
||||||
~Section() = default;
|
~Section() = default;
|
||||||
void writeCacheMetadata() const;
|
bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||||
bool loadCacheMetadata();
|
int marginLeft);
|
||||||
void setupCacheDir() const;
|
void setupCacheDir() const;
|
||||||
void clearCache() const;
|
void clearCache() const;
|
||||||
bool persistPageDataToSD();
|
bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||||
void renderPage() const;
|
int marginLeft);
|
||||||
|
Page* loadPageFromSD() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
class EpdRenderer;
|
class GfxRenderer;
|
||||||
|
|
||||||
typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
|
typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
|
|||||||
class Block {
|
class Block {
|
||||||
public:
|
public:
|
||||||
virtual ~Block() = default;
|
virtual ~Block() = default;
|
||||||
virtual void layout(EpdRenderer& renderer) = 0;
|
virtual void layout(GfxRenderer& renderer) = 0;
|
||||||
virtual BlockType getType() = 0;
|
virtual BlockType getType() = 0;
|
||||||
virtual bool isEmpty() = 0;
|
virtual bool isEmpty() = 0;
|
||||||
virtual void finish() {}
|
virtual void finish() {}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "TextBlock.h"
|
#include "TextBlock.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
void TextBlock::addWord(const std::string& word, const bool is_bold, const bool is_italic) {
|
void TextBlock::addWord(const std::string& word, const bool is_bold, const bool is_italic) {
|
||||||
@@ -10,10 +10,11 @@ void TextBlock::addWord(const std::string& word, const bool is_bold, const bool
|
|||||||
wordStyles.push_back((is_bold ? BOLD_SPAN : 0) | (is_italic ? ITALIC_SPAN : 0));
|
wordStyles.push_back((is_bold ? BOLD_SPAN : 0) | (is_italic ? ITALIC_SPAN : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
|
std::list<TextBlock*> TextBlock::splitIntoLines(const GfxRenderer& renderer, const int fontId,
|
||||||
|
const int horizontalMargin) {
|
||||||
const int totalWordCount = words.size();
|
const int totalWordCount = words.size();
|
||||||
const int pageWidth = renderer.getPageWidth();
|
const int pageWidth = GfxRenderer::getScreenWidth() - horizontalMargin;
|
||||||
const int spaceWidth = renderer.getSpaceWidth();
|
const int spaceWidth = renderer.getSpaceWidth(fontId);
|
||||||
|
|
||||||
words.shrink_to_fit();
|
words.shrink_to_fit();
|
||||||
wordStyles.shrink_to_fit();
|
wordStyles.shrink_to_fit();
|
||||||
@@ -21,7 +22,7 @@ std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
|
|||||||
|
|
||||||
// measure each word
|
// measure each word
|
||||||
uint16_t wordWidths[totalWordCount];
|
uint16_t wordWidths[totalWordCount];
|
||||||
for (int i = 0; i < words.size(); i++) {
|
for (int i = 0; i < totalWordCount; i++) {
|
||||||
// measure the word
|
// measure the word
|
||||||
EpdFontStyle fontStyle = REGULAR;
|
EpdFontStyle fontStyle = REGULAR;
|
||||||
if (wordStyles[i] & BOLD_SPAN) {
|
if (wordStyles[i] & BOLD_SPAN) {
|
||||||
@@ -33,7 +34,7 @@ std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
|
|||||||
} else if (wordStyles[i] & ITALIC_SPAN) {
|
} else if (wordStyles[i] & ITALIC_SPAN) {
|
||||||
fontStyle = ITALIC;
|
fontStyle = ITALIC;
|
||||||
}
|
}
|
||||||
const int width = renderer.getTextWidth(words[i].c_str(), fontStyle);
|
const int width = renderer.getTextWidth(fontId, words[i].c_str(), fontStyle);
|
||||||
wordWidths[i] = width;
|
wordWidths[i] = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +155,7 @@ std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBlock::render(const EpdRenderer& renderer, const int x, const int y) const {
|
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
||||||
for (int i = 0; i < words.size(); i++) {
|
for (int i = 0; i < words.size(); i++) {
|
||||||
// render the word
|
// render the word
|
||||||
EpdFontStyle fontStyle = REGULAR;
|
EpdFontStyle fontStyle = REGULAR;
|
||||||
@@ -165,7 +166,7 @@ void TextBlock::render(const EpdRenderer& renderer, const int x, const int y) co
|
|||||||
} else if (wordStyles[i] & ITALIC_SPAN) {
|
} else if (wordStyles[i] & ITALIC_SPAN) {
|
||||||
fontStyle = ITALIC;
|
fontStyle = ITALIC;
|
||||||
}
|
}
|
||||||
renderer.drawText(x + wordXpos[i], y, words[i].c_str(), 1, fontStyle);
|
renderer.drawText(fontId, x + wordXpos[i], y, words[i].c_str(), true, fontStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ class TextBlock final : public Block {
|
|||||||
void setStyle(const BLOCK_STYLE style) { this->style = style; }
|
void setStyle(const BLOCK_STYLE style) { this->style = style; }
|
||||||
BLOCK_STYLE getStyle() const { return style; }
|
BLOCK_STYLE getStyle() const { return style; }
|
||||||
bool isEmpty() override { return words.empty(); }
|
bool isEmpty() override { return words.empty(); }
|
||||||
void layout(EpdRenderer& renderer) override {};
|
void layout(GfxRenderer& renderer) override {};
|
||||||
// given a renderer works out where to break the words into lines
|
// given a renderer works out where to break the words into lines
|
||||||
std::list<TextBlock*> splitIntoLines(const EpdRenderer& renderer);
|
std::list<TextBlock*> splitIntoLines(const GfxRenderer& renderer, int fontId, int horizontalMargin);
|
||||||
void render(const EpdRenderer& renderer, int x, int y) const;
|
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
|
||||||
BlockType getType() override { return TEXT_BLOCK; }
|
BlockType getType() override { return TEXT_BLOCK; }
|
||||||
void serialize(std::ostream& os) const;
|
void serialize(std::ostream& os) const;
|
||||||
static TextBlock* deserialize(std::istream& is);
|
static TextBlock* deserialize(std::istream& is);
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
#include "GfxRenderer.h"
|
||||||
|
|
||||||
|
#include <Utf8.h>
|
||||||
|
|
||||||
|
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
||||||
|
|
||||||
|
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
||||||
|
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||||
|
|
||||||
|
// Early return if no framebuffer is set
|
||||||
|
if (!frameBuffer) {
|
||||||
|
Serial.printf("[%lu] [GFX] !! No framebuffer\n", millis());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate coordinates: portrait (480x800) -> landscape (800x480)
|
||||||
|
// Rotation: 90 degrees clockwise
|
||||||
|
const int rotatedX = y;
|
||||||
|
const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
||||||
|
|
||||||
|
// Bounds checking (portrait: 480x800)
|
||||||
|
if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 ||
|
||||||
|
rotatedY >= EInkDisplay::DISPLAY_HEIGHT) {
|
||||||
|
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d)\n", millis(), x, y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate byte position and bit position
|
||||||
|
const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
|
||||||
|
const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit
|
||||||
|
} else {
|
||||||
|
frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GfxRenderer::getTextWidth(const int fontId, const char* text, const EpdFontStyle style) const {
|
||||||
|
if (fontMap.count(fontId) == 0) {
|
||||||
|
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = 0, h = 0;
|
||||||
|
fontMap.at(fontId).getTextDimensions(text, &w, &h, style);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawCenteredText(const int fontId, const int y, const char* text, const bool black,
|
||||||
|
const EpdFontStyle style) const {
|
||||||
|
const int x = (getScreenWidth() - getTextWidth(fontId, text, style)) / 2;
|
||||||
|
drawText(fontId, x, y, text, black, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black,
|
||||||
|
const EpdFontStyle style) const {
|
||||||
|
const int yPos = y + getLineHeight(fontId);
|
||||||
|
int xpos = x;
|
||||||
|
|
||||||
|
// cannot draw a NULL / empty string
|
||||||
|
if (text == nullptr || *text == '\0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontMap.count(fontId) == 0) {
|
||||||
|
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto font = fontMap.at(fontId);
|
||||||
|
|
||||||
|
// no printable characters
|
||||||
|
if (!font.hasPrintableChars(text, style)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cp;
|
||||||
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||||
|
renderChar(font, cp, &xpos, &yPos, black, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const {
|
||||||
|
if (x1 == x2) {
|
||||||
|
if (y2 < y1) {
|
||||||
|
std::swap(y1, y2);
|
||||||
|
}
|
||||||
|
for (int y = y1; y <= y2; y++) {
|
||||||
|
drawPixel(x1, y, state);
|
||||||
|
}
|
||||||
|
} else if (y1 == y2) {
|
||||||
|
if (x2 < x1) {
|
||||||
|
std::swap(x1, x2);
|
||||||
|
}
|
||||||
|
for (int x = x1; x <= x2; x++) {
|
||||||
|
drawPixel(x, y1, state);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: Implement
|
||||||
|
Serial.printf("[%lu] [GFX] Line drawing not supported\n", millis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||||
|
drawLine(x, y, x + width - 1, y, state);
|
||||||
|
drawLine(x + width - 1, y, x + width - 1, y + height - 1, state);
|
||||||
|
drawLine(x + width - 1, y + height - 1, x, y + height - 1, state);
|
||||||
|
drawLine(x, y, x, y + height - 1, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||||
|
for (int fillY = y; fillY < y + height; fillY++) {
|
||||||
|
drawLine(x, fillY, x + width - 1, fillY, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
||||||
|
// Flip X and Y for portrait mode
|
||||||
|
einkDisplay.drawImage(bitmap, y, x, height, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); }
|
||||||
|
|
||||||
|
void GfxRenderer::invertScreen() const {
|
||||||
|
uint8_t* buffer = einkDisplay.getFrameBuffer();
|
||||||
|
for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) {
|
||||||
|
buffer[i] = ~buffer[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::displayBuffer(const EInkDisplay::RefreshMode refreshMode) const {
|
||||||
|
einkDisplay.displayBuffer(refreshMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support partial window update
|
||||||
|
// void GfxRenderer::flushArea(const int x, const int y, const int width, const int height) const {
|
||||||
|
// const int rotatedX = y;
|
||||||
|
// const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
||||||
|
//
|
||||||
|
// einkDisplay.displayBuffer(EInkDisplay::FAST_REFRESH, rotatedX, rotatedY, height, width);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Note: Internal driver treats screen in command orientation, this library treats in portrait orientation
|
||||||
|
int GfxRenderer::getScreenWidth() { return EInkDisplay::DISPLAY_HEIGHT; }
|
||||||
|
int GfxRenderer::getScreenHeight() { return EInkDisplay::DISPLAY_WIDTH; }
|
||||||
|
|
||||||
|
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
||||||
|
if (fontMap.count(fontId) == 0) {
|
||||||
|
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fontMap.at(fontId).getGlyph(' ', REGULAR)->advanceX;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GfxRenderer::getLineHeight(const int fontId) const {
|
||||||
|
if (fontMap.count(fontId) == 0) {
|
||||||
|
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fontMap.at(fontId).getData(REGULAR)->advanceY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::swapBuffers() const { einkDisplay.swapBuffers(); }
|
||||||
|
|
||||||
|
void GfxRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); }
|
||||||
|
|
||||||
|
void GfxRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); }
|
||||||
|
|
||||||
|
void GfxRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }
|
||||||
|
|
||||||
|
void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y,
|
||||||
|
const bool pixelState, const EpdFontStyle style) const {
|
||||||
|
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
|
||||||
|
if (!glyph) {
|
||||||
|
// TODO: Replace with fallback glyph property?
|
||||||
|
glyph = fontFamily.getGlyph('?', style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no glyph?
|
||||||
|
if (!glyph) {
|
||||||
|
Serial.printf("[%lu] [GFX] No glyph for codepoint %d\n", millis(), cp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int is2Bit = fontFamily.getData(style)->is2Bit;
|
||||||
|
const uint32_t offset = glyph->dataOffset;
|
||||||
|
const uint8_t width = glyph->width;
|
||||||
|
const uint8_t height = glyph->height;
|
||||||
|
const int left = glyph->left;
|
||||||
|
|
||||||
|
const uint8_t* bitmap = nullptr;
|
||||||
|
bitmap = &fontFamily.getData(style)->bitmap[offset];
|
||||||
|
|
||||||
|
if (bitmap != nullptr) {
|
||||||
|
for (int glyphY = 0; glyphY < height; glyphY++) {
|
||||||
|
const int screenY = *y - glyph->top + glyphY;
|
||||||
|
for (int glyphX = 0; glyphX < width; glyphX++) {
|
||||||
|
const int pixelPosition = glyphY * width + glyphX;
|
||||||
|
const int screenX = *x + left + glyphX;
|
||||||
|
|
||||||
|
if (is2Bit) {
|
||||||
|
const uint8_t byte = bitmap[pixelPosition / 4];
|
||||||
|
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
|
||||||
|
|
||||||
|
const uint8_t val = (byte >> bit_index) & 0x3;
|
||||||
|
if (fontRenderMode == BW && val > 0) {
|
||||||
|
drawPixel(screenX, screenY, pixelState);
|
||||||
|
} else if (fontRenderMode == GRAYSCALE_MSB && val == 1) {
|
||||||
|
// TODO: Not sure how this anti-aliasing goes on black backgrounds
|
||||||
|
drawPixel(screenX, screenY, false);
|
||||||
|
} else if (fontRenderMode == GRAYSCALE_LSB && val == 2) {
|
||||||
|
drawPixel(screenX, screenY, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const uint8_t byte = bitmap[pixelPosition / 8];
|
||||||
|
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
||||||
|
|
||||||
|
if ((byte >> bit_index) & 1) {
|
||||||
|
drawPixel(screenX, screenY, pixelState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*x += glyph->advanceX;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <EInkDisplay.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "EpdFontFamily.h"
|
||||||
|
|
||||||
|
class GfxRenderer {
|
||||||
|
public:
|
||||||
|
enum FontRenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
||||||
|
|
||||||
|
private:
|
||||||
|
EInkDisplay& einkDisplay;
|
||||||
|
FontRenderMode fontRenderMode;
|
||||||
|
std::map<int, EpdFontFamily> fontMap;
|
||||||
|
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
||||||
|
EpdFontStyle style) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), fontRenderMode(BW) {}
|
||||||
|
~GfxRenderer() = default;
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
void insertFont(int fontId, EpdFontFamily font);
|
||||||
|
|
||||||
|
// Screen ops
|
||||||
|
static int getScreenWidth();
|
||||||
|
static int getScreenHeight();
|
||||||
|
void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
||||||
|
void invertScreen() const;
|
||||||
|
void clearScreen(uint8_t color = 0xFF) const;
|
||||||
|
|
||||||
|
// Drawing
|
||||||
|
void drawPixel(int x, int y, bool state = true) const;
|
||||||
|
void drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
|
||||||
|
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
||||||
|
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
||||||
|
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||||
|
|
||||||
|
// Text
|
||||||
|
int getTextWidth(int fontId, const char* text, EpdFontStyle style = REGULAR) const;
|
||||||
|
void drawCenteredText(int fontId, int y, const char* text, bool black = true, EpdFontStyle style = REGULAR) const;
|
||||||
|
void drawText(int fontId, int x, int y, const char* text, bool black = true, EpdFontStyle style = REGULAR) const;
|
||||||
|
void setFontRenderMode(const FontRenderMode mode) { this->fontRenderMode = mode; }
|
||||||
|
int getSpaceWidth(int fontId) const;
|
||||||
|
int getLineHeight(int fontId) const;
|
||||||
|
|
||||||
|
// Low level functions
|
||||||
|
void swapBuffers() const;
|
||||||
|
void copyGrayscaleLsbBuffers() const;
|
||||||
|
void copyGrayscaleMsbBuffers() const;
|
||||||
|
void displayGrayBuffer() const;
|
||||||
|
};
|
||||||
+24
-24
@@ -7,7 +7,7 @@ bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t*
|
|||||||
// Setup inflator
|
// Setup inflator
|
||||||
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||||
if (!inflator) {
|
if (!inflator) {
|
||||||
Serial.println("Failed to allocate memory for inflator");
|
Serial.printf("[%lu] [ZIP] Failed to allocate memory for inflator\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
memset(inflator, 0, sizeof(tinfl_decompressor));
|
memset(inflator, 0, sizeof(tinfl_decompressor));
|
||||||
@@ -20,7 +20,7 @@ bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t*
|
|||||||
free(inflator);
|
free(inflator);
|
||||||
|
|
||||||
if (status != TINFL_STATUS_DONE) {
|
if (status != TINFL_STATUS_DONE) {
|
||||||
Serial.printf("tinfl_decompress() failed with status %d\n", status);
|
Serial.printf("[%lu] [ZIP] tinfl_decompress() failed with status %d\n", millis(), status);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,20 +32,22 @@ bool ZipFile::loadFileStat(const char* filename, mz_zip_archive_file_stat* fileS
|
|||||||
const bool status = mz_zip_reader_init_file(&zipArchive, filePath.c_str(), 0);
|
const bool status = mz_zip_reader_init_file(&zipArchive, filePath.c_str(), 0);
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
Serial.printf("mz_zip_reader_init_file() failed!\nError %s\n", mz_zip_get_error_string(zipArchive.m_last_error));
|
Serial.printf("[%lu] [ZIP] mz_zip_reader_init_file() failed! Error: %s\n", millis(),
|
||||||
|
mz_zip_get_error_string(zipArchive.m_last_error));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the file
|
// find the file
|
||||||
mz_uint32 fileIndex = 0;
|
mz_uint32 fileIndex = 0;
|
||||||
if (!mz_zip_reader_locate_file_v2(&zipArchive, filename, nullptr, 0, &fileIndex)) {
|
if (!mz_zip_reader_locate_file_v2(&zipArchive, filename, nullptr, 0, &fileIndex)) {
|
||||||
Serial.printf("Could not find file %s\n", filename);
|
Serial.printf("[%lu] [ZIP] Could not find file %s\n", millis, filename);
|
||||||
mz_zip_reader_end(&zipArchive);
|
mz_zip_reader_end(&zipArchive);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mz_zip_reader_file_stat(&zipArchive, fileIndex, fileStat)) {
|
if (!mz_zip_reader_file_stat(&zipArchive, fileIndex, fileStat)) {
|
||||||
Serial.printf("mz_zip_reader_file_stat() failed!\nError %s\n", mz_zip_get_error_string(zipArchive.m_last_error));
|
Serial.printf("[%lu] [ZIP] mz_zip_reader_file_stat() failed! Error: %s\n", millis(),
|
||||||
|
mz_zip_get_error_string(zipArchive.m_last_error));
|
||||||
mz_zip_reader_end(&zipArchive);
|
mz_zip_reader_end(&zipArchive);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -65,13 +67,13 @@ long ZipFile::getDataOffset(const mz_zip_archive_file_stat& fileStat) const {
|
|||||||
fclose(file);
|
fclose(file);
|
||||||
|
|
||||||
if (read != localHeaderSize) {
|
if (read != localHeaderSize) {
|
||||||
Serial.println("Something went wrong reading the local header");
|
Serial.printf("[%lu] [ZIP] Something went wrong reading the local header\n", millis());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pLocalHeader[0] + (pLocalHeader[1] << 8) + (pLocalHeader[2] << 16) + (pLocalHeader[3] << 24) !=
|
if (pLocalHeader[0] + (pLocalHeader[1] << 8) + (pLocalHeader[2] << 16) + (pLocalHeader[3] << 24) !=
|
||||||
0x04034b50 /* MZ_ZIP_LOCAL_DIR_HEADER_SIG */) {
|
0x04034b50 /* MZ_ZIP_LOCAL_DIR_HEADER_SIG */) {
|
||||||
Serial.println("Not a valid zip file header");
|
Serial.printf("[%lu] [ZIP] Not a valid zip file header\n", millis());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +107,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
fclose(file);
|
fclose(file);
|
||||||
|
|
||||||
if (dataRead != inflatedDataSize) {
|
if (dataRead != inflatedDataSize) {
|
||||||
Serial.println("Failed to read data");
|
Serial.printf("[%lu] [ZIP] Failed to read data\n", millis());
|
||||||
free(data);
|
free(data);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -115,7 +117,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
// Read out deflated content from file
|
// Read out deflated content from file
|
||||||
const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize));
|
const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize));
|
||||||
if (deflatedData == nullptr) {
|
if (deflatedData == nullptr) {
|
||||||
Serial.println("Failed to allocate memory for decompression buffer");
|
Serial.printf("[%lu] [ZIP] Failed to allocate memory for decompression buffer\n", millis());
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -124,7 +126,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
fclose(file);
|
fclose(file);
|
||||||
|
|
||||||
if (dataRead != deflatedDataSize) {
|
if (dataRead != deflatedDataSize) {
|
||||||
Serial.printf("Failed to read data, expected %d got %d\n", deflatedDataSize, dataRead);
|
Serial.printf("[%lu] [ZIP] Failed to read data, expected %d got %d\n", millis(), deflatedDataSize, dataRead);
|
||||||
free(deflatedData);
|
free(deflatedData);
|
||||||
free(data);
|
free(data);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -134,14 +136,14 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
free(deflatedData);
|
free(deflatedData);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.println("Failed to inflate file");
|
Serial.printf("[%lu] [ZIP] Failed to inflate file\n", millis());
|
||||||
free(data);
|
free(data);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue out of block with data set
|
// Continue out of block with data set
|
||||||
} else {
|
} else {
|
||||||
Serial.println("Unsupported compression method");
|
Serial.printf("[%lu] [ZIP] Unsupported compression method\n", millis());
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -172,7 +174,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
// no deflation, just read content
|
// no deflation, just read content
|
||||||
const auto buffer = static_cast<uint8_t*>(malloc(chunkSize));
|
const auto buffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
Serial.println("Failed to allocate memory for buffer");
|
Serial.printf("[%lu] [ZIP] Failed to allocate memory for buffer\n", millis());
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -181,7 +183,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
const size_t dataRead = fread(buffer, 1, remaining < chunkSize ? remaining : chunkSize, file);
|
const size_t dataRead = fread(buffer, 1, remaining < chunkSize ? remaining : chunkSize, file);
|
||||||
if (dataRead == 0) {
|
if (dataRead == 0) {
|
||||||
Serial.println("Could not read more bytes");
|
Serial.printf("[%lu] [ZIP] Could not read more bytes\n", millis());
|
||||||
free(buffer);
|
free(buffer);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return false;
|
return false;
|
||||||
@@ -200,7 +202,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
// Setup inflator
|
// Setup inflator
|
||||||
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||||
if (!inflator) {
|
if (!inflator) {
|
||||||
Serial.println("Failed to allocate memory for inflator");
|
Serial.printf("[%lu] [ZIP] Failed to allocate memory for inflator\n", millis());
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -210,7 +212,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
// Setup file read buffer
|
// Setup file read buffer
|
||||||
const auto fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
const auto fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||||
if (!fileReadBuffer) {
|
if (!fileReadBuffer) {
|
||||||
Serial.println("Failed to allocate memory for zip file read buffer");
|
Serial.printf("[%lu] [ZIP] Failed to allocate memory for zip file read buffer\n", millis());
|
||||||
free(inflator);
|
free(inflator);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return false;
|
return false;
|
||||||
@@ -218,7 +220,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
|
|
||||||
const auto outputBuffer = static_cast<uint8_t*>(malloc(TINFL_LZ_DICT_SIZE));
|
const auto outputBuffer = static_cast<uint8_t*>(malloc(TINFL_LZ_DICT_SIZE));
|
||||||
if (!outputBuffer) {
|
if (!outputBuffer) {
|
||||||
Serial.println("Failed to allocate memory for dictionary");
|
Serial.printf("[%lu] [ZIP] Failed to allocate memory for dictionary\n", millis());
|
||||||
free(inflator);
|
free(inflator);
|
||||||
free(fileReadBuffer);
|
free(fileReadBuffer);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
@@ -271,11 +273,8 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
outputCursor = (outputCursor + outBytes) & (TINFL_LZ_DICT_SIZE - 1);
|
outputCursor = (outputCursor + outBytes) & (TINFL_LZ_DICT_SIZE - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("Decompressing - %d/%d deflated into %d/%d inflated\n", deflatedDataSize - fileRemainingBytes,
|
|
||||||
deflatedDataSize, processedOutputBytes, inflatedDataSize);
|
|
||||||
|
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
Serial.printf("tinfl_decompress() failed with status %d\n", status);
|
Serial.printf("[%lu] [ZIP] tinfl_decompress() failed with status %d\n", millis(), status);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
free(outputBuffer);
|
free(outputBuffer);
|
||||||
free(fileReadBuffer);
|
free(fileReadBuffer);
|
||||||
@@ -284,7 +283,8 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status == TINFL_STATUS_DONE) {
|
if (status == TINFL_STATUS_DONE) {
|
||||||
Serial.println("Decompression finished");
|
Serial.printf("[%lu] [ZIP] Decompressed %d bytes into %d bytes\n", millis(), deflatedDataSize,
|
||||||
|
inflatedDataSize);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
free(inflator);
|
free(inflator);
|
||||||
free(fileReadBuffer);
|
free(fileReadBuffer);
|
||||||
@@ -294,7 +294,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we get here, EOF reached without TINFL_STATUS_DONE
|
// If we get here, EOF reached without TINFL_STATUS_DONE
|
||||||
Serial.println("Unexpected EOF");
|
Serial.printf("[%lu] [ZIP] Unexpected EOF\n", millis());
|
||||||
fclose(file);
|
fclose(file);
|
||||||
free(outputBuffer);
|
free(outputBuffer);
|
||||||
free(fileReadBuffer);
|
free(fileReadBuffer);
|
||||||
@@ -302,6 +302,6 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.println("Unsupported compression method");
|
Serial.printf("[%lu] [ZIP] Unsupported compression method\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
Submodule open-x4-sdk updated: 8224d278c5...a126d4b0bf
+25
-19
@@ -1,14 +1,8 @@
|
|||||||
; PlatformIO Project Configuration File
|
[platformio]
|
||||||
;
|
crosspoint_version = 0.3.0
|
||||||
; Build options: build flags, source filter
|
default_envs = default
|
||||||
; Upload options: custom upload port, speed and extra flags
|
|
||||||
; Library options: dependencies, extra library storages
|
|
||||||
; Advanced options: extra scripting
|
|
||||||
;
|
|
||||||
; Please visit documentation for the other options and examples
|
|
||||||
; https://docs.platformio.org/page/projectconf.html
|
|
||||||
|
|
||||||
[env:esp32-c3-devkitm-1]
|
[base]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = esp32-c3-devkitm-1
|
board = esp32-c3-devkitm-1
|
||||||
framework = arduino
|
framework = arduino
|
||||||
@@ -19,22 +13,34 @@ board_upload.flash_size = 16MB
|
|||||||
board_upload.maximum_size = 16777216
|
board_upload.maximum_size = 16777216
|
||||||
board_upload.offset_address = 0x10000
|
board_upload.offset_address = 0x10000
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
-DARDUINO_USB_MODE=1
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1
|
||||||
|
# https://libexpat.github.io/doc/api/latest/#XML_GE
|
||||||
|
-DXML_GE=0
|
||||||
|
-DXML_CONTEXT_BYTES=1024
|
||||||
|
|
||||||
; Board configuration
|
; Board configuration
|
||||||
board_build.flash_mode = dio
|
board_build.flash_mode = dio
|
||||||
board_build.flash_size = 16MB
|
board_build.flash_size = 16MB
|
||||||
board_build.partitions = partitions.csv
|
board_build.partitions = partitions.csv
|
||||||
|
|
||||||
build_flags =
|
|
||||||
-DARDUINO_USB_MODE=1
|
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
|
||||||
-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1
|
|
||||||
# https://libexpat.github.io/doc/api/latest/#XML_GE
|
|
||||||
-DXML_GE=0
|
|
||||||
-DXML_CONTEXT_BYTES=1024
|
|
||||||
|
|
||||||
; Libraries
|
; Libraries
|
||||||
lib_deps =
|
lib_deps =
|
||||||
zinggjm/GxEPD2@^1.6.5
|
|
||||||
https://github.com/leethomason/tinyxml2.git#11.0.0
|
https://github.com/leethomason/tinyxml2.git#11.0.0
|
||||||
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor
|
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor
|
||||||
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
|
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
|
||||||
|
EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay
|
||||||
|
|
||||||
|
[env:default]
|
||||||
|
extends = base
|
||||||
|
build_flags =
|
||||||
|
${base.build_flags}
|
||||||
|
-DCROSSPOINT_VERSION=\"${platformio.crosspoint_version}-dev\"
|
||||||
|
|
||||||
|
[env:gh_release]
|
||||||
|
extends = base
|
||||||
|
build_flags =
|
||||||
|
${base.build_flags}
|
||||||
|
-DCROSSPOINT_VERSION=\"${platformio.crosspoint_version}\"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ bool CrossPointState::loadFromFile() {
|
|||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(inputFile, version);
|
serialization::readPod(inputFile, version);
|
||||||
if (version != STATE_FILE_VERSION) {
|
if (version != STATE_FILE_VERSION) {
|
||||||
Serial.printf("CrossPointState: Unknown version %u\n", version);
|
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated with:
|
||||||
|
* ruby -rdigest -e 'puts [
|
||||||
|
* "./lib/EpdFont/builtinFonts/bookerly_2b.h",
|
||||||
|
* "./lib/EpdFont/builtinFonts/bookerly_bold_2b.h",
|
||||||
|
* "./lib/EpdFont/builtinFonts/bookerly_bold_italic_2b.h",
|
||||||
|
* "./lib/EpdFont/builtinFonts/bookerly_italic_2b.h",
|
||||||
|
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
|
*/
|
||||||
|
#define READER_FONT_ID 1747632454
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated with:
|
||||||
|
* ruby -rdigest -e 'puts [
|
||||||
|
* "./lib/EpdFont/builtinFonts/ubuntu_10.h",
|
||||||
|
* "./lib/EpdFont/builtinFonts/ubuntu_bold_10.h",
|
||||||
|
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
|
*/
|
||||||
|
#define UI_FONT_ID 225955604
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated with:
|
||||||
|
* ruby -rdigest -e 'puts [
|
||||||
|
* "./lib/EpdFont/builtinFonts/babyblue.h",
|
||||||
|
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
|
*/
|
||||||
|
#define SMALL_FONT_ID 141891058
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
extern const uint8_t CrossLarge[] = {
|
static const uint8_t CrossLarge[] = {
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF,
|
0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF,
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+47
-25
@@ -1,13 +1,21 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <EpdRenderer.h>
|
#include <EInkDisplay.h>
|
||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
#include <GxEPD2_BW.h>
|
#include <GfxRenderer.h>
|
||||||
#include <InputManager.h>
|
#include <InputManager.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
|
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
|
#include "builtinFonts/babyblue.h"
|
||||||
|
#include "builtinFonts/bookerly_2b.h"
|
||||||
|
#include "builtinFonts/bookerly_bold_2b.h"
|
||||||
|
#include "builtinFonts/bookerly_bold_italic_2b.h"
|
||||||
|
#include "builtinFonts/bookerly_italic_2b.h"
|
||||||
|
#include "builtinFonts/ubuntu_10.h"
|
||||||
|
#include "builtinFonts/ubuntu_bold_10.h"
|
||||||
|
#include "config.h"
|
||||||
#include "screens/BootLogoScreen.h"
|
#include "screens/BootLogoScreen.h"
|
||||||
#include "screens/EpubReaderScreen.h"
|
#include "screens/EpubReaderScreen.h"
|
||||||
#include "screens/FileSelectionScreen.h"
|
#include "screens/FileSelectionScreen.h"
|
||||||
@@ -28,13 +36,26 @@
|
|||||||
#define SD_SPI_CS 12
|
#define SD_SPI_CS 12
|
||||||
#define SD_SPI_MISO 7
|
#define SD_SPI_MISO 7
|
||||||
|
|
||||||
GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT> display(GxEPD2_426_GDEQ0426T82(EPD_CS, EPD_DC,
|
EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
|
||||||
EPD_RST, EPD_BUSY));
|
|
||||||
InputManager inputManager;
|
InputManager inputManager;
|
||||||
EpdRenderer renderer(display);
|
GfxRenderer renderer(einkDisplay);
|
||||||
Screen* currentScreen;
|
Screen* currentScreen;
|
||||||
CrossPointState appState;
|
CrossPointState appState;
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
EpdFont bookerlyFont(&bookerly_2b);
|
||||||
|
EpdFont bookerlyBoldFont(&bookerly_bold_2b);
|
||||||
|
EpdFont bookerlyItalicFont(&bookerly_italic_2b);
|
||||||
|
EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic_2b);
|
||||||
|
EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont);
|
||||||
|
|
||||||
|
EpdFont smallFont(&babyblue);
|
||||||
|
EpdFontFamily smallFontFamily(&smallFont);
|
||||||
|
|
||||||
|
EpdFont ubuntu10Font(&ubuntu_10);
|
||||||
|
EpdFont ubuntuBold10Font(&ubuntu_bold_10);
|
||||||
|
EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ubuntuBold10Font);
|
||||||
|
|
||||||
// Power button timing
|
// Power button timing
|
||||||
// Time required to confirm boot from sleep
|
// Time required to confirm boot from sleep
|
||||||
constexpr unsigned long POWER_BUTTON_WAKEUP_MS = 1000;
|
constexpr unsigned long POWER_BUTTON_WAKEUP_MS = 1000;
|
||||||
@@ -43,7 +64,7 @@ constexpr unsigned long POWER_BUTTON_SLEEP_MS = 1000;
|
|||||||
|
|
||||||
Epub* loadEpub(const std::string& path) {
|
Epub* loadEpub(const std::string& path) {
|
||||||
if (!SD.exists(path.c_str())) {
|
if (!SD.exists(path.c_str())) {
|
||||||
Serial.printf("File does not exist: %s\n", path.c_str());
|
Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +73,7 @@ Epub* loadEpub(const std::string& path) {
|
|||||||
return epub;
|
return epub;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.println("Failed to load epub");
|
Serial.printf("[%lu] [ ] Failed to load epub\n", millis());
|
||||||
delete epub;
|
delete epub;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -75,15 +96,13 @@ void verifyWakeupLongPress() {
|
|||||||
const auto start = millis();
|
const auto start = millis();
|
||||||
bool abort = false;
|
bool abort = false;
|
||||||
|
|
||||||
Serial.println("Verifying power button press");
|
Serial.printf("[%lu] [ ] Verifying power button press\n", millis());
|
||||||
inputManager.update();
|
inputManager.update();
|
||||||
while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) {
|
while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) {
|
||||||
delay(50);
|
delay(50);
|
||||||
inputManager.update();
|
inputManager.update();
|
||||||
Serial.println("Waiting...");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("Made it? %s\n", inputManager.isPressed(InputManager::BTN_POWER) ? "yes" : "no");
|
|
||||||
if (inputManager.isPressed(InputManager::BTN_POWER)) {
|
if (inputManager.isPressed(InputManager::BTN_POWER)) {
|
||||||
do {
|
do {
|
||||||
delay(50);
|
delay(50);
|
||||||
@@ -94,8 +113,6 @@ void verifyWakeupLongPress() {
|
|||||||
abort = true;
|
abort = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("held for %lu\n", inputManager.getHeldTime());
|
|
||||||
|
|
||||||
if (abort) {
|
if (abort) {
|
||||||
// Button released too early. Returning to sleep.
|
// Button released too early. Returning to sleep.
|
||||||
// IMPORTANT: Re-arm the wakeup trigger before sleeping again
|
// IMPORTANT: Re-arm the wakeup trigger before sleeping again
|
||||||
@@ -117,13 +134,13 @@ void enterDeepSleep() {
|
|||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new SleepScreen(renderer, inputManager));
|
enterNewScreen(new SleepScreen(renderer, inputManager));
|
||||||
|
|
||||||
Serial.println("Power button released after a long press. Entering deep sleep.");
|
Serial.printf("[%lu] [ ] Power button released after a long press. Entering deep sleep.\n", millis());
|
||||||
delay(1000); // Allow Serial buffer to empty and display to update
|
delay(1000); // Allow Serial buffer to empty and display to update
|
||||||
|
|
||||||
// Enable Wakeup on LOW (button press)
|
// Enable Wakeup on LOW (button press)
|
||||||
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
||||||
|
|
||||||
display.hibernate();
|
einkDisplay.deepSleep();
|
||||||
|
|
||||||
// Enter Deep Sleep
|
// Enter Deep Sleep
|
||||||
esp_deep_sleep_start();
|
esp_deep_sleep_start();
|
||||||
@@ -142,7 +159,8 @@ void onSelectEpubFile(const std::string& path) {
|
|||||||
enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome));
|
enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome));
|
||||||
} else {
|
} else {
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, false, false));
|
enterNewScreen(
|
||||||
|
new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH));
|
||||||
delay(2000);
|
delay(2000);
|
||||||
onGoHome();
|
onGoHome();
|
||||||
}
|
}
|
||||||
@@ -154,15 +172,17 @@ void onGoHome() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
inputManager.begin();
|
|
||||||
verifyWakeupLongPress();
|
|
||||||
|
|
||||||
// Begin serial only if USB connected
|
// Begin serial only if USB connected
|
||||||
pinMode(UART0_RXD, INPUT);
|
pinMode(UART0_RXD, INPUT);
|
||||||
if (digitalRead(UART0_RXD) == HIGH) {
|
if (digitalRead(UART0_RXD) == HIGH) {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
||||||
|
|
||||||
|
inputManager.begin();
|
||||||
|
verifyWakeupLongPress();
|
||||||
|
|
||||||
// Initialize pins
|
// Initialize pins
|
||||||
pinMode(BAT_GPIO0, INPUT);
|
pinMode(BAT_GPIO0, INPUT);
|
||||||
|
|
||||||
@@ -170,11 +190,13 @@ void setup() {
|
|||||||
SPI.begin(EPD_SCLK, SD_SPI_MISO, EPD_MOSI, EPD_CS);
|
SPI.begin(EPD_SCLK, SD_SPI_MISO, EPD_MOSI, EPD_CS);
|
||||||
|
|
||||||
// Initialize display
|
// Initialize display
|
||||||
const SPISettings spi_settings(SPI_FQ, MSBFIRST, SPI_MODE0);
|
einkDisplay.begin();
|
||||||
display.init(115200, true, 2, false, SPI, spi_settings);
|
Serial.printf("[%lu] [ ] Display initialized\n", millis());
|
||||||
display.setRotation(3); // 270 degrees
|
|
||||||
display.setTextColor(GxEPD_BLACK);
|
renderer.insertFont(READER_FONT_ID, bookerlyFontFamily);
|
||||||
Serial.println("Display initialized");
|
renderer.insertFont(UI_FONT_ID, ubuntuFontFamily);
|
||||||
|
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
||||||
|
Serial.printf("[%lu] [ ] Fonts setup\n", millis());
|
||||||
|
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new BootLogoScreen(renderer, inputManager));
|
enterNewScreen(new BootLogoScreen(renderer, inputManager));
|
||||||
@@ -205,8 +227,8 @@ void loop() {
|
|||||||
delay(10);
|
delay(10);
|
||||||
|
|
||||||
static unsigned long lastMemPrint = 0;
|
static unsigned long lastMemPrint = 0;
|
||||||
if (Serial && millis() - lastMemPrint >= 5000) {
|
if (Serial && millis() - lastMemPrint >= 10000) {
|
||||||
Serial.printf("[%lu] Memory - Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(),
|
Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(),
|
||||||
ESP.getHeapSize(), ESP.getMinFreeHeap());
|
ESP.getHeapSize(), ESP.getMinFreeHeap());
|
||||||
lastMemPrint = millis();
|
lastMemPrint = millis();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
#include "BootLogoScreen.h"
|
#include "BootLogoScreen.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "images/CrossLarge.h"
|
#include "images/CrossLarge.h"
|
||||||
|
|
||||||
void BootLogoScreen::onEnter() {
|
void BootLogoScreen::onEnter() {
|
||||||
const auto pageWidth = renderer.getPageWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto pageHeight = renderer.getPageHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
// Location for images is from top right in landscape orientation
|
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
||||||
renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128);
|
renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
||||||
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING");
|
||||||
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION);
|
||||||
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
class BootLogoScreen final : public Screen {
|
class BootLogoScreen final : public Screen {
|
||||||
public:
|
public:
|
||||||
explicit BootLogoScreen(EpdRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
explicit BootLogoScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
#include "EpubReaderScreen.h"
|
#include "EpubReaderScreen.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <Epub/Page.h>
|
||||||
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
|
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
constexpr int PAGES_PER_REFRESH = 20;
|
constexpr int PAGES_PER_REFRESH = 15;
|
||||||
constexpr unsigned long SKIP_CHAPTER_MS = 700;
|
constexpr unsigned long SKIP_CHAPTER_MS = 700;
|
||||||
|
constexpr float lineCompression = 0.95f;
|
||||||
|
constexpr int marginTop = 11;
|
||||||
|
constexpr int marginRight = 10;
|
||||||
|
constexpr int marginBottom = 30;
|
||||||
|
constexpr int marginLeft = 10;
|
||||||
|
|
||||||
void EpubReaderScreen::taskTrampoline(void* param) {
|
void EpubReaderScreen::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<EpubReaderScreen*>(param);
|
auto* self = static_cast<EpubReaderScreen*>(param);
|
||||||
@@ -29,7 +36,7 @@ void EpubReaderScreen::onEnter() {
|
|||||||
f.read(data, 4);
|
f.read(data, 4);
|
||||||
currentSpineIndex = data[0] + (data[1] << 8);
|
currentSpineIndex = data[0] + (data[1] << 8);
|
||||||
nextPageNumber = data[2] + (data[3] << 8);
|
nextPageNumber = data[2] + (data[3] << 8);
|
||||||
Serial.printf("Loaded cache: %d, %d\n", currentSpineIndex, nextPageNumber);
|
Serial.printf("[%lu] [ERS] Loaded cache: %d, %d\n", millis(), currentSpineIndex, nextPageNumber);
|
||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +135,7 @@ void EpubReaderScreen::displayTaskLoop() {
|
|||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
renderPage();
|
renderScreen();
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
}
|
}
|
||||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
@@ -136,7 +143,7 @@ void EpubReaderScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Failure handling
|
// TODO: Failure handling
|
||||||
void EpubReaderScreen::renderPage() {
|
void EpubReaderScreen::renderScreen() {
|
||||||
if (!epub) {
|
if (!epub) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -147,33 +154,37 @@ void EpubReaderScreen::renderPage() {
|
|||||||
|
|
||||||
if (!section) {
|
if (!section) {
|
||||||
const auto filepath = epub->getSpineItem(currentSpineIndex);
|
const auto filepath = epub->getSpineItem(currentSpineIndex);
|
||||||
Serial.printf("Loading file: %s, index: %d\n", filepath.c_str(), currentSpineIndex);
|
Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex);
|
||||||
section = new Section(epub, currentSpineIndex, renderer);
|
section = new Section(epub, currentSpineIndex, renderer);
|
||||||
if (!section->loadCacheMetadata()) {
|
if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
|
||||||
Serial.println("Cache not found, building...");
|
marginLeft)) {
|
||||||
|
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
|
||||||
|
|
||||||
{
|
{
|
||||||
const int textWidth = renderer.getTextWidth("Indexing...");
|
const int textWidth = renderer.getTextWidth(READER_FONT_ID, "Indexing...");
|
||||||
constexpr int margin = 20;
|
constexpr int margin = 20;
|
||||||
const int x = (renderer.getPageWidth() - textWidth - margin * 2) / 2;
|
const int x = (GfxRenderer::getScreenWidth() - textWidth - margin * 2) / 2;
|
||||||
constexpr int y = 50;
|
constexpr int y = 50;
|
||||||
const int w = textWidth + margin * 2;
|
const int w = textWidth + margin * 2;
|
||||||
const int h = renderer.getLineHeight() + margin * 2;
|
const int h = renderer.getLineHeight(READER_FONT_ID) + margin * 2;
|
||||||
|
renderer.swapBuffers();
|
||||||
renderer.fillRect(x, y, w, h, 0);
|
renderer.fillRect(x, y, w, h, 0);
|
||||||
renderer.drawText(x + margin, y + margin, "Indexing...");
|
renderer.drawText(READER_FONT_ID, x + margin, y + margin, "Indexing...");
|
||||||
renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
|
renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
|
||||||
renderer.flushArea(x, y, w, h);
|
renderer.displayBuffer();
|
||||||
|
pagesUntilFullRefresh = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
section->setupCacheDir();
|
section->setupCacheDir();
|
||||||
if (!section->persistPageDataToSD()) {
|
if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
|
||||||
Serial.println("Failed to persist page data to SD");
|
marginLeft)) {
|
||||||
|
Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis());
|
||||||
delete section;
|
delete section;
|
||||||
section = nullptr;
|
section = nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Serial.println("Cache found, skipping build...");
|
Serial.printf("[%lu] [ERS] Cache found, skipping build...\n", millis());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextPageNumber == UINT16_MAX) {
|
if (nextPageNumber == UINT16_MAX) {
|
||||||
@@ -184,16 +195,29 @@ void EpubReaderScreen::renderPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
section->renderPage();
|
|
||||||
|
if (section->pageCount == 0) {
|
||||||
|
Serial.printf("[%lu] [ERS] No pages to render\n", millis());
|
||||||
|
renderer.drawCenteredText(READER_FONT_ID, 300, "Empty chapter", true, BOLD);
|
||||||
renderStatusBar();
|
renderStatusBar();
|
||||||
if (pagesUntilFullRefresh <= 1) {
|
renderer.displayBuffer();
|
||||||
renderer.flushDisplay(false);
|
return;
|
||||||
pagesUntilFullRefresh = PAGES_PER_REFRESH;
|
|
||||||
} else {
|
|
||||||
renderer.flushDisplay();
|
|
||||||
pagesUntilFullRefresh--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (section->currentPage < 0 || section->currentPage >= section->pageCount) {
|
||||||
|
Serial.printf("[%lu] [ERS] Page out of bounds: %d (max %d)\n", millis(), section->currentPage, section->pageCount);
|
||||||
|
renderer.drawCenteredText(READER_FONT_ID, 300, "Out of bounds", true, BOLD);
|
||||||
|
renderStatusBar();
|
||||||
|
renderer.displayBuffer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page* p = section->loadPageFromSD();
|
||||||
|
const auto start = millis();
|
||||||
|
renderContents(p);
|
||||||
|
Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start);
|
||||||
|
delete p;
|
||||||
|
|
||||||
File f = SD.open((epub->getCachePath() + "/progress.bin").c_str(), FILE_WRITE);
|
File f = SD.open((epub->getCachePath() + "/progress.bin").c_str(), FILE_WRITE);
|
||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
data[0] = currentSpineIndex & 0xFF;
|
data[0] = currentSpineIndex & 0xFF;
|
||||||
@@ -204,23 +228,55 @@ void EpubReaderScreen::renderPage() {
|
|||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EpubReaderScreen::renderContents(const Page* p) {
|
||||||
|
p->render(renderer, READER_FONT_ID);
|
||||||
|
renderStatusBar();
|
||||||
|
if (pagesUntilFullRefresh <= 1) {
|
||||||
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
|
pagesUntilFullRefresh = PAGES_PER_REFRESH;
|
||||||
|
} else {
|
||||||
|
renderer.displayBuffer();
|
||||||
|
pagesUntilFullRefresh--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// grayscale rendering
|
||||||
|
// TODO: Only do this if font supports it
|
||||||
|
{
|
||||||
|
renderer.clearScreen(0x00);
|
||||||
|
renderer.setFontRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
||||||
|
p->render(renderer, READER_FONT_ID);
|
||||||
|
renderer.copyGrayscaleLsbBuffers();
|
||||||
|
|
||||||
|
// Render and copy to MSB buffer
|
||||||
|
renderer.clearScreen(0x00);
|
||||||
|
renderer.setFontRenderMode(GfxRenderer::GRAYSCALE_MSB);
|
||||||
|
p->render(renderer, READER_FONT_ID);
|
||||||
|
renderer.copyGrayscaleMsbBuffers();
|
||||||
|
|
||||||
|
// display grayscale part
|
||||||
|
renderer.displayGrayBuffer();
|
||||||
|
renderer.setFontRenderMode(GfxRenderer::BW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::renderStatusBar() const {
|
void EpubReaderScreen::renderStatusBar() const {
|
||||||
const auto pageWidth = renderer.getPageWidth();
|
// Right aligned text for progress counter
|
||||||
|
|
||||||
const std::string progress = std::to_string(section->currentPage + 1) + " / " + std::to_string(section->pageCount);
|
const std::string progress = std::to_string(section->currentPage + 1) + " / " + std::to_string(section->pageCount);
|
||||||
const auto progressTextWidth = renderer.getSmallTextWidth(progress.c_str());
|
const auto progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str());
|
||||||
renderer.drawSmallText(pageWidth - progressTextWidth, 765, progress.c_str());
|
renderer.drawText(SMALL_FONT_ID, GfxRenderer::getScreenWidth() - marginRight - progressTextWidth, 776,
|
||||||
|
progress.c_str());
|
||||||
|
|
||||||
|
// Left aligned battery icon and percentage
|
||||||
const uint16_t percentage = battery.readPercentage();
|
const uint16_t percentage = battery.readPercentage();
|
||||||
const auto percentageText = std::to_string(percentage) + "%";
|
const auto percentageText = std::to_string(percentage) + "%";
|
||||||
const auto percentageTextWidth = renderer.getSmallTextWidth(percentageText.c_str());
|
const auto percentageTextWidth = renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
||||||
renderer.drawSmallText(20, 765, percentageText.c_str());
|
renderer.drawText(SMALL_FONT_ID, 20 + marginLeft, 776, percentageText.c_str());
|
||||||
|
|
||||||
// 1 column on left, 2 columns on right, 5 columns of battery body
|
// 1 column on left, 2 columns on right, 5 columns of battery body
|
||||||
constexpr int batteryWidth = 15;
|
constexpr int batteryWidth = 15;
|
||||||
constexpr int batteryHeight = 10;
|
constexpr int batteryHeight = 10;
|
||||||
constexpr int x = 0;
|
constexpr int x = marginLeft;
|
||||||
constexpr int y = 772;
|
constexpr int y = 783;
|
||||||
|
|
||||||
// Top line
|
// Top line
|
||||||
renderer.drawLine(x, y, x + batteryWidth - 4, y);
|
renderer.drawLine(x, y, x + batteryWidth - 4, y);
|
||||||
@@ -241,17 +297,18 @@ void EpubReaderScreen::renderStatusBar() const {
|
|||||||
}
|
}
|
||||||
renderer.fillRect(x + 1, y + 1, filledWidth, batteryHeight - 2);
|
renderer.fillRect(x + 1, y + 1, filledWidth, batteryHeight - 2);
|
||||||
|
|
||||||
|
// Centered chatper title text
|
||||||
// Page width minus existing content with 30px padding on each side
|
// Page width minus existing content with 30px padding on each side
|
||||||
const int leftMargin = 20 + percentageTextWidth + 30;
|
const int titleMarginLeft = 20 + percentageTextWidth + 30 + marginLeft;
|
||||||
const int rightMargin = progressTextWidth + 30;
|
const int titleMarginRight = progressTextWidth + 30 + marginRight;
|
||||||
const int availableTextWidth = pageWidth - leftMargin - rightMargin;
|
const int availableTextWidth = GfxRenderer::getScreenWidth() - titleMarginLeft - titleMarginRight;
|
||||||
const auto tocItem = epub->getTocItem(epub->getTocIndexForSpineIndex(currentSpineIndex));
|
const auto tocItem = epub->getTocItem(epub->getTocIndexForSpineIndex(currentSpineIndex));
|
||||||
auto title = tocItem.title;
|
auto title = tocItem.title;
|
||||||
int titleWidth = renderer.getSmallTextWidth(title.c_str());
|
int titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
|
||||||
while (titleWidth > availableTextWidth) {
|
while (titleWidth > availableTextWidth) {
|
||||||
title = title.substr(0, title.length() - 8) + "...";
|
title = title.substr(0, title.length() - 8) + "...";
|
||||||
titleWidth = renderer.getSmallTextWidth(title.c_str());
|
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.drawSmallText(leftMargin + (availableTextWidth - titleWidth) / 2, 765, title.c_str());
|
renderer.drawText(SMALL_FONT_ID, titleMarginLeft + (availableTextWidth - titleWidth) / 2, 777, title.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,12 @@ class EpubReaderScreen final : public Screen {
|
|||||||
|
|
||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
void renderPage();
|
void renderScreen();
|
||||||
|
void renderContents(const Page* p);
|
||||||
void renderStatusBar() const;
|
void renderStatusBar() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderScreen(EpdRenderer& renderer, InputManager& inputManager, Epub* epub,
|
explicit EpubReaderScreen(GfxRenderer& renderer, InputManager& inputManager, Epub* epub,
|
||||||
const std::function<void()>& onGoHome)
|
const std::function<void()>& onGoHome)
|
||||||
: Screen(renderer, inputManager), epub(epub), onGoHome(onGoHome) {}
|
: Screen(renderer, inputManager), epub(epub), onGoHome(onGoHome) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#include "FileSelectionScreen.h"
|
#include "FileSelectionScreen.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
void sortFileList(std::vector<std::string>& strs) {
|
void sortFileList(std::vector<std::string>& strs) {
|
||||||
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
|
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
|
||||||
if (str1.back() == '/' && str2.back() != '/') return true;
|
if (str1.back() == '/' && str2.back() != '/') return true;
|
||||||
@@ -51,7 +53,7 @@ void FileSelectionScreen::onEnter() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&FileSelectionScreen::taskTrampoline, "FileSelectionScreenTask",
|
xTaskCreate(&FileSelectionScreen::taskTrampoline, "FileSelectionScreenTask",
|
||||||
1024, // Stack size
|
2048, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
&displayTaskHandle // Task handle
|
&displayTaskHandle // Task handle
|
||||||
@@ -118,21 +120,20 @@ void FileSelectionScreen::displayTaskLoop() {
|
|||||||
void FileSelectionScreen::render() const {
|
void FileSelectionScreen::render() const {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = renderer.getPageWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto titleWidth = renderer.getTextWidth("CrossPoint Reader", BOLD);
|
renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD);
|
||||||
renderer.drawText((pageWidth - titleWidth) / 2, 0, "CrossPoint Reader", 1, BOLD);
|
|
||||||
|
|
||||||
if (files.empty()) {
|
if (files.empty()) {
|
||||||
renderer.drawUiText(10, 50, "No EPUBs found");
|
renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found");
|
||||||
} else {
|
} else {
|
||||||
// Draw selection
|
// Draw selection
|
||||||
renderer.fillRect(0, 50 + selectorIndex * 30 + 2, pageWidth - 1, 30);
|
renderer.fillRect(0, 60 + selectorIndex * 30 + 2, pageWidth - 1, 30);
|
||||||
|
|
||||||
for (size_t i = 0; i < files.size(); i++) {
|
for (size_t i = 0; i < files.size(); i++) {
|
||||||
const auto file = files[i];
|
const auto file = files[i];
|
||||||
renderer.drawUiText(10, 50 + i * 30, file.c_str(), i == selectorIndex ? 0 : 1);
|
renderer.drawText(UI_FONT_ID, 20, 60 + i * 30, file.c_str(), i != selectorIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.flushDisplay();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class FileSelectionScreen final : public Screen {
|
|||||||
void loadFiles();
|
void loadFiles();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FileSelectionScreen(EpdRenderer& renderer, InputManager& inputManager,
|
explicit FileSelectionScreen(GfxRenderer& renderer, InputManager& inputManager,
|
||||||
const std::function<void(const std::string&)>& onSelect)
|
const std::function<void(const std::string&)>& onSelect)
|
||||||
: Screen(renderer, inputManager), onSelect(onSelect) {}
|
: Screen(renderer, inputManager), onSelect(onSelect) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#include "FullScreenMessageScreen.h"
|
#include "FullScreenMessageScreen.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
void FullScreenMessageScreen::onEnter() {
|
void FullScreenMessageScreen::onEnter() {
|
||||||
const auto width = renderer.getUiTextWidth(text.c_str(), style);
|
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||||
const auto height = renderer.getLineHeight();
|
const auto top = (GfxRenderer::getScreenHeight() - height) / 2;
|
||||||
const auto left = (renderer.getPageWidth() - width) / 2;
|
|
||||||
const auto top = (renderer.getPageHeight() - height) / 2;
|
|
||||||
|
|
||||||
renderer.clearScreen(invert);
|
renderer.clearScreen();
|
||||||
renderer.drawUiText(left, top, text.c_str(), invert ? 0 : 1, style);
|
renderer.drawCenteredText(UI_FONT_ID, top, text.c_str(), true, style);
|
||||||
renderer.flushDisplay(partialUpdate);
|
renderer.displayBuffer(refreshMode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <EInkDisplay.h>
|
||||||
|
#include <EpdFontFamily.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "EpdFontFamily.h"
|
|
||||||
#include "Screen.h"
|
#include "Screen.h"
|
||||||
|
|
||||||
class FullScreenMessageScreen final : public Screen {
|
class FullScreenMessageScreen final : public Screen {
|
||||||
std::string text;
|
std::string text;
|
||||||
EpdFontStyle style;
|
EpdFontStyle style;
|
||||||
bool invert;
|
EInkDisplay::RefreshMode refreshMode;
|
||||||
bool partialUpdate;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FullScreenMessageScreen(EpdRenderer& renderer, InputManager& inputManager, std::string text,
|
explicit FullScreenMessageScreen(GfxRenderer& renderer, InputManager& inputManager, std::string text,
|
||||||
const EpdFontStyle style = REGULAR, const bool invert = false,
|
const EpdFontStyle style = REGULAR,
|
||||||
const bool partialUpdate = true)
|
const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
|
||||||
: Screen(renderer, inputManager),
|
: Screen(renderer, inputManager), text(std::move(text)), style(style), refreshMode(refreshMode) {}
|
||||||
text(std::move(text)),
|
|
||||||
style(style),
|
|
||||||
invert(invert),
|
|
||||||
partialUpdate(partialUpdate) {}
|
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <InputManager.h>
|
#include <InputManager.h>
|
||||||
|
|
||||||
class EpdRenderer;
|
class GfxRenderer;
|
||||||
|
|
||||||
class Screen {
|
class Screen {
|
||||||
protected:
|
protected:
|
||||||
EpdRenderer& renderer;
|
GfxRenderer& renderer;
|
||||||
InputManager& inputManager;
|
InputManager& inputManager;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Screen(EpdRenderer& renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {}
|
explicit Screen(GfxRenderer& renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {}
|
||||||
virtual ~Screen() = default;
|
virtual ~Screen() = default;
|
||||||
virtual void onEnter() {}
|
virtual void onEnter() {}
|
||||||
virtual void onExit() {}
|
virtual void onExit() {}
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
#include "SleepScreen.h"
|
#include "SleepScreen.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
#include "images/SleepScreenImg.h"
|
#include "config.h"
|
||||||
|
#include "images/CrossLarge.h"
|
||||||
|
|
||||||
void SleepScreen::onEnter() { renderer.drawImageNoMargin(SleepScreenImg, 0, 0, 800, 480, false, true); }
|
void SleepScreen::onEnter() {
|
||||||
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
|
renderer.clearScreen();
|
||||||
|
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
||||||
|
renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
||||||
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
|
||||||
|
renderer.invertScreen();
|
||||||
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
class SleepScreen final : public Screen {
|
class SleepScreen final : public Screen {
|
||||||
public:
|
public:
|
||||||
explicit SleepScreen(EpdRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
explicit SleepScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user