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);
// Maintain classic prose indenting when extra paragraph spacing is disabled.
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 firstLinePageWidth = allowIndent ? std::max(pageWidth - indentWidth, 0) : 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;
}
if (lineWordCount > 0 && availableWidth > 0) {
if (allowHyphenation && lineWordCount > 0 && availableWidth > 0) {
// Try hyphenating the next word so the current line stays compact.
HyphenationResult split;
if (Hyphenator::splitWord(renderer, fontId, *wordIt, *styleIt, availableWidth, &split, false)) {

View File

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

View File

@ -10,7 +10,7 @@
#include "parsers/ChapterHtmlSlimParser.h"
namespace {
constexpr uint8_t SECTION_FILE_VERSION = 5;
constexpr uint8_t SECTION_FILE_VERSION = 6;
}
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,
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());
serialization::writePod(outputFile, SECTION_FILE_VERSION);
serialization::writePod(outputFile, fontId);
@ -37,13 +37,14 @@ void Section::writeCacheMetadata(const int fontId, const float lineCompression,
serialization::writePod(outputFile, marginBottom);
serialization::writePod(outputFile, marginLeft);
serialization::writePod(outputFile, extraParagraphSpacing);
serialization::writePod(outputFile, hyphenationEnabled);
serialization::writePod(outputFile, pageCount);
outputFile.close();
}
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
const int marginRight, const int marginBottom, const int marginLeft,
const bool extraParagraphSpacing) {
const bool extraParagraphSpacing, const bool hyphenationEnabled) {
if (!SD.exists(cachePath.c_str())) {
return false;
}
@ -69,6 +70,7 @@ bool Section::loadCacheMetadata(const int fontId, const float lineCompression, c
int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft;
float fileLineCompression;
bool fileExtraParagraphSpacing;
bool fileHyphenationEnabled;
serialization::readPod(inputFile, fileFontId);
serialization::readPod(inputFile, fileLineCompression);
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, fileMarginLeft);
serialization::readPod(inputFile, fileExtraParagraphSpacing);
serialization::readPod(inputFile, fileHyphenationEnabled);
if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop ||
marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft ||
extraParagraphSpacing != fileExtraParagraphSpacing) {
extraParagraphSpacing != fileExtraParagraphSpacing || hyphenationEnabled != fileHyphenationEnabled) {
inputFile.close();
Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis());
clearCache();
@ -116,7 +119,7 @@ bool Section::clearCache() const {
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop,
const int marginRight, const int marginBottom, const int marginLeft,
const bool extraParagraphSpacing) {
const bool extraParagraphSpacing, const bool hyphenationEnabled) {
const auto localPath = epub->getSpineItem(spineIndex);
// 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;
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)); });
success = visitor.parseAndBuildPages();
@ -147,7 +150,8 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
return false;
}
writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft, extraParagraphSpacing);
writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft, extraParagraphSpacing,
hyphenationEnabled);
return true;
}

View File

@ -13,7 +13,7 @@ class Section {
std::string cachePath;
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);
public:
@ -26,10 +26,10 @@ class Section {
}
~Section() = default;
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;
bool clearCache() const;
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;
};

View File

@ -48,7 +48,7 @@ void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::BLOCK_STYLE style
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) {

View File

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

View File

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

View File

@ -20,6 +20,7 @@ class CrossPointSettings {
// Text rendering settings
uint8_t extraParagraphSpacing = 1;
uint8_t hyphenationEnabled = 1;
~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);
section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer));
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());
{
@ -227,7 +227,7 @@ void EpubReaderActivity::renderScreen() {
section->setupCacheDir();
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());
section.reset();
return;

View File

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

View File

@ -25,7 +25,7 @@ class SettingsActivity final : public Activity {
const std::function<void()> onGoHome;
// 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 void taskTrampoline(void* param);