Compare commits
16 Commits
d461d93e76
...
e32d41a37e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e32d41a37e | ||
|
|
7717ae2683 | ||
|
|
f02c9784ec | ||
|
|
693dba4c94 | ||
|
|
9c55c15a72 | ||
|
|
6ba9658f15 | ||
|
|
22b96ec22a | ||
|
|
c9faf2a8c0 | ||
|
|
356fe9a31e | ||
|
|
5da23eed82 | ||
|
|
388fbf206a | ||
|
|
2a38bfd8af | ||
|
|
d7f89e6c0d | ||
|
|
07d715e32d | ||
|
|
63b2643534 | ||
|
|
cabbfcfd7e |
@@ -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
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -268,64 +268,69 @@ 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()) {
|
||||||
// No cache yet - parse CSS files
|
LOG_DBG("EBP", "CSS cache exists, skipping parseCssFiles");
|
||||||
for (const auto& cssPath : cssFiles) {
|
return;
|
||||||
LOG_DBG("EBP", "Parsing CSS file: %s", cssPath.c_str());
|
}
|
||||||
|
|
||||||
// Check heap before parsing - CSS parsing allocates heavily
|
// No cache yet - parse CSS files
|
||||||
const uint32_t freeHeap = ESP.getFreeHeap();
|
for (const auto& cssPath : cssFiles) {
|
||||||
if (freeHeap < MIN_HEAP_FOR_CSS_PARSING) {
|
LOG_DBG("EBP", "Parsing CSS file: %s", cssPath.c_str());
|
||||||
LOG_ERR("EBP", "Insufficient heap for CSS parsing (%u bytes free, need %zu), skipping: %s", freeHeap,
|
|
||||||
MIN_HEAP_FOR_CSS_PARSING, cssPath.c_str());
|
// Check heap before parsing - CSS parsing allocates heavily
|
||||||
|
const uint32_t freeHeap = ESP.getFreeHeap();
|
||||||
|
if (freeHeap < MIN_HEAP_FOR_CSS_PARSING) {
|
||||||
|
LOG_ERR("EBP", "Insufficient heap for CSS parsing (%u bytes free, need %zu), skipping: %s", freeHeap,
|
||||||
|
MIN_HEAP_FOR_CSS_PARSING, cssPath.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check CSS file size before decompressing - skip files that are too large
|
||||||
|
size_t cssFileSize = 0;
|
||||||
|
if (getItemSize(cssPath, &cssFileSize)) {
|
||||||
|
if (cssFileSize > MAX_CSS_FILE_SIZE) {
|
||||||
|
LOG_ERR("EBP", "CSS file too large (%zu bytes > %zu max), skipping: %s", cssFileSize, MAX_CSS_FILE_SIZE,
|
||||||
|
cssPath.c_str());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check CSS file size before decompressing - skip files that are too large
|
// Extract CSS file to temp location
|
||||||
size_t cssFileSize = 0;
|
const auto tmpCssPath = getCachePath() + "/.tmp.css";
|
||||||
if (getItemSize(cssPath, &cssFileSize)) {
|
FsFile tempCssFile;
|
||||||
if (cssFileSize > MAX_CSS_FILE_SIZE) {
|
if (!Storage.openFileForWrite("EBP", tmpCssPath, tempCssFile)) {
|
||||||
LOG_ERR("EBP", "CSS file too large (%zu bytes > %zu max), skipping: %s", cssFileSize, MAX_CSS_FILE_SIZE,
|
LOG_ERR("EBP", "Could not create temp CSS file");
|
||||||
cssPath.c_str());
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) {
|
||||||
}
|
LOG_ERR("EBP", "Could not read CSS file: %s", cssPath.c_str());
|
||||||
|
|
||||||
// Extract CSS file to temp location
|
|
||||||
const auto tmpCssPath = getCachePath() + "/.tmp.css";
|
|
||||||
FsFile tempCssFile;
|
|
||||||
if (!Storage.openFileForWrite("EBP", tmpCssPath, tempCssFile)) {
|
|
||||||
LOG_ERR("EBP", "Could not create temp CSS file");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) {
|
|
||||||
LOG_ERR("EBP", "Could not read CSS file: %s", cssPath.c_str());
|
|
||||||
tempCssFile.close();
|
|
||||||
Storage.remove(tmpCssPath.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
tempCssFile.close();
|
|
||||||
|
|
||||||
// Parse the CSS file
|
|
||||||
if (!Storage.openFileForRead("EBP", tmpCssPath, tempCssFile)) {
|
|
||||||
LOG_ERR("EBP", "Could not open temp CSS file for reading");
|
|
||||||
Storage.remove(tmpCssPath.c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cssParser->loadFromStream(tempCssFile);
|
|
||||||
tempCssFile.close();
|
tempCssFile.close();
|
||||||
Storage.remove(tmpCssPath.c_str());
|
Storage.remove(tmpCssPath.c_str());
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
tempCssFile.close();
|
||||||
|
|
||||||
// Save to cache for next time
|
// Parse the CSS file
|
||||||
if (!cssParser->saveToCache()) {
|
if (!Storage.openFileForRead("EBP", tmpCssPath, tempCssFile)) {
|
||||||
LOG_ERR("EBP", "Failed to save CSS rules to cache");
|
LOG_ERR("EBP", "Could not open temp CSS file for reading");
|
||||||
|
Storage.remove(tmpCssPath.c_str());
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
cssParser->clear();
|
cssParser->loadFromStream(tempCssFile);
|
||||||
|
tempCssFile.close();
|
||||||
LOG_DBG("EBP", "Loaded %zu CSS style rules from %zu files", cssParser->ruleCount(), cssFiles.size());
|
Storage.remove(tmpCssPath.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save to cache for next time
|
||||||
|
if (!cssParser->saveToCache()) {
|
||||||
|
LOG_ERR("EBP", "Failed to save CSS rules to cache");
|
||||||
|
}
|
||||||
|
cssParser->clear();
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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()) {
|
||||||
if (!parseContentOpf(bookMetadataCache->coreMetadata)) {
|
LOG_DBG("EBP", "CSS rules cache missing or stale, attempting to parse CSS files");
|
||||||
LOG_ERR("EBP", "Could not parse content.opf from cached bookMetadata for CSS files");
|
cssParser->deleteCache();
|
||||||
// continue anyway - book will work without CSS and we'll still load any inline style CSS
|
|
||||||
|
if (!parseContentOpf(bookMetadataCache->coreMetadata)) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
parseCssFiles();
|
||||||
|
// Invalidate section caches so they are rebuilt with the new CSS
|
||||||
|
Storage.removeDir((cachePath + "/sections").c_str());
|
||||||
}
|
}
|
||||||
parseCssFiles();
|
|
||||||
}
|
}
|
||||||
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());
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -257,18 +257,93 @@ 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);
|
||||||
|
|
||||||
// Scale to fit viewport while maintaining aspect ratio
|
int displayWidth = 0;
|
||||||
int maxWidth = self->viewportWidth;
|
int displayHeight = 0;
|
||||||
int maxHeight = self->viewportHeight;
|
const float emSize =
|
||||||
float scaleX = (dims.width > maxWidth) ? (float)maxWidth / dims.width : 1.0f;
|
static_cast<float>(self->renderer.getLineHeight(self->fontId)) * self->lineCompression;
|
||||||
float scaleY = (dims.height > maxHeight) ? (float)maxHeight / dims.height : 1.0f;
|
CssStyle imgStyle = self->cssParser ? self->cssParser->resolveStyle("img", classAttr) : CssStyle{};
|
||||||
float scale = (scaleX < scaleY) ? scaleX : scaleY;
|
// Merge inline style (e.g. style="height: 2em") so it overrides stylesheet rules
|
||||||
if (scale > 1.0f) scale = 1.0f;
|
if (!styleAttr.empty()) {
|
||||||
|
imgStyle.applyOver(CssParser::parseInlineStyle(styleAttr));
|
||||||
|
}
|
||||||
|
const bool hasCssHeight = imgStyle.hasImageHeight();
|
||||||
|
const bool hasCssWidth = imgStyle.hasImageWidth();
|
||||||
|
|
||||||
int displayWidth = (int)(dims.width * scale);
|
if (hasCssHeight && hasCssWidth && dims.width > 0 && dims.height > 0) {
|
||||||
int displayHeight = (int)(dims.height * scale);
|
// 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
|
||||||
|
int maxWidth = self->viewportWidth;
|
||||||
|
int maxHeight = self->viewportHeight;
|
||||||
|
float scaleX = (dims.width > maxWidth) ? (float)maxWidth / dims.width : 1.0f;
|
||||||
|
float scaleY = (dims.height > maxHeight) ? (float)maxHeight / dims.height : 1.0f;
|
||||||
|
float scale = (scaleX < scaleY) ? scaleX : scaleY;
|
||||||
|
if (scale > 1.0f) scale = 1.0f;
|
||||||
|
|
||||||
LOG_DBG("EHP", "Display size: %dx%d (scale %.2f)", displayWidth, displayHeight, scale);
|
displayWidth = (int)(dims.width * scale);
|
||||||
|
displayHeight = (int)(dims.height * 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() &&
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
319
lib/I18n/translations/catalan.yaml
Normal file
319
lib/I18n/translations/catalan.yaml
Normal 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"
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
318
lib/I18n/translations/romanian.yaml
Normal file
318
lib/I18n/translations/romanian.yaml
Normal 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"
|
||||||
@@ -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: "Статус"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <BatteryMonitor.h>
|
|
||||||
|
|
||||||
#define BAT_GPIO0 0 // Battery voltage
|
|
||||||
|
|
||||||
static BatteryMonitor battery(BAT_GPIO0);
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
101
src/activities/util/BmpViewerActivity.cpp
Normal file
101
src/activities/util/BmpViewerActivity.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/activities/util/BmpViewerActivity.h
Normal file
21
src/activities/util/BmpViewerActivity.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "../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;
|
||||||
|
};
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw either way
|
||||||
|
renderer.drawRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection, coverWidth,
|
||||||
|
LyraMetrics::values.homeCoverHeight, true);
|
||||||
|
|
||||||
if (!hasCover) {
|
if (!hasCover) {
|
||||||
// Render empty cover
|
// Render empty cover
|
||||||
renderer.drawRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection, coverWidth,
|
|
||||||
LyraMetrics::values.homeCoverHeight, true);
|
|
||||||
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);
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user