Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e12083566 | ||
|
|
cf4f7be8af | ||
|
|
e69241ef54 | ||
|
|
72b99a2a9c | ||
|
|
62448b3c08 |
@@ -1,13 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Scope or Roadmap Question (start here if unsure)
|
|
||||||
url: https://github.com/crosspoint-reader/crosspoint-reader/discussions
|
|
||||||
about: |
|
|
||||||
Not sure if your idea fits CrossPoint's scope? Start a Discussion before filing an issue.
|
|
||||||
See SCOPE.md and ROADMAP.md for what is in, out, and currently paused.
|
|
||||||
- name: Read the Scope document
|
|
||||||
url: https://github.com/crosspoint-reader/crosspoint-reader/blob/master/SCOPE.md
|
|
||||||
about: The authoritative list of what CrossPoint will and will not accept.
|
|
||||||
- name: Read the Roadmap
|
|
||||||
url: https://github.com/crosspoint-reader/crosspoint-reader/blob/master/ROADMAP.md
|
|
||||||
about: Current phase, what is being closed out, and what comes next.
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
name: Feature Request
|
|
||||||
description: Propose a new feature, enhancement, or change to CrossPoint
|
|
||||||
title: "Short, descriptive title of the request"
|
|
||||||
labels: ["enhancement", "needs-scope-review"]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thanks for taking the time to propose a change to CrossPoint!
|
|
||||||
|
|
||||||
**Before you continue, please read [SCOPE.md](../blob/master/SCOPE.md) and [ROADMAP.md](../blob/master/ROADMAP.md).**
|
|
||||||
|
|
||||||
CrossPoint is intentionally narrow. Most rejected proposals are rejected for scope reasons that are already
|
|
||||||
documented. The checklist below exists to save both of us time.
|
|
||||||
|
|
||||||
If you are not sure whether your idea fits, open a [Discussion](../../discussions) first instead of filing
|
|
||||||
this issue.
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: scope-check
|
|
||||||
attributes:
|
|
||||||
label: Scope Self-Check (required)
|
|
||||||
description: Please confirm each of the following. If any are unchecked, your issue will likely be closed.
|
|
||||||
options:
|
|
||||||
- label: I have read SCOPE.md and ROADMAP.md.
|
|
||||||
required: true
|
|
||||||
- label: This is **not** a new theme or theming change (themes are temporarily closed pending the move to SD-loaded themes).
|
|
||||||
required: true
|
|
||||||
- label: This is **not** a new external network connector (sync engine, cloud storage, remote file access, OPDS extensions beyond what exists, or any new "talk to a server" feature).
|
|
||||||
required: true
|
|
||||||
- label: This is **not** an interactive app (game, calculator, notepad), writing/authoring tool, RSS/news/browser feature, media playback feature, or PDF rendering.
|
|
||||||
required: true
|
|
||||||
- label: The stock firmware does **not** already handle this well.
|
|
||||||
required: true
|
|
||||||
- label: No other popular CrossPoint fork already handles this well (or, if one does, I explain below why CrossPoint still needs it).
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: problem
|
|
||||||
attributes:
|
|
||||||
label: Problem this solves
|
|
||||||
description: What user-facing problem or reading-experience gap does this address? Be concrete.
|
|
||||||
placeholder: e.g., "When reading in landscape, paragraph breaks are inconsistent because..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: stock-gap
|
|
||||||
attributes:
|
|
||||||
label: Why the stock firmware (and other forks) do not already solve this
|
|
||||||
description: Explain which existing solutions you checked and why they fall short. This is the core scope filter.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: proposal
|
|
||||||
attributes:
|
|
||||||
label: Proposed change
|
|
||||||
description: A short description of what you would build or change. Focus on user impact; implementation details can come later.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: tradeoffs
|
|
||||||
attributes:
|
|
||||||
label: Memory / flash / complexity cost
|
|
||||||
description: |
|
|
||||||
CrossPoint runs on 380KB of RAM. Roughly how much DRAM, flash, or code complexity does this add?
|
|
||||||
"Don't know" is a valid answer, but please attempt an estimate.
|
|
||||||
placeholder: e.g., "Adds ~2KB flash for the new font tables, no DRAM impact at runtime."
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: additional-context
|
|
||||||
attributes:
|
|
||||||
label: Additional context
|
|
||||||
description: Anything else relevant (links, screenshots, related discussions).
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
@@ -3,28 +3,10 @@
|
|||||||
* **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.)
|
* **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.)
|
||||||
* **What changes are included?**
|
* **What changes are included?**
|
||||||
|
|
||||||
## Scope Check
|
|
||||||
|
|
||||||
CrossPoint is intentionally narrow. See [SCOPE.md](../blob/master/SCOPE.md) and [ROADMAP.md](../blob/master/ROADMAP.md).
|
|
||||||
Please confirm:
|
|
||||||
|
|
||||||
- [ ] I have read SCOPE.md and ROADMAP.md.
|
|
||||||
- [ ] This PR is **not** a new built-in theme (themes are temporarily closed pending the move to SD-loaded themes).
|
|
||||||
- [ ] This PR is **not** a new external network connector (sync engine, cloud storage, remote file access, etc.).
|
|
||||||
- [ ] This PR is **not** an interactive app, writing tool, RSS/news/browser, media playback, or PDF feature.
|
|
||||||
- [ ] The stock firmware does not already handle this well, **and** no other popular CrossPoint fork already does
|
|
||||||
(or, if one does, I explain why CrossPoint still needs it below).
|
|
||||||
- [ ] If this PR touches `freeink-sdk/`, `lib/hal/`, the bootloader, OTA, or recovery code, I have coordinated with
|
|
||||||
the relevant maintainer.
|
|
||||||
|
|
||||||
**If this PR was opened against the previous (broader) scope and was already in flight under Phase 0, link the
|
|
||||||
relevant Discussion or issue so reviewers can see the history.**
|
|
||||||
|
|
||||||
## Additional Context
|
## Additional Context
|
||||||
|
|
||||||
* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks,
|
* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks,
|
||||||
specific areas to focus on).
|
specific areas to focus on).
|
||||||
* Memory / flash impact, if known.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -91,13 +91,9 @@ jobs:
|
|||||||
restore-keys: pio-${{ runner.os }}-
|
restore-keys: pio-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Build CrossPoint
|
- name: Build CrossPoint
|
||||||
# Both envs in ONE pio invocation: a second `pio run` can wipe the whole
|
|
||||||
# .pio/build tree (project-checksum mismatch after the S3 toolchain
|
|
||||||
# installs into the restored cache), deleting the default firmware.bin
|
|
||||||
# before the artifact upload.
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
pio run -e default -e sticky | tee pio.log
|
pio run | tee pio.log
|
||||||
|
|
||||||
- name: Extract firmware stats
|
- name: Extract firmware stats
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
.vscode
|
||||||
open-x4-sdk
|
|
||||||
fs_
|
|
||||||
lib/EpdFont/fontsrc
|
lib/EpdFont/fontsrc
|
||||||
lib/I18n/I18nKeys.h
|
lib/I18n/I18nKeys.h
|
||||||
lib/I18n/I18nStrings.h
|
lib/I18n/I18nStrings.h
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ CrossPoint is open-source e-reader firmware - community-built, fully hackable, f
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
> If you're planning to buy an Xteink device, consider purchasing an **X3/X4 Developer Edition** through https://crosspointreader.com. CrossPoint receives a small share of each sale, helping fund development costs.
|
|
||||||
|
|
||||||
## What can CrossPoint do?
|
## What can CrossPoint do?
|
||||||
|
|
||||||
- **Reader engine**: EPUB 2/3 rendering with embedded-style option, image handling, hyphenation, kerning, chapter navigation, footnotes, bookmarks, dictionary lookups ([StarDict](docs/dictionary.md)), go-to-percent, auto page turn, orientation control, focus reading, KOReader progress sync and more.
|
- **Reader engine**: EPUB 2/3 rendering with embedded-style option, image handling, hyphenation, kerning, chapter navigation, footnotes, bookmarks, go-to-percent, auto page turn, orientation control, focus reading, KOReader progress sync and more.
|
||||||
|
|
||||||
- **Various formats**: native handling for `.epub`, `.xtc/.xtch`, `.txt`, and `.bmp`.
|
- **Various formats**: native handling for `.epub`, `.xtc/.xtch`, `.txt`, and `.bmp`.
|
||||||
|
|
||||||
@@ -42,6 +40,8 @@ CrossPoint is open-source e-reader firmware - community-built, fully hackable, f
|
|||||||
|
|
||||||
### Coming soon:
|
### Coming soon:
|
||||||
|
|
||||||
|
- Dictionary lookup — inline word lookup without leaving the reader.
|
||||||
|
|
||||||
- More themes.
|
- More themes.
|
||||||
|
|
||||||
- Much more! stay tuned.
|
- Much more! stay tuned.
|
||||||
@@ -136,7 +136,6 @@ Conversion runs the firmware repo's `lib/EpdFont/scripts/fontconvert_sdcard.py`
|
|||||||
- [Web server endpoints](./docs/webserver-endpoints.md)
|
- [Web server endpoints](./docs/webserver-endpoints.md)
|
||||||
- [Project scope](./SCOPE.md)
|
- [Project scope](./SCOPE.md)
|
||||||
- [Contributing docs](./docs/contributing/README.md)
|
- [Contributing docs](./docs/contributing/README.md)
|
||||||
- [Touch and UI development](./docs/contributing/touch-and-ui.md) - FreeInkUI components for new screens, the touch bridge for existing ones, and build envs for the non-Xteink touch devices
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -159,24 +158,6 @@ cd crosspoint-reader
|
|||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
### Nix/NixOS
|
|
||||||
|
|
||||||
Nix/NixOS users can enter the development shell with either `nix develop` (flakes) or `nix-shell`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix develop -f nix
|
|
||||||
# or
|
|
||||||
nix-shell nix
|
|
||||||
```
|
|
||||||
|
|
||||||
To flash a connected ESP32-C3 device, enable PlatformIO's udev rules in your NixOS configuration:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
services.udev.packages = with pkgs; [ platformio-core.udev ];
|
|
||||||
```
|
|
||||||
|
|
||||||
After rebuilding the system configuration, reconnect the device or reload udev rules.
|
|
||||||
|
|
||||||
### Build / flash / monitor
|
### Build / flash / monitor
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
-88
@@ -1,88 +0,0 @@
|
|||||||
# CrossPoint Reader Roadmap
|
|
||||||
|
|
||||||
This roadmap describes how CrossPoint is moving through the tighter scope defined in [SCOPE.md](SCOPE.md). It is
|
|
||||||
intentionally phased: Phase 0 closed out the commitments already in flight before locking down to the stricter
|
|
||||||
"fill gaps the stock firmware leaves" delineator.
|
|
||||||
|
|
||||||
Phases are sequential. We do not start the next phase until the prior one is wrapped or explicitly carried over.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 0 - Close Out Legacy Scope Items — **COMPLETE**
|
|
||||||
|
|
||||||
**Goal:** Land the work that was already in motion under the prior, broader scope so contributors are not left
|
|
||||||
hanging, and so we enter the stricter phases with a clean slate.
|
|
||||||
|
|
||||||
**Landed in Phase 0:**
|
|
||||||
|
|
||||||
* **RTL support PRs.** The in-flight right-to-left work was reviewed, iterated, and merged.
|
|
||||||
* **Dictionary PR.** The offline dictionary lookup work was reviewed and merged.
|
|
||||||
* **Bookmarks** feature. First-class navigation markers in EPUBs.
|
|
||||||
* ~~**Transparent sleep screens.**~~ Shelved; not picked back up under the stricter phases.
|
|
||||||
|
|
||||||
Phase 0 is closed. The tighter scope in [SCOPE.md](SCOPE.md) is now fully enforced. "But it was on the old roadmap"
|
|
||||||
is not a valid argument for accepting a PR.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 1 - Consolidation, Footprint, and Multi-Device Support — **IN PROGRESS**
|
|
||||||
|
|
||||||
**Goal:** Reduce memory and flash usage, clean up the codebase, and land the SDK / HAL generalization work so
|
|
||||||
CrossPoint runs cleanly on ESP32-based e-reader hardware beyond Xteink (X3 / X4), including ESP32-S3 class devices.
|
|
||||||
|
|
||||||
**Focus areas:**
|
|
||||||
|
|
||||||
* DRAM and heap fragmentation reduction across the reader core.
|
|
||||||
* Flash footprint reduction (dead code, redundant strings, oversized tables).
|
|
||||||
* Refactors that tighten the HAL / SDK boundary.
|
|
||||||
* Pluggable per-device SDK layers (display, input, storage, battery) and per-device build configuration without
|
|
||||||
forking the reader core.
|
|
||||||
* Documentation for adding a new ESP32 e-reader target.
|
|
||||||
* E-ink driver refinement (ghosting, partial update behavior).
|
|
||||||
|
|
||||||
**Closed during this phase:** new themes built into firmware, new external network connectors (sync engines, cloud
|
|
||||||
storage, remote file access).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 2 - Languages, Fonts, and Themes
|
|
||||||
|
|
||||||
**Goal:** With the codebase smaller and portable, make reading great in every language: multi-language support,
|
|
||||||
better font support with custom fonts, UI translations, and themes loaded from the SD card instead of consuming
|
|
||||||
flash.
|
|
||||||
|
|
||||||
**Focus areas:**
|
|
||||||
|
|
||||||
* Multi-language reading support (underserved languages, complex script support where realistic on ESP32 hardware).
|
|
||||||
* Better font support and custom fonts.
|
|
||||||
* UI languages and localization.
|
|
||||||
* Moving themes off-firmware to SD-loaded assets (see SCOPE.md Section 6).
|
|
||||||
* **Moving hyphenation files off-firmware.** Hyphenation rules vary per language and the files are large (German
|
|
||||||
alone is ~200KB). Today these eat flash budget that should be available for the reader core. The plan is to build
|
|
||||||
a downloader analogous to the existing font downloader and store the dictionaries on SD / SPIFFS, loading on
|
|
||||||
demand. This unlocks better hyphenation for long-word languages (German, Finnish, Norwegian, etc.) without paying
|
|
||||||
the flash cost up front.
|
|
||||||
|
|
||||||
This phase depends on Phase 1 cleanup landing first; otherwise we generalize a moving target.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Out of Roadmap
|
|
||||||
|
|
||||||
The following are explicitly *not* on the roadmap. They may live in other CrossPoint forks; they will not be picked
|
|
||||||
up here:
|
|
||||||
|
|
||||||
* Interactive apps (games, calculators, notepads).
|
|
||||||
* Writing / authoring tools.
|
|
||||||
* Active connectivity features (RSS, news, browsers).
|
|
||||||
* PDF rendering as a first-class format.
|
|
||||||
|
|
||||||
See [SCOPE.md](SCOPE.md) for the full rationale.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How This Roadmap Changes
|
|
||||||
|
|
||||||
* Phase boundaries are decided by maintainers, not by individual PRs.
|
|
||||||
* If a phase needs to be extended or an item carried over, that is documented here with a short note.
|
|
||||||
* Proposals for new phases or reordering should go through a Discussion first.
|
|
||||||
@@ -1,100 +1,62 @@
|
|||||||
# Project Vision & Scope: CrossPoint Reader
|
# Project Vision & Scope: CrossPoint Reader
|
||||||
|
|
||||||
The goal of CrossPoint Reader is to create an efficient, open-source reading experience for ESP32-based e-reader devices. Xteink hardware (X3, X4) is where the project started and remains a primary target, but CrossPoint is explicitly broadening to support the wider ecosystem of small ESP32 e-ink readers. We believe a dedicated e-reader should do one thing exceptionally well: **facilitate focused reading.**
|
The goal of CrossPoint Reader is to create an efficient, open-source reading experience for the Xteink X4. We believe a
|
||||||
|
dedicated e-reader should do one thing exceptionally well: **facilitate focused reading.**
|
||||||
|
|
||||||
## 1. Core Mission
|
## 1. Core Mission
|
||||||
|
|
||||||
To provide a lightweight, high-performance firmware that maximizes the potential of ESP32-based e-reader hardware, prioritizing legibility, performance, and usability over "swiss-army-knife" functionality.
|
To provide a lightweight, high-performance firmware that maximizes the potential of the X4, prioritizing legibility and
|
||||||
|
usability over "swiss-army-knife" functionality.
|
||||||
|
|
||||||
CrossPoint is **not** a kitchen-sink firmware, and it is **not** Xteink-only. We want clean, maintainable code that the community can build on, and that runs across the range of ESP32 e-reader devices (ESP32-C3, ESP32-S3, and adjacent variants). Every accepted change should make that goal easier, not harder. Device-specific code should live behind the HAL / SDK boundary so the reader core stays portable.
|
## 2. Scope
|
||||||
|
|
||||||
## 2. Guiding Principle: Fill Gaps the Stock Firmware Leaves
|
|
||||||
|
|
||||||
CrossPoint exists to do the things the stock firmware does poorly or not at all. New work is evaluated against that delineator:
|
|
||||||
|
|
||||||
* **Does the stock firmware already do this well?** We should hit that bar or surpass it
|
|
||||||
* **Is another popular CrossPoint fork already solving this well?** If yes, we generally defer to that fork if it's not part of the core reading experience. e.g. stats
|
|
||||||
* **Does this directly improve the reading experience or the firmware's long-term maintainability?** If no, it is out of scope.
|
|
||||||
|
|
||||||
## 3. Current Focus
|
|
||||||
|
|
||||||
We are intentionally narrowing scope to consolidate the codebase as we open it up to more ESP32 e-reader devices.
|
|
||||||
|
|
||||||
During this period, the priorities are:
|
|
||||||
|
|
||||||
* **Memory footprint:** Reducing DRAM usage and heap fragmentation. The ESP32-C3 is the tightest target and sets the ceiling, but the gains benefit every ESP32 variant we run on.
|
|
||||||
* **Flash footprint:** Trimming binary size to leave room for additional device targets and features.
|
|
||||||
* **Code cleanup:** Refactoring, removing dead code, tightening abstractions, and improving readability.
|
|
||||||
* **Reading experience:** EPUB parsing and rendering, typography, hyphenation, line spacing, font handling, and legibility improvements.
|
|
||||||
|
|
||||||
### Temporarily Closed Areas
|
|
||||||
|
|
||||||
PRs in the following areas will be closed until this notice is lifted. Adding these now makes the cleanup and multi-device work materially harder:
|
|
||||||
|
|
||||||
* **New themes.** The existing theming surface is frozen.
|
|
||||||
* **New external network connectors.** This includes sync engines, cloud storage clients, OPDS extensions beyond what exists, remote file access, and any new "talk to a server" feature. We now have our own CrossPoint KOSync server which gives us a way to sync to 3rd party systems like Hardcover at an API level instead of bloating the firmware. If you're interested in helping here, the sync server is also open source.
|
|
||||||
|
|
||||||
If you are unsure whether your idea falls into one of these categories, open a Discussion first.
|
|
||||||
|
|
||||||
## 4. Scope
|
|
||||||
|
|
||||||
### In-Scope
|
### In-Scope
|
||||||
|
|
||||||
*Features that directly improve the core reading experience or the firmware's maintainability.*
|
*These are features that directly improve the primary purpose of the device.*
|
||||||
|
|
||||||
* **EPUB Rendering & Optimization:** Improvements to the rendering engine, CSS/image handling, and parsing performance.
|
* **User Experience:** E.g. User-friendly interfaces, and interactions, both inside the reader and navigating the
|
||||||
* **Typography & Legibility:** Custom font support, hyphenation, line and paragraph spacing, margins.
|
firmware. This includes things like button mapping, book loading, and book navigation like bookmarks.
|
||||||
* **E-Ink Driver Refinement:** Reducing full-screen flashes (ghosting management) and improving general rendering.
|
* **Document Rendering:** E.g. Support for rendering documents (primarily EPUB) and improvements to the rendering
|
||||||
* **Reading UX:** Bookmarks, progress tracking, button mapping, page navigation, and other in-reader interactions.
|
engine.
|
||||||
* **Library Management:** Simple, intuitive ways to organize and navigate a local book collection.
|
* **Format Optimization:** E.g. Efficiently parsing EPUB (CSS/Images) and other documents within the device's
|
||||||
* **Memory, Flash, and Code Quality:** Refactors and cleanups that reduce resource use or improve maintainability, even without a user-visible feature.
|
capabilities.
|
||||||
|
* **Typography & Legibility:** E.g. Custom font support, hyphenation engines, and adjustable line spacing.
|
||||||
|
* **E-Ink Driver Refinement:** E.g. Reducing full-screen flashes (ghosting management) and improving general rendering.
|
||||||
|
* **Library Management:** E.g. Simple, intuitive ways to organize and navigate a collection of books.
|
||||||
|
* **Local Transfer:** E.g. Simple, "pull" based book loading via a basic web-server or public and widely-used standards.
|
||||||
|
* **Language Support:** E.g. Support for multiple languages both in the reader and in the interfaces.
|
||||||
|
* **Reference Tools:** E.g. Local dictionary lookup. Providing quick, offline definitions to enhance comprehension
|
||||||
|
without breaking focus.
|
||||||
|
* **Clock Display (device dependent):**
|
||||||
|
|
||||||
|
| Device | Scope |
|
||||||
|
| -- | -- |
|
||||||
|
| X3 | The X3 uses a dedicated DS3231 RTC, which maintains accurate time across sleep cycles and can be treated as a reliable wall clock. |
|
||||||
|
| X4 | The X4 relies on the ESP32-C3's internal RTC, which drifts significantly during deep sleep. NTP sync could correct this, with an appropriate user experience around connecting to the internet on wake or on demand. This causes some tension with the **Active Connectivity** section below, so please open a discussion about this UX if it's a feature you would find useful. |
|
||||||
|
|
||||||
### Out-of-Scope
|
### Out-of-Scope
|
||||||
|
|
||||||
*Rejected because they compromise the device's stability, maintainability, or core mission.*
|
*These items are rejected because they compromise the device's stability or mission.*
|
||||||
|
|
||||||
* **Interactive Apps:** No notepads, calculators, or games. These belong in other forks and are not part of CrossPoint's focus.
|
* **Interactive Apps:** No Notepads, Calculators, or Games. This is a reader, not a PDA.
|
||||||
* **Writing / Authoring Tools:** No typed notes, journals, or editors. Input hardware and RAM are wrong for this, and other forks already explore this space.
|
* **Active Connectivity:** No RSS readers, News aggregators, or Web browsers. Background Wi-Fi tasks drain the battery
|
||||||
* **Active Connectivity:** No RSS readers, news aggregators, or web browsers. Background Wi-Fi drains the battery and complicates the single-core CPU.
|
and complicate the single-core CPU's execution.
|
||||||
* **PDF Rendering:** PDFs are fixed-layout documents, so rendering them requires displaying pages as images rather than reflowable text, resulting in constant panning and zooming that makes for a poor reading experience on e-ink. Out of scope on the current hardware class.
|
* **Media Playback:** No Audio players or Audio-books.
|
||||||
|
* **Complex Annotation:** No typed out notes. These features are better suited for devices with better input
|
||||||
|
capabilities and more powerful chips.
|
||||||
|
|
||||||
## 5. Calls to Action
|
### In-scope — Technically Unsupported
|
||||||
|
|
||||||
These are the areas where contributor help is most valuable right now. If you want to take one of these on, open a Discussion or issue first so we can coordinate.
|
*These features align with CrossPoint's goals but are impractical on the current hardware or produce poor UX.*
|
||||||
|
|
||||||
### Theme System: Move Themes Off-Firmware
|
* **PDF Rendering:** PDFs are fixed-layout documents, so rendering them requires displaying pages as images rather than reflowable text — resulting in constant panning and zooming that makes for a poor reading experience on e-ink.
|
||||||
|
|
||||||
We want to abstract themes out of the firmware entirely so they no longer consume flash, and instead load from the SD card. This directly supports the current focus on flash footprint and code cleanup.
|
## 3. Idea Evaluation
|
||||||
|
|
||||||
* **Status:** [@itsthisjustin](https://github.com/itsthisjustin) plans to take this on eventually but is very open to someone else claiming it sooner.
|
While I appreciate the desire to add new and exciting features to CrossPoint Reader, CrossPoint Reader is designed to be
|
||||||
* **Why it matters:** Every built-in theme costs flash that we would rather spend on rendering, fonts, or future device support. SD-loaded themes also let users customize without rebuilding firmware. It also leads to SD font loading for better language support in the UI.
|
a lightweight, reliable, and performant e-reader. Things which distract or compromise the device's core mission will not
|
||||||
* **How to claim:** Comment on the relevant Discussion (or open one) before starting.
|
be accepted. As a guiding question, consider if your idea improve the "core reading experience" for the average user,
|
||||||
|
and, critically, not distract from that reading experience.
|
||||||
|
|
||||||
### Identifying Other Stock-Firmware Gaps
|
> **Note to Contributors:** If you are unsure if your idea fits the scope, please open a **Discussion** before you start
|
||||||
|
> coding!
|
||||||
We want help cataloguing things the stock firmware (and other popular CrossPoint forks) handle poorly or not at all, so future work has a clear target list. Particularly interested in:
|
|
||||||
|
|
||||||
* **RTL (right-to-left) text support:** Arabic, Hebrew, Persian, and similar scripts.
|
|
||||||
* **Languages with poor stock and fork coverage:** Especially those that need shaping, complex layout, or non-Latin font work that nobody is handling well today.
|
|
||||||
* **Other gaps:** Rendering edge cases, accessibility issues, input quirks, anything stock does badly and existing forks have not fixed.
|
|
||||||
|
|
||||||
If you can read or use the device in one of these languages, your feedback (even without code) is genuinely useful. Open a Discussion with concrete examples (screenshots, sample EPUBs, expected vs actual behavior) and we will prioritize from there.
|
|
||||||
|
|
||||||
## 6. Funding and Contributor Sustainability
|
|
||||||
|
|
||||||
CrossPoint uses [Royalty.dev](https://royalty.dev) (yes, a product built by [@itsthisjustin](https://github.com/itsthisjustin)) to fund contributors. There has been some tension in the community around this, so the intent is being clarified here directly.
|
|
||||||
|
|
||||||
**Why we do this:**
|
|
||||||
|
|
||||||
* To maintain long-term interest from contributors and maintainers, in direct response to substantial community requests for a way to give back.
|
|
||||||
* To motivate contributors to invest in the *core* project rather than spinning up competing forks.
|
|
||||||
* To help pay for new ESP32 devices so we can port CrossPoint to additional hardware.
|
|
||||||
* To give the project a credible long-term path to sustainability.
|
|
||||||
|
|
||||||
**How it works:**
|
|
||||||
|
|
||||||
* Funds are distributed automatically to contributors based on impact to the codebase and tenure on the project.
|
|
||||||
* Over **$600** was raised in the first few hours after opening up funding, which is a signal the demand is real.
|
|
||||||
* The exact scoring methodology is published at <https://app.royalty.dev/transparency>.
|
|
||||||
|
|
||||||
**This is not fixed in stone.** The weighting, eligibility, and distribution rules can be tweaked as we learn what works for this project. If you have concerns or suggestions about how funds are allocated, open a Discussion. The goal is a system that fairly recognizes the people doing the work, not a perfect one on day one.
|
|
||||||
|
|||||||
+28
-45
@@ -28,10 +28,8 @@ Welcome to the **CrossPoint** firmware. This guide outlines the hardware control
|
|||||||
- [3.6.5 OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries)
|
- [3.6.5 OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries)
|
||||||
- [3.6.6 Web Settings (Wi-Fi + OPDS)](#366-web-settings-wi-fi--opds)
|
- [3.6.6 Web Settings (Wi-Fi + OPDS)](#366-web-settings-wi-fi--opds)
|
||||||
- [3.6.7 KOReader Sync Quick Setup](#367-koreader-sync-quick-setup)
|
- [3.6.7 KOReader Sync Quick Setup](#367-koreader-sync-quick-setup)
|
||||||
- [Option A: CrossPoint Sync Server (`sync.crosspointreader.com`, default)](#option-a-crosspoint-sync-server-synccrosspointreadercom-default)
|
- [Option A: Free Public Server (`sync.koreader.rocks`)](#option-a-free-public-server-synckoreaderrocks)
|
||||||
- [Option B: Legacy Public KOReader Server (`sync.koreader.rocks`)](#option-b-legacy-public-koreader-server-synckoreaderrocks)
|
- [Option B: Self-Hosted Server (Docker Compose)](#option-b-self-hosted-server-docker-compose)
|
||||||
- [Option C: Self-Hosted Server (Docker Compose)](#option-c-self-hosted-server-docker-compose)
|
|
||||||
- [Syncing While Reading](#syncing-while-reading)
|
|
||||||
- [3.7 Sleep Screen](#37-sleep-screen)
|
- [3.7 Sleep Screen](#37-sleep-screen)
|
||||||
- [Cover settings](#cover-settings)
|
- [Cover settings](#cover-settings)
|
||||||
- [Custom images](#custom-images)
|
- [Custom images](#custom-images)
|
||||||
@@ -263,8 +261,6 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
|||||||
- "ON" - Vertical space will be added between paragraphs in Reading Mode
|
- "ON" - Vertical space will be added between paragraphs in Reading Mode
|
||||||
- "OFF" - Paragraphs will not have vertical space added, but will have first-line indentation
|
- "OFF" - Paragraphs will not have vertical space added, but will have first-line indentation
|
||||||
|
|
||||||
- **Dictionary**: Select the StarDict dictionary used for word lookups while reading, or "None" to disable lookups. *(Only shown when at least one dictionary folder exists under `/dictionaries/` on the SD card — see [docs/dictionary.md](docs/dictionary.md) for setup and usage.)*
|
|
||||||
|
|
||||||
- **Text Anti-Aliasing**: Whether to show smooth grey edges (anti-aliasing) on text in reading mode. Note this slows down page turns slightly.
|
- **Text Anti-Aliasing**: Whether to show smooth grey edges (anti-aliasing) on text in reading mode. Note this slows down page turns slightly.
|
||||||
|
|
||||||
- **Images**: Whether to display embedded images (JPG/PNG) found in EPUB files; options are "ON" (default) or "OFF".
|
- **Images**: Whether to display embedded images (JPG/PNG) found in EPUB files; options are "ON" (default) or "OFF".
|
||||||
@@ -284,7 +280,6 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
|||||||
- **Long-press Menu**: Selects the function bound to holding the menu button (Confirm) while reading an EPUB. **Cycles through the available functions** each time the setting is selected — additional functions may be added in future releases, so this is not a binary on/off toggle. A short press of Confirm always opens the reader menu as normal:
|
- **Long-press Menu**: Selects the function bound to holding the menu button (Confirm) while reading an EPUB. **Cycles through the available functions** each time the setting is selected — additional functions may be added in future releases, so this is not a binary on/off toggle. A short press of Confirm always opens the reader menu as normal:
|
||||||
- "Bookmark" (default) - Hold Confirm (~0.4 second) to drop a bookmark at the current page.
|
- "Bookmark" (default) - Hold Confirm (~0.4 second) to drop a bookmark at the current page.
|
||||||
- "KOSync" - Hold Confirm (~1 second) to launch KOReader sync directly.
|
- "KOSync" - Hold Confirm (~1 second) to launch KOReader sync directly.
|
||||||
- "Dictionary" - Hold Confirm (~0.4 second) to start dictionary word selection on the current page (see [docs/dictionary.md](docs/dictionary.md)).
|
|
||||||
- "Disabled" - Long-press is ignored; only short-press opens the reader menu.
|
- "Disabled" - Long-press is ignored; only short-press opens the reader menu.
|
||||||
|
|
||||||
- **Short Power Button Click**: Controls the effect of a short click of the power button:
|
- **Short Power Button Click**: Controls the effect of a short click of the power button:
|
||||||
@@ -302,7 +297,7 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
|||||||
|
|
||||||
- **Wi-Fi Networks**: Connect to Wi-Fi networks for file transfers and firmware updates.
|
- **Wi-Fi Networks**: Connect to Wi-Fi networks for file transfers and firmware updates.
|
||||||
|
|
||||||
- **KOReader Sync**: Options for setting up KOReader for syncing book progress. **Smart sync** is the default for new configurations and auto-resolves simple push/pull decisions. Existing credential files retain **Ask every time** when migrated; you can switch Sync Behavior at any time if you prefer manual confirmation.
|
- **KOReader Sync**: Options for setting up KOReader for syncing book progress.
|
||||||
|
|
||||||
- **OPDS Servers**: Manage one or more OPDS [(Open Publication Distribution System)](https://en.wikipedia.org/wiki/Open_Publication_Distribution_System) libraries for browsing and downloading books. See [OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries) below.
|
- **OPDS Servers**: Manage one or more OPDS [(Open Publication Distribution System)](https://en.wikipedia.org/wiki/Open_Publication_Distribution_System) libraries for browsing and downloading books. See [OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries) below.
|
||||||
|
|
||||||
@@ -365,37 +360,9 @@ Behavior notes:
|
|||||||
CrossPoint can sync reading progress with KOReader-compatible sync servers.
|
CrossPoint can sync reading progress with KOReader-compatible sync servers.
|
||||||
It also interoperates with KOReader apps/devices when they use the same server and credentials.
|
It also interoperates with KOReader apps/devices when they use the same server and credentials.
|
||||||
|
|
||||||
##### Option A: CrossPoint Sync Server (`sync.crosspointreader.com`, default)
|
##### Option A: Free Public Server (`sync.koreader.rocks`)
|
||||||
|
|
||||||
When **Sync Server URL** is left empty, CrossPoint uses the free CrossPoint sync server at `https://sync.crosspointreader.com`. It speaks the standard KOReader sync protocol (so KOReader apps can use it too) and additionally stores an exact spine/page position for lossless CrossPoint-to-CrossPoint sync.
|
1. Register a user once (only if needed):
|
||||||
|
|
||||||
1. On each CrossPoint device:
|
|
||||||
|
|
||||||
- Go to **Settings -> System -> KOReader Sync**.
|
|
||||||
|
|
||||||
- Set **Username** and **Password** (enter the plain password; CrossPoint computes MD5 internally, and use the same values on all devices).
|
|
||||||
|
|
||||||
- Leave **Sync Server URL** empty (or set it to `https://sync.crosspointreader.com`).
|
|
||||||
|
|
||||||
- On the first device, run **Sign Up** once to create the account directly from the device. On every other device, just run **Authenticate**.
|
|
||||||
|
|
||||||
Accounts are per server. Existing `sync.koreader.rocks` credentials do not exist on the CrossPoint server; either sign up again with the same username/password or use Option B to keep using the legacy server.
|
|
||||||
|
|
||||||
##### Option B: Legacy Public KOReader Server (`sync.koreader.rocks`)
|
|
||||||
|
|
||||||
Use this if you already sync KOReader devices against the official public server.
|
|
||||||
|
|
||||||
1. On each CrossPoint device:
|
|
||||||
|
|
||||||
- Go to **Settings -> System -> KOReader Sync**.
|
|
||||||
|
|
||||||
- Set **Sync Server URL** to `https://sync.koreader.rocks` (required; an empty URL now points at the CrossPoint server instead).
|
|
||||||
|
|
||||||
- Set **Username** and **Password** to your existing KOReader Sync credentials.
|
|
||||||
|
|
||||||
- Run **Authenticate**.
|
|
||||||
|
|
||||||
2. If you do not have an account yet, run **Sign Up** on the device, or register once with curl:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
USERNAME="user"
|
USERNAME="user"
|
||||||
@@ -408,9 +375,27 @@ curl -i "https://sync.koreader.rocks/users/create" \
|
|||||||
--data "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD_MD5\"}"
|
--data "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD_MD5\"}"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Already have KOReader Sync credentials? Skip registration; basic sync only requires using the same existing username/password on all devices.
|
||||||
|
|
||||||
When this returns `HTTP 402` with `{"code":2002,"message":"Username is already registered."}`, pick a different username or use that existing account.
|
When this returns `HTTP 402` with `{"code":2002,"message":"Username is already registered."}`, pick a different username or use that existing account.
|
||||||
|
|
||||||
##### Option C: Self-Hosted Server (Docker Compose)
|
2. On each CrossPoint device:
|
||||||
|
|
||||||
|
- Go to **Settings -> System -> KOReader Sync**.
|
||||||
|
|
||||||
|
- Set **Username** and **Password** (enter the plain password; CrossPoint computes MD5 internally, and use the same values on all devices).
|
||||||
|
|
||||||
|
- Set **Sync Server URL** to `https://sync.koreader.rocks`, or leave it empty (both use the same default KOReader sync server).
|
||||||
|
|
||||||
|
- Run **Authenticate**.
|
||||||
|
|
||||||
|
3. While reading, press **Confirm** to open the reader menu, then select **Sync Progress**.
|
||||||
|
|
||||||
|
- Choose **Apply Remote** to jump to remote progress.
|
||||||
|
|
||||||
|
- Choose **Upload Local** to push current progress.
|
||||||
|
|
||||||
|
##### Option B: Self-Hosted Server (Docker Compose)
|
||||||
|
|
||||||
1. Start a sync server:
|
1. Start a sync server:
|
||||||
|
|
||||||
@@ -483,12 +468,11 @@ If this returns `HTTP 402` with `{"code":2002,"message":"Username is already reg
|
|||||||
|
|
||||||
If you use the HTTPS listener, use `https://<server-ip>:7200` (`curl -k` only for self-signed certificate testing).
|
If you use the HTTPS listener, use `https://<server-ip>:7200` (`curl -k` only for self-signed certificate testing).
|
||||||
|
|
||||||
##### Syncing While Reading
|
5. While reading, press **Confirm** to open the reader menu, then select **Sync Progress**.
|
||||||
|
|
||||||
Once any of the options above is set up, press **Confirm** while reading to open the reader menu, then select **Sync Progress**. Alternatively, set **Settings -> Controls -> Long-press Menu** to **KOSync** and hold Confirm to launch sync directly.
|
- Choose **Apply Remote** to jump to remote progress.
|
||||||
|
|
||||||
- With **Sync Behavior** set to **Ask every time**, choose **Apply Remote** to jump to remote progress or **Upload Local** to push current progress.
|
- Choose **Upload Local** to push current progress.
|
||||||
- With **Sync Behavior** set to **Smart sync**, CrossPoint auto-resolves simple cases: upload when no remote progress exists, confirm and leave both unchanged when local and remote progress are already synchronized, upload when local progress is further ahead, or apply remote when remote progress is further ahead.
|
|
||||||
|
|
||||||
### 3.7 Sleep Screen
|
### 3.7 Sleep Screen
|
||||||
|
|
||||||
@@ -586,7 +570,7 @@ If the device goes to sleep or you close the book while viewing a footnote, the
|
|||||||
* **Return to Home:** Press the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
|
* **Return to Home:** Press the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
|
||||||
* **Return to Browse Files:** Press and hold the **Back** button to close the book and return to the **[Browse Files](#33-browse-files-screen)** screen.
|
* **Return to Browse Files:** Press and hold the **Back** button to close the book and return to the **[Browse Files](#33-browse-files-screen)** screen.
|
||||||
* **Reader Menu:** Press **Confirm** to open the **[Reader Menu](#5-reader-menu)**, which includes chapter navigation, reading options, and more.
|
* **Reader Menu:** Press **Confirm** to open the **[Reader Menu](#5-reader-menu)**, which includes chapter navigation, reading options, and more.
|
||||||
* **Long-press Confirm (configurable):** Holding **Confirm** runs the function chosen by the **Long-press Menu** setting in **[Controls Settings](#363-controls)** — "Bookmark" (default) drops a bookmark, "KOSync" launches KOReader Sync, "Dictionary" starts a word lookup, "Disabled" does nothing. A short press always opens the Reader Menu.
|
* **Long-press Confirm (configurable):** Holding **Confirm** runs the function chosen by the **Long-press Menu** setting in **[Controls Settings](#363-controls)** — "Bookmark" (default) drops a bookmark, "KOSync" launches KOReader Sync, "Disabled" does nothing. A short press always opens the Reader Menu.
|
||||||
|
|
||||||
### Supported Languages
|
### Supported Languages
|
||||||
|
|
||||||
@@ -608,7 +592,6 @@ Available options include:
|
|||||||
|
|
||||||
- **Select Chapter** – Open the table of contents to jump to a specific chapter (see [Chapter Selection](#51-chapter-selection) below).
|
- **Select Chapter** – Open the table of contents to jump to a specific chapter (see [Chapter Selection](#51-chapter-selection) below).
|
||||||
- **Footnotes** – Navigate to the footnotes for the current section *(only shown in books that contain footnotes)*.
|
- **Footnotes** – Navigate to the footnotes for the current section *(only shown in books that contain footnotes)*.
|
||||||
- **Look Up** – Select a word on the current page and show its dictionary definition (see [docs/dictionary.md](docs/dictionary.md)). Requires a dictionary to be selected in **Settings → Reader → Dictionary**.
|
|
||||||
- **Reading Orientation** – Cycle through screen orientations without leaving the reader.
|
- **Reading Orientation** – Cycle through screen orientations without leaving the reader.
|
||||||
- **Auto Turn (Pages Per Minute)** – Cycle through automatic page turn speed options for hands-free reading.
|
- **Auto Turn (Pages Per Minute)** – Cycle through automatic page turn speed options for hands-free reading.
|
||||||
- **Go to %** – Jump to a specific position in the book by percentage.
|
- **Go to %** – Jump to a specific position in the book by percentage.
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ git ls-files --exclude-standard ${GIT_LS_FILES_FLAGS} \
|
|||||||
| grep -v -E '^lib/EpdFont/builtinFonts/' \
|
| grep -v -E '^lib/EpdFont/builtinFonts/' \
|
||||||
| grep -v -E '^lib/Epub/Epub/hyphenation/generated/' \
|
| grep -v -E '^lib/Epub/Epub/hyphenation/generated/' \
|
||||||
| grep -v -E '^lib/uzlib/' \
|
| grep -v -E '^lib/uzlib/' \
|
||||||
| grep -v -E '^lib/miniz/third_party/' \
|
|
||||||
| xargs -r "${CLANG_FORMAT_BIN}" -style=file -i
|
| xargs -r "${CLANG_FORMAT_BIN}" -style=file -i
|
||||||
# Restore strict pipeline failure handling for the rest of the script.
|
# Restore strict pipeline failure handling for the rest of the script.
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|||||||
@@ -7,6 +7,5 @@ It is written for software developers who may be new to embedded development.
|
|||||||
- [Architecture Overview](./architecture.md)
|
- [Architecture Overview](./architecture.md)
|
||||||
- [Development Workflow](./development-workflow.md)
|
- [Development Workflow](./development-workflow.md)
|
||||||
- [Testing and Debugging](./testing-debugging.md)
|
- [Testing and Debugging](./testing-debugging.md)
|
||||||
- [Touch and UI Development](./touch-and-ui.md)
|
|
||||||
|
|
||||||
If you are new, start with [Getting Started](./getting-started.md).
|
If you are new, start with [Getting Started](./getting-started.md).
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
# Touch and UI Development
|
|
||||||
|
|
||||||
CrossPoint now runs on touch devices (Seeed Sticky, M5Paper, LilyGo T5) alongside the button-only Xteink X3/X4. Every screen must work with both input styles. There are two supported ways to get there:
|
|
||||||
|
|
||||||
1. **New screens: build them with FreeInkUI components.** Touch hit-testing, tap highlighting, long-press, and button focus navigation come with the component; you never hand-roll coordinate math.
|
|
||||||
2. **Existing screens and in-flight features: use the MappedInputManager touch bridge.** A small set of helpers adds tap/hold/swipe support to hand-rolled rendering without restructuring the activity.
|
|
||||||
|
|
||||||
If you are starting a new activity, use FreeInkUI. If you have a feature branch with a hand-rolled screen already working on buttons, use the bridge; do not rewrite mid-flight.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Path 1: New screens with FreeInkUI
|
|
||||||
|
|
||||||
FreeInkUI (`freeink-sdk/libs/ui/FreeInkUI`, namespace `freeink::ui`) is an immediate-mode component library. The core idea:
|
|
||||||
|
|
||||||
- While a component renders, it registers its tappable areas ("hit rects") into the frame's interaction buffer via `Frame::hit(rect, action, value, inputMask, state)`.
|
|
||||||
- Each loop you build an `InputSnapshot` from the input manager and route it against that buffer. If a tap (or mapped button press) lands in a registered rect, the routed `ActionEvent` tells you which action fired and with what value.
|
|
||||||
- Components query `frame.stateFor(action, value)` while painting, so touch-down highlight and focus states render correctly without any per-activity code.
|
|
||||||
|
|
||||||
You get touch, hold highlighting, long-press, minimum touch-target sizing, and orientation-aware coordinates for free. The same action IDs fire from physical buttons, so one code path serves both input styles.
|
|
||||||
|
|
||||||
### Component inventory
|
|
||||||
|
|
||||||
All under `freeink-sdk/libs/ui/FreeInkUI/include/components/`:
|
|
||||||
|
|
||||||
| Category | Components |
|
|
||||||
|---|---|
|
|
||||||
| Controls | `button`, `checkbox`, `slider`, `progress-bar`, `header` |
|
|
||||||
| Lists | `list` (virtualized), `table`, `dropdown`, `radio-group`, `setting-row`, `toggle-row`, `stepper-row` |
|
|
||||||
| Keyboard | `keyboard` (QWERTY/AZERTY/QWERTZ/ES layouts), `key-grid` |
|
|
||||||
| Overlays | `popup`, `option-dialog`, `context-menu`, `message-panel`, `toast` |
|
|
||||||
| Bars | `status-bar`, `tab-bar`, `reader-chrome`, `battery-indicator`, `gesture-bar`, `tap-zones` |
|
|
||||||
| Media | `book-card`, `cover-grid`, `cover-carousel`, `metric-card` |
|
|
||||||
| Text | `text-field`, `text-area` |
|
|
||||||
|
|
||||||
### Integration skeleton
|
|
||||||
|
|
||||||
The in-tree reference is [`src/activities/util/KeyboardEntryActivity.cpp`](../../src/activities/util/KeyboardEntryActivity.cpp); it drives the FreeInkUI keyboard component inside a normal Activity. The shape:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <FreeInkUIGfxRenderer.h>
|
|
||||||
namespace fui = freeink::ui;
|
|
||||||
|
|
||||||
// Render step: draw the component and (re)register its hit rects.
|
|
||||||
fui::GfxRendererTarget target(renderer); // adapts GfxRenderer to FreeInkUI's DrawTarget
|
|
||||||
target.setFont(fui::GfxRendererTarget::FONT_BODY, UI_12_FONT_ID);
|
|
||||||
const fui::DeviceContext device = target.deviceContext(); // orientation, safe area, touch sizing
|
|
||||||
|
|
||||||
fui::Frame<48> frame(target, device, fui::InputSnapshot{}, interactions); // 48 = max hit rects
|
|
||||||
fui::KeyboardProps props;
|
|
||||||
props.layout = ¤tLayout();
|
|
||||||
props.keyAction = ACTION_KEY; // one action id; the key is the event value
|
|
||||||
props.inputMask = fui::InputTouch | fui::InputLongPress;
|
|
||||||
fui::keyboard(frame, kbRect, props); // draws AND registers hit rects
|
|
||||||
```
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// Input step (each loop): feed touch state, act on routed events.
|
|
||||||
int tx, ty, tapX, tapY, hx, hy;
|
|
||||||
const bool pressedDown = mappedInput.wasScreenTouchDown(tx, ty);
|
|
||||||
const bool tapped = mappedInput.wasScreenTapped(tapX, tapY);
|
|
||||||
const bool inContact = mappedInput.isScreenTouchHeld(hx, hy);
|
|
||||||
|
|
||||||
const auto result = touchRouter.update(interactions, pressedDown, tx, ty,
|
|
||||||
tapped, tapX, tapY, inContact, millis());
|
|
||||||
if (result.event) {
|
|
||||||
activateValue(result.event.value, result.event.longPress);
|
|
||||||
requestUpdate();
|
|
||||||
} else if (result.activeChanged) {
|
|
||||||
requestUpdate(); // repaint touch-down highlight
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
|
|
||||||
- `Frame<N>` is templated on the max number of hit rects; the buffer is stack/static, no heap.
|
|
||||||
- `fui::TouchHoldRouter` (see `components/keyboard/keyboard.h`) synthesizes long-press while the finger is still down and swallows the eventual release. Use it whenever a component distinguishes tap from long-press.
|
|
||||||
- For simpler screens without long-press, `FreeInkApp` / `Screen<N>` (`FreeInkApp.h`) is the ergonomic layer: it owns the interaction buffer, offers a top-to-bottom builder (`takeTop`, footer helpers, etc.), and its `render(input)` call routes the snapshot and dispatches registered action callbacks in one step. `snapshotFrom(input, device)` in `FreeInkUIInputManager.h` builds the `InputSnapshot` with orientation-aware touch mapping.
|
|
||||||
- Everything stays allocation-free and works on the button-only devices unchanged: physical buttons route through the same interaction table via each hit rect's `inputMask`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Path 2: Adding touch to existing hand-rolled screens
|
|
||||||
|
|
||||||
For activities that draw their own rows, menus, and buttons, `MappedInputManager` ([src/MappedInputManager.h](../../src/MappedInputManager.h)) exposes bridge helpers. They all return **logical screen coordinates** (orientation already applied via `GfxRenderer::tapToLogical`); never touch raw normalized panel coordinates or the SDK `InputManager` directly.
|
|
||||||
|
|
||||||
| Helper | Use for |
|
|
||||||
|---|---|
|
|
||||||
| `wasScreenTapped(x, y)` | A completed tap (press + release), with logical coords |
|
|
||||||
| `wasScreenTouchDown(x, y)` | Touch-down (held > 90 ms, not yet released): draw selection highlight |
|
|
||||||
| `isScreenTouchHeld(x, y)` | Live contact position while the finger is down (drag tracking) |
|
|
||||||
| `wasTapInRect(x, y, w, h)` | One-off hit test on a rectangle (a single button, a banner) |
|
|
||||||
| `wasListItemTapped(index, count, selected, listTop, listHeight, hasSubtitle)` | Taps on a standard UITheme list; does the row/paging math for you |
|
|
||||||
| `wasListItemTouchedDown(...)` | Same geometry, touch-down phase (highlight before activate) |
|
|
||||||
| `rowTouch(row, top, rowStep, rowCount, xStart, xEnd, rowHeight)` | Any custom band of equal-height rows; returns `RowTouch::None/Down/Tap` |
|
|
||||||
| `colTouch(col, left, colStep, colCount, yStart, yEnd, colWidth)` | Horizontal button bands (dialogs, prompts) |
|
|
||||||
| `wasSwipe()` | Raw swipe direction if you need one beyond the global gestures |
|
|
||||||
| `hasTouch()` | True when the device has a touch panel (rarely needed; helpers simply never fire without one) |
|
|
||||||
|
|
||||||
### Pattern A: standard themed list
|
|
||||||
|
|
||||||
One call per loop; UITheme owns the row geometry ([EpubReaderBookmarksActivity.cpp:123](../../src/activities/reader/EpubReaderBookmarksActivity.cpp)):
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
int tapped = -1;
|
|
||||||
if (mappedInput.wasListItemTapped(tapped, bookmarks.size(), selectorIndex, listY, listHeight, true)) {
|
|
||||||
selectorIndex = tapped;
|
|
||||||
openBookmark();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern B: custom rows with hold highlight
|
|
||||||
|
|
||||||
For non-theme row layouts, use `rowTouch` and distinguish `Down` (highlight) from `Tap` (activate), as in [EpubReaderFootnotesActivity.cpp](../../src/activities/reader/EpubReaderFootnotesActivity.cpp):
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
int row = -1;
|
|
||||||
const auto touch = mappedInput.rowTouch(row, listTop, lineHeight, visibleCount,
|
|
||||||
contentX, contentX + contentWidth);
|
|
||||||
if (touch != MappedInputManager::RowTouch::None) {
|
|
||||||
const int touched = scrollOffset + row;
|
|
||||||
if (touch == MappedInputManager::RowTouch::Down) {
|
|
||||||
if (selectedIndex != touched) { selectedIndex = touched; requestUpdate(); }
|
|
||||||
} else { // RowTouch::Tap
|
|
||||||
selectedIndex = touched;
|
|
||||||
activateSelection();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `Down` state exists because e-ink repaints are slow: highlight on touch-down gives immediate feedback, activation happens on release.
|
|
||||||
|
|
||||||
### Global gestures: do not reimplement these
|
|
||||||
|
|
||||||
Three gestures are handled once, for every screen. Activities must not add their own edge-swipe handling:
|
|
||||||
|
|
||||||
| Gesture | Trigger | Where it is handled |
|
|
||||||
|---|---|---|
|
|
||||||
| Back | Right-swipe starting in the left 25% of the screen | Folded into `Button::Back`, so the existing `wasPressed(Button::Back)` in your activity already fires |
|
|
||||||
| Home | Up-swipe starting in the bottom 14% | `ActivityManager::loop()`; pops to Home (activities can override via `handleHomeGesture()`) |
|
|
||||||
| Menu | Down-swipe starting in the top 14% | Activities that have a menu check `wasMenuGesture()` themselves (the reader does this) |
|
|
||||||
|
|
||||||
Because the back gesture arrives as `Button::Back`, most button-era activities gain back-swipe support with zero changes. That is the bar to aim for: bridge helpers should make touch an additive layer over the button flow, not a second input state machine.
|
|
||||||
|
|
||||||
### Bridge rules
|
|
||||||
|
|
||||||
- Handle touch in `loop()` next to the existing button handling, one helper call per interaction zone, and `return` after consuming an event (mirrors the button pattern).
|
|
||||||
- Never call the SDK `InputManager` or read GPIO directly; the HAL rule from the main guide applies to touch too.
|
|
||||||
- Coordinates from the helpers are logical and orientation-correct on all four rotations; test at least Portrait and one Landscape mode before PR.
|
|
||||||
- Nothing to clean up in `onExit()`; the helpers are stateless from the activity's point of view.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Building and testing on non-Xteink devices
|
|
||||||
|
|
||||||
Each MCU family is its own binary: X3/X4 are ESP32-C3, Sticky and LilyGo T5 are ESP32-S3, M5Paper v1.1 is a classic ESP32. The Sticky env ships in `platformio.ini` (`pio run -e sticky`). Envs for other devices go in **`platformio.local.ini`**, a gitignored file that PlatformIO merges over `platformio.ini` (see `extra_configs`). Create it next to `platformio.ini`; personal envs, ports, and debug flags live there and never get committed.
|
|
||||||
|
|
||||||
Both envs below extend the repo's `[base]`, so they build against the `freeink-sdk` submodule with all the normal deps and scripts.
|
|
||||||
|
|
||||||
### M5Paper v1.1 (classic ESP32, IT8951 panel)
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[env:m5paper_v11]
|
|
||||||
extends = base
|
|
||||||
board = esp32dev
|
|
||||||
board_build.mcu = esp32
|
|
||||||
board_build.flash_mode = qio
|
|
||||||
; CP2104 UART bridge: 921600 drops out on macOS after the stub baud switch
|
|
||||||
upload_speed = 460800
|
|
||||||
build_unflags =
|
|
||||||
${base.build_unflags}
|
|
||||||
; classic ESP32 has UART serial, not USB CDC; Logging.h keys off these
|
|
||||||
-DARDUINO_USB_MODE=1
|
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
|
||||||
build_flags =
|
|
||||||
${base.build_flags}
|
|
||||||
-DFREEINK_DEVICE_M5PAPER=1
|
|
||||||
; the 63KB 540x960 framebuffer lives in PSRAM (FREEINK_FB_PSRAM auto-on)
|
|
||||||
-DBOARD_HAS_PSRAM
|
|
||||||
-DCROSSPOINT_VERSION=\"${crosspoint.version}-m5paper\"
|
|
||||||
-DENABLE_SERIAL_LOG
|
|
||||||
-DLOG_LEVEL=2
|
|
||||||
; touch-first device: hide front-button hint labels
|
|
||||||
-DCROSSPOINT_SHOW_BUTTON_HINTS=0
|
|
||||||
; archive-scan-order workaround: without these a full relink drops Wire's i2c symbols
|
|
||||||
-Wl,-u,i2cInit
|
|
||||||
-Wl,-u,i2cSlaveInit
|
|
||||||
```
|
|
||||||
|
|
||||||
### LilyGo T5 S3 (ESP32-S3, controller-less panel via LovyanGFX)
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[env:lilygo_t5s3]
|
|
||||||
extends = base
|
|
||||||
board = esp32-s3-devkitc1-n16r8
|
|
||||||
board_build.mcu = esp32s3
|
|
||||||
build_flags =
|
|
||||||
${base.build_flags}
|
|
||||||
-DFREEINK_DEVICE_LILYGO=1
|
|
||||||
; board injects the parallel-bus pins + PMIC power hooks (BoardT5S3)
|
|
||||||
-DFREEINK_LGFX_EPD_CONFIG=lilygoT5S3LgfxConfig
|
|
||||||
-DCROSSPOINT_VERSION=\"${crosspoint.version}-lilygo\"
|
|
||||||
-DENABLE_SERIAL_LOG
|
|
||||||
-DLOG_LEVEL=2
|
|
||||||
-DCROSSPOINT_SHOW_BUTTON_HINTS=0
|
|
||||||
lib_deps =
|
|
||||||
${base.lib_deps}
|
|
||||||
; LgfxEpdConfig for the T5 S3 (pins, PCA9535/TPS65185 power sequence)
|
|
||||||
BoardT5S3=symlink://freeink-sdk/libs/hardware/BoardT5S3
|
|
||||||
; LovyanGFX Panel_EPD drives the controller-less ED047TC1 panel
|
|
||||||
m5stack/M5GFX @ 0.2.20
|
|
||||||
```
|
|
||||||
|
|
||||||
Then `pio run -e m5paper_v11 -t upload` (or `-e lilygo_t5s3`). Gotchas worth knowing:
|
|
||||||
|
|
||||||
- **Flash mode matters.** The M5Paper is `qio`; the X4-family standalone envs need `dio`. A wrong flash-mode header boots into a `partition 0 invalid magic number 0xffff` loop even though esptool verified the write.
|
|
||||||
- **One `FREEINK_DEVICE_*` flag per env** selects the board profile (pins, panel, touch controller) from the SDK's `BoardConfig`. See `freeink-sdk/platformio.sample.ini` for reference envs of every supported device.
|
|
||||||
- **Serial logs:** `[base]` does not enable logging; without `-DENABLE_SERIAL_LOG` a non-default env prints nothing.
|
|
||||||
- No touch hardware on your desk? The X4 build still exercises the same code paths through buttons; touch-specific behavior (tap zones, gestures) needs a real device.
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# Dictionary
|
|
||||||
|
|
||||||
Look up words while reading an EPUB using an offline StarDict dictionary stored on the SD card.
|
|
||||||
|
|
||||||
## Supported Format
|
|
||||||
|
|
||||||
The reader supports **StarDict** dictionaries. When searching for dictionaries online, look for "StarDict format" or files with `.dict`, `.idx`, and `.ifo` extensions.
|
|
||||||
|
|
||||||
A dictionary folder must contain:
|
|
||||||
|
|
||||||
- `.idx` — word index (required, **must be uncompressed** — a `.idx.gz` will not work; decompress it on your computer with `gzip -d` first)
|
|
||||||
- `.dict` or `.dict.dz` — definition data (`.dict.dz` is supported as-is; entries are decompressed on the fly during lookup)
|
|
||||||
- `.ifo` — metadata (optional)
|
|
||||||
|
|
||||||
Not supported: `.syn` synonym files (ignored), dictionaries with 64-bit index offsets (`idxoffsetbits=64` in the `.ifo` — rare, and rejected with an error), and HTML-formatted definitions render as raw markup rather than styled text.
|
|
||||||
|
|
||||||
## Setting Up a Dictionary
|
|
||||||
|
|
||||||
1. Copy your dictionary folder(s) to `/dictionaries/` on the SD card — one dictionary per folder, e.g. `/dictionaries/webster/webster.idx` + `webster.dict.dz`. A hidden `/.dictionaries/` folder (dot-prefixed) works the same way, for keeping it out of the file browser.
|
|
||||||
2. Open **Settings → Reader → Dictionary** on the device.
|
|
||||||
3. Select a dictionary from the list, or **None** to disable lookups.
|
|
||||||
|
|
||||||
The Dictionary setting only appears when at least one usable dictionary folder exists. Folders containing more than one dictionary (multiple `.idx` stems) are skipped as ambiguous.
|
|
||||||
|
|
||||||
## Looking Up a Word
|
|
||||||
|
|
||||||
Two ways to start a lookup while reading:
|
|
||||||
|
|
||||||
- Open the reader menu (**Confirm**) and choose **Look Up**.
|
|
||||||
- Or set **Settings → Controls → Long-press Menu** to "Dictionary", then hold **Confirm** (~0.4s) on the reading page.
|
|
||||||
|
|
||||||
One word on the page becomes highlighted:
|
|
||||||
|
|
||||||
1. Use **Left/Right** to move between words in reading order, and the side **Up/Down** buttons to jump between lines.
|
|
||||||
2. Press **Confirm** to look up the highlighted word.
|
|
||||||
3. Press **Back** to return to the reader.
|
|
||||||
|
|
||||||
On the very first lookup with a dictionary (and again if the dictionary file changes), the reader shows *"Indexing dictionary…"* while it builds a small `.qidx` sidecar file next to the `.idx`. This takes a few seconds for large dictionaries and makes all subsequent lookups fast. The sidecar can be deleted safely at any time — it will simply be rebuilt.
|
|
||||||
|
|
||||||
### How Lookup Works
|
|
||||||
|
|
||||||
1. **Direct match** — the word is found as-is (case-insensitive) in the dictionary index. Surrounding punctuation is ignored.
|
|
||||||
2. **Stemming** — on a miss, common English word forms are retried automatically: possessives and plurals (`dogs` → `dog`, `stories` → `story`) and verb endings (`walked` → `walk`, `running` → `run`, `making` → `make`).
|
|
||||||
3. **Not found** — a short popup appears and you return to word selection.
|
|
||||||
|
|
||||||
## The Definition Screen
|
|
||||||
|
|
||||||
When a word is found, the definition screen shows the matched headword at the top and the definition text below, with a page counter for long definitions.
|
|
||||||
|
|
||||||
- **Left/Right** or side **Up/Down** — previous / next page
|
|
||||||
- **Back** — return to word selection
|
|
||||||
+7
-23
@@ -90,19 +90,13 @@ if (parsedSize != fileSize) {
|
|||||||
|
|
||||||
## `section.bin`
|
## `section.bin`
|
||||||
|
|
||||||
### Version 30
|
### Version 28
|
||||||
|
|
||||||
Each file in `sections/*.bin` stores one laid-out spine section. The header is
|
Each file in `sections/*.bin` stores one laid-out spine section. The header is
|
||||||
also the cache-busting key: if any layout-affecting setting differs from the
|
also the cache-busting key: if any layout-affecting setting differs from the
|
||||||
current reader settings, the section is discarded and rebuilt.
|
current reader settings, the section is discarded and rebuilt.
|
||||||
|
|
||||||
Version 30 is binary-identical to version 29. The version was bumped because
|
Version 28 includes:
|
||||||
Arabic contextual shaping changed text measurement (`getTextAdvanceX` now
|
|
||||||
measures the shaped visual text), so word positions cached by v29 no longer
|
|
||||||
match what `drawText` renders.
|
|
||||||
|
|
||||||
Version 28 introduced serialized word style bits for underline, strikethrough,
|
|
||||||
superscript, and subscript. The format also includes:
|
|
||||||
|
|
||||||
- cache-busting fields for paragraph alignment, hyphenation, embedded CSS,
|
- cache-busting fields for paragraph alignment, hyphenation, embedded CSS,
|
||||||
image rendering mode, and Focus Reading
|
image rendering mode, and Focus Reading
|
||||||
@@ -113,10 +107,6 @@ superscript, and subscript. The format also includes:
|
|||||||
- per-page footnote entries
|
- per-page footnote entries
|
||||||
- serialized word style bits for underline, strikethrough, superscript, and
|
- serialized word style bits for underline, strikethrough, superscript, and
|
||||||
subscript
|
subscript
|
||||||
- flat TextBlock word storage (v29): per-word arrays plus one shared
|
|
||||||
NUL-terminated text blob, replacing v28's length-prefixed word strings. The
|
|
||||||
on-disk order mirrors the in-RAM arena so the firmware reads a whole block
|
|
||||||
payload with a single allocation and a single SD read
|
|
||||||
|
|
||||||
ImHex pattern:
|
ImHex pattern:
|
||||||
|
|
||||||
@@ -125,7 +115,7 @@ import std.mem;
|
|||||||
import std.string;
|
import std.string;
|
||||||
import std.core;
|
import std.core;
|
||||||
|
|
||||||
#define EXPECTED_VERSION 30
|
#define EXPECTED_VERSION 28
|
||||||
#define MAX_STRING_LENGTH 65535
|
#define MAX_STRING_LENGTH 65535
|
||||||
#define FOOTNOTE_NUMBER_LEN 32
|
#define FOOTNOTE_NUMBER_LEN 32
|
||||||
#define FOOTNOTE_HREF_LEN 96
|
#define FOOTNOTE_HREF_LEN 96
|
||||||
@@ -186,20 +176,14 @@ struct BlockStyle {
|
|||||||
|
|
||||||
struct TextBlock {
|
struct TextBlock {
|
||||||
u16 wordCount;
|
u16 wordCount;
|
||||||
u8 hasFocus;
|
String words[wordCount];
|
||||||
u16 textBytes [[comment("Total size of text[], including one NUL per word")]];
|
|
||||||
|
|
||||||
if (wordCount > 0) {
|
|
||||||
u16 textOff[wordCount] [[comment("Byte offset of word i's text within text[]")]];
|
|
||||||
s16 wordXPos[wordCount];
|
s16 wordXPos[wordCount];
|
||||||
if (hasFocus != 0) {
|
|
||||||
u16 wordFocusSuffixX[wordCount] [[comment("Suffix x offset from word start")]];
|
|
||||||
}
|
|
||||||
WordStyle wordStyle[wordCount];
|
WordStyle wordStyle[wordCount];
|
||||||
|
|
||||||
|
u8 hasFocus;
|
||||||
if (hasFocus != 0) {
|
if (hasFocus != 0) {
|
||||||
u8 wordFocusBoundary[wordCount] [[comment("UTF-8 byte boundary between bold prefix and suffix")]];
|
u8 wordFocusBoundary[wordCount] [[comment("UTF-8 byte boundary between bold prefix and suffix")]];
|
||||||
}
|
u16 wordFocusSuffixX[wordCount] [[comment("Suffix x offset from word start")]];
|
||||||
char text[textBytes] [[comment("All words back to back, each NUL-terminated")]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockStyle blockStyle;
|
BlockStyle blockStyle;
|
||||||
|
|||||||
+3
-8
@@ -32,14 +32,9 @@ networks or in hotspot mode when you control who is connected.
|
|||||||
## Join Network Mode
|
## Join Network Mode
|
||||||
|
|
||||||
1. Select **Join Network**.
|
1. Select **Join Network**.
|
||||||
2. If you have saved Wi-Fi credentials, CrossPoint first tries the last
|
2. Pick a 2.4 GHz Wi-Fi network from the scan results.
|
||||||
connected network, then other visible saved networks in signal-strength
|
3. Enter the password if prompted.
|
||||||
order. Press **Back** to cancel or **Confirm** to stop auto-connect and show
|
4. Save credentials if you want the reader to reconnect automatically next time.
|
||||||
the network list.
|
|
||||||
3. If the network list is shown, pick a 2.4 GHz Wi-Fi network from the scan
|
|
||||||
results.
|
|
||||||
4. Enter the password if prompted.
|
|
||||||
5. Save credentials if you want the reader to reconnect automatically next time.
|
|
||||||
|
|
||||||
After connection, the reader shows:
|
After connection, the reader shows:
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule freeink-sdk updated: 566fce3d4f...f611d71d8e
@@ -44,16 +44,15 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const combiningMark::Anchor anchor = combiningMark::anchorFor(cp);
|
const int raiseBy = isCombining ? combiningMark::raiseAboveBase(glyph->top, glyph->height, lastBaseTop) : 0;
|
||||||
const int raiseBy = isCombining ? combiningMark::raiseAboveBase(anchor, glyph->top, glyph->height, lastBaseTop) : 0;
|
|
||||||
|
|
||||||
if (!isCombining && prevCp != 0) {
|
if (!isCombining && prevCp != 0) {
|
||||||
const auto kernFP = getKerning(prevCp, cp); // 4.4 fixed-point kern
|
const auto kernFP = getKerning(prevCp, cp); // 4.4 fixed-point kern
|
||||||
lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP);
|
lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int glyphBaseX = isCombining ? combiningMark::anchorOver(anchor, lastBaseX, lastBaseLeft, lastBaseWidth,
|
const int glyphBaseX =
|
||||||
glyph->left, glyph->width)
|
isCombining ? combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, glyph->left, glyph->width)
|
||||||
: lastBaseX;
|
: lastBaseX;
|
||||||
const int glyphBaseY = startY - raiseBy;
|
const int glyphBaseY = startY - raiseBy;
|
||||||
|
|
||||||
|
|||||||
+10
-75
@@ -36,71 +36,24 @@ namespace combiningMark {
|
|||||||
|
|
||||||
constexpr int MIN_GAP_PX = 1;
|
constexpr int MIN_GAP_PX = 1;
|
||||||
|
|
||||||
/// Placement of a mark relative to its base glyph. The default heuristic —
|
|
||||||
/// centered over the base, raised clear of its top — suits Latin diacritics
|
|
||||||
/// and Arabic harakat, but misplaces the Hebrew niqqud whose identity depends
|
|
||||||
/// on position: dagesh sits inside the letter body, the shin/sin dots
|
|
||||||
/// distinguish the letter by sitting over its right/left arm, and holam hangs
|
|
||||||
/// over the left corner. "Native" anchors keep the glyph's font-designed
|
|
||||||
/// height (which may overlap the base) instead of raising it.
|
|
||||||
enum class Anchor : uint8_t {
|
|
||||||
CenterRaised, ///< centered over the base, lifted above its top (default)
|
|
||||||
CenterNative, ///< centered over the base at font-native height
|
|
||||||
RightNative, ///< right edges aligned, font-native height
|
|
||||||
LeftNative, ///< left edges aligned, font-native height
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr Anchor anchorFor(const uint32_t cp) {
|
|
||||||
switch (cp) {
|
|
||||||
case 0x05BC: // dagesh / mapiq / shuruk dot: inside the letter body
|
|
||||||
case 0x05BA: // holam haser for vav: straight above the vav stem
|
|
||||||
return Anchor::CenterNative;
|
|
||||||
case 0x05C1: // shin dot: over the letter's right arm
|
|
||||||
return Anchor::RightNative;
|
|
||||||
case 0x05B9: // holam: above the letter's left corner
|
|
||||||
case 0x05C2: // sin dot: over the letter's left arm
|
|
||||||
return Anchor::LeftNative;
|
|
||||||
default:
|
|
||||||
return Anchor::CenterRaised;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Horizontal offset from the base bitmap's left edge to the mark bitmap's
|
|
||||||
/// left edge for a given anchor.
|
|
||||||
constexpr int anchorShift(const Anchor anchor, const int baseWidth, const int markWidth) {
|
|
||||||
switch (anchor) {
|
|
||||||
case Anchor::LeftNative:
|
|
||||||
return 0;
|
|
||||||
case Anchor::RightNative:
|
|
||||||
return baseWidth - markWidth;
|
|
||||||
default:
|
|
||||||
return baseWidth / 2 - markWidth / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the cursor-X at which to render a combining mark so its bitmap
|
/// Compute the cursor-X at which to render a combining mark so its bitmap
|
||||||
/// lands at its anchor position over the base glyph's bitmap.
|
/// is visually centered over the base glyph's bitmap.
|
||||||
constexpr int anchorOver(const Anchor anchor, const int baseCursorPos, const int baseLeft, const int baseWidth,
|
constexpr int centerOver(int baseCursorPos, int baseLeft, int baseWidth, int markLeft, int markWidth) {
|
||||||
const int markLeft, const int markWidth) {
|
return baseCursorPos + baseLeft + baseWidth / 2 - markWidth / 2 - markLeft;
|
||||||
return baseCursorPos + baseLeft + anchorShift(anchor, baseWidth, markWidth) - markLeft;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rotated-90CW variant of anchorOver. In the rotated coordinate system
|
/// Rotated-90CW variant of centerOver. In the rotated coordinate system
|
||||||
/// renderCharImpl uses (cursorY - left) instead of (cursorX + left), so
|
/// renderCharImpl uses (cursorY - left) instead of (cursorX + left), so
|
||||||
/// every left/width term inverts sign.
|
/// every left/width term inverts sign.
|
||||||
constexpr int anchorOverRotated90CW(const Anchor anchor, const int baseCursorPos, const int baseLeft,
|
constexpr int centerOverRotated90CW(int baseCursorPos, int baseLeft, int baseWidth, int markLeft, int markWidth) {
|
||||||
const int baseWidth, const int markLeft, const int markWidth) {
|
return baseCursorPos - baseLeft - baseWidth / 2 + markWidth / 2 + markLeft;
|
||||||
return baseCursorPos - baseLeft - anchorShift(anchor, baseWidth, markWidth) + markLeft;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For combining marks that sit entirely above the baseline, compute how many
|
/// For combining marks that sit entirely above the baseline, compute how many
|
||||||
/// pixels to raise the mark so there is at least MIN_GAP_PX between its bottom
|
/// pixels to raise the mark so there is at least MIN_GAP_PX between its bottom
|
||||||
/// edge and the top of the base glyph. Returns 0 for marks that extend to or
|
/// edge and the top of the base glyph. Returns 0 for marks that extend to or
|
||||||
/// below the baseline (e.g. cedilla, dot-below, ogonek) and for anchors that
|
/// below the baseline (e.g. cedilla, dot-below, ogonek).
|
||||||
/// keep the font-native height (dagesh must stay inside the letter, the
|
constexpr int raiseAboveBase(int markTop, int markHeight, int baseTop) {
|
||||||
/// shin/sin dots touch its arms).
|
|
||||||
constexpr int raiseAboveBase(const Anchor anchor, const int markTop, const int markHeight, const int baseTop) {
|
|
||||||
if (anchor != Anchor::CenterRaised) return 0;
|
|
||||||
if (markTop - markHeight <= 0) return 0;
|
if (markTop - markHeight <= 0) return 0;
|
||||||
const int gap = markTop - markHeight - baseTop;
|
const int gap = markTop - markHeight - baseTop;
|
||||||
return (gap < MIN_GAP_PX) ? (MIN_GAP_PX - gap) : 0;
|
return (gap < MIN_GAP_PX) ? (MIN_GAP_PX - gap) : 0;
|
||||||
@@ -108,20 +61,6 @@ constexpr int raiseAboveBase(const Anchor anchor, const int markTop, const int m
|
|||||||
|
|
||||||
} // namespace combiningMark
|
} // namespace combiningMark
|
||||||
|
|
||||||
/// GCC/Clang (the ESP32 firmware toolchain) pack structs with __attribute__((packed)).
|
|
||||||
/// MSVC (host unit tests) has no equivalent attribute and instead needs a #pragma pack
|
|
||||||
/// region achieving the same 1-byte alignment. These macros keep the on-disk font layout
|
|
||||||
/// identical across both toolchains.
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#define EPD_PACKED_BEGIN __pragma(pack(push, 1))
|
|
||||||
#define EPD_PACKED_END __pragma(pack(pop))
|
|
||||||
#define EPD_PACKED_ATTR
|
|
||||||
#else
|
|
||||||
#define EPD_PACKED_BEGIN
|
|
||||||
#define EPD_PACKED_END
|
|
||||||
#define EPD_PACKED_ATTR __attribute__((packed))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Fixed-point conventions used by EpdGlyph and EpdFontData:
|
/// Fixed-point conventions used by EpdGlyph and EpdFontData:
|
||||||
/// advanceX: 12.4 unsigned fixed-point in uint16_t (use fp4::toPixel)
|
/// advanceX: 12.4 unsigned fixed-point in uint16_t (use fp4::toPixel)
|
||||||
/// kernMatrix: 4.4 signed fixed-point in int8_t (use fp4::toPixel)
|
/// kernMatrix: 4.4 signed fixed-point in int8_t (use fp4::toPixel)
|
||||||
@@ -156,21 +95,17 @@ typedef struct {
|
|||||||
|
|
||||||
/// Maps a codepoint to a kerning class ID, sorted by codepoint for binary search.
|
/// Maps a codepoint to a kerning class ID, sorted by codepoint for binary search.
|
||||||
/// Class IDs are 1-based; codepoints not in the table have implicit class 0 (no kerning).
|
/// Class IDs are 1-based; codepoints not in the table have implicit class 0 (no kerning).
|
||||||
EPD_PACKED_BEGIN
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t codepoint; ///< Unicode codepoint
|
uint16_t codepoint; ///< Unicode codepoint
|
||||||
uint8_t classId; ///< 1-based kerning class ID
|
uint8_t classId; ///< 1-based kerning class ID
|
||||||
} EPD_PACKED_ATTR EpdKernClassEntry;
|
} __attribute__((packed)) EpdKernClassEntry;
|
||||||
EPD_PACKED_END
|
|
||||||
|
|
||||||
/// Ligature substitution for a specific glyph pair, sorted by `pair` for binary search.
|
/// Ligature substitution for a specific glyph pair, sorted by `pair` for binary search.
|
||||||
/// `pair` encodes (leftCodepoint << 16 | rightCodepoint) for single-key lookup.
|
/// `pair` encodes (leftCodepoint << 16 | rightCodepoint) for single-key lookup.
|
||||||
EPD_PACKED_BEGIN
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t pair; ///< Packed codepoint pair (left << 16 | right)
|
uint32_t pair; ///< Packed codepoint pair (left << 16 | right)
|
||||||
uint32_t ligatureCp; ///< Codepoint of the replacement ligature glyph
|
uint32_t ligatureCp; ///< Codepoint of the replacement ligature glyph
|
||||||
} EPD_PACKED_ATTR EpdLigaturePair;
|
} __attribute__((packed)) EpdLigaturePair;
|
||||||
EPD_PACKED_END
|
|
||||||
|
|
||||||
/// Data stored for FONT AS A WHOLE
|
/// Data stored for FONT AS A WHOLE
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -33,24 +33,12 @@ void FontDecompressor::freePageBuffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FontDecompressor::freeHotGroup() {
|
void FontDecompressor::freeHotGroup() {
|
||||||
free(hotGroup);
|
hotGroup.clear();
|
||||||
hotGroup = nullptr;
|
hotGroup.shrink_to_fit();
|
||||||
hotGroupCapacity = 0;
|
|
||||||
hotGroupFont = nullptr;
|
hotGroupFont = nullptr;
|
||||||
hotGroupIndex = UINT16_MAX;
|
hotGroupIndex = UINT16_MAX;
|
||||||
free(hotGlyphBuf);
|
hotGlyphBuf.clear();
|
||||||
hotGlyphBuf = nullptr;
|
hotGlyphBuf.shrink_to_fit();
|
||||||
hotGlyphBufCapacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FontDecompressor::ensureCapacity(uint8_t*& buf, uint32_t& capacity, uint32_t needed) {
|
|
||||||
if (capacity >= needed) return true;
|
|
||||||
// Grow-only, free-then-malloc: every caller fully rewrites the buffer after a grow, so the
|
|
||||||
// old contents are dead -- freeing first gives the allocator its best shot on a tight heap.
|
|
||||||
free(buf);
|
|
||||||
buf = static_cast<uint8_t*>(malloc(needed)); // owned by FontDecompressor, freed in freeHotGroup()
|
|
||||||
capacity = buf ? needed : 0;
|
|
||||||
return buf != nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t FontDecompressor::getGroupIndex(const EpdFontData* fontData, uint32_t glyphIndex) {
|
uint16_t FontDecompressor::getGroupIndex(const EpdFontData* fontData, uint32_t glyphIndex) {
|
||||||
@@ -182,20 +170,24 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if hot group already has this group decompressed — if not, decompress it
|
// Check if hot group already has this group decompressed — if not, decompress it
|
||||||
if (!(hotGroup != nullptr && hotGroupFont == fontData && hotGroupIndex == groupIndex)) {
|
if (!(!hotGroup.empty() && hotGroupFont == fontData && hotGroupIndex == groupIndex)) {
|
||||||
stats.cacheMisses++;
|
stats.cacheMisses++;
|
||||||
const EpdFontGroup& group = fontData->groups[groupIndex];
|
const EpdFontGroup& group = fontData->groups[groupIndex];
|
||||||
|
|
||||||
// ensureCapacity may free the buffer, so the cached-group identity dies with it either way.
|
hotGroup.resize(group.uncompressedSize);
|
||||||
|
if (hotGroup.empty()) {
|
||||||
|
LOG_ERR("FDC", "Failed to allocate %u bytes for hot group %u", group.uncompressedSize, groupIndex);
|
||||||
hotGroupFont = nullptr;
|
hotGroupFont = nullptr;
|
||||||
hotGroupIndex = UINT16_MAX;
|
hotGroupIndex = UINT16_MAX;
|
||||||
if (!ensureCapacity(hotGroup, hotGroupCapacity, group.uncompressedSize)) {
|
|
||||||
LOG_ERR("FDC", "Failed to allocate %u bytes for hot group %u", group.uncompressedSize, groupIndex);
|
|
||||||
stats.getBitmapTimeUs += micros() - tStart;
|
stats.getBitmapTimeUs += micros() - tStart;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!decompressGroup(fontData, groupIndex, hotGroup, group.uncompressedSize)) {
|
if (!decompressGroup(fontData, groupIndex, hotGroup.data(), group.uncompressedSize)) {
|
||||||
|
hotGroup.clear();
|
||||||
|
hotGroup.shrink_to_fit();
|
||||||
|
hotGroupFont = nullptr;
|
||||||
|
hotGroupIndex = UINT16_MAX;
|
||||||
stats.getBitmapTimeUs += micros() - tStart;
|
stats.getBitmapTimeUs += micros() - tStart;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -208,16 +200,18 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compact just the requested glyph from byte-aligned data into scratch buffer
|
// Compact just the requested glyph from byte-aligned data into scratch buffer
|
||||||
if (!ensureCapacity(hotGlyphBuf, hotGlyphBufCapacity, glyph->dataLength)) {
|
if (glyph->dataLength > hotGlyphBuf.size()) {
|
||||||
LOG_ERR("FDC", "Failed to allocate %u bytes for glyph scratch", (unsigned)glyph->dataLength);
|
hotGlyphBuf.resize(glyph->dataLength);
|
||||||
|
}
|
||||||
|
if (hotGlyphBuf.empty()) {
|
||||||
stats.getBitmapTimeUs += micros() - tStart;
|
stats.getBitmapTimeUs += micros() - tStart;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t alignedOff = getAlignedOffset(fontData, groupIndex, glyphIndex);
|
uint32_t alignedOff = getAlignedOffset(fontData, groupIndex, glyphIndex);
|
||||||
compactSingleGlyph(&hotGroup[alignedOff], hotGlyphBuf, glyph->width, glyph->height);
|
compactSingleGlyph(&hotGroup[alignedOff], hotGlyphBuf.data(), glyph->width, glyph->height);
|
||||||
stats.getBitmapTimeUs += micros() - tStart;
|
stats.getBitmapTimeUs += micros() - tStart;
|
||||||
return hotGlyphBuf;
|
return hotGlyphBuf.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Prewarm: pre-decompress glyph bitmaps for a page of text ---
|
// --- Prewarm: pre-decompress glyph bitmaps for a page of text ---
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <InflateReader.h>
|
#include <InflateReader.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|
||||||
class FontDecompressor {
|
class FontDecompressor {
|
||||||
@@ -65,22 +67,13 @@ class FontDecompressor {
|
|||||||
|
|
||||||
// Hot group: last decompressed group (byte-aligned) for non-prewarmed fallback path.
|
// Hot group: last decompressed group (byte-aligned) for non-prewarmed fallback path.
|
||||||
// Kept in byte-aligned format; individual glyphs are compacted on demand into hotGlyphBuf.
|
// Kept in byte-aligned format; individual glyphs are compacted on demand into hotGlyphBuf.
|
||||||
// Nothrow high-water malloc buffers, NOT std::vector: getBitmap() runs on the render path,
|
|
||||||
// and under -fno-exceptions a vector resize that hits OOM abort()s the firmware instead of
|
|
||||||
// failing (field crash: hotGroup.resize() -> std::bad_alloc -> abort with ~11 KB free).
|
|
||||||
// ensureCapacity() returns false on OOM so the caller can skip the glyph gracefully.
|
|
||||||
const EpdFontData* hotGroupFont = nullptr;
|
const EpdFontData* hotGroupFont = nullptr;
|
||||||
uint16_t hotGroupIndex = UINT16_MAX;
|
uint16_t hotGroupIndex = UINT16_MAX;
|
||||||
uint8_t* hotGroup = nullptr; // owned; freed in freeHotGroup()/dtor
|
std::vector<uint8_t> hotGroup;
|
||||||
uint32_t hotGroupCapacity = 0;
|
|
||||||
|
|
||||||
// Scratch buffer for compacting a single glyph from the hot group.
|
// Scratch buffer for compacting a single glyph from the hot group.
|
||||||
// Valid until the next getBitmap() call. Same ownership/OOM contract as hotGroup.
|
// Valid until the next getBitmap() call.
|
||||||
uint8_t* hotGlyphBuf = nullptr;
|
std::vector<uint8_t> hotGlyphBuf;
|
||||||
uint32_t hotGlyphBufCapacity = 0;
|
|
||||||
|
|
||||||
// Grow (never shrink) an owned buffer to at least `needed` bytes; false on OOM, buffer freed.
|
|
||||||
static bool ensureCapacity(uint8_t*& buf, uint32_t& capacity, uint32_t needed);
|
|
||||||
|
|
||||||
void freePageBuffer();
|
void freePageBuffer();
|
||||||
void freeHotGroup();
|
void freeHotGroup();
|
||||||
|
|||||||
@@ -1195,8 +1195,7 @@ int SdCardFont::fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Iter>
|
template <typename Iter>
|
||||||
int SdCardFont::buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask,
|
int SdCardFont::buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask) {
|
||||||
const char* extraText) {
|
|
||||||
if (!loaded_) return -1;
|
if (!loaded_) return -1;
|
||||||
styleMask = resolveStyleMask(styleMask);
|
styleMask = resolveStyleMask(styleMask);
|
||||||
if (styleMask == 0) return 0;
|
if (styleMask == 0) return 0;
|
||||||
@@ -1216,9 +1215,6 @@ int SdCardFont::buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace,
|
|||||||
for (auto it = begin; it != end && !hitCap; ++it) {
|
for (auto it = begin; it != end && !hitCap; ++it) {
|
||||||
hitCap = collectUniqueCodepoints(asCStr(*it), codepoints, cpCount, MAX_UNIQUE_CODEPOINTS);
|
hitCap = collectUniqueCodepoints(asCStr(*it), codepoints, cpCount, MAX_UNIQUE_CODEPOINTS);
|
||||||
}
|
}
|
||||||
if (extraText && !hitCap) {
|
|
||||||
hitCap = collectUniqueCodepoints(extraText, codepoints, cpCount, MAX_UNIQUE_CODEPOINTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (includeSpace && std::none_of(codepoints, codepoints + cpCount, [](uint32_t c) { return c == ' '; }))
|
if (includeSpace && std::none_of(codepoints, codepoints + cpCount, [](uint32_t c) { return c == ' '; }))
|
||||||
codepoints[cpCount++] = ' ';
|
codepoints[cpCount++] = ' ';
|
||||||
@@ -1236,13 +1232,12 @@ int SdCardFont::buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace,
|
|||||||
return totalMissed;
|
return totalMissed;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SdCardFont::buildAdvanceTable(const char* utf8Text, uint8_t styleMask, const char* extraText) {
|
int SdCardFont::buildAdvanceTable(const char* utf8Text, uint8_t styleMask) {
|
||||||
return buildAdvanceTableRange(&utf8Text, &utf8Text + 1, false, false, styleMask, extraText);
|
return buildAdvanceTableRange(&utf8Text, &utf8Text + 1, false, false, styleMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SdCardFont::buildAdvanceTable(const std::vector<std::string>& words, bool includeHyphen, uint8_t styleMask,
|
int SdCardFont::buildAdvanceTable(const std::vector<std::string>& words, bool includeHyphen, uint8_t styleMask) {
|
||||||
const char* extraText) {
|
return buildAdvanceTableRange(words.begin(), words.end(), words.size() > 1, includeHyphen, styleMask);
|
||||||
return buildAdvanceTableRange(words.begin(), words.end(), words.size() > 1, includeHyphen, styleMask, extraText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Stats ---
|
// --- Stats ---
|
||||||
|
|||||||
@@ -47,12 +47,9 @@ class SdCardFont {
|
|||||||
// Build a compact advance-only table for layout measurement.
|
// Build a compact advance-only table for layout measurement.
|
||||||
// Extracts ALL unique codepoints from words (no MAX_PAGE_GLYPHS cap),
|
// Extracts ALL unique codepoints from words (no MAX_PAGE_GLYPHS cap),
|
||||||
// batch-reads advanceX from SD, stores in a sorted per-style table.
|
// batch-reads advanceX from SD, stores in a sorted per-style table.
|
||||||
// extraText: optional additional codepoints to warm in the same SD pass
|
|
||||||
// (e.g. shaped Arabic presentation forms the measurement path will look up).
|
|
||||||
// Returns number of codepoints not found in font coverage.
|
// Returns number of codepoints not found in font coverage.
|
||||||
int buildAdvanceTable(const char* utf8Text, uint8_t styleMask = 0x0F, const char* extraText = nullptr);
|
int buildAdvanceTable(const char* utf8Text, uint8_t styleMask = 0x0F);
|
||||||
int buildAdvanceTable(const std::vector<std::string>& words, bool includeHyphen, uint8_t styleMask = 0x0F,
|
int buildAdvanceTable(const std::vector<std::string>& words, bool includeHyphen, uint8_t styleMask = 0x0F);
|
||||||
const char* extraText = nullptr);
|
|
||||||
|
|
||||||
// Look up advanceX for a codepoint from the advance table.
|
// Look up advanceX for a codepoint from the advance table.
|
||||||
// Returns the 12.4 fixed-point advance, or 0 if not found.
|
// Returns the 12.4 fixed-point advance, or 0 if not found.
|
||||||
@@ -143,13 +140,11 @@ class SdCardFont {
|
|||||||
|
|
||||||
// Full intervals loaded from file (kept in RAM for codepoint lookup)
|
// Full intervals loaded from file (kept in RAM for codepoint lookup)
|
||||||
EpdUnicodeInterval* fullIntervals = nullptr;
|
EpdUnicodeInterval* fullIntervals = nullptr;
|
||||||
EPD_PACKED_BEGIN
|
|
||||||
struct BmpInterval16 {
|
struct BmpInterval16 {
|
||||||
uint16_t first;
|
uint16_t first;
|
||||||
uint16_t last;
|
uint16_t last;
|
||||||
uint16_t offset;
|
uint16_t offset;
|
||||||
} EPD_PACKED_ATTR;
|
} __attribute__((packed));
|
||||||
EPD_PACKED_END
|
|
||||||
static_assert(sizeof(BmpInterval16) == 6, "BmpInterval16 must remain compact");
|
static_assert(sizeof(BmpInterval16) == 6, "BmpInterval16 must remain compact");
|
||||||
BmpInterval16* bmpIntervals = nullptr;
|
BmpInterval16* bmpIntervals = nullptr;
|
||||||
bool intervalsAreBmp16 = false;
|
bool intervalsAreBmp16 = false;
|
||||||
@@ -254,8 +249,7 @@ class SdCardFont {
|
|||||||
int32_t findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const;
|
int32_t findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const;
|
||||||
int fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCount, uint8_t styleMask);
|
int fetchAdvancesForCodepoints(uint32_t* codepoints, uint32_t cpCount, uint8_t styleMask);
|
||||||
template <typename Iter>
|
template <typename Iter>
|
||||||
int buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask,
|
int buildAdvanceTableRange(Iter begin, Iter end, bool includeSpace, bool includeHyphen, uint8_t styleMask);
|
||||||
const char* extraText = nullptr);
|
|
||||||
int prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint32_t cpCount, bool metadataOnly);
|
int prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint32_t cpCount, bool metadataOnly);
|
||||||
|
|
||||||
// Global helpers
|
// Global helpers
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,6 @@
|
|||||||
!NotoSerif/**
|
!NotoSerif/**
|
||||||
!NotoSans/
|
!NotoSans/
|
||||||
!NotoSans/**
|
!NotoSans/**
|
||||||
!NotoSansArabic/
|
|
||||||
!NotoSansArabic/**
|
|
||||||
!NotoSansHebrew/
|
!NotoSansHebrew/
|
||||||
!NotoSansHebrew/**
|
!NotoSansHebrew/**
|
||||||
!OpenDyslexic/
|
!OpenDyslexic/
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,93 +0,0 @@
|
|||||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/arabic)
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
https://openfontlicense.org
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font creation
|
|
||||||
efforts of academic and linguistic communities, and to provide a free and
|
|
||||||
open framework in which fonts may be shared and improved in partnership
|
|
||||||
with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply
|
|
||||||
to any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components as
|
|
||||||
distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
||||||
or substituting -- in part or in whole -- any of the components of the
|
|
||||||
Original Version, by changing formats or by porting the Font Software to a
|
|
||||||
new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
|
||||||
Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,
|
|
||||||
in Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the corresponding
|
|
||||||
Copyright Holder. This restriction only applies to the primary font name as
|
|
||||||
presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created
|
|
||||||
using the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are
|
|
||||||
not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
+1256
-1872
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1483
-2244
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -31,48 +31,19 @@ done
|
|||||||
UI_FONT_SIZES=(10 12)
|
UI_FONT_SIZES=(10 12)
|
||||||
UI_FONT_STYLES=("Regular" "Bold")
|
UI_FONT_STYLES=("Regular" "Bold")
|
||||||
|
|
||||||
# Arabic glyphs for UI text (menus, file browser titles). The built-in fonts
|
|
||||||
# must cover the *output* of MiniBidi's do_shape() — contextual presentation
|
|
||||||
# forms — not base letters, or shaped UI text silently drops glyphs.
|
|
||||||
# Curated for firmware-size budget: core Arabic (Presentation Forms-B,
|
|
||||||
# incl. the Lam-Alef ligature forms) plus the Farsi/Urdu extra letters'
|
|
||||||
# Presentation Forms-A blocks, the few characters shaping leaves at their
|
|
||||||
# base codepoint, Arabic punctuation, and both digit sets. No harakat and
|
|
||||||
# no Sindhi/Pashto/Kurdish forms — book text gets those from SD-card fonts.
|
|
||||||
ARABIC_INTERVALS=(
|
|
||||||
--additional-intervals 0x060C,0x060C # Arabic comma
|
|
||||||
--additional-intervals 0x061B,0x061B # Arabic semicolon
|
|
||||||
--additional-intervals 0x061F,0x061F # Arabic question mark
|
|
||||||
--additional-intervals 0x0621,0x0621 # hamza (non-joining, never shaped)
|
|
||||||
--additional-intervals 0x0640,0x0640 # tatweel
|
|
||||||
--additional-intervals 0x0660,0x0669 # Arabic-Indic digits
|
|
||||||
--additional-intervals 0x06BA,0x06BA # noon ghunna base (initial/medial keep base cp)
|
|
||||||
--additional-intervals 0x06D4,0x06D4 # Urdu full stop
|
|
||||||
--additional-intervals 0x06F0,0x06F9 # extended Arabic-Indic digits (Farsi/Urdu)
|
|
||||||
--additional-intervals 0xFB56,0xFB59 # peh (Farsi)
|
|
||||||
--additional-intervals 0xFB66,0xFB69 # tteh (Urdu)
|
|
||||||
--additional-intervals 0xFB7A,0xFB7D # tcheh (Farsi)
|
|
||||||
--additional-intervals 0xFB88,0xFB95 # ddal, jeh, rreh (Urdu), keheh, gaf (Farsi/Urdu)
|
|
||||||
--additional-intervals 0xFB9E,0xFB9F # noon ghunna isolated/final (Urdu)
|
|
||||||
--additional-intervals 0xFBA6,0xFBB1 # heh goal, heh doachashmee, yeh barree(+hamza) (Urdu)
|
|
||||||
--additional-intervals 0xFBFC,0xFBFF # farsi yeh (Farsi/Urdu)
|
|
||||||
--additional-intervals 0xFE80,0xFEFC # Presentation Forms-B: core Arabic + Lam-Alef
|
|
||||||
)
|
|
||||||
|
|
||||||
for size in ${UI_FONT_SIZES[@]}; do
|
for size in ${UI_FONT_SIZES[@]}; do
|
||||||
for style in ${UI_FONT_STYLES[@]}; do
|
for style in ${UI_FONT_STYLES[@]}; do
|
||||||
font_name="ubuntu_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')"
|
font_name="ubuntu_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')"
|
||||||
font_path="../builtinFonts/source/Ubuntu/Ubuntu-${style}.ttf"
|
font_path="../builtinFonts/source/Ubuntu/Ubuntu-${style}.ttf"
|
||||||
hebrew_path="../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-${style}.ttf"
|
hebrew_path="../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-${style}.ttf"
|
||||||
arabic_path="../builtinFonts/source/NotoSansArabic/NotoSansArabic-${style}.ttf"
|
|
||||||
# Ubuntu lacks the Latin Extended Additional block (U+1EA0-U+1EF9) used for
|
# Ubuntu lacks the Latin Extended Additional block (U+1EA0-U+1EF9) used for
|
||||||
# Vietnamese tone marks. Append a Vietnamese-only Ubuntu cut so those glyphs
|
# Vietnamese tone marks. Append a Vietnamese-only Ubuntu cut so those glyphs
|
||||||
# are filled from it while every glyph Ubuntu already has stays unchanged
|
# are filled from it while every glyph Ubuntu already has stays unchanged
|
||||||
# (fontstack is ordered by descending priority).
|
# (fontstack is ordered by descending priority).
|
||||||
viet_path="../builtinFonts/source/Ubuntu/Ubuntu-Vietnamese-${style}.ttf"
|
viet_path="../builtinFonts/source/Ubuntu/Ubuntu-Vietnamese-${style}.ttf"
|
||||||
output_path="../builtinFonts/${font_name}.h"
|
output_path="../builtinFonts/${font_name}.h"
|
||||||
python fontconvert.py $font_name $size $font_path $hebrew_path $arabic_path $viet_path \
|
python fontconvert.py $font_name $size $font_path $hebrew_path $viet_path \
|
||||||
--additional-intervals 0x05D0,0x05EA "${ARABIC_INTERVALS[@]}" > $output_path
|
--additional-intervals 0x05D0,0x05EA > $output_path
|
||||||
echo "Generated $output_path"
|
echo "Generated $output_path"
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
@@ -80,8 +51,7 @@ done
|
|||||||
python fontconvert.py notosans_8_regular 8 \
|
python fontconvert.py notosans_8_regular 8 \
|
||||||
../builtinFonts/source/NotoSans/NotoSans-Regular.ttf \
|
../builtinFonts/source/NotoSans/NotoSans-Regular.ttf \
|
||||||
../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-Regular.ttf \
|
../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-Regular.ttf \
|
||||||
../builtinFonts/source/NotoSansArabic/NotoSansArabic-Regular.ttf \
|
--additional-intervals 0x05D0,0x05EA > ../builtinFonts/notosans_8_regular.h
|
||||||
--additional-intervals 0x05D0,0x05EA "${ARABIC_INTERVALS[@]}" > ../builtinFonts/notosans_8_regular.h
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Running compression verification..."
|
echo "Running compression verification..."
|
||||||
|
|||||||
+59
-19
@@ -153,11 +153,18 @@ bool Epub::parseTocNcxFile() const {
|
|||||||
|
|
||||||
LOG_DBG("EBP", "Parsing toc ncx file: %s", tocNcxItem.c_str());
|
LOG_DBG("EBP", "Parsing toc ncx file: %s", tocNcxItem.c_str());
|
||||||
|
|
||||||
size_t ncxSize;
|
const auto tmpNcxPath = getCachePath() + "/toc.ncx";
|
||||||
if (!getItemSize(tocNcxItem, &ncxSize)) {
|
HalFile tempNcxFile;
|
||||||
LOG_ERR("EBP", "Could not get size of toc ncx file");
|
if (!Storage.openFileForWrite("EBP", tmpNcxPath, tempNcxFile)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
readItemContentsToStream(tocNcxItem, tempNcxFile, 1024);
|
||||||
|
// Explicitly close() file before reopening for reading
|
||||||
|
tempNcxFile.close();
|
||||||
|
if (!Storage.openFileForRead("EBP", tmpNcxPath, tempNcxFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto ncxSize = tempNcxFile.size();
|
||||||
|
|
||||||
TocNcxParser ncxParser(contentBasePath, ncxSize, bookMetadataCache.get());
|
TocNcxParser ncxParser(contentBasePath, ncxSize, bookMetadataCache.get());
|
||||||
|
|
||||||
@@ -166,13 +173,29 @@ bool Epub::parseTocNcxFile() const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stream the decompressed NCX straight into the parser instead of round-tripping
|
const auto ncxBuffer = static_cast<uint8_t*>(malloc(1024));
|
||||||
// through a temp file on the SD card (decompress -> write -> reopen -> reread -> delete).
|
if (!ncxBuffer) {
|
||||||
if (!readItemContentsToStream(tocNcxItem, ncxParser, 1024)) {
|
LOG_ERR("EBP", "Could not allocate memory for toc ncx parser");
|
||||||
LOG_ERR("EBP", "Could not read toc ncx file");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (tempNcxFile.available()) {
|
||||||
|
const auto readSize = tempNcxFile.read(ncxBuffer, 1024);
|
||||||
|
if (readSize == 0) break;
|
||||||
|
const auto processedSize = ncxParser.write(ncxBuffer, readSize);
|
||||||
|
|
||||||
|
if (processedSize != readSize) {
|
||||||
|
LOG_ERR("EBP", "Could not process all toc ncx data");
|
||||||
|
free(ncxBuffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(ncxBuffer);
|
||||||
|
// Explicitly close() file before calling Storage.remove()
|
||||||
|
tempNcxFile.close();
|
||||||
|
Storage.remove(tmpNcxPath.c_str());
|
||||||
|
|
||||||
LOG_DBG("EBP", "Parsed TOC items");
|
LOG_DBG("EBP", "Parsed TOC items");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -186,11 +209,18 @@ bool Epub::parseTocNavFile() const {
|
|||||||
|
|
||||||
LOG_DBG("EBP", "Parsing toc nav file: %s", tocNavItem.c_str());
|
LOG_DBG("EBP", "Parsing toc nav file: %s", tocNavItem.c_str());
|
||||||
|
|
||||||
size_t navSize;
|
const auto tmpNavPath = getCachePath() + "/toc.nav";
|
||||||
if (!getItemSize(tocNavItem, &navSize)) {
|
HalFile tempNavFile;
|
||||||
LOG_ERR("EBP", "Could not get size of toc nav file");
|
if (!Storage.openFileForWrite("EBP", tmpNavPath, tempNavFile)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
readItemContentsToStream(tocNavItem, tempNavFile, 1024);
|
||||||
|
// Explicitly close() file before reopening for reading
|
||||||
|
tempNavFile.close();
|
||||||
|
if (!Storage.openFileForRead("EBP", tmpNavPath, tempNavFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto navSize = tempNavFile.size();
|
||||||
|
|
||||||
// Note: We can't use `contentBasePath` here as the nav file may be in a different folder to the content.opf
|
// Note: We can't use `contentBasePath` here as the nav file may be in a different folder to the content.opf
|
||||||
// and the HTMLX nav file will have hrefs relative to itself
|
// and the HTMLX nav file will have hrefs relative to itself
|
||||||
@@ -202,13 +232,28 @@ bool Epub::parseTocNavFile() const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stream the decompressed nav document straight into the parser instead of round-tripping
|
const auto navBuffer = static_cast<uint8_t*>(malloc(1024));
|
||||||
// through a temp file on the SD card (decompress -> write -> reopen -> reread -> delete).
|
if (!navBuffer) {
|
||||||
if (!readItemContentsToStream(tocNavItem, navParser, 1024)) {
|
LOG_ERR("EBP", "Could not allocate memory for toc nav parser");
|
||||||
LOG_ERR("EBP", "Could not read toc nav file");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (tempNavFile.available()) {
|
||||||
|
const auto readSize = tempNavFile.read(navBuffer, 1024);
|
||||||
|
const auto processedSize = navParser.write(navBuffer, readSize);
|
||||||
|
|
||||||
|
if (processedSize != readSize) {
|
||||||
|
LOG_ERR("EBP", "Could not process all toc nav data");
|
||||||
|
free(navBuffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(navBuffer);
|
||||||
|
// Explicitly close() file before calling Storage.remove()
|
||||||
|
tempNavFile.close();
|
||||||
|
Storage.remove(tmpNavPath.c_str());
|
||||||
|
|
||||||
LOG_DBG("EBP", "Parsed TOC nav items");
|
LOG_DBG("EBP", "Parsed TOC nav items");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -351,11 +396,6 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) {
|
|||||||
Storage.removeDir((cachePath + "/sections").c_str());
|
Storage.removeDir((cachePath + "/sections").c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Release the resolved CSS rule map: it is only needed transiently while building
|
|
||||||
// section caches, and createSectionFile reloads it from cache on demand. Holding it
|
|
||||||
// resident pins tens of KB for the whole reading session (more on warm resume into
|
|
||||||
// an already-cached chapter, where createSectionFile never runs to clear it).
|
|
||||||
cssParser->clear();
|
|
||||||
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());
|
LOG_DBG("EBP", "Loaded ePub: %s", filepath.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "BookMetadataCache.h"
|
#include "BookMetadataCache.h"
|
||||||
|
|
||||||
#include <BufferedFile.h>
|
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
@@ -15,52 +14,6 @@ constexpr uint8_t BOOK_CACHE_VERSION = 8; // v8: TOC/book titles stored NFC-com
|
|||||||
constexpr char bookBinFile[] = "/book.bin";
|
constexpr char bookBinFile[] = "/book.bin";
|
||||||
constexpr char tmpSpineBinFile[] = "/spine.bin.tmp";
|
constexpr char tmpSpineBinFile[] = "/spine.bin.tmp";
|
||||||
constexpr char tmpTocBinFile[] = "/toc.bin.tmp";
|
constexpr char tmpTocBinFile[] = "/toc.bin.tmp";
|
||||||
// Buffer size for the buildBookBin streams. 3 buffers x 4KB, transient (freed on
|
|
||||||
// return); 4KB = 8 SD sectors per transfer, enough to stop the sector-cache thrash.
|
|
||||||
constexpr size_t BUILD_IO_BUFFER_SIZE = 4096;
|
|
||||||
|
|
||||||
// Entry (de)serializers, templated so they run over HalFile and the Buffered*
|
|
||||||
// wrappers alike (two instantiations each -- a few hundred bytes of flash, in
|
|
||||||
// exchange for the build path streaming at SD speed instead of per-pod).
|
|
||||||
template <typename F>
|
|
||||||
uint32_t writeSpineEntryTo(F& file, const BookMetadataCache::SpineEntry& entry) {
|
|
||||||
const uint32_t pos = file.position();
|
|
||||||
serialization::writeString(file, entry.href);
|
|
||||||
serialization::writePod(file, entry.cumulativeSize);
|
|
||||||
serialization::writePod(file, entry.tocIndex);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename F>
|
|
||||||
uint32_t writeTocEntryTo(F& file, const BookMetadataCache::TocEntry& entry) {
|
|
||||||
const uint32_t pos = file.position();
|
|
||||||
serialization::writeString(file, entry.title);
|
|
||||||
serialization::writeString(file, entry.href);
|
|
||||||
serialization::writeString(file, entry.anchor);
|
|
||||||
serialization::writePod(file, entry.level);
|
|
||||||
serialization::writePod(file, entry.spineIndex);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename F>
|
|
||||||
BookMetadataCache::SpineEntry readSpineEntryFrom(F& file) {
|
|
||||||
BookMetadataCache::SpineEntry entry;
|
|
||||||
serialization::readString(file, entry.href);
|
|
||||||
serialization::readPod(file, entry.cumulativeSize);
|
|
||||||
serialization::readPod(file, entry.tocIndex);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename F>
|
|
||||||
BookMetadataCache::TocEntry readTocEntryFrom(F& file) {
|
|
||||||
BookMetadataCache::TocEntry entry;
|
|
||||||
serialization::readString(file, entry.title);
|
|
||||||
serialization::readString(file, entry.href);
|
|
||||||
serialization::readString(file, entry.anchor);
|
|
||||||
serialization::readPod(file, entry.level);
|
|
||||||
serialization::readPod(file, entry.spineIndex);
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
/* ============= WRITING / BUILDING FUNCTIONS ================ */
|
/* ============= WRITING / BUILDING FUNCTIONS ================ */
|
||||||
@@ -77,23 +30,13 @@ bool BookMetadataCache::beginContentOpfPass() {
|
|||||||
LOG_DBG("BMC", "Beginning content opf pass");
|
LOG_DBG("BMC", "Beginning content opf pass");
|
||||||
|
|
||||||
// Open spine file for writing
|
// Open spine file for writing
|
||||||
if (!Storage.openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile)) {
|
return Storage.openFileForWrite("BMC", cachePath + tmpSpineBinFile, spineFile);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Wrapper OOM is fine: createSpineEntry falls back to unbuffered writes.
|
|
||||||
passOut = makeUniqueNoThrow<serialization::BufferedFileWriter>(spineFile, BUILD_IO_BUFFER_SIZE);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BookMetadataCache::endContentOpfPass() {
|
bool BookMetadataCache::endContentOpfPass() {
|
||||||
const bool flushed = !passOut || passOut->flush();
|
|
||||||
passOut.reset();
|
|
||||||
// Explicit close() required: member variable persists beyond function scope
|
// Explicit close() required: member variable persists beyond function scope
|
||||||
spineFile.close();
|
spineFile.close();
|
||||||
if (!flushed) {
|
return true;
|
||||||
LOG_ERR("BMC", "Failed writing spine tmp file");
|
|
||||||
}
|
|
||||||
return flushed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BookMetadataCache::beginTocPass() {
|
bool BookMetadataCache::beginTocPass() {
|
||||||
@@ -131,17 +74,10 @@ bool BookMetadataCache::beginTocPass() {
|
|||||||
useSpineHrefIndex = false;
|
useSpineHrefIndex = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper OOM is fine: createTocEntry falls back to unbuffered writes.
|
|
||||||
passOut = makeUniqueNoThrow<serialization::BufferedFileWriter>(tocFile, BUILD_IO_BUFFER_SIZE);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BookMetadataCache::endTocPass() {
|
bool BookMetadataCache::endTocPass() {
|
||||||
const bool flushed = !passOut || passOut->flush();
|
|
||||||
passOut.reset();
|
|
||||||
if (!flushed) {
|
|
||||||
LOG_ERR("BMC", "Failed writing toc tmp file");
|
|
||||||
}
|
|
||||||
// Explicit close() required: member variables persist beyond function scope
|
// Explicit close() required: member variables persist beyond function scope
|
||||||
tocFile.close();
|
tocFile.close();
|
||||||
spineFile.close();
|
spineFile.close();
|
||||||
@@ -150,7 +86,7 @@ bool BookMetadataCache::endTocPass() {
|
|||||||
spineHrefIndex.shrink_to_fit();
|
spineHrefIndex.shrink_to_fit();
|
||||||
useSpineHrefIndex = false;
|
useSpineHrefIndex = false;
|
||||||
|
|
||||||
return flushed;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BookMetadataCache::endWrite() {
|
bool BookMetadataCache::endWrite() {
|
||||||
@@ -183,14 +119,6 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffered streams for the whole build: every access below is sequential per
|
|
||||||
// file, but interleaved ACROSS files, which thrashes SdFat's single shared
|
|
||||||
// sector cache when unbuffered (one 512B SD transaction per 4-byte pod --
|
|
||||||
// measured 31s for a 1,732-spine omnibus). Three 4KB buffers, freed on return.
|
|
||||||
serialization::BufferedFileWriter bookOut(bookFile, BUILD_IO_BUFFER_SIZE);
|
|
||||||
serialization::BufferedFileReader spineIn(spineFile, BUILD_IO_BUFFER_SIZE);
|
|
||||||
serialization::BufferedFileReader tocIn(tocFile, BUILD_IO_BUFFER_SIZE);
|
|
||||||
|
|
||||||
constexpr uint32_t headerASize =
|
constexpr uint32_t headerASize =
|
||||||
sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(uint32_t) + sizeof(spineCount) + sizeof(tocCount);
|
sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(uint32_t) + sizeof(spineCount) + sizeof(tocCount);
|
||||||
const uint32_t metadataSize = metadata.title.size() + metadata.author.size() + metadata.language.size() +
|
const uint32_t metadataSize = metadata.title.size() + metadata.author.size() + metadata.language.size() +
|
||||||
@@ -200,34 +128,31 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
const uint32_t lutOffset = headerASize + metadataSize;
|
const uint32_t lutOffset = headerASize + metadataSize;
|
||||||
|
|
||||||
// Header A
|
// Header A
|
||||||
serialization::writePod(bookOut, BOOK_CACHE_VERSION);
|
serialization::writePod(bookFile, BOOK_CACHE_VERSION);
|
||||||
serialization::writePod(bookOut, lutOffset);
|
serialization::writePod(bookFile, lutOffset);
|
||||||
serialization::writePod(bookOut, spineCount);
|
serialization::writePod(bookFile, spineCount);
|
||||||
serialization::writePod(bookOut, tocCount);
|
serialization::writePod(bookFile, tocCount);
|
||||||
// Metadata
|
// Metadata
|
||||||
serialization::writeString(bookOut, metadata.title);
|
serialization::writeString(bookFile, metadata.title);
|
||||||
serialization::writeString(bookOut, metadata.author);
|
serialization::writeString(bookFile, metadata.author);
|
||||||
serialization::writeString(bookOut, metadata.language);
|
serialization::writeString(bookFile, metadata.language);
|
||||||
serialization::writeString(bookOut, metadata.coverItemHref);
|
serialization::writeString(bookFile, metadata.coverItemHref);
|
||||||
serialization::writeString(bookOut, metadata.textReferenceHref);
|
serialization::writeString(bookFile, metadata.textReferenceHref);
|
||||||
|
|
||||||
// Loop through spine entries, writing LUT positions
|
// Loop through spine entries, writing LUT positions
|
||||||
spineIn.seek(0);
|
spineFile.seek(0);
|
||||||
for (int i = 0; i < spineCount; i++) {
|
for (int i = 0; i < spineCount; i++) {
|
||||||
const uint32_t pos = spineIn.position();
|
uint32_t pos = spineFile.position();
|
||||||
readSpineEntryFrom(spineIn);
|
auto spineEntry = readSpineEntry(spineFile);
|
||||||
serialization::writePod(bookOut, pos + lutOffset + lutSize);
|
serialization::writePod(bookFile, pos + lutOffset + lutSize);
|
||||||
}
|
}
|
||||||
// Total size of the spine tmp file: entries land in book.bin after the toc LUT
|
|
||||||
// and the full spine block, so toc LUT positions are offset by it.
|
|
||||||
const auto spineBytes = static_cast<uint32_t>(spineIn.position());
|
|
||||||
|
|
||||||
// Loop through toc entries, writing LUT positions
|
// Loop through toc entries, writing LUT positions
|
||||||
tocIn.seek(0);
|
tocFile.seek(0);
|
||||||
for (int i = 0; i < tocCount; i++) {
|
for (int i = 0; i < tocCount; i++) {
|
||||||
const uint32_t pos = tocIn.position();
|
uint32_t pos = tocFile.position();
|
||||||
readTocEntryFrom(tocIn);
|
auto tocEntry = readTocEntry(tocFile);
|
||||||
serialization::writePod(bookOut, pos + lutOffset + lutSize + spineBytes);
|
serialization::writePod(bookFile, pos + lutOffset + lutSize + static_cast<uint32_t>(spineFile.position()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// LUTs complete
|
// LUTs complete
|
||||||
@@ -235,9 +160,9 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
|
|
||||||
// Build spineIndex->tocIndex mapping in one pass (O(n) instead of O(n*m))
|
// Build spineIndex->tocIndex mapping in one pass (O(n) instead of O(n*m))
|
||||||
std::deque<int16_t> spineToTocIndex(spineCount, -1);
|
std::deque<int16_t> spineToTocIndex(spineCount, -1);
|
||||||
tocIn.seek(0);
|
tocFile.seek(0);
|
||||||
for (int j = 0; j < tocCount; j++) {
|
for (int j = 0; j < tocCount; j++) {
|
||||||
auto tocEntry = readTocEntryFrom(tocIn);
|
auto tocEntry = readTocEntry(tocFile);
|
||||||
if (tocEntry.spineIndex >= 0 && tocEntry.spineIndex < spineCount) {
|
if (tocEntry.spineIndex >= 0 && tocEntry.spineIndex < spineCount) {
|
||||||
if (spineToTocIndex[tocEntry.spineIndex] == -1) {
|
if (spineToTocIndex[tocEntry.spineIndex] == -1) {
|
||||||
spineToTocIndex[tocEntry.spineIndex] = static_cast<int16_t>(j);
|
spineToTocIndex[tocEntry.spineIndex] = static_cast<int16_t>(j);
|
||||||
@@ -272,9 +197,9 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
std::deque<ZipFile::SizeTarget> targets;
|
std::deque<ZipFile::SizeTarget> targets;
|
||||||
targets.resize(spineCount);
|
targets.resize(spineCount);
|
||||||
|
|
||||||
spineIn.seek(0);
|
spineFile.seek(0);
|
||||||
for (int i = 0; i < spineCount; i++) {
|
for (int i = 0; i < spineCount; i++) {
|
||||||
auto entry = readSpineEntryFrom(spineIn);
|
auto entry = readSpineEntry(spineFile);
|
||||||
std::string path = FsHelpers::normalisePath(entry.href);
|
std::string path = FsHelpers::normalisePath(entry.href);
|
||||||
|
|
||||||
ZipFile::SizeTarget t;
|
ZipFile::SizeTarget t;
|
||||||
@@ -299,10 +224,10 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t cumSize = 0;
|
uint32_t cumSize = 0;
|
||||||
spineIn.seek(0);
|
spineFile.seek(0);
|
||||||
int lastSpineTocIndex = -1;
|
int lastSpineTocIndex = -1;
|
||||||
for (int i = 0; i < spineCount; i++) {
|
for (int i = 0; i < spineCount; i++) {
|
||||||
auto spineEntry = readSpineEntryFrom(spineIn);
|
auto spineEntry = readSpineEntry(spineFile);
|
||||||
|
|
||||||
spineEntry.tocIndex = spineToTocIndex[i];
|
spineEntry.tocIndex = spineToTocIndex[i];
|
||||||
|
|
||||||
@@ -335,33 +260,23 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
spineEntry.cumulativeSize = cumSize;
|
spineEntry.cumulativeSize = cumSize;
|
||||||
|
|
||||||
// Write out spine data to book.bin
|
// Write out spine data to book.bin
|
||||||
writeSpineEntryTo(bookOut, spineEntry);
|
writeSpineEntry(bookFile, spineEntry);
|
||||||
}
|
}
|
||||||
// Close opened zip file
|
// Close opened zip file
|
||||||
zip.close();
|
zip.close();
|
||||||
|
|
||||||
// Loop through toc entries from toc file writing to book.bin
|
// Loop through toc entries from toc file writing to book.bin
|
||||||
tocIn.seek(0);
|
tocFile.seek(0);
|
||||||
for (int i = 0; i < tocCount; i++) {
|
for (int i = 0; i < tocCount; i++) {
|
||||||
auto tocEntry = readTocEntryFrom(tocIn);
|
auto tocEntry = readTocEntry(tocFile);
|
||||||
writeTocEntryTo(bookOut, tocEntry);
|
writeTocEntry(bookFile, tocEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool written = bookOut.flush();
|
|
||||||
|
|
||||||
// Explicit close() required: member variables persist beyond function scope
|
// Explicit close() required: member variables persist beyond function scope
|
||||||
bookFile.close();
|
bookFile.close();
|
||||||
spineFile.close();
|
spineFile.close();
|
||||||
tocFile.close();
|
tocFile.close();
|
||||||
|
|
||||||
if (!written) {
|
|
||||||
// A short write (card full/removed) would leave a truncated book.bin that
|
|
||||||
// still passes the version check on load; remove it so the next open rebuilds.
|
|
||||||
LOG_ERR("BMC", "Failed writing book.bin, removing truncated file");
|
|
||||||
Storage.remove((cachePath + bookBinFile).c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("BMC", "Successfully built book.bin");
|
LOG_DBG("BMC", "Successfully built book.bin");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -379,11 +294,21 @@ bool BookMetadataCache::cleanupTmpFiles() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t BookMetadataCache::writeSpineEntry(HalFile& file, const SpineEntry& entry) const {
|
uint32_t BookMetadataCache::writeSpineEntry(HalFile& file, const SpineEntry& entry) const {
|
||||||
return writeSpineEntryTo(file, entry);
|
const uint32_t pos = file.position();
|
||||||
|
serialization::writeString(file, entry.href);
|
||||||
|
serialization::writePod(file, entry.cumulativeSize);
|
||||||
|
serialization::writePod(file, entry.tocIndex);
|
||||||
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t BookMetadataCache::writeTocEntry(HalFile& file, const TocEntry& entry) const {
|
uint32_t BookMetadataCache::writeTocEntry(HalFile& file, const TocEntry& entry) const {
|
||||||
return writeTocEntryTo(file, entry);
|
const uint32_t pos = file.position();
|
||||||
|
serialization::writeString(file, entry.title);
|
||||||
|
serialization::writeString(file, entry.href);
|
||||||
|
serialization::writeString(file, entry.anchor);
|
||||||
|
serialization::writePod(file, entry.level);
|
||||||
|
serialization::writePod(file, entry.spineIndex);
|
||||||
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: for the LUT to be accurate, this **MUST** be called for all spine items before `addTocEntry` is ever called
|
// Note: for the LUT to be accurate, this **MUST** be called for all spine items before `addTocEntry` is ever called
|
||||||
@@ -395,11 +320,7 @@ void BookMetadataCache::createSpineEntry(const std::string& href) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SpineEntry entry(href, 0, -1);
|
const SpineEntry entry(href, 0, -1);
|
||||||
if (passOut) {
|
|
||||||
writeSpineEntryTo(*passOut, entry);
|
|
||||||
} else {
|
|
||||||
writeSpineEntry(spineFile, entry);
|
writeSpineEntry(spineFile, entry);
|
||||||
}
|
|
||||||
spineCount++;
|
spineCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,11 +368,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri
|
|||||||
// Compose the title to NFC at index time so the cache stores precomposed glyphs;
|
// Compose the title to NFC at index time so the cache stores precomposed glyphs;
|
||||||
// device fonts have no combining-mark positioning, so NFD titles render broken.
|
// device fonts have no combining-mark positioning, so NFD titles render broken.
|
||||||
const TocEntry entry(utf8ComposeNfc(title), href, anchor, level, spineIndex);
|
const TocEntry entry(utf8ComposeNfc(title), href, anchor, level, spineIndex);
|
||||||
if (passOut) {
|
|
||||||
writeTocEntryTo(*passOut, entry);
|
|
||||||
} else {
|
|
||||||
writeTocEntry(tocFile, entry);
|
writeTocEntry(tocFile, entry);
|
||||||
}
|
|
||||||
tocCount++;
|
tocCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +442,19 @@ BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(HalFile& file) const {
|
BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(HalFile& file) const {
|
||||||
return readSpineEntryFrom(file);
|
SpineEntry entry;
|
||||||
|
serialization::readString(file, entry.href);
|
||||||
|
serialization::readPod(file, entry.cumulativeSize);
|
||||||
|
serialization::readPod(file, entry.tocIndex);
|
||||||
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(HalFile& file) const { return readTocEntryFrom(file); }
|
BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(HalFile& file) const {
|
||||||
|
TocEntry entry;
|
||||||
|
serialization::readString(file, entry.title);
|
||||||
|
serialization::readString(file, entry.href);
|
||||||
|
serialization::readString(file, entry.anchor);
|
||||||
|
serialization::readPod(file, entry.level);
|
||||||
|
serialization::readPod(file, entry.spineIndex);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <BufferedFile.h>
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class BookMetadataCache {
|
class BookMetadataCache {
|
||||||
@@ -56,11 +54,6 @@ class BookMetadataCache {
|
|||||||
// Temp file handles during build
|
// Temp file handles during build
|
||||||
HalFile spineFile;
|
HalFile spineFile;
|
||||||
HalFile tocFile;
|
HalFile tocFile;
|
||||||
// Buffers the per-entry tmp-file writes during the OPF/TOC passes: those
|
|
||||||
// writes interleave with zip-inflate SD reads, and unbuffered they thrash
|
|
||||||
// SdFat's shared sector cache (one 512B transaction per 4-byte pod). One
|
|
||||||
// wrapper serves whichever pass is active (spine, then toc).
|
|
||||||
std::unique_ptr<serialization::BufferedFileWriter> passOut;
|
|
||||||
|
|
||||||
// Index for fast href→spineIndex lookup (used only for large EPUBs)
|
// Index for fast href→spineIndex lookup (used only for large EPUBs)
|
||||||
struct SpineHrefIndexEntry {
|
struct SpineHrefIndexEntry {
|
||||||
|
|||||||
+1
-32
@@ -39,17 +39,7 @@ std::unique_ptr<PageLine> PageLine::deserialize(HalFile& file) {
|
|||||||
serialization::readPod(file, yPos);
|
serialization::readPod(file, yPos);
|
||||||
|
|
||||||
auto tb = TextBlock::deserialize(file);
|
auto tb = TextBlock::deserialize(file);
|
||||||
if (!tb) {
|
return std::unique_ptr<PageLine>(new PageLine(std::move(tb), xPos, yPos));
|
||||||
LOG_ERR("PGE", "Deserialization failed: null TextBlock");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* line = new (std::nothrow) PageLine(std::move(tb), xPos, yPos);
|
|
||||||
if (!line) {
|
|
||||||
LOG_ERR("PGE", "Deserialization failed: could not allocate PageLine");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return std::unique_ptr<PageLine>(line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PageImage::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
void PageImage::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
||||||
@@ -57,10 +47,6 @@ void PageImage::render(GfxRenderer& renderer, const int fontId, const int xOffse
|
|||||||
imageBlock->render(renderer, xPos + xOffset, yPos + yOffset);
|
imageBlock->render(renderer, xPos + xOffset, yPos + yOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PageImage::renderPlaceholder(GfxRenderer& renderer, const int xOffset, const int yOffset) const {
|
|
||||||
imageBlock->renderPlaceholder(renderer, xPos + xOffset, yPos + yOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PageImage::serialize(HalFile& file) {
|
bool PageImage::serialize(HalFile& file) {
|
||||||
serialization::writePod(file, xPos);
|
serialization::writePod(file, xPos);
|
||||||
serialization::writePod(file, yPos);
|
serialization::writePod(file, yPos);
|
||||||
@@ -129,17 +115,6 @@ void Page::renderImages(GfxRenderer& renderer, const int fontId, const int xOffs
|
|||||||
[](const PageElement& element) { return element.getTag() == TAG_PageImage; });
|
[](const PageElement& element) { return element.getTag() == TAG_PageImage; });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Page::renderWithImagePlaceholders(GfxRenderer& renderer, const int fontId, const int xOffset,
|
|
||||||
const int yOffset) const {
|
|
||||||
for (const auto& element : elements) {
|
|
||||||
if (element->getTag() == TAG_PageImage) {
|
|
||||||
static_cast<const PageImage&>(*element).renderPlaceholder(renderer, xOffset, yOffset);
|
|
||||||
} else {
|
|
||||||
element->render(renderer, fontId, xOffset, yOffset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Page::serialize(HalFile& file) const {
|
bool Page::serialize(HalFile& file) const {
|
||||||
const uint16_t count = elements.size();
|
const uint16_t count = elements.size();
|
||||||
serialization::writePod(file, count);
|
serialization::writePod(file, count);
|
||||||
@@ -180,15 +155,9 @@ std::unique_ptr<Page> Page::deserialize(HalFile& file) {
|
|||||||
|
|
||||||
if (tag == TAG_PageLine) {
|
if (tag == TAG_PageLine) {
|
||||||
auto pl = PageLine::deserialize(file);
|
auto pl = PageLine::deserialize(file);
|
||||||
if (!pl) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
page->elements.push_back(std::move(pl));
|
page->elements.push_back(std::move(pl));
|
||||||
} else if (tag == TAG_PageImage) {
|
} else if (tag == TAG_PageImage) {
|
||||||
auto pi = PageImage::deserialize(file);
|
auto pi = PageImage::deserialize(file);
|
||||||
if (!pi) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
page->elements.push_back(std::move(pi));
|
page->elements.push_back(std::move(pi));
|
||||||
} else if (tag == TAG_PageHorizontalRule) {
|
} else if (tag == TAG_PageHorizontalRule) {
|
||||||
auto rule = PageHorizontalRule::deserialize(file);
|
auto rule = PageHorizontalRule::deserialize(file);
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ class PageImage final : public PageElement {
|
|||||||
PageImage(std::shared_ptr<ImageBlock> block, const int16_t xPos, const int16_t yPos)
|
PageImage(std::shared_ptr<ImageBlock> block, const int16_t xPos, const int16_t yPos)
|
||||||
: PageElement(xPos, yPos), imageBlock(std::move(block)) {}
|
: PageElement(xPos, yPos), imageBlock(std::move(block)) {}
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
||||||
void renderPlaceholder(GfxRenderer& renderer, int xOffset, int yOffset) const;
|
|
||||||
bool serialize(HalFile& file) override;
|
bool serialize(HalFile& file) override;
|
||||||
PageElementTag getTag() const override { return TAG_PageImage; }
|
PageElementTag getTag() const override { return TAG_PageImage; }
|
||||||
static std::unique_ptr<PageImage> deserialize(HalFile& file);
|
static std::unique_ptr<PageImage> deserialize(HalFile& file);
|
||||||
@@ -90,7 +89,6 @@ class Page {
|
|||||||
|
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
||||||
void renderImages(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
void renderImages(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
||||||
void renderWithImagePlaceholders(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
|
||||||
bool serialize(HalFile& file) const;
|
bool serialize(HalFile& file) const;
|
||||||
static std::unique_ptr<Page> deserialize(HalFile& file);
|
static std::unique_ptr<Page> deserialize(HalFile& file);
|
||||||
|
|
||||||
@@ -100,13 +98,6 @@ class Page {
|
|||||||
[](const std::shared_ptr<PageElement>& el) { return el->getTag() == TAG_PageImage; });
|
[](const std::shared_ptr<PageElement>& el) { return el->getTag() == TAG_PageImage; });
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasImagesNeedingDecode() const {
|
|
||||||
return std::any_of(elements.begin(), elements.end(), [](const std::shared_ptr<PageElement>& element) {
|
|
||||||
return element->getTag() == TAG_PageImage &&
|
|
||||||
static_cast<const PageImage&>(*element).getImageBlock().needsDecode();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get bounding box of all images on the page (union of image rects)
|
// Get bounding box of all images on the page (union of image rects)
|
||||||
// Returns false if no images. Coordinates are relative to page origin.
|
// 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 getImageBoundingBox(int16_t& outX, int16_t& outY, int16_t& outW, int16_t& outH) const {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <BidiUtils.h>
|
#include <BidiUtils.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <Logging.h>
|
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -287,30 +286,7 @@ void ParsedText::addWord(std::string word, const EpdFontFamily::Style fontStyle,
|
|||||||
effectiveNoSpaceBefore = true;
|
effectiveNoSpaceBefore = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto ensureTokenCapacity = [&](const size_t additionalTokens) {
|
|
||||||
if (additionalTokens == 0) return;
|
|
||||||
const size_t requiredSize = words.size() + additionalTokens;
|
|
||||||
if (words.capacity() >= requiredSize) return;
|
|
||||||
|
|
||||||
size_t newCapacity = words.capacity();
|
|
||||||
if (newCapacity < 16) {
|
|
||||||
newCapacity = 16;
|
|
||||||
}
|
|
||||||
while (newCapacity < requiredSize) {
|
|
||||||
newCapacity *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
words.reserve(newCapacity);
|
|
||||||
wordStyles.reserve(newCapacity);
|
|
||||||
wordContinues.reserve(newCapacity);
|
|
||||||
wordNoSpaceBefore.reserve(newCapacity);
|
|
||||||
wordIsFocusSuffix.reserve(newCapacity);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (auto breakOffsets = cjkCharacterBreakByteOffsets(word); !breakOffsets.empty()) {
|
if (auto breakOffsets = cjkCharacterBreakByteOffsets(word); !breakOffsets.empty()) {
|
||||||
// CJK-heavy paragraphs can push hundreds of tiny tokens quickly when CSS toggles
|
|
||||||
// inline styles. Reserve once up front to avoid repeated vector growth reallocations.
|
|
||||||
ensureTokenCapacity(breakOffsets.size() + 1);
|
|
||||||
bool firstToken = true;
|
bool firstToken = true;
|
||||||
size_t tokenStart = 0;
|
size_t tokenStart = 0;
|
||||||
for (const size_t breakOffset : breakOffsets) {
|
for (const size_t breakOffset : breakOffsets) {
|
||||||
@@ -349,8 +325,29 @@ void ParsedText::addWord(std::string word, const EpdFontFamily::Style fontStyle,
|
|||||||
|
|
||||||
// --- FOCUS READING LOGIC BELOW ---
|
// --- FOCUS READING LOGIC BELOW ---
|
||||||
|
|
||||||
// Worst case: a segment boundary on each byte (highly punctuated UTF-8 text).
|
// Pre-reserve capacity to prevent mid-word heap reallocations.
|
||||||
ensureTokenCapacity(word.length());
|
size_t maxPossibleNewTokens = word.length();
|
||||||
|
size_t requiredSize = words.size() + maxPossibleNewTokens;
|
||||||
|
|
||||||
|
if (words.capacity() < requiredSize) {
|
||||||
|
// Emulate standard geometric growth (doubling) to ensure we don't reallocate on every word.
|
||||||
|
size_t newCapacity = words.capacity() * 2;
|
||||||
|
|
||||||
|
// Ensure the doubled capacity is actually enough for this specific word
|
||||||
|
if (newCapacity < requiredSize) {
|
||||||
|
newCapacity = requiredSize;
|
||||||
|
}
|
||||||
|
// Set a sensible minimum starting size so the first few words don't trigger tiny reallocations
|
||||||
|
if (newCapacity < 16) {
|
||||||
|
newCapacity = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
words.reserve(newCapacity);
|
||||||
|
wordStyles.reserve(newCapacity);
|
||||||
|
wordContinues.reserve(newCapacity);
|
||||||
|
wordNoSpaceBefore.reserve(newCapacity);
|
||||||
|
wordIsFocusSuffix.reserve(newCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
// Lambda helper to process and push individual sub-segments of the string
|
// Lambda helper to process and push individual sub-segments of the string
|
||||||
// Use std::string_view to avoid heap allocations when slicing
|
// Use std::string_view to avoid heap allocations when slicing
|
||||||
@@ -1136,14 +1133,8 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!lineHasFocusSplit) {
|
if (!lineHasFocusSplit) {
|
||||||
// TextBlock flattens the vectors into its arena; they stay owned here and die at return.
|
processLine(std::make_shared<TextBlock>(std::move(lineWords), std::move(lineXPos), std::move(lineWordStyles),
|
||||||
auto block = std::make_shared<TextBlock>(lineWords, lineXPos, lineWordStyles, std::vector<uint8_t>{},
|
std::vector<uint8_t>{}, std::vector<uint16_t>{}, blockStyle));
|
||||||
std::vector<uint16_t>{}, blockStyle);
|
|
||||||
if (!block->valid()) {
|
|
||||||
LOG_ERR("PTX", "Dropping line: TextBlock arena allocation failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
processLine(std::move(block));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1188,10 +1179,6 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto block = std::make_shared<TextBlock>(outWords, outXPos, outStyles, outBoundaries, outSuffixX, blockStyle);
|
processLine(std::make_shared<TextBlock>(std::move(outWords), std::move(outXPos), std::move(outStyles),
|
||||||
if (!block->valid()) {
|
std::move(outBoundaries), std::move(outSuffixX), blockStyle));
|
||||||
LOG_ERR("PTX", "Dropping line: TextBlock arena allocation failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
processLine(std::move(block));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,7 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// v28: text decoration bits now include line-through in serialized wordStyles.
|
// v28: text decoration bits now include line-through in serialized wordStyles.
|
||||||
// v29: TextBlock word data stored as one flat arena (offset table + NUL-terminated
|
constexpr uint8_t SECTION_FILE_VERSION = 28;
|
||||||
// text blob) instead of length-prefixed strings and per-field arrays.
|
|
||||||
// v30: Arabic shaping changed both drawing and measurement (getTextAdvanceX now
|
|
||||||
// measures the shaped visual text); cached word positions from v29 no longer
|
|
||||||
// match what drawText renders.
|
|
||||||
constexpr uint8_t SECTION_FILE_VERSION = 31;
|
|
||||||
// Written into the version field while a build is in progress; patched to
|
// Written into the version field while a build is in progress; patched to
|
||||||
// SECTION_FILE_VERSION only when the build is finalized. An abandoned /
|
// SECTION_FILE_VERSION only when the build is finalized. An abandoned /
|
||||||
// crash-interrupted .bin therefore carries version 0, which loadSectionFile rejects
|
// crash-interrupted .bin therefore carries version 0, which loadSectionFile rejects
|
||||||
@@ -30,11 +25,7 @@ constexpr uint8_t SECTION_FILE_INCOMPLETE_VERSION = 0;
|
|||||||
// rebuilding in the background. Uses the same header layout as SECTION_FILE_VERSION,
|
// rebuilding in the background. Uses the same header layout as SECTION_FILE_VERSION,
|
||||||
// so finalized files are untouched by this feature; older firmware treats the sentinel
|
// so finalized files are untouched by this feature; older firmware treats the sentinel
|
||||||
// as an unknown version and rebuilds, which is a safe downgrade.
|
// as an unknown version and rebuilds, which is a safe downgrade.
|
||||||
// MUST change in lockstep with SECTION_FILE_VERSION: the sentinel IS the partial's
|
constexpr uint8_t SECTION_FILE_PARTIAL_VERSION = 0xFE;
|
||||||
// format version, so a stale-format partial otherwise passes the header check and
|
|
||||||
// only fails (noisily, via the block-decode error path) when a page is loaded.
|
|
||||||
// Derived so the pairing can't be forgotten: 0xFE for v28, 0xFD for v29, ...
|
|
||||||
constexpr uint8_t SECTION_FILE_PARTIAL_VERSION = 0xFE - (SECTION_FILE_VERSION - 28);
|
|
||||||
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) +
|
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) +
|
||||||
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(bool) + sizeof(bool) +
|
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(bool) + sizeof(bool) +
|
||||||
sizeof(uint8_t) + sizeof(bool) + sizeof(uint32_t) + sizeof(uint32_t) +
|
sizeof(uint8_t) + sizeof(bool) + sizeof(uint32_t) + sizeof(uint32_t) +
|
||||||
@@ -742,10 +733,10 @@ std::string Section::getTextFromSectionFile() {
|
|||||||
if (el->getTag() == TAG_PageLine) {
|
if (el->getTag() == TAG_PageLine) {
|
||||||
const auto& line = static_cast<const PageLine&>(*el);
|
const auto& line = static_cast<const PageLine&>(*el);
|
||||||
if (line.getBlock()) {
|
if (line.getBlock()) {
|
||||||
const auto& block = *line.getBlock();
|
const auto& words = line.getBlock()->getWords();
|
||||||
for (uint16_t i = 0; i < block.wordCount(); i++) {
|
for (const auto& w : words) {
|
||||||
if (!fullText.empty()) fullText += " ";
|
if (!fullText.empty()) fullText += " ";
|
||||||
fullText += block.wordText(i);
|
fullText += w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,6 @@ struct BlockStyle {
|
|||||||
bool isRtl = false; // true if resolved direction is RTL
|
bool isRtl = false; // true if resolved direction is RTL
|
||||||
bool directionDefined = false; // true if direction was explicitly set in CSS/HTML
|
bool directionDefined = false; // true if direction was explicitly set in CSS/HTML
|
||||||
|
|
||||||
// Set when this block was created by a <br> element. Used by startNewTextBlock to inject
|
|
||||||
// a full line-height gap when the <br> block stays empty (section-break use case).
|
|
||||||
// NOT propagated through getCombinedBlockStyle so it can't leak into sibling blocks.
|
|
||||||
bool fromBrElement = false;
|
|
||||||
|
|
||||||
// Combined insets (margin + padding)
|
// Combined insets (margin + padding)
|
||||||
[[nodiscard]] int16_t leftInset() const { return marginLeft + paddingLeft; }
|
[[nodiscard]] int16_t leftInset() const { return marginLeft + paddingLeft; }
|
||||||
[[nodiscard]] int16_t rightInset() const { return marginRight + paddingRight; }
|
[[nodiscard]] int16_t rightInset() const { return marginRight + paddingRight; }
|
||||||
@@ -97,9 +92,6 @@ struct BlockStyle {
|
|||||||
result.directionDefined = true;
|
result.directionDefined = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fromBrElement is consumed by startNewTextBlock when an empty <br> block
|
|
||||||
// is merged with the following paragraph; never propagate it further.
|
|
||||||
result.fromBrElement = false;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include "Epub/converters/DirectPixelWriter.h"
|
#include "Epub/converters/DirectPixelWriter.h"
|
||||||
#include "Epub/converters/ImageDecoderFactory.h"
|
#include "Epub/converters/ImageDecoderFactory.h"
|
||||||
|
|
||||||
@@ -31,54 +29,6 @@ std::string getCachePath(const std::string& imagePath) {
|
|||||||
return imagePath + ".pxc";
|
return imagePath + ".pxc";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool readValidCacheHeader(HalFile& cacheFile, const int expectedWidth, const int expectedHeight, uint16_t& cachedWidth,
|
|
||||||
uint16_t& cachedHeight) {
|
|
||||||
if (cacheFile.read(&cachedWidth, 2) != 2 || cacheFile.read(&cachedHeight, 2) != 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int widthDiff = abs(cachedWidth - expectedWidth);
|
|
||||||
const int heightDiff = abs(cachedHeight - expectedHeight);
|
|
||||||
if (widthDiff > 1 || heightDiff > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t bytesPerRow = (cachedWidth + 3) / 4;
|
|
||||||
const size_t expectedSize = 4 + bytesPerRow * cachedHeight;
|
|
||||||
return cacheFile.size() >= expectedSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pages are deserialized afresh on each visit. Keep a bounded, allocation-free
|
|
||||||
// record so an image that failed renders its placeholder directly for the rest
|
|
||||||
// of the reader session instead of paying another placeholder refresh and
|
|
||||||
// decode. The reader clears this on entry so transient memory/storage failures
|
|
||||||
// are retried.
|
|
||||||
constexpr size_t MAX_SESSION_IMAGE_FAILURES = 16;
|
|
||||||
uint64_t failedImageHashes[MAX_SESSION_IMAGE_FAILURES];
|
|
||||||
size_t failedImageCount = 0;
|
|
||||||
|
|
||||||
uint64_t imagePathHash(const std::string& path) {
|
|
||||||
uint64_t hash = 14695981039346656037ull;
|
|
||||||
for (const char c : path) {
|
|
||||||
hash ^= static_cast<uint8_t>(c);
|
|
||||||
hash *= 1099511628211ull;
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool imageFailedThisSession(const std::string& path) {
|
|
||||||
const uint64_t hash = imagePathHash(path);
|
|
||||||
for (size_t i = 0; i < failedImageCount; i++) {
|
|
||||||
if (failedImageHashes[i] == hash) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rememberImageFailure(const std::string& path) {
|
|
||||||
if (failedImageCount == MAX_SESSION_IMAGE_FAILURES || imageFailedThisSession(path)) return;
|
|
||||||
failedImageHashes[failedImageCount++] = imagePathHash(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, int y, int expectedWidth,
|
bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, int y, int expectedWidth,
|
||||||
int expectedHeight) {
|
int expectedHeight) {
|
||||||
HalFile cacheFile;
|
HalFile cacheFile;
|
||||||
@@ -87,8 +37,16 @@ bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x,
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint16_t cachedWidth, cachedHeight;
|
uint16_t cachedWidth, cachedHeight;
|
||||||
if (!readValidCacheHeader(cacheFile, expectedWidth, expectedHeight, cachedWidth, cachedHeight)) {
|
if (cacheFile.read(&cachedWidth, 2) != 2 || cacheFile.read(&cachedHeight, 2) != 2) {
|
||||||
LOG_ERR("IMG", "Invalid image cache: %s", cachePath.c_str());
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify dimensions are close (allow 1 pixel tolerance for rounding differences)
|
||||||
|
int widthDiff = abs(cachedWidth - expectedWidth);
|
||||||
|
int heightDiff = abs(cachedHeight - expectedHeight);
|
||||||
|
if (widthDiff > 1 || heightDiff > 1) {
|
||||||
|
LOG_ERR("IMG", "Cache dimension mismatch: %dx%d vs %dx%d", cachedWidth, cachedHeight, expectedWidth,
|
||||||
|
expectedHeight);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,28 +119,6 @@ bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x,
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool ImageBlock::hasValidCache() const {
|
|
||||||
const auto cachePath = getCachePath(imagePath);
|
|
||||||
HalFile cacheFile;
|
|
||||||
if (!Storage.openFileForRead("IMG", cachePath, cacheFile)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t cachedWidth, cachedHeight;
|
|
||||||
return readValidCacheHeader(cacheFile, width, height, cachedWidth, cachedHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ImageBlock::needsDecode() const { return !imageFailedThisSession(imagePath) && !hasValidCache(); }
|
|
||||||
|
|
||||||
void ImageBlock::clearSessionRenderFailures() { failedImageCount = 0; }
|
|
||||||
|
|
||||||
void ImageBlock::renderPlaceholder(GfxRenderer& renderer, const int x, const int y) const {
|
|
||||||
renderer.fillRect(x, y, width, height, true);
|
|
||||||
if (width > 2 && height > 2) {
|
|
||||||
renderer.fillRect(x + 1, y + 1, width - 2, height - 2, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
|
void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
|
||||||
// The font-prewarm scan pass only accumulates glyphs; an image contributes
|
// The font-prewarm scan pass only accumulates glyphs; an image contributes
|
||||||
// none, and its DirectPixelWriter output bypasses the renderer's scan-mode
|
// none, and its DirectPixelWriter output bypasses the renderer's scan-mode
|
||||||
@@ -214,11 +150,6 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageFailedThisSession(imagePath)) {
|
|
||||||
renderPlaceholder(renderer, x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to render from cache first
|
// Try to render from cache first
|
||||||
std::string cachePath = getCachePath(imagePath);
|
std::string cachePath = getCachePath(imagePath);
|
||||||
if (renderFromCache(renderer, cachePath, x, y, width, height)) {
|
if (renderFromCache(renderer, cachePath, x, y, width, height)) {
|
||||||
@@ -230,8 +161,6 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
|
|||||||
HalFile file;
|
HalFile file;
|
||||||
if (!Storage.openFileForRead("IMG", imagePath, file)) {
|
if (!Storage.openFileForRead("IMG", imagePath, file)) {
|
||||||
LOG_ERR("IMG", "Image file not found: %s", imagePath.c_str());
|
LOG_ERR("IMG", "Image file not found: %s", imagePath.c_str());
|
||||||
rememberImageFailure(imagePath);
|
|
||||||
renderPlaceholder(renderer, x, y);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
size_t fileSize = file.size();
|
size_t fileSize = file.size();
|
||||||
@@ -239,8 +168,6 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
|
|||||||
|
|
||||||
if (fileSize == 0) {
|
if (fileSize == 0) {
|
||||||
LOG_ERR("IMG", "Image file is empty: %s", imagePath.c_str());
|
LOG_ERR("IMG", "Image file is empty: %s", imagePath.c_str());
|
||||||
rememberImageFailure(imagePath);
|
|
||||||
renderPlaceholder(renderer, x, y);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,8 +187,6 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
|
|||||||
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(imagePath);
|
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(imagePath);
|
||||||
if (!decoder) {
|
if (!decoder) {
|
||||||
LOG_ERR("IMG", "No decoder found for image: %s", imagePath.c_str());
|
LOG_ERR("IMG", "No decoder found for image: %s", imagePath.c_str());
|
||||||
rememberImageFailure(imagePath);
|
|
||||||
renderPlaceholder(renderer, x, y);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,8 +195,6 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
|
|||||||
bool success = decoder->decodeToFramebuffer(imagePath, renderer, config);
|
bool success = decoder->decodeToFramebuffer(imagePath, renderer, config);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
LOG_ERR("IMG", "Failed to decode image: %s", imagePath.c_str());
|
LOG_ERR("IMG", "Failed to decode image: %s", imagePath.c_str());
|
||||||
rememberImageFailure(imagePath);
|
|
||||||
renderPlaceholder(renderer, x, y);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ class ImageBlock final : public Block {
|
|||||||
int16_t getHeight() const { return height; }
|
int16_t getHeight() const { return height; }
|
||||||
|
|
||||||
bool imageExists() const;
|
bool imageExists() const;
|
||||||
bool hasValidCache() const;
|
|
||||||
bool needsDecode() const;
|
|
||||||
void renderPlaceholder(GfxRenderer& renderer, int x, int y) const;
|
|
||||||
static void clearSessionRenderFailures();
|
|
||||||
|
|
||||||
BlockType getType() override { return IMAGE_BLOCK; }
|
BlockType getType() override { return IMAGE_BLOCK; }
|
||||||
bool isEmpty() override { return false; }
|
bool isEmpty() override { return false; }
|
||||||
|
|||||||
@@ -3,114 +3,19 @@
|
|||||||
#include <BidiUtils.h>
|
#include <BidiUtils.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <Memory.h>
|
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
size_t TextBlock::arenaSize(const uint16_t wordCount, const bool hasFocus, const uint16_t textBytes) {
|
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
||||||
// Layout documented in TextBlock.h: 16-bit arrays first, then 8-bit arrays, then text.
|
|
||||||
size_t size = static_cast<size_t>(wordCount) * (sizeof(uint16_t) + sizeof(int16_t) + sizeof(uint8_t));
|
|
||||||
if (hasFocus) {
|
|
||||||
size += static_cast<size_t>(wordCount) * (sizeof(uint16_t) + sizeof(uint8_t));
|
|
||||||
}
|
|
||||||
return size + textBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextBlock::bindArenaPointers() {
|
|
||||||
uint8_t* base = arena.get();
|
|
||||||
const size_t wc = numWords;
|
|
||||||
textOffArr = reinterpret_cast<const uint16_t*>(base);
|
|
||||||
xposArr = reinterpret_cast<const int16_t*>(base + wc * 2);
|
|
||||||
size_t off = wc * 4;
|
|
||||||
if (focusPresent) {
|
|
||||||
focusSuffixXArr = reinterpret_cast<const uint16_t*>(base + off);
|
|
||||||
off += wc * 2;
|
|
||||||
}
|
|
||||||
stylesArr = base + off;
|
|
||||||
off += wc;
|
|
||||||
if (focusPresent) {
|
|
||||||
focusBoundaryArr = base + off;
|
|
||||||
off += wc;
|
|
||||||
}
|
|
||||||
textArr = reinterpret_cast<const char*>(base + off);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextBlock::TextBlock(const std::vector<std::string>& words, const std::vector<int16_t>& wordXpos,
|
|
||||||
const std::vector<EpdFontFamily::Style>& wordStyles, const std::vector<uint8_t>& focusBoundary,
|
|
||||||
const std::vector<uint16_t>& focusSuffixX, const BlockStyle& blockStyle)
|
|
||||||
: blockStyle(blockStyle) {
|
|
||||||
// Focus annotations are optional: empty vectors mean no word in this block has a split.
|
// Focus annotations are optional: empty vectors mean no word in this block has a split.
|
||||||
// When present, they must be sized in lockstep with words[].
|
// When present, they must be sized in lockstep with words[].
|
||||||
const bool hasFocus = !focusBoundary.empty();
|
const bool hasFocus = !wordFocusBoundary.empty();
|
||||||
if (words.size() != wordXpos.size() || words.size() != wordStyles.size() || words.size() > 10000 ||
|
if (words.size() != wordXpos.size() || words.size() != wordStyles.size() ||
|
||||||
(hasFocus && (words.size() != focusBoundary.size() || words.size() != focusSuffixX.size()))) {
|
(hasFocus && (words.size() != wordFocusBoundary.size() || words.size() != wordFocusSuffixX.size()))) {
|
||||||
LOG_ERR("TXB", "Construction failed: size mismatch (words=%u, xpos=%u, styles=%u, boundary=%u, suffixX=%u)",
|
LOG_ERR("TXB", "Render skipped: size mismatch (words=%u, xpos=%u, styles=%u, boundary=%u, suffixX=%u)\n",
|
||||||
static_cast<uint32_t>(words.size()), static_cast<uint32_t>(wordXpos.size()),
|
(uint32_t)words.size(), (uint32_t)wordXpos.size(), (uint32_t)wordStyles.size(),
|
||||||
static_cast<uint32_t>(wordStyles.size()), static_cast<uint32_t>(focusBoundary.size()),
|
(uint32_t)wordFocusBoundary.size(), (uint32_t)wordFocusSuffixX.size());
|
||||||
static_cast<uint32_t>(focusSuffixX.size()));
|
|
||||||
isValid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
numWords = static_cast<uint16_t>(words.size());
|
|
||||||
focusPresent = hasFocus;
|
|
||||||
if (numWords == 0) {
|
|
||||||
return; // valid empty block, no arena
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass 1: total text size, one NUL per word. A line is at most a physical
|
|
||||||
// row of the page, so uint16_t offsets are ample; reject anything larger.
|
|
||||||
size_t totalText = 0;
|
|
||||||
for (const auto& w : words) totalText += w.size() + 1;
|
|
||||||
if (totalText > UINT16_MAX) {
|
|
||||||
LOG_ERR("TXB", "Construction failed: text size %u exceeds arena limit", static_cast<uint32_t>(totalText));
|
|
||||||
numWords = 0;
|
|
||||||
focusPresent = false;
|
|
||||||
isValid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
textBytes = static_cast<uint16_t>(totalText);
|
|
||||||
|
|
||||||
const size_t size = arenaSize(numWords, focusPresent, textBytes);
|
|
||||||
arena = makeUniqueNoThrow<uint8_t[]>(size);
|
|
||||||
if (!arena) {
|
|
||||||
LOG_ERR("TXB", "OOM: arena %u bytes", static_cast<uint32_t>(size));
|
|
||||||
numWords = 0;
|
|
||||||
textBytes = 0;
|
|
||||||
focusPresent = false;
|
|
||||||
isValid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bindArenaPointers();
|
|
||||||
|
|
||||||
// Pass 2: fill. Mutable aliases of the const views bound above.
|
|
||||||
auto* textOff = const_cast<uint16_t*>(textOffArr);
|
|
||||||
auto* xpos = const_cast<int16_t*>(xposArr);
|
|
||||||
auto* styles = const_cast<uint8_t*>(stylesArr);
|
|
||||||
auto* text = const_cast<char*>(textArr);
|
|
||||||
uint16_t off = 0;
|
|
||||||
for (uint16_t i = 0; i < numWords; i++) {
|
|
||||||
textOff[i] = off;
|
|
||||||
xpos[i] = wordXpos[i];
|
|
||||||
styles[i] = static_cast<uint8_t>(wordStyles[i]);
|
|
||||||
memcpy(text + off, words[i].data(), words[i].size());
|
|
||||||
off += static_cast<uint16_t>(words[i].size());
|
|
||||||
text[off++] = '\0';
|
|
||||||
}
|
|
||||||
if (focusPresent) {
|
|
||||||
auto* suffixX = const_cast<uint16_t*>(focusSuffixXArr);
|
|
||||||
auto* boundary = const_cast<uint8_t*>(focusBoundaryArr);
|
|
||||||
for (uint16_t i = 0; i < numWords; i++) {
|
|
||||||
suffixX[i] = focusSuffixX[i];
|
|
||||||
boundary[i] = focusBoundary[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
|
||||||
if (!isValid) {
|
|
||||||
LOG_ERR("TXB", "Render skipped: invalid block");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,13 +54,12 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (uint16_t i = 0; i < numWords; i++) {
|
for (size_t i = 0; i < words.size(); i++) {
|
||||||
const char* word = wordText(i);
|
const int wordX = wordXpos[i] + x;
|
||||||
const int wordX = xposArr[i] + x;
|
const EpdFontFamily::Style currentStyle = wordStyles[i];
|
||||||
const EpdFontFamily::Style currentStyle = wordStyle(i);
|
const auto baseDir = static_cast<BidiUtils::BidiBaseDir>(
|
||||||
const auto baseDir =
|
BidiUtils::detectParagraphLevel(words[i].c_str(), blockStyle.isRtl ? 1 : 0));
|
||||||
static_cast<BidiUtils::BidiBaseDir>(BidiUtils::detectParagraphLevel(word, blockStyle.isRtl ? 1 : 0));
|
const uint8_t boundary = hasFocus ? wordFocusBoundary[i] : 0;
|
||||||
const uint8_t boundary = focusBoundary(i);
|
|
||||||
|
|
||||||
// SUP/SUB shift the baseline passed to drawText; the glyph is also scaled 50% inside
|
// SUP/SUB shift the baseline passed to drawText; the glyph is also scaled 50% inside
|
||||||
// drawText, so these offsets are chosen relative to the full-size ascender:
|
// drawText, so these offsets are chosen relative to the full-size ascender:
|
||||||
@@ -178,15 +82,14 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
|
|||||||
static_assert(sizeof(boldBuf) >= MAX_FOCUS_PREFIX_BYTES,
|
static_assert(sizeof(boldBuf) >= MAX_FOCUS_PREFIX_BYTES,
|
||||||
"boldBuf too small for max focus prefix (9 codepoints * 4 UTF-8 bytes + null)");
|
"boldBuf too small for max focus prefix (9 codepoints * 4 UTF-8 bytes + null)");
|
||||||
const auto boldStyle = static_cast<EpdFontFamily::Style>(currentStyle | EpdFontFamily::BOLD);
|
const auto boldStyle = static_cast<EpdFontFamily::Style>(currentStyle | EpdFontFamily::BOLD);
|
||||||
const size_t boldLen =
|
const size_t boldLen = std::min<size_t>({static_cast<size_t>(boundary), words[i].size(), sizeof(boldBuf) - 1});
|
||||||
std::min<size_t>({static_cast<size_t>(boundary), static_cast<size_t>(wordTextLen(i)), sizeof(boldBuf) - 1});
|
memcpy(boldBuf, words[i].c_str(), boldLen);
|
||||||
memcpy(boldBuf, word, boldLen);
|
|
||||||
boldBuf[boldLen] = '\0';
|
boldBuf[boldLen] = '\0';
|
||||||
renderer.drawText(fontId, wordX, wordY, boldBuf, true, boldStyle, baseDir);
|
renderer.drawText(fontId, wordX, wordY, boldBuf, true, boldStyle, baseDir);
|
||||||
const int suffixX = wordX + focusSuffixXArr[i];
|
const int suffixX = wordX + wordFocusSuffixX[i];
|
||||||
renderer.drawText(fontId, suffixX, wordY, word + boldLen, true, currentStyle, baseDir);
|
renderer.drawText(fontId, suffixX, wordY, words[i].c_str() + boldLen, true, currentStyle, baseDir);
|
||||||
} else {
|
} else {
|
||||||
renderer.drawText(fontId, wordX, wordY, word, true, currentStyle, baseDir);
|
renderer.drawText(fontId, wordX, wordY, words[i].c_str(), true, currentStyle, baseDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scanning) {
|
if (scanning) {
|
||||||
@@ -194,17 +97,18 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (EpdFontFamily::hasTextDecoration(currentStyle)) {
|
if (EpdFontFamily::hasTextDecoration(currentStyle)) {
|
||||||
|
const std::string& w = words[i];
|
||||||
int lineStartX = wordX;
|
int lineStartX = wordX;
|
||||||
int lineWidth = renderer.getTextWidth(fontId, word, currentStyle, baseDir);
|
int lineWidth = renderer.getTextWidth(fontId, w.c_str(), currentStyle, baseDir);
|
||||||
|
|
||||||
if ((currentStyle & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0) {
|
if ((currentStyle & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0) {
|
||||||
lineWidth = (lineWidth + 1) / 2;
|
lineWidth = (lineWidth + 1) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not decorate the synthetic em-space used for paragraph indentation.
|
// Do not decorate the synthetic em-space used for paragraph indentation.
|
||||||
if (wordTextLen(i) >= 3 && static_cast<uint8_t>(word[0]) == 0xE2 && static_cast<uint8_t>(word[1]) == 0x80 &&
|
if (w.size() >= 3 && static_cast<uint8_t>(w[0]) == 0xE2 && static_cast<uint8_t>(w[1]) == 0x80 &&
|
||||||
static_cast<uint8_t>(word[2]) == 0x83) {
|
static_cast<uint8_t>(w[2]) == 0x83) {
|
||||||
const char* visibleText = word + 3;
|
const char* visibleText = w.c_str() + 3;
|
||||||
lineStartX += renderer.getTextAdvanceX(fontId, "\xe2\x80\x83", currentStyle);
|
lineStartX += renderer.getTextAdvanceX(fontId, "\xe2\x80\x83", currentStyle);
|
||||||
lineWidth = renderer.getTextWidth(fontId, visibleText, currentStyle, baseDir);
|
lineWidth = renderer.getTextWidth(fontId, visibleText, currentStyle, baseDir);
|
||||||
if ((currentStyle & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0) {
|
if ((currentStyle & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0) {
|
||||||
@@ -236,23 +140,29 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TextBlock::serialize(HalFile& file) const {
|
bool TextBlock::serialize(HalFile& file) const {
|
||||||
if (!isValid) {
|
// Focus annotations are optional; vectors are either empty (no splits in this block)
|
||||||
LOG_ERR("TXB", "Serialization failed: invalid block");
|
// or sized in lockstep with words[].
|
||||||
|
const bool hasFocus = !wordFocusBoundary.empty();
|
||||||
|
if (words.size() != wordXpos.size() || words.size() != wordStyles.size() ||
|
||||||
|
(hasFocus && (words.size() != wordFocusBoundary.size() || words.size() != wordFocusSuffixX.size()))) {
|
||||||
|
LOG_ERR("TXB", "Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u, boundary=%u, suffixX=%u)\n",
|
||||||
|
static_cast<uint32_t>(words.size()), static_cast<uint32_t>(wordXpos.size()),
|
||||||
|
static_cast<uint32_t>(wordStyles.size()), static_cast<uint32_t>(wordFocusBoundary.size()),
|
||||||
|
static_cast<uint32_t>(wordFocusSuffixX.size()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Word data: scalars, then the arena verbatim -- its in-memory layout is
|
// Word data
|
||||||
// exactly the on-disk layout (see TextBlock.h), so one write covers all
|
serialization::writePod(file, static_cast<uint16_t>(words.size()));
|
||||||
// per-word arrays and the text blob.
|
for (const auto& w : words) serialization::writeString(file, w);
|
||||||
serialization::writePod(file, numWords);
|
for (auto x : wordXpos) serialization::writePod(file, x);
|
||||||
serialization::writePod(file, static_cast<uint8_t>(focusPresent ? 1 : 0));
|
for (auto s : wordStyles) serialization::writePod(file, s);
|
||||||
serialization::writePod(file, textBytes);
|
// Focus block: 1-byte presence flag, followed by per-word vectors only when present.
|
||||||
if (numWords > 0) {
|
// Saves 3 bytes/word when focus reading is disabled or no word on this line was split.
|
||||||
const size_t size = arenaSize(numWords, focusPresent, textBytes);
|
serialization::writePod(file, static_cast<uint8_t>(hasFocus ? 1 : 0));
|
||||||
if (file.write(arena.get(), size) != size) {
|
if (hasFocus) {
|
||||||
LOG_ERR("TXB", "Serialization failed: arena write (%u bytes)", static_cast<uint32_t>(size));
|
for (auto b : wordFocusBoundary) serialization::writePod(file, b);
|
||||||
return false;
|
for (auto sx : wordFocusSuffixX) serialization::writePod(file, sx);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style (alignment + margins/padding/indent)
|
// Style (alignment + margins/padding/indent)
|
||||||
@@ -276,64 +186,41 @@ bool TextBlock::serialize(HalFile& file) const {
|
|||||||
|
|
||||||
std::unique_ptr<TextBlock> TextBlock::deserialize(HalFile& file) {
|
std::unique_ptr<TextBlock> TextBlock::deserialize(HalFile& file) {
|
||||||
uint16_t wc;
|
uint16_t wc;
|
||||||
uint8_t hasFocus;
|
std::vector<std::string> words;
|
||||||
uint16_t textBytes;
|
std::vector<int16_t> wordXpos;
|
||||||
serialization::readPod(file, wc);
|
std::vector<EpdFontFamily::Style> wordStyles;
|
||||||
serialization::readPod(file, hasFocus);
|
std::vector<uint8_t> wordFocusBoundary;
|
||||||
serialization::readPod(file, textBytes);
|
std::vector<uint16_t> wordFocusSuffixX;
|
||||||
|
BlockStyle blockStyle;
|
||||||
|
|
||||||
// Sanity checks: cap the arena allocation and reject impossible geometry
|
// Word count
|
||||||
// (every word carries at least its NUL terminator).
|
serialization::readPod(file, wc);
|
||||||
|
|
||||||
|
// Sanity check: prevent allocation of unreasonably large vectors (max 10000 words per block)
|
||||||
if (wc > 10000) {
|
if (wc > 10000) {
|
||||||
LOG_ERR("TXB", "Deserialization failed: word count %u exceeds maximum", wc);
|
LOG_ERR("TXB", "Deserialization failed: word count %u exceeds maximum", wc);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if ((wc == 0 && textBytes != 0) || (wc > 0 && textBytes < wc)) {
|
|
||||||
LOG_ERR("TXB", "Deserialization failed: bad text size %u for %u words", textBytes, wc);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<TextBlock> block(new (std::nothrow) TextBlock());
|
// Word data
|
||||||
if (!block) {
|
words.resize(wc);
|
||||||
LOG_ERR("TXB", "OOM: TextBlock");
|
wordXpos.resize(wc);
|
||||||
return nullptr;
|
wordStyles.resize(wc);
|
||||||
}
|
for (auto& w : words) serialization::readString(file, w);
|
||||||
block->numWords = wc;
|
for (auto& x : wordXpos) serialization::readPod(file, x);
|
||||||
block->textBytes = textBytes;
|
for (auto& s : wordStyles) serialization::readPod(file, s);
|
||||||
block->focusPresent = hasFocus != 0;
|
// Focus block: presence flag, then vectors only if present. Empty vectors when absent
|
||||||
|
// signal "no splits in this block" to render() (zero per-word RAM cost).
|
||||||
if (wc > 0) {
|
uint8_t hasFocus;
|
||||||
const size_t size = arenaSize(wc, block->focusPresent, textBytes);
|
serialization::readPod(file, hasFocus);
|
||||||
block->arena = makeUniqueNoThrow<uint8_t[]>(size);
|
if (hasFocus) {
|
||||||
if (!block->arena) {
|
wordFocusBoundary.resize(wc);
|
||||||
LOG_ERR("TXB", "OOM: arena %u bytes", static_cast<uint32_t>(size));
|
wordFocusSuffixX.resize(wc);
|
||||||
return nullptr;
|
for (auto& b : wordFocusBoundary) serialization::readPod(file, b);
|
||||||
}
|
for (auto& sx : wordFocusSuffixX) serialization::readPod(file, sx);
|
||||||
if (file.read(block->arena.get(), size) != size) {
|
|
||||||
LOG_ERR("TXB", "Deserialization failed: arena read (%u bytes)", static_cast<uint32_t>(size));
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
block->bindArenaPointers();
|
|
||||||
|
|
||||||
// Validate offsets before anything dereferences wordText(): offset 0 first,
|
|
||||||
// strictly increasing, in bounds, and every word NUL-terminated (word i ends
|
|
||||||
// at the byte before offset i+1; the last word at the last text byte).
|
|
||||||
const uint16_t* textOff = block->textOffArr;
|
|
||||||
const char* text = block->textArr;
|
|
||||||
if (textOff[0] != 0 || text[textBytes - 1] != '\0') {
|
|
||||||
LOG_ERR("TXB", "Deserialization failed: corrupt text layout");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
for (uint16_t i = 1; i < wc; i++) {
|
|
||||||
if (textOff[i] <= textOff[i - 1] || textOff[i] >= textBytes || text[textOff[i] - 1] != '\0') {
|
|
||||||
LOG_ERR("TXB", "Deserialization failed: corrupt word offset %u", i);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style (alignment + margins/padding/indent)
|
// Style (alignment + margins/padding/indent)
|
||||||
BlockStyle& blockStyle = block->blockStyle;
|
|
||||||
serialization::readPod(file, blockStyle.alignment);
|
serialization::readPod(file, blockStyle.alignment);
|
||||||
serialization::readPod(file, blockStyle.textAlignDefined);
|
serialization::readPod(file, blockStyle.textAlignDefined);
|
||||||
serialization::readPod(file, blockStyle.marginTop);
|
serialization::readPod(file, blockStyle.marginTop);
|
||||||
@@ -349,5 +236,7 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(HalFile& file) {
|
|||||||
serialization::readPod(file, blockStyle.isRtl);
|
serialization::readPod(file, blockStyle.isRtl);
|
||||||
serialization::readPod(file, blockStyle.directionDefined);
|
serialization::readPod(file, blockStyle.directionDefined);
|
||||||
|
|
||||||
return block;
|
return std::unique_ptr<TextBlock>(new TextBlock(std::move(words), std::move(wordXpos), std::move(wordStyles),
|
||||||
|
std::move(wordFocusBoundary), std::move(wordFocusSuffixX),
|
||||||
|
blockStyle));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,83 +9,42 @@
|
|||||||
#include "Block.h"
|
#include "Block.h"
|
||||||
#include "BlockStyle.h"
|
#include "BlockStyle.h"
|
||||||
|
|
||||||
// Represents a line of text on a page.
|
// Represents a line of text on a page
|
||||||
//
|
|
||||||
// All per-word data lives in ONE flat heap allocation (the arena) instead of
|
|
||||||
// six parallel vectors: a resident page holds ~25-30 of these blocks, and the
|
|
||||||
// vector-of-string layout cost ~250 throwing allocations per page load, which
|
|
||||||
// was the primary driver of heap fragmentation on the ESP32-C3.
|
|
||||||
//
|
|
||||||
// Arena layout, in order (2-byte alignment holds by construction: all 16-bit
|
|
||||||
// arrays come first and the arena base is allocator-aligned; RISC-V faults on
|
|
||||||
// unaligned multi-byte access):
|
|
||||||
// uint16_t textOff[wordCount] byte offset of word i's text in text[]
|
|
||||||
// int16_t xpos[wordCount]
|
|
||||||
// uint16_t focusSuffixX[wordCount] present only when focusPresent
|
|
||||||
// uint8_t styles[wordCount]
|
|
||||||
// uint8_t focusBoundary[wordCount] present only when focusPresent
|
|
||||||
// char text[textBytes] all words back to back, NUL-terminated
|
|
||||||
//
|
|
||||||
// Each word is stored NUL-terminated so render() can hand `text + textOff[i]`
|
|
||||||
// straight to C APIs (drawText) with no std::string materialization.
|
|
||||||
//
|
|
||||||
// Focus split semantics (unchanged from the vector layout): boundary N > 0
|
|
||||||
// means the first N bytes of word i render bold, the remainder in the base
|
|
||||||
// style. N is bounded to 9 codepoints (<= 36 UTF-8 bytes) by the clamp in
|
|
||||||
// ParsedText::addWord. focusSuffixX is the pre-computed pixel offset from the
|
|
||||||
// word start to the regular suffix. Both arrays are omitted from the arena
|
|
||||||
// entirely when no word on the line has a split (zero per-word RAM cost when
|
|
||||||
// focus reading is disabled).
|
|
||||||
class TextBlock final : public Block {
|
class TextBlock final : public Block {
|
||||||
private:
|
private:
|
||||||
|
std::vector<std::string> words;
|
||||||
|
std::vector<int16_t> wordXpos;
|
||||||
|
std::vector<EpdFontFamily::Style> wordStyles;
|
||||||
|
// Per-word focus boundary: N > 0 means the first N bytes of words[i] are rendered bold,
|
||||||
|
// the remainder in the base style. 0 means no split (whole word uses wordStyles[i]).
|
||||||
|
// N encodes the bold PREFIX length only — bounded to 9 codepoints (≤36 UTF-8 bytes) by
|
||||||
|
// FOCUS_READING_PERCENT's 1..9 clamp in ParsedText::addWord, so it always fits in uint8_t.
|
||||||
|
// Vector is empty when no focus splits exist anywhere in the block (zero per-word RAM cost
|
||||||
|
// when focus reading is disabled, or on lines that happen to contain no splittable words).
|
||||||
|
std::vector<uint8_t> wordFocusBoundary;
|
||||||
|
// Pre-computed pixel offset from word start to the regular suffix, stored when boundary > 0.
|
||||||
|
// Eliminates getTextAdvanceX from the render path. 0 when boundary == 0.
|
||||||
|
// Empty in lockstep with wordFocusBoundary.
|
||||||
|
std::vector<uint16_t> wordFocusSuffixX;
|
||||||
BlockStyle blockStyle;
|
BlockStyle blockStyle;
|
||||||
uint16_t numWords = 0;
|
|
||||||
uint16_t textBytes = 0; // total size of the text region, including NULs
|
|
||||||
bool focusPresent = false;
|
|
||||||
bool isValid = true;
|
|
||||||
// The ONLY allocation: makeUniqueNoThrow, so OOM yields an invalid block
|
|
||||||
// instead of abort() (bare new is not nothrow with -fno-exceptions).
|
|
||||||
std::unique_ptr<uint8_t[]> arena;
|
|
||||||
// Typed views into the arena, bound once after the arena is filled. All
|
|
||||||
// 16-bit bases sit at even offsets, so direct dereference is alignment-safe.
|
|
||||||
const uint16_t* textOffArr = nullptr;
|
|
||||||
const int16_t* xposArr = nullptr;
|
|
||||||
const uint16_t* focusSuffixXArr = nullptr; // null when !focusPresent
|
|
||||||
const uint8_t* stylesArr = nullptr;
|
|
||||||
const uint8_t* focusBoundaryArr = nullptr; // null when !focusPresent
|
|
||||||
const char* textArr = nullptr;
|
|
||||||
|
|
||||||
TextBlock() = default; // deserialize() fills the fields directly
|
|
||||||
static size_t arenaSize(uint16_t wordCount, bool hasFocus, uint16_t textBytes);
|
|
||||||
void bindArenaPointers();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Flatten-on-construct: copies the layout-time vectors into the arena; the
|
explicit TextBlock(std::vector<std::string> words, std::vector<int16_t> word_xpos,
|
||||||
// vectors die with the caller. On arena OOM the block is empty and valid()
|
std::vector<EpdFontFamily::Style> word_styles, std::vector<uint8_t> focus_boundary,
|
||||||
// is false -- callers must check and fail the line instead of using it.
|
std::vector<uint16_t> focus_suffix_x, const BlockStyle& blockStyle = BlockStyle())
|
||||||
explicit TextBlock(const std::vector<std::string>& words, const std::vector<int16_t>& wordXpos,
|
: words(std::move(words)),
|
||||||
const std::vector<EpdFontFamily::Style>& wordStyles, const std::vector<uint8_t>& focusBoundary,
|
wordXpos(std::move(word_xpos)),
|
||||||
const std::vector<uint16_t>& focusSuffixX, const BlockStyle& blockStyle = BlockStyle());
|
wordStyles(std::move(word_styles)),
|
||||||
|
wordFocusBoundary(std::move(focus_boundary)),
|
||||||
|
wordFocusSuffixX(std::move(focus_suffix_x)),
|
||||||
|
blockStyle(blockStyle) {}
|
||||||
~TextBlock() override = default;
|
~TextBlock() override = default;
|
||||||
TextBlock(const TextBlock&) = delete;
|
|
||||||
TextBlock& operator=(const TextBlock&) = delete;
|
|
||||||
|
|
||||||
void setBlockStyle(const BlockStyle& blockStyle) { this->blockStyle = blockStyle; }
|
void setBlockStyle(const BlockStyle& blockStyle) { this->blockStyle = blockStyle; }
|
||||||
const BlockStyle& getBlockStyle() const { return blockStyle; }
|
const BlockStyle& getBlockStyle() const { return blockStyle; }
|
||||||
bool isEmpty() override { return numWords == 0; }
|
const std::vector<std::string>& getWords() const { return words; }
|
||||||
bool valid() const { return isValid; }
|
bool isEmpty() override { return words.empty(); }
|
||||||
uint16_t wordCount() const { return numWords; }
|
size_t wordCount() const { return words.size(); }
|
||||||
// NUL-terminated by construction; safe to pass to C APIs directly.
|
// given a renderer works out where to break the words into lines
|
||||||
const char* wordText(const uint16_t i) const { return textArr + textOffArr[i]; }
|
|
||||||
uint16_t wordTextLen(const uint16_t i) const {
|
|
||||||
const uint16_t end = (i + 1 < numWords) ? textOffArr[i + 1] : textBytes;
|
|
||||||
return end - textOffArr[i] - 1; // exclude the NUL
|
|
||||||
}
|
|
||||||
int16_t wordXpos(const uint16_t i) const { return xposArr[i]; }
|
|
||||||
EpdFontFamily::Style wordStyle(const uint16_t i) const { return static_cast<EpdFontFamily::Style>(stylesArr[i]); }
|
|
||||||
uint8_t focusBoundary(const uint16_t i) const { return focusPresent ? focusBoundaryArr[i] : 0; }
|
|
||||||
uint16_t focusSuffixX(const uint16_t i) const { return focusPresent ? focusSuffixXArr[i] : 0; }
|
|
||||||
|
|
||||||
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
|
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
|
||||||
BlockType getType() override { return TEXT_BLOCK; }
|
BlockType getType() override { return TEXT_BLOCK; }
|
||||||
bool serialize(HalFile& file) const;
|
bool serialize(HalFile& file) const;
|
||||||
|
|||||||
@@ -99,58 +99,24 @@ int bytesPerPixelFromType(int pixelType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int packedRowBytes(int srcWidth, int bitsPerSample) { return (srcWidth * bitsPerSample + 7) / 8; }
|
int requiredPngInternalBufferBytes(int srcWidth, int pixelType) {
|
||||||
|
|
||||||
int requiredPngInternalBufferBytes(int srcWidth, int pixelType, int bitsPerSample) {
|
|
||||||
// +1 filter byte per scanline, *2 for current+previous lines, +32 for alignment margin.
|
// +1 filter byte per scanline, *2 for current+previous lines, +32 for alignment margin.
|
||||||
int pitch = srcWidth * bytesPerPixelFromType(pixelType);
|
int pitch = srcWidth * bytesPerPixelFromType(pixelType);
|
||||||
if ((pixelType == PNG_PIXEL_GRAYSCALE || pixelType == PNG_PIXEL_INDEXED) && bitsPerSample < 8) {
|
|
||||||
pitch = packedRowBytes(srcWidth, bitsPerSample);
|
|
||||||
}
|
|
||||||
return ((pitch + 1) * 2) + 32;
|
return ((pitch + 1) * 2) + 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSupportedBitDepth(int pixelType, int bitsPerSample) {
|
|
||||||
if (bitsPerSample == 8) return true;
|
|
||||||
if (bitsPerSample != 1 && bitsPerSample != 2 && bitsPerSample != 4) return false;
|
|
||||||
return pixelType == PNG_PIXEL_GRAYSCALE || pixelType == PNG_PIXEL_INDEXED;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t readPackedSample(const uint8_t* pixels, int x, int bitsPerSample) {
|
|
||||||
if (bitsPerSample == 8) return pixels[x];
|
|
||||||
|
|
||||||
const int bitOffset = x * bitsPerSample;
|
|
||||||
const int shift = 8 - bitsPerSample - (bitOffset & 7);
|
|
||||||
const uint8_t mask = (1U << bitsPerSample) - 1;
|
|
||||||
return (pixels[bitOffset >> 3] >> shift) & mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t expandSampleToByte(uint8_t sample, int bitsPerSample) {
|
|
||||||
if (bitsPerSample == 8) return sample;
|
|
||||||
const uint8_t maxSample = (1U << bitsPerSample) - 1;
|
|
||||||
return static_cast<uint8_t>((sample * 255U) / maxSample);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert entire source line to grayscale with alpha blending to white background.
|
// Convert entire source line to grayscale with alpha blending to white background.
|
||||||
// Low-bit-depth grayscale/indexed scanlines are packed most-significant sample first.
|
|
||||||
// For indexed PNGs with tRNS chunk, alpha values are stored at palette[768] onwards.
|
// For indexed PNGs with tRNS chunk, alpha values are stored at palette[768] onwards.
|
||||||
// Processing the whole line at once improves cache locality and reduces per-pixel overhead.
|
// Processing the whole line at once improves cache locality and reduces per-pixel overhead.
|
||||||
void convertLineToGray(const uint8_t* pPixels, uint8_t* grayLine, int width, int pixelType, int bitsPerSample,
|
void convertLineToGray(uint8_t* pPixels, uint8_t* grayLine, int width, int pixelType, uint8_t* palette, int hasAlpha) {
|
||||||
uint8_t* palette, int hasAlpha) {
|
|
||||||
switch (pixelType) {
|
switch (pixelType) {
|
||||||
case PNG_PIXEL_GRAYSCALE:
|
case PNG_PIXEL_GRAYSCALE:
|
||||||
if (bitsPerSample == 8) {
|
|
||||||
memcpy(grayLine, pPixels, width);
|
memcpy(grayLine, pPixels, width);
|
||||||
} else {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
grayLine[x] = expandSampleToByte(readPackedSample(pPixels, x, bitsPerSample), bitsPerSample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PNG_PIXEL_TRUECOLOR:
|
case PNG_PIXEL_TRUECOLOR:
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
const uint8_t* p = &pPixels[x * 3];
|
uint8_t* p = &pPixels[x * 3];
|
||||||
grayLine[x] = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
grayLine[x] = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -159,7 +125,7 @@ void convertLineToGray(const uint8_t* pPixels, uint8_t* grayLine, int width, int
|
|||||||
if (palette) {
|
if (palette) {
|
||||||
if (hasAlpha) {
|
if (hasAlpha) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
uint8_t idx = readPackedSample(pPixels, x, bitsPerSample);
|
uint8_t idx = pPixels[x];
|
||||||
uint8_t* p = &palette[idx * 3];
|
uint8_t* p = &palette[idx * 3];
|
||||||
uint8_t gray = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
uint8_t gray = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||||
uint8_t alpha = palette[768 + idx];
|
uint8_t alpha = palette[768 + idx];
|
||||||
@@ -167,15 +133,12 @@ void convertLineToGray(const uint8_t* pPixels, uint8_t* grayLine, int width, int
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
uint8_t idx = readPackedSample(pPixels, x, bitsPerSample);
|
uint8_t* p = &palette[pPixels[x] * 3];
|
||||||
uint8_t* p = &palette[idx * 3];
|
|
||||||
grayLine[x] = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
grayLine[x] = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int x = 0; x < width; x++) {
|
memcpy(grayLine, pPixels, width);
|
||||||
grayLine[x] = expandSampleToByte(readPackedSample(pPixels, x, bitsPerSample), bitsPerSample);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -189,7 +152,7 @@ void convertLineToGray(const uint8_t* pPixels, uint8_t* grayLine, int width, int
|
|||||||
|
|
||||||
case PNG_PIXEL_TRUECOLOR_ALPHA:
|
case PNG_PIXEL_TRUECOLOR_ALPHA:
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
const uint8_t* p = &pPixels[x * 4];
|
uint8_t* p = &pPixels[x * 4];
|
||||||
uint8_t gray = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
uint8_t gray = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
|
||||||
uint8_t alpha = p[3];
|
uint8_t alpha = p[3];
|
||||||
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
|
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
|
||||||
@@ -209,47 +172,39 @@ int pngDrawCallback(PNGDRAW* pDraw) {
|
|||||||
int srcY = pDraw->y;
|
int srcY = pDraw->y;
|
||||||
int srcWidth = ctx->srcWidth;
|
int srcWidth = ctx->srcWidth;
|
||||||
|
|
||||||
// Map source rows with the exact output-height ratio. During downscaling,
|
// Calculate destination Y with scaling
|
||||||
// multiple source rows can select the same output row; during upscaling, one
|
int dstY = (int)(srcY * ctx->scale);
|
||||||
// source row must be repeated across every output row in its range. Emitting
|
|
||||||
// only the first row of an upscale leaves zero-filled (black) gaps in the
|
|
||||||
// streamed pixel cache.
|
|
||||||
int firstDstY = (srcY * ctx->dstHeight) / ctx->srcHeight;
|
|
||||||
int endDstY = firstDstY + 1;
|
|
||||||
if (ctx->dstHeight > ctx->srcHeight) {
|
|
||||||
endDstY = ((srcY + 1) * ctx->dstHeight) / ctx->srcHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstDstY <= ctx->lastDstY) firstDstY = ctx->lastDstY + 1;
|
// Skip if we already rendered this destination row (multiple source rows map to same dest)
|
||||||
if (firstDstY >= endDstY || firstDstY >= ctx->dstHeight) return 1;
|
if (dstY == ctx->lastDstY) return 1;
|
||||||
if (endDstY > ctx->dstHeight) endDstY = ctx->dstHeight;
|
ctx->lastDstY = dstY;
|
||||||
|
|
||||||
|
// Check bounds
|
||||||
|
if (dstY >= ctx->dstHeight) return 1;
|
||||||
|
|
||||||
|
int outY = ctx->config->y + dstY;
|
||||||
|
if (outY >= ctx->screenHeight) return 1;
|
||||||
|
|
||||||
// Convert entire source line to grayscale (improves cache locality)
|
// Convert entire source line to grayscale (improves cache locality)
|
||||||
convertLineToGray(pDraw->pPixels, ctx->grayLineBuffer, srcWidth, pDraw->iPixelType, pDraw->iBpp, pDraw->pPalette,
|
convertLineToGray(pDraw->pPixels, ctx->grayLineBuffer, srcWidth, pDraw->iPixelType, pDraw->pPalette,
|
||||||
pDraw->iHasAlpha);
|
pDraw->iHasAlpha);
|
||||||
|
|
||||||
// Render scaled rows using Bresenham-style integer stepping (no floating-point division)
|
// Render scaled row using Bresenham-style integer stepping (no floating-point division)
|
||||||
int dstWidth = ctx->dstWidth;
|
int dstWidth = ctx->dstWidth;
|
||||||
int outXBase = ctx->config->x;
|
int outXBase = ctx->config->x;
|
||||||
int screenWidth = ctx->screenWidth;
|
int screenWidth = ctx->screenWidth;
|
||||||
bool useDithering = ctx->config->useDithering;
|
bool useDithering = ctx->config->useDithering;
|
||||||
|
bool caching = ctx->caching;
|
||||||
|
|
||||||
// Pre-compute orientation and render-mode state once per callback.
|
// Pre-compute orientation and render-mode state once per row
|
||||||
DirectPixelWriter pw;
|
DirectPixelWriter pw;
|
||||||
pw.init(*ctx->renderer);
|
pw.init(*ctx->renderer);
|
||||||
|
|
||||||
for (int dstY = firstDstY; dstY < endDstY; dstY++) {
|
|
||||||
ctx->lastDstY = dstY;
|
|
||||||
int outY = ctx->config->y + dstY;
|
|
||||||
if (outY >= ctx->screenHeight) continue;
|
|
||||||
|
|
||||||
pw.beginRow(outY);
|
pw.beginRow(outY);
|
||||||
|
|
||||||
// The cache streams to disk one row at a time. Flushing rows below this one
|
// The cache streams to disk one row at a time. Flushing rows below this one
|
||||||
// (PNGdec delivers scanlines top to bottom) repositions the single-row band.
|
// (PNGdec delivers scanlines top to bottom) repositions the single-row band.
|
||||||
// A flush failure stops caching for the rest of the decode so we never write
|
// A flush failure stops caching for the rest of the decode so we never write
|
||||||
// past the band buffer; finalize() then drops the partial file.
|
// past the band buffer; finalize() then drops the partial file.
|
||||||
bool caching = ctx->caching;
|
|
||||||
DirectCacheWriter cw;
|
DirectCacheWriter cw;
|
||||||
if (caching) {
|
if (caching) {
|
||||||
if (!ctx->cache.advanceTo(dstY)) {
|
if (!ctx->cache.advanceTo(dstY)) {
|
||||||
@@ -287,7 +242,6 @@ int pngDrawCallback(PNGDRAW* pDraw) {
|
|||||||
srcX++;
|
srcX++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -378,44 +332,30 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
|
|||||||
}
|
}
|
||||||
ctx.lastDstY = -1; // Reset row tracking
|
ctx.lastDstY = -1; // Reset row tracking
|
||||||
|
|
||||||
const int pixelType = png->getPixelType();
|
LOG_DBG("PNG", "PNG %dx%d -> %dx%d (scale %.2f), bpp: %d", ctx.srcWidth, ctx.srcHeight, ctx.dstWidth, ctx.dstHeight,
|
||||||
const int bitsPerSample = png->getBpp();
|
ctx.scale, png->getBpp());
|
||||||
LOG_DBG("PNG", "PNG %dx%d -> %dx%d (scale %.2f), type: %d, bpp: %d", ctx.srcWidth, ctx.srcHeight, ctx.dstWidth,
|
|
||||||
ctx.dstHeight, ctx.scale, pixelType, bitsPerSample);
|
|
||||||
|
|
||||||
const int requiredInternal = requiredPngInternalBufferBytes(ctx.srcWidth, pixelType, bitsPerSample);
|
const int pixelType = png->getPixelType();
|
||||||
|
const int requiredInternal = requiredPngInternalBufferBytes(ctx.srcWidth, pixelType);
|
||||||
if (requiredInternal > PNG_MAX_BUFFERED_PIXELS) {
|
if (requiredInternal > PNG_MAX_BUFFERED_PIXELS) {
|
||||||
LOG_ERR(
|
LOG_ERR("PNG",
|
||||||
"PNG",
|
"PNG row buffer too small: need %d bytes for width=%d type=%d, configured PNG_MAX_BUFFERED_PIXELS=%d",
|
||||||
"PNG row buffer too small: need %d bytes for width=%d type=%d bpp=%d, configured PNG_MAX_BUFFERED_PIXELS=%d",
|
requiredInternal, ctx.srcWidth, pixelType, PNG_MAX_BUFFERED_PIXELS);
|
||||||
requiredInternal, ctx.srcWidth, pixelType, bitsPerSample, PNG_MAX_BUFFERED_PIXELS);
|
|
||||||
LOG_ERR("PNG", "Aborting decode to avoid PNGdec internal buffer overflow");
|
LOG_ERR("PNG", "Aborting decode to avoid PNGdec internal buffer overflow");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSupportedBitDepth(pixelType, bitsPerSample)) {
|
if (png->getBpp() != 8) {
|
||||||
warnUnsupportedFeature(
|
warnUnsupportedFeature("bit depth (" + std::to_string(png->getBpp()) + "bpp)", imagePath);
|
||||||
"bit depth (" + std::to_string(bitsPerSample) + "bpp) for pixel type " + std::to_string(pixelType), imagePath);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The converter expands each source row to 8-bit grayscale before dithering,
|
// Allocate grayscale line buffer on demand (~3.2 KB) - freed after decode
|
||||||
// so this scratch buffer is sized by source pixels even when PNGdec reads a
|
const size_t grayBufSize = PNG_MAX_BUFFERED_PIXELS / 2;
|
||||||
// packed 1/2/4-bit row internally.
|
ctx.grayLineBuffer = static_cast<uint8_t*>(malloc(grayBufSize));
|
||||||
constexpr size_t MAX_GRAY_LINE_BUFFER_BYTES = PNG_MAX_BUFFERED_PIXELS / 2;
|
if (!ctx.grayLineBuffer) {
|
||||||
const size_t grayBufSize = static_cast<size_t>(ctx.srcWidth);
|
|
||||||
if (grayBufSize > MAX_GRAY_LINE_BUFFER_BYTES) {
|
|
||||||
LOG_ERR("PNG", "Expanded gray row too wide: need %u bytes for width=%d, max=%u", static_cast<unsigned>(grayBufSize),
|
|
||||||
ctx.srcWidth, static_cast<unsigned>(MAX_GRAY_LINE_BUFFER_BYTES));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto grayLineBuffer = makeUniqueNoThrow<uint8_t[]>(grayBufSize);
|
|
||||||
if (!grayLineBuffer) {
|
|
||||||
LOG_ERR("PNG", "Failed to allocate gray line buffer");
|
LOG_ERR("PNG", "Failed to allocate gray line buffer");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ctx.grayLineBuffer = grayLineBuffer.get();
|
|
||||||
|
|
||||||
// Stream the pixel cache to disk. PNGdec delivers source scanlines top to
|
// Stream the pixel cache to disk. PNGdec delivers source scanlines top to
|
||||||
// bottom and we emit at most one (downscaled) output row per callback, so the
|
// bottom and we emit at most one (downscaled) output row per callback, so the
|
||||||
@@ -435,6 +375,7 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
|
|||||||
rc = png->decode(&ctx, 0);
|
rc = png->decode(&ctx, 0);
|
||||||
unsigned long decodeTime = millis() - decodeStart;
|
unsigned long decodeTime = millis() - decodeStart;
|
||||||
|
|
||||||
|
free(ctx.grayLineBuffer);
|
||||||
ctx.grayLineBuffer = nullptr;
|
ctx.grayLineBuffer = nullptr;
|
||||||
|
|
||||||
if (rc != PNG_SUCCESS) {
|
if (rc != PNG_SUCCESS) {
|
||||||
|
|||||||
@@ -431,11 +431,6 @@ CssStyle CssParser::parseDeclarations(std::string_view declBlock) {
|
|||||||
// Rule processing
|
// Rule processing
|
||||||
|
|
||||||
void CssParser::processRuleBlockWithStyle(std::string_view selectorGroup, const CssStyle& style) {
|
void CssParser::processRuleBlockWithStyle(std::string_view selectorGroup, const CssStyle& style) {
|
||||||
// Skip rules that don't define any supported properties to save RAM.
|
|
||||||
if (!style.defined.anySet()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we've reached the rule limit before processing
|
// Check if we've reached the rule limit before processing
|
||||||
if (rulesBySelector_.size() >= MAX_RULES) {
|
if (rulesBySelector_.size() >= MAX_RULES) {
|
||||||
LOG_DBG("CSS", "Reached max rules limit (%zu), stopping CSS parsing", MAX_RULES);
|
LOG_DBG("CSS", "Reached max rules limit (%zu), stopping CSS parsing", MAX_RULES);
|
||||||
@@ -808,9 +803,6 @@ bool CssParser::loadFromCache() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size the bucket array up front to avoid incremental rehashes while loading rules.
|
|
||||||
rulesBySelector_.reserve(ruleCount);
|
|
||||||
|
|
||||||
auto hasRemainingBytes = [&file](const size_t neededBytes) -> bool {
|
auto hasRemainingBytes = [&file](const size_t neededBytes) -> bool {
|
||||||
return static_cast<size_t>(file.available()) >= neededBytes;
|
return static_cast<size_t>(file.available()) >= neededBytes;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
class CssParser {
|
class CssParser {
|
||||||
public:
|
public:
|
||||||
// Bump when CSS cache format or rules change; section caches are invalidated when this changes
|
// Bump when CSS cache format or rules change; section caches are invalidated when this changes
|
||||||
static constexpr uint8_t CSS_CACHE_VERSION = 8;
|
static constexpr uint8_t CSS_CACHE_VERSION = 7;
|
||||||
|
|
||||||
explicit CssParser(std::string cachePath) : cachePath(std::move(cachePath)) {}
|
explicit CssParser(std::string cachePath) : cachePath(std::move(cachePath)) {}
|
||||||
~CssParser() = default;
|
~CssParser() = default;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ struct Iso639Mapping {
|
|||||||
};
|
};
|
||||||
static constexpr Iso639Mapping kIso639Mappings[] = {{"eng", "en"}, {"fra", "fr"}, {"fre", "fr"}, {"deu", "de"},
|
static constexpr Iso639Mapping kIso639Mappings[] = {{"eng", "en"}, {"fra", "fr"}, {"fre", "fr"}, {"deu", "de"},
|
||||||
{"ger", "de"}, {"rus", "ru"}, {"spa", "es"}, {"ita", "it"},
|
{"ger", "de"}, {"rus", "ru"}, {"spa", "es"}, {"ita", "it"},
|
||||||
{"ukr", "uk"}, {"swe", "sv"}, {"fin", "fi"}};
|
{"ukr", "uk"}, {"swe", "sv"}};
|
||||||
|
|
||||||
// Maps a BCP-47 or ISO 639-2 language tag to a language-specific hyphenator.
|
// Maps a BCP-47 or ISO 639-2 language tag to a language-specific hyphenator.
|
||||||
const LanguageHyphenator* hyphenatorForLanguage(const std::string& langTag) {
|
const LanguageHyphenator* hyphenatorForLanguage(const std::string& langTag) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include "generated/hyph-de.trie.h"
|
#include "generated/hyph-de.trie.h"
|
||||||
#include "generated/hyph-en.trie.h"
|
#include "generated/hyph-en.trie.h"
|
||||||
#include "generated/hyph-es.trie.h"
|
#include "generated/hyph-es.trie.h"
|
||||||
#include "generated/hyph-fi.trie.h"
|
|
||||||
#include "generated/hyph-fr.trie.h"
|
#include "generated/hyph-fr.trie.h"
|
||||||
#include "generated/hyph-it.trie.h"
|
#include "generated/hyph-it.trie.h"
|
||||||
#include "generated/hyph-pl.trie.h"
|
#include "generated/hyph-pl.trie.h"
|
||||||
@@ -27,9 +26,8 @@ LanguageHyphenator italianHyphenator(it_patterns, isLatinLetter, toLowerLatin);
|
|||||||
LanguageHyphenator swedishHyphenator(sv_patterns, isLatinLetter, toLowerLatin);
|
LanguageHyphenator swedishHyphenator(sv_patterns, isLatinLetter, toLowerLatin);
|
||||||
LanguageHyphenator ukrainianHyphenator(uk_patterns, isCyrillicLetter, toLowerCyrillic);
|
LanguageHyphenator ukrainianHyphenator(uk_patterns, isCyrillicLetter, toLowerCyrillic);
|
||||||
LanguageHyphenator polishHyphenator(pl_patterns, isLatinLetter, toLowerLatin);
|
LanguageHyphenator polishHyphenator(pl_patterns, isLatinLetter, toLowerLatin);
|
||||||
LanguageHyphenator finnishHyphenator(fi_patterns, isLatinLetter, toLowerLatin);
|
|
||||||
|
|
||||||
using EntryArray = std::array<LanguageEntry, 10>;
|
using EntryArray = std::array<LanguageEntry, 9>;
|
||||||
|
|
||||||
const EntryArray& entries() {
|
const EntryArray& entries() {
|
||||||
static const EntryArray kEntries = {{{"english", "en", &englishHyphenator},
|
static const EntryArray kEntries = {{{"english", "en", &englishHyphenator},
|
||||||
@@ -40,8 +38,7 @@ const EntryArray& entries() {
|
|||||||
{"italian", "it", &italianHyphenator},
|
{"italian", "it", &italianHyphenator},
|
||||||
{"polish", "pl", &polishHyphenator},
|
{"polish", "pl", &polishHyphenator},
|
||||||
{"swedish", "sv", &swedishHyphenator},
|
{"swedish", "sv", &swedishHyphenator},
|
||||||
{"ukrainian", "uk", &ukrainianHyphenator},
|
{"ukrainian", "uk", &ukrainianHyphenator}}};
|
||||||
{"finnish", "fi", &finnishHyphenator}}};
|
|
||||||
return kEntries;
|
return kEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "Epub/hyphenation/SerializedHyphenationTrie.h"
|
|
||||||
|
|
||||||
// Auto-generated by generate_hyphenation_trie.py. Do not edit manually.
|
|
||||||
alignas(4) constexpr uint8_t fi_trie_data[] = {
|
|
||||||
0x01, 0x01, 0x16, 0x0B, 0x0C, 0x17, 0x0C, 0x15, 0x0C, 0x0B, 0x16, 0x29, 0x2B, 0x20, 0x1F, 0x0C,
|
|
||||||
0x33, 0x02, 0x0B, 0x02, 0x0B, 0x0C, 0x01, 0x0C, 0x34, 0x0B, 0x2A, 0x0B, 0x2A, 0x0B, 0x0C, 0x20,
|
|
||||||
0x0B, 0x21, 0xA0, 0x00, 0x41, 0xA0, 0x02, 0x51, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0xA1, 0x00,
|
|
||||||
0x41, 0x62, 0xFD, 0xA0, 0x01, 0xA2, 0xA1, 0x00, 0x81, 0x6F, 0xFD, 0xA3, 0x00, 0x81, 0x69, 0x6F,
|
|
||||||
0x75, 0xF8, 0xF8, 0xF8, 0x28, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0x6C, 0x72, 0xDE, 0xDE, 0xEA,
|
|
||||||
0xDE, 0xDE, 0xDE, 0xF2, 0xF7, 0x22, 0xA4, 0xB6, 0xCD, 0xCD, 0xA1, 0x00, 0x81, 0x61, 0xD9, 0x28,
|
|
||||||
0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x72, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xF6, 0xFB,
|
|
||||||
0xA2, 0x00, 0x81, 0x61, 0x65, 0xC3, 0xC3, 0x48, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0x6C, 0x72,
|
|
||||||
0xFF, 0xAB, 0xFF, 0xAB, 0xFF, 0xAB, 0xFF, 0xAB, 0xFF, 0xAB, 0xFF, 0xAB, 0xFF, 0xE3, 0xFF, 0xF9,
|
|
||||||
0x49, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x6C, 0x72, 0xFF, 0x92, 0xFF, 0x92, 0xFF, 0x92,
|
|
||||||
0xFF, 0x92, 0xFF, 0x92, 0xFF, 0x92, 0xFF, 0xC5, 0xFF, 0xA6, 0xFF, 0xCA, 0x47, 0x61, 0x65, 0x69,
|
|
||||||
0x6F, 0x75, 0x79, 0xC3, 0xFF, 0x76, 0xFF, 0x76, 0xFF, 0x76, 0xFF, 0x76, 0xFF, 0x76, 0xFF, 0x76,
|
|
||||||
0xFF, 0xA9, 0xA0, 0x00, 0xF1, 0x21, 0x73, 0xFD, 0xA1, 0x00, 0x41, 0x75, 0xFD, 0xA0, 0x00, 0x81,
|
|
||||||
0x43, 0x61, 0x65, 0x69, 0xFF, 0x63, 0xFF, 0x63, 0xFF, 0x63, 0xC1, 0x01, 0xA2, 0x61, 0xFF, 0x59,
|
|
||||||
0x4A, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x6C, 0x72, 0x76, 0xFF, 0x42, 0xFF, 0xE8, 0xFF,
|
|
||||||
0x42, 0xFF, 0x42, 0xFF, 0x42, 0xFF, 0x42, 0xFF, 0x75, 0xFF, 0xED, 0xFF, 0xF0, 0xFF, 0xFA, 0xA1,
|
|
||||||
0x00, 0x41, 0x73, 0xCE, 0x47, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0xFF, 0xFB, 0xFF, 0x1E,
|
|
||||||
0xFF, 0x1E, 0xFF, 0x1E, 0xFF, 0x1E, 0xFF, 0x1E, 0xFF, 0x51, 0xA0, 0x01, 0x52, 0x21, 0x6F, 0xFD,
|
|
||||||
0x22, 0x74, 0x6E, 0xFD, 0xFD, 0xA0, 0x01, 0x73, 0x21, 0x6E, 0xFD, 0x22, 0x61, 0x6F, 0xFD, 0xFA,
|
|
||||||
0x21, 0x61, 0xEA, 0x21, 0x6B, 0xFD, 0x21, 0x65, 0xF2, 0xA4, 0x00, 0x41, 0x6E, 0x6A, 0x69, 0x6C,
|
|
||||||
0xE7, 0xF2, 0xFA, 0xFD, 0x21, 0x73, 0xE1, 0x21, 0x75, 0xFD, 0xA1, 0x00, 0x41, 0x64, 0xFD, 0x21,
|
|
||||||
0x74, 0xD6, 0x21, 0x73, 0xFD, 0x22, 0x65, 0x69, 0xFA, 0xFD, 0x21, 0x61, 0xCB, 0x21, 0x6E, 0xBD,
|
|
||||||
0x22, 0x74, 0x6F, 0xBD, 0xFD, 0x21, 0x69, 0xC0, 0x21, 0x61, 0xFD, 0xA4, 0x00, 0x41, 0x70, 0x73,
|
|
||||||
0x74, 0x6D, 0xEA, 0xEF, 0xF5, 0xFD, 0x21, 0x69, 0xD9, 0xA1, 0x00, 0x41, 0x6C, 0xFD, 0x47, 0x61,
|
|
||||||
0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0xFF, 0xBB, 0xFF, 0xCC, 0xFE, 0xA4, 0xFF, 0xED, 0xFE, 0xA4,
|
|
||||||
0xFF, 0xFB, 0xFE, 0xD7, 0xA0, 0x01, 0x41, 0x21, 0x73, 0xFD, 0x21, 0x75, 0xFD, 0xA1, 0x00, 0x41,
|
|
||||||
0x72, 0xFD, 0x49, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x6C, 0x72, 0xFE, 0x80, 0xFF, 0xFB,
|
|
||||||
0xFE, 0x80, 0xFE, 0x80, 0xFE, 0x80, 0xFE, 0x80, 0xFE, 0xB3, 0xFF, 0x2B, 0xFE, 0x94, 0x21, 0x61,
|
|
||||||
0xDC, 0x48, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x74, 0xFF, 0x3E, 0xFE, 0x61, 0xFE, 0x61,
|
|
||||||
0xFE, 0x61, 0xFE, 0x61, 0xFE, 0x61, 0xFE, 0x94, 0xFF, 0xFD, 0x42, 0x69, 0x65, 0xFF, 0x80, 0xFF,
|
|
||||||
0x40, 0x42, 0x6F, 0x65, 0xFF, 0x84, 0xFF, 0x47, 0x41, 0x75, 0xFF, 0x32, 0x21, 0x74, 0xFC, 0x42,
|
|
||||||
0x61, 0x6F, 0xFF, 0xFD, 0xFF, 0x36, 0xA4, 0x00, 0x41, 0x73, 0x6C, 0x6A, 0x70, 0xE4, 0xEB, 0xF9,
|
|
||||||
0xF2, 0x41, 0x79, 0xFF, 0x24, 0x21, 0x74, 0xFC, 0x21, 0x69, 0xFD, 0xA1, 0x00, 0x41, 0x73, 0xFD,
|
|
||||||
0x42, 0x2E, 0x6E, 0xFF, 0x15, 0xFF, 0x15, 0x21, 0x61, 0xF9, 0x21, 0x65, 0xFD, 0xA1, 0x00, 0x41,
|
|
||||||
0x64, 0xFD, 0x41, 0x65, 0xFE, 0xF8, 0x21, 0x6A, 0xFC, 0x42, 0x6B, 0x74, 0xFE, 0xFC, 0xFE, 0xFC,
|
|
||||||
0x21, 0x73, 0xF9, 0x21, 0x69, 0xFD, 0xC3, 0x00, 0x41, 0x68, 0x70, 0x73, 0xFF, 0xF0, 0xFF, 0xFD,
|
|
||||||
0xFF, 0x24, 0x41, 0x74, 0xFF, 0x23, 0xC2, 0x00, 0x41, 0x72, 0x68, 0xFF, 0x30, 0xFF, 0xFC, 0xA0,
|
|
||||||
0x00, 0x52, 0x21, 0x72, 0xFD, 0x21, 0x69, 0xFA, 0x21, 0x6C, 0xFD, 0xA0, 0x00, 0x61, 0x21, 0x68,
|
|
||||||
0xFD, 0x4A, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x74, 0x70, 0x63, 0xFF, 0x95, 0xFF, 0xAA,
|
|
||||||
0xFF, 0xBC, 0xFF, 0xD5, 0xFD, 0xC1, 0xFF, 0xE5, 0xFD, 0xF4, 0xFF, 0xF1, 0xFF, 0xF7, 0xFF, 0xFD,
|
|
||||||
0x48, 0x61, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xC3, 0x73, 0xFD, 0xA2, 0xFD, 0xA2, 0xFD, 0xA2, 0xFD,
|
|
||||||
0xA2, 0xFD, 0xA2, 0xFD, 0xA2, 0xFD, 0xD5, 0xFF, 0xDE, 0xA0, 0x00, 0x92, 0xA0, 0x00, 0xB2, 0xA0,
|
|
||||||
0x01, 0x01, 0xC3, 0x00, 0x61, 0x69, 0x65, 0x79, 0xFE, 0x20, 0xFE, 0x20, 0xFF, 0xFD, 0x22, 0xA4,
|
|
||||||
0xB6, 0xF4, 0xAD, 0x25, 0x79, 0x61, 0x6F, 0x75, 0xC3, 0xA8, 0xE6, 0xE6, 0xE9, 0xFB, 0x42, 0xB6,
|
|
||||||
0xA4, 0xFF, 0x9D, 0xFF, 0x9D, 0x46, 0x79, 0x61, 0x6F, 0x75, 0xC3, 0x65, 0xFF, 0x96, 0xFF, 0xD4,
|
|
||||||
0xFF, 0xD4, 0xFF, 0xD7, 0xFF, 0xF9, 0xFF, 0xD7, 0x22, 0xA4, 0xB6, 0xDB, 0xED, 0xA0, 0x00, 0x72,
|
|
||||||
0xA0, 0x00, 0x71, 0x21, 0xA4, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0xA4, 0xFD, 0x21, 0x69, 0xF4, 0xA0,
|
|
||||||
0x01, 0x22, 0x21, 0x70, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x69, 0xFD, 0x26, 0x61, 0x6F, 0x75, 0xC3,
|
|
||||||
0x65, 0x6C, 0xE2, 0xE2, 0xE2, 0xEE, 0xF1, 0xFD, 0x22, 0xA4, 0xB6, 0xD8, 0xD8, 0x21, 0x61, 0xD3,
|
|
||||||
0xA0, 0x00, 0xB1, 0x24, 0x75, 0x69, 0x65, 0x6F, 0xCD, 0xCD, 0xFD, 0xFD, 0x24, 0x61, 0x65, 0x6F,
|
|
||||||
0x75, 0xF4, 0xF4, 0xF4, 0xF4, 0x25, 0x79, 0xC3, 0x61, 0x75, 0x69, 0xBB, 0xE3, 0xE8, 0xEE, 0xF7,
|
|
||||||
0xA0, 0x00, 0xD2, 0x22, 0xA4, 0xB6, 0xFD, 0xFD, 0x44, 0x61, 0x65, 0x6F, 0x69, 0xFF, 0x64, 0xFF,
|
|
||||||
0x64, 0xFF, 0x64, 0xFF, 0x64, 0x42, 0x65, 0x61, 0xFF, 0x9B, 0xFF, 0xCB, 0x21, 0x65, 0xC4, 0x22,
|
|
||||||
0x61, 0x75, 0xC1, 0xC1, 0xA0, 0x02, 0x32, 0x21, 0x73, 0xFD, 0x21, 0x6F, 0xFD, 0x49, 0x79, 0xC3,
|
|
||||||
0x75, 0x61, 0x65, 0x69, 0x6F, 0x73, 0x6C, 0xFF, 0x80, 0xFF, 0xD6, 0xFF, 0xDB, 0xFF, 0xB0, 0xFF,
|
|
||||||
0xE8, 0xFF, 0xEF, 0xFF, 0xF2, 0xFD, 0x70, 0xFF, 0xFD, 0x44, 0x69, 0x65, 0x6F, 0x75, 0xFF, 0x23,
|
|
||||||
0xFF, 0x23, 0xFF, 0x23, 0xFF, 0x23, 0x43, 0x75, 0x61, 0x65, 0xFF, 0x5A, 0xFF, 0x8A, 0xFF, 0x8A,
|
|
||||||
0x41, 0x76, 0xFF, 0x5F, 0x21, 0x61, 0xFC, 0xA0, 0x01, 0xC2, 0x21, 0x73, 0xFD, 0x21, 0x69, 0xFD,
|
|
||||||
0x21, 0x65, 0xFD, 0x43, 0x69, 0x6F, 0x6B, 0xFF, 0xF1, 0xFD, 0xF7, 0xFF, 0xFD, 0xA0, 0x01, 0xA4,
|
|
||||||
0x21, 0x73, 0xFD, 0x21, 0x61, 0xFD, 0x43, 0x6E, 0x74, 0x6B, 0xFC, 0x7D, 0xFC, 0x7D, 0xFF, 0xFD,
|
|
||||||
0x41, 0x69, 0xFC, 0x73, 0x22, 0x61, 0x6F, 0xF2, 0xFC, 0x21, 0x69, 0xFB, 0x48, 0xC3, 0x61, 0x75,
|
|
||||||
0x65, 0x6F, 0x69, 0x6C, 0x73, 0xFF, 0x3C, 0xFF, 0xAD, 0xFF, 0xBA, 0xFF, 0x20, 0xFF, 0x20, 0xFF,
|
|
||||||
0x50, 0xFF, 0xD7, 0xFF, 0xFD, 0x44, 0x61, 0x69, 0x75, 0x79, 0xFE, 0xB7, 0xFE, 0xB7, 0xFE, 0xB7,
|
|
||||||
0xFE, 0xB7, 0x42, 0x61, 0x69, 0xFE, 0xEE, 0xFE, 0xEE, 0x42, 0x75, 0x61, 0xFE, 0xE7, 0xFF, 0x17,
|
|
||||||
0x42, 0xA4, 0xB6, 0xFE, 0xE6, 0xFF, 0x30, 0x24, 0x65, 0x61, 0x75, 0xC3, 0xDE, 0xEB, 0xF2, 0xF9,
|
|
||||||
0x43, 0x61, 0x65, 0x6F, 0xFE, 0x8C, 0xFE, 0x8C, 0xFE, 0x8C, 0x42, 0x61, 0x75, 0xFE, 0xC6, 0xFE,
|
|
||||||
0xC6, 0x44, 0x75, 0x61, 0x65, 0x6F, 0xFE, 0xBF, 0xFE, 0xEF, 0xFE, 0xEF, 0xFE, 0xEF, 0x41, 0xB6,
|
|
||||||
0xFE, 0xB2, 0x21, 0xC3, 0xFC, 0x42, 0xA4, 0xB6, 0xFE, 0xB1, 0xFF, 0xFD, 0x43, 0x61, 0x6F, 0x79,
|
|
||||||
0xFE, 0xD4, 0xFE, 0xD4, 0xFE, 0xD4, 0x42, 0x61, 0x65, 0xFE, 0x56, 0xFE, 0x56, 0x26, 0x69, 0x61,
|
|
||||||
0x75, 0xC3, 0x65, 0x6F, 0xC3, 0xCD, 0xD4, 0xE8, 0xEF, 0xF9, 0xA0, 0x01, 0x11, 0x21, 0xA4, 0xFD,
|
|
||||||
0xA0, 0x01, 0xE2, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x64, 0xFD, 0xA0, 0x02, 0x03, 0x21,
|
|
||||||
0x61, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x75, 0xFD, 0x21, 0x75, 0xFD, 0x23, 0xC3, 0x79, 0x73, 0xE2,
|
|
||||||
0xEE, 0xFD, 0x41, 0x72, 0xFD, 0xD9, 0x42, 0x6C, 0x68, 0xFC, 0x47, 0xFF, 0xFC, 0xC1, 0x00, 0x81,
|
|
||||||
0x69, 0xFB, 0xA6, 0x21, 0x76, 0xFA, 0x59, 0x62, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D,
|
|
||||||
0x6E, 0x70, 0x72, 0x73, 0x74, 0x76, 0xC3, 0x79, 0x6F, 0x75, 0x61, 0x65, 0x69, 0x2E, 0x63, 0x71,
|
|
||||||
0xFB, 0xAE, 0xFB, 0xC9, 0xFB, 0xE1, 0xFB, 0xFA, 0xFC, 0x16, 0xFC, 0x16, 0xFC, 0x4A, 0xFC, 0x6E,
|
|
||||||
0xFC, 0x16, 0xFC, 0xE8, 0xFD, 0x0C, 0xFD, 0x2B, 0xFD, 0xCB, 0xFD, 0xEA, 0xFC, 0x16, 0xFE, 0x42,
|
|
||||||
0xFE, 0x65, 0xFE, 0x8F, 0xFE, 0xC7, 0xFF, 0x36, 0xFF, 0x71, 0xFF, 0xB7, 0xFF, 0xE5, 0xFF, 0xF0,
|
|
||||||
0xFF, 0xFD,
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr SerializedHyphenationPatterns fi_patterns = {
|
|
||||||
0x496u,
|
|
||||||
fi_trie_data,
|
|
||||||
sizeof(fi_trie_data),
|
|
||||||
};
|
|
||||||
@@ -22,17 +22,6 @@
|
|||||||
constexpr size_t MIN_SIZE_FOR_POPUP = 10 * 1024; // 10KB
|
constexpr size_t MIN_SIZE_FOR_POPUP = 10 * 1024; // 10KB
|
||||||
constexpr size_t PARSE_BUFFER_SIZE = 1024;
|
constexpr size_t PARSE_BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
// This number comes from PR #73
|
|
||||||
// If we have > 750 words buffered up, perform the layout and consume out all but the last line
|
|
||||||
// There should be enough here to build out 1-2 full pages and doing this will free up a lot of
|
|
||||||
// memory.
|
|
||||||
// Spotted when reading Intermezzo, there are some really long text blocks in there.
|
|
||||||
constexpr size_t TEXT_BLOCK_SOFT_FLUSH_WORDS = 750;
|
|
||||||
|
|
||||||
// When CSS is enabled, flush earlier to save RAM. 320 is still more than enough to build a CJK
|
|
||||||
// page at font size 14
|
|
||||||
constexpr size_t TEXT_BLOCK_SOFT_FLUSH_WORDS_WITH_CSS = 320;
|
|
||||||
|
|
||||||
// Hard cap on the number of anchor IDs recorded per chapter. Legitimate navigation
|
// Hard cap on the number of anchor IDs recorded per chapter. Legitimate navigation
|
||||||
// anchors (TOC entries, footnotes, cross-references) rarely exceed a few hundred per
|
// anchors (TOC entries, footnotes, cross-references) rarely exceed a few hundred per
|
||||||
// chapter. A runaway count usually means a converter injected machine-generated IDs on
|
// chapter. A runaway count usually means a converter injected machine-generated IDs on
|
||||||
@@ -46,7 +35,7 @@ constexpr const char* BOLD_TAGS[] = {"b", "strong"};
|
|||||||
constexpr const char* ITALIC_TAGS[] = {"i", "em"};
|
constexpr const char* ITALIC_TAGS[] = {"i", "em"};
|
||||||
constexpr const char* UNDERLINE_TAGS[] = {"u", "ins"};
|
constexpr const char* UNDERLINE_TAGS[] = {"u", "ins"};
|
||||||
constexpr const char* LINETHROUGH_TAGS[] = {"del", "s", "strike"};
|
constexpr const char* LINETHROUGH_TAGS[] = {"del", "s", "strike"};
|
||||||
constexpr const char* IMAGE_TAGS[] = {"img", "image"};
|
constexpr const char* IMAGE_TAGS[] = {"img"};
|
||||||
constexpr const char* SKIP_TAGS[] = {"head"};
|
constexpr const char* SKIP_TAGS[] = {"head"};
|
||||||
|
|
||||||
bool isWhitespace(const char c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t'; }
|
bool isWhitespace(const char c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t'; }
|
||||||
@@ -236,7 +225,6 @@ void ChapterHtmlSlimParser::flushPartWordBuffer() {
|
|||||||
currentTextBlock->addWord(partWordBuffer, fontStyle, false, nextWordContinues);
|
currentTextBlock->addWord(partWordBuffer, fontStyle, false, nextWordContinues);
|
||||||
partWordBufferIndex = 0;
|
partWordBufferIndex = 0;
|
||||||
nextWordContinues = false;
|
nextWordContinues = false;
|
||||||
listItemBulletOnly = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// start a new text block if needed
|
// start a new text block if needed
|
||||||
@@ -250,29 +238,9 @@ void ChapterHtmlSlimParser::startNewTextBlock(const BlockStyle& blockStyle) {
|
|||||||
// container elements deposit their vertical margins on the empty block when they
|
// container elements deposit their vertical margins on the empty block when they
|
||||||
// open. Merge those into the new style so the first child in a container inherits
|
// open. Merge those into the new style so the first child in a container inherits
|
||||||
// the container's vertical spacing.
|
// the container's vertical spacing.
|
||||||
const auto style = currentTextBlock->getBlockStyle();
|
|
||||||
BlockStyle incoming = blockStyle;
|
|
||||||
if (style.fromBrElement) {
|
|
||||||
// The empty block was created by a <br> section separator. Inject a full line of
|
|
||||||
// blank space before the following paragraph so the scene/section break is visible.
|
|
||||||
// This only fires when the <br> block stayed empty (i.e. no inline text was added).
|
|
||||||
const int16_t lineHeight = static_cast<int16_t>(renderer.getLineHeight(fontId) * lineCompression + 0.5f);
|
|
||||||
incoming.marginTop = static_cast<int16_t>(incoming.marginTop + lineHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTextBlock->setBlockStyle(style.getCombinedBlockStyle(incoming, BlockStyle::CombineAxis::Vertical));
|
|
||||||
|
|
||||||
flushPendingAnchor();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// <li> added a bullet as the first word, making the block non-empty. When a nested
|
|
||||||
// block-level child (<p>, <div>, etc.) opens, reuse the block instead of flushing
|
|
||||||
// the bullet to its own line. The bullet stays inline with the child's text.
|
|
||||||
if (listItemBulletOnly) {
|
|
||||||
const auto style = currentTextBlock->getBlockStyle();
|
const auto style = currentTextBlock->getBlockStyle();
|
||||||
currentTextBlock->setBlockStyle(style.getCombinedBlockStyle(blockStyle, BlockStyle::CombineAxis::Vertical));
|
currentTextBlock->setBlockStyle(style.getCombinedBlockStyle(blockStyle, BlockStyle::CombineAxis::Vertical));
|
||||||
listItemBulletOnly = false;
|
|
||||||
flushPendingAnchor();
|
flushPendingAnchor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -284,7 +252,6 @@ void ChapterHtmlSlimParser::startNewTextBlock(const BlockStyle& blockStyle) {
|
|||||||
flushPendingAnchor();
|
flushPendingAnchor();
|
||||||
currentTextBlock.reset(new ParsedText(extraParagraphSpacing, hyphenationEnabled, focusReadingEnabled, blockStyle));
|
currentTextBlock.reset(new ParsedText(extraParagraphSpacing, hyphenationEnabled, focusReadingEnabled, blockStyle));
|
||||||
wordsExtractedInBlock = 0;
|
wordsExtractedInBlock = 0;
|
||||||
listItemBulletOnly = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChapterHtmlSlimParser::emitHorizontalRule(const BlockStyle& blockStyle) {
|
void ChapterHtmlSlimParser::emitHorizontalRule(const BlockStyle& blockStyle) {
|
||||||
@@ -519,18 +486,11 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
for (int i = 0; atts[i]; i += 2) {
|
for (int i = 0; atts[i]; i += 2) {
|
||||||
if (strcmp(atts[i], "src") == 0) {
|
if (strcmp(atts[i], "src") == 0) {
|
||||||
src = atts[i + 1];
|
src = atts[i + 1];
|
||||||
} else if (src.empty() && (strcmp(atts[i], "href") == 0 || strcmp(atts[i], "xlink:href") == 0)) {
|
|
||||||
src = atts[i + 1];
|
|
||||||
} else if (strcmp(atts[i], "alt") == 0) {
|
} else if (strcmp(atts[i], "alt") == 0) {
|
||||||
alt = atts[i + 1];
|
alt = atts[i + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t fragmentPos = src.find('#');
|
|
||||||
if (fragmentPos != std::string::npos) {
|
|
||||||
src.resize(fragmentPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// imageRendering: 0=display, 1=placeholder (alt text only), 2=suppress entirely
|
// imageRendering: 0=display, 1=placeholder (alt text only), 2=suppress entirely
|
||||||
if (self->imageRendering == 2) {
|
if (self->imageRendering == 2) {
|
||||||
self->skipUntilDepth = self->depth;
|
self->skipUntilDepth = self->depth;
|
||||||
@@ -538,6 +498,19 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip image if CSS display:none
|
||||||
|
if (self->cssParser) {
|
||||||
|
CssStyle imgDisplayStyle = self->cssParser->resolveStyle("img", classAttr);
|
||||||
|
if (!styleAttr.empty()) {
|
||||||
|
imgDisplayStyle.applyOver(CssParser::parseInlineStyle(styleAttr));
|
||||||
|
}
|
||||||
|
if (imgDisplayStyle.hasDisplay() && imgDisplayStyle.display == CssDisplay::None) {
|
||||||
|
self->skipUntilDepth = self->depth;
|
||||||
|
self->depth += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!src.empty() && self->imageRendering != 1) {
|
if (!src.empty() && self->imageRendering != 1) {
|
||||||
LOG_DBG("EHP", "Found image: src=%s", src.c_str());
|
LOG_DBG("EHP", "Found image: src=%s", src.c_str());
|
||||||
|
|
||||||
@@ -561,28 +534,24 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
extractSuccess = self->epub->readItemContentsToStream(resolvedPath, cachedImageFile, 4096);
|
extractSuccess = self->epub->readItemContentsToStream(resolvedPath, cachedImageFile, 4096);
|
||||||
cachedImageFile.flush();
|
cachedImageFile.flush();
|
||||||
cachedImageFile.close();
|
cachedImageFile.close();
|
||||||
|
delay(50); // Give SD card time to sync
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extractSuccess) {
|
if (extractSuccess) {
|
||||||
// Get image dimensions, retrying to absorb SD-card sync latency on slow
|
// Get image dimensions
|
||||||
// cards. Replaces a blanket delay(50) that cost ~50ms on every image, and
|
|
||||||
// closes the silent-drop bug where a single getDimensions failure was fatal.
|
|
||||||
ImageDimensions dims = {0, 0};
|
ImageDimensions dims = {0, 0};
|
||||||
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(cachedImagePath);
|
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(cachedImagePath);
|
||||||
bool gotDimensions = false;
|
if (decoder && decoder->getDimensions(cachedImagePath, dims)) {
|
||||||
for (int attempt = 0; attempt < 3 && !gotDimensions; attempt++) {
|
|
||||||
if (attempt > 0) {
|
|
||||||
delay(50); // Give a slow SD card time to finish syncing before retrying
|
|
||||||
}
|
|
||||||
gotDimensions = decoder && decoder->getDimensions(cachedImagePath, dims);
|
|
||||||
}
|
|
||||||
if (gotDimensions) {
|
|
||||||
LOG_DBG("EHP", "Image dimensions: %dx%d", dims.width, dims.height);
|
LOG_DBG("EHP", "Image dimensions: %dx%d", dims.width, dims.height);
|
||||||
|
|
||||||
int displayWidth = 0;
|
int displayWidth = 0;
|
||||||
int displayHeight = 0;
|
int displayHeight = 0;
|
||||||
const float emSize = static_cast<float>(self->renderer.getFontAscenderSize(self->fontId));
|
const float emSize = static_cast<float>(self->renderer.getFontAscenderSize(self->fontId));
|
||||||
const CssStyle& imgStyle = cssStyle;
|
CssStyle imgStyle = self->cssParser ? self->cssParser->resolveStyle("img", classAttr) : CssStyle{};
|
||||||
|
// Merge inline style (e.g. style="height: 2em") so it overrides stylesheet rules
|
||||||
|
if (!styleAttr.empty()) {
|
||||||
|
imgStyle.applyOver(CssParser::parseInlineStyle(styleAttr));
|
||||||
|
}
|
||||||
const bool hasCssHeight = imgStyle.hasImageHeight();
|
const bool hasCssHeight = imgStyle.hasImageHeight();
|
||||||
const bool hasCssWidth = imgStyle.hasImageWidth();
|
const bool hasCssWidth = imgStyle.hasImageWidth();
|
||||||
|
|
||||||
@@ -886,14 +855,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
// flush word preceding <br/> to currentTextBlock before calling startNewTextBlock
|
// flush word preceding <br/> to currentTextBlock before calling startNewTextBlock
|
||||||
self->flushPartWordBuffer();
|
self->flushPartWordBuffer();
|
||||||
}
|
}
|
||||||
// Tag the new block so startNewTextBlock can inject a full line-height gap if
|
self->startNewTextBlock(self->blockStyleStack.back().withoutBottom());
|
||||||
// the block remains empty (i.e. <br> is a section separator between paragraphs).
|
|
||||||
// If the block gets text added before the next block opens it becomes non-empty,
|
|
||||||
// goes through makePages() normally, and the flag has no effect (inline <br> case).
|
|
||||||
BlockStyle brStyle =
|
|
||||||
self->currentTextBlock ? self->currentTextBlock->getBlockStyle() : self->blockStyleStack.back();
|
|
||||||
brStyle.fromBrElement = true;
|
|
||||||
self->startNewTextBlock(brStyle);
|
|
||||||
} else {
|
} else {
|
||||||
self->currentCssStyle = cssStyle;
|
self->currentCssStyle = cssStyle;
|
||||||
const auto accumulated = self->blockStyleStack.back().getCombinedBlockStyle(userAlignmentBlockStyle,
|
const auto accumulated = self->blockStyleStack.back().getCombinedBlockStyle(userAlignmentBlockStyle,
|
||||||
@@ -904,7 +866,6 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
|
|
||||||
if (strcmp(name, "li") == 0) {
|
if (strcmp(name, "li") == 0) {
|
||||||
self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR);
|
self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR);
|
||||||
self->listItemBulletOnly = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (matches(name, UNDERLINE_TAGS, std::size(UNDERLINE_TAGS))) {
|
} else if (matches(name, UNDERLINE_TAGS, std::size(UNDERLINE_TAGS))) {
|
||||||
@@ -1154,28 +1115,24 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
|
|||||||
}
|
}
|
||||||
self->partWordBufferIndex = safeLen;
|
self->partWordBufferIndex = safeLen;
|
||||||
self->flushPartWordBuffer();
|
self->flushPartWordBuffer();
|
||||||
self->nextWordContinues = true;
|
|
||||||
for (int j = 0; j < overflow; j++) {
|
for (int j = 0; j < overflow; j++) {
|
||||||
self->partWordBuffer[j] = saved[j];
|
self->partWordBuffer[j] = saved[j];
|
||||||
}
|
}
|
||||||
self->partWordBufferIndex = overflow;
|
self->partWordBufferIndex = overflow;
|
||||||
} else {
|
} else {
|
||||||
self->flushPartWordBuffer();
|
self->flushPartWordBuffer();
|
||||||
self->nextWordContinues = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self->partWordBuffer[self->partWordBufferIndex++] = s[i];
|
self->partWordBuffer[self->partWordBufferIndex++] = s[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep token growth bounded: CSS-heavy spans can fragment text into many tiny
|
// If we have > 750 words buffered up, perform the layout and consume out all but the last line
|
||||||
// words, so flush earlier when embedded CSS is active. We still keep the
|
// There should be enough here to build out 1-2 full pages and doing this will free up a lot of
|
||||||
// "exclude last line" behavior to preserve paragraph flow across chunks.
|
// memory.
|
||||||
const size_t blockWordCount = self->currentTextBlock->size();
|
// Spotted when reading Intermezzo, there are some really long text blocks in there.
|
||||||
const size_t softFlushThreshold =
|
if (self->currentTextBlock->size() > 750) {
|
||||||
self->embeddedStyle ? TEXT_BLOCK_SOFT_FLUSH_WORDS_WITH_CSS : TEXT_BLOCK_SOFT_FLUSH_WORDS;
|
LOG_DBG("EHP", "Text block too long, splitting into multiple pages");
|
||||||
if (blockWordCount > softFlushThreshold) {
|
|
||||||
LOG_DBG("EHP", "Text block soft flush (%u words)", static_cast<unsigned>(blockWordCount));
|
|
||||||
const int horizontalInset = self->currentTextBlock->getBlockStyle().totalHorizontalInset();
|
const int horizontalInset = self->currentTextBlock->getBlockStyle().totalHorizontalInset();
|
||||||
const uint16_t effectiveWidth = (horizontalInset < self->viewportWidth)
|
const uint16_t effectiveWidth = (horizontalInset < self->viewportWidth)
|
||||||
? static_cast<uint16_t>(self->viewportWidth - horizontalInset)
|
? static_cast<uint16_t>(self->viewportWidth - horizontalInset)
|
||||||
@@ -1315,13 +1272,6 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
|
|||||||
}
|
}
|
||||||
self->blockStyleStack.pop_back();
|
self->blockStyleStack.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
// </li> closes: if the bullet never got inline text (empty <li> or <li> with only
|
|
||||||
// block children that were flushed), clear the flag so the next sibling doesn't
|
|
||||||
// merge into this block.
|
|
||||||
if (strcmp(name, "li") == 0) {
|
|
||||||
self->listItemBulletOnly = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ class ChapterHtmlSlimParser {
|
|||||||
int tableDepth = 0;
|
int tableDepth = 0;
|
||||||
int tableRowIndex = 0;
|
int tableRowIndex = 0;
|
||||||
int tableColIndex = 0;
|
int tableColIndex = 0;
|
||||||
bool listItemBulletOnly = false; // true when currentTextBlock has only the <li> bullet
|
|
||||||
|
|
||||||
// Anchor-to-page mapping: tracks which page each HTML id attribute lands on
|
// Anchor-to-page mapping: tracks which page each HTML id attribute lands on
|
||||||
int completedPageCount = 0;
|
int completedPageCount = 0;
|
||||||
|
|||||||
@@ -137,10 +137,8 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error.");
|
LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the (unconditionally-built) item index so every idref lookup uses binary
|
// Sort item index for binary search if we have enough items
|
||||||
// search. Without this, small/medium manifests fell back to an O(spine × manifest)
|
if (self->itemIndex.size() >= LARGE_SPINE_THRESHOLD) {
|
||||||
// linear rescan of .items.bin per itemref (up to ~200ms/item at large scale).
|
|
||||||
if (!self->itemIndex.empty()) {
|
|
||||||
std::sort(self->itemIndex.begin(), self->itemIndex.end(), [](const ItemIndexEntry& a, const ItemIndexEntry& b) {
|
std::sort(self->itemIndex.begin(), self->itemIndex.end(), [](const ItemIndexEntry& a, const ItemIndexEntry& b) {
|
||||||
return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen);
|
return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen);
|
||||||
});
|
});
|
||||||
@@ -286,8 +284,9 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback linear scan, only reached when the index is empty (no manifest
|
// Slow path: linear scan (for small manifests, keeps original behavior)
|
||||||
// items). The fast binary-search path above is used for all real manifests.
|
// TODO: This lookup is slow as need to scan through all items each time.
|
||||||
|
// It can take up to 200ms per item when getting to 1500 items.
|
||||||
self->tempItemStore.seek(0);
|
self->tempItemStore.seek(0);
|
||||||
std::string itemId;
|
std::string itemId;
|
||||||
while (self->tempItemStore.available()) {
|
while (self->tempItemStore.available()) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class ContentOpfParser final : public Print {
|
|||||||
HalFile tempItemStore;
|
HalFile tempItemStore;
|
||||||
std::string coverItemId;
|
std::string coverItemId;
|
||||||
|
|
||||||
// Index for fast idref→href lookup (binary search over .items.bin)
|
// Index for fast idref→href lookup (used only for large EPUBs)
|
||||||
struct ItemIndexEntry {
|
struct ItemIndexEntry {
|
||||||
uint32_t idHash; // FNV-1a hash of itemId
|
uint32_t idHash; // FNV-1a hash of itemId
|
||||||
uint16_t idLen; // length for collision reduction
|
uint16_t idLen; // length for collision reduction
|
||||||
@@ -41,6 +41,8 @@ class ContentOpfParser final : public Print {
|
|||||||
std::deque<ItemIndexEntry> itemIndex;
|
std::deque<ItemIndexEntry> itemIndex;
|
||||||
bool useItemIndex = false;
|
bool useItemIndex = false;
|
||||||
|
|
||||||
|
static constexpr uint16_t LARGE_SPINE_THRESHOLD = 400;
|
||||||
|
|
||||||
// FNV-1a hash function
|
// FNV-1a hash function
|
||||||
static uint32_t fnvHash(const std::string& s) {
|
static uint32_t fnvHash(const std::string& s) {
|
||||||
uint32_t hash = 2166136261u;
|
uint32_t hash = 2166136261u;
|
||||||
|
|||||||
+31
-255
@@ -1,7 +1,6 @@
|
|||||||
#include "GfxRenderer.h"
|
#include "GfxRenderer.h"
|
||||||
|
|
||||||
#include <BidiUtils.h>
|
#include <BidiUtils.h>
|
||||||
#include <BuildScratch.h>
|
|
||||||
#include <FontDecompressor.h>
|
#include <FontDecompressor.h>
|
||||||
#include <HalGPIO.h>
|
#include <HalGPIO.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
@@ -25,36 +24,6 @@ uint8_t resolveSdCardStyle(const SdCardFont& font, const EpdFontFamily::Style st
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const char* resolveVisualText(const char* text, std::string& visualBuffer, BidiUtils::BidiBaseDir baseDir);
|
const char* resolveVisualText(const char* text, std::string& visualBuffer, BidiUtils::BidiBaseDir baseDir);
|
||||||
|
|
||||||
// Appends the shaped visual form of every RTL token in `text` to `shapedOut`.
|
|
||||||
// getTextAdvanceX() measures the bidi-reordered, Arabic-shaped codepoint stream,
|
|
||||||
// so the SD advance table must be warmed with the presentation forms as well as
|
|
||||||
// the logical codepoints — otherwise every RTL word measurement misses the fast
|
|
||||||
// path and falls through to onGlyphMiss(), which opens the .cpfont and reads
|
|
||||||
// glyph metadata + bitmap into the 8-slot overflow ring, once per glyph.
|
|
||||||
// Tokens without RTL lead bytes (0xD6-0xDB) are skipped with a byte scan, so
|
|
||||||
// pure-LTR text pays almost nothing.
|
|
||||||
void appendShapedRtlTokens(const char* text, std::string& shapedOut) {
|
|
||||||
const auto isBreak = [](const char c) { return c == ' ' || c == '\n' || c == '\r' || c == '\t'; };
|
|
||||||
std::string token;
|
|
||||||
std::string visual;
|
|
||||||
const char* p = text;
|
|
||||||
while (*p) {
|
|
||||||
while (*p && isBreak(*p)) ++p;
|
|
||||||
const char* start = p;
|
|
||||||
bool hasRtlBytes = false;
|
|
||||||
while (*p && !isBreak(*p)) {
|
|
||||||
const auto b = static_cast<unsigned char>(*p);
|
|
||||||
hasRtlBytes = hasRtlBytes || (b >= 0xD6 && b <= 0xDB);
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
if (!hasRtlBytes) continue;
|
|
||||||
token.assign(start, p - start);
|
|
||||||
if (BidiUtils::applyBidiVisual(token.c_str(), visual, static_cast<int>(BidiUtils::BidiBaseDir::AUTO))) {
|
|
||||||
shapedOut += visual;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const {
|
const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const {
|
||||||
@@ -88,9 +57,7 @@ const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const Ep
|
|||||||
void GfxRenderer::ensureSdCardFontReady(int fontId, const char* utf8Text, uint8_t styleMask) const {
|
void GfxRenderer::ensureSdCardFontReady(int fontId, const char* utf8Text, uint8_t styleMask) const {
|
||||||
auto it = sdCardFonts_.find(fontId);
|
auto it = sdCardFonts_.find(fontId);
|
||||||
if (it != sdCardFonts_.end()) {
|
if (it != sdCardFonts_.end()) {
|
||||||
std::string shaped;
|
int missed = it->second->buildAdvanceTable(utf8Text, styleMask);
|
||||||
appendShapedRtlTokens(utf8Text, shaped);
|
|
||||||
int missed = it->second->buildAdvanceTable(utf8Text, styleMask, shaped.empty() ? nullptr : shaped.c_str());
|
|
||||||
if (missed > 0) {
|
if (missed > 0) {
|
||||||
LOG_DBG("GFX", "ensureSdCardFontReady: %d glyph(s) not found", missed);
|
LOG_DBG("GFX", "ensureSdCardFontReady: %d glyph(s) not found", missed);
|
||||||
}
|
}
|
||||||
@@ -104,12 +71,7 @@ void GfxRenderer::ensureSdCardFontReady(int fontId, const std::vector<std::strin
|
|||||||
// Augment the persistent advance-only table for layout measurement.
|
// Augment the persistent advance-only table for layout measurement.
|
||||||
// The table survives across paragraphs/sections (capped per font), so
|
// The table survives across paragraphs/sections (capped per font), so
|
||||||
// repeated indexing of the same SD font amortizes glyph-metric SD reads.
|
// repeated indexing of the same SD font amortizes glyph-metric SD reads.
|
||||||
std::string shaped;
|
int missed = it->second->buildAdvanceTable(words, includeHyphen, styleMask);
|
||||||
for (const auto& w : words) {
|
|
||||||
appendShapedRtlTokens(w.c_str(), shaped);
|
|
||||||
}
|
|
||||||
int missed =
|
|
||||||
it->second->buildAdvanceTable(words, includeHyphen, styleMask, shaped.empty() ? nullptr : shaped.c_str());
|
|
||||||
if (missed > 0) {
|
if (missed > 0) {
|
||||||
LOG_DBG("GFX", "ensureSdCardFontReady: %d glyph(s) not found", missed);
|
LOG_DBG("GFX", "ensureSdCardFontReady: %d glyph(s) not found", missed);
|
||||||
}
|
}
|
||||||
@@ -129,47 +91,6 @@ void GfxRenderer::begin() {
|
|||||||
bwBufferChunks.assign((frameBufferSize + BW_BUFFER_CHUNK_SIZE - 1) / BW_BUFFER_CHUNK_SIZE, nullptr);
|
bwBufferChunks.assign((frameBufferSize + BW_BUFFER_CHUNK_SIZE - 1) / BW_BUFFER_CHUNK_SIZE, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::releaseFrameBufferForBuild() {
|
|
||||||
// Lend the framebuffer's bytes IN PLACE: the allocation is never freed, so
|
|
||||||
// it cannot move and repeated loans cannot fragment the heap (the previous
|
|
||||||
// free+realloc model measurably decayed the max contiguous block over a
|
|
||||||
// session). The bytes are deposited in the build-scratch registry so
|
|
||||||
// memory-hungry build phases (e.g. InflateStream's tinfl state + window)
|
|
||||||
// can claim them instead of allocating.
|
|
||||||
uint32_t size = 0;
|
|
||||||
uint8_t* scratch = display.lendFrameBufferStorage(&size);
|
|
||||||
frameBuffer = nullptr;
|
|
||||||
if (scratch) {
|
|
||||||
buildscratch::lend(scratch, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GfxRenderer::restoreFrameBufferAfterBuild() {
|
|
||||||
buildscratch::reclaim();
|
|
||||||
display.returnFrameBufferStorage(); // cannot fail: the allocation was never freed
|
|
||||||
frameBuffer = display.getFrameBuffer();
|
|
||||||
return frameBuffer != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxRenderer::FrameBufferLoan::FrameBufferLoan(GfxRenderer& renderer) : renderer_(renderer) {
|
|
||||||
// Nesting guard: if the framebuffer is already lent out (an outer loan),
|
|
||||||
// stay inert so this end() cannot return storage the outer loan still owns.
|
|
||||||
if (!renderer_.hasFrameBuffer()) return;
|
|
||||||
renderer_.releaseFrameBufferForBuild();
|
|
||||||
active_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GfxRenderer::FrameBufferLoan::end() {
|
|
||||||
if (!active_) return;
|
|
||||||
active_ = false;
|
|
||||||
if (!renderer_.restoreFrameBufferAfterBuild()) {
|
|
||||||
// Only reachable if the framebuffer never existed, which begin() already
|
|
||||||
// asserts against; kept as a backstop since running blind helps nobody.
|
|
||||||
LOG_ERR("GFX", "Framebuffer restore failed - restarting");
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GfxRenderer::isFontCacheScanning() const { return fontCacheManager_ && fontCacheManager_->isScanning(); }
|
bool GfxRenderer::isFontCacheScanning() const { return fontCacheManager_ && fontCacheManager_->isScanning(); }
|
||||||
|
|
||||||
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) {
|
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) {
|
||||||
@@ -213,61 +134,6 @@ static inline void rotateCoordinates(const GfxRenderer::Orientation orientation,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output of screenRectToAlignedMemRect: a rectangle in panel-memory
|
|
||||||
// coordinates whose x and width are guaranteed to be multiples of 8 (the
|
|
||||||
// SDK's EInkDisplay::displayWindow alignment requirement). `valid == false`
|
|
||||||
// means the input was empty or fully outside the panel.
|
|
||||||
struct AlignedMemRect {
|
|
||||||
uint16_t x = 0;
|
|
||||||
uint16_t y = 0;
|
|
||||||
uint16_t w = 0;
|
|
||||||
uint16_t h = 0;
|
|
||||||
bool valid = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Translate a screen-coordinate rectangle (the coordinate system used by
|
|
||||||
// fillRect / drawText / the rest of the renderer's public API) into a
|
|
||||||
// panel-memory rectangle suitable for direct framebuffer indexing. Rotates
|
|
||||||
// the rectangle's two opposite corners with rotateCoordinates(), takes the
|
|
||||||
// bounding box (which naturally swaps width/height in Portrait /
|
|
||||||
// PortraitInverted), then snaps the x extent outward to multiples of 8 and
|
|
||||||
// clamps to panel bounds. Precondition: panel dims are multiples of 8 (true
|
|
||||||
// for the 800x480 panel), so clamping cannot re-break alignment.
|
|
||||||
static AlignedMemRect screenRectToAlignedMemRect(GfxRenderer::Orientation orientation, int sx, int sy, int sw, int sh,
|
|
||||||
uint16_t panelWidth, uint16_t panelHeight) {
|
|
||||||
AlignedMemRect out;
|
|
||||||
if (sw <= 0 || sh <= 0) return out;
|
|
||||||
|
|
||||||
int x0, y0, x1, y1;
|
|
||||||
rotateCoordinates(orientation, sx, sy, &x0, &y0, panelWidth, panelHeight);
|
|
||||||
rotateCoordinates(orientation, sx + sw - 1, sy + sh - 1, &x1, &y1, panelWidth, panelHeight);
|
|
||||||
|
|
||||||
const int memXLo = std::min(x0, x1);
|
|
||||||
const int memYLo = std::min(y0, y1);
|
|
||||||
const int memXHi = std::max(x0, x1) + 1; // exclusive upper bound
|
|
||||||
const int memYHi = std::max(y0, y1) + 1;
|
|
||||||
|
|
||||||
// Snap x outward to multiples of 8.
|
|
||||||
int alignedXLo = memXLo & ~0x7; // round down
|
|
||||||
int alignedXHi = (memXHi + 7) & ~0x7; // round up
|
|
||||||
|
|
||||||
if (alignedXLo < 0) alignedXLo = 0;
|
|
||||||
if (alignedXHi > panelWidth) alignedXHi = panelWidth;
|
|
||||||
int clampedYLo = memYLo;
|
|
||||||
int clampedYHi = memYHi;
|
|
||||||
if (clampedYLo < 0) clampedYLo = 0;
|
|
||||||
if (clampedYHi > panelHeight) clampedYHi = panelHeight;
|
|
||||||
|
|
||||||
if (alignedXHi <= alignedXLo || clampedYHi <= clampedYLo) return out;
|
|
||||||
|
|
||||||
out.x = static_cast<uint16_t>(alignedXLo);
|
|
||||||
out.y = static_cast<uint16_t>(clampedYLo);
|
|
||||||
out.w = static_cast<uint16_t>(alignedXHi - alignedXLo);
|
|
||||||
out.h = static_cast<uint16_t>(clampedYHi - clampedYLo);
|
|
||||||
out.valid = true;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class TextRotation { None, Rotated90CW };
|
enum class TextRotation { None, Rotated90CW };
|
||||||
|
|
||||||
// Shared glyph rendering logic for normal and rotated text.
|
// Shared glyph rendering logic for normal and rotated text.
|
||||||
@@ -416,8 +282,17 @@ static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode
|
|||||||
const uint8_t bmpVal = 3 - ((byte >> bit_index) & 0x3);
|
const uint8_t bmpVal = 3 - ((byte >> bit_index) & 0x3);
|
||||||
|
|
||||||
if (renderMode == GfxRenderer::BW && bmpVal < 3) {
|
if (renderMode == GfxRenderer::BW && bmpVal < 3) {
|
||||||
// Black (also paints over the grays in BW mode)
|
// Default: gray pixels paint solid black. Required when grayscale
|
||||||
|
// passes follow (their LUT lightens pixels it assumes were just
|
||||||
|
// driven solid black), and it is the plain non-AA look otherwise.
|
||||||
|
// Fast AA: partial (anti-aliased edge) coverage dithers with the
|
||||||
|
// same period-2 patterns as fillRectDither (dark grey = 50%
|
||||||
|
// checkerboard, light grey = 25%), approximating AA in a single
|
||||||
|
// BW pass. Skipped pixels leave the background untouched.
|
||||||
|
if (!renderer.isFastAntiAliasing() || bmpVal == 0 || (bmpVal == 1 && ((screenX + screenY) & 1) == 0) ||
|
||||||
|
(bmpVal == 2 && (screenX & 1) == 0 && (screenY & 1) == 0)) {
|
||||||
renderer.drawPixel(screenX, screenY, pixelState);
|
renderer.drawPixel(screenX, screenY, pixelState);
|
||||||
|
}
|
||||||
} else if (renderMode == GfxRenderer::GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
|
} 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)
|
// Light gray (also mark the MSB if it's going to be a dark gray too)
|
||||||
// Dedicated X3 gray LUTs now provide proper 4-level gray on both devices
|
// Dedicated X3 gray LUTs now provide proper 4-level gray on both devices
|
||||||
@@ -552,21 +427,18 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
|
|||||||
uint32_t cp;
|
uint32_t cp;
|
||||||
uint32_t prevCp = 0;
|
uint32_t prevCp = 0;
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&textCursor)))) {
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&textCursor)))) {
|
||||||
// RTL vowel marks (Hebrew niqqud, Arabic harakat) ride the combining-mark
|
// Skip Hebrew Niqqud (vowel marks)
|
||||||
// path: zero-advance overlays on the preceding base glyph (applyBidiVisual
|
// Temporary: avoid adding Niqqud to built-in fonts. Remove when custom fonts are supported.
|
||||||
// emits base-then-marks per UAX#9 L3). anchorFor pins position-sensitive
|
if (cp >= 0x0591 && cp <= 0x05C7) {
|
||||||
// niqqud (dagesh, shin/sin dots, holam) to their spot on the base; other
|
continue;
|
||||||
// marks stay centered, raised above the base or (kasra) at their
|
}
|
||||||
// font-native position. Fonts without their glyphs — the built-ins — miss
|
|
||||||
// the getGlyph lookup and skip them, as before.
|
if (utf8IsCombiningMark(cp)) {
|
||||||
if (utf8IsCombiningMark(cp) || BidiUtils::isTransparentMark(cp)) {
|
|
||||||
const EpdGlyph* combiningGlyph = font.getGlyph(cp, style);
|
const EpdGlyph* combiningGlyph = font.getGlyph(cp, style);
|
||||||
if (!combiningGlyph) continue;
|
if (!combiningGlyph) continue;
|
||||||
const auto anchor = combiningMark::anchorFor(cp);
|
const int raiseBy = combiningMark::raiseAboveBase(combiningGlyph->top, combiningGlyph->height, lastBaseTop);
|
||||||
const int raiseBy =
|
const int combiningX = combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, combiningGlyph->left,
|
||||||
combiningMark::raiseAboveBase(anchor, combiningGlyph->top, combiningGlyph->height, lastBaseTop);
|
combiningGlyph->width);
|
||||||
const int combiningX = combiningMark::anchorOver(anchor, lastBaseX, lastBaseLeft, lastBaseWidth,
|
|
||||||
combiningGlyph->left, combiningGlyph->width);
|
|
||||||
renderCharImpl<TextRotation::None>(*this, renderMode, font, cp, combiningX, yPos - raiseBy, black, style);
|
renderCharImpl<TextRotation::None>(*this, renderMode, font, cp, combiningX, yPos - raiseBy, black, style);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1506,53 +1378,6 @@ void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const
|
|||||||
display.displayBuffer(refreshMode, fadingFix);
|
display.displayBuffer(refreshMode, fadingFix);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::displayBufferAsync(const HalDisplay::RefreshMode refreshMode) const {
|
|
||||||
// The async path has no turn-off-screen hook, which the sunlight fading fix
|
|
||||||
// relies on; keep those users on the blocking path.
|
|
||||||
if (fadingFix) {
|
|
||||||
display.displayBuffer(refreshMode, fadingFix);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
display.displayBufferAsync(refreshMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GfxRenderer::waitRefreshComplete() const { display.waitRefreshComplete(); }
|
|
||||||
|
|
||||||
bool GfxRenderer::supportsAsyncRefresh() const { return !fadingFix && display.supportsAsyncRefresh(); }
|
|
||||||
|
|
||||||
size_t GfxRenderer::readFramebufferRegion(int x, int y, int w, int h, uint8_t* dst, size_t dstCapacity) const {
|
|
||||||
if (dst == nullptr || w <= 0 || h <= 0) return 0;
|
|
||||||
|
|
||||||
const AlignedMemRect mem = screenRectToAlignedMemRect(orientation, x, y, w, h, panelWidth, panelHeight);
|
|
||||||
if (!mem.valid) return 0;
|
|
||||||
|
|
||||||
const size_t rowBytes = mem.w / 8; // exact: mem.w is a multiple of 8
|
|
||||||
const size_t needed = rowBytes * mem.h;
|
|
||||||
if (needed > dstCapacity) return 0;
|
|
||||||
|
|
||||||
for (uint16_t row = 0; row < mem.h; ++row) {
|
|
||||||
const uint8_t* srcRow = frameBuffer + (static_cast<uint32_t>(mem.y + row) * panelWidthBytes) + (mem.x / 8);
|
|
||||||
uint8_t* dstRow = dst + (static_cast<size_t>(row) * rowBytes);
|
|
||||||
memcpy(dstRow, srcRow, rowBytes);
|
|
||||||
}
|
|
||||||
return needed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GfxRenderer::writeFramebufferRegion(int x, int y, int w, int h, const uint8_t* src) {
|
|
||||||
if (src == nullptr || w <= 0 || h <= 0) return;
|
|
||||||
|
|
||||||
const AlignedMemRect mem = screenRectToAlignedMemRect(orientation, x, y, w, h, panelWidth, panelHeight);
|
|
||||||
if (!mem.valid) return;
|
|
||||||
|
|
||||||
const size_t rowBytes = mem.w / 8; // exact: mem.w is a multiple of 8
|
|
||||||
|
|
||||||
for (uint16_t row = 0; row < mem.h; ++row) {
|
|
||||||
const uint8_t* srcRow = src + (static_cast<size_t>(row) * rowBytes);
|
|
||||||
uint8_t* dstRow = frameBuffer + (static_cast<uint32_t>(mem.y + row) * panelWidthBytes) + (mem.x / 8);
|
|
||||||
memcpy(dstRow, srcRow, rowBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth,
|
std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth,
|
||||||
const EpdFontFamily::Style style) const {
|
const EpdFontFamily::Style style) const {
|
||||||
if (!text || maxWidth <= 0) return "";
|
if (!text || maxWidth <= 0) return "";
|
||||||
@@ -1666,35 +1491,6 @@ int GfxRenderer::getScreenHeight() const {
|
|||||||
return panelWidth;
|
return panelWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::tapToLogical(float nx, float ny, int& outX, int& outY) const {
|
|
||||||
int phyX = static_cast<int>(nx * panelWidth);
|
|
||||||
int phyY = static_cast<int>(ny * panelHeight);
|
|
||||||
if (phyX < 0) phyX = 0;
|
|
||||||
if (phyX > panelWidth - 1) phyX = panelWidth - 1;
|
|
||||||
if (phyY < 0) phyY = 0;
|
|
||||||
if (phyY > panelHeight - 1) phyY = panelHeight - 1;
|
|
||||||
|
|
||||||
switch (orientation) {
|
|
||||||
case Portrait:
|
|
||||||
outX = panelHeight - 1 - phyY;
|
|
||||||
outY = phyX;
|
|
||||||
break;
|
|
||||||
case PortraitInverted:
|
|
||||||
outX = phyY;
|
|
||||||
outY = panelWidth - 1 - phyX;
|
|
||||||
break;
|
|
||||||
case LandscapeClockwise:
|
|
||||||
outX = panelWidth - 1 - phyX;
|
|
||||||
outY = panelHeight - 1 - phyY;
|
|
||||||
break;
|
|
||||||
case LandscapeCounterClockwise:
|
|
||||||
default:
|
|
||||||
outX = phyX;
|
|
||||||
outY = phyY;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate a logical rect through rotateCoordinates and take the bounding
|
// Translate a logical rect through rotateCoordinates and take the bounding
|
||||||
// box of its four corners on the physical panel. Output coords are inclusive
|
// box of its four corners on the physical panel. Output coords are inclusive
|
||||||
// and clamped. Returns false if the rect ends up fully off-panel.
|
// and clamped. Returns false if the rect ends up fully off-panel.
|
||||||
@@ -1828,15 +1624,6 @@ int GfxRenderer::getKerning(const int fontId, const uint32_t leftCp, const uint3
|
|||||||
}
|
}
|
||||||
|
|
||||||
int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFamily::Style style) const {
|
int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFamily::Style style) const {
|
||||||
// Measure the exact codepoint stream drawText renders: bidi-reordered and
|
|
||||||
// Arabic-shaped (contextual presentation forms, Lam-Alef collapse).
|
|
||||||
// Measuring the raw logical text counts the Alef a ligature absorbs and
|
|
||||||
// uses base-letter advances instead of presentation-form advances, so RTL
|
|
||||||
// lines come out wider than they draw — uneven word gaps and a ragged
|
|
||||||
// right margin.
|
|
||||||
std::string visual;
|
|
||||||
text = resolveVisualText(text, visual, BidiUtils::BidiBaseDir::AUTO);
|
|
||||||
|
|
||||||
// Advance table fast-path for SD card fonts during layout.
|
// Advance table fast-path for SD card fonts during layout.
|
||||||
// No kerning/ligature lookup — consistent with previous metadataOnly behavior
|
// No kerning/ligature lookup — consistent with previous metadataOnly behavior
|
||||||
// where kern/lig data was not loaded.
|
// where kern/lig data was not loaded.
|
||||||
@@ -1852,10 +1639,6 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFami
|
|||||||
}
|
}
|
||||||
const auto& font = fontIt->second;
|
const auto& font = fontIt->second;
|
||||||
while (uint32_t cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text))) {
|
while (uint32_t cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text))) {
|
||||||
// RTL vowel marks (niqqud/harakat) are zero-advance overlays in drawText — no width.
|
|
||||||
if (BidiUtils::isTransparentMark(cp)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int32_t advFP = sdIt->second->getAdvance(cp, styleIdx);
|
int32_t advFP = sdIt->second->getAdvance(cp, styleIdx);
|
||||||
if (advFP == 0 && !utf8IsCombiningMark(cp)) {
|
if (advFP == 0 && !utf8IsCombiningMark(cp)) {
|
||||||
const EpdGlyph* glyph = font.getGlyph(cp, style);
|
const EpdGlyph* glyph = font.getGlyph(cp, style);
|
||||||
@@ -1878,10 +1661,6 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFami
|
|||||||
int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap
|
int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap
|
||||||
const auto& font = fontIt->second;
|
const auto& font = fontIt->second;
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||||
// RTL vowel marks (niqqud/harakat) are zero-advance overlays in drawText — no width.
|
|
||||||
if (BidiUtils::isTransparentMark(cp)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (utf8IsCombiningMark(cp)) {
|
if (utf8IsCombiningMark(cp)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1958,21 +1737,18 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
|||||||
uint32_t cp;
|
uint32_t cp;
|
||||||
uint32_t prevCp = 0;
|
uint32_t prevCp = 0;
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||||
// RTL vowel marks (Hebrew niqqud, Arabic harakat) ride the combining-mark
|
// Skip Hebrew Niqqud (vowel marks)
|
||||||
// path: zero-advance overlays on the preceding base glyph (applyBidiVisual
|
// Temporary: avoid adding Niqqud to built-in fonts. Remove when custom fonts are supported.
|
||||||
// emits base-then-marks per UAX#9 L3). anchorFor pins position-sensitive
|
if (cp >= 0x0591 && cp <= 0x05C7) {
|
||||||
// niqqud (dagesh, shin/sin dots, holam) to their spot on the base; other
|
continue;
|
||||||
// marks stay centered, raised above the base or (kasra) at their
|
}
|
||||||
// font-native position. Fonts without their glyphs — the built-ins — miss
|
|
||||||
// the getGlyph lookup and skip them, as before.
|
if (utf8IsCombiningMark(cp)) {
|
||||||
if (utf8IsCombiningMark(cp) || BidiUtils::isTransparentMark(cp)) {
|
|
||||||
const EpdGlyph* combiningGlyph = font.getGlyph(cp, style);
|
const EpdGlyph* combiningGlyph = font.getGlyph(cp, style);
|
||||||
if (!combiningGlyph) continue;
|
if (!combiningGlyph) continue;
|
||||||
const auto anchor = combiningMark::anchorFor(cp);
|
const int raiseBy = combiningMark::raiseAboveBase(combiningGlyph->top, combiningGlyph->height, lastBaseTop);
|
||||||
const int raiseBy =
|
|
||||||
combiningMark::raiseAboveBase(anchor, combiningGlyph->top, combiningGlyph->height, lastBaseTop);
|
|
||||||
const int combiningX = x - raiseBy;
|
const int combiningX = x - raiseBy;
|
||||||
const int combiningY = combiningMark::anchorOverRotated90CW(anchor, lastBaseY, lastBaseLeft, lastBaseWidth,
|
const int combiningY = combiningMark::centerOverRotated90CW(lastBaseY, lastBaseLeft, lastBaseWidth,
|
||||||
combiningGlyph->left, combiningGlyph->width);
|
combiningGlyph->left, combiningGlyph->width);
|
||||||
renderCharImpl<TextRotation::Rotated90CW>(*this, renderMode, font, cp, combiningX, combiningY, black, style);
|
renderCharImpl<TextRotation::Rotated90CW>(*this, renderMode, font, cp, combiningX, combiningY, black, style);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ class GfxRenderer {
|
|||||||
RenderMode renderMode;
|
RenderMode renderMode;
|
||||||
Orientation orientation;
|
Orientation orientation;
|
||||||
bool fadingFix;
|
bool fadingFix;
|
||||||
|
// Fast text anti-aliasing: when set, BW-mode rendering of 2-bit glyphs
|
||||||
|
// dithers partial-coverage (gray) pixels instead of painting them solid
|
||||||
|
// black, approximating AA in a single BW pass with no grayscale refresh.
|
||||||
|
// Must stay false when grayscale LSB/MSB passes follow the BW render: the
|
||||||
|
// gray LUT lightens pixels it assumes were just driven solid black, and
|
||||||
|
// dither-skipped white pixels would take its white-drive frames unopposed.
|
||||||
|
bool fastAntiAliasing = false;
|
||||||
uint8_t* frameBuffer = nullptr;
|
uint8_t* frameBuffer = nullptr;
|
||||||
uint16_t panelWidth = HalDisplay::DISPLAY_WIDTH;
|
uint16_t panelWidth = HalDisplay::DISPLAY_WIDTH;
|
||||||
uint16_t panelHeight = HalDisplay::DISPLAY_HEIGHT;
|
uint16_t panelHeight = HalDisplay::DISPLAY_HEIGHT;
|
||||||
@@ -134,19 +141,7 @@ class GfxRenderer {
|
|||||||
// Screen ops
|
// Screen ops
|
||||||
int getScreenWidth() const;
|
int getScreenWidth() const;
|
||||||
int getScreenHeight() const;
|
int getScreenHeight() const;
|
||||||
void tapToLogical(float nx, float ny, int& outX, int& outY) const;
|
|
||||||
void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const;
|
void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const;
|
||||||
// Non-blocking refresh: starts the waveform and returns so CPU work (e.g.
|
|
||||||
// grayscale strip rendering) can overlap the panel's refresh time. The
|
|
||||||
// framebuffer must stay untouched until waitRefreshComplete(). Falls back to
|
|
||||||
// a blocking refresh when fadingFix is enabled or the panel lacks deferral
|
|
||||||
// support. See HalDisplay::displayBufferAsync for the baseline contract.
|
|
||||||
void displayBufferAsync(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const;
|
|
||||||
void waitRefreshComplete() const;
|
|
||||||
// True when displayBufferAsync() genuinely overlaps: panel defers and
|
|
||||||
// fadingFix isn't forcing the blocking path. Callers can skip overlap
|
|
||||||
// scaffolding (e.g. whole-plane grayscale buffers) when false.
|
|
||||||
bool supportsAsyncRefresh() const;
|
|
||||||
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
||||||
// void displayWindow(int x, int y, int width, int height) const;
|
// void displayWindow(int x, int y, int width, int height) const;
|
||||||
void invertScreen() const;
|
void invertScreen() const;
|
||||||
@@ -201,15 +196,6 @@ class GfxRenderer {
|
|||||||
void drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;
|
void drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;
|
||||||
void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) const;
|
void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) const;
|
||||||
|
|
||||||
// Snapshot / restore a screen-coordinate framebuffer region (byte-aligned in
|
|
||||||
// panel memory). readFramebufferRegion returns the bytes written to dst, or
|
|
||||||
// 0 when the region is empty, offscreen, or exceeds dstCapacity. Pass the
|
|
||||||
// same rectangle to writeFramebufferRegion to restore the saved pixels.
|
|
||||||
// Enables partial-repaint patterns (e.g. moving a selection highlight)
|
|
||||||
// without re-rendering the whole page.
|
|
||||||
size_t readFramebufferRegion(int x, int y, int w, int h, uint8_t* dst, size_t dstCapacity) const;
|
|
||||||
void writeFramebufferRegion(int x, int y, int w, int h, const uint8_t* src);
|
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR,
|
int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR,
|
||||||
BidiUtils::BidiBaseDir baseDir = BidiUtils::BidiBaseDir::AUTO) const;
|
BidiUtils::BidiBaseDir baseDir = BidiUtils::BidiBaseDir::AUTO) const;
|
||||||
@@ -245,6 +231,8 @@ class GfxRenderer {
|
|||||||
// Grayscale functions
|
// Grayscale functions
|
||||||
void setRenderMode(const RenderMode mode) { this->renderMode = mode; }
|
void setRenderMode(const RenderMode mode) { this->renderMode = mode; }
|
||||||
RenderMode getRenderMode() const { return renderMode; }
|
RenderMode getRenderMode() const { return renderMode; }
|
||||||
|
void setFastAntiAliasing(const bool enabled) { this->fastAntiAliasing = enabled; }
|
||||||
|
bool isFastAntiAliasing() const { return fastAntiAliasing; }
|
||||||
// Grayscale preconditioning settle pass (no-op on X4). The rect overload
|
// Grayscale preconditioning settle pass (no-op on X4). The rect overload
|
||||||
// takes the gray region in LOGICAL screen coordinates and rotates it to the
|
// takes the gray region in LOGICAL screen coordinates and rotates it to the
|
||||||
// panel; the no-arg overload settles the full frame. Call after the BW base
|
// panel; the no-arg overload settles the full frame. Call after the BW base
|
||||||
@@ -271,34 +259,6 @@ class GfxRenderer {
|
|||||||
// Font helpers
|
// Font helpers
|
||||||
const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const;
|
const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const;
|
||||||
|
|
||||||
// Lend the 48 KB framebuffer's bytes to a memory-hungry phase (chapter
|
|
||||||
// builds) WITHOUT freeing the allocation, so it never moves and repeated
|
|
||||||
// loans cannot fragment the heap. Between release and restore NOTHING may
|
|
||||||
// draw or display — the panel keeps showing its last refreshed image. The
|
|
||||||
// lent bytes are published via buildscratch::claim() for consumers like
|
|
||||||
// InflateStream. restore returns the buffer white, so the caller must
|
|
||||||
// redraw the full screen; it cannot fail (no allocation involved).
|
|
||||||
void releaseFrameBufferForBuild();
|
|
||||||
bool restoreFrameBufferAfterBuild();
|
|
||||||
bool hasFrameBuffer() const { return frameBuffer != nullptr; }
|
|
||||||
|
|
||||||
// RAII form of the loan above, for blocking build regions with early-return
|
|
||||||
// error paths: restores on scope exit (or explicitly via end()). Display the
|
|
||||||
// popup/screen the panel should hold BEFORE constructing one. Constructing
|
|
||||||
// while the framebuffer is already lent yields an inert loan (nesting-safe).
|
|
||||||
class FrameBufferLoan {
|
|
||||||
public:
|
|
||||||
explicit FrameBufferLoan(GfxRenderer& renderer);
|
|
||||||
~FrameBufferLoan() { end(); }
|
|
||||||
void end();
|
|
||||||
FrameBufferLoan(const FrameBufferLoan&) = delete;
|
|
||||||
FrameBufferLoan& operator=(const FrameBufferLoan&) = delete;
|
|
||||||
|
|
||||||
private:
|
|
||||||
GfxRenderer& renderer_;
|
|
||||||
bool active_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Low level functions
|
// Low level functions
|
||||||
uint8_t* getFrameBuffer() const;
|
uint8_t* getFrameBuffer() const;
|
||||||
size_t getBufferSize() const;
|
size_t getBufferSize() const;
|
||||||
|
|||||||
@@ -1,394 +0,0 @@
|
|||||||
_language_name: "العربية"
|
|
||||||
_language_code: "AR"
|
|
||||||
_order: "28"
|
|
||||||
|
|
||||||
STR_CROSSPOINT: "CrossPoint"
|
|
||||||
STR_BOOTING: "جاري بدء التشغيل"
|
|
||||||
STR_SLEEPING: "وضع السكون"
|
|
||||||
STR_ENTERING_SLEEP: "جاري الدخول في وضع السكون"
|
|
||||||
STR_BROWSE_FILES: "تصفح الملفات"
|
|
||||||
STR_FILE_TRANSFER: "نقل الملفات"
|
|
||||||
STR_SETTINGS_TITLE: "الإعدادات"
|
|
||||||
STR_CONTINUE_READING: "متابعة القراءة"
|
|
||||||
STR_NO_OPEN_BOOK: "لا يوجد كتاب مفتوح"
|
|
||||||
STR_START_READING: "ابدأ القراءة أدناه"
|
|
||||||
STR_NO_FILES_FOUND: "لم يتم العثور على ملفات"
|
|
||||||
STR_SELECT_CHAPTER: "اختر الفصل"
|
|
||||||
STR_NO_CHAPTERS: "لا توجد فصول"
|
|
||||||
STR_END_OF_BOOK: "نهاية الكتاب"
|
|
||||||
STR_EMPTY_CHAPTER: "فصل فارغ"
|
|
||||||
STR_INDEXING: "جاري الفهرسة"
|
|
||||||
STR_INDEX_FAILED: "فشلت الفهرسة - كتاب غير صالح"
|
|
||||||
STR_MEMORY_ERROR: "خطأ في الذاكرة"
|
|
||||||
STR_PAGE_LOAD_ERROR: "خطأ في تحميل الصفحة"
|
|
||||||
STR_EMPTY_FILE: "ملف فارغ"
|
|
||||||
STR_OUT_OF_BOUNDS: "خارج النطاق"
|
|
||||||
STR_LOADING: "جاري التحميل..."
|
|
||||||
STR_LOADING_POPUP: "جاري التحميل"
|
|
||||||
STR_WIFI_NETWORKS: "شبكات Wi-Fi"
|
|
||||||
STR_NO_NETWORKS: "لم يتم العثور على شبكات"
|
|
||||||
STR_NETWORKS_FOUND: "تم العثور على %zu شبكة"
|
|
||||||
STR_SCANNING: "جاري البحث..."
|
|
||||||
STR_FINDING_SAVED_WIFI: "جاري البحث عن شبكة Wi-Fi محفوظة..."
|
|
||||||
STR_CONNECTING: "جاري الاتصال..."
|
|
||||||
STR_CONNECTING_SAVED_WIFI: "جاري الاتصال بشبكة Wi-Fi محفوظة..."
|
|
||||||
STR_SHOW_NETWORKS: "عرض"
|
|
||||||
STR_CONNECTED: "تم الاتصال!"
|
|
||||||
STR_CONNECTION_FAILED: "فشل الاتصال"
|
|
||||||
STR_FORGET_NETWORK: "نسيان هذه الشبكة؟"
|
|
||||||
STR_SAVE_PASSWORD: "حفظ كلمة المرور للمرة القادمة؟"
|
|
||||||
STR_PRESS_OK_SCAN: "اضغط موافق لإعادة البحث"
|
|
||||||
STR_JOIN_NETWORK: "الانضمام إلى شبكة"
|
|
||||||
STR_CREATE_HOTSPOT: "إنشاء نقطة اتصال"
|
|
||||||
STR_JOIN_DESC: "الاتصال بشبكة Wi-Fi موجودة"
|
|
||||||
STR_HOTSPOT_DESC: "إنشاء شبكة Wi-Fi يمكن للآخرين الانضمام إليها"
|
|
||||||
STR_STARTING_HOTSPOT: "جاري تشغيل نقطة الاتصال..."
|
|
||||||
STR_HOTSPOT_MODE: "وضع نقطة الاتصال"
|
|
||||||
STR_CONNECT_WIFI_HINT: "قم بتوصيل جهازك بشبكة Wi-Fi هذه"
|
|
||||||
STR_OPEN_URL_HINT: "افتح هذا العنوان في المتصفح"
|
|
||||||
STR_OR_HTTP_PREFIX: "أو http://"
|
|
||||||
STR_SCAN_QR_HINT: "أو امسح رمز QR بهاتفك:"
|
|
||||||
STR_CALIBRE_WIRELESS: "اتصال Calibre اللاسلكي"
|
|
||||||
STR_NETWORK_LEGEND: "* = مشفرة | + = محفوظة"
|
|
||||||
STR_MAC_ADDRESS: "عنوان MAC:"
|
|
||||||
STR_CHECKING_WIFI: "جاري فحص Wi-Fi..."
|
|
||||||
STR_ENTER_WIFI_PASSWORD: "أدخل كلمة مرور الشبكة"
|
|
||||||
STR_ADD_HIDDEN_NETWORK: "إضافة شبكة مخفية..."
|
|
||||||
STR_ENTER_WIFI_SSID: "أدخل اسم الشبكة (SSID)"
|
|
||||||
STR_TO_PREFIX: "إلى "
|
|
||||||
STR_CALIBRE_RECEIVING: "جاري الاستلام: "
|
|
||||||
STR_CALIBRE_RECEIVED: "تم الاستلام: "
|
|
||||||
STR_CALIBRE_INSTRUCTION_1: "1) قم بتثبيت إضافة CrossPoint Reader"
|
|
||||||
STR_CALIBRE_INSTRUCTION_2: "2) اتصل بنفس شبكة Wi-Fi"
|
|
||||||
STR_CALIBRE_INSTRUCTION_3: "3) في Calibre: اختر \"إرسال إلى الجهاز\""
|
|
||||||
STR_CALIBRE_INSTRUCTION_4: "اترك هذه الشاشة مفتوحة أثناء الإرسال"
|
|
||||||
STR_CAT_DISPLAY: "الشاشة"
|
|
||||||
STR_CAT_READER: "القراءة"
|
|
||||||
STR_CAT_CONTROLS: "الأزرار"
|
|
||||||
STR_CAT_SYSTEM: "النظام"
|
|
||||||
STR_SLEEP_SCREEN: "شاشة السكون"
|
|
||||||
STR_QUICK_RESUME_TIMEOUT: "استئناف سريع بعد المهلة"
|
|
||||||
STR_SLEEP_COVER_MODE: "عرض الغلاف في وضع السكون"
|
|
||||||
STR_HIDE_BATTERY: "إخفاء نسبة البطارية"
|
|
||||||
STR_EXTRA_SPACING: "تباعد إضافي بين الفقرات"
|
|
||||||
STR_TEXT_AA: "تنعيم حواف النص"
|
|
||||||
STR_IMAGES: "الصور"
|
|
||||||
STR_IMAGES_DISPLAY: "عرض"
|
|
||||||
STR_IMAGES_PLACEHOLDER: "عنصر نائب"
|
|
||||||
STR_IMAGES_SUPPRESS: "إخفاء"
|
|
||||||
STR_EOB_HOME: "الرئيسية"
|
|
||||||
STR_EOB_CONTINUE_WITH: "المتابعة إلى"
|
|
||||||
STR_SHORT_PWR_BTN: "ضغطة قصيرة على زر التشغيل"
|
|
||||||
STR_ORIENTATION: "اتجاه القراءة"
|
|
||||||
STR_SIDE_BTN_LAYOUT: "تخطيط الأزرار الجانبية (أثناء القراءة)"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "تدوير الأزرار الأمامية مع الشاشة"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR: "سلوك الضغطة الطويلة"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "إيقاف"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "تخطي الفصل"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "تغيير الاتجاه"
|
|
||||||
STR_LONG_PRESS_MENU: "قائمة الضغطة الطويلة"
|
|
||||||
STR_FONT_PREVIEW_TEXT: "نص حكيم له سر قاطع وذو شأن عظيم مكتوب على ثوب أخضر ومغلف بجلد أزرق"
|
|
||||||
STR_FONT_FAMILY: "خط القراءة"
|
|
||||||
STR_FONT_SIZE: "حجم الخط"
|
|
||||||
STR_LINE_SPACING: "تباعد الأسطر"
|
|
||||||
STR_SCREEN_MARGIN: "هوامش شاشة القراءة"
|
|
||||||
STR_PARA_ALIGNMENT: "محاذاة الفقرات"
|
|
||||||
STR_HYPHENATION: "تقسيم الكلمات"
|
|
||||||
STR_TIME_TO_SLEEP: "مهلة الدخول في السكون"
|
|
||||||
STR_SHOW_HIDDEN_FILES: "عرض الملفات المخفية"
|
|
||||||
STR_REMOVE_READ_FROM_RECENTS: "إزالة الكتب المقروءة من قائمة الكتب الأخيرة"
|
|
||||||
STR_MOVE_FINISHED_TO_READ: "نقل الكتب المنتهية إلى مجلد المقروءة"
|
|
||||||
STR_REFRESH_FREQ: "معدل تحديث الشاشة"
|
|
||||||
STR_KOREADER_SYNC: "مزامنة KOReader"
|
|
||||||
STR_CHECK_UPDATES: "التحقق من التحديثات"
|
|
||||||
STR_LANGUAGE: "اللغة"
|
|
||||||
STR_CLEAR_READING_CACHE: "مسح ذاكرة القراءة المؤقتة"
|
|
||||||
STR_USERNAME: "اسم المستخدم"
|
|
||||||
STR_PASSWORD: "كلمة المرور"
|
|
||||||
STR_SYNC_SERVER_URL: "عنوان خادم المزامنة"
|
|
||||||
STR_DOCUMENT_MATCHING: "مطابقة المستندات"
|
|
||||||
STR_SEND_METADATA: "إرسال البيانات الوصفية للمستند"
|
|
||||||
STR_AUTHENTICATE: "تسجيل الدخول"
|
|
||||||
STR_KOREADER_USERNAME: "اسم مستخدم KOReader"
|
|
||||||
STR_KOREADER_PASSWORD: "كلمة مرور KOReader"
|
|
||||||
STR_FILENAME: "اسم الملف"
|
|
||||||
STR_BINARY: "ثنائي"
|
|
||||||
STR_SET_CREDENTIALS_FIRST: "أدخل بيانات الاعتماد أولا"
|
|
||||||
STR_WIFI_CONN_FAILED: "فشل الاتصال بالشبكة"
|
|
||||||
STR_AUTHENTICATING: "جاري التحقق..."
|
|
||||||
STR_AUTH_SUCCESS: "تم تسجيل الدخول بنجاح!"
|
|
||||||
STR_KOREADER_AUTH: "تسجيل الدخول إلى KOReader"
|
|
||||||
STR_SYNC_READY: "مزامنة KOReader جاهزة للاستخدام"
|
|
||||||
STR_AUTH_FAILED: "فشل تسجيل الدخول"
|
|
||||||
STR_DONE: "تم"
|
|
||||||
STR_CLEAR_CACHE_WARNING_1: "سيؤدي هذا إلى مسح جميع بيانات الكتب المخزنة."
|
|
||||||
STR_CLEAR_CACHE_WARNING_2: "سيتم فقدان كل تقدم القراءة!"
|
|
||||||
STR_CLEAR_CACHE_WARNING_3: "ستحتاج الكتب إلى إعادة فهرسة"
|
|
||||||
STR_CLEAR_CACHE_WARNING_4: "عند فتحها مرة أخرى."
|
|
||||||
STR_CLEARING_CACHE: "جاري مسح الذاكرة المؤقتة..."
|
|
||||||
STR_CACHE_CLEARED: "تم مسح الذاكرة المؤقتة"
|
|
||||||
STR_ITEMS_REMOVED: "عناصر تمت إزالتها"
|
|
||||||
STR_FAILED_LOWER: "فشل"
|
|
||||||
STR_CLEAR_CACHE_FAILED: "فشل مسح الذاكرة المؤقتة"
|
|
||||||
STR_CHECK_SERIAL_OUTPUT: "راجع مخرجات المنفذ التسلسلي للتفاصيل"
|
|
||||||
STR_DARK: "داكن"
|
|
||||||
STR_LIGHT: "فاتح"
|
|
||||||
STR_CUSTOM: "مخصص"
|
|
||||||
STR_COVER: "الغلاف"
|
|
||||||
STR_NONE_OPT: "بدون"
|
|
||||||
STR_FIT: "ملاءمة"
|
|
||||||
STR_CROP: "قص"
|
|
||||||
STR_NEVER: "أبدا"
|
|
||||||
STR_IN_READER: "أثناء القراءة"
|
|
||||||
STR_ALWAYS: "دائما"
|
|
||||||
STR_IGNORE: "تجاهل"
|
|
||||||
STR_SLEEP: "سكون"
|
|
||||||
STR_PAGE_TURN: "تقليب الصفحة"
|
|
||||||
STR_FORCE_REFRESH: "تحديث الشاشة"
|
|
||||||
STR_PORTRAIT: "عمودي"
|
|
||||||
STR_LANDSCAPE_CW: "أفقي (يمين)"
|
|
||||||
STR_INVERTED: "عكس الألوان"
|
|
||||||
STR_ORIENTATION_INVERTED: "عمودي 180°"
|
|
||||||
STR_LANDSCAPE_CCW: "أفقي (يسار)"
|
|
||||||
STR_PREV_NEXT: "السابق/التالي"
|
|
||||||
STR_NEXT_PREV: "التالي/السابق"
|
|
||||||
STR_KOSYNC: "KOSync"
|
|
||||||
STR_BOOKMARK_OPTION: "إشارة مرجعية"
|
|
||||||
STR_DISABLED: "معطل"
|
|
||||||
STR_NOTO_SERIF: "Noto Serif"
|
|
||||||
STR_NOTO_SANS: "Noto Sans"
|
|
||||||
STR_SMALL: "صغير"
|
|
||||||
STR_MEDIUM: "متوسط"
|
|
||||||
STR_LARGE: "كبير"
|
|
||||||
STR_X_LARGE: "كبير جدا"
|
|
||||||
STR_TIGHT: "ضيق"
|
|
||||||
STR_NORMAL: "عادي"
|
|
||||||
STR_WIDE: "واسع"
|
|
||||||
STR_JUSTIFY: "ضبط الطرفين"
|
|
||||||
STR_ALIGN_LEFT: "يسار"
|
|
||||||
STR_CENTER: "وسط"
|
|
||||||
STR_ALIGN_RIGHT: "يمين"
|
|
||||||
STR_PAGES_1: "صفحة واحدة"
|
|
||||||
STR_PAGES_5: "5 صفحات"
|
|
||||||
STR_PAGES_10: "10 صفحات"
|
|
||||||
STR_PAGES_15: "15 صفحة"
|
|
||||||
STR_PAGES_30: "30 صفحة"
|
|
||||||
STR_UPDATE: "تحديث"
|
|
||||||
STR_CHECKING_UPDATE: "جاري التحقق من التحديثات..."
|
|
||||||
STR_NEW_UPDATE: "يتوفر تحديث جديد!"
|
|
||||||
STR_CURRENT_VERSION: "الإصدار الحالي: "
|
|
||||||
STR_NEW_VERSION: "الإصدار الجديد: "
|
|
||||||
STR_UPDATING: "جاري التحديث..."
|
|
||||||
STR_NO_UPDATE: "لا يتوفر تحديث"
|
|
||||||
STR_UPDATE_FAILED: "فشل التحديث"
|
|
||||||
STR_UPDATE_COMPLETE: "اكتمل التحديث"
|
|
||||||
STR_POWER_ON_HINT: "اضغط مطولا على زر التشغيل لتشغيل الجهاز من جديد"
|
|
||||||
STR_RESTARTING_HINT: "جاري إعادة التشغيل... إذا لم يعمل الجهاز، اضغط مطولا على زر التشغيل لعدة ثوان."
|
|
||||||
STR_NO_ENTRIES: "لم يتم العثور على عناصر"
|
|
||||||
STR_DOWNLOADING: "جاري التنزيل..."
|
|
||||||
STR_DOWNLOAD_FAILED: "فشل التنزيل"
|
|
||||||
STR_ERROR_MSG: "خطأ:"
|
|
||||||
STR_UNNAMED: "بدون اسم"
|
|
||||||
STR_HOLD_OPEN_TO_DELETE: "اضغط مطولا على فتح للحذف"
|
|
||||||
STR_NO_SERVER_URL: "لم يتم تحديد عنوان الخادم"
|
|
||||||
STR_FETCH_FEED_FAILED: "فشل جلب القائمة"
|
|
||||||
STR_PARSE_FEED_FAILED: "فشل تحليل القائمة"
|
|
||||||
STR_NEXT_PAGE: "الصفحة التالية"
|
|
||||||
STR_PREV_PAGE: "الصفحة السابقة"
|
|
||||||
STR_NETWORK_PREFIX: "الشبكة: "
|
|
||||||
STR_IP_ADDRESS_PREFIX: "عنوان IP: "
|
|
||||||
STR_ERROR_GENERAL_FAILURE: "خطأ: فشل عام"
|
|
||||||
STR_ERROR_NETWORK_NOT_FOUND: "خطأ: الشبكة غير موجودة"
|
|
||||||
STR_ERROR_CONNECTION_TIMEOUT: "خطأ: انتهت مهلة الاتصال"
|
|
||||||
STR_SD_CARD: "بطاقة SD"
|
|
||||||
STR_BACK: "رجوع »"
|
|
||||||
STR_EXIT: "خروج »"
|
|
||||||
STR_HOME: "الرئيسية »"
|
|
||||||
STR_SELECT: "اختيار"
|
|
||||||
STR_SELECTED: "محدد"
|
|
||||||
STR_TOGGLE: "تبديل"
|
|
||||||
STR_TOGGLE_BOOKMARK: "إضافة/إزالة إشارة مرجعية"
|
|
||||||
STR_CONFIRM: "تأكيد"
|
|
||||||
STR_CANCEL: "إلغاء"
|
|
||||||
STR_CONNECT: "اتصال"
|
|
||||||
STR_OPEN: "فتح"
|
|
||||||
STR_DOWNLOAD: "تنزيل"
|
|
||||||
STR_RETRY: "إعادة المحاولة"
|
|
||||||
STR_YES: "نعم"
|
|
||||||
STR_NO: "لا"
|
|
||||||
STR_SHOW: "عرض"
|
|
||||||
STR_HIDE: "إخفاء"
|
|
||||||
STR_STATE_ON: "تشغيل"
|
|
||||||
STR_STATE_OFF: "إيقاف"
|
|
||||||
STR_NOT_SET: "غير محدد"
|
|
||||||
STR_DIR_LEFT: "يسار"
|
|
||||||
STR_DIR_RIGHT: "يمين"
|
|
||||||
STR_DIR_UP: "أعلى"
|
|
||||||
STR_DIR_DOWN: "أسفل"
|
|
||||||
STR_OK_BUTTON: "موافق"
|
|
||||||
STR_SLEEP_COVER_FILTER: "مرشح غلاف شاشة السكون"
|
|
||||||
STR_FILTER_CONTRAST: "التباين"
|
|
||||||
STR_CUSTOMISE_STATUS_BAR: "تخصيص شريط الحالة"
|
|
||||||
STR_CHAPTER_PAGE_COUNT: "عدد صفحات الفصل"
|
|
||||||
STR_BOOK_PROGRESS_PERCENTAGE: "نسبة التقدم في الكتاب"
|
|
||||||
STR_PROGRESS_BAR: "شريط التقدم"
|
|
||||||
STR_PROGRESS_BAR_THICKNESS: "سمك شريط التقدم"
|
|
||||||
STR_PROGRESS_BAR_THIN: "رفيع"
|
|
||||||
STR_PROGRESS_BAR_MEDIUM: "متوسط"
|
|
||||||
STR_PROGRESS_BAR_THICK: "سميك"
|
|
||||||
STR_BOOK: "الكتاب"
|
|
||||||
STR_CHAPTER: "الفصل"
|
|
||||||
STR_EXAMPLE_CHAPTER: "الفصل 21"
|
|
||||||
STR_EXAMPLE_BOOK: "عنوان الكتاب"
|
|
||||||
STR_PREVIEW: "معاينة"
|
|
||||||
STR_TITLE: "العنوان"
|
|
||||||
STR_BATTERY: "البطارية"
|
|
||||||
STR_XTC_STATUS_BAR: "شريط حالة XTC"
|
|
||||||
STR_BOTTOM: "أسفل"
|
|
||||||
STR_TOP: "أعلى"
|
|
||||||
STR_CLOCK: "الساعة"
|
|
||||||
STR_CLOCK_UTC_OFFSET: "فرق التوقيت عن UTC"
|
|
||||||
STR_CLOCK_FORMAT: "تنسيق الساعة"
|
|
||||||
STR_CLOCK_FORMAT_24H: "24 ساعة"
|
|
||||||
STR_CLOCK_FORMAT_12H: "12 ساعة"
|
|
||||||
STR_CURRENT_TIME: "الوقت الحالي:"
|
|
||||||
STR_NEXT_FIELD: "التالي"
|
|
||||||
STR_CLOCK_SYNC: "مزامنة الساعة"
|
|
||||||
STR_CLOCK_SYNC_NOW: "مزامنة الساعة الآن"
|
|
||||||
STR_CLOCK_SYNCING: "جاري المزامنة عبر NTP..."
|
|
||||||
STR_CLOCK_SYNC_OK: "تمت مزامنة الساعة"
|
|
||||||
STR_CLOCK_SYNC_FAIL: "فشلت المزامنة"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI: "لا يوجد اتصال Wi-Fi"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI_HINT: "اتصل بشبكة Wi-Fi أولا، ثم حاول مرة أخرى."
|
|
||||||
STR_CLOCK_SYNCED: "تمت مزامنة الساعة"
|
|
||||||
STR_UI_THEME: "مظهر الواجهة"
|
|
||||||
STR_THEME_CLASSIC: "كلاسيكي"
|
|
||||||
STR_THEME_LYRA: "Lyra"
|
|
||||||
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
|
|
||||||
STR_THEME_LYRA_EXTENDED: "Lyra موسع"
|
|
||||||
STR_SUNLIGHT_FADING_FIX: "إصلاح بهتان الشاشة في الشمس"
|
|
||||||
STR_REMAP_FRONT_BUTTONS: "تغيير وظائف الأزرار الأمامية"
|
|
||||||
STR_BOOKMARKS: "الإشارات المرجعية"
|
|
||||||
STR_BOOKMARK_ADDED: "تمت إضافة الإشارة المرجعية."
|
|
||||||
STR_BOOKMARK_REMOVED: "تمت إزالة الإشارة المرجعية."
|
|
||||||
STR_OPDS_BROWSER: "متصفح OPDS"
|
|
||||||
STR_SEARCH: "بحث"
|
|
||||||
STR_COVER_CUSTOM: "الغلاف + مخصص"
|
|
||||||
STR_QUICK_RESUME: "استئناف سريع"
|
|
||||||
STR_MENU_RECENT_BOOKS: "الكتب الأخيرة"
|
|
||||||
STR_REMOVE_FROM_RECENTS: "إزالة من الكتب الأخيرة؟"
|
|
||||||
STR_NO_RECENT_BOOKS: "لا توجد كتب أخيرة"
|
|
||||||
STR_CALIBRE_DESC: "استخدم النقل اللاسلكي من Calibre"
|
|
||||||
STR_FORGET_AND_REMOVE: "نسيان الشبكة وحذف كلمة المرور المحفوظة؟"
|
|
||||||
STR_FORGET_BUTTON: "نسيان"
|
|
||||||
STR_CALIBRE_STARTING: "جاري تشغيل Calibre..."
|
|
||||||
STR_CALIBRE_SETUP: "الإعداد"
|
|
||||||
STR_CALIBRE_STATUS: "الحالة"
|
|
||||||
STR_CLEAR_BUTTON: "مسح"
|
|
||||||
STR_DEFAULT_VALUE: "افتراضي"
|
|
||||||
STR_REMAP_PROMPT: "اضغط زرا أماميا لكل وظيفة"
|
|
||||||
STR_UNASSIGNED: "غير معين"
|
|
||||||
STR_ALREADY_ASSIGNED: "معين مسبقا"
|
|
||||||
STR_REMAP_RESET_HINT: "الزر الجانبي العلوي: استعادة التخطيط الافتراضي"
|
|
||||||
STR_REMAP_CANCEL_HINT: "الزر الجانبي السفلي: إلغاء التغيير"
|
|
||||||
STR_HW_BACK_LABEL: "رجوع (الزر 1)"
|
|
||||||
STR_HW_CONFIRM_LABEL: "تأكيد (الزر 2)"
|
|
||||||
STR_HW_LEFT_LABEL: "يسار (الزر 3)"
|
|
||||||
STR_HW_RIGHT_LABEL: "يمين (الزر 4)"
|
|
||||||
STR_GO_TO_PERCENT: "الانتقال إلى نسبة %"
|
|
||||||
STR_GO_HOME_BUTTON: "العودة إلى الرئيسية"
|
|
||||||
STR_SYNC_PROGRESS: "مزامنة التقدم"
|
|
||||||
STR_DELETE_CACHE: "حذف ذاكرة الكتاب المؤقتة"
|
|
||||||
STR_DELETE: "حذف"
|
|
||||||
STR_CONFIRM_DELETE_BOOKMARK: "حذف هذه الإشارة المرجعية؟"
|
|
||||||
STR_DISPLAY_QR: "عرض الصفحة كرمز QR"
|
|
||||||
STR_CHAPTER_PREFIX: "الفصل: "
|
|
||||||
STR_PAGES_SEPARATOR: " صفحات | "
|
|
||||||
STR_BOOK_PREFIX: "الكتاب: "
|
|
||||||
STR_CALIBRE_URL_HINT: "في Calibre، أضف /opds إلى العنوان"
|
|
||||||
STR_SYNCING_TIME: "جاري مزامنة الوقت..."
|
|
||||||
STR_CALC_HASH: "جاري حساب بصمة المستند..."
|
|
||||||
STR_HASH_FAILED: "فشل حساب بصمة المستند"
|
|
||||||
STR_FETCH_PROGRESS: "جاري جلب التقدم من الخادم..."
|
|
||||||
STR_UPLOAD_PROGRESS: "جاري رفع التقدم..."
|
|
||||||
STR_NO_CREDENTIALS_MSG: "لم يتم إعداد بيانات الاعتماد"
|
|
||||||
STR_KOREADER_SETUP_HINT: "قم بإعداد حساب KOReader في الإعدادات"
|
|
||||||
STR_PROGRESS_FOUND: "تم العثور على تقدم سابق!"
|
|
||||||
STR_REMOTE_LABEL: "الخادم:"
|
|
||||||
STR_LOCAL_LABEL: "الجهاز:"
|
|
||||||
STR_PAGE_OVERALL_FORMAT: "صفحة %d، %.2f%% إجمالا"
|
|
||||||
STR_PAGE_TOTAL_OVERALL_FORMAT: "صفحة %d من %d، %.2f%% إجمالا"
|
|
||||||
STR_DEVICE_FROM_FORMAT: " من: %s"
|
|
||||||
STR_APPLY_REMOTE: "استخدام تقدم الخادم"
|
|
||||||
STR_UPLOAD_LOCAL: "رفع التقدم المحلي"
|
|
||||||
STR_NO_REMOTE_MSG: "لا يوجد تقدم على الخادم"
|
|
||||||
STR_UPLOAD_PROMPT: "رفع الموضع الحالي؟"
|
|
||||||
STR_UPLOAD_SUCCESS: "تم رفع التقدم!"
|
|
||||||
STR_SYNC_FAILED_MSG: "فشلت المزامنة"
|
|
||||||
STR_SAVE_PROGRESS_FAILED: "تعذر حفظ التقدم"
|
|
||||||
STR_SECTION_PREFIX: "القسم "
|
|
||||||
STR_UPLOAD: "رفع"
|
|
||||||
STR_BOOK_S_STYLE: "تنسيق الكتاب الأصلي"
|
|
||||||
STR_EMBEDDED_STYLE: "التنسيق المضمن"
|
|
||||||
STR_FOCUS_READING: "قراءة مركزة"
|
|
||||||
STR_OPDS_SERVER_URL: "عنوان خادم OPDS"
|
|
||||||
STR_PWR_BTN_FOOTNOTE_BACK: "عودة سريعة من الحواشي"
|
|
||||||
STR_SET_SLEEP_COVER: "تعيين الغلاف"
|
|
||||||
STR_FOOTNOTES: "الحواشي"
|
|
||||||
STR_NO_FOOTNOTES: "لا توجد حواش في هذه الصفحة"
|
|
||||||
STR_LINK: "[رابط]"
|
|
||||||
STR_SCREENSHOT_BUTTON: "التقاط لقطة شاشة"
|
|
||||||
STR_SLEEP_TIMER_VALUE_FORMAT: "%u دقيقة"
|
|
||||||
STR_SLEEP_NEVER: "أبدا"
|
|
||||||
STR_STEP_HINT_FRONT: "الأزرار الأمامية:"
|
|
||||||
STR_STEP_HINT_SIDE: "الأزرار الجانبية:"
|
|
||||||
STR_ADD_SERVER: "إضافة خادم"
|
|
||||||
STR_SERVER_NAME: "اسم الخادم"
|
|
||||||
STR_NO_SERVERS: "لم يتم إعداد خوادم OPDS"
|
|
||||||
STR_DELETE_SERVER: "حذف الخادم"
|
|
||||||
STR_OPDS_SERVERS: "خوادم OPDS"
|
|
||||||
STR_AUTO_TURN_ENABLED: "التقليب التلقائي مفعل: "
|
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "التقليب التلقائي (صفحات في الدقيقة)"
|
|
||||||
STR_MANAGE_FONTS: "إدارة الخطوط"
|
|
||||||
STR_FONT_BROWSER: "متصفح الخطوط"
|
|
||||||
STR_LOADING_FONT_LIST: "جاري تحميل قائمة الخطوط..."
|
|
||||||
STR_NO_FONTS_AVAILABLE: "لا تتوفر خطوط"
|
|
||||||
STR_FONT_INSTALLED: "تم تثبيت الخط!"
|
|
||||||
STR_FONT_INSTALL_FAILED: "فشل تثبيت الخط"
|
|
||||||
STR_INSTALLED: "مثبت"
|
|
||||||
STR_DOWNLOAD_ALL: "تنزيل الكل"
|
|
||||||
STR_UPDATE_ALL: "تحديث الكل"
|
|
||||||
STR_UPDATE_AVAILABLE: "تحديث"
|
|
||||||
STR_CRASH_TITLE: "تعطل النظام"
|
|
||||||
STR_CRASH_DESCRIPTION: "تم حفظ تقرير مفصل في الملف crash_report.txt. يرجى إرفاق هذا الملف عند الإبلاغ عن المشكلة."
|
|
||||||
STR_CRASH_REASON: "سبب التعطل:"
|
|
||||||
STR_CRASH_NO_REASON: "(لم يتم تسجيل سبب)"
|
|
||||||
STR_TILT_PAGE_TURN: "تقليب الصفحة بالإمالة"
|
|
||||||
STR_KB_HINT_MOVE_CURSOR: "اضغط زر اليمين أو اليسار لتحريك المؤشر"
|
|
||||||
STR_KB_HINT_RETURN_CURSOR: "اضغط زر اليسار للعودة إلى موضع المؤشر"
|
|
||||||
STR_KB_HINT_HIDE_PASSWORD: "اضغط مطولا على زر اليمين ثم اضغط [***] لإخفاء كلمة المرور"
|
|
||||||
STR_KB_HINT_SHOW_PASSWORD: "اضغط مطولا على زر اليمين ثم اضغط [abc] لإظهار كلمة المرور"
|
|
||||||
STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "اضغط [***] لإخفاء كلمة المرور"
|
|
||||||
STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "اضغط [abc] لإظهار كلمة المرور"
|
|
||||||
STR_KB_HINT_EDIT_ENTRY: "اضغط مطولا على زر الأعلى للتعديل"
|
|
||||||
STR_KB_TIPS: "نصائح:"
|
|
||||||
STR_KB_HINT_RETURN_KEYBOARD: "اضغط زر الأسفل للعودة إلى لوحة المفاتيح"
|
|
||||||
STR_KB_HINT_EXIT_URL_MODE: "اضغط ABC للخروج من وضع URL"
|
|
||||||
STR_KB_HINT_CLEAR_TEXT: "اضغط مطولا على DEL لمسح كل النص"
|
|
||||||
STR_KB_HINT_SECONDARY_CHAR: "اضغط مطولا على زر الاختيار للحرف الثانوي"
|
|
||||||
STR_KB_HINT_UPPER_SECONDARY: "اضغط مطولا على زر الاختيار للحرف الكبير أو الثانوي"
|
|
||||||
STR_KB_HINT_LOWER_SECONDARY: "اضغط مطولا على زر الاختيار للحرف الصغير أو الثانوي"
|
|
||||||
STR_KB_HINT_URL_SNIPPETS: "اضغط URL للاختصارات"
|
|
||||||
STR_SD_FIRMWARE_UPDATE: "تحديث البرنامج الثابت من بطاقة SD"
|
|
||||||
STR_SELECT_FIRMWARE_FILE: "اختر ملف البرنامج الثابت (.bin)"
|
|
||||||
STR_NO_BIN_FILES: "لم يتم العثور على ملفات .bin"
|
|
||||||
STR_VALIDATING_FIRMWARE: "جاري التحقق من البرنامج الثابت..."
|
|
||||||
STR_INVALID_FIRMWARE: "ملف برنامج ثابت غير صالح"
|
|
||||||
STR_FIRMWARE_TOO_LARGE: "البرنامج الثابت أكبر من القسم المخصص له"
|
|
||||||
STR_FIRMWARE_TOO_SMALL: "ملف البرنامج الثابت صغير جدا"
|
|
||||||
STR_FIRMWARE_UPDATE_PROMPT: "تحديث البرنامج الثابت؟"
|
|
||||||
STR_FIRMWARE_FILE_OPEN_FAILED: "تعذر فتح الملف"
|
|
||||||
STR_FIRMWARE_WRITE_FAILED: "فشلت كتابة البرنامج الثابت"
|
|
||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "لا تطفئ الجهاز!"
|
|
||||||
STR_RECOVERY_MODE: "وضع الاستعادة"
|
|
||||||
STR_RECOVERY_MODE_HINT: "ضع الملف firmware.bin في المجلد الرئيسي لبطاقة SD ثم اختره"
|
|
||||||
@@ -65,10 +65,11 @@ STR_SLEEP_COVER_MODE: "Рэжым вокладкі сну"
|
|||||||
STR_HIDE_BATTERY: "Схаваць % батарэі"
|
STR_HIDE_BATTERY: "Схаваць % батарэі"
|
||||||
STR_EXTRA_SPACING: "Дадат. інтэрвал абзаца"
|
STR_EXTRA_SPACING: "Дадат. інтэрвал абзаца"
|
||||||
STR_TEXT_AA: "Згладжванне тэксту"
|
STR_TEXT_AA: "Згладжванне тэксту"
|
||||||
|
STR_TEXT_AA_FULL: "Поўнае"
|
||||||
|
STR_TEXT_AA_FAST: "Хуткае"
|
||||||
STR_SHORT_PWR_BTN: "Кароткае націсканне PWR"
|
STR_SHORT_PWR_BTN: "Кароткае націсканне PWR"
|
||||||
STR_ORIENTATION: "Арыентацыя чытання"
|
STR_ORIENTATION: "Арыентацыя чытання"
|
||||||
STR_SIDE_BTN_LAYOUT: "Бакавыя кнопкі"
|
STR_SIDE_BTN_LAYOUT: "Бакавыя кнопкі"
|
||||||
STR_TOUCH_READER_CONTROLS: "Сэнсарнае кіраванне чытаннем"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Арыентаваць пярэднія кнопкі"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Арыентаваць пярэднія кнопкі"
|
||||||
STR_LONG_PRESS_SKIP: "Доўгае націсканне - змена раздзела"
|
STR_LONG_PRESS_SKIP: "Доўгае націсканне - змена раздзела"
|
||||||
STR_FONT_PREVIEW_TEXT: "У Іўі худы жвавы чорт у зялёнай камізэльцы пабег пад'есці фаршу з юшкай"
|
STR_FONT_PREVIEW_TEXT: "У Іўі худы жвавы чорт у зялёнай камізэльцы пабег пад'есці фаршу з юшкай"
|
||||||
@@ -88,7 +89,6 @@ STR_USERNAME: "Імя карыстальніка"
|
|||||||
STR_PASSWORD: "Пароль"
|
STR_PASSWORD: "Пароль"
|
||||||
STR_SYNC_SERVER_URL: "URL сервера сінхранізацыі"
|
STR_SYNC_SERVER_URL: "URL сервера сінхранізацыі"
|
||||||
STR_DOCUMENT_MATCHING: "Супастаўленне дакументаў"
|
STR_DOCUMENT_MATCHING: "Супастаўленне дакументаў"
|
||||||
STR_SEND_METADATA: "Адпраўляць метаданыя дакумента"
|
|
||||||
STR_AUTHENTICATE: "Аўтарызацыя"
|
STR_AUTHENTICATE: "Аўтарызацыя"
|
||||||
STR_KOREADER_USERNAME: "Імя карыстальніка KOReader"
|
STR_KOREADER_USERNAME: "Імя карыстальніка KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Пароль KOReader"
|
STR_KOREADER_PASSWORD: "Пароль KOReader"
|
||||||
@@ -299,13 +299,6 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u хв"
|
|||||||
STR_SLEEP_NEVER: "Ніколі"
|
STR_SLEEP_NEVER: "Ніколі"
|
||||||
STR_STEP_HINT_FRONT: "Пярэднія кнопкі:"
|
STR_STEP_HINT_FRONT: "Пярэднія кнопкі:"
|
||||||
STR_STEP_HINT_SIDE: "Бакавыя кнопкі:"
|
STR_STEP_HINT_SIDE: "Бакавыя кнопкі:"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Папка для спампоўкі"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Фармат назвы файла"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Аўтар - Назва"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Назва - Аўтар"
|
|
||||||
STR_FMT_TITLE: "Назва"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Аўтаперагортванне: "
|
STR_AUTO_TURN_ENABLED: "Аўтаперагортванне: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Аўтаперагортванне (старонак за хвіліну)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Аўтаперагортванне (старонак за хвіліну)"
|
||||||
STR_TILT_PAGE_TURN: "Перагортванне нахілам"
|
STR_TILT_PAGE_TURN: "Перагортванне нахілам"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Дадаць схаваную сетку..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Увядзіце назву сеткі (SSID)"
|
|
||||||
|
|||||||
@@ -1,393 +0,0 @@
|
|||||||
_language_name: "Bosanski"
|
|
||||||
_language_code: "BS"
|
|
||||||
_order: "29"
|
|
||||||
STR_CROSSPOINT: "CrossPoint"
|
|
||||||
STR_BOOTING: "POKRETANJE"
|
|
||||||
STR_SLEEPING: "REŽIM MIROVANJA"
|
|
||||||
STR_ENTERING_SLEEP: "Prelazak u režim mirovanja"
|
|
||||||
STR_BROWSE_FILES: "Pregled datoteka"
|
|
||||||
STR_FILE_TRANSFER: "Prijenos datoteka"
|
|
||||||
STR_SETTINGS_TITLE: "Postavke"
|
|
||||||
STR_CONTINUE_READING: "Nastavi čitanje"
|
|
||||||
STR_NO_OPEN_BOOK: "Nema otvorene knjige"
|
|
||||||
STR_START_READING: "Počni čitati ispod"
|
|
||||||
STR_NO_FILES_FOUND: "Nema pronađenih datoteka"
|
|
||||||
STR_SELECT_CHAPTER: "Odaberi poglavlje"
|
|
||||||
STR_NO_CHAPTERS: "Nema poglavlja"
|
|
||||||
STR_END_OF_BOOK: "Kraj knjige"
|
|
||||||
STR_EMPTY_CHAPTER: "Prazno poglavlje"
|
|
||||||
STR_INDEXING: "Indeksiranje"
|
|
||||||
STR_INDEX_FAILED: "Indeksiranje nije uspjelo - nevažeća knjiga"
|
|
||||||
STR_MEMORY_ERROR: "Greška memorije"
|
|
||||||
STR_PAGE_LOAD_ERROR: "Greška pri učitavanju stranice"
|
|
||||||
STR_EMPTY_FILE: "Prazna datoteka"
|
|
||||||
STR_OUT_OF_BOUNDS: "Izvan granica"
|
|
||||||
STR_LOADING: "Učitavanje..."
|
|
||||||
STR_LOADING_POPUP: "Učitavanje"
|
|
||||||
STR_WIFI_NETWORKS: "Wi-Fi mreže"
|
|
||||||
STR_NO_NETWORKS: "Nema pronađenih mreža"
|
|
||||||
STR_NETWORKS_FOUND: "%zu pronađenih mreža"
|
|
||||||
STR_SCANNING: "Skeniranje..."
|
|
||||||
STR_FINDING_SAVED_WIFI: "Traženje sačuvanog Wi-Fi-a..."
|
|
||||||
STR_CONNECTING: "Povezivanje..."
|
|
||||||
STR_CONNECTING_SAVED_WIFI: "Povezivanje na sačuvani Wi-Fi..."
|
|
||||||
STR_SHOW_NETWORKS: "Prikaži"
|
|
||||||
STR_CONNECTED: "Povezano!"
|
|
||||||
STR_CONNECTION_FAILED: "Povezivanje nije uspjelo"
|
|
||||||
STR_FORGET_NETWORK: "Zaboraviti mrežu?"
|
|
||||||
STR_SAVE_PASSWORD: "Sačuvati lozinku za sljedeći put?"
|
|
||||||
STR_PRESS_OK_SCAN: "Pritisni OK za ponovno skeniranje"
|
|
||||||
STR_JOIN_NETWORK: "Pridruži se mreži"
|
|
||||||
STR_CREATE_HOTSPOT: "Kreiraj hotspot"
|
|
||||||
STR_JOIN_DESC: "Poveži se na postojeću Wi-Fi mrežu"
|
|
||||||
STR_HOTSPOT_DESC: "Kreiraj Wi-Fi mrežu kojoj se drugi mogu pridružiti"
|
|
||||||
STR_STARTING_HOTSPOT: "Pokretanje hotspota..."
|
|
||||||
STR_HOTSPOT_MODE: "Hotspot način rada"
|
|
||||||
STR_CONNECT_WIFI_HINT: "Poveži svoj uređaj na ovu Wi-Fi mrežu"
|
|
||||||
STR_OPEN_URL_HINT: "Otvori ovaj URL u svom pregledniku"
|
|
||||||
STR_OR_HTTP_PREFIX: "ili http://"
|
|
||||||
STR_SCAN_QR_HINT: "ili skeniraj QR kod svojim telefonom:"
|
|
||||||
STR_CALIBRE_WIRELESS: "Calibre bežično"
|
|
||||||
STR_NETWORK_LEGEND: "* = Šifrovano | + = Sačuvano"
|
|
||||||
STR_MAC_ADDRESS: "MAC adresa:"
|
|
||||||
STR_CHECKING_WIFI: "Provjera Wi-Fi-a..."
|
|
||||||
STR_ENTER_WIFI_PASSWORD: "Unesi Wi-Fi lozinku"
|
|
||||||
STR_ADD_HIDDEN_NETWORK: "Dodaj skrivenu mrežu..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Unesi naziv mreže (SSID)"
|
|
||||||
STR_TO_PREFIX: "na "
|
|
||||||
STR_CALIBRE_RECEIVING: "Primanje: "
|
|
||||||
STR_CALIBRE_RECEIVED: "Primljeno: "
|
|
||||||
STR_CALIBRE_INSTRUCTION_1: "1) Instaliraj CrossPoint Reader dodatak"
|
|
||||||
STR_CALIBRE_INSTRUCTION_2: "2) Budi na istoj Wi-Fi mreži"
|
|
||||||
STR_CALIBRE_INSTRUCTION_3: "3) U Calibreu: \"Pošalji na uređaj\""
|
|
||||||
STR_CALIBRE_INSTRUCTION_4: "\"Drži ovaj ekran otvorenim tokom slanja\""
|
|
||||||
STR_CAT_DISPLAY: "Ekran"
|
|
||||||
STR_CAT_READER: "Čitač"
|
|
||||||
STR_CAT_CONTROLS: "Kontrole"
|
|
||||||
STR_CAT_SYSTEM: "Sistem"
|
|
||||||
STR_SLEEP_SCREEN: "Ekran mirovanja"
|
|
||||||
STR_QUICK_RESUME_TIMEOUT: "Brzi nastavak nakon isteka vremena"
|
|
||||||
STR_SLEEP_COVER_MODE: "Način prikaza korica na ekranu mirovanja"
|
|
||||||
STR_HIDE_BATTERY: "Sakrij % baterije"
|
|
||||||
STR_EXTRA_SPACING: "Dodatni razmak između paragrafa"
|
|
||||||
STR_TEXT_AA: "Zaglađivanje teksta"
|
|
||||||
STR_IMAGES: "Slike"
|
|
||||||
STR_IMAGES_DISPLAY: "Prikaz"
|
|
||||||
STR_IMAGES_PLACEHOLDER: "Rezervisano mjesto"
|
|
||||||
STR_IMAGES_SUPPRESS: "Sakrij"
|
|
||||||
STR_EOB_HOME: "Početna"
|
|
||||||
STR_EOB_CONTINUE_WITH: "Nastavi sa"
|
|
||||||
STR_SHORT_PWR_BTN: "Kratak klik dugmeta napajanja"
|
|
||||||
STR_ORIENTATION: "Orijentacija čitanja"
|
|
||||||
STR_SIDE_BTN_LAYOUT: "Raspored bočnih dugmadi (čitač)"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orijentiši prednja dugmad"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Ponašanje dugog pritiska dugmeta"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "ISKLJUČENO"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "Preskakanje poglavlja"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Promjena orijentacije"
|
|
||||||
STR_LONG_PRESS_MENU: "Meni dugog pritiska"
|
|
||||||
STR_FONT_PREVIEW_TEXT: "Brza smeđa lisica preskače lijenog psa"
|
|
||||||
STR_FONT_FAMILY: "Porodica fonta čitača"
|
|
||||||
STR_FONT_SIZE: "Veličina fonta čitača"
|
|
||||||
STR_LINE_SPACING: "Razmak između redova čitača"
|
|
||||||
STR_SCREEN_MARGIN: "Margina ekrana čitača"
|
|
||||||
STR_PARA_ALIGNMENT: "Poravnanje paragrafa čitača"
|
|
||||||
STR_HYPHENATION: "Rastavljanje riječi"
|
|
||||||
STR_TIME_TO_SLEEP: "Vrijeme do mirovanja"
|
|
||||||
STR_SHOW_HIDDEN_FILES: "Prikaži skrivene datoteke"
|
|
||||||
STR_REMOVE_READ_FROM_RECENTS: "Ukloni pročitane knjige sa liste nedavnih"
|
|
||||||
STR_MOVE_FINISHED_TO_READ: "Premjesti završene knjige u fasciklu Read"
|
|
||||||
STR_REFRESH_FREQ: "Učestalost osvježavanja"
|
|
||||||
STR_KOREADER_SYNC: "KOReader sinhronizacija"
|
|
||||||
STR_CHECK_UPDATES: "Provjeri ažuriranja"
|
|
||||||
STR_LANGUAGE: "Jezik"
|
|
||||||
STR_CLEAR_READING_CACHE: "Očisti keš čitanja"
|
|
||||||
STR_USERNAME: "Korisničko ime"
|
|
||||||
STR_PASSWORD: "Lozinka"
|
|
||||||
STR_SYNC_SERVER_URL: "URL servera za sinhronizaciju"
|
|
||||||
STR_DOCUMENT_MATCHING: "Podudaranje dokumenata"
|
|
||||||
STR_SEND_METADATA: "Pošalji metapodatke dokumenta"
|
|
||||||
STR_AUTHENTICATE: "Autentifikuj"
|
|
||||||
STR_KOREADER_USERNAME: "KOReader korisničko ime"
|
|
||||||
STR_KOREADER_PASSWORD: "KOReader lozinka"
|
|
||||||
STR_FILENAME: "Naziv datoteke"
|
|
||||||
STR_BINARY: "Binarno"
|
|
||||||
STR_SET_CREDENTIALS_FIRST: "Prvo postavi vjerodajnice"
|
|
||||||
STR_WIFI_CONN_FAILED: "Wi-Fi povezivanje nije uspjelo"
|
|
||||||
STR_AUTHENTICATING: "Autentifikacija..."
|
|
||||||
STR_AUTH_SUCCESS: "Uspješno autentifikovano!"
|
|
||||||
STR_KOREADER_AUTH: "KOReader autentifikacija"
|
|
||||||
STR_SYNC_READY: "KOReader sinhronizacija je spremna za upotrebu"
|
|
||||||
STR_AUTH_FAILED: "Autentifikacija nije uspjela"
|
|
||||||
STR_DONE: "Gotovo"
|
|
||||||
STR_CLEAR_CACHE_WARNING_1: "Ovo će očistiti sve keširane podatke knjiga."
|
|
||||||
STR_CLEAR_CACHE_WARNING_2: "Sav napredak čitanja će biti izgubljen!"
|
|
||||||
STR_CLEAR_CACHE_WARNING_3: "Knjige će morati biti ponovo indeksirane"
|
|
||||||
STR_CLEAR_CACHE_WARNING_4: "kada se ponovo otvore."
|
|
||||||
STR_CLEARING_CACHE: "Čišćenje keša..."
|
|
||||||
STR_CACHE_CLEARED: "Keš očišćen"
|
|
||||||
STR_ITEMS_REMOVED: "stavki uklonjeno"
|
|
||||||
STR_FAILED_LOWER: "nije uspjelo"
|
|
||||||
STR_CLEAR_CACHE_FAILED: "Čišćenje keša nije uspjelo"
|
|
||||||
STR_CHECK_SERIAL_OUTPUT: "Provjeri serijski izlaz za detalje"
|
|
||||||
STR_DARK: "Tamno"
|
|
||||||
STR_LIGHT: "Svijetlo"
|
|
||||||
STR_CUSTOM: "Prilagođeno"
|
|
||||||
STR_COVER: "Korice"
|
|
||||||
STR_NONE_OPT: "Ništa"
|
|
||||||
STR_FIT: "Prilagodi"
|
|
||||||
STR_CROP: "Isjeci"
|
|
||||||
STR_NEVER: "Nikad"
|
|
||||||
STR_IN_READER: "U čitaču"
|
|
||||||
STR_ALWAYS: "Uvijek"
|
|
||||||
STR_IGNORE: "Ignoriši"
|
|
||||||
STR_SLEEP: "Mirovanje"
|
|
||||||
STR_PAGE_TURN: "Okretanje stranice"
|
|
||||||
STR_FORCE_REFRESH: "Osvježi ekran"
|
|
||||||
STR_PORTRAIT: "Uspravno"
|
|
||||||
STR_LANDSCAPE_CW: "Položeno (u smjeru kazaljke na satu)"
|
|
||||||
STR_INVERTED: "Obrnuto"
|
|
||||||
STR_ORIENTATION_INVERTED: "Uspravno 180°"
|
|
||||||
STR_LANDSCAPE_CCW: "Položeno (suprotno smjeru kazaljke na satu)"
|
|
||||||
STR_PREV_NEXT: "Prethodno/Sljedeće"
|
|
||||||
STR_NEXT_PREV: "Sljedeće/Prethodno"
|
|
||||||
STR_KOSYNC: "KOSync"
|
|
||||||
STR_BOOKMARK_OPTION: "Obilježivač"
|
|
||||||
STR_DISABLED: "Onemogućeno"
|
|
||||||
STR_NOTO_SERIF: "Noto Serif"
|
|
||||||
STR_NOTO_SANS: "Noto Sans"
|
|
||||||
STR_SMALL: "Malo"
|
|
||||||
STR_MEDIUM: "Srednje"
|
|
||||||
STR_LARGE: "Veliko"
|
|
||||||
STR_X_LARGE: "Vrlo veliko"
|
|
||||||
STR_TIGHT: "Usko"
|
|
||||||
STR_NORMAL: "Normalno"
|
|
||||||
STR_WIDE: "Široko"
|
|
||||||
STR_JUSTIFY: "Poravnaj obostrano"
|
|
||||||
STR_ALIGN_LEFT: "Lijevo"
|
|
||||||
STR_CENTER: "Centrirano"
|
|
||||||
STR_ALIGN_RIGHT: "Desno"
|
|
||||||
STR_PAGES_1: "1 stranica"
|
|
||||||
STR_PAGES_5: "5 stranica"
|
|
||||||
STR_PAGES_10: "10 stranica"
|
|
||||||
STR_PAGES_15: "15 stranica"
|
|
||||||
STR_PAGES_30: "30 stranica"
|
|
||||||
STR_UPDATE: "Ažuriraj"
|
|
||||||
STR_CHECKING_UPDATE: "Provjera ažuriranja..."
|
|
||||||
STR_NEW_UPDATE: "Dostupno je novo ažuriranje!"
|
|
||||||
STR_CURRENT_VERSION: "Trenutna verzija: "
|
|
||||||
STR_NEW_VERSION: "Nova verzija: "
|
|
||||||
STR_UPDATING: "Ažuriranje..."
|
|
||||||
STR_NO_UPDATE: "Nema dostupnih ažuriranja"
|
|
||||||
STR_UPDATE_FAILED: "Ažuriranje nije uspjelo"
|
|
||||||
STR_UPDATE_COMPLETE: "Ažuriranje završeno"
|
|
||||||
STR_POWER_ON_HINT: "Pritisni i drži dugme napajanja da ponovo uključiš"
|
|
||||||
STR_RESTARTING_HINT: "Ponovno pokretanje... Ako se uređaj ne pokrene ponovo, drži dugme napajanja nekoliko sekundi."
|
|
||||||
STR_NO_ENTRIES: "Nema pronađenih unosa"
|
|
||||||
STR_DOWNLOADING: "Preuzimanje..."
|
|
||||||
STR_DOWNLOAD_FAILED: "Preuzimanje nije uspjelo"
|
|
||||||
STR_ERROR_MSG: "Greška:"
|
|
||||||
STR_UNNAMED: "Neimenovano"
|
|
||||||
STR_HOLD_OPEN_TO_DELETE: "Drži Otvori za brisanje"
|
|
||||||
STR_NO_SERVER_URL: "Nije podešen URL servera"
|
|
||||||
STR_FETCH_FEED_FAILED: "Dohvatanje feeda nije uspjelo"
|
|
||||||
STR_PARSE_FEED_FAILED: "Obrada feeda nije uspjela"
|
|
||||||
STR_NEXT_PAGE: "Sljedeća stranica »"
|
|
||||||
STR_PREV_PAGE: "« Prethodna stranica"
|
|
||||||
STR_NETWORK_PREFIX: "Mreža: "
|
|
||||||
STR_IP_ADDRESS_PREFIX: "IP adresa: "
|
|
||||||
STR_ERROR_GENERAL_FAILURE: "Greška: Opšta greška"
|
|
||||||
STR_ERROR_NETWORK_NOT_FOUND: "Greška: Mreža nije pronađena"
|
|
||||||
STR_ERROR_CONNECTION_TIMEOUT: "Greška: Isteklo vrijeme veze"
|
|
||||||
STR_SD_CARD: "SD kartica"
|
|
||||||
STR_BACK: "« Nazad"
|
|
||||||
STR_EXIT: "« Izlaz"
|
|
||||||
STR_HOME: "« Početna"
|
|
||||||
STR_SELECT: "Odaberi"
|
|
||||||
STR_SELECTED: "Odabrano"
|
|
||||||
STR_TOGGLE: "Promijeni"
|
|
||||||
STR_TOGGLE_BOOKMARK: "Promijeni obilježivač"
|
|
||||||
STR_CONFIRM: "Potvrdi"
|
|
||||||
STR_CANCEL: "Otkaži"
|
|
||||||
STR_CONNECT: "Poveži"
|
|
||||||
STR_OPEN: "Otvori"
|
|
||||||
STR_DOWNLOAD: "Preuzmi"
|
|
||||||
STR_RETRY: "Pokušaj ponovo"
|
|
||||||
STR_YES: "Da"
|
|
||||||
STR_NO: "Ne"
|
|
||||||
STR_SHOW: "Prikaži"
|
|
||||||
STR_HIDE: "Sakrij"
|
|
||||||
STR_STATE_ON: "UKLJUČENO"
|
|
||||||
STR_STATE_OFF: "ISKLJUČENO"
|
|
||||||
STR_NOT_SET: "Nije postavljeno"
|
|
||||||
STR_DIR_LEFT: "Lijevo"
|
|
||||||
STR_DIR_RIGHT: "Desno"
|
|
||||||
STR_DIR_UP: "Gore"
|
|
||||||
STR_DIR_DOWN: "Dolje"
|
|
||||||
STR_OK_BUTTON: "OK"
|
|
||||||
STR_SLEEP_COVER_FILTER: "Filter korica ekrana mirovanja"
|
|
||||||
STR_FILTER_CONTRAST: "Kontrast"
|
|
||||||
STR_CUSTOMISE_STATUS_BAR: "Prilagodi statusnu traku"
|
|
||||||
STR_CHAPTER_PAGE_COUNT: "Broj stranica poglavlja"
|
|
||||||
STR_BOOK_PROGRESS_PERCENTAGE: "Procenat napretka knjige"
|
|
||||||
STR_PROGRESS_BAR: "Traka napretka"
|
|
||||||
STR_PROGRESS_BAR_THICKNESS: "Debljina trake napretka"
|
|
||||||
STR_PROGRESS_BAR_THIN: "Tanka"
|
|
||||||
STR_PROGRESS_BAR_MEDIUM: "Srednja"
|
|
||||||
STR_PROGRESS_BAR_THICK: "Debela"
|
|
||||||
STR_BOOK: "Knjiga"
|
|
||||||
STR_CHAPTER: "Poglavlje"
|
|
||||||
STR_EXAMPLE_CHAPTER: "Poglavlje 21"
|
|
||||||
STR_EXAMPLE_BOOK: "Naslov knjige"
|
|
||||||
STR_PREVIEW: "Pregled"
|
|
||||||
STR_TITLE: "Naslov"
|
|
||||||
STR_BATTERY: "Baterija"
|
|
||||||
STR_XTC_STATUS_BAR: "XTC statusna traka"
|
|
||||||
STR_BOTTOM: "Dolje"
|
|
||||||
STR_TOP: "Gore"
|
|
||||||
STR_CLOCK: "Sat"
|
|
||||||
STR_CLOCK_UTC_OFFSET: "UTC odstupanje sata"
|
|
||||||
STR_CLOCK_FORMAT: "Format sata"
|
|
||||||
STR_CLOCK_FORMAT_24H: "24-časovni"
|
|
||||||
STR_CLOCK_FORMAT_12H: "12-časovni"
|
|
||||||
STR_CURRENT_TIME: "Trenutno vrijeme:"
|
|
||||||
STR_NEXT_FIELD: "Sljedeće"
|
|
||||||
STR_CLOCK_SYNC: "Sinhronizuj sat"
|
|
||||||
STR_CLOCK_SYNC_NOW: "Sinhronizuj sat sada"
|
|
||||||
STR_CLOCK_SYNCING: "Sinhronizacija sa NTP-a..."
|
|
||||||
STR_CLOCK_SYNC_OK: "Sat sinhronizovan"
|
|
||||||
STR_CLOCK_SYNC_FAIL: "Sinhronizacija nije uspjela"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI: "Wi-Fi nije povezan"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI_HINT: "Prvo se poveži na Wi-Fi, pa pokušaj ponovo."
|
|
||||||
STR_CLOCK_SYNCED: "Sat sinhronizovan"
|
|
||||||
STR_UI_THEME: "Tema korisničkog interfejsa"
|
|
||||||
STR_THEME_CLASSIC: "Klasična"
|
|
||||||
STR_THEME_LYRA: "Lyra"
|
|
||||||
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
|
|
||||||
STR_THEME_LYRA_EXTENDED: "Lyra proširena"
|
|
||||||
STR_SUNLIGHT_FADING_FIX: "Ispravka blijeđenja na suncu"
|
|
||||||
STR_REMAP_FRONT_BUTTONS: "Premapiraj prednja dugmad"
|
|
||||||
STR_BOOKMARKS: "Obilježivači"
|
|
||||||
STR_BOOKMARK_ADDED: "Obilježivač dodan."
|
|
||||||
STR_BOOKMARK_REMOVED: "Obilježivač uklonjen."
|
|
||||||
STR_OPDS_BROWSER: "OPDS pregledač"
|
|
||||||
STR_SEARCH: "Pretraga"
|
|
||||||
STR_COVER_CUSTOM: "Korice + Prilagođeno"
|
|
||||||
STR_QUICK_RESUME: "Brzi nastavak"
|
|
||||||
STR_MENU_RECENT_BOOKS: "Nedavne knjige"
|
|
||||||
STR_REMOVE_FROM_RECENTS: "Ukloniti iz nedavnih knjiga?"
|
|
||||||
STR_NO_RECENT_BOOKS: "Nema nedavnih knjiga"
|
|
||||||
STR_CALIBRE_DESC: "Koristi Calibre bežični prijenos uređaja"
|
|
||||||
STR_FORGET_AND_REMOVE: "Zaboraviti mrežu i ukloniti sačuvanu lozinku?"
|
|
||||||
STR_FORGET_BUTTON: "Zaboravi"
|
|
||||||
STR_CALIBRE_STARTING: "Pokretanje Calibrea..."
|
|
||||||
STR_CALIBRE_SETUP: "Podešavanje"
|
|
||||||
STR_CALIBRE_STATUS: "Status"
|
|
||||||
STR_CLEAR_BUTTON: "Očisti"
|
|
||||||
STR_DEFAULT_VALUE: "Zadano"
|
|
||||||
STR_REMAP_PROMPT: "Pritisni prednje dugme za svaku ulogu"
|
|
||||||
STR_UNASSIGNED: "Nedodijeljeno"
|
|
||||||
STR_ALREADY_ASSIGNED: "Već dodijeljeno"
|
|
||||||
STR_REMAP_RESET_HINT: "Bočno dugme Gore: Vrati na zadani raspored"
|
|
||||||
STR_REMAP_CANCEL_HINT: "Bočno dugme Dolje: Otkaži premapiranje"
|
|
||||||
STR_HW_BACK_LABEL: "Nazad (1. dugme)"
|
|
||||||
STR_HW_CONFIRM_LABEL: "Potvrdi (2. dugme)"
|
|
||||||
STR_HW_LEFT_LABEL: "Lijevo (3. dugme)"
|
|
||||||
STR_HW_RIGHT_LABEL: "Desno (4. dugme)"
|
|
||||||
STR_GO_TO_PERCENT: "Idi na %"
|
|
||||||
STR_GO_HOME_BUTTON: "Idi na početnu"
|
|
||||||
STR_SYNC_PROGRESS: "Sinhronizuj napredak"
|
|
||||||
STR_DELETE_CACHE: "Obriši keš knjige"
|
|
||||||
STR_DELETE: "Obriši"
|
|
||||||
STR_CONFIRM_DELETE_BOOKMARK: "Obrisati ovaj obilježivač?"
|
|
||||||
STR_DISPLAY_QR: "Prikaži stranicu kao QR"
|
|
||||||
STR_CHAPTER_PREFIX: "Poglavlje: "
|
|
||||||
STR_PAGES_SEPARATOR: " stranica | "
|
|
||||||
STR_BOOK_PREFIX: "Knjiga: "
|
|
||||||
STR_CALIBRE_URL_HINT: "Za Calibre, dodaj /opds na svoj URL"
|
|
||||||
STR_SYNCING_TIME: "Sinhronizacija vremena..."
|
|
||||||
STR_CALC_HASH: "Izračunavanje heša dokumenta..."
|
|
||||||
STR_HASH_FAILED: "Izračunavanje heša dokumenta nije uspjelo"
|
|
||||||
STR_FETCH_PROGRESS: "Dohvatanje udaljenog napretka..."
|
|
||||||
STR_UPLOAD_PROGRESS: "Slanje napretka..."
|
|
||||||
STR_NO_CREDENTIALS_MSG: "Nisu podešene vjerodajnice"
|
|
||||||
STR_KOREADER_SETUP_HINT: "Podesi KOReader nalog u Postavkama"
|
|
||||||
STR_PROGRESS_FOUND: "Napredak pronađen!"
|
|
||||||
STR_REMOTE_LABEL: "Udaljeno:"
|
|
||||||
STR_LOCAL_LABEL: "Lokalno:"
|
|
||||||
STR_PAGE_OVERALL_FORMAT: "Stranica %d, %.2f%% ukupno"
|
|
||||||
STR_PAGE_TOTAL_OVERALL_FORMAT: "Stranica %d/%d, %.2f%% ukupno"
|
|
||||||
STR_DEVICE_FROM_FORMAT: " Sa: %s"
|
|
||||||
STR_APPLY_REMOTE: "Primijeni udaljeni napredak"
|
|
||||||
STR_UPLOAD_LOCAL: "Pošalji lokalni napredak"
|
|
||||||
STR_NO_REMOTE_MSG: "Nije pronađen udaljeni napredak"
|
|
||||||
STR_UPLOAD_PROMPT: "Poslati trenutnu poziciju?"
|
|
||||||
STR_UPLOAD_SUCCESS: "Napredak poslan!"
|
|
||||||
STR_SYNC_FAILED_MSG: "Sinhronizacija nije uspjela"
|
|
||||||
STR_SAVE_PROGRESS_FAILED: "Napredak nije mogao biti sačuvan"
|
|
||||||
STR_SECTION_PREFIX: "Sekcija "
|
|
||||||
STR_UPLOAD: "Pošalji"
|
|
||||||
STR_BOOK_S_STYLE: "Stil knjige"
|
|
||||||
STR_EMBEDDED_STYLE: "Ugrađeni stil"
|
|
||||||
STR_FOCUS_READING: "Fokusirano čitanje"
|
|
||||||
STR_OPDS_SERVER_URL: "URL OPDS servera"
|
|
||||||
STR_PWR_BTN_FOOTNOTE_BACK: "Brzi povratak iz fusnota"
|
|
||||||
STR_SET_SLEEP_COVER: "Postavi korice"
|
|
||||||
STR_FOOTNOTES: "Fusnote"
|
|
||||||
STR_NO_FOOTNOTES: "Nema fusnota na ovoj stranici"
|
|
||||||
STR_LINK: "[link]"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Snimi ekran"
|
|
||||||
STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
|
||||||
STR_SLEEP_NEVER: "Nikad"
|
|
||||||
STR_STEP_HINT_FRONT: "Prednja dugmad:"
|
|
||||||
STR_STEP_HINT_SIDE: "Bočna dugmad:"
|
|
||||||
STR_ADD_SERVER: "Dodaj server"
|
|
||||||
STR_SERVER_NAME: "Naziv servera"
|
|
||||||
STR_NO_SERVERS: "Nema podešenih OPDS servera"
|
|
||||||
STR_DELETE_SERVER: "Obriši server"
|
|
||||||
STR_OPDS_SERVERS: "OPDS serveri"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Automatsko okretanje omogućeno: "
|
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Automatsko okretanje (stranica po minuti)"
|
|
||||||
STR_MANAGE_FONTS: "Upravljaj fontovima"
|
|
||||||
STR_FONT_BROWSER: "Pregledač fontova"
|
|
||||||
STR_LOADING_FONT_LIST: "Učitavanje liste fontova..."
|
|
||||||
STR_NO_FONTS_AVAILABLE: "Nema dostupnih fontova"
|
|
||||||
STR_FONT_INSTALLED: "Font instaliran!"
|
|
||||||
STR_FONT_INSTALL_FAILED: "Instalacija fonta nije uspjela"
|
|
||||||
STR_INSTALLED: "Instalirano"
|
|
||||||
STR_DOWNLOAD_ALL: "Preuzmi sve"
|
|
||||||
STR_UPDATE_ALL: "Ažuriraj sve"
|
|
||||||
STR_UPDATE_AVAILABLE: "Ažuriranje"
|
|
||||||
STR_CRASH_TITLE: "Sistemski pad"
|
|
||||||
STR_CRASH_DESCRIPTION: "Detaljan izvještaj je sačuvan u crash_report.txt. Molimo priloži ovu datoteku u svoj izvještaj o grešci."
|
|
||||||
STR_CRASH_REASON: "Razlog pada:"
|
|
||||||
STR_CRASH_NO_REASON: "(Razlog nije zabilježen)"
|
|
||||||
STR_TILT_PAGE_TURN: "Okretanje stranice naginjanjem"
|
|
||||||
STR_KB_HINT_MOVE_CURSOR: "Pritisni LIJEVO ili DESNO za pomjeranje kursora"
|
|
||||||
STR_KB_HINT_RETURN_CURSOR: "Pritisni LIJEVO za povratak na poziciju kursora"
|
|
||||||
STR_KB_HINT_HIDE_PASSWORD: "Drži DESNO pa pritisni [***] za skrivanje lozinke"
|
|
||||||
STR_KB_HINT_SHOW_PASSWORD: "Drži DESNO pa pritisni [abc] za prikaz lozinke"
|
|
||||||
STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Pritisni [***] za skrivanje lozinke"
|
|
||||||
STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Pritisni [abc] za prikaz lozinke"
|
|
||||||
STR_KB_HINT_EDIT_ENTRY: "Drži GORE za uređivanje unosa"
|
|
||||||
STR_KB_TIPS: "Savjeti:"
|
|
||||||
STR_KB_HINT_RETURN_KEYBOARD: "Pritisni DOLJE za povratak na tastaturu"
|
|
||||||
STR_KB_HINT_EXIT_URL_MODE: "Pritisni ABC za izlazak iz URL načina"
|
|
||||||
STR_KB_HINT_CLEAR_TEXT: "Drži DEL za brisanje cijelog teksta"
|
|
||||||
STR_KB_HINT_SECONDARY_CHAR: "Drži SELECT za sekundarni znak"
|
|
||||||
STR_KB_HINT_UPPER_SECONDARY: "Drži SELECT za VELIKA SLOVA ili sekundarni znak"
|
|
||||||
STR_KB_HINT_LOWER_SECONDARY: "Drži SELECT za mala slova ili sekundarni znak"
|
|
||||||
STR_KB_HINT_URL_SNIPPETS: "Pritisni URL za isječke"
|
|
||||||
STR_SD_FIRMWARE_UPDATE: "Ažuriranje firmvera sa SD kartice"
|
|
||||||
STR_SELECT_FIRMWARE_FILE: "Odaberi datoteku firmvera (.bin)"
|
|
||||||
STR_NO_BIN_FILES: "Nema pronađenih .bin datoteka"
|
|
||||||
STR_VALIDATING_FIRMWARE: "Provjera firmvera..."
|
|
||||||
STR_INVALID_FIRMWARE: "Nevažeća datoteka firmvera"
|
|
||||||
STR_FIRMWARE_TOO_LARGE: "Firmver je prevelik za particiju"
|
|
||||||
STR_FIRMWARE_TOO_SMALL: "Datoteka firmvera je premala"
|
|
||||||
STR_FIRMWARE_UPDATE_PROMPT: "Ažurirati firmver?"
|
|
||||||
STR_FIRMWARE_FILE_OPEN_FAILED: "Datoteka se ne može otvoriti"
|
|
||||||
STR_FIRMWARE_WRITE_FAILED: "Upisivanje firmvera nije uspjelo"
|
|
||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Ne isključuj napajanje!"
|
|
||||||
STR_RECOVERY_MODE: "Način oporavka"
|
|
||||||
STR_RECOVERY_MODE_HINT: "Stavi firmware.bin u korijenski direktorij SD kartice i odaberi ga"
|
|
||||||
@@ -7,7 +7,7 @@ STR_BOOTING: "ARRENCANT"
|
|||||||
STR_SLEEPING: "ENTRANT EN REPÒS"
|
STR_SLEEPING: "ENTRANT EN REPÒS"
|
||||||
STR_ENTERING_SLEEP: "Entrant en repòs"
|
STR_ENTERING_SLEEP: "Entrant en repòs"
|
||||||
STR_BROWSE_FILES: "Explora fitxers"
|
STR_BROWSE_FILES: "Explora fitxers"
|
||||||
STR_FILE_TRANSFER: "Transferència de fitxers"
|
STR_FILE_TRANSFER: "Transferència"
|
||||||
STR_SETTINGS_TITLE: "Configuració"
|
STR_SETTINGS_TITLE: "Configuració"
|
||||||
STR_CONTINUE_READING: "Continua llegint"
|
STR_CONTINUE_READING: "Continua llegint"
|
||||||
STR_NO_OPEN_BOOK: "Cap llibre obert"
|
STR_NO_OPEN_BOOK: "Cap llibre obert"
|
||||||
@@ -18,7 +18,6 @@ STR_NO_CHAPTERS: "Sense capítols"
|
|||||||
STR_END_OF_BOOK: "Final del llibre"
|
STR_END_OF_BOOK: "Final del llibre"
|
||||||
STR_EMPTY_CHAPTER: "Capítol buit"
|
STR_EMPTY_CHAPTER: "Capítol buit"
|
||||||
STR_INDEXING: "S'està indexant"
|
STR_INDEXING: "S'està indexant"
|
||||||
STR_INDEX_FAILED: "No s'ha pogut indexar: llibre no vàlid"
|
|
||||||
STR_MEMORY_ERROR: "Error de memòria"
|
STR_MEMORY_ERROR: "Error de memòria"
|
||||||
STR_PAGE_LOAD_ERROR: "Error en carregar la pàgina"
|
STR_PAGE_LOAD_ERROR: "Error en carregar la pàgina"
|
||||||
STR_EMPTY_FILE: "Fitxer buit"
|
STR_EMPTY_FILE: "Fitxer buit"
|
||||||
@@ -29,64 +28,60 @@ STR_WIFI_NETWORKS: "Xarxes Wi-Fi"
|
|||||||
STR_NO_NETWORKS: "No s'han trobat xarxes"
|
STR_NO_NETWORKS: "No s'han trobat xarxes"
|
||||||
STR_NETWORKS_FOUND: "%zu xarxes trobades"
|
STR_NETWORKS_FOUND: "%zu xarxes trobades"
|
||||||
STR_SCANNING: "S'està escanejant..."
|
STR_SCANNING: "S'està escanejant..."
|
||||||
STR_FINDING_SAVED_WIFI: "S'estan cercant xarxes Wi-Fi desades..."
|
|
||||||
STR_CONNECTING: "S'està connectant..."
|
STR_CONNECTING: "S'està connectant..."
|
||||||
STR_CONNECTING_SAVED_WIFI: "S'està connectant a una xarxa Wi-Fi desada..."
|
|
||||||
STR_SHOW_NETWORKS: "Mostra"
|
|
||||||
STR_CONNECTED: "S'ha connectat!"
|
STR_CONNECTED: "S'ha connectat!"
|
||||||
STR_CONNECTION_FAILED: "Error de connexió"
|
STR_CONNECTION_FAILED: "Error de connexió"
|
||||||
STR_FORGET_NETWORK: "Vols oblidar aquesta xarxa?"
|
STR_FORGET_NETWORK: "Voleu oblidar aquesta xarxa?"
|
||||||
STR_SAVE_PASSWORD: "Vols desar la contrasenya per a la propera vegada?"
|
STR_SAVE_PASSWORD: "Voleu desar la contrasenya per a la propera vegada?"
|
||||||
STR_PRESS_OK_SCAN: "Prem OK per tornar a escanejar"
|
STR_PRESS_OK_SCAN: "Premeu OK per tornar a escanejar"
|
||||||
STR_JOIN_NETWORK: "Uneix-te a una xarxa"
|
STR_JOIN_NETWORK: "Uneix-te a una xarxa"
|
||||||
STR_CREATE_HOTSPOT: "Crea un punt d'accés"
|
STR_CREATE_HOTSPOT: "Crea un punt d'accés"
|
||||||
STR_JOIN_DESC: "Connecta't a una xarxa Wi-Fi existent"
|
STR_JOIN_DESC: "Connecta't a una xarxa Wi-Fi existent"
|
||||||
STR_HOTSPOT_DESC: "Crea una xarxa Wi-Fi per unir-s'hi"
|
STR_HOTSPOT_DESC: "Crea una xarxa Wi-Fi per unir-s'hi"
|
||||||
STR_STARTING_HOTSPOT: "S'està iniciant el punt d'accés..."
|
STR_STARTING_HOTSPOT: "S'està iniciant el punt d'accés..."
|
||||||
STR_HOTSPOT_MODE: "Mode de punt d'accés"
|
STR_HOTSPOT_MODE: "Mode de punt d'accés"
|
||||||
STR_CONNECT_WIFI_HINT: "Connecta el dispositiu a aquesta xarxa Wi-Fi"
|
STR_CONNECT_WIFI_HINT: "Connecteu el dispositiu a aquesta xarxa Wi-Fi"
|
||||||
STR_OPEN_URL_HINT: "Obre aquest URL al navegador"
|
STR_OPEN_URL_HINT: "Obriu aquest URL al navegador"
|
||||||
STR_OR_HTTP_PREFIX: "o http://"
|
STR_OR_HTTP_PREFIX: "o http://"
|
||||||
STR_SCAN_QR_HINT: "o escaneja el codi QR amb el telèfon:"
|
STR_SCAN_QR_HINT: "o escanegeu el codi QR amb el telèfon:"
|
||||||
STR_CALIBRE_WIRELESS: "Calibre sense fils"
|
STR_CALIBRE_WIRELESS: "Calibre sense fils"
|
||||||
STR_NETWORK_LEGEND: "* = Encriptat | + = Desat"
|
STR_NETWORK_LEGEND: "* = Encriptat | + = Desat"
|
||||||
STR_MAC_ADDRESS: "Adreça MAC:"
|
STR_MAC_ADDRESS: "Adreça MAC:"
|
||||||
STR_CHECKING_WIFI: "S'està comprovant el Wi-Fi..."
|
STR_CHECKING_WIFI: "S'està comprovant el Wi-Fi..."
|
||||||
STR_ENTER_WIFI_PASSWORD: "Introdueix la contrasenya Wi-Fi"
|
STR_ENTER_WIFI_PASSWORD: "Introduïu la contrasenya Wi-Fi"
|
||||||
STR_TO_PREFIX: "a "
|
STR_TO_PREFIX: "a "
|
||||||
STR_CALIBRE_RECEIVING: "S'està rebent: "
|
STR_CALIBRE_RECEIVING: "S'està rebent: "
|
||||||
STR_CALIBRE_RECEIVED: "S'ha rebut: "
|
STR_CALIBRE_RECEIVED: "S'ha rebut: "
|
||||||
STR_CALIBRE_INSTRUCTION_1: "1) Instal·la el connector CrossPoint Reader"
|
STR_CALIBRE_INSTRUCTION_1: "1) Instal·leu el connector CrossPoint Reader"
|
||||||
STR_CALIBRE_INSTRUCTION_2: "2) Estigues a la mateixa xarxa Wi-Fi"
|
STR_CALIBRE_INSTRUCTION_2: "2) Estigueu a la mateixa xarxa Wi-Fi"
|
||||||
STR_CALIBRE_INSTRUCTION_3: "3) A Calibre: \"Envia a un dispositiu\""
|
STR_CALIBRE_INSTRUCTION_3: "3) A Calibre: \"Envia a un dispositiu\""
|
||||||
STR_CALIBRE_INSTRUCTION_4: "\"Mantén aquesta pantalla oberta mentre s'envia\""
|
STR_CALIBRE_INSTRUCTION_4: "\"Mantingueu aquesta pantalla oberta mentre s'envia\""
|
||||||
STR_CAT_DISPLAY: "Visualització"
|
STR_CAT_DISPLAY: "Visualització"
|
||||||
STR_CAT_READER: "Lector"
|
STR_CAT_READER: "Lector"
|
||||||
STR_CAT_CONTROLS: "Controls"
|
STR_CAT_CONTROLS: "Controls"
|
||||||
STR_CAT_SYSTEM: "Sistema"
|
STR_CAT_SYSTEM: "Sistema"
|
||||||
STR_SLEEP_SCREEN: "Pantalla de repòs"
|
STR_SLEEP_SCREEN: "Pantalla de repòs"
|
||||||
STR_SLEEP_COVER_MODE: "Ajust de la portada en repòs"
|
STR_SLEEP_COVER_MODE: "Mode de pantalla de repòs"
|
||||||
STR_HIDE_BATTERY: "Oculta el % de bateria"
|
STR_HIDE_BATTERY: "Oculta el % de bateria"
|
||||||
STR_EXTRA_SPACING: "Espaiat de paràgraf extra"
|
STR_EXTRA_SPACING: "Espaiat de paràgraf extra"
|
||||||
STR_TEXT_AA: "Antialiàsing del text"
|
STR_TEXT_AA: "Antialiàsing del text"
|
||||||
|
STR_TEXT_AA_FULL: "Complet"
|
||||||
|
STR_TEXT_AA_FAST: "Ràpid"
|
||||||
STR_IMAGES: "Imatges"
|
STR_IMAGES: "Imatges"
|
||||||
STR_IMAGES_DISPLAY: "Mostrar"
|
STR_IMAGES_DISPLAY: "Mostrar"
|
||||||
STR_IMAGES_PLACEHOLDER: "Text alternatiu"
|
STR_IMAGES_PLACEHOLDER: "Text de mostra"
|
||||||
STR_IMAGES_SUPPRESS: "Suprimir"
|
STR_IMAGES_SUPPRESS: "Suprimir"
|
||||||
STR_EOB_HOME: "Inici"
|
STR_SHORT_PWR_BTN: "Clic curt del botó d'engegada"
|
||||||
STR_EOB_CONTINUE_WITH: "Continua amb"
|
|
||||||
STR_SHORT_PWR_BTN: "Pulsació curta del botó d'engegada"
|
|
||||||
STR_ORIENTATION: "Orientació de lectura"
|
STR_ORIENTATION: "Orientació de lectura"
|
||||||
STR_SIDE_BTN_LAYOUT: "Disposició botons laterals"
|
STR_SIDE_BTN_LAYOUT: "Disposició botons laterals"
|
||||||
STR_TOUCH_READER_CONTROLS: "Controls tàctils del lector"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botons frontals"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botons frontals"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Acció en mantenir premut un botó"
|
STR_LONG_PRESS_BEHAVIOR: "Comportament de prémer llargament el botó"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítols"
|
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítols"
|
||||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Canvi d'orientació"
|
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Canvi d'orientació"
|
||||||
STR_LONG_PRESS_MENU: "Funció de pulsació llarga"
|
STR_LONG_PRESS_MENU: "Funció de pulsació llarga"
|
||||||
STR_FONT_FAMILY: "Font del lector"
|
STR_FONT_FAMILY: "Tipus de lletra"
|
||||||
STR_FONT_SIZE: "Cos de lletra del lector"
|
STR_FONT_SIZE: "Mida de la lletra (UI)"
|
||||||
STR_LINE_SPACING: "Interlineat del lector"
|
STR_LINE_SPACING: "Interlineat del lector"
|
||||||
STR_SCREEN_MARGIN: "Marge de pantalla del lector"
|
STR_SCREEN_MARGIN: "Marge de pantalla del lector"
|
||||||
STR_PARA_ALIGNMENT: "Alineació de paràgrafs del lector"
|
STR_PARA_ALIGNMENT: "Alineació de paràgrafs del lector"
|
||||||
@@ -95,16 +90,15 @@ STR_TIME_TO_SLEEP: "Temps per entrar en repòs"
|
|||||||
STR_SHOW_HIDDEN_FILES: "Mostra fitxers ocults"
|
STR_SHOW_HIDDEN_FILES: "Mostra fitxers ocults"
|
||||||
STR_REMOVE_READ_FROM_RECENTS: "Esborra els llibres llegits de la llista de recents"
|
STR_REMOVE_READ_FROM_RECENTS: "Esborra els llibres llegits de la llista de recents"
|
||||||
STR_MOVE_FINISHED_TO_READ: "Mou els llibres acabats a la carpeta Read"
|
STR_MOVE_FINISHED_TO_READ: "Mou els llibres acabats a la carpeta Read"
|
||||||
STR_REFRESH_FREQ: "Freqüència d'actualització"
|
STR_REFRESH_FREQ: "Freqüència de refresc"
|
||||||
STR_KOREADER_SYNC: "Sincronització del KOReader"
|
STR_KOREADER_SYNC: "Sincronització del KOReader"
|
||||||
STR_CHECK_UPDATES: "Comprova si hi ha actualitzacions"
|
STR_CHECK_UPDATES: "Comprova si hi ha actualitzacions"
|
||||||
STR_LANGUAGE: "Llengua"
|
STR_LANGUAGE: "Idioma"
|
||||||
STR_CLEAR_READING_CACHE: "Esborra la memòria cau de lectura"
|
STR_CLEAR_READING_CACHE: "Esborra la memòria cau de lectura"
|
||||||
STR_USERNAME: "Nom d'usuari"
|
STR_USERNAME: "Nom d'usuari"
|
||||||
STR_PASSWORD: "Contrasenya"
|
STR_PASSWORD: "Contrasenya"
|
||||||
STR_SYNC_SERVER_URL: "URL del servidor de sincronització"
|
STR_SYNC_SERVER_URL: "URL del servidor de sincronització"
|
||||||
STR_DOCUMENT_MATCHING: "Coincidència de documents"
|
STR_DOCUMENT_MATCHING: "Coincidència de documents"
|
||||||
STR_SEND_METADATA: "Envia metadades del document"
|
|
||||||
STR_AUTHENTICATE: "Autentica"
|
STR_AUTHENTICATE: "Autentica"
|
||||||
STR_KOREADER_USERNAME: "Nom d'usuari del KOReader"
|
STR_KOREADER_USERNAME: "Nom d'usuari del KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Contrasenya del KOReader"
|
STR_KOREADER_PASSWORD: "Contrasenya del KOReader"
|
||||||
@@ -150,7 +144,7 @@ STR_PREV_NEXT: "Anterior/Següent"
|
|||||||
STR_NEXT_PREV: "Següent/Anterior"
|
STR_NEXT_PREV: "Següent/Anterior"
|
||||||
STR_KOSYNC: "KOSync"
|
STR_KOSYNC: "KOSync"
|
||||||
STR_BOOKMARK_OPTION: "Punt de llibre"
|
STR_BOOKMARK_OPTION: "Punt de llibre"
|
||||||
STR_DISABLED: "Sense funció"
|
STR_DISABLED: "Desactivats"
|
||||||
STR_NOTO_SERIF: "Noto Serif"
|
STR_NOTO_SERIF: "Noto Serif"
|
||||||
STR_NOTO_SANS: "Noto Sans"
|
STR_NOTO_SANS: "Noto Sans"
|
||||||
STR_SMALL: "Petita"
|
STR_SMALL: "Petita"
|
||||||
@@ -178,16 +172,16 @@ STR_UPDATING: "S'està actualitzant..."
|
|||||||
STR_NO_UPDATE: "No hi ha actualitzacions disponibles"
|
STR_NO_UPDATE: "No hi ha actualitzacions disponibles"
|
||||||
STR_UPDATE_FAILED: "Ha fallat l'actualització"
|
STR_UPDATE_FAILED: "Ha fallat l'actualització"
|
||||||
STR_UPDATE_COMPLETE: "Actualització completada"
|
STR_UPDATE_COMPLETE: "Actualització completada"
|
||||||
STR_POWER_ON_HINT: "Prem i mantén premut el botó d'encesa per tornar a engegar"
|
STR_POWER_ON_HINT: "Premeu i manteniu premut el botó d'encesa per tornar a engegar"
|
||||||
STR_NO_ENTRIES: "No s'ha trobat cap entrada"
|
STR_NO_ENTRIES: "No s'ha trobat cap entrada"
|
||||||
STR_DOWNLOADING: "S'està baixant..."
|
STR_DOWNLOADING: "S'està baixant..."
|
||||||
STR_DOWNLOAD_FAILED: "Ha fallat la baixada"
|
STR_DOWNLOAD_FAILED: "Ha fallat la baixada"
|
||||||
STR_ERROR_MSG: "Error:"
|
STR_ERROR_MSG: "Error:"
|
||||||
STR_UNNAMED: "Sense nom"
|
STR_UNNAMED: "Sense nom"
|
||||||
STR_HOLD_OPEN_TO_DELETE: "Mantén premut Obre per esborrar"
|
STR_HOLD_OPEN_TO_DELETE: "Manteniu premut Obre per esborrar"
|
||||||
STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor"
|
STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor"
|
||||||
STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del canal de continguts"
|
STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del feed"
|
||||||
STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del canal de continguts"
|
STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del feed"
|
||||||
STR_NETWORK_PREFIX: "Xarxa: "
|
STR_NETWORK_PREFIX: "Xarxa: "
|
||||||
STR_IP_ADDRESS_PREFIX: "Adreça IP: "
|
STR_IP_ADDRESS_PREFIX: "Adreça IP: "
|
||||||
STR_ERROR_GENERAL_FAILURE: "Error: Fallada general"
|
STR_ERROR_GENERAL_FAILURE: "Error: Fallada general"
|
||||||
@@ -200,7 +194,7 @@ STR_HOME: "« Inici"
|
|||||||
STR_SELECT: "Selecciona"
|
STR_SELECT: "Selecciona"
|
||||||
STR_SELECTED: "Seleccionat"
|
STR_SELECTED: "Seleccionat"
|
||||||
STR_TOGGLE: "Canvia"
|
STR_TOGGLE: "Canvia"
|
||||||
STR_TOGGLE_BOOKMARK: "Afegeix o elimina el punt de llibre"
|
STR_TOGGLE_BOOKMARK: "Commuta punt de llibre"
|
||||||
STR_CONFIRM: "Confirma"
|
STR_CONFIRM: "Confirma"
|
||||||
STR_CANCEL: "Cancel·la"
|
STR_CANCEL: "Cancel·la"
|
||||||
STR_CONNECT: "Connecta"
|
STR_CONNECT: "Connecta"
|
||||||
@@ -219,7 +213,7 @@ STR_DIR_RIGHT: "Dreta"
|
|||||||
STR_DIR_UP: "Amunt"
|
STR_DIR_UP: "Amunt"
|
||||||
STR_DIR_DOWN: "Avall"
|
STR_DIR_DOWN: "Avall"
|
||||||
STR_OK_BUTTON: "OK"
|
STR_OK_BUTTON: "OK"
|
||||||
STR_SLEEP_COVER_FILTER: "Filtre de la portada en repòs"
|
STR_SLEEP_COVER_FILTER: "Filtre de pantalla de repòs"
|
||||||
STR_FILTER_CONTRAST: "Contrast"
|
STR_FILTER_CONTRAST: "Contrast"
|
||||||
STR_CUSTOMISE_STATUS_BAR: "Personalitza la barra d'estat"
|
STR_CUSTOMISE_STATUS_BAR: "Personalitza la barra d'estat"
|
||||||
STR_CHAPTER_PAGE_COUNT: "Comptador de pàgines del capítol"
|
STR_CHAPTER_PAGE_COUNT: "Comptador de pàgines del capítol"
|
||||||
@@ -241,7 +235,7 @@ STR_THEME_CLASSIC: "Clàssic"
|
|||||||
STR_THEME_LYRA: "Lyra"
|
STR_THEME_LYRA: "Lyra"
|
||||||
STR_THEME_LYRA_EXTENDED: "Lyra Ampliat"
|
STR_THEME_LYRA_EXTENDED: "Lyra Ampliat"
|
||||||
STR_SUNLIGHT_FADING_FIX: "Correcció de l'esvaïment pel sol"
|
STR_SUNLIGHT_FADING_FIX: "Correcció de l'esvaïment pel sol"
|
||||||
STR_QUICK_RESUME_TIMEOUT: "Represa ràpida per inactivitat"
|
STR_QUICK_RESUME_TIMEOUT: "Represa ràpida després del temps"
|
||||||
STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals"
|
STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals"
|
||||||
STR_BOOKMARKS: "Punts de llibre"
|
STR_BOOKMARKS: "Punts de llibre"
|
||||||
STR_BOOKMARK_ADDED: "S'ha afegit el punt de llibre."
|
STR_BOOKMARK_ADDED: "S'ha afegit el punt de llibre."
|
||||||
@@ -250,17 +244,17 @@ STR_OPDS_BROWSER: "Navegador OPDS"
|
|||||||
STR_COVER_CUSTOM: "Portada + Personalitzat"
|
STR_COVER_CUSTOM: "Portada + Personalitzat"
|
||||||
STR_QUICK_RESUME: "Represa ràpida"
|
STR_QUICK_RESUME: "Represa ràpida"
|
||||||
STR_MENU_RECENT_BOOKS: "Llibres recents"
|
STR_MENU_RECENT_BOOKS: "Llibres recents"
|
||||||
STR_REMOVE_FROM_RECENTS: "Vols suprimir-lo de Llibres recents?"
|
STR_REMOVE_FROM_RECENTS: "Voleu suprimir-lo de Llibres recents?"
|
||||||
STR_NO_RECENT_BOOKS: "No hi ha llibres recents"
|
STR_NO_RECENT_BOOKS: "No hi ha llibres recents"
|
||||||
STR_CALIBRE_DESC: "Usa les transferències sense fils de Calibre"
|
STR_CALIBRE_DESC: "Usa les transferències sense fils de Calibre"
|
||||||
STR_FORGET_AND_REMOVE: "Vols oblidar la xarxa i suprimir la contrasenya desada?"
|
STR_FORGET_AND_REMOVE: "Voleu suprimir la contrasenya desada?"
|
||||||
STR_FORGET_BUTTON: "Oblida"
|
STR_FORGET_BUTTON: "Oblida"
|
||||||
STR_CALIBRE_STARTING: "S'està iniciant el Calibre..."
|
STR_CALIBRE_STARTING: "S'està iniciant el Calibre..."
|
||||||
STR_CALIBRE_SETUP: "Configura"
|
STR_CALIBRE_SETUP: "Configura"
|
||||||
STR_CALIBRE_STATUS: "Estat"
|
STR_CALIBRE_STATUS: "Estat"
|
||||||
STR_CLEAR_BUTTON: "Esborra"
|
STR_CLEAR_BUTTON: "Esborra"
|
||||||
STR_DEFAULT_VALUE: "Per defecte"
|
STR_DEFAULT_VALUE: "Per defecte"
|
||||||
STR_REMAP_PROMPT: "Prem un botó frontal per a cada rol"
|
STR_REMAP_PROMPT: "Premeu un botó frontal per a cada rol"
|
||||||
STR_UNASSIGNED: "No assignat"
|
STR_UNASSIGNED: "No assignat"
|
||||||
STR_ALREADY_ASSIGNED: "Ja assignat"
|
STR_ALREADY_ASSIGNED: "Ja assignat"
|
||||||
STR_REMAP_RESET_HINT: "Botó lateral Amunt: Restableix la disposició per defecte"
|
STR_REMAP_RESET_HINT: "Botó lateral Amunt: Restableix la disposició per defecte"
|
||||||
@@ -274,19 +268,19 @@ STR_GO_HOME_BUTTON: "Ves a l'inici"
|
|||||||
STR_SYNC_PROGRESS: "Sincronitza el progrés"
|
STR_SYNC_PROGRESS: "Sincronitza el progrés"
|
||||||
STR_DELETE_CACHE: "Esborra la memòria cau del llibre"
|
STR_DELETE_CACHE: "Esborra la memòria cau del llibre"
|
||||||
STR_DELETE: "Esborra"
|
STR_DELETE: "Esborra"
|
||||||
STR_CONFIRM_DELETE_BOOKMARK: "Vols esborrar aquest punt de llibre?"
|
STR_CONFIRM_DELETE_BOOKMARK: "Voleu esborrar aquest punt de llibre?"
|
||||||
STR_DISPLAY_QR: "Mostra la pàgina com a QR"
|
STR_DISPLAY_QR: "Mostra la pàgina com a QR"
|
||||||
STR_CHAPTER_PREFIX: "Capítol: "
|
STR_CHAPTER_PREFIX: "Capítol: "
|
||||||
STR_PAGES_SEPARATOR: " pàgines | "
|
STR_PAGES_SEPARATOR: " pàgines | "
|
||||||
STR_BOOK_PREFIX: "Llibre: "
|
STR_BOOK_PREFIX: "Llibre: "
|
||||||
STR_CALIBRE_URL_HINT: "Per al Calibre, afegeix /opds a la URL"
|
STR_CALIBRE_URL_HINT: "Per al Calibre, afegiu /opds a la URL"
|
||||||
STR_SYNCING_TIME: "S'està sincronitzant el temps..."
|
STR_SYNCING_TIME: "S'està sincronitzant el temps..."
|
||||||
STR_CALC_HASH: "S'està calculant l'empremta electrònica del document..."
|
STR_CALC_HASH: "S'està calculant el hash del document..."
|
||||||
STR_HASH_FAILED: "No s'ha pogut calcular l'empremta electrònica 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_FETCH_PROGRESS: "S'està obtenint el progrés remot..."
|
||||||
STR_UPLOAD_PROGRESS: "S'està pujant el progrés..."
|
STR_UPLOAD_PROGRESS: "S'està pujant el progrés..."
|
||||||
STR_NO_CREDENTIALS_MSG: "No s'han configurat credencials"
|
STR_NO_CREDENTIALS_MSG: "No s'han configurat credencials"
|
||||||
STR_KOREADER_SETUP_HINT: "Configura el compte de KOReader a la configuració"
|
STR_KOREADER_SETUP_HINT: "Configureu el compte de KOReader a la configuració"
|
||||||
STR_PROGRESS_FOUND: "S'ha trobat progrés!"
|
STR_PROGRESS_FOUND: "S'ha trobat progrés!"
|
||||||
STR_REMOTE_LABEL: "Remot:"
|
STR_REMOTE_LABEL: "Remot:"
|
||||||
STR_LOCAL_LABEL: "Local:"
|
STR_LOCAL_LABEL: "Local:"
|
||||||
@@ -296,7 +290,7 @@ STR_DEVICE_FROM_FORMAT: " De: %s"
|
|||||||
STR_APPLY_REMOTE: "Aplica el progrés remot"
|
STR_APPLY_REMOTE: "Aplica el progrés remot"
|
||||||
STR_UPLOAD_LOCAL: "Puja el progrés local"
|
STR_UPLOAD_LOCAL: "Puja el progrés local"
|
||||||
STR_NO_REMOTE_MSG: "No s'ha trobat progrés remot"
|
STR_NO_REMOTE_MSG: "No s'ha trobat progrés remot"
|
||||||
STR_UPLOAD_PROMPT: "Vols pujar la posició actual?"
|
STR_UPLOAD_PROMPT: "Voleu pujar la posició actual?"
|
||||||
STR_UPLOAD_SUCCESS: "Progrés pujat!"
|
STR_UPLOAD_SUCCESS: "Progrés pujat!"
|
||||||
STR_SYNC_FAILED_MSG: "Sincronització fallida"
|
STR_SYNC_FAILED_MSG: "Sincronització fallida"
|
||||||
STR_SAVE_PROGRESS_FAILED: "No s'ha pogut desar el progrés"
|
STR_SAVE_PROGRESS_FAILED: "No s'ha pogut desar el progrés"
|
||||||
@@ -315,11 +309,11 @@ STR_SLEEP_NEVER: "Mai"
|
|||||||
STR_STEP_HINT_FRONT: "Botons frontals:"
|
STR_STEP_HINT_FRONT: "Botons frontals:"
|
||||||
STR_STEP_HINT_SIDE: "Botons laterals:"
|
STR_STEP_HINT_SIDE: "Botons laterals:"
|
||||||
STR_SCREENSHOT_BUTTON: "Fes una captura de pantalla"
|
STR_SCREENSHOT_BUTTON: "Fes una captura de pantalla"
|
||||||
STR_AUTO_TURN_ENABLED: "Pas automàtic de pàgina activat"
|
STR_AUTO_TURN_ENABLED: "Passar automàtic activat: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Pas automàtic de pàgina (pàg./min)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Passar automàtic (pàgines per minut)"
|
||||||
STR_TILT_PAGE_TURN: "Pas de pàgina per inclinació"
|
STR_TILT_PAGE_TURN: "Pas de pàgina per inclinació"
|
||||||
STR_FORCE_REFRESH: "Refresca la pantalla"
|
STR_FORCE_REFRESH: "Refresca la pantalla"
|
||||||
STR_RESTARTING_HINT: "S'està reiniciant... Si el dispositiu no es reinicia, mantén premut el botó d'encesa durant uns segons."
|
STR_RESTARTING_HINT: "S'està reiniciant... Si el dispositiu no es reinicia, manteniu premut el botó d'encesa durant uns segons."
|
||||||
STR_NEXT_PAGE: "Pàgina següent »"
|
STR_NEXT_PAGE: "Pàgina següent »"
|
||||||
STR_PREV_PAGE: "« Pàgina anterior"
|
STR_PREV_PAGE: "« Pàgina anterior"
|
||||||
STR_XTC_STATUS_BAR: "Barra d'estat XTC"
|
STR_XTC_STATUS_BAR: "Barra d'estat XTC"
|
||||||
@@ -348,25 +342,20 @@ STR_SERVER_NAME: "Nom del servidor"
|
|||||||
STR_NO_SERVERS: "No hi ha servidors OPDS configurats"
|
STR_NO_SERVERS: "No hi ha servidors OPDS configurats"
|
||||||
STR_DELETE_SERVER: "Suprimeix el servidor"
|
STR_DELETE_SERVER: "Suprimeix el servidor"
|
||||||
STR_OPDS_SERVERS: "Servidors OPDS"
|
STR_OPDS_SERVERS: "Servidors OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Carpeta de baixades"
|
STR_MANAGE_FONTS: "Gestiona els tipus de lletra"
|
||||||
STR_OPDS_FILENAME_FORMAT: "Format del nom de fitxer"
|
STR_FONT_BROWSER: "Navegador de tipus de lletra"
|
||||||
STR_FMT_AUTHOR_TITLE: "Autor - Títol"
|
STR_LOADING_FONT_LIST: "S'està carregant la llista de tipus de lletra..."
|
||||||
STR_FMT_TITLE_AUTHOR: "Títol - Autor"
|
STR_NO_FONTS_AVAILABLE: "No hi ha tipus de lletra disponibles"
|
||||||
STR_FMT_TITLE: "Títol"
|
|
||||||
STR_MANAGE_FONTS: "Gestiona les fonts"
|
|
||||||
STR_FONT_BROWSER: "Navegador de fonts"
|
|
||||||
STR_LOADING_FONT_LIST: "S'està carregant la llista de fonts..."
|
|
||||||
STR_NO_FONTS_AVAILABLE: "No hi ha fonts disponibles"
|
|
||||||
STR_FONT_PREVIEW_TEXT: "Jove xef, porti whisky amb quinze glaçons d'hidrogen, coi!"
|
STR_FONT_PREVIEW_TEXT: "Jove xef, porti whisky amb quinze glaçons d'hidrogen, coi!"
|
||||||
STR_FONT_INSTALLED: "Font instal·lada!"
|
STR_FONT_INSTALLED: "Tipus de lletra instal·lat!"
|
||||||
STR_FONT_INSTALL_FAILED: "Ha fallat la instal·lació de la font"
|
STR_FONT_INSTALL_FAILED: "Ha fallat la instal·lació del tipus de lletra"
|
||||||
STR_INSTALLED: "Instal·lat"
|
STR_INSTALLED: "Instal·lat"
|
||||||
STR_DOWNLOAD_ALL: "Descarrega-ho tot"
|
STR_DOWNLOAD_ALL: "Descarrega-ho tot"
|
||||||
STR_UPDATE_ALL: "Actualitza-ho tot"
|
STR_UPDATE_ALL: "Actualitza-ho tot"
|
||||||
STR_UPDATE_AVAILABLE: "Actualitza"
|
STR_UPDATE_AVAILABLE: "Actualitza"
|
||||||
STR_CRASH_TITLE: "Fallada del sistema"
|
STR_CRASH_TITLE: "Bloqueig del sistema"
|
||||||
STR_CRASH_DESCRIPTION: "S'ha desat un informe detallat a crash_report.txt. Inclou aquest fitxer a l'informe d'errors."
|
STR_CRASH_DESCRIPTION: "S'ha desat un informe detallat a crash_report.txt. Incloeu aquest fitxer a l'informe d'errors."
|
||||||
STR_CRASH_REASON: "Motiu de la fallada"
|
STR_CRASH_REASON: "Motiu del bloqueig:"
|
||||||
STR_CRASH_NO_REASON: "(No s'ha registrat cap motiu)"
|
STR_CRASH_NO_REASON: "(No s'ha registrat cap motiu)"
|
||||||
STR_KB_HINT_MOVE_CURSOR: "Prem Esquerra o Dreta per moure el cursor"
|
STR_KB_HINT_MOVE_CURSOR: "Prem Esquerra o Dreta per moure el cursor"
|
||||||
STR_KB_HINT_RETURN_CURSOR: "Prem Esquerra per tornar a la posició del cursor"
|
STR_KB_HINT_RETURN_CURSOR: "Prem Esquerra per tornar a la posició del cursor"
|
||||||
@@ -379,22 +368,20 @@ STR_KB_TIPS: "Consells:"
|
|||||||
STR_KB_HINT_RETURN_KEYBOARD: "Prem Avall per tornar al teclat"
|
STR_KB_HINT_RETURN_KEYBOARD: "Prem Avall per tornar al teclat"
|
||||||
STR_KB_HINT_EXIT_URL_MODE: "Prem ABC per sortir del mode URL"
|
STR_KB_HINT_EXIT_URL_MODE: "Prem ABC per sortir del mode URL"
|
||||||
STR_KB_HINT_CLEAR_TEXT: "Mantén premut DEL per esborrar tot el text"
|
STR_KB_HINT_CLEAR_TEXT: "Mantén premut DEL per esborrar tot el text"
|
||||||
STR_KB_HINT_SECONDARY_CHAR: "Mantén premut SELECT: caràcter secundari"
|
STR_KB_HINT_SECONDARY_CHAR: "Mantén premut SELECT per al caràcter secundari"
|
||||||
STR_KB_HINT_UPPER_SECONDARY: "Mantén premut SELECT: majúscules o caràcter secundari"
|
STR_KB_HINT_UPPER_SECONDARY: "Mantén premut SELECT per MAJÚSCULES o caràcter secundari"
|
||||||
STR_KB_HINT_LOWER_SECONDARY: "Mantén premut SELECT: minúscules o caràcter secundari"
|
STR_KB_HINT_LOWER_SECONDARY: "Mantén premut SELECT per minúscules o caràcter secundari"
|
||||||
STR_KB_HINT_URL_SNIPPETS: "Prem URL per inserir fragments"
|
STR_KB_HINT_URL_SNIPPETS: "Prem URL per inserir fragments"
|
||||||
STR_SD_FIRMWARE_UPDATE: "Actualització de firmware des de la targeta SD"
|
STR_SD_FIRMWARE_UPDATE: "Actualització de firmware des de la targeta SD"
|
||||||
STR_SELECT_FIRMWARE_FILE: "Selecciona un fitxer de firmware (.bin)"
|
STR_SELECT_FIRMWARE_FILE: "Seleccioneu un fitxer de firmware (.bin)"
|
||||||
STR_NO_BIN_FILES: "No s'han trobat fitxers .bin"
|
STR_NO_BIN_FILES: "No s'han trobat fitxers .bin"
|
||||||
STR_VALIDATING_FIRMWARE: "S'està validant el firmware..."
|
STR_VALIDATING_FIRMWARE: "S'està validant el firmware..."
|
||||||
STR_INVALID_FIRMWARE: "Fitxer de firmware no vàlid"
|
STR_INVALID_FIRMWARE: "Fitxer de firmware no vàlid"
|
||||||
STR_FIRMWARE_TOO_LARGE: "El firmware és massa gran per a la partició"
|
STR_FIRMWARE_TOO_LARGE: "El firmware és massa gran per a la partició"
|
||||||
STR_FIRMWARE_TOO_SMALL: "El fitxer de firmware és massa petit"
|
STR_FIRMWARE_TOO_SMALL: "El fitxer de firmware és massa petit"
|
||||||
STR_FIRMWARE_UPDATE_PROMPT: "Vols actualitzar el firmware?"
|
STR_FIRMWARE_UPDATE_PROMPT: "Voleu actualitzar el firmware?"
|
||||||
STR_FIRMWARE_FILE_OPEN_FAILED: "No es pot obrir el fitxer"
|
STR_FIRMWARE_FILE_OPEN_FAILED: "No es pot obrir el fitxer"
|
||||||
STR_FIRMWARE_WRITE_FAILED: "Ha fallat l'escriptura del firmware"
|
STR_FIRMWARE_WRITE_FAILED: "Ha fallat l'escriptura del firmware"
|
||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "No apaguis el dispositiu!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "No apagueu el dispositiu!"
|
||||||
STR_RECOVERY_MODE: "Mode de recuperació"
|
STR_RECOVERY_MODE: "Mode de recuperació"
|
||||||
STR_RECOVERY_MODE_HINT: "Posa firmware.bin a l'arrel de la targeta SD i selecciona'l"
|
STR_RECOVERY_MODE_HINT: "Poseu firmware.bin a l'arrel de la targeta SD i seleccioneu-lo"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Afegeix una xarxa oculta..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Introdueix el nom de la xarxa (SSID)"
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ STR_NETWORK_LEGEND: "* = Šifrováno | + = Uloženo"
|
|||||||
STR_MAC_ADDRESS: "MAC adresa:"
|
STR_MAC_ADDRESS: "MAC adresa:"
|
||||||
STR_CHECKING_WIFI: "Kontrola Wi-Fi..."
|
STR_CHECKING_WIFI: "Kontrola Wi-Fi..."
|
||||||
STR_ENTER_WIFI_PASSWORD: "Zadejte heslo Wi-Fi"
|
STR_ENTER_WIFI_PASSWORD: "Zadejte heslo Wi-Fi"
|
||||||
STR_TO_PREFIX: "k "
|
STR_TO_PREFIX: "pro"
|
||||||
STR_CALIBRE_RECEIVING: "Příjem:"
|
STR_CALIBRE_RECEIVING: "Příjem:"
|
||||||
STR_CALIBRE_RECEIVED: "Přijato:"
|
STR_CALIBRE_RECEIVED: "Přijato:"
|
||||||
STR_CALIBRE_INSTRUCTION_1: "1) Nainstalujte plugin CrossPoint Reader"
|
STR_CALIBRE_INSTRUCTION_1: "1) Nainstalujte plugin CrossPoint Reader"
|
||||||
@@ -65,10 +65,11 @@ STR_SLEEP_COVER_MODE: "Obrazovka spánku Režim krytu"
|
|||||||
STR_HIDE_BATTERY: "Skrýt baterii %"
|
STR_HIDE_BATTERY: "Skrýt baterii %"
|
||||||
STR_EXTRA_SPACING: "Extra mezery mezi odstavci"
|
STR_EXTRA_SPACING: "Extra mezery mezi odstavci"
|
||||||
STR_TEXT_AA: "Vyhlazování textu"
|
STR_TEXT_AA: "Vyhlazování textu"
|
||||||
|
STR_TEXT_AA_FULL: "Plné"
|
||||||
|
STR_TEXT_AA_FAST: "Rychlé"
|
||||||
STR_SHORT_PWR_BTN: "Krátké stisknutí tlačítka napájení"
|
STR_SHORT_PWR_BTN: "Krátké stisknutí tlačítka napájení"
|
||||||
STR_ORIENTATION: "Orientace čtení"
|
STR_ORIENTATION: "Orientace čtení"
|
||||||
STR_SIDE_BTN_LAYOUT: "Rozvržení bočních tlačítek (čtečka)"
|
STR_SIDE_BTN_LAYOUT: "Rozvržení bočních tlačítek (čtečka)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Dotykové ovládání čtečky"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientovat přední tlačítka"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientovat přední tlačítka"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Chování při dlouhém stisknutí tlačítka"
|
STR_LONG_PRESS_BEHAVIOR: "Chování při dlouhém stisknutí tlačítka"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "VYP"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "VYP"
|
||||||
@@ -93,7 +94,6 @@ STR_USERNAME: "Uživatelské jméno"
|
|||||||
STR_PASSWORD: "Heslo"
|
STR_PASSWORD: "Heslo"
|
||||||
STR_SYNC_SERVER_URL: "URL synch. serveru"
|
STR_SYNC_SERVER_URL: "URL synch. serveru"
|
||||||
STR_DOCUMENT_MATCHING: "Párování dokumentů"
|
STR_DOCUMENT_MATCHING: "Párování dokumentů"
|
||||||
STR_SEND_METADATA: "Odesílat metadata dokumentu"
|
|
||||||
STR_AUTHENTICATE: "Ověření"
|
STR_AUTHENTICATE: "Ověření"
|
||||||
STR_KOREADER_USERNAME: "Uživ. jméno KOReaderu"
|
STR_KOREADER_USERNAME: "Uživ. jméno KOReaderu"
|
||||||
STR_KOREADER_PASSWORD: "Heslo KOReaderu"
|
STR_KOREADER_PASSWORD: "Heslo KOReaderu"
|
||||||
@@ -271,16 +271,9 @@ STR_BOOK_S_STYLE: "Styl knihy"
|
|||||||
STR_EMBEDDED_STYLE: "Vložený styl"
|
STR_EMBEDDED_STYLE: "Vložený styl"
|
||||||
STR_FOCUS_READING: "Soustředěné čtení"
|
STR_FOCUS_READING: "Soustředěné čtení"
|
||||||
STR_OPDS_SERVER_URL: "URL serveru OPDS"
|
STR_OPDS_SERVER_URL: "URL serveru OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Složka pro stahování"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Formát názvu souboru"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autor - Název"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Název - Autor"
|
|
||||||
STR_FMT_TITLE: "Název"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Udělat snímek obrazovky"
|
STR_SCREENSHOT_BUTTON: "Udělat snímek obrazovky"
|
||||||
STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
||||||
STR_SLEEP_NEVER: "Nikdy"
|
STR_SLEEP_NEVER: "Nikdy"
|
||||||
STR_STEP_HINT_FRONT: "Přední tlačítka:"
|
STR_STEP_HINT_FRONT: "Přední tlačítka:"
|
||||||
STR_STEP_HINT_SIDE: "Boční tlačítka:"
|
STR_STEP_HINT_SIDE: "Boční tlačítka:"
|
||||||
STR_TILT_PAGE_TURN: "Otáčení stránek nakloněním"
|
STR_TILT_PAGE_TURN: "Otáčení stránek nakloněním"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Přidat skrytou síť..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Zadejte název sítě (SSID)"
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ STR_SLEEP_COVER_MODE: "Hvile-skærm omslag-tilstand"
|
|||||||
STR_HIDE_BATTERY: "Skjul batteri %"
|
STR_HIDE_BATTERY: "Skjul batteri %"
|
||||||
STR_EXTRA_SPACING: "Ekstra afsnitsafstand"
|
STR_EXTRA_SPACING: "Ekstra afsnitsafstand"
|
||||||
STR_TEXT_AA: "Tekst Anti-Aliasing"
|
STR_TEXT_AA: "Tekst Anti-Aliasing"
|
||||||
|
STR_TEXT_AA_FULL: "Fuld"
|
||||||
|
STR_TEXT_AA_FAST: "Hurtig"
|
||||||
STR_IMAGES: "Billeder"
|
STR_IMAGES: "Billeder"
|
||||||
STR_IMAGES_DISPLAY: "Vis"
|
STR_IMAGES_DISPLAY: "Vis"
|
||||||
STR_IMAGES_PLACEHOLDER: "Pladsholder"
|
STR_IMAGES_PLACEHOLDER: "Pladsholder"
|
||||||
@@ -72,7 +74,6 @@ STR_IMAGES_SUPPRESS: "Skjul"
|
|||||||
STR_SHORT_PWR_BTN: "Kort tryk på tænd/sluk-knap"
|
STR_SHORT_PWR_BTN: "Kort tryk på tænd/sluk-knap"
|
||||||
STR_ORIENTATION: "Læseretning"
|
STR_ORIENTATION: "Læseretning"
|
||||||
STR_SIDE_BTN_LAYOUT: "Knaplayout på siden (læser)"
|
STR_SIDE_BTN_LAYOUT: "Knaplayout på siden (læser)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Touchkontroller i læser"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientér forreste knapper"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientér forreste knapper"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Comportamiento al mantener pulsado el botón"
|
STR_LONG_PRESS_BEHAVIOR: "Comportamiento al mantener pulsado el botón"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivado"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivado"
|
||||||
@@ -98,7 +99,6 @@ STR_USERNAME: "Brugernavn"
|
|||||||
STR_PASSWORD: "Adgangskode"
|
STR_PASSWORD: "Adgangskode"
|
||||||
STR_SYNC_SERVER_URL: "Synkroniseringsserver-URL"
|
STR_SYNC_SERVER_URL: "Synkroniseringsserver-URL"
|
||||||
STR_DOCUMENT_MATCHING: "Dokumentsammenkobling"
|
STR_DOCUMENT_MATCHING: "Dokumentsammenkobling"
|
||||||
STR_SEND_METADATA: "Send dokumentmetadata"
|
|
||||||
STR_AUTHENTICATE: "Godkend"
|
STR_AUTHENTICATE: "Godkend"
|
||||||
STR_KOREADER_USERNAME: "KOReader brugernavn"
|
STR_KOREADER_USERNAME: "KOReader brugernavn"
|
||||||
STR_KOREADER_PASSWORD: "KOReader adgangskode"
|
STR_KOREADER_PASSWORD: "KOReader adgangskode"
|
||||||
@@ -301,14 +301,7 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
|||||||
STR_SLEEP_NEVER: "Aldrig"
|
STR_SLEEP_NEVER: "Aldrig"
|
||||||
STR_STEP_HINT_FRONT: "Frontknapper:"
|
STR_STEP_HINT_FRONT: "Frontknapper:"
|
||||||
STR_STEP_HINT_SIDE: "Sideknapper:"
|
STR_STEP_HINT_SIDE: "Sideknapper:"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Downloadmappe"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Filnavnsformat"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Forfatter - Titel"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Titel - Forfatter"
|
|
||||||
STR_FMT_TITLE: "Titel"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Tag skærmbillede"
|
STR_SCREENSHOT_BUTTON: "Tag skærmbillede"
|
||||||
STR_AUTO_TURN_ENABLED: "Automatisk sidevendning aktiveret: "
|
STR_AUTO_TURN_ENABLED: "Automatisk sidevendning aktiveret: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Automatisk vending (sider per minut)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Automatisk vending (sider per minut)"
|
||||||
STR_TILT_PAGE_TURN: "Vip for at vende side"
|
STR_TILT_PAGE_TURN: "Vip for at vende side"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Tilføj skjult netværk..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Indtast netværksnavn (SSID)"
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ STR_SLEEP_COVER_MODE: "Slaapscherm omslag-modus"
|
|||||||
STR_HIDE_BATTERY: "Batterij % verbergen"
|
STR_HIDE_BATTERY: "Batterij % verbergen"
|
||||||
STR_EXTRA_SPACING: "Extra regelafstand alinea"
|
STR_EXTRA_SPACING: "Extra regelafstand alinea"
|
||||||
STR_TEXT_AA: "Tekst Anti-Aliasing"
|
STR_TEXT_AA: "Tekst Anti-Aliasing"
|
||||||
|
STR_TEXT_AA_FULL: "Volledig"
|
||||||
|
STR_TEXT_AA_FAST: "Snel"
|
||||||
STR_IMAGES: "Afbeeldingen"
|
STR_IMAGES: "Afbeeldingen"
|
||||||
STR_IMAGES_DISPLAY: "Weergave"
|
STR_IMAGES_DISPLAY: "Weergave"
|
||||||
STR_IMAGES_PLACEHOLDER: "Placeholder"
|
STR_IMAGES_PLACEHOLDER: "Placeholder"
|
||||||
@@ -72,7 +74,6 @@ STR_IMAGES_SUPPRESS: "Verbergen"
|
|||||||
STR_SHORT_PWR_BTN: "Korte klik aan/uit-knop"
|
STR_SHORT_PWR_BTN: "Korte klik aan/uit-knop"
|
||||||
STR_ORIENTATION: "Leesstand"
|
STR_ORIENTATION: "Leesstand"
|
||||||
STR_SIDE_BTN_LAYOUT: "Indeling zijknoppen (lezer)"
|
STR_SIDE_BTN_LAYOUT: "Indeling zijknoppen (lezer)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Aanraakbediening lezer"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Richt voorste knoppen"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Richt voorste knoppen"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||||
@@ -98,7 +99,6 @@ STR_USERNAME: "Gebruikersnaam"
|
|||||||
STR_PASSWORD: "Wachtwoord"
|
STR_PASSWORD: "Wachtwoord"
|
||||||
STR_SYNC_SERVER_URL: "Sync-server URL"
|
STR_SYNC_SERVER_URL: "Sync-server URL"
|
||||||
STR_DOCUMENT_MATCHING: "Documentkoppeling"
|
STR_DOCUMENT_MATCHING: "Documentkoppeling"
|
||||||
STR_SEND_METADATA: "Documentmetadata versturen"
|
|
||||||
STR_AUTHENTICATE: "Authenticatie"
|
STR_AUTHENTICATE: "Authenticatie"
|
||||||
STR_KOREADER_USERNAME: "KOReader gebruikersnaam"
|
STR_KOREADER_USERNAME: "KOReader gebruikersnaam"
|
||||||
STR_KOREADER_PASSWORD: "KOReader wachtwoord"
|
STR_KOREADER_PASSWORD: "KOReader wachtwoord"
|
||||||
@@ -301,14 +301,7 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
|||||||
STR_SLEEP_NEVER: "Nooit"
|
STR_SLEEP_NEVER: "Nooit"
|
||||||
STR_STEP_HINT_FRONT: "Voorknoppen:"
|
STR_STEP_HINT_FRONT: "Voorknoppen:"
|
||||||
STR_STEP_HINT_SIDE: "Zijknoppen:"
|
STR_STEP_HINT_SIDE: "Zijknoppen:"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Downloadmap"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Bestandsnaamformaat"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Auteur - Titel"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Titel - Auteur"
|
|
||||||
STR_FMT_TITLE: "Titel"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Screenshot maken"
|
STR_SCREENSHOT_BUTTON: "Screenshot maken"
|
||||||
STR_AUTO_TURN_ENABLED: "Automatisch omslaan ingeschakeld: "
|
STR_AUTO_TURN_ENABLED: "Automatisch omslaan ingeschakeld: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Autom. omslaan (pagina's per minuut)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Autom. omslaan (pagina's per minuut)"
|
||||||
STR_TILT_PAGE_TURN: "Kantel om te bladeren"
|
STR_TILT_PAGE_TURN: "Kantel om te bladeren"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Verborgen netwerk toevoegen..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Voer netwerknaam in (SSID)"
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ STR_BROWSE_FILES: "Browse Files"
|
|||||||
STR_FILE_TRANSFER: "File Transfer"
|
STR_FILE_TRANSFER: "File Transfer"
|
||||||
STR_SETTINGS_TITLE: "Settings"
|
STR_SETTINGS_TITLE: "Settings"
|
||||||
STR_CONTINUE_READING: "Continue Reading"
|
STR_CONTINUE_READING: "Continue Reading"
|
||||||
STR_RESUME: "Resume"
|
|
||||||
STR_NO_OPEN_BOOK: "No open book"
|
STR_NO_OPEN_BOOK: "No open book"
|
||||||
STR_START_READING: "Start reading below"
|
STR_START_READING: "Start reading below"
|
||||||
STR_NO_FILES_FOUND: "No files found"
|
STR_NO_FILES_FOUND: "No files found"
|
||||||
@@ -19,27 +18,17 @@ STR_NO_CHAPTERS: "No chapters"
|
|||||||
STR_END_OF_BOOK: "End of book"
|
STR_END_OF_BOOK: "End of book"
|
||||||
STR_EMPTY_CHAPTER: "Empty chapter"
|
STR_EMPTY_CHAPTER: "Empty chapter"
|
||||||
STR_INDEXING: "Indexing"
|
STR_INDEXING: "Indexing"
|
||||||
STR_INDEX_FAILED: "Failed to index - invalid book"
|
|
||||||
STR_MEMORY_ERROR: "Memory error"
|
STR_MEMORY_ERROR: "Memory error"
|
||||||
STR_PAGE_LOAD_ERROR: "Page load error"
|
STR_PAGE_LOAD_ERROR: "Page load error"
|
||||||
STR_EMPTY_FILE: "Empty file"
|
STR_EMPTY_FILE: "Empty file"
|
||||||
STR_OUT_OF_BOUNDS: "Out of bounds"
|
STR_OUT_OF_BOUNDS: "Out of bounds"
|
||||||
STR_LOADING: "Loading..."
|
STR_LOADING: "Loading..."
|
||||||
STR_LOADING_POPUP: "Loading"
|
STR_LOADING_POPUP: "Loading"
|
||||||
STR_LOOKUP: "Look Up"
|
|
||||||
STR_DICT_LOOKING_UP: "Looking up..."
|
|
||||||
STR_DICT_INDEXING: "Indexing dictionary..."
|
|
||||||
STR_DICT_NOT_FOUND: "Not found"
|
|
||||||
STR_DICT_NO_DICT_SET: "No dictionary set"
|
|
||||||
STR_DICT_ERROR: "Dictionary error"
|
|
||||||
STR_WIFI_NETWORKS: "Wi-Fi Networks"
|
STR_WIFI_NETWORKS: "Wi-Fi Networks"
|
||||||
STR_NO_NETWORKS: "No networks found"
|
STR_NO_NETWORKS: "No networks found"
|
||||||
STR_NETWORKS_FOUND: "%zu networks found"
|
STR_NETWORKS_FOUND: "%zu networks found"
|
||||||
STR_SCANNING: "Scanning..."
|
STR_SCANNING: "Scanning..."
|
||||||
STR_FINDING_SAVED_WIFI: "Finding saved Wi-Fi..."
|
|
||||||
STR_CONNECTING: "Connecting..."
|
STR_CONNECTING: "Connecting..."
|
||||||
STR_CONNECTING_SAVED_WIFI: "Connecting to saved Wi-Fi..."
|
|
||||||
STR_SHOW_NETWORKS: "Show"
|
|
||||||
STR_CONNECTED: "Connected!"
|
STR_CONNECTED: "Connected!"
|
||||||
STR_CONNECTION_FAILED: "Connection Failed"
|
STR_CONNECTION_FAILED: "Connection Failed"
|
||||||
STR_FORGET_NETWORK: "Forget Network?"
|
STR_FORGET_NETWORK: "Forget Network?"
|
||||||
@@ -60,8 +49,6 @@ STR_NETWORK_LEGEND: "* = Encrypted | + = Saved"
|
|||||||
STR_MAC_ADDRESS: "MAC address:"
|
STR_MAC_ADDRESS: "MAC address:"
|
||||||
STR_CHECKING_WIFI: "Checking Wi-Fi..."
|
STR_CHECKING_WIFI: "Checking Wi-Fi..."
|
||||||
STR_ENTER_WIFI_PASSWORD: "Enter Wi-Fi password"
|
STR_ENTER_WIFI_PASSWORD: "Enter Wi-Fi password"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Add hidden network..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Enter network name (SSID)"
|
|
||||||
STR_TO_PREFIX: "to "
|
STR_TO_PREFIX: "to "
|
||||||
STR_CALIBRE_RECEIVING: "Receiving: "
|
STR_CALIBRE_RECEIVING: "Receiving: "
|
||||||
STR_CALIBRE_RECEIVED: "Received: "
|
STR_CALIBRE_RECEIVED: "Received: "
|
||||||
@@ -79,6 +66,8 @@ STR_SLEEP_COVER_MODE: "Sleep Screen Cover Mode"
|
|||||||
STR_HIDE_BATTERY: "Hide Battery %"
|
STR_HIDE_BATTERY: "Hide Battery %"
|
||||||
STR_EXTRA_SPACING: "Extra Paragraph Spacing"
|
STR_EXTRA_SPACING: "Extra Paragraph Spacing"
|
||||||
STR_TEXT_AA: "Text Anti-Aliasing"
|
STR_TEXT_AA: "Text Anti-Aliasing"
|
||||||
|
STR_TEXT_AA_FULL: "Full"
|
||||||
|
STR_TEXT_AA_FAST: "Fast"
|
||||||
STR_IMAGES: "Images"
|
STR_IMAGES: "Images"
|
||||||
STR_IMAGES_DISPLAY: "Display"
|
STR_IMAGES_DISPLAY: "Display"
|
||||||
STR_IMAGES_PLACEHOLDER: "Placeholder"
|
STR_IMAGES_PLACEHOLDER: "Placeholder"
|
||||||
@@ -88,7 +77,6 @@ STR_EOB_CONTINUE_WITH: "Continue with"
|
|||||||
STR_SHORT_PWR_BTN: "Short Power Button Click"
|
STR_SHORT_PWR_BTN: "Short Power Button Click"
|
||||||
STR_ORIENTATION: "Reading Orientation"
|
STR_ORIENTATION: "Reading Orientation"
|
||||||
STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)"
|
STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Touch Reader Controls"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orient front buttons"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orient front buttons"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||||
@@ -115,16 +103,7 @@ STR_USERNAME: "Username"
|
|||||||
STR_PASSWORD: "Password"
|
STR_PASSWORD: "Password"
|
||||||
STR_SYNC_SERVER_URL: "Sync Server URL"
|
STR_SYNC_SERVER_URL: "Sync Server URL"
|
||||||
STR_DOCUMENT_MATCHING: "Document Matching"
|
STR_DOCUMENT_MATCHING: "Document Matching"
|
||||||
STR_SEND_METADATA: "Send Document Metadata"
|
|
||||||
STR_SYNC_BEHAVIOR: "Sync Behavior"
|
|
||||||
STR_ASK_EVERY_TIME: "Ask every time"
|
|
||||||
STR_SMART_SYNC: "Smart sync"
|
|
||||||
STR_AUTHENTICATE: "Authenticate"
|
STR_AUTHENTICATE: "Authenticate"
|
||||||
STR_SIGN_UP: "Sign Up"
|
|
||||||
STR_CREATING_ACCOUNT: "Creating account..."
|
|
||||||
STR_ACCOUNT_CREATED: "Account created"
|
|
||||||
STR_SIGNUP_FAILED: "Sign up failed"
|
|
||||||
STR_USERNAME_TAKEN: "Username is already registered"
|
|
||||||
STR_KOREADER_USERNAME: "KOReader Username"
|
STR_KOREADER_USERNAME: "KOReader Username"
|
||||||
STR_KOREADER_PASSWORD: "KOReader Password"
|
STR_KOREADER_PASSWORD: "KOReader Password"
|
||||||
STR_FILENAME: "Filename"
|
STR_FILENAME: "Filename"
|
||||||
@@ -170,7 +149,6 @@ STR_PREV_NEXT: "Prev/Next"
|
|||||||
STR_NEXT_PREV: "Next/Prev"
|
STR_NEXT_PREV: "Next/Prev"
|
||||||
STR_KOSYNC: "KOSync"
|
STR_KOSYNC: "KOSync"
|
||||||
STR_BOOKMARK_OPTION: "Bookmark"
|
STR_BOOKMARK_OPTION: "Bookmark"
|
||||||
STR_DICTIONARY: "Dictionary"
|
|
||||||
STR_DISABLED: "Disabled"
|
STR_DISABLED: "Disabled"
|
||||||
STR_NOTO_SERIF: "Noto Serif"
|
STR_NOTO_SERIF: "Noto Serif"
|
||||||
STR_NOTO_SANS: "Noto Sans"
|
STR_NOTO_SANS: "Noto Sans"
|
||||||
@@ -231,7 +209,6 @@ STR_CONNECT: "Connect"
|
|||||||
STR_OPEN: "Open"
|
STR_OPEN: "Open"
|
||||||
STR_DOWNLOAD: "Download"
|
STR_DOWNLOAD: "Download"
|
||||||
STR_RETRY: "Retry"
|
STR_RETRY: "Retry"
|
||||||
STR_TAP_TO_RETRY: "Tap to retry"
|
|
||||||
STR_YES: "Yes"
|
STR_YES: "Yes"
|
||||||
STR_NO: "No"
|
STR_NO: "No"
|
||||||
STR_SHOW: "Show"
|
STR_SHOW: "Show"
|
||||||
@@ -244,9 +221,6 @@ STR_DIR_RIGHT: "Right"
|
|||||||
STR_DIR_UP: "Up"
|
STR_DIR_UP: "Up"
|
||||||
STR_DIR_DOWN: "Down"
|
STR_DIR_DOWN: "Down"
|
||||||
STR_OK_BUTTON: "OK"
|
STR_OK_BUTTON: "OK"
|
||||||
STR_KEY_SHIFT: "Shift"
|
|
||||||
STR_KEY_MODE_SYMBOLS: "?123"
|
|
||||||
STR_KEY_MODE_ABC: "abc"
|
|
||||||
STR_SLEEP_COVER_FILTER: "Sleep Screen Cover Filter"
|
STR_SLEEP_COVER_FILTER: "Sleep Screen Cover Filter"
|
||||||
STR_FILTER_CONTRAST: "Contrast"
|
STR_FILTER_CONTRAST: "Contrast"
|
||||||
STR_CUSTOMISE_STATUS_BAR: "Customise Status Bar"
|
STR_CUSTOMISE_STATUS_BAR: "Customise Status Bar"
|
||||||
@@ -345,7 +319,6 @@ STR_UPLOAD_LOCAL: "Upload local progress"
|
|||||||
STR_NO_REMOTE_MSG: "No remote progress found"
|
STR_NO_REMOTE_MSG: "No remote progress found"
|
||||||
STR_UPLOAD_PROMPT: "Upload current position?"
|
STR_UPLOAD_PROMPT: "Upload current position?"
|
||||||
STR_UPLOAD_SUCCESS: "Progress uploaded!"
|
STR_UPLOAD_SUCCESS: "Progress uploaded!"
|
||||||
STR_ALREADY_SYNCED: "Already synced"
|
|
||||||
STR_SYNC_FAILED_MSG: "Sync failed"
|
STR_SYNC_FAILED_MSG: "Sync failed"
|
||||||
STR_SAVE_PROGRESS_FAILED: "Could not save progress"
|
STR_SAVE_PROGRESS_FAILED: "Could not save progress"
|
||||||
STR_SECTION_PREFIX: "Section "
|
STR_SECTION_PREFIX: "Section "
|
||||||
@@ -355,7 +328,6 @@ STR_EMBEDDED_STYLE: "Embedded Style"
|
|||||||
STR_FOCUS_READING: "Focus Reading"
|
STR_FOCUS_READING: "Focus Reading"
|
||||||
STR_OPDS_SERVER_URL: "OPDS Server URL"
|
STR_OPDS_SERVER_URL: "OPDS Server URL"
|
||||||
STR_PWR_BTN_FOOTNOTE_BACK: "Quick-return from footnotes"
|
STR_PWR_BTN_FOOTNOTE_BACK: "Quick-return from footnotes"
|
||||||
STR_BACK_SHORT_TO_FILE_BROWSER: "Short Back to File Browser"
|
|
||||||
STR_SET_SLEEP_COVER: "Set Cover"
|
STR_SET_SLEEP_COVER: "Set Cover"
|
||||||
STR_FOOTNOTES: "Footnotes"
|
STR_FOOTNOTES: "Footnotes"
|
||||||
STR_NO_FOOTNOTES: "No footnotes on this page"
|
STR_NO_FOOTNOTES: "No footnotes on this page"
|
||||||
@@ -370,12 +342,6 @@ STR_SERVER_NAME: "Server Name"
|
|||||||
STR_NO_SERVERS: "No OPDS servers configured"
|
STR_NO_SERVERS: "No OPDS servers configured"
|
||||||
STR_DELETE_SERVER: "Delete Server"
|
STR_DELETE_SERVER: "Delete Server"
|
||||||
STR_OPDS_SERVERS: "OPDS Servers"
|
STR_OPDS_SERVERS: "OPDS Servers"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Download folder"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Filename format"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Author - Title"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Title - Author"
|
|
||||||
STR_FMT_TITLE: "Title"
|
|
||||||
STR_OPDS_SD_ROOT: "SD root"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Auto Turn Enabled: "
|
STR_AUTO_TURN_ENABLED: "Auto Turn Enabled: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)"
|
||||||
STR_MANAGE_FONTS: "Manage Fonts"
|
STR_MANAGE_FONTS: "Manage Fonts"
|
||||||
|
|||||||
@@ -65,10 +65,11 @@ STR_SLEEP_COVER_MODE: "Lepotilanäytön kansitila"
|
|||||||
STR_HIDE_BATTERY: "Piilota akun %"
|
STR_HIDE_BATTERY: "Piilota akun %"
|
||||||
STR_EXTRA_SPACING: "Kappaleiden lisäväli"
|
STR_EXTRA_SPACING: "Kappaleiden lisäväli"
|
||||||
STR_TEXT_AA: "Tekstin reunanpehmennys"
|
STR_TEXT_AA: "Tekstin reunanpehmennys"
|
||||||
|
STR_TEXT_AA_FULL: "Täysi"
|
||||||
|
STR_TEXT_AA_FAST: "Nopea"
|
||||||
STR_SHORT_PWR_BTN: "Lyhyt virtapainikkeen painallus"
|
STR_SHORT_PWR_BTN: "Lyhyt virtapainikkeen painallus"
|
||||||
STR_ORIENTATION: "Lukusuunta"
|
STR_ORIENTATION: "Lukusuunta"
|
||||||
STR_SIDE_BTN_LAYOUT: "Sivupainikkeiden asettelu (lukija)"
|
STR_SIDE_BTN_LAYOUT: "Sivupainikkeiden asettelu (lukija)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Lukijan kosketusohjaus"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Suuntaa etupainikkeet"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Suuntaa etupainikkeet"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||||
@@ -93,7 +94,6 @@ STR_USERNAME: "Käyttäjänimi"
|
|||||||
STR_PASSWORD: "Salasana"
|
STR_PASSWORD: "Salasana"
|
||||||
STR_SYNC_SERVER_URL: "Synkronointipalvelimen osoite"
|
STR_SYNC_SERVER_URL: "Synkronointipalvelimen osoite"
|
||||||
STR_DOCUMENT_MATCHING: "Dokumenttien tunnistus"
|
STR_DOCUMENT_MATCHING: "Dokumenttien tunnistus"
|
||||||
STR_SEND_METADATA: "Lähetä asiakirjan metatiedot"
|
|
||||||
STR_AUTHENTICATE: "Tunnistaudu"
|
STR_AUTHENTICATE: "Tunnistaudu"
|
||||||
STR_KOREADER_USERNAME: "KOReader-käyttäjänimi"
|
STR_KOREADER_USERNAME: "KOReader-käyttäjänimi"
|
||||||
STR_KOREADER_PASSWORD: "KOReader-salasana"
|
STR_KOREADER_PASSWORD: "KOReader-salasana"
|
||||||
@@ -269,16 +269,9 @@ STR_BOOK_S_STYLE: "Kirjan tyyli"
|
|||||||
STR_EMBEDDED_STYLE: "Upotettu tyyli"
|
STR_EMBEDDED_STYLE: "Upotettu tyyli"
|
||||||
STR_FOCUS_READING: "Keskittynyt lukeminen"
|
STR_FOCUS_READING: "Keskittynyt lukeminen"
|
||||||
STR_OPDS_SERVER_URL: "OPDS-palvelimen osoite"
|
STR_OPDS_SERVER_URL: "OPDS-palvelimen osoite"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Latauskansio"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Tiedostonimen muoto"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Tekijä - Nimi"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Nimi - Tekijä"
|
|
||||||
STR_FMT_TITLE: "Nimi"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Ota kuvakaappaus"
|
STR_SCREENSHOT_BUTTON: "Ota kuvakaappaus"
|
||||||
STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
||||||
STR_SLEEP_NEVER: "Ei koskaan"
|
STR_SLEEP_NEVER: "Ei koskaan"
|
||||||
STR_STEP_HINT_FRONT: "Etupainikkeet:"
|
STR_STEP_HINT_FRONT: "Etupainikkeet:"
|
||||||
STR_STEP_HINT_SIDE: "Sivupainikkeet:"
|
STR_STEP_HINT_SIDE: "Sivupainikkeet:"
|
||||||
STR_TILT_PAGE_TURN: "Sivunkääntö kallistamalla"
|
STR_TILT_PAGE_TURN: "Sivunkääntö kallistamalla"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Lisää piilotettu verkko..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Anna verkon nimi (SSID)"
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ STR_SLEEP_COVER_MODE: "Mode écran de veille"
|
|||||||
STR_HIDE_BATTERY: "Masquer % batterie"
|
STR_HIDE_BATTERY: "Masquer % batterie"
|
||||||
STR_EXTRA_SPACING: "Espacement paragraphes"
|
STR_EXTRA_SPACING: "Espacement paragraphes"
|
||||||
STR_TEXT_AA: "Lissage du texte"
|
STR_TEXT_AA: "Lissage du texte"
|
||||||
|
STR_TEXT_AA_FULL: "Complet"
|
||||||
|
STR_TEXT_AA_FAST: "Rapide"
|
||||||
STR_IMAGES: "Images"
|
STR_IMAGES: "Images"
|
||||||
STR_IMAGES_DISPLAY: "Affichage"
|
STR_IMAGES_DISPLAY: "Affichage"
|
||||||
STR_IMAGES_PLACEHOLDER: "Espace réservé"
|
STR_IMAGES_PLACEHOLDER: "Espace réservé"
|
||||||
@@ -72,7 +74,6 @@ STR_IMAGES_SUPPRESS: "Masquer"
|
|||||||
STR_SHORT_PWR_BTN: "Appui court alim."
|
STR_SHORT_PWR_BTN: "Appui court alim."
|
||||||
STR_ORIENTATION: "Orientation de lecture"
|
STR_ORIENTATION: "Orientation de lecture"
|
||||||
STR_SIDE_BTN_LAYOUT: "Boutons latéraux"
|
STR_SIDE_BTN_LAYOUT: "Boutons latéraux"
|
||||||
STR_TOUCH_READER_CONTROLS: "Commandes tactiles lecteur"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orienter boutons avant"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orienter boutons avant"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Comportement lors d'un appui long"
|
STR_LONG_PRESS_BEHAVIOR: "Comportement lors d'un appui long"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Désactivé"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Désactivé"
|
||||||
@@ -98,7 +99,6 @@ STR_USERNAME: "Nom d’utilisateur"
|
|||||||
STR_PASSWORD: "Mot de passe"
|
STR_PASSWORD: "Mot de passe"
|
||||||
STR_SYNC_SERVER_URL: "URL du serveur"
|
STR_SYNC_SERVER_URL: "URL du serveur"
|
||||||
STR_DOCUMENT_MATCHING: "Correspondance"
|
STR_DOCUMENT_MATCHING: "Correspondance"
|
||||||
STR_SEND_METADATA: "Envoyer métadonnées"
|
|
||||||
STR_AUTHENTICATE: "Connexion"
|
STR_AUTHENTICATE: "Connexion"
|
||||||
STR_KOREADER_USERNAME: "Utilisateur"
|
STR_KOREADER_USERNAME: "Utilisateur"
|
||||||
STR_KOREADER_PASSWORD: "Mot de passe"
|
STR_KOREADER_PASSWORD: "Mot de passe"
|
||||||
@@ -302,14 +302,7 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
|||||||
STR_SLEEP_NEVER: "Jamais"
|
STR_SLEEP_NEVER: "Jamais"
|
||||||
STR_STEP_HINT_FRONT: "Boutons avant :"
|
STR_STEP_HINT_FRONT: "Boutons avant :"
|
||||||
STR_STEP_HINT_SIDE: "Boutons latéraux :"
|
STR_STEP_HINT_SIDE: "Boutons latéraux :"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Dossier de téléchargement"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Format du nom de fichier"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Auteur - Titre"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Titre - Auteur"
|
|
||||||
STR_FMT_TITLE: "Titre"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Capture d'écran"
|
STR_SCREENSHOT_BUTTON: "Capture d'écran"
|
||||||
STR_AUTO_TURN_ENABLED: "Tourne-page auto : "
|
STR_AUTO_TURN_ENABLED: "Tourne-page auto : "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Tourne-page auto (pages par minute)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Tourne-page auto (pages par minute)"
|
||||||
STR_TILT_PAGE_TURN: "Tourner par inclinaison"
|
STR_TILT_PAGE_TURN: "Tourner par inclinaison"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Ajouter un réseau masqué..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Saisir le nom du réseau (SSID)"
|
|
||||||
|
|||||||
@@ -65,10 +65,11 @@ STR_SLEEP_COVER_MODE: "Standby-Bildmodus"
|
|||||||
STR_HIDE_BATTERY: "Batterie % ausblenden"
|
STR_HIDE_BATTERY: "Batterie % ausblenden"
|
||||||
STR_EXTRA_SPACING: "Absatzabstand"
|
STR_EXTRA_SPACING: "Absatzabstand"
|
||||||
STR_TEXT_AA: "Schriftglättung"
|
STR_TEXT_AA: "Schriftglättung"
|
||||||
|
STR_TEXT_AA_FULL: "Voll"
|
||||||
|
STR_TEXT_AA_FAST: "Schnell"
|
||||||
STR_SHORT_PWR_BTN: "An-Taste kurz drücken"
|
STR_SHORT_PWR_BTN: "An-Taste kurz drücken"
|
||||||
STR_ORIENTATION: "Leseausrichtung"
|
STR_ORIENTATION: "Leseausrichtung"
|
||||||
STR_SIDE_BTN_LAYOUT: "Seitliche Tasten (Lesen)"
|
STR_SIDE_BTN_LAYOUT: "Seitliche Tasten (Lesen)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Touch-Steuerung beim Lesen"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Vordere Tasten ausrichten"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Vordere Tasten ausrichten"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Verhalten bei langem Tastendruck"
|
STR_LONG_PRESS_BEHAVIOR: "Verhalten bei langem Tastendruck"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Aus"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Aus"
|
||||||
@@ -92,7 +93,6 @@ STR_USERNAME: "Benutzername"
|
|||||||
STR_PASSWORD: "Passwort"
|
STR_PASSWORD: "Passwort"
|
||||||
STR_SYNC_SERVER_URL: "Sync-Server-URL"
|
STR_SYNC_SERVER_URL: "Sync-Server-URL"
|
||||||
STR_DOCUMENT_MATCHING: "Dateizuordnung"
|
STR_DOCUMENT_MATCHING: "Dateizuordnung"
|
||||||
STR_SEND_METADATA: "Metadaten senden"
|
|
||||||
STR_AUTHENTICATE: "Authentifizieren"
|
STR_AUTHENTICATE: "Authentifizieren"
|
||||||
STR_KOREADER_USERNAME: "KOReader-Benutzername"
|
STR_KOREADER_USERNAME: "KOReader-Benutzername"
|
||||||
STR_KOREADER_PASSWORD: "KOReader-Passwort"
|
STR_KOREADER_PASSWORD: "KOReader-Passwort"
|
||||||
@@ -316,7 +316,6 @@ STR_BOOK_S_STYLE: "Buch-Stil"
|
|||||||
STR_EMBEDDED_STYLE: "Eingebetteter Stil"
|
STR_EMBEDDED_STYLE: "Eingebetteter Stil"
|
||||||
STR_FOCUS_READING: "Fokus-Lesen"
|
STR_FOCUS_READING: "Fokus-Lesen"
|
||||||
STR_OPDS_SERVER_URL: "OPDS-Server-URL"
|
STR_OPDS_SERVER_URL: "OPDS-Server-URL"
|
||||||
STR_BACK_SHORT_TO_FILE_BROWSER: "Kurz zurück drücken zum Datei-Browser"
|
|
||||||
STR_SET_SLEEP_COVER: "Wähle Cover"
|
STR_SET_SLEEP_COVER: "Wähle Cover"
|
||||||
STR_SCREENSHOT_BUTTON: "Screenshot aufnehmen"
|
STR_SCREENSHOT_BUTTON: "Screenshot aufnehmen"
|
||||||
STR_FOOTNOTES: "Fußnoten"
|
STR_FOOTNOTES: "Fußnoten"
|
||||||
@@ -331,11 +330,6 @@ STR_SERVER_NAME: "Servername"
|
|||||||
STR_NO_SERVERS: "Keine OPDS-Server konfiguriert"
|
STR_NO_SERVERS: "Keine OPDS-Server konfiguriert"
|
||||||
STR_DELETE_SERVER: "Server entfernen"
|
STR_DELETE_SERVER: "Server entfernen"
|
||||||
STR_OPDS_SERVERS: "OPDS-Server"
|
STR_OPDS_SERVERS: "OPDS-Server"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Download-Ordner"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Dateinamenformat"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autor - Titel"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Titel - Autor"
|
|
||||||
STR_FMT_TITLE: "Titel"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Auto-Umblättern aktiv: "
|
STR_AUTO_TURN_ENABLED: "Auto-Umblättern aktiv: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Auto-Umblättern (Seiten/Min.)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Auto-Umblättern (Seiten/Min.)"
|
||||||
STR_IMAGES: "Bilder"
|
STR_IMAGES: "Bilder"
|
||||||
@@ -388,5 +382,3 @@ STR_FIRMWARE_WRITE_FAILED: "Schreiben der Firmware-Datei ist fehlgeschlagen"
|
|||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Nicht ausschalten!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Nicht ausschalten!"
|
||||||
STR_RECOVERY_MODE: "Wiederherstellungsmodus"
|
STR_RECOVERY_MODE: "Wiederherstellungsmodus"
|
||||||
STR_RECOVERY_MODE_HINT: "Lege firmware.bin im SD-Kartenwurzelverzeichnis ab und wähle es aus"
|
STR_RECOVERY_MODE_HINT: "Lege firmware.bin im SD-Kartenwurzelverzeichnis ab und wähle es aus"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Verstecktes Netzwerk hinzufügen..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Netzwerknamen eingeben (SSID)"
|
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ STR_SLEEP_COVER_MODE: "תצוגת כריכה במצב שינה"
|
|||||||
STR_HIDE_BATTERY: "הסתר אחוזי סוללה"
|
STR_HIDE_BATTERY: "הסתר אחוזי סוללה"
|
||||||
STR_EXTRA_SPACING: "מרווח פסקאות מוגדל"
|
STR_EXTRA_SPACING: "מרווח פסקאות מוגדל"
|
||||||
STR_TEXT_AA: "החלקת קצוות טקסט"
|
STR_TEXT_AA: "החלקת קצוות טקסט"
|
||||||
|
STR_TEXT_AA_FULL: "מלאה"
|
||||||
|
STR_TEXT_AA_FAST: "מהירה"
|
||||||
STR_IMAGES: "תמונות"
|
STR_IMAGES: "תמונות"
|
||||||
STR_IMAGES_DISPLAY: "הצג תמונות"
|
STR_IMAGES_DISPLAY: "הצג תמונות"
|
||||||
STR_IMAGES_PLACEHOLDER: "סמן"
|
STR_IMAGES_PLACEHOLDER: "סמן"
|
||||||
@@ -73,7 +75,6 @@ STR_IMAGES_SUPPRESS: "הסתר תמונות"
|
|||||||
STR_SHORT_PWR_BTN: "לחיצה קצרה על כפתור ההפעלה"
|
STR_SHORT_PWR_BTN: "לחיצה קצרה על כפתור ההפעלה"
|
||||||
STR_ORIENTATION: "כיוון קריאה (מסך)"
|
STR_ORIENTATION: "כיוון קריאה (מסך)"
|
||||||
STR_SIDE_BTN_LAYOUT: "פריסת כפתורי צד (בקריאה)"
|
STR_SIDE_BTN_LAYOUT: "פריסת כפתורי צד (בקריאה)"
|
||||||
STR_TOUCH_READER_CONTROLS: "שליטה במגע (קורא)"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR: "פעולת לחיצה ארוכה"
|
STR_LONG_PRESS_BEHAVIOR: "פעולת לחיצה ארוכה"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "כבוי"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "כבוי"
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "דלג פרק"
|
STR_LONG_PRESS_BEHAVIOR_SKIP: "דלג פרק"
|
||||||
@@ -96,7 +97,6 @@ STR_USERNAME: "שם משתמש"
|
|||||||
STR_PASSWORD: "סיסמה"
|
STR_PASSWORD: "סיסמה"
|
||||||
STR_SYNC_SERVER_URL: "כתובת שרת סנכרון"
|
STR_SYNC_SERVER_URL: "כתובת שרת סנכרון"
|
||||||
STR_DOCUMENT_MATCHING: "התאמת מסמכים"
|
STR_DOCUMENT_MATCHING: "התאמת מסמכים"
|
||||||
STR_SEND_METADATA: "שלח מטא-נתוני מסמך"
|
|
||||||
STR_AUTHENTICATE: "התחבר"
|
STR_AUTHENTICATE: "התחבר"
|
||||||
STR_KOREADER_USERNAME: "שם משתמש ב-KOReader"
|
STR_KOREADER_USERNAME: "שם משתמש ב-KOReader"
|
||||||
STR_KOREADER_PASSWORD: "סיסמת KOReader"
|
STR_KOREADER_PASSWORD: "סיסמת KOReader"
|
||||||
@@ -307,11 +307,6 @@ STR_NO_SERVERS: "לא הוגדרו שרתי OPDS"
|
|||||||
STR_DELETE_SERVER: "מחק שרת"
|
STR_DELETE_SERVER: "מחק שרת"
|
||||||
STR_DELETE_CONFIRM: "למחוק שרת זה?"
|
STR_DELETE_CONFIRM: "למחוק שרת זה?"
|
||||||
STR_OPDS_SERVERS: "שרתי OPDS"
|
STR_OPDS_SERVERS: "שרתי OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "תיקיית הורדות"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "תבנית שם קובץ"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "מחבר - כותרת"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "כותרת - מחבר"
|
|
||||||
STR_FMT_TITLE: "כותרת"
|
|
||||||
STR_AUTO_TURN_ENABLED: "דפדוף אוטומטי פועל: "
|
STR_AUTO_TURN_ENABLED: "דפדוף אוטומטי פועל: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "דפדוף אוטומטי (דפים בדקה)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "דפדוף אוטומטי (דפים בדקה)"
|
||||||
STR_MANAGE_FONTS: "ניהול גופנים"
|
STR_MANAGE_FONTS: "ניהול גופנים"
|
||||||
@@ -398,5 +393,3 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u דקות"
|
|||||||
STR_SLEEP_NEVER: "אף פעם"
|
STR_SLEEP_NEVER: "אף פעם"
|
||||||
STR_STEP_HINT_FRONT: "לחצנים קדמיים:"
|
STR_STEP_HINT_FRONT: "לחצנים קדמיים:"
|
||||||
STR_STEP_HINT_SIDE: "לחצני צד:"
|
STR_STEP_HINT_SIDE: "לחצני צד:"
|
||||||
STR_ADD_HIDDEN_NETWORK: "הוסף רשת מוסתרת..."
|
|
||||||
STR_ENTER_WIFI_SSID: "הזן שם רשת (SSID)"
|
|
||||||
|
|||||||
@@ -49,8 +49,6 @@ STR_NETWORK_LEGEND: "* = Titkosított | + = Mentett"
|
|||||||
STR_MAC_ADDRESS: "MAC-cím:"
|
STR_MAC_ADDRESS: "MAC-cím:"
|
||||||
STR_CHECKING_WIFI: "Wi-Fi ellenőrzése..."
|
STR_CHECKING_WIFI: "Wi-Fi ellenőrzése..."
|
||||||
STR_ENTER_WIFI_PASSWORD: "Add meg a Wi-Fi jelszót"
|
STR_ENTER_WIFI_PASSWORD: "Add meg a Wi-Fi jelszót"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Rejtett hálózat hozzáadása..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Add meg a hálózat nevét (SSID)"
|
|
||||||
STR_TO_PREFIX: "- "
|
STR_TO_PREFIX: "- "
|
||||||
STR_CALIBRE_RECEIVING: "Fogadás: "
|
STR_CALIBRE_RECEIVING: "Fogadás: "
|
||||||
STR_CALIBRE_RECEIVED: "Fogadva: "
|
STR_CALIBRE_RECEIVED: "Fogadva: "
|
||||||
@@ -67,6 +65,8 @@ STR_SLEEP_COVER_MODE: "Alvásképernyő borítómód"
|
|||||||
STR_HIDE_BATTERY: "Akkumulátor % elrejtése"
|
STR_HIDE_BATTERY: "Akkumulátor % elrejtése"
|
||||||
STR_EXTRA_SPACING: "Extra bekezdésköz"
|
STR_EXTRA_SPACING: "Extra bekezdésköz"
|
||||||
STR_TEXT_AA: "Szöveg élsimítás"
|
STR_TEXT_AA: "Szöveg élsimítás"
|
||||||
|
STR_TEXT_AA_FULL: "Teljes"
|
||||||
|
STR_TEXT_AA_FAST: "Gyors"
|
||||||
STR_IMAGES: "Képek"
|
STR_IMAGES: "Képek"
|
||||||
STR_IMAGES_DISPLAY: "Megjelenítés"
|
STR_IMAGES_DISPLAY: "Megjelenítés"
|
||||||
STR_IMAGES_PLACEHOLDER: "Helyőrző"
|
STR_IMAGES_PLACEHOLDER: "Helyőrző"
|
||||||
@@ -74,7 +74,6 @@ STR_IMAGES_SUPPRESS: "Elnyomás"
|
|||||||
STR_SHORT_PWR_BTN: "Rövid bekapcsológomb nyomás"
|
STR_SHORT_PWR_BTN: "Rövid bekapcsológomb nyomás"
|
||||||
STR_ORIENTATION: "Olvasási irány"
|
STR_ORIENTATION: "Olvasási irány"
|
||||||
STR_SIDE_BTN_LAYOUT: "Oldalsó gomb elrendezés (olvasó)"
|
STR_SIDE_BTN_LAYOUT: "Oldalsó gomb elrendezés (olvasó)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Érintős olvasóvezérlés"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Elülső gombok tájolása"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Elülső gombok tájolása"
|
||||||
STR_LONG_PRESS_SKIP: "Hosszú nyomás - fejezet ugrás"
|
STR_LONG_PRESS_SKIP: "Hosszú nyomás - fejezet ugrás"
|
||||||
STR_FONT_PREVIEW_TEXT: "Egy hűtlen vejét fülöncsípő, dühös mexikói úr Wesselényinél mázol Quitóban"
|
STR_FONT_PREVIEW_TEXT: "Egy hűtlen vejét fülöncsípő, dühös mexikói úr Wesselényinél mázol Quitóban"
|
||||||
@@ -97,7 +96,6 @@ STR_USERNAME: "Felhasználónév"
|
|||||||
STR_PASSWORD: "Jelszó"
|
STR_PASSWORD: "Jelszó"
|
||||||
STR_SYNC_SERVER_URL: "Szinkronizálás szerver URL"
|
STR_SYNC_SERVER_URL: "Szinkronizálás szerver URL"
|
||||||
STR_DOCUMENT_MATCHING: "Dokumentum egyeztetés"
|
STR_DOCUMENT_MATCHING: "Dokumentum egyeztetés"
|
||||||
STR_SEND_METADATA: "Dokumentum metaadatok küldése"
|
|
||||||
STR_AUTHENTICATE: "Hitelesítés"
|
STR_AUTHENTICATE: "Hitelesítés"
|
||||||
STR_KOREADER_USERNAME: "KOReader felhasználónév"
|
STR_KOREADER_USERNAME: "KOReader felhasználónév"
|
||||||
STR_KOREADER_PASSWORD: "KOReader jelszó"
|
STR_KOREADER_PASSWORD: "KOReader jelszó"
|
||||||
@@ -300,102 +298,7 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u perc"
|
|||||||
STR_SLEEP_NEVER: "Soha"
|
STR_SLEEP_NEVER: "Soha"
|
||||||
STR_STEP_HINT_FRONT: "Elülső gombok:"
|
STR_STEP_HINT_FRONT: "Elülső gombok:"
|
||||||
STR_STEP_HINT_SIDE: "Oldalsó gombok:"
|
STR_STEP_HINT_SIDE: "Oldalsó gombok:"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Letöltési mappa"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Fájlnév formátuma"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Szerző - Cím"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Cím - Szerző"
|
|
||||||
STR_FMT_TITLE: "Cím"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Képernyőkép készítése"
|
STR_SCREENSHOT_BUTTON: "Képernyőkép készítése"
|
||||||
STR_AUTO_TURN_ENABLED: "Automatikus lapozás bekapcsolva: "
|
STR_AUTO_TURN_ENABLED: "Automatikus lapozás bekapcsolva: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Automatikus lapozás (oldal/perc)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Automatikus lapozás (oldal/perc)"
|
||||||
STR_TILT_PAGE_TURN: "Döntéses lapozás"
|
STR_TILT_PAGE_TURN: "Döntéses lapozás"
|
||||||
STR_ADD_SERVER: "Szerver hozzáadása"
|
|
||||||
STR_BOOKMARKS: "Könyvjelzők"
|
|
||||||
STR_BOOKMARK_ADDED: "Könyvjelző hozzáadva."
|
|
||||||
STR_BOOKMARK_OPTION: "Könyvjelző"
|
|
||||||
STR_BOTTOM: "Alul"
|
|
||||||
STR_CLOCK: "Óra"
|
|
||||||
STR_CLOCK_FORMAT: "Óraformátum"
|
|
||||||
STR_CLOCK_FORMAT_12H: "12 órás"
|
|
||||||
STR_CLOCK_FORMAT_24H: "24 órás"
|
|
||||||
STR_CLOCK_SYNC: "Óra szinkronizálása"
|
|
||||||
STR_CLOCK_SYNCED: "Óra szinkronizálva"
|
|
||||||
STR_CLOCK_SYNCING: "Szinkronizálás NTP-ről..."
|
|
||||||
STR_CLOCK_SYNC_FAIL: "Szinkronizálás sikertelen"
|
|
||||||
STR_CLOCK_SYNC_NOW: "Óra szinkronizálása most"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI: "Wi-Fi nincs csatlakoztatva"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI_HINT: "Először csatlakozz Wi-Fi-hez, majd próbáld újra."
|
|
||||||
STR_CLOCK_SYNC_OK: "Óra szinkronizálva"
|
|
||||||
STR_CLOCK_UTC_OFFSET: "Óra UTC eltolás"
|
|
||||||
STR_CONFIRM_DELETE_BOOKMARK: "Törlöd ezt a könyvjelzőt?"
|
|
||||||
STR_CONNECTING_SAVED_WIFI: "Csatlakozás mentett Wi-Fi-hez..."
|
|
||||||
STR_CRASH_DESCRIPTION: "A részletes jelentés a crash_report.txt fájlba lett mentve. Kérjük, csatold ezt a fájlt a hibajelentéshez."
|
|
||||||
STR_CRASH_NO_REASON: "(Nem került rögzítésre ok)"
|
|
||||||
STR_CRASH_REASON: "Összeomlás oka:"
|
|
||||||
STR_CRASH_TITLE: "Rendszerösszeomlás"
|
|
||||||
STR_CURRENT_TIME: "Aktuális idő:"
|
|
||||||
STR_DELETE_SERVER: "Szerver törlése"
|
|
||||||
STR_DISABLED: "Letiltva"
|
|
||||||
STR_DOWNLOAD_ALL: "Összes letöltése"
|
|
||||||
STR_EOB_CONTINUE_WITH: "Folytatás ezzel"
|
|
||||||
STR_EOB_HOME: "Kezdőképernyő"
|
|
||||||
STR_FINDING_SAVED_WIFI: "Mentett Wi-Fi keresése..."
|
|
||||||
STR_FIRMWARE_FILE_OPEN_FAILED: "A fájl nem nyitható meg"
|
|
||||||
STR_FIRMWARE_TOO_LARGE: "A firmware túl nagy a partícióhoz"
|
|
||||||
STR_FIRMWARE_TOO_SMALL: "A firmware fájl túl kicsi"
|
|
||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Ne kapcsold ki!"
|
|
||||||
STR_FIRMWARE_UPDATE_PROMPT: "Frissíted a firmware-t?"
|
|
||||||
STR_FIRMWARE_WRITE_FAILED: "A firmware írása sikertelen"
|
|
||||||
STR_FONT_BROWSER: "Betűtípus-böngésző"
|
|
||||||
STR_FONT_INSTALLED: "Betűtípus telepítve!"
|
|
||||||
STR_FONT_INSTALL_FAILED: "A betűtípus telepítése sikertelen"
|
|
||||||
STR_FORCE_REFRESH: "Képernyő frissítése"
|
|
||||||
STR_INDEX_FAILED: "Indexelés sikertelen – érvénytelen könyv"
|
|
||||||
STR_INSTALLED: "Telepítve"
|
|
||||||
STR_INVALID_FIRMWARE: "Érvénytelen firmware fájl"
|
|
||||||
STR_KB_HINT_CLEAR_TEXT: "Tartsd nyomva a DEL-t az összes szöveg törléséhez"
|
|
||||||
STR_KB_HINT_EDIT_ENTRY: "Tartsd nyomva a FEL-t a bejegyzés szerkesztéséhez"
|
|
||||||
STR_KB_HINT_EXIT_URL_MODE: "Nyomd meg az ABC-t az URL mód elhagyásához"
|
|
||||||
STR_KB_HINT_HIDE_PASSWORD: "Tartsd nyomva a JOBBRA-t, majd nyomd meg a [***]-ot a jelszó elrejtéséhez"
|
|
||||||
STR_KB_HINT_LOWER_SECONDARY: "Tartsd nyomva a SELECT-et kisbetűért vagy másodlagos karakterért"
|
|
||||||
STR_KB_HINT_MOVE_CURSOR: "Nyomd meg a BALRA vagy JOBBRA gombot a kurzor mozgatásához"
|
|
||||||
STR_KB_HINT_RETURN_CURSOR: "Nyomd meg a BALRA-t a kurzorpozícióhoz való visszatéréshez"
|
|
||||||
STR_KB_HINT_RETURN_KEYBOARD: "Nyomd meg a LE-t a billentyűzethez való visszatéréshez"
|
|
||||||
STR_KB_HINT_SECONDARY_CHAR: "Tartsd nyomva a SELECT-et másodlagos karakterért"
|
|
||||||
STR_KB_HINT_SHOW_PASSWORD: "Tartsd nyomva a JOBBRA-t, majd nyomd meg az [abc]-t a jelszó megjelenítéséhez"
|
|
||||||
STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Nyomd meg a [***]-ot a jelszó elrejtéséhez"
|
|
||||||
STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Nyomd meg az [abc]-t a jelszó megjelenítéséhez"
|
|
||||||
STR_KB_HINT_UPPER_SECONDARY: "Tartsd nyomva a SELECT-et NAGYBETŰÉRT vagy másodlagos karakterért"
|
|
||||||
STR_KB_HINT_URL_SNIPPETS: "Nyomd meg az URL-t az URL-részletekért"
|
|
||||||
STR_KB_TIPS: "Tippek:"
|
|
||||||
STR_KOSYNC: "KOSync"
|
|
||||||
STR_LOADING_FONT_LIST: "Betűtípuslista betöltése..."
|
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Hosszú gombnyomás viselkedése"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "KI"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Tájolásváltás"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "Fejezetugrás"
|
|
||||||
STR_LONG_PRESS_MENU: "Hosszú nyomás – Menü"
|
|
||||||
STR_MANAGE_FONTS: "Betűtípusok kezelése"
|
|
||||||
STR_NEXT_FIELD: "Következő"
|
|
||||||
STR_NEXT_PAGE: "Következő oldal »"
|
|
||||||
STR_NO_BIN_FILES: "Nincs .bin fájl"
|
|
||||||
STR_NO_FONTS_AVAILABLE: "Nincs elérhető betűtípus"
|
|
||||||
STR_NO_SERVERS: "Nincs beállított OPDS szerver"
|
|
||||||
STR_OPDS_SERVERS: "OPDS szerverek"
|
|
||||||
STR_PREV_PAGE: "« Előző oldal"
|
|
||||||
STR_PWR_BTN_FOOTNOTE_BACK: "Gyors visszatérés lábjegyzetből"
|
|
||||||
STR_RECOVERY_MODE: "Helyreállítási mód"
|
|
||||||
STR_RECOVERY_MODE_HINT: "Helyezd a firmware.bin fájlt az SD-kártya gyökerébe, majd válaszd ki"
|
|
||||||
STR_RESTARTING_HINT: "Újraindítás... Ha az eszköz nem indul újra, tartsd nyomva a bekapcsológombot néhány másodpercig."
|
|
||||||
STR_SD_FIRMWARE_UPDATE: "Firmware frissítés SD-kártyáról"
|
|
||||||
STR_SEARCH: "Keresés"
|
|
||||||
STR_SELECT_FIRMWARE_FILE: "Válassz firmware fájlt (.bin)"
|
|
||||||
STR_SERVER_NAME: "Szerver neve"
|
|
||||||
STR_SET_SLEEP_COVER: "Borító beállítása"
|
|
||||||
STR_SHOW_NETWORKS: "Megjelenítés"
|
|
||||||
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
|
|
||||||
STR_TOP: "Felül"
|
|
||||||
STR_UPDATE_ALL: "Összes frissítése"
|
|
||||||
STR_UPDATE_AVAILABLE: "Frissítés"
|
|
||||||
STR_VALIDATING_FIRMWARE: "Firmware ellenőrzése..."
|
|
||||||
STR_XTC_STATUS_BAR: "XTC állapotsáv"
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ STR_SLEEP_COVER_MODE: "Modalità copertina"
|
|||||||
STR_HIDE_BATTERY: "Nascondi % batteria"
|
STR_HIDE_BATTERY: "Nascondi % batteria"
|
||||||
STR_EXTRA_SPACING: "Spaziatura extra paragrafi"
|
STR_EXTRA_SPACING: "Spaziatura extra paragrafi"
|
||||||
STR_TEXT_AA: "Anti-aliasing testo"
|
STR_TEXT_AA: "Anti-aliasing testo"
|
||||||
|
STR_TEXT_AA_FULL: "Completo"
|
||||||
|
STR_TEXT_AA_FAST: "Veloce"
|
||||||
STR_IMAGES: "Immagini"
|
STR_IMAGES: "Immagini"
|
||||||
STR_IMAGES_DISPLAY: "Visualizza"
|
STR_IMAGES_DISPLAY: "Visualizza"
|
||||||
STR_IMAGES_PLACEHOLDER: "Segnaposto"
|
STR_IMAGES_PLACEHOLDER: "Segnaposto"
|
||||||
@@ -72,7 +74,6 @@ STR_IMAGES_SUPPRESS: "Nascondi"
|
|||||||
STR_SHORT_PWR_BTN: "Press. breve pul. accensione"
|
STR_SHORT_PWR_BTN: "Press. breve pul. accensione"
|
||||||
STR_ORIENTATION: "Orientamento lettura"
|
STR_ORIENTATION: "Orientamento lettura"
|
||||||
STR_SIDE_BTN_LAYOUT: "Pul. laterali (lettore)"
|
STR_SIDE_BTN_LAYOUT: "Pul. laterali (lettore)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Controlli touch lettore"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orienta pul. frontali"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orienta pul. frontali"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Press. lunga pul. laterali"
|
STR_LONG_PRESS_BEHAVIOR: "Press. lunga pul. laterali"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||||
@@ -99,7 +100,6 @@ STR_USERNAME: "Nome utente"
|
|||||||
STR_PASSWORD: "Password"
|
STR_PASSWORD: "Password"
|
||||||
STR_SYNC_SERVER_URL: "URL server di sincronizzazione"
|
STR_SYNC_SERVER_URL: "URL server di sincronizzazione"
|
||||||
STR_DOCUMENT_MATCHING: "Corrispondenza documenti"
|
STR_DOCUMENT_MATCHING: "Corrispondenza documenti"
|
||||||
STR_SEND_METADATA: "Invia metadati documento"
|
|
||||||
STR_AUTHENTICATE: "Autentica"
|
STR_AUTHENTICATE: "Autentica"
|
||||||
STR_KOREADER_USERNAME: "Nome utente KOReader"
|
STR_KOREADER_USERNAME: "Nome utente KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Password KOReader"
|
STR_KOREADER_PASSWORD: "Password KOReader"
|
||||||
@@ -316,11 +316,6 @@ STR_SERVER_NAME: "Nome server"
|
|||||||
STR_NO_SERVERS: "Nessun server OPDS configurato"
|
STR_NO_SERVERS: "Nessun server OPDS configurato"
|
||||||
STR_DELETE_SERVER: "Elimina server"
|
STR_DELETE_SERVER: "Elimina server"
|
||||||
STR_OPDS_SERVERS: "Server OPDS"
|
STR_OPDS_SERVERS: "Server OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Cartella download"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Formato nome file"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autore - Titolo"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Titolo - Autore"
|
|
||||||
STR_FMT_TITLE: "Titolo"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Volta pagina automatico: "
|
STR_AUTO_TURN_ENABLED: "Volta pagina automatico: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Volta pagina automatico (pag/min)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Volta pagina automatico (pag/min)"
|
||||||
STR_MANAGE_FONTS: "Gestisci font"
|
STR_MANAGE_FONTS: "Gestisci font"
|
||||||
@@ -390,8 +385,3 @@ STR_DISABLED: "Disattivato"
|
|||||||
STR_BOOKMARK_OPTION: "Segnalibro"
|
STR_BOOKMARK_OPTION: "Segnalibro"
|
||||||
STR_KOSYNC: "KOSync"
|
STR_KOSYNC: "KOSync"
|
||||||
STR_PWR_BTN_FOOTNOTE_BACK: "Rientro rapido dalle note"
|
STR_PWR_BTN_FOOTNOTE_BACK: "Rientro rapido dalle note"
|
||||||
STR_EOB_CONTINUE_WITH: "Continua con"
|
|
||||||
STR_EOB_HOME: "Home"
|
|
||||||
STR_INDEX_FAILED: "Indicizzazione fallita - libro non valido"
|
|
||||||
STR_ADD_HIDDEN_NETWORK: "Aggiungi rete nascosta..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Inserisci il nome della rete (SSID)"
|
|
||||||
|
|||||||
@@ -64,10 +64,11 @@ STR_SLEEP_COVER_MODE: "Ұйқы экраны мұқаба режимі"
|
|||||||
STR_HIDE_BATTERY: "Батарея % жасыру"
|
STR_HIDE_BATTERY: "Батарея % жасыру"
|
||||||
STR_EXTRA_SPACING: "Қосымша абзац аралығы"
|
STR_EXTRA_SPACING: "Қосымша абзац аралығы"
|
||||||
STR_TEXT_AA: "Мәтін сырғытпасы"
|
STR_TEXT_AA: "Мәтін сырғытпасы"
|
||||||
|
STR_TEXT_AA_FULL: "Толық"
|
||||||
|
STR_TEXT_AA_FAST: "Жылдам"
|
||||||
STR_SHORT_PWR_BTN: "Қуат түймесін қысқа басу"
|
STR_SHORT_PWR_BTN: "Қуат түймесін қысқа басу"
|
||||||
STR_ORIENTATION: "Оқу бағдары"
|
STR_ORIENTATION: "Оқу бағдары"
|
||||||
STR_SIDE_BTN_LAYOUT: "Бүйірлік түймелер орналасуы (оқырман)"
|
STR_SIDE_BTN_LAYOUT: "Бүйірлік түймелер орналасуы (оқырман)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Оқырманның сенсорлық басқаруы"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Алдыңғы түймелерді бағдарлау"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Алдыңғы түймелерді бағдарлау"
|
||||||
STR_LONG_PRESS_SKIP: "Ұзақ басу арқылы тарау өткізу"
|
STR_LONG_PRESS_SKIP: "Ұзақ басу арқылы тарау өткізу"
|
||||||
STR_FONT_PREVIEW_TEXT: "Канагаттандырылмагандыктарыныздан"
|
STR_FONT_PREVIEW_TEXT: "Канагаттандырылмагандыктарыныздан"
|
||||||
@@ -89,7 +90,6 @@ STR_USERNAME: "Пайдаланушы аты"
|
|||||||
STR_PASSWORD: "Құпия сөз"
|
STR_PASSWORD: "Құпия сөз"
|
||||||
STR_SYNC_SERVER_URL: "Синхрондау сервері URL"
|
STR_SYNC_SERVER_URL: "Синхрондау сервері URL"
|
||||||
STR_DOCUMENT_MATCHING: "Құжат сәйкестендіру"
|
STR_DOCUMENT_MATCHING: "Құжат сәйкестендіру"
|
||||||
STR_SEND_METADATA: "Құжат метадеректерін жіберу"
|
|
||||||
STR_AUTHENTICATE: "Аутентификация"
|
STR_AUTHENTICATE: "Аутентификация"
|
||||||
STR_KOREADER_USERNAME: "KOReader пайдаланушы аты"
|
STR_KOREADER_USERNAME: "KOReader пайдаланушы аты"
|
||||||
STR_KOREADER_PASSWORD: "KOReader құпия сөзі"
|
STR_KOREADER_PASSWORD: "KOReader құпия сөзі"
|
||||||
@@ -297,14 +297,7 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u мин"
|
|||||||
STR_SLEEP_NEVER: "Ешқашан"
|
STR_SLEEP_NEVER: "Ешқашан"
|
||||||
STR_STEP_HINT_FRONT: "Алдыңғы түймелер:"
|
STR_STEP_HINT_FRONT: "Алдыңғы түймелер:"
|
||||||
STR_STEP_HINT_SIDE: "Бүйір түймелері:"
|
STR_STEP_HINT_SIDE: "Бүйір түймелері:"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Жүктеп алу қалтасы"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Файл атауының пішімі"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Автор - Атауы"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Атауы - Автор"
|
|
||||||
STR_FMT_TITLE: "Атауы"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Скриншот түсіру"
|
STR_SCREENSHOT_BUTTON: "Скриншот түсіру"
|
||||||
STR_AUTO_TURN_ENABLED: "Автоматты бет аудару қосулы: "
|
STR_AUTO_TURN_ENABLED: "Автоматты бет аудару қосулы: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Автоматты бет аудару (минутына бет саны)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Автоматты бет аудару (минутына бет саны)"
|
||||||
STR_TILT_PAGE_TURN: "Еңкейту арқылы бет аудару"
|
STR_TILT_PAGE_TURN: "Еңкейту арқылы бет аудару"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Жасырын желіні қосу..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Желі атауын енгізіңіз (SSID)"
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ STR_SLEEP_COVER_MODE: "Viršelio režimas"
|
|||||||
STR_HIDE_BATTERY: "Slėpti baterijos %"
|
STR_HIDE_BATTERY: "Slėpti baterijos %"
|
||||||
STR_EXTRA_SPACING: "Tarpai tarp pastraipų"
|
STR_EXTRA_SPACING: "Tarpai tarp pastraipų"
|
||||||
STR_TEXT_AA: "Teksto glotninimas"
|
STR_TEXT_AA: "Teksto glotninimas"
|
||||||
|
STR_TEXT_AA_FULL: "Pilnas"
|
||||||
|
STR_TEXT_AA_FAST: "Greitas"
|
||||||
STR_IMAGES: "Paveikslėliai"
|
STR_IMAGES: "Paveikslėliai"
|
||||||
STR_IMAGES_DISPLAY: "Rodyti"
|
STR_IMAGES_DISPLAY: "Rodyti"
|
||||||
STR_IMAGES_PLACEHOLDER: "Vietaženklis"
|
STR_IMAGES_PLACEHOLDER: "Vietaženklis"
|
||||||
@@ -72,7 +74,6 @@ STR_IMAGES_SUPPRESS: "Slėpti"
|
|||||||
STR_SHORT_PWR_BTN: "Trumpas įjungimo pasp."
|
STR_SHORT_PWR_BTN: "Trumpas įjungimo pasp."
|
||||||
STR_ORIENTATION: "Orientacija"
|
STR_ORIENTATION: "Orientacija"
|
||||||
STR_SIDE_BTN_LAYOUT: "Šoniniai mygtukai"
|
STR_SIDE_BTN_LAYOUT: "Šoniniai mygtukai"
|
||||||
STR_TOUCH_READER_CONTROLS: "Lietimo valdikliai skaityklėje"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientuoti priekinius mygtukus"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientuoti priekinius mygtukus"
|
||||||
STR_LONG_PRESS_SKIP: "Praleisti skyrių (ilgai)"
|
STR_LONG_PRESS_SKIP: "Praleisti skyrių (ilgai)"
|
||||||
STR_FONT_PREVIEW_TEXT: "Įlinkdama fechtuotojo špaga sublykčiojusi pragręžė apvalų arbūzą"
|
STR_FONT_PREVIEW_TEXT: "Įlinkdama fechtuotojo špaga sublykčiojusi pragręžė apvalų arbūzą"
|
||||||
@@ -95,7 +96,6 @@ STR_USERNAME: "Vartotojas"
|
|||||||
STR_PASSWORD: "Slaptažodis"
|
STR_PASSWORD: "Slaptažodis"
|
||||||
STR_SYNC_SERVER_URL: "Serverio URL"
|
STR_SYNC_SERVER_URL: "Serverio URL"
|
||||||
STR_DOCUMENT_MATCHING: "Atpažinimas"
|
STR_DOCUMENT_MATCHING: "Atpažinimas"
|
||||||
STR_SEND_METADATA: "Siųsti dokumento metaduomenis"
|
|
||||||
STR_AUTHENTICATE: "Prisijungti"
|
STR_AUTHENTICATE: "Prisijungti"
|
||||||
STR_KOREADER_USERNAME: "KOReader vartotojas"
|
STR_KOREADER_USERNAME: "KOReader vartotojas"
|
||||||
STR_KOREADER_PASSWORD: "KOReader slaptažodis"
|
STR_KOREADER_PASSWORD: "KOReader slaptažodis"
|
||||||
@@ -298,14 +298,7 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u min."
|
|||||||
STR_SLEEP_NEVER: "Niekada"
|
STR_SLEEP_NEVER: "Niekada"
|
||||||
STR_STEP_HINT_FRONT: "Priekiniai mygtukai:"
|
STR_STEP_HINT_FRONT: "Priekiniai mygtukai:"
|
||||||
STR_STEP_HINT_SIDE: "Šoniniai mygtukai:"
|
STR_STEP_HINT_SIDE: "Šoniniai mygtukai:"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Atsisiuntimų aplankas"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Failo pavadinimo formatas"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autorius - Pavadinimas"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Pavadinimas - Autorius"
|
|
||||||
STR_FMT_TITLE: "Pavadinimas"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Ekrano nuotrauka"
|
STR_SCREENSHOT_BUTTON: "Ekrano nuotrauka"
|
||||||
STR_AUTO_TURN_ENABLED: "Auto-vertimas: "
|
STR_AUTO_TURN_ENABLED: "Auto-vertimas: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Auto-vertimas (psl/min)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Auto-vertimas (psl/min)"
|
||||||
STR_TILT_PAGE_TURN: "Puslapio vertimas pakreipiant"
|
STR_TILT_PAGE_TURN: "Puslapio vertimas pakreipiant"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Pridėti paslėptą tinklą..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Įveskite tinklo pavadinimą (SSID)"
|
|
||||||
|
|||||||
@@ -1,385 +0,0 @@
|
|||||||
_language_name: "Norsk bokmål"
|
|
||||||
_language_code: "NB"
|
|
||||||
_order: "26"
|
|
||||||
|
|
||||||
STR_CROSSPOINT: "CrossPoint"
|
|
||||||
STR_BOOTING: "STARTER"
|
|
||||||
STR_SLEEPING: "HVILER"
|
|
||||||
STR_ENTERING_SLEEP: "Går i hvilemodus"
|
|
||||||
STR_BROWSE_FILES: "Bla i filer"
|
|
||||||
STR_FILE_TRANSFER: "Filoverføring"
|
|
||||||
STR_SETTINGS_TITLE: "Innstillinger"
|
|
||||||
STR_CONTINUE_READING: "Fortsett å lese"
|
|
||||||
STR_NO_OPEN_BOOK: "Ingen åpen bok"
|
|
||||||
STR_START_READING: "Start å lese nedenfor"
|
|
||||||
STR_NO_FILES_FOUND: "Ingen filer funnet"
|
|
||||||
STR_SELECT_CHAPTER: "Velg kapittel"
|
|
||||||
STR_NO_CHAPTERS: "Ingen kapitler"
|
|
||||||
STR_END_OF_BOOK: "Slutten av boken"
|
|
||||||
STR_EMPTY_CHAPTER: "Tomt kapittel"
|
|
||||||
STR_INDEXING: "Indekserer"
|
|
||||||
STR_MEMORY_ERROR: "Minnefeil"
|
|
||||||
STR_PAGE_LOAD_ERROR: "Feil ved sidelasting"
|
|
||||||
STR_EMPTY_FILE: "Tom fil"
|
|
||||||
STR_OUT_OF_BOUNDS: "Utenfor område"
|
|
||||||
STR_LOADING: "Laster..."
|
|
||||||
STR_LOADING_POPUP: "Laster"
|
|
||||||
STR_WIFI_NETWORKS: "WiFi-nettverk"
|
|
||||||
STR_NO_NETWORKS: "Ingen nettverk funnet"
|
|
||||||
STR_NETWORKS_FOUND: "%zu nettverk funnet"
|
|
||||||
STR_SCANNING: "Skanner..."
|
|
||||||
STR_CONNECTING: "Kobler til..."
|
|
||||||
STR_CONNECTED: "Tilkoblet!"
|
|
||||||
STR_CONNECTION_FAILED: "Tilkobling mislyktes"
|
|
||||||
STR_FORGET_NETWORK: "Glem nettverk?"
|
|
||||||
STR_SAVE_PASSWORD: "Lagre passord til neste gang?"
|
|
||||||
STR_PRESS_OK_SCAN: "Trykk OK for å skanne på nytt"
|
|
||||||
STR_JOIN_NETWORK: "Koble til et nettverk"
|
|
||||||
STR_CREATE_HOTSPOT: "Opprett hotspot"
|
|
||||||
STR_JOIN_DESC: "Koble til et eksisterende WiFi-nettverk"
|
|
||||||
STR_HOTSPOT_DESC: "Opprett et WiFi-nettverk andre kan koble seg til"
|
|
||||||
STR_STARTING_HOTSPOT: "Starter hotspot..."
|
|
||||||
STR_HOTSPOT_MODE: "Hotspot-modus"
|
|
||||||
STR_CONNECT_WIFI_HINT: "Koble enheten din til dette WiFi-nettverket"
|
|
||||||
STR_OPEN_URL_HINT: "Åpne denne URL-en i nettleseren din"
|
|
||||||
STR_OR_HTTP_PREFIX: "eller http://"
|
|
||||||
STR_SCAN_QR_HINT: "eller skann QR-kode med telefonen din:"
|
|
||||||
STR_CALIBRE_WIRELESS: "Calibre Wireless"
|
|
||||||
STR_NETWORK_LEGEND: "* = Kryptert | + = Lagret"
|
|
||||||
STR_MAC_ADDRESS: "MAC-adresse:"
|
|
||||||
STR_CHECKING_WIFI: "Sjekker WiFi..."
|
|
||||||
STR_ENTER_WIFI_PASSWORD: "Skriv inn WiFi-passord"
|
|
||||||
STR_TO_PREFIX: "til "
|
|
||||||
STR_CALIBRE_RECEIVING: "Mottar: "
|
|
||||||
STR_CALIBRE_RECEIVED: "Mottatt: "
|
|
||||||
STR_CALIBRE_INSTRUCTION_1: "1) Installer CrossPoint Reader-plugin"
|
|
||||||
STR_CALIBRE_INSTRUCTION_2: "2) Vær på samme WiFi-nettverk"
|
|
||||||
STR_CALIBRE_INSTRUCTION_3: "3) I Calibre: \"Send til enhet\""
|
|
||||||
STR_CALIBRE_INSTRUCTION_4: "\"Hold denne skjermen åpen mens du sender\""
|
|
||||||
STR_CAT_DISPLAY: "Skjerm"
|
|
||||||
STR_CAT_READER: "Leser"
|
|
||||||
STR_CAT_CONTROLS: "Betjening"
|
|
||||||
STR_CAT_SYSTEM: "System"
|
|
||||||
STR_SLEEP_SCREEN: "Hvileskjerm"
|
|
||||||
STR_QUICK_RESUME_TIMEOUT: "Hurtig gjenopptak ved tidsavbrudd"
|
|
||||||
STR_SLEEP_COVER_MODE: "Omslagsmodus for hvileskjerm"
|
|
||||||
STR_HIDE_BATTERY: "Skjul batteri %"
|
|
||||||
STR_EXTRA_SPACING: "Ekstra avsnittsavstand"
|
|
||||||
STR_TEXT_AA: "Tekstutjevning"
|
|
||||||
STR_IMAGES: "Bilder"
|
|
||||||
STR_IMAGES_DISPLAY: "Vis"
|
|
||||||
STR_IMAGES_PLACEHOLDER: "Plassholder"
|
|
||||||
STR_IMAGES_SUPPRESS: "Skjul"
|
|
||||||
STR_SHORT_PWR_BTN: "Kort trykk på av/på-knapp"
|
|
||||||
STR_ORIENTATION: "Leseretning"
|
|
||||||
STR_SIDE_BTN_LAYOUT: "Sideknapp-oppsett (leser)"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orienter frontknapper"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Atferd ved langt trykk"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "AV"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "Kapittelhopp"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Endre orientering"
|
|
||||||
STR_LONG_PRESS_MENU: "Langt trykk på Meny"
|
|
||||||
STR_FONT_PREVIEW_TEXT: "Høvdingens kjære squaw får litt pizza i Mexico by"
|
|
||||||
STR_FONT_FAMILY: "Skrifttype i leser"
|
|
||||||
STR_FONT_SIZE: "Skriftstørrelse i leser"
|
|
||||||
STR_LINE_SPACING: "Linjeavstand i leser"
|
|
||||||
STR_SCREEN_MARGIN: "Skjermmarg i leser"
|
|
||||||
STR_PARA_ALIGNMENT: "Avsnittsjustering i leser"
|
|
||||||
STR_HYPHENATION: "Orddeling"
|
|
||||||
STR_TIME_TO_SLEEP: "Tid før hvile"
|
|
||||||
STR_SHOW_HIDDEN_FILES: "Vis skjulte filer"
|
|
||||||
STR_REMOVE_READ_FROM_RECENTS: "Fjern leste bøker fra Nylig-listen"
|
|
||||||
STR_MOVE_FINISHED_TO_READ: "Flytt fullførte bøker til Read-mappen"
|
|
||||||
STR_REFRESH_FREQ: "Oppdateringsfrekvens"
|
|
||||||
STR_KOREADER_SYNC: "KOReader Sync"
|
|
||||||
STR_CHECK_UPDATES: "Se etter oppdateringer"
|
|
||||||
STR_LANGUAGE: "Språk"
|
|
||||||
STR_CLEAR_READING_CACHE: "Tøm lesebuffer"
|
|
||||||
STR_USERNAME: "Brukernavn"
|
|
||||||
STR_PASSWORD: "Passord"
|
|
||||||
STR_SYNC_SERVER_URL: "URL til synk-server"
|
|
||||||
STR_DOCUMENT_MATCHING: "Dokumentgjenkjenning"
|
|
||||||
STR_AUTHENTICATE: "Godkjenn"
|
|
||||||
STR_KOREADER_USERNAME: "KOReader-brukernavn"
|
|
||||||
STR_KOREADER_PASSWORD: "KOReader-passord"
|
|
||||||
STR_FILENAME: "Filnavn"
|
|
||||||
STR_BINARY: "Binær"
|
|
||||||
STR_SET_CREDENTIALS_FIRST: "Angi påloggingsinfo først"
|
|
||||||
STR_WIFI_CONN_FAILED: "WiFi-tilkobling mislyktes"
|
|
||||||
STR_AUTHENTICATING: "Godkjenner..."
|
|
||||||
STR_AUTH_SUCCESS: "Godkjenning vellykket!"
|
|
||||||
STR_KOREADER_AUTH: "KOReader-godkjenning"
|
|
||||||
STR_SYNC_READY: "KOReader-synk er klar til bruk"
|
|
||||||
STR_AUTH_FAILED: "Godkjenning mislyktes"
|
|
||||||
STR_DONE: "Ferdig"
|
|
||||||
STR_CLEAR_CACHE_WARNING_1: "Dette tømmer alle bufrede bokdata."
|
|
||||||
STR_CLEAR_CACHE_WARNING_2: "All lesefremdrift går tapt!"
|
|
||||||
STR_CLEAR_CACHE_WARNING_3: "Bøker må indekseres på nytt"
|
|
||||||
STR_CLEAR_CACHE_WARNING_4: "når de åpnes igjen."
|
|
||||||
STR_CLEARING_CACHE: "Tømmer buffer..."
|
|
||||||
STR_CACHE_CLEARED: "Buffer tømt"
|
|
||||||
STR_ITEMS_REMOVED: "elementer fjernet"
|
|
||||||
STR_FAILED_LOWER: "mislyktes"
|
|
||||||
STR_CLEAR_CACHE_FAILED: "Kunne ikke tømme buffer"
|
|
||||||
STR_CHECK_SERIAL_OUTPUT: "Sjekk seriell utdata for detaljer"
|
|
||||||
STR_DARK: "Mørk"
|
|
||||||
STR_LIGHT: "Lys"
|
|
||||||
STR_CUSTOM: "Egendefinert"
|
|
||||||
STR_COVER: "Omslag"
|
|
||||||
STR_NONE_OPT: "Ingen"
|
|
||||||
STR_FIT: "Tilpass"
|
|
||||||
STR_CROP: "Beskjær"
|
|
||||||
STR_NEVER: "Aldri"
|
|
||||||
STR_IN_READER: "I leseren"
|
|
||||||
STR_ALWAYS: "Alltid"
|
|
||||||
STR_IGNORE: "Ignorer"
|
|
||||||
STR_SLEEP: "Hvile"
|
|
||||||
STR_PAGE_TURN: "Bla om"
|
|
||||||
STR_FORCE_REFRESH: "Oppdater skjerm"
|
|
||||||
STR_PORTRAIT: "Stående"
|
|
||||||
STR_LANDSCAPE_CW: "Liggende med klokken"
|
|
||||||
STR_INVERTED: "Opp ned"
|
|
||||||
STR_ORIENTATION_INVERTED: "Portrett 180°"
|
|
||||||
STR_LANDSCAPE_CCW: "Liggende mot klokken"
|
|
||||||
STR_PREV_NEXT: "Forrige/Neste"
|
|
||||||
STR_NEXT_PREV: "Neste/Forrige"
|
|
||||||
STR_KOSYNC: "KOSync"
|
|
||||||
STR_BOOKMARK_OPTION: "Bokmerke"
|
|
||||||
STR_DISABLED: "Deaktivert"
|
|
||||||
STR_NOTO_SERIF: "Noto Serif"
|
|
||||||
STR_NOTO_SANS: "Noto Sans"
|
|
||||||
STR_SMALL: "Liten"
|
|
||||||
STR_MEDIUM: "Middels"
|
|
||||||
STR_LARGE: "Stor"
|
|
||||||
STR_X_LARGE: "Ekstra stor"
|
|
||||||
STR_TIGHT: "Tett"
|
|
||||||
STR_NORMAL: "Normal"
|
|
||||||
STR_WIDE: "Bred"
|
|
||||||
STR_JUSTIFY: "Blokkjustert"
|
|
||||||
STR_ALIGN_LEFT: "Venstre"
|
|
||||||
STR_CENTER: "Midtstilt"
|
|
||||||
STR_ALIGN_RIGHT: "Høyre"
|
|
||||||
STR_PAGES_1: "1 side"
|
|
||||||
STR_PAGES_5: "5 sider"
|
|
||||||
STR_PAGES_10: "10 sider"
|
|
||||||
STR_PAGES_15: "15 sider"
|
|
||||||
STR_PAGES_30: "30 sider"
|
|
||||||
STR_UPDATE: "Oppdater"
|
|
||||||
STR_CHECKING_UPDATE: "Ser etter oppdatering..."
|
|
||||||
STR_NEW_UPDATE: "Ny oppdatering tilgjengelig!"
|
|
||||||
STR_CURRENT_VERSION: "Nåværende versjon: "
|
|
||||||
STR_NEW_VERSION: "Ny versjon: "
|
|
||||||
STR_UPDATING: "Oppdaterer..."
|
|
||||||
STR_NO_UPDATE: "Ingen oppdatering tilgjengelig"
|
|
||||||
STR_UPDATE_FAILED: "Oppdatering mislyktes"
|
|
||||||
STR_UPDATE_COMPLETE: "Oppdatering fullført"
|
|
||||||
STR_POWER_ON_HINT: "Trykk og hold av/på-knappen for å slå på igjen"
|
|
||||||
STR_RESTARTING_HINT: "Starter på nytt... Hvis enheten ikke starter, hold av/på-knappen i noen sekunder."
|
|
||||||
STR_NO_ENTRIES: "Ingen oppføringer funnet"
|
|
||||||
STR_DOWNLOADING: "Laster ned..."
|
|
||||||
STR_DOWNLOAD_FAILED: "Nedlasting mislyktes"
|
|
||||||
STR_ERROR_MSG: "Feil:"
|
|
||||||
STR_UNNAMED: "Uten navn"
|
|
||||||
STR_HOLD_OPEN_TO_DELETE: "Hold Åpne for å slette"
|
|
||||||
STR_NO_SERVER_URL: "Ingen server-URL konfigurert"
|
|
||||||
STR_FETCH_FEED_FAILED: "Kunne ikke hente feed"
|
|
||||||
STR_PARSE_FEED_FAILED: "Kunne ikke tolke feed"
|
|
||||||
STR_NEXT_PAGE: "Neste side »"
|
|
||||||
STR_PREV_PAGE: "« Forrige side"
|
|
||||||
STR_NETWORK_PREFIX: "Nettverk: "
|
|
||||||
STR_IP_ADDRESS_PREFIX: "IP-adresse: "
|
|
||||||
STR_ERROR_GENERAL_FAILURE: "Feil: Generell feil"
|
|
||||||
STR_ERROR_NETWORK_NOT_FOUND: "Feil: Nettverk ikke funnet"
|
|
||||||
STR_ERROR_CONNECTION_TIMEOUT: "Feil: Tidsavbrudd for tilkobling"
|
|
||||||
STR_SD_CARD: "SD-kort"
|
|
||||||
STR_BACK: "« Tilbake"
|
|
||||||
STR_EXIT: "« Avslutt"
|
|
||||||
STR_HOME: "« Hjem"
|
|
||||||
STR_SELECT: "Velg"
|
|
||||||
STR_SELECTED: "Valgt"
|
|
||||||
STR_TOGGLE: "Veksle"
|
|
||||||
STR_TOGGLE_BOOKMARK: "Veksle bokmerke"
|
|
||||||
STR_CONFIRM: "Bekreft"
|
|
||||||
STR_CANCEL: "Avbryt"
|
|
||||||
STR_CONNECT: "Koble til"
|
|
||||||
STR_OPEN: "Åpne"
|
|
||||||
STR_DOWNLOAD: "Last ned"
|
|
||||||
STR_RETRY: "Prøv igjen"
|
|
||||||
STR_YES: "Ja"
|
|
||||||
STR_NO: "Nei"
|
|
||||||
STR_SHOW: "Vis"
|
|
||||||
STR_HIDE: "Skjul"
|
|
||||||
STR_STATE_ON: "PÅ"
|
|
||||||
STR_STATE_OFF: "AV"
|
|
||||||
STR_NOT_SET: "Ikke angitt"
|
|
||||||
STR_DIR_LEFT: "Venstre"
|
|
||||||
STR_DIR_RIGHT: "Høyre"
|
|
||||||
STR_DIR_UP: "Opp"
|
|
||||||
STR_DIR_DOWN: "Ned"
|
|
||||||
STR_OK_BUTTON: "OK"
|
|
||||||
STR_SLEEP_COVER_FILTER: "Omslagsfilter for hvileskjerm"
|
|
||||||
STR_FILTER_CONTRAST: "Kontrast"
|
|
||||||
STR_CUSTOMISE_STATUS_BAR: "Tilpass statuslinje"
|
|
||||||
STR_CHAPTER_PAGE_COUNT: "Sideantall i kapittel"
|
|
||||||
STR_BOOK_PROGRESS_PERCENTAGE: "Bokfremdrift i prosent"
|
|
||||||
STR_PROGRESS_BAR: "Fremdriftslinje"
|
|
||||||
STR_PROGRESS_BAR_THICKNESS: "Tykkelse på fremdriftslinje"
|
|
||||||
STR_PROGRESS_BAR_THIN: "Tynn"
|
|
||||||
STR_PROGRESS_BAR_MEDIUM: "Middels"
|
|
||||||
STR_PROGRESS_BAR_THICK: "Tykk"
|
|
||||||
STR_BOOK: "Bok"
|
|
||||||
STR_CHAPTER: "Kapittel"
|
|
||||||
STR_EXAMPLE_CHAPTER: "Kapittel 21"
|
|
||||||
STR_EXAMPLE_BOOK: "Boktittel"
|
|
||||||
STR_PREVIEW: "Forhåndsvisning"
|
|
||||||
STR_TITLE: "Tittel"
|
|
||||||
STR_BATTERY: "Batteri"
|
|
||||||
STR_XTC_STATUS_BAR: "XTC-statuslinje"
|
|
||||||
STR_BOTTOM: "Nederst"
|
|
||||||
STR_TOP: "Øverst"
|
|
||||||
STR_CLOCK: "Klokke"
|
|
||||||
STR_CLOCK_UTC_OFFSET: "UTC-forskyvning for klokke"
|
|
||||||
STR_CLOCK_FORMAT: "Klokkeformat"
|
|
||||||
STR_CLOCK_FORMAT_24H: "24-timers"
|
|
||||||
STR_CLOCK_FORMAT_12H: "12-timers"
|
|
||||||
STR_CURRENT_TIME: "Nåværende tid:"
|
|
||||||
STR_NEXT_FIELD: "Neste"
|
|
||||||
STR_CLOCK_SYNC: "Synkroniser klokke"
|
|
||||||
STR_CLOCK_SYNC_NOW: "Synkroniser klokke nå"
|
|
||||||
STR_CLOCK_SYNCING: "Synkroniserer fra NTP..."
|
|
||||||
STR_CLOCK_SYNC_OK: "Klokke synkronisert"
|
|
||||||
STR_CLOCK_SYNC_FAIL: "Synkronisering mislyktes"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI: "WiFi ikke tilkoblet"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI_HINT: "Koble til WiFi først, og prøv igjen."
|
|
||||||
STR_CLOCK_SYNCED: "Klokke synkronisert"
|
|
||||||
STR_UI_THEME: "Grensesnittstema"
|
|
||||||
STR_THEME_CLASSIC: "Klassisk"
|
|
||||||
STR_THEME_LYRA: "Lyra"
|
|
||||||
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
|
|
||||||
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
|
||||||
STR_SUNLIGHT_FADING_FIX: "Retting for soluttoning"
|
|
||||||
STR_REMAP_FRONT_BUTTONS: "Tilordne frontknapper på nytt"
|
|
||||||
STR_BOOKMARKS: "Bokmerker"
|
|
||||||
STR_BOOKMARK_ADDED: "Bokmerke lagt til."
|
|
||||||
STR_BOOKMARK_REMOVED: "Bokmerke fjernet."
|
|
||||||
STR_OPDS_BROWSER: "OPDS-leser"
|
|
||||||
STR_SEARCH: "Søk"
|
|
||||||
STR_COVER_CUSTOM: "Omslag + Egendefinert"
|
|
||||||
STR_QUICK_RESUME: "Hurtig gjenopptak"
|
|
||||||
STR_MENU_RECENT_BOOKS: "Nylige bøker"
|
|
||||||
STR_REMOVE_FROM_RECENTS: "Fjern fra Nylige bøker?"
|
|
||||||
STR_NO_RECENT_BOOKS: "Ingen nylige bøker"
|
|
||||||
STR_CALIBRE_DESC: "Bruk trådløs enhetsoverføring med Calibre"
|
|
||||||
STR_FORGET_AND_REMOVE: "Glem nettverk og fjern lagret passord?"
|
|
||||||
STR_FORGET_BUTTON: "Glem"
|
|
||||||
STR_CALIBRE_STARTING: "Starter Calibre..."
|
|
||||||
STR_CALIBRE_SETUP: "Oppsett"
|
|
||||||
STR_CALIBRE_STATUS: "Status"
|
|
||||||
STR_CLEAR_BUTTON: "Tøm"
|
|
||||||
STR_DEFAULT_VALUE: "Standard"
|
|
||||||
STR_REMAP_PROMPT: "Trykk en frontknapp for hver rolle"
|
|
||||||
STR_UNASSIGNED: "Ikke tilordnet"
|
|
||||||
STR_ALREADY_ASSIGNED: "Allerede tilordnet"
|
|
||||||
STR_REMAP_RESET_HINT: "Sideknapp opp: Nullstill til standardoppsett"
|
|
||||||
STR_REMAP_CANCEL_HINT: "Sideknapp ned: Avbryt omtilordning"
|
|
||||||
STR_HW_BACK_LABEL: "Tilbake (1. knapp)"
|
|
||||||
STR_HW_CONFIRM_LABEL: "Bekreft (2. knapp)"
|
|
||||||
STR_HW_LEFT_LABEL: "Venstre (3. knapp)"
|
|
||||||
STR_HW_RIGHT_LABEL: "Høyre (4. knapp)"
|
|
||||||
STR_GO_TO_PERCENT: "Gå til %"
|
|
||||||
STR_GO_HOME_BUTTON: "Hjem"
|
|
||||||
STR_SYNC_PROGRESS: "Synkroniser fremdrift"
|
|
||||||
STR_DELETE_CACHE: "Slett bokbuffer"
|
|
||||||
STR_DELETE: "Slett"
|
|
||||||
STR_CONFIRM_DELETE_BOOKMARK: "Slette dette bokmerket?"
|
|
||||||
STR_DISPLAY_QR: "Vis side som QR"
|
|
||||||
STR_CHAPTER_PREFIX: "Kapittel: "
|
|
||||||
STR_PAGES_SEPARATOR: " sider | "
|
|
||||||
STR_BOOK_PREFIX: "Bok: "
|
|
||||||
STR_CALIBRE_URL_HINT: "For Calibre, legg til /opds i URL-en din"
|
|
||||||
STR_PERCENT_STEP_HINT: "Venstre/Høyre: 1% Opp/Ned: 10%"
|
|
||||||
STR_SYNCING_TIME: "Synkroniserer tid..."
|
|
||||||
STR_CALC_HASH: "Beregner dokument-hash..."
|
|
||||||
STR_HASH_FAILED: "Kunne ikke beregne dokument-hash"
|
|
||||||
STR_FETCH_PROGRESS: "Henter ekstern fremdrift..."
|
|
||||||
STR_UPLOAD_PROGRESS: "Laster opp fremdrift..."
|
|
||||||
STR_NO_CREDENTIALS_MSG: "Ingen påloggingsinfo angitt"
|
|
||||||
STR_KOREADER_SETUP_HINT: "Sett opp KOReader-konto i Innstillinger"
|
|
||||||
STR_PROGRESS_FOUND: "Fremdrift funnet!"
|
|
||||||
STR_REMOTE_LABEL: "Ekstern:"
|
|
||||||
STR_LOCAL_LABEL: "Lokal:"
|
|
||||||
STR_PAGE_OVERALL_FORMAT: "Side %d, %.2f%% totalt"
|
|
||||||
STR_PAGE_TOTAL_OVERALL_FORMAT: "Side %d/%d, %.2f%% totalt"
|
|
||||||
STR_DEVICE_FROM_FORMAT: " Fra: %s"
|
|
||||||
STR_APPLY_REMOTE: "Bruk ekstern fremdrift"
|
|
||||||
STR_UPLOAD_LOCAL: "Last opp lokal fremdrift"
|
|
||||||
STR_NO_REMOTE_MSG: "Ingen ekstern fremdrift funnet"
|
|
||||||
STR_UPLOAD_PROMPT: "Last opp nåværende posisjon?"
|
|
||||||
STR_UPLOAD_SUCCESS: "Fremdrift lastet opp!"
|
|
||||||
STR_SYNC_FAILED_MSG: "Synkronisering mislyktes"
|
|
||||||
STR_SAVE_PROGRESS_FAILED: "Kunne ikke lagre fremdrift"
|
|
||||||
STR_SECTION_PREFIX: "Del "
|
|
||||||
STR_UPLOAD: "Last opp"
|
|
||||||
STR_BOOK_S_STYLE: "Bokens stil"
|
|
||||||
STR_EMBEDDED_STYLE: "Innebygd stil"
|
|
||||||
STR_FOCUS_READING: "Fokuslesing"
|
|
||||||
STR_OPDS_SERVER_URL: "OPDS-server-URL"
|
|
||||||
STR_PWR_BTN_FOOTNOTE_BACK: "Hurtig retur fra fotnoter"
|
|
||||||
STR_SET_SLEEP_COVER: "Velg omslag"
|
|
||||||
STR_FOOTNOTES: "Fotnoter"
|
|
||||||
STR_NO_FOOTNOTES: "Ingen fotnoter på denne siden"
|
|
||||||
STR_LINK: "[lenke]"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Ta skjermbilde"
|
|
||||||
STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
|
||||||
STR_SLEEP_NEVER: "Aldri"
|
|
||||||
STR_SLEEP_TIMER_STEP_HINT: "Venstre/Høyre: 1 min Opp/Ned: 5 min"
|
|
||||||
STR_ADD_SERVER: "Legg til server"
|
|
||||||
STR_SERVER_NAME: "Servernavn"
|
|
||||||
STR_NO_SERVERS: "Ingen OPDS-servere konfigurert"
|
|
||||||
STR_DELETE_SERVER: "Slett server"
|
|
||||||
STR_OPDS_SERVERS: "OPDS-servere"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Automatisk bla aktivert: "
|
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Automatisk bla (sider per minutt)"
|
|
||||||
STR_MANAGE_FONTS: "Administrer skrifttyper"
|
|
||||||
STR_FONT_BROWSER: "Bla i skrifttyper"
|
|
||||||
STR_LOADING_FONT_LIST: "Laster skrifttypeliste..."
|
|
||||||
STR_NO_FONTS_AVAILABLE: "Ingen skrifttyper tilgjengelig"
|
|
||||||
STR_FONT_INSTALLED: "Skrifttype installert!"
|
|
||||||
STR_FONT_INSTALL_FAILED: "Installasjon av skrifttype mislyktes"
|
|
||||||
STR_INSTALLED: "Installert"
|
|
||||||
STR_DOWNLOAD_ALL: "Last ned alle"
|
|
||||||
STR_UPDATE_ALL: "Oppdater alle"
|
|
||||||
STR_UPDATE_AVAILABLE: "Oppdater"
|
|
||||||
STR_CRASH_TITLE: "Systemkrasj"
|
|
||||||
STR_CRASH_DESCRIPTION: "En detaljert rapport ble lagret i crash_report.txt. Legg ved denne filen i feilrapporten din."
|
|
||||||
STR_CRASH_REASON: "Krasjårsak:"
|
|
||||||
STR_CRASH_NO_REASON: "(Ingen årsak ble registrert)"
|
|
||||||
STR_TILT_PAGE_TURN: "Vipp for å bla"
|
|
||||||
STR_KB_HINT_MOVE_CURSOR: "Trykk VENSTRE eller HØYRE for å flytte markøren"
|
|
||||||
STR_KB_HINT_RETURN_CURSOR: "Trykk VENSTRE for å gå tilbake til markørposisjonen"
|
|
||||||
STR_KB_HINT_HIDE_PASSWORD: "Hold HØYRE og trykk [***] for å skjule passord"
|
|
||||||
STR_KB_HINT_SHOW_PASSWORD: "Hold HØYRE og trykk [abc] for å vise passord"
|
|
||||||
STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Trykk [***] for å skjule passord"
|
|
||||||
STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Trykk [abc] for å vise passord"
|
|
||||||
STR_KB_HINT_EDIT_ENTRY: "Hold OPP for å redigere oppføring"
|
|
||||||
STR_KB_TIPS: "Tips:"
|
|
||||||
STR_KB_HINT_RETURN_KEYBOARD: "Trykk NED for å gå tilbake til tastaturet"
|
|
||||||
STR_KB_HINT_EXIT_URL_MODE: "Trykk ABC for å avslutte URL-modus"
|
|
||||||
STR_KB_HINT_CLEAR_TEXT: "Hold DEL for å slette all tekst"
|
|
||||||
STR_KB_HINT_SECONDARY_CHAR: "Hold VELG for sekundærtegn"
|
|
||||||
STR_KB_HINT_UPPER_SECONDARY: "Hold VELG for STORE BOKSTAVER eller sekundærtegn"
|
|
||||||
STR_KB_HINT_LOWER_SECONDARY: "Hold VELG for små bokstaver eller sekundærtegn"
|
|
||||||
STR_KB_HINT_URL_SNIPPETS: "Trykk URL for tekstbiter"
|
|
||||||
STR_SD_FIRMWARE_UPDATE: "Fastvareoppdatering fra SD-kort"
|
|
||||||
STR_SELECT_FIRMWARE_FILE: "Velg fastvarefil (.bin)"
|
|
||||||
STR_NO_BIN_FILES: "Ingen .bin-filer funnet"
|
|
||||||
STR_VALIDATING_FIRMWARE: "Validerer fastvare..."
|
|
||||||
STR_INVALID_FIRMWARE: "Ugyldig fastvarefil"
|
|
||||||
STR_FIRMWARE_TOO_LARGE: "Fastvaren er for stor for partisjonen"
|
|
||||||
STR_FIRMWARE_TOO_SMALL: "Fastvarefilen er for liten"
|
|
||||||
STR_FIRMWARE_UPDATE_PROMPT: "Oppdatere fastvare?"
|
|
||||||
STR_FIRMWARE_FILE_OPEN_FAILED: "Kan ikke åpne filen"
|
|
||||||
STR_FIRMWARE_WRITE_FAILED: "Skriving av fastvare mislyktes"
|
|
||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Ikke slå av!"
|
|
||||||
STR_RECOVERY_MODE: "Gjenopprettingsmodus"
|
|
||||||
STR_RECOVERY_MODE_HINT: "Legg firmware.bin i roten av SD-kortet og velg den"
|
|
||||||
@@ -65,6 +65,8 @@ STR_SLEEP_COVER_MODE: "Okładki wygaszacza"
|
|||||||
STR_HIDE_BATTERY: "Ukryj % baterii"
|
STR_HIDE_BATTERY: "Ukryj % baterii"
|
||||||
STR_EXTRA_SPACING: "Dodatkowe odstępy paragrafów"
|
STR_EXTRA_SPACING: "Dodatkowe odstępy paragrafów"
|
||||||
STR_TEXT_AA: "Wygładzanie tekstu"
|
STR_TEXT_AA: "Wygładzanie tekstu"
|
||||||
|
STR_TEXT_AA_FULL: "Pełne"
|
||||||
|
STR_TEXT_AA_FAST: "Szybkie"
|
||||||
STR_IMAGES: "Obrazki"
|
STR_IMAGES: "Obrazki"
|
||||||
STR_IMAGES_DISPLAY: "Pokazuj"
|
STR_IMAGES_DISPLAY: "Pokazuj"
|
||||||
STR_IMAGES_PLACEHOLDER: "Ramki"
|
STR_IMAGES_PLACEHOLDER: "Ramki"
|
||||||
@@ -72,7 +74,6 @@ STR_IMAGES_SUPPRESS: "Pomijaj"
|
|||||||
STR_SHORT_PWR_BTN: "Krótkie naciśnięcie zasilania"
|
STR_SHORT_PWR_BTN: "Krótkie naciśnięcie zasilania"
|
||||||
STR_ORIENTATION: "Układ czytania"
|
STR_ORIENTATION: "Układ czytania"
|
||||||
STR_SIDE_BTN_LAYOUT: "Układ przycisków bocznych"
|
STR_SIDE_BTN_LAYOUT: "Układ przycisków bocznych"
|
||||||
STR_TOUCH_READER_CONTROLS: "Sterowanie dotykowe czytnika"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientuj przednie przyciski"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientuj przednie przyciski"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Funkcja długiego przyciśnięcia"
|
STR_LONG_PRESS_BEHAVIOR: "Funkcja długiego przyciśnięcia"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Wył."
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Wył."
|
||||||
@@ -98,7 +99,6 @@ STR_USERNAME: "Użytkownik"
|
|||||||
STR_PASSWORD: "Hasło"
|
STR_PASSWORD: "Hasło"
|
||||||
STR_SYNC_SERVER_URL: "Serwer URL synchronizacji"
|
STR_SYNC_SERVER_URL: "Serwer URL synchronizacji"
|
||||||
STR_DOCUMENT_MATCHING: "Dopasowanie dokumentów"
|
STR_DOCUMENT_MATCHING: "Dopasowanie dokumentów"
|
||||||
STR_SEND_METADATA: "Wyślij metadane dokumentu"
|
|
||||||
STR_AUTHENTICATE: "Uwierzytelnianie"
|
STR_AUTHENTICATE: "Uwierzytelnianie"
|
||||||
STR_KOREADER_USERNAME: "Użytkownik KOReader"
|
STR_KOREADER_USERNAME: "Użytkownik KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Hasło KOReader"
|
STR_KOREADER_PASSWORD: "Hasło KOReader"
|
||||||
@@ -317,11 +317,6 @@ STR_SERVER_NAME: "Nazwa serwera"
|
|||||||
STR_NO_SERVERS: "Brak skonfigurowanych serwerów OPDS"
|
STR_NO_SERVERS: "Brak skonfigurowanych serwerów OPDS"
|
||||||
STR_DELETE_SERVER: "Usuń serwer"
|
STR_DELETE_SERVER: "Usuń serwer"
|
||||||
STR_OPDS_SERVERS: "Serwery OPDS"
|
STR_OPDS_SERVERS: "Serwery OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Folder pobierania"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Format nazwy pliku"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autor - Tytuł"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Tytuł - Autor"
|
|
||||||
STR_FMT_TITLE: "Tytuł"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Auto-kartkowanie: "
|
STR_AUTO_TURN_ENABLED: "Auto-kartkowanie: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Auto-kartkowanie (str./min)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Auto-kartkowanie (str./min)"
|
||||||
STR_DOWNLOAD_FONTS: "Pobierz czcionki"
|
STR_DOWNLOAD_FONTS: "Pobierz czcionki"
|
||||||
@@ -367,5 +362,3 @@ STR_FIRMWARE_WRITE_FAILED: "Zapis oprogramowania nieudany"
|
|||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Nie wyłączać!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Nie wyłączać!"
|
||||||
STR_RECOVERY_MODE: "Tryb przywracania"
|
STR_RECOVERY_MODE: "Tryb przywracania"
|
||||||
STR_RECOVERY_MODE_HINT: "Umieść firmware.bin w głównym katalogu karty SD i wybierz go"
|
STR_RECOVERY_MODE_HINT: "Umieść firmware.bin w głównym katalogu karty SD i wybierz go"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Dodaj ukrytą sieć..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Wprowadź nazwę sieci (SSID)"
|
|
||||||
|
|||||||
@@ -1,393 +0,0 @@
|
|||||||
_language_name: "Português (Portugal)"
|
|
||||||
_language_code: "P2"
|
|
||||||
_order: "27"
|
|
||||||
|
|
||||||
STR_CROSSPOINT: "CrossPoint"
|
|
||||||
STR_BOOTING: "A INICIAR"
|
|
||||||
STR_SLEEPING: "EM REPOUSO"
|
|
||||||
STR_ENTERING_SLEEP: "A entrar em repouso"
|
|
||||||
STR_BROWSE_FILES: "Explorar ficheiros"
|
|
||||||
STR_FILE_TRANSFER: "Transferência de ficheiros"
|
|
||||||
STR_SETTINGS_TITLE: "Definições"
|
|
||||||
STR_CONTINUE_READING: "Continuar a ler"
|
|
||||||
STR_NO_OPEN_BOOK: "Nenhum livro aberto"
|
|
||||||
STR_START_READING: "Comece a ler abaixo"
|
|
||||||
STR_NO_FILES_FOUND: "Nenhum ficheiro encontrado"
|
|
||||||
STR_SELECT_CHAPTER: "Selecionar capítulo"
|
|
||||||
STR_NO_CHAPTERS: "Sem capítulos"
|
|
||||||
STR_END_OF_BOOK: "Fim do livro"
|
|
||||||
STR_EMPTY_CHAPTER: "Capítulo vazio"
|
|
||||||
STR_INDEXING: "A indexar"
|
|
||||||
STR_MEMORY_ERROR: "Erro de memória"
|
|
||||||
STR_PAGE_LOAD_ERROR: "Erro ao carregar a página"
|
|
||||||
STR_EMPTY_FILE: "Ficheiro vazio"
|
|
||||||
STR_OUT_OF_BOUNDS: "Fora dos limites"
|
|
||||||
STR_LOADING: "A carregar..."
|
|
||||||
STR_LOADING_POPUP: "A carregar"
|
|
||||||
STR_WIFI_NETWORKS: "Redes Wi-Fi"
|
|
||||||
STR_NO_NETWORKS: "Nenhuma rede encontrada"
|
|
||||||
STR_NETWORKS_FOUND: "%zu redes encontradas"
|
|
||||||
STR_SCANNING: "A procurar..."
|
|
||||||
STR_CONNECTING: "A ligar..."
|
|
||||||
STR_CONNECTED: "Ligado!"
|
|
||||||
STR_CONNECTION_FAILED: "Falha na ligação"
|
|
||||||
STR_FORGET_NETWORK: "Esquecer rede?"
|
|
||||||
STR_SAVE_PASSWORD: "Guardar palavra-passe para a próxima vez?"
|
|
||||||
STR_PRESS_OK_SCAN: "Prima OK para procurar novamente"
|
|
||||||
STR_JOIN_NETWORK: "Aderir a uma rede"
|
|
||||||
STR_CREATE_HOTSPOT: "Criar ponto de acesso"
|
|
||||||
STR_JOIN_DESC: "Ligue-se a uma rede Wi-Fi existente"
|
|
||||||
STR_HOTSPOT_DESC: "Crie uma rede Wi-Fi para outras pessoas se ligarem"
|
|
||||||
STR_STARTING_HOTSPOT: "A iniciar ponto de acesso..."
|
|
||||||
STR_HOTSPOT_MODE: "Modo de ponto de acesso"
|
|
||||||
STR_CONNECT_WIFI_HINT: "Ligue o seu dispositivo a esta rede Wi-Fi"
|
|
||||||
STR_OPEN_URL_HINT: "Abra este URL no seu navegador"
|
|
||||||
STR_OR_HTTP_PREFIX: "ou http://"
|
|
||||||
STR_SCAN_QR_HINT: "ou leia o código QR com o seu telemóvel:"
|
|
||||||
STR_CALIBRE_WIRELESS: "Calibre sem fios"
|
|
||||||
STR_NETWORK_LEGEND: "* = Encriptada | + = Guardada"
|
|
||||||
STR_MAC_ADDRESS: "Endereço MAC:"
|
|
||||||
STR_CHECKING_WIFI: "A verificar o Wi-Fi..."
|
|
||||||
STR_ENTER_WIFI_PASSWORD: "Introduza a palavra-passe do Wi-Fi"
|
|
||||||
STR_TO_PREFIX: "para "
|
|
||||||
STR_CALIBRE_RECEIVING: "A receber: "
|
|
||||||
STR_CALIBRE_RECEIVED: "Recebido: "
|
|
||||||
STR_CALIBRE_INSTRUCTION_1: "1) Instale o plugin CrossPoint Reader"
|
|
||||||
STR_CALIBRE_INSTRUCTION_2: "2) Esteja na mesma rede Wi-Fi"
|
|
||||||
STR_CALIBRE_INSTRUCTION_3: "3) No Calibre: \"Enviar para o dispositivo\""
|
|
||||||
STR_CALIBRE_INSTRUCTION_4: "\"Mantenha este ecrã aberto durante o envio\""
|
|
||||||
STR_CAT_DISPLAY: "Ecrã"
|
|
||||||
STR_CAT_READER: "Leitor"
|
|
||||||
STR_CAT_CONTROLS: "Controlos"
|
|
||||||
STR_CAT_SYSTEM: "Sistema"
|
|
||||||
STR_SLEEP_SCREEN: "Ecrã de repouso"
|
|
||||||
STR_QUICK_RESUME_TIMEOUT: "Retoma rápida após tempo limite"
|
|
||||||
STR_SLEEP_COVER_MODE: "Modo de capa no ecrã de repouso"
|
|
||||||
STR_HIDE_BATTERY: "Ocultar % da bateria"
|
|
||||||
STR_EXTRA_SPACING: "Espaçamento extra entre parágrafos"
|
|
||||||
STR_TEXT_AA: "Suavização do texto"
|
|
||||||
STR_IMAGES: "Imagens"
|
|
||||||
STR_IMAGES_DISPLAY: "Exibição"
|
|
||||||
STR_IMAGES_PLACEHOLDER: "Espaço reservado"
|
|
||||||
STR_IMAGES_SUPPRESS: "Suprimir"
|
|
||||||
STR_SHORT_PWR_BTN: "Clique curto no botão de energia"
|
|
||||||
STR_ORIENTATION: "Orientação de leitura"
|
|
||||||
STR_SIDE_BTN_LAYOUT: "Disposição dos botões laterais (leitor)"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botões frontais"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Comportamento de pressão longa"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "DESL."
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítulo"
|
|
||||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Alterar orientação"
|
|
||||||
STR_LONG_PRESS_MENU: "Pressão longa no Menu"
|
|
||||||
STR_FONT_PREVIEW_TEXT: "A rápida raposa castanha salta por cima do cão preguiçoso"
|
|
||||||
STR_FONT_FAMILY: "Tipo de letra do leitor"
|
|
||||||
STR_FONT_SIZE: "Tamanho do tipo de letra"
|
|
||||||
STR_LINE_SPACING: "Espaçamento entre linhas"
|
|
||||||
STR_SCREEN_MARGIN: "Margens do ecrã"
|
|
||||||
STR_PARA_ALIGNMENT: "Alinhamento dos parágrafos"
|
|
||||||
STR_HYPHENATION: "Hifenização"
|
|
||||||
STR_TIME_TO_SLEEP: "Tempo até repousar"
|
|
||||||
STR_SHOW_HIDDEN_FILES: "Mostrar ficheiros ocultos"
|
|
||||||
STR_REMOVE_READ_FROM_RECENTS: "Limpar livros lidos da lista de recentes"
|
|
||||||
STR_MOVE_FINISHED_TO_READ: "Mover livros concluídos para a pasta Read"
|
|
||||||
STR_REFRESH_FREQ: "Frequência de atualização"
|
|
||||||
STR_KOREADER_SYNC: "Sincronização KOReader"
|
|
||||||
STR_CHECK_UPDATES: "Procurar atualizações"
|
|
||||||
STR_LANGUAGE: "Idioma"
|
|
||||||
STR_CLEAR_READING_CACHE: "Limpar cache de leitura"
|
|
||||||
STR_USERNAME: "Nome de utilizador"
|
|
||||||
STR_PASSWORD: "Palavra-passe"
|
|
||||||
STR_SYNC_SERVER_URL: "URL do servidor de sincronização"
|
|
||||||
STR_DOCUMENT_MATCHING: "Correspondência de documentos"
|
|
||||||
STR_SEND_METADATA: "Enviar metadados do documento"
|
|
||||||
STR_AUTHENTICATE: "Autenticar"
|
|
||||||
STR_KOREADER_USERNAME: "Utilizador do KOReader"
|
|
||||||
STR_KOREADER_PASSWORD: "Palavra-passe do KOReader"
|
|
||||||
STR_FILENAME: "Nome do ficheiro"
|
|
||||||
STR_BINARY: "Binário"
|
|
||||||
STR_SET_CREDENTIALS_FIRST: "Defina as credenciais primeiro"
|
|
||||||
STR_WIFI_CONN_FAILED: "Falha na ligação Wi-Fi"
|
|
||||||
STR_AUTHENTICATING: "A autenticar..."
|
|
||||||
STR_AUTH_SUCCESS: "Autenticado com sucesso!"
|
|
||||||
STR_KOREADER_AUTH: "Autenticação KOReader"
|
|
||||||
STR_SYNC_READY: "A sincronização KOReader está pronta a usar"
|
|
||||||
STR_AUTH_FAILED: "Falha na autenticação"
|
|
||||||
STR_DONE: "Concluído"
|
|
||||||
STR_CLEAR_CACHE_WARNING_1: "Isto irá limpar todos os dados de livros em cache."
|
|
||||||
STR_CLEAR_CACHE_WARNING_2: "Todo o progresso de leitura será perdido!"
|
|
||||||
STR_CLEAR_CACHE_WARNING_3: "Os livros terão de ser reindexados"
|
|
||||||
STR_CLEAR_CACHE_WARNING_4: "quando forem abertos novamente."
|
|
||||||
STR_CLEARING_CACHE: "A limpar a cache..."
|
|
||||||
STR_CACHE_CLEARED: "Cache limpa"
|
|
||||||
STR_ITEMS_REMOVED: "itens removidos"
|
|
||||||
STR_FAILED_LOWER: "falhou"
|
|
||||||
STR_CLEAR_CACHE_FAILED: "Falha ao limpar a cache"
|
|
||||||
STR_CHECK_SERIAL_OUTPUT: "Verifique a saída de série para obter detalhes"
|
|
||||||
STR_DARK: "Escuro"
|
|
||||||
STR_LIGHT: "Claro"
|
|
||||||
STR_CUSTOM: "Personalizado"
|
|
||||||
STR_COVER: "Capa"
|
|
||||||
STR_NONE_OPT: "Nenhum"
|
|
||||||
STR_FIT: "Ajustar"
|
|
||||||
STR_CROP: "Recortar"
|
|
||||||
STR_NEVER: "Nunca"
|
|
||||||
STR_IN_READER: "No leitor"
|
|
||||||
STR_ALWAYS: "Sempre"
|
|
||||||
STR_IGNORE: "Ignorar"
|
|
||||||
STR_SLEEP: "Repouso"
|
|
||||||
STR_PAGE_TURN: "Virar página"
|
|
||||||
STR_FORCE_REFRESH: "Atualizar ecrã"
|
|
||||||
STR_PORTRAIT: "Retrato"
|
|
||||||
STR_LANDSCAPE_CW: "Paisagem (Dir.)"
|
|
||||||
STR_INVERTED: "Invertido"
|
|
||||||
STR_ORIENTATION_INVERTED: "Retrato 180°"
|
|
||||||
STR_LANDSCAPE_CCW: "Paisagem (Esq.)"
|
|
||||||
STR_PREV_NEXT: "Ant/Seg"
|
|
||||||
STR_NEXT_PREV: "Seg/Ant"
|
|
||||||
STR_KOSYNC: "KOSync"
|
|
||||||
STR_BOOKMARK_OPTION: "Marcador"
|
|
||||||
STR_DISABLED: "Desativado"
|
|
||||||
STR_NOTO_SERIF: "Noto Serif"
|
|
||||||
STR_NOTO_SANS: "Noto Sans"
|
|
||||||
STR_SMALL: "Pequeno"
|
|
||||||
STR_MEDIUM: "Médio"
|
|
||||||
STR_LARGE: "Grande"
|
|
||||||
STR_X_LARGE: "Extra grande"
|
|
||||||
STR_TIGHT: "Apertado"
|
|
||||||
STR_NORMAL: "Normal"
|
|
||||||
STR_WIDE: "Largo"
|
|
||||||
STR_JUSTIFY: "Justificar"
|
|
||||||
STR_ALIGN_LEFT: "Esquerda"
|
|
||||||
STR_CENTER: "Centrar"
|
|
||||||
STR_ALIGN_RIGHT: "Direita"
|
|
||||||
STR_PAGES_1: "1 página"
|
|
||||||
STR_PAGES_5: "5 páginas"
|
|
||||||
STR_PAGES_10: "10 páginas"
|
|
||||||
STR_PAGES_15: "15 páginas"
|
|
||||||
STR_PAGES_30: "30 páginas"
|
|
||||||
STR_UPDATE: "Atualizar"
|
|
||||||
STR_CHECKING_UPDATE: "A procurar atualização..."
|
|
||||||
STR_NEW_UPDATE: "Nova atualização disponível!"
|
|
||||||
STR_CURRENT_VERSION: "Versão atual: "
|
|
||||||
STR_NEW_VERSION: "Nova versão: "
|
|
||||||
STR_UPDATING: "A atualizar..."
|
|
||||||
STR_NO_UPDATE: "Nenhuma atualização disponível"
|
|
||||||
STR_UPDATE_FAILED: "Falha na atualização"
|
|
||||||
STR_UPDATE_COMPLETE: "Atualização concluída"
|
|
||||||
STR_POWER_ON_HINT: "Prima sem soltar o botão de energia para voltar a ligar"
|
|
||||||
STR_RESTARTING_HINT: "A reiniciar... Se o dispositivo não reiniciar, mantenha premido o botão de energia durante alguns segundos."
|
|
||||||
STR_NO_ENTRIES: "Nenhum registo encontrado"
|
|
||||||
STR_DOWNLOADING: "A transferir..."
|
|
||||||
STR_DOWNLOAD_FAILED: "Falha na transferência"
|
|
||||||
STR_ERROR_MSG: "Erro:"
|
|
||||||
STR_UNNAMED: "Sem nome"
|
|
||||||
STR_HOLD_OPEN_TO_DELETE: "Mantenha premido Abrir para eliminar"
|
|
||||||
STR_NO_SERVER_URL: "Nenhum URL de servidor configurado"
|
|
||||||
STR_FETCH_FEED_FAILED: "Falha ao obter o feed"
|
|
||||||
STR_PARSE_FEED_FAILED: "Falha ao analisar o feed"
|
|
||||||
STR_NEXT_PAGE: "Página seguinte »"
|
|
||||||
STR_PREV_PAGE: "« Página anterior"
|
|
||||||
STR_NETWORK_PREFIX: "Rede: "
|
|
||||||
STR_IP_ADDRESS_PREFIX: "Endereço IP: "
|
|
||||||
STR_ERROR_GENERAL_FAILURE: "Erro: falha geral"
|
|
||||||
STR_ERROR_NETWORK_NOT_FOUND: "Erro: rede não encontrada"
|
|
||||||
STR_ERROR_CONNECTION_TIMEOUT: "Erro: tempo limite de ligação esgotado"
|
|
||||||
STR_SD_CARD: "Cartão SD"
|
|
||||||
STR_BACK: "« Voltar"
|
|
||||||
STR_EXIT: "« Sair"
|
|
||||||
STR_HOME: "« Início"
|
|
||||||
STR_SELECT: "Selecionar"
|
|
||||||
STR_SELECTED: "Selecionado"
|
|
||||||
STR_TOGGLE: "Alternar"
|
|
||||||
STR_TOGGLE_BOOKMARK: "Alternar marcador"
|
|
||||||
STR_CONFIRM: "Confirmar"
|
|
||||||
STR_CANCEL: "Cancelar"
|
|
||||||
STR_CONNECT: "Ligar"
|
|
||||||
STR_OPEN: "Abrir"
|
|
||||||
STR_DOWNLOAD: "Transferir"
|
|
||||||
STR_RETRY: "Tentar novamente"
|
|
||||||
STR_YES: "Sim"
|
|
||||||
STR_NO: "Não"
|
|
||||||
STR_SHOW: "Mostrar"
|
|
||||||
STR_HIDE: "Ocultar"
|
|
||||||
STR_STATE_ON: "LIG."
|
|
||||||
STR_STATE_OFF: "DESL."
|
|
||||||
STR_NOT_SET: "Não definido"
|
|
||||||
STR_DIR_LEFT: "Esquerda"
|
|
||||||
STR_DIR_RIGHT: "Direita"
|
|
||||||
STR_DIR_UP: "Cima"
|
|
||||||
STR_DIR_DOWN: "Baixo"
|
|
||||||
STR_OK_BUTTON: "OK"
|
|
||||||
STR_SLEEP_COVER_FILTER: "Filtro da capa de ecrã de repouso"
|
|
||||||
STR_FILTER_CONTRAST: "Contraste"
|
|
||||||
STR_CUSTOMISE_STATUS_BAR: "Personalizar barra de estado"
|
|
||||||
STR_CHAPTER_PAGE_COUNT: "Contagem de páginas do capítulo"
|
|
||||||
STR_BOOK_PROGRESS_PERCENTAGE: "Percentagem de progresso do livro"
|
|
||||||
STR_PROGRESS_BAR: "Barra de progresso"
|
|
||||||
STR_PROGRESS_BAR_THICKNESS: "Espessura da barra de progresso"
|
|
||||||
STR_PROGRESS_BAR_THIN: "Fina"
|
|
||||||
STR_PROGRESS_BAR_MEDIUM: "Média"
|
|
||||||
STR_PROGRESS_BAR_THICK: "Grossa"
|
|
||||||
STR_BOOK: "Livro"
|
|
||||||
STR_CHAPTER: "Capítulo"
|
|
||||||
STR_EXAMPLE_CHAPTER: "Capítulo 21"
|
|
||||||
STR_EXAMPLE_BOOK: "Título do livro"
|
|
||||||
STR_PREVIEW: "Pré-visualização"
|
|
||||||
STR_TITLE: "Título"
|
|
||||||
STR_BATTERY: "Bateria"
|
|
||||||
STR_XTC_STATUS_BAR: "Barra de estado XTC"
|
|
||||||
STR_BOTTOM: "Inferior"
|
|
||||||
STR_TOP: "Superior"
|
|
||||||
STR_CLOCK: "Relógio"
|
|
||||||
STR_CLOCK_UTC_OFFSET: "Deslocamento UTC do relógio"
|
|
||||||
STR_CLOCK_FORMAT: "Formato do relógio"
|
|
||||||
STR_CLOCK_FORMAT_24H: "24 horas"
|
|
||||||
STR_CLOCK_FORMAT_12H: "12 horas"
|
|
||||||
STR_CURRENT_TIME: "Hora atual:"
|
|
||||||
STR_NEXT_FIELD: "Seguinte"
|
|
||||||
STR_CLOCK_SYNC: "Sincronizar relógio"
|
|
||||||
STR_CLOCK_SYNC_NOW: "Sincronizar relógio agora"
|
|
||||||
STR_CLOCK_SYNCING: "A sincronizar via NTP..."
|
|
||||||
STR_CLOCK_SYNC_OK: "Relógio sincronizado"
|
|
||||||
STR_CLOCK_SYNC_FAIL: "Falha na sincronização"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI: "Wi-Fi não ligado"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI_HINT: "Ligue-se primeiro ao Wi-Fi e, em seguida, tente novamente."
|
|
||||||
STR_CLOCK_SYNCED: "Relógio sincronizado"
|
|
||||||
STR_UI_THEME: "Tema da interface"
|
|
||||||
STR_THEME_CLASSIC: "Clássico"
|
|
||||||
STR_THEME_LYRA: "Lyra"
|
|
||||||
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
|
|
||||||
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
|
||||||
STR_SUNLIGHT_FADING_FIX: "Ajuste de desbotamento ao sol"
|
|
||||||
STR_REMAP_FRONT_BUTTONS: "Remapear botões frontais"
|
|
||||||
STR_BOOKMARKS: "Marcadores"
|
|
||||||
STR_BOOKMARK_ADDED: "Marcador adicionado."
|
|
||||||
STR_BOOKMARK_REMOVED: "Marcador removido."
|
|
||||||
STR_OPDS_BROWSER: "Navegador OPDS"
|
|
||||||
STR_SEARCH: "Pesquisar"
|
|
||||||
STR_COVER_CUSTOM: "Capa + Personalizado"
|
|
||||||
STR_QUICK_RESUME: "Retoma rápida"
|
|
||||||
STR_MENU_RECENT_BOOKS: "Livros recentes"
|
|
||||||
STR_REMOVE_FROM_RECENTS: "Remover dos Livros recentes?"
|
|
||||||
STR_NO_RECENT_BOOKS: "Nenhum livro recente"
|
|
||||||
STR_CALIBRE_DESC: "Usar transferências sem fios de dispositivos do Calibre"
|
|
||||||
STR_FORGET_AND_REMOVE: "Esquecer rede e remover palavra-passe guardada?"
|
|
||||||
STR_FORGET_BUTTON: "Esquecer"
|
|
||||||
STR_CALIBRE_STARTING: "A iniciar o Calibre..."
|
|
||||||
STR_CALIBRE_SETUP: "Configuração"
|
|
||||||
STR_CALIBRE_STATUS: "Estado"
|
|
||||||
STR_CLEAR_BUTTON: "Limpar"
|
|
||||||
STR_DEFAULT_VALUE: "Predefinição"
|
|
||||||
STR_REMAP_PROMPT: "Prima um botão frontal para cada função"
|
|
||||||
STR_UNASSIGNED: "Não atribuído"
|
|
||||||
STR_ALREADY_ASSIGNED: "Já atribuído"
|
|
||||||
STR_REMAP_RESET_HINT: "Botão lateral Cima: Repor disposição predefinida"
|
|
||||||
STR_REMAP_CANCEL_HINT: "Botão lateral Baixo: Cancelar remapeamento"
|
|
||||||
STR_HW_BACK_LABEL: "Voltar (1º botão)"
|
|
||||||
STR_HW_CONFIRM_LABEL: "Confirmar (2º botão)"
|
|
||||||
STR_HW_LEFT_LABEL: "Esquerda (3º botão)"
|
|
||||||
STR_HW_RIGHT_LABEL: "Direita (4º botão)"
|
|
||||||
STR_GO_TO_PERCENT: "Ir para %"
|
|
||||||
STR_GO_HOME_BUTTON: "Ir para o Início"
|
|
||||||
STR_SYNC_PROGRESS: "Sincronizar progresso"
|
|
||||||
STR_DELETE_CACHE: "Eliminar cache do livro"
|
|
||||||
STR_DELETE: "Eliminar"
|
|
||||||
STR_CONFIRM_DELETE_BOOKMARK: "Eliminar este marcador?"
|
|
||||||
STR_DISPLAY_QR: "Mostrar página como QR"
|
|
||||||
STR_CHAPTER_PREFIX: "Capítulo: "
|
|
||||||
STR_PAGES_SEPARATOR: " páginas | "
|
|
||||||
STR_BOOK_PREFIX: "Livro: "
|
|
||||||
STR_CALIBRE_URL_HINT: "Para o Calibre, adicione /opds ao seu URL"
|
|
||||||
STR_SYNCING_TIME: "A sincronizar a hora..."
|
|
||||||
STR_CALC_HASH: "A calcular o hash do documento..."
|
|
||||||
STR_HASH_FAILED: "Falha ao calcular o hash do documento"
|
|
||||||
STR_FETCH_PROGRESS: "A procurar progresso remoto..."
|
|
||||||
STR_UPLOAD_PROGRESS: "A enviar progresso..."
|
|
||||||
STR_NO_CREDENTIALS_MSG: "Nenhuma credencial configurada"
|
|
||||||
STR_KOREADER_SETUP_HINT: "Configure a conta do KOReader nas Definições"
|
|
||||||
STR_PROGRESS_FOUND: "Progresso encontrado!"
|
|
||||||
STR_REMOTE_LABEL: "Remoto:"
|
|
||||||
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: "Aplicar progresso remoto"
|
|
||||||
STR_UPLOAD_LOCAL: "Enviar progresso local"
|
|
||||||
STR_NO_REMOTE_MSG: "Nenhum progresso remoto encontrado"
|
|
||||||
STR_UPLOAD_PROMPT: "Enviar posição atual?"
|
|
||||||
STR_UPLOAD_SUCCESS: "Progresso enviado!"
|
|
||||||
STR_SYNC_FAILED_MSG: "Falha na sincronização"
|
|
||||||
STR_SAVE_PROGRESS_FAILED: "Não foi possível guardar o progresso"
|
|
||||||
STR_SECTION_PREFIX: "Secção "
|
|
||||||
STR_UPLOAD: "Enviar"
|
|
||||||
STR_BOOK_S_STYLE: "Estilo do livro"
|
|
||||||
STR_EMBEDDED_STYLE: "Estilo embutido"
|
|
||||||
STR_FOCUS_READING: "Leitura focada"
|
|
||||||
STR_OPDS_SERVER_URL: "URL do servidor OPDS"
|
|
||||||
STR_PWR_BTN_FOOTNOTE_BACK: "Retorno rápido das notas de rodapé"
|
|
||||||
STR_SET_SLEEP_COVER: "Definir capa"
|
|
||||||
STR_FOOTNOTES: "Notas de rodapé"
|
|
||||||
STR_NO_FOOTNOTES: "Nenhuma nota de rodapé nesta página"
|
|
||||||
STR_LINK: "[ligação]"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Captura de ecrã"
|
|
||||||
STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
|
||||||
STR_SLEEP_NEVER: "Nunca"
|
|
||||||
STR_STEP_HINT_FRONT: "Botões frontais:"
|
|
||||||
STR_STEP_HINT_SIDE: "Botões laterais:"
|
|
||||||
STR_ADD_SERVER: "Adicionar servidor"
|
|
||||||
STR_SERVER_NAME: "Nome do servidor"
|
|
||||||
STR_NO_SERVERS: "Nenhum servidor OPDS configurado"
|
|
||||||
STR_DELETE_SERVER: "Eliminar servidor"
|
|
||||||
STR_OPDS_SERVERS: "Servidores OPDS"
|
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Pasta de downloads"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Formato do nome do ficheiro"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autor - Título"
|
|
||||||
STR_FMT_TITLE: "Título"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Título - Autor"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Virar página automático ativado: "
|
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Virar página automático (Páginas por minuto)"
|
|
||||||
STR_MANAGE_FONTS: "Gerir tipos de letra"
|
|
||||||
STR_FONT_BROWSER: "Navegador de tipos de letra"
|
|
||||||
STR_LOADING_FONT_LIST: "A carregar a lista de tipos de letra..."
|
|
||||||
STR_NO_FONTS_AVAILABLE: "Nenhum tipo de letra disponível"
|
|
||||||
STR_FONT_INSTALLED: "Tipo de letra instalado!"
|
|
||||||
STR_FONT_INSTALL_FAILED: "Falha na instalação do tipo de letra"
|
|
||||||
STR_INSTALLED: "Instalado"
|
|
||||||
STR_DOWNLOAD_ALL: "Transferir tudo"
|
|
||||||
STR_UPDATE_ALL: "Atualizar tudo"
|
|
||||||
STR_UPDATE_AVAILABLE: "Atualizar"
|
|
||||||
STR_CRASH_TITLE: "Falha do sistema"
|
|
||||||
STR_CRASH_DESCRIPTION: "Um relatório detalhado foi guardado em crash_report.txt. Por favor, inclua este ficheiro no seu relatório de erro."
|
|
||||||
STR_CRASH_REASON: "Motivo da falha:"
|
|
||||||
STR_CRASH_NO_REASON: "(Nenhum motivo foi registado)"
|
|
||||||
STR_TILT_PAGE_TURN: "Virar página por inclinação"
|
|
||||||
STR_KB_HINT_MOVE_CURSOR: "Prima ESQUERDA ou DIREITA para mover o cursor"
|
|
||||||
STR_KB_HINT_RETURN_CURSOR: "Prima ESQUERDA para regressar à posição do cursor"
|
|
||||||
STR_KB_HINT_HIDE_PASSWORD: "Mantenha premido DIREITA e depois prima [***] para ocultar a palavra-passe"
|
|
||||||
STR_KB_HINT_SHOW_PASSWORD: "Mantenha premido DIREITA e depois prima [abc] para mostrar a palavra-passe"
|
|
||||||
STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Prima [***] para ocultar a palavra-passe"
|
|
||||||
STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Prima [abc] para mostrar a palavra-passe"
|
|
||||||
STR_KB_HINT_EDIT_ENTRY: "Mantenha premido CIMA para editar a entrada"
|
|
||||||
STR_KB_TIPS: "Dicas:"
|
|
||||||
STR_KB_HINT_RETURN_KEYBOARD: "Prima BAIXO para regressar ao teclado"
|
|
||||||
STR_KB_HINT_EXIT_URL_MODE: "Prima ABC para sair do modo URL"
|
|
||||||
STR_KB_HINT_CLEAR_TEXT: "Mantenha premido DEL para limpar todo o texto"
|
|
||||||
STR_KB_HINT_SECONDARY_CHAR: "Mantenha premido SELECT para o carácter secundário"
|
|
||||||
STR_KB_HINT_UPPER_SECONDARY: "Mantenha premido SELECT para MAIÚSCULAS ou carácter secundário"
|
|
||||||
STR_KB_HINT_LOWER_SECONDARY: "Mantenha premido SELECT para minúsculas ou carácter secundário"
|
|
||||||
STR_KB_HINT_URL_SNIPPETS: "Prima URL para atalhos"
|
|
||||||
STR_SD_FIRMWARE_UPDATE: "Atualização de firmware via cartão SD"
|
|
||||||
STR_SELECT_FIRMWARE_FILE: "Selecione o ficheiro de firmware (.bin)"
|
|
||||||
STR_NO_BIN_FILES: "Nenhum ficheiro .bin encontrado"
|
|
||||||
STR_VALIDATING_FIRMWARE: "A validar o firmware..."
|
|
||||||
STR_INVALID_FIRMWARE: "Ficheiro de firmware inválido"
|
|
||||||
STR_FIRMWARE_TOO_LARGE: "Firmware demasiado grande para a partição"
|
|
||||||
STR_FIRMWARE_TOO_SMALL: "Ficheiro de firmware demasiado pequeno"
|
|
||||||
STR_FIRMWARE_UPDATE_PROMPT: "Atualizar firmware?"
|
|
||||||
STR_FIRMWARE_FILE_OPEN_FAILED: "Não foi possível abrir o ficheiro"
|
|
||||||
STR_FIRMWARE_WRITE_FAILED: "Falha na gravação do firmware"
|
|
||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Não desligue o dispositivo!"
|
|
||||||
STR_RECOVERY_MODE: "Modo de Recuperação"
|
|
||||||
STR_RECOVERY_MODE_HINT: "Coloque o ficheiro firmware.bin na raiz do cartão SD e selecione-o"
|
|
||||||
STR_ADD_HIDDEN_NETWORK: "Adicionar rede oculta..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Introduza o nome da rede (SSID)"
|
|
||||||
@@ -66,6 +66,8 @@ STR_SLEEP_COVER_MODE: "Modo capa tela repouso"
|
|||||||
STR_HIDE_BATTERY: "Ocultar % da bateria"
|
STR_HIDE_BATTERY: "Ocultar % da bateria"
|
||||||
STR_EXTRA_SPACING: "Espaço de parágrafos extra"
|
STR_EXTRA_SPACING: "Espaço de parágrafos extra"
|
||||||
STR_TEXT_AA: "Suavização de texto"
|
STR_TEXT_AA: "Suavização de texto"
|
||||||
|
STR_TEXT_AA_FULL: "Completa"
|
||||||
|
STR_TEXT_AA_FAST: "Rápida"
|
||||||
STR_IMAGES: "Imagens"
|
STR_IMAGES: "Imagens"
|
||||||
STR_IMAGES_DISPLAY: "Exibição"
|
STR_IMAGES_DISPLAY: "Exibição"
|
||||||
STR_IMAGES_PLACEHOLDER: "Espaço reservado"
|
STR_IMAGES_PLACEHOLDER: "Espaço reservado"
|
||||||
@@ -73,7 +75,6 @@ STR_IMAGES_SUPPRESS: "Ocultar"
|
|||||||
STR_SHORT_PWR_BTN: "Clique curto botão ligar"
|
STR_SHORT_PWR_BTN: "Clique curto botão ligar"
|
||||||
STR_ORIENTATION: "Orientação de leitura"
|
STR_ORIENTATION: "Orientação de leitura"
|
||||||
STR_SIDE_BTN_LAYOUT: "Disposição botões laterais"
|
STR_SIDE_BTN_LAYOUT: "Disposição botões laterais"
|
||||||
STR_TOUCH_READER_CONTROLS: "Controles táteis do leitor"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botões frontais"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botões frontais"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Comportamento de Pressionar e segurar"
|
STR_LONG_PRESS_BEHAVIOR: "Comportamento de Pressionar e segurar"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "DESL."
|
STR_LONG_PRESS_BEHAVIOR_OFF: "DESL."
|
||||||
@@ -100,7 +101,6 @@ STR_USERNAME: "Nome de usuário"
|
|||||||
STR_PASSWORD: "Senha"
|
STR_PASSWORD: "Senha"
|
||||||
STR_SYNC_SERVER_URL: "URL servidor sincronização"
|
STR_SYNC_SERVER_URL: "URL servidor sincronização"
|
||||||
STR_DOCUMENT_MATCHING: "Documento correspondente"
|
STR_DOCUMENT_MATCHING: "Documento correspondente"
|
||||||
STR_SEND_METADATA: "Enviar metadados do doc."
|
|
||||||
STR_AUTHENTICATE: "Autenticar"
|
STR_AUTHENTICATE: "Autenticar"
|
||||||
STR_KOREADER_USERNAME: "Usuário do KOReader"
|
STR_KOREADER_USERNAME: "Usuário do KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Senha do KOReader"
|
STR_KOREADER_PASSWORD: "Senha do KOReader"
|
||||||
@@ -340,11 +340,6 @@ STR_SERVER_NAME: "Nome do Servidor"
|
|||||||
STR_NO_SERVERS: "Nenhum servidor OPDS configurado"
|
STR_NO_SERVERS: "Nenhum servidor OPDS configurado"
|
||||||
STR_DELETE_SERVER: "Excluir Servidor"
|
STR_DELETE_SERVER: "Excluir Servidor"
|
||||||
STR_OPDS_SERVERS: "Servidores OPDS"
|
STR_OPDS_SERVERS: "Servidores OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Pasta de downloads"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Formato do nome do arquivo"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autor - Título"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Título - Autor"
|
|
||||||
STR_FMT_TITLE: "Título"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Virada automática ativada: "
|
STR_AUTO_TURN_ENABLED: "Virada automática ativada: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Virada automática (páginas por minuto)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Virada automática (páginas por minuto)"
|
||||||
STR_MANAGE_FONTS: "Gerenciar fontes"
|
STR_MANAGE_FONTS: "Gerenciar fontes"
|
||||||
@@ -390,5 +385,3 @@ STR_FIRMWARE_WRITE_FAILED: "Falha na gravação do firmware"
|
|||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Não desligue o dispositivo!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Não desligue o dispositivo!"
|
||||||
STR_RECOVERY_MODE: "Modo de Recuperação"
|
STR_RECOVERY_MODE: "Modo de Recuperação"
|
||||||
STR_RECOVERY_MODE_HINT: "Coloque o arquivo firmware.bin na raiz do cartão SD e selecione-o"
|
STR_RECOVERY_MODE_HINT: "Coloque o arquivo firmware.bin na raiz do cartão SD e selecione-o"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Adicionar rede oculta..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Insira o nome da rede (SSID)"
|
|
||||||
@@ -65,6 +65,8 @@ STR_SLEEP_COVER_MODE: "Mod ecran de repaus cu copertă"
|
|||||||
STR_HIDE_BATTERY: "Ascunde procentul bateriei"
|
STR_HIDE_BATTERY: "Ascunde procentul bateriei"
|
||||||
STR_EXTRA_SPACING: "Spaţiere suplimentară între paragrafe"
|
STR_EXTRA_SPACING: "Spaţiere suplimentară între paragrafe"
|
||||||
STR_TEXT_AA: "Anti-Aliasing text"
|
STR_TEXT_AA: "Anti-Aliasing text"
|
||||||
|
STR_TEXT_AA_FULL: "Complet"
|
||||||
|
STR_TEXT_AA_FAST: "Rapid"
|
||||||
STR_IMAGES: "Imagini"
|
STR_IMAGES: "Imagini"
|
||||||
STR_IMAGES_DISPLAY: "Afişare"
|
STR_IMAGES_DISPLAY: "Afişare"
|
||||||
STR_IMAGES_PLACEHOLDER: "Substituent"
|
STR_IMAGES_PLACEHOLDER: "Substituent"
|
||||||
@@ -72,7 +74,6 @@ STR_IMAGES_SUPPRESS: "Suprimare"
|
|||||||
STR_SHORT_PWR_BTN: "Apăsare scurtă întrerupător"
|
STR_SHORT_PWR_BTN: "Apăsare scurtă întrerupător"
|
||||||
STR_ORIENTATION: "Orientare lectură"
|
STR_ORIENTATION: "Orientare lectură"
|
||||||
STR_SIDE_BTN_LAYOUT: "Aspect butoane laterale (lectură)"
|
STR_SIDE_BTN_LAYOUT: "Aspect butoane laterale (lectură)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Comenzi tactile cititor"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientare butoane frontale"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientare butoane frontale"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Comportament buton apăsat lung"
|
STR_LONG_PRESS_BEHAVIOR: "Comportament buton apăsat lung"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Dezactivat"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Dezactivat"
|
||||||
@@ -98,7 +99,6 @@ STR_USERNAME: "Utilizator"
|
|||||||
STR_PASSWORD: "Parolă"
|
STR_PASSWORD: "Parolă"
|
||||||
STR_SYNC_SERVER_URL: "URL server sincronizare"
|
STR_SYNC_SERVER_URL: "URL server sincronizare"
|
||||||
STR_DOCUMENT_MATCHING: "Corespondenţă document"
|
STR_DOCUMENT_MATCHING: "Corespondenţă document"
|
||||||
STR_SEND_METADATA: "Trimite metadate document"
|
|
||||||
STR_AUTHENTICATE: "Autentificare"
|
STR_AUTHENTICATE: "Autentificare"
|
||||||
STR_KOREADER_USERNAME: "Nume utilizator KOReader"
|
STR_KOREADER_USERNAME: "Nume utilizator KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Parolă KOReader"
|
STR_KOREADER_PASSWORD: "Parolă KOReader"
|
||||||
@@ -301,14 +301,7 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
|||||||
STR_SLEEP_NEVER: "Niciodată"
|
STR_SLEEP_NEVER: "Niciodată"
|
||||||
STR_STEP_HINT_FRONT: "Butoane frontale:"
|
STR_STEP_HINT_FRONT: "Butoane frontale:"
|
||||||
STR_STEP_HINT_SIDE: "Butoane laterale:"
|
STR_STEP_HINT_SIDE: "Butoane laterale:"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Dosar descărcări"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Format nume fișier"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autor - Titlu"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Titlu - Autor"
|
|
||||||
STR_FMT_TITLE: "Titlu"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Captură ecran"
|
STR_SCREENSHOT_BUTTON: "Captură ecran"
|
||||||
STR_AUTO_TURN_ENABLED: "Răsfoire automată: "
|
STR_AUTO_TURN_ENABLED: "Răsfoire automată: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Pagini pe minut"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Pagini pe minut"
|
||||||
STR_TILT_PAGE_TURN: "Întoarcere pagină prin înclinare"
|
STR_TILT_PAGE_TURN: "Întoarcere pagină prin înclinare"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Adaugă rețea ascunsă..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Introduceți numele rețelei (SSID)"
|
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ STR_SLEEP_COVER_MODE: "Режим обложки сна"
|
|||||||
STR_HIDE_BATTERY: "Скрыть % батареи"
|
STR_HIDE_BATTERY: "Скрыть % батареи"
|
||||||
STR_EXTRA_SPACING: "Доп. интервал абзаца"
|
STR_EXTRA_SPACING: "Доп. интервал абзаца"
|
||||||
STR_TEXT_AA: "Сглаживание текста"
|
STR_TEXT_AA: "Сглаживание текста"
|
||||||
|
STR_TEXT_AA_FULL: "Полное"
|
||||||
|
STR_TEXT_AA_FAST: "Быстрое"
|
||||||
STR_IMAGES: "Изображения"
|
STR_IMAGES: "Изображения"
|
||||||
STR_IMAGES_DISPLAY: "Показать"
|
STR_IMAGES_DISPLAY: "Показать"
|
||||||
STR_IMAGES_PLACEHOLDER: "Заглушки"
|
STR_IMAGES_PLACEHOLDER: "Заглушки"
|
||||||
@@ -73,7 +75,6 @@ STR_IMAGES_SUPPRESS: "Скрыть"
|
|||||||
STR_SHORT_PWR_BTN: "Короткое нажатие PWR"
|
STR_SHORT_PWR_BTN: "Короткое нажатие PWR"
|
||||||
STR_ORIENTATION: "Ориентация чтения"
|
STR_ORIENTATION: "Ориентация чтения"
|
||||||
STR_SIDE_BTN_LAYOUT: "Боковые кнопки"
|
STR_SIDE_BTN_LAYOUT: "Боковые кнопки"
|
||||||
STR_TOUCH_READER_CONTROLS: "Сенсорное управление чтением"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Ориентировать передние кнопки"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Ориентировать передние кнопки"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Долгое нажатие"
|
STR_LONG_PRESS_BEHAVIOR: "Долгое нажатие"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Ничего"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Ничего"
|
||||||
@@ -100,7 +101,6 @@ STR_USERNAME: "Имя пользователя"
|
|||||||
STR_PASSWORD: "Пароль"
|
STR_PASSWORD: "Пароль"
|
||||||
STR_SYNC_SERVER_URL: "URL сервера синхронизации"
|
STR_SYNC_SERVER_URL: "URL сервера синхронизации"
|
||||||
STR_DOCUMENT_MATCHING: "Сопоставление документов"
|
STR_DOCUMENT_MATCHING: "Сопоставление документов"
|
||||||
STR_SEND_METADATA: "Отправлять метаданные"
|
|
||||||
STR_AUTHENTICATE: "Авторизация"
|
STR_AUTHENTICATE: "Авторизация"
|
||||||
STR_KOREADER_USERNAME: "Имя пользователя KOReader"
|
STR_KOREADER_USERNAME: "Имя пользователя KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Пароль KOReader"
|
STR_KOREADER_PASSWORD: "Пароль KOReader"
|
||||||
@@ -340,11 +340,6 @@ STR_SERVER_NAME: "Имя сервера"
|
|||||||
STR_NO_SERVERS: "Нет настроенных серверов OPDS"
|
STR_NO_SERVERS: "Нет настроенных серверов OPDS"
|
||||||
STR_DELETE_SERVER: "Удалить сервер"
|
STR_DELETE_SERVER: "Удалить сервер"
|
||||||
STR_OPDS_SERVERS: "Серверы OPDS"
|
STR_OPDS_SERVERS: "Серверы OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Папка загрузок"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Формат имени файла"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Автор - Название"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Название - Автор"
|
|
||||||
STR_FMT_TITLE: "Название"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Автоперелистывание: "
|
STR_AUTO_TURN_ENABLED: "Автоперелистывание: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Автоперелистывание (стр./мин)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Автоперелистывание (стр./мин)"
|
||||||
STR_MANAGE_FONTS: "Управление шрифтами"
|
STR_MANAGE_FONTS: "Управление шрифтами"
|
||||||
@@ -390,5 +385,3 @@ STR_FIRMWARE_WRITE_FAILED: "Ошибка записи прошивки"
|
|||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Не выключайте питание!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Не выключайте питание!"
|
||||||
STR_RECOVERY_MODE: "Режим восстановления"
|
STR_RECOVERY_MODE: "Режим восстановления"
|
||||||
STR_RECOVERY_MODE_HINT: "Поместите firmware.bin в корень SD-карты и выберите его"
|
STR_RECOVERY_MODE_HINT: "Поместите firmware.bin в корень SD-карты и выберите его"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Добавить скрытую сеть..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Введите имя сети (SSID)"
|
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ STR_SLEEP_COVER_MODE: "Obrazovka spánku – režim krytu"
|
|||||||
STR_HIDE_BATTERY: "Skryť % batérie"
|
STR_HIDE_BATTERY: "Skryť % batérie"
|
||||||
STR_EXTRA_SPACING: "Dodatočné medzery medzi odsekmi"
|
STR_EXTRA_SPACING: "Dodatočné medzery medzi odsekmi"
|
||||||
STR_TEXT_AA: "Vyhladzovanie textu"
|
STR_TEXT_AA: "Vyhladzovanie textu"
|
||||||
|
STR_TEXT_AA_FULL: "Plné"
|
||||||
|
STR_TEXT_AA_FAST: "Rýchle"
|
||||||
STR_IMAGES: "Obrázky"
|
STR_IMAGES: "Obrázky"
|
||||||
STR_IMAGES_DISPLAY: "Zobraz"
|
STR_IMAGES_DISPLAY: "Zobraz"
|
||||||
STR_IMAGES_PLACEHOLDER: "Rezervované miesto"
|
STR_IMAGES_PLACEHOLDER: "Rezervované miesto"
|
||||||
@@ -73,7 +75,6 @@ STR_IMAGES_SUPPRESS: "Potlačiť"
|
|||||||
STR_SHORT_PWR_BTN: "Krátke stlačenie tlačidla napájania"
|
STR_SHORT_PWR_BTN: "Krátke stlačenie tlačidla napájania"
|
||||||
STR_ORIENTATION: "Orientácia čítania"
|
STR_ORIENTATION: "Orientácia čítania"
|
||||||
STR_SIDE_BTN_LAYOUT: "Rozloženie bočných tlačidiel (čítačka)"
|
STR_SIDE_BTN_LAYOUT: "Rozloženie bočných tlačidiel (čítačka)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Dotykové ovládanie čítačky"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Prispôsobiť predné tlačidlá orientácii"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Prispôsobiť predné tlačidlá orientácii"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Správanie pri dlhom stlačení tlačidla"
|
STR_LONG_PRESS_BEHAVIOR: "Správanie pri dlhom stlačení tlačidla"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "VYP"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "VYP"
|
||||||
@@ -99,7 +100,6 @@ STR_USERNAME: "Používateľské meno"
|
|||||||
STR_PASSWORD: "Heslo"
|
STR_PASSWORD: "Heslo"
|
||||||
STR_SYNC_SERVER_URL: "URL synchronizačného servera"
|
STR_SYNC_SERVER_URL: "URL synchronizačného servera"
|
||||||
STR_DOCUMENT_MATCHING: "Párovanie dokumentov"
|
STR_DOCUMENT_MATCHING: "Párovanie dokumentov"
|
||||||
STR_SEND_METADATA: "Odosielať metadáta dokumentu"
|
|
||||||
STR_AUTHENTICATE: "Overiť"
|
STR_AUTHENTICATE: "Overiť"
|
||||||
STR_KOREADER_USERNAME: "Používateľské meno KOReader"
|
STR_KOREADER_USERNAME: "Používateľské meno KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Heslo KOReader"
|
STR_KOREADER_PASSWORD: "Heslo KOReader"
|
||||||
@@ -336,11 +336,6 @@ STR_SERVER_NAME: "Názov servera"
|
|||||||
STR_NO_SERVERS: "Nie sú nakonfigurované žiadne OPDS servery"
|
STR_NO_SERVERS: "Nie sú nakonfigurované žiadne OPDS servery"
|
||||||
STR_DELETE_SERVER: "Odstrániť server"
|
STR_DELETE_SERVER: "Odstrániť server"
|
||||||
STR_OPDS_SERVERS: "OPDS servery"
|
STR_OPDS_SERVERS: "OPDS servery"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Priečinok sťahovania"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Formát názvu súboru"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autor - Názov"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Názov - Autor"
|
|
||||||
STR_FMT_TITLE: "Názov"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Automatické otáčanie strán zapnuté: "
|
STR_AUTO_TURN_ENABLED: "Automatické otáčanie strán zapnuté: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Automatické otáčanie (strán za minútu)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Automatické otáčanie (strán za minútu)"
|
||||||
STR_MANAGE_FONTS: "Správa písiem"
|
STR_MANAGE_FONTS: "Správa písiem"
|
||||||
@@ -386,5 +381,3 @@ STR_FIRMWARE_WRITE_FAILED: "Zápis firmvéru zlyhal"
|
|||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Nevypínajte zariadenie!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Nevypínajte zariadenie!"
|
||||||
STR_RECOVERY_MODE: "Režim obnovenia"
|
STR_RECOVERY_MODE: "Režim obnovenia"
|
||||||
STR_RECOVERY_MODE_HINT: "Umiestnite firmware.bin do koreňového adresára SD karty a vyberte ho"
|
STR_RECOVERY_MODE_HINT: "Umiestnite firmware.bin do koreňového adresára SD karty a vyberte ho"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Pridať skrytú sieť..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Zadajte názov siete (SSID)"
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ STR_SLEEP_COVER_MODE: "Način naslovnice v spanju"
|
|||||||
STR_HIDE_BATTERY: "Skrij % baterije"
|
STR_HIDE_BATTERY: "Skrij % baterije"
|
||||||
STR_EXTRA_SPACING: "Dodaten razmik med odstavki"
|
STR_EXTRA_SPACING: "Dodaten razmik med odstavki"
|
||||||
STR_TEXT_AA: "Glajenje besedila (AA)"
|
STR_TEXT_AA: "Glajenje besedila (AA)"
|
||||||
|
STR_TEXT_AA_FULL: "Polno"
|
||||||
|
STR_TEXT_AA_FAST: "Hitro"
|
||||||
STR_IMAGES: "Slike"
|
STR_IMAGES: "Slike"
|
||||||
STR_IMAGES_DISPLAY: "Prikaži"
|
STR_IMAGES_DISPLAY: "Prikaži"
|
||||||
STR_IMAGES_PLACEHOLDER: "Oznaka mesta"
|
STR_IMAGES_PLACEHOLDER: "Oznaka mesta"
|
||||||
@@ -72,7 +74,6 @@ STR_IMAGES_SUPPRESS: "Zatdi"
|
|||||||
STR_SHORT_PWR_BTN: "Kratek pritisk na gumb za vklop"
|
STR_SHORT_PWR_BTN: "Kratek pritisk na gumb za vklop"
|
||||||
STR_ORIENTATION: "Orientacija branja"
|
STR_ORIENTATION: "Orientacija branja"
|
||||||
STR_SIDE_BTN_LAYOUT: "Razpored stranskih gumbov"
|
STR_SIDE_BTN_LAYOUT: "Razpored stranskih gumbov"
|
||||||
STR_TOUCH_READER_CONTROLS: "Upravljanje bralnika na dotik"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Usmeri sprednje gumbe"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Usmeri sprednje gumbe"
|
||||||
STR_LONG_PRESS_SKIP: "Dolgi pritisk za preskok poglavja"
|
STR_LONG_PRESS_SKIP: "Dolgi pritisk za preskok poglavja"
|
||||||
STR_FONT_PREVIEW_TEXT: "V kožuščku hudobnega fanta stopiclja mizar"
|
STR_FONT_PREVIEW_TEXT: "V kožuščku hudobnega fanta stopiclja mizar"
|
||||||
@@ -95,7 +96,6 @@ STR_USERNAME: "Uporabniško ime"
|
|||||||
STR_PASSWORD: "Geslo"
|
STR_PASSWORD: "Geslo"
|
||||||
STR_SYNC_SERVER_URL: "URL strežnika za sinhronizacijo"
|
STR_SYNC_SERVER_URL: "URL strežnika za sinhronizacijo"
|
||||||
STR_DOCUMENT_MATCHING: "Ujemanje dokumentov"
|
STR_DOCUMENT_MATCHING: "Ujemanje dokumentov"
|
||||||
STR_SEND_METADATA: "Pošlji metapodatke dokumenta"
|
|
||||||
STR_AUTHENTICATE: "Avtentikacija"
|
STR_AUTHENTICATE: "Avtentikacija"
|
||||||
STR_KOREADER_USERNAME: "KOReader uporabnik"
|
STR_KOREADER_USERNAME: "KOReader uporabnik"
|
||||||
STR_KOREADER_PASSWORD: "KOReader geslo"
|
STR_KOREADER_PASSWORD: "KOReader geslo"
|
||||||
@@ -298,14 +298,7 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
|||||||
STR_SLEEP_NEVER: "Nikoli"
|
STR_SLEEP_NEVER: "Nikoli"
|
||||||
STR_STEP_HINT_FRONT: "Sprednji gumbi:"
|
STR_STEP_HINT_FRONT: "Sprednji gumbi:"
|
||||||
STR_STEP_HINT_SIDE: "Stranski gumbi:"
|
STR_STEP_HINT_SIDE: "Stranski gumbi:"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Mapa za prenose"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Oblika imena datoteke"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Avtor - Naslov"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Naslov - Avtor"
|
|
||||||
STR_FMT_TITLE: "Naslov"
|
|
||||||
STR_SCREENSHOT_BUTTON: "Naredi posnetek zaslona"
|
STR_SCREENSHOT_BUTTON: "Naredi posnetek zaslona"
|
||||||
STR_AUTO_TURN_ENABLED: "Samodejno obračanje: "
|
STR_AUTO_TURN_ENABLED: "Samodejno obračanje: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Samodejno obračanje (strani na minuto)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Samodejno obračanje (strani na minuto)"
|
||||||
STR_TILT_PAGE_TURN: "Obračanje s priklonom"
|
STR_TILT_PAGE_TURN: "Obračanje s priklonom"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Dodaj skrito omrežje..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Vnesite ime omrežja (SSID)"
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ STR_NO_CHAPTERS: "Sin capítulos"
|
|||||||
STR_END_OF_BOOK: "Fin del libro"
|
STR_END_OF_BOOK: "Fin del libro"
|
||||||
STR_EMPTY_CHAPTER: "Capítulo vacío"
|
STR_EMPTY_CHAPTER: "Capítulo vacío"
|
||||||
STR_INDEXING: "Indexando"
|
STR_INDEXING: "Indexando"
|
||||||
STR_INDEX_FAILED: "No se pudo indexar: libro no válido"
|
|
||||||
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"
|
||||||
@@ -29,10 +28,7 @@ STR_WIFI_NETWORKS: "Redes Wi-Fi"
|
|||||||
STR_NO_NETWORKS: "No hay redes disponibles"
|
STR_NO_NETWORKS: "No hay redes disponibles"
|
||||||
STR_NETWORKS_FOUND: "%zu red(es) encontrada(s)"
|
STR_NETWORKS_FOUND: "%zu red(es) encontrada(s)"
|
||||||
STR_SCANNING: "Buscando..."
|
STR_SCANNING: "Buscando..."
|
||||||
STR_FINDING_SAVED_WIFI: "Buscando redes Wi-Fi guardadas..."
|
|
||||||
STR_CONNECTING: "Conectando..."
|
STR_CONNECTING: "Conectando..."
|
||||||
STR_CONNECTING_SAVED_WIFI: "Conectando a una red Wi-Fi guardada..."
|
|
||||||
STR_SHOW_NETWORKS: "Mostrar"
|
|
||||||
STR_CONNECTED: "¡Conectado!"
|
STR_CONNECTED: "¡Conectado!"
|
||||||
STR_CONNECTION_FAILED: "Error de conexión"
|
STR_CONNECTION_FAILED: "Error de conexión"
|
||||||
STR_FORGET_NETWORK: "¿Olvidar la red?"
|
STR_FORGET_NETWORK: "¿Olvidar la red?"
|
||||||
@@ -69,16 +65,15 @@ STR_SLEEP_COVER_MODE: "Modo de pantalla de suspensión"
|
|||||||
STR_HIDE_BATTERY: "Ocultar % de batería"
|
STR_HIDE_BATTERY: "Ocultar % de batería"
|
||||||
STR_EXTRA_SPACING: "Espaciado entre párrafos"
|
STR_EXTRA_SPACING: "Espaciado entre párrafos"
|
||||||
STR_TEXT_AA: "Suavizado de texto"
|
STR_TEXT_AA: "Suavizado de texto"
|
||||||
|
STR_TEXT_AA_FULL: "Completo"
|
||||||
|
STR_TEXT_AA_FAST: "Rápido"
|
||||||
STR_IMAGES: "Imágenes"
|
STR_IMAGES: "Imágenes"
|
||||||
STR_IMAGES_DISPLAY: "Mostrar"
|
STR_IMAGES_DISPLAY: "Mostrar"
|
||||||
STR_IMAGES_PLACEHOLDER: "Reemplazar"
|
STR_IMAGES_PLACEHOLDER: "Reemplazar"
|
||||||
STR_IMAGES_SUPPRESS: "Ocultar"
|
STR_IMAGES_SUPPRESS: "Ocultar"
|
||||||
STR_EOB_HOME: "Inicio"
|
|
||||||
STR_EOB_CONTINUE_WITH: "Continuar con"
|
|
||||||
STR_SHORT_PWR_BTN: "Toque corto del encendido"
|
STR_SHORT_PWR_BTN: "Toque corto del encendido"
|
||||||
STR_ORIENTATION: "Orientación"
|
STR_ORIENTATION: "Orientación"
|
||||||
STR_SIDE_BTN_LAYOUT: "Función botones laterales (lector)"
|
STR_SIDE_BTN_LAYOUT: "Función botones laterales (lector)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Controles táctiles del lector"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botones frontales"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botones frontales"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Al mantener pulsado un botón"
|
STR_LONG_PRESS_BEHAVIOR: "Al mantener pulsado un botón"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "No hacer nada"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "No hacer nada"
|
||||||
@@ -105,7 +100,6 @@ STR_USERNAME: "Usuario"
|
|||||||
STR_PASSWORD: "Contraseña"
|
STR_PASSWORD: "Contraseña"
|
||||||
STR_SYNC_SERVER_URL: "URL del servidor de sinc."
|
STR_SYNC_SERVER_URL: "URL del servidor de sinc."
|
||||||
STR_DOCUMENT_MATCHING: "Coincidencia de doc."
|
STR_DOCUMENT_MATCHING: "Coincidencia de doc."
|
||||||
STR_SEND_METADATA: "Enviar metadatos doc."
|
|
||||||
STR_AUTHENTICATE: "Autenticar"
|
STR_AUTHENTICATE: "Autenticar"
|
||||||
STR_KOREADER_USERNAME: "Usuario de KOReader"
|
STR_KOREADER_USERNAME: "Usuario de KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Contraseña de KOReader"
|
STR_KOREADER_PASSWORD: "Contraseña de KOReader"
|
||||||
@@ -346,11 +340,6 @@ STR_SERVER_NAME: "Nombre de servidor"
|
|||||||
STR_NO_SERVERS: "No se configuraron servidores OPDS"
|
STR_NO_SERVERS: "No se configuraron servidores OPDS"
|
||||||
STR_DELETE_SERVER: "Borrar servidor"
|
STR_DELETE_SERVER: "Borrar servidor"
|
||||||
STR_OPDS_SERVERS: "Servidores OPDS"
|
STR_OPDS_SERVERS: "Servidores OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Carpeta de descargas"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Formato del nombre de archivo"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autor - Título"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Título - Autor"
|
|
||||||
STR_FMT_TITLE: "Título"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Avance activado: "
|
STR_AUTO_TURN_ENABLED: "Avance activado: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Avance auto. (pág./min)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Avance auto. (pág./min)"
|
||||||
STR_MANAGE_FONTS: "Gestionar tipografías"
|
STR_MANAGE_FONTS: "Gestionar tipografías"
|
||||||
@@ -396,5 +385,3 @@ STR_FIRMWARE_WRITE_FAILED: "Falló la escritura del firmware"
|
|||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "¡No apague el dispositivo!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "¡No apague el dispositivo!"
|
||||||
STR_RECOVERY_MODE: "Modo de recuperación"
|
STR_RECOVERY_MODE: "Modo de recuperación"
|
||||||
STR_RECOVERY_MODE_HINT: "Ponga firmware.bin en la raíz de la tarj. SD y selecciónelo"
|
STR_RECOVERY_MODE_HINT: "Ponga firmware.bin en la raíz de la tarj. SD y selecciónelo"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Añadir red oculta..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Introduce el nombre de la red (SSID)"
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ STR_BROWSE_FILES: "Bläddra filer…"
|
|||||||
STR_FILE_TRANSFER: "Filöverföring"
|
STR_FILE_TRANSFER: "Filöverföring"
|
||||||
STR_SETTINGS_TITLE: "Inställningar"
|
STR_SETTINGS_TITLE: "Inställningar"
|
||||||
STR_CONTINUE_READING: "Fortsätt läsa"
|
STR_CONTINUE_READING: "Fortsätt läsa"
|
||||||
STR_RESUME: "Återuppta"
|
|
||||||
STR_NO_OPEN_BOOK: "Ingen öppen bok"
|
STR_NO_OPEN_BOOK: "Ingen öppen bok"
|
||||||
STR_START_READING: "Börja läsa nedan"
|
STR_START_READING: "Börja läsa nedan"
|
||||||
STR_NO_FILES_FOUND: "Inga filer hittades"
|
STR_NO_FILES_FOUND: "Inga filer hittades"
|
||||||
@@ -19,27 +18,17 @@ STR_NO_CHAPTERS: "Inga kapitel"
|
|||||||
STR_END_OF_BOOK: "Slutet på boken"
|
STR_END_OF_BOOK: "Slutet på boken"
|
||||||
STR_EMPTY_CHAPTER: "Tomt kapitel"
|
STR_EMPTY_CHAPTER: "Tomt kapitel"
|
||||||
STR_INDEXING: "Indexerar"
|
STR_INDEXING: "Indexerar"
|
||||||
STR_INDEX_FAILED: "Misslyckades att indexera - ogiltig bok"
|
|
||||||
STR_MEMORY_ERROR: "Minnesfel"
|
STR_MEMORY_ERROR: "Minnesfel"
|
||||||
STR_PAGE_LOAD_ERROR: "Sidladdningsfel"
|
STR_PAGE_LOAD_ERROR: "Sidladdningsfel"
|
||||||
STR_EMPTY_FILE: "Tom fil"
|
STR_EMPTY_FILE: "Tom fil"
|
||||||
STR_OUT_OF_BOUNDS: "Utanför gränserna"
|
STR_OUT_OF_BOUNDS: "Utanför gränserna"
|
||||||
STR_LOADING: "Laddar…"
|
STR_LOADING: "Laddar…"
|
||||||
STR_LOADING_POPUP: "Laddar"
|
STR_LOADING_POPUP: "Laddar"
|
||||||
STR_LOOKUP: "Slå upp"
|
|
||||||
STR_DICT_LOOKING_UP: "Slår upp..."
|
|
||||||
STR_DICT_INDEXING: "Indexerar ordbok..."
|
|
||||||
STR_DICT_NOT_FOUND: "Hittades inte"
|
|
||||||
STR_DICT_NO_DICT_SET: "Ingen ordbok angiven"
|
|
||||||
STR_DICT_ERROR: "Ordboksfel"
|
|
||||||
STR_WIFI_NETWORKS: "Trådlösa nätverk"
|
STR_WIFI_NETWORKS: "Trådlösa nätverk"
|
||||||
STR_NO_NETWORKS: "Inga nätverk funna"
|
STR_NO_NETWORKS: "Inga nätverk funna"
|
||||||
STR_NETWORKS_FOUND: "%zu nätverk funna"
|
STR_NETWORKS_FOUND: "%zu nätverk funna"
|
||||||
STR_SCANNING: "Scannar…"
|
STR_SCANNING: "Scannar…"
|
||||||
STR_FINDING_SAVED_WIFI: "Hittar sparade Wi-Fi..."
|
|
||||||
STR_CONNECTING: "Ansluter…"
|
STR_CONNECTING: "Ansluter…"
|
||||||
STR_CONNECTING_SAVED_WIFI: "Ansluter till sparat Wi-Fi..."
|
|
||||||
STR_SHOW_NETWORKS: "Visa"
|
|
||||||
STR_CONNECTED: "Ansluten!"
|
STR_CONNECTED: "Ansluten!"
|
||||||
STR_CONNECTION_FAILED: "Anslutning misslyckades"
|
STR_CONNECTION_FAILED: "Anslutning misslyckades"
|
||||||
STR_FORGET_NETWORK: "Glöm nätverk?"
|
STR_FORGET_NETWORK: "Glöm nätverk?"
|
||||||
@@ -60,8 +49,6 @@ STR_NETWORK_LEGEND: "* = Krypterad | + = Sparad"
|
|||||||
STR_MAC_ADDRESS: "MAC-adress:"
|
STR_MAC_ADDRESS: "MAC-adress:"
|
||||||
STR_CHECKING_WIFI: "Kontrollerar trådlöst nätverk…"
|
STR_CHECKING_WIFI: "Kontrollerar trådlöst nätverk…"
|
||||||
STR_ENTER_WIFI_PASSWORD: "Skriv in Wi-Fi-lösenord"
|
STR_ENTER_WIFI_PASSWORD: "Skriv in Wi-Fi-lösenord"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Lägg till dolt nätverk..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Ange nätverksnamn (SSID)"
|
|
||||||
STR_TO_PREFIX: "till "
|
STR_TO_PREFIX: "till "
|
||||||
STR_CALIBRE_RECEIVING: "Tar emot:"
|
STR_CALIBRE_RECEIVING: "Tar emot:"
|
||||||
STR_CALIBRE_RECEIVED: "Mottaget:"
|
STR_CALIBRE_RECEIVED: "Mottaget:"
|
||||||
@@ -74,27 +61,25 @@ STR_CAT_READER: "Läsare"
|
|||||||
STR_CAT_CONTROLS: "Kontroller"
|
STR_CAT_CONTROLS: "Kontroller"
|
||||||
STR_CAT_SYSTEM: "System"
|
STR_CAT_SYSTEM: "System"
|
||||||
STR_SLEEP_SCREEN: "Viloskärm"
|
STR_SLEEP_SCREEN: "Viloskärm"
|
||||||
STR_QUICK_RESUME_TIMEOUT: "Snabb återupptagning efter timeout"
|
STR_SEAMLESS_SLEEP: "Sida som viloskärm"
|
||||||
STR_SLEEP_COVER_MODE: "Viloskärmens omslagsläge"
|
STR_SLEEP_COVER_MODE: "Viloskärmens omslagsläge"
|
||||||
STR_HIDE_BATTERY: "Dölj batteriprocent"
|
STR_HIDE_BATTERY: "Dölj batteriprocent"
|
||||||
STR_EXTRA_SPACING: "Extra paragrafmellanrum"
|
STR_EXTRA_SPACING: "Extra paragrafmellanrum"
|
||||||
STR_TEXT_AA: "Textkantutjämning"
|
STR_TEXT_AA: "Textkantutjämning"
|
||||||
|
STR_TEXT_AA_FULL: "Full"
|
||||||
|
STR_TEXT_AA_FAST: "Snabb"
|
||||||
STR_IMAGES: "Bilder"
|
STR_IMAGES: "Bilder"
|
||||||
STR_IMAGES_DISPLAY: "Visa"
|
STR_IMAGES_DISPLAY: "Visa"
|
||||||
STR_IMAGES_PLACEHOLDER: "Platshållare"
|
STR_IMAGES_PLACEHOLDER: "Platshållare"
|
||||||
STR_IMAGES_SUPPRESS: "Dölj"
|
STR_IMAGES_SUPPRESS: "Dölj"
|
||||||
STR_EOB_HOME: "Hem"
|
|
||||||
STR_EOB_CONTINUE_WITH: "Fortsätt med"
|
|
||||||
STR_SHORT_PWR_BTN: "Kort strömknappsklick"
|
STR_SHORT_PWR_BTN: "Kort strömknappsklick"
|
||||||
STR_ORIENTATION: "Läsrikting"
|
STR_ORIENTATION: "Läsrikting"
|
||||||
STR_SIDE_BTN_LAYOUT: "Sidoknappslayout (Läsare)"
|
STR_SIDE_BTN_LAYOUT: "Sidoknappslayout (Läsare)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Pekkontroller i läsaren"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Rikta främre knappar"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Rikta främre knappar"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Beteende vid lång knapptryckning"
|
STR_LONG_PRESS_BEHAVIOR: "Beteende vid lång knapptryckning"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "AV"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "AV"
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "Hoppa över kapitel"
|
STR_LONG_PRESS_BEHAVIOR_SKIP: "Hoppa över kapitel"
|
||||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Ändra orientering"
|
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Ändra orientering"
|
||||||
STR_LONG_PRESS_MENU: "Långtrycksmeny"
|
|
||||||
STR_FONT_PREVIEW_TEXT: "Flygande bäckasiner söka hwila på mjuka tuvor"
|
STR_FONT_PREVIEW_TEXT: "Flygande bäckasiner söka hwila på mjuka tuvor"
|
||||||
STR_FONT_FAMILY: "Eboksläsarens typsnittsfamilj"
|
STR_FONT_FAMILY: "Eboksläsarens typsnittsfamilj"
|
||||||
STR_FONT_SIZE: "Eboksläsarens typsnittsstorlek"
|
STR_FONT_SIZE: "Eboksläsarens typsnittsstorlek"
|
||||||
@@ -115,16 +100,7 @@ STR_USERNAME: "Användarnamn"
|
|||||||
STR_PASSWORD: "Lösenord"
|
STR_PASSWORD: "Lösenord"
|
||||||
STR_SYNC_SERVER_URL: "Synkronisera serveradress"
|
STR_SYNC_SERVER_URL: "Synkronisera serveradress"
|
||||||
STR_DOCUMENT_MATCHING: "Dokumentmatchning"
|
STR_DOCUMENT_MATCHING: "Dokumentmatchning"
|
||||||
STR_SEND_METADATA: "Skicka dokumentmetadata"
|
|
||||||
STR_SYNC_BEHAVIOR: "Synkroniseringsbeteende"
|
|
||||||
STR_ASK_EVERY_TIME: "Fråga varje gång"
|
|
||||||
STR_SMART_SYNC: "Smart synkronisering"
|
|
||||||
STR_AUTHENTICATE: "Autentisera "
|
STR_AUTHENTICATE: "Autentisera "
|
||||||
STR_SIGN_UP: "Registrera dig"
|
|
||||||
STR_CREATING_ACCOUNT: "Skapar konto..."
|
|
||||||
STR_ACCOUNT_CREATED: "Konto skapat"
|
|
||||||
STR_SIGNUP_FAILED: "Registreringen misslyckades"
|
|
||||||
STR_USERNAME_TAKEN: "Användarnamnet är redan registrerat"
|
|
||||||
STR_KOREADER_USERNAME: "KOReader användarnamn"
|
STR_KOREADER_USERNAME: "KOReader användarnamn"
|
||||||
STR_KOREADER_PASSWORD: "KOReader lösenord"
|
STR_KOREADER_PASSWORD: "KOReader lösenord"
|
||||||
STR_FILENAME: "Filnamn"
|
STR_FILENAME: "Filnamn"
|
||||||
@@ -168,9 +144,6 @@ STR_ORIENTATION_INVERTED: "Porträtt 180°"
|
|||||||
STR_LANDSCAPE_CCW: "Landskap moturs"
|
STR_LANDSCAPE_CCW: "Landskap moturs"
|
||||||
STR_PREV_NEXT: "Förra/Nästa"
|
STR_PREV_NEXT: "Förra/Nästa"
|
||||||
STR_NEXT_PREV: "Nästa/Förra"
|
STR_NEXT_PREV: "Nästa/Förra"
|
||||||
STR_KOSYNC: "KOSync"
|
|
||||||
STR_BOOKMARK_OPTION: "Bokmärke"
|
|
||||||
STR_DICTIONARY: "Ordbok"
|
|
||||||
STR_DISABLED: "Inaktiverad"
|
STR_DISABLED: "Inaktiverad"
|
||||||
STR_NOTO_SERIF: "Noto Serif"
|
STR_NOTO_SERIF: "Noto Serif"
|
||||||
STR_NOTO_SANS: "Noto Sans"
|
STR_NOTO_SANS: "Noto Sans"
|
||||||
@@ -284,6 +257,7 @@ STR_THEME_LYRA: "Lyra"
|
|||||||
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
|
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
|
||||||
STR_THEME_LYRA_EXTENDED: "Lyra utökad"
|
STR_THEME_LYRA_EXTENDED: "Lyra utökad"
|
||||||
STR_SUNLIGHT_FADING_FIX: "Fix för solskensmattning"
|
STR_SUNLIGHT_FADING_FIX: "Fix för solskensmattning"
|
||||||
|
STR_QUICK_RESUME_TIMEOUT: "Snabb återupptagning efter timeout"
|
||||||
STR_REMAP_FRONT_BUTTONS: "Ändra frontknappar"
|
STR_REMAP_FRONT_BUTTONS: "Ändra frontknappar"
|
||||||
STR_BOOKMARKS: "Bokmärken"
|
STR_BOOKMARKS: "Bokmärken"
|
||||||
STR_BOOKMARK_ADDED: "Bokmärke tillagt."
|
STR_BOOKMARK_ADDED: "Bokmärke tillagt."
|
||||||
@@ -341,7 +315,6 @@ STR_UPLOAD_LOCAL: "Ladda upp lokala framsteg"
|
|||||||
STR_NO_REMOTE_MSG: "Inga fjärrframsteg funna"
|
STR_NO_REMOTE_MSG: "Inga fjärrframsteg funna"
|
||||||
STR_UPLOAD_PROMPT: "Ladda upp nuvarande position?"
|
STR_UPLOAD_PROMPT: "Ladda upp nuvarande position?"
|
||||||
STR_UPLOAD_SUCCESS: "Framsteg uppladdade!"
|
STR_UPLOAD_SUCCESS: "Framsteg uppladdade!"
|
||||||
STR_ALREADY_SYNCED: "Redan synkroniserad"
|
|
||||||
STR_SYNC_FAILED_MSG: "Synkronisering misslyckades"
|
STR_SYNC_FAILED_MSG: "Synkronisering misslyckades"
|
||||||
STR_SAVE_PROGRESS_FAILED: "Kunde inte spara framsteg"
|
STR_SAVE_PROGRESS_FAILED: "Kunde inte spara framsteg"
|
||||||
STR_SECTION_PREFIX: "Sektion"
|
STR_SECTION_PREFIX: "Sektion"
|
||||||
@@ -350,28 +323,20 @@ STR_BOOK_S_STYLE: "Bokstil"
|
|||||||
STR_EMBEDDED_STYLE: "Inbäddad stil"
|
STR_EMBEDDED_STYLE: "Inbäddad stil"
|
||||||
STR_FOCUS_READING: "Fokusläsning"
|
STR_FOCUS_READING: "Fokusläsning"
|
||||||
STR_OPDS_SERVER_URL: "OPDS-serveradress"
|
STR_OPDS_SERVER_URL: "OPDS-serveradress"
|
||||||
STR_PWR_BTN_FOOTNOTE_BACK: "Snabbåtergång från fotnoter"
|
|
||||||
STR_BACK_SHORT_TO_FILE_BROWSER: "Snabb tillbaka till filläsaren"
|
|
||||||
STR_SET_SLEEP_COVER: "Ställ in omslag"
|
STR_SET_SLEEP_COVER: "Ställ in omslag"
|
||||||
STR_FOOTNOTES: "Fotnoter"
|
STR_FOOTNOTES: "Fotnoter"
|
||||||
STR_NO_FOOTNOTES: "Inga fotnoter på den här sidan"
|
STR_NO_FOOTNOTES: "Inga fotnoter på den här sidan"
|
||||||
STR_LINK: "[länk]"
|
STR_LINK: "[länk]"
|
||||||
STR_SCREENSHOT_BUTTON: "Ta en skärmdump"
|
|
||||||
STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
STR_SLEEP_TIMER_VALUE_FORMAT: "%u min"
|
||||||
STR_SLEEP_NEVER: "Aldrig"
|
STR_SLEEP_NEVER: "Aldrig"
|
||||||
STR_STEP_HINT_FRONT: "Framknappar:"
|
STR_STEP_HINT_FRONT: "Framknappar:"
|
||||||
STR_STEP_HINT_SIDE: "Sidoknappar:"
|
STR_STEP_HINT_SIDE: "Sidoknappar:"
|
||||||
|
STR_SCREENSHOT_BUTTON: "Ta en skärmdump"
|
||||||
STR_ADD_SERVER: "Lägg till server"
|
STR_ADD_SERVER: "Lägg till server"
|
||||||
STR_SERVER_NAME: "Servernamn"
|
STR_SERVER_NAME: "Servernamn"
|
||||||
STR_NO_SERVERS: "Inga OPDS-servrar konfigurerade"
|
STR_NO_SERVERS: "Inga OPDS-servrar konfigurerade"
|
||||||
STR_DELETE_SERVER: "Ta bort server"
|
STR_DELETE_SERVER: "Ta bort server"
|
||||||
STR_OPDS_SERVERS: "OPDS-servrar"
|
STR_OPDS_SERVERS: "OPDS-servrar"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Hämtningsmapp"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Filnamnsformat"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Författare - Titel"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Titel - Författare"
|
|
||||||
STR_FMT_TITLE: "Titel"
|
|
||||||
STR_OPDS_SD_ROOT: "SD-rot"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Automatisk vändning aktiverad: "
|
STR_AUTO_TURN_ENABLED: "Automatisk vändning aktiverad: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Automatisk vändning (sidor per minut)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Automatisk vändning (sidor per minut)"
|
||||||
STR_MANAGE_FONTS: "Hantera teckensnitt"
|
STR_MANAGE_FONTS: "Hantera teckensnitt"
|
||||||
|
|||||||
@@ -64,15 +64,16 @@ STR_SLEEP_COVER_MODE: "Uyku Ekranı Kapak Modu"
|
|||||||
STR_HIDE_BATTERY: "Pil Yüzdesini Gizle"
|
STR_HIDE_BATTERY: "Pil Yüzdesini Gizle"
|
||||||
STR_EXTRA_SPACING: "Ekstra Paragraf Boşluğu"
|
STR_EXTRA_SPACING: "Ekstra Paragraf Boşluğu"
|
||||||
STR_TEXT_AA: "Metin Yumuşatma (AA)"
|
STR_TEXT_AA: "Metin Yumuşatma (AA)"
|
||||||
|
STR_TEXT_AA_FULL: "Tam"
|
||||||
|
STR_TEXT_AA_FAST: "Hızlı"
|
||||||
STR_SHORT_PWR_BTN: "Kısa Güç Tuşu Tıklaması"
|
STR_SHORT_PWR_BTN: "Kısa Güç Tuşu Tıklaması"
|
||||||
STR_ORIENTATION: "Okuma Yönü"
|
STR_ORIENTATION: "Okuma Yönü"
|
||||||
STR_SIDE_BTN_LAYOUT: "Yan Tuş Dizilimi (okuyucu)"
|
STR_SIDE_BTN_LAYOUT: "Yan Tuş Dizilimi (okuyucu)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Dokunmatik okuyucu kontrolleri"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Ön düğmeleri yönlendir"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Ön düğmeleri yönlendir"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Uzun basma tuş davranışı"
|
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "KAPALI"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "Bölüm atlama"
|
STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip"
|
||||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Yön değiştirme"
|
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change"
|
||||||
STR_FONT_PREVIEW_TEXT: "Pijamalı hasta yağız şoföre çabucak güvendi"
|
STR_FONT_PREVIEW_TEXT: "Pijamalı hasta yağız şoföre çabucak güvendi"
|
||||||
STR_FONT_FAMILY: "Okuyucu Yazı Tipi Ailesi"
|
STR_FONT_FAMILY: "Okuyucu Yazı Tipi Ailesi"
|
||||||
STR_FONT_SIZE: "Arayüz Yazı Boyutu"
|
STR_FONT_SIZE: "Arayüz Yazı Boyutu"
|
||||||
@@ -93,7 +94,6 @@ STR_USERNAME: "Kullanıcı Adı"
|
|||||||
STR_PASSWORD: "Şifre"
|
STR_PASSWORD: "Şifre"
|
||||||
STR_SYNC_SERVER_URL: "Senkronizasyon Sunucu Adresi"
|
STR_SYNC_SERVER_URL: "Senkronizasyon Sunucu Adresi"
|
||||||
STR_DOCUMENT_MATCHING: "Belge Eşleştirme"
|
STR_DOCUMENT_MATCHING: "Belge Eşleştirme"
|
||||||
STR_SEND_METADATA: "Belge üst verisi gönder"
|
|
||||||
STR_AUTHENTICATE: "Kimlik Doğrula"
|
STR_AUTHENTICATE: "Kimlik Doğrula"
|
||||||
STR_KOREADER_USERNAME: "KOReader Kullanıcı Adı"
|
STR_KOREADER_USERNAME: "KOReader Kullanıcı Adı"
|
||||||
STR_KOREADER_PASSWORD: "KOReader Şifresi"
|
STR_KOREADER_PASSWORD: "KOReader Şifresi"
|
||||||
@@ -292,11 +292,6 @@ STR_SLEEP_TIMER_VALUE_FORMAT: "%u dak"
|
|||||||
STR_SLEEP_NEVER: "Asla"
|
STR_SLEEP_NEVER: "Asla"
|
||||||
STR_STEP_HINT_FRONT: "Ön tuşlar:"
|
STR_STEP_HINT_FRONT: "Ön tuşlar:"
|
||||||
STR_STEP_HINT_SIDE: "Yan tuşlar:"
|
STR_STEP_HINT_SIDE: "Yan tuşlar:"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "İndirme klasörü"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Dosya adı biçimi"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Yazar - Başlık"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Başlık - Yazar"
|
|
||||||
STR_FMT_TITLE: "Başlık"
|
|
||||||
STR_NO_FILES_FOUND: "Dosya bulunamadı"
|
STR_NO_FILES_FOUND: "Dosya bulunamadı"
|
||||||
STR_NO_FOOTNOTES: "Bu sayfada dipnot yok"
|
STR_NO_FOOTNOTES: "Bu sayfada dipnot yok"
|
||||||
STR_PREVIEW: "Önizleme"
|
STR_PREVIEW: "Önizleme"
|
||||||
@@ -310,91 +305,3 @@ STR_SELECTED: "Seçili"
|
|||||||
STR_SHOW: "Göster"
|
STR_SHOW: "Göster"
|
||||||
STR_TITLE: "Başlık"
|
STR_TITLE: "Başlık"
|
||||||
STR_TILT_PAGE_TURN: "Eğerek sayfa çevirme"
|
STR_TILT_PAGE_TURN: "Eğerek sayfa çevirme"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Gizli ağ ekle..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Ağ adını girin (SSID)"
|
|
||||||
STR_ADD_SERVER: "Sunucu Ekle"
|
|
||||||
STR_BOOKMARKS: "Yer İmleri"
|
|
||||||
STR_BOOKMARK_ADDED: "Yer imi eklendi."
|
|
||||||
STR_BOOKMARK_OPTION: "Yer İmi"
|
|
||||||
STR_BOTTOM: "Alt"
|
|
||||||
STR_CLOCK: "Saat"
|
|
||||||
STR_CLOCK_FORMAT: "Saat Biçimi"
|
|
||||||
STR_CLOCK_FORMAT_12H: "12 saat"
|
|
||||||
STR_CLOCK_FORMAT_24H: "24 saat"
|
|
||||||
STR_CLOCK_SYNC: "Saati Eşitle"
|
|
||||||
STR_CLOCK_SYNCED: "Saat Eşitlendi"
|
|
||||||
STR_CLOCK_SYNCING: "NTP'den eşitleniyor..."
|
|
||||||
STR_CLOCK_SYNC_FAIL: "Eşitleme başarısız"
|
|
||||||
STR_CLOCK_SYNC_NOW: "Saati şimdi eşitle"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI: "Wi-Fi bağlı değil"
|
|
||||||
STR_CLOCK_SYNC_NO_WIFI_HINT: "Önce Wi-Fi'ye bağlanın, sonra tekrar deneyin."
|
|
||||||
STR_CLOCK_SYNC_OK: "Saat eşitlendi"
|
|
||||||
STR_CLOCK_UTC_OFFSET: "Saat UTC Farkı"
|
|
||||||
STR_CONFIRM_DELETE_BOOKMARK: "Bu yer imi silinsin mi?"
|
|
||||||
STR_CONNECTING_SAVED_WIFI: "Kayıtlı Wi-Fi'ye bağlanılıyor..."
|
|
||||||
STR_CRASH_DESCRIPTION: "Ayrıntılı rapor crash_report.txt dosyasına kaydedildi. Lütfen hata bildiriminize bu dosyayı ekleyin."
|
|
||||||
STR_CRASH_NO_REASON: "(Neden kaydedilmedi)"
|
|
||||||
STR_CRASH_REASON: "Çökme nedeni:"
|
|
||||||
STR_CRASH_TITLE: "Sistem Çökmesi"
|
|
||||||
STR_CURRENT_TIME: "Şu anki saat:"
|
|
||||||
STR_DELETE_SERVER: "Sunucuyu Sil"
|
|
||||||
STR_DISABLED: "Devre dışı"
|
|
||||||
STR_DOWNLOAD_ALL: "Tümünü İndir"
|
|
||||||
STR_EOB_CONTINUE_WITH: "Şununla devam et"
|
|
||||||
STR_EOB_HOME: "Ana Ekran"
|
|
||||||
STR_FINDING_SAVED_WIFI: "Kayıtlı Wi-Fi aranıyor..."
|
|
||||||
STR_FIRMWARE_FILE_OPEN_FAILED: "Dosya açılamıyor"
|
|
||||||
STR_FIRMWARE_TOO_LARGE: "Firmware, bölümlemeye sığmıyor"
|
|
||||||
STR_FIRMWARE_TOO_SMALL: "Firmware dosyası çok küçük"
|
|
||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Cihazı kapatmayın!"
|
|
||||||
STR_FIRMWARE_UPDATE_PROMPT: "Firmware güncellensin mi?"
|
|
||||||
STR_FIRMWARE_WRITE_FAILED: "Firmware yazılamadı"
|
|
||||||
STR_FONT_BROWSER: "Yazı Tipi Tarayıcısı"
|
|
||||||
STR_FONT_INSTALLED: "Yazı tipi yüklendi!"
|
|
||||||
STR_FONT_INSTALL_FAILED: "Yazı tipi yüklenemedi"
|
|
||||||
STR_FORCE_REFRESH: "Ekranı Yenile"
|
|
||||||
STR_INDEX_FAILED: "Endeksleme başarısız - geçersiz kitap"
|
|
||||||
STR_INSTALLED: "Yüklü"
|
|
||||||
STR_INVALID_FIRMWARE: "Geçersiz firmware dosyası"
|
|
||||||
STR_KB_HINT_CLEAR_TEXT: "Tüm metni silmek için DEL tuşunu basılı tutun"
|
|
||||||
STR_KB_HINT_EDIT_ENTRY: "Girdiyi düzenlemek için UP tuşunu basılı tutun"
|
|
||||||
STR_KB_HINT_EXIT_URL_MODE: "URL modundan çıkmak için ABC'ye basın"
|
|
||||||
STR_KB_HINT_HIDE_PASSWORD: "Şifreyi gizlemek için RIGHT'ı basılı tutup [***] tuşuna basın"
|
|
||||||
STR_KB_HINT_LOWER_SECONDARY: "Küçük harf veya ikincil karakter için SELECT'i basılı tutun"
|
|
||||||
STR_KB_HINT_MOVE_CURSOR: "İmleci taşımak için LEFT veya RIGHT'a basın"
|
|
||||||
STR_KB_HINT_RETURN_CURSOR: "İmleç konumuna dönmek için LEFT'e basın"
|
|
||||||
STR_KB_HINT_RETURN_KEYBOARD: "Klavyeye dönmek için DOWN'a basın"
|
|
||||||
STR_KB_HINT_SECONDARY_CHAR: "İkincil karakter için SELECT'i basılı tutun"
|
|
||||||
STR_KB_HINT_SHOW_PASSWORD: "Şifreyi göstermek için RIGHT'ı basılı tutup [abc] tuşuna basın"
|
|
||||||
STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Şifreyi gizlemek için [***] tuşuna basın"
|
|
||||||
STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Şifreyi göstermek için [abc] tuşuna basın"
|
|
||||||
STR_KB_HINT_UPPER_SECONDARY: "BÜYÜK harf veya ikincil karakter için SELECT'i basılı tutun"
|
|
||||||
STR_KB_HINT_URL_SNIPPETS: "Hazır kalıplar için URL'ye basın"
|
|
||||||
STR_KB_TIPS: "İpuçları:"
|
|
||||||
STR_KOSYNC: "KOSync"
|
|
||||||
STR_LOADING_FONT_LIST: "Yazı tipi listesi yükleniyor..."
|
|
||||||
STR_LONG_PRESS_MENU: "Uzun Basma Menüsü"
|
|
||||||
STR_MANAGE_FONTS: "Yazı Tiplerini Yönet"
|
|
||||||
STR_NEXT_FIELD: "Sonraki"
|
|
||||||
STR_NEXT_PAGE: "Sonraki Sayfa »"
|
|
||||||
STR_NO_BIN_FILES: ".bin dosyası bulunamadı"
|
|
||||||
STR_NO_FONTS_AVAILABLE: "Kullanılabilir yazı tipi yok"
|
|
||||||
STR_NO_SERVERS: "Yapılandırılmış OPDS sunucusu yok"
|
|
||||||
STR_OPDS_SERVERS: "OPDS Sunucuları"
|
|
||||||
STR_PREV_PAGE: "« Önceki Sayfa"
|
|
||||||
STR_PWR_BTN_FOOTNOTE_BACK: "Dipnottan hızlı dönüş"
|
|
||||||
STR_RECOVERY_MODE: "Kurtarma Modu"
|
|
||||||
STR_RECOVERY_MODE_HINT: "firmware.bin dosyasını SD kartın köküne koyup seçin"
|
|
||||||
STR_RESTARTING_HINT: "Yeniden başlatılıyor... Cihaz yeniden başlamazsa güç düğmesini birkaç saniye basılı tutun."
|
|
||||||
STR_SD_FIRMWARE_UPDATE: "SD Karttan Firmware Güncelleme"
|
|
||||||
STR_SEARCH: "Ara"
|
|
||||||
STR_SELECT_FIRMWARE_FILE: "Firmware dosyası seçin (.bin)"
|
|
||||||
STR_SERVER_NAME: "Sunucu Adı"
|
|
||||||
STR_SET_SLEEP_COVER: "Kapak Ayarla"
|
|
||||||
STR_SHOW_NETWORKS: "Göster"
|
|
||||||
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
|
|
||||||
STR_TOP: "Üst"
|
|
||||||
STR_UPDATE_ALL: "Tümünü Güncelle"
|
|
||||||
STR_UPDATE_AVAILABLE: "Güncelle"
|
|
||||||
STR_VALIDATING_FIRMWARE: "Firmware doğrulanıyor..."
|
|
||||||
STR_XTC_STATUS_BAR: "XTC Durum Çubuğu"
|
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ STR_SLEEP_COVER_MODE: "Режим заповнення"
|
|||||||
STR_HIDE_BATTERY: "Приховати % батареї"
|
STR_HIDE_BATTERY: "Приховати % батареї"
|
||||||
STR_EXTRA_SPACING: "Додатковий інтервал між абзацами"
|
STR_EXTRA_SPACING: "Додатковий інтервал між абзацами"
|
||||||
STR_TEXT_AA: "Згладжування тексту"
|
STR_TEXT_AA: "Згладжування тексту"
|
||||||
|
STR_TEXT_AA_FULL: "Повне"
|
||||||
|
STR_TEXT_AA_FAST: "Швидке"
|
||||||
STR_IMAGES: "Зображення"
|
STR_IMAGES: "Зображення"
|
||||||
STR_IMAGES_DISPLAY: "Показати"
|
STR_IMAGES_DISPLAY: "Показати"
|
||||||
STR_IMAGES_PLACEHOLDER: "Заглушка"
|
STR_IMAGES_PLACEHOLDER: "Заглушка"
|
||||||
@@ -73,7 +75,6 @@ STR_IMAGES_SUPPRESS: "Приховати"
|
|||||||
STR_SHORT_PWR_BTN: "Короткий натиск кн. живл."
|
STR_SHORT_PWR_BTN: "Короткий натиск кн. живл."
|
||||||
STR_ORIENTATION: "Орієнтація читання"
|
STR_ORIENTATION: "Орієнтація читання"
|
||||||
STR_SIDE_BTN_LAYOUT: "Схема бічних кнопок"
|
STR_SIDE_BTN_LAYOUT: "Схема бічних кнопок"
|
||||||
STR_TOUCH_READER_CONTROLS: "Сенсорне керування читанням"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Орієнтувати передні кнопки"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Орієнтувати передні кнопки"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Поведінка при довгому настику"
|
STR_LONG_PRESS_BEHAVIOR: "Поведінка при довгому настику"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Немає"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Немає"
|
||||||
@@ -99,7 +100,6 @@ STR_USERNAME: "Ім'я користувача"
|
|||||||
STR_PASSWORD: "Пароль"
|
STR_PASSWORD: "Пароль"
|
||||||
STR_SYNC_SERVER_URL: "URL для синхронізації"
|
STR_SYNC_SERVER_URL: "URL для синхронізації"
|
||||||
STR_DOCUMENT_MATCHING: "Порівняння документів"
|
STR_DOCUMENT_MATCHING: "Порівняння документів"
|
||||||
STR_SEND_METADATA: "Надсилати метадані"
|
|
||||||
STR_AUTHENTICATE: "Автентифікувати"
|
STR_AUTHENTICATE: "Автентифікувати"
|
||||||
STR_KOREADER_USERNAME: "Ім'я користувача KOReader"
|
STR_KOREADER_USERNAME: "Ім'я користувача KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Пароль KOReader"
|
STR_KOREADER_PASSWORD: "Пароль KOReader"
|
||||||
@@ -337,11 +337,6 @@ STR_SERVER_NAME: "Назва сервера"
|
|||||||
STR_NO_SERVERS: "Не налаштовано жодного сервера OPDS"
|
STR_NO_SERVERS: "Не налаштовано жодного сервера OPDS"
|
||||||
STR_DELETE_SERVER: "Видалити сервер"
|
STR_DELETE_SERVER: "Видалити сервер"
|
||||||
STR_OPDS_SERVERS: "Сервери OPDS"
|
STR_OPDS_SERVERS: "Сервери OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Папка завантажень"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Формат імені файлу"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Автор - Назва"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Назва - Автор"
|
|
||||||
STR_FMT_TITLE: "Назва"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Автоперегортання увімк: "
|
STR_AUTO_TURN_ENABLED: "Автоперегортання увімк: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Автоперегортання (ст/хв)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Автоперегортання (ст/хв)"
|
||||||
STR_MANAGE_FONTS: "Керування шрифтами"
|
STR_MANAGE_FONTS: "Керування шрифтами"
|
||||||
@@ -387,5 +382,3 @@ STR_FIRMWARE_WRITE_FAILED: "Помилка запису прошивки"
|
|||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Не вимикайте пристрій!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Не вимикайте пристрій!"
|
||||||
STR_RECOVERY_MODE: "Режим відновлення"
|
STR_RECOVERY_MODE: "Режим відновлення"
|
||||||
STR_RECOVERY_MODE_HINT: "Помістіть firmware.bin у корінь SD-карти та виберіть його"
|
STR_RECOVERY_MODE_HINT: "Помістіть firmware.bin у корінь SD-карти та виберіть його"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Додати приховану мережу..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Введіть назву мережі (SSID)"
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ STR_BOOTING: "ARRENCANT"
|
|||||||
STR_SLEEPING: "ENTRANT EN REPÒS"
|
STR_SLEEPING: "ENTRANT EN REPÒS"
|
||||||
STR_ENTERING_SLEEP: "Entrant en repòs"
|
STR_ENTERING_SLEEP: "Entrant en repòs"
|
||||||
STR_BROWSE_FILES: "Explora arxius"
|
STR_BROWSE_FILES: "Explora arxius"
|
||||||
STR_FILE_TRANSFER: "Transferència d'arxius"
|
STR_FILE_TRANSFER: "Transferència"
|
||||||
STR_SETTINGS_TITLE: "Configuració"
|
STR_SETTINGS_TITLE: "Configuració"
|
||||||
STR_CONTINUE_READING: "Continua llegint"
|
STR_CONTINUE_READING: "Continua llegint"
|
||||||
STR_NO_OPEN_BOOK: "Cap llibre obert"
|
STR_NO_OPEN_BOOK: "Cap llibre obert"
|
||||||
@@ -18,7 +18,6 @@ STR_NO_CHAPTERS: "Sense capítols"
|
|||||||
STR_END_OF_BOOK: "Final del llibre"
|
STR_END_OF_BOOK: "Final del llibre"
|
||||||
STR_EMPTY_CHAPTER: "Capítol buit"
|
STR_EMPTY_CHAPTER: "Capítol buit"
|
||||||
STR_INDEXING: "S'està indexant"
|
STR_INDEXING: "S'està indexant"
|
||||||
STR_INDEX_FAILED: "No s'ha pogut indexar: llibre no vàlid"
|
|
||||||
STR_MEMORY_ERROR: "Error de memòria"
|
STR_MEMORY_ERROR: "Error de memòria"
|
||||||
STR_PAGE_LOAD_ERROR: "Error en carregar la pàgina"
|
STR_PAGE_LOAD_ERROR: "Error en carregar la pàgina"
|
||||||
STR_EMPTY_FILE: "Arxiu buit"
|
STR_EMPTY_FILE: "Arxiu buit"
|
||||||
@@ -29,66 +28,62 @@ STR_WIFI_NETWORKS: "Xarxes Wi-Fi"
|
|||||||
STR_NO_NETWORKS: "No s'han trobat xarxes"
|
STR_NO_NETWORKS: "No s'han trobat xarxes"
|
||||||
STR_NETWORKS_FOUND: "%zu xarxes trobades"
|
STR_NETWORKS_FOUND: "%zu xarxes trobades"
|
||||||
STR_SCANNING: "S'està escanejant..."
|
STR_SCANNING: "S'està escanejant..."
|
||||||
STR_FINDING_SAVED_WIFI: "S'estan buscant xarxes Wi-Fi guardades..."
|
|
||||||
STR_CONNECTING: "S'està connectant..."
|
STR_CONNECTING: "S'està connectant..."
|
||||||
STR_CONNECTING_SAVED_WIFI: "S'està connectant a una xarxa Wi-Fi guardada..."
|
|
||||||
STR_SHOW_NETWORKS: "Mostra"
|
|
||||||
STR_CONNECTED: "S'ha connectat!"
|
STR_CONNECTED: "S'ha connectat!"
|
||||||
STR_CONNECTION_FAILED: "Error de connexió"
|
STR_CONNECTION_FAILED: "Error de connexió"
|
||||||
STR_FORGET_NETWORK: "Vols oblidar esta xarxa?"
|
STR_FORGET_NETWORK: "Voleu oblidar esta xarxa?"
|
||||||
STR_SAVE_PASSWORD: "Vols guardar la contrasenya per a la pròxima vegada?"
|
STR_SAVE_PASSWORD: "Voleu guardar la contrasenya per a la pròxima vegada?"
|
||||||
STR_PRESS_OK_SCAN: "Prem OK per tornar a escanejar"
|
STR_PRESS_OK_SCAN: "Premeu OK per tornar a escanejar"
|
||||||
STR_JOIN_NETWORK: "Unix-te a una xarxa"
|
STR_JOIN_NETWORK: "Uneix-te a una xarxa"
|
||||||
STR_CREATE_HOTSPOT: "Crea un punt d'accés"
|
STR_CREATE_HOTSPOT: "Crea un punt d'accés"
|
||||||
STR_JOIN_DESC: "Connecta't a una xarxa Wi-Fi existent"
|
STR_JOIN_DESC: "Connecta't a una xarxa Wi-Fi existent"
|
||||||
STR_HOTSPOT_DESC: "Crea una xarxa Wi-Fi per unir-s'hi"
|
STR_HOTSPOT_DESC: "Crea una xarxa Wi-Fi per unir-s'hi"
|
||||||
STR_STARTING_HOTSPOT: "S'està iniciant el punt d'accés..."
|
STR_STARTING_HOTSPOT: "S'està iniciant el punt d'accés..."
|
||||||
STR_HOTSPOT_MODE: "Mode de punt d'accés"
|
STR_HOTSPOT_MODE: "Mode de punt d'accés"
|
||||||
STR_CONNECT_WIFI_HINT: "Connecta el dispositiu a esta xarxa Wi-Fi"
|
STR_CONNECT_WIFI_HINT: "Connecteu el dispositiu a esta xarxa Wi-Fi"
|
||||||
STR_OPEN_URL_HINT: "Obri este URL al navegador"
|
STR_OPEN_URL_HINT: "Obriu este URL al navegador"
|
||||||
STR_OR_HTTP_PREFIX: "o http://"
|
STR_OR_HTTP_PREFIX: "o http://"
|
||||||
STR_SCAN_QR_HINT: "o escaneja el codi QR amb el telèfon:"
|
STR_SCAN_QR_HINT: "o escanegeu el codi QR amb el telèfon:"
|
||||||
STR_CALIBRE_WIRELESS: "Calibre sense fils"
|
STR_CALIBRE_WIRELESS: "Calibre sense fils"
|
||||||
STR_NETWORK_LEGEND: "* = Encriptat | + = Guardat"
|
STR_NETWORK_LEGEND: "* = Encriptat | + = Guardat"
|
||||||
STR_MAC_ADDRESS: "Adreça MAC:"
|
STR_MAC_ADDRESS: "Adreça MAC:"
|
||||||
STR_CHECKING_WIFI: "S'està comprovant el Wi-Fi..."
|
STR_CHECKING_WIFI: "S'està comprovant el Wi-Fi..."
|
||||||
STR_ENTER_WIFI_PASSWORD: "Introduïx la contrasenya Wi-Fi"
|
STR_ENTER_WIFI_PASSWORD: "Introduïu la contrasenya Wi-Fi"
|
||||||
STR_TO_PREFIX: "a "
|
STR_TO_PREFIX: "a "
|
||||||
STR_CALIBRE_RECEIVING: "S'està rebent: "
|
STR_CALIBRE_RECEIVING: "S'està rebent: "
|
||||||
STR_CALIBRE_RECEIVED: "S'ha rebut: "
|
STR_CALIBRE_RECEIVED: "S'ha rebut: "
|
||||||
STR_CALIBRE_INSTRUCTION_1: "1) Instal·la el connector CrossPoint Reader"
|
STR_CALIBRE_INSTRUCTION_1: "1) Instal·leu el connector CrossPoint Reader"
|
||||||
STR_CALIBRE_INSTRUCTION_2: "2) Estigues a la mateixa xarxa Wi-Fi"
|
STR_CALIBRE_INSTRUCTION_2: "2) Estigueu a la mateixa xarxa Wi-Fi"
|
||||||
STR_CALIBRE_INSTRUCTION_3: "3) A Calibre: \"Envia a un dispositiu\""
|
STR_CALIBRE_INSTRUCTION_3: "3) A Calibre: \"Envia a un dispositiu\""
|
||||||
STR_CALIBRE_INSTRUCTION_4: "\"Mantín esta pantalla oberta mentre s'envia\""
|
STR_CALIBRE_INSTRUCTION_4: "\"Mantingueu esta pantalla oberta mentre s'envia\""
|
||||||
STR_CAT_DISPLAY: "Visualització"
|
STR_CAT_DISPLAY: "Visualització"
|
||||||
STR_CAT_READER: "Lector"
|
STR_CAT_READER: "Lector"
|
||||||
STR_CAT_CONTROLS: "Controls"
|
STR_CAT_CONTROLS: "Controls"
|
||||||
STR_CAT_SYSTEM: "Sistema"
|
STR_CAT_SYSTEM: "Sistema"
|
||||||
STR_SLEEP_SCREEN: "Pantalla de repòs"
|
STR_SLEEP_SCREEN: "Pantalla de repòs"
|
||||||
STR_QUICK_RESUME_TIMEOUT: "Represa ràpida per inactivitat"
|
STR_QUICK_RESUME_TIMEOUT: "Represa ràpida després del temps"
|
||||||
STR_SLEEP_COVER_MODE: "Ajust de la portada en repòs"
|
STR_SLEEP_COVER_MODE: "Mode de pantalla de repòs"
|
||||||
STR_HIDE_BATTERY: "Oculta el % de bateria"
|
STR_HIDE_BATTERY: "Oculta el % de bateria"
|
||||||
STR_EXTRA_SPACING: "Espaiat de paràgraf extra"
|
STR_EXTRA_SPACING: "Espaiat de paràgraf extra"
|
||||||
STR_TEXT_AA: "Antialiàsing del text"
|
STR_TEXT_AA: "Antialiàsing del text"
|
||||||
|
STR_TEXT_AA_FULL: "Complet"
|
||||||
|
STR_TEXT_AA_FAST: "Ràpid"
|
||||||
STR_IMAGES: "Imatges"
|
STR_IMAGES: "Imatges"
|
||||||
STR_IMAGES_DISPLAY: "Mostrar"
|
STR_IMAGES_DISPLAY: "Mostrar"
|
||||||
STR_IMAGES_PLACEHOLDER: "Text alternatiu"
|
STR_IMAGES_PLACEHOLDER: "Text de mostra"
|
||||||
STR_IMAGES_SUPPRESS: "Eliminar"
|
STR_IMAGES_SUPPRESS: "Eliminar"
|
||||||
STR_EOB_HOME: "Inici"
|
STR_SHORT_PWR_BTN: "Clic curt del botó d'engegada"
|
||||||
STR_EOB_CONTINUE_WITH: "Continua amb"
|
|
||||||
STR_SHORT_PWR_BTN: "Pulsació curta del botó d'engegada"
|
|
||||||
STR_ORIENTATION: "Orientació de lectura"
|
STR_ORIENTATION: "Orientació de lectura"
|
||||||
STR_SIDE_BTN_LAYOUT: "Disposició botons laterals"
|
STR_SIDE_BTN_LAYOUT: "Disposició botons laterals"
|
||||||
STR_TOUCH_READER_CONTROLS: "Controls tàctils del lector"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botons frontals"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientar botons frontals"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Acció en mantindre premut un botó"
|
STR_LONG_PRESS_BEHAVIOR: "Comportament de prémer llargament el botó"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
|
||||||
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítols"
|
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítols"
|
||||||
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Canvi d'orientació"
|
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Canvi d'orientació"
|
||||||
STR_LONG_PRESS_MENU: "Funció de pulsació llarga"
|
STR_LONG_PRESS_MENU: "Funció de pulsació llarga"
|
||||||
STR_FONT_PREVIEW_TEXT: "Jove xef, porti whisky amb quinze glaçons d'hidrogen, coi!"
|
STR_FONT_PREVIEW_TEXT: "Jove xef, porti whisky amb quinze glaçons d'hidrogen, coi!"
|
||||||
STR_FONT_FAMILY: "Família de fonts"
|
STR_FONT_FAMILY: "Família de fonts"
|
||||||
STR_FONT_SIZE: "Cos de lletra del lector"
|
STR_FONT_SIZE: "Grandària de la lletra (UI)"
|
||||||
STR_LINE_SPACING: "Interlineat del lector"
|
STR_LINE_SPACING: "Interlineat del lector"
|
||||||
STR_SCREEN_MARGIN: "Marge de pantalla del lector"
|
STR_SCREEN_MARGIN: "Marge de pantalla del lector"
|
||||||
STR_PARA_ALIGNMENT: "Alineació de paràgrafs del lector"
|
STR_PARA_ALIGNMENT: "Alineació de paràgrafs del lector"
|
||||||
@@ -101,22 +96,21 @@ STR_STEP_HINT_SIDE: "Botons laterals:"
|
|||||||
STR_SHOW_HIDDEN_FILES: "Mostra arxius ocults"
|
STR_SHOW_HIDDEN_FILES: "Mostra arxius ocults"
|
||||||
STR_REMOVE_READ_FROM_RECENTS: "Esborra els llibres llegits de la llista de recents"
|
STR_REMOVE_READ_FROM_RECENTS: "Esborra els llibres llegits de la llista de recents"
|
||||||
STR_MOVE_FINISHED_TO_READ: "Mou els llibres acabats a la carpeta Read"
|
STR_MOVE_FINISHED_TO_READ: "Mou els llibres acabats a la carpeta Read"
|
||||||
STR_REFRESH_FREQ: "Freqüència d'actualització"
|
STR_REFRESH_FREQ: "Freqüència de refresc"
|
||||||
STR_KOREADER_SYNC: "Sincronització del KOReader"
|
STR_KOREADER_SYNC: "Sincronització del KOReader"
|
||||||
STR_CHECK_UPDATES: "Comprova si hi ha actualitzacions"
|
STR_CHECK_UPDATES: "Comprova si hi ha actualitzacions"
|
||||||
STR_LANGUAGE: "Llengua"
|
STR_LANGUAGE: "Idioma"
|
||||||
STR_CLEAR_READING_CACHE: "Esborra la memòria cau de lectura"
|
STR_CLEAR_READING_CACHE: "Esborra la memòria cau de lectura"
|
||||||
STR_USERNAME: "Nom d'usuari"
|
STR_USERNAME: "Nom d'usuari"
|
||||||
STR_PASSWORD: "Contrasenya"
|
STR_PASSWORD: "Contrasenya"
|
||||||
STR_SYNC_SERVER_URL: "URL del servidor de sincronització"
|
STR_SYNC_SERVER_URL: "URL del servidor de sincronització"
|
||||||
STR_DOCUMENT_MATCHING: "Coincidència de documents"
|
STR_DOCUMENT_MATCHING: "Coincidència de documents"
|
||||||
STR_SEND_METADATA: "Envia metadades del document"
|
|
||||||
STR_AUTHENTICATE: "Autentica"
|
STR_AUTHENTICATE: "Autentica"
|
||||||
STR_KOREADER_USERNAME: "Nom d'usuari del KOReader"
|
STR_KOREADER_USERNAME: "Nom d'usuari del KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Contrasenya del KOReader"
|
STR_KOREADER_PASSWORD: "Contrasenya del KOReader"
|
||||||
STR_FILENAME: "Nom d'arxiu"
|
STR_FILENAME: "Nom d'arxiu"
|
||||||
STR_BINARY: "Binari"
|
STR_BINARY: "Binari"
|
||||||
STR_SET_CREDENTIALS_FIRST: "Establix les credencials primer"
|
STR_SET_CREDENTIALS_FIRST: "Estableix les credencials primer"
|
||||||
STR_WIFI_CONN_FAILED: "Connexió Wi-Fi fallida"
|
STR_WIFI_CONN_FAILED: "Connexió Wi-Fi fallida"
|
||||||
STR_AUTHENTICATING: "S'està autenticant..."
|
STR_AUTHENTICATING: "S'està autenticant..."
|
||||||
STR_AUTH_SUCCESS: "Autenticació correcta!"
|
STR_AUTH_SUCCESS: "Autenticació correcta!"
|
||||||
@@ -156,7 +150,7 @@ STR_PREV_NEXT: "Anterior/Següent"
|
|||||||
STR_NEXT_PREV: "Següent/Anterior"
|
STR_NEXT_PREV: "Següent/Anterior"
|
||||||
STR_KOSYNC: "KOSync"
|
STR_KOSYNC: "KOSync"
|
||||||
STR_BOOKMARK_OPTION: "Punt de llibre"
|
STR_BOOKMARK_OPTION: "Punt de llibre"
|
||||||
STR_DISABLED: "Sense funció"
|
STR_DISABLED: "Desactivats"
|
||||||
STR_NOTO_SERIF: "Noto Serif"
|
STR_NOTO_SERIF: "Noto Serif"
|
||||||
STR_NOTO_SANS: "Noto Sans"
|
STR_NOTO_SANS: "Noto Sans"
|
||||||
STR_SMALL: "Petita"
|
STR_SMALL: "Petita"
|
||||||
@@ -184,16 +178,16 @@ STR_UPDATING: "S'està actualitzant..."
|
|||||||
STR_NO_UPDATE: "No hi ha actualitzacions disponibles"
|
STR_NO_UPDATE: "No hi ha actualitzacions disponibles"
|
||||||
STR_UPDATE_FAILED: "Ha fallat l'actualització"
|
STR_UPDATE_FAILED: "Ha fallat l'actualització"
|
||||||
STR_UPDATE_COMPLETE: "Actualització completada"
|
STR_UPDATE_COMPLETE: "Actualització completada"
|
||||||
STR_POWER_ON_HINT: "Prem i mantín premut el botó d'encesa per tornar a engegar"
|
STR_POWER_ON_HINT: "Premeu i manteniu premut el botó d'encesa per tornar a engegar"
|
||||||
STR_NO_ENTRIES: "No s'ha trobat cap entrada"
|
STR_NO_ENTRIES: "No s'ha trobat cap entrada"
|
||||||
STR_DOWNLOADING: "S'està baixant..."
|
STR_DOWNLOADING: "S'està baixant..."
|
||||||
STR_DOWNLOAD_FAILED: "Ha fallat la baixada"
|
STR_DOWNLOAD_FAILED: "Ha fallat la baixada"
|
||||||
STR_ERROR_MSG: "Error:"
|
STR_ERROR_MSG: "Error:"
|
||||||
STR_UNNAMED: "Sense nom"
|
STR_UNNAMED: "Sense nom"
|
||||||
STR_HOLD_OPEN_TO_DELETE: "Mantín premut Obri per esborrar"
|
STR_HOLD_OPEN_TO_DELETE: "Manteniu premut Obre per esborrar"
|
||||||
STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor"
|
STR_NO_SERVER_URL: "No s'ha configurat cap URL de servidor"
|
||||||
STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del canal de continguts"
|
STR_FETCH_FEED_FAILED: "Ha fallat l'obtenció del feed"
|
||||||
STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del canal de continguts"
|
STR_PARSE_FEED_FAILED: "Ha fallat l'anàlisi del feed"
|
||||||
STR_NETWORK_PREFIX: "Xarxa: "
|
STR_NETWORK_PREFIX: "Xarxa: "
|
||||||
STR_IP_ADDRESS_PREFIX: "Adreça IP: "
|
STR_IP_ADDRESS_PREFIX: "Adreça IP: "
|
||||||
STR_ERROR_GENERAL_FAILURE: "Error: Fallada general"
|
STR_ERROR_GENERAL_FAILURE: "Error: Fallada general"
|
||||||
@@ -206,11 +200,11 @@ STR_HOME: "« Inici"
|
|||||||
STR_SELECT: "Selecciona"
|
STR_SELECT: "Selecciona"
|
||||||
STR_SELECTED: "Seleccionat"
|
STR_SELECTED: "Seleccionat"
|
||||||
STR_TOGGLE: "Canvia"
|
STR_TOGGLE: "Canvia"
|
||||||
STR_TOGGLE_BOOKMARK: "Afig o elimina el punt de llibre"
|
STR_TOGGLE_BOOKMARK: "Canvia punt de llibre"
|
||||||
STR_CONFIRM: "Confirma"
|
STR_CONFIRM: "Confirma"
|
||||||
STR_CANCEL: "Cancel·la"
|
STR_CANCEL: "Cancel·la"
|
||||||
STR_CONNECT: "Connecta"
|
STR_CONNECT: "Connecta"
|
||||||
STR_OPEN: "Obri"
|
STR_OPEN: "Obre"
|
||||||
STR_DOWNLOAD: "Descarrega"
|
STR_DOWNLOAD: "Descarrega"
|
||||||
STR_RETRY: "Reintenta"
|
STR_RETRY: "Reintenta"
|
||||||
STR_YES: "Sí"
|
STR_YES: "Sí"
|
||||||
@@ -225,7 +219,7 @@ STR_DIR_RIGHT: "Dreta"
|
|||||||
STR_DIR_UP: "Amunt"
|
STR_DIR_UP: "Amunt"
|
||||||
STR_DIR_DOWN: "Avall"
|
STR_DIR_DOWN: "Avall"
|
||||||
STR_OK_BUTTON: "OK"
|
STR_OK_BUTTON: "OK"
|
||||||
STR_SLEEP_COVER_FILTER: "Filtre de la portada en repòs"
|
STR_SLEEP_COVER_FILTER: "Filtre de pantalla de repòs"
|
||||||
STR_FILTER_CONTRAST: "Contrast"
|
STR_FILTER_CONTRAST: "Contrast"
|
||||||
STR_CUSTOMISE_STATUS_BAR: "Personalitza la barra d'estat"
|
STR_CUSTOMISE_STATUS_BAR: "Personalitza la barra d'estat"
|
||||||
STR_CHAPTER_PAGE_COUNT: "Comptador de pàgines del capítol"
|
STR_CHAPTER_PAGE_COUNT: "Comptador de pàgines del capítol"
|
||||||
@@ -255,20 +249,20 @@ STR_OPDS_BROWSER: "Navegador OPDS"
|
|||||||
STR_COVER_CUSTOM: "Portada + Personalitzat"
|
STR_COVER_CUSTOM: "Portada + Personalitzat"
|
||||||
STR_QUICK_RESUME: "Represa ràpida"
|
STR_QUICK_RESUME: "Represa ràpida"
|
||||||
STR_MENU_RECENT_BOOKS: "Llibres recents"
|
STR_MENU_RECENT_BOOKS: "Llibres recents"
|
||||||
STR_REMOVE_FROM_RECENTS: "Vols eliminar-lo de Llibres recents?"
|
STR_REMOVE_FROM_RECENTS: "Voleu eliminar-lo de Llibres recents?"
|
||||||
STR_NO_RECENT_BOOKS: "No hi ha llibres recents"
|
STR_NO_RECENT_BOOKS: "No hi ha llibres recents"
|
||||||
STR_CALIBRE_DESC: "Utilitza les transferències sense fils de Calibre"
|
STR_CALIBRE_DESC: "Utilitza les transferències sense fils de Calibre"
|
||||||
STR_FORGET_AND_REMOVE: "Vols oblidar la xarxa i eliminar la contrasenya guardada?"
|
STR_FORGET_AND_REMOVE: "Voleu eliminar la contrasenya guardada?"
|
||||||
STR_FORGET_BUTTON: "Oblida"
|
STR_FORGET_BUTTON: "Oblida"
|
||||||
STR_CALIBRE_STARTING: "S'està iniciant el Calibre..."
|
STR_CALIBRE_STARTING: "S'està iniciant el Calibre..."
|
||||||
STR_CALIBRE_SETUP: "Configura"
|
STR_CALIBRE_SETUP: "Configura"
|
||||||
STR_CALIBRE_STATUS: "Estat"
|
STR_CALIBRE_STATUS: "Estat"
|
||||||
STR_CLEAR_BUTTON: "Esborra"
|
STR_CLEAR_BUTTON: "Esborra"
|
||||||
STR_DEFAULT_VALUE: "Per defecte"
|
STR_DEFAULT_VALUE: "Per defecte"
|
||||||
STR_REMAP_PROMPT: "Prem un botó frontal per a cada rol"
|
STR_REMAP_PROMPT: "Premeu un botó frontal per a cada rol"
|
||||||
STR_UNASSIGNED: "No assignat"
|
STR_UNASSIGNED: "No assignat"
|
||||||
STR_ALREADY_ASSIGNED: "Ja assignat"
|
STR_ALREADY_ASSIGNED: "Ja assignat"
|
||||||
STR_REMAP_RESET_HINT: "Botó lateral Amunt: Restablix la disposició per defecte"
|
STR_REMAP_RESET_HINT: "Botó lateral Amunt: Restableix la disposició per defecte"
|
||||||
STR_REMAP_CANCEL_HINT: "Botó lateral Avall: Cancel·la la reassignació"
|
STR_REMAP_CANCEL_HINT: "Botó lateral Avall: Cancel·la la reassignació"
|
||||||
STR_HW_BACK_LABEL: "Arrere (1r botó)"
|
STR_HW_BACK_LABEL: "Arrere (1r botó)"
|
||||||
STR_HW_CONFIRM_LABEL: "Confirma (2n botó)"
|
STR_HW_CONFIRM_LABEL: "Confirma (2n botó)"
|
||||||
@@ -279,19 +273,19 @@ STR_GO_HOME_BUTTON: "Ves a l'inici"
|
|||||||
STR_SYNC_PROGRESS: "Sincronitza el progrés"
|
STR_SYNC_PROGRESS: "Sincronitza el progrés"
|
||||||
STR_DELETE_CACHE: "Esborra la memòria cau del llibre"
|
STR_DELETE_CACHE: "Esborra la memòria cau del llibre"
|
||||||
STR_DELETE: "Esborra"
|
STR_DELETE: "Esborra"
|
||||||
STR_CONFIRM_DELETE_BOOKMARK: "Vols esborrar este punt de llibre?"
|
STR_CONFIRM_DELETE_BOOKMARK: "Voleu esborrar este punt de llibre?"
|
||||||
STR_DISPLAY_QR: "Mostra la pàgina com a QR"
|
STR_DISPLAY_QR: "Mostra la pàgina com a QR"
|
||||||
STR_CHAPTER_PREFIX: "Capítol: "
|
STR_CHAPTER_PREFIX: "Capítol: "
|
||||||
STR_PAGES_SEPARATOR: " pàgines | "
|
STR_PAGES_SEPARATOR: " pàgines | "
|
||||||
STR_BOOK_PREFIX: "Llibre: "
|
STR_BOOK_PREFIX: "Llibre: "
|
||||||
STR_CALIBRE_URL_HINT: "Per al Calibre, afig /opds a la URL"
|
STR_CALIBRE_URL_HINT: "Per al Calibre, afegiu /opds a la URL"
|
||||||
STR_SYNCING_TIME: "S'està sincronitzant el temps..."
|
STR_SYNCING_TIME: "S'està sincronitzant el temps..."
|
||||||
STR_CALC_HASH: "S'està calculant l'empremta electrònica del document..."
|
STR_CALC_HASH: "S'està calculant el hash del document..."
|
||||||
STR_HASH_FAILED: "No s'ha pogut calcular l'empremta electrònica 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_FETCH_PROGRESS: "S'està obtenint el progrés remot..."
|
||||||
STR_UPLOAD_PROGRESS: "S'està pujant el progrés..."
|
STR_UPLOAD_PROGRESS: "S'està pujant el progrés..."
|
||||||
STR_NO_CREDENTIALS_MSG: "No s'han configurat credencials"
|
STR_NO_CREDENTIALS_MSG: "No s'han configurat credencials"
|
||||||
STR_KOREADER_SETUP_HINT: "Configura el compte de KOReader en Configuració"
|
STR_KOREADER_SETUP_HINT: "Configureu el compte de KOReader en Configuració"
|
||||||
STR_PROGRESS_FOUND: "S'ha trobat progrés!"
|
STR_PROGRESS_FOUND: "S'ha trobat progrés!"
|
||||||
STR_REMOTE_LABEL: "Remot:"
|
STR_REMOTE_LABEL: "Remot:"
|
||||||
STR_LOCAL_LABEL: "Local:"
|
STR_LOCAL_LABEL: "Local:"
|
||||||
@@ -301,7 +295,7 @@ STR_DEVICE_FROM_FORMAT: " De: %s"
|
|||||||
STR_APPLY_REMOTE: "Aplica el progrés remot"
|
STR_APPLY_REMOTE: "Aplica el progrés remot"
|
||||||
STR_UPLOAD_LOCAL: "Puja el progrés local"
|
STR_UPLOAD_LOCAL: "Puja el progrés local"
|
||||||
STR_NO_REMOTE_MSG: "No s'ha trobat progrés remot"
|
STR_NO_REMOTE_MSG: "No s'ha trobat progrés remot"
|
||||||
STR_UPLOAD_PROMPT: "Vols pujar la posició actual?"
|
STR_UPLOAD_PROMPT: "Voleu pujar la posició actual?"
|
||||||
STR_UPLOAD_SUCCESS: "Progrés pujat!"
|
STR_UPLOAD_SUCCESS: "Progrés pujat!"
|
||||||
STR_SYNC_FAILED_MSG: "Sincronització fallida"
|
STR_SYNC_FAILED_MSG: "Sincronització fallida"
|
||||||
STR_SAVE_PROGRESS_FAILED: "No s'ha pogut guardar el progrés"
|
STR_SAVE_PROGRESS_FAILED: "No s'ha pogut guardar el progrés"
|
||||||
@@ -316,11 +310,11 @@ STR_FOOTNOTES: "Notes al peu"
|
|||||||
STR_NO_FOOTNOTES: "No hi ha notes al peu en esta pàgina"
|
STR_NO_FOOTNOTES: "No hi ha notes al peu en esta pàgina"
|
||||||
STR_LINK: "[enllaç]"
|
STR_LINK: "[enllaç]"
|
||||||
STR_SCREENSHOT_BUTTON: "Fes una captura de pantalla"
|
STR_SCREENSHOT_BUTTON: "Fes una captura de pantalla"
|
||||||
STR_AUTO_TURN_ENABLED: "Pas automàtic de pàgina activat"
|
STR_AUTO_TURN_ENABLED: "Passar automàtic activat: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Pas automàtic de pàgina (pàg./min)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Passar automàtic (pàgines per minut)"
|
||||||
STR_TILT_PAGE_TURN: "Pas de pàgina per inclinació"
|
STR_TILT_PAGE_TURN: "Pas de pàgina per inclinació"
|
||||||
STR_FORCE_REFRESH: "Refresca la pantalla"
|
STR_FORCE_REFRESH: "Refresca la pantalla"
|
||||||
STR_RESTARTING_HINT: "S'està reiniciant... Si el dispositiu no es reinicia, mantín premut el botó d'encesa durant uns segons."
|
STR_RESTARTING_HINT: "S'està reiniciant... Si el dispositiu no es reinicia, manteniu premut el botó d'encesa durant uns segons."
|
||||||
STR_NEXT_PAGE: "Pàgina següent »"
|
STR_NEXT_PAGE: "Pàgina següent »"
|
||||||
STR_PREV_PAGE: "« Pàgina anterior"
|
STR_PREV_PAGE: "« Pàgina anterior"
|
||||||
STR_XTC_STATUS_BAR: "Barra d'estat XTC"
|
STR_XTC_STATUS_BAR: "Barra d'estat XTC"
|
||||||
@@ -349,11 +343,6 @@ STR_SERVER_NAME: "Nom del servidor"
|
|||||||
STR_NO_SERVERS: "No hi ha servidors OPDS configurats"
|
STR_NO_SERVERS: "No hi ha servidors OPDS configurats"
|
||||||
STR_DELETE_SERVER: "Elimina el servidor"
|
STR_DELETE_SERVER: "Elimina el servidor"
|
||||||
STR_OPDS_SERVERS: "Servidors OPDS"
|
STR_OPDS_SERVERS: "Servidors OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Carpeta de baixades"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Format del nom de fitxer"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Autor - Títol"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Títol - Autor"
|
|
||||||
STR_FMT_TITLE: "Títol"
|
|
||||||
STR_MANAGE_FONTS: "Gestiona les fonts"
|
STR_MANAGE_FONTS: "Gestiona les fonts"
|
||||||
STR_FONT_BROWSER: "Navegador de fonts"
|
STR_FONT_BROWSER: "Navegador de fonts"
|
||||||
STR_LOADING_FONT_LIST: "S'està carregant la llista de fonts..."
|
STR_LOADING_FONT_LIST: "S'està carregant la llista de fonts..."
|
||||||
@@ -364,37 +353,35 @@ STR_INSTALLED: "Instal·lat"
|
|||||||
STR_DOWNLOAD_ALL: "Descarrega-ho tot"
|
STR_DOWNLOAD_ALL: "Descarrega-ho tot"
|
||||||
STR_UPDATE_ALL: "Actualitza-ho tot"
|
STR_UPDATE_ALL: "Actualitza-ho tot"
|
||||||
STR_UPDATE_AVAILABLE: "Actualitza"
|
STR_UPDATE_AVAILABLE: "Actualitza"
|
||||||
STR_CRASH_TITLE: "Fallada del sistema"
|
STR_CRASH_TITLE: "Bloqueig del sistema"
|
||||||
STR_CRASH_DESCRIPTION: "S'ha guardat un informe detallat a crash_report.txt. Inclou este arxiu en l'informe d'errors."
|
STR_CRASH_DESCRIPTION: "S'ha guardat un informe detallat a crash_report.txt. Incloeu este arxiu en l'informe d'errors."
|
||||||
STR_CRASH_REASON: "Motiu de la fallada"
|
STR_CRASH_REASON: "Motiu del bloqueig:"
|
||||||
STR_CRASH_NO_REASON: "(No s'ha registrat cap motiu)"
|
STR_CRASH_NO_REASON: "(No s'ha registrat cap motiu)"
|
||||||
STR_KB_HINT_MOVE_CURSOR: "Prem Esquerra o Dreta per moure el cursor"
|
STR_KB_HINT_MOVE_CURSOR: "Prem Esquerra o Dreta per moure el cursor"
|
||||||
STR_KB_HINT_RETURN_CURSOR: "Prem Esquerra per tornar a la posició del cursor"
|
STR_KB_HINT_RETURN_CURSOR: "Prem Esquerra per tornar a la posició del cursor"
|
||||||
STR_KB_HINT_HIDE_PASSWORD: "Mantín premut Dreta i prem [***] per ocultar la contrasenya"
|
STR_KB_HINT_HIDE_PASSWORD: "Mantén premut Dreta i prem [***] per ocultar la contrasenya"
|
||||||
STR_KB_HINT_SHOW_PASSWORD: "Mantín premut Dreta i prem [abc] per mostrar la contrasenya"
|
STR_KB_HINT_SHOW_PASSWORD: "Mantén premut Dreta i prem [abc] per mostrar la contrasenya"
|
||||||
STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Prem [***] per ocultar la contrasenya"
|
STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Prem [***] per ocultar la contrasenya"
|
||||||
STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Prem [abc] per mostrar la contrasenya"
|
STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Prem [abc] per mostrar la contrasenya"
|
||||||
STR_KB_HINT_EDIT_ENTRY: "Mantín premut Amunt per editar l'entrada"
|
STR_KB_HINT_EDIT_ENTRY: "Mantén premut Amunt per editar l'entrada"
|
||||||
STR_KB_TIPS: "Consells:"
|
STR_KB_TIPS: "Consells:"
|
||||||
STR_KB_HINT_RETURN_KEYBOARD: "Prem Avall per tornar al teclat"
|
STR_KB_HINT_RETURN_KEYBOARD: "Prem Avall per tornar al teclat"
|
||||||
STR_KB_HINT_EXIT_URL_MODE: "Prem ABC per eixir del mode URL"
|
STR_KB_HINT_EXIT_URL_MODE: "Prem ABC per eixir del mode URL"
|
||||||
STR_KB_HINT_CLEAR_TEXT: "Mantín premut DEL per esborrar tot el text"
|
STR_KB_HINT_CLEAR_TEXT: "Mantén premut DEL per esborrar tot el text"
|
||||||
STR_KB_HINT_SECONDARY_CHAR: "Mantín premut SELECT: caràcter secundari"
|
STR_KB_HINT_SECONDARY_CHAR: "Mantén premut SELECT per al caràcter secundari"
|
||||||
STR_KB_HINT_UPPER_SECONDARY: "Mantín premut SELECT: majúscules o caràcter secundari"
|
STR_KB_HINT_UPPER_SECONDARY: "Mantén premut SELECT per MAJÚSCULES o caràcter secundari"
|
||||||
STR_KB_HINT_LOWER_SECONDARY: "Mantín premut SELECT: minúscules o caràcter secundari"
|
STR_KB_HINT_LOWER_SECONDARY: "Mantén premut SELECT per minúscules o caràcter secundari"
|
||||||
STR_KB_HINT_URL_SNIPPETS: "Prem URL per inserir fragments"
|
STR_KB_HINT_URL_SNIPPETS: "Prem URL per inserir fragments"
|
||||||
STR_SD_FIRMWARE_UPDATE: "Actualització de firmware des de la targeta SD"
|
STR_SD_FIRMWARE_UPDATE: "Actualització de firmware des de la targeta SD"
|
||||||
STR_SELECT_FIRMWARE_FILE: "Selecciona un arxiu de firmware (.bin)"
|
STR_SELECT_FIRMWARE_FILE: "Seleccioneu un arxiu de firmware (.bin)"
|
||||||
STR_NO_BIN_FILES: "No s'han trobat arxius .bin"
|
STR_NO_BIN_FILES: "No s'han trobat arxius .bin"
|
||||||
STR_VALIDATING_FIRMWARE: "S'està validant el firmware..."
|
STR_VALIDATING_FIRMWARE: "S'està validant el firmware..."
|
||||||
STR_INVALID_FIRMWARE: "Arxiu de firmware no vàlid"
|
STR_INVALID_FIRMWARE: "Arxiu de firmware no vàlid"
|
||||||
STR_FIRMWARE_TOO_LARGE: "El firmware és massa gran per a la partició"
|
STR_FIRMWARE_TOO_LARGE: "El firmware és massa gran per a la partició"
|
||||||
STR_FIRMWARE_TOO_SMALL: "L'arxiu de firmware és massa menut"
|
STR_FIRMWARE_TOO_SMALL: "L'arxiu de firmware és massa menut"
|
||||||
STR_FIRMWARE_UPDATE_PROMPT: "Vols actualitzar el firmware?"
|
STR_FIRMWARE_UPDATE_PROMPT: "Voleu actualitzar el firmware?"
|
||||||
STR_FIRMWARE_FILE_OPEN_FAILED: "No es pot obrir l'arxiu"
|
STR_FIRMWARE_FILE_OPEN_FAILED: "No es pot obrir l'arxiu"
|
||||||
STR_FIRMWARE_WRITE_FAILED: "Ha fallat l'escriptura del firmware"
|
STR_FIRMWARE_WRITE_FAILED: "Ha fallat l'escriptura del firmware"
|
||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "No apagues el dispositiu!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "No apagueu el dispositiu!"
|
||||||
STR_RECOVERY_MODE: "Mode de recuperació"
|
STR_RECOVERY_MODE: "Mode de recuperació"
|
||||||
STR_RECOVERY_MODE_HINT: "Posa firmware.bin a l'arrel de la targeta SD i selecciona'l"
|
STR_RECOVERY_MODE_HINT: "Poseu firmware.bin a l'arrel de la targeta SD i seleccioneu-lo"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Afig una xarxa oculta..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Introduïx el nom de la xarxa (SSID)"
|
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ STR_SLEEP_COVER_MODE: "Kiểu ảnh bìa màn hình ngủ"
|
|||||||
STR_HIDE_BATTERY: "Ẩn % pin"
|
STR_HIDE_BATTERY: "Ẩn % pin"
|
||||||
STR_EXTRA_SPACING: "Giãn cách đoạn thêm"
|
STR_EXTRA_SPACING: "Giãn cách đoạn thêm"
|
||||||
STR_TEXT_AA: "Khử răng cưa chữ"
|
STR_TEXT_AA: "Khử răng cưa chữ"
|
||||||
|
STR_TEXT_AA_FULL: "Đầy đủ"
|
||||||
|
STR_TEXT_AA_FAST: "Nhanh"
|
||||||
STR_IMAGES: "Hình ảnh"
|
STR_IMAGES: "Hình ảnh"
|
||||||
STR_IMAGES_DISPLAY: "Hiển thị"
|
STR_IMAGES_DISPLAY: "Hiển thị"
|
||||||
STR_IMAGES_PLACEHOLDER: "Khung thay thế"
|
STR_IMAGES_PLACEHOLDER: "Khung thay thế"
|
||||||
@@ -73,7 +75,6 @@ STR_IMAGES_SUPPRESS: "Ẩn đi"
|
|||||||
STR_SHORT_PWR_BTN: "Nhấn nhanh nút nguồn"
|
STR_SHORT_PWR_BTN: "Nhấn nhanh nút nguồn"
|
||||||
STR_ORIENTATION: "Hướng đọc"
|
STR_ORIENTATION: "Hướng đọc"
|
||||||
STR_SIDE_BTN_LAYOUT: "Bố trí nút bên (trình đọc)"
|
STR_SIDE_BTN_LAYOUT: "Bố trí nút bên (trình đọc)"
|
||||||
STR_TOUCH_READER_CONTROLS: "Điều khiển đọc bằng cảm ứng"
|
|
||||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Xoay nút trước theo hướng"
|
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Xoay nút trước theo hướng"
|
||||||
STR_LONG_PRESS_BEHAVIOR: "Hành vi nhấn giữ nút"
|
STR_LONG_PRESS_BEHAVIOR: "Hành vi nhấn giữ nút"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "TẮT"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "TẮT"
|
||||||
@@ -99,7 +100,6 @@ STR_USERNAME: "Tên đăng nhập"
|
|||||||
STR_PASSWORD: "Mật khẩu"
|
STR_PASSWORD: "Mật khẩu"
|
||||||
STR_SYNC_SERVER_URL: "URL máy chủ đồng bộ"
|
STR_SYNC_SERVER_URL: "URL máy chủ đồng bộ"
|
||||||
STR_DOCUMENT_MATCHING: "Khớp tài liệu"
|
STR_DOCUMENT_MATCHING: "Khớp tài liệu"
|
||||||
STR_SEND_METADATA: "Gửi siêu dữ liệu tài liệu"
|
|
||||||
STR_AUTHENTICATE: "Xác thực"
|
STR_AUTHENTICATE: "Xác thực"
|
||||||
STR_KOREADER_USERNAME: "Tên đăng nhập KOReader"
|
STR_KOREADER_USERNAME: "Tên đăng nhập KOReader"
|
||||||
STR_KOREADER_PASSWORD: "Mật khẩu KOReader"
|
STR_KOREADER_PASSWORD: "Mật khẩu KOReader"
|
||||||
@@ -336,11 +336,6 @@ STR_SERVER_NAME: "Tên máy chủ"
|
|||||||
STR_NO_SERVERS: "Chưa cấu hình máy chủ OPDS"
|
STR_NO_SERVERS: "Chưa cấu hình máy chủ OPDS"
|
||||||
STR_DELETE_SERVER: "Xóa máy chủ"
|
STR_DELETE_SERVER: "Xóa máy chủ"
|
||||||
STR_OPDS_SERVERS: "Máy chủ OPDS"
|
STR_OPDS_SERVERS: "Máy chủ OPDS"
|
||||||
STR_OPDS_DOWNLOAD_FOLDER: "Thư mục tải xuống"
|
|
||||||
STR_OPDS_FILENAME_FORMAT: "Định dạng tên tệp"
|
|
||||||
STR_FMT_AUTHOR_TITLE: "Tác giả - Tựa đề"
|
|
||||||
STR_FMT_TITLE_AUTHOR: "Tựa đề - Tác giả"
|
|
||||||
STR_FMT_TITLE: "Tựa đề"
|
|
||||||
STR_AUTO_TURN_ENABLED: "Tự lật trang: "
|
STR_AUTO_TURN_ENABLED: "Tự lật trang: "
|
||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Tự lật (số trang mỗi phút)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Tự lật (số trang mỗi phút)"
|
||||||
STR_MANAGE_FONTS: "Quản lý phông chữ"
|
STR_MANAGE_FONTS: "Quản lý phông chữ"
|
||||||
@@ -386,5 +381,3 @@ STR_FIRMWARE_WRITE_FAILED: "Ghi firmware thất bại"
|
|||||||
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Không được tắt nguồn!"
|
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Không được tắt nguồn!"
|
||||||
STR_RECOVERY_MODE: "Chế độ phục hồi"
|
STR_RECOVERY_MODE: "Chế độ phục hồi"
|
||||||
STR_RECOVERY_MODE_HINT: "Đặt firmware.bin ở thư mục gốc thẻ SD rồi chọn"
|
STR_RECOVERY_MODE_HINT: "Đặt firmware.bin ở thư mục gốc thẻ SD rồi chọn"
|
||||||
STR_ADD_HIDDEN_NETWORK: "Thêm mạng ẩn..."
|
|
||||||
STR_ENTER_WIFI_SSID: "Nhập tên mạng (SSID)"
|
|
||||||
|
|||||||
@@ -13,11 +13,6 @@ enum class InflateStatus {
|
|||||||
|
|
||||||
// Streaming deflate decompressor wrapping uzlib.
|
// Streaming deflate decompressor wrapping uzlib.
|
||||||
//
|
//
|
||||||
// NOTE: retained ONLY for FontDecompressor's tiny one-shot flash-resident group
|
|
||||||
// decompressions, where uzlib's ~1KB state beats tinfl's ~11KB on the
|
|
||||||
// OOM-sensitive render path. All throughput paths (zip entries, PNG IDAT) use
|
|
||||||
// InflateStream (lib/miniz), which decodes several times faster.
|
|
||||||
//
|
|
||||||
// Two modes:
|
// Two modes:
|
||||||
// init(false) — one-shot: input is a contiguous buffer, call read() once.
|
// init(false) — one-shot: input is a contiguous buffer, call read() once.
|
||||||
// init(true) — streaming: allocates a 32KB ring buffer for back-references
|
// init(true) — streaming: allocates a 32KB ring buffer for back-references
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
#include <JPEGDEC.h>
|
#include <JPEGDEC.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <Memory.h>
|
#include <Memory.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -171,22 +169,11 @@ constexpr uint32_t FP_ONE = 1UL << 16;
|
|||||||
// Static file pointer for JPEGDEC open callback.
|
// Static file pointer for JPEGDEC open callback.
|
||||||
// Safe in single-threaded embedded context; never accessed concurrently.
|
// Safe in single-threaded embedded context; never accessed concurrently.
|
||||||
static HalFile* s_jpegFile = nullptr;
|
static HalFile* s_jpegFile = nullptr;
|
||||||
static uint8_t s_jpegIoSinceYield = 0;
|
|
||||||
|
|
||||||
static void yieldToIdle() { vTaskDelay(1); }
|
|
||||||
|
|
||||||
static void yieldDuringJpegIo() {
|
|
||||||
if (++s_jpegIoSinceYield < 4) return;
|
|
||||||
s_jpegIoSinceYield = 0;
|
|
||||||
yieldToIdle();
|
|
||||||
}
|
|
||||||
|
|
||||||
void* bmpJpegOpen(const char* /*filename*/, int32_t* size) {
|
void* bmpJpegOpen(const char* /*filename*/, int32_t* size) {
|
||||||
if (!s_jpegFile || !*s_jpegFile) return nullptr;
|
if (!s_jpegFile || !*s_jpegFile) return nullptr;
|
||||||
s_jpegIoSinceYield = 0;
|
|
||||||
s_jpegFile->seek(0);
|
s_jpegFile->seek(0);
|
||||||
*size = static_cast<int32_t>(s_jpegFile->size());
|
*size = static_cast<int32_t>(s_jpegFile->size());
|
||||||
yieldDuringJpegIo();
|
|
||||||
return s_jpegFile;
|
return s_jpegFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +187,6 @@ int32_t bmpJpegRead(JPEGFILE* pFile, uint8_t* pBuf, int32_t len) {
|
|||||||
int32_t n = f->read(pBuf, len);
|
int32_t n = f->read(pBuf, len);
|
||||||
if (n < 0) n = 0;
|
if (n < 0) n = 0;
|
||||||
pFile->iPos += n;
|
pFile->iPos += n;
|
||||||
yieldDuringJpegIo();
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +194,6 @@ int32_t bmpJpegSeek(JPEGFILE* pFile, int32_t pos) {
|
|||||||
auto* f = reinterpret_cast<HalFile*>(pFile->fHandle);
|
auto* f = reinterpret_cast<HalFile*>(pFile->fHandle);
|
||||||
if (!f || !f->seek(pos)) return -1;
|
if (!f || !f->seek(pos)) return -1;
|
||||||
pFile->iPos = pos;
|
pFile->iPos = pos;
|
||||||
yieldDuringJpegIo();
|
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,23 +236,9 @@ struct BmpConvertCtx {
|
|||||||
std::unique_ptr<FloydSteinbergDitherer> fsDitherer;
|
std::unique_ptr<FloydSteinbergDitherer> fsDitherer;
|
||||||
std::unique_ptr<Atkinson1BitDitherer> atkinson1BitDitherer;
|
std::unique_ptr<Atkinson1BitDitherer> atkinson1BitDitherer;
|
||||||
|
|
||||||
uint8_t rowsSinceYield;
|
|
||||||
uint8_t blocksSinceYield;
|
|
||||||
bool error;
|
bool error;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void yieldDuringDecode(BmpConvertCtx* ctx) {
|
|
||||||
if (++ctx->rowsSinceYield < 8) return;
|
|
||||||
ctx->rowsSinceYield = 0;
|
|
||||||
yieldToIdle();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void yieldDuringDecodeBlock(BmpConvertCtx* ctx) {
|
|
||||||
if (++ctx->blocksSinceYield < 16) return;
|
|
||||||
ctx->blocksSinceYield = 0;
|
|
||||||
yieldToIdle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a fully-assembled output row (grayscale bytes, length outWidth) to BMP
|
// Write a fully-assembled output row (grayscale bytes, length outWidth) to BMP
|
||||||
static void writeOutputRow(BmpConvertCtx* ctx, const uint8_t* srcRow, int outY) {
|
static void writeOutputRow(BmpConvertCtx* ctx, const uint8_t* srcRow, int outY) {
|
||||||
memset(ctx->bmpRow.get(), 0, ctx->bytesPerRow);
|
memset(ctx->bmpRow.get(), 0, ctx->bytesPerRow);
|
||||||
@@ -303,7 +274,6 @@ static void writeOutputRow(BmpConvertCtx* ctx, const uint8_t* srcRow, int outY)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx->bmpOut->write(ctx->bmpRow.get(), ctx->bytesPerRow);
|
ctx->bmpOut->write(ctx->bmpRow.get(), ctx->bytesPerRow);
|
||||||
yieldDuringDecode(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matches the progressive-JPEG smoothing used by JpegToFramebufferConverter, but stays
|
// Matches the progressive-JPEG smoothing used by JpegToFramebufferConverter, but stays
|
||||||
@@ -426,7 +396,6 @@ static void flushScaledRow(BmpConvertCtx* ctx) {
|
|||||||
|
|
||||||
ctx->bmpOut->write(ctx->bmpRow.get(), ctx->bytesPerRow);
|
ctx->bmpOut->write(ctx->bmpRow.get(), ctx->bytesPerRow);
|
||||||
ctx->currentOutY++;
|
ctx->currentOutY++;
|
||||||
yieldDuringDecode(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// JPEGDEC draw callback — receives one MCU-width × MCU-height block at a time,
|
// JPEGDEC draw callback — receives one MCU-width × MCU-height block at a time,
|
||||||
@@ -436,7 +405,6 @@ static void flushScaledRow(BmpConvertCtx* ctx) {
|
|||||||
int bmpDrawCallback(JPEGDRAW* pDraw) {
|
int bmpDrawCallback(JPEGDRAW* pDraw) {
|
||||||
auto* ctx = reinterpret_cast<BmpConvertCtx*>(pDraw->pUser);
|
auto* ctx = reinterpret_cast<BmpConvertCtx*>(pDraw->pUser);
|
||||||
if (!ctx || ctx->error) return 0;
|
if (!ctx || ctx->error) return 0;
|
||||||
yieldDuringDecodeBlock(ctx);
|
|
||||||
|
|
||||||
const uint8_t* pixels = reinterpret_cast<uint8_t*>(pDraw->pPixels);
|
const uint8_t* pixels = reinterpret_cast<uint8_t*>(pDraw->pPixels);
|
||||||
const int stride = pDraw->iWidth;
|
const int stride = pDraw->iWidth;
|
||||||
@@ -631,8 +599,6 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(HalFile& jpegFile, Print& b
|
|||||||
ctx.smoothScaleY_fp = interpolationStep(ctx.srcHeight, outHeight);
|
ctx.smoothScaleY_fp = interpolationStep(ctx.srcHeight, outHeight);
|
||||||
ctx.smoothNextOutY = 0;
|
ctx.smoothNextOutY = 0;
|
||||||
ctx.smoothPrevY = -1;
|
ctx.smoothPrevY = -1;
|
||||||
ctx.rowsSinceYield = 0;
|
|
||||||
ctx.blocksSinceYield = 0;
|
|
||||||
ctx.error = false;
|
ctx.error = false;
|
||||||
|
|
||||||
// MCU row buffer: MAX_MCU_HEIGHT rows × decoded srcWidth columns of grayscale
|
// MCU row buffer: MAX_MCU_HEIGHT rows × decoded srcWidth columns of grayscale
|
||||||
|
|||||||
@@ -1,81 +1,118 @@
|
|||||||
#include "KOReaderCredentialStore.h"
|
#include "KOReaderCredentialStore.h"
|
||||||
|
|
||||||
|
#include <HalStorage.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <MD5Builder.h>
|
#include <MD5Builder.h>
|
||||||
#include <ObfuscationUtils.h>
|
#include <ObfuscationUtils.h>
|
||||||
|
#include <Serialization.h>
|
||||||
|
|
||||||
|
#include "KOReaderJsonIO.h"
|
||||||
|
|
||||||
|
// Initialize the static instance
|
||||||
|
KOReaderCredentialStore KOReaderCredentialStore::instance;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Default sync server URL. crosspoint-sync speaks the full KOSync protocol, so
|
// File format version (for binary migration)
|
||||||
// pointing at any other kosync server (e.g. https://sync.koreader.rocks:443)
|
constexpr uint8_t KOREADER_FILE_VERSION = 1;
|
||||||
// still works via the custom server URL setting.
|
|
||||||
constexpr char DEFAULT_SERVER_URL[] = "https://sync.crosspointreader.com";
|
|
||||||
|
|
||||||
// Default before config version 2. Configs saved without a version stamp and an
|
// File paths
|
||||||
// empty serverUrl were implicitly syncing here — they get pinned on upgrade.
|
constexpr char KOREADER_FILE_BIN[] = "/.crosspoint/koreader.bin";
|
||||||
constexpr char LEGACY_DEFAULT_SERVER_URL[] = "https://sync.koreader.rocks:443";
|
constexpr char KOREADER_FILE_JSON[] = "/.crosspoint/koreader.json";
|
||||||
|
constexpr char KOREADER_FILE_BAK[] = "/.crosspoint/koreader.bin.bak";
|
||||||
|
|
||||||
// Bumped when a change to defaults would alter behavior for existing configs.
|
// Default sync server URL
|
||||||
constexpr uint8_t CONFIG_VERSION = 2;
|
constexpr char DEFAULT_SERVER_URL[] = "https://sync.koreader.rocks:443";
|
||||||
|
|
||||||
|
// Legacy obfuscation key - "KOReader" in ASCII (only used for binary migration)
|
||||||
|
constexpr uint8_t LEGACY_OBFUSCATION_KEY[] = {0x4B, 0x4F, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72};
|
||||||
|
constexpr size_t LEGACY_KEY_LENGTH = sizeof(LEGACY_OBFUSCATION_KEY);
|
||||||
|
|
||||||
|
void legacyDeobfuscate(std::string& data) {
|
||||||
|
for (size_t i = 0; i < data.size(); i++) {
|
||||||
|
data[i] ^= LEGACY_OBFUSCATION_KEY[i % LEGACY_KEY_LENGTH];
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void KOReaderCredentialStore::toJson(JsonDocument& doc) const {
|
bool KOReaderCredentialStore::saveToFile() const {
|
||||||
doc["cfgVersion"] = CONFIG_VERSION;
|
Storage.mkdir("/.crosspoint");
|
||||||
doc["username"] = getUsername();
|
return KOReaderJsonIO::save(*this, KOREADER_FILE_JSON);
|
||||||
doc["password_obf"] = obfuscation::obfuscateToBase64(getPassword());
|
|
||||||
doc["serverUrl"] = getServerUrl();
|
|
||||||
doc["matchMethod"] = static_cast<uint8_t>(getMatchMethod());
|
|
||||||
doc["sendMetadata"] = getSendMetadata();
|
|
||||||
doc["syncBehavior"] = static_cast<uint8_t>(getSyncBehavior());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KOReaderCredentialStore::fromJson(JsonVariantConst doc) {
|
bool KOReaderCredentialStore::loadFromFile() {
|
||||||
std::string user = doc["username"] | "";
|
// Try JSON first
|
||||||
|
if (Storage.exists(KOREADER_FILE_JSON)) {
|
||||||
bool needsResave = false;
|
String json = Storage.readFile(KOREADER_FILE_JSON);
|
||||||
std::string pass = extractPassword(doc, needsResave);
|
if (!json.isEmpty()) {
|
||||||
|
bool resave = false;
|
||||||
setCredentials(user, pass);
|
bool result = KOReaderJsonIO::load(*this, json.c_str(), &resave);
|
||||||
setServerUrl(doc["serverUrl"] | "");
|
if (result && resave) {
|
||||||
|
|
||||||
// The default server changed in config v2 (sync.koreader.rocks -> crosspoint-sync).
|
|
||||||
// A pre-v2 config with credentials and no explicit URL was actively syncing
|
|
||||||
// against the old default — pin that URL so the upgrade doesn't switch servers
|
|
||||||
// out from under the user. Fresh setups get the new default.
|
|
||||||
const uint8_t cfgVersion = doc["cfgVersion"] | (uint8_t)1;
|
|
||||||
if (cfgVersion < CONFIG_VERSION) {
|
|
||||||
if (getServerUrl().empty() && hasCredentials()) {
|
|
||||||
LOG_DBG("KRS", "Pre-v2 config used the old default server; pinning %s", LEGACY_DEFAULT_SERVER_URL);
|
|
||||||
setServerUrl(LEGACY_DEFAULT_SERVER_URL);
|
|
||||||
}
|
|
||||||
needsResave = true; // stamp cfgVersion so this migration runs once
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t method = doc["matchMethod"] | (uint8_t)0;
|
|
||||||
if (method <= static_cast<uint8_t>(DocumentMatchMethod::BINARY)) {
|
|
||||||
setMatchMethod(static_cast<DocumentMatchMethod>(method));
|
|
||||||
} else {
|
|
||||||
LOG_DBG("KRS", "Invalid matchMethod %u in JSON, resetting to FILENAME", method);
|
|
||||||
setMatchMethod(DocumentMatchMethod::FILENAME);
|
|
||||||
}
|
|
||||||
setSendMetadata(doc["sendMetadata"] | false);
|
|
||||||
|
|
||||||
const JsonVariantConst behaviorValue = doc["syncBehavior"];
|
|
||||||
const bool missingBehavior = behaviorValue.isNull();
|
|
||||||
uint8_t behavior = behaviorValue | static_cast<uint8_t>(KOReaderSyncBehavior::ASK_EVERY_TIME);
|
|
||||||
if (behavior <= static_cast<uint8_t>(KOReaderSyncBehavior::SMART)) {
|
|
||||||
setSyncBehavior(static_cast<KOReaderSyncBehavior>(behavior));
|
|
||||||
needsResave = needsResave || missingBehavior;
|
|
||||||
} else {
|
|
||||||
LOG_DBG("KRS", "Invalid syncBehavior %u in JSON, resetting to ASK_EVERY_TIME", behavior);
|
|
||||||
setSyncBehavior(KOReaderSyncBehavior::ASK_EVERY_TIME);
|
|
||||||
needsResave = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsResave) {
|
|
||||||
LOG_DBG("KRS", "Resaved KOReader credentials to update format");
|
|
||||||
saveToFile();
|
saveToFile();
|
||||||
|
LOG_DBG("KRS", "Resaved KOReader credentials to update format");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fall back to binary migration
|
||||||
|
if (Storage.exists(KOREADER_FILE_BIN)) {
|
||||||
|
if (loadFromBinaryFile()) {
|
||||||
|
if (saveToFile()) {
|
||||||
|
Storage.rename(KOREADER_FILE_BIN, KOREADER_FILE_BAK);
|
||||||
|
LOG_DBG("KRS", "Migrated koreader.bin to koreader.json");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
LOG_ERR("KRS", "Failed to save KOReader credentials during migration");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("KRS", "No credentials file found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KOReaderCredentialStore::loadFromBinaryFile() {
|
||||||
|
HalFile file;
|
||||||
|
if (!Storage.openFileForRead("KRS", KOREADER_FILE_BIN, file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t version;
|
||||||
|
serialization::readPod(file, version);
|
||||||
|
if (version != KOREADER_FILE_VERSION) {
|
||||||
|
LOG_DBG("KRS", "Unknown file version: %u", version);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.available()) {
|
||||||
|
serialization::readString(file, username);
|
||||||
|
} else {
|
||||||
|
username.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.available()) {
|
||||||
|
serialization::readString(file, password);
|
||||||
|
legacyDeobfuscate(password);
|
||||||
|
} else {
|
||||||
|
password.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.available()) {
|
||||||
|
serialization::readString(file, serverUrl);
|
||||||
|
} else {
|
||||||
|
serverUrl.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.available()) {
|
||||||
|
uint8_t method;
|
||||||
|
serialization::readPod(file, method);
|
||||||
|
matchMethod = static_cast<DocumentMatchMethod>(method);
|
||||||
|
} else {
|
||||||
|
matchMethod = DocumentMatchMethod::FILENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("KRS", "Loaded KOReader credentials from binary for user: %s", username.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,16 +173,3 @@ void KOReaderCredentialStore::setMatchMethod(DocumentMatchMethod method) {
|
|||||||
matchMethod = method;
|
matchMethod = method;
|
||||||
LOG_DBG("KRS", "Set match method: %s", method == DocumentMatchMethod::FILENAME ? "Filename" : "Binary");
|
LOG_DBG("KRS", "Set match method: %s", method == DocumentMatchMethod::FILENAME ? "Filename" : "Binary");
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderCredentialStore::setSendMetadata(bool enabled) {
|
|
||||||
sendMetadata = enabled;
|
|
||||||
LOG_DBG("KRS", "Set send metadata: %s", enabled ? "true" : "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
void KOReaderCredentialStore::setSyncBehavior(KOReaderSyncBehavior behavior) {
|
|
||||||
if (static_cast<uint8_t>(behavior) > static_cast<uint8_t>(KOReaderSyncBehavior::SMART)) {
|
|
||||||
behavior = KOReaderSyncBehavior::ASK_EVERY_TIME;
|
|
||||||
}
|
|
||||||
syncBehavior = behavior;
|
|
||||||
LOG_DBG("KRS", "Set sync behavior: %s", behavior == KOReaderSyncBehavior::SMART ? "Smart" : "Ask");
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <PersistableStore.h>
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -11,38 +8,36 @@ enum class DocumentMatchMethod : uint8_t {
|
|||||||
BINARY = 1, // Match by partial MD5 of file content (more accurate, but files must be identical)
|
BINARY = 1, // Match by partial MD5 of file content (more accurate, but files must be identical)
|
||||||
};
|
};
|
||||||
|
|
||||||
// How manual "Sync Progress" resolves differences after fetching remote progress.
|
|
||||||
enum class KOReaderSyncBehavior : uint8_t {
|
|
||||||
ASK_EVERY_TIME = 0, // Preserve legacy behavior: always show Apply/Upload choices.
|
|
||||||
SMART = 1, // Auto-resolve simple cases using furthest progress.
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton class for storing KOReader sync credentials on the SD card.
|
* Singleton class for storing KOReader sync credentials on the SD card.
|
||||||
* Passwords are XOR-obfuscated with the device's unique hardware MAC address
|
* Passwords are XOR-obfuscated with the device's unique hardware MAC address
|
||||||
* and base64-encoded before writing to JSON (not cryptographically secure,
|
* and base64-encoded before writing to JSON (not cryptographically secure,
|
||||||
* but prevents casual reading and ties credentials to the specific device).
|
* but prevents casual reading and ties credentials to the specific device).
|
||||||
*/
|
*/
|
||||||
|
class KOReaderCredentialStore {
|
||||||
class KOReaderCredentialStore : public PersistableStore<KOReaderCredentialStore> {
|
|
||||||
private:
|
private:
|
||||||
|
static KOReaderCredentialStore instance;
|
||||||
std::string username;
|
std::string username;
|
||||||
std::string password;
|
std::string password;
|
||||||
std::string serverUrl; // Custom sync server URL (empty = default)
|
std::string serverUrl; // Custom sync server URL (empty = default)
|
||||||
DocumentMatchMethod matchMethod = DocumentMatchMethod::FILENAME; // Default to filename for compatibility
|
DocumentMatchMethod matchMethod = DocumentMatchMethod::FILENAME; // Default to filename for compatibility
|
||||||
bool sendMetadata = false; // Send document metadata with progress sync
|
|
||||||
KOReaderSyncBehavior syncBehavior = KOReaderSyncBehavior::SMART;
|
|
||||||
|
|
||||||
// Private constructor for singleton
|
// Private constructor for singleton
|
||||||
KOReaderCredentialStore() = default;
|
KOReaderCredentialStore() = default;
|
||||||
~KOReaderCredentialStore() = default;
|
|
||||||
|
|
||||||
friend class PersistableStore<KOReaderCredentialStore>;
|
bool loadFromBinaryFile();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static const char* getFilePath() { return "/.crosspoint/koreader.json"; }
|
// Delete copy constructor and assignment
|
||||||
void toJson(JsonDocument& doc) const;
|
KOReaderCredentialStore(const KOReaderCredentialStore&) = delete;
|
||||||
bool fromJson(JsonVariantConst doc);
|
KOReaderCredentialStore& operator=(const KOReaderCredentialStore&) = delete;
|
||||||
|
|
||||||
|
// Get singleton instance
|
||||||
|
static KOReaderCredentialStore& getInstance() { return instance; }
|
||||||
|
|
||||||
|
// Save/load from SD card
|
||||||
|
bool saveToFile() const;
|
||||||
|
bool loadFromFile();
|
||||||
|
|
||||||
// Credential management
|
// Credential management
|
||||||
void setCredentials(const std::string& user, const std::string& pass);
|
void setCredentials(const std::string& user, const std::string& pass);
|
||||||
@@ -68,14 +63,6 @@ class KOReaderCredentialStore : public PersistableStore<KOReaderCredentialStore>
|
|||||||
// Document matching method
|
// Document matching method
|
||||||
void setMatchMethod(DocumentMatchMethod method);
|
void setMatchMethod(DocumentMatchMethod method);
|
||||||
DocumentMatchMethod getMatchMethod() const { return matchMethod; }
|
DocumentMatchMethod getMatchMethod() const { return matchMethod; }
|
||||||
|
|
||||||
// Send metadata setting
|
|
||||||
void setSendMetadata(bool enabled);
|
|
||||||
bool getSendMetadata() const { return sendMetadata; }
|
|
||||||
|
|
||||||
// Sync behavior
|
|
||||||
void setSyncBehavior(KOReaderSyncBehavior behavior);
|
|
||||||
KOReaderSyncBehavior getSyncBehavior() const { return syncBehavior; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper macro to access credential store
|
// Helper macro to access credential store
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#include "KOReaderJsonIO.h"
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <HalStorage.h>
|
||||||
|
#include <Logging.h>
|
||||||
|
#include <ObfuscationUtils.h>
|
||||||
|
|
||||||
|
#include "KOReaderCredentialStore.h"
|
||||||
|
|
||||||
|
namespace KOReaderJsonIO {
|
||||||
|
|
||||||
|
bool save(const KOReaderCredentialStore& store, const char* path) {
|
||||||
|
JsonDocument doc;
|
||||||
|
doc["username"] = store.getUsername();
|
||||||
|
doc["password_obf"] = obfuscation::obfuscateToBase64(store.getPassword());
|
||||||
|
doc["serverUrl"] = store.getServerUrl();
|
||||||
|
doc["matchMethod"] = static_cast<uint8_t>(store.getMatchMethod());
|
||||||
|
|
||||||
|
String json;
|
||||||
|
serializeJson(doc, json);
|
||||||
|
return Storage.writeFile(path, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load(KOReaderCredentialStore& store, const char* json, bool* needsResave) {
|
||||||
|
if (needsResave) *needsResave = false;
|
||||||
|
JsonDocument doc;
|
||||||
|
auto error = deserializeJson(doc, json);
|
||||||
|
if (error) {
|
||||||
|
LOG_ERR("KRS", "JSON parse error: %s", error.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string user = doc["username"] | std::string("");
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
std::string pass = obfuscation::deobfuscateFromBase64(doc["password_obf"] | "", &ok);
|
||||||
|
if (!ok || pass.empty()) {
|
||||||
|
pass = doc["password"] | std::string("");
|
||||||
|
if (!pass.empty() && needsResave) *needsResave = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setCredentials(user, pass);
|
||||||
|
store.setServerUrl(doc["serverUrl"] | std::string(""));
|
||||||
|
|
||||||
|
uint8_t method = doc["matchMethod"] | (uint8_t)0;
|
||||||
|
store.setMatchMethod(static_cast<DocumentMatchMethod>(method));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace KOReaderJsonIO
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class KOReaderCredentialStore;
|
||||||
|
|
||||||
|
namespace KOReaderJsonIO {
|
||||||
|
bool save(const KOReaderCredentialStore& store, const char* path);
|
||||||
|
bool load(KOReaderCredentialStore& store, const char* json, bool* needsResave);
|
||||||
|
} // namespace KOReaderJsonIO
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <SecureHttpClient.h>
|
#include <esp_crt_bundle.h>
|
||||||
#include <base64.h>
|
#include <esp_http_client.h>
|
||||||
|
|
||||||
#include <string>
|
#include <ctime>
|
||||||
|
|
||||||
#include "KOReaderCredentialStore.h"
|
#include "KOReaderCredentialStore.h"
|
||||||
|
|
||||||
@@ -16,35 +16,82 @@ namespace {
|
|||||||
constexpr char DEVICE_NAME[] = "CrossPoint";
|
constexpr char DEVICE_NAME[] = "CrossPoint";
|
||||||
constexpr char DEVICE_ID[] = "crosspoint-reader";
|
constexpr char DEVICE_ID[] = "crosspoint-reader";
|
||||||
|
|
||||||
// KOSync's TLS-1.3 servers can't be reached through the precompiled system
|
// Small TLS buffers to fit in ESP32-C3's limited heap (~46KB free after WiFi).
|
||||||
// mbedTLS (TLS 1.3 is stubbed out), so requests run over wolfSSL via
|
// KOSync payloads are tiny JSON (<1KB), so 2KB buffers are sufficient.
|
||||||
// SecureHttpClient. The handshake still needs working heap; gate on it. wolfSSL's
|
// Default 16KB buffers cause OOM during TLS handshake.
|
||||||
// footprint is smaller than mbedTLS's old ~48KB peak, but keep a conservative
|
constexpr int HTTP_BUF_SIZE = 2048;
|
||||||
// floor. Check both total free heap and largest contiguous block so fragmented
|
|
||||||
// heap does not fall through into a failed TLS allocation path.
|
// Cloudflare tunnels send a 3-cert Google Trust Services chain. During the TLS handshake
|
||||||
|
// mbedTLS makes many small allocations that collectively consume ~48KB of heap. With only
|
||||||
|
// ~50KB free after WiFi connects, the session drove min-free-ever down to 2600 bytes before
|
||||||
|
// failing with MBEDTLS_ERR_X509_ALLOC_FAILED (-0x2880). Check total free heap (not max
|
||||||
|
// contiguous block) because the failure mode is aggregate exhaustion, not one large alloc.
|
||||||
constexpr uint32_t MIN_HEAP_FOR_TLS = 55000;
|
constexpr uint32_t MIN_HEAP_FOR_TLS = 55000;
|
||||||
|
|
||||||
// Apply the shared KOSync auth headers after begin(). x-auth-* is the native
|
// Response buffer for reading HTTP body
|
||||||
// KOSync scheme; Basic auth is added for Calibre-Web-Automated compatibility.
|
struct ResponseBuffer {
|
||||||
void applyAuthHeaders(freeink::SecureHttpClient& http) {
|
char* data = nullptr;
|
||||||
http.addHeader("Accept", "application/vnd.koreader.v1+json");
|
int len = 0;
|
||||||
http.addHeader("x-auth-user", KOREADER_STORE.getUsername());
|
int capacity = 0;
|
||||||
http.addHeader("x-auth-key", KOREADER_STORE.getMd5Password());
|
|
||||||
const std::string credentials = KOREADER_STORE.getUsername() + ":" + KOREADER_STORE.getPassword();
|
|
||||||
const String encoded = base64::encode(credentials.c_str());
|
|
||||||
http.addHeader("Authorization", std::string("Basic ") + encoded.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// True when free heap is too low to risk a TLS handshake.
|
~ResponseBuffer() { free(data); }
|
||||||
bool insufficientHeap() {
|
|
||||||
const uint32_t freeHeap = ESP.getFreeHeap();
|
bool ensure(int size) {
|
||||||
const uint32_t maxAllocHeap = ESP.getMaxAllocHeap();
|
if (size <= capacity) return true;
|
||||||
if (freeHeap < MIN_HEAP_FOR_TLS || maxAllocHeap < MIN_HEAP_FOR_TLS) {
|
char* newData = (char*)realloc(data, size);
|
||||||
LOG_ERR("KOSync", "Insufficient heap for TLS handshake: %u bytes free, %u max alloc (need %u)", freeHeap,
|
if (!newData) return false;
|
||||||
maxAllocHeap, MIN_HEAP_FOR_TLS);
|
data = newData;
|
||||||
|
capacity = size;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
};
|
||||||
|
|
||||||
|
// HTTP event handler to collect response body
|
||||||
|
esp_err_t httpEventHandler(esp_http_client_event_t* evt) {
|
||||||
|
auto* buf = static_cast<ResponseBuffer*>(evt->user_data);
|
||||||
|
if (evt->event_id == HTTP_EVENT_ON_DATA && buf) {
|
||||||
|
if (buf->ensure(buf->len + evt->data_len + 1)) {
|
||||||
|
memcpy(buf->data + buf->len, evt->data, evt->data_len);
|
||||||
|
buf->len += evt->data_len;
|
||||||
|
buf->data[buf->len] = '\0';
|
||||||
|
} else {
|
||||||
|
LOG_ERR("KOSync", "Response buffer allocation failed (%d bytes)", evt->data_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create configured esp_http_client with small TLS buffers
|
||||||
|
esp_http_client_handle_t createClient(const char* url, ResponseBuffer* buf,
|
||||||
|
esp_http_client_method_t method = HTTP_METHOD_GET) {
|
||||||
|
esp_http_client_config_t config = {};
|
||||||
|
config.url = url;
|
||||||
|
config.event_handler = httpEventHandler;
|
||||||
|
config.user_data = buf;
|
||||||
|
config.method = method;
|
||||||
|
config.timeout_ms = 15000;
|
||||||
|
config.buffer_size = HTTP_BUF_SIZE;
|
||||||
|
config.buffer_size_tx = HTTP_BUF_SIZE;
|
||||||
|
config.crt_bundle_attach = esp_crt_bundle_attach;
|
||||||
|
|
||||||
|
// HTTP Basic Auth for Calibre-Web-Automated compatibility
|
||||||
|
config.username = KOREADER_STORE.getUsername().c_str();
|
||||||
|
config.password = KOREADER_STORE.getPassword().c_str();
|
||||||
|
config.auth_type = HTTP_AUTH_TYPE_BASIC;
|
||||||
|
|
||||||
|
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||||
|
if (!client) return nullptr;
|
||||||
|
|
||||||
|
// KOSync auth headers
|
||||||
|
if (esp_http_client_set_header(client, "Accept", "application/vnd.koreader.v1+json") != ESP_OK ||
|
||||||
|
esp_http_client_set_header(client, "x-auth-user", KOREADER_STORE.getUsername().c_str()) != ESP_OK ||
|
||||||
|
esp_http_client_set_header(client, "x-auth-key", KOREADER_STORE.getMd5Password().c_str()) != ESP_OK) {
|
||||||
|
LOG_ERR("KOSync", "Failed to set auth headers");
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -55,66 +102,31 @@ KOReaderSyncClient::Error KOReaderSyncClient::authenticate() {
|
|||||||
return NO_CREDENTIALS;
|
return NO_CREDENTIALS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string url = KOREADER_STORE.getBaseUrl() + "/users/auth";
|
std::string url = KOREADER_STORE.getBaseUrl() + "/users/auth";
|
||||||
LOG_DBG("KOSync", "Authenticating: %s (heap: %u)", url.c_str(), (unsigned)ESP.getFreeHeap());
|
const uint32_t freeHeap = ESP.getFreeHeap();
|
||||||
if (insufficientHeap()) return LOW_MEMORY;
|
LOG_DBG("KOSync", "Authenticating: %s (heap: %u)", url.c_str(), (unsigned)freeHeap);
|
||||||
|
if (freeHeap < MIN_HEAP_FOR_TLS) {
|
||||||
freeink::SecureHttpClient http;
|
LOG_ERR("KOSync", "Insufficient heap for TLS handshake: %u bytes free (need %u)", freeHeap, MIN_HEAP_FOR_TLS);
|
||||||
http.setInsecure();
|
return LOW_MEMORY;
|
||||||
if (!http.begin(url)) {
|
|
||||||
LOG_ERR("KOSync", "Bad URL: %s", url.c_str());
|
|
||||||
return NETWORK_ERROR;
|
|
||||||
}
|
}
|
||||||
applyAuthHeaders(http);
|
|
||||||
const int httpCode = http.GET();
|
ResponseBuffer buf;
|
||||||
http.end();
|
esp_http_client_handle_t client = createClient(url.c_str(), &buf);
|
||||||
|
if (!client) return NETWORK_ERROR;
|
||||||
|
|
||||||
|
esp_err_t err = esp_http_client_perform(client);
|
||||||
|
const int httpCode = esp_http_client_get_status_code(client);
|
||||||
lastHttpCode = httpCode;
|
lastHttpCode = httpCode;
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
|
||||||
LOG_DBG("KOSync", "Auth response: %d", httpCode);
|
LOG_DBG("KOSync", "Auth response: %d (err: %d)", httpCode, err);
|
||||||
|
|
||||||
if (httpCode <= 0) return NETWORK_ERROR;
|
if (err != ESP_OK) return NETWORK_ERROR;
|
||||||
if (httpCode == 200) return OK;
|
if (httpCode == 200) return OK;
|
||||||
if (httpCode == 401) return AUTH_FAILED;
|
if (httpCode == 401) return AUTH_FAILED;
|
||||||
return SERVER_ERROR;
|
return SERVER_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
KOReaderSyncClient::Error KOReaderSyncClient::createUser() {
|
|
||||||
lastHttpCode = 0;
|
|
||||||
if (!KOREADER_STORE.hasCredentials()) {
|
|
||||||
LOG_DBG("KOSync", "No credentials configured");
|
|
||||||
return NO_CREDENTIALS;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string url = KOREADER_STORE.getBaseUrl() + "/users/create";
|
|
||||||
LOG_DBG("KOSync", "Creating account: %s (heap: %u)", url.c_str(), (unsigned)ESP.getFreeHeap());
|
|
||||||
if (insufficientHeap()) return LOW_MEMORY;
|
|
||||||
|
|
||||||
JsonDocument doc;
|
|
||||||
doc["username"] = KOREADER_STORE.getUsername();
|
|
||||||
doc["password"] = KOREADER_STORE.getMd5Password();
|
|
||||||
std::string body;
|
|
||||||
serializeJson(doc, body);
|
|
||||||
|
|
||||||
freeink::SecureHttpClient http;
|
|
||||||
http.setInsecure();
|
|
||||||
if (!http.begin(url)) {
|
|
||||||
LOG_ERR("KOSync", "Bad URL: %s", url.c_str());
|
|
||||||
return NETWORK_ERROR;
|
|
||||||
}
|
|
||||||
http.addHeader("Accept", "application/vnd.koreader.v1+json");
|
|
||||||
http.addHeader("Content-Type", "application/json");
|
|
||||||
const int httpCode = http.sendRequest("POST", body);
|
|
||||||
http.end();
|
|
||||||
lastHttpCode = httpCode;
|
|
||||||
|
|
||||||
LOG_DBG("KOSync", "Create user response: %d", httpCode);
|
|
||||||
|
|
||||||
if (httpCode <= 0) return NETWORK_ERROR;
|
|
||||||
if (httpCode == 200 || httpCode == 201) return OK;
|
|
||||||
if (httpCode == 402) return USER_EXISTS;
|
|
||||||
return SERVER_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& documentHash,
|
KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& documentHash,
|
||||||
KOReaderProgress& outProgress) {
|
KOReaderProgress& outProgress) {
|
||||||
lastHttpCode = 0;
|
lastHttpCode = 0;
|
||||||
@@ -123,31 +135,30 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc
|
|||||||
return NO_CREDENTIALS;
|
return NO_CREDENTIALS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress/" + documentHash;
|
std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress/" + documentHash;
|
||||||
LOG_DBG("KOSync", "Getting progress: %s (heap: %u)", url.c_str(), (unsigned)ESP.getFreeHeap());
|
const uint32_t freeHeap = ESP.getFreeHeap();
|
||||||
if (insufficientHeap()) return LOW_MEMORY;
|
LOG_DBG("KOSync", "Getting progress: %s (heap: %u)", url.c_str(), (unsigned)freeHeap);
|
||||||
|
if (freeHeap < MIN_HEAP_FOR_TLS) {
|
||||||
freeink::SecureHttpClient http;
|
LOG_ERR("KOSync", "Insufficient heap for TLS handshake: %u bytes free (need %u)", freeHeap, MIN_HEAP_FOR_TLS);
|
||||||
http.setInsecure();
|
return LOW_MEMORY;
|
||||||
if (!http.begin(url)) {
|
|
||||||
LOG_ERR("KOSync", "Bad URL: %s", url.c_str());
|
|
||||||
return NETWORK_ERROR;
|
|
||||||
}
|
}
|
||||||
applyAuthHeaders(http);
|
|
||||||
const int httpCode = http.GET();
|
ResponseBuffer buf;
|
||||||
|
esp_http_client_handle_t client = createClient(url.c_str(), &buf);
|
||||||
|
if (!client) return NETWORK_ERROR;
|
||||||
|
|
||||||
|
esp_err_t err = esp_http_client_perform(client);
|
||||||
|
const int httpCode = esp_http_client_get_status_code(client);
|
||||||
lastHttpCode = httpCode;
|
lastHttpCode = httpCode;
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
|
||||||
LOG_DBG("KOSync", "Get progress response: %d", httpCode);
|
LOG_DBG("KOSync", "Get progress response: %d (err: %d)", httpCode, err);
|
||||||
|
|
||||||
if (httpCode <= 0) {
|
if (err != ESP_OK) return NETWORK_ERROR;
|
||||||
http.end();
|
|
||||||
return NETWORK_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (httpCode == 200) {
|
if (httpCode == 200 && buf.data) {
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
const DeserializationError error = deserializeJson(doc, http.getString().c_str());
|
const DeserializationError error = deserializeJson(doc, buf.data);
|
||||||
http.end();
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
LOG_ERR("KOSync", "JSON parse failed: %s", error.c_str());
|
LOG_ERR("KOSync", "JSON parse failed: %s", error.c_str());
|
||||||
@@ -161,29 +172,10 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc
|
|||||||
outProgress.deviceId = doc["device_id"].as<std::string>();
|
outProgress.deviceId = doc["device_id"].as<std::string>();
|
||||||
outProgress.timestamp = doc["timestamp"].as<int64_t>();
|
outProgress.timestamp = doc["timestamp"].as<int64_t>();
|
||||||
|
|
||||||
// Extended crosspoint-sync field; absent on plain kosync servers.
|
|
||||||
outProgress.position.reset();
|
|
||||||
const JsonObjectConst pos = doc["position"].as<JsonObjectConst>();
|
|
||||||
if (!pos.isNull()) {
|
|
||||||
KOReaderRichPosition rich;
|
|
||||||
rich.pctQ = pos["pctQ"].as<uint32_t>();
|
|
||||||
rich.spineIndex = pos["spine"].as<uint16_t>();
|
|
||||||
rich.pageNumber = pos["page"].as<uint16_t>();
|
|
||||||
const uint16_t pages = pos["pages"].as<uint16_t>();
|
|
||||||
rich.totalPages = pages > 0 ? pages : 1;
|
|
||||||
const uint16_t para = pos["para"].as<uint16_t>();
|
|
||||||
if (para > 0) rich.paragraphIndex = para;
|
|
||||||
rich.xpath = pos["xpath"].as<const char*>() ? pos["xpath"].as<const char*>() : "";
|
|
||||||
LOG_DBG("KOSync", "Got rich position: spine=%u page=%u/%u para=%u", rich.spineIndex, rich.pageNumber,
|
|
||||||
rich.totalPages, para);
|
|
||||||
outProgress.position = std::move(rich);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("KOSync", "Got progress: %.2f%% at %s", outProgress.percentage * 100, outProgress.progress.c_str());
|
LOG_DBG("KOSync", "Got progress: %.2f%% at %s", outProgress.percentage * 100, outProgress.progress.c_str());
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
http.end();
|
|
||||||
if (httpCode == 401) return AUTH_FAILED;
|
if (httpCode == 401) return AUTH_FAILED;
|
||||||
if (httpCode == 404) return NOT_FOUND;
|
if (httpCode == 404) return NOT_FOUND;
|
||||||
return SERVER_ERROR;
|
return SERVER_ERROR;
|
||||||
@@ -196,56 +188,46 @@ KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgr
|
|||||||
return NO_CREDENTIALS;
|
return NO_CREDENTIALS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress";
|
std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress";
|
||||||
LOG_DBG("KOSync", "Updating progress: %s (heap: %u)", url.c_str(), (unsigned)ESP.getFreeHeap());
|
const uint32_t freeHeap = ESP.getFreeHeap();
|
||||||
if (insufficientHeap()) return LOW_MEMORY;
|
LOG_DBG("KOSync", "Updating progress: %s (heap: %u)", url.c_str(), (unsigned)freeHeap);
|
||||||
|
if (freeHeap < MIN_HEAP_FOR_TLS) {
|
||||||
|
LOG_ERR("KOSync", "Insufficient heap for TLS handshake: %u bytes free (need %u)", freeHeap, MIN_HEAP_FOR_TLS);
|
||||||
|
return LOW_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
// Build JSON body
|
// Build JSON body
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc["document"] = progress.document;
|
doc["document"] = progress.document;
|
||||||
if (progress.metadata.has_value()) {
|
|
||||||
auto meta = doc["metadata"].to<JsonObject>();
|
|
||||||
meta["filename"] = progress.metadata->filename;
|
|
||||||
meta["title"] = progress.metadata->title;
|
|
||||||
meta["authors"] = progress.metadata->authors;
|
|
||||||
}
|
|
||||||
doc["progress"] = progress.progress;
|
doc["progress"] = progress.progress;
|
||||||
doc["percentage"] = progress.percentage;
|
doc["percentage"] = progress.percentage;
|
||||||
doc["device"] = DEVICE_NAME;
|
doc["device"] = DEVICE_NAME;
|
||||||
doc["device_id"] = DEVICE_ID;
|
doc["device_id"] = DEVICE_ID;
|
||||||
if (progress.position.has_value()) {
|
|
||||||
// Extended crosspoint-sync field; kosync servers ignore unknown keys.
|
|
||||||
const auto& p = *progress.position;
|
|
||||||
auto pos = doc["position"].to<JsonObject>();
|
|
||||||
pos["pctQ"] = p.pctQ;
|
|
||||||
pos["spine"] = p.spineIndex;
|
|
||||||
pos["page"] = p.pageNumber;
|
|
||||||
pos["pages"] = p.totalPages;
|
|
||||||
if (p.paragraphIndex.has_value()) pos["para"] = *p.paragraphIndex;
|
|
||||||
// Server rejects the whole position object if xpath exceeds 120 bytes.
|
|
||||||
if (!p.xpath.empty() && p.xpath.size() <= 120) pos["xpath"] = p.xpath;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string body;
|
std::string body;
|
||||||
serializeJson(doc, body);
|
serializeJson(doc, body);
|
||||||
|
|
||||||
LOG_DBG("KOSync", "Request body: %s", body.c_str());
|
LOG_DBG("KOSync", "Request body: %s", body.c_str());
|
||||||
|
|
||||||
freeink::SecureHttpClient http;
|
ResponseBuffer buf;
|
||||||
http.setInsecure();
|
esp_http_client_handle_t client = createClient(url.c_str(), &buf, HTTP_METHOD_PUT);
|
||||||
if (!http.begin(url)) {
|
if (!client) return NETWORK_ERROR;
|
||||||
LOG_ERR("KOSync", "Bad URL: %s", url.c_str());
|
|
||||||
|
if (esp_http_client_set_header(client, "Content-Type", "application/json") != ESP_OK ||
|
||||||
|
esp_http_client_set_post_field(client, body.c_str(), body.length()) != ESP_OK) {
|
||||||
|
LOG_ERR("KOSync", "Failed to set request body");
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
return NETWORK_ERROR;
|
return NETWORK_ERROR;
|
||||||
}
|
}
|
||||||
applyAuthHeaders(http);
|
|
||||||
http.addHeader("Content-Type", "application/json");
|
esp_err_t err = esp_http_client_perform(client);
|
||||||
const int httpCode = http.sendRequest("PUT", body);
|
const int httpCode = esp_http_client_get_status_code(client);
|
||||||
http.end();
|
|
||||||
lastHttpCode = httpCode;
|
lastHttpCode = httpCode;
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
|
||||||
LOG_DBG("KOSync", "Update progress response: %d", httpCode);
|
LOG_DBG("KOSync", "Update progress response: %d (err: %d)", httpCode, err);
|
||||||
|
|
||||||
if (httpCode <= 0) return NETWORK_ERROR;
|
if (err != ESP_OK) return NETWORK_ERROR;
|
||||||
if (httpCode == 200 || httpCode == 202) return OK;
|
if (httpCode == 200 || httpCode == 202) return OK;
|
||||||
if (httpCode == 401) return AUTH_FAILED;
|
if (httpCode == 401) return AUTH_FAILED;
|
||||||
return SERVER_ERROR;
|
return SERVER_ERROR;
|
||||||
|
|||||||
@@ -1,34 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional document metadata sent alongside progress sync requests.
|
|
||||||
* Mirrors the metadata object added in KOReader PR #15306.
|
|
||||||
* The official sync server ignores this field; custom servers may use it.
|
|
||||||
*/
|
|
||||||
struct KOReaderMetadata {
|
|
||||||
std::string filename; // e.g. "my_book.epub"
|
|
||||||
std::string title; // Document title from EPUB metadata
|
|
||||||
std::string authors; // Author(s) from EPUB metadata
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rich CrossPoint position sent alongside progress uploads. Maps 1:1 onto the
|
|
||||||
* crosspoint-sync extended `position` object (see crosspoint-sync docs/API.md).
|
|
||||||
* The official KOSync server ignores unknown fields; crosspoint-sync stores it
|
|
||||||
* so CrossPoint<->CrossPoint sync is lossless instead of xpath-approximated.
|
|
||||||
*/
|
|
||||||
struct KOReaderRichPosition {
|
|
||||||
uint32_t pctQ = 0; // Percentage quantized 0..1,000,000 (authoritative)
|
|
||||||
uint16_t spineIndex = 0; // Spine (chapter) index
|
|
||||||
uint16_t pageNumber = 0; // Page within spine (layout-dependent hint)
|
|
||||||
uint16_t totalPages = 1; // Spine page count (layout-dependent hint)
|
|
||||||
std::optional<uint16_t> paragraphIndex; // Synthetic 1-based paragraph index
|
|
||||||
std::string xpath; // KOReader-style xpath (server cap: 120 bytes)
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Progress data from KOReader sync server.
|
* Progress data from KOReader sync server.
|
||||||
*/
|
*/
|
||||||
@@ -39,8 +11,6 @@ struct KOReaderProgress {
|
|||||||
std::string device; // Device name
|
std::string device; // Device name
|
||||||
std::string deviceId; // Device ID
|
std::string deviceId; // Device ID
|
||||||
int64_t timestamp; // Unix timestamp of last update
|
int64_t timestamp; // Unix timestamp of last update
|
||||||
std::optional<KOReaderMetadata> metadata; // Optional document metadata
|
|
||||||
std::optional<KOReaderRichPosition> position; // Optional rich position (crosspoint-sync servers only)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,17 +29,7 @@ struct KOReaderProgress {
|
|||||||
*/
|
*/
|
||||||
class KOReaderSyncClient {
|
class KOReaderSyncClient {
|
||||||
public:
|
public:
|
||||||
enum Error {
|
enum Error { OK = 0, NO_CREDENTIALS, NETWORK_ERROR, AUTH_FAILED, SERVER_ERROR, JSON_ERROR, NOT_FOUND, LOW_MEMORY };
|
||||||
OK = 0,
|
|
||||||
NO_CREDENTIALS,
|
|
||||||
NETWORK_ERROR,
|
|
||||||
AUTH_FAILED,
|
|
||||||
SERVER_ERROR,
|
|
||||||
JSON_ERROR,
|
|
||||||
NOT_FOUND,
|
|
||||||
LOW_MEMORY,
|
|
||||||
USER_EXISTS
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticate with the sync server (validate credentials).
|
* Authenticate with the sync server (validate credentials).
|
||||||
@@ -77,14 +37,6 @@ class KOReaderSyncClient {
|
|||||||
*/
|
*/
|
||||||
static Error authenticate();
|
static Error authenticate();
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a new account on the sync server using the stored credentials
|
|
||||||
* (POST /users/create with the MD5 auth key — the server never sees the
|
|
||||||
* plain password).
|
|
||||||
* @return OK on success, USER_EXISTS if the username is taken
|
|
||||||
*/
|
|
||||||
static Error createUser();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get reading progress for a document.
|
* Get reading progress for a document.
|
||||||
* @param documentHash The document hash (from KOReaderDocumentId)
|
* @param documentHash The document hash (from KOReaderDocumentId)
|
||||||
|
|||||||
@@ -724,59 +724,6 @@ SavedProgressPosition ProgressMapper::toSavedProgress(const std::shared_ptr<Epub
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<CrossPointPosition> ProgressMapper::fromRichPosition(const std::shared_ptr<Epub>& epub,
|
|
||||||
const KOReaderRichPosition& rich,
|
|
||||||
GfxRenderer& renderer) {
|
|
||||||
const int spineCount = epub->getSpineItemsCount();
|
|
||||||
if (static_cast<int>(rich.spineIndex) >= spineCount) {
|
|
||||||
LOG_DBG("PM", "Rich position spine %u out of range (%d spine items)", rich.spineIndex, spineCount);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
CrossPointPosition result{};
|
|
||||||
result.spineIndex = rich.spineIndex;
|
|
||||||
|
|
||||||
Section tempSection(epub, result.spineIndex, renderer);
|
|
||||||
const auto cachedCount = tempSection.getCachedPageCount();
|
|
||||||
if (!cachedCount || *cachedCount <= 0) {
|
|
||||||
// No local layout for the target spine yet; the percentage/xpath mapping
|
|
||||||
// handles density estimation better than a blind copy of remote pages.
|
|
||||||
LOG_DBG("PM", "Rich position spine %u has no cached page count", rich.spineIndex);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
result.totalPages = *cachedCount;
|
|
||||||
|
|
||||||
const int remotePages = rich.totalPages > 0 ? rich.totalPages : 1;
|
|
||||||
if (result.totalPages == remotePages) {
|
|
||||||
// Identical layout (same render settings) — the page transfers losslessly.
|
|
||||||
result.pageNumber = std::min<int>(rich.pageNumber, result.totalPages - 1);
|
|
||||||
LOG_DBG("PM", "Rich position exact: spine=%d page=%d/%d", result.spineIndex, result.pageNumber, result.totalPages);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Layout differs; the paragraph LUT is the most accurate anchor we have.
|
|
||||||
if (rich.paragraphIndex.has_value()) {
|
|
||||||
const auto lutPage = tempSection.getPageForParagraphIndex(*rich.paragraphIndex);
|
|
||||||
if (lutPage.has_value()) {
|
|
||||||
result.paragraphIndex = *rich.paragraphIndex;
|
|
||||||
result.hasParagraphIndex = true;
|
|
||||||
result.pageNumber = std::min<int>(*lutPage, result.totalPages - 1);
|
|
||||||
LOG_DBG("PM", "Rich position para %u -> spine=%d page=%d/%d", *rich.paragraphIndex, result.spineIndex,
|
|
||||||
result.pageNumber, result.totalPages);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to the intra-spine page fraction.
|
|
||||||
const float intra =
|
|
||||||
(remotePages > 1) ? static_cast<float>(rich.pageNumber) / static_cast<float>(remotePages - 1) : 0.0f;
|
|
||||||
result.pageNumber = std::max(
|
|
||||||
0, std::min(static_cast<int>(intra * static_cast<float>(result.totalPages - 1) + 0.5f), result.totalPages - 1));
|
|
||||||
LOG_DBG("PM", "Rich position scaled: spine=%d remote %u/%d -> page=%d/%d", result.spineIndex, rich.pageNumber,
|
|
||||||
remotePages, result.pageNumber, result.totalPages);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr<Epub>& epub, const SavedProgressPosition& koPos,
|
CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr<Epub>& epub, const SavedProgressPosition& koPos,
|
||||||
GfxRenderer& renderer, int currentSpineIndex,
|
GfxRenderer& renderer, int currentSpineIndex,
|
||||||
int totalPagesInCurrentSpine, int fallbackTotalPages) {
|
int totalPagesInCurrentSpine, int fallbackTotalPages) {
|
||||||
|
|||||||
@@ -3,11 +3,8 @@
|
|||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "KOReaderSyncClient.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CrossPoint position representation.
|
* CrossPoint position representation.
|
||||||
*/
|
*/
|
||||||
@@ -68,20 +65,6 @@ class ProgressMapper {
|
|||||||
GfxRenderer& renderer, int currentSpineIndex = -1,
|
GfxRenderer& renderer, int currentSpineIndex = -1,
|
||||||
int totalPagesInCurrentSpine = 0, int fallbackTotalPages = 0);
|
int totalPagesInCurrentSpine = 0, int fallbackTotalPages = 0);
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a rich CrossPoint position (downloaded from a crosspoint-sync
|
|
||||||
* server) directly to a CrossPoint position, without XPath approximation.
|
|
||||||
* When the local layout matches the uploader's (same spine page count) the
|
|
||||||
* page transfers losslessly; otherwise the paragraph LUT or the intra-spine
|
|
||||||
* page fraction is used.
|
|
||||||
*
|
|
||||||
* @return The position, or std::nullopt when the rich position cannot be
|
|
||||||
* applied (spine out of range, no section cache) and the caller
|
|
||||||
* should fall back to toCrossPoint().
|
|
||||||
*/
|
|
||||||
static std::optional<CrossPointPosition> fromRichPosition(const std::shared_ptr<Epub>& epub,
|
|
||||||
const KOReaderRichPosition& rich, GfxRenderer& renderer);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Generate a fallback XPath by streaming the spine item's XHTML and resolving
|
* Generate a fallback XPath by streaming the spine item's XHTML and resolving
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
|
|
||||||
#include <BoardConfig.h>
|
|
||||||
#include <esp_rom_sys.h>
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#define MAX_ENTRY_LEN 256
|
#define MAX_ENTRY_LEN 256
|
||||||
@@ -62,16 +59,9 @@ void logPrintf(const char* level, const char* origin, const char* format, ...) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
va_end(args);
|
va_end(args);
|
||||||
#if FREEINK_LOG_TRANSPORT == FREEINK_LOG_TRANSPORT_ROM_PRINTF
|
|
||||||
// IDF/ROM console path for boards monitored over USB-Serial-JTAG, where the
|
|
||||||
// HWCDC `operator bool` reads false under `pio device monitor` and logs would
|
|
||||||
// otherwise be silently dropped (e.g. Sticky).
|
|
||||||
esp_rom_printf("%s", buf);
|
|
||||||
#else
|
|
||||||
if (logSerial) {
|
if (logSerial) {
|
||||||
logSerial.print(buf);
|
logSerial.print(buf);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
addToLogRingBuffer(buf);
|
addToLogRingBuffer(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
|
|
||||||
#include <HWCDC.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -31,13 +27,7 @@ won't trigger deprecation warnings.
|
|||||||
#define LOG_LEVEL 0
|
#define LOG_LEVEL 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
|
|
||||||
static HWCDC& logSerial = Serial;
|
static HWCDC& logSerial = Serial;
|
||||||
#define LOG_SERIAL_HAS_TX_TIMEOUT 1
|
|
||||||
#else
|
|
||||||
static HardwareSerial& logSerial = Serial;
|
|
||||||
#define LOG_SERIAL_HAS_TX_TIMEOUT 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void logPrintf(const char* level, const char* origin, const char* format, ...);
|
void logPrintf(const char* level, const char* origin, const char* format, ...);
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
#include "BuildScratch.h"
|
|
||||||
|
|
||||||
#include <Logging.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace buildscratch {
|
|
||||||
namespace {
|
|
||||||
uint8_t* block = nullptr;
|
|
||||||
size_t blockLen = 0;
|
|
||||||
// atomic exchange so an opportunistic claim from another task can never
|
|
||||||
// double-hand-out the block (single core, but FreeRTOS preempts).
|
|
||||||
std::atomic<bool> claimed{false};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void lend(uint8_t* buf, const size_t len) {
|
|
||||||
if (block) {
|
|
||||||
LOG_ERR("SCR", "Build scratch lent twice; ignoring second lend");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
block = buf;
|
|
||||||
blockLen = len;
|
|
||||||
claimed.store(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reclaim() {
|
|
||||||
if (claimed.load()) {
|
|
||||||
// A consumer still holds the block. The storage stays valid (it is the
|
|
||||||
// framebuffer allocation, never freed) but its contents are about to be
|
|
||||||
// clobbered; the consumer's output will be garbage. Loud log so a
|
|
||||||
// lifetime bug is visible instead of a silent corrupt decode.
|
|
||||||
LOG_ERR("SCR", "Build scratch reclaimed while still claimed");
|
|
||||||
}
|
|
||||||
block = nullptr;
|
|
||||||
blockLen = 0;
|
|
||||||
claimed.store(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t* claim(const size_t minLen, size_t* lenOut) {
|
|
||||||
if (!block || blockLen < minLen) return nullptr;
|
|
||||||
bool expected = false;
|
|
||||||
if (!claimed.compare_exchange_strong(expected, true)) return nullptr;
|
|
||||||
if (lenOut) *lenOut = blockLen;
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
void release(const uint8_t* p) {
|
|
||||||
if (p && p == block) claimed.store(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace buildscratch
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
// Registry for the framebuffer bytes lent out during a build phase
|
|
||||||
// (GfxRenderer::FrameBufferLoan). The lender (GfxRenderer) deposits the block
|
|
||||||
// with lend()/reclaim(); a memory-hungry consumer (e.g. InflateStream's ~43KB
|
|
||||||
// tinfl state + window) may claim() it instead of allocating from the heap.
|
|
||||||
//
|
|
||||||
// Exactly one claimant at a time; claim() returns nullptr when the block is
|
|
||||||
// absent or already claimed, and consumers must fall back to the heap. The
|
|
||||||
// underlying storage is the framebuffer allocation itself, which is never
|
|
||||||
// freed -- so even the pathological case (reclaim() while still claimed, which
|
|
||||||
// logs an error) reads garbage, never freed memory.
|
|
||||||
namespace buildscratch {
|
|
||||||
|
|
||||||
// Lender side (GfxRenderer only).
|
|
||||||
void lend(uint8_t* buf, size_t len);
|
|
||||||
void reclaim();
|
|
||||||
|
|
||||||
// Consumer side: exclusive claim of the whole block if it is at least minLen
|
|
||||||
// bytes; nullptr means "use the heap". Release with the same pointer.
|
|
||||||
uint8_t* claim(size_t minLen, size_t* lenOut = nullptr);
|
|
||||||
void release(const uint8_t* p);
|
|
||||||
|
|
||||||
} // namespace buildscratch
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user