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_12_bold.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)'
|
||||
))"
|
||||
|
||||
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 ($(
|
||||
ruby -rdigest -e 'puts [
|
||||
"./notosans_8_regular.h",
|
||||
|
||||
@@ -28,23 +28,42 @@ for size in ${NOTOSANS_FONT_SIZES[@]}; do
|
||||
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)
|
||||
# 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")
|
||||
|
||||
# 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 style in ${UI_FONT_STYLES[@]}; do
|
||||
font_name="ubuntu_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')"
|
||||
font_path="../builtinFonts/source/Ubuntu/Ubuntu-${style}.ttf"
|
||||
hebrew_path="../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-${style}.ttf"
|
||||
# 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).
|
||||
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"
|
||||
generate_ui_font $size $style ""
|
||||
done
|
||||
done
|
||||
|
||||
for size in ${UI_FONT_SIZES_LARGE[@]}; do
|
||||
for style in ${UI_FONT_STYLES[@]}; do
|
||||
generate_ui_font $size $style "--2bit --compress"
|
||||
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 tmpSpineBinFile[] = "/spine.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
|
||||
|
||||
BookMetadataCache::~BookMetadataCache() {
|
||||
if (ioMutex) {
|
||||
vSemaphoreDelete(ioMutex);
|
||||
ioMutex = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============= WRITING / BUILDING FUNCTIONS ================ */
|
||||
|
||||
bool BookMetadataCache::beginWrite() {
|
||||
@@ -375,6 +414,7 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri
|
||||
/* ============= READING / LOADING FUNCTIONS ================ */
|
||||
|
||||
bool BookMetadataCache::load() {
|
||||
CacheIoLock ioLock(ioMutex);
|
||||
if (!Storage.openFileForRead("BMC", cachePath + bookBinFile, bookFile)) {
|
||||
return false;
|
||||
}
|
||||
@@ -392,11 +432,13 @@ bool BookMetadataCache::load() {
|
||||
serialization::readPod(bookFile, spineCount);
|
||||
serialization::readPod(bookFile, tocCount);
|
||||
|
||||
serialization::readString(bookFile, coreMetadata.title);
|
||||
serialization::readString(bookFile, coreMetadata.author);
|
||||
serialization::readString(bookFile, coreMetadata.language);
|
||||
serialization::readString(bookFile, coreMetadata.coverItemHref);
|
||||
serialization::readString(bookFile, coreMetadata.textReferenceHref);
|
||||
if (!readStringBounded(bookFile, coreMetadata.title) || !readStringBounded(bookFile, coreMetadata.author) ||
|
||||
!readStringBounded(bookFile, coreMetadata.language) || !readStringBounded(bookFile, coreMetadata.coverItemHref) ||
|
||||
!readStringBounded(bookFile, coreMetadata.textReferenceHref)) {
|
||||
LOG_ERR("BMC", "Invalid cache metadata strings");
|
||||
bookFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
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) {
|
||||
CacheIoLock ioLock(ioMutex);
|
||||
if (!loaded) {
|
||||
LOG_ERR("BMC", "getSpineEntry called but cache not loaded");
|
||||
return {};
|
||||
@@ -418,11 +461,16 @@ BookMetadataCache::SpineEntry BookMetadataCache::getSpineEntry(const int index)
|
||||
bookFile.seek(lutOffset + sizeof(uint32_t) * index);
|
||||
uint32_t 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);
|
||||
return readSpineEntry(bookFile);
|
||||
}
|
||||
|
||||
BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
|
||||
CacheIoLock ioLock(ioMutex);
|
||||
if (!loaded) {
|
||||
LOG_ERR("BMC", "getTocEntry called but cache not loaded");
|
||||
return {};
|
||||
@@ -437,24 +485,34 @@ BookMetadataCache::TocEntry BookMetadataCache::getTocEntry(const int index) {
|
||||
bookFile.seek(lutOffset + sizeof(uint32_t) * spineCount + sizeof(uint32_t) * index);
|
||||
uint32_t 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);
|
||||
return readTocEntry(bookFile);
|
||||
}
|
||||
|
||||
BookMetadataCache::SpineEntry BookMetadataCache::readSpineEntry(HalFile& file) const {
|
||||
SpineEntry entry;
|
||||
serialization::readString(file, entry.href);
|
||||
serialization::readPod(file, entry.cumulativeSize);
|
||||
serialization::readPod(file, entry.tocIndex);
|
||||
if (!readStringBounded(file, entry.href) ||
|
||||
file.read(&entry.cumulativeSize, sizeof(entry.cumulativeSize)) !=
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
if (!readStringBounded(file, entry.title) || !readStringBounded(file, entry.href) ||
|
||||
!readStringBounded(file, entry.anchor) ||
|
||||
file.read(&entry.level, sizeof(entry.level)) != static_cast<int>(sizeof(entry.level)) ||
|
||||
file.read(&entry.spineIndex, sizeof(entry.spineIndex)) != static_cast<int>(sizeof(entry.spineIndex))) {
|
||||
LOG_ERR("BMC", "Invalid TOC cache entry");
|
||||
return {};
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <HalStorage.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
@@ -54,6 +55,7 @@ class BookMetadataCache {
|
||||
// Temp file handles during build
|
||||
HalFile spineFile;
|
||||
HalFile tocFile;
|
||||
SemaphoreHandle_t ioMutex;
|
||||
|
||||
// Index for fast href→spineIndex lookup (used only for large EPUBs)
|
||||
struct SpineHrefIndexEntry {
|
||||
@@ -85,8 +87,14 @@ class BookMetadataCache {
|
||||
BookMetadata coreMetadata;
|
||||
|
||||
explicit BookMetadataCache(std::string cachePath)
|
||||
: cachePath(std::move(cachePath)), lutOffset(0), spineCount(0), tocCount(0), loaded(false), buildMode(false) {}
|
||||
~BookMetadataCache() = default;
|
||||
: cachePath(std::move(cachePath)),
|
||||
lutOffset(0),
|
||||
spineCount(0),
|
||||
tocCount(0),
|
||||
loaded(false),
|
||||
buildMode(false),
|
||||
ioMutex(xSemaphoreCreateRecursiveMutex()) {}
|
||||
~BookMetadataCache();
|
||||
|
||||
// Building phase (stream to disk immediately)
|
||||
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)) {
|
||||
self->state = IN_GUIDE;
|
||||
// TODO Remove print
|
||||
LOG_DBG("COF", "Entering guide state.");
|
||||
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.");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <BidiUtils.h>
|
||||
#include <FontDecompressor.h>
|
||||
#include <HalGPIO.h>
|
||||
#include <Icon.h>
|
||||
#include <Logging.h>
|
||||
#include <SdCardFont.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
|
||||
// This should always be inlined for better performance
|
||||
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;
|
||||
}
|
||||
|
||||
const auto fontIt = fontMap.find(fontId);
|
||||
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||
if (fontIt == fontMap.end()) {
|
||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||
return 0;
|
||||
@@ -407,7 +425,7 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
|
||||
return;
|
||||
}
|
||||
|
||||
const auto fontIt = fontMap.find(fontId);
|
||||
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||
if (fontIt == fontMap.end()) {
|
||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
const float cropX, const float cropY) const {
|
||||
if (fontCacheManager_ && fontCacheManager_->isScanning()) return;
|
||||
@@ -1466,6 +1499,38 @@ int GfxRenderer::getScreenHeight() const {
|
||||
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
|
||||
// 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.
|
||||
@@ -1557,7 +1622,7 @@ int GfxRenderer::getSpaceWidth(const int fontId, const EpdFontFamily::Style styl
|
||||
return fp4::toPixel(sdIt->second->getAdvance(' ', resolvedStyle));
|
||||
}
|
||||
|
||||
const auto fontIt = fontMap.find(fontId);
|
||||
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||
if (fontIt == fontMap.end()) {
|
||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||
return 0;
|
||||
@@ -1578,7 +1643,7 @@ int GfxRenderer::getSpaceAdvance(const int fontId, const uint32_t leftCp, const
|
||||
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;
|
||||
const auto& font = fontIt->second;
|
||||
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,
|
||||
const EpdFontFamily::Style style) const {
|
||||
const auto fontIt = fontMap.find(fontId);
|
||||
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||
if (fontIt == fontMap.end()) return 0;
|
||||
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
|
||||
@@ -1624,7 +1689,7 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFami
|
||||
return fp4::toPixel(widthFP);
|
||||
}
|
||||
|
||||
const auto fontIt = fontMap.find(fontId);
|
||||
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||
if (fontIt == fontMap.end()) {
|
||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||
return 0;
|
||||
@@ -1660,7 +1725,7 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFami
|
||||
}
|
||||
|
||||
int GfxRenderer::getFontAscenderSize(const int fontId) const {
|
||||
const auto fontIt = fontMap.find(fontId);
|
||||
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||
if (fontIt == fontMap.end()) {
|
||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||
return 0;
|
||||
@@ -1670,7 +1735,7 @@ int GfxRenderer::getFontAscenderSize(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()) {
|
||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||
return 0;
|
||||
@@ -1680,7 +1745,7 @@ int GfxRenderer::getLineHeight(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()) {
|
||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||
return 0;
|
||||
@@ -1688,6 +1753,27 @@ int GfxRenderer::getTextHeight(const int fontId) const {
|
||||
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,
|
||||
const EpdFontFamily::Style style) const {
|
||||
// Cannot draw a NULL / empty string
|
||||
@@ -1695,7 +1781,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
||||
return;
|
||||
}
|
||||
|
||||
const auto fontIt = fontMap.find(fontId);
|
||||
const auto fontIt = fontMap.find(remapUiFont(fontId));
|
||||
if (fontIt == fontMap.end()) {
|
||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||
return;
|
||||
|
||||
@@ -13,6 +13,9 @@ enum class BidiBaseDir : signed char { AUTO = -1, LTR = 0, RTL = 1 };
|
||||
|
||||
class FontCacheManager;
|
||||
class SdCardFont;
|
||||
namespace freeink {
|
||||
struct Icon;
|
||||
}
|
||||
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
@@ -51,6 +54,13 @@ class GfxRenderer {
|
||||
uint32_t frameBufferSize = HalDisplay::BUFFER_SIZE;
|
||||
std::vector<uint8_t*> bwBufferChunks;
|
||||
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
|
||||
// that holds a const GfxRenderer&) but triggers SD card reads and heap
|
||||
// allocation inside the SdCardFont objects. Same pragmatic compromise as
|
||||
@@ -101,6 +111,14 @@ class GfxRenderer {
|
||||
// Setup
|
||||
void begin(); // must be called right after display.begin()
|
||||
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.
|
||||
// Coupled to avoid dangling SdCardFont* in sdCardFonts_ when callers free
|
||||
// the underlying SdCardFont and forget the SD-side unregister.
|
||||
@@ -141,6 +159,11 @@ class GfxRenderer {
|
||||
void clearScreen(uint8_t color = 0xFF) 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()
|
||||
// operate on `scratch` (panelWidthBytes * stripRows bytes, holding physical
|
||||
// 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 getFontAscenderSize(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,
|
||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||
/// 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_ORIENTATION: "Арыентацыя чытання"
|
||||
STR_SIDE_BTN_LAYOUT: "Бакавыя кнопкі"
|
||||
STR_TOUCH_READER_CONTROLS: "Сэнсарнае кіраванне чытаннем"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Арыентаваць пярэднія кнопкі"
|
||||
STR_LONG_PRESS_SKIP: "Доўгае націсканне - змена раздзела"
|
||||
STR_FONT_PREVIEW_TEXT: "У Іўі худы жвавы чорт у зялёнай камізэльцы пабег пад'есці фаршу з юшкай"
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Suprimir"
|
||||
STR_SHORT_PWR_BTN: "Clic curt del botó d'engegada"
|
||||
STR_ORIENTATION: "Orientació de lectura"
|
||||
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_LONG_PRESS_BEHAVIOR: "Comportament de prémer llargament el botó"
|
||||
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_ORIENTATION: "Orientace čtení"
|
||||
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_LONG_PRESS_BEHAVIOR: "Chování při dlouhém stisknutí tlačítka"
|
||||
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_ORIENTATION: "Læseretning"
|
||||
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_LONG_PRESS_BEHAVIOR: "Comportamiento al mantener pulsado el botón"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivado"
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Verbergen"
|
||||
STR_SHORT_PWR_BTN: "Korte klik aan/uit-knop"
|
||||
STR_ORIENTATION: "Leesstand"
|
||||
STR_SIDE_BTN_LAYOUT: "Indeling zijknoppen (lezer)"
|
||||
STR_TOUCH_READER_CONTROLS: "Aanraakbediening lezer"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Richt voorste knoppen"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||
|
||||
@@ -73,6 +73,7 @@ STR_IMAGES_SUPPRESS: "Suppress"
|
||||
STR_SHORT_PWR_BTN: "Short Power Button Click"
|
||||
STR_ORIENTATION: "Reading Orientation"
|
||||
STR_SIDE_BTN_LAYOUT: "Side Button Layout (reader)"
|
||||
STR_TOUCH_READER_CONTROLS: "Touch Reader Controls"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orient front buttons"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||
|
||||
@@ -68,6 +68,7 @@ STR_TEXT_AA: "Tekstin reunanpehmennys"
|
||||
STR_SHORT_PWR_BTN: "Lyhyt virtapainikkeen painallus"
|
||||
STR_ORIENTATION: "Lukusuunta"
|
||||
STR_SIDE_BTN_LAYOUT: "Sivupainikkeiden asettelu (lukija)"
|
||||
STR_TOUCH_READER_CONTROLS: "Kosketusohjaus (lukija)"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Suuntaa etupainikkeet"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
|
||||
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
|
||||
|
||||
@@ -72,6 +72,7 @@ STR_IMAGES_SUPPRESS: "Masquer"
|
||||
STR_SHORT_PWR_BTN: "Appui court alim."
|
||||
STR_ORIENTATION: "Orientation de lecture"
|
||||
STR_SIDE_BTN_LAYOUT: "Boutons latéraux"
|
||||
STR_TOUCH_READER_CONTROLS: "Commandes tactiles (lecteur)"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orienter boutons avant"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Comportement lors d'un appui long"
|
||||
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_ORIENTATION: "Leseausrichtung"
|
||||
STR_SIDE_BTN_LAYOUT: "Seitliche Tasten (Lesen)"
|
||||
STR_TOUCH_READER_CONTROLS: "Touch-Steuerung (Lesen)"
|
||||
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Vordere Tasten ausrichten"
|
||||
STR_LONG_PRESS_BEHAVIOR: "Verhalten bei langem Tastendruck"
|
||||
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