mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-06 23:57:39 +03:00
## Major Features ### 1. CJK UI Font System - Implemented external font loading system for CJK characters - Added Source Han Sans (思源黑体) as base font for UI rendering - Support for multiple font sizes (20pt, 22pt, 24pt) - Font selection UI for both reader and UI fonts - Automatic fallback to built-in fonts when external fonts unavailable - External UI font now renders ALL characters (including ASCII) for consistent style - Proportional spacing for external fonts (variable width per character) ### 2. Complete I18N Implementation - Added comprehensive internationalization system - Support for English, Chinese Simplified, and Japanese - Translated all UI strings across the entire application - Language selection UI in settings with native language names - English displayed as "English" - Chinese displayed as "简体中文" - Japanese displayed as "日本語" - Dynamic language switching without restart ### 3. Bug Fixes #### Rendering Race Conditions - Fixed race condition where parent and child Activity rendering tasks run simultaneously - Added 500ms delay in child Activity displayTaskLoop() to wait for parent rendering completion - Unified displayTaskLoop() logic: `if (updateRequired && !subActivity)` - Prevents duplicate RED RAM writes and incomplete screen refreshes **Affected Activities:** - CategorySettingsActivity: Unified displayTaskLoop check logic - KOReaderSettingsActivity: Added 500ms delay before first render - CalibreSettingsActivity: Added 500ms delay before first render - FontSelectActivity: Added 500ms delay before first render - ClearCacheActivity: Added 500ms delay and subActivity check - LanguageSelectActivity: Added 500ms delay in displayTaskLoop (not onEnter) #### Button Response Issues - Fixed CrossPointWebServer exit button requiring long press - Added MappedInputManager::update() method - Call update() before wasPressed() in tight HTTP processing loop - Button presses during loop are now properly detected #### ClearCache Crash - Fixed FreeRTOS mutex deadlock when exiting ClearCache activity - Added isExiting flag to prevent operations during exit - Added clearCacheTaskHandle tracking - Wait for clearCache task completion before deleting mutex #### External UI Font Rendering - Fixed ASCII characters not using external UI font (was using built-in EPD font) - Fixed character spacing too wide (now uses proportional spacing via getGlyphMetrics) ## Technical Details **Files Added:** - lib/ExternalFont/: External font loading system - lib/I18n/: Internationalization system - lib/GfxRenderer/cjk_ui_font*.h: Pre-rendered CJK font data - scripts/generate_cjk_ui_font.py: Font generation script - src/activities/settings/FontSelectActivity.*: Font selection UI - src/activities/settings/LanguageSelectActivity.*: Language selection UI - docs/cjk-fonts.md: CJK font documentation - docs/i18n.md: I18N documentation **Files Modified:** - lib/GfxRenderer/: Added CJK font rendering support with proportional spacing - src/activities/: I18N integration across all activities - src/MappedInputManager.*: Added update() method - src/CrossPointSettings.cpp: Added language and font settings **Memory Usage:** - Flash: 94.7% (6204434 bytes / 6553600 bytes) - RAM: 66.4% (217556 bytes / 327680 bytes) ## Testing Notes All rendering race conditions and button response issues have been fixed and tested. ClearCache no longer crashes when exiting. File transfer page now responds to short press on exit button. External UI font now renders all characters with proper proportional spacing. Language selection page displays language names in their native scripts. Co-authored-by: Claude (Anthropic AI Assistant)
171 lines
7.3 KiB
C++
171 lines
7.3 KiB
C++
#pragma once
|
|
|
|
#include <EInkDisplay.h>
|
|
#include <EpdFontFamily.h>
|
|
|
|
#include <map>
|
|
|
|
#include "Bitmap.h"
|
|
|
|
// Forward declaration for external font support
|
|
class ExternalFont;
|
|
|
|
class GfxRenderer {
|
|
public:
|
|
enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
|
|
|
// Logical screen orientation from the perspective of callers
|
|
enum Orientation {
|
|
Portrait, // 480x800 logical coordinates (current default)
|
|
LandscapeClockwise, // 800x480 logical coordinates, rotated 180° (swap
|
|
// top/bottom)
|
|
PortraitInverted, // 480x800 logical coordinates, inverted
|
|
LandscapeCounterClockwise // 800x480 logical coordinates, native panel
|
|
// orientation
|
|
};
|
|
|
|
private:
|
|
static constexpr size_t BW_BUFFER_CHUNK_SIZE =
|
|
8000; // 8KB chunks to allow for non-contiguous memory
|
|
static constexpr size_t BW_BUFFER_NUM_CHUNKS =
|
|
EInkDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
|
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS ==
|
|
EInkDisplay::BUFFER_SIZE,
|
|
"BW buffer chunking does not line up with display buffer size");
|
|
|
|
EInkDisplay &einkDisplay;
|
|
RenderMode renderMode;
|
|
Orientation orientation;
|
|
uint8_t *bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
|
std::map<int, EpdFontFamily> fontMap;
|
|
// UI font size: 0=20px(SMALL), 1=22px(MEDIUM), 2=24px(LARGE)
|
|
uint8_t uiFontSize = 0;
|
|
// Dark mode: true = black background, false = white background
|
|
bool darkMode = false;
|
|
// Extra spacing (in pixels) for ASCII letters/digits when using external reader font.
|
|
int8_t asciiLetterSpacing = 0;
|
|
int8_t asciiDigitSpacing = 0;
|
|
// Extra spacing (in pixels) for CJK characters when using external reader font.
|
|
int8_t cjkSpacing = 0;
|
|
// Skip dark mode inversion for images (cover art should not be inverted)
|
|
mutable bool skipDarkModeForImages = false;
|
|
void renderChar(int fontId, const EpdFontFamily &fontFamily, uint32_t cp,
|
|
int *x, const int *y, bool pixelState,
|
|
EpdFontFamily::Style style) const;
|
|
void renderExternalGlyph(const uint8_t *bitmap, ExternalFont *font, int *x,
|
|
int y, bool pixelState,
|
|
int advanceOverride = -1) const;
|
|
// Render CJK character using built-in UI font (from PROGMEM)
|
|
void renderBuiltinCjkGlyph(uint32_t cp, int *x, int y, bool pixelState) const;
|
|
// Check if fontId is a reader font (should use external Chinese font)
|
|
static bool isReaderFont(int fontId);
|
|
void freeBwBufferChunks();
|
|
void rotateCoordinates(int x, int y, int *rotatedX, int *rotatedY) const;
|
|
|
|
public:
|
|
explicit GfxRenderer(EInkDisplay &einkDisplay)
|
|
: einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {}
|
|
~GfxRenderer() { freeBwBufferChunks(); }
|
|
|
|
static constexpr int VIEWABLE_MARGIN_TOP = 9;
|
|
static constexpr int VIEWABLE_MARGIN_RIGHT = 3;
|
|
static constexpr int VIEWABLE_MARGIN_BOTTOM = 3;
|
|
static constexpr int VIEWABLE_MARGIN_LEFT = 3;
|
|
static constexpr int BUTTON_HINT_WIDTH = 106;
|
|
static constexpr int BUTTON_HINT_HEIGHT = 40;
|
|
static constexpr int BUTTON_HINT_BOTTOM_INSET = 40;
|
|
static constexpr int BUTTON_HINT_TEXT_OFFSET = 7;
|
|
|
|
// Setup
|
|
void insertFont(int fontId, EpdFontFamily font);
|
|
|
|
// Orientation control (affects logical width/height and coordinate
|
|
// transforms)
|
|
void setOrientation(const Orientation o) { orientation = o; }
|
|
Orientation getOrientation() const { return orientation; }
|
|
|
|
// UI font size control (0=20px, 1=22px, 2=24px)
|
|
void setUiFontSize(uint8_t size) { uiFontSize = (size > 2) ? 2 : size; }
|
|
uint8_t getUiFontSize() const { return uiFontSize; }
|
|
|
|
// Dark mode control
|
|
void setDarkMode(bool darkMode) { this->darkMode = darkMode; }
|
|
bool isDarkMode() const { return darkMode; }
|
|
void setAsciiLetterSpacing(int8_t spacing) { asciiLetterSpacing = spacing; }
|
|
void setAsciiDigitSpacing(int8_t spacing) { asciiDigitSpacing = spacing; }
|
|
void setCjkSpacing(int8_t spacing) { cjkSpacing = spacing; }
|
|
int8_t getAsciiLetterSpacing() const { return asciiLetterSpacing; }
|
|
int8_t getAsciiDigitSpacing() const { return asciiDigitSpacing; }
|
|
int8_t getCjkSpacing() const { return cjkSpacing; }
|
|
|
|
// Screen ops
|
|
int getScreenWidth() const;
|
|
int getScreenHeight() const;
|
|
void displayBuffer(
|
|
EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
|
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
|
void displayWindow(int x, int y, int width, int height) const;
|
|
void invertScreen() const;
|
|
void clearScreen(uint8_t color = 0xFF) const;
|
|
|
|
// Drawing
|
|
void drawPixel(int x, int y, bool state = true) const;
|
|
void drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
|
|
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
|
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
|
void drawImage(const uint8_t bitmap[], int x, int y, int width,
|
|
int height) const;
|
|
void drawBitmap(const Bitmap &bitmap, int x, int y, int maxWidth,
|
|
int maxHeight, float cropX = 0, float cropY = 0) const;
|
|
void drawBitmap1Bit(const Bitmap &bitmap, int x, int y, int maxWidth,
|
|
int maxHeight) const;
|
|
void fillPolygon(const int *xPoints, const int *yPoints, int numPoints,
|
|
bool state = true) const;
|
|
|
|
// Text
|
|
int getTextWidth(int fontId, const char *text,
|
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
|
void
|
|
drawCenteredText(int fontId, int y, const char *text, bool black = true,
|
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
|
void drawText(int fontId, int x, int y, const char *text, bool black = true,
|
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
|
int getSpaceWidth(int fontId) const;
|
|
int getFontAscenderSize(int fontId) const;
|
|
int getLineHeight(int fontId) const;
|
|
std::string
|
|
truncatedText(int fontId, const char *text, int maxWidth,
|
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
|
|
|
// UI Components
|
|
void drawButtonHints(int fontId, const char *btn1, const char *btn2,
|
|
const char *btn3, const char *btn4);
|
|
void drawSideButtonHints(int fontId, const char *topBtn,
|
|
const char *bottomBtn) const;
|
|
|
|
private:
|
|
// Helper for drawing rotated text (90 degrees clockwise, for side buttons)
|
|
void drawTextRotated90CW(
|
|
int fontId, int x, int y, const char *text, bool black = true,
|
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
|
int getTextHeight(int fontId) const;
|
|
|
|
public:
|
|
// Grayscale functions
|
|
void setRenderMode(const RenderMode mode) { this->renderMode = mode; }
|
|
void copyGrayscaleLsbBuffers() const;
|
|
void copyGrayscaleMsbBuffers() const;
|
|
void displayGrayBuffer(bool turnOffScreen = false,
|
|
bool darkMode = false) const;
|
|
bool storeBwBuffer(); // Returns true if buffer was stored successfully
|
|
void restoreBwBuffer(); // Restore and free the stored buffer
|
|
void cleanupGrayscaleWithFrameBuffer() const;
|
|
|
|
// Low level functions
|
|
uint8_t *getFrameBuffer() const;
|
|
static size_t getBufferSize();
|
|
void grayscaleRevert() const;
|
|
void getOrientedViewableTRBL(int *outTop, int *outRight, int *outBottom,
|
|
int *outLeft) const;
|
|
};
|