4 Commits
0.1.0 ... 0.1.1

Author SHA1 Message Date
Dave Allie
2c80aca7b5 Use correct current page on reader screen
Fixes the page counter not updating
2025-12-03 22:34:16 +11:00
Dave Allie
7704772ebe Handle nested navpoint elements in nxc TOC 2025-12-03 22:30:50 +11:00
Dave Allie
4186c7da9e Remove debug lines 2025-12-03 22:30:13 +11:00
Dave Allie
802c9d0a30 Add web flashing instructions 2025-12-03 22:21:11 +11:00
5 changed files with 35 additions and 27 deletions

View File

@@ -50,10 +50,22 @@ This project is **not affiliated with Xteink**; it's built as a community projec
#### Command line
Connect your Xteink X4 to your computer via USB-C and run the following command.
```sh
pio run --target upload
```
#### Web
1. Connect your Xteink X4 to your computer via USB-C
2. Download the `firmware.bin` file from the latest release via the [releases page](https://github.com/daveallie/crosspoint-reader/releases)
3. Go to https://xteink.dve.al/ and flash the firmware file using the "OTA fast flash controls" section
4. Press the reset button on the Xteink X4 to restart the device
To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap
back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug.
## Internals
CrossPoint Reader is pretty aggressive about caching data down to the SD card to minimise RAM usage. The ESP32-C3 only

View File

@@ -3,7 +3,6 @@
#include <HardwareSerial.h>
#include <SD.h>
#include <ZipFile.h>
#include <tinyxml2.h>
#include <map>
@@ -162,14 +161,14 @@ bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
return true;
}
bool Epub::parseTocNcxFile(ZipFile& zip) {
bool Epub::parseTocNcxFile(const ZipFile& zip) {
// the ncx file should have been specified in the content.opf file
if (tocNcxItem.empty()) {
Serial.println("No ncx file specified");
return false;
}
auto ncxData = zip.readTextFileToMemory(tocNcxItem.c_str());
const auto ncxData = zip.readTextFileToMemory(tocNcxItem.c_str());
if (!ncxData) {
Serial.printf("Could not find %s\n", tocNcxItem.c_str());
return false;
@@ -177,7 +176,7 @@ bool Epub::parseTocNcxFile(ZipFile& zip) {
// Parse the Toc contents
tinyxml2::XMLDocument doc;
auto result = doc.Parse(ncxData);
const auto result = doc.Parse(ncxData);
free(ncxData);
if (result != tinyxml2::XML_SUCCESS) {
@@ -185,27 +184,30 @@ bool Epub::parseTocNcxFile(ZipFile& zip) {
return false;
}
auto ncx = doc.FirstChildElement("ncx");
const auto ncx = doc.FirstChildElement("ncx");
if (!ncx) {
Serial.println("Could not find first child ncx in toc");
return false;
}
auto navMap = ncx->FirstChildElement("navMap");
const auto navMap = ncx->FirstChildElement("navMap");
if (!navMap) {
Serial.println("Could not find navMap child in ncx");
return false;
}
auto navPoint = navMap->FirstChildElement("navPoint");
recursivelyParseNavMap(navMap->FirstChildElement("navPoint"));
return true;
}
void Epub::recursivelyParseNavMap(tinyxml2::XMLElement* element) {
// Fills toc map
while (navPoint) {
std::string navTitle = navPoint->FirstChildElement("navLabel")->FirstChildElement("text")->FirstChild()->Value();
auto content = navPoint->FirstChildElement("content");
while (element) {
std::string navTitle = element->FirstChildElement("navLabel")->FirstChildElement("text")->FirstChild()->Value();
const auto content = element->FirstChildElement("content");
std::string href = contentBasePath + content->Attribute("src");
// split the href on the # to get the href and the anchor
size_t pos = href.find('#');
const size_t pos = href.find('#');
std::string anchor;
if (pos != std::string::npos) {
@@ -214,10 +216,13 @@ bool Epub::parseTocNcxFile(ZipFile& zip) {
}
toc.emplace_back(navTitle, href, anchor, 0);
navPoint = navPoint->NextSiblingElement("navPoint");
}
return true;
tinyxml2::XMLElement* nestedNavPoint = element->FirstChildElement("navPoint");
if (nestedNavPoint) {
recursivelyParseNavMap(nestedNavPoint);
}
element = element->NextSiblingElement("navPoint");
}
}
// load in the meta data for the epub file
@@ -369,9 +374,7 @@ int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
int Epub::getTocIndexForSpineIndex(const int spineIndex) const {
// the toc entry should have an href that matches the spine item
// so we can find the toc index by looking for the href
Serial.printf("Looking for %s\n", spine[spineIndex].second.c_str());
for (int i = 0; i < toc.size(); i++) {
Serial.printf("Looking at %s\n", toc[i].href.c_str());
if (toc[i].href == spine[spineIndex].second) {
return i;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <HardwareSerial.h>
#include <tinyxml2.h>
#include <string>
#include <unordered_map>
@@ -38,36 +39,29 @@ class Epub {
// find the path for the content.opf file
static bool findContentOpfFile(const ZipFile& zip, std::string& contentOpfFile);
bool parseContentOpf(ZipFile& zip, std::string& content_opf_file);
bool parseTocNcxFile(ZipFile& zip);
bool parseTocNcxFile(const ZipFile& zip);
void recursivelyParseNavMap(tinyxml2::XMLElement* element);
public:
explicit Epub(std::string filepath, const std::string& cacheDir) : filepath(std::move(filepath)) {
// create a cache key based on the filepath
cachePath = cacheDir + "/epub_" + std::to_string(std::hash<std::string>{}(this->filepath));
}
~Epub() = default;
std::string& getBasePath() { return contentBasePath; }
bool load();
void clearCache() const;
void setupCacheDir() const;
const std::string& getCachePath() const;
const std::string& getPath() const;
const std::string& getTitle() const;
const std::string& getCoverImageItem() const;
uint8_t* getItemContents(const std::string& itemHref, size_t* size = nullptr) const;
char* getTextItemContents(const std::string& itemHref, size_t* size = nullptr) const;
std::string& getSpineItem(int spineIndex);
int getSpineItemsCount() const;
EpubTocEntry& getTocItem(int tocTndex);
int getTocItemsCount() const;
// work out the section index for a toc index
int getSpineIndexForTocIndex(int tocIndex) const;
int getTocIndexForSpineIndex(int spineIndex) const;
};

View File

@@ -159,7 +159,7 @@ void EpubReaderScreen::renderPage() {
void EpubReaderScreen::renderStatusBar() const {
const auto pageWidth = renderer->getPageWidth();
std::string progress = std::to_string(currentPage + 1) + " / " + std::to_string(section->pageCount);
std::string progress = std::to_string(section->currentPage + 1) + " / " + std::to_string(section->pageCount);
const auto progressTextWidth = renderer->getSmallTextWidth(progress.c_str());
renderer->drawSmallText(pageWidth - progressTextWidth, 765, progress.c_str());

View File

@@ -14,7 +14,6 @@ class EpubReaderScreen final : public Screen {
SemaphoreHandle_t sectionMutex = nullptr;
int currentSpineIndex = 0;
int nextPageNumber = 0;
int currentPage = 0;
bool updateRequired = false;
static void taskTrampoline(void* param);