Add hyphenation support and update settings management

- Introduced hyphenationEnabled flag in ParsedText and Section classes.
- Updated constructors and methods to handle hyphenation settings.
- Modified settings file versioning to include hyphenationEnabled.
- Enhanced settings UI to allow toggling of hyphenation feature.
This commit is contained in:
Arthur Tazhitdinov 2025-12-19 12:41:35 +05:00
parent 26bea34921
commit ca88c2eef7
11 changed files with 42 additions and 29 deletions

View File

@ -36,6 +36,7 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo
const int spaceWidth = renderer.getSpaceWidth(fontId); const int spaceWidth = renderer.getSpaceWidth(fontId);
// Maintain classic prose indenting when extra paragraph spacing is disabled. // Maintain classic prose indenting when extra paragraph spacing is disabled.
const bool allowIndent = !extraParagraphSpacing && (style == TextBlock::JUSTIFIED || style == TextBlock::LEFT_ALIGN); const bool allowIndent = !extraParagraphSpacing && (style == TextBlock::JUSTIFIED || style == TextBlock::LEFT_ALIGN);
const bool allowHyphenation = hyphenationEnabled;
const int indentWidth = allowIndent ? renderer.getTextWidth(fontId, "m", REGULAR) : 0; const int indentWidth = allowIndent ? renderer.getTextWidth(fontId, "m", REGULAR) : 0;
const int firstLinePageWidth = allowIndent ? std::max(pageWidth - indentWidth, 0) : pageWidth; const int firstLinePageWidth = allowIndent ? std::max(pageWidth - indentWidth, 0) : pageWidth;
auto pageWidthForLine = [&](const bool isFirstLine) -> int { return isFirstLine ? firstLinePageWidth : pageWidth; }; auto pageWidthForLine = [&](const bool isFirstLine) -> int { return isFirstLine ? firstLinePageWidth : pageWidth; };
@ -153,7 +154,7 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo
continue; continue;
} }
if (lineWordCount > 0 && availableWidth > 0) { if (allowHyphenation && lineWordCount > 0 && availableWidth > 0) {
// Try hyphenating the next word so the current line stays compact. // Try hyphenating the next word so the current line stays compact.
HyphenationResult split; HyphenationResult split;
if (Hyphenator::splitWord(renderer, fontId, *wordIt, *styleIt, availableWidth, &split, false)) { if (Hyphenator::splitWord(renderer, fontId, *wordIt, *styleIt, availableWidth, &split, false)) {

View File

@ -17,10 +17,12 @@ class ParsedText {
std::list<EpdFontStyle> wordStyles; std::list<EpdFontStyle> wordStyles;
TextBlock::BLOCK_STYLE style; TextBlock::BLOCK_STYLE style;
bool extraParagraphSpacing; bool extraParagraphSpacing;
bool hyphenationEnabled;
public: public:
explicit ParsedText(const TextBlock::BLOCK_STYLE style, const bool extraParagraphSpacing) explicit ParsedText(const TextBlock::BLOCK_STYLE style, const bool extraParagraphSpacing,
: style(style), extraParagraphSpacing(extraParagraphSpacing) {} const bool hyphenationEnabled)
: style(style), extraParagraphSpacing(extraParagraphSpacing), hyphenationEnabled(hyphenationEnabled) {}
~ParsedText() = default; ~ParsedText() = default;
void addWord(std::string word, EpdFontStyle fontStyle); void addWord(std::string word, EpdFontStyle fontStyle);

View File

@ -10,7 +10,7 @@
#include "parsers/ChapterHtmlSlimParser.h" #include "parsers/ChapterHtmlSlimParser.h"
namespace { namespace {
constexpr uint8_t SECTION_FILE_VERSION = 5; constexpr uint8_t SECTION_FILE_VERSION = 6;
} }
void Section::onPageComplete(std::unique_ptr<Page> page) { void Section::onPageComplete(std::unique_ptr<Page> page) {
@ -27,7 +27,7 @@ void Section::onPageComplete(std::unique_ptr<Page> page) {
void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop, void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
const int marginRight, const int marginBottom, const int marginLeft, const int marginRight, const int marginBottom, const int marginLeft,
const bool extraParagraphSpacing) const { const bool extraParagraphSpacing, const bool hyphenationEnabled) const {
std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str()); std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str());
serialization::writePod(outputFile, SECTION_FILE_VERSION); serialization::writePod(outputFile, SECTION_FILE_VERSION);
serialization::writePod(outputFile, fontId); serialization::writePod(outputFile, fontId);
@ -37,13 +37,14 @@ void Section::writeCacheMetadata(const int fontId, const float lineCompression,
serialization::writePod(outputFile, marginBottom); serialization::writePod(outputFile, marginBottom);
serialization::writePod(outputFile, marginLeft); serialization::writePod(outputFile, marginLeft);
serialization::writePod(outputFile, extraParagraphSpacing); serialization::writePod(outputFile, extraParagraphSpacing);
serialization::writePod(outputFile, hyphenationEnabled);
serialization::writePod(outputFile, pageCount); serialization::writePod(outputFile, pageCount);
outputFile.close(); outputFile.close();
} }
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop, bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
const int marginRight, const int marginBottom, const int marginLeft, const int marginRight, const int marginBottom, const int marginLeft,
const bool extraParagraphSpacing) { const bool extraParagraphSpacing, const bool hyphenationEnabled) {
if (!SD.exists(cachePath.c_str())) { if (!SD.exists(cachePath.c_str())) {
return false; return false;
} }
@ -69,6 +70,7 @@ bool Section::loadCacheMetadata(const int fontId, const float lineCompression, c
int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft; int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft;
float fileLineCompression; float fileLineCompression;
bool fileExtraParagraphSpacing; bool fileExtraParagraphSpacing;
bool fileHyphenationEnabled;
serialization::readPod(inputFile, fileFontId); serialization::readPod(inputFile, fileFontId);
serialization::readPod(inputFile, fileLineCompression); serialization::readPod(inputFile, fileLineCompression);
serialization::readPod(inputFile, fileMarginTop); serialization::readPod(inputFile, fileMarginTop);
@ -76,10 +78,11 @@ bool Section::loadCacheMetadata(const int fontId, const float lineCompression, c
serialization::readPod(inputFile, fileMarginBottom); serialization::readPod(inputFile, fileMarginBottom);
serialization::readPod(inputFile, fileMarginLeft); serialization::readPod(inputFile, fileMarginLeft);
serialization::readPod(inputFile, fileExtraParagraphSpacing); serialization::readPod(inputFile, fileExtraParagraphSpacing);
serialization::readPod(inputFile, fileHyphenationEnabled);
if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop || if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop ||
marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft || marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft ||
extraParagraphSpacing != fileExtraParagraphSpacing) { extraParagraphSpacing != fileExtraParagraphSpacing || hyphenationEnabled != fileHyphenationEnabled) {
inputFile.close(); inputFile.close();
Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis()); Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis());
clearCache(); clearCache();
@ -116,7 +119,7 @@ bool Section::clearCache() const {
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop, bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop,
const int marginRight, const int marginBottom, const int marginLeft, const int marginRight, const int marginBottom, const int marginLeft,
const bool extraParagraphSpacing) { const bool extraParagraphSpacing, const bool hyphenationEnabled) {
const auto localPath = epub->getSpineItem(spineIndex); const auto localPath = epub->getSpineItem(spineIndex);
// TODO: Should we get rid of this file all together? // TODO: Should we get rid of this file all together?
@ -137,7 +140,7 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath; const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath;
ChapterHtmlSlimParser visitor(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight, ChapterHtmlSlimParser visitor(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight,
marginBottom, marginLeft, extraParagraphSpacing, marginBottom, marginLeft, extraParagraphSpacing, hyphenationEnabled,
[this](std::unique_ptr<Page> page) { this->onPageComplete(std::move(page)); }); [this](std::unique_ptr<Page> page) { this->onPageComplete(std::move(page)); });
success = visitor.parseAndBuildPages(); success = visitor.parseAndBuildPages();
@ -147,7 +150,8 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
return false; return false;
} }
writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft, extraParagraphSpacing); writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft, extraParagraphSpacing,
hyphenationEnabled);
return true; return true;
} }

View File

@ -13,7 +13,7 @@ class Section {
std::string cachePath; std::string cachePath;
void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
int marginLeft, bool extraParagraphSpacing) const; int marginLeft, bool extraParagraphSpacing, bool hyphenationEnabled) const;
void onPageComplete(std::unique_ptr<Page> page); void onPageComplete(std::unique_ptr<Page> page);
public: public:
@ -26,10 +26,10 @@ class Section {
} }
~Section() = default; ~Section() = default;
bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
int marginLeft, bool extraParagraphSpacing); int marginLeft, bool extraParagraphSpacing, bool hyphenationEnabled);
void setupCacheDir() const; void setupCacheDir() const;
bool clearCache() const; bool clearCache() const;
bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
int marginLeft, bool extraParagraphSpacing); int marginLeft, bool extraParagraphSpacing, bool hyphenationEnabled);
std::unique_ptr<Page> loadPageFromSD() const; std::unique_ptr<Page> loadPageFromSD() const;
}; };

View File

@ -48,7 +48,7 @@ void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::BLOCK_STYLE style
makePages(); makePages();
} }
currentTextBlock.reset(new ParsedText(style, extraParagraphSpacing)); currentTextBlock.reset(new ParsedText(style, extraParagraphSpacing, hyphenationEnabled));
} }
void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) { void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {

View File

@ -36,6 +36,7 @@ class ChapterHtmlSlimParser {
int marginBottom; int marginBottom;
int marginLeft; int marginLeft;
bool extraParagraphSpacing; bool extraParagraphSpacing;
bool hyphenationEnabled;
void startNewTextBlock(TextBlock::BLOCK_STYLE style); void startNewTextBlock(TextBlock::BLOCK_STYLE style);
void makePages(); void makePages();
@ -48,6 +49,7 @@ class ChapterHtmlSlimParser {
explicit ChapterHtmlSlimParser(const char* filepath, GfxRenderer& renderer, const int fontId, explicit ChapterHtmlSlimParser(const char* filepath, GfxRenderer& renderer, const int fontId,
const float lineCompression, const int marginTop, const int marginRight, const float lineCompression, const int marginTop, const int marginRight,
const int marginBottom, const int marginLeft, const bool extraParagraphSpacing, const int marginBottom, const int marginLeft, const bool extraParagraphSpacing,
const bool hyphenationEnabled,
const std::function<void(std::unique_ptr<Page>)>& completePageFn) const std::function<void(std::unique_ptr<Page>)>& completePageFn)
: filepath(filepath), : filepath(filepath),
renderer(renderer), renderer(renderer),
@ -58,6 +60,7 @@ class ChapterHtmlSlimParser {
marginBottom(marginBottom), marginBottom(marginBottom),
marginLeft(marginLeft), marginLeft(marginLeft),
extraParagraphSpacing(extraParagraphSpacing), extraParagraphSpacing(extraParagraphSpacing),
hyphenationEnabled(hyphenationEnabled),
completePageFn(completePageFn) {} completePageFn(completePageFn) {}
~ChapterHtmlSlimParser() = default; ~ChapterHtmlSlimParser() = default;
bool parseAndBuildPages(); bool parseAndBuildPages();

View File

@ -12,7 +12,7 @@ CrossPointSettings CrossPointSettings::instance;
namespace { namespace {
constexpr uint8_t SETTINGS_FILE_VERSION = 1; constexpr uint8_t SETTINGS_FILE_VERSION = 1;
constexpr uint8_t SETTINGS_COUNT = 2; constexpr uint8_t SETTINGS_COUNT = 3;
constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin"; constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin";
} // namespace } // namespace
@ -25,6 +25,7 @@ bool CrossPointSettings::saveToFile() const {
serialization::writePod(outputFile, SETTINGS_COUNT); serialization::writePod(outputFile, SETTINGS_COUNT);
serialization::writePod(outputFile, whiteSleepScreen); serialization::writePod(outputFile, whiteSleepScreen);
serialization::writePod(outputFile, extraParagraphSpacing); serialization::writePod(outputFile, extraParagraphSpacing);
serialization::writePod(outputFile, hyphenationEnabled);
outputFile.close(); outputFile.close();
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
@ -50,15 +51,15 @@ bool CrossPointSettings::loadFromFile() {
uint8_t fileSettingsCount = 0; uint8_t fileSettingsCount = 0;
serialization::readPod(inputFile, fileSettingsCount); serialization::readPod(inputFile, fileSettingsCount);
// load settings that exist // load settings that exist in the file (supports backward compatibility)
switch (fileSettingsCount) { if (fileSettingsCount >= 1) {
case 1: serialization::readPod(inputFile, whiteSleepScreen);
serialization::readPod(inputFile, whiteSleepScreen); }
break; if (fileSettingsCount >= 2) {
case 2: serialization::readPod(inputFile, extraParagraphSpacing);
serialization::readPod(inputFile, whiteSleepScreen); }
serialization::readPod(inputFile, extraParagraphSpacing); if (fileSettingsCount >= 3) {
break; serialization::readPod(inputFile, hyphenationEnabled);
} }
inputFile.close(); inputFile.close();

View File

@ -20,6 +20,7 @@ class CrossPointSettings {
// Text rendering settings // Text rendering settings
uint8_t extraParagraphSpacing = 1; uint8_t extraParagraphSpacing = 1;
uint8_t hyphenationEnabled = 1;
~CrossPointSettings() = default; ~CrossPointSettings() = default;

View File

@ -207,7 +207,7 @@ void EpubReaderActivity::renderScreen() {
Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex); Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex);
section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer)); section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer));
if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, marginLeft, if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, marginLeft,
SETTINGS.extraParagraphSpacing)) { SETTINGS.extraParagraphSpacing, SETTINGS.hyphenationEnabled)) {
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis()); Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
{ {
@ -227,7 +227,7 @@ void EpubReaderActivity::renderScreen() {
section->setupCacheDir(); section->setupCacheDir();
if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
marginLeft, SETTINGS.extraParagraphSpacing)) { marginLeft, SETTINGS.extraParagraphSpacing, SETTINGS.hyphenationEnabled)) {
Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis()); Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis());
section.reset(); section.reset();
return; return;

View File

@ -8,8 +8,9 @@
// Define the static settings list // Define the static settings list
const SettingInfo SettingsActivity::settingsList[settingsCount] = { const SettingInfo SettingsActivity::settingsList[settingsCount] = {
{"White Sleep Screen", &CrossPointSettings::whiteSleepScreen}, {"White Sleep Screen", &CrossPointSettings::whiteSleepScreen},
{"Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing}}; {"Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing},
{"Hyphenation", &CrossPointSettings::hyphenationEnabled}};
void SettingsActivity::taskTrampoline(void* param) { void SettingsActivity::taskTrampoline(void* param) {
auto* self = static_cast<SettingsActivity*>(param); auto* self = static_cast<SettingsActivity*>(param);

View File

@ -25,7 +25,7 @@ class SettingsActivity final : public Activity {
const std::function<void()> onGoHome; const std::function<void()> onGoHome;
// Static settings list // Static settings list
static constexpr int settingsCount = 2; // Number of settings static constexpr int settingsCount = 3; // Number of settings
static const SettingInfo settingsList[settingsCount]; static const SettingInfo settingsList[settingsCount];
static void taskTrampoline(void* param); static void taskTrampoline(void* param);