Compare commits
2 Commits
ff33b2b3be
...
2eae521b6a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eae521b6a
|
||
|
|
9d9bc019a2
|
203
README.md
203
README.md
@@ -1,18 +1,21 @@
|
|||||||
# CrossPoint Reader
|
# CrossPoint Reader (Mod)
|
||||||
|
|
||||||
Firmware for the **Xteink X4** e-paper display reader (unaffiliated with Xteink).
|
A modified fork of [CrossPoint Reader](https://github.com/crosspoint-reader/crosspoint-reader) for the **Xteink X4**
|
||||||
Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller.
|
e-paper display reader. Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller.
|
||||||
|
|
||||||
CrossPoint Reader is a purpose-built firmware designed to be a drop-in, fully open-source replacement for the official
|
This mod is maintained on the `mod/master` branch and tracks upstream `master`. It ports upstream PRs ahead of merge
|
||||||
Xteink firmware. It aims to match or improve upon the standard EPUB reading experience.
|
and adds features not yet available in the official project, including bookmarks, dictionary lookup, a clock, book
|
||||||
|
management with archiving, an overhauled reader menu, and various rendering and performance improvements.
|
||||||
|
|
||||||
|
> **Upstream:** [crosspoint-reader/crosspoint-reader](https://github.com/crosspoint-reader/crosspoint-reader)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
E-paper devices are fantastic for reading, but most commercially available readers are closed systems with limited
|
E-paper devices are fantastic for reading, but most commercially available readers are closed systems with limited
|
||||||
customisation. The **Xteink X4** is an affordable, e-paper device, however the official firmware remains closed.
|
customisation. The **Xteink X4** is an affordable, e-paper device, however the official firmware remains closed.
|
||||||
CrossPoint exists partly as a fun side-project and partly to open up the ecosystem and truely unlock the device's
|
CrossPoint exists partly as a fun side-project and partly to open up the ecosystem and truly unlock the device's
|
||||||
potential.
|
potential.
|
||||||
|
|
||||||
CrossPoint Reader aims to:
|
CrossPoint Reader aims to:
|
||||||
@@ -21,54 +24,159 @@ CrossPoint Reader aims to:
|
|||||||
* Support **customisable font, layout, and display** options.
|
* Support **customisable font, layout, and display** options.
|
||||||
* Run purely on the **Xteink X4 hardware**.
|
* Run purely on the **Xteink X4 hardware**.
|
||||||
|
|
||||||
This project is **not affiliated with Xteink**; it's built as a community project.
|
This mod exists to iterate faster on features and fixes while upstream reviews and merges PRs at its own pace. It is
|
||||||
|
**not affiliated with Xteink** or the upstream CrossPoint project; it's a personal fork built on top of their work.
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
This mod was forked at [#46c2109](https://github.com/crosspoint-reader/crosspoint-reader/commit/46c2109f1fe5cb41ef1a84a15eeb3db64cdca082). A major sync took place at v1.1.0-rc.
|
||||||
|
|
||||||
## Features & Usage
|
## Features & Usage
|
||||||
|
|
||||||
|
This is not all-inclusive, but in general:
|
||||||
|
|
||||||
- [x] EPUB parsing and rendering (EPUB 2 and EPUB 3)
|
- [x] EPUB parsing and rendering (EPUB 2 and EPUB 3)
|
||||||
- [ ] Image support within EPUB
|
- [x] Image support within EPUB (JPEG and PNG)
|
||||||
|
- [x] Table rendering within EPUB
|
||||||
- [x] Saved reading position
|
- [x] Saved reading position
|
||||||
|
- [x] Bookmarks (add, remove, navigate with snippet preview)
|
||||||
|
- [x] Dictionary lookup (offline, StarDict format)
|
||||||
- [x] File explorer with file picker
|
- [x] File explorer with file picker
|
||||||
- [x] Basic EPUB picker from root directory
|
- [x] Basic EPUB picker from root directory
|
||||||
- [x] Support nested folders
|
- [x] Support nested folders
|
||||||
|
- [x] File extensions displayed
|
||||||
|
- [x] Expandable selected row for long filenames
|
||||||
- [ ] EPUB picker with cover art
|
- [ ] EPUB picker with cover art
|
||||||
|
- [x] Book management (archive, unarchive, delete, reindex)
|
||||||
|
- [x] Clock display (12h/24h, NTP sync, timezone support)
|
||||||
- [x] Custom sleep screen
|
- [x] Custom sleep screen
|
||||||
- [x] Cover sleep screen
|
- [x] Cover sleep screen
|
||||||
|
- [x] Letterbox fill modes (Solid, Dithered, None) with per-book override
|
||||||
|
- [x] Placeholder covers for books without embedded cover images
|
||||||
- [x] Wifi book upload
|
- [x] Wifi book upload
|
||||||
- [x] Wifi OTA updates
|
- [x] Wifi OTA updates
|
||||||
- [x] Configurable font, layout, and display options
|
- [x] Configurable font, layout, and display options
|
||||||
- [ ] User provided fonts
|
- [ ] User provided fonts
|
||||||
- [ ] Full UTF support
|
- [ ] Full UTF support
|
||||||
- [x] Screen rotation
|
- [x] Screen rotation (Portrait, Landscape CW, Inverted, Landscape CCW)
|
||||||
|
- [x] End-of-book interactive menu
|
||||||
|
- [x] Silent background chapter pre-indexing
|
||||||
|
|
||||||
Multi-language support: Read EPUBs in various languages, including English, Spanish, French, German, Italian, Portuguese, Russian, Ukrainian, Polish, Swedish, Norwegian, [and more](./USER_GUIDE.md#supported-languages).
|
Multi-language support: Read EPUBs in various languages, including English, Spanish, French, German, Italian, Portuguese, Russian, Ukrainian, Polish, Swedish, Norwegian, [and more](./USER_GUIDE.md#supported-languages).
|
||||||
|
|
||||||
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
||||||
|
|
||||||
For more details about the scope of the project, see the [SCOPE.md](SCOPE.md) document.
|
For more details about the scope of the project, see the [SCOPE.md](SCOPE.md) document.
|
||||||
|
|
||||||
|
## What This Mod Adds
|
||||||
|
|
||||||
|
This section describes features and improvements in the mod that are not present in upstream CrossPoint Reader.
|
||||||
|
|
||||||
|
### Reading Enhancements
|
||||||
|
|
||||||
|
* **Bookmarks** — Save and remove bookmarks per book. Each bookmark stores a snippet (first sentence) for quick
|
||||||
|
identification. A ribbon indicator marks bookmarked pages. Navigate bookmarks via the reader menu.
|
||||||
|
* **Dictionary lookup** — Offline word lookup using StarDict-format dictionaries stored in `/.dictionary/` on the SD
|
||||||
|
card. Supports stemming, fuzzy matching, edit-distance suggestions, and lookup history. The dictionary index is cached
|
||||||
|
to a binary file for fast subsequent access.
|
||||||
|
* **Table rendering** — EPUB tables render with column alignment, colspan support, HTML/CSS width hints, cell padding,
|
||||||
|
borders, and `<br>` line breaks within cells. Full-width spanning cells are center-aligned.
|
||||||
|
* **End-of-book menu** — An interactive menu at the end of a book (Archive, Delete, Back to Beginning, Close) replaces
|
||||||
|
the static end-of-book text.
|
||||||
|
* **Long-press Confirm** — While reading, long-pressing Confirm opens the Table of Contents directly, bypassing the
|
||||||
|
reader menu.
|
||||||
|
|
||||||
|
### Home Screen & Navigation
|
||||||
|
|
||||||
|
* **Clock** — Displays in all screen headers. Configurable format (off, 12h AM/PM, 24h) and size (small, medium,
|
||||||
|
large). Supports NTP time sync over WiFi, timezone presets (UTC, US time zones), and custom UTC offset.
|
||||||
|
* **Adaptive home screen** — Book card sizing adjusts based on cover aspect ratio. The home screen integrates with
|
||||||
|
recent books and book management.
|
||||||
|
* **File browser improvements** — File extensions are shown alongside filenames. When a selected filename overflows the
|
||||||
|
row width, the row expands to two lines with smart text wrapping (breaks at dashes/separators, then word boundaries,
|
||||||
|
then character-level).
|
||||||
|
* **Long-press shortcuts** — Long-press on a book in the home screen or recents opens the book management menu.
|
||||||
|
Long-press on "Browse Files" opens the archive browser (`/.archive/`).
|
||||||
|
|
||||||
|
### Book Management
|
||||||
|
|
||||||
|
* **Archive** — Move books to `/.archive/` on the SD card, preserving directory structure. Unarchive restores them to
|
||||||
|
their original location.
|
||||||
|
* **Manage Book menu** — A popup menu accessible from the home screen, file browser, recents, reader menu, and
|
||||||
|
end-of-book menu. Actions include archive/unarchive, delete book, delete cache, reindex, and full reindex.
|
||||||
|
* **Recent books** — A dedicated recent books list with book management integration.
|
||||||
|
|
||||||
|
### Reader Menu
|
||||||
|
|
||||||
|
* **Long-press actions** — Long-press "Lookup Word" to open the Looked Up Words history. Long-press "Toggle
|
||||||
|
Orientation" to open a sub-menu for selecting any of the four orientations (Portrait, Landscape CW, Inverted,
|
||||||
|
Landscape CCW).
|
||||||
|
* **Letterbox fill** — Short-press cycles through letterbox fill modes (Default, Dithered, Solid, None). Per-book
|
||||||
|
overrides are stored in `BookSettings`.
|
||||||
|
* **Landscape CCW** — A fourth orientation option. All text rendering, button hints, and dictionary layout support
|
||||||
|
counter-clockwise landscape.
|
||||||
|
|
||||||
|
### Display & Rendering
|
||||||
|
|
||||||
|
* **Silent pre-indexing** — The next chapter is pre-indexed in the background when approaching a chapter boundary.
|
||||||
|
Configurable display mode: popup, status bar text, or status bar icon.
|
||||||
|
* **Placeholder covers** — Books without an embedded cover image get a generated placeholder (title, author, icon
|
||||||
|
layout) for the home screen and sleep screen.
|
||||||
|
* **Sleep screen letterbox fill** — Multiple fill modes for the letterbox area around cover images on the sleep screen,
|
||||||
|
with per-book override support.
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
Several upstream PRs have been ported ahead of their merge into upstream `master`:
|
||||||
|
|
||||||
|
* **Byte-level framebuffer writes** — 232-470x speedup for `fillRect`, `fillRectDither`, and axis-aligned `drawLine`
|
||||||
|
operations. Upstream [PR #1055](https://github.com/crosspoint-reader/crosspoint-reader/pull/1055).
|
||||||
|
* **Word-width cache and hyphenation early exit** — 5-9% layout time reduction via a 128-entry direct-mapped cache and
|
||||||
|
monotonic early exit in the hyphenation loop. Upstream
|
||||||
|
[PR #1027](https://github.com/crosspoint-reader/crosspoint-reader/pull/1027).
|
||||||
|
* **`std::list` to `std::vector` in text layout** — 11% faster chapter parse time and ~50KB heap savings. Upstream
|
||||||
|
[PR #1038](https://github.com/crosspoint-reader/crosspoint-reader/pull/1038).
|
||||||
|
* **Combining mark rendering** — Proper rendering of decomposed Unicode characters with NFC-like precomposition for
|
||||||
|
hyphenation pattern matching. Upstream
|
||||||
|
[PR #1037](https://github.com/crosspoint-reader/crosspoint-reader/pull/1037).
|
||||||
|
* **URL hyphenation** — Long URLs can now be line-wrapped at path separators without crashing. Upstream
|
||||||
|
[PR #1068](https://github.com/crosspoint-reader/crosspoint-reader/pull/1068).
|
||||||
|
|
||||||
|
For detailed porting notes and differences from upstream, see [mod/prs/MERGED.md](mod/prs/MERGED.md). Note that this document was created well after many features were added manually so it is not all-inclusive. Sorry!
|
||||||
|
|
||||||
|
## Upstream Compatibility
|
||||||
|
|
||||||
|
This mod tracks upstream `master` and manually ports relevant PRs. Some upstream features are not present in the mod,
|
||||||
|
and the mod's build configuration differs slightly.
|
||||||
|
|
||||||
|
**Features on upstream `master` not yet in the mod:**
|
||||||
|
* Catalan language support
|
||||||
|
* Improved Spanish translations
|
||||||
|
|
||||||
|
**Build differences:**
|
||||||
|
* The `[env:mod]` build environment omits the OpenDyslexic font and some hyphenation patterns (German, Spanish, French,
|
||||||
|
Italian, Russian) to save flash space. These can be re-enabled by using `[env:default]` or removing the corresponding
|
||||||
|
`-DOMIT_*` flags.
|
||||||
|
* The mod version string is `<version>-mod+<git-hash>` (e.g., `1.1.2-mod+abc1234`).
|
||||||
|
|
||||||
|
See [mod/prs/MERGED.md](mod/prs/MERGED.md) for the full list of upstream PRs ported into this mod, including what was
|
||||||
|
changed or enhanced during the port.
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
### Web (latest firmware)
|
This mod is built from source. There is no web flasher for the mod firmware.
|
||||||
|
|
||||||
1. Connect your Xteink X4 to your computer via USB-C and wake/unlock the device
|
Connect your Xteink X4 to your computer via USB-C, wake/unlock the device, and run:
|
||||||
2. Go to https://xteink.dve.al/ and click "Flash CrossPoint firmware"
|
|
||||||
|
|
||||||
To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap
|
```sh
|
||||||
back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug.
|
pio run -e mod --target upload
|
||||||
|
```
|
||||||
|
|
||||||
### Web (specific firmware version)
|
You can also use `pio run -e default --target upload` for a build without the mod's flash-saving omissions (see
|
||||||
|
[Upstream Compatibility](#upstream-compatibility)).
|
||||||
|
|
||||||
1. Connect your Xteink X4 to your computer via USB-C
|
To revert to upstream CrossPoint or the official Xteink firmware, flash via https://xteink.dve.al/ or swap back to the
|
||||||
2. Download the `firmware.bin` file from the release of your choice via the [releases page](https://github.com/crosspoint-reader/crosspoint-reader/releases)
|
other partition using the "Swap boot partition" button at https://xteink.dve.al/debug.
|
||||||
3. Go to https://xteink.dve.al/ and flash the firmware file using the "OTA fast flash controls" section
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
### Manual
|
|
||||||
|
|
||||||
See [Development](#development) below.
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
@@ -81,21 +189,29 @@ See [Development](#development) below.
|
|||||||
|
|
||||||
### Checking out the code
|
### Checking out the code
|
||||||
|
|
||||||
CrossPoint uses PlatformIO for building and flashing the firmware. To get started, clone the repository:
|
CrossPoint uses PlatformIO for building and flashing the firmware. To get started, clone this repository and check out
|
||||||
|
the mod branch:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
git clone --recursive https://github.com/crosspoint-reader/crosspoint-reader
|
git clone --recursive -b mod/master https://github.com/crosspoint-reader/crosspoint-reader
|
||||||
|
|
||||||
# Or, if you've already cloned without --recursive:
|
# Or, if you've already cloned without --recursive:
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Build environments
|
||||||
|
|
||||||
|
| Environment | Description |
|
||||||
|
| ----------- | ----------- |
|
||||||
|
| `mod` | **Recommended.** Includes serial logging, version tagging (`-mod+<hash>`), and omits some fonts/hyphenation patterns to save flash. |
|
||||||
|
| `default` | Standard upstream-equivalent build with all fonts and hyphenation patterns included. |
|
||||||
|
|
||||||
### Flashing your device
|
### Flashing your device
|
||||||
|
|
||||||
Connect your Xteink X4 to your computer via USB-C and run the following command.
|
Connect your Xteink X4 to your computer via USB-C and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pio run --target upload
|
pio run -e mod --target upload
|
||||||
```
|
```
|
||||||
### Debugging
|
### Debugging
|
||||||
|
|
||||||
@@ -152,24 +268,17 @@ For more details on the internal file structures, see the [file formats document
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are very welcome!
|
This is a personal mod fork. If you'd like to contribute to the upstream CrossPoint Reader project, head to the
|
||||||
|
[upstream repository](https://github.com/crosspoint-reader/crosspoint-reader) and check out the
|
||||||
|
[ideas discussion board](https://github.com/crosspoint-reader/crosspoint-reader/discussions/categories/ideas).
|
||||||
|
|
||||||
If you're looking for a way to help out, take a look at the [ideas discussion board](https://github.com/crosspoint-reader/crosspoint-reader/discussions/categories/ideas).
|
For more details on upstream governance and community principles, see [GOVERNANCE.md](GOVERNANCE.md).
|
||||||
If there's something there you'd like to work on, leave a comment so that we can avoid duplicated effort.
|
|
||||||
|
|
||||||
Everyone here is a volunteer, so please be respectful and patient. For more details on our goverance and community
|
|
||||||
principles, please see [GOVERNANCE.md](GOVERNANCE.md).
|
|
||||||
|
|
||||||
### To submit a contribution:
|
|
||||||
|
|
||||||
1. Fork the repo
|
|
||||||
2. Create a branch (`feature/dithering-improvement`)
|
|
||||||
3. Make changes
|
|
||||||
4. Submit a PR
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
CrossPoint Reader is **not affiliated with Xteink or any manufacturer of the X4 hardware**.
|
CrossPoint Reader is **not affiliated with Xteink or any manufacturer of the X4 hardware**.
|
||||||
|
|
||||||
Huge shoutout to [**diy-esp32-epub-reader** by atomic14](https://github.com/atomic14/diy-esp32-epub-reader), which was a project I took a lot of inspiration from as I
|
This mod is not **not affilitated with CrossPoint Reader**.
|
||||||
was making CrossPoint.
|
|
||||||
|
Huge shoutout to [**diy-esp32-epub-reader** by atomic14](https://github.com/atomic14/diy-esp32-epub-reader), which was
|
||||||
|
a project the original CrossPoint author took a lot of inspiration from.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
default_envs = default
|
default_envs = default
|
||||||
|
|
||||||
[crosspoint]
|
[crosspoint]
|
||||||
version = 1.1.1-rc
|
version = 1.1.2
|
||||||
|
|
||||||
[base]
|
[base]
|
||||||
platform = espressif32 @ 6.12.0
|
platform = espressif32 @ 6.12.0
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ void MyLibraryActivity::loadFiles() {
|
|||||||
auto filename = std::string(name);
|
auto filename = std::string(name);
|
||||||
if (StringUtils::checkFileExtension(filename, ".epub") || StringUtils::checkFileExtension(filename, ".xtch") ||
|
if (StringUtils::checkFileExtension(filename, ".epub") || StringUtils::checkFileExtension(filename, ".xtch") ||
|
||||||
StringUtils::checkFileExtension(filename, ".xtc") || StringUtils::checkFileExtension(filename, ".txt") ||
|
StringUtils::checkFileExtension(filename, ".xtc") || StringUtils::checkFileExtension(filename, ".txt") ||
|
||||||
StringUtils::checkFileExtension(filename, ".md")) {
|
StringUtils::checkFileExtension(filename, ".md") || StringUtils::checkFileExtension(filename, ".bmp")) {
|
||||||
files.emplace_back(filename);
|
files.emplace_back(filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "TxtReaderActivity.h"
|
#include "TxtReaderActivity.h"
|
||||||
#include "Xtc.h"
|
#include "Xtc.h"
|
||||||
#include "XtcReaderActivity.h"
|
#include "XtcReaderActivity.h"
|
||||||
|
#include "activities/util/BmpViewerActivity.h"
|
||||||
#include "activities/util/FullScreenMessageActivity.h"
|
#include "activities/util/FullScreenMessageActivity.h"
|
||||||
#include "util/StringUtils.h"
|
#include "util/StringUtils.h"
|
||||||
|
|
||||||
@@ -29,6 +30,8 @@ bool ReaderActivity::isTxtFile(const std::string& path) {
|
|||||||
StringUtils::checkFileExtension(path, ".md"); // Treat .md as txt files (until we have a markdown reader)
|
StringUtils::checkFileExtension(path, ".md"); // Treat .md as txt files (until we have a markdown reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ReaderActivity::isBmpFile(const std::string& path) { return StringUtils::checkFileExtension(path, ".bmp"); }
|
||||||
|
|
||||||
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
||||||
if (!Storage.exists(path.c_str())) {
|
if (!Storage.exists(path.c_str())) {
|
||||||
LOG_ERR("READER", "File does not exist: %s", path.c_str());
|
LOG_ERR("READER", "File does not exist: %s", path.c_str());
|
||||||
@@ -104,6 +107,12 @@ void ReaderActivity::onGoToTxtReader(std::unique_ptr<Txt> txt) {
|
|||||||
renderer, mappedInput, std::move(txt), [this, txtPath] { goToLibrary(txtPath); }, [this] { onGoBack(); }));
|
renderer, mappedInput, std::move(txt), [this, txtPath] { goToLibrary(txtPath); }, [this] { onGoBack(); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ReaderActivity::onGoToBmpViewer(const std::string& path) {
|
||||||
|
currentBookPath = path;
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new BmpViewerActivity(renderer, mappedInput, path, [this, path] { goToLibrary(path); }));
|
||||||
|
}
|
||||||
|
|
||||||
void ReaderActivity::onEnter() {
|
void ReaderActivity::onEnter() {
|
||||||
ActivityWithSubactivity::onEnter();
|
ActivityWithSubactivity::onEnter();
|
||||||
|
|
||||||
@@ -114,6 +123,11 @@ void ReaderActivity::onEnter() {
|
|||||||
|
|
||||||
currentBookPath = initialBookPath;
|
currentBookPath = initialBookPath;
|
||||||
|
|
||||||
|
if (isBmpFile(initialBookPath)) {
|
||||||
|
onGoToBmpViewer(initialBookPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isXtcFile(initialBookPath)) {
|
if (isXtcFile(initialBookPath)) {
|
||||||
auto xtc = loadXtc(initialBookPath);
|
auto xtc = loadXtc(initialBookPath);
|
||||||
if (!xtc) {
|
if (!xtc) {
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ class ReaderActivity final : public ActivityWithSubactivity {
|
|||||||
static std::unique_ptr<Txt> loadTxt(const std::string& path);
|
static std::unique_ptr<Txt> loadTxt(const std::string& path);
|
||||||
static bool isXtcFile(const std::string& path);
|
static bool isXtcFile(const std::string& path);
|
||||||
static bool isTxtFile(const std::string& path);
|
static bool isTxtFile(const std::string& path);
|
||||||
|
static bool isBmpFile(const std::string& path);
|
||||||
|
|
||||||
static std::string extractFolderPath(const std::string& filePath);
|
static std::string extractFolderPath(const std::string& filePath);
|
||||||
void goToLibrary(const std::string& fromBookPath = "");
|
void goToLibrary(const std::string& fromBookPath = "");
|
||||||
void onGoToEpubReader(std::unique_ptr<Epub> epub);
|
void onGoToEpubReader(std::unique_ptr<Epub> epub);
|
||||||
void onGoToXtcReader(std::unique_ptr<Xtc> xtc);
|
void onGoToXtcReader(std::unique_ptr<Xtc> xtc);
|
||||||
void onGoToTxtReader(std::unique_ptr<Txt> txt);
|
void onGoToTxtReader(std::unique_ptr<Txt> txt);
|
||||||
|
void onGoToBmpViewer(const std::string& path);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialBookPath,
|
explicit ReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialBookPath,
|
||||||
|
|||||||
72
src/activities/util/BmpViewerActivity.cpp
Normal file
72
src/activities/util/BmpViewerActivity.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include "BmpViewerActivity.h"
|
||||||
|
|
||||||
|
#include <HalDisplay.h>
|
||||||
|
#include <HalStorage.h>
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
|
#include "Bitmap.h"
|
||||||
|
#include "components/UITheme.h"
|
||||||
|
#include "fontIds.h"
|
||||||
|
|
||||||
|
void BmpViewerActivity::onEnter() {
|
||||||
|
Activity::onEnter();
|
||||||
|
|
||||||
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
|
// Show loading indicator while BMP is parsed
|
||||||
|
renderer.clearScreen();
|
||||||
|
renderer.drawCenteredText(UI_10_FONT_ID, (pageHeight - renderer.getLineHeight(UI_10_FONT_ID)) / 2,
|
||||||
|
tr(STR_LOADING), true, EpdFontFamily::BOLD);
|
||||||
|
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||||
|
|
||||||
|
FsFile file;
|
||||||
|
if (!Storage.openFileForRead("BMP", filePath, file)) {
|
||||||
|
LOG_ERR("BMP", "Failed to open file: %s", filePath.c_str());
|
||||||
|
loadFailed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap bitmap(file, true);
|
||||||
|
if (bitmap.parseHeaders() != BmpReaderError::Ok) {
|
||||||
|
LOG_ERR("BMP", "Failed to parse BMP headers: %s", filePath.c_str());
|
||||||
|
file.close();
|
||||||
|
loadFailed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("BMP", "Loaded %s (%d x %d)", filePath.c_str(), bitmap.getWidth(), bitmap.getHeight());
|
||||||
|
|
||||||
|
// Compute centered position; drawBitmap handles aspect-ratio-preserving scaling
|
||||||
|
const float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
||||||
|
const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
|
||||||
|
int x, y;
|
||||||
|
if (ratio > screenRatio) {
|
||||||
|
x = 0;
|
||||||
|
y = std::round((static_cast<float>(pageHeight) - static_cast<float>(pageWidth) / ratio) / 2);
|
||||||
|
} else {
|
||||||
|
x = std::round((static_cast<float>(pageWidth) - static_cast<float>(pageHeight) * ratio) / 2);
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.clearScreen();
|
||||||
|
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||||
|
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
|
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BmpViewerActivity::loop() {
|
||||||
|
if (loadFailed) {
|
||||||
|
loadFailed = false;
|
||||||
|
onGoBack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
|
onGoBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/activities/util/BmpViewerActivity.h
Normal file
21
src/activities/util/BmpViewerActivity.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "../Activity.h"
|
||||||
|
|
||||||
|
class BmpViewerActivity final : public Activity {
|
||||||
|
std::string filePath;
|
||||||
|
const std::function<void()> onGoBack;
|
||||||
|
bool loadFailed = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BmpViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string path,
|
||||||
|
std::function<void()> onGoBack)
|
||||||
|
: Activity("BmpViewer", renderer, mappedInput),
|
||||||
|
filePath(std::move(path)),
|
||||||
|
onGoBack(std::move(onGoBack)) {}
|
||||||
|
void onEnter() override;
|
||||||
|
void loop() override;
|
||||||
|
};
|
||||||
@@ -569,7 +569,8 @@ void LyraTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const c
|
|||||||
const int x = buttonPositions[i];
|
const int x = buttonPositions[i];
|
||||||
if (labels[i] != nullptr && labels[i][0] != '\0') {
|
if (labels[i] != nullptr && labels[i][0] != '\0') {
|
||||||
// Draw the filled background and border for a FULL-sized button
|
// Draw the filled background and border for a FULL-sized button
|
||||||
renderer.fillRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, false);
|
renderer.fillRoundedRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, cornerRadius, true, true, false,
|
||||||
|
false, Color::White);
|
||||||
renderer.drawRoundedRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, 1, cornerRadius, true, true, false,
|
renderer.drawRoundedRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, 1, cornerRadius, true, true, false,
|
||||||
false, true);
|
false, true);
|
||||||
const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, labels[i]);
|
const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, labels[i]);
|
||||||
@@ -577,7 +578,8 @@ void LyraTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const c
|
|||||||
renderer.drawText(SMALL_FONT_ID, textX, pageHeight - buttonY + textYOffset, labels[i]);
|
renderer.drawText(SMALL_FONT_ID, textX, pageHeight - buttonY + textYOffset, labels[i]);
|
||||||
} else {
|
} else {
|
||||||
// Draw the filled background and border for a SMALL-sized button
|
// Draw the filled background and border for a SMALL-sized button
|
||||||
renderer.fillRect(x, pageHeight - smallButtonHeight, buttonWidth, smallButtonHeight, false);
|
renderer.fillRoundedRect(x, pageHeight - smallButtonHeight, buttonWidth, smallButtonHeight, cornerRadius, true,
|
||||||
|
true, false, false, Color::White);
|
||||||
renderer.drawRoundedRect(x, pageHeight - smallButtonHeight, buttonWidth, smallButtonHeight, 1, cornerRadius, true,
|
renderer.drawRoundedRect(x, pageHeight - smallButtonHeight, buttonWidth, smallButtonHeight, 1, cornerRadius, true,
|
||||||
true, false, false, true);
|
true, false, false, true);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user