Merge pull request #156 from jpirnay/feat-bionic
feat: Add yet another bionic reader implementation
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "hyphenation/HyphenationCommon.h"
|
||||||
#include "hyphenation/Hyphenator.h"
|
#include "hyphenation/Hyphenator.h"
|
||||||
|
|
||||||
constexpr int MAX_COST = std::numeric_limits<int>::max();
|
constexpr int MAX_COST = std::numeric_limits<int>::max();
|
||||||
@@ -123,6 +124,71 @@ std::string buildLinePreview(const std::vector<std::string>& words, const std::v
|
|||||||
return preview;
|
return preview;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr int kBionicReadingMinCodepoints = 4;
|
||||||
|
constexpr int kBionicReadingMinBoldPrefix = 1;
|
||||||
|
constexpr int kBionicReadingBoldPrefixNumerator = 1;
|
||||||
|
constexpr int kBionicReadingBoldPrefixDenominator = 2;
|
||||||
|
|
||||||
|
struct TokenSpan {
|
||||||
|
size_t start;
|
||||||
|
size_t end;
|
||||||
|
bool isWord;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int computeBionicBoldPrefixCount(const int codepointCount) {
|
||||||
|
return std::max(kBionicReadingMinBoldPrefix,
|
||||||
|
(codepointCount * kBionicReadingBoldPrefixNumerator + kBionicReadingBoldPrefixDenominator - 1) /
|
||||||
|
kBionicReadingBoldPrefixDenominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isBionicWordCodepoint(const uint32_t cp) {
|
||||||
|
if (cp == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (utf8IsCombiningMark(cp)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isAlphabetic(cp) || isAsciiDigit(cp) || isApostrophe(cp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split a word token into contiguous spans of "word-like" characters and non-word characters.
|
||||||
|
// This avoids applying bionic bolding to punctuation, digits-only runs, or other separators.
|
||||||
|
// Only spans marked as word-like are eligible for the bionic prefix transform.
|
||||||
|
static std::vector<TokenSpan> tokenizeBionicWord(const std::string& word) {
|
||||||
|
std::vector<TokenSpan> spans;
|
||||||
|
spans.reserve(2);
|
||||||
|
|
||||||
|
const unsigned char* base = reinterpret_cast<const unsigned char*>(word.c_str());
|
||||||
|
const unsigned char* ptr = base;
|
||||||
|
const unsigned char* segmentStart = ptr;
|
||||||
|
bool currentIsWord = false;
|
||||||
|
bool haveCurrent = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const unsigned char* cpStart = ptr;
|
||||||
|
uint32_t cp = utf8NextCodepoint(&ptr);
|
||||||
|
if (cp == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cpIsWord = isBionicWordCodepoint(cp);
|
||||||
|
if (!haveCurrent) {
|
||||||
|
currentIsWord = cpIsWord;
|
||||||
|
haveCurrent = true;
|
||||||
|
} else if (!utf8IsCombiningMark(cp) && cpIsWord != currentIsWord) {
|
||||||
|
spans.push_back({static_cast<size_t>(segmentStart - base), static_cast<size_t>(cpStart - base), currentIsWord});
|
||||||
|
segmentStart = cpStart;
|
||||||
|
currentIsWord = cpIsWord;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveCurrent) {
|
||||||
|
spans.push_back({static_cast<size_t>(segmentStart - base), word.size(), currentIsWord});
|
||||||
|
}
|
||||||
|
|
||||||
|
return spans;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void ParsedText::addWord(std::string word, const EpdFontFamily::Style fontStyle, const bool underline,
|
void ParsedText::addWord(std::string word, const EpdFontFamily::Style fontStyle, const bool underline,
|
||||||
@@ -149,6 +215,9 @@ void ParsedText::layoutAndExtractLines(
|
|||||||
|
|
||||||
// Apply fixed transforms before any per-line layout work.
|
// Apply fixed transforms before any per-line layout work.
|
||||||
applyParagraphIndent();
|
applyParagraphIndent();
|
||||||
|
if (bionicReadingEnabled) {
|
||||||
|
applyBionicReadingTransform();
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure SD card font glyph metrics are loaded before measuring word widths.
|
// Ensure SD card font glyph metrics are loaded before measuring word widths.
|
||||||
// For flash-based fonts isSdCardFont() returns false and this block is skipped
|
// For flash-based fonts isSdCardFont() returns false and this block is skipped
|
||||||
@@ -506,6 +575,85 @@ void ParsedText::applyParagraphIndent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ParsedText::applyBionicReadingTransform() {
|
||||||
|
if (words.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> transformedWords;
|
||||||
|
std::vector<EpdFontFamily::Style> transformedStyles;
|
||||||
|
std::vector<bool> transformedContinues;
|
||||||
|
transformedWords.reserve(words.size() * 2);
|
||||||
|
transformedStyles.reserve(wordStyles.size() * 2);
|
||||||
|
transformedContinues.reserve(wordContinues.size() * 2);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < words.size(); ++i) {
|
||||||
|
std::string source = std::move(words[i]);
|
||||||
|
const auto originalStyle = wordStyles[i];
|
||||||
|
const bool originalAttachToPrevious = wordContinues[i];
|
||||||
|
const char* raw = source.c_str();
|
||||||
|
|
||||||
|
const auto spans = tokenizeBionicWord(source);
|
||||||
|
if (spans.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool attachToPrevious = originalAttachToPrevious;
|
||||||
|
for (size_t spanIndex = 0; spanIndex < spans.size(); ++spanIndex) {
|
||||||
|
const TokenSpan span = spans[spanIndex];
|
||||||
|
const size_t spanLength = span.end - span.start;
|
||||||
|
std::string token;
|
||||||
|
if (spans.size() == 1 && spanIndex == 0) {
|
||||||
|
token = std::move(source);
|
||||||
|
} else {
|
||||||
|
token.assign(raw + span.start, spanLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (span.isWord) {
|
||||||
|
const unsigned char* ptr = reinterpret_cast<const unsigned char*>(token.c_str());
|
||||||
|
int codepointCount = 0;
|
||||||
|
while (utf8NextCodepoint(&ptr)) {
|
||||||
|
codepointCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codepointCount >= kBionicReadingMinCodepoints) {
|
||||||
|
const int boldPrefixCount = computeBionicBoldPrefixCount(codepointCount);
|
||||||
|
ptr = reinterpret_cast<const unsigned char*>(token.c_str());
|
||||||
|
const unsigned char* prefixEnd = ptr;
|
||||||
|
for (int j = 0; j < boldPrefixCount && *prefixEnd; ++j) {
|
||||||
|
utf8NextCodepoint(&prefixEnd);
|
||||||
|
}
|
||||||
|
const size_t prefixByteCount =
|
||||||
|
static_cast<size_t>(prefixEnd - reinterpret_cast<const unsigned char*>(token.c_str()));
|
||||||
|
if (prefixByteCount < token.size()) {
|
||||||
|
std::string suffix(reinterpret_cast<const char*>(prefixEnd), token.size() - prefixByteCount);
|
||||||
|
token.resize(prefixByteCount);
|
||||||
|
const auto boldStyle = static_cast<EpdFontFamily::Style>(originalStyle | EpdFontFamily::BOLD);
|
||||||
|
transformedWords.push_back(std::move(token));
|
||||||
|
transformedStyles.push_back(boldStyle);
|
||||||
|
transformedContinues.push_back(attachToPrevious);
|
||||||
|
|
||||||
|
transformedWords.push_back(std::move(suffix));
|
||||||
|
transformedStyles.push_back(originalStyle);
|
||||||
|
transformedContinues.push_back(true);
|
||||||
|
attachToPrevious = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transformedWords.push_back(std::move(token));
|
||||||
|
transformedStyles.push_back(originalStyle);
|
||||||
|
transformedContinues.push_back(attachToPrevious);
|
||||||
|
attachToPrevious = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
words = std::move(transformedWords);
|
||||||
|
wordStyles = std::move(transformedStyles);
|
||||||
|
wordContinues = std::move(transformedContinues);
|
||||||
|
}
|
||||||
|
|
||||||
// Builds break indices while opportunistically splitting the word that would overflow the current line.
|
// Builds break indices while opportunistically splitting the word that would overflow the current line.
|
||||||
std::vector<size_t> ParsedText::computeHyphenatedLineBreaks(const GfxRenderer& renderer, const int fontId,
|
std::vector<size_t> ParsedText::computeHyphenatedLineBreaks(const GfxRenderer& renderer, const int fontId,
|
||||||
const int pageWidth, std::vector<uint16_t>& wordWidths,
|
const int pageWidth, std::vector<uint16_t>& wordWidths,
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ class ParsedText {
|
|||||||
BlockStyle blockStyle;
|
BlockStyle blockStyle;
|
||||||
bool extraParagraphSpacing;
|
bool extraParagraphSpacing;
|
||||||
bool hyphenationEnabled;
|
bool hyphenationEnabled;
|
||||||
|
bool bionicReadingEnabled;
|
||||||
|
|
||||||
void applyParagraphIndent();
|
void applyParagraphIndent();
|
||||||
|
void applyBionicReadingTransform();
|
||||||
std::vector<size_t> computeLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
std::vector<size_t> computeLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
||||||
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec);
|
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec);
|
||||||
std::vector<size_t> computeHyphenatedLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
std::vector<size_t> computeHyphenatedLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
||||||
@@ -60,8 +62,11 @@ class ParsedText {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ParsedText(const bool extraParagraphSpacing, const bool hyphenationEnabled = false,
|
explicit ParsedText(const bool extraParagraphSpacing, const bool hyphenationEnabled = false,
|
||||||
const BlockStyle& blockStyle = BlockStyle())
|
const BlockStyle& blockStyle = BlockStyle(), const bool bionicReadingEnabled = false)
|
||||||
: blockStyle(blockStyle), extraParagraphSpacing(extraParagraphSpacing), hyphenationEnabled(hyphenationEnabled) {}
|
: blockStyle(blockStyle),
|
||||||
|
extraParagraphSpacing(extraParagraphSpacing),
|
||||||
|
hyphenationEnabled(hyphenationEnabled),
|
||||||
|
bionicReadingEnabled(bionicReadingEnabled) {}
|
||||||
~ParsedText() = default;
|
~ParsedText() = default;
|
||||||
|
|
||||||
void addWord(std::string word, EpdFontFamily::Style fontStyle, bool underline = false, bool attachToPrevious = false);
|
void addWord(std::string word, EpdFontFamily::Style fontStyle, bool underline = false, bool attachToPrevious = false);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#include "parsers/ChapterHtmlSlimParser.h"
|
#include "parsers/ChapterHtmlSlimParser.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t SECTION_FILE_VERSION = 23;
|
constexpr uint8_t SECTION_FILE_VERSION = 24;
|
||||||
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + // SECTION_FILE_VERSION
|
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + // SECTION_FILE_VERSION
|
||||||
sizeof(int) + // fontId
|
sizeof(int) + // fontId
|
||||||
sizeof(float) + // lineCompression
|
sizeof(float) + // lineCompression
|
||||||
@@ -23,6 +23,7 @@ constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + // SECTION_FILE_VERSION
|
|||||||
sizeof(uint16_t) + // pageCount (stored as 16-bit in header)
|
sizeof(uint16_t) + // pageCount (stored as 16-bit in header)
|
||||||
sizeof(bool) + // hyphenationEnabled
|
sizeof(bool) + // hyphenationEnabled
|
||||||
sizeof(bool) + // embeddedStyle
|
sizeof(bool) + // embeddedStyle
|
||||||
|
sizeof(bool) + // bionicReadingEnabled
|
||||||
sizeof(uint8_t) + // imageRendering
|
sizeof(uint8_t) + // imageRendering
|
||||||
sizeof(uint32_t) + // page LUT offset
|
sizeof(uint32_t) + // page LUT offset
|
||||||
sizeof(uint32_t) + // anchor map offset
|
sizeof(uint32_t) + // anchor map offset
|
||||||
@@ -55,7 +56,8 @@ uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
|
|||||||
void Section::writeSectionFileHeader(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
void Section::writeSectionFileHeader(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
||||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
||||||
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
||||||
const bool embeddedStyle, const uint8_t imageRendering) {
|
const bool embeddedStyle, const bool bionicReadingEnabled,
|
||||||
|
const uint8_t imageRendering) {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
LOG_DBG("SCT", "File not open for writing header");
|
LOG_DBG("SCT", "File not open for writing header");
|
||||||
return;
|
return;
|
||||||
@@ -63,8 +65,8 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
|
|||||||
static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
|
static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
|
||||||
sizeof(extraParagraphSpacing) + sizeof(paragraphAlignment) + sizeof(viewportWidth) +
|
sizeof(extraParagraphSpacing) + sizeof(paragraphAlignment) + sizeof(viewportWidth) +
|
||||||
sizeof(viewportHeight) + sizeof(pageCount) + sizeof(hyphenationEnabled) +
|
sizeof(viewportHeight) + sizeof(pageCount) + sizeof(hyphenationEnabled) +
|
||||||
sizeof(embeddedStyle) + sizeof(imageRendering) + sizeof(uint32_t) +
|
sizeof(embeddedStyle) + sizeof(bionicReadingEnabled) + sizeof(imageRendering) +
|
||||||
sizeof(uint32_t) + sizeof(uint32_t),
|
sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t),
|
||||||
"Header size mismatch");
|
"Header size mismatch");
|
||||||
serialization::writePod(file, SECTION_FILE_VERSION);
|
serialization::writePod(file, SECTION_FILE_VERSION);
|
||||||
serialization::writePod(file, fontId);
|
serialization::writePod(file, fontId);
|
||||||
@@ -75,6 +77,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
|
|||||||
serialization::writePod(file, viewportHeight);
|
serialization::writePod(file, viewportHeight);
|
||||||
serialization::writePod(file, hyphenationEnabled);
|
serialization::writePod(file, hyphenationEnabled);
|
||||||
serialization::writePod(file, embeddedStyle);
|
serialization::writePod(file, embeddedStyle);
|
||||||
|
serialization::writePod(file, bionicReadingEnabled);
|
||||||
serialization::writePod(file, imageRendering);
|
serialization::writePod(file, imageRendering);
|
||||||
serialization::writePod(file, pageCount); // Placeholder for page count (will be initially 0, patched later)
|
serialization::writePod(file, pageCount); // Placeholder for page count (will be initially 0, patched later)
|
||||||
serialization::writePod(file, static_cast<uint32_t>(0)); // Placeholder for LUT offset (patched later)
|
serialization::writePod(file, static_cast<uint32_t>(0)); // Placeholder for LUT offset (patched later)
|
||||||
@@ -85,7 +88,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
|
|||||||
bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
||||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
||||||
const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle,
|
const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle,
|
||||||
const uint8_t imageRendering) {
|
const bool bionicReadingEnabled, const uint8_t imageRendering) {
|
||||||
if (!Storage.openFileForRead("SCT", filePath, file)) {
|
if (!Storage.openFileForRead("SCT", filePath, file)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -107,6 +110,7 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
|
|||||||
uint8_t fileParagraphAlignment;
|
uint8_t fileParagraphAlignment;
|
||||||
bool fileHyphenationEnabled;
|
bool fileHyphenationEnabled;
|
||||||
bool fileEmbeddedStyle;
|
bool fileEmbeddedStyle;
|
||||||
|
bool fileBionicReadingEnabled;
|
||||||
uint8_t fileImageRendering;
|
uint8_t fileImageRendering;
|
||||||
serialization::readPod(file, fileFontId);
|
serialization::readPod(file, fileFontId);
|
||||||
serialization::readPod(file, fileLineCompression);
|
serialization::readPod(file, fileLineCompression);
|
||||||
@@ -116,13 +120,14 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
|
|||||||
serialization::readPod(file, fileViewportHeight);
|
serialization::readPod(file, fileViewportHeight);
|
||||||
serialization::readPod(file, fileHyphenationEnabled);
|
serialization::readPod(file, fileHyphenationEnabled);
|
||||||
serialization::readPod(file, fileEmbeddedStyle);
|
serialization::readPod(file, fileEmbeddedStyle);
|
||||||
|
serialization::readPod(file, fileBionicReadingEnabled);
|
||||||
serialization::readPod(file, fileImageRendering);
|
serialization::readPod(file, fileImageRendering);
|
||||||
|
|
||||||
if (fontId != fileFontId || lineCompression != fileLineCompression ||
|
if (fontId != fileFontId || lineCompression != fileLineCompression ||
|
||||||
extraParagraphSpacing != fileExtraParagraphSpacing || paragraphAlignment != fileParagraphAlignment ||
|
extraParagraphSpacing != fileExtraParagraphSpacing || paragraphAlignment != fileParagraphAlignment ||
|
||||||
viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight ||
|
viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight ||
|
||||||
hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle ||
|
hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle ||
|
||||||
imageRendering != fileImageRendering) {
|
bionicReadingEnabled != fileBionicReadingEnabled || imageRendering != fileImageRendering) {
|
||||||
LOG_ERR("SCT", "Deserialization failed: Parameters do not match");
|
LOG_ERR("SCT", "Deserialization failed: Parameters do not match");
|
||||||
clearCache(); // closes file before removal
|
clearCache(); // closes file before removal
|
||||||
return false;
|
return false;
|
||||||
@@ -188,7 +193,8 @@ bool Section::clearCache() {
|
|||||||
bool Section::createSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
bool Section::createSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
||||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
||||||
const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle,
|
const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle,
|
||||||
const uint8_t imageRendering, const std::function<void(int)>& progressFn) {
|
const bool bionicReadingEnabled, const uint8_t imageRendering,
|
||||||
|
const std::function<void(int)>& progressFn) {
|
||||||
const uint32_t phaseTotalStart = millis();
|
const uint32_t phaseTotalStart = millis();
|
||||||
const auto localPath = epub->getSpineItem(spineIndex).href;
|
const auto localPath = epub->getSpineItem(spineIndex).href;
|
||||||
|
|
||||||
@@ -210,7 +216,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth,
|
writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth,
|
||||||
viewportHeight, hyphenationEnabled, embeddedStyle, imageRendering);
|
viewportHeight, hyphenationEnabled, embeddedStyle, bionicReadingEnabled, imageRendering);
|
||||||
std::vector<uint32_t> lut = {};
|
std::vector<uint32_t> lut = {};
|
||||||
|
|
||||||
// Derive the content base directory and image cache path prefix for the parser
|
// Derive the content base directory and image cache path prefix for the parser
|
||||||
@@ -243,7 +249,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
|
|||||||
|
|
||||||
ChapterHtmlSlimParser visitor(
|
ChapterHtmlSlimParser visitor(
|
||||||
epub, renderer, fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth, viewportHeight,
|
epub, renderer, fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth, viewportHeight,
|
||||||
hyphenationEnabled,
|
hyphenationEnabled, bionicReadingEnabled,
|
||||||
[this, &lut](std::unique_ptr<Page> page) { lut.emplace_back(this->onPageComplete(std::move(page))); },
|
[this, &lut](std::unique_ptr<Page> page) { lut.emplace_back(this->onPageComplete(std::move(page))); },
|
||||||
embeddedStyle, contentBase, imageBasePath, imageRendering, std::move(tocAnchors), progressFn, cssParser);
|
embeddedStyle, contentBase, imageBasePath, imageRendering, std::move(tocAnchors), progressFn, cssParser);
|
||||||
Hyphenator::setPreferredLanguage(epub->getLanguage());
|
Hyphenator::setPreferredLanguage(epub->getLanguage());
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Section {
|
|||||||
|
|
||||||
void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
||||||
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled,
|
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled,
|
||||||
bool embeddedStyle, uint8_t imageRendering);
|
bool embeddedStyle, bool bionicReadingEnabled, uint8_t imageRendering);
|
||||||
uint32_t onPageComplete(std::unique_ptr<Page> page);
|
uint32_t onPageComplete(std::unique_ptr<Page> page);
|
||||||
|
|
||||||
struct TocBoundary {
|
struct TocBoundary {
|
||||||
@@ -50,11 +50,12 @@ class Section {
|
|||||||
~Section() = default;
|
~Section() = default;
|
||||||
bool loadSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
bool loadSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
||||||
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle,
|
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle,
|
||||||
uint8_t imageRendering);
|
bool bionicReadingEnabled, uint8_t imageRendering);
|
||||||
bool clearCache();
|
bool clearCache();
|
||||||
bool createSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
bool createSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
||||||
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle,
|
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle,
|
||||||
uint8_t imageRendering, const std::function<void(int)>& progressFn = nullptr);
|
bool bionicReadingEnabled, uint8_t imageRendering,
|
||||||
|
const std::function<void(int)>& progressFn = nullptr);
|
||||||
std::unique_ptr<Page> loadPageFromSectionFile();
|
std::unique_ptr<Page> loadPageFromSectionFile();
|
||||||
|
|
||||||
// Given a page in this section, return the TOC index for that page.
|
// Given a page in this section, return the TOC index for that page.
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ void ChapterHtmlSlimParser::startNewTextBlock(const BlockStyle& blockStyle) {
|
|||||||
anchorData.push_back({std::move(pendingAnchorId), static_cast<uint16_t>(completedPageCount)});
|
anchorData.push_back({std::move(pendingAnchorId), static_cast<uint16_t>(completedPageCount)});
|
||||||
pendingAnchorId.clear();
|
pendingAnchorId.clear();
|
||||||
}
|
}
|
||||||
currentTextBlock.reset(new ParsedText(extraParagraphSpacing, hyphenationEnabled, blockStyle));
|
currentTextBlock.reset(new ParsedText(extraParagraphSpacing, hyphenationEnabled, blockStyle, bionicReadingEnabled));
|
||||||
wordsExtractedInBlock = 0;
|
wordsExtractedInBlock = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ class ChapterHtmlSlimParser final : public Print {
|
|||||||
int currentFootnoteLinkTextLen = 0;
|
int currentFootnoteLinkTextLen = 0;
|
||||||
std::vector<std::pair<int, FootnoteEntry>> pendingFootnotes; // <wordIndex, entry>
|
std::vector<std::pair<int, FootnoteEntry>> pendingFootnotes; // <wordIndex, entry>
|
||||||
int wordsExtractedInBlock = 0;
|
int wordsExtractedInBlock = 0;
|
||||||
|
bool bionicReadingEnabled = false;
|
||||||
|
|
||||||
// Per-chapter caches: resolveStyle and parseInlineStyle are called for every HTML element;
|
// Per-chapter caches: resolveStyle and parseInlineStyle are called for every HTML element;
|
||||||
// caching by (tag|classAttr) and styleAttr avoids repeated string operations and hash lookups.
|
// caching by (tag|classAttr) and styleAttr avoids repeated string operations and hash lookups.
|
||||||
@@ -149,16 +150,14 @@ class ChapterHtmlSlimParser final : public Print {
|
|||||||
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ChapterHtmlSlimParser(std::shared_ptr<Epub> epub, GfxRenderer& renderer, const int fontId,
|
explicit ChapterHtmlSlimParser(
|
||||||
const float lineCompression, const bool extraParagraphSpacing,
|
std::shared_ptr<Epub> epub, GfxRenderer& renderer, const int fontId, const float lineCompression,
|
||||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
const bool extraParagraphSpacing, const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
||||||
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
const uint16_t viewportHeight, const bool hyphenationEnabled, const bool bionicReadingEnabled,
|
||||||
const std::function<void(std::unique_ptr<Page>)>& completePageFn,
|
const std::function<void(std::unique_ptr<Page>)>& completePageFn, const bool embeddedStyle,
|
||||||
const bool embeddedStyle, const std::string& contentBase,
|
const std::string& contentBase, const std::string& imageBasePath, const uint8_t imageRendering = 0,
|
||||||
const std::string& imageBasePath, const uint8_t imageRendering = 0,
|
std::vector<std::string> tocAnchors = {}, const std::function<void(int)>& progressFn = nullptr,
|
||||||
std::vector<std::string> tocAnchors = {},
|
const CssParser* cssParser = nullptr)
|
||||||
const std::function<void(int)>& progressFn = nullptr,
|
|
||||||
const CssParser* cssParser = nullptr)
|
|
||||||
|
|
||||||
: epub(epub),
|
: epub(epub),
|
||||||
renderer(renderer),
|
renderer(renderer),
|
||||||
@@ -169,6 +168,7 @@ class ChapterHtmlSlimParser final : public Print {
|
|||||||
viewportWidth(viewportWidth),
|
viewportWidth(viewportWidth),
|
||||||
viewportHeight(viewportHeight),
|
viewportHeight(viewportHeight),
|
||||||
hyphenationEnabled(hyphenationEnabled),
|
hyphenationEnabled(hyphenationEnabled),
|
||||||
|
bionicReadingEnabled(bionicReadingEnabled),
|
||||||
completePageFn(completePageFn),
|
completePageFn(completePageFn),
|
||||||
progressFn(progressFn),
|
progressFn(progressFn),
|
||||||
cssParser(cssParser),
|
cssParser(cssParser),
|
||||||
|
|||||||
@@ -598,3 +598,5 @@ STR_KB_HINT_SECONDARY_CHAR: "Hold SELECT for secondary char"
|
|||||||
STR_KB_HINT_UPPER_SECONDARY: "Hold SELECT for UPPERCASE or secondary char"
|
STR_KB_HINT_UPPER_SECONDARY: "Hold SELECT for UPPERCASE or secondary char"
|
||||||
STR_KB_HINT_LOWER_SECONDARY: "Hold SELECT for lowercase or secondary char"
|
STR_KB_HINT_LOWER_SECONDARY: "Hold SELECT for lowercase or secondary char"
|
||||||
STR_KB_HINT_URL_SNIPPETS: "Press URL for snippets"
|
STR_KB_HINT_URL_SNIPPETS: "Press URL for snippets"
|
||||||
|
STR_BIONIC_READING: "Bionic Reading"
|
||||||
|
STR_BTN_ACT_TOGGLE_BIONIC_READING: "Toggle Bionic Reading"
|
||||||
@@ -249,6 +249,8 @@ class CrossPointSettings {
|
|||||||
uint8_t imageDithering = IMAGE_DITHER_BAYER;
|
uint8_t imageDithering = IMAGE_DITHER_BAYER;
|
||||||
// Enable synthetic TOC fallback for malformed/sparse TOC books (1 = enabled, 0 = disabled)
|
// Enable synthetic TOC fallback for malformed/sparse TOC books (1 = enabled, 0 = disabled)
|
||||||
uint8_t syntheticTocFallback = 1;
|
uint8_t syntheticTocFallback = 1;
|
||||||
|
// Default bionic reading in EPUB pages when no per-book override is set (1 = enabled, 0 = disabled)
|
||||||
|
uint8_t bionicReading = 0;
|
||||||
// Automatically push reading progress to the KOReader sync server when leaving the reader
|
// Automatically push reading progress to the KOReader sync server when leaving the reader
|
||||||
// (1 = enabled, 0 = disabled). The push only fires when credentials are configured and the
|
// (1 = enabled, 0 = disabled). The push only fires when credentials are configured and the
|
||||||
// reader session advanced at least 3 pages, and is skipped when remote progress is already ahead.
|
// reader session advanced at least 3 pages, and is skipped when remote progress is already ahead.
|
||||||
@@ -285,6 +287,7 @@ class CrossPointSettings {
|
|||||||
BTN_EXIT_READER,
|
BTN_EXIT_READER,
|
||||||
BTN_READER_MENU,
|
BTN_READER_MENU,
|
||||||
BTN_KOREADER_SYNC,
|
BTN_KOREADER_SYNC,
|
||||||
|
BTN_TOGGLE_BIONIC_READING,
|
||||||
BUTTON_ACTION_COUNT
|
BUTTON_ACTION_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -418,6 +418,7 @@ bool JsonSettingsIO::saveRecentBooks(const RecentBooksStore& store, const char*
|
|||||||
obj["imageRenderingOverride"] = book.imageRenderingOverride;
|
obj["imageRenderingOverride"] = book.imageRenderingOverride;
|
||||||
obj["fontFamilyOverride"] = book.fontFamilyOverride;
|
obj["fontFamilyOverride"] = book.fontFamilyOverride;
|
||||||
obj["fontSizeOverride"] = book.fontSizeOverride;
|
obj["fontSizeOverride"] = book.fontSizeOverride;
|
||||||
|
obj["bionicReadingOverride"] = book.bionicReadingOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
String json;
|
String json;
|
||||||
@@ -455,6 +456,7 @@ bool JsonSettingsIO::loadRecentBooks(RecentBooksStore& store, const char* json)
|
|||||||
book.fontFamilyOverride =
|
book.fontFamilyOverride =
|
||||||
clampInt8(obj["fontFamilyOverride"] | -1, -1, CrossPointSettings::FONT_FAMILY_COUNT - 1, -1);
|
clampInt8(obj["fontFamilyOverride"] | -1, -1, CrossPointSettings::FONT_FAMILY_COUNT - 1, -1);
|
||||||
book.fontSizeOverride = clampInt8(obj["fontSizeOverride"] | -1, -1, CrossPointSettings::FONT_SIZE_COUNT - 1, -1);
|
book.fontSizeOverride = clampInt8(obj["fontSizeOverride"] | -1, -1, CrossPointSettings::FONT_SIZE_COUNT - 1, -1);
|
||||||
|
book.bionicReadingOverride = clampInt8(obj["bionicReadingOverride"] | -1, -1, 1, -1);
|
||||||
store.recentBooks.push_back(book);
|
store.recentBooks.push_back(book);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title
|
|||||||
int8_t imageRenderingOverride = -1;
|
int8_t imageRenderingOverride = -1;
|
||||||
int8_t fontFamilyOverride = -1;
|
int8_t fontFamilyOverride = -1;
|
||||||
int8_t fontSizeOverride = -1;
|
int8_t fontSizeOverride = -1;
|
||||||
|
int8_t bionicReadingOverride = -1;
|
||||||
|
|
||||||
// Remove existing entry if present
|
// Remove existing entry if present
|
||||||
auto it =
|
auto it =
|
||||||
@@ -35,12 +36,14 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title
|
|||||||
imageRenderingOverride = it->imageRenderingOverride;
|
imageRenderingOverride = it->imageRenderingOverride;
|
||||||
fontFamilyOverride = it->fontFamilyOverride;
|
fontFamilyOverride = it->fontFamilyOverride;
|
||||||
fontSizeOverride = it->fontSizeOverride;
|
fontSizeOverride = it->fontSizeOverride;
|
||||||
|
bionicReadingOverride = it->bionicReadingOverride;
|
||||||
recentBooks.erase(it);
|
recentBooks.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to front
|
// Add to front
|
||||||
recentBooks.insert(recentBooks.begin(), {path, title, author, series, coverBmpPath, embeddedStyleOverride,
|
recentBooks.insert(recentBooks.begin(),
|
||||||
imageRenderingOverride, fontFamilyOverride, fontSizeOverride});
|
{path, title, author, series, coverBmpPath, embeddedStyleOverride, imageRenderingOverride,
|
||||||
|
fontFamilyOverride, fontSizeOverride, bionicReadingOverride});
|
||||||
|
|
||||||
// Trim to max size
|
// Trim to max size
|
||||||
if (recentBooks.size() > MAX_RECENT_BOOKS) {
|
if (recentBooks.size() > MAX_RECENT_BOOKS) {
|
||||||
@@ -90,7 +93,7 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, it->fontFamilyOverride,
|
return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, it->fontFamilyOverride,
|
||||||
it->fontSizeOverride);
|
it->fontSizeOverride, it->bionicReadingOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride,
|
bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride,
|
||||||
@@ -101,11 +104,35 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t
|
|||||||
if (it == recentBooks.end()) {
|
if (it == recentBooks.end()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, fontSizeOverride,
|
||||||
|
it->bionicReadingOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride,
|
||||||
|
const int8_t imageRenderingOverride, const bool bionicReadingOverride) {
|
||||||
|
auto it =
|
||||||
|
std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; });
|
||||||
|
if (it == recentBooks.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, it->fontFamilyOverride,
|
||||||
|
it->fontSizeOverride, bionicReadingOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride,
|
||||||
|
const int8_t imageRenderingOverride, const int8_t fontFamilyOverride,
|
||||||
|
const int8_t fontSizeOverride, const bool bionicReadingOverride) {
|
||||||
|
auto it =
|
||||||
|
std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; });
|
||||||
|
if (it == recentBooks.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
it->embeddedStyleOverride = embeddedStyleOverride;
|
it->embeddedStyleOverride = embeddedStyleOverride;
|
||||||
it->imageRenderingOverride = imageRenderingOverride;
|
it->imageRenderingOverride = imageRenderingOverride;
|
||||||
it->fontFamilyOverride = fontFamilyOverride;
|
it->fontFamilyOverride = fontFamilyOverride;
|
||||||
it->fontSizeOverride = fontSizeOverride;
|
it->fontSizeOverride = fontSizeOverride;
|
||||||
|
it->bionicReadingOverride = bionicReadingOverride;
|
||||||
return saveToFile();
|
return saveToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ struct RecentBook {
|
|||||||
int8_t fontFamilyOverride = -1;
|
int8_t fontFamilyOverride = -1;
|
||||||
// -1 = use global setting, otherwise CrossPointSettings::FONT_SIZE value.
|
// -1 = use global setting, otherwise CrossPointSettings::FONT_SIZE value.
|
||||||
int8_t fontSizeOverride = -1;
|
int8_t fontSizeOverride = -1;
|
||||||
|
// -1 = use global default, otherwise explicit per-book override (0 = off, 1 = on).
|
||||||
|
int8_t bionicReadingOverride = -1;
|
||||||
|
|
||||||
bool operator==(const RecentBook& other) const { return path == other.path; }
|
bool operator==(const RecentBook& other) const { return path == other.path; }
|
||||||
};
|
};
|
||||||
@@ -64,6 +66,10 @@ class RecentBooksStore {
|
|||||||
bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride);
|
bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride);
|
||||||
bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride,
|
bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride,
|
||||||
int8_t fontFamilyOverride, int8_t fontSizeOverride);
|
int8_t fontFamilyOverride, int8_t fontSizeOverride);
|
||||||
|
bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride,
|
||||||
|
bool bionicReadingOverride);
|
||||||
|
bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride,
|
||||||
|
int8_t fontFamilyOverride, int8_t fontSizeOverride, bool bionicReadingOverride);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool loadFromBinaryFile();
|
bool loadFromBinaryFile();
|
||||||
|
|||||||
+3
-1
@@ -117,6 +117,8 @@ inline const std::vector<SettingInfo> list = {
|
|||||||
StrId::STR_CAT_READER),
|
StrId::STR_CAT_READER),
|
||||||
SettingInfo::Toggle(StrId::STR_HYPHENATION, &CrossPointSettings::hyphenationEnabled, "hyphenationEnabled",
|
SettingInfo::Toggle(StrId::STR_HYPHENATION, &CrossPointSettings::hyphenationEnabled, "hyphenationEnabled",
|
||||||
StrId::STR_CAT_READER),
|
StrId::STR_CAT_READER),
|
||||||
|
SettingInfo::Toggle(StrId::STR_BIONIC_READING, &CrossPointSettings::bionicReading, "bionicReading",
|
||||||
|
StrId::STR_CAT_READER),
|
||||||
SettingInfo::Enum(StrId::STR_IMAGES, &CrossPointSettings::imageRendering,
|
SettingInfo::Enum(StrId::STR_IMAGES, &CrossPointSettings::imageRendering,
|
||||||
{StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, StrId::STR_IMAGES_SUPPRESS},
|
{StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, StrId::STR_IMAGES_SUPPRESS},
|
||||||
"imageRendering", StrId::STR_CAT_READER),
|
"imageRendering", StrId::STR_CAT_READER),
|
||||||
@@ -145,7 +147,7 @@ inline const std::vector<SettingInfo> list = {
|
|||||||
StrId::STR_BTN_ACT_FORCE_REFRESH, StrId::STR_BTN_ACT_OPEN_TOC, StrId::STR_BTN_ACT_OPEN_BOOKMARKS, \
|
StrId::STR_BTN_ACT_FORCE_REFRESH, StrId::STR_BTN_ACT_OPEN_TOC, StrId::STR_BTN_ACT_OPEN_BOOKMARKS, \
|
||||||
StrId::STR_BTN_ACT_STAR_PAGE, StrId::STR_BTN_ACT_FOOTNOTES, StrId::STR_BTN_ACT_NEXT_SECTION, \
|
StrId::STR_BTN_ACT_STAR_PAGE, StrId::STR_BTN_ACT_FOOTNOTES, StrId::STR_BTN_ACT_NEXT_SECTION, \
|
||||||
StrId::STR_BTN_ACT_PREV_SECTION, StrId::STR_BTN_ACT_EXIT_READER, StrId::STR_BTN_ACT_READER_MENU, \
|
StrId::STR_BTN_ACT_PREV_SECTION, StrId::STR_BTN_ACT_EXIT_READER, StrId::STR_BTN_ACT_READER_MENU, \
|
||||||
StrId::STR_BTN_ACT_KOREADER_SYNC
|
StrId::STR_BTN_ACT_TOGGLE_BIONIC_READING, StrId::STR_BTN_ACT_KOREADER_SYNC
|
||||||
|
|
||||||
// Back button: short=exit reader, double=ignore, long=go home
|
// Back button: short=exit reader, double=ignore, long=go home
|
||||||
SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortBack, {StrId::STR_BTN_DEF_EXIT_READER},
|
SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortBack, {StrId::STR_BTN_DEF_EXIT_READER},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ struct MenuResult {
|
|||||||
int8_t fontFamilyOverride = -1;
|
int8_t fontFamilyOverride = -1;
|
||||||
int8_t fontSizeOverride = -1;
|
int8_t fontSizeOverride = -1;
|
||||||
uint8_t textDarkness = 1;
|
uint8_t textDarkness = 1;
|
||||||
|
uint8_t bionicReadingOverride = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ChapterResult {
|
struct ChapterResult {
|
||||||
|
|||||||
@@ -191,6 +191,9 @@ void EpubReaderActivity::onEnter() {
|
|||||||
bookImageRenderingOverride = currentBook.imageRenderingOverride;
|
bookImageRenderingOverride = currentBook.imageRenderingOverride;
|
||||||
bookFontFamilyOverride = currentBook.fontFamilyOverride;
|
bookFontFamilyOverride = currentBook.fontFamilyOverride;
|
||||||
bookFontSizeOverride = currentBook.fontSizeOverride;
|
bookFontSizeOverride = currentBook.fontSizeOverride;
|
||||||
|
bookBionicReadingOverride = (currentBook.bionicReadingOverride >= 0)
|
||||||
|
? static_cast<bool>(currentBook.bionicReadingOverride)
|
||||||
|
: static_cast<bool>(SETTINGS.bionicReading);
|
||||||
logReaderMemSnapshot("onEnter_after_recent_books");
|
logReaderMemSnapshot("onEnter_after_recent_books");
|
||||||
|
|
||||||
// Trigger first update
|
// Trigger first update
|
||||||
@@ -1033,13 +1036,14 @@ void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption
|
|||||||
|
|
||||||
void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOverride,
|
void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOverride,
|
||||||
const int8_t imageRenderingOverride, const int8_t fontFamilyOverride,
|
const int8_t imageRenderingOverride, const int8_t fontFamilyOverride,
|
||||||
const int8_t fontSizeOverride) {
|
const int8_t fontSizeOverride, const bool bionicReadingOverride) {
|
||||||
if (!epub) {
|
if (!epub) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bookEmbeddedStyleOverride == embeddedStyleOverride && bookImageRenderingOverride == imageRenderingOverride &&
|
if (bookEmbeddedStyleOverride == embeddedStyleOverride && bookImageRenderingOverride == imageRenderingOverride &&
|
||||||
bookFontFamilyOverride == fontFamilyOverride && bookFontSizeOverride == fontSizeOverride) {
|
bookFontFamilyOverride == fontFamilyOverride && bookFontSizeOverride == fontSizeOverride &&
|
||||||
|
bookBionicReadingOverride == bionicReadingOverride) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1047,8 +1051,9 @@ void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOver
|
|||||||
bookImageRenderingOverride = imageRenderingOverride;
|
bookImageRenderingOverride = imageRenderingOverride;
|
||||||
bookFontFamilyOverride = fontFamilyOverride;
|
bookFontFamilyOverride = fontFamilyOverride;
|
||||||
bookFontSizeOverride = fontSizeOverride;
|
bookFontSizeOverride = fontSizeOverride;
|
||||||
|
bookBionicReadingOverride = bionicReadingOverride;
|
||||||
RECENT_BOOKS.setReaderOverrides(epub->getPath(), bookEmbeddedStyleOverride, bookImageRenderingOverride,
|
RECENT_BOOKS.setReaderOverrides(epub->getPath(), bookEmbeddedStyleOverride, bookImageRenderingOverride,
|
||||||
bookFontFamilyOverride, bookFontSizeOverride);
|
bookFontFamilyOverride, bookFontSizeOverride, bookBionicReadingOverride);
|
||||||
|
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
if (section) {
|
if (section) {
|
||||||
@@ -1251,7 +1256,8 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
|
|
||||||
if (!section->loadSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
|
if (!section->loadSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
|
||||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||||
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) {
|
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, bookBionicReadingOverride,
|
||||||
|
imageRendering)) {
|
||||||
LOG_DBG("ERS", "Cache not found, building...");
|
LOG_DBG("ERS", "Cache not found, building...");
|
||||||
lastRenderStats.cacheRebuilt = true;
|
lastRenderStats.cacheRebuilt = true;
|
||||||
|
|
||||||
@@ -1272,8 +1278,8 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
renderer.clearSdCardFontAccumulation();
|
renderer.clearSdCardFontAccumulation();
|
||||||
if (!section->createSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
|
if (!section->createSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
|
||||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||||
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering,
|
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle,
|
||||||
progressFn)) {
|
bookBionicReadingOverride, imageRendering, progressFn)) {
|
||||||
LOG_ERR("ERS", "Failed to persist page data to SD");
|
LOG_ERR("ERS", "Failed to persist page data to SD");
|
||||||
section.reset();
|
section.reset();
|
||||||
return;
|
return;
|
||||||
@@ -1427,7 +1433,8 @@ void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportW
|
|||||||
Section nextSection(epub, nextSpineIndex, renderer);
|
Section nextSection(epub, nextSpineIndex, renderer);
|
||||||
if (nextSection.loadSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
|
if (nextSection.loadSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
|
||||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||||
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) {
|
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, bookBionicReadingOverride,
|
||||||
|
imageRendering)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1436,7 +1443,8 @@ void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportW
|
|||||||
renderer.clearSdCardFontAccumulation();
|
renderer.clearSdCardFontAccumulation();
|
||||||
if (!nextSection.createSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
|
if (!nextSection.createSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(),
|
||||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||||
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) {
|
viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle,
|
||||||
|
bookBionicReadingOverride, imageRendering)) {
|
||||||
LOG_ERR("ERS", "Failed silent indexing for chapter: %d", nextSpineIndex);
|
LOG_ERR("ERS", "Failed silent indexing for chapter: %d", nextSpineIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1819,12 +1827,13 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf
|
|||||||
if (!section->loadSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize), effectiveLineCompression,
|
if (!section->loadSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize), effectiveLineCompression,
|
||||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||||
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle,
|
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle,
|
||||||
SETTINGS.imageRendering)) {
|
static_cast<bool>(SETTINGS.bionicReading), SETTINGS.imageRendering)) {
|
||||||
LOG_DBG("SLP", "EPUB: section cache not found for spine %d, rebuilding", spineIndex);
|
LOG_DBG("SLP", "EPUB: section cache not found for spine %d, rebuilding", spineIndex);
|
||||||
if (!section->createSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize),
|
if (!section->createSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize),
|
||||||
effectiveLineCompression, SETTINGS.extraParagraphSpacing,
|
effectiveLineCompression, SETTINGS.extraParagraphSpacing,
|
||||||
SETTINGS.paragraphAlignment, viewportWidth, viewportHeight,
|
SETTINGS.paragraphAlignment, viewportWidth, viewportHeight,
|
||||||
SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, SETTINGS.imageRendering)) {
|
SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle,
|
||||||
|
static_cast<bool>(SETTINGS.bionicReading), SETTINGS.imageRendering)) {
|
||||||
LOG_ERR("SLP", "EPUB: failed to rebuild section cache for spine %d", spineIndex);
|
LOG_ERR("SLP", "EPUB: failed to rebuild section cache for spine %d", spineIndex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1861,14 +1870,15 @@ void EpubReaderActivity::openReaderMenu() {
|
|||||||
std::make_unique<EpubReaderMenuActivity>(
|
std::make_unique<EpubReaderMenuActivity>(
|
||||||
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, SETTINGS.orientation,
|
renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, SETTINGS.orientation,
|
||||||
!currentPageFootnotes.empty(), bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride,
|
!currentPageFootnotes.empty(), bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride,
|
||||||
bookFontSizeOverride, SETTINGS.textDarkness, !bookmarkStore.isEmpty(), isCurrentPageStarred),
|
bookFontSizeOverride, SETTINGS.textDarkness, bookBionicReadingOverride, !bookmarkStore.isEmpty(),
|
||||||
|
isCurrentPageStarred),
|
||||||
[this](const ActivityResult& result) {
|
[this](const ActivityResult& result) {
|
||||||
const auto& menu = std::get<MenuResult>(result.data);
|
const auto& menu = std::get<MenuResult>(result.data);
|
||||||
applyOrientation(menu.orientation);
|
applyOrientation(menu.orientation);
|
||||||
applyTextDarkness(menu.textDarkness);
|
applyTextDarkness(menu.textDarkness);
|
||||||
toggleAutoPageTurn(menu.pageTurnOption);
|
toggleAutoPageTurn(menu.pageTurnOption);
|
||||||
applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride, menu.fontFamilyOverride,
|
applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride, menu.fontFamilyOverride,
|
||||||
menu.fontSizeOverride);
|
menu.fontSizeOverride, static_cast<bool>(menu.bionicReadingOverride));
|
||||||
if (!result.isCancelled) {
|
if (!result.isCancelled) {
|
||||||
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
|
onReaderMenuConfirm(static_cast<EpubReaderMenuActivity::MenuAction>(menu.action));
|
||||||
}
|
}
|
||||||
@@ -1997,6 +2007,13 @@ void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION
|
|||||||
openReaderMenu();
|
openReaderMenu();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case BA::BTN_TOGGLE_BIONIC_READING:
|
||||||
|
if (epub) {
|
||||||
|
applyBookReaderOverrides(bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride,
|
||||||
|
bookFontSizeOverride, !bookBionicReadingOverride);
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case BA::BTN_KOREADER_SYNC:
|
case BA::BTN_KOREADER_SYNC:
|
||||||
launchKOReaderSync(SyncLaunchMode::COMPARE);
|
launchKOReaderSync(SyncLaunchMode::COMPARE);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ class EpubReaderActivity final : public Activity {
|
|||||||
int8_t bookImageRenderingOverride = -1;
|
int8_t bookImageRenderingOverride = -1;
|
||||||
int8_t bookFontFamilyOverride = -1;
|
int8_t bookFontFamilyOverride = -1;
|
||||||
int8_t bookFontSizeOverride = -1;
|
int8_t bookFontSizeOverride = -1;
|
||||||
|
bool bookBionicReadingOverride = false;
|
||||||
|
|
||||||
// Bookmarks (starred pages)
|
// Bookmarks (starred pages)
|
||||||
BookmarkStore bookmarkStore;
|
BookmarkStore bookmarkStore;
|
||||||
@@ -176,7 +177,7 @@ class EpubReaderActivity final : public Activity {
|
|||||||
void applyTextDarkness(uint8_t textDarkness);
|
void applyTextDarkness(uint8_t textDarkness);
|
||||||
void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
|
void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
|
||||||
void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride,
|
void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride,
|
||||||
int8_t fontSizeOverride);
|
int8_t fontSizeOverride, bool bionicReadingOverride);
|
||||||
void openReaderMenu();
|
void openReaderMenu();
|
||||||
bool getEffectiveEmbeddedStyle() const;
|
bool getEffectiveEmbeddedStyle() const;
|
||||||
uint8_t getEffectiveImageRendering() const;
|
uint8_t getEffectiveImageRendering() const;
|
||||||
|
|||||||
@@ -30,14 +30,12 @@ std::string defaultFontFamilyLabel(const SettingInfo& item) {
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
EpubReaderMenuActivity::EpubReaderMenuActivity(
|
||||||
const std::string& title, const int currentPage, const int totalPages,
|
GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title, const int currentPage,
|
||||||
const int bookProgressPercent, const uint8_t currentOrientation,
|
const int totalPages, const int bookProgressPercent, const uint8_t currentOrientation, const bool hasFootnotes,
|
||||||
const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride,
|
const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride,
|
||||||
const int8_t initialImageRenderingOverride,
|
const int8_t initialFontFamilyOverride, const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness,
|
||||||
const int8_t initialFontFamilyOverride,
|
const bool initialBionicReadingOverride, const bool hasStarredPages, const bool isCurrentPageStarred)
|
||||||
const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness,
|
|
||||||
const bool hasStarredPages, const bool isCurrentPageStarred)
|
|
||||||
: MenuListActivity("EpubReaderMenu", renderer, mappedInput),
|
: MenuListActivity("EpubReaderMenu", renderer, mappedInput),
|
||||||
currentPageStarred(isCurrentPageStarred),
|
currentPageStarred(isCurrentPageStarred),
|
||||||
pendingOrientation(currentOrientation),
|
pendingOrientation(currentOrientation),
|
||||||
@@ -46,6 +44,7 @@ EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInpu
|
|||||||
pendingFontFamilyOverride(initialFontFamilyOverride),
|
pendingFontFamilyOverride(initialFontFamilyOverride),
|
||||||
pendingFontSizeOverride(initialFontSizeOverride),
|
pendingFontSizeOverride(initialFontSizeOverride),
|
||||||
pendingTextDarkness(initialTextDarkness),
|
pendingTextDarkness(initialTextDarkness),
|
||||||
|
pendingBionicReading(initialBionicReadingOverride),
|
||||||
title(title),
|
title(title),
|
||||||
currentPage(currentPage),
|
currentPage(currentPage),
|
||||||
totalPages(totalPages),
|
totalPages(totalPages),
|
||||||
@@ -161,6 +160,15 @@ void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPa
|
|||||||
[](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingTextDarkness = v; })
|
[](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingTextDarkness = v; })
|
||||||
.withSubmenu(StrId::STR_READER_OVERRIDES));
|
.withSubmenu(StrId::STR_READER_OVERRIDES));
|
||||||
|
|
||||||
|
menuItems.push_back(
|
||||||
|
SettingInfo::DynamicEnumCtx(
|
||||||
|
StrId::STR_BIONIC_READING, {StrId::STR_STATE_OFF, StrId::STR_STATE_ON}, self,
|
||||||
|
[](const void* ctx) -> uint8_t {
|
||||||
|
return static_cast<const EpubReaderMenuActivity*>(ctx)->pendingBionicReading ? 1 : 0;
|
||||||
|
},
|
||||||
|
[](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingBionicReading = (v != 0); })
|
||||||
|
.withSubmenu(StrId::STR_READER_OVERRIDES));
|
||||||
|
|
||||||
// Helper functions, reading ruler, auto page turn, orientation
|
// Helper functions, reading ruler, auto page turn, orientation
|
||||||
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_UTILS));
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_UTILS));
|
||||||
// Auto page turn: ACTION type with custom cycling in onActionSelected
|
// Auto page turn: ACTION type with custom cycling in onActionSelected
|
||||||
@@ -240,7 +248,7 @@ EpubReaderMenuActivity::MenuAction EpubReaderMenuActivity::actionForSettingActio
|
|||||||
void EpubReaderMenuActivity::finishWithAction(MenuAction action) {
|
void EpubReaderMenuActivity::finishWithAction(MenuAction action) {
|
||||||
setResult(MenuResult{static_cast<int>(action), -1, pendingOrientation, selectedPageTurnOption,
|
setResult(MenuResult{static_cast<int>(action), -1, pendingOrientation, selectedPageTurnOption,
|
||||||
pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingFontFamilyOverride,
|
pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingFontFamilyOverride,
|
||||||
pendingFontSizeOverride, pendingTextDarkness});
|
pendingFontSizeOverride, pendingTextDarkness, static_cast<uint8_t>(pendingBionicReading)});
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +281,8 @@ void EpubReaderMenuActivity::onBackPressed() {
|
|||||||
pendingImageRenderingOverride,
|
pendingImageRenderingOverride,
|
||||||
pendingFontFamilyOverride,
|
pendingFontFamilyOverride,
|
||||||
pendingFontSizeOverride,
|
pendingFontSizeOverride,
|
||||||
pendingTextDarkness};
|
pendingTextDarkness,
|
||||||
|
static_cast<uint8_t>(pendingBionicReading)};
|
||||||
setResult(std::move(result));
|
setResult(std::move(result));
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ class EpubReaderMenuActivity final : public MenuListActivity {
|
|||||||
const uint8_t currentOrientation, const bool hasFootnotes,
|
const uint8_t currentOrientation, const bool hasFootnotes,
|
||||||
const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride,
|
const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride,
|
||||||
const int8_t initialFontFamilyOverride, const int8_t initialFontSizeOverride,
|
const int8_t initialFontFamilyOverride, const int8_t initialFontSizeOverride,
|
||||||
const uint8_t initialTextDarkness, const bool hasStarredPages,
|
const uint8_t initialTextDarkness, const bool initialBionicReadingOverride,
|
||||||
const bool isCurrentPageStarred);
|
const bool hasStarredPages, const bool isCurrentPageStarred);
|
||||||
|
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void render(RenderLock&&) override;
|
void render(RenderLock&&) override;
|
||||||
@@ -69,6 +69,7 @@ class EpubReaderMenuActivity final : public MenuListActivity {
|
|||||||
int8_t pendingFontFamilyOverride = -1;
|
int8_t pendingFontFamilyOverride = -1;
|
||||||
int8_t pendingFontSizeOverride = -1;
|
int8_t pendingFontSizeOverride = -1;
|
||||||
uint8_t pendingTextDarkness = 1;
|
uint8_t pendingTextDarkness = 1;
|
||||||
|
bool pendingBionicReading = false;
|
||||||
|
|
||||||
static constexpr const char* pageTurnLabels[] = {"", "1", "3", "6", "12"};
|
static constexpr const char* pageTurnLabels[] = {"", "1", "3", "6", "12"};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user