Merge remote-tracking branch 'upstream/master' into feature/calibre-web-and-margins
# Conflicts: # src/CrossPointSettings.cpp # src/CrossPointSettings.h # src/activities/settings/SettingsActivity.cpp
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "Epub/parsers/ContainerParser.h"
|
||||
#include "Epub/parsers/ContentOpfParser.h"
|
||||
#include "Epub/parsers/TocNavParser.h"
|
||||
#include "Epub/parsers/TocNcxParser.h"
|
||||
|
||||
bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
|
||||
@@ -80,6 +81,10 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
|
||||
tocNcxItem = opfParser.tocNcxPath;
|
||||
}
|
||||
|
||||
if (!opfParser.tocNavPath.empty()) {
|
||||
tocNavItem = opfParser.tocNavPath;
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [EBP] Successfully parsed content.opf\n", millis());
|
||||
return true;
|
||||
}
|
||||
@@ -141,6 +146,60 @@ bool Epub::parseTocNcxFile() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Epub::parseTocNavFile() const {
|
||||
// the nav file should have been specified in the content.opf file (EPUB 3)
|
||||
if (tocNavItem.empty()) {
|
||||
Serial.printf("[%lu] [EBP] No nav file specified\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [EBP] Parsing toc nav file: %s\n", millis(), tocNavItem.c_str());
|
||||
|
||||
const auto tmpNavPath = getCachePath() + "/toc.nav";
|
||||
FsFile tempNavFile;
|
||||
if (!SdMan.openFileForWrite("EBP", tmpNavPath, tempNavFile)) {
|
||||
return false;
|
||||
}
|
||||
readItemContentsToStream(tocNavItem, tempNavFile, 1024);
|
||||
tempNavFile.close();
|
||||
if (!SdMan.openFileForRead("EBP", tmpNavPath, tempNavFile)) {
|
||||
return false;
|
||||
}
|
||||
const auto navSize = tempNavFile.size();
|
||||
|
||||
TocNavParser navParser(contentBasePath, navSize, bookMetadataCache.get());
|
||||
|
||||
if (!navParser.setup()) {
|
||||
Serial.printf("[%lu] [EBP] Could not setup toc nav parser\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto navBuffer = static_cast<uint8_t*>(malloc(1024));
|
||||
if (!navBuffer) {
|
||||
Serial.printf("[%lu] [EBP] Could not allocate memory for toc nav parser\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
while (tempNavFile.available()) {
|
||||
const auto readSize = tempNavFile.read(navBuffer, 1024);
|
||||
const auto processedSize = navParser.write(navBuffer, readSize);
|
||||
|
||||
if (processedSize != readSize) {
|
||||
Serial.printf("[%lu] [EBP] Could not process all toc nav data\n", millis());
|
||||
free(navBuffer);
|
||||
tempNavFile.close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
free(navBuffer);
|
||||
tempNavFile.close();
|
||||
SdMan.remove(tmpNavPath.c_str());
|
||||
|
||||
Serial.printf("[%lu] [EBP] Parsed TOC nav items\n", millis());
|
||||
return true;
|
||||
}
|
||||
|
||||
// load in the meta data for the epub file
|
||||
bool Epub::load(const bool buildIfMissing) {
|
||||
Serial.printf("[%lu] [EBP] Loading ePub: %s\n", millis(), filepath.c_str());
|
||||
@@ -184,15 +243,31 @@ bool Epub::load(const bool buildIfMissing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TOC Pass
|
||||
// TOC Pass - try EPUB 3 nav first, fall back to NCX
|
||||
if (!bookMetadataCache->beginTocPass()) {
|
||||
Serial.printf("[%lu] [EBP] Could not begin writing toc pass\n", millis());
|
||||
return false;
|
||||
}
|
||||
if (!parseTocNcxFile()) {
|
||||
Serial.printf("[%lu] [EBP] Could not parse toc\n", millis());
|
||||
return false;
|
||||
|
||||
bool tocParsed = false;
|
||||
|
||||
// Try EPUB 3 nav document first (preferred)
|
||||
if (!tocNavItem.empty()) {
|
||||
Serial.printf("[%lu] [EBP] Attempting to parse EPUB 3 nav document\n", millis());
|
||||
tocParsed = parseTocNavFile();
|
||||
}
|
||||
|
||||
// Fall back to NCX if nav parsing failed or wasn't available
|
||||
if (!tocParsed && !tocNcxItem.empty()) {
|
||||
Serial.printf("[%lu] [EBP] Falling back to NCX TOC\n", millis());
|
||||
tocParsed = parseTocNcxFile();
|
||||
}
|
||||
|
||||
if (!tocParsed) {
|
||||
Serial.printf("[%lu] [EBP] Warning: Could not parse any TOC format\n", millis());
|
||||
// Continue anyway - book will work without TOC
|
||||
}
|
||||
|
||||
if (!bookMetadataCache->endTocPass()) {
|
||||
Serial.printf("[%lu] [EBP] Could not end writing toc pass\n", millis());
|
||||
return false;
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
class ZipFile;
|
||||
|
||||
class Epub {
|
||||
// the ncx file
|
||||
// the ncx file (EPUB 2)
|
||||
std::string tocNcxItem;
|
||||
// the nav file (EPUB 3)
|
||||
std::string tocNavItem;
|
||||
// where is the EPUBfile?
|
||||
std::string filepath;
|
||||
// the base path for items in the EPUB file
|
||||
@@ -26,6 +28,7 @@ class Epub {
|
||||
bool findContentOpfFile(std::string* contentOpfFile) const;
|
||||
bool parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata);
|
||||
bool parseTocNcxFile() const;
|
||||
bool parseTocNavFile() const;
|
||||
|
||||
public:
|
||||
explicit Epub(std::string filepath, const std::string& cacheDir) : filepath(std::move(filepath)) {
|
||||
|
||||
@@ -161,6 +161,7 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
||||
std::string itemId;
|
||||
std::string href;
|
||||
std::string mediaType;
|
||||
std::string properties;
|
||||
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "id") == 0) {
|
||||
@@ -169,6 +170,8 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
||||
href = self->baseContentPath + atts[i + 1];
|
||||
} else if (strcmp(atts[i], "media-type") == 0) {
|
||||
mediaType = atts[i + 1];
|
||||
} else if (strcmp(atts[i], "properties") == 0) {
|
||||
properties = atts[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +191,15 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
||||
href.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// EPUB 3: Check for nav document (properties contains "nav")
|
||||
if (!properties.empty() && self->tocNavPath.empty()) {
|
||||
// Properties is space-separated, check if "nav" is present as a word
|
||||
if (properties == "nav" || properties.find("nav ") == 0 || properties.find(" nav") != std::string::npos) {
|
||||
self->tocNavPath = href;
|
||||
Serial.printf("[%lu] [COF] Found EPUB 3 nav document: %s\n", millis(), href.c_str());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ class ContentOpfParser final : public Print {
|
||||
std::string title;
|
||||
std::string author;
|
||||
std::string tocNcxPath;
|
||||
std::string tocNavPath; // EPUB 3 nav document path
|
||||
std::string coverItemHref;
|
||||
std::string textReferenceHref;
|
||||
|
||||
|
||||
184
lib/Epub/Epub/parsers/TocNavParser.cpp
Normal file
184
lib/Epub/Epub/parsers/TocNavParser.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
#include "TocNavParser.h"
|
||||
|
||||
#include <HardwareSerial.h>
|
||||
|
||||
#include "../BookMetadataCache.h"
|
||||
|
||||
bool TocNavParser::setup() {
|
||||
parser = XML_ParserCreate(nullptr);
|
||||
if (!parser) {
|
||||
Serial.printf("[%lu] [NAV] Couldn't allocate memory for parser\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
XML_SetUserData(parser, this);
|
||||
XML_SetElementHandler(parser, startElement, endElement);
|
||||
XML_SetCharacterDataHandler(parser, characterData);
|
||||
return true;
|
||||
}
|
||||
|
||||
TocNavParser::~TocNavParser() {
|
||||
if (parser) {
|
||||
XML_StopParser(parser, XML_FALSE);
|
||||
XML_SetElementHandler(parser, nullptr, nullptr);
|
||||
XML_SetCharacterDataHandler(parser, nullptr);
|
||||
XML_ParserFree(parser);
|
||||
parser = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
size_t TocNavParser::write(const uint8_t data) { return write(&data, 1); }
|
||||
|
||||
size_t TocNavParser::write(const uint8_t* buffer, const size_t size) {
|
||||
if (!parser) return 0;
|
||||
|
||||
const uint8_t* currentBufferPos = buffer;
|
||||
auto remainingInBuffer = size;
|
||||
|
||||
while (remainingInBuffer > 0) {
|
||||
void* const buf = XML_GetBuffer(parser, 1024);
|
||||
if (!buf) {
|
||||
Serial.printf("[%lu] [NAV] Couldn't allocate memory for buffer\n", millis());
|
||||
XML_StopParser(parser, XML_FALSE);
|
||||
XML_SetElementHandler(parser, nullptr, nullptr);
|
||||
XML_SetCharacterDataHandler(parser, nullptr);
|
||||
XML_ParserFree(parser);
|
||||
parser = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto toRead = remainingInBuffer < 1024 ? remainingInBuffer : 1024;
|
||||
memcpy(buf, currentBufferPos, toRead);
|
||||
|
||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
||||
Serial.printf("[%lu] [NAV] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
|
||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||
XML_StopParser(parser, XML_FALSE);
|
||||
XML_SetElementHandler(parser, nullptr, nullptr);
|
||||
XML_SetCharacterDataHandler(parser, nullptr);
|
||||
XML_ParserFree(parser);
|
||||
parser = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
currentBufferPos += toRead;
|
||||
remainingInBuffer -= toRead;
|
||||
remainingSize -= toRead;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void XMLCALL TocNavParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
||||
auto* self = static_cast<TocNavParser*>(userData);
|
||||
|
||||
// Track HTML structure loosely - we mainly care about finding <nav epub:type="toc">
|
||||
if (strcmp(name, "html") == 0) {
|
||||
self->state = IN_HTML;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_HTML && strcmp(name, "body") == 0) {
|
||||
self->state = IN_BODY;
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for <nav epub:type="toc"> anywhere in body (or nested elements)
|
||||
if (self->state >= IN_BODY && strcmp(name, "nav") == 0) {
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if ((strcmp(atts[i], "epub:type") == 0 || strcmp(atts[i], "type") == 0) && strcmp(atts[i + 1], "toc") == 0) {
|
||||
self->state = IN_NAV_TOC;
|
||||
Serial.printf("[%lu] [NAV] Found nav toc element\n", millis());
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process ol/li/a if we're inside the toc nav
|
||||
if (self->state < IN_NAV_TOC) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(name, "ol") == 0) {
|
||||
self->olDepth++;
|
||||
self->state = IN_OL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_OL && strcmp(name, "li") == 0) {
|
||||
self->state = IN_LI;
|
||||
self->currentLabel.clear();
|
||||
self->currentHref.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_LI && strcmp(name, "a") == 0) {
|
||||
self->state = IN_ANCHOR;
|
||||
// Get href attribute
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "href") == 0) {
|
||||
self->currentHref = atts[i + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL TocNavParser::characterData(void* userData, const XML_Char* s, const int len) {
|
||||
auto* self = static_cast<TocNavParser*>(userData);
|
||||
|
||||
// Only collect text when inside an anchor within the TOC nav
|
||||
if (self->state == IN_ANCHOR) {
|
||||
self->currentLabel.append(s, len);
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL TocNavParser::endElement(void* userData, const XML_Char* name) {
|
||||
auto* self = static_cast<TocNavParser*>(userData);
|
||||
|
||||
if (strcmp(name, "a") == 0 && self->state == IN_ANCHOR) {
|
||||
// Create TOC entry when closing anchor tag (we have all data now)
|
||||
if (!self->currentLabel.empty() && !self->currentHref.empty()) {
|
||||
std::string href = self->baseContentPath + self->currentHref;
|
||||
std::string anchor;
|
||||
|
||||
const size_t pos = href.find('#');
|
||||
if (pos != std::string::npos) {
|
||||
anchor = href.substr(pos + 1);
|
||||
href = href.substr(0, pos);
|
||||
}
|
||||
|
||||
if (self->cache) {
|
||||
// olDepth gives us the nesting level (1-based from the outer ol)
|
||||
self->cache->createTocEntry(self->currentLabel, href, anchor, self->olDepth);
|
||||
}
|
||||
|
||||
self->currentLabel.clear();
|
||||
self->currentHref.clear();
|
||||
}
|
||||
self->state = IN_LI;
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(name, "li") == 0 && (self->state == IN_LI || self->state == IN_OL)) {
|
||||
self->state = IN_OL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(name, "ol") == 0 && self->state >= IN_NAV_TOC) {
|
||||
self->olDepth--;
|
||||
if (self->olDepth == 0) {
|
||||
self->state = IN_NAV_TOC;
|
||||
} else {
|
||||
self->state = IN_LI; // Back to parent li
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(name, "nav") == 0 && self->state >= IN_NAV_TOC) {
|
||||
self->state = IN_BODY;
|
||||
Serial.printf("[%lu] [NAV] Finished parsing nav toc\n", millis());
|
||||
return;
|
||||
}
|
||||
}
|
||||
47
lib/Epub/Epub/parsers/TocNavParser.h
Normal file
47
lib/Epub/Epub/parsers/TocNavParser.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <Print.h>
|
||||
#include <expat.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
class BookMetadataCache;
|
||||
|
||||
// Parser for EPUB 3 nav.xhtml navigation documents
|
||||
// Parses HTML5 nav elements with epub:type="toc" to extract table of contents
|
||||
class TocNavParser final : public Print {
|
||||
enum ParserState {
|
||||
START,
|
||||
IN_HTML,
|
||||
IN_BODY,
|
||||
IN_NAV_TOC, // Inside <nav epub:type="toc">
|
||||
IN_OL, // Inside <ol>
|
||||
IN_LI, // Inside <li>
|
||||
IN_ANCHOR, // Inside <a>
|
||||
};
|
||||
|
||||
const std::string& baseContentPath;
|
||||
size_t remainingSize;
|
||||
XML_Parser parser = nullptr;
|
||||
ParserState state = START;
|
||||
BookMetadataCache* cache;
|
||||
|
||||
// Track nesting depth for <ol> elements to determine TOC depth
|
||||
uint8_t olDepth = 0;
|
||||
// Current entry data being collected
|
||||
std::string currentLabel;
|
||||
std::string currentHref;
|
||||
|
||||
static void startElement(void* userData, const XML_Char* name, const XML_Char** atts);
|
||||
static void characterData(void* userData, const XML_Char* s, int len);
|
||||
static void endElement(void* userData, const XML_Char* name);
|
||||
|
||||
public:
|
||||
explicit TocNavParser(const std::string& baseContentPath, const size_t xmlSize, BookMetadataCache* cache)
|
||||
: baseContentPath(baseContentPath), remainingSize(xmlSize), cache(cache) {}
|
||||
~TocNavParser() override;
|
||||
|
||||
bool setup();
|
||||
|
||||
size_t write(uint8_t) override;
|
||||
size_t write(const uint8_t* buffer, size_t size) override;
|
||||
};
|
||||
@@ -327,6 +327,148 @@ void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char
|
||||
}
|
||||
}
|
||||
|
||||
void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, const char* bottomBtn) const {
|
||||
const int screenWidth = getScreenWidth();
|
||||
constexpr int buttonWidth = 40; // Width on screen (height when rotated)
|
||||
constexpr int buttonHeight = 80; // Height on screen (width when rotated)
|
||||
constexpr int buttonX = 5; // Distance from right edge
|
||||
// Position for the button group - buttons share a border so they're adjacent
|
||||
constexpr int topButtonY = 345; // Top button position
|
||||
|
||||
const char* labels[] = {topBtn, bottomBtn};
|
||||
|
||||
// Draw the shared border for both buttons as one unit
|
||||
const int x = screenWidth - buttonX - buttonWidth;
|
||||
|
||||
// Draw top button outline (3 sides, bottom open)
|
||||
if (topBtn != nullptr && topBtn[0] != '\0') {
|
||||
drawLine(x, topButtonY, x + buttonWidth - 1, topButtonY); // Top
|
||||
drawLine(x, topButtonY, x, topButtonY + buttonHeight - 1); // Left
|
||||
drawLine(x + buttonWidth - 1, topButtonY, x + buttonWidth - 1, topButtonY + buttonHeight - 1); // Right
|
||||
}
|
||||
|
||||
// Draw shared middle border
|
||||
if ((topBtn != nullptr && topBtn[0] != '\0') || (bottomBtn != nullptr && bottomBtn[0] != '\0')) {
|
||||
drawLine(x, topButtonY + buttonHeight, x + buttonWidth - 1, topButtonY + buttonHeight); // Shared border
|
||||
}
|
||||
|
||||
// Draw bottom button outline (3 sides, top is shared)
|
||||
if (bottomBtn != nullptr && bottomBtn[0] != '\0') {
|
||||
drawLine(x, topButtonY + buttonHeight, x, topButtonY + 2 * buttonHeight - 1); // Left
|
||||
drawLine(x + buttonWidth - 1, topButtonY + buttonHeight, x + buttonWidth - 1,
|
||||
topButtonY + 2 * buttonHeight - 1); // Right
|
||||
drawLine(x, topButtonY + 2 * buttonHeight - 1, x + buttonWidth - 1, topButtonY + 2 * buttonHeight - 1); // Bottom
|
||||
}
|
||||
|
||||
// Draw text for each button
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (labels[i] != nullptr && labels[i][0] != '\0') {
|
||||
const int y = topButtonY + i * buttonHeight;
|
||||
|
||||
// Draw rotated text centered in the button
|
||||
const int textWidth = getTextWidth(fontId, labels[i]);
|
||||
const int textHeight = getTextHeight(fontId);
|
||||
|
||||
// Center the rotated text in the button
|
||||
const int textX = x + (buttonWidth - textHeight) / 2;
|
||||
const int textY = y + (buttonHeight + textWidth) / 2;
|
||||
|
||||
drawTextRotated90CW(fontId, textX, textY, labels[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GfxRenderer::getTextHeight(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(EpdFontFamily::REGULAR)->ascender;
|
||||
}
|
||||
|
||||
void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y, const char* text, const bool black,
|
||||
const EpdFontFamily::Style style) const {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// For 90° clockwise rotation:
|
||||
// Original (glyphX, glyphY) -> Rotated (glyphY, -glyphX)
|
||||
// Text reads from bottom to top
|
||||
|
||||
int yPos = y; // Current Y position (decreases as we draw characters)
|
||||
|
||||
uint32_t cp;
|
||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||
const EpdGlyph* glyph = font.getGlyph(cp, style);
|
||||
if (!glyph) {
|
||||
glyph = font.getGlyph('?', style);
|
||||
}
|
||||
if (!glyph) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int is2Bit = font.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 int top = glyph->top;
|
||||
|
||||
const uint8_t* bitmap = &font.getData(style)->bitmap[offset];
|
||||
|
||||
if (bitmap != nullptr) {
|
||||
for (int glyphY = 0; glyphY < height; glyphY++) {
|
||||
for (int glyphX = 0; glyphX < width; glyphX++) {
|
||||
const int pixelPosition = glyphY * width + glyphX;
|
||||
|
||||
// 90° clockwise rotation transformation:
|
||||
// screenX = x + (ascender - top + glyphY)
|
||||
// screenY = yPos - (left + glyphX)
|
||||
const int screenX = x + (font.getData(style)->ascender - top + glyphY);
|
||||
const int screenY = yPos - left - glyphX;
|
||||
|
||||
if (is2Bit) {
|
||||
const uint8_t byte = bitmap[pixelPosition / 4];
|
||||
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
|
||||
const uint8_t bmpVal = 3 - (byte >> bit_index) & 0x3;
|
||||
|
||||
if (renderMode == BW && bmpVal < 3) {
|
||||
drawPixel(screenX, screenY, black);
|
||||
} else if (renderMode == GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
|
||||
drawPixel(screenX, screenY, false);
|
||||
} else if (renderMode == GRAYSCALE_LSB && bmpVal == 1) {
|
||||
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, black);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next character position (going up, so decrease Y)
|
||||
yPos -= glyph->advanceX;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* GfxRenderer::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
|
||||
|
||||
size_t GfxRenderer::getBufferSize() { return EInkDisplay::BUFFER_SIZE; }
|
||||
|
||||
@@ -82,7 +82,15 @@ class GfxRenderer {
|
||||
|
||||
// UI Components
|
||||
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4) const;
|
||||
void drawSideButtonHints(int fontId, const char* topBtn, const char* bottomBtn) const;
|
||||
|
||||
private:
|
||||
// Helper for drawing rotated text (90 degrees clockwise, for side buttons)
|
||||
void drawTextRotated90CW(int fontId, int x, int y, const char* text, bool black = true,
|
||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||
int getTextHeight(int fontId) const;
|
||||
|
||||
public:
|
||||
// Grayscale functions
|
||||
void setRenderMode(const RenderMode mode) { this->renderMode = mode; }
|
||||
void copyGrayscaleLsbBuffers() const;
|
||||
|
||||
Reference in New Issue
Block a user