16 Commits

Author SHA1 Message Date
pablohc
e32d41a37e fix: improve Spanish translations (#1054)
Some checks failed
CI (build) / clang-format (push) Has been cancelled
CI (build) / cppcheck (push) Has been cancelled
CI (build) / build (push) Has been cancelled
CI (build) / Test Status (push) Has been cancelled
## Summary

* **What is the goal of this PR?**
* improve Spanish translations

* **What changes are included?**

- Fix typos and accents (Librería, conexión, etc.)
- Translate untranslated strings (BOOTING, SLEEPING, etc.)
- Improve consistency and conciseness
- Fix question mark placement (¿...?)
- Standardize terminology (Punto de Acceso, Suspensión, etc.)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< YES >**_
2026-02-21 22:30:42 +11:00
Lev Roland-Kalb
7717ae2683 feat: Added BmpViewer activity for viewing .bmp images in file browser (#887)
## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

Implements new feature for viewing .bmp files directly from the "Browse
Files" menu.

* **What changes are included?**

You can now view .bmp files when browsing. You can click the select
button to open the file, and then click back to close it and continue
browsing in the same location. Once open a file will display on the
screen with no additional options to interact outside of exiting with
the back button.

The attached video shows this feature in action:


https://github.com/user-attachments/assets/9659b6da-abf7-4458-b158-e11c248c8bef

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

The changes implemented in #884 are also present here as this feature is
actually what led to me noticing this issue. I figured I would add that
PR as a separate request in case that one could be more easily merged
given this feature is significantly more complicated and will likely be
subject to more intense review.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **YES**

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-21 12:27:25 +03:00
Àngel
f02c9784ec feat: add Catalan strings (#1049)
## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

Add support for Catalan language user interface.
* **What changes are included?**

A new i18n file catalan.yml.

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code?  NO
2026-02-21 12:26:50 +03:00
Luke Stein
693dba4c94 fix: Shorten "Forget Wifi" button labels to fit on button (#1045) 2026-02-20 20:05:03 -05:00
ariel-lindemann
9c55c15a72 feat: added Romanian strings (#987)
## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

To add upport for a romanian language user interface.

* **What changes are included?**

A new i18n file `romanian.yml`

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
2026-02-20 20:27:43 +03:00
Arnau
6ba9658f15 fix: typo in USER_GUIDE.md (#1036)
## Summary

Just fixed a typo `Xtink` -> `Xteink`

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
2026-02-20 17:22:17 +03:00
Dave Allie
22b96ec22a fix: Destroy CSS Cache file when invalid (#1018)
## Summary

* Destroy CSS Cache file when invalid

## Additional Context

* Fixes issue where it would attempt to rebuild every book open

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? No
2026-02-20 17:04:50 +11:00
pablohc
c9faf2a8c0 fix: continue reading card classic theme (#990)
## Summary

* **What is the goal of this PR?** 
* **What changes are included?**

- Adapt card width to cover image aspect ratio in Classic theme
- Increase homeTopPadding from 20px to 40px to avoid overlap with
battery icon
- Card width now calculated from BMP dimensions instead of fixed 240px
- Maximum card width limited to 90% of screen width
- Falls back to original behavior (half screen width) when no cover
available

## Additional Context

* Solve conflicts in PR #683 

Before:
<img width="1052" height="1014" alt="image"
src="https://github.com/user-attachments/assets/6c857913-d697-4e9e-9695-443c0a4c0804"
/>

PR:

![Screenshot_2026-02-19-14-22-36-68_99c04817c0de5652397fc8b56c3b3817](https://github.com/user-attachments/assets/81505728-d42e-41bd-bd77-44848e05b1eb)


---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< YES >**_
2026-02-20 16:35:58 +11:00
Dave Allie
356fe9a31e fix: Strip unused CSS rules (#1014)
## Summary

* In a sample book I loaded, it had 900+ CSS rules, and took up 180kB of
memory loading the cache in
* Looking at the rules, a lot of them were completely useless as we only
ever apply look for 3 kinds of CSS rules:
    * `tag`
    * `tag.class1`
    * `.class1`
* Stripping out CSS rules with descendant, nested, attribute matching,
sibling matching, pseudo element selection (as we never actually read
these from the cache) reduced the rule count down to 200

## Additional Context

* I've left in `.class1.class2` rules for now, even though we
technically can never match on them as they're likely to be addressed
soonest out of the all the CSS expansion
* Because we don't ever delete the CSS cache, users will need to delete
the book cache through the menu in order to get this new logic
* A new PR should be done up to address this - tracked here
https://github.com/crosspoint-reader/crosspoint-reader/issues/1015

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? No
2026-02-20 16:35:49 +11:00
DestinySpeaker
5da23eed82 fix: Fixed Image Sizing When No Width is Set (#1002)
## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
When no width is set for an image, the image currently automatically
sets to the width of the page. However, with this fix, the parser will
use the height and aspect ratio of the image to properly set a height
for it. See below example:


Before:

![IMG_8862](https://github.com/user-attachments/assets/64b3b92f-1165-45ca-8bdb-8e69613d9725)

After:

![IMG_8863](https://github.com/user-attachments/assets/5cb99b12-d150-4b37-ae4c-c8a20eb9f3a0)


* **What changes are included?✱
Changes to the CSS parser

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? YES, Cursor
2026-02-20 16:34:28 +11:00
ariel-lindemann
388fbf206a docs: image support marked as completed (#1008)
## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

Epub image support was added in #556. The goal of this PR is to document
that in the readme.

* **What changes are included?**

Only the checkmark in the readme.

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
2026-02-20 13:13:25 +11:00
martin brook
2a38bfd8af fix: use double FAST_REFRESH to prevent washout on large grey images (#957)
## Summary

Fixes https://github.com/crosspoint-reader/crosspoint-reader/issues/1011

Use double FAST_REFRESH for image pages to prevent grayscale washout,
HALF_REFRESH sets e-ink particles too firmly for the grayscale LUT to
adjust, causing washed-out images (especially large, light-gray ones).
Replace HALF_REFRESH with @pablohc's double FAST_REFRESH technique:
blank only the image bounding box area, then re-render with images. This
clears ghosting while keeping particles loosely set for grayscale.

## Additional Context

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**<  PARTIALLY  >**_
2026-02-20 12:05:15 +11:00
Lev Roland-Kalb
d7f89e6c0d fix: re-implementing Cover Outlines for the new Lyra Themes (#1017)
## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

Improve legibility of Cover Icons on the home page and elsewhere. Fixes
#898
Re implements the changes made in #907 that were overwritten by the new
lyra themes

* **What changes are included?**
Cover outline is now shown even when cover is found to prevent issues
with low contrast covers blending into the background. Photo is attached
below:

<img width="1137" height="758" alt="Untitled (4)"
src="https://github.com/user-attachments/assets/21ae6c94-4b43-4a0c-bec7-a6e4c642ffad"
/>



## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

Re implements the changes made in #907 that were overwritten by the new
lyra themes

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**Yes**_
2026-02-20 11:56:19 +11:00
jpirnay
07d715e32d perf: Improve font drawing performance (#978)
## Summary

* ``renderChar`` checked ``is2Bit`` on every pixel inside the inner
loop, even though the value is constant for the lifetime of a single
glyph
* Moved the branch above both loops so each path (2-bit antialiased /
1-bit monochrome) runs without a per-pixel conditional
* Eliminates redundant work in the two inner loops that render font
glyphs to the frame buffer, targeting ``renderChar`` and
``drawTextRotated90CW`` in ``GfxRenderer.cpp``

## Additional Context
* Measured on device using a dedicated framebuffer benchmark (no display
refresh). 100 repetitions of "The quick brown fox jumps".

| Test            | Before          | After           | Change  |
|-----------------|-----------------|-----------------|---------|
| drawText UI12   | 1,337 µs/call | 1,024 µs/call | −23%|
| drawText Bookerly14 | 2.174 µs / call | 1,847 µs/call | −15% |

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**PARTIALLY**_ Claude did
the analysis and wrote the benchmarks
2026-02-20 11:12:05 +11:00
Uri Tauber
63b2643534 fix: Fix dangling pointer (#1010)
## Summary

* **What is the goal of this PR?** Small fix for bug I found.

## Additional Context


1. `RecentBooksActivity::loop()` calls
`onSelectBook(recentBooks[selectorIndex].path)` - passing a
**reference** to the path
  2. `onSelectBook` is `onGoToReader` which first calls `exitActivity()`
3. `exitActivity()` triggers `RecentBooksActivity::onExit()` which call
`recentBooks.clear()`
4. The string reference `initialEpubPath` is now a **dangling
reference** - the underlying string has been destroyed
5. When the reference is then used in `new ReaderActivity(...)`, it
reads garbage memory
6. The same issue occurs in `HomeActivity` at line 200 with the same
pattern

The fix is to make a copy of the string in `onGoToReader` before calling
`exitActivity()`, so the path data persists even after the activity
clears its data structures.

---

### AI Usage

Did you use AI tools to help write this code? _**< YES >**_ Claude found
the bug, after I shared with it a serial log.
2026-02-20 11:08:37 +11:00
Vincent Politzer
cabbfcfd7e fix: Use HalPowerManager for battery percentage (#1005)
## Summary

The introduction of `HalGPIO` moved the `BatteryMonitor battery` object
into the member function `HalGPIO::getBatteryPercentage()`.

Then, with the introduction of `HalPowerManager`, this function was
moved to `HalPowerManager::getBatteryPercentage()`.

However, the original `BatteryMonitor battery` object is still utilized
by themes for displaying the battery percentage.

This PR replaces these deprecated uses of `BatteryMonitor battery` with
the new `HalPowerManager::getBatteryPercentage()` function.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO **_
2026-02-20 00:05:35 +01:00
39 changed files with 1471 additions and 426 deletions

View File

@@ -26,7 +26,7 @@ This project is **not affiliated with Xteink**; it's built as a community projec
## Features & Usage ## Features & Usage
- [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
- [x] Saved reading position - [x] Saved reading position
- [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

View File

@@ -28,7 +28,7 @@ Welcome to the **CrossPoint** firmware. This guide outlines the hardware control
## 1. Hardware Overview ## 1. Hardware Overview
The device utilises the standard buttons on the Xtink X4 (in the same layout as the manufacturer firmware, by default): The device utilises the standard buttons on the Xteink X4 (in the same layout as the manufacturer firmware, by default):
### Button Layout ### Button Layout
| Location | Buttons | | Location | Buttons |

View File

@@ -30,6 +30,13 @@ If you'd like to add your name to this list, please open a PR adding yourself an
## Spanish ## Spanish
- [yeyeto2788](https://github.com/yeyeto2788) - [yeyeto2788](https://github.com/yeyeto2788)
- [Skrzakk](https://github.com/Skrzakk) - [Skrzakk](https://github.com/Skrzakk)
- [pablohc](https://github.com/pablohc)
## Swedish ## Swedish
- [dawiik](https://github.com/dawiik) - [dawiik](https://github.com/dawiik)
## Romanian
- [ariel-lindemann](https://github.com/ariel-lindemann)
## Catalan
- [angeldenom](https://github.com/angeldenom)

View File

@@ -268,8 +268,14 @@ void Epub::parseCssFiles() const {
LOG_DBG("EBP", "No CSS files to parse, but CssParser created for inline styles"); LOG_DBG("EBP", "No CSS files to parse, but CssParser created for inline styles");
} }
LOG_DBG("EBP", "CSS files to parse: %zu", cssFiles.size());
// See if we have a cached version of the CSS rules // See if we have a cached version of the CSS rules
if (!cssParser->hasCache()) { if (cssParser->hasCache()) {
LOG_DBG("EBP", "CSS cache exists, skipping parseCssFiles");
return;
}
// No cache yet - parse CSS files // No cache yet - parse CSS files
for (const auto& cssPath : cssFiles) { for (const auto& cssPath : cssFiles) {
LOG_DBG("EBP", "Parsing CSS file: %s", cssPath.c_str()); LOG_DBG("EBP", "Parsing CSS file: %s", cssPath.c_str());
@@ -326,7 +332,6 @@ void Epub::parseCssFiles() const {
LOG_DBG("EBP", "Loaded %zu CSS style rules from %zu files", cssParser->ruleCount(), cssFiles.size()); LOG_DBG("EBP", "Loaded %zu CSS style rules from %zu files", cssParser->ruleCount(), cssFiles.size());
} }
}
// load in the meta data for the epub file // load in the meta data for the epub file
bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) { bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
@@ -339,14 +344,20 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
// Try to load existing cache first // Try to load existing cache first
if (bookMetadataCache->load()) { if (bookMetadataCache->load()) {
if (!skipLoadingCss && !cssParser->hasCache()) { if (!skipLoadingCss) {
LOG_DBG("EBP", "Warning: CSS rules cache not found, attempting to parse CSS files"); // Rebuild CSS cache when missing or when cache version changed (loadFromCache removes stale file)
// to get CSS file list if (!cssParser->hasCache() || !cssParser->loadFromCache()) {
LOG_DBG("EBP", "CSS rules cache missing or stale, attempting to parse CSS files");
cssParser->deleteCache();
if (!parseContentOpf(bookMetadataCache->coreMetadata)) { if (!parseContentOpf(bookMetadataCache->coreMetadata)) {
LOG_ERR("EBP", "Could not parse content.opf from cached bookMetadata for CSS files"); LOG_ERR("EBP", "Could not parse content.opf from cached bookMetadata for CSS files");
// continue anyway - book will work without CSS and we'll still load any inline style CSS // continue anyway - book will work without CSS and we'll still load any inline style CSS
} }
parseCssFiles(); parseCssFiles();
// Invalidate section caches so they are rebuilt with the new CSS
Storage.removeDir((cachePath + "/sections").c_str());
}
} }
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str()); LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());
return true; return true;
@@ -447,6 +458,7 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
if (!skipLoadingCss) { if (!skipLoadingCss) {
// Parse CSS files after cache reload // Parse CSS files after cache reload
parseCssFiles(); parseCssFiles();
Storage.removeDir((cachePath + "/sections").c_str());
} }
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str()); LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());

View File

@@ -49,6 +49,7 @@ class PageImage final : public PageElement {
bool serialize(FsFile& file) override; bool serialize(FsFile& file) override;
PageElementTag getTag() const override { return TAG_PageImage; } PageElementTag getTag() const override { return TAG_PageImage; }
static std::unique_ptr<PageImage> deserialize(FsFile& file); static std::unique_ptr<PageImage> deserialize(FsFile& file);
const ImageBlock& getImageBlock() const { return *imageBlock; }
}; };
class Page { class Page {
@@ -64,4 +65,32 @@ class Page {
return std::any_of(elements.begin(), elements.end(), return std::any_of(elements.begin(), elements.end(),
[](const std::shared_ptr<PageElement>& el) { return el->getTag() == TAG_PageImage; }); [](const std::shared_ptr<PageElement>& el) { return el->getTag() == TAG_PageImage; });
} }
// Get bounding box of all images on the page (union of image rects)
// Returns false if no images. Coordinates are relative to page origin.
bool getImageBoundingBox(int16_t& outX, int16_t& outY, int16_t& outW, int16_t& outH) const {
bool found = false;
int16_t minX = INT16_MAX, minY = INT16_MAX, maxX = INT16_MIN, maxY = INT16_MIN;
for (const auto& el : elements) {
if (el->getTag() == TAG_PageImage) {
const auto& img = static_cast<const PageImage&>(*el);
int16_t x = img.xPos;
int16_t y = img.yPos;
int16_t right = x + img.getImageBlock().getWidth();
int16_t bottom = y + img.getImageBlock().getHeight();
minX = std::min(minX, x);
minY = std::min(minY, y);
maxX = std::max(maxX, right);
maxY = std::max(maxY, bottom);
found = true;
}
}
if (found) {
outX = minX;
outY = minY;
outW = maxX - minX;
outH = maxY - minY;
}
return found;
}
}; };

View File

@@ -4,6 +4,7 @@
#include <Logging.h> #include <Logging.h>
#include <Serialization.h> #include <Serialization.h>
#include "Epub/css/CssParser.h"
#include "Page.h" #include "Page.h"
#include "hyphenation/Hyphenator.h" #include "hyphenation/Hyphenator.h"
#include "parsers/ChapterHtmlSlimParser.h" #include "parsers/ChapterHtmlSlimParser.h"

View File

@@ -74,7 +74,7 @@ std::string CssParser::normalized(const std::string& s) {
} }
// Remove trailing space // Remove trailing space
if (!result.empty() && result.back() == ' ') { while (!result.empty() && (result.back() == ' ' || result.back() == '\n')) {
result.pop_back(); result.pop_back();
} }
return result; return result;
@@ -189,10 +189,18 @@ CssTextDecoration CssParser::interpretDecoration(const std::string& val) {
} }
CssLength CssParser::interpretLength(const std::string& val) { CssLength CssParser::interpretLength(const std::string& val) {
const std::string v = normalized(val); CssLength result;
if (v.empty()) return CssLength{}; tryInterpretLength(val, result);
return result;
}
bool CssParser::tryInterpretLength(const std::string& val, CssLength& out) {
const std::string v = normalized(val);
if (v.empty()) {
out = CssLength{};
return false;
}
// Find where the number ends
size_t unitStart = v.size(); size_t unitStart = v.size();
for (size_t i = 0; i < v.size(); ++i) { for (size_t i = 0; i < v.size(); ++i) {
const char c = v[i]; const char c = v[i];
@@ -205,12 +213,13 @@ CssLength CssParser::interpretLength(const std::string& val) {
const std::string numPart = v.substr(0, unitStart); const std::string numPart = v.substr(0, unitStart);
const std::string unitPart = v.substr(unitStart); const std::string unitPart = v.substr(unitStart);
// Parse numeric value
char* endPtr = nullptr; char* endPtr = nullptr;
const float numericValue = std::strtof(numPart.c_str(), &endPtr); const float numericValue = std::strtof(numPart.c_str(), &endPtr);
if (endPtr == numPart.c_str()) return CssLength{}; // No number parsed if (endPtr == numPart.c_str()) {
out = CssLength{};
return false; // No number parsed (e.g. auto, inherit, initial)
}
// Determine unit type (preserve for deferred resolution)
auto unit = CssUnit::Pixels; auto unit = CssUnit::Pixels;
if (unitPart == "em") { if (unitPart == "em") {
unit = CssUnit::Em; unit = CssUnit::Em;
@@ -221,10 +230,11 @@ CssLength CssParser::interpretLength(const std::string& val) {
} else if (unitPart == "%") { } else if (unitPart == "%") {
unit = CssUnit::Percent; unit = CssUnit::Percent;
} }
// px and unitless default to Pixels
return CssLength{numericValue, unit}; out = CssLength{numericValue, unit};
return true;
} }
// Declaration parsing // Declaration parsing
void CssParser::parseDeclarationIntoStyle(const std::string& decl, CssStyle& style, std::string& propNameBuf, void CssParser::parseDeclarationIntoStyle(const std::string& decl, CssStyle& style, std::string& propNameBuf,
@@ -295,6 +305,18 @@ void CssParser::parseDeclarationIntoStyle(const std::string& decl, CssStyle& sty
style.defined.paddingTop = style.defined.paddingRight = style.defined.paddingBottom = style.defined.paddingLeft = style.defined.paddingTop = style.defined.paddingRight = style.defined.paddingBottom = style.defined.paddingLeft =
1; 1;
} }
} else if (propNameBuf == "height") {
CssLength len;
if (tryInterpretLength(propValueBuf, len)) {
style.imageHeight = len;
style.defined.imageHeight = 1;
}
} else if (propNameBuf == "width") {
CssLength len;
if (tryInterpretLength(propValueBuf, len)) {
style.imageWidth = len;
style.defined.imageWidth = 1;
}
} }
} }
@@ -343,6 +365,56 @@ void CssParser::processRuleBlockWithStyle(const std::string& selectorGroup, cons
std::string key = normalized(sel); std::string key = normalized(sel);
if (key.empty()) continue; if (key.empty()) continue;
// TODO: Consider adding support for sibling css selectors in the future
// Ensure no + in selector as we don't support adjacent CSS selectors for now
if (key.find('+') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for direct nested css selectors in the future
// Ensure no > in selector as we don't support nested CSS selectors for now
if (key.find('>') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for attribute css selectors in the future
// Ensure no [ in selector as we don't support attribute CSS selectors for now
if (key.find('[') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for pseudo selectors in the future
// Ensure no : in selector as we don't support pseudo CSS selectors for now
if (key.find(':') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for ID css selectors in the future
// Ensure no # in selector as we don't support ID CSS selectors for now
if (key.find('#') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for general sibling combinator selectors in the future
// Ensure no ~ in selector as we don't support general sibling combinator CSS selectors for now
if (key.find('~') != std::string_view::npos) {
continue;
}
// TODO: Consider adding support for wildcard css selectors in the future
// Ensure no * in selector as we don't support wildcard CSS selectors for now
if (key.find('*') != std::string_view::npos) {
continue;
}
// TODO: Add support for more complex selectors in the future
// At the moment, we only ever check for `tag`, `tag.class1` or `.class1`
// If the selector has whitespace in it, then it's either a CSS selector for a descendant element (e.g. `tag1 tag2`)
// or some other slightly more advanced CSS selector which we don't support yet
if (key.find(' ') != std::string_view::npos) {
continue;
}
// Skip if this would exceed the rule limit // Skip if this would exceed the rule limit
if (rulesBySelector_.size() >= MAX_RULES) { if (rulesBySelector_.size() >= MAX_RULES) {
LOG_DBG("CSS", "Reached max rules limit, stopping selector processing"); LOG_DBG("CSS", "Reached max rules limit, stopping selector processing");
@@ -528,6 +600,7 @@ CssStyle CssParser::resolveStyle(const std::string& tagName, const std::string&
result.applyOver(tagIt->second); result.applyOver(tagIt->second);
} }
// TODO: Support combinations of classes (e.g. style on .class1.class2)
// 2. Apply class styles (medium priority) // 2. Apply class styles (medium priority)
if (!classAttr.empty()) { if (!classAttr.empty()) {
const auto classes = splitWhitespace(classAttr); const auto classes = splitWhitespace(classAttr);
@@ -541,6 +614,7 @@ CssStyle CssParser::resolveStyle(const std::string& tagName, const std::string&
} }
} }
// TODO: Support combinations of classes (e.g. style on p.class1.class2)
// 3. Apply element.class styles (higher priority) // 3. Apply element.class styles (higher priority)
for (const auto& cls : classes) { for (const auto& cls : classes) {
std::string combinedKey = tag + "." + normalized(cls); std::string combinedKey = tag + "." + normalized(cls);
@@ -561,12 +635,15 @@ CssStyle CssParser::parseInlineStyle(const std::string& styleValue) { return par
// Cache serialization // Cache serialization
// Cache format version - increment when format changes // Cache file name (version is CssParser::CSS_CACHE_VERSION)
constexpr uint8_t CSS_CACHE_VERSION = 2;
constexpr char rulesCache[] = "/css_rules.cache"; constexpr char rulesCache[] = "/css_rules.cache";
bool CssParser::hasCache() const { return Storage.exists((cachePath + rulesCache).c_str()); } bool CssParser::hasCache() const { return Storage.exists((cachePath + rulesCache).c_str()); }
void CssParser::deleteCache() const {
if (hasCache()) Storage.remove((cachePath + rulesCache).c_str());
}
bool CssParser::saveToCache() const { bool CssParser::saveToCache() const {
if (cachePath.empty()) { if (cachePath.empty()) {
return false; return false;
@@ -578,7 +655,7 @@ bool CssParser::saveToCache() const {
} }
// Write version // Write version
file.write(CSS_CACHE_VERSION); file.write(CssParser::CSS_CACHE_VERSION);
// Write rule count // Write rule count
const auto ruleCount = static_cast<uint16_t>(rulesBySelector_.size()); const auto ruleCount = static_cast<uint16_t>(rulesBySelector_.size());
@@ -613,6 +690,8 @@ bool CssParser::saveToCache() const {
writeLength(style.paddingBottom); writeLength(style.paddingBottom);
writeLength(style.paddingLeft); writeLength(style.paddingLeft);
writeLength(style.paddingRight); writeLength(style.paddingRight);
writeLength(style.imageHeight);
writeLength(style.imageWidth);
// Write defined flags as uint16_t // Write defined flags as uint16_t
uint16_t definedBits = 0; uint16_t definedBits = 0;
@@ -629,6 +708,8 @@ bool CssParser::saveToCache() const {
if (style.defined.paddingBottom) definedBits |= 1 << 10; if (style.defined.paddingBottom) definedBits |= 1 << 10;
if (style.defined.paddingLeft) definedBits |= 1 << 11; if (style.defined.paddingLeft) definedBits |= 1 << 11;
if (style.defined.paddingRight) definedBits |= 1 << 12; if (style.defined.paddingRight) definedBits |= 1 << 12;
if (style.defined.imageHeight) definedBits |= 1 << 13;
if (style.defined.imageWidth) definedBits |= 1 << 14;
file.write(reinterpret_cast<const uint8_t*>(&definedBits), sizeof(definedBits)); file.write(reinterpret_cast<const uint8_t*>(&definedBits), sizeof(definedBits));
} }
@@ -652,9 +733,11 @@ bool CssParser::loadFromCache() {
// Read and verify version // Read and verify version
uint8_t version = 0; uint8_t version = 0;
if (file.read(&version, 1) != 1 || version != CSS_CACHE_VERSION) { if (file.read(&version, 1) != 1 || version != CssParser::CSS_CACHE_VERSION) {
LOG_DBG("CSS", "Cache version mismatch (got %u, expected %u)", version, CSS_CACHE_VERSION); LOG_DBG("CSS", "Cache version mismatch (got %u, expected %u), removing stale cache for rebuild", version,
CssParser::CSS_CACHE_VERSION);
file.close(); file.close();
Storage.remove((cachePath + rulesCache).c_str());
return false; return false;
} }
@@ -730,7 +813,8 @@ bool CssParser::loadFromCache() {
if (!readLength(style.textIndent) || !readLength(style.marginTop) || !readLength(style.marginBottom) || if (!readLength(style.textIndent) || !readLength(style.marginTop) || !readLength(style.marginBottom) ||
!readLength(style.marginLeft) || !readLength(style.marginRight) || !readLength(style.paddingTop) || !readLength(style.marginLeft) || !readLength(style.marginRight) || !readLength(style.paddingTop) ||
!readLength(style.paddingBottom) || !readLength(style.paddingLeft) || !readLength(style.paddingRight)) { !readLength(style.paddingBottom) || !readLength(style.paddingLeft) || !readLength(style.paddingRight) ||
!readLength(style.imageHeight) || !readLength(style.imageWidth)) {
rulesBySelector_.clear(); rulesBySelector_.clear();
file.close(); file.close();
return false; return false;
@@ -756,6 +840,8 @@ bool CssParser::loadFromCache() {
style.defined.paddingBottom = (definedBits & 1 << 10) != 0; style.defined.paddingBottom = (definedBits & 1 << 10) != 0;
style.defined.paddingLeft = (definedBits & 1 << 11) != 0; style.defined.paddingLeft = (definedBits & 1 << 11) != 0;
style.defined.paddingRight = (definedBits & 1 << 12) != 0; style.defined.paddingRight = (definedBits & 1 << 12) != 0;
style.defined.imageHeight = (definedBits & 1 << 13) != 0;
style.defined.imageWidth = (definedBits & 1 << 14) != 0;
rulesBySelector_[selector] = style; rulesBySelector_[selector] = style;
} }

View File

@@ -30,6 +30,9 @@
*/ */
class CssParser { class CssParser {
public: public:
// Bump when CSS cache format or rules change; section caches are invalidated when this changes
static constexpr uint8_t CSS_CACHE_VERSION = 3;
explicit CssParser(std::string cachePath) : cachePath(std::move(cachePath)) {} explicit CssParser(std::string cachePath) : cachePath(std::move(cachePath)) {}
~CssParser() = default; ~CssParser() = default;
@@ -82,6 +85,11 @@ class CssParser {
*/ */
bool hasCache() const; bool hasCache() const;
/**
* Delete CSS rules cache file exists
*/
void deleteCache() const;
/** /**
* Save parsed CSS rules to a cache file. * Save parsed CSS rules to a cache file.
* @return true if cache was written successfully * @return true if cache was written successfully
@@ -113,6 +121,8 @@ class CssParser {
static CssFontWeight interpretFontWeight(const std::string& val); static CssFontWeight interpretFontWeight(const std::string& val);
static CssTextDecoration interpretDecoration(const std::string& val); static CssTextDecoration interpretDecoration(const std::string& val);
static CssLength interpretLength(const std::string& val); static CssLength interpretLength(const std::string& val);
/** Returns true only when a numeric length was parsed (e.g. 2em, 50%). False for auto/inherit/initial. */
static bool tryInterpretLength(const std::string& val, CssLength& out);
// String utilities // String utilities
static std::string normalized(const std::string& s); static std::string normalized(const std::string& s);

View File

@@ -69,6 +69,8 @@ struct CssPropertyFlags {
uint16_t paddingBottom : 1; uint16_t paddingBottom : 1;
uint16_t paddingLeft : 1; uint16_t paddingLeft : 1;
uint16_t paddingRight : 1; uint16_t paddingRight : 1;
uint16_t imageHeight : 1;
uint16_t imageWidth : 1;
CssPropertyFlags() CssPropertyFlags()
: textAlign(0), : textAlign(0),
@@ -83,17 +85,21 @@ struct CssPropertyFlags {
paddingTop(0), paddingTop(0),
paddingBottom(0), paddingBottom(0),
paddingLeft(0), paddingLeft(0),
paddingRight(0) {} paddingRight(0),
imageHeight(0),
imageWidth(0) {}
[[nodiscard]] bool anySet() const { [[nodiscard]] bool anySet() const {
return textAlign || fontStyle || fontWeight || textDecoration || textIndent || marginTop || marginBottom || return textAlign || fontStyle || fontWeight || textDecoration || textIndent || marginTop || marginBottom ||
marginLeft || marginRight || paddingTop || paddingBottom || paddingLeft || paddingRight; marginLeft || marginRight || paddingTop || paddingBottom || paddingLeft || paddingRight || imageHeight ||
imageWidth;
} }
void clearAll() { void clearAll() {
textAlign = fontStyle = fontWeight = textDecoration = textIndent = 0; textAlign = fontStyle = fontWeight = textDecoration = textIndent = 0;
marginTop = marginBottom = marginLeft = marginRight = 0; marginTop = marginBottom = marginLeft = marginRight = 0;
paddingTop = paddingBottom = paddingLeft = paddingRight = 0; paddingTop = paddingBottom = paddingLeft = paddingRight = 0;
imageHeight = imageWidth = 0;
} }
}; };
@@ -115,6 +121,8 @@ struct CssStyle {
CssLength paddingBottom; // Padding after CssLength paddingBottom; // Padding after
CssLength paddingLeft; // Padding left CssLength paddingLeft; // Padding left
CssLength paddingRight; // Padding right CssLength paddingRight; // Padding right
CssLength imageHeight; // Height for img (e.g. 2em) width derived from aspect ratio when only height set
CssLength imageWidth; // Width for img when both or only width set
CssPropertyFlags defined; // Tracks which properties were explicitly set CssPropertyFlags defined; // Tracks which properties were explicitly set
@@ -173,6 +181,14 @@ struct CssStyle {
paddingRight = base.paddingRight; paddingRight = base.paddingRight;
defined.paddingRight = 1; defined.paddingRight = 1;
} }
if (base.hasImageHeight()) {
imageHeight = base.imageHeight;
defined.imageHeight = 1;
}
if (base.hasImageWidth()) {
imageWidth = base.imageWidth;
defined.imageWidth = 1;
}
} }
[[nodiscard]] bool hasTextAlign() const { return defined.textAlign; } [[nodiscard]] bool hasTextAlign() const { return defined.textAlign; }
@@ -188,6 +204,8 @@ struct CssStyle {
[[nodiscard]] bool hasPaddingBottom() const { return defined.paddingBottom; } [[nodiscard]] bool hasPaddingBottom() const { return defined.paddingBottom; }
[[nodiscard]] bool hasPaddingLeft() const { return defined.paddingLeft; } [[nodiscard]] bool hasPaddingLeft() const { return defined.paddingLeft; }
[[nodiscard]] bool hasPaddingRight() const { return defined.paddingRight; } [[nodiscard]] bool hasPaddingRight() const { return defined.paddingRight; }
[[nodiscard]] bool hasImageHeight() const { return defined.imageHeight; }
[[nodiscard]] bool hasImageWidth() const { return defined.imageWidth; }
void reset() { void reset() {
textAlign = CssTextAlign::Left; textAlign = CssTextAlign::Left;
@@ -197,6 +215,7 @@ struct CssStyle {
textIndent = CssLength{}; textIndent = CssLength{};
marginTop = marginBottom = marginLeft = marginRight = CssLength{}; marginTop = marginBottom = marginLeft = marginRight = CssLength{};
paddingTop = paddingBottom = paddingLeft = paddingRight = CssLength{}; paddingTop = paddingBottom = paddingLeft = paddingRight = CssLength{};
imageHeight = imageWidth = CssLength{};
defined.clearAll(); defined.clearAll();
} }
}; };

View File

@@ -257,6 +257,81 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
if (decoder && decoder->getDimensions(cachedImagePath, dims)) { if (decoder && decoder->getDimensions(cachedImagePath, dims)) {
LOG_DBG("EHP", "Image dimensions: %dx%d", dims.width, dims.height); LOG_DBG("EHP", "Image dimensions: %dx%d", dims.width, dims.height);
int displayWidth = 0;
int displayHeight = 0;
const float emSize =
static_cast<float>(self->renderer.getLineHeight(self->fontId)) * self->lineCompression;
CssStyle imgStyle = self->cssParser ? self->cssParser->resolveStyle("img", classAttr) : CssStyle{};
// Merge inline style (e.g. style="height: 2em") so it overrides stylesheet rules
if (!styleAttr.empty()) {
imgStyle.applyOver(CssParser::parseInlineStyle(styleAttr));
}
const bool hasCssHeight = imgStyle.hasImageHeight();
const bool hasCssWidth = imgStyle.hasImageWidth();
if (hasCssHeight && hasCssWidth && dims.width > 0 && dims.height > 0) {
// Both CSS height and width set: resolve both, then clamp to viewport preserving requested ratio
displayHeight = static_cast<int>(
imgStyle.imageHeight.toPixels(emSize, static_cast<float>(self->viewportHeight)) + 0.5f);
displayWidth = static_cast<int>(
imgStyle.imageWidth.toPixels(emSize, static_cast<float>(self->viewportWidth)) + 0.5f);
if (displayHeight < 1) displayHeight = 1;
if (displayWidth < 1) displayWidth = 1;
if (displayWidth > self->viewportWidth || displayHeight > self->viewportHeight) {
float scaleX = (displayWidth > self->viewportWidth)
? static_cast<float>(self->viewportWidth) / displayWidth
: 1.0f;
float scaleY = (displayHeight > self->viewportHeight)
? static_cast<float>(self->viewportHeight) / displayHeight
: 1.0f;
float scale = (scaleX < scaleY) ? scaleX : scaleY;
displayWidth = static_cast<int>(displayWidth * scale + 0.5f);
displayHeight = static_cast<int>(displayHeight * scale + 0.5f);
if (displayWidth < 1) displayWidth = 1;
if (displayHeight < 1) displayHeight = 1;
}
LOG_DBG("EHP", "Display size from CSS height+width: %dx%d", displayWidth, displayHeight);
} else if (hasCssHeight && !hasCssWidth && dims.width > 0 && dims.height > 0) {
// Use CSS height (resolve % against viewport height) and derive width from aspect ratio
displayHeight = static_cast<int>(
imgStyle.imageHeight.toPixels(emSize, static_cast<float>(self->viewportHeight)) + 0.5f);
if (displayHeight < 1) displayHeight = 1;
displayWidth =
static_cast<int>(displayHeight * (static_cast<float>(dims.width) / dims.height) + 0.5f);
if (displayHeight > self->viewportHeight) {
displayHeight = self->viewportHeight;
// Rescale width to preserve aspect ratio when height is clamped
displayWidth =
static_cast<int>(displayHeight * (static_cast<float>(dims.width) / dims.height) + 0.5f);
if (displayWidth < 1) displayWidth = 1;
}
if (displayWidth > self->viewportWidth) {
displayWidth = self->viewportWidth;
// Rescale height to preserve aspect ratio when width is clamped
displayHeight =
static_cast<int>(displayWidth * (static_cast<float>(dims.height) / dims.width) + 0.5f);
if (displayHeight < 1) displayHeight = 1;
}
if (displayWidth < 1) displayWidth = 1;
LOG_DBG("EHP", "Display size from CSS height: %dx%d", displayWidth, displayHeight);
} else if (hasCssWidth && !hasCssHeight && dims.width > 0 && dims.height > 0) {
// Use CSS width (resolve % against viewport width) and derive height from aspect ratio
displayWidth = static_cast<int>(
imgStyle.imageWidth.toPixels(emSize, static_cast<float>(self->viewportWidth)) + 0.5f);
if (displayWidth > self->viewportWidth) displayWidth = self->viewportWidth;
if (displayWidth < 1) displayWidth = 1;
displayHeight =
static_cast<int>(displayWidth * (static_cast<float>(dims.height) / dims.width) + 0.5f);
if (displayHeight > self->viewportHeight) {
displayHeight = self->viewportHeight;
// Rescale width to preserve aspect ratio when height is clamped
displayWidth =
static_cast<int>(displayHeight * (static_cast<float>(dims.width) / dims.height) + 0.5f);
if (displayWidth < 1) displayWidth = 1;
}
if (displayHeight < 1) displayHeight = 1;
LOG_DBG("EHP", "Display size from CSS width: %dx%d", displayWidth, displayHeight);
} else {
// Scale to fit viewport while maintaining aspect ratio // Scale to fit viewport while maintaining aspect ratio
int maxWidth = self->viewportWidth; int maxWidth = self->viewportWidth;
int maxHeight = self->viewportHeight; int maxHeight = self->viewportHeight;
@@ -265,10 +340,10 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
float scale = (scaleX < scaleY) ? scaleX : scaleY; float scale = (scaleX < scaleY) ? scaleX : scaleY;
if (scale > 1.0f) scale = 1.0f; if (scale > 1.0f) scale = 1.0f;
int displayWidth = (int)(dims.width * scale); displayWidth = (int)(dims.width * scale);
int displayHeight = (int)(dims.height * scale); displayHeight = (int)(dims.height * scale);
LOG_DBG("EHP", "Display size: %dx%d (scale %.2f)", displayWidth, displayHeight, scale); LOG_DBG("EHP", "Display size: %dx%d (scale %.2f)", displayWidth, displayHeight, scale);
}
// Create page for image - only break if image won't fit remaining space // Create page for image - only break if image won't fit remaining space
if (self->currentPage && !self->currentPage->elements.empty() && if (self->currentPage && !self->currentPage->elements.empty() &&

View File

@@ -59,6 +59,111 @@ static inline void rotateCoordinates(const GfxRenderer::Orientation orientation,
} }
} }
enum class TextRotation { None, Rotated90CW };
// Shared glyph rendering logic for normal and rotated text.
// Coordinate mapping and cursor advance direction are selected at compile time via the template parameter.
template <TextRotation rotation>
static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode renderMode,
const EpdFontFamily& fontFamily, const uint32_t cp, int* cursorX, int* cursorY,
const bool pixelState, const EpdFontFamily::Style style) {
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
if (!glyph) {
glyph = fontFamily.getGlyph(REPLACEMENT_GLYPH, style);
}
if (!glyph) {
LOG_ERR("GFX", "No glyph for codepoint %d", cp);
return;
}
const EpdFontData* fontData = fontFamily.getData(style);
const bool is2Bit = fontData->is2Bit;
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 = renderer.getGlyphBitmap(fontData, glyph);
if (bitmap != nullptr) {
// For Normal: outer loop advances screenY, inner loop advances screenX
// For Rotated: outer loop advances screenX, inner loop advances screenY (in reverse)
int outerBase, innerBase;
if constexpr (rotation == TextRotation::Rotated90CW) {
outerBase = *cursorX + fontData->ascender - top; // screenX = outerBase + glyphY
innerBase = *cursorY - left; // screenY = innerBase - glyphX
} else {
outerBase = *cursorY - top; // screenY = outerBase + glyphY
innerBase = *cursorX + left; // screenX = innerBase + glyphX
}
if (is2Bit) {
int pixelPosition = 0;
for (int glyphY = 0; glyphY < height; glyphY++) {
const int outerCoord = outerBase + glyphY;
for (int glyphX = 0; glyphX < width; glyphX++, pixelPosition++) {
int screenX, screenY;
if constexpr (rotation == TextRotation::Rotated90CW) {
screenX = outerCoord;
screenY = innerBase - glyphX;
} else {
screenX = innerBase + glyphX;
screenY = outerCoord;
}
const uint8_t byte = bitmap[pixelPosition >> 2];
const uint8_t bit_index = (3 - (pixelPosition & 3)) * 2;
// the direct bit from the font is 0 -> white, 1 -> light gray, 2 -> dark gray, 3 -> black
// we swap this to better match the way images and screen think about colors:
// 0 -> black, 1 -> dark grey, 2 -> light grey, 3 -> white
const uint8_t bmpVal = 3 - ((byte >> bit_index) & 0x3);
if (renderMode == GfxRenderer::BW && bmpVal < 3) {
// Black (also paints over the grays in BW mode)
renderer.drawPixel(screenX, screenY, pixelState);
} else if (renderMode == GfxRenderer::GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
// Light gray (also mark the MSB if it's going to be a dark gray too)
// We have to flag pixels in reverse for the gray buffers, as 0 leave alone, 1 update
renderer.drawPixel(screenX, screenY, false);
} else if (renderMode == GfxRenderer::GRAYSCALE_LSB && bmpVal == 1) {
// Dark gray
renderer.drawPixel(screenX, screenY, false);
}
}
}
} else {
int pixelPosition = 0;
for (int glyphY = 0; glyphY < height; glyphY++) {
const int outerCoord = outerBase + glyphY;
for (int glyphX = 0; glyphX < width; glyphX++, pixelPosition++) {
int screenX, screenY;
if constexpr (rotation == TextRotation::Rotated90CW) {
screenX = outerCoord;
screenY = innerBase - glyphX;
} else {
screenX = innerBase + glyphX;
screenY = outerCoord;
}
const uint8_t byte = bitmap[pixelPosition >> 3];
const uint8_t bit_index = 7 - (pixelPosition & 7);
if ((byte >> bit_index) & 1) {
renderer.drawPixel(screenX, screenY, pixelState);
}
}
}
}
}
if constexpr (rotation == TextRotation::Rotated90CW) {
*cursorY -= glyph->advanceX;
} else {
*cursorX += glyph->advanceX;
}
}
// IMPORTANT: This function is in critical rendering path and is called for every pixel. Please keep it as simple and // IMPORTANT: This function is in critical rendering path and is called for every pixel. Please keep it as simple and
// efficient as possible. // efficient as possible.
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
@@ -105,7 +210,7 @@ void GfxRenderer::drawCenteredText(const int fontId, const int y, const char* te
void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black, void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black,
const EpdFontFamily::Style style) const { const EpdFontFamily::Style style) const {
const int yPos = y + getFontAscenderSize(fontId); int yPos = y + getFontAscenderSize(fontId);
int xpos = x; int xpos = x;
// cannot draw a NULL / empty string // cannot draw a NULL / empty string
@@ -810,68 +915,12 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
const auto& font = fontIt->second; const auto& font = fontIt->second;
// For 90° clockwise rotation: int xPos = x;
// Original (glyphX, glyphY) -> Rotated (glyphY, -glyphX) int yPos = y;
// Text reads from bottom to top
int yPos = y; // Current Y position (decreases as we draw characters)
uint32_t cp; uint32_t cp;
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) { while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
const EpdGlyph* glyph = font.getGlyph(cp, style); renderCharImpl<TextRotation::Rotated90CW>(*this, renderMode, font, cp, &xPos, &yPos, black, style);
if (!glyph) {
glyph = font.getGlyph(REPLACEMENT_GLYPH, style);
}
if (!glyph) {
continue;
}
const EpdFontData* fontData = font.getData(style);
const int is2Bit = fontData->is2Bit;
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 = getGlyphBitmap(fontData, glyph);
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 + (fontData->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;
} }
} }
@@ -936,7 +985,7 @@ bool GfxRenderer::storeBwBuffer() {
* Uses chunked restoration to match chunked storage. * Uses chunked restoration to match chunked storage.
*/ */
void GfxRenderer::restoreBwBuffer() { void GfxRenderer::restoreBwBuffer() {
// Check if any all chunks are allocated // Check if all chunks are allocated
bool missingChunks = false; bool missingChunks = false;
for (const auto& bwBufferChunk : bwBufferChunks) { for (const auto& bwBufferChunk : bwBufferChunks) {
if (!bwBufferChunk) { if (!bwBufferChunk) {
@@ -951,13 +1000,6 @@ void GfxRenderer::restoreBwBuffer() {
} }
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) { for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
// Check if chunk is missing
if (!bwBufferChunks[i]) {
LOG_ERR("GFX", "!! BW buffer chunks not stored - this is likely a bug");
freeBwBufferChunks();
return;
}
const size_t offset = i * BW_BUFFER_CHUNK_SIZE; const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE); memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE);
} }
@@ -978,66 +1020,9 @@ void GfxRenderer::cleanupGrayscaleWithFrameBuffer() const {
} }
} }
void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y, void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, int* y, bool pixelState,
const bool pixelState, const EpdFontFamily::Style style) const { EpdFontFamily::Style style) const {
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style); renderCharImpl<TextRotation::None>(*this, renderMode, fontFamily, cp, x, y, pixelState, style);
if (!glyph) {
glyph = fontFamily.getGlyph(REPLACEMENT_GLYPH, style);
}
// no glyph?
if (!glyph) {
LOG_ERR("GFX", "No glyph for codepoint %d", cp);
return;
}
const EpdFontData* fontData = fontFamily.getData(style);
const int is2Bit = fontData->is2Bit;
const uint8_t width = glyph->width;
const uint8_t height = glyph->height;
const int left = glyph->left;
const uint8_t* bitmap = getGlyphBitmap(fontData, glyph);
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;
// the direct bit from the font is 0 -> white, 1 -> light gray, 2 -> dark gray, 3 -> black
// we swap this to better match the way images and screen think about colors:
// 0 -> black, 1 -> dark grey, 2 -> light grey, 3 -> white
const uint8_t bmpVal = 3 - (byte >> bit_index) & 0x3;
if (renderMode == BW && bmpVal < 3) {
// Black (also paints over the grays in BW mode)
drawPixel(screenX, screenY, pixelState);
} else if (renderMode == GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
// Light gray (also mark the MSB if it's going to be a dark gray too)
// We have to flag pixels in reverse for the gray buffers, as 0 leave alone, 1 update
drawPixel(screenX, screenY, false);
} else if (renderMode == GRAYSCALE_LSB && bmpVal == 1) {
// Dark gray
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;
} }
void GfxRenderer::getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const { void GfxRenderer::getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const {

View File

@@ -38,10 +38,9 @@ class GfxRenderer {
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr}; uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
std::map<int, EpdFontFamily> fontMap; std::map<int, EpdFontFamily> fontMap;
FontDecompressor* fontDecompressor = nullptr; FontDecompressor* fontDecompressor = nullptr;
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState, void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, int* y, bool pixelState,
EpdFontFamily::Style style) const; EpdFontFamily::Style style) const;
void freeBwBufferChunks(); void freeBwBufferChunks();
const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const;
template <Color color> template <Color color>
void drawPixelDither(int x, int y) const; void drawPixelDither(int x, int y) const;
template <Color color> template <Color color>
@@ -132,6 +131,9 @@ class GfxRenderer {
void restoreBwBuffer(); // Restore and free the stored buffer void restoreBwBuffer(); // Restore and free the stored buffer
void cleanupGrayscaleWithFrameBuffer() const; void cleanupGrayscaleWithFrameBuffer() const;
// Font helpers
const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const;
// Low level functions // Low level functions
uint8_t* getFrameBuffer() const; uint8_t* getFrameBuffer() const;
static size_t getBufferSize(); static size_t getBufferSize();

View File

@@ -13,6 +13,8 @@ extern const char* const STRINGS_CZ[];
extern const char* const STRINGS_PO[]; extern const char* const STRINGS_PO[];
extern const char* const STRINGS_RU[]; extern const char* const STRINGS_RU[];
extern const char* const STRINGS_SV[]; extern const char* const STRINGS_SV[];
extern const char* const STRINGS_RO[];
extern const char* const STRINGS_CA[];
} // namespace i18n_strings } // namespace i18n_strings
// Language enum // Language enum
@@ -25,6 +27,8 @@ enum class Language : uint8_t {
PORTUGUESE = 5, PORTUGUESE = 5,
RUSSIAN = 6, RUSSIAN = 6,
SWEDISH = 7, SWEDISH = 7,
ROMANIAN = 8,
CATALAN = 9,
_COUNT _COUNT
}; };
@@ -374,6 +378,10 @@ inline const char* const* getStringArray(Language lang) {
return i18n_strings::STRINGS_RU; return i18n_strings::STRINGS_RU;
case Language::SWEDISH: case Language::SWEDISH:
return i18n_strings::STRINGS_SV; return i18n_strings::STRINGS_SV;
case Language::ROMANIAN:
return i18n_strings::STRINGS_RO;
case Language::CATALAN:
return i18n_strings::STRINGS_CA;
default: default:
return i18n_strings::STRINGS_EN; return i18n_strings::STRINGS_EN;
} }

View File

@@ -15,5 +15,7 @@ extern const char* const STRINGS_CZ[];
extern const char* const STRINGS_PO[]; extern const char* const STRINGS_PO[];
extern const char* const STRINGS_RU[]; extern const char* const STRINGS_RU[];
extern const char* const STRINGS_SV[]; extern const char* const STRINGS_SV[];
extern const char* const STRINGS_RO[];
extern const char* const STRINGS_CA[];
} // namespace i18n_strings } // namespace i18n_strings

View File

@@ -0,0 +1,319 @@
_language_name: "Català"
_language_code: "CATALAN"
_order: "9"
STR_CROSSPOINT: "CrossPoint"
STR_BOOTING: "ARRENCANT"
STR_SLEEPING: "ENTRANT EN REPÒS"
STR_ENTERING_SLEEP: "Entrant en repòs"
STR_BROWSE_FILES: "Explora fitxers"
STR_FILE_TRANSFER: "Transferència"
STR_SETTINGS_TITLE: "Configuració"
STR_CALIBRE_LIBRARY: "Biblioteca del Calibre"
STR_CONTINUE_READING: "Continua llegint"
STR_NO_OPEN_BOOK: "Cap llibre obert"
STR_START_READING: "Inicia la lectura a continuació"
STR_BOOKS: "Llibres"
STR_NO_BOOKS_FOUND: "No s'ha trobat cap llibre"
STR_SELECT_CHAPTER: "Selecciona el capítol"
STR_NO_CHAPTERS: "Sense capítols"
STR_END_OF_BOOK: "Final del llibre"
STR_EMPTY_CHAPTER: "Capítol buit"
STR_INDEXING: "S'està indexant"
STR_MEMORY_ERROR: "Error de memòria"
STR_PAGE_LOAD_ERROR: "Error en carregar la pàgina"
STR_EMPTY_FILE: "Fitxer buit"
STR_OUT_OF_BOUNDS: "Fora de límits"
STR_LOADING: "S'està carregant..."
STR_LOADING_POPUP: "S'està carregant"
STR_LOAD_XTC_FAILED: "No s'ha pogut carregar l'XTC"
STR_LOAD_TXT_FAILED: "No s'ha pogut carregar el TXT"
STR_LOAD_EPUB_FAILED: "No s'ha pogut carregar l'EPUB"
STR_SD_CARD_ERROR: "Error de targeta SD"
STR_WIFI_NETWORKS: "Xarxes WiFi"
STR_NO_NETWORKS: "No s'han trobat xarxes"
STR_NETWORKS_FOUND: "%zu xarxes trobades"
STR_SCANNING: "S'està escanejant..."
STR_CONNECTING: "S'està connectant..."
STR_CONNECTED: "S'ha connectat!"
STR_CONNECTION_FAILED: "Error de connexió"
STR_CONNECTION_TIMEOUT: "S'ha esgotat el temps de connexió"
STR_FORGET_NETWORK: "Voleu oblidar aquesta xarxa?"
STR_SAVE_PASSWORD: "Voleu desar la contrasenya per a la propera vegada?"
STR_REMOVE_PASSWORD: "Voleu suprimir la contrasenya desada?"
STR_PRESS_OK_SCAN: "Premeu OK per tornar a escanejar"
STR_PRESS_ANY_CONTINUE: "Premeu qualsevol botó per continuar"
STR_SELECT_HINT: "ESQUERRA/DRETA: Selecciona | OK: Confirma"
STR_HOW_CONNECT: "Com voleu connectar-vos?"
STR_JOIN_NETWORK: "Uneix-te a una xarxa"
STR_CREATE_HOTSPOT: "Crea un punt d'accés"
STR_JOIN_DESC: "Connecta't a una xarxa WiFi existent"
STR_HOTSPOT_DESC: "Crea una xarxa WiFi per unir-s'hi"
STR_STARTING_HOTSPOT: "S'està iniciant el punt d'accés..."
STR_HOTSPOT_MODE: "Mode de punt d'accés"
STR_CONNECT_WIFI_HINT: "Connecteu el dispositiu a aquesta xarxa WiFi"
STR_OPEN_URL_HINT: "Obriu aquest URL al navegador"
STR_OR_HTTP_PREFIX: "o http://"
STR_SCAN_QR_HINT: "o escanegeu el codi QR amb el telèfon:"
STR_CALIBRE_WIRELESS: "Calibre sense fils"
STR_CALIBRE_WEB_URL: "URL web del Calibre"
STR_CONNECT_WIRELESS: "Connecta com a dispositiu sense fils"
STR_NETWORK_LEGEND: "* = Encriptat | + = Desat"
STR_MAC_ADDRESS: "Adreça MAC:"
STR_CHECKING_WIFI: "S'està comprovant el WiFi..."
STR_ENTER_WIFI_PASSWORD: "Introduïu la contrasenya WiFi"
STR_ENTER_TEXT: "Introduïu el text"
STR_TO_PREFIX: "a "
STR_CALIBRE_DISCOVERING: "S'està descobrint el Calibre..."
STR_CALIBRE_CONNECTING_TO: "S'està connectant a "
STR_CALIBRE_CONNECTED_TO: "S'ha connectat a "
STR_CALIBRE_WAITING_COMMANDS: "S'estan esperant les ordres..."
STR_CONNECTION_FAILED_RETRYING: "(La connexió ha fallat, s'està tornant a intentar)"
STR_CALIBRE_DISCONNECTED: "Calibre desconnectat"
STR_CALIBRE_WAITING_TRANSFER: "S'està esperant la transferència..."
STR_CALIBRE_TRANSFER_HINT: "Si la transferència falla, activeu\\n'Ignora l'espai lliure' a la configuració del\\nconnector SmartDevice a Calibre."
STR_CALIBRE_RECEIVING: "S'està rebent: "
STR_CALIBRE_RECEIVED: "S'ha rebut: "
STR_CALIBRE_WAITING_MORE: "S'està esperant més..."
STR_CALIBRE_FAILED_CREATE_FILE: "No s'ha pogut crear el fitxer"
STR_CALIBRE_PASSWORD_REQUIRED: "Contrasenya requerida"
STR_CALIBRE_TRANSFER_INTERRUPTED: "Transferència interrompuda"
STR_CALIBRE_INSTRUCTION_1: "1) Instal·leu el connector CrossPoint Reader"
STR_CALIBRE_INSTRUCTION_2: "2) Estigueu a la mateixa xarxa WiFi"
STR_CALIBRE_INSTRUCTION_3: "3) A Calibre: \"Envia a un dispositiu\""
STR_CALIBRE_INSTRUCTION_4: "\"Mantingueu aquesta pantalla oberta mentre s'envia\""
STR_CAT_DISPLAY: "Visualització"
STR_CAT_READER: "Lector"
STR_CAT_CONTROLS: "Controls"
STR_CAT_SYSTEM: "Sistema"
STR_SLEEP_SCREEN: "Pantalla de repòs"
STR_SLEEP_COVER_MODE: "Mode de pantalla de repòs"
STR_STATUS_BAR: "Barra d'estat"
STR_HIDE_BATTERY: "Oculta el % de bateria"
STR_EXTRA_SPACING: "Espaiat de paràgraf extra"
STR_TEXT_AA: "Antialiàsing del text"
STR_SHORT_PWR_BTN: "Clic curt del botó d'engegada"
STR_ORIENTATION: "Orientació de lectura"
STR_FRONT_BTN_LAYOUT: "Disposició dels botons frontals"
STR_SIDE_BTN_LAYOUT: "Disposició botons laterals"
STR_LONG_PRESS_SKIP: "Pressió llarga omet el capítol"
STR_FONT_FAMILY: "Tipus de lletra"
STR_EXT_READER_FONT: "Tipus de lletra extern"
STR_EXT_CHINESE_FONT: "Tipus de lletra"
STR_EXT_UI_FONT: "Tipus de lletra (UI)"
STR_FONT_SIZE: "Mida de la lletra (UI)"
STR_LINE_SPACING: "Interlineat del lector"
STR_ASCII_LETTER_SPACING: "Espaiat de la lletra ASCII"
STR_ASCII_DIGIT_SPACING: "Espaiat del dígit ASCII"
STR_CJK_SPACING: "Espaiat CJK"
STR_COLOR_MODE: "Mode de color"
STR_SCREEN_MARGIN: "Marge de pantalla del lector"
STR_PARA_ALIGNMENT: "Alineació de paràgrafs del lector"
STR_HYPHENATION: "Partició de mots"
STR_TIME_TO_SLEEP: "Temps per entrar en repòs"
STR_REFRESH_FREQ: "Freqüència de refresc"
STR_CALIBRE_SETTINGS: "Configuració del Calibre"
STR_KOREADER_SYNC: "Sincronització del KOReader"
STR_CHECK_UPDATES: "Comprova si hi ha actualitzacions"
STR_LANGUAGE: "Idioma"
STR_SELECT_WALLPAPER: "Selecciona un fons de pantalla"
STR_CLEAR_READING_CACHE: "Esborra la memòria cau de lectura"
STR_CALIBRE: "Calibre"
STR_USERNAME: "Nom d'usuari"
STR_PASSWORD: "Contrasenya"
STR_SYNC_SERVER_URL: "URL del servidor de sincronització"
STR_DOCUMENT_MATCHING: "Coincidència de documents"
STR_AUTHENTICATE: "Autentica"
STR_KOREADER_USERNAME: "Nom d'usuari del KOReader"
STR_KOREADER_PASSWORD: "Contrasenya del KOReader"
STR_FILENAME: "Nom de fitxer"
STR_BINARY: "Binari"
STR_SET_CREDENTIALS_FIRST: "Estableix les credencials primer"
STR_WIFI_CONN_FAILED: "Connexió WiFi fallida"
STR_AUTHENTICATING: "S'està autenticant..."
STR_AUTH_SUCCESS: "Autenticació correcta!"
STR_KOREADER_AUTH: "Autenticació del KOReader"
STR_SYNC_READY: "La sincronització del KOReader està preparada per utilitzar-se"
STR_AUTH_FAILED: "Autenticació fallida"
STR_DONE: "Fet"
STR_CLEAR_CACHE_WARNING_1: "Això esborrarà totes les dades de lectura de la memòria cau."
STR_CLEAR_CACHE_WARNING_2: "Es perdrà tot el progrés de lectura!"
STR_CLEAR_CACHE_WARNING_3: "Els llibres hauran de ser reindexats"
STR_CLEAR_CACHE_WARNING_4: "quan s'obrin de nou."
STR_CLEARING_CACHE: "S'està esborrant la memòria cau..."
STR_CACHE_CLEARED: "Memòria cau esborrada"
STR_ITEMS_REMOVED: "elements suprimits"
STR_FAILED_LOWER: "ha fallat"
STR_CLEAR_CACHE_FAILED: "No s'ha pogut esborrar la memòria cau"
STR_CHECK_SERIAL_OUTPUT: "Comprova la sortida en sèrie per obtenir detalls"
STR_DARK: "Fosc"
STR_LIGHT: "Clar"
STR_CUSTOM: "Personalitzat"
STR_COVER: "Portada"
STR_NONE_OPT: "Cap"
STR_FIT: "Ajustar"
STR_CROP: "Retallar"
STR_NO_PROGRESS: "Sense progrés"
STR_FULL_OPT: "Completa"
STR_NEVER: "Mai"
STR_IN_READER: "Al lector"
STR_ALWAYS: "Sempre"
STR_IGNORE: "Ignora"
STR_SLEEP: "Dormir"
STR_PAGE_TURN: "Canvi de pàgina"
STR_PORTRAIT: "Vertical"
STR_LANDSCAPE_CW: "Horitzontal horari"
STR_INVERTED: "Invertit"
STR_LANDSCAPE_CCW: "Horitzontal antihorari"
STR_FRONT_LAYOUT_BCLR: "Enr, Cnfrm, Esq, Dreta"
STR_FRONT_LAYOUT_LRBC: "Esq, Dreta, Enr, Cnfrm"
STR_FRONT_LAYOUT_LBCR: "Esq, Enr, Cnfrm, Dreta"
STR_PREV_NEXT: "Anterior/Següent"
STR_NEXT_PREV: "Següent/Anterior"
STR_BOOKERLY: "Bookerly"
STR_NOTO_SANS: "Noto Sans"
STR_OPEN_DYSLEXIC: "Open Dyslexic"
STR_SMALL: "Petita"
STR_MEDIUM: "Mitjana"
STR_LARGE: "Gran"
STR_X_LARGE: "Molt gran"
STR_TIGHT: "Estret"
STR_NORMAL: "Normal"
STR_WIDE: "Ample"
STR_JUSTIFY: "Justificat"
STR_ALIGN_LEFT: "Esquerra"
STR_CENTER: "Centre"
STR_ALIGN_RIGHT: "Dreta"
STR_MIN_1: "1 min"
STR_MIN_5: "5 min"
STR_MIN_10: "10 min"
STR_MIN_15: "15 min"
STR_MIN_30: "30 min"
STR_PAGES_1: "1 pàgina"
STR_PAGES_5: "5 pàgines"
STR_PAGES_10: "10 pàgines"
STR_PAGES_15: "15 pàgines"
STR_PAGES_30: "30 pàgines"
STR_UPDATE: "Actualitza"
STR_CHECKING_UPDATE: "S'està comprovant si hi ha actualitzacions..."
STR_NEW_UPDATE: "Nova actualització disponible!"
STR_CURRENT_VERSION: "Versió actual: "
STR_NEW_VERSION: "Nova versió: "
STR_UPDATING: "S'està actualitzant..."
STR_NO_UPDATE: "No hi ha actualitzacions disponibles"
STR_UPDATE_FAILED: "Ha fallat l'actualització"
STR_UPDATE_COMPLETE: "Actualització completada"
STR_POWER_ON_HINT: "Premeu i manteniu premut el botó d'encesa per tornar a engegar"
STR_EXTERNAL_FONT: "Tipus de lletra extern"
STR_BUILTIN_DISABLED: "Integrat (desactivat)"
STR_NO_ENTRIES: "No s'ha trobat cap entrada"
STR_DOWNLOADING: "S'està baixant..."
STR_DOWNLOAD_FAILED: "Ha fallat la baixada"
STR_ERROR_MSG: "Error:"
STR_UNNAMED: "Sense nom"
STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor"
STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del feed"
STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del feed"
STR_NETWORK_PREFIX: "Xarxa: "
STR_IP_ADDRESS_PREFIX: "Adreça IP: "
STR_SCAN_QR_WIFI_HINT: "o escanegeu el codi QR amb el telèfon per connectar el WiFi."
STR_ERROR_GENERAL_FAILURE: "Error: Fallada general"
STR_ERROR_NETWORK_NOT_FOUND: "Error: No s'ha trobat la xarxa"
STR_ERROR_CONNECTION_TIMEOUT: "Error: temps de connexió esgotat"
STR_SD_CARD: "Targeta SD"
STR_BACK: "« Enrere"
STR_EXIT: "« Surt"
STR_HOME: "« Inici"
STR_SAVE: "« Desa"
STR_SELECT: "Selecciona"
STR_TOGGLE: "Canvia"
STR_CONFIRM: "Confirma"
STR_CANCEL: "Cancel·la"
STR_CONNECT: "Connecta"
STR_OPEN: "Obre"
STR_DOWNLOAD: "Descarrega"
STR_RETRY: "Nou intent"
STR_YES: "Sí"
STR_NO: "No"
STR_STATE_ON: "ON"
STR_STATE_OFF: "OFF"
STR_SET: "Establert"
STR_NOT_SET: "No establert"
STR_DIR_LEFT: "Esquerra"
STR_DIR_RIGHT: "Dreta"
STR_DIR_UP: "Amunt"
STR_DIR_DOWN: "Avall"
STR_CAPS_ON: "MAJS"
STR_CAPS_OFF: "majs"
STR_OK_BUTTON: "OK"
STR_ON_MARKER: "[ON]"
STR_SLEEP_COVER_FILTER: "Filtre de pantalla de repòs"
STR_FILTER_CONTRAST: "Contrast"
STR_STATUS_BAR_FULL_PERCENT: "Amb percentatge"
STR_STATUS_BAR_FULL_BOOK: "Amb progrés llibre"
STR_STATUS_BAR_BOOK_ONLY: "Només progrés llibre"
STR_STATUS_BAR_FULL_CHAPTER: "Amb progrés capítol"
STR_UI_THEME: "Tema de la interfície"
STR_THEME_CLASSIC: "Clàssic"
STR_THEME_LYRA: "Lyra"
STR_THEME_LYRA_EXTENDED: "Lyra Ampliat"
STR_SUNLIGHT_FADING_FIX: "Correcció de l'esvaïment pel sol"
STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals"
STR_OPDS_BROWSER: "Navegador OPDS"
STR_COVER_CUSTOM: "Portada + Personalitzat"
STR_RECENTS: "Recents"
STR_MENU_RECENT_BOOKS: "Llibres recents"
STR_NO_RECENT_BOOKS: "No hi ha llibres recents"
STR_CALIBRE_DESC: "Usa les transferències sense fils de Calibre"
STR_FORGET_AND_REMOVE: "Voleu suprimir la contrasenya desada?"
STR_FORGET_BUTTON: "Oblida"
STR_CALIBRE_STARTING: "S'està iniciant el Calibre..."
STR_CALIBRE_SETUP: "Configura"
STR_CALIBRE_STATUS: "Estat"
STR_CLEAR_BUTTON: "Esborra"
STR_DEFAULT_VALUE: "Per defecte"
STR_REMAP_PROMPT: "Premeu un botó frontal per a cada rol"
STR_UNASSIGNED: "No assignat"
STR_ALREADY_ASSIGNED: "Ja assignat"
STR_REMAP_RESET_HINT: "Botó lateral Amunt: Restableix la disposició per defecte"
STR_REMAP_CANCEL_HINT: "Botó lateral Avall: Cancel·la la reassignació"
STR_HW_BACK_LABEL: "Enrere (1r botó)"
STR_HW_CONFIRM_LABEL: "Confirma (2n botó)"
STR_HW_LEFT_LABEL: "Esquerra (3r botó)"
STR_HW_RIGHT_LABEL: "Dreta (4t botó)"
STR_GO_TO_PERCENT: "Ves al %"
STR_GO_HOME_BUTTON: "Ves a l'inici"
STR_SYNC_PROGRESS: "Sincronitza el progrés"
STR_DELETE_CACHE: "Esborra la memòria cau del llibre"
STR_CHAPTER_PREFIX: "Capítol: "
STR_PAGES_SEPARATOR: " pàgines | "
STR_BOOK_PREFIX: "Llibre: "
STR_KBD_SHIFT: "maj"
STR_KBD_SHIFT_CAPS: "MAJ"
STR_KBD_LOCK: "BLOCA"
STR_CALIBRE_URL_HINT: "Per al Calibre, afegiu /opds a la URL"
STR_PERCENT_STEP_HINT: "Esquerra/Dreta: 1% Amunt/Avall: 10%"
STR_SYNCING_TIME: "S'està sincronitzant el temps..."
STR_CALC_HASH: "S'està calculant el hash del document..."
STR_HASH_FAILED: "No s'ha pogut calcular el hash del document"
STR_FETCH_PROGRESS: "S'està obtenint el progrés remot..."
STR_UPLOAD_PROGRESS: "S'està pujant el progrés..."
STR_NO_CREDENTIALS_MSG: "No s'han configurat credencials"
STR_KOREADER_SETUP_HINT: "Configureu el compte de KOReader a la configuració"
STR_PROGRESS_FOUND: "S'ha trobat progrés!"
STR_REMOTE_LABEL: "Remot:"
STR_LOCAL_LABEL: "Local:"
STR_PAGE_OVERALL_FORMAT: "Pàgina %d, %.2f%% total"
STR_PAGE_TOTAL_OVERALL_FORMAT: "Pàgina %d/%d, %.2f%% total"
STR_DEVICE_FROM_FORMAT: " De: %s"
STR_APPLY_REMOTE: "Aplica el progrés remot"
STR_UPLOAD_LOCAL: "Puja el progrés local"
STR_NO_REMOTE_MSG: "No s'ha trobat progrés remot"
STR_UPLOAD_PROMPT: "Voleu pujar la posició actual?"
STR_UPLOAD_SUCCESS: "Progrés pujat!"
STR_SYNC_FAILED_MSG: "Sincronització fallida"
STR_SECTION_PREFIX: "Secció "
STR_UPLOAD: "Puja"
STR_BOOK_S_STYLE: "Estil del llibre"
STR_EMBEDDED_STYLE: "Estil incrustat"
STR_OPDS_SERVER_URL: "URL del servidor OPDS"

View File

@@ -266,7 +266,7 @@ STR_MENU_RECENT_BOOKS: "Nedávné knihy"
STR_NO_RECENT_BOOKS: "Žádné nedávné knihy" STR_NO_RECENT_BOOKS: "Žádné nedávné knihy"
STR_CALIBRE_DESC: "Používat přenosy bezdrátových zařízení Calibre" STR_CALIBRE_DESC: "Používat přenosy bezdrátových zařízení Calibre"
STR_FORGET_AND_REMOVE: "Zapomenout síť a odstranit uložené heslo?" STR_FORGET_AND_REMOVE: "Zapomenout síť a odstranit uložené heslo?"
STR_FORGET_BUTTON: "Zapomenout na síť" STR_FORGET_BUTTON: "Zapomenout"
STR_CALIBRE_STARTING: "Spuštění Calibre..." STR_CALIBRE_STARTING: "Spuštění Calibre..."
STR_CALIBRE_SETUP: "Nastavení" STR_CALIBRE_SETUP: "Nastavení"
STR_CALIBRE_STATUS: "Stav" STR_CALIBRE_STATUS: "Stav"

View File

@@ -266,7 +266,7 @@ STR_MENU_RECENT_BOOKS: "Recent Books"
STR_NO_RECENT_BOOKS: "No recent books" STR_NO_RECENT_BOOKS: "No recent books"
STR_CALIBRE_DESC: "Use Calibre wireless device transfers" STR_CALIBRE_DESC: "Use Calibre wireless device transfers"
STR_FORGET_AND_REMOVE: "Forget network and remove saved password?" STR_FORGET_AND_REMOVE: "Forget network and remove saved password?"
STR_FORGET_BUTTON: "Forget network" STR_FORGET_BUTTON: "Forget"
STR_CALIBRE_STARTING: "Starting Calibre..." STR_CALIBRE_STARTING: "Starting Calibre..."
STR_CALIBRE_SETUP: "Setup" STR_CALIBRE_SETUP: "Setup"
STR_CALIBRE_STATUS: "Status" STR_CALIBRE_STATUS: "Status"

View File

@@ -266,7 +266,7 @@ STR_MENU_RECENT_BOOKS: "Livres récents"
STR_NO_RECENT_BOOKS: "Aucun livre récent" STR_NO_RECENT_BOOKS: "Aucun livre récent"
STR_CALIBRE_DESC: "Utiliser les transferts sans fil Calibre" STR_CALIBRE_DESC: "Utiliser les transferts sans fil Calibre"
STR_FORGET_AND_REMOVE: "Oublier le réseau et supprimer le mot de passe enregistré ?" STR_FORGET_AND_REMOVE: "Oublier le réseau et supprimer le mot de passe enregistré ?"
STR_FORGET_BUTTON: "Oublier le réseau" STR_FORGET_BUTTON: "Oublier"
STR_CALIBRE_STARTING: "Démarrage de Calibre..." STR_CALIBRE_STARTING: "Démarrage de Calibre..."
STR_CALIBRE_SETUP: "Configuration" STR_CALIBRE_SETUP: "Configuration"
STR_CALIBRE_STATUS: "Statut" STR_CALIBRE_STATUS: "Statut"

View File

@@ -266,7 +266,7 @@ STR_MENU_RECENT_BOOKS: "Zuletzt gelesen"
STR_NO_RECENT_BOOKS: "Keine Bücher" STR_NO_RECENT_BOOKS: "Keine Bücher"
STR_CALIBRE_DESC: "Calibre-Übertragung (WLAN)" STR_CALIBRE_DESC: "Calibre-Übertragung (WLAN)"
STR_FORGET_AND_REMOVE: "WLAN entfernen & Passwort löschen?" STR_FORGET_AND_REMOVE: "WLAN entfernen & Passwort löschen?"
STR_FORGET_BUTTON: "WLAN entfernen" STR_FORGET_BUTTON: "Entfernen"
STR_CALIBRE_STARTING: "Calibre starten…" STR_CALIBRE_STARTING: "Calibre starten…"
STR_CALIBRE_SETUP: "Installation" STR_CALIBRE_SETUP: "Installation"
STR_CALIBRE_STATUS: "Status" STR_CALIBRE_STATUS: "Status"

View File

@@ -266,7 +266,7 @@ STR_MENU_RECENT_BOOKS: "Livros recentes"
STR_NO_RECENT_BOOKS: "Sem livros recentes" STR_NO_RECENT_BOOKS: "Sem livros recentes"
STR_CALIBRE_DESC: "Usar transferências sem fio Calibre" STR_CALIBRE_DESC: "Usar transferências sem fio Calibre"
STR_FORGET_AND_REMOVE: "Esquecer a rede e remover a senha salva?" STR_FORGET_AND_REMOVE: "Esquecer a rede e remover a senha salva?"
STR_FORGET_BUTTON: "Esquecer rede" STR_FORGET_BUTTON: "Esquecer"
STR_CALIBRE_STARTING: "Iniciando Calibre..." STR_CALIBRE_STARTING: "Iniciando Calibre..."
STR_CALIBRE_SETUP: "Configuração" STR_CALIBRE_SETUP: "Configuração"
STR_CALIBRE_STATUS: "Status" STR_CALIBRE_STATUS: "Status"

View File

@@ -0,0 +1,318 @@
_language_name: "Română"
_language_code: "ROMANIAN"
_order: "8"
STR_CROSSPOINT: "CrossPoint"
STR_BOOTING: "PORNEŞTE"
STR_SLEEPING: "REPAUS"
STR_ENTERING_SLEEP: "Intră în repaus..."
STR_BROWSE_FILES: "Răsfoieşte fişierele"
STR_FILE_TRANSFER: "Transfer de fişiere"
STR_SETTINGS_TITLE: "Setări"
STR_CALIBRE_LIBRARY: "Biblioteca Calibre"
STR_CONTINUE_READING: "Continuă lectura"
STR_NO_OPEN_BOOK: "Nicio carte deschisă"
STR_START_READING: "Începeţi lectura"
STR_BOOKS: "Cărţi"
STR_NO_BOOKS_FOUND: "Nicio carte găsită"
STR_SELECT_CHAPTER: "Selectaţi capitolul"
STR_NO_CHAPTERS: "Niciun capitol"
STR_END_OF_BOOK: "Sfârşitul cărţii"
STR_EMPTY_CHAPTER: "Capitol gol"
STR_INDEXING: "Indexează..."
STR_MEMORY_ERROR: "Eroare de memorie"
STR_PAGE_LOAD_ERROR: "Eroare la încărcarea paginii"
STR_EMPTY_FILE: "Fişier gol"
STR_OUT_OF_BOUNDS: "Eroare: În afara limitelor"
STR_LOADING: "Se încarcă..."
STR_LOADING_POPUP: "Se încarcă..."
STR_LOAD_XTC_FAILED: "Eroare la încărcarea XTC"
STR_LOAD_TXT_FAILED: "Eroare la încărcarea TXT"
STR_LOAD_EPUB_FAILED: "Eroare la încărcarea EPUB"
STR_SD_CARD_ERROR: "Eroare la cardul SD"
STR_WIFI_NETWORKS: "Reţele WiFi"
STR_NO_NETWORKS: "Nu s-au găsit reţele"
STR_NETWORKS_FOUND: "%zu reţele găsite"
STR_SCANNING: "Scanează..."
STR_CONNECTING: "Se conectează..."
STR_CONNECTED: "Conectat!"
STR_CONNECTION_FAILED: "Conexiune eşuată"
STR_CONNECTION_TIMEOUT: "Timp de conectare depăşit"
STR_FORGET_NETWORK: "Uitaţi reţeaua?"
STR_SAVE_PASSWORD: "Salvaţi parola?"
STR_REMOVE_PASSWORD: "Ştergeţi parola salvată?"
STR_PRESS_OK_SCAN: "Apăsaţi OK pentru a scana din nou"
STR_PRESS_ANY_CONTINUE: "Apăsaţi orice buton pentru a continua"
STR_SELECT_HINT: "STÂNGA/DREAPTA: Selectaţi | OK: Confirmaţi"
STR_HOW_CONNECT: "Cum doriţi să vă conectaţi?"
STR_JOIN_NETWORK: "Conectaţi-vă la o reţea"
STR_CREATE_HOTSPOT: "Creaţi un hotspot"
STR_JOIN_DESC: "Conectaţi-vă la o reţea WiFi existentă"
STR_HOTSPOT_DESC: "Creaţi un hotspot WiFi"
STR_STARTING_HOTSPOT: "Hotspot porneşte..."
STR_HOTSPOT_MODE: "Mod Hotspot"
STR_CONNECT_WIFI_HINT: "Conectaţi-vă dispozitivul la această reţea WiFi"
STR_OPEN_URL_HINT: "Deschideţi acest URL în browserul dvs."
STR_OR_HTTP_PREFIX: "sau http://"
STR_SCAN_QR_HINT: "sau scanaţi codul QR cu telefonul dvs.:"
STR_CALIBRE_WIRELESS: "Calibre Wireless"
STR_CALIBRE_WEB_URL: "Calibre URL"
STR_CONNECT_WIRELESS: "Conectaţi-vă ca dispozitiv wireless"
STR_NETWORK_LEGEND: "* = Criptat | + = Salvat"
STR_MAC_ADDRESS: "Adresă MAC:"
STR_CHECKING_WIFI: "Verificare WiFi..."
STR_ENTER_WIFI_PASSWORD: "Introduceţi parola WiFi"
STR_ENTER_TEXT: "Introduceţi textul"
STR_TO_PREFIX: "la "
STR_CALIBRE_DISCOVERING: "Descoperă Calibre..."
STR_CALIBRE_CONNECTING_TO: "Se conectează la "
STR_CALIBRE_CONNECTED_TO: "Conectat la "
STR_CALIBRE_WAITING_COMMANDS: "Se aşteaptă comenzi..."
STR_CONNECTION_FAILED_RETRYING: "(Conexiune eşuată, se reîncearcă)"
STR_CALIBRE_DISCONNECTED: "Calibre deconectat"
STR_CALIBRE_WAITING_TRANSFER: "Se aşteaptă transfer..."
STR_CALIBRE_TRANSFER_HINT: "Dacă transferul eşuează, activaţi\\n'Ignoraţi spaţiul liber' în setările\\nplugin-ului SmartDevice din Calibre."
STR_CALIBRE_RECEIVING: "Se primeşte: "
STR_CALIBRE_RECEIVED: "Primite: "
STR_CALIBRE_WAITING_MORE: "Se aşteaptă mai multe..."
STR_CALIBRE_FAILED_CREATE_FILE: "Creare fişier eşuată"
STR_CALIBRE_PASSWORD_REQUIRED: "Necesită parolă"
STR_CALIBRE_TRANSFER_INTERRUPTED: "Transfer întrerupt"
STR_CALIBRE_INSTRUCTION_1: "1) Instalaţi plugin-ul CrossPoint Reader"
STR_CALIBRE_INSTRUCTION_2: "2) Fiţi în aceeaşi reţea WiFi"
STR_CALIBRE_INSTRUCTION_3: "3) În Calibre: \"Trimiteţi la dispozitiv\""
STR_CALIBRE_INSTRUCTION_4: "\"Păstraţi acest ecran deschis în timpul trimiterii\""
STR_CAT_DISPLAY: "Ecran"
STR_CAT_READER: "Lectură"
STR_CAT_CONTROLS: "Controale"
STR_CAT_SYSTEM: "Sistem"
STR_SLEEP_SCREEN: "Ecran de repaus"
STR_SLEEP_COVER_MODE: "Mod ecran de repaus cu copertă"
STR_STATUS_BAR: "Bara de stare"
STR_HIDE_BATTERY: "Ascunde procentul bateriei"
STR_EXTRA_SPACING: "Spaţiere suplimentară între paragrafe"
STR_TEXT_AA: "Anti-Aliasing text"
STR_SHORT_PWR_BTN: "Apăsare scurtă întrerupător"
STR_ORIENTATION: "Orientare lectură"
STR_FRONT_BTN_LAYOUT: "Aspect butoane frontale"
STR_SIDE_BTN_LAYOUT: "Aspect butoane laterale (lectură)"
STR_LONG_PRESS_SKIP: "Sărire capitol la apăsare lungă"
STR_FONT_FAMILY: "Familie font lectură"
STR_EXT_READER_FONT: "Font lectură extern"
STR_EXT_CHINESE_FONT: "Font lectură"
STR_EXT_UI_FONT: "Font meniu"
STR_FONT_SIZE: "Dimensiune font"
STR_LINE_SPACING: "Spaţiere între rânduri"
STR_ASCII_LETTER_SPACING: "Spaţiere litere ASCII "
STR_ASCII_DIGIT_SPACING: "Spaţiere cifre ASCII"
STR_CJK_SPACING: "Spaţiere CJK"
STR_COLOR_MODE: "Mod culoare"
STR_SCREEN_MARGIN: "Margine ecran lectură"
STR_PARA_ALIGNMENT: "Aliniere paragrafe reader"
STR_HYPHENATION: "Silabisire"
STR_TIME_TO_SLEEP: "Timp până la repaus"
STR_REFRESH_FREQ: "Frecvenţă reîmprospătare"
STR_CALIBRE_SETTINGS: "Setări Calibre"
STR_KOREADER_SYNC: "Sincronizare KOReader"
STR_CHECK_UPDATES: "Căutaţi actualizări"
STR_LANGUAGE: "Limbă"
STR_SELECT_WALLPAPER: "Selectaţi imaginea de fundal"
STR_CLEAR_READING_CACHE: "Goliţi cache-ul de lectură"
STR_CALIBRE: "Calibre"
STR_USERNAME: "Utilizator"
STR_PASSWORD: "Parolă"
STR_SYNC_SERVER_URL: "URL server sincronizare"
STR_DOCUMENT_MATCHING: "Corespondenţă document"
STR_AUTHENTICATE: "Autentificare"
STR_KOREADER_USERNAME: "Nume utilizator KOReader"
STR_KOREADER_PASSWORD: "Parolă KOReader"
STR_FILENAME: "Nume fişier"
STR_BINARY: "Fişier binar"
STR_SET_CREDENTIALS_FIRST: "Vă rugăm să setaţi mai întâi acreditările"
STR_WIFI_CONN_FAILED: "Conexiune WiFi eşuată"
STR_AUTHENTICATING: "Se autentifică..."
STR_AUTH_SUCCESS: "Autentificare reuşită!"
STR_KOREADER_AUTH: "Autentificare KOReader"
STR_SYNC_READY: "Sincronizare KOReader gata de utilizare"
STR_AUTH_FAILED: "Autentificare eşuată"
STR_DONE: "Gata"
STR_CLEAR_CACHE_WARNING_1: "Aceasta va şterge tot cache-ul de lectură."
STR_CLEAR_CACHE_WARNING_2: "Tot progresul de lectură va fi pierdut!"
STR_CLEAR_CACHE_WARNING_3: "Cărţile vor trebui reindexate"
STR_CLEAR_CACHE_WARNING_4: "când vor fi deschise din nou."
STR_CLEARING_CACHE: "Se şterge cache-ul..."
STR_CACHE_CLEARED: "Cache şters"
STR_ITEMS_REMOVED: "elemente eliminate"
STR_FAILED_LOWER: "eşuat"
STR_CLEAR_CACHE_FAILED: "ştergerea cache-ului a eşuat"
STR_CHECK_SERIAL_OUTPUT: "Verificaţi ieşirea serială pentru detalii"
STR_DARK: "Întunecat"
STR_LIGHT: "Luminos"
STR_CUSTOM: "Personalizat"
STR_COVER: "Copertă"
STR_NONE_OPT: "Niciunul"
STR_FIT: "Potrivit"
STR_CROP: "Decupat"
STR_NO_PROGRESS: "Fără progres"
STR_FULL_OPT: "Complet"
STR_NEVER: "Niciodată"
STR_IN_READER: "În lectură"
STR_ALWAYS: "Întotdeauna"
STR_IGNORE: "Ignoră"
STR_SLEEP: "Repaus"
STR_PAGE_TURN: "Răsfoire pagină"
STR_PORTRAIT: "Vertical"
STR_LANDSCAPE_CW: "Orizontal dreapta"
STR_INVERTED: "Invers"
STR_LANDSCAPE_CCW: "Orizontal stânga"
STR_FRONT_LAYOUT_BCLR: "Înapoi, Cnfrm, St, Dr"
STR_FRONT_LAYOUT_LRBC: "St, Dr, Înapoi, Cnfrm"
STR_FRONT_LAYOUT_LBCR: "St, Înapoi, Cnfrm, Dr"
STR_PREV_NEXT: "Înainte/Înapoi"
STR_NEXT_PREV: "Înapoi/Înainte"
STR_BOOKERLY: "Bookerly"
STR_NOTO_SANS: "Noto Sans"
STR_OPEN_DYSLEXIC: "Open Dyslexic"
STR_SMALL: "Mic"
STR_MEDIUM: "Mediu"
STR_LARGE: "Mare"
STR_X_LARGE: "Foarte mare"
STR_TIGHT: "Strâns"
STR_NORMAL: "Normal"
STR_WIDE: "Larg"
STR_JUSTIFY: "Aliniere"
STR_ALIGN_LEFT: "Stânga"
STR_CENTER: "Centru"
STR_ALIGN_RIGHT: "Dreapta"
STR_MIN_1: "1 min"
STR_MIN_5: "5 min"
STR_MIN_10: "10 min"
STR_MIN_15: "15 min"
STR_MIN_30: "30 min"
STR_PAGES_1: "1 pagină"
STR_PAGES_5: "5 pagini"
STR_PAGES_10: "10 pagini"
STR_PAGES_15: "15 pagini"
STR_PAGES_30: "30 pagini"
STR_UPDATE: "Actualizare"
STR_CHECKING_UPDATE: "Se verifică actualizările..."
STR_NEW_UPDATE: "Nouă actualizare disponibilă!"
STR_CURRENT_VERSION: "Versiune curentă: "
STR_NEW_VERSION: "Noua versiune: "
STR_UPDATING: "Se actualizează..."
STR_NO_UPDATE: "Nicio actualizare disponibilă"
STR_UPDATE_FAILED: "Actualizare eşuată"
STR_UPDATE_COMPLETE: "Actualizare completă"
STR_POWER_ON_HINT: "Apăsaţi şi menţineţi apăsat întrerupătorul pentru a porni din nou"
STR_EXTERNAL_FONT: "Font extern"
STR_BUILTIN_DISABLED: "Încorporat (Dezactivat)"
STR_NO_ENTRIES: "Niciun rezultat găsit"
STR_DOWNLOADING: "Se descarcă..."
STR_DOWNLOAD_FAILED: "Descărcare eşuată"
STR_ERROR_MSG: "Eroare:"
STR_UNNAMED: "Fără nume"
STR_NO_SERVER_URL: "Niciun URL de server configurat"
STR_FETCH_FEED_FAILED: "Eşec la preluarea feed-ului"
STR_PARSE_FEED_FAILED: "Eşec la analizarea feed-ului"
STR_NETWORK_PREFIX: "Reţea: "
STR_IP_ADDRESS_PREFIX: "Adresă IP: "
STR_SCAN_QR_WIFI_HINT: "sau scanaţi codul QR cu telefonul pentru a vă conecta la Wifi."
STR_ERROR_GENERAL_FAILURE: "Eroare: Eşec general"
STR_ERROR_NETWORK_NOT_FOUND: "Eroare: Reţea negăsită"
STR_ERROR_CONNECTION_TIMEOUT: "Eroare: Timp de conectare depăşit"
STR_SD_CARD: "Card SD"
STR_BACK: "« Înapoi"
STR_EXIT: "« Ieşire"
STR_HOME: "« Acasă"
STR_SAVE: "« Salvare"
STR_SELECT: "Selectează"
STR_TOGGLE: "Schimbă"
STR_CONFIRM: "Confirmă"
STR_CANCEL: "Anulare"
STR_CONNECT: "Conectare"
STR_OPEN: "Deschidere"
STR_DOWNLOAD: "Descarcă"
STR_RETRY: "Reîncercare"
STR_YES: "Da"
STR_NO: "Nu"
STR_STATE_ON: "Pornit"
STR_STATE_OFF: "Oprit"
STR_SET: "Setare"
STR_NOT_SET: "Neconfigurat"
STR_DIR_LEFT: "Stânga"
STR_DIR_RIGHT: "Dreapta"
STR_DIR_UP: "Sus"
STR_DIR_DOWN: "Jos"
STR_CAPS_ON: "CAPS"
STR_CAPS_OFF: "caps"
STR_OK_BUTTON: "OK"
STR_ON_MARKER: "[ON]"
STR_SLEEP_COVER_FILTER: "Filtru ecran de repaus"
STR_FILTER_CONTRAST: "Contrast"
STR_STATUS_BAR_FULL_PERCENT: "Complet cu procentaj"
STR_STATUS_BAR_FULL_BOOK: "Complet cu bara de carte"
STR_STATUS_BAR_BOOK_ONLY: "Doar bara de carte"
STR_STATUS_BAR_FULL_CHAPTER: "Complet cu bara de capitol"
STR_UI_THEME: "Tema UI"
STR_THEME_CLASSIC: "Clasic"
STR_THEME_LYRA: "Lyra"
STR_SUNLIGHT_FADING_FIX: "Corecţie estompare lumină"
STR_REMAP_FRONT_BUTTONS: "Remapare butoane frontale"
STR_OPDS_BROWSER: "Browser OPDS"
STR_COVER_CUSTOM: "Copertă + Personalizat"
STR_RECENTS: "Recente"
STR_MENU_RECENT_BOOKS: "Cărţi recente"
STR_NO_RECENT_BOOKS: "Nicio carte recentă"
STR_CALIBRE_DESC: "Utilizaţi transferurile wireless ale dispozitivului Calibre"
STR_FORGET_AND_REMOVE: "Uitaţi reţeaua şi eliminaţi parola salvată?"
STR_FORGET_BUTTON: "Uitaţi"
STR_CALIBRE_STARTING: "Pornirea Calibre..."
STR_CALIBRE_SETUP: "Configurare"
STR_CALIBRE_STATUS: "Stare"
STR_CLEAR_BUTTON: "ştergere"
STR_DEFAULT_VALUE: "Implicit"
STR_REMAP_PROMPT: "Apăsaţi un buton frontal pentru fiecare rol"
STR_UNASSIGNED: "Neatribuit"
STR_ALREADY_ASSIGNED: "Deja atribuit"
STR_REMAP_RESET_HINT: "Buton lateral Sus: Resetaţi la aspectul implicit"
STR_REMAP_CANCEL_HINT: "Buton lateral Jos: Anulaţi remaparea"
STR_HW_BACK_LABEL: "Înapoi (butonul 1)"
STR_HW_CONFIRM_LABEL: "Confirmare (butonul 2)"
STR_HW_LEFT_LABEL: "Stânga (butonul 3)"
STR_HW_RIGHT_LABEL: "Dreapta (butonul 4)"
STR_GO_TO_PERCENT: "Săriţi la %"
STR_GO_HOME_BUTTON: "Acasă"
STR_SYNC_PROGRESS: "Progres sincronizare"
STR_DELETE_CACHE: "Ştergere cache cărţi"
STR_CHAPTER_PREFIX: "Capitol: "
STR_PAGES_SEPARATOR: " pagini | "
STR_BOOK_PREFIX: "Carte: "
STR_KBD_SHIFT: "shift"
STR_KBD_SHIFT_CAPS: "SHIFT"
STR_KBD_LOCK: "LOCK"
STR_CALIBRE_URL_HINT: "Pentru Calibre, adăugaţi /opds la URL"
STR_PERCENT_STEP_HINT: "Stânga/Dreapta: 1% Sus/Jos: 10%"
STR_SYNCING_TIME: "Timp de sincronizare..."
STR_CALC_HASH: "Calcularea hash-ului documentului..."
STR_HASH_FAILED: "Eşec la calcularea hash-ului documentului"
STR_FETCH_PROGRESS: "Preluarea progresului de la distanţă..."
STR_UPLOAD_PROGRESS: "Încărcarea progresului..."
STR_NO_CREDENTIALS_MSG: "Nicio acreditare configurată"
STR_KOREADER_SETUP_HINT: "Configuraţi contul KOReader în setări"
STR_PROGRESS_FOUND: "Progres găsit!"
STR_REMOTE_LABEL: "Remote:"
STR_LOCAL_LABEL: "Local:"
STR_PAGE_OVERALL_FORMAT: "Pagina %d, %.2f%% din total"
STR_PAGE_TOTAL_OVERALL_FORMAT: "Pagina %d/%d, %.2f%% din total"
STR_DEVICE_FROM_FORMAT: " De la: %s"
STR_APPLY_REMOTE: "Aplică progresul remote"
STR_UPLOAD_LOCAL: "Încărcaţi progresul local"
STR_NO_REMOTE_MSG: "Niciun progres remote găsit"
STR_UPLOAD_PROMPT: "Încărcaţi poziţia curentă?"
STR_UPLOAD_SUCCESS: "Progres încărcat!"
STR_SYNC_FAILED_MSG: "Sincronizare eşuată"
STR_SECTION_PREFIX: "Secţiune "
STR_UPLOAD: "Încărcare"
STR_BOOK_S_STYLE: "Stilul cărţii"
STR_EMBEDDED_STYLE: "Stil încorporat"
STR_OPDS_SERVER_URL: "URL server OPDS"

View File

@@ -266,7 +266,7 @@ STR_MENU_RECENT_BOOKS: "Недавние книги"
STR_NO_RECENT_BOOKS: "Нет недавних книг" STR_NO_RECENT_BOOKS: "Нет недавних книг"
STR_CALIBRE_DESC: "Использовать беспроводную передачу Calibre" STR_CALIBRE_DESC: "Использовать беспроводную передачу Calibre"
STR_FORGET_AND_REMOVE: "Забыть сеть и удалить сохранённый пароль?" STR_FORGET_AND_REMOVE: "Забыть сеть и удалить сохранённый пароль?"
STR_FORGET_BUTTON: "Забыть сеть" STR_FORGET_BUTTON: "Забыть"
STR_CALIBRE_STARTING: "Запуск Calibre..." STR_CALIBRE_STARTING: "Запуск Calibre..."
STR_CALIBRE_SETUP: "Настройка" STR_CALIBRE_SETUP: "Настройка"
STR_CALIBRE_STATUS: "Статус" STR_CALIBRE_STATUS: "Статус"

View File

@@ -3,16 +3,16 @@ _language_code: "SPANISH"
_order: "1" _order: "1"
STR_CROSSPOINT: "CrossPoint" STR_CROSSPOINT: "CrossPoint"
STR_BOOTING: "BOOTING" STR_BOOTING: "Iniciando..."
STR_SLEEPING: "SLEEPING" STR_SLEEPING: "Suspendido"
STR_ENTERING_SLEEP: "ENTERING SLEEP" STR_ENTERING_SLEEP: "Entrando en suspensión"
STR_BROWSE_FILES: "Buscar archivos" STR_BROWSE_FILES: "Explorador de Archivos"
STR_FILE_TRANSFER: "Transferencia de archivos" STR_FILE_TRANSFER: "Transferir archivos"
STR_SETTINGS_TITLE: "Configuración" STR_SETTINGS_TITLE: "Ajustes"
STR_CALIBRE_LIBRARY: "Libreria Calibre" STR_CALIBRE_LIBRARY: "Biblioteca de Calibre"
STR_CONTINUE_READING: "Continuar leyendo" STR_CONTINUE_READING: "Continuar leyendo"
STR_NO_OPEN_BOOK: "No hay libros abiertos" STR_NO_OPEN_BOOK: "No hay libros abiertos"
STR_START_READING: "Start reading below" STR_START_READING: "Comenzar a leer"
STR_BOOKS: "Libros" STR_BOOKS: "Libros"
STR_NO_BOOKS_FOUND: "No se encontraron libros" STR_NO_BOOKS_FOUND: "No se encontraron libros"
STR_SELECT_CHAPTER: "Seleccionar capítulo" STR_SELECT_CHAPTER: "Seleccionar capítulo"
@@ -23,129 +23,129 @@ STR_INDEXING: "Indexando"
STR_MEMORY_ERROR: "Error de memoria" STR_MEMORY_ERROR: "Error de memoria"
STR_PAGE_LOAD_ERROR: "Error al cargar la página" STR_PAGE_LOAD_ERROR: "Error al cargar la página"
STR_EMPTY_FILE: "Archivo vacío" STR_EMPTY_FILE: "Archivo vacío"
STR_OUT_OF_BOUNDS: "Out of bounds" STR_OUT_OF_BOUNDS: "Fuera de rango"
STR_LOADING: "Cargando..." STR_LOADING: "Cargando..."
STR_LOADING_POPUP: "Cargando" STR_LOADING_POPUP: "Cargando"
STR_LOAD_XTC_FAILED: "Error al cargar XTC" STR_LOAD_XTC_FAILED: "Error al cargar XTC"
STR_LOAD_TXT_FAILED: "Error al cargar TXT" STR_LOAD_TXT_FAILED: "Error al cargar TXT"
STR_LOAD_EPUB_FAILED: "Error al cargar EPUB" STR_LOAD_EPUB_FAILED: "Error al cargar EPUB"
STR_SD_CARD_ERROR: "Error en la tarjeta SD" STR_SD_CARD_ERROR: "Error en la tarjeta microSD"
STR_WIFI_NETWORKS: "Redes Wi-Fi" STR_WIFI_NETWORKS: "Redes Wi-Fi"
STR_NO_NETWORKS: "No hay redes disponibles" STR_NO_NETWORKS: "No hay redes disponibles"
STR_NETWORKS_FOUND: "%zu redes encontradas" STR_NETWORKS_FOUND: "%zu redes encontradas"
STR_SCANNING: "Buscando..." STR_SCANNING: "Buscando..."
STR_CONNECTING: "Conectando..." STR_CONNECTING: "Conectando..."
STR_CONNECTED: "Conectado!" STR_CONNECTED: "¡Conectado!"
STR_CONNECTION_FAILED: "Error de conexion" STR_CONNECTION_FAILED: "Error de conexión"
STR_CONNECTION_TIMEOUT: "Connection timeout" STR_CONNECTION_TIMEOUT: "Tiempo de espera agotado"
STR_FORGET_NETWORK: "Olvidar la red?" STR_FORGET_NETWORK: "¿Olvidar la red?"
STR_SAVE_PASSWORD: "Guardar contraseña para la próxima vez?" STR_SAVE_PASSWORD: "¿Guardar contraseña?"
STR_REMOVE_PASSWORD: "Borrar contraseñas guardadas?" STR_REMOVE_PASSWORD: "¿Olvidar contraseña?"
STR_PRESS_OK_SCAN: "Presione OK para buscar de nuevo" STR_PRESS_OK_SCAN: "Pulse OK para buscar de nuevo"
STR_PRESS_ANY_CONTINUE: "Presione cualquier botón para continuar" STR_PRESS_ANY_CONTINUE: "Pulse cualquier botón para continuar"
STR_SELECT_HINT: "Izquierda/Derecha: Seleccionar | OK: Confirmar" STR_SELECT_HINT: "Izq./Der.: Seleccionar | OK: Confirmar"
STR_HOW_CONNECT: "Cómo te gustaría conectarte?" STR_HOW_CONNECT: "¿Cómo desea conectarse?"
STR_JOIN_NETWORK: "Unirse a una red" STR_JOIN_NETWORK: "Unirse a una red"
STR_CREATE_HOTSPOT: "Crear punto de acceso" STR_CREATE_HOTSPOT: "Crear Punto de Acceso"
STR_JOIN_DESC: "Conectarse a una red Wi-Fi existente" STR_JOIN_DESC: "Conectarse a una red Wi-Fi existente"
STR_HOTSPOT_DESC: "Crear una red Wi-Fi para que otros se unan" STR_HOTSPOT_DESC: "Conectarse a este dispositivo"
STR_STARTING_HOTSPOT: "Iniciando punto de acceso..." STR_STARTING_HOTSPOT: "Iniciando Punto de Acceso..."
STR_HOTSPOT_MODE: "Modo punto de acceso" STR_HOTSPOT_MODE: "Modo Punto de Acceso"
STR_CONNECT_WIFI_HINT: "Conectar su dispositivo a esta red Wi-Fi" STR_CONNECT_WIFI_HINT: "Conecte su dispositivo a esta red Wi-Fi"
STR_OPEN_URL_HINT: "Abre esta dirección en tu navegador" STR_OPEN_URL_HINT: "Abra esta dirección en su navegador"
STR_OR_HTTP_PREFIX: "o http://" STR_OR_HTTP_PREFIX: "o http://"
STR_SCAN_QR_HINT: "o escanee este código QR con su móvil:" STR_SCAN_QR_HINT: "o escanee el código QR con su móvil:"
STR_CALIBRE_WIRELESS: "Calibre inalámbrico" STR_CALIBRE_WIRELESS: "Calibre inalámbrico"
STR_CALIBRE_WEB_URL: "URL del sitio web de Calibre" STR_CALIBRE_WEB_URL: "URL del sitio web de Calibre"
STR_CONNECT_WIRELESS: "Conectar como dispositivo inalámbrico" STR_CONNECT_WIRELESS: "Conectar como dispositivo inalámbrico"
STR_NETWORK_LEGEND: "* = Cifrado | + = Guardado" STR_NETWORK_LEGEND: "* (Cifrado) | + (Guardado)"
STR_MAC_ADDRESS: "Dirección MAC:" STR_MAC_ADDRESS: "MAC Address:"
STR_CHECKING_WIFI: "Verificando Wi-Fi..." STR_CHECKING_WIFI: "Verificando Wi-Fi..."
STR_ENTER_WIFI_PASSWORD: "Introduzca la contraseña de Wi-Fi" STR_ENTER_WIFI_PASSWORD: "Introduzca la contraseña del Wi-Fi"
STR_ENTER_TEXT: "Introduzca el texto" STR_ENTER_TEXT: "Introduzca el texto"
STR_TO_PREFIX: "a " STR_TO_PREFIX: "a "
STR_CALIBRE_DISCOVERING: "Discovering Calibre..." STR_CALIBRE_DISCOVERING: "Buscando Calibre..."
STR_CALIBRE_CONNECTING_TO: "Conectándose a" STR_CALIBRE_CONNECTING_TO: "Conectándose a"
STR_CALIBRE_CONNECTED_TO: "Conectado a " STR_CALIBRE_CONNECTED_TO: "Conectado a "
STR_CALIBRE_WAITING_COMMANDS: "Esperando comandos..." STR_CALIBRE_WAITING_COMMANDS: "Esperando comandos..."
STR_CONNECTION_FAILED_RETRYING: "(Error de conexión, intentándolo nuevamente)" STR_CONNECTION_FAILED_RETRYING: "(Error de conexión, reintentando...)"
STR_CALIBRE_DISCONNECTED: "Calibre desconectado" STR_CALIBRE_DISCONNECTED: "Calibre desconectado"
STR_CALIBRE_WAITING_TRANSFER: "Esperando transferencia..." STR_CALIBRE_WAITING_TRANSFER: "Esperando transferencia..."
STR_CALIBRE_TRANSFER_HINT: "Si la transferencia falla, habilite \\n'Ignorar espacio libre' en las configuraciones del \\nplugin smartdevice de calibre." STR_CALIBRE_TRANSFER_HINT: "Si la transferencia falla, active \\n'Ignorar espacio libre' en la configuración del \\nPlugin Smart Device de Calibre."
STR_CALIBRE_RECEIVING: "Recibiendo: " STR_CALIBRE_RECEIVING: "Recibiendo: "
STR_CALIBRE_RECEIVED: "Recibido: " STR_CALIBRE_RECEIVED: "Recibido: "
STR_CALIBRE_WAITING_MORE: "Esperando más..." STR_CALIBRE_WAITING_MORE: "Esperando más..."
STR_CALIBRE_FAILED_CREATE_FILE: "Error al crear el archivo" STR_CALIBRE_FAILED_CREATE_FILE: "Error al crear el archivo"
STR_CALIBRE_PASSWORD_REQUIRED: "Contraseña requerida" STR_CALIBRE_PASSWORD_REQUIRED: "Contraseña requerida"
STR_CALIBRE_TRANSFER_INTERRUPTED: "Transferencia interrumpida" STR_CALIBRE_TRANSFER_INTERRUPTED: "Transferencia interrumpida"
STR_CALIBRE_INSTRUCTION_1: "1) Instala CrossPoint Reader plugin" STR_CALIBRE_INSTRUCTION_1: "1) Instale el Plugin CrossPoint Reader"
STR_CALIBRE_INSTRUCTION_2: "2) Conéctese a la misma red Wi-Fi" STR_CALIBRE_INSTRUCTION_2: "2) Conéctese a la misma red Wi-Fi"
STR_CALIBRE_INSTRUCTION_3: "3) En Calibre: \"Enviar a dispotivo\"" STR_CALIBRE_INSTRUCTION_3: "3) Desde Calibre seleccione: \"Enviar a dispositivo\""
STR_CALIBRE_INSTRUCTION_4: "\"Permanezca en esta pantalla mientras se envía\"" STR_CALIBRE_INSTRUCTION_4: "\"Permanezca en esta pantalla mientras se envía\""
STR_CAT_DISPLAY: "Pantalla" STR_CAT_DISPLAY: "Pantalla"
STR_CAT_READER: "Lector" STR_CAT_READER: "Lector"
STR_CAT_CONTROLS: "Control" STR_CAT_CONTROLS: "Controles"
STR_CAT_SYSTEM: "Sistema" STR_CAT_SYSTEM: "Sistema"
STR_SLEEP_SCREEN: "Salva Pantallas" STR_SLEEP_SCREEN: "Pantalla de suspensión"
STR_SLEEP_COVER_MODE: "Modo de salva pantallas" STR_SLEEP_COVER_MODE: "Modo de pantalla de suspensión"
STR_STATUS_BAR: "Barra de estado" STR_STATUS_BAR: "Barra de estado"
STR_HIDE_BATTERY: "Ocultar porcentaje de batería" STR_HIDE_BATTERY: "Ocultar % de batería"
STR_EXTRA_SPACING: "Espaciado extra de párrafos" STR_EXTRA_SPACING: "Espaciado entre párrafos"
STR_TEXT_AA: "Suavizado de bordes de texto" STR_TEXT_AA: "Suavizado de texto"
STR_SHORT_PWR_BTN: "Clic breve del botón de encendido" STR_SHORT_PWR_BTN: "Función especial botón Power"
STR_ORIENTATION: "Orientación de la lectura" STR_ORIENTATION: "Orientación"
STR_FRONT_BTN_LAYOUT: "Diseño de los botones frontales" STR_FRONT_BTN_LAYOUT: "Diseño de los botones frontales"
STR_SIDE_BTN_LAYOUT: "Diseño de los botones laterales (Lector)" STR_SIDE_BTN_LAYOUT: "Función botones laterales (Lector)"
STR_LONG_PRESS_SKIP: "Pasar a la capítulo al presiónar largamente" STR_LONG_PRESS_SKIP: "Saltar capítulo (pulsación larga)"
STR_FONT_FAMILY: "Familia de tipografía del lector" STR_FONT_FAMILY: "Tipografía"
STR_EXT_READER_FONT: "Tipografía externa" STR_EXT_READER_FONT: "Tipografía externa"
STR_EXT_CHINESE_FONT: "Tipografía (Lectura)" STR_EXT_CHINESE_FONT: "Tipografía"
STR_EXT_UI_FONT: "Tipografía (Pantalla)" STR_EXT_UI_FONT: "Tipografía (Pantalla)"
STR_FONT_SIZE: "Tamaño de la fuente (Pantalla)" STR_FONT_SIZE: "Tamaño"
STR_LINE_SPACING: "Interlineado (Lectura)" STR_LINE_SPACING: "Interlineado"
STR_ASCII_LETTER_SPACING: "Espaciado de letras ASCII" STR_ASCII_LETTER_SPACING: "Espaciado entre letras ASCII"
STR_ASCII_DIGIT_SPACING: "Espaciado de dígitos ASCII" STR_ASCII_DIGIT_SPACING: "Espaciado entre dígitos ASCII"
STR_CJK_SPACING: "Espaciado CJK" STR_CJK_SPACING: "Espaciado entre caracteres CJK"
STR_COLOR_MODE: "Modo de color" STR_COLOR_MODE: "Modo de color"
STR_SCREEN_MARGIN: "Margen de lectura" STR_SCREEN_MARGIN: "Margen de lectura"
STR_PARA_ALIGNMENT: "Ajuste de parágrafo del lector" STR_PARA_ALIGNMENT: "Ajuste de párrafo"
STR_HYPHENATION: "Hyphenation" STR_HYPHENATION: "División de palabras"
STR_TIME_TO_SLEEP: "Tiempo para dormir" STR_TIME_TO_SLEEP: "Auto suspensión"
STR_REFRESH_FREQ: "Frecuencia de actualización" STR_REFRESH_FREQ: "Frecuencia de refresco"
STR_CALIBRE_SETTINGS: "Configuraciones de Calibre" STR_CALIBRE_SETTINGS: "Ajustes de Calibre"
STR_KOREADER_SYNC: "Síncronización de KOReader" STR_KOREADER_SYNC: "Sincronización de KOReader"
STR_CHECK_UPDATES: "Verificar actualizaciones" STR_CHECK_UPDATES: "Verificar actualizaciones"
STR_LANGUAGE: "Idioma" STR_LANGUAGE: "Idioma"
STR_SELECT_WALLPAPER: "Seleccionar fondo" STR_SELECT_WALLPAPER: "Seleccionar fondo"
STR_CLEAR_READING_CACHE: "Borrar caché de lectura" STR_CLEAR_READING_CACHE: "Borrar caché de lectura"
STR_CALIBRE: "Calibre" STR_CALIBRE: "Calibre"
STR_USERNAME: "Nombre de usuario" STR_USERNAME: "Usuario"
STR_PASSWORD: "Contraseña" STR_PASSWORD: "Contraseña"
STR_SYNC_SERVER_URL: "URL del servidor de síncronización" STR_SYNC_SERVER_URL: "URL del servidor de sinc."
STR_DOCUMENT_MATCHING: "Coincidencia de documentos" STR_DOCUMENT_MATCHING: "Coincidencia de doc."
STR_AUTHENTICATE: "Autentificar" STR_AUTHENTICATE: "Autenticar"
STR_KOREADER_USERNAME: "Nombre de usuario de KOReader" STR_KOREADER_USERNAME: "Usuario de KOReader"
STR_KOREADER_PASSWORD: "Contraseña de KOReader" STR_KOREADER_PASSWORD: "Contraseña de KOReader"
STR_FILENAME: "Nombre del archivo" STR_FILENAME: "Nombre del archivo"
STR_BINARY: "Binario" STR_BINARY: "Binario"
STR_SET_CREDENTIALS_FIRST: "Configurar credenciales primero" STR_SET_CREDENTIALS_FIRST: "Configurar credenciales"
STR_WIFI_CONN_FAILED: "Falló la conexión Wi-Fi" STR_WIFI_CONN_FAILED: "Fallo de conexión Wi-Fi"
STR_AUTHENTICATING: "Autentificando..." STR_AUTHENTICATING: "Autenticando..."
STR_AUTH_SUCCESS: "Autenticación exitsosa!" STR_AUTH_SUCCESS: "¡Autenticación exitosa!"
STR_KOREADER_AUTH: "Autenticación KOReader" STR_KOREADER_AUTH: "Autenticación KOReader"
STR_SYNC_READY: "La síncronización de KOReader está lista para usarse" STR_SYNC_READY: "La sincronización de KOReader está lista para usarse"
STR_AUTH_FAILED: "Falló la autenticación" STR_AUTH_FAILED: "Error de autenticación"
STR_DONE: "Hecho" STR_DONE: "Hecho"
STR_CLEAR_CACHE_WARNING_1: "Esto borrará todos los datos en cache del libro." STR_CLEAR_CACHE_WARNING_1: "Esto borrará todos los datos del libro en caché."
STR_CLEAR_CACHE_WARNING_2: " ¡Se perderá todo el avance de leer!" STR_CLEAR_CACHE_WARNING_2: "¡Se perderá todo el progreso de lectura!"
STR_CLEAR_CACHE_WARNING_3: "Los libros deberán ser reíndexados" STR_CLEAR_CACHE_WARNING_3: "Los libros deberán ser reindexados"
STR_CLEAR_CACHE_WARNING_4: "cuando se abran de nuevo." STR_CLEAR_CACHE_WARNING_4: "cuando se vuelvan a abrir."
STR_CLEARING_CACHE: "Borrando caché..." STR_CLEARING_CACHE: "Borrando caché..."
STR_CACHE_CLEARED: "Cache limpia" STR_CACHE_CLEARED: "Caché borrada"
STR_ITEMS_REMOVED: "Elementos eliminados" STR_ITEMS_REMOVED: "Elementos eliminados"
STR_FAILED_LOWER: "Falló" STR_FAILED_LOWER: "Error"
STR_CLEAR_CACHE_FAILED: "No se pudo borrar la cache" STR_CLEAR_CACHE_FAILED: "No se pudo borrar la caché"
STR_CHECK_SERIAL_OUTPUT: "Verifique la salida serial para detalles" STR_CHECK_SERIAL_OUTPUT: "Consulte los registros del puerto serie"
STR_DARK: "Oscuro" STR_DARK: "Oscuro"
STR_LIGHT: "Claro" STR_LIGHT: "Claro"
STR_CUSTOM: "Personalizado" STR_CUSTOM: "Personalizado"
@@ -159,34 +159,34 @@ STR_NEVER: "Nunca"
STR_IN_READER: "En el lector" STR_IN_READER: "En el lector"
STR_ALWAYS: "Siempre" STR_ALWAYS: "Siempre"
STR_IGNORE: "Ignorar" STR_IGNORE: "Ignorar"
STR_SLEEP: "Dormir" STR_SLEEP: "Suspender"
STR_PAGE_TURN: "Paso de página" STR_PAGE_TURN: "Pasar página"
STR_PORTRAIT: "Portrato" STR_PORTRAIT: "Vertical"
STR_LANDSCAPE_CW: "Paisaje sentido horario" STR_LANDSCAPE_CW: "Horizontal (horario)"
STR_INVERTED: "Invertido" STR_INVERTED: "Invertido"
STR_LANDSCAPE_CCW: "Paisaje sentido antihorario" STR_LANDSCAPE_CCW: "Horizontal (antihorario)"
STR_FRONT_LAYOUT_BCLR: "Atrás, Confirmar, Izquierda, Derecha" STR_FRONT_LAYOUT_BCLR: "Atrás, Confirmar, Izq., Der."
STR_FRONT_LAYOUT_LRBC: "Izquierda, Derecha, Atrás, Confirmar" STR_FRONT_LAYOUT_LRBC: "Izq., Der., Atrás, Confirmar"
STR_FRONT_LAYOUT_LBCR: "Izquierda, Atrás, Confirmar, Derecha" STR_FRONT_LAYOUT_LBCR: "Izq., Atrás, Confirmar, Der."
STR_PREV_NEXT: "Anterior/Siguiente" STR_PREV_NEXT: "Ant./Sig."
STR_NEXT_PREV: "Siguiente/Anterior" STR_NEXT_PREV: "Sig./Ant."
STR_BOOKERLY: "Relacionado con libros" STR_BOOKERLY: "Bookerly"
STR_NOTO_SANS: "Noto Sans" STR_NOTO_SANS: "Noto Sans"
STR_OPEN_DYSLEXIC: "Open Dyslexic" STR_OPEN_DYSLEXIC: "Open Dyslexic"
STR_SMALL: "Pequeño" STR_SMALL: "Pequeño"
STR_MEDIUM: "Medio" STR_MEDIUM: "Mediano"
STR_LARGE: "Grande" STR_LARGE: "Grande"
STR_X_LARGE: "Extra grande" STR_X_LARGE: "Extra grande"
STR_TIGHT: "Ajustado" STR_TIGHT: "Estrecho"
STR_NORMAL: "Normal" STR_NORMAL: "Normal"
STR_WIDE: "Ancho" STR_WIDE: "Amplio"
STR_JUSTIFY: "Justificar" STR_JUSTIFY: "Justificado"
STR_ALIGN_LEFT: "Izquierda" STR_ALIGN_LEFT: "Izquierda"
STR_CENTER: "Centro" STR_CENTER: "Centro"
STR_ALIGN_RIGHT: "Derecha" STR_ALIGN_RIGHT: "Derecha"
STR_MIN_1: "1 Minuto" STR_MIN_1: "1 Minuto"
STR_MIN_5: "10 Minutos" STR_MIN_5: "5 Minutos"
STR_MIN_10: "5 Minutos" STR_MIN_10: "10 Minutos"
STR_MIN_15: "15 Minutos" STR_MIN_15: "15 Minutos"
STR_MIN_30: "30 Minutos" STR_MIN_30: "30 Minutos"
STR_PAGES_1: "1 Página" STR_PAGES_1: "1 Página"
@@ -194,38 +194,38 @@ STR_PAGES_5: "5 Páginas"
STR_PAGES_10: "10 Páginas" STR_PAGES_10: "10 Páginas"
STR_PAGES_15: "15 Páginas" STR_PAGES_15: "15 Páginas"
STR_PAGES_30: "30 Páginas" STR_PAGES_30: "30 Páginas"
STR_UPDATE: "ActualizaR" STR_UPDATE: "Actualizar"
STR_CHECKING_UPDATE: "Verificando actualización..." STR_CHECKING_UPDATE: "Verificando actualización..."
STR_NEW_UPDATE: "¡Nueva actualización disponible!" STR_NEW_UPDATE: "¡Nueva actualización disponible!"
STR_CURRENT_VERSION: "Versión actual:" STR_CURRENT_VERSION: "Versión actual:"
STR_NEW_VERSION: "Nueva versión:" STR_NEW_VERSION: "Nueva versión:"
STR_UPDATING: "Actualizando..." STR_UPDATING: "Actualizando..."
STR_NO_UPDATE: "No hay actualizaciones disponibles" STR_NO_UPDATE: "No hay actualizaciones disponibles"
STR_UPDATE_FAILED: "Falló la actualización" STR_UPDATE_FAILED: "Fallo de actualización"
STR_UPDATE_COMPLETE: "Actualización completada" STR_UPDATE_COMPLETE: "Actualización completada"
STR_POWER_ON_HINT: "Presione y mantenga presionado el botón de encendido para volver a encender" STR_POWER_ON_HINT: "Pulse y mantenga presionado el botón de encendido para volver a encender"
STR_EXTERNAL_FONT: "Fuente externa" STR_EXTERNAL_FONT: "Fuente externa"
STR_BUILTIN_DISABLED: "Incorporado (Desactivado)" STR_BUILTIN_DISABLED: "Incorporado (Desactivado)"
STR_NO_ENTRIES: "No se encontraron elementos" STR_NO_ENTRIES: "No se encontraron elementos"
STR_DOWNLOADING: "Descargando..." STR_DOWNLOADING: "Descargando..."
STR_DOWNLOAD_FAILED: "Falló la descarga" STR_DOWNLOAD_FAILED: "Fallo de descarga"
STR_ERROR_MSG: "Error" STR_ERROR_MSG: "Error"
STR_UNNAMED: "Sin nombre" STR_UNNAMED: "Sin nombre"
STR_NO_SERVER_URL: "No se ha configurado la url del servidor" STR_NO_SERVER_URL: "No se ha configurado la URL del servidor"
STR_FETCH_FEED_FAILED: "Failed to fetch feed" STR_FETCH_FEED_FAILED: "Fallo al obtener el feed"
STR_PARSE_FEED_FAILED: "Failed to parse feed" STR_PARSE_FEED_FAILED: "Fallo al procesar el feed"
STR_NETWORK_PREFIX: "Red: " STR_NETWORK_PREFIX: "Red: "
STR_IP_ADDRESS_PREFIX: "Dirección IP: " STR_IP_ADDRESS_PREFIX: "IP: "
STR_SCAN_QR_WIFI_HINT: "O escanee el código QR con su teléfono para conectarse a WI-FI." STR_SCAN_QR_WIFI_HINT: "O escanee el código QR con su teléfono para conectarse a Wi-Fi."
STR_ERROR_GENERAL_FAILURE: "Error: Fallo general" STR_ERROR_GENERAL_FAILURE: "Error: Fallo general"
STR_ERROR_NETWORK_NOT_FOUND: "Error: Red no encontrada" STR_ERROR_NETWORK_NOT_FOUND: "Error: Red no encontrada"
STR_ERROR_CONNECTION_TIMEOUT: "Error: Connection timeout" STR_ERROR_CONNECTION_TIMEOUT: "Error: Tiempo de conexión agotado"
STR_SD_CARD: "Tarjeta SD" STR_SD_CARD: "Tarjeta microSD"
STR_BACK: "« Atrás" STR_BACK: "« Atrás"
STR_EXIT: "« SaliR" STR_EXIT: "« Salir"
STR_HOME: "« Inicio" STR_HOME: "« Inicio"
STR_SAVE: "« Guardar" STR_SAVE: "« Guardar"
STR_SELECT: "Seleccionar" STR_SELECT: "Elegir"
STR_TOGGLE: "Cambiar" STR_TOGGLE: "Cambiar"
STR_CONFIRM: "Confirmar" STR_CONFIRM: "Confirmar"
STR_CANCEL: "Cancelar" STR_CANCEL: "Cancelar"
@@ -235,67 +235,67 @@ STR_DOWNLOAD: "Descargar"
STR_RETRY: "Reintentar" STR_RETRY: "Reintentar"
STR_YES: "Sí" STR_YES: "Sí"
STR_NO: "No" STR_NO: "No"
STR_STATE_ON: "ENCENDIDO" STR_STATE_ON: "Activado"
STR_STATE_OFF: "APAGADO" STR_STATE_OFF: "Desactivado"
STR_SET: "Configurar" STR_SET: "Configurar"
STR_NOT_SET: "No configurado" STR_NOT_SET: "No configurado"
STR_DIR_LEFT: "Izquierda" STR_DIR_LEFT: "Izq."
STR_DIR_RIGHT: "Derecha" STR_DIR_RIGHT: "Der."
STR_DIR_UP: "Arriba" STR_DIR_UP: "Subir"
STR_DIR_DOWN: "Abajo" STR_DIR_DOWN: "Bajar"
STR_CAPS_ON: "MAYÚSCULAS" STR_CAPS_ON: "MAYÚSCULAS"
STR_CAPS_OFF: "caps" STR_CAPS_OFF: "minúsculas"
STR_OK_BUTTON: "OK" STR_OK_BUTTON: "OK"
STR_ON_MARKER: "[ENCENDIDO]" STR_ON_MARKER: "[Activo]"
STR_SLEEP_COVER_FILTER: "Filtro de salva pantalla y protección de la pantalla" STR_SLEEP_COVER_FILTER: "Filtro de pantalla de suspensión"
STR_FILTER_CONTRAST: "Contraste" STR_FILTER_CONTRAST: "Contraste"
STR_STATUS_BAR_FULL_PERCENT: "Completa con porcentaje" STR_STATUS_BAR_FULL_PERCENT: "Completa con %"
STR_STATUS_BAR_FULL_BOOK: "Completa con progreso del libro" STR_STATUS_BAR_FULL_BOOK: "Completa con progreso lect."
STR_STATUS_BAR_BOOK_ONLY: "Solo progreso del libro" STR_STATUS_BAR_BOOK_ONLY: "Solo progreso"
STR_STATUS_BAR_FULL_CHAPTER: "Completa con progreso de capítulos" STR_STATUS_BAR_FULL_CHAPTER: "Completa con progreso cap."
STR_UI_THEME: "Estilo de pantalla" STR_UI_THEME: "Interfaz"
STR_THEME_CLASSIC: "Clásico" STR_THEME_CLASSIC: "Clásico"
STR_THEME_LYRA: "Lyra" STR_THEME_LYRA: "Lyra"
STR_THEME_LYRA_EXTENDED: "Lyra Extended" STR_THEME_LYRA_EXTENDED: "Lyra Extendido"
STR_SUNLIGHT_FADING_FIX: "Corrección de desvastado por sol" STR_SUNLIGHT_FADING_FIX: "Corrección de desvanecimiento"
STR_REMAP_FRONT_BUTTONS: "Reconfigurar botones frontales" STR_REMAP_FRONT_BUTTONS: "Reconfigurar botones frontales"
STR_OPDS_BROWSER: "Navegador opds" STR_OPDS_BROWSER: "Navegador OPDS"
STR_COVER_CUSTOM: "Portada + Personalizado" STR_COVER_CUSTOM: "Portada + Pers."
STR_RECENTS: "Recientes" STR_RECENTS: "Recientes"
STR_MENU_RECENT_BOOKS: "Libros recientes" STR_MENU_RECENT_BOOKS: "Libros recientes"
STR_NO_RECENT_BOOKS: "No hay libros recientes" STR_NO_RECENT_BOOKS: "No hay libros recientes"
STR_CALIBRE_DESC: "Utilice las transferencias dispositivos inalámbricos de calibre" STR_CALIBRE_DESC: "Transferir contenido a este dispositivo"
STR_FORGET_AND_REMOVE: "Olvidar la red y eliminar la contraseña guardada?" STR_FORGET_AND_REMOVE: "¿Desea olvidar la red y la contraseña guardada?"
STR_FORGET_BUTTON: "Olvidar la red" STR_FORGET_BUTTON: "Olvidar"
STR_CALIBRE_STARTING: "Iniciando calibre..." STR_CALIBRE_STARTING: "Iniciando Calibre..."
STR_CALIBRE_SETUP: "Configuración" STR_CALIBRE_SETUP: "Configuración"
STR_CALIBRE_STATUS: "Estado" STR_CALIBRE_STATUS: "Estado"
STR_CLEAR_BUTTON: "Borrar" STR_CLEAR_BUTTON: "Borrar"
STR_DEFAULT_VALUE: "Previo" STR_DEFAULT_VALUE: "Predeterminado"
STR_REMAP_PROMPT: "Presione un botón frontal para cada función" STR_REMAP_PROMPT: "Pulse un botón frontal para cada función"
STR_UNASSIGNED: "No asignado" STR_UNASSIGNED: "Sin asignar"
STR_ALREADY_ASSIGNED: "Ya asignado" STR_ALREADY_ASSIGNED: "Ya asignado"
STR_REMAP_RESET_HINT: "Botón lateral arriba: Restablecer a la configuración previo" STR_REMAP_RESET_HINT: "Botón lateral arriba: Restablecer configuración"
STR_REMAP_CANCEL_HINT: "Botón lateral abajo: Anular reconfiguración" STR_REMAP_CANCEL_HINT: "Botón lateral abajo: Anular reconfiguración"
STR_HW_BACK_LABEL: "Atrás (Primer botón)" STR_HW_BACK_LABEL: "Atrás (Primer botón)"
STR_HW_CONFIRM_LABEL: "Confirmar (Segundo botón)" STR_HW_CONFIRM_LABEL: "Confirmar (Segundo botón)"
STR_HW_LEFT_LABEL: "Izquierda (Tercer botón)" STR_HW_LEFT_LABEL: "Izq. (Tercer botón)"
STR_HW_RIGHT_LABEL: "Derecha (Cuarto botón)" STR_HW_RIGHT_LABEL: "Der. (Cuarto botón)"
STR_GO_TO_PERCENT: "Ir a %" STR_GO_TO_PERCENT: "Ir a %"
STR_GO_HOME_BUTTON: "Volver a inicio" STR_GO_HOME_BUTTON: "Volver a inicio"
STR_SYNC_PROGRESS: "Progreso de síncronización" STR_SYNC_PROGRESS: "Sincronizar progreso de lectura"
STR_DELETE_CACHE: "Borrar cache del libro" STR_DELETE_CACHE: "Borrar caché del libro"
STR_CHAPTER_PREFIX: "Capítulo:" STR_CHAPTER_PREFIX: "Cap.:"
STR_PAGES_SEPARATOR: " Páginas |" STR_PAGES_SEPARATOR: " Páginas |"
STR_BOOK_PREFIX: "Libro:" STR_BOOK_PREFIX: "Libro:"
STR_KBD_SHIFT: "shift" STR_KBD_SHIFT: "shift"
STR_KBD_SHIFT_CAPS: "SHIFT" STR_KBD_SHIFT_CAPS: "SHIFT"
STR_KBD_LOCK: "BLOQUEAR" STR_KBD_LOCK: "BLOQUEAR"
STR_CALIBRE_URL_HINT: "Para calibre, agregue /opds a su urL" STR_CALIBRE_URL_HINT: "Para Calibre, agregue /opds a su URL"
STR_PERCENT_STEP_HINT: "Izquierda/Derecha: 1% Arriba/Abajo: 10%" STR_PERCENT_STEP_HINT: "Izq./Der.: 1% | Subir/Bajar: 10%"
STR_SYNCING_TIME: "Tiempo de síncronización..." STR_SYNCING_TIME: "Tiempo de sincronización..."
STR_CALC_HASH: "Calculando hash del documento..." STR_CALC_HASH: "Calculando HASH del documento..."
STR_HASH_FAILED: "No se pudo calcular el hash del documento" STR_HASH_FAILED: "No se pudo calcular el HASH del documento"
STR_FETCH_PROGRESS: "Recuperando progreso remoto..." STR_FETCH_PROGRESS: "Recuperando progreso remoto..."
STR_UPLOAD_PROGRESS: "Subiendo progreso..." STR_UPLOAD_PROGRESS: "Subiendo progreso..."
STR_NO_CREDENTIALS_MSG: "No se han configurado credenciales" STR_NO_CREDENTIALS_MSG: "No se han configurado credenciales"
@@ -304,15 +304,15 @@ STR_PROGRESS_FOUND: "¡Progreso encontrado!"
STR_REMOTE_LABEL: "Remoto" STR_REMOTE_LABEL: "Remoto"
STR_LOCAL_LABEL: "Local" STR_LOCAL_LABEL: "Local"
STR_PAGE_OVERALL_FORMAT: "Página %d, %.2f%% Completada" STR_PAGE_OVERALL_FORMAT: "Página %d, %.2f%% Completada"
STR_PAGE_TOTAL_OVERALL_FORMAT: "Página %d / %d, %.2f% Completada" STR_PAGE_TOTAL_OVERALL_FORMAT: "Página %d / %d, %.2f%% Completada"
STR_DEVICE_FROM_FORMAT: " De: %s" STR_DEVICE_FROM_FORMAT: " De: %s"
STR_APPLY_REMOTE: "Aplicar progreso remoto" STR_APPLY_REMOTE: "Aplicar progreso remoto"
STR_UPLOAD_LOCAL: "Subir progreso local" STR_UPLOAD_LOCAL: "Subir progreso local"
STR_NO_REMOTE_MSG: "No se encontró progreso remoto" STR_NO_REMOTE_MSG: "No se encontró progreso remoto"
STR_UPLOAD_PROMPT: "Subir posicion actual?" STR_UPLOAD_PROMPT: "¿Subir posición actual?"
STR_UPLOAD_SUCCESS: "¡Progreso subido!" STR_UPLOAD_SUCCESS: "¡Progreso subido!"
STR_SYNC_FAILED_MSG: "Fallo de síncronización" STR_SYNC_FAILED_MSG: "Fallo de sincronización"
STR_SECTION_PREFIX: "Seccion" STR_SECTION_PREFIX: "Secc.:"
STR_UPLOAD: "Subir" STR_UPLOAD: "Subir"
STR_BOOK_S_STYLE: "Estilo del libro" STR_BOOK_S_STYLE: "Estilo del libro"
STR_EMBEDDED_STYLE: "Estilo integrado" STR_EMBEDDED_STYLE: "Estilo integrado"

View File

@@ -266,7 +266,7 @@ STR_MENU_RECENT_BOOKS: "Senaste böckerna"
STR_NO_RECENT_BOOKS: "Inga senaste böcker" STR_NO_RECENT_BOOKS: "Inga senaste böcker"
STR_CALIBRE_DESC: "Använd Calibres trådlösa enhetsöverföring" STR_CALIBRE_DESC: "Använd Calibres trådlösa enhetsöverföring"
STR_FORGET_AND_REMOVE: "Glöm nätverk och ta bort sparat lösenord?" STR_FORGET_AND_REMOVE: "Glöm nätverk och ta bort sparat lösenord?"
STR_FORGET_BUTTON: "Glöm nätverk" STR_FORGET_BUTTON: "Glöm"
STR_CALIBRE_STARTING: "Starar Calibre…" STR_CALIBRE_STARTING: "Starar Calibre…"
STR_CALIBRE_SETUP: "Inställning" STR_CALIBRE_SETUP: "Inställning"
STR_CALIBRE_STATUS: "Status" STR_CALIBRE_STATUS: "Status"

View File

@@ -64,7 +64,7 @@ void HalPowerManager::startDeepSleep(HalGPIO& gpio) const {
esp_deep_sleep_start(); esp_deep_sleep_start();
} }
int HalPowerManager::getBatteryPercentage() const { uint16_t HalPowerManager::getBatteryPercentage() const {
static const BatteryMonitor battery = BatteryMonitor(BAT_GPIO0); static const BatteryMonitor battery = BatteryMonitor(BAT_GPIO0);
return battery.readPercentage(); return battery.readPercentage();
} }

View File

@@ -34,7 +34,7 @@ class HalPowerManager {
void startDeepSleep(HalGPIO& gpio) const; void startDeepSleep(HalGPIO& gpio) const;
// Get battery percentage (range 0-100) // Get battery percentage (range 0-100)
int getBatteryPercentage() const; uint16_t getBatteryPercentage() const;
// RAII helper class to manage power saving locks // RAII helper class to manage power saving locks
// Usage: create an instance of Lock in a scope to disable power saving, for example when running a task that needs // Usage: create an instance of Lock in a scope to disable power saving, for example when running a task that needs

View File

@@ -1,6 +0,0 @@
#pragma once
#include <BatteryMonitor.h>
#define BAT_GPIO0 0 // Battery voltage
static BatteryMonitor battery(BAT_GPIO0);

View File

@@ -11,7 +11,6 @@
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include "Battery.h"
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "CrossPointState.h" #include "CrossPointState.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"

View File

@@ -92,7 +92,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);
} }
} }

View File

@@ -638,13 +638,31 @@ void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageC
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int orientedMarginTop, void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int orientedMarginTop,
const int orientedMarginRight, const int orientedMarginBottom, const int orientedMarginRight, const int orientedMarginBottom,
const int orientedMarginLeft) { const int orientedMarginLeft) {
// Force full refresh for pages with images when anti-aliasing is on, // Force special handling for pages with images when anti-aliasing is on
// as grayscale tones require half refresh to display correctly bool imagePageWithAA = page->hasImages() && SETTINGS.textAntiAliasing;
bool forceFullRefresh = page->hasImages() && SETTINGS.textAntiAliasing;
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
if (forceFullRefresh || pagesUntilFullRefresh <= 1) { if (imagePageWithAA) {
// Double FAST_REFRESH with selective image blanking (pablohc's technique):
// HALF_REFRESH sets particles too firmly for the grayscale LUT to adjust.
// Instead, blank only the image area and do two fast refreshes.
// Step 1: Display page with image area blanked (text appears, image area white)
// Step 2: Re-render with images and display again (images appear clean)
int16_t imgX, imgY, imgW, imgH;
if (page->getImageBoundingBox(imgX, imgY, imgW, imgH)) {
renderer.fillRect(imgX + orientedMarginLeft, imgY + orientedMarginTop, imgW, imgH, false);
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
// Re-render page content to restore images into the blanked area
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
} else {
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
}
// Double FAST_REFRESH handles ghosting for image pages; don't count toward full refresh cadence
} else if (pagesUntilFullRefresh <= 1) {
renderer.displayBuffer(HalDisplay::HALF_REFRESH); renderer.displayBuffer(HalDisplay::HALF_REFRESH);
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
} else { } else {

View File

@@ -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());
@@ -88,6 +91,11 @@ void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
renderer, mappedInput, std::move(epub), [this, epubPath] { goToLibrary(epubPath); }, [this] { onGoBack(); })); renderer, mappedInput, std::move(epub), [this, epubPath] { goToLibrary(epubPath); }, [this] { onGoBack(); }));
} }
void ReaderActivity::onGoToBmpViewer(const std::string& path) {
exitActivity();
enterNewActivity(new BmpViewerActivity(renderer, mappedInput, path, [this, path] { goToLibrary(path); }));
}
void ReaderActivity::onGoToXtcReader(std::unique_ptr<Xtc> xtc) { void ReaderActivity::onGoToXtcReader(std::unique_ptr<Xtc> xtc) {
const auto xtcPath = xtc->getPath(); const auto xtcPath = xtc->getPath();
currentBookPath = xtcPath; currentBookPath = xtcPath;
@@ -113,8 +121,9 @@ void ReaderActivity::onEnter() {
} }
currentBookPath = initialBookPath; currentBookPath = initialBookPath;
if (isBmpFile(initialBookPath)) {
if (isXtcFile(initialBookPath)) { onGoToBmpViewer(initialBookPath);
} else if (isXtcFile(initialBookPath)) {
auto xtc = loadXtc(initialBookPath); auto xtc = loadXtc(initialBookPath);
if (!xtc) { if (!xtc) {
onGoBack(); onGoBack();

View File

@@ -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,

View File

@@ -0,0 +1,101 @@
#include "BmpViewerActivity.h"
#include <Bitmap.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
#include "components/UITheme.h"
#include "fontIds.h"
BmpViewerActivity::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 BmpViewerActivity::onEnter() {
Activity::onEnter();
// Removed the redundant initial renderer.clearScreen()
FsFile file;
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
Rect popupRect = GUI.drawPopup(renderer, tr(STR_LOADING_POPUP));
GUI.fillPopupProgress(renderer, popupRect, 20); // Initial 20% progress
// 1. Open the file
if (Storage.openFileForRead("BMP", filePath, file)) {
Bitmap bitmap(file, true);
// 2. Parse headers to get dimensions
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
int x, y;
if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) {
float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
if (ratio > screenRatio) {
// Wider than screen
x = 0;
y = std::round((static_cast<float>(pageHeight) - static_cast<float>(pageWidth) / ratio) / 2);
} else {
// Taller than screen
x = std::round((static_cast<float>(pageWidth) - static_cast<float>(pageHeight) * ratio) / 2);
y = 0;
}
} else {
// Center small images
x = (pageWidth - bitmap.getWidth()) / 2;
y = (pageHeight - bitmap.getHeight()) / 2;
}
// 4. Prepare Rendering
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
GUI.fillPopupProgress(renderer, popupRect, 50);
renderer.clearScreen();
// Assuming drawBitmap defaults to 0,0 crop if omitted, or pass explicitly: drawBitmap(bitmap, x, y, pageWidth,
// pageHeight, 0, 0)
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, 0, 0);
// Draw UI hints on the base layer
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
// Single pass for non-grayscale images
renderer.displayBuffer(HalDisplay::FULL_REFRESH);
} else {
// Handle file parsing error
renderer.clearScreen();
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Invalid BMP File");
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
}
file.close();
} else {
// Handle file open error
renderer.clearScreen();
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Could not open file");
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(HalDisplay::FULL_REFRESH);
}
}
void BmpViewerActivity::onExit() {
Activity::onExit();
renderer.clearScreen();
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
}
void BmpViewerActivity::loop() {
// Keep CPU awake/polling so 1st click works
Activity::loop();
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
if (onGoBack) onGoBack();
return;
}
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <functional>
#include <string>
#include "../Activity.h"
#include "MappedInputManager.h"
class BmpViewerActivity final : public Activity {
public:
BmpViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string filePath,
std::function<void()> onGoBack);
void onEnter() override;
void onExit() override;
void loop() override;
private:
std::string filePath;
std::function<void()> onGoBack;
};

View File

@@ -1,6 +1,7 @@
#include "BaseTheme.h" #include "BaseTheme.h"
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <HalPowerManager.h>
#include <HalStorage.h> #include <HalStorage.h>
#include <Logging.h> #include <Logging.h>
#include <Utf8.h> #include <Utf8.h>
@@ -8,7 +9,6 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include "Battery.h"
#include "I18n.h" #include "I18n.h"
#include "RecentBooksStore.h" #include "RecentBooksStore.h"
#include "components/UITheme.h" #include "components/UITheme.h"
@@ -47,7 +47,7 @@ void drawBatteryIcon(const GfxRenderer& renderer, int x, int y, int battWidth, i
void BaseTheme::drawBatteryLeft(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { void BaseTheme::drawBatteryLeft(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
// Left aligned: icon on left, percentage on right (reader mode) // Left aligned: icon on left, percentage on right (reader mode)
const uint16_t percentage = battery.readPercentage(); const uint16_t percentage = powerManager.getBatteryPercentage();
const int y = rect.y + 6; const int y = rect.y + 6;
if (showPercentage) { if (showPercentage) {
@@ -62,7 +62,7 @@ void BaseTheme::drawBatteryLeft(const GfxRenderer& renderer, Rect rect, const bo
void BaseTheme::drawBatteryRight(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { void BaseTheme::drawBatteryRight(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
// Right aligned: percentage on left, icon on right (UI headers) // Right aligned: percentage on left, icon on right (UI headers)
// rect.x is already positioned for the icon (drawHeader calculated it) // rect.x is already positioned for the icon (drawHeader calculated it)
const uint16_t percentage = battery.readPercentage(); const uint16_t percentage = powerManager.getBatteryPercentage();
const int y = rect.y + 6; const int y = rect.y + 6;
if (showPercentage) { if (showPercentage) {
@@ -341,14 +341,57 @@ void BaseTheme::drawTabBar(const GfxRenderer& renderer, const Rect rect, const s
void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std::vector<RecentBook>& recentBooks, void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std::vector<RecentBook>& recentBooks,
const int selectorIndex, bool& coverRendered, bool& coverBufferStored, const int selectorIndex, bool& coverRendered, bool& coverBufferStored,
bool& bufferRestored, std::function<bool()> storeCoverBuffer) const { bool& bufferRestored, std::function<bool()> storeCoverBuffer) const {
// --- Top "book" card for the current title (selectorIndex == 0) ---
const int bookWidth = rect.width / 2;
const int bookHeight = rect.height;
const int bookX = (rect.width - bookWidth) / 2;
const int bookY = rect.y;
const bool hasContinueReading = !recentBooks.empty(); const bool hasContinueReading = !recentBooks.empty();
const bool bookSelected = hasContinueReading && selectorIndex == 0; const bool bookSelected = hasContinueReading && selectorIndex == 0;
// --- Top "book" card for the current title (selectorIndex == 0) ---
// When there's no cover image, use fixed size (half screen)
// When there's cover image, adapt width to image aspect ratio, keep height fixed at 400px
const int baseHeight = rect.height; // Fixed height (400px)
int bookWidth, bookX;
bool hasCoverImage = false;
if (hasContinueReading && !recentBooks[0].coverBmpPath.empty()) {
// Try to get actual image dimensions from BMP header
const std::string coverBmpPath =
UITheme::getCoverThumbPath(recentBooks[0].coverBmpPath, BaseMetrics::values.homeCoverHeight);
FsFile file;
if (Storage.openFileForRead("HOME", coverBmpPath, file)) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
hasCoverImage = true;
const int imgWidth = bitmap.getWidth();
const int imgHeight = bitmap.getHeight();
// Calculate width based on aspect ratio, maintaining baseHeight
if (imgWidth > 0 && imgHeight > 0) {
const float aspectRatio = static_cast<float>(imgWidth) / static_cast<float>(imgHeight);
bookWidth = static_cast<int>(baseHeight * aspectRatio);
// Ensure width doesn't exceed reasonable limits (max 90% of screen width)
const int maxWidth = static_cast<int>(rect.width * 0.9f);
if (bookWidth > maxWidth) {
bookWidth = maxWidth;
}
} else {
bookWidth = rect.width / 2; // Fallback
}
}
file.close();
}
}
if (!hasCoverImage) {
// No cover: use half screen size
bookWidth = rect.width / 2;
}
bookX = rect.x + (rect.width - bookWidth) / 2;
const int bookY = rect.y;
const int bookHeight = baseHeight;
// Bookmark dimensions (used in multiple places) // Bookmark dimensions (used in multiple places)
const int bookmarkWidth = bookWidth / 8; const int bookmarkWidth = bookWidth / 8;
const int bookmarkHeight = bookHeight / 5; const int bookmarkHeight = bookHeight / 5;
@@ -370,27 +413,9 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
Bitmap bitmap(file); Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) { if (bitmap.parseHeaders() == BmpReaderError::Ok) {
LOG_DBG("THEME", "Rendering bmp"); LOG_DBG("THEME", "Rendering bmp");
// Calculate position to center image within the book card
int coverX, coverY;
if (bitmap.getWidth() > bookWidth || bitmap.getHeight() > bookHeight) { // Draw the cover image (bookWidth and bookHeight already match image aspect ratio)
const float imgRatio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight()); renderer.drawBitmap(bitmap, bookX, bookY, bookWidth, bookHeight);
const float boxRatio = static_cast<float>(bookWidth) / static_cast<float>(bookHeight);
if (imgRatio > boxRatio) {
coverX = bookX;
coverY = bookY + (bookHeight - static_cast<int>(bookWidth / imgRatio)) / 2;
} else {
coverX = bookX + (bookWidth - static_cast<int>(bookHeight * imgRatio)) / 2;
coverY = bookY;
}
} else {
coverX = bookX + (bookWidth - bitmap.getWidth()) / 2;
coverY = bookY + (bookHeight - bitmap.getHeight()) / 2;
}
// Draw the cover image centered within the book card
renderer.drawBitmap(bitmap, coverX, coverY, bookWidth, bookHeight);
// Draw border around the card // Draw border around the card
renderer.drawRect(bookX, bookY, bookWidth, bookHeight); renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
@@ -573,7 +598,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
const int boxWidth = maxTextWidth + boxPadding * 2; const int boxWidth = maxTextWidth + boxPadding * 2;
const int boxHeight = totalTextHeight + boxPadding * 2; const int boxHeight = totalTextHeight + boxPadding * 2;
const int boxX = (rect.width - boxWidth) / 2; const int boxX = rect.x + (rect.width - boxWidth) / 2;
const int boxY = titleYStart - boxPadding; const int boxY = titleYStart - boxPadding;
// Draw box (inverted when selected: black box instead of white) // Draw box (inverted when selected: black box instead of white)
@@ -616,7 +641,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
constexpr int continuePadding = 6; constexpr int continuePadding = 6;
const int continueBoxWidth = continueTextWidth + continuePadding * 2; const int continueBoxWidth = continueTextWidth + continuePadding * 2;
const int continueBoxHeight = renderer.getLineHeight(UI_10_FONT_ID) + continuePadding; const int continueBoxHeight = renderer.getLineHeight(UI_10_FONT_ID) + continuePadding;
const int continueBoxX = (rect.width - continueBoxWidth) / 2; const int continueBoxX = rect.x + (rect.width - continueBoxWidth) / 2;
const int continueBoxY = continueY - continuePadding / 2; const int continueBoxY = continueY - continuePadding / 2;
renderer.fillRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, bookSelected); renderer.fillRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, bookSelected);
renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, !bookSelected); renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, !bookSelected);

View File

@@ -82,7 +82,7 @@ constexpr ThemeMetrics values = {.batteryWidth = 15,
.tabBarHeight = 50, .tabBarHeight = 50,
.scrollBarWidth = 4, .scrollBarWidth = 4,
.scrollBarRightOffset = 5, .scrollBarRightOffset = 5,
.homeTopPadding = 20, .homeTopPadding = 40,
.homeCoverHeight = 400, .homeCoverHeight = 400,
.homeCoverTileHeight = 400, .homeCoverTileHeight = 400,
.homeRecentBooksCount = 1, .homeRecentBooksCount = 1,

View File

@@ -64,11 +64,12 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con
file.close(); file.close();
} }
} }
// Draw either way
renderer.drawRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection, tileWidth - 2 * hPaddingInSelection,
Lyra3CoversMetrics::values.homeCoverHeight, true);
if (!hasCover) { if (!hasCover) {
// Render empty cover // Render empty cover
renderer.drawRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection,
tileWidth - 2 * hPaddingInSelection, Lyra3CoversMetrics::values.homeCoverHeight, true);
renderer.fillRect(tileX + hPaddingInSelection, renderer.fillRect(tileX + hPaddingInSelection,
tileY + hPaddingInSelection + (Lyra3CoversMetrics::values.homeCoverHeight / 3), tileY + hPaddingInSelection + (Lyra3CoversMetrics::values.homeCoverHeight / 3),
tileWidth - 2 * hPaddingInSelection, 2 * Lyra3CoversMetrics::values.homeCoverHeight / 3, tileWidth - 2 * hPaddingInSelection, 2 * Lyra3CoversMetrics::values.homeCoverHeight / 3,

View File

@@ -1,13 +1,13 @@
#include "LyraTheme.h" #include "LyraTheme.h"
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <HalPowerManager.h>
#include <HalStorage.h> #include <HalStorage.h>
#include <I18n.h> #include <I18n.h>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include "Battery.h"
#include "RecentBooksStore.h" #include "RecentBooksStore.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "components/icons/book.h" #include "components/icons/book.h"
@@ -85,7 +85,7 @@ const uint8_t* iconForName(UIIcon icon, int size) {
void LyraTheme::drawBatteryLeft(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { void LyraTheme::drawBatteryLeft(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
// Left aligned: icon on left, percentage on right (reader mode) // Left aligned: icon on left, percentage on right (reader mode)
const uint16_t percentage = battery.readPercentage(); const uint16_t percentage = powerManager.getBatteryPercentage();
const int y = rect.y + 6; const int y = rect.y + 6;
const int battWidth = LyraMetrics::values.batteryWidth; const int battWidth = LyraMetrics::values.batteryWidth;
@@ -122,7 +122,7 @@ void LyraTheme::drawBatteryLeft(const GfxRenderer& renderer, Rect rect, const bo
void LyraTheme::drawBatteryRight(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { void LyraTheme::drawBatteryRight(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
// Right aligned: percentage on left, icon on right (UI headers) // Right aligned: percentage on left, icon on right (UI headers)
const uint16_t percentage = battery.readPercentage(); const uint16_t percentage = powerManager.getBatteryPercentage();
const int y = rect.y + 6; const int y = rect.y + 6;
const int battWidth = LyraMetrics::values.batteryWidth; const int battWidth = LyraMetrics::values.batteryWidth;
@@ -355,7 +355,7 @@ 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, 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]);
@@ -363,7 +363,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,
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);
} }
@@ -448,10 +449,12 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
} }
} }
if (!hasCover) { // Draw either way
// Render empty cover
renderer.drawRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection, coverWidth, renderer.drawRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection, coverWidth,
LyraMetrics::values.homeCoverHeight, true); LyraMetrics::values.homeCoverHeight, true);
if (!hasCover) {
// Render empty cover
renderer.fillRect(tileX + hPaddingInSelection, renderer.fillRect(tileX + hPaddingInSelection,
tileY + hPaddingInSelection + (LyraMetrics::values.homeCoverHeight / 3), coverWidth, tileY + hPaddingInSelection + (LyraMetrics::values.homeCoverHeight / 3), coverWidth,
2 * LyraMetrics::values.homeCoverHeight / 3, true); 2 * LyraMetrics::values.homeCoverHeight / 3, true);

View File

@@ -13,7 +13,6 @@
#include <cstring> #include <cstring>
#include "Battery.h"
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "CrossPointState.h" #include "CrossPointState.h"
#include "KOReaderCredentialStore.h" #include "KOReaderCredentialStore.h"
@@ -216,9 +215,9 @@ void onGoHome();
void onGoToMyLibraryWithPath(const std::string& path); void onGoToMyLibraryWithPath(const std::string& path);
void onGoToRecentBooks(); void onGoToRecentBooks();
void onGoToReader(const std::string& initialEpubPath) { void onGoToReader(const std::string& initialEpubPath) {
const std::string bookPath = initialEpubPath; // Copy before exitActivity() invalidates the reference
exitActivity(); exitActivity();
enterNewActivity( enterNewActivity(new ReaderActivity(renderer, mappedInputManager, bookPath, onGoHome, onGoToMyLibraryWithPath));
new ReaderActivity(renderer, mappedInputManager, initialEpubPath, onGoHome, onGoToMyLibraryWithPath));
} }
void onGoToFileTransfer() { void onGoToFileTransfer() {