mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
Compare commits
42
Commits
develop
...
feat-touch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
757c0b6d9e | ||
|
|
2c8ef01894 | ||
|
|
1bd718fad9 | ||
|
|
61cb4946d6 | ||
|
|
4e352b9894 | ||
|
|
4e156e1617 | ||
|
|
3e0ba4488b | ||
|
|
e42503f1a4 | ||
|
|
c812091a4a | ||
|
|
beb4e82c9c | ||
|
|
a242397fa0 | ||
|
|
1511ab35d5 | ||
|
|
57c389c0b6 | ||
|
|
06ff5aa44a | ||
|
|
526cf1b6f9 | ||
|
|
6648a980cb | ||
|
|
e567620003 | ||
|
|
0b575d1965 | ||
|
|
4f55444f67 | ||
|
|
4a17d21d80 | ||
|
|
1c63847631 | ||
|
|
4a5b45409f | ||
|
|
e103cdfd11 | ||
|
|
cef0d267b7 | ||
|
|
9f3dddabe6 | ||
|
|
350fc8472a | ||
|
|
79ef9d6a35 | ||
|
|
0036a220be | ||
|
|
8ad538c98b | ||
|
|
f1b5da25b0 | ||
|
|
d739827db2 | ||
|
|
9176e76f5a | ||
|
|
7294610894 | ||
|
|
a51098725d | ||
|
|
6acecd8ea6 | ||
|
|
9b7b9e9094 | ||
|
|
204aff6be6 | ||
|
|
28d8ad565f | ||
|
|
ed6690c0cc | ||
|
|
1bc154da8b | ||
|
|
c308520f9c | ||
|
|
38ad4f9036 |
No files matched your search
+1
-1
Submodule freeink-sdk updated: 8be3236609...329a4bebef.
@@ -37,3 +37,5 @@
|
|||||||
#include <builtinFonts/ubuntu_10_regular.h>
|
#include <builtinFonts/ubuntu_10_regular.h>
|
||||||
#include <builtinFonts/ubuntu_12_bold.h>
|
#include <builtinFonts/ubuntu_12_bold.h>
|
||||||
#include <builtinFonts/ubuntu_12_regular.h>
|
#include <builtinFonts/ubuntu_12_regular.h>
|
||||||
|
#include <builtinFonts/ubuntu_14_bold.h>
|
||||||
|
#include <builtinFonts/ubuntu_14_regular.h>
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -94,6 +94,13 @@ ruby -rdigest -e 'puts [
|
|||||||
].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
))"
|
))"
|
||||||
|
|
||||||
|
echo "#define UI_14_FONT_ID ($(
|
||||||
|
ruby -rdigest -e 'puts [
|
||||||
|
"./ubuntu_14_regular.h",
|
||||||
|
"./ubuntu_14_bold.h",
|
||||||
|
].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
|
))"
|
||||||
|
|
||||||
echo "#define SMALL_FONT_ID ($(
|
echo "#define SMALL_FONT_ID ($(
|
||||||
ruby -rdigest -e 'puts [
|
ruby -rdigest -e 'puts [
|
||||||
"./notosans_8_regular.h",
|
"./notosans_8_regular.h",
|
||||||
|
|||||||
@@ -28,23 +28,42 @@ for size in ${NOTOSANS_FONT_SIZES[@]}; do
|
|||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Small UI chrome (button devices, uiScale 1.0). Rendered 1-bit: crisp at these
|
||||||
|
# sizes and half the flash of 2-bit.
|
||||||
UI_FONT_SIZES=(10 12)
|
UI_FONT_SIZES=(10 12)
|
||||||
|
# Larger UI chrome substituted in on touch/high-density boards via the uiScale
|
||||||
|
# remap (see src/main.cpp setupFonts). Rendered 2-bit so it stays smooth when
|
||||||
|
# enlarged. Touch boards use uiScale 1.2, so UI_12 -> 14.4 -> 14 is the size the
|
||||||
|
# remap actually lands on; add larger sizes here if a board adopts a higher scale.
|
||||||
|
UI_FONT_SIZES_LARGE=(14)
|
||||||
UI_FONT_STYLES=("Regular" "Bold")
|
UI_FONT_STYLES=("Regular" "Bold")
|
||||||
|
|
||||||
|
# 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 are
|
||||||
|
# filled from it while every glyph Ubuntu already has stays unchanged (fontstack
|
||||||
|
# is ordered by descending priority). NotoSansHebrew fills U+05D0-U+05EA so the
|
||||||
|
# Hebrew UI translation renders in menus and settings.
|
||||||
|
generate_ui_font() {
|
||||||
|
local size="$1" style="$2" extra_flags="$3"
|
||||||
|
local font_name="ubuntu_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')"
|
||||||
|
local font_path="../builtinFonts/source/Ubuntu/Ubuntu-${style}.ttf"
|
||||||
|
local hebrew_path="../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-${style}.ttf"
|
||||||
|
local viet_path="../builtinFonts/source/Ubuntu/Ubuntu-Vietnamese-${style}.ttf"
|
||||||
|
local output_path="../builtinFonts/${font_name}.h"
|
||||||
|
python fontconvert.py $font_name $size $font_path $hebrew_path $viet_path \
|
||||||
|
--additional-intervals 0x05D0,0x05EA $extra_flags > $output_path
|
||||||
|
echo "Generated $output_path"
|
||||||
|
}
|
||||||
|
|
||||||
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:]')"
|
generate_ui_font $size $style ""
|
||||||
font_path="../builtinFonts/source/Ubuntu/Ubuntu-${style}.ttf"
|
done
|
||||||
hebrew_path="../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-${style}.ttf"
|
done
|
||||||
# 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
|
for size in ${UI_FONT_SIZES_LARGE[@]}; do
|
||||||
# are filled from it while every glyph Ubuntu already has stays unchanged
|
for style in ${UI_FONT_STYLES[@]}; do
|
||||||
# (fontstack is ordered by descending priority).
|
generate_ui_font $size $style "--2bit --compress"
|
||||||
viet_path="../builtinFonts/source/Ubuntu/Ubuntu-Vietnamese-${style}.ttf"
|
|
||||||
output_path="../builtinFonts/${font_name}.h"
|
|
||||||
python fontconvert.py $font_name $size $font_path $hebrew_path $viet_path \
|
|
||||||
--additional-intervals 0x05D0,0x05EA > $output_path
|
|
||||||
echo "Generated $output_path"
|
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,47 @@ 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";
|
||||||
|
constexpr uint32_t MAX_CACHE_STRING_LEN = 4096;
|
||||||
|
|
||||||
|
bool readStringBounded(HalFile& file, std::string& out, const uint32_t maxLen = MAX_CACHE_STRING_LEN) {
|
||||||
|
uint32_t len = 0;
|
||||||
|
if (file.read(&len, sizeof(len)) != static_cast<int>(sizeof(len))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (len > maxLen || len > static_cast<uint32_t>(file.available())) {
|
||||||
|
LOG_ERR("BMC", "Invalid cache string length: %lu (max=%lu available=%d)", static_cast<unsigned long>(len),
|
||||||
|
static_cast<unsigned long>(maxLen), file.available());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.clear();
|
||||||
|
if (len == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
out.resize(len);
|
||||||
|
return file.read(out.data(), len) == static_cast<int>(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CacheIoLock {
|
||||||
|
public:
|
||||||
|
explicit CacheIoLock(SemaphoreHandle_t mutex) : mutex(mutex) {
|
||||||
|
if (mutex) xSemaphoreTakeRecursive(mutex, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
~CacheIoLock() {
|
||||||
|
if (mutex) xSemaphoreGiveRecursive(mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t mutex;
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
BookMetadataCache::~BookMetadataCache() {
|
||||||
|
if (ioMutex) {
|
||||||
|
vSemaphoreDelete(ioMutex);
|
||||||
|
ioMutex = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ============= WRITING / BUILDING FUNCTIONS ================ */
|
/* ============= WRITING / BUILDING FUNCTIONS ================ */
|
||||||
|
|
||||||
bool BookMetadataCache::beginWrite() {
|
bool BookMetadataCache::beginWrite() {
|
||||||
@@ -375,6 +414,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri
|
|||||||
/* ============= READING / LOADING FUNCTIONS ================ */
|
/* ============= READING / LOADING FUNCTIONS ================ */
|
||||||
|
|
||||||
bool BookMetadataCache::load() {
|
bool BookMetadataCache::load() {
|
||||||
|
CacheIoLock ioLock(ioMutex);
|
||||||
if (!Storage.openFileForRead("BMC", cachePath + bookBinFile, bookFile)) {
|
if (!Storage.openFileForRead("BMC", cachePath + bookBinFile, bookFile)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -392,11 +432,13 @@ bool BookMetadataCache::load() {
|
|||||||
serialization::readPod(bookFile, spineCount);
|
serialization::readPod(bookFile, spineCount);
|
||||||
serialization::readPod(bookFile, tocCount);
|
serialization::readPod(bookFile, tocCount);
|
||||||
|
|
||||||
serialization::readString(bookFile, coreMetadata.title);
|
if (!readStringBounded(bookFile, coreMetadata.title) || !readStringBounded(bookFile, coreMetadata.author) ||
|
||||||
serialization::readString(bookFile, coreMetadata.author);
|
!readStringBounded(bookFile, coreMetadata.language) || !readStringBounded(bookFile, coreMetadata.coverItemHref) ||
|
||||||
serialization::readString(bookFile, coreMetadata.language);
|
!readStringBounded(bookFile, coreMetadata.textReferenceHref)) {
|
||||||
serialization::readString(bookFile, coreMetadata.coverItemHref);
|
LOG_ERR("BMC", "Invalid cache metadata strings");
|
||||||
serialization::readString(bookFile, coreMetadata.textReferenceHref);
|
bookFile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
LOG_DBG("BMC", "Loaded cache data: %d spine, %d TOC entries", spineCount, tocCount);
|
LOG_DBG("BMC", "Loaded cache data: %d spine, %d TOC entries", spineCount, tocCount);
|
||||||
@@ -404,6 +446,7 @@ bool BookMetadataCache::load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index) {
|
BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index) {
|
||||||
|
CacheIoLock ioLock(ioMutex);
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
LOG_ERR("BMC", "getSpineEntry called but cache not loaded");
|
LOG_ERR("BMC", "getSpineEntry called but cache not loaded");
|
||||||
return {};
|
return {};
|
||||||
@@ -418,11 +461,16 @@ BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index)
|
|||||||
bookFile.seek(lutOffset + sizeof(uint32_t) * index);
|
bookFile.seek(lutOffset + sizeof(uint32_t) * index);
|
||||||
uint32_t spineEntryPos;
|
uint32_t spineEntryPos;
|
||||||
serialization::readPod(bookFile, spineEntryPos);
|
serialization::readPod(bookFile, spineEntryPos);
|
||||||
|
if (spineEntryPos >= bookFile.size()) {
|
||||||
|
LOG_ERR("BMC", "Spine entry offset out of range: %lu", static_cast<unsigned long>(spineEntryPos));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
bookFile.seek(spineEntryPos);
|
bookFile.seek(spineEntryPos);
|
||||||
return readSpineEntry(bookFile);
|
return readSpineEntry(bookFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
|
BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
|
||||||
|
CacheIoLock ioLock(ioMutex);
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
LOG_ERR("BMC", "getTocEntry called but cache not loaded");
|
LOG_ERR("BMC", "getTocEntry called but cache not loaded");
|
||||||
return {};
|
return {};
|
||||||
@@ -437,24 +485,34 @@ BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
|
|||||||
bookFile.seek(lutOffset + sizeof(uint32_t) * spineCount + sizeof(uint32_t) * index);
|
bookFile.seek(lutOffset + sizeof(uint32_t) * spineCount + sizeof(uint32_t) * index);
|
||||||
uint32_t tocEntryPos;
|
uint32_t tocEntryPos;
|
||||||
serialization::readPod(bookFile, tocEntryPos);
|
serialization::readPod(bookFile, tocEntryPos);
|
||||||
|
if (tocEntryPos >= bookFile.size()) {
|
||||||
|
LOG_ERR("BMC", "TOC entry offset out of range: %lu", static_cast<unsigned long>(tocEntryPos));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
bookFile.seek(tocEntryPos);
|
bookFile.seek(tocEntryPos);
|
||||||
return readTocEntry(bookFile);
|
return readTocEntry(bookFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(HalFile& file) const {
|
BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(HalFile& file) const {
|
||||||
SpineEntry entry;
|
SpineEntry entry;
|
||||||
serialization::readString(file, entry.href);
|
if (!readStringBounded(file, entry.href) ||
|
||||||
serialization::readPod(file, entry.cumulativeSize);
|
file.read(&entry.cumulativeSize, sizeof(entry.cumulativeSize)) !=
|
||||||
serialization::readPod(file, entry.tocIndex);
|
static_cast<int>(sizeof(entry.cumulativeSize)) ||
|
||||||
|
file.read(&entry.tocIndex, sizeof(entry.tocIndex)) != static_cast<int>(sizeof(entry.tocIndex))) {
|
||||||
|
LOG_ERR("BMC", "Invalid spine cache entry");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(HalFile& file) const {
|
BookMetadataCache::TocEntry BookMetadataCache::readTocEntry(HalFile& file) const {
|
||||||
TocEntry entry;
|
TocEntry entry;
|
||||||
serialization::readString(file, entry.title);
|
if (!readStringBounded(file, entry.title) || !readStringBounded(file, entry.href) ||
|
||||||
serialization::readString(file, entry.href);
|
!readStringBounded(file, entry.anchor) ||
|
||||||
serialization::readString(file, entry.anchor);
|
file.read(&entry.level, sizeof(entry.level)) != static_cast<int>(sizeof(entry.level)) ||
|
||||||
serialization::readPod(file, entry.level);
|
file.read(&entry.spineIndex, sizeof(entry.spineIndex)) != static_cast<int>(sizeof(entry.spineIndex))) {
|
||||||
serialization::readPod(file, entry.spineIndex);
|
LOG_ERR("BMC", "Invalid TOC cache entry");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
@@ -54,6 +55,7 @@ class BookMetadataCache {
|
|||||||
// Temp file handles during build
|
// Temp file handles during build
|
||||||
HalFile spineFile;
|
HalFile spineFile;
|
||||||
HalFile tocFile;
|
HalFile tocFile;
|
||||||
|
SemaphoreHandle_t ioMutex;
|
||||||
|
|
||||||
// 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 {
|
||||||
@@ -85,8 +87,14 @@ class BookMetadataCache {
|
|||||||
BookMetadata coreMetadata;
|
BookMetadata coreMetadata;
|
||||||
|
|
||||||
explicit BookMetadataCache(std::string cachePath)
|
explicit BookMetadataCache(std::string cachePath)
|
||||||
: cachePath(std::move(cachePath)), lutOffset(0), spineCount(0), tocCount(0), loaded(false), buildMode(false) {}
|
: cachePath(std::move(cachePath)),
|
||||||
~BookMetadataCache() = default;
|
lutOffset(0),
|
||||||
|
spineCount(0),
|
||||||
|
tocCount(0),
|
||||||
|
loaded(false),
|
||||||
|
buildMode(false),
|
||||||
|
ioMutex(xSemaphoreCreateRecursiveMutex()) {}
|
||||||
|
~BookMetadataCache();
|
||||||
|
|
||||||
// Building phase (stream to disk immediately)
|
// Building phase (stream to disk immediately)
|
||||||
bool beginWrite();
|
bool beginWrite();
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
|
|
||||||
if (self->state == IN_PACKAGE && (strcmp(name, "guide") == 0 || strcmp(name, "opf:guide") == 0)) {
|
if (self->state == IN_PACKAGE && (strcmp(name, "guide") == 0 || strcmp(name, "opf:guide") == 0)) {
|
||||||
self->state = IN_GUIDE;
|
self->state = IN_GUIDE;
|
||||||
// TODO Remove print
|
|
||||||
LOG_DBG("COF", "Entering guide state.");
|
LOG_DBG("COF", "Entering guide state.");
|
||||||
if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
if (!Storage.openFileForRead("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
||||||
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.");
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <BidiUtils.h>
|
#include <BidiUtils.h>
|
||||||
#include <FontDecompressor.h>
|
#include <FontDecompressor.h>
|
||||||
#include <HalGPIO.h>
|
#include <HalGPIO.h>
|
||||||
|
#include <Icon.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <SdCardFont.h>
|
#include <SdCardFont.h>
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
@@ -100,6 +101,23 @@ void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::setUiFontRemap(const int* from, const int* to, int count) {
|
||||||
|
if (count < 0) count = 0;
|
||||||
|
if (count > MAX_UI_FONT_REMAP) count = MAX_UI_FONT_REMAP;
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
uiFontFrom_[i] = from[i];
|
||||||
|
uiFontTo_[i] = to[i];
|
||||||
|
}
|
||||||
|
uiFontRemapCount_ = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GfxRenderer::remapUiFont(const int fontId) const {
|
||||||
|
for (int i = 0; i < uiFontRemapCount_; ++i) {
|
||||||
|
if (uiFontFrom_[i] == fontId) return uiFontTo_[i];
|
||||||
|
}
|
||||||
|
return fontId;
|
||||||
|
}
|
||||||
|
|
||||||
// Translate logical (x,y) coordinates to physical panel coordinates based on current orientation
|
// Translate logical (x,y) coordinates to physical panel coordinates based on current orientation
|
||||||
// This should always be inlined for better performance
|
// This should always be inlined for better performance
|
||||||
static inline void rotateCoordinates(const GfxRenderer::Orientation orientation, const int x, const int y, int* phyX,
|
static inline void rotateCoordinates(const GfxRenderer::Orientation orientation, const int x, const int y, int* phyX,
|
||||||
@@ -365,7 +383,7 @@ int GfxRenderer::getTextWidth(const int fontId, const char* text, const EpdFontF
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto fontIt = fontMap.find(fontId);
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
if (fontIt == fontMap.end()) {
|
if (fontIt == fontMap.end()) {
|
||||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -407,7 +425,7 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto fontIt = fontMap.find(fontId);
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
if (fontIt == fontMap.end()) {
|
if (fontIt == fontMap.end()) {
|
||||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return;
|
return;
|
||||||
@@ -1058,6 +1076,21 @@ void GfxRenderer::drawIcon(const uint8_t bitmap[], const int x, const int y, con
|
|||||||
display.drawImageTransparent(bitmap, y, getScreenWidth() - width - x, height, width);
|
display.drawImageTransparent(bitmap, y, getScreenWidth() - width - x, height, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawIcon(const freeink::Icon& icon, const int x, const int y, const bool black) const {
|
||||||
|
// Bits are un-rotated, so each drawn pixel goes through drawPixel (which applies
|
||||||
|
// the orientation transform) — correct in every orientation, unlike the legacy
|
||||||
|
// uint8_t* overload that pre-rotates for one orientation.
|
||||||
|
const int rowBytes = (icon.w + 7) / 8;
|
||||||
|
for (int row = 0; row < icon.h; ++row) {
|
||||||
|
const uint8_t* r = icon.bits + static_cast<int>(row) * rowBytes;
|
||||||
|
for (int col = 0; col < icon.w; ++col) {
|
||||||
|
if (((r[col >> 3] >> (7 - (col & 7))) & 1) == 0) { // 0 = drawn
|
||||||
|
drawPixel(x + col, y + row, black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
|
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
|
||||||
const float cropX, const float cropY) const {
|
const float cropX, const float cropY) const {
|
||||||
if (fontCacheManager_ && fontCacheManager_->isScanning()) return;
|
if (fontCacheManager_ && fontCacheManager_->isScanning()) return;
|
||||||
@@ -1466,6 +1499,38 @@ int GfxRenderer::getScreenHeight() const {
|
|||||||
return panelWidth;
|
return panelWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::tapToLogical(float nx, float ny, int& outX, int& outY) const {
|
||||||
|
// Native panel pixel of the tap (wasTouchTap normalizes over the native panel).
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Inverse of rotateCoordinates() (see the forward transform above): map a
|
||||||
|
// physical/native point back into the current orientation's logical frame.
|
||||||
|
switch (orientation) {
|
||||||
|
case Portrait: // forward: phyX=logY, phyY=panelHeight-1-logX
|
||||||
|
outX = panelHeight - 1 - phyY;
|
||||||
|
outY = phyX;
|
||||||
|
break;
|
||||||
|
case PortraitInverted: // forward: phyX=panelWidth-1-logY, phyY=logX
|
||||||
|
outX = phyY;
|
||||||
|
outY = panelWidth - 1 - phyX;
|
||||||
|
break;
|
||||||
|
case LandscapeClockwise: // forward: phyX=panelWidth-1-logX, phyY=panelHeight-1-logY
|
||||||
|
outX = panelWidth - 1 - phyX;
|
||||||
|
outY = panelHeight - 1 - phyY;
|
||||||
|
break;
|
||||||
|
case LandscapeCounterClockwise: // forward: identity
|
||||||
|
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.
|
||||||
@@ -1557,7 +1622,7 @@ int GfxRenderer::getSpaceWidth(const int fontId, const EpdFontFamily::Style styl
|
|||||||
return fp4::toPixel(sdIt->second->getAdvance(' ', resolvedStyle));
|
return fp4::toPixel(sdIt->second->getAdvance(' ', resolvedStyle));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto fontIt = fontMap.find(fontId);
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
if (fontIt == fontMap.end()) {
|
if (fontIt == fontMap.end()) {
|
||||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1578,7 +1643,7 @@ int GfxRenderer::getSpaceAdvance(const int fontId, const uint32_t leftCp, const
|
|||||||
return fp4::toPixel(sdIt->second->getAdvance(' ', resolvedStyle));
|
return fp4::toPixel(sdIt->second->getAdvance(' ', resolvedStyle));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto fontIt = fontMap.find(fontId);
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
if (fontIt == fontMap.end()) return 0;
|
if (fontIt == fontMap.end()) return 0;
|
||||||
const auto& font = fontIt->second;
|
const auto& font = fontIt->second;
|
||||||
const EpdGlyph* spaceGlyph = font.getGlyph(' ', style);
|
const EpdGlyph* spaceGlyph = font.getGlyph(' ', style);
|
||||||
@@ -1592,7 +1657,7 @@ int GfxRenderer::getSpaceAdvance(const int fontId, const uint32_t leftCp, const
|
|||||||
|
|
||||||
int GfxRenderer::getKerning(const int fontId, const uint32_t leftCp, const uint32_t rightCp,
|
int GfxRenderer::getKerning(const int fontId, const uint32_t leftCp, const uint32_t rightCp,
|
||||||
const EpdFontFamily::Style style) const {
|
const EpdFontFamily::Style style) const {
|
||||||
const auto fontIt = fontMap.find(fontId);
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
if (fontIt == fontMap.end()) return 0;
|
if (fontIt == fontMap.end()) return 0;
|
||||||
const int kernFP = fontIt->second.getKerning(leftCp, rightCp, style); // 4.4 fixed-point
|
const int kernFP = fontIt->second.getKerning(leftCp, rightCp, style); // 4.4 fixed-point
|
||||||
return fp4::toPixel(kernFP); // snap 4.4 fixed-point to nearest pixel
|
return fp4::toPixel(kernFP); // snap 4.4 fixed-point to nearest pixel
|
||||||
@@ -1624,7 +1689,7 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFami
|
|||||||
return fp4::toPixel(widthFP);
|
return fp4::toPixel(widthFP);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto fontIt = fontMap.find(fontId);
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
if (fontIt == fontMap.end()) {
|
if (fontIt == fontMap.end()) {
|
||||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1660,7 +1725,7 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFami
|
|||||||
}
|
}
|
||||||
|
|
||||||
int GfxRenderer::getFontAscenderSize(const int fontId) const {
|
int GfxRenderer::getFontAscenderSize(const int fontId) const {
|
||||||
const auto fontIt = fontMap.find(fontId);
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
if (fontIt == fontMap.end()) {
|
if (fontIt == fontMap.end()) {
|
||||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1670,7 +1735,7 @@ int GfxRenderer::getFontAscenderSize(const int fontId) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int GfxRenderer::getLineHeight(const int fontId) const {
|
int GfxRenderer::getLineHeight(const int fontId) const {
|
||||||
const auto fontIt = fontMap.find(fontId);
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
if (fontIt == fontMap.end()) {
|
if (fontIt == fontMap.end()) {
|
||||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1680,7 +1745,7 @@ int GfxRenderer::getLineHeight(const int fontId) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int GfxRenderer::getTextHeight(const int fontId) const {
|
int GfxRenderer::getTextHeight(const int fontId) const {
|
||||||
const auto fontIt = fontMap.find(fontId);
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
if (fontIt == fontMap.end()) {
|
if (fontIt == fontMap.end()) {
|
||||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1688,6 +1753,27 @@ int GfxRenderer::getTextHeight(const int fontId) const {
|
|||||||
return fontIt->second.getData(EpdFontFamily::REGULAR)->ascender;
|
return fontIt->second.getData(EpdFontFamily::REGULAR)->ascender;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Height of a reference glyph above the baseline (glyph->top): 'H' gives the real
|
||||||
|
// cap height, the actual visible font extent for vertical centering.
|
||||||
|
int GfxRenderer::glyphTop(const int fontId, const uint32_t cp) const {
|
||||||
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
|
if (fontIt == fontMap.end()) return 0;
|
||||||
|
const EpdGlyph* g = fontIt->second.getGlyph(cp, EpdFontFamily::REGULAR);
|
||||||
|
return g ? g->top : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GfxRenderer::getFontCapHeight(const int fontId) const { return glyphTop(fontId, 'H'); }
|
||||||
|
|
||||||
|
int GfxRenderer::getTextVisualCenterOffset(const int fontId) const {
|
||||||
|
// Cap-height middle: baseline is at top + ascender, caps reach capHeight above it,
|
||||||
|
// so the optical center is ascender - capHeight/2 below the top. Cap (not x) height
|
||||||
|
// because UI labels are capital-led. Falls back to ascender*0.65 without an 'H'.
|
||||||
|
const int ascender = getFontAscenderSize(fontId);
|
||||||
|
const int capHeight = getFontCapHeight(fontId);
|
||||||
|
if (capHeight <= 0) return (ascender * 65) / 100;
|
||||||
|
return ascender - capHeight / 2;
|
||||||
|
}
|
||||||
|
|
||||||
void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y, const char* text, const bool black,
|
void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y, const char* text, const bool black,
|
||||||
const EpdFontFamily::Style style) const {
|
const EpdFontFamily::Style style) const {
|
||||||
// Cannot draw a NULL / empty string
|
// Cannot draw a NULL / empty string
|
||||||
@@ -1695,7 +1781,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto fontIt = fontMap.find(fontId);
|
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||||
if (fontIt == fontMap.end()) {
|
if (fontIt == fontMap.end()) {
|
||||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ enum class BidiBaseDir : signed char { AUTO = -1, LTR = 0, RTL = 1 };
|
|||||||
|
|
||||||
class FontCacheManager;
|
class FontCacheManager;
|
||||||
class SdCardFont;
|
class SdCardFont;
|
||||||
|
namespace freeink {
|
||||||
|
struct Icon;
|
||||||
|
}
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -51,6 +54,13 @@ class GfxRenderer {
|
|||||||
uint32_t frameBufferSize = HalDisplay::BUFFER_SIZE;
|
uint32_t frameBufferSize = HalDisplay::BUFFER_SIZE;
|
||||||
std::vector<uint8_t*> bwBufferChunks;
|
std::vector<uint8_t*> bwBufferChunks;
|
||||||
std::map<int, EpdFontFamily> fontMap;
|
std::map<int, EpdFontFamily> fontMap;
|
||||||
|
// UI chrome font remap table (see setUiFontRemap). Empty = identity.
|
||||||
|
static constexpr int MAX_UI_FONT_REMAP = 8;
|
||||||
|
int uiFontFrom_[MAX_UI_FONT_REMAP] = {0};
|
||||||
|
int uiFontTo_[MAX_UI_FONT_REMAP] = {0};
|
||||||
|
int uiFontRemapCount_ = 0;
|
||||||
|
int remapUiFont(int fontId) const;
|
||||||
|
int glyphTop(int fontId, uint32_t cp) const;
|
||||||
// Mutable because ensureSdCardFontReady() is const (called from layout code
|
// Mutable because ensureSdCardFontReady() is const (called from layout code
|
||||||
// that holds a const GfxRenderer&) but triggers SD card reads and heap
|
// that holds a const GfxRenderer&) but triggers SD card reads and heap
|
||||||
// allocation inside the SdCardFont objects. Same pragmatic compromise as
|
// allocation inside the SdCardFont objects. Same pragmatic compromise as
|
||||||
@@ -101,6 +111,14 @@ class GfxRenderer {
|
|||||||
// Setup
|
// Setup
|
||||||
void begin(); // must be called right after display.begin()
|
void begin(); // must be called right after display.begin()
|
||||||
void insertFont(int fontId, EpdFontFamily font);
|
void insertFont(int fontId, EpdFontFamily font);
|
||||||
|
// Orientation-correct icon blit for the freeink::Icon format (un-rotated bits +
|
||||||
|
// optical-center metadata). Prefer this over the legacy raw-bitmap drawIcon.
|
||||||
|
void drawIcon(const freeink::Icon& icon, int x, int y, bool black = true) const;
|
||||||
|
// UI chrome font scaling: firmware supplies a small remap table (from the board
|
||||||
|
// uiScale) that substitutes a larger font for each scaled UI font id at lookup
|
||||||
|
// time, so layout and drawing stay consistent with no call-site changes. Reader
|
||||||
|
// body fonts aren't in the table, so book text is unaffected. count <= 8.
|
||||||
|
void setUiFontRemap(const int* from, const int* to, int count);
|
||||||
// Clears both the flash-font map and any SD-font registration for fontId.
|
// Clears both the flash-font map and any SD-font registration for fontId.
|
||||||
// Coupled to avoid dangling SdCardFont* in sdCardFonts_ when callers free
|
// Coupled to avoid dangling SdCardFont* in sdCardFonts_ when callers free
|
||||||
// the underlying SdCardFont and forget the SD-side unregister.
|
// the underlying SdCardFont and forget the SD-side unregister.
|
||||||
@@ -141,6 +159,11 @@ class GfxRenderer {
|
|||||||
void clearScreen(uint8_t color = 0xFF) const;
|
void clearScreen(uint8_t color = 0xFF) const;
|
||||||
void getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const;
|
void getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const;
|
||||||
|
|
||||||
|
// Map a touch tap (normalized 0..1 in panel-native orientation, from
|
||||||
|
// InputManager::wasTouchTap) to logical screen coordinates matching the Rects the
|
||||||
|
// UI draws in. Inverse of rotateCoordinates() for the current orientation.
|
||||||
|
void tapToLogical(float nx, float ny, int& outX, int& outY) const;
|
||||||
|
|
||||||
// Tiled grayscale strip target. While active, drawPixel() and clearScreen()
|
// Tiled grayscale strip target. While active, drawPixel() and clearScreen()
|
||||||
// operate on `scratch` (panelWidthBytes * stripRows bytes, holding physical
|
// operate on `scratch` (panelWidthBytes * stripRows bytes, holding physical
|
||||||
// rows [stripY0, stripY0 + stripRows)) instead of the framebuffer; pixels
|
// rows [stripY0, stripY0 + stripRows)) instead of the framebuffer; pixels
|
||||||
@@ -208,6 +231,12 @@ class GfxRenderer {
|
|||||||
int getTextAdvanceX(int fontId, const char* text, EpdFontFamily::Style style) const;
|
int getTextAdvanceX(int fontId, const char* text, EpdFontFamily::Style style) const;
|
||||||
int getFontAscenderSize(int fontId) const;
|
int getFontAscenderSize(int fontId) const;
|
||||||
int getLineHeight(int fontId) const;
|
int getLineHeight(int fontId) const;
|
||||||
|
// Cap height (from the 'H' glyph), the real visible font extent for vertical
|
||||||
|
// alignment that follows the font instead of a guessed ascender fraction.
|
||||||
|
int getFontCapHeight(int fontId) const;
|
||||||
|
// Offset from text top (drawText's y) to the text's optical center; align an
|
||||||
|
// element to a line via centerY = textTop + getTextVisualCenterOffset(fontId).
|
||||||
|
int getTextVisualCenterOffset(int fontId) const;
|
||||||
std::string truncatedText(int fontId, const char* text, int maxWidth,
|
std::string truncatedText(int fontId, const char* text, int maxWidth,
|
||||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
/// Word-wrap \p text into at most \p maxLines lines, each no wider than
|
/// Word-wrap \p text into at most \p maxLines lines, each no wider than
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ STR_TEXT_AA: "Згладжванне тэксту"
|
|||||||
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: "У Іўі худы жвавы чорт у зялёнай камізэльцы пабег пад'есці фаршу з юшкай"
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Suprimir"
|
|||||||
STR_SHORT_PWR_BTN: "Clic curt del botó d'engegada"
|
STR_SHORT_PWR_BTN: "Clic curt 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: "Comportament de prémer llargament el botó"
|
STR_LONG_PRESS_BEHAVIOR: "Comportament de prémer llargament el botó"
|
||||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
|
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ STR_TEXT_AA: "Vyhlazování textu"
|
|||||||
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"
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ 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: "Touch-betjening (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"
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ 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"
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ STR_IMAGES_SUPPRESS: "Suppress"
|
|||||||
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"
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ STR_TEXT_AA: "Tekstin reunanpehmennys"
|
|||||||
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: "Kosketusohjaus (lukija)"
|
||||||
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"
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ 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é"
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ STR_TEXT_AA: "Schriftglättung"
|
|||||||
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 (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"
|
||||||
|
|||||||
Loaded 20 of 132 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user