## Summary This PR introduces **Focus Reading**, a generic implementation of artificial fixation points (similar to Bionic Reading) designed to improve reading speed and focus by bolding the initial characters of words. This is achieved by dynamically bolding characters during indexing. <img width="500" alt="Focus Reading on X3" src="https://github.com/user-attachments/assets/94a632a5-82da-47be-957c-538b35bf84d9" /> ### Implementation Details #### Core Text Engine (`ParsedText`) - Modified `ParsedText::addWord` to implement a custom bolding algorithm. It uses a 45% ratio for bolding, with a minimum of 1 character and a maximum of 9. - UTF-8 Safety: Integrated `utf8NextCodepoint` to ensure character counting and string slicing occur at safe byte boundaries, preventing corruption of multi-byte characters (e.g., accented letters or smart quotes). - Intelligent Tokenization: This correctly identifies and separates "word" characters (letters, apostrophes, hyphens) from "non-word" characters (numbers, brackets, smart quotes). - Formatting Preservation: The logic ensures that punctuation is not "stolen" for the bolding count and that existing styles (like italics or underlines) are preserved across the bold/regular split. - Processing at indexing stage reduces CPU load at render-time and ensures layout/fit is unaffected. - Split details are tracked with `wordIsFocusSuffix`. After splitting and layout, suffixes are merged back into their preceding word entries to prevent a doubling of RAM usage. #### Settings and UI - Version Management: Bumped `SECTION_FILE_VERSION` to `21` - Global Settings: Added `focusReadingEnabled` to `CrossPointSettings`. - User Interface: Added a new toggle in the "Reader" section of the settings menu, positioned after the "Embedded Style" option. - Localization: Added the `STR_FOCUS_READING` string #### Plumbing - Plumbed the `focusReadingEnabled` boolean through `EpubReaderActivity`, `Section`, and `ChapterHtmlSlimParser` to ensure the user's setting reaches the `ParsedText` constructor during chapter indexing. ## Additional Context ### Files Changed - `lib/Epub/Epub/ParsedText.h / .cpp`: Core fixation logic and UTF-8 tokenization. - `lib/Epub/Epub/Section.h / .cpp`: Cache header updates and invalidation logic. - `lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h / .cpp`: Plumbing the setting to text blocks. - `src/CrossPointSettings.h`: Data persistence for the new setting. - `src/SettingsList.h`: UI toggle implementation. - `src/activities/reader/EpubReaderActivity.cpp`: Handling settings changes during reading sessions. - `lib/I18n/translations/*.yaml`: UI strings. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**YES**_
265 lines
8.5 KiB
C++
265 lines
8.5 KiB
C++
#pragma once
|
|
#include <HalStorage.h>
|
|
|
|
#include <cstdint>
|
|
#include <iosfwd>
|
|
|
|
class CrossPointSettings {
|
|
private:
|
|
// Private constructor for singleton
|
|
CrossPointSettings() = default;
|
|
|
|
// Static instance
|
|
static CrossPointSettings instance;
|
|
|
|
public:
|
|
// Delete copy constructor and assignment
|
|
CrossPointSettings(const CrossPointSettings&) = delete;
|
|
CrossPointSettings& operator=(const CrossPointSettings&) = delete;
|
|
|
|
enum SLEEP_SCREEN_MODE {
|
|
DARK = 0,
|
|
LIGHT = 1,
|
|
CUSTOM = 2,
|
|
COVER = 3,
|
|
BLANK = 4,
|
|
COVER_CUSTOM = 5,
|
|
SLEEP_SCREEN_MODE_COUNT
|
|
};
|
|
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT };
|
|
enum SLEEP_SCREEN_COVER_FILTER {
|
|
NO_FILTER = 0,
|
|
BLACK_AND_WHITE = 1,
|
|
INVERTED_BLACK_AND_WHITE = 2,
|
|
SLEEP_SCREEN_COVER_FILTER_COUNT
|
|
};
|
|
|
|
// Status bar enum - legacy
|
|
enum STATUS_BAR_MODE {
|
|
NONE = 0,
|
|
NO_PROGRESS = 1,
|
|
FULL = 2,
|
|
BOOK_PROGRESS_BAR = 3,
|
|
ONLY_BOOK_PROGRESS_BAR = 4,
|
|
CHAPTER_PROGRESS_BAR = 5,
|
|
STATUS_BAR_MODE_COUNT
|
|
};
|
|
enum STATUS_BAR_PROGRESS_BAR {
|
|
BOOK_PROGRESS = 0,
|
|
CHAPTER_PROGRESS = 1,
|
|
HIDE_PROGRESS = 2,
|
|
STATUS_BAR_PROGRESS_BAR_COUNT
|
|
};
|
|
enum STATUS_BAR_PROGRESS_BAR_THICKNESS {
|
|
PROGRESS_BAR_THIN = 0,
|
|
PROGRESS_BAR_NORMAL = 1,
|
|
PROGRESS_BAR_THICK = 2,
|
|
STATUS_BAR_PROGRESS_BAR_THICKNESS_COUNT
|
|
};
|
|
enum STATUS_BAR_TITLE { BOOK_TITLE = 0, CHAPTER_TITLE = 1, HIDE_TITLE = 2, STATUS_BAR_TITLE_COUNT };
|
|
enum XTC_STATUS_BAR_MODE {
|
|
XTC_STATUS_BAR_HIDE = 0,
|
|
XTC_STATUS_BAR_BOTTOM = 1,
|
|
XTC_STATUS_BAR_TOP = 2,
|
|
XTC_STATUS_BAR_MODE_COUNT
|
|
};
|
|
|
|
enum ORIENTATION {
|
|
PORTRAIT = 0, // 480x800 logical coordinates (current default)
|
|
LANDSCAPE_CW = 1, // 800x480 logical coordinates, rotated 180° (swap top/bottom)
|
|
INVERTED = 2, // 480x800 logical coordinates, inverted
|
|
LANDSCAPE_CCW = 3, // 800x480 logical coordinates, native panel orientation
|
|
ORIENTATION_COUNT
|
|
};
|
|
|
|
// Front button layout options (legacy)
|
|
// Default: Back, Confirm, Left, Right
|
|
// Swapped: Left, Right, Back, Confirm
|
|
enum FRONT_BUTTON_LAYOUT {
|
|
BACK_CONFIRM_LEFT_RIGHT = 0,
|
|
LEFT_RIGHT_BACK_CONFIRM = 1,
|
|
LEFT_BACK_CONFIRM_RIGHT = 2,
|
|
BACK_CONFIRM_RIGHT_LEFT = 3,
|
|
FRONT_BUTTON_LAYOUT_COUNT
|
|
};
|
|
|
|
// Front button hardware identifiers (for remapping)
|
|
enum FRONT_BUTTON_HARDWARE {
|
|
FRONT_HW_BACK = 0,
|
|
FRONT_HW_CONFIRM = 1,
|
|
FRONT_HW_LEFT = 2,
|
|
FRONT_HW_RIGHT = 3,
|
|
FRONT_BUTTON_HARDWARE_COUNT
|
|
};
|
|
|
|
// Side button layout options
|
|
// Default: Previous, Next
|
|
// Swapped: Next, Previous
|
|
enum SIDE_BUTTON_LAYOUT { PREV_NEXT = 0, NEXT_PREV = 1, SIDE_BUTTON_LAYOUT_COUNT };
|
|
|
|
// Font family options (built-in fonts only; SD card fonts use sdFontFamilyName)
|
|
enum FONT_FAMILY { NOTOSERIF = 0, NOTOSANS = 1, OPENDYSLEXIC = 2, FONT_FAMILY_COUNT };
|
|
static constexpr uint8_t BUILTIN_FONT_COUNT = FONT_FAMILY_COUNT;
|
|
// Font size options
|
|
enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3, FONT_SIZE_COUNT };
|
|
enum LINE_COMPRESSION { TIGHT = 0, NORMAL = 1, WIDE = 2, LINE_COMPRESSION_COUNT };
|
|
enum PARAGRAPH_ALIGNMENT {
|
|
JUSTIFIED = 0,
|
|
LEFT_ALIGN = 1,
|
|
CENTER_ALIGN = 2,
|
|
RIGHT_ALIGN = 3,
|
|
BOOK_STYLE = 4,
|
|
PARAGRAPH_ALIGNMENT_COUNT
|
|
};
|
|
|
|
// Auto-sleep timeout options (in minutes)
|
|
enum SLEEP_TIMEOUT {
|
|
SLEEP_1_MIN = 0,
|
|
SLEEP_5_MIN = 1,
|
|
SLEEP_10_MIN = 2,
|
|
SLEEP_15_MIN = 3,
|
|
SLEEP_30_MIN = 4,
|
|
SLEEP_TIMEOUT_COUNT
|
|
};
|
|
|
|
// E-ink refresh frequency (pages between full refreshes)
|
|
enum REFRESH_FREQUENCY {
|
|
REFRESH_1 = 0,
|
|
REFRESH_5 = 1,
|
|
REFRESH_10 = 2,
|
|
REFRESH_15 = 3,
|
|
REFRESH_30 = 4,
|
|
REFRESH_FREQUENCY_COUNT
|
|
};
|
|
|
|
// Short power button press actions
|
|
enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2, FORCE_REFRESH = 3, SHORT_PWRBTN_COUNT };
|
|
|
|
// Hide battery percentage
|
|
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2, HIDE_BATTERY_PERCENTAGE_COUNT };
|
|
|
|
// Page turn button long press behavior
|
|
enum LONG_PRESS_BUTTON_BEHAVIOR {
|
|
OFF = 0,
|
|
CHAPTER_SKIP = 1,
|
|
ORIENTATION_CHANGE = 2,
|
|
LONG_PRESS_BUTTON_BEHAVIOR_COUNT
|
|
};
|
|
|
|
// UI Theme
|
|
enum UI_THEME { CLASSIC = 0, LYRA = 1, LYRA_3_COVERS = 2, ROUNDEDRAFF = 3 };
|
|
|
|
// Image rendering in EPUB reader
|
|
enum IMAGE_RENDERING { IMAGES_DISPLAY = 0, IMAGES_PLACEHOLDER = 1, IMAGES_SUPPRESS = 2, IMAGE_RENDERING_COUNT };
|
|
|
|
enum TILT_PAGE_TURN { TILT_OFF = 0, TILT_NORMAL = 1, TILT_NVERTED = 2, TILT_PAGE_TURN_COUNT };
|
|
|
|
// Sleep screen settings
|
|
uint8_t sleepScreen = DARK;
|
|
// Sleep screen cover mode settings
|
|
uint8_t sleepScreenCoverMode = FIT;
|
|
// Sleep screen cover filter
|
|
uint8_t sleepScreenCoverFilter = NO_FILTER;
|
|
// Status bar settings (statusBar retained for migration only)
|
|
uint8_t statusBar = FULL;
|
|
uint8_t statusBarChapterPageCount = 1;
|
|
uint8_t statusBarBookProgressPercentage = 1;
|
|
uint8_t statusBarProgressBar = HIDE_PROGRESS;
|
|
uint8_t statusBarProgressBarThickness = PROGRESS_BAR_NORMAL;
|
|
uint8_t statusBarTitle = CHAPTER_TITLE;
|
|
uint8_t statusBarBattery = 1;
|
|
uint8_t xtcStatusBarMode = XTC_STATUS_BAR_HIDE;
|
|
// Text rendering settings
|
|
uint8_t extraParagraphSpacing = 1;
|
|
uint8_t textAntiAliasing = 1;
|
|
// Short power button click behaviour
|
|
uint8_t shortPwrBtn = IGNORE;
|
|
// EPUB reading orientation settings
|
|
// 0 = portrait (default), 1 = landscape clockwise, 2 = inverted, 3 = landscape counter-clockwise
|
|
uint8_t orientation = PORTRAIT;
|
|
// Button layouts (front layout retained for migration only)
|
|
uint8_t frontButtonLayout = BACK_CONFIRM_LEFT_RIGHT;
|
|
uint8_t sideButtonLayout = PREV_NEXT;
|
|
// Front button remap (logical -> hardware)
|
|
// Used by MappedInputManager to translate logical buttons into physical front buttons.
|
|
uint8_t frontButtonBack = FRONT_HW_BACK;
|
|
uint8_t frontButtonConfirm = FRONT_HW_CONFIRM;
|
|
uint8_t frontButtonLeft = FRONT_HW_LEFT;
|
|
uint8_t frontButtonRight = FRONT_HW_RIGHT;
|
|
// Reader font settings
|
|
uint8_t fontFamily = NOTOSERIF;
|
|
uint8_t fontSize = MEDIUM;
|
|
uint8_t lineSpacing = NORMAL;
|
|
uint8_t paragraphAlignment = JUSTIFIED;
|
|
// Auto-sleep timeout setting (default 10 minutes)
|
|
uint8_t sleepTimeout = SLEEP_10_MIN;
|
|
// E-ink refresh frequency (default 15 pages)
|
|
uint8_t refreshFrequency = REFRESH_15;
|
|
uint8_t hyphenationEnabled = 0;
|
|
|
|
// Reader screen margin settings
|
|
uint8_t screenMargin = 5;
|
|
// OPDS browser settings
|
|
char opdsServerUrl[128] = "";
|
|
char opdsUsername[64] = "";
|
|
char opdsPassword[64] = "";
|
|
// Hide battery percentage
|
|
uint8_t hideBatteryPercentage = HIDE_NEVER;
|
|
// Long-press page turn button behavior
|
|
uint8_t longPressButtonBehavior = OFF;
|
|
// UI Theme
|
|
uint8_t uiTheme = LYRA;
|
|
// Sunlight fading compensation
|
|
uint8_t fadingFix = 0;
|
|
// Use book's embedded CSS styles for EPUB rendering (1 = enabled, 0 = disabled)
|
|
uint8_t embeddedStyle = 1;
|
|
// Focus Reading - emphasizes the first part of words with bold
|
|
uint8_t focusReadingEnabled = 0;
|
|
// SD card font family name (empty = use built-in fontFamily)
|
|
char sdFontFamilyName[32] = "";
|
|
// Show hidden files/directories (starting with '.') in the file browser (0 = hidden, 1 = show)
|
|
uint8_t showHiddenFiles = 0;
|
|
// Image rendering mode in EPUB reader
|
|
uint8_t imageRendering = IMAGES_DISPLAY;
|
|
// Tilt-based page turning (X3 only — requires QMI8658 IMU)
|
|
uint8_t tiltPageTurn = TILT_OFF;
|
|
// Language setting (Language enum index, default 0 = EN)
|
|
uint8_t language = 0;
|
|
|
|
~CrossPointSettings() = default;
|
|
|
|
// Get singleton instance
|
|
static CrossPointSettings& getInstance() { return instance; }
|
|
|
|
// Callback to resolve SD card font IDs. Set by SdCardFontSystem::begin().
|
|
// Returns font ID or 0 if not found.
|
|
using SdFontIdResolver = int (*)(void* ctx, const char* familyName, uint8_t fontSize);
|
|
SdFontIdResolver sdFontIdResolver = nullptr;
|
|
void* sdFontResolverCtx = nullptr;
|
|
|
|
uint16_t getPowerButtonDuration() const {
|
|
return (shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::SLEEP) ? 10 : 400;
|
|
}
|
|
int getReaderFontId() const;
|
|
|
|
// If count_only is true, returns the number of settings items that would be written.
|
|
uint8_t writeSettings(FsFile& file, bool count_only = false) const;
|
|
|
|
bool saveToFile() const;
|
|
bool loadFromFile();
|
|
|
|
static void validateFrontButtonMapping(CrossPointSettings& settings);
|
|
|
|
private:
|
|
bool loadFromBinaryFile();
|
|
bool migrateLanguageBinaryFile();
|
|
|
|
public:
|
|
float getReaderLineCompression() const;
|
|
unsigned long getSleepTimeoutMs() const;
|
|
int getRefreshFrequency() const;
|
|
};
|
|
|
|
// Helper macro to access settings
|
|
#define SETTINGS CrossPointSettings::getInstance()
|