mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-07 08:07:40 +03:00
feat: Add CJK UI font support and complete I18N implementation
## 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)
This commit is contained in:
parent
3ce11f14ce
commit
895fe470c6
6
.gitignore
vendored
6
.gitignore
vendored
@ -6,3 +6,9 @@ lib/EpdFont/fontsrc
|
|||||||
*.generated.h
|
*.generated.h
|
||||||
build
|
build
|
||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
|
|
||||||
|
# AI
|
||||||
|
.claude/
|
||||||
|
.serena/
|
||||||
|
.spec-workflow/
|
||||||
|
docs/plans
|
||||||
|
|||||||
258
docs/cjk-fonts.md
Normal file
258
docs/cjk-fonts.md
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
# CJK Font Support
|
||||||
|
|
||||||
|
This guide explains how to use Chinese, Japanese, and Korean (CJK) fonts on your CrossPoint Reader.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
CrossPoint Reader supports external CJK fonts for both:
|
||||||
|
|
||||||
|
- **UI Font** - Used for menus, settings, and system interface
|
||||||
|
- **Reader Font** - Used for reading ebook content
|
||||||
|
|
||||||
|
The device includes a built-in CJK UI font (Source Han Sans subset) for basic interface rendering, and supports loading custom external fonts from the SD card.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- CrossPoint Reader device with firmware version supporting CJK fonts
|
||||||
|
- SD card with available space for font files
|
||||||
|
- TrueType font files (.ttf) converted to the CrossPoint font format
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Font File Format
|
||||||
|
|
||||||
|
CrossPoint Reader uses a custom binary font format optimized for e-ink displays. Font files must be placed in the `/fonts/` directory on the SD card.
|
||||||
|
|
||||||
|
### File Naming Convention
|
||||||
|
|
||||||
|
Font files must follow this naming pattern:
|
||||||
|
|
||||||
|
```
|
||||||
|
{FontName}_{Size}_{Width}x{Height}.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `SourceHanSansCN-Medium_20_20x20.bin`
|
||||||
|
- `KingHwaOldSong_38_33x39.bin`
|
||||||
|
- `Yozai-Medium_36_31x31.bin`
|
||||||
|
|
||||||
|
### Font File Structure
|
||||||
|
|
||||||
|
The binary font file contains:
|
||||||
|
|
||||||
|
1. **Header** (variable length)
|
||||||
|
- Font name (null-terminated string)
|
||||||
|
- Font size (uint8_t)
|
||||||
|
- Character width (uint8_t)
|
||||||
|
- Character height (uint8_t)
|
||||||
|
- Bytes per character (uint16_t)
|
||||||
|
|
||||||
|
2. **Character Data**
|
||||||
|
- Sequential bitmap data for each character
|
||||||
|
- Characters are stored in Unicode order
|
||||||
|
- Each character uses `width * height / 8` bytes (1-bit per pixel)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Generating Font Files
|
||||||
|
|
||||||
|
Use the provided Python script to convert TrueType fonts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/generate_cjk_ui_font.py \
|
||||||
|
--font /path/to/font.ttf \
|
||||||
|
--size 20 \
|
||||||
|
--output /path/to/output.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Script Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--font` | Path to TrueType font file (.ttf) |
|
||||||
|
| `--size` | Font size in points |
|
||||||
|
| `--output` | Output binary file path |
|
||||||
|
| `--chars` | (Optional) Custom character set file |
|
||||||
|
|
||||||
|
### Character Set
|
||||||
|
|
||||||
|
By default, the script generates fonts for:
|
||||||
|
- Basic ASCII characters (0x20-0x7E)
|
||||||
|
- Common CJK characters (GB2312 subset)
|
||||||
|
- Japanese Hiragana and Katakana
|
||||||
|
- Common punctuation marks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installing Fonts
|
||||||
|
|
||||||
|
1. **Create the fonts directory** (if it doesn't exist):
|
||||||
|
```
|
||||||
|
/fonts/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Copy font files** to the `/fonts/` directory on your SD card
|
||||||
|
|
||||||
|
3. **Restart the device** or go to Settings to scan for new fonts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Selecting Fonts
|
||||||
|
|
||||||
|
### UI Font
|
||||||
|
|
||||||
|
1. Go to **Settings** → **Display**
|
||||||
|
2. Select **External UI Font**
|
||||||
|
3. Choose from available fonts or select **Built-in (Disabled)** to use the default font
|
||||||
|
|
||||||
|
### Reader Font
|
||||||
|
|
||||||
|
1. Go to **Settings** → **Reader**
|
||||||
|
2. Select **External Reader Font**
|
||||||
|
3. Choose from available fonts or select **Built-in (Disabled)** to use the default font
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Built-in CJK UI Font
|
||||||
|
|
||||||
|
The firmware includes a pre-rendered subset of Source Han Sans (思源黑体) for UI rendering. This font covers:
|
||||||
|
|
||||||
|
- All ASCII characters
|
||||||
|
- Common Chinese characters used in the UI
|
||||||
|
- Japanese Hiragana and Katakana
|
||||||
|
- Common punctuation marks
|
||||||
|
|
||||||
|
The built-in font is automatically used when:
|
||||||
|
- No external UI font is selected
|
||||||
|
- The external font file is missing or corrupted
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## External UI Font Features
|
||||||
|
|
||||||
|
When an external UI font is selected:
|
||||||
|
|
||||||
|
### Full Character Coverage
|
||||||
|
- **All characters** (including ASCII letters, numbers, and punctuation) are rendered using the external font
|
||||||
|
- This ensures consistent visual style across the entire UI
|
||||||
|
- If a character is missing from the external font, the system falls back to built-in fonts
|
||||||
|
|
||||||
|
### Proportional Spacing
|
||||||
|
- External fonts use **proportional spacing** (variable width)
|
||||||
|
- Each character advances by its actual width, not a fixed width
|
||||||
|
- This makes English text look more natural with proper letter spacing
|
||||||
|
- CJK characters still use their full width as designed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended Fonts
|
||||||
|
|
||||||
|
### For UI (Small sizes, 18-24pt)
|
||||||
|
|
||||||
|
| Font | Description | License |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| Source Han Sans | Clean, modern sans-serif | OFL |
|
||||||
|
| Noto Sans CJK | Google's CJK font family | OFL |
|
||||||
|
| WenQuanYi Micro Hei | Compact Chinese font | GPL |
|
||||||
|
|
||||||
|
### For Reading (Larger sizes, 28-40pt)
|
||||||
|
|
||||||
|
| Font | Description | License |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| Source Han Serif | Traditional serif style | OFL |
|
||||||
|
| Noto Serif CJK | Google's serif CJK font | OFL |
|
||||||
|
| FangSong | Classic Chinese style | Varies |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Memory Considerations
|
||||||
|
|
||||||
|
External fonts consume RAM when loaded. Consider these guidelines:
|
||||||
|
|
||||||
|
| Font Size | Approx. Memory Usage |
|
||||||
|
|-----------|---------------------|
|
||||||
|
| 20pt | ~50KB per 1000 characters |
|
||||||
|
| 28pt | ~100KB per 1000 characters |
|
||||||
|
| 36pt | ~160KB per 1000 characters |
|
||||||
|
|
||||||
|
**Tips:**
|
||||||
|
- Use smaller font sizes for UI (18-24pt)
|
||||||
|
- Larger fonts (32pt+) are better for reader content
|
||||||
|
- Only one UI font and one reader font are loaded at a time
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Font not appearing in selection list
|
||||||
|
|
||||||
|
1. Check the file is in `/fonts/` directory
|
||||||
|
2. Verify the filename follows the naming convention
|
||||||
|
3. Ensure the file is not corrupted (try regenerating)
|
||||||
|
|
||||||
|
### Characters displaying as boxes or question marks
|
||||||
|
|
||||||
|
1. The character may not be included in the font
|
||||||
|
2. Try a font with broader character coverage
|
||||||
|
3. Check if the font file was generated correctly
|
||||||
|
|
||||||
|
### Device running slowly after selecting font
|
||||||
|
|
||||||
|
1. The font file may be too large
|
||||||
|
2. Try a smaller font size
|
||||||
|
3. Reduce the character set when generating the font
|
||||||
|
|
||||||
|
### Font looks blurry or pixelated
|
||||||
|
|
||||||
|
1. E-ink displays work best with specific font sizes
|
||||||
|
2. Try sizes that are multiples of the display's native resolution
|
||||||
|
3. Ensure anti-aliasing is disabled for 1-bit rendering
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Font Manager API
|
||||||
|
|
||||||
|
The `FontManager` class provides:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Scan for available fonts
|
||||||
|
FontMgr.scanFonts();
|
||||||
|
|
||||||
|
// Get font count
|
||||||
|
int count = FontMgr.getFontCount();
|
||||||
|
|
||||||
|
// Get font info
|
||||||
|
const FontInfo* info = FontMgr.getFontInfo(index);
|
||||||
|
|
||||||
|
// Select reader font (-1 to disable)
|
||||||
|
FontMgr.selectFont(index);
|
||||||
|
|
||||||
|
// Select UI font (-1 to disable)
|
||||||
|
FontMgr.selectUiFont(index);
|
||||||
|
|
||||||
|
// Check if external font is enabled
|
||||||
|
bool enabled = FontMgr.isExternalFontEnabled();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Font Info Structure
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct FontInfo {
|
||||||
|
char name[32]; // Font name
|
||||||
|
uint8_t size; // Font size in points
|
||||||
|
uint8_t width; // Character width in pixels
|
||||||
|
uint8_t height; // Character height in pixels
|
||||||
|
uint16_t bytesPerChar; // Bytes per character
|
||||||
|
char path[64]; // Full path to font file
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Internationalization (I18N)](./i18n.md) - Multi-language support
|
||||||
|
- [File Formats](./file-formats.md) - Binary file format specifications
|
||||||
|
- [Troubleshooting](./troubleshooting.md) - General troubleshooting guide
|
||||||
311
docs/i18n.md
Normal file
311
docs/i18n.md
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
# Internationalization (I18N)
|
||||||
|
|
||||||
|
This guide explains the multi-language support system in CrossPoint Reader.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
CrossPoint Reader supports multiple languages for the user interface:
|
||||||
|
|
||||||
|
- **English** (Default)
|
||||||
|
- **Chinese Simplified** (简体中文)
|
||||||
|
- **Japanese** (日本語)
|
||||||
|
|
||||||
|
All UI text, menus, settings, and system messages are translated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changing Language
|
||||||
|
|
||||||
|
1. Go to **Settings** → **System**
|
||||||
|
2. Select **Language**
|
||||||
|
3. Choose your preferred language from the list:
|
||||||
|
- **English** - Displayed as "English"
|
||||||
|
- **简体中文** - Displayed as "简体中文" (Chinese Simplified)
|
||||||
|
- **日本語** - Displayed as "日本語" (Japanese)
|
||||||
|
4. The interface will update immediately
|
||||||
|
|
||||||
|
**Note:** Language changes take effect immediately without requiring a restart.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Supported Languages
|
||||||
|
|
||||||
|
### English (Default)
|
||||||
|
|
||||||
|
The default language. All text is displayed in English.
|
||||||
|
|
||||||
|
### Chinese Simplified (简体中文)
|
||||||
|
|
||||||
|
Full Chinese translation including:
|
||||||
|
- All menu items and settings
|
||||||
|
- System messages and prompts
|
||||||
|
- Button labels and hints
|
||||||
|
- Error messages
|
||||||
|
|
||||||
|
**Requirements:** CJK UI font must be enabled for proper character display.
|
||||||
|
|
||||||
|
### Japanese (日本語)
|
||||||
|
|
||||||
|
Full Japanese translation including:
|
||||||
|
- All menu items and settings
|
||||||
|
- System messages and prompts
|
||||||
|
- Button labels and hints
|
||||||
|
- Error messages
|
||||||
|
|
||||||
|
**Requirements:** CJK UI font must be enabled for proper character display.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Language and Font Relationship
|
||||||
|
|
||||||
|
When using Chinese or Japanese:
|
||||||
|
|
||||||
|
1. **Enable CJK UI Font** - Go to Settings → Display → UI Font
|
||||||
|
2. **Select a font** that supports your language's characters
|
||||||
|
3. **Change language** - The UI will now display correctly
|
||||||
|
|
||||||
|
If CJK font is not enabled, Chinese/Japanese characters may display as boxes or question marks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
### Adding New Translations
|
||||||
|
|
||||||
|
#### 1. Add String ID
|
||||||
|
|
||||||
|
In `lib/I18n/I18n.h`, add a new entry to the `StrId` enum:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class StrId : uint16_t {
|
||||||
|
// ... existing entries ...
|
||||||
|
MY_NEW_STRING,
|
||||||
|
// ... more entries ...
|
||||||
|
_COUNT // Must be last
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Add Translations
|
||||||
|
|
||||||
|
In `lib/I18n/I18n.cpp`, add translations to each language array:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// English
|
||||||
|
static const char* const STRINGS_EN[] = {
|
||||||
|
// ... existing strings ...
|
||||||
|
"My New String",
|
||||||
|
// ... more strings ...
|
||||||
|
};
|
||||||
|
|
||||||
|
// Chinese Simplified
|
||||||
|
static const char* const STRINGS_ZH[] = {
|
||||||
|
// ... existing strings ...
|
||||||
|
"\xE6\x88\x91\xE7\x9A\x84\xE6\x96\xB0\xE5\xAD\x97\xE7\xAC\xA6\xE4\xB8\xB2", // 我的新字符串
|
||||||
|
// ... more strings ...
|
||||||
|
};
|
||||||
|
|
||||||
|
// Japanese
|
||||||
|
static const char* const STRINGS_JA[] = {
|
||||||
|
// ... existing strings ...
|
||||||
|
"\xE6\x96\xB0\xE3\x81\x97\xE3\x81\x84\xE6\x96\x87\xE5\xAD\x97\xE5\x88\x97", // 新しい文字列
|
||||||
|
// ... more strings ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:**
|
||||||
|
- Arrays must have the same number of entries
|
||||||
|
- Use UTF-8 hex encoding for non-ASCII characters
|
||||||
|
- Keep entries in the same order as the enum
|
||||||
|
|
||||||
|
#### 3. Use in Code
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
|
// Using the TR() macro (recommended)
|
||||||
|
renderer.drawText(font, x, y, TR(MY_NEW_STRING));
|
||||||
|
|
||||||
|
// Using I18N.get() directly
|
||||||
|
const char* text = I18N.get(StrId::MY_NEW_STRING);
|
||||||
|
```
|
||||||
|
|
||||||
|
### UTF-8 Hex Encoding
|
||||||
|
|
||||||
|
For non-ASCII characters, use UTF-8 hex encoding:
|
||||||
|
|
||||||
|
| Character | UTF-8 Hex |
|
||||||
|
|-----------|-----------|
|
||||||
|
| 中 | `\xE4\xB8\xAD` |
|
||||||
|
| 文 | `\xE6\x96\x87` |
|
||||||
|
| 日 | `\xE6\x97\xA5` |
|
||||||
|
| 本 | `\xE6\x9C\xAC` |
|
||||||
|
|
||||||
|
**Python helper:**
|
||||||
|
```python
|
||||||
|
def to_utf8_hex(text):
|
||||||
|
return ''.join(f'\\x{b:02X}' for b in text.encode('utf-8'))
|
||||||
|
|
||||||
|
print(to_utf8_hex("设置")) # \xE8\xAE\xBE\xE7\xBD\xAE
|
||||||
|
```
|
||||||
|
|
||||||
|
### I18N API Reference
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Get the singleton instance
|
||||||
|
I18n& i18n = I18n::getInstance();
|
||||||
|
|
||||||
|
// Or use the global macro
|
||||||
|
I18N.get(StrId::SETTINGS);
|
||||||
|
|
||||||
|
// Get translated string
|
||||||
|
const char* text = I18N.get(StrId::SETTINGS);
|
||||||
|
|
||||||
|
// Set language
|
||||||
|
I18N.setLanguage(Language::ZH);
|
||||||
|
|
||||||
|
// Get current language
|
||||||
|
Language lang = I18N.getLanguage();
|
||||||
|
|
||||||
|
// Save language setting to file
|
||||||
|
I18N.saveSettings();
|
||||||
|
|
||||||
|
// Load language setting from file
|
||||||
|
I18N.loadSettings();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Language Enum
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class Language : uint8_t {
|
||||||
|
EN = 0, // English
|
||||||
|
ZH = 1, // Chinese Simplified
|
||||||
|
JA = 2, // Japanese
|
||||||
|
_COUNT // Number of languages
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## String Categories
|
||||||
|
|
||||||
|
The I18N system organizes strings into categories:
|
||||||
|
|
||||||
|
### Navigation & Actions
|
||||||
|
- `BACK`, `CONFIRM`, `CANCEL`, `SELECT`, `EXIT`, `DONE`
|
||||||
|
|
||||||
|
### Settings Categories
|
||||||
|
- `CAT_DISPLAY`, `CAT_READER`, `CAT_CONTROLS`, `CAT_SYSTEM`
|
||||||
|
|
||||||
|
### Display Settings
|
||||||
|
- `SLEEP_SCREEN`, `STATUS_BAR`, `REFRESH_FREQ`, etc.
|
||||||
|
|
||||||
|
### Reader Settings
|
||||||
|
- `FONT_SIZE`, `LINE_SPACING`, `ORIENTATION`, etc.
|
||||||
|
|
||||||
|
### System Settings
|
||||||
|
- `LANGUAGE`, `KOREADER_SYNC`, `CALIBRE_SETTINGS`, etc.
|
||||||
|
|
||||||
|
### Status Messages
|
||||||
|
- `CONNECTING`, `CONNECTED`, `FAILED`, `SUCCESS`, etc.
|
||||||
|
|
||||||
|
### File Transfer
|
||||||
|
- `FILE_TRANSFER`, `HOTSPOT_MODE`, `NETWORK_PREFIX`, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Storage
|
||||||
|
|
||||||
|
Language settings are stored in:
|
||||||
|
```
|
||||||
|
/.crosspoint/i18n.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
This file contains:
|
||||||
|
- Current language selection (1 byte)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Adding a New Language
|
||||||
|
|
||||||
|
To add support for a new language:
|
||||||
|
|
||||||
|
1. **Add language to enum** in `lib/I18n/I18n.h`:
|
||||||
|
```cpp
|
||||||
|
enum class Language : uint8_t {
|
||||||
|
EN = 0,
|
||||||
|
ZH = 1,
|
||||||
|
JA = 2,
|
||||||
|
KO = 3, // New: Korean
|
||||||
|
_COUNT
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create translation array** in `lib/I18n/I18n.cpp`:
|
||||||
|
```cpp
|
||||||
|
static const char* const STRINGS_KO[] = {
|
||||||
|
// All strings translated to Korean
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Add to language arrays**:
|
||||||
|
```cpp
|
||||||
|
static const char* const* const LANGUAGE_STRINGS[] = {
|
||||||
|
STRINGS_EN,
|
||||||
|
STRINGS_ZH,
|
||||||
|
STRINGS_JA,
|
||||||
|
STRINGS_KO, // New
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Add language name** for the language selection UI:
|
||||||
|
```cpp
|
||||||
|
// In LanguageSelectActivity or I18n
|
||||||
|
const char* languageNames[] = {
|
||||||
|
"English",
|
||||||
|
"简体中文",
|
||||||
|
"日本語",
|
||||||
|
"한국어", // New
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Ensure CJK font support** for the new language's characters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Keep translations concise** - E-ink displays have limited space
|
||||||
|
2. **Test on device** - Some characters may render differently
|
||||||
|
3. **Use consistent terminology** - Keep translations consistent across the UI
|
||||||
|
4. **Consider context** - Same word may need different translations in different contexts
|
||||||
|
5. **Update all languages** - When adding new strings, add translations for all languages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Characters showing as boxes
|
||||||
|
|
||||||
|
1. Enable CJK UI font in Settings → Display
|
||||||
|
2. Ensure the font includes the required characters
|
||||||
|
3. Check that the font file is not corrupted
|
||||||
|
|
||||||
|
### Language not saving
|
||||||
|
|
||||||
|
1. Check SD card is not write-protected
|
||||||
|
2. Verify `/.crosspoint/` directory exists
|
||||||
|
3. Check available space on SD card
|
||||||
|
|
||||||
|
### Missing translations
|
||||||
|
|
||||||
|
1. Verify string ID exists in the enum
|
||||||
|
2. Check all language arrays have the same number of entries
|
||||||
|
3. Rebuild firmware after adding new strings
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [CJK Font Support](./cjk-fonts.md) - External font installation
|
||||||
|
- [File Formats](./file-formats.md) - Binary file format specifications
|
||||||
|
- [Troubleshooting](./troubleshooting.md) - General troubleshooting guide
|
||||||
345
lib/ExternalFont/ExternalFont.cpp
Normal file
345
lib/ExternalFont/ExternalFont.cpp
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
#include "ExternalFont.h"
|
||||||
|
|
||||||
|
#include <HardwareSerial.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
ExternalFont::~ExternalFont() { unload(); }
|
||||||
|
|
||||||
|
void ExternalFont::unload() {
|
||||||
|
if (_fontFile) {
|
||||||
|
_fontFile.close();
|
||||||
|
}
|
||||||
|
_isLoaded = false;
|
||||||
|
_fontName[0] = '\0';
|
||||||
|
_fontSize = 0;
|
||||||
|
_charWidth = 0;
|
||||||
|
_charHeight = 0;
|
||||||
|
_bytesPerRow = 0;
|
||||||
|
_bytesPerChar = 0;
|
||||||
|
_accessCounter = 0;
|
||||||
|
|
||||||
|
// Clear cache and hash table
|
||||||
|
for (int i = 0; i < CACHE_SIZE; i++) {
|
||||||
|
_cache[i].codepoint = 0xFFFFFFFF;
|
||||||
|
_cache[i].lastUsed = 0;
|
||||||
|
_cache[i].notFound = false;
|
||||||
|
_hashTable[i] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExternalFont::parseFilename(const char *filepath) {
|
||||||
|
// Extract filename from path
|
||||||
|
const char *filename = strrchr(filepath, '/');
|
||||||
|
if (filename) {
|
||||||
|
filename++; // Skip '/'
|
||||||
|
} else {
|
||||||
|
filename = filepath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse format: FontName_size_WxH.bin
|
||||||
|
// Example: KingHwaOldSong_38_33x39.bin
|
||||||
|
|
||||||
|
char nameCopy[64];
|
||||||
|
strncpy(nameCopy, filename, sizeof(nameCopy) - 1);
|
||||||
|
nameCopy[sizeof(nameCopy) - 1] = '\0';
|
||||||
|
|
||||||
|
// Remove .bin extension
|
||||||
|
char *ext = strstr(nameCopy, ".bin");
|
||||||
|
if (!ext) {
|
||||||
|
Serial.printf("[EXT_FONT] Invalid filename: no .bin extension\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*ext = '\0';
|
||||||
|
|
||||||
|
// Find _WxH part from the end
|
||||||
|
char *lastUnderscore = strrchr(nameCopy, '_');
|
||||||
|
if (!lastUnderscore) {
|
||||||
|
Serial.printf("[EXT_FONT] Invalid filename format\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse WxH
|
||||||
|
int w, h;
|
||||||
|
if (sscanf(lastUnderscore + 1, "%dx%d", &w, &h) != 2) {
|
||||||
|
Serial.printf("[EXT_FONT] Failed to parse dimensions\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_charWidth = (uint8_t)w;
|
||||||
|
_charHeight = (uint8_t)h;
|
||||||
|
*lastUnderscore = '\0';
|
||||||
|
|
||||||
|
// Find size
|
||||||
|
lastUnderscore = strrchr(nameCopy, '_');
|
||||||
|
if (!lastUnderscore) {
|
||||||
|
Serial.printf("[EXT_FONT] Invalid filename format: no size\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size;
|
||||||
|
if (sscanf(lastUnderscore + 1, "%d", &size) != 1) {
|
||||||
|
Serial.printf("[EXT_FONT] Failed to parse size\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_fontSize = (uint8_t)size;
|
||||||
|
*lastUnderscore = '\0';
|
||||||
|
|
||||||
|
// Remaining part is font name
|
||||||
|
strncpy(_fontName, nameCopy, sizeof(_fontName) - 1);
|
||||||
|
_fontName[sizeof(_fontName) - 1] = '\0';
|
||||||
|
|
||||||
|
// Calculate bytes per char
|
||||||
|
_bytesPerRow = (_charWidth + 7) / 8;
|
||||||
|
_bytesPerChar = _bytesPerRow * _charHeight;
|
||||||
|
|
||||||
|
if (_bytesPerChar > MAX_GLYPH_BYTES) {
|
||||||
|
Serial.printf("[EXT_FONT] Glyph too large: %d bytes (max %d)\n",
|
||||||
|
_bytesPerChar, MAX_GLYPH_BYTES);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[EXT_FONT] Parsed: name=%s, size=%d, %dx%d, %d bytes/char\n",
|
||||||
|
_fontName, _fontSize, _charWidth, _charHeight, _bytesPerChar);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExternalFont::load(const char *filepath) {
|
||||||
|
unload();
|
||||||
|
|
||||||
|
if (!parseFilename(filepath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SdMan.openFileForRead("EXT_FONT", filepath, _fontFile)) {
|
||||||
|
Serial.printf("[EXT_FONT] Failed to open: %s\n", filepath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isLoaded = true;
|
||||||
|
Serial.printf("[EXT_FONT] Loaded: %s\n", filepath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExternalFont::findInCache(uint32_t codepoint) {
|
||||||
|
// O(1) hash table lookup with linear probing for collisions
|
||||||
|
int hash = hashCodepoint(codepoint);
|
||||||
|
for (int i = 0; i < CACHE_SIZE; i++) {
|
||||||
|
int idx = (hash + i) % CACHE_SIZE;
|
||||||
|
int16_t cacheIdx = _hashTable[idx];
|
||||||
|
if (cacheIdx == -1) {
|
||||||
|
// Empty slot, not found
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (_cache[cacheIdx].codepoint == codepoint) {
|
||||||
|
return cacheIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExternalFont::getLruSlot() {
|
||||||
|
int lruIndex = 0;
|
||||||
|
uint32_t minUsed = _cache[0].lastUsed;
|
||||||
|
|
||||||
|
for (int i = 1; i < CACHE_SIZE; i++) {
|
||||||
|
// Prefer unused slots
|
||||||
|
if (_cache[i].codepoint == 0xFFFFFFFF) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
if (_cache[i].lastUsed < minUsed) {
|
||||||
|
minUsed = _cache[i].lastUsed;
|
||||||
|
lruIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lruIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExternalFont::readGlyphFromSD(uint32_t codepoint, uint8_t *buffer) {
|
||||||
|
if (!_fontFile) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate offset
|
||||||
|
uint32_t offset = codepoint * _bytesPerChar;
|
||||||
|
|
||||||
|
// Seek and read
|
||||||
|
if (!_fontFile.seek(offset)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytesRead = _fontFile.read(buffer, _bytesPerChar);
|
||||||
|
if (bytesRead != _bytesPerChar) {
|
||||||
|
// May be end of file or other error, fill with zeros
|
||||||
|
memset(buffer, 0, _bytesPerChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *ExternalFont::getGlyph(uint32_t codepoint) {
|
||||||
|
if (!_isLoaded) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check cache (O(1) with hash table)
|
||||||
|
int cacheIndex = findInCache(codepoint);
|
||||||
|
if (cacheIndex >= 0) {
|
||||||
|
_cache[cacheIndex].lastUsed = ++_accessCounter;
|
||||||
|
// Return nullptr if this codepoint was previously marked as not found
|
||||||
|
if (_cache[cacheIndex].notFound) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return _cache[cacheIndex].bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache miss, need to read from SD card
|
||||||
|
int slot = getLruSlot();
|
||||||
|
|
||||||
|
// If replacing an existing entry, remove it from hash table
|
||||||
|
if (_cache[slot].codepoint != 0xFFFFFFFF) {
|
||||||
|
int oldHash = hashCodepoint(_cache[slot].codepoint);
|
||||||
|
for (int i = 0; i < CACHE_SIZE; i++) {
|
||||||
|
int idx = (oldHash + i) % CACHE_SIZE;
|
||||||
|
if (_hashTable[idx] == slot) {
|
||||||
|
_hashTable[idx] = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read glyph from SD card
|
||||||
|
bool readSuccess = readGlyphFromSD(codepoint, _cache[slot].bitmap);
|
||||||
|
|
||||||
|
// Calculate metrics and check if glyph is empty
|
||||||
|
uint8_t minX = _charWidth;
|
||||||
|
uint8_t maxX = 0;
|
||||||
|
bool isEmpty = true;
|
||||||
|
|
||||||
|
if (readSuccess && _bytesPerChar > 0) {
|
||||||
|
for (int y = 0; y < _charHeight; y++) {
|
||||||
|
for (int x = 0; x < _charWidth; x++) {
|
||||||
|
int byteIndex = y * _bytesPerRow + (x / 8);
|
||||||
|
int bitIndex = 7 - (x % 8);
|
||||||
|
if ((_cache[slot].bitmap[byteIndex] >> bitIndex) & 1) {
|
||||||
|
isEmpty = false;
|
||||||
|
if (x < minX)
|
||||||
|
minX = x;
|
||||||
|
if (x > maxX)
|
||||||
|
maxX = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cache entry
|
||||||
|
_cache[slot].codepoint = codepoint;
|
||||||
|
_cache[slot].lastUsed = ++_accessCounter;
|
||||||
|
|
||||||
|
// Check if this is a whitespace character (U+2000-U+200F: various spaces)
|
||||||
|
bool isWhitespace = (codepoint >= 0x2000 && codepoint <= 0x200F);
|
||||||
|
|
||||||
|
// Mark as notFound only if read failed or (empty AND not whitespace AND non-ASCII)
|
||||||
|
// Whitespace characters are expected to be empty but should still be rendered
|
||||||
|
_cache[slot].notFound = !readSuccess || (isEmpty && !isWhitespace && codepoint > 0x7F);
|
||||||
|
|
||||||
|
// Store metrics
|
||||||
|
if (!isEmpty) {
|
||||||
|
_cache[slot].minX = minX;
|
||||||
|
// Variable width: content width + 2px padding
|
||||||
|
_cache[slot].advanceX = (maxX - minX + 1) + 2;
|
||||||
|
} else {
|
||||||
|
_cache[slot].minX = 0;
|
||||||
|
// Special handling for whitespace characters
|
||||||
|
if (isWhitespace) {
|
||||||
|
// em-space (U+2003) and similar should be full-width (same as CJK char)
|
||||||
|
// en-space (U+2002) should be half-width
|
||||||
|
// Other spaces use appropriate widths
|
||||||
|
if (codepoint == 0x2003) {
|
||||||
|
// em-space: full CJK character width
|
||||||
|
_cache[slot].advanceX = _charWidth;
|
||||||
|
} else if (codepoint == 0x2002) {
|
||||||
|
// en-space: half CJK character width
|
||||||
|
_cache[slot].advanceX = _charWidth / 2;
|
||||||
|
} else if (codepoint == 0x3000) {
|
||||||
|
// Ideographic space (CJK full-width space): full width
|
||||||
|
_cache[slot].advanceX = _charWidth;
|
||||||
|
} else {
|
||||||
|
// Other spaces: use standard space width
|
||||||
|
_cache[slot].advanceX = _charWidth / 3;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback for other empty glyphs
|
||||||
|
_cache[slot].advanceX = _charWidth / 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to hash table
|
||||||
|
int hash = hashCodepoint(codepoint);
|
||||||
|
for (int i = 0; i < CACHE_SIZE; i++) {
|
||||||
|
int idx = (hash + i) % CACHE_SIZE;
|
||||||
|
if (_hashTable[idx] == -1) {
|
||||||
|
_hashTable[idx] = slot;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cache[slot].notFound) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _cache[slot].bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExternalFont::getGlyphMetrics(uint32_t codepoint, uint8_t *outMinX,
|
||||||
|
uint8_t *outAdvanceX) {
|
||||||
|
int idx = findInCache(codepoint);
|
||||||
|
if (idx >= 0 && !_cache[idx].notFound) {
|
||||||
|
if (outMinX)
|
||||||
|
*outMinX = _cache[idx].minX;
|
||||||
|
if (outAdvanceX)
|
||||||
|
*outAdvanceX = _cache[idx].advanceX;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExternalFont::preloadGlyphs(const uint32_t *codepoints, size_t count) {
|
||||||
|
if (!_isLoaded || !codepoints || count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to cache size to avoid thrashing
|
||||||
|
const size_t maxLoad = std::min(count, static_cast<size_t>(CACHE_SIZE));
|
||||||
|
|
||||||
|
// Create a sorted copy for sequential SD card access
|
||||||
|
// Sequential reads are much faster than random seeks
|
||||||
|
std::vector<uint32_t> sorted(codepoints, codepoints + maxLoad);
|
||||||
|
std::sort(sorted.begin(), sorted.end());
|
||||||
|
|
||||||
|
// Remove duplicates
|
||||||
|
sorted.erase(std::unique(sorted.begin(), sorted.end()), sorted.end());
|
||||||
|
|
||||||
|
Serial.printf("[EXT_FONT] Preloading %zu unique glyphs\n", sorted.size());
|
||||||
|
const unsigned long startTime = millis();
|
||||||
|
|
||||||
|
size_t loaded = 0;
|
||||||
|
size_t skipped = 0;
|
||||||
|
|
||||||
|
for (uint32_t cp : sorted) {
|
||||||
|
// Skip if already in cache
|
||||||
|
if (findInCache(cp) >= 0) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load into cache (getGlyph handles all the cache management)
|
||||||
|
getGlyph(cp);
|
||||||
|
loaded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf(
|
||||||
|
"[EXT_FONT] Preload done: %zu loaded, %zu already cached, took %lums\n",
|
||||||
|
loaded, skipped, millis() - startTime);
|
||||||
|
}
|
||||||
128
lib/ExternalFont/ExternalFont.h
Normal file
128
lib/ExternalFont/ExternalFont.h
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDCardManager.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External font loader - supports Xteink .bin format
|
||||||
|
* Filename format: FontName_size_WxH.bin (e.g. KingHwaOldSong_38_33x39.bin)
|
||||||
|
*
|
||||||
|
* Font format:
|
||||||
|
* - Direct Unicode codepoint indexing
|
||||||
|
* - Offset = codepoint * bytesPerChar
|
||||||
|
* - Each char = bytesPerRow * charHeight bytes
|
||||||
|
* - 1-bit black/white bitmap, MSB first
|
||||||
|
*/
|
||||||
|
class ExternalFont {
|
||||||
|
public:
|
||||||
|
ExternalFont() = default;
|
||||||
|
~ExternalFont();
|
||||||
|
|
||||||
|
// Disable copy
|
||||||
|
ExternalFont(const ExternalFont &) = delete;
|
||||||
|
ExternalFont &operator=(const ExternalFont &) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load font from .bin file
|
||||||
|
* @param filepath Full path on SD card (e.g.
|
||||||
|
* "/fonts/KingHwaOldSong_38_33x39.bin")
|
||||||
|
* @return true on success
|
||||||
|
*/
|
||||||
|
bool load(const char *filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get glyph bitmap data (with LRU cache)
|
||||||
|
* @param codepoint Unicode codepoint
|
||||||
|
* @return Bitmap data pointer, nullptr if char not found
|
||||||
|
*/
|
||||||
|
const uint8_t *getGlyph(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preload multiple glyphs at once (optimized for batch SD reads)
|
||||||
|
* Call this before rendering a chapter to warm up the cache
|
||||||
|
* @param codepoints Array of unicode codepoints to preload
|
||||||
|
* @param count Number of codepoints in the array
|
||||||
|
*/
|
||||||
|
void preloadGlyphs(const uint32_t *codepoints, size_t count);
|
||||||
|
|
||||||
|
// Font properties
|
||||||
|
uint8_t getCharWidth() const { return _charWidth; }
|
||||||
|
uint8_t getCharHeight() const { return _charHeight; }
|
||||||
|
uint8_t getBytesPerRow() const { return _bytesPerRow; }
|
||||||
|
uint16_t getBytesPerChar() const { return _bytesPerChar; }
|
||||||
|
const char *getFontName() const { return _fontName; }
|
||||||
|
uint8_t getFontSize() const { return _fontSize; }
|
||||||
|
|
||||||
|
bool isLoaded() const { return _isLoaded; }
|
||||||
|
void unload();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached metrics for a glyph.
|
||||||
|
* Must call getGlyph() first to ensure it's loaded!
|
||||||
|
* @param cp Unicode codepoint
|
||||||
|
* @param outMinX Minimum X offset (left bearing)
|
||||||
|
* @param outAdvanceX Advance width for cursor positioning
|
||||||
|
* @return true if metrics found in cache, false otherwise
|
||||||
|
*/
|
||||||
|
bool getGlyphMetrics(uint32_t cp, uint8_t *outMinX, uint8_t *outAdvanceX);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Font file handle (keep open to avoid repeated open/close)
|
||||||
|
FsFile _fontFile;
|
||||||
|
bool _isLoaded = false;
|
||||||
|
|
||||||
|
// Properties parsed from filename
|
||||||
|
char _fontName[32] = {0};
|
||||||
|
uint8_t _fontSize = 0;
|
||||||
|
uint8_t _charWidth = 0;
|
||||||
|
uint8_t _charHeight = 0;
|
||||||
|
uint8_t _bytesPerRow = 0;
|
||||||
|
uint16_t _bytesPerChar = 0;
|
||||||
|
|
||||||
|
// LRU cache - 256 glyphs for better Chinese text performance
|
||||||
|
// Memory: ~52KB (256 * 204 bytes per entry)
|
||||||
|
static constexpr int CACHE_SIZE = 256; // 256 glyphs
|
||||||
|
static constexpr int MAX_GLYPH_BYTES =
|
||||||
|
200; // Max 200 bytes per glyph (enough for 33x39)
|
||||||
|
|
||||||
|
// Flag to mark cached "non-existent" glyphs (avoid repeated SD reads)
|
||||||
|
static constexpr uint8_t GLYPH_NOT_FOUND_MARKER = 0xFE;
|
||||||
|
|
||||||
|
struct CacheEntry {
|
||||||
|
uint32_t codepoint = 0xFFFFFFFF; // Invalid marker
|
||||||
|
uint8_t bitmap[MAX_GLYPH_BYTES];
|
||||||
|
uint32_t lastUsed = 0;
|
||||||
|
bool notFound = false; // True if glyph doesn't exist in font
|
||||||
|
uint8_t minX = 0; // Cached rendering metrics
|
||||||
|
uint8_t advanceX = 0; // Cached advance width
|
||||||
|
};
|
||||||
|
CacheEntry _cache[CACHE_SIZE];
|
||||||
|
uint32_t _accessCounter = 0;
|
||||||
|
|
||||||
|
// Simple hash table for O(1) cache lookup (codepoint -> cache index, -1 if
|
||||||
|
// not cached)
|
||||||
|
int16_t _hashTable[CACHE_SIZE];
|
||||||
|
static int hashCodepoint(uint32_t cp) { return cp % CACHE_SIZE; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read glyph data from SD card
|
||||||
|
*/
|
||||||
|
bool readGlyphFromSD(uint32_t codepoint, uint8_t *buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse filename to get font parameters
|
||||||
|
* Format: FontName_size_WxH.bin
|
||||||
|
*/
|
||||||
|
bool parseFilename(const char *filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find glyph in cache
|
||||||
|
* @return Cache index, -1 if not found
|
||||||
|
*/
|
||||||
|
int findInCache(uint32_t codepoint);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get LRU cache slot (least recently used)
|
||||||
|
*/
|
||||||
|
int getLruSlot();
|
||||||
|
};
|
||||||
284
lib/ExternalFont/FontManager.cpp
Normal file
284
lib/ExternalFont/FontManager.cpp
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
#include "FontManager.h"
|
||||||
|
|
||||||
|
#include <HardwareSerial.h>
|
||||||
|
#include <SDCardManager.h>
|
||||||
|
#include <Serialization.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// Out-of-class definitions for static constexpr members (required for ODR-use
|
||||||
|
// in C++14)
|
||||||
|
constexpr int FontManager::MAX_FONTS;
|
||||||
|
constexpr const char *FontManager::FONTS_DIR;
|
||||||
|
constexpr const char *FontManager::SETTINGS_FILE;
|
||||||
|
constexpr uint8_t FontManager::SETTINGS_VERSION;
|
||||||
|
|
||||||
|
FontManager &FontManager::getInstance() {
|
||||||
|
static FontManager instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontManager::scanFonts() {
|
||||||
|
_fontCount = 0;
|
||||||
|
|
||||||
|
FsFile dir = SdMan.open(FONTS_DIR, O_RDONLY);
|
||||||
|
if (!dir) {
|
||||||
|
Serial.printf("[FONT_MGR] Cannot open fonts directory: %s\n", FONTS_DIR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dir.isDir()) {
|
||||||
|
Serial.printf("[FONT_MGR] %s is not a directory\n", FONTS_DIR);
|
||||||
|
dir.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FsFile entry;
|
||||||
|
while (_fontCount < MAX_FONTS && entry.openNext(&dir, O_RDONLY)) {
|
||||||
|
if (entry.isDir()) {
|
||||||
|
entry.close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char filename[64];
|
||||||
|
entry.getName(filename, sizeof(filename));
|
||||||
|
entry.close();
|
||||||
|
|
||||||
|
// Check .bin extension
|
||||||
|
if (!strstr(filename, ".bin")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse filename
|
||||||
|
FontInfo &info = _fonts[_fontCount];
|
||||||
|
strncpy(info.filename, filename, sizeof(info.filename) - 1);
|
||||||
|
info.filename[sizeof(info.filename) - 1] = '\0';
|
||||||
|
|
||||||
|
// Parse filename to get font info
|
||||||
|
char nameCopy[64];
|
||||||
|
strncpy(nameCopy, filename, sizeof(nameCopy) - 1);
|
||||||
|
nameCopy[sizeof(nameCopy) - 1] = '\0';
|
||||||
|
|
||||||
|
// Remove .bin
|
||||||
|
char *ext = strstr(nameCopy, ".bin");
|
||||||
|
if (ext)
|
||||||
|
*ext = '\0';
|
||||||
|
|
||||||
|
// Parse _WxH
|
||||||
|
char *lastUnderscore = strrchr(nameCopy, '_');
|
||||||
|
if (!lastUnderscore)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int w, h;
|
||||||
|
if (sscanf(lastUnderscore + 1, "%dx%d", &w, &h) != 2)
|
||||||
|
continue;
|
||||||
|
info.width = (uint8_t)w;
|
||||||
|
info.height = (uint8_t)h;
|
||||||
|
*lastUnderscore = '\0';
|
||||||
|
|
||||||
|
// Parse _size
|
||||||
|
lastUnderscore = strrchr(nameCopy, '_');
|
||||||
|
if (!lastUnderscore)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int size;
|
||||||
|
if (sscanf(lastUnderscore + 1, "%d", &size) != 1)
|
||||||
|
continue;
|
||||||
|
info.size = (uint8_t)size;
|
||||||
|
*lastUnderscore = '\0';
|
||||||
|
|
||||||
|
// Font name
|
||||||
|
strncpy(info.name, nameCopy, sizeof(info.name) - 1);
|
||||||
|
info.name[sizeof(info.name) - 1] = '\0';
|
||||||
|
|
||||||
|
Serial.printf("[FONT_MGR] Found font: %s (%dpt, %dx%d)\n", info.name,
|
||||||
|
info.size, info.width, info.height);
|
||||||
|
|
||||||
|
_fontCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.close();
|
||||||
|
Serial.printf("[FONT_MGR] Scan complete: %d fonts found\n", _fontCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FontInfo *FontManager::getFontInfo(int index) const {
|
||||||
|
if (index < 0 || index >= _fontCount) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &_fonts[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FontManager::loadSelectedFont() {
|
||||||
|
_activeFont.unload();
|
||||||
|
|
||||||
|
if (_selectedIndex < 0 || _selectedIndex >= _fontCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char filepath[80];
|
||||||
|
snprintf(filepath, sizeof(filepath), "%s/%s", FONTS_DIR,
|
||||||
|
_fonts[_selectedIndex].filename);
|
||||||
|
|
||||||
|
return _activeFont.load(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FontManager::loadSelectedUiFont() {
|
||||||
|
_activeUiFont.unload();
|
||||||
|
|
||||||
|
if (_selectedUiIndex < 0 || _selectedUiIndex >= _fontCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char filepath[80];
|
||||||
|
snprintf(filepath, sizeof(filepath), "%s/%s", FONTS_DIR,
|
||||||
|
_fonts[_selectedUiIndex].filename);
|
||||||
|
|
||||||
|
return _activeUiFont.load(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontManager::selectFont(int index) {
|
||||||
|
if (index == _selectedIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedIndex = index;
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
loadSelectedFont();
|
||||||
|
} else {
|
||||||
|
_activeFont.unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontManager::selectUiFont(int index) {
|
||||||
|
if (index == _selectedUiIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedUiIndex = index;
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
loadSelectedUiFont();
|
||||||
|
} else {
|
||||||
|
_activeUiFont.unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalFont *FontManager::getActiveFont() {
|
||||||
|
if (_selectedIndex >= 0 && _activeFont.isLoaded()) {
|
||||||
|
return &_activeFont;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalFont *FontManager::getActiveUiFont() {
|
||||||
|
if (_selectedUiIndex >= 0 && _activeUiFont.isLoaded()) {
|
||||||
|
return &_activeUiFont;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontManager::saveSettings() {
|
||||||
|
SdMan.mkdir("/.crosspoint");
|
||||||
|
|
||||||
|
FsFile file;
|
||||||
|
if (!SdMan.openFileForWrite("FONT_MGR", SETTINGS_FILE, file)) {
|
||||||
|
Serial.printf("[FONT_MGR] Failed to save settings\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
serialization::writePod(file, SETTINGS_VERSION);
|
||||||
|
serialization::writePod(file, _selectedIndex);
|
||||||
|
|
||||||
|
// Save selected reader font filename (for matching when restoring)
|
||||||
|
if (_selectedIndex >= 0 && _selectedIndex < _fontCount) {
|
||||||
|
serialization::writeString(file,
|
||||||
|
std::string(_fonts[_selectedIndex].filename));
|
||||||
|
} else {
|
||||||
|
serialization::writeString(file, std::string(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save UI font settings (version 2+)
|
||||||
|
serialization::writePod(file, _selectedUiIndex);
|
||||||
|
if (_selectedUiIndex >= 0 && _selectedUiIndex < _fontCount) {
|
||||||
|
serialization::writeString(file,
|
||||||
|
std::string(_fonts[_selectedUiIndex].filename));
|
||||||
|
} else {
|
||||||
|
serialization::writeString(file, std::string(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
Serial.printf("[FONT_MGR] Settings saved\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontManager::loadSettings() {
|
||||||
|
FsFile file;
|
||||||
|
if (!SdMan.openFileForRead("FONT_MGR", SETTINGS_FILE, file)) {
|
||||||
|
Serial.printf("[FONT_MGR] No settings file, using defaults\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t version;
|
||||||
|
serialization::readPod(file, version);
|
||||||
|
if (version < 1 || version > SETTINGS_VERSION) {
|
||||||
|
Serial.printf("[FONT_MGR] Settings version mismatch (%d vs %d)\n", version,
|
||||||
|
SETTINGS_VERSION);
|
||||||
|
file.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reader font settings
|
||||||
|
int savedIndex;
|
||||||
|
serialization::readPod(file, savedIndex);
|
||||||
|
|
||||||
|
std::string savedFilename;
|
||||||
|
serialization::readString(file, savedFilename);
|
||||||
|
|
||||||
|
// Find matching reader font by filename
|
||||||
|
if (savedIndex >= 0 && !savedFilename.empty()) {
|
||||||
|
for (int i = 0; i < _fontCount; i++) {
|
||||||
|
if (savedFilename == _fonts[i].filename) {
|
||||||
|
_selectedIndex = i;
|
||||||
|
loadSelectedFont();
|
||||||
|
Serial.printf("[FONT_MGR] Restored reader font: %s\n",
|
||||||
|
savedFilename.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_selectedIndex < 0) {
|
||||||
|
Serial.printf("[FONT_MGR] Saved reader font not found: %s\n",
|
||||||
|
savedFilename.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load UI font settings (version 2+)
|
||||||
|
if (version >= 2) {
|
||||||
|
int savedUiIndex;
|
||||||
|
serialization::readPod(file, savedUiIndex);
|
||||||
|
|
||||||
|
std::string savedUiFilename;
|
||||||
|
serialization::readString(file, savedUiFilename);
|
||||||
|
|
||||||
|
if (savedUiIndex >= 0 && !savedUiFilename.empty()) {
|
||||||
|
for (int i = 0; i < _fontCount; i++) {
|
||||||
|
if (savedUiFilename == _fonts[i].filename) {
|
||||||
|
_selectedUiIndex = i;
|
||||||
|
loadSelectedUiFont();
|
||||||
|
Serial.printf("[FONT_MGR] Restored UI font: %s\n",
|
||||||
|
savedUiFilename.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_selectedUiIndex < 0) {
|
||||||
|
Serial.printf("[FONT_MGR] Saved UI font not found: %s\n",
|
||||||
|
savedUiFilename.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
137
lib/ExternalFont/FontManager.h
Normal file
137
lib/ExternalFont/FontManager.h
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "ExternalFont.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Font information structure
|
||||||
|
*/
|
||||||
|
struct FontInfo {
|
||||||
|
char filename[64]; // Full filename
|
||||||
|
char name[32]; // Font name
|
||||||
|
uint8_t size; // Font size (pt)
|
||||||
|
uint8_t width; // Character width
|
||||||
|
uint8_t height; // Character height
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Font Manager - Singleton pattern
|
||||||
|
* Manages font scanning, selection, and settings persistence
|
||||||
|
* Supports two font slots: Reader font (for book content) and UI font (for
|
||||||
|
* menus/titles)
|
||||||
|
*/
|
||||||
|
class FontManager {
|
||||||
|
public:
|
||||||
|
static FontManager &getInstance();
|
||||||
|
|
||||||
|
// Disable copy
|
||||||
|
FontManager(const FontManager &) = delete;
|
||||||
|
FontManager &operator=(const FontManager &) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan /fonts/ directory to get available font list
|
||||||
|
*/
|
||||||
|
void scanFonts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get font count
|
||||||
|
*/
|
||||||
|
int getFontCount() const { return _fontCount; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get font info
|
||||||
|
* @param index Font index (0 to getFontCount()-1)
|
||||||
|
*/
|
||||||
|
const FontInfo *getFontInfo(int index) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select reader font (for book content)
|
||||||
|
* @param index Font index, -1 means disable external font (use built-in)
|
||||||
|
*/
|
||||||
|
void selectFont(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select UI font (for menus, titles, etc.)
|
||||||
|
* @param index Font index, -1 means disable (fallback to reader font or
|
||||||
|
* built-in)
|
||||||
|
*/
|
||||||
|
void selectUiFont(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get currently selected reader font index
|
||||||
|
* @return -1 means using built-in font
|
||||||
|
*/
|
||||||
|
int getSelectedIndex() const { return _selectedIndex; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get currently selected UI font index
|
||||||
|
* @return -1 means using reader font fallback
|
||||||
|
*/
|
||||||
|
int getUiSelectedIndex() const { return _selectedUiIndex; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get currently active reader font
|
||||||
|
* @return Font pointer, nullptr if not enabled
|
||||||
|
*/
|
||||||
|
ExternalFont *getActiveFont();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get currently active UI font
|
||||||
|
* @return Font pointer, nullptr if not enabled (will fallback to reader font)
|
||||||
|
*/
|
||||||
|
ExternalFont *getActiveUiFont();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if external reader font is enabled
|
||||||
|
*/
|
||||||
|
bool isExternalFontEnabled() const {
|
||||||
|
return _selectedIndex >= 0 && _activeFont.isLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if external UI font is enabled
|
||||||
|
*/
|
||||||
|
bool isUiFontEnabled() const {
|
||||||
|
return _selectedUiIndex >= 0 && _activeUiFont.isLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save settings to SD card
|
||||||
|
*/
|
||||||
|
void saveSettings();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load settings from SD card
|
||||||
|
*/
|
||||||
|
void loadSettings();
|
||||||
|
|
||||||
|
private:
|
||||||
|
FontManager() = default;
|
||||||
|
|
||||||
|
static constexpr int MAX_FONTS = 16;
|
||||||
|
static constexpr const char *FONTS_DIR = "/fonts";
|
||||||
|
static constexpr const char *SETTINGS_FILE = "/.crosspoint/font_settings.bin";
|
||||||
|
static constexpr uint8_t SETTINGS_VERSION = 2; // Bumped for UI font support
|
||||||
|
|
||||||
|
FontInfo _fonts[MAX_FONTS];
|
||||||
|
int _fontCount = 0;
|
||||||
|
int _selectedIndex = -1; // -1 = built-in font (reader)
|
||||||
|
int _selectedUiIndex = -1; // -1 = fallback to reader font
|
||||||
|
|
||||||
|
ExternalFont _activeFont; // Reader font
|
||||||
|
ExternalFont _activeUiFont; // UI font
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load selected reader font file
|
||||||
|
*/
|
||||||
|
bool loadSelectedFont();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load selected UI font file
|
||||||
|
*/
|
||||||
|
bool loadSelectedUiFont();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convenience macro
|
||||||
|
#define FontMgr FontManager::getInstance()
|
||||||
File diff suppressed because it is too large
Load Diff
@ -7,54 +7,102 @@
|
|||||||
|
|
||||||
#include "Bitmap.h"
|
#include "Bitmap.h"
|
||||||
|
|
||||||
|
// Forward declaration for external font support
|
||||||
|
class ExternalFont;
|
||||||
|
|
||||||
class GfxRenderer {
|
class GfxRenderer {
|
||||||
public:
|
public:
|
||||||
enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
||||||
|
|
||||||
// Logical screen orientation from the perspective of callers
|
// Logical screen orientation from the perspective of callers
|
||||||
enum Orientation {
|
enum Orientation {
|
||||||
Portrait, // 480x800 logical coordinates (current default)
|
Portrait, // 480x800 logical coordinates (current default)
|
||||||
LandscapeClockwise, // 800x480 logical coordinates, rotated 180° (swap top/bottom)
|
LandscapeClockwise, // 800x480 logical coordinates, rotated 180° (swap
|
||||||
PortraitInverted, // 480x800 logical coordinates, inverted
|
// top/bottom)
|
||||||
LandscapeCounterClockwise // 800x480 logical coordinates, native panel orientation
|
PortraitInverted, // 480x800 logical coordinates, inverted
|
||||||
|
LandscapeCounterClockwise // 800x480 logical coordinates, native panel
|
||||||
|
// orientation
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory
|
static constexpr size_t BW_BUFFER_CHUNK_SIZE =
|
||||||
static constexpr size_t BW_BUFFER_NUM_CHUNKS = EInkDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
8000; // 8KB chunks to allow for non-contiguous memory
|
||||||
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == EInkDisplay::BUFFER_SIZE,
|
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");
|
"BW buffer chunking does not line up with display buffer size");
|
||||||
|
|
||||||
EInkDisplay& einkDisplay;
|
EInkDisplay &einkDisplay;
|
||||||
RenderMode renderMode;
|
RenderMode renderMode;
|
||||||
Orientation orientation;
|
Orientation orientation;
|
||||||
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
uint8_t *bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
||||||
std::map<int, EpdFontFamily> fontMap;
|
std::map<int, EpdFontFamily> fontMap;
|
||||||
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
// 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;
|
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 freeBwBufferChunks();
|
||||||
void rotateCoordinates(int x, int y, int* rotatedX, int* rotatedY) const;
|
void rotateCoordinates(int x, int y, int *rotatedX, int *rotatedY) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {}
|
explicit GfxRenderer(EInkDisplay &einkDisplay)
|
||||||
|
: einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {}
|
||||||
~GfxRenderer() { freeBwBufferChunks(); }
|
~GfxRenderer() { freeBwBufferChunks(); }
|
||||||
|
|
||||||
static constexpr int VIEWABLE_MARGIN_TOP = 9;
|
static constexpr int VIEWABLE_MARGIN_TOP = 9;
|
||||||
static constexpr int VIEWABLE_MARGIN_RIGHT = 3;
|
static constexpr int VIEWABLE_MARGIN_RIGHT = 3;
|
||||||
static constexpr int VIEWABLE_MARGIN_BOTTOM = 3;
|
static constexpr int VIEWABLE_MARGIN_BOTTOM = 3;
|
||||||
static constexpr int VIEWABLE_MARGIN_LEFT = 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
|
// Setup
|
||||||
void insertFont(int fontId, EpdFontFamily font);
|
void insertFont(int fontId, EpdFontFamily font);
|
||||||
|
|
||||||
// Orientation control (affects logical width/height and coordinate transforms)
|
// Orientation control (affects logical width/height and coordinate
|
||||||
|
// transforms)
|
||||||
void setOrientation(const Orientation o) { orientation = o; }
|
void setOrientation(const Orientation o) { orientation = o; }
|
||||||
Orientation getOrientation() const { return orientation; }
|
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
|
// Screen ops
|
||||||
int getScreenWidth() const;
|
int getScreenWidth() const;
|
||||||
int getScreenHeight() const;
|
int getScreenHeight() const;
|
||||||
void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
void displayBuffer(
|
||||||
|
EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
||||||
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
||||||
void displayWindow(int x, int y, int width, int height) const;
|
void displayWindow(int x, int y, int width, int height) const;
|
||||||
void invertScreen() const;
|
void invertScreen() const;
|
||||||
@ -65,47 +113,58 @@ class GfxRenderer {
|
|||||||
void drawLine(int x1, int y1, int x2, int y2, 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 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 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 drawImage(const uint8_t bitmap[], int x, int y, int width,
|
||||||
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0,
|
int height) const;
|
||||||
float cropY = 0) const;
|
void drawBitmap(const Bitmap &bitmap, int x, int y, int maxWidth,
|
||||||
void drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;
|
int maxHeight, float cropX = 0, float cropY = 0) const;
|
||||||
void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) 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
|
// Text
|
||||||
int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
int getTextWidth(int fontId, const char *text,
|
||||||
void drawCenteredText(int fontId, int y, const char* text, bool black = true,
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
void
|
||||||
void drawText(int fontId, int x, int y, const char* text, bool black = true,
|
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;
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
int getSpaceWidth(int fontId) const;
|
int getSpaceWidth(int fontId) const;
|
||||||
int getFontAscenderSize(int fontId) const;
|
int getFontAscenderSize(int fontId) const;
|
||||||
int getLineHeight(int fontId) const;
|
int getLineHeight(int fontId) const;
|
||||||
std::string truncatedText(int fontId, const char* text, int maxWidth,
|
std::string
|
||||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
truncatedText(int fontId, const char *text, int maxWidth,
|
||||||
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4);
|
void drawButtonHints(int fontId, const char *btn1, const char *btn2,
|
||||||
void drawSideButtonHints(int fontId, const char* topBtn, const char* bottomBtn) const;
|
const char *btn3, const char *btn4);
|
||||||
|
void drawSideButtonHints(int fontId, const char *topBtn,
|
||||||
|
const char *bottomBtn) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Helper for drawing rotated text (90 degrees clockwise, for side buttons)
|
// 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,
|
void drawTextRotated90CW(
|
||||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
int fontId, int x, int y, const char *text, bool black = true,
|
||||||
|
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||||
int getTextHeight(int fontId) const;
|
int getTextHeight(int fontId) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Grayscale functions
|
// Grayscale functions
|
||||||
void setRenderMode(const RenderMode mode) { this->renderMode = mode; }
|
void setRenderMode(const RenderMode mode) { this->renderMode = mode; }
|
||||||
void copyGrayscaleLsbBuffers() const;
|
void copyGrayscaleLsbBuffers() const;
|
||||||
void copyGrayscaleMsbBuffers() const;
|
void copyGrayscaleMsbBuffers() const;
|
||||||
void displayGrayBuffer() const;
|
void displayGrayBuffer(bool turnOffScreen = false,
|
||||||
bool storeBwBuffer(); // Returns true if buffer was stored successfully
|
bool darkMode = false) const;
|
||||||
void restoreBwBuffer(); // Restore and free the stored buffer
|
bool storeBwBuffer(); // Returns true if buffer was stored successfully
|
||||||
|
void restoreBwBuffer(); // Restore and free the stored buffer
|
||||||
void cleanupGrayscaleWithFrameBuffer() const;
|
void cleanupGrayscaleWithFrameBuffer() const;
|
||||||
|
|
||||||
// Low level functions
|
// Low level functions
|
||||||
uint8_t* getFrameBuffer() const;
|
uint8_t *getFrameBuffer() const;
|
||||||
static size_t getBufferSize();
|
static size_t getBufferSize();
|
||||||
void grayscaleRevert() const;
|
void grayscaleRevert() const;
|
||||||
void getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const;
|
void getOrientedViewableTRBL(int *outTop, int *outRight, int *outBottom,
|
||||||
|
int *outLeft) const;
|
||||||
};
|
};
|
||||||
|
|||||||
839
lib/GfxRenderer/cjk_ui_font.h
Normal file
839
lib/GfxRenderer/cjk_ui_font.h
Normal file
@ -0,0 +1,839 @@
|
|||||||
|
/**
|
||||||
|
* Auto-generated CJK UI font data (optimized - UI characters only)
|
||||||
|
* Font: 思源黑体-Medium
|
||||||
|
* Size: 12pt
|
||||||
|
* Dimensions: 16x16
|
||||||
|
* Characters: 370
|
||||||
|
* Total size: 11840 bytes (11.6 KB)
|
||||||
|
*
|
||||||
|
* This is a sparse font containing only UI-required CJK characters.
|
||||||
|
* Uses a lookup table for codepoint -> glyph index mapping.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <pgmspace.h>
|
||||||
|
|
||||||
|
// Font parameters
|
||||||
|
static constexpr uint8_t CJK_UI_FONT_WIDTH = 16;
|
||||||
|
static constexpr uint8_t CJK_UI_FONT_HEIGHT = 16;
|
||||||
|
static constexpr uint8_t CJK_UI_FONT_BYTES_PER_ROW = 2;
|
||||||
|
static constexpr uint8_t CJK_UI_FONT_BYTES_PER_CHAR = 32;
|
||||||
|
static constexpr uint16_t CJK_UI_FONT_GLYPH_COUNT = 370;
|
||||||
|
|
||||||
|
// Codepoint lookup table (sorted for binary search)
|
||||||
|
static const uint16_t CJK_UI_CODEPOINTS[] PROGMEM = {
|
||||||
|
0x3042, 0x3044, 0x3048, 0x304B, 0x304C, 0x304D, 0x304F, 0x3053, 0x3057, 0x3059, 0x305B, 0x305F, 0x3064, 0x3066, 0x3067, 0x3068,
|
||||||
|
0x306A, 0x306B, 0x306E, 0x306F, 0x307E, 0x307F, 0x3080, 0x3081, 0x3089, 0x308A, 0x308B, 0x308C, 0x308F, 0x3092, 0x3093, 0x30A1,
|
||||||
|
0x30A2, 0x30A3, 0x30A4, 0x30A6, 0x30A8, 0x30A9, 0x30AA, 0x30AB, 0x30AD, 0x30AF, 0x30B3, 0x30B5, 0x30B6, 0x30B7, 0x30B8, 0x30B9,
|
||||||
|
0x30BA, 0x30BB, 0x30BF, 0x30C0, 0x30C1, 0x30C3, 0x30C6, 0x30C7, 0x30C8, 0x30C9, 0x30CD, 0x30D0, 0x30D1, 0x30D5, 0x30D6, 0x30D7,
|
||||||
|
0x30DA, 0x30DB, 0x30DC, 0x30DD, 0x30DE, 0x30DF, 0x30E0, 0x30E1, 0x30E2, 0x30E3, 0x30E4, 0x30E5, 0x30E7, 0x30E9, 0x30EA, 0x30EB,
|
||||||
|
0x30EC, 0x30ED, 0x30EF, 0x30F3, 0x30FC, 0x4E00, 0x4E0A, 0x4E0B, 0x4E0D, 0x4E21, 0x4E24, 0x4E2D, 0x4E3A, 0x4E3B, 0x4E49, 0x4E66,
|
||||||
|
0x4E86, 0x4E8C, 0x4EBA, 0x4ECE, 0x4ED6, 0x4EF6, 0x4EFB, 0x4F11, 0x4F20, 0x4F53, 0x4F59, 0x4F5C, 0x4F9B, 0x4FA7, 0x4FDD, 0x5012,
|
||||||
|
0x5074, 0x5165, 0x5173, 0x5185, 0x518D, 0x51D1, 0x51FA, 0x5206, 0x5207, 0x521B, 0x5220, 0x5230, 0x5237, 0x524A, 0x524D, 0x526A,
|
||||||
|
0x52A0, 0x52A8, 0x52B9, 0x52D5, 0x5316, 0x53C2, 0x53CD, 0x53D6, 0x53EF, 0x53F3, 0x53F7, 0x5411, 0x5426, 0x542F, 0x5668, 0x56DE,
|
||||||
|
0x56F2, 0x56F4, 0x56FD, 0x5728, 0x5730, 0x5740, 0x5907, 0x5916, 0x5927, 0x592E, 0x5931, 0x59CB, 0x5B57, 0x5B58, 0x5B8C, 0x5B9A,
|
||||||
|
0x5BBD, 0x5BC6, 0x5BF9, 0x5C01, 0x5C06, 0x5C0F, 0x5C40, 0x5C45, 0x5C4F, 0x5DE6, 0x5DF2, 0x5E03, 0x5E38, 0x5E55, 0x5E83, 0x5E93,
|
||||||
|
0x5E94, 0x5EA6, 0x5EFA, 0x5F00, 0x5F0F, 0x5F15, 0x5F53, 0x5FD8, 0x5FFD, 0x6001, 0x610F, 0x6210, 0x6216, 0x623B, 0x624B, 0x6253,
|
||||||
|
0x626B, 0x627E, 0x6297, 0x629E, 0x62BC, 0x62E9, 0x6309, 0x6357, 0x6362, 0x63A5, 0x63C3, 0x63CF, 0x6557, 0x6574, 0x6587, 0x65B0,
|
||||||
|
0x65B9, 0x65E0, 0x65E2, 0x65E5, 0x65F6, 0x662F, 0x663E, 0x6642, 0x666E, 0x6697, 0x66F4, 0x66F8, 0x66FF, 0x6700, 0x6709, 0x672A,
|
||||||
|
0x672B, 0x672C, 0x673A, 0x6761, 0x677E, 0x67E5, 0x680F, 0x68C0, 0x6A21, 0x6A2A, 0x6B21, 0x6B63, 0x6B64, 0x6BB5, 0x6BD4, 0x6CD5,
|
||||||
|
0x6D45, 0x6D4F, 0x6D88, 0x6DF1, 0x6E08, 0x6E90, 0x70B9, 0x70ED, 0x7121, 0x7248, 0x7279, 0x72B6, 0x72ED, 0x7387, 0x73B0, 0x73FE,
|
||||||
|
0x7528, 0x7535, 0x753B, 0x7565, 0x767D, 0x767E, 0x7684, 0x76EE, 0x7720, 0x77ED, 0x7801, 0x786E, 0x78BA, 0x793A, 0x7981, 0x7A7A,
|
||||||
|
0x7AD6, 0x7AE0, 0x7AEF, 0x7BC4, 0x7C4D, 0x7D22, 0x7D27, 0x7D42, 0x7D9A, 0x7E26, 0x7EBF, 0x7EC8, 0x7EDC, 0x7EE7, 0x7EED, 0x7EF4,
|
||||||
|
0x7F51, 0x7F6E, 0x7FFB, 0x81EA, 0x8272, 0x8282, 0x8303, 0x843D, 0x8535, 0x85CF, 0x884C, 0x88C1, 0x898B, 0x8996, 0x89A7, 0x89C8,
|
||||||
|
0x8A00, 0x8A08, 0x8A2D, 0x8A66, 0x8A8D, 0x8A9E, 0x8AAD, 0x8BA4, 0x8BB0, 0x8BBE, 0x8BD5, 0x8BED, 0x8BEF, 0x8BFB, 0x8D25, 0x8D77,
|
||||||
|
0x8D85, 0x8DDD, 0x8DF3, 0x8EE2, 0x8F6C, 0x8F7D, 0x8F93, 0x8FB9, 0x8FBC, 0x8FD4, 0x8FDB, 0x8FDE, 0x8FFD, 0x9000, 0x9001, 0x9002,
|
||||||
|
0x9006, 0x9009, 0x901A, 0x9032, 0x9078, 0x90E8, 0x914D, 0x91CD, 0x91CF, 0x9488, 0x949F, 0x94AE, 0x9519, 0x952E, 0x952F, 0x9577,
|
||||||
|
0x957F, 0x958B, 0x9593, 0x95F4, 0x9605, 0x9664, 0x9690, 0x9694, 0x96A0, 0x96FB, 0x9762, 0x983B, 0x9875, 0x987A, 0x9891, 0x989D,
|
||||||
|
0x9F50, 0x9F7F,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Glyph bitmap data (stored in Flash/PROGMEM)
|
||||||
|
static const uint8_t CJK_UI_FONT_DATA[] PROGMEM = {
|
||||||
|
// U+3042 (あ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x38, 0x3F, 0xF8, 0x1F, 0xA0, 0x06, 0x70, 0x07, 0xF8, 0x0F, 0x7C, 0x1E, 0x6C, 0x3E, 0xC6, 0x33, 0xC6, 0x33, 0x8E, 0x77, 0x0C, 0x3F, 0x3C, 0x1A, 0x70,
|
||||||
|
// U+3044 (い)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x30, 0x10, 0x30, 0x18, 0x30, 0x18, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0E, 0x31, 0x86, 0x39, 0x86, 0x1B, 0x80, 0x1F, 0x00, 0x0E, 0x00,
|
||||||
|
// U+3048 (え)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x07, 0xF0, 0x00, 0x60, 0x00, 0x00, 0x1F, 0xF0, 0x1F, 0xE0, 0x00, 0xC0, 0x01, 0x80, 0x03, 0x80, 0x07, 0xC0, 0x0E, 0xC0, 0x1C, 0xC0, 0x38, 0x7E, 0x30, 0x7E,
|
||||||
|
// U+304B (か)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x18, 0x7F, 0x9C, 0x7F, 0xEC, 0x6C, 0x6E, 0x0C, 0x66, 0x0C, 0x66, 0x1C, 0x62, 0x18, 0x60, 0x18, 0x60, 0x30, 0xE0, 0x37, 0xC0, 0x77, 0xC0,
|
||||||
|
// U+304C (が)
|
||||||
|
0x00, 0x00, 0x00, 0x06, 0x06, 0x1A, 0x06, 0x0F, 0x06, 0x18, 0x0F, 0x98, 0x7F, 0xCC, 0x6C, 0xEC, 0x0C, 0x66, 0x0C, 0x66, 0x18, 0x66, 0x18, 0x60, 0x38, 0x60, 0x30, 0xC0, 0x77, 0xC0, 0x67, 0x80,
|
||||||
|
// U+304D (き)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x01, 0xF8, 0x1F, 0xF0, 0x0F, 0xC0, 0x00, 0x6C, 0x1F, 0xFC, 0x1F, 0xF0, 0x00, 0x30, 0x00, 0x70, 0x18, 0xF8, 0x18, 0x10, 0x18, 0x00, 0x1F, 0xE0, 0x0F, 0xE0,
|
||||||
|
// U+304F (く)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0x70, 0x00, 0x20,
|
||||||
|
// U+3053 (こ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x1F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x38, 0x00, 0x18, 0x00, 0x1F, 0xFC, 0x0F, 0xF8,
|
||||||
|
// U+3057 (し)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x1C, 0x04, 0x1C, 0x0C, 0x0C, 0x3C, 0x0F, 0xF8, 0x07, 0xE0,
|
||||||
|
// U+3059 (す)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0xC0, 0x03, 0xC0, 0x07, 0xC0, 0x06, 0x60, 0x06, 0x60, 0x07, 0xE0, 0x03, 0xE0, 0x01, 0xC0, 0x07, 0x80, 0x07, 0x00,
|
||||||
|
// U+305B (せ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x3F, 0xFE, 0x7F, 0xFE, 0x7C, 0x30, 0x0C, 0x30, 0x0C, 0xF0, 0x0C, 0x60, 0x0C, 0x00, 0x0E, 0x08, 0x0F, 0xF8, 0x03, 0xF8,
|
||||||
|
// U+305F (た)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x3F, 0xC0, 0x7F, 0x80, 0x0E, 0x00, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0x00, 0x1C, 0x00, 0x19, 0x80, 0x19, 0x80, 0x39, 0xC0, 0x30, 0xFE, 0x30, 0x7C,
|
||||||
|
// U+3064 (つ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0x0F, 0xF8, 0x7F, 0x1C, 0x70, 0x0C, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x1C, 0x00, 0x78, 0x07, 0xF0, 0x07, 0xC0, 0x00, 0x00,
|
||||||
|
// U+3066 (て)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFC, 0x7F, 0xFC, 0x78, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xF8, 0x00, 0x78,
|
||||||
|
// U+3067 (で)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x7F, 0xFC, 0x7F, 0xE0, 0x01, 0x84, 0x03, 0x16, 0x03, 0x1E, 0x03, 0x08, 0x03, 0x00, 0x03, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x01, 0xF8, 0x00, 0x78,
|
||||||
|
// U+3068 (と)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1C, 0x00, 0x0C, 0x10, 0x0C, 0x78, 0x07, 0xF0, 0x07, 0xC0, 0x07, 0x00, 0x0E, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x1F, 0xF8, 0x0F, 0xF8,
|
||||||
|
// U+306A (な)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x7F, 0x98, 0x7F, 0xBC, 0x0C, 0x0E, 0x0C, 0x64, 0x1C, 0x60, 0x18, 0x60, 0x38, 0x60, 0x33, 0xF0, 0x37, 0xF8, 0x06, 0x7C, 0x07, 0xE4, 0x03, 0xE0,
|
||||||
|
// U+306B (に)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x38, 0x00, 0x31, 0xFC, 0x31, 0xFC, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x38, 0x00, 0x3B, 0x00, 0x3B, 0x00, 0x3B, 0x84, 0x31, 0xFE, 0x30, 0xFC,
|
||||||
|
// U+306E (の)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x0F, 0xF8, 0x1D, 0x9C, 0x39, 0x8C, 0x31, 0x8C, 0x63, 0x86, 0x63, 0x06, 0x63, 0x0E, 0x67, 0x0E, 0x76, 0x0C, 0x3E, 0x3C, 0x1C, 0xF8, 0x00, 0xE0,
|
||||||
|
// U+306F (は)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0xFE, 0x33, 0xFE, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x38, 0x30, 0x39, 0xF8, 0x3B, 0x3C, 0x33, 0x3E, 0x33, 0xF6, 0x31, 0xE0,
|
||||||
|
// U+307E (ま)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0xC0, 0x01, 0xC8, 0x1F, 0xF8, 0x0F, 0xE0, 0x01, 0x80, 0x1F, 0xFC, 0x1F, 0xFC, 0x01, 0xC0, 0x01, 0xC0, 0x0F, 0xC0, 0x1F, 0xF0, 0x19, 0xFC, 0x1F, 0x8C, 0x0F, 0x80,
|
||||||
|
// U+307F (み)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xC0, 0x0F, 0xC0, 0x01, 0x80, 0x01, 0x8C, 0x03, 0x8C, 0x1F, 0xEC, 0x3F, 0xFC, 0x76, 0x1E, 0x6E, 0x1E, 0x6C, 0x3A, 0x7C, 0x30, 0x38, 0xE0, 0x00, 0xC0,
|
||||||
|
// U+3080 (む)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x80, 0x7F, 0x98, 0x7F, 0x1C, 0x0C, 0x0E, 0x1C, 0x04, 0x3E, 0x00, 0x66, 0x00, 0x66, 0x00, 0x7C, 0x1C, 0x3C, 0x1C, 0x1C, 0x18, 0x0F, 0xF8, 0x07, 0xF0,
|
||||||
|
// U+3081 (め)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x18, 0x60, 0x18, 0xE0, 0x1F, 0xF8, 0x1E, 0xFC, 0x1C, 0xCC, 0x3D, 0x8E, 0x37, 0x86, 0x67, 0x86, 0x67, 0x0E, 0x67, 0x8C, 0x7F, 0x1C, 0x3C, 0xF8, 0x00, 0xF0,
|
||||||
|
// U+3089 (ら)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0xF0, 0x01, 0xF0, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x1B, 0xF0, 0x1F, 0xF8, 0x1C, 0x1C, 0x18, 0x0C, 0x00, 0x1C, 0x00, 0x38, 0x07, 0xF8, 0x07, 0xE0,
|
||||||
|
// U+308A (り)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x1D, 0xE0, 0x1B, 0xF0, 0x1E, 0x30, 0x1C, 0x18, 0x1C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x38, 0x00, 0x30, 0x00, 0xE0, 0x07, 0xE0, 0x07, 0x80,
|
||||||
|
// U+308B (る)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x0F, 0xE0, 0x00, 0xC0, 0x01, 0x80, 0x03, 0xC0, 0x0F, 0xF0, 0x1E, 0x38, 0x38, 0x0C, 0x37, 0x8C, 0x07, 0xCC, 0x0C, 0xF8, 0x07, 0xF8, 0x07, 0xE0,
|
||||||
|
// U+308C (れ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0C, 0x70, 0x7E, 0xF8, 0x7F, 0x98, 0x0F, 0x18, 0x0E, 0x18, 0x1C, 0x18, 0x3C, 0x18, 0x3C, 0x18, 0x6C, 0x18, 0x6C, 0x3B, 0x0C, 0x1F, 0x0C, 0x1C,
|
||||||
|
// U+308F (わ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x7C, 0xF8, 0x7F, 0xFC, 0x0F, 0x0E, 0x0C, 0x06, 0x1C, 0x06, 0x3C, 0x06, 0x7C, 0x0E, 0x6C, 0x1C, 0x6C, 0x7C, 0x0C, 0xF0, 0x0C, 0x40,
|
||||||
|
// U+3092 (を)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x07, 0x70, 0x3F, 0xF0, 0x3F, 0xC0, 0x0C, 0x00, 0x1F, 0x9C, 0x1F, 0xF8, 0x39, 0xE0, 0x33, 0xC0, 0x06, 0xC0, 0x0C, 0xC0, 0x0C, 0x00, 0x0F, 0xF8, 0x07, 0xF8,
|
||||||
|
// U+3093 (ん)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x03, 0x80, 0x03, 0x00, 0x07, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x0F, 0x80, 0x1F, 0xC0, 0x1C, 0xC6, 0x38, 0xC6, 0x30, 0xC6, 0x30, 0xCC, 0x70, 0xFC, 0x60, 0x78,
|
||||||
|
// U+30A1 (ァ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFC, 0x1F, 0xFC, 0x01, 0x98, 0x01, 0xB0, 0x01, 0xF0, 0x01, 0x80, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0E, 0x00,
|
||||||
|
// U+30A2 (ア)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x3F, 0xFE, 0x00, 0x0C, 0x01, 0x98, 0x01, 0xB8, 0x01, 0xF0, 0x01, 0x80, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x06, 0x00, 0x1E, 0x00, 0x1C, 0x00,
|
||||||
|
// U+30A3 (ィ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x38, 0x00, 0xF0, 0x01, 0xC0, 0x0F, 0x80, 0x3F, 0x80, 0x39, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
|
||||||
|
// U+30A4 (イ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xE0, 0x03, 0xC0, 0x1F, 0xC0, 0x7C, 0xC0, 0x30, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0,
|
||||||
|
// U+30A6 (ウ)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x1C, 0x30, 0x18, 0x00, 0x18, 0x00, 0x38, 0x00, 0x70, 0x01, 0xE0, 0x07, 0xC0, 0x07, 0x00,
|
||||||
|
// U+30A8 (エ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00,
|
||||||
|
// U+30A9 (ォ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x1F, 0xFC, 0x1F, 0xFC, 0x00, 0xE0, 0x01, 0xE0, 0x03, 0x60, 0x0F, 0x60, 0x1C, 0x60, 0x18, 0x60, 0x01, 0xE0,
|
||||||
|
// U+30AA (オ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x3F, 0xFE, 0x3F, 0xFE, 0x01, 0xE0, 0x01, 0xE0, 0x03, 0x60, 0x0E, 0x60, 0x1C, 0x60, 0x78, 0x60, 0x70, 0x60, 0x03, 0xE0, 0x03, 0xE0,
|
||||||
|
// U+30AB (カ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x03, 0x0C, 0x03, 0x0C, 0x07, 0x0C, 0x06, 0x0C, 0x0E, 0x1C, 0x0C, 0x18, 0x1C, 0x18, 0x38, 0xF8, 0x30, 0xF0,
|
||||||
|
// U+30AD (キ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x18, 0x07, 0xF8, 0x3F, 0xF0, 0x3B, 0x80, 0x01, 0x84, 0x01, 0xFE, 0x3F, 0xFC, 0x3F, 0x80, 0x01, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0,
|
||||||
|
// U+30AF (ク)
|
||||||
|
0x00, 0x00, 0x01, 0x00, 0x03, 0x80, 0x03, 0x00, 0x07, 0xFC, 0x0F, 0xFC, 0x0C, 0x18, 0x1C, 0x18, 0x38, 0x38, 0x30, 0x30, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x0F, 0x00, 0x0E, 0x00,
|
||||||
|
// U+30B3 (コ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x1F, 0xFC, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x00, 0x0C,
|
||||||
|
// U+30B5 (サ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x00, 0x60, 0x00, 0x60, 0x00, 0xC0, 0x03, 0xC0, 0x03, 0x00,
|
||||||
|
// U+30B6 (ザ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0C, 0x3E, 0x0C, 0x3E, 0x0C, 0x30, 0x7F, 0xFE, 0x7F, 0xFC, 0x0C, 0x30, 0x0C, 0x70, 0x0C, 0x60, 0x08, 0x60, 0x00, 0x60, 0x00, 0xC0, 0x03, 0xC0, 0x07, 0x80,
|
||||||
|
// U+30B7 (シ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x0F, 0x00, 0x07, 0x00, 0x01, 0x00, 0x30, 0x06, 0x3C, 0x0E, 0x0C, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x01, 0xE0, 0x07, 0xC0, 0x3F, 0x00, 0x1C, 0x00,
|
||||||
|
// U+30B8 (ジ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0E, 0x36, 0x0F, 0x1A, 0x03, 0x08, 0x20, 0x04, 0x78, 0x0E, 0x1C, 0x0C, 0x08, 0x1C, 0x00, 0x78, 0x00, 0xE0, 0x03, 0xC0, 0x1F, 0x80, 0x3E, 0x00, 0x10, 0x00,
|
||||||
|
// U+30B9 (ス)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x1F, 0xF8, 0x00, 0x38, 0x00, 0x30, 0x00, 0x70, 0x00, 0xE0, 0x00, 0xC0, 0x01, 0xE0, 0x03, 0xF0, 0x0F, 0x38, 0x1E, 0x1C, 0x78, 0x0E, 0x30, 0x0C,
|
||||||
|
// U+30BA (ズ)
|
||||||
|
0x00, 0x00, 0x00, 0x06, 0x00, 0x1E, 0x00, 0x0A, 0x3F, 0xF8, 0x3F, 0xF0, 0x00, 0x30, 0x00, 0x70, 0x00, 0xE0, 0x00, 0xC0, 0x01, 0xC0, 0x03, 0xE0, 0x07, 0x70, 0x1E, 0x38, 0x7C, 0x1C, 0x70, 0x0C,
|
||||||
|
// U+30BB (セ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x7C, 0x0F, 0xFC, 0x7F, 0xDC, 0x7E, 0x18, 0x0E, 0x30, 0x0E, 0x70, 0x0E, 0x20, 0x0E, 0x00, 0x0E, 0x00, 0x07, 0xFC, 0x07, 0xFC,
|
||||||
|
// U+30BF (タ)
|
||||||
|
0x00, 0x00, 0x01, 0x00, 0x03, 0x80, 0x03, 0x00, 0x07, 0xFC, 0x0F, 0xFC, 0x0C, 0x1C, 0x1C, 0x18, 0x7B, 0x38, 0x33, 0xF0, 0x00, 0xF0, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x0F, 0x00, 0x0E, 0x00,
|
||||||
|
// U+30C0 (ダ)
|
||||||
|
0x00, 0x00, 0x00, 0x06, 0x03, 0x1E, 0x07, 0x0E, 0x07, 0xF8, 0x0F, 0xFC, 0x0C, 0x18, 0x18, 0x18, 0x3A, 0x38, 0x77, 0xB0, 0x03, 0xF0, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x0F, 0x00, 0x1E, 0x00,
|
||||||
|
// U+30C1 (チ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x07, 0xF8, 0x1F, 0xF0, 0x01, 0x80, 0x01, 0x80, 0x3F, 0xFC, 0x7F, 0xFC, 0x01, 0x80, 0x01, 0x80, 0x03, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x0C, 0x00,
|
||||||
|
// U+30C3 (ッ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x08, 0x19, 0x9C, 0x19, 0x98, 0x1D, 0x98, 0x0C, 0x38, 0x00, 0x30, 0x00, 0x70, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x80,
|
||||||
|
// U+30C6 (テ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x3F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x03, 0x80, 0x03, 0x00, 0x0F, 0x00, 0x0E, 0x00,
|
||||||
|
// U+30C7 (デ)
|
||||||
|
0x00, 0x00, 0x00, 0x06, 0x00, 0x1B, 0x1F, 0xFF, 0x1F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x0E, 0x00,
|
||||||
|
// U+30C8 (ト)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x07, 0x80, 0x07, 0xE0, 0x06, 0xFC, 0x06, 0x3C, 0x06, 0x08, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00,
|
||||||
|
// U+30C9 (ド)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x28, 0x0E, 0x3C, 0x0E, 0x34, 0x0E, 0x00, 0x0F, 0x00, 0x0F, 0xE0, 0x0F, 0xF8, 0x0E, 0x38, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00,
|
||||||
|
// U+30CD (ネ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x3F, 0xF8, 0x3F, 0xFC, 0x00, 0x38, 0x00, 0x70, 0x01, 0xE0, 0x03, 0xE0, 0x0F, 0xF8, 0x7D, 0xBC, 0x71, 0x8E, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
|
||||||
|
// U+30D0 (バ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x1A, 0x04, 0x6E, 0x0E, 0x60, 0x0C, 0x70, 0x0C, 0x30, 0x0C, 0x38, 0x1C, 0x18, 0x18, 0x1C, 0x18, 0x1C, 0x38, 0x0C, 0x70, 0x0E, 0x60, 0x0E, 0x20, 0x04,
|
||||||
|
// U+30D1 (パ)
|
||||||
|
0x00, 0x00, 0x00, 0x0E, 0x00, 0x0B, 0x04, 0x6B, 0x0E, 0x6E, 0x0E, 0x70, 0x0C, 0x30, 0x0C, 0x38, 0x0C, 0x18, 0x1C, 0x18, 0x18, 0x1C, 0x38, 0x0C, 0x30, 0x0C, 0x70, 0x0E, 0x60, 0x0E, 0x20, 0x04,
|
||||||
|
// U+30D5 (フ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8, 0x3F, 0xFC, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x18, 0x00, 0x38, 0x00, 0x30, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xE0, 0x07, 0xC0, 0x0F, 0x00, 0x06, 0x00,
|
||||||
|
// U+30D6 (ブ)
|
||||||
|
0x00, 0x00, 0x00, 0x1E, 0x00, 0x1B, 0x3F, 0xF8, 0x3F, 0xFC, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x38, 0x00, 0x30, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x0E, 0x00,
|
||||||
|
// U+30D7 (プ)
|
||||||
|
0x00, 0x00, 0x00, 0x06, 0x00, 0x0B, 0x3F, 0xFB, 0x3F, 0xFE, 0x00, 0x1C, 0x00, 0x18, 0x00, 0x18, 0x00, 0x38, 0x00, 0x30, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x0E, 0x00,
|
||||||
|
// U+30DA (ペ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x34, 0x07, 0x36, 0x0F, 0xB4, 0x0D, 0xDC, 0x18, 0xE0, 0x38, 0x60, 0x70, 0x70, 0x60, 0x38, 0x20, 0x1C, 0x00, 0x0E, 0x00, 0x04, 0x00, 0x00,
|
||||||
|
// U+30DB (ホ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x3F, 0xFE, 0x01, 0x80, 0x09, 0x88, 0x1D, 0x98, 0x19, 0x8C, 0x31, 0x8E, 0x71, 0x86, 0x21, 0x86, 0x07, 0x80, 0x07, 0x80,
|
||||||
|
// U+30DC (ボ)
|
||||||
|
0x00, 0x00, 0x00, 0x04, 0x01, 0x9E, 0x01, 0x9A, 0x01, 0x88, 0x7F, 0xFC, 0x7F, 0xFC, 0x01, 0x80, 0x01, 0x80, 0x19, 0x98, 0x19, 0x9C, 0x31, 0x8C, 0x71, 0x8E, 0x61, 0x84, 0x07, 0x80, 0x07, 0x80,
|
||||||
|
// U+30DD (ポ)
|
||||||
|
0x00, 0x00, 0x00, 0x0C, 0x01, 0x9A, 0x01, 0x9A, 0x01, 0x8C, 0x7F, 0xFC, 0x7F, 0xFC, 0x01, 0x80, 0x01, 0x80, 0x19, 0x98, 0x19, 0x9C, 0x31, 0x8C, 0x71, 0x8E, 0x61, 0x84, 0x07, 0x80, 0x07, 0x80,
|
||||||
|
// U+30DE (マ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x3F, 0xFE, 0x00, 0x1C, 0x00, 0x18, 0x00, 0x38, 0x1C, 0x70, 0x0F, 0xE0, 0x07, 0xC0, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xE0, 0x00, 0x60,
|
||||||
|
// U+30DF (ミ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x0F, 0xF8, 0x00, 0xF8, 0x00, 0x00, 0x0C, 0x00, 0x1F, 0xC0, 0x03, 0xF0, 0x00, 0x30, 0x00, 0x00, 0x1E, 0x00, 0x3F, 0xE0, 0x03, 0xF8, 0x00, 0x30,
|
||||||
|
// U+30E0 (ム)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x80, 0x03, 0x00, 0x03, 0x00, 0x07, 0x00, 0x06, 0x00, 0x06, 0x30, 0x0E, 0x38, 0x0C, 0x18, 0x0C, 0x1C, 0x1D, 0xFC, 0x7F, 0xFE, 0x7F, 0x06, 0x00, 0x06,
|
||||||
|
// U+30E1 (メ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x08, 0x38, 0x1C, 0x30, 0x0F, 0x60, 0x03, 0xE0, 0x01, 0xE0, 0x01, 0xF0, 0x03, 0xF8, 0x0F, 0x1C, 0x1E, 0x0C, 0x38, 0x00, 0x30, 0x00,
|
||||||
|
// U+30E2 (モ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x1F, 0xFC, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x3F, 0xFE, 0x3F, 0xFE, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0xFC, 0x01, 0xFC,
|
||||||
|
// U+30E3 (ャ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x1C, 0x07, 0xFC, 0x3F, 0xFC, 0x3F, 0x18, 0x03, 0x30, 0x03, 0x20, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
|
||||||
|
// U+30E4 (ヤ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x0C, 0x0C, 0x0F, 0xFE, 0x7F, 0xFC, 0x7E, 0x1C, 0x46, 0x38, 0x07, 0x70, 0x03, 0x20, 0x03, 0x00, 0x03, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
|
||||||
|
// U+30E5 (ュ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x0F, 0xF0, 0x00, 0x30, 0x00, 0x30, 0x00, 0x70, 0x00, 0x60, 0x00, 0x60, 0x3F, 0xFC, 0x3F, 0xFC,
|
||||||
|
// U+30E7 (ョ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x1F, 0xF8, 0x00, 0x18, 0x00, 0x18, 0x0F, 0xF8, 0x0F, 0xF8, 0x00, 0x18, 0x00, 0x18, 0x1F, 0xF8, 0x1F, 0xF8,
|
||||||
|
// U+30E9 (ラ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x00, 0x1C, 0x00, 0x18, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x07, 0xC0, 0x07, 0x00,
|
||||||
|
// U+30EA (リ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x38, 0x18, 0x38, 0x00, 0x30, 0x00, 0x70, 0x00, 0xE0, 0x03, 0xC0, 0x03, 0x80,
|
||||||
|
// U+30EB (ル)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x0C, 0xC0, 0x0C, 0xC0, 0x0C, 0xC0, 0x0C, 0xC0, 0x0C, 0xC0, 0x0C, 0xC2, 0x1C, 0xC6, 0x18, 0xCE, 0x38, 0xFC, 0x30, 0xF8, 0x70, 0xE0, 0x00, 0x40,
|
||||||
|
// U+30EC (レ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x04, 0x18, 0x0E, 0x18, 0x1C, 0x18, 0x78, 0x19, 0xE0, 0x1F, 0xC0, 0x1F, 0x00, 0x08, 0x00,
|
||||||
|
// U+30ED (ロ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C,
|
||||||
|
// U+30EF (ワ)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x1C, 0x30, 0x1C, 0x30, 0x18, 0x00, 0x38, 0x00, 0x30, 0x00, 0xF0, 0x01, 0xE0, 0x07, 0xC0, 0x07, 0x00,
|
||||||
|
// U+30F3 (ン)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x06, 0x02, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x01, 0xE0, 0x07, 0xC0, 0x3F, 0x00, 0x3C, 0x00, 0x00, 0x00,
|
||||||
|
// U+30FC (ー)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
// U+4E00 (一)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
// U+4E0A (上)
|
||||||
|
0x00, 0x00, 0x01, 0x00, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x03, 0xFC, 0x03, 0xFC, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x7F, 0xFE, 0x7F, 0xFE,
|
||||||
|
// U+4E0B (下)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xE0, 0x01, 0xF0, 0x01, 0xBC, 0x01, 0x8C, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
|
||||||
|
// U+4E0D (不)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0xC0, 0x01, 0x80, 0x03, 0x80, 0x07, 0xF0, 0x0F, 0xB8, 0x1D, 0x9C, 0x79, 0x8E, 0x61, 0x86, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
|
||||||
|
// U+4E21 (両)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x61, 0x86, 0x6D, 0xB6, 0x6D, 0xB6, 0x6D, 0xB6, 0x6F, 0xF6, 0x6F, 0xF6, 0x60, 0x06, 0x60, 0x1E,
|
||||||
|
// U+4E24 (两)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x06, 0x60, 0x06, 0x60, 0x7F, 0xFE, 0x7F, 0xFE, 0x66, 0x66, 0x66, 0x66, 0x67, 0x76, 0x6F, 0xFE, 0x6D, 0xDE, 0x79, 0x8E, 0x78, 0x86, 0x60, 0x3E,
|
||||||
|
// U+4E2D (中)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x61, 0x86, 0x61, 0x86, 0x61, 0x86, 0x61, 0x86, 0x7F, 0xFE, 0x7F, 0xFE, 0x21, 0x84, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
|
||||||
|
// U+4E3A (为)
|
||||||
|
0x00, 0x00, 0x03, 0x00, 0x3B, 0x00, 0x1B, 0x00, 0x1B, 0x00, 0x7F, 0xFC, 0x7F, 0xFE, 0x03, 0x06, 0x03, 0x06, 0x06, 0xCE, 0x06, 0xEC, 0x0E, 0x6C, 0x0C, 0x2C, 0x18, 0x0C, 0x38, 0x0C, 0x60, 0x7C,
|
||||||
|
// U+4E3B (主)
|
||||||
|
0x00, 0x00, 0x03, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x3F, 0xFC, 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x3F, 0xFC, 0x3F, 0xFC, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE,
|
||||||
|
// U+4E49 (义)
|
||||||
|
0x00, 0x00, 0x01, 0x00, 0x01, 0x88, 0x31, 0x8C, 0x39, 0x9C, 0x18, 0x98, 0x18, 0x18, 0x0C, 0x30, 0x0E, 0x70, 0x07, 0xE0, 0x03, 0xC0, 0x03, 0xC0, 0x07, 0xE0, 0x1E, 0x78, 0x78, 0x1E, 0x60, 0x06,
|
||||||
|
// U+4E66 (书)
|
||||||
|
0x00, 0x00, 0x03, 0x00, 0x03, 0x1C, 0x03, 0x0E, 0x3F, 0xFE, 0x3F, 0xF8, 0x03, 0x18, 0x03, 0x18, 0x7F, 0xFE, 0x7F, 0xFE, 0x03, 0x06, 0x03, 0x06, 0x03, 0x06, 0x03, 0x3E, 0x03, 0x3C, 0x03, 0x00,
|
||||||
|
// U+4E86 (了)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x7F, 0xFC, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x0F, 0x80,
|
||||||
|
// U+4E8C (二)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00,
|
||||||
|
// U+4EBA (人)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x03, 0x80, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x06, 0x60, 0x06, 0x60, 0x0C, 0x30, 0x1C, 0x38, 0x38, 0x1E, 0x70, 0x0E,
|
||||||
|
// U+4ECE (从)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x1C, 0x70, 0x1C, 0x70, 0x1E, 0x70, 0x1E, 0x70, 0x1B, 0x78, 0x3B, 0xD8, 0x30, 0xDC, 0x31, 0xCC, 0x63, 0x8E, 0x63, 0x06,
|
||||||
|
// U+4ED6 (他)
|
||||||
|
0x00, 0x00, 0x08, 0x20, 0x18, 0x20, 0x1B, 0x20, 0x3B, 0x2E, 0x33, 0x7E, 0x73, 0xFE, 0xF7, 0xE6, 0xFF, 0x26, 0x33, 0x26, 0x33, 0x3E, 0x33, 0x3C, 0x33, 0x20, 0x33, 0x03, 0x33, 0x06, 0x33, 0xFE,
|
||||||
|
// U+4EF6 (件)
|
||||||
|
0x00, 0x00, 0x08, 0x60, 0x1B, 0x60, 0x1B, 0x60, 0x3B, 0xFC, 0x33, 0xFE, 0x76, 0x60, 0xF6, 0x60, 0x70, 0x60, 0x37, 0xFE, 0x37, 0xFF, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60,
|
||||||
|
// U+4EFB (任)
|
||||||
|
0x00, 0x00, 0x0C, 0x04, 0x0C, 0x7E, 0x1F, 0xFC, 0x1B, 0xE0, 0x38, 0x60, 0x38, 0x60, 0x78, 0x60, 0xFF, 0xFE, 0x5F, 0xFF, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x1B, 0xFE, 0x1F, 0xFE,
|
||||||
|
// U+4F11 (休)
|
||||||
|
0x00, 0x00, 0x0C, 0x60, 0x0C, 0x60, 0x1C, 0x60, 0x18, 0x60, 0x3F, 0xFE, 0x7F, 0xFE, 0x78, 0xF0, 0xF8, 0xF8, 0x59, 0xF8, 0x19, 0xFC, 0x1B, 0x6C, 0x1F, 0x66, 0x1E, 0x67, 0x1C, 0x62, 0x18, 0x60,
|
||||||
|
// U+4F20 (传)
|
||||||
|
0x00, 0x00, 0x08, 0x40, 0x18, 0xC0, 0x1F, 0xFE, 0x33, 0xFC, 0x30, 0xC0, 0x7F, 0xFE, 0xF7, 0xFE, 0xF1, 0x80, 0x31, 0xFC, 0x33, 0xFE, 0x30, 0x1C, 0x30, 0xB8, 0x31, 0xF0, 0x30, 0x70, 0x30, 0x38,
|
||||||
|
// U+4F53 (体)
|
||||||
|
0x00, 0x00, 0x08, 0x60, 0x18, 0x60, 0x18, 0x60, 0x37, 0xFE, 0x37, 0xFE, 0x70, 0xF0, 0x71, 0xF0, 0xF1, 0xF8, 0x31, 0xF8, 0x33, 0x6C, 0x37, 0x6C, 0x37, 0xFE, 0x3F, 0xFF, 0x30, 0x60, 0x30, 0x60,
|
||||||
|
// U+4F59 (余)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x07, 0xC0, 0x0E, 0x70, 0x1C, 0x38, 0x7F, 0xFE, 0xFF, 0xF6, 0x01, 0x80, 0x3F, 0xFC, 0x7F, 0xFE, 0x01, 0x80, 0x1D, 0xB0, 0x19, 0x98, 0x31, 0x8C, 0x67, 0x8E,
|
||||||
|
// U+4F5C (作)
|
||||||
|
0x00, 0x00, 0x0C, 0x80, 0x0C, 0xC0, 0x19, 0x80, 0x19, 0xFF, 0x33, 0xFE, 0x73, 0x60, 0x76, 0x7E, 0x76, 0x7E, 0x30, 0x60, 0x30, 0x60, 0x30, 0x7E, 0x30, 0x7E, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60,
|
||||||
|
// U+4F9B (供)
|
||||||
|
0x00, 0x00, 0x09, 0x98, 0x19, 0x98, 0x19, 0x98, 0x3B, 0xFE, 0x37, 0xFE, 0x71, 0x98, 0xF1, 0x98, 0x71, 0x98, 0x31, 0x98, 0x37, 0xFF, 0x37, 0xFE, 0x31, 0x88, 0x31, 0x9C, 0x33, 0x0C, 0x36, 0x06,
|
||||||
|
// U+4FA7 (侧)
|
||||||
|
0x00, 0x00, 0x10, 0x06, 0x3F, 0xD6, 0x3C, 0x5E, 0x3D, 0x5E, 0x7D, 0x5E, 0x7D, 0x5E, 0xFD, 0x5E, 0x7D, 0x5E, 0x3D, 0x5E, 0x3D, 0x5E, 0x3F, 0x5E, 0x3F, 0x5E, 0x33, 0x86, 0x33, 0xC6, 0x36, 0xCE,
|
||||||
|
// U+4FDD (保)
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0x1F, 0xFE, 0x1B, 0xFE, 0x3B, 0x06, 0x33, 0x06, 0x73, 0xFE, 0xF3, 0xFC, 0x70, 0x60, 0x37, 0xFE, 0x37, 0xFE, 0x30, 0xF0, 0x31, 0xF8, 0x33, 0xEC, 0x3F, 0x6E, 0x3E, 0x62,
|
||||||
|
// U+5012 (倒)
|
||||||
|
0x00, 0x00, 0x10, 0x00, 0x18, 0x06, 0x3F, 0xFE, 0x33, 0x1E, 0x36, 0xDE, 0x77, 0xFE, 0xFF, 0xFE, 0xF1, 0x1E, 0x33, 0x1E, 0x3F, 0xFE, 0x33, 0x1E, 0x33, 0x16, 0x33, 0xE6, 0x3F, 0xC6, 0x38, 0x1E,
|
||||||
|
// U+5074 (側)
|
||||||
|
0x00, 0x00, 0x18, 0x06, 0x1F, 0xC6, 0x36, 0xD6, 0x36, 0xD6, 0x77, 0xD6, 0x76, 0xD6, 0xF6, 0xD6, 0xF7, 0xD6, 0x36, 0xD6, 0x36, 0xD6, 0x36, 0xD6, 0x37, 0xD6, 0x32, 0x96, 0x36, 0xC6, 0x3E, 0xCE,
|
||||||
|
// U+5165 (入)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x03, 0x00, 0x01, 0x80, 0x01, 0xC0, 0x03, 0xC0, 0x03, 0xE0, 0x03, 0x60, 0x06, 0x30, 0x0E, 0x30, 0x0C, 0x18, 0x1C, 0x1C, 0x78, 0x0E, 0x70, 0x06,
|
||||||
|
// U+5173 (关)
|
||||||
|
0x00, 0x00, 0x08, 0x18, 0x0C, 0x38, 0x0C, 0x30, 0x3F, 0xFC, 0x3F, 0xFC, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x03, 0xC0, 0x07, 0x60, 0x0E, 0x70, 0x3C, 0x3C, 0x78, 0x1E,
|
||||||
|
// U+5185 (内)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x61, 0x86, 0x61, 0x86, 0x63, 0xC6, 0x63, 0xE6, 0x67, 0x76, 0x6E, 0x36, 0x7C, 0x1E, 0x68, 0x06, 0x60, 0x06, 0x60, 0x3E,
|
||||||
|
// U+518D (再)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x3F, 0xFC, 0x3F, 0xFC, 0x31, 0x8C, 0x3F, 0xFC, 0x3F, 0xFC, 0x31, 0x8C, 0x7F, 0xFE, 0xFF, 0xFF, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x3C,
|
||||||
|
// U+51D1 (凑)
|
||||||
|
0x00, 0x00, 0x00, 0x40, 0x60, 0xE0, 0x67, 0xFE, 0x30, 0xC0, 0x37, 0xFC, 0x01, 0x80, 0x0F, 0xFE, 0x03, 0xB8, 0x07, 0x1C, 0x3F, 0xFE, 0x34, 0x42, 0x37, 0xFC, 0x61, 0xF0, 0x63, 0xBC, 0x6F, 0x1E,
|
||||||
|
// U+51FA (出)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x11, 0x88, 0x31, 0x8C, 0x31, 0x8C, 0x31, 0x8C, 0x31, 0x8C, 0x3F, 0xFC, 0x3F, 0xFC, 0x21, 0x84, 0x61, 0x8E, 0x61, 0x8E, 0x61, 0x8E, 0x61, 0x8E, 0x7F, 0xFE, 0x7F, 0xFE,
|
||||||
|
// U+5206 (分)
|
||||||
|
0x00, 0x00, 0x04, 0x20, 0x0E, 0x70, 0x0C, 0x30, 0x1C, 0x18, 0x18, 0x1C, 0x30, 0x0E, 0x7F, 0xFE, 0x7F, 0xFA, 0x03, 0x18, 0x06, 0x18, 0x06, 0x18, 0x06, 0x18, 0x0C, 0x18, 0x3C, 0x38, 0x78, 0xF0,
|
||||||
|
// U+5207 (切)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x31, 0xFE, 0x33, 0xFE, 0x30, 0x66, 0x3E, 0x66, 0xFE, 0x66, 0xF0, 0x66, 0x30, 0x66, 0x30, 0xC6, 0x32, 0xC6, 0x3E, 0xC6, 0x3D, 0xC6, 0x31, 0x86, 0x03, 0x8E, 0x07, 0x3C,
|
||||||
|
// U+521B (创)
|
||||||
|
0x00, 0x00, 0x04, 0x06, 0x0C, 0x06, 0x0E, 0x36, 0x1B, 0x36, 0x33, 0xB6, 0x71, 0xB6, 0xFF, 0xB6, 0x7F, 0x36, 0x33, 0x36, 0x33, 0x36, 0x33, 0x36, 0x37, 0x36, 0x30, 0x86, 0x31, 0x86, 0x3F, 0xBE,
|
||||||
|
// U+5220 (删)
|
||||||
|
0x00, 0x00, 0x39, 0xC6, 0x7F, 0xC6, 0x6F, 0xDE, 0x6F, 0xDE, 0x6F, 0xDE, 0x6F, 0xDE, 0x7F, 0xDE, 0xFF, 0xFE, 0x6F, 0xDE, 0x6F, 0xDE, 0x6F, 0xDE, 0x6F, 0xD6, 0x6E, 0xC6, 0x6E, 0xC6, 0x7F, 0xCE,
|
||||||
|
// U+5230 (到)
|
||||||
|
0x00, 0x00, 0x00, 0x06, 0x7F, 0xF6, 0x7F, 0xB6, 0x1B, 0x36, 0x33, 0x36, 0x7F, 0xB6, 0x3F, 0xB6, 0x0C, 0x36, 0x0C, 0x36, 0x7F, 0xB6, 0x3F, 0xB6, 0x0C, 0x36, 0x0F, 0x86, 0x7F, 0x86, 0x78, 0x1E,
|
||||||
|
// U+5237 (刷)
|
||||||
|
0x00, 0x00, 0x00, 0x06, 0x7F, 0xC6, 0x7F, 0xF6, 0x60, 0xF6, 0x7F, 0xF6, 0x7F, 0xB6, 0x66, 0x36, 0x66, 0x36, 0x7F, 0xF6, 0x7E, 0xF6, 0x7E, 0xF6, 0x7E, 0xF6, 0x7E, 0xC6, 0x7F, 0x86, 0xC6, 0x1E,
|
||||||
|
// U+524A (削)
|
||||||
|
0x00, 0x00, 0x0C, 0x06, 0x6D, 0x86, 0x6D, 0xE6, 0x6F, 0x66, 0x2C, 0x66, 0x7F, 0x66, 0x7F, 0x66, 0x63, 0x66, 0x7F, 0x66, 0x63, 0x66, 0x63, 0x66, 0x7F, 0x26, 0x63, 0x06, 0x63, 0x06, 0x67, 0x3E,
|
||||||
|
// U+524D (前)
|
||||||
|
0x00, 0x00, 0x18, 0x18, 0x1C, 0x38, 0x0C, 0x30, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x04, 0x3F, 0x6C, 0x23, 0x6C, 0x3F, 0x6C, 0x33, 0x6C, 0x23, 0x6C, 0x3F, 0x6C, 0x33, 0x6C, 0x23, 0x0C, 0x23, 0x1C,
|
||||||
|
// U+526A (剪)
|
||||||
|
0x00, 0x00, 0x0C, 0x30, 0x0C, 0x30, 0x7F, 0xFE, 0x00, 0x0C, 0x3F, 0x6C, 0x3F, 0x6C, 0x3F, 0x6C, 0x3F, 0x6C, 0x33, 0x0C, 0x37, 0x1C, 0x00, 0x00, 0x7F, 0xFC, 0x03, 0x0C, 0x06, 0x0C, 0x3E, 0x1C,
|
||||||
|
// U+52A0 (加)
|
||||||
|
0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x7E, 0x7F, 0x7E, 0x7F, 0x66, 0x1B, 0x66, 0x1B, 0x66, 0x1B, 0x66, 0x33, 0x66, 0x33, 0x66, 0x33, 0x66, 0x33, 0x66, 0x33, 0x66, 0x67, 0x7E, 0x7E, 0x7E,
|
||||||
|
// U+52A8 (动)
|
||||||
|
0x00, 0x00, 0x00, 0x30, 0x3E, 0x30, 0x7F, 0x30, 0x00, 0x30, 0x00, 0xFE, 0x7F, 0xFE, 0x7F, 0x36, 0x18, 0x36, 0x32, 0x36, 0x32, 0x66, 0x33, 0x66, 0x7F, 0x66, 0x7F, 0xC6, 0x60, 0xC6, 0x01, 0xBC,
|
||||||
|
// U+52B9 (効)
|
||||||
|
0x00, 0x00, 0x18, 0x30, 0x0C, 0x30, 0x7F, 0x30, 0x7F, 0xB0, 0x12, 0xFE, 0x33, 0x7E, 0x73, 0x36, 0x67, 0xB6, 0x77, 0x36, 0x1E, 0x36, 0x0C, 0x26, 0x0E, 0x66, 0x1E, 0x66, 0x32, 0xC6, 0x71, 0xDE,
|
||||||
|
// U+52D5 (動)
|
||||||
|
0x00, 0x00, 0x0F, 0x30, 0x7F, 0x30, 0x0C, 0x30, 0xFF, 0xB0, 0x0C, 0xFE, 0x7F, 0xFE, 0x6D, 0xB6, 0x7F, 0xB6, 0x6D, 0xB6, 0x7F, 0xE6, 0x0C, 0x66, 0x7F, 0xE6, 0x0C, 0xE6, 0x1F, 0xC6, 0x7F, 0xFC,
|
||||||
|
// U+5316 (化)
|
||||||
|
0x00, 0x00, 0x0C, 0xC0, 0x0C, 0xC0, 0x1C, 0xC0, 0x18, 0xC6, 0x38, 0xCE, 0x38, 0xDC, 0x78, 0xF8, 0x78, 0xF0, 0x18, 0xE0, 0x1B, 0xC0, 0x1F, 0xC2, 0x1A, 0xC3, 0x18, 0xC2, 0x18, 0xC6, 0x18, 0xFE,
|
||||||
|
// U+53C2 (参)
|
||||||
|
0x00, 0x00, 0x03, 0x00, 0x07, 0x70, 0x1C, 0x38, 0x3F, 0xFC, 0x1F, 0x0C, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x30, 0x1B, 0xD8, 0x7F, 0x0E, 0x64, 0xE6, 0x0F, 0xD8, 0x0E, 0x38, 0x01, 0xF0, 0x1F, 0xC0,
|
||||||
|
// U+53CD (反)
|
||||||
|
0x00, 0x00, 0x00, 0x18, 0x1F, 0xFC, 0x3F, 0xF0, 0x30, 0x00, 0x30, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x36, 0x18, 0x33, 0x18, 0x33, 0x30, 0x31, 0xF0, 0x31, 0xE0, 0x31, 0xF0, 0x67, 0xFC, 0x6E, 0x1E,
|
||||||
|
// U+53D6 (取)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x7F, 0xFE, 0x26, 0xFE, 0x36, 0x66, 0x3E, 0x66, 0x26, 0x6C, 0x26, 0x6C, 0x3E, 0x3C, 0x3E, 0x38, 0x26, 0x38, 0x3F, 0xB8, 0x7F, 0x3C, 0x66, 0x6E, 0x06, 0xC6,
|
||||||
|
// U+53EF (可)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x18, 0x00, 0x18, 0x3F, 0x98, 0x3F, 0x98, 0x31, 0x98, 0x31, 0x98, 0x31, 0x98, 0x3F, 0x98, 0x3F, 0x98, 0x30, 0x18, 0x00, 0x18, 0x00, 0xF8,
|
||||||
|
// U+53F3 (右)
|
||||||
|
0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x0E, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x1F, 0xFC, 0x3F, 0xFC, 0x7C, 0x0C, 0xEC, 0x0C, 0x4C, 0x0C, 0x0F, 0xFC, 0x0F, 0xFC,
|
||||||
|
// U+53F7 (号)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x1F, 0xF8, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x00, 0x1F, 0xF8, 0x1F, 0xF8, 0x00, 0x18, 0x00, 0x18, 0x01, 0xF8,
|
||||||
|
// U+5411 (向)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x03, 0x80, 0x03, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x60, 0x06, 0x67, 0xE6, 0x67, 0xE6, 0x66, 0x26, 0x66, 0x26, 0x66, 0x26, 0x67, 0xE6, 0x67, 0xE6, 0x64, 0x06, 0x60, 0x3E,
|
||||||
|
// U+5426 (否)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x03, 0x80, 0x07, 0xB0, 0x1F, 0xB8, 0x7D, 0x9E, 0x71, 0x86, 0x41, 0x80, 0x1F, 0xF8, 0x1F, 0xFC, 0x18, 0x1C, 0x18, 0x1C, 0x1F, 0xFC, 0x1F, 0xFC,
|
||||||
|
// U+542F (启)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x1F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x00, 0x3F, 0xFE, 0x3F, 0xFE, 0x3C, 0x06, 0x7C, 0x06, 0x6F, 0xFE, 0x6F, 0xFE,
|
||||||
|
// U+5668 (器)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3E, 0xFC, 0x3E, 0xFC, 0x26, 0xCC, 0x3E, 0xFC, 0x3F, 0x7C, 0x03, 0x38, 0x7F, 0xFE, 0x7F, 0xFE, 0x1C, 0x38, 0x7E, 0x7E, 0x7F, 0xFE, 0x33, 0xCC, 0x33, 0xCC, 0x3F, 0xFC,
|
||||||
|
// U+56DE (回)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x60, 0x06, 0x67, 0xE6, 0x67, 0xE6, 0x64, 0x66, 0x64, 0x66, 0x64, 0x66, 0x67, 0xE6, 0x67, 0xE6, 0x60, 0x06, 0x60, 0x06, 0x7F, 0xFE, 0x7F, 0xFE,
|
||||||
|
// U+56F2 (囲)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x66, 0x66, 0x66, 0x66, 0x7F, 0xFE, 0x66, 0x66, 0x66, 0x66, 0x7F, 0xFE, 0x66, 0x66, 0x66, 0x66, 0x6C, 0x66, 0x6C, 0x66, 0x7F, 0xFE, 0x7F, 0xFE,
|
||||||
|
// U+56F4 (围)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x61, 0x86, 0x7F, 0xFE, 0x61, 0x86, 0x6F, 0xF6, 0x61, 0x86, 0x61, 0x86, 0x7F, 0xFE, 0x61, 0x9E, 0x61, 0xF6, 0x61, 0x86, 0x61, 0x86, 0x7F, 0xFE,
|
||||||
|
// U+56FD (国)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x6F, 0xF6, 0x6F, 0xF6, 0x61, 0x86, 0x61, 0x86, 0x6F, 0xF6, 0x67, 0xE6, 0x61, 0xA6, 0x61, 0xB6, 0x7F, 0xFE, 0x60, 0x06, 0x7F, 0xFE, 0x7F, 0xFE,
|
||||||
|
// U+5728 (在)
|
||||||
|
0x00, 0x00, 0x03, 0x00, 0x07, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x00, 0x0C, 0x60, 0x18, 0x60, 0x38, 0x60, 0x7B, 0xFE, 0xFB, 0xFC, 0x58, 0x60, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x1F, 0xFE,
|
||||||
|
// U+5730 (地)
|
||||||
|
0x00, 0x00, 0x10, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x3E, 0xFD, 0x3E, 0x7D, 0xF6, 0x37, 0xF6, 0x37, 0xB6, 0x33, 0x36, 0x35, 0x36, 0x3D, 0x3E, 0x79, 0x30, 0x61, 0x03, 0x01, 0x86, 0x01, 0xFE,
|
||||||
|
// U+5740 (址)
|
||||||
|
0x00, 0x00, 0x10, 0x30, 0x10, 0x30, 0x10, 0x30, 0x11, 0x30, 0xFF, 0xB0, 0x7D, 0xB0, 0x11, 0xBE, 0x11, 0xBE, 0x11, 0xB0, 0x1D, 0xB0, 0x3F, 0xB0, 0x79, 0xB0, 0x61, 0xB0, 0x07, 0xFE, 0x07, 0xFF,
|
||||||
|
// U+5907 (备)
|
||||||
|
0x00, 0x00, 0x06, 0x00, 0x07, 0xF0, 0x0F, 0xF8, 0x3C, 0x30, 0x7E, 0xE0, 0x23, 0xC0, 0x0F, 0xF8, 0xFE, 0x7F, 0x7F, 0xFE, 0x3F, 0xF8, 0x31, 0x98, 0x3F, 0xF8, 0x39, 0x98, 0x31, 0x98, 0x3F, 0xF8,
|
||||||
|
// U+5916 (外)
|
||||||
|
0x00, 0x00, 0x18, 0x60, 0x18, 0x60, 0x1F, 0x60, 0x3F, 0xE0, 0x33, 0x60, 0x33, 0x70, 0x63, 0x78, 0x7B, 0x7C, 0x1E, 0x66, 0x0E, 0x66, 0x0E, 0x60, 0x0C, 0x60, 0x18, 0x60, 0x78, 0x60, 0x70, 0x60,
|
||||||
|
// U+5927 (大)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x03, 0xC0, 0x03, 0xC0, 0x07, 0xE0, 0x06, 0x60, 0x0E, 0x70, 0x1C, 0x38, 0x38, 0x1C, 0x70, 0x0E,
|
||||||
|
// U+592E (央)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x1F, 0xF8, 0x3F, 0xFC, 0x31, 0x8C, 0x31, 0x8C, 0x31, 0x8C, 0x31, 0x8C, 0x7F, 0xFE, 0x7F, 0xFE, 0x03, 0xC0, 0x07, 0x60, 0x0E, 0x70, 0x3C, 0x3C, 0x78, 0x1F,
|
||||||
|
// U+5931 (失)
|
||||||
|
0x00, 0x00, 0x19, 0x80, 0x19, 0x80, 0x19, 0x80, 0x3F, 0xFC, 0x3F, 0xFC, 0x61, 0x80, 0x61, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x03, 0xC0, 0x03, 0xC0, 0x07, 0x60, 0x0E, 0x70, 0x3C, 0x3C, 0x78, 0x1F,
|
||||||
|
// U+59CB (始)
|
||||||
|
0x00, 0x00, 0x10, 0x60, 0x30, 0x60, 0x30, 0x60, 0x7C, 0xCC, 0xFC, 0xCC, 0x25, 0xFE, 0x6F, 0xFE, 0x6D, 0xC3, 0x6C, 0x00, 0x7D, 0xFE, 0x39, 0xFE, 0x1D, 0x86, 0x3D, 0x86, 0x35, 0xFE, 0x61, 0xFE,
|
||||||
|
// U+5B57 (字)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x60, 0x06, 0x6F, 0xF6, 0x0F, 0xF0, 0x00, 0xE0, 0x01, 0xC0, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x07, 0x80,
|
||||||
|
// U+5B58 (存)
|
||||||
|
0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x00, 0x0D, 0xFC, 0x19, 0xFC, 0x38, 0x18, 0x78, 0x30, 0xF8, 0x60, 0x5F, 0xFF, 0x1B, 0xFE, 0x18, 0x60, 0x18, 0x60, 0x19, 0xE0,
|
||||||
|
// U+5B8C (完)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x60, 0x06, 0x7F, 0xF6, 0x0F, 0xF0, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x06, 0x60, 0x0E, 0x60, 0x0C, 0x62, 0x1C, 0x66, 0x78, 0x7E,
|
||||||
|
// U+5B9A (定)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x60, 0x06, 0x6F, 0xF6, 0x1F, 0xF8, 0x01, 0x80, 0x19, 0x80, 0x19, 0xF8, 0x19, 0xFC, 0x19, 0x80, 0x3D, 0x80, 0x37, 0x80, 0x63, 0xFE,
|
||||||
|
// U+5BBD (宽)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x3F, 0xFC, 0x7F, 0xFE, 0x66, 0x66, 0x7F, 0xFE, 0x1F, 0xF8, 0x06, 0x60, 0x1F, 0xF8, 0x1B, 0x98, 0x19, 0x98, 0x19, 0x98, 0x1B, 0xD8, 0x1B, 0xDA, 0x0E, 0xC2, 0x7C, 0xFE,
|
||||||
|
// U+5BC6 (密)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x3F, 0xFE, 0x7F, 0xFE, 0x63, 0x06, 0x63, 0xB6, 0x35, 0xF8, 0x35, 0xCC, 0x67, 0x9E, 0x6F, 0x32, 0x7F, 0xF0, 0x71, 0x80, 0x31, 0x8C, 0x31, 0x8C, 0x3F, 0xFC, 0x3F, 0xFC,
|
||||||
|
// U+5BF9 (对)
|
||||||
|
0x00, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x7E, 0x0C, 0x7E, 0x0C, 0x47, 0xFF, 0x66, 0xFE, 0x3C, 0x0C, 0x3C, 0xCC, 0x1C, 0xCC, 0x1C, 0x6C, 0x1C, 0x6C, 0x36, 0x0C, 0x76, 0x0C, 0x60, 0x0C, 0x40, 0x7C,
|
||||||
|
// U+5C01 (封)
|
||||||
|
0x00, 0x00, 0x08, 0x0C, 0x08, 0x0C, 0x7F, 0x0C, 0x7E, 0x0C, 0x08, 0xFF, 0x7F, 0xFE, 0x7F, 0x0C, 0x08, 0x4C, 0x08, 0x6C, 0x7F, 0x6C, 0x7F, 0x3C, 0x08, 0x2C, 0x08, 0x0C, 0x7F, 0x8C, 0xFF, 0x3C,
|
||||||
|
// U+5C06 (将)
|
||||||
|
0x00, 0x00, 0x18, 0x60, 0x18, 0x60, 0x19, 0xFE, 0x7B, 0x8C, 0x7B, 0xDC, 0x38, 0x78, 0x19, 0xF8, 0x1B, 0xCC, 0x1B, 0xFE, 0x3F, 0xFE, 0x79, 0x0C, 0x79, 0x8C, 0x19, 0xCC, 0x18, 0xCC, 0x18, 0x3C,
|
||||||
|
// U+5C0F (小)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x19, 0x98, 0x19, 0x98, 0x31, 0x8C, 0x31, 0x8C, 0x31, 0x86, 0x71, 0x86, 0x61, 0x86, 0x61, 0x83, 0x01, 0x80, 0x01, 0x80, 0x0F, 0x80,
|
||||||
|
// U+5C40 (局)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xF8, 0x30, 0x00, 0x3F, 0xFE, 0x3F, 0xFE, 0x30, 0x06, 0x37, 0xE6, 0x66, 0x66, 0x66, 0x66, 0x67, 0xEC, 0xC6, 0x3C,
|
||||||
|
// U+5C45 (居)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0xC0, 0x3F, 0xFE, 0x3F, 0xFE, 0x30, 0xC0, 0x37, 0xFC, 0x37, 0xFC, 0x64, 0x0C, 0x64, 0x0C, 0x67, 0xFC,
|
||||||
|
// U+5C4F (屏)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x3F, 0xFE, 0x30, 0x0E, 0x3F, 0xFE, 0x32, 0x18, 0x33, 0x18, 0x3F, 0xFE, 0x33, 0x38, 0x33, 0x10, 0x33, 0x38, 0x3F, 0xFE, 0x63, 0x10, 0x66, 0x10, 0xEE, 0x10,
|
||||||
|
// U+5DE6 (左)
|
||||||
|
0x00, 0x00, 0x02, 0x00, 0x06, 0x00, 0x06, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x00, 0x0C, 0x00, 0x0F, 0xFC, 0x1F, 0xFE, 0x18, 0x60, 0x18, 0x60, 0x30, 0x60, 0x70, 0x60, 0xE0, 0x60, 0x4F, 0xFE,
|
||||||
|
// U+5DF2 (已)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xF8, 0x3F, 0xF8, 0x00, 0x18, 0x30, 0x18, 0x30, 0x18, 0x30, 0x18, 0x3F, 0xF8, 0x3F, 0xF8, 0x30, 0x08, 0x30, 0x00, 0x30, 0x06, 0x30, 0x06, 0x30, 0x06, 0x3F, 0xFE,
|
||||||
|
// U+5E03 (布)
|
||||||
|
0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x00, 0x0C, 0xC0, 0x18, 0xC0, 0x3F, 0xFC, 0x7F, 0xFC, 0xF8, 0xCC, 0x58, 0xCC, 0x18, 0xCC, 0x18, 0xDC, 0x18, 0xFC, 0x00, 0xC0,
|
||||||
|
// U+5E38 (常)
|
||||||
|
0x00, 0x00, 0x11, 0x88, 0x19, 0x98, 0x3F, 0xFC, 0x7F, 0xFE, 0x60, 0x06, 0x6F, 0xF6, 0x6C, 0x36, 0x0C, 0x30, 0x0F, 0xF0, 0x01, 0x80, 0x3F, 0xFC, 0x3F, 0xFC, 0x31, 0x8C, 0x31, 0xBC, 0x31, 0xBC,
|
||||||
|
// U+5E55 (幕)
|
||||||
|
0x00, 0x00, 0x0C, 0x30, 0x7F, 0xFE, 0x0E, 0x70, 0x3F, 0xFC, 0x30, 0x0C, 0x3F, 0xFC, 0x30, 0x0C, 0x3F, 0xFC, 0x06, 0x00, 0x7F, 0xFE, 0x1D, 0xB8, 0x39, 0x9C, 0x7F, 0xFE, 0x19, 0x98, 0x19, 0xB8,
|
||||||
|
// U+5E83 (広)
|
||||||
|
0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x3F, 0xFE, 0x3F, 0xFE, 0x30, 0x00, 0x30, 0xC0, 0x31, 0xC0, 0x31, 0x80, 0x31, 0x80, 0x33, 0x30, 0x33, 0x18, 0x36, 0x1C, 0x66, 0x0C, 0x6F, 0xFE, 0x6F, 0xF6,
|
||||||
|
// U+5E93 (库)
|
||||||
|
0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x3F, 0xFE, 0x3F, 0xFE, 0x31, 0x80, 0x3F, 0xFE, 0x3F, 0xFC, 0x33, 0x60, 0x36, 0x60, 0x27, 0xFC, 0x67, 0xFC, 0x60, 0x60, 0x6F, 0xFE, 0x6F, 0xFE, 0xE0, 0x60,
|
||||||
|
// U+5E94 (应)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0xC0, 0x3F, 0xFE, 0x3F, 0xFE, 0x30, 0x00, 0x31, 0x8C, 0x3C, 0xCC, 0x3C, 0xCC, 0x36, 0xCC, 0x36, 0xD8, 0x66, 0xD8, 0x62, 0x30, 0x60, 0x30, 0x6F, 0xFE, 0x7F, 0xFF,
|
||||||
|
// U+5EA6 (度)
|
||||||
|
0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x3F, 0xFE, 0x3F, 0xFE, 0x33, 0x18, 0x3F, 0xFE, 0x33, 0x18, 0x33, 0x18, 0x33, 0xF8, 0x30, 0x00, 0x2F, 0xFC, 0x67, 0x18, 0x63, 0xB8, 0x61, 0xF0, 0x7F, 0xFE,
|
||||||
|
// U+5EFA (建)
|
||||||
|
0x00, 0x00, 0x00, 0x60, 0x7C, 0x60, 0x7B, 0xFC, 0x18, 0x6C, 0x17, 0xFE, 0x30, 0x6C, 0x3F, 0xFC, 0x3C, 0x60, 0x08, 0x60, 0x7B, 0xFE, 0x78, 0x60, 0x3F, 0xFE, 0x38, 0x60, 0x7F, 0x60, 0x67, 0xFE,
|
||||||
|
// U+5F00 (开)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x7F, 0xFE, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x30, 0x0C, 0x30, 0x1C, 0x30, 0x18, 0x30, 0x38, 0x30, 0x70, 0x30,
|
||||||
|
// U+5F0F (式)
|
||||||
|
0x00, 0x00, 0x00, 0x68, 0x00, 0x6C, 0x00, 0x64, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x60, 0x00, 0x60, 0x7F, 0xE0, 0x3F, 0x60, 0x0C, 0x60, 0x0C, 0x30, 0x0C, 0x32, 0x0D, 0xB3, 0x3F, 0xBB, 0x7E, 0x1E,
|
||||||
|
// U+5F15 (引)
|
||||||
|
0x00, 0x00, 0x00, 0x0C, 0x3F, 0x8C, 0x3F, 0x8C, 0x01, 0x8C, 0x3F, 0x8C, 0x3F, 0x8C, 0x30, 0x0C, 0x30, 0x0C, 0x7F, 0x8C, 0x7F, 0x8C, 0x01, 0x8C, 0x01, 0x8C, 0x01, 0x8C, 0x03, 0x8C, 0x1F, 0x0C,
|
||||||
|
// U+5F53 (当)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x31, 0x8C, 0x39, 0x8C, 0x19, 0x98, 0x19, 0x98, 0x01, 0x80, 0x3F, 0xFC, 0x3F, 0xFC, 0x00, 0x0C, 0x00, 0x0C, 0x3F, 0xFC, 0x1F, 0xFC, 0x00, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC,
|
||||||
|
// U+5FD8 (忘)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x18, 0x00, 0x18, 0x00, 0x1F, 0xFC, 0x1F, 0xFC, 0x01, 0x00, 0x01, 0x80, 0x2D, 0xCC, 0x3C, 0xCE, 0x6C, 0x1E, 0x6C, 0x1E, 0x4F, 0xF2,
|
||||||
|
// U+5FFD (忽)
|
||||||
|
0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x1F, 0xFC, 0x3F, 0xFC, 0x76, 0x6C, 0x66, 0xCC, 0x0C, 0xCC, 0x39, 0x8C, 0x33, 0xBC, 0x03, 0x38, 0x2D, 0x80, 0x6D, 0xCC, 0x6C, 0xDE, 0x6C, 0x36, 0x4F, 0xF0,
|
||||||
|
// U+6001 (态)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x03, 0xC0, 0x03, 0x60, 0x06, 0x70, 0x1F, 0x38, 0x79, 0xDE, 0x71, 0xCE, 0x0D, 0x88, 0x3D, 0xCC, 0x2C, 0xDE, 0x6C, 0x1E, 0x6F, 0xF6,
|
||||||
|
// U+610F (意)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x3F, 0xFC, 0x0C, 0x30, 0x0C, 0x30, 0x7F, 0xFE, 0x00, 0x00, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x03, 0x80, 0x3D, 0xCC, 0x3C, 0xBC, 0x6E, 0x36,
|
||||||
|
// U+6210 (成)
|
||||||
|
0x00, 0x00, 0x00, 0xD0, 0x00, 0xDC, 0x00, 0xCC, 0x3F, 0xFE, 0x3F, 0xFE, 0x30, 0x60, 0x3E, 0x6C, 0x3F, 0x6C, 0x33, 0x7C, 0x33, 0x78, 0x33, 0x78, 0x73, 0x72, 0x6E, 0xF2, 0x6D, 0xFE, 0x63, 0x9E,
|
||||||
|
// U+6216 (或)
|
||||||
|
0x00, 0x00, 0x00, 0x58, 0x00, 0xFC, 0x00, 0xE4, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x64, 0x3F, 0x6C, 0x33, 0x6C, 0x33, 0x7C, 0x33, 0x78, 0x3F, 0x78, 0x00, 0x32, 0x1F, 0x73, 0x7F, 0xFA, 0x71, 0xDE,
|
||||||
|
// U+623B (戻)
|
||||||
|
0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x01, 0x80, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x30, 0xC0, 0x30, 0xC0, 0x3F, 0xFC, 0x3F, 0xFE, 0x61, 0xE0, 0x61, 0xE0, 0x67, 0x38, 0xDE, 0x1E,
|
||||||
|
// U+624B (手)
|
||||||
|
0x00, 0x00, 0x00, 0x18, 0x3F, 0xFC, 0x3F, 0xE0, 0x01, 0x80, 0x01, 0x80, 0x3F, 0xFE, 0x3F, 0xFC, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x0F, 0x80,
|
||||||
|
// U+6253 (打)
|
||||||
|
0x00, 0x00, 0x18, 0x00, 0x19, 0xFE, 0x1B, 0xFF, 0x7C, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x18, 0x7E, 0x18, 0x78, 0x18, 0x58, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0xF8,
|
||||||
|
// U+626B (扫)
|
||||||
|
0x00, 0x00, 0x18, 0x00, 0x19, 0xFC, 0x19, 0xFE, 0x7C, 0x06, 0x7E, 0x06, 0x18, 0x06, 0x18, 0x06, 0x1F, 0xFE, 0x7F, 0xFE, 0x78, 0x06, 0x58, 0x06, 0x18, 0x06, 0x18, 0x06, 0x1B, 0xFE, 0x79, 0xFE,
|
||||||
|
// U+627E (找)
|
||||||
|
0x00, 0x00, 0x18, 0xC0, 0x18, 0xD8, 0x18, 0xCC, 0x7C, 0xCC, 0x7F, 0xFE, 0x1B, 0xFE, 0x18, 0x64, 0x1C, 0x6C, 0x7E, 0x6C, 0xF8, 0x78, 0x18, 0x78, 0x18, 0x72, 0x18, 0xF3, 0x19, 0xFA, 0x73, 0x9E,
|
||||||
|
// U+6297 (抗)
|
||||||
|
0x00, 0x00, 0x18, 0x60, 0x18, 0x60, 0x18, 0x20, 0x7F, 0xFF, 0x7F, 0xFE, 0x18, 0x00, 0x19, 0xF8, 0x1D, 0xF8, 0x7D, 0x98, 0xF9, 0x98, 0x19, 0x98, 0x19, 0x9A, 0x19, 0x9B, 0x1B, 0x1B, 0x77, 0x0E,
|
||||||
|
// U+629E (択)
|
||||||
|
0x00, 0x00, 0x18, 0x00, 0x19, 0xFE, 0x19, 0xFE, 0x7D, 0x86, 0x7F, 0x86, 0x19, 0x86, 0x19, 0xFE, 0x1F, 0xFE, 0x7F, 0xB0, 0xF9, 0xB0, 0x59, 0x98, 0x1B, 0x98, 0x1B, 0x1C, 0x1B, 0x0C, 0x7E, 0x07,
|
||||||
|
// U+62BC (押)
|
||||||
|
0x00, 0x00, 0x10, 0x00, 0x33, 0xFE, 0x33, 0xFE, 0x7F, 0x36, 0x7F, 0x36, 0x33, 0xFE, 0x33, 0xFE, 0x33, 0x36, 0x3F, 0xFE, 0x7B, 0xFE, 0x73, 0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x70, 0x30,
|
||||||
|
// U+62E9 (择)
|
||||||
|
0x00, 0x00, 0x10, 0x00, 0x33, 0xFE, 0x31, 0xFC, 0x7C, 0xDC, 0x7C, 0x78, 0x30, 0xF8, 0x33, 0xFE, 0x3F, 0xAE, 0x3C, 0x20, 0xFB, 0xFC, 0x71, 0xFC, 0x33, 0xFE, 0x33, 0xFF, 0x30, 0x20, 0x70, 0x20,
|
||||||
|
// U+6309 (按)
|
||||||
|
0x00, 0x00, 0x10, 0x60, 0x10, 0x70, 0x13, 0xFE, 0x7F, 0xFE, 0xFF, 0x66, 0x13, 0x62, 0x13, 0xFE, 0x17, 0xFF, 0x3D, 0xCC, 0xFD, 0x8C, 0xF1, 0x98, 0x11, 0xF8, 0x10, 0x78, 0x10, 0xFC, 0x73, 0xCE,
|
||||||
|
// U+6357 (捗)
|
||||||
|
0x00, 0x00, 0x30, 0x20, 0x31, 0x20, 0x31, 0x3E, 0x79, 0x30, 0xF9, 0x20, 0x37, 0xFF, 0x33, 0xFE, 0x39, 0xE4, 0x7B, 0x64, 0xF3, 0x6C, 0x36, 0x6C, 0x30, 0x18, 0x30, 0x30, 0x31, 0xE0, 0x73, 0xC0,
|
||||||
|
// U+6362 (换)
|
||||||
|
0x00, 0x00, 0x30, 0xC0, 0x30, 0xC8, 0x31, 0xFC, 0x7B, 0x98, 0x7F, 0x18, 0x33, 0xFC, 0x33, 0x6C, 0x33, 0x6C, 0x3F, 0x6C, 0xFF, 0xFE, 0x77, 0xFE, 0x30, 0xF0, 0x31, 0xD8, 0x33, 0x9C, 0x77, 0x06,
|
||||||
|
// U+63A5 (接)
|
||||||
|
0x00, 0x00, 0x30, 0x60, 0x30, 0x60, 0x33, 0xFE, 0x79, 0x9C, 0xFD, 0x98, 0x31, 0xF8, 0x37, 0xFE, 0x38, 0x60, 0x7C, 0xE0, 0xF7, 0xFF, 0x31, 0x8C, 0x33, 0x98, 0x33, 0xF8, 0x30, 0xF8, 0x73, 0xFE,
|
||||||
|
// U+63C3 (揃)
|
||||||
|
0x00, 0x00, 0x31, 0x0C, 0x31, 0x8C, 0x30, 0x98, 0x7F, 0xFF, 0x7F, 0xFE, 0x30, 0x06, 0x33, 0xDE, 0x3E, 0x5E, 0x7F, 0xDE, 0x72, 0x5E, 0x32, 0x5E, 0x33, 0xDE, 0x32, 0x56, 0x32, 0x46, 0x72, 0xCE,
|
||||||
|
// U+63CF (描)
|
||||||
|
0x00, 0x00, 0x30, 0x88, 0x30, 0x88, 0x33, 0xFE, 0x7F, 0xFE, 0xFC, 0x88, 0x30, 0x88, 0x31, 0xFC, 0x3B, 0xFE, 0x7F, 0x26, 0xF3, 0x26, 0x33, 0xFE, 0x33, 0xFE, 0x33, 0x26, 0x33, 0xFE, 0x73, 0xFE,
|
||||||
|
// U+6557 (敗)
|
||||||
|
0x00, 0x00, 0x00, 0x60, 0x7E, 0x60, 0x66, 0x60, 0x66, 0xFE, 0x7E, 0xFF, 0x67, 0xCC, 0x67, 0xCC, 0x7F, 0xEC, 0x67, 0x7C, 0x66, 0x38, 0x7E, 0x38, 0x34, 0x38, 0x36, 0x7C, 0x67, 0xEE, 0x63, 0xC7,
|
||||||
|
// U+6574 (整)
|
||||||
|
0x00, 0x00, 0x0C, 0x20, 0x7F, 0x60, 0x0C, 0x7E, 0x7F, 0xEC, 0x6F, 0xEC, 0x7F, 0x38, 0x1E, 0x38, 0x7F, 0xFE, 0x68, 0xC6, 0x3F, 0xFC, 0x01, 0x80, 0x19, 0x80, 0x19, 0xF8, 0x19, 0x80, 0x7F, 0xFE,
|
||||||
|
// U+6587 (文)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x38, 0x0C, 0x30, 0x0E, 0x70, 0x06, 0x60, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x0F, 0xF0, 0x3C, 0x3C, 0x78, 0x1E,
|
||||||
|
// U+65B0 (新)
|
||||||
|
0x00, 0x00, 0x18, 0x04, 0x18, 0x7E, 0x7F, 0x78, 0x37, 0x40, 0x36, 0x40, 0x36, 0xC0, 0x7F, 0xFF, 0x08, 0x7E, 0x1C, 0xCC, 0x7F, 0xCC, 0x2E, 0xCC, 0x6F, 0xCC, 0x6B, 0xCC, 0x49, 0xCC, 0x39, 0x8C,
|
||||||
|
// U+65B9 (方)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x06, 0x00, 0x06, 0x00, 0x07, 0xFC, 0x07, 0xFC, 0x0C, 0x0C, 0x0C, 0x18, 0x1C, 0x18, 0x18, 0x18, 0x38, 0x18, 0x71, 0xF8,
|
||||||
|
// U+65E0 (无)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x03, 0xC0, 0x03, 0xC0, 0x06, 0xC0, 0x0E, 0xC2, 0x1C, 0xC3, 0x38, 0xC6, 0x70, 0xFE,
|
||||||
|
// U+65E2 (既)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7E, 0xFE, 0x7E, 0xFE, 0x66, 0x58, 0x7E, 0xD8, 0x66, 0xD0, 0x66, 0xD0, 0x7F, 0xFE, 0x7E, 0xFE, 0x64, 0x38, 0x66, 0x78, 0x6E, 0x78, 0x7F, 0xFA, 0x7B, 0xDA, 0x61, 0x9E,
|
||||||
|
// U+65E5 (日)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC,
|
||||||
|
// U+65F6 (时)
|
||||||
|
0x00, 0x00, 0x00, 0x0C, 0x7C, 0x0C, 0x7E, 0x0C, 0x67, 0xFE, 0x67, 0xFF, 0x66, 0x0C, 0x7E, 0x0C, 0x7F, 0xCC, 0x66, 0xCC, 0x66, 0x6C, 0x66, 0x6C, 0x7E, 0x0C, 0x7C, 0x0C, 0x60, 0x0C, 0x00, 0x7C,
|
||||||
|
// U+662F (是)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x30, 0x0C, 0x3F, 0xFC, 0x30, 0x0C, 0x3F, 0xFC, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x09, 0x80, 0x19, 0xFC, 0x19, 0xFC, 0x3D, 0x80, 0x3F, 0x80, 0x67, 0xFE,
|
||||||
|
// U+663E (显)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x38, 0x1C, 0x38, 0x1C, 0x3F, 0xFC, 0x30, 0x0C, 0x38, 0x1C, 0x3F, 0xFC, 0x02, 0x40, 0x36, 0x6C, 0x36, 0x6C, 0x1E, 0x78, 0x1E, 0x78, 0x7F, 0xFE, 0xFF, 0xFF,
|
||||||
|
// U+6642 (時)
|
||||||
|
0x00, 0x00, 0x00, 0x30, 0x7C, 0x30, 0x7D, 0xFE, 0x6D, 0xFC, 0x6C, 0x30, 0x6F, 0xFE, 0x7F, 0xFF, 0x7C, 0x0C, 0x6D, 0xFE, 0x6F, 0xFE, 0x6D, 0x8C, 0x7D, 0xCC, 0x7C, 0xCC, 0x60, 0xCC, 0x00, 0x3C,
|
||||||
|
// U+666E (普)
|
||||||
|
0x00, 0x00, 0x0C, 0x30, 0x0C, 0x30, 0x7F, 0xFE, 0x37, 0xEC, 0x36, 0x48, 0x1E, 0x58, 0x1F, 0xF8, 0x7F, 0xFE, 0x00, 0x00, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x1F, 0xF8,
|
||||||
|
// U+6697 (暗)
|
||||||
|
0x00, 0x00, 0x00, 0x20, 0x78, 0x30, 0x7F, 0xFE, 0x6D, 0xFC, 0x6C, 0x8C, 0x6C, 0xCC, 0x7F, 0xFF, 0x7F, 0xFE, 0x6D, 0xFE, 0x6D, 0x8E, 0x6D, 0x86, 0x7D, 0xFE, 0x7D, 0x86, 0x61, 0x8E, 0x01, 0xFE,
|
||||||
|
// U+66F4 (更)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x3F, 0xFC, 0x31, 0x8C, 0x31, 0x8C, 0x3F, 0xFC, 0x31, 0x8C, 0x3F, 0xFC, 0x1B, 0x80, 0x1F, 0x00, 0x0F, 0x00, 0x1F, 0xE0, 0x7D, 0xFF,
|
||||||
|
// U+66F8 (書)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x3F, 0xF8, 0x01, 0x98, 0x7F, 0xFE, 0x01, 0x98, 0x3F, 0xF8, 0x01, 0x80, 0x3F, 0xFC, 0x01, 0x80, 0x7F, 0xFE, 0x1F, 0xF8, 0x1F, 0xFC, 0x1F, 0xFC, 0x18, 0x0C, 0x1F, 0xFC,
|
||||||
|
// U+66FF (替)
|
||||||
|
0x00, 0x00, 0x08, 0x30, 0x1C, 0x30, 0x7E, 0xFE, 0x18, 0x30, 0x7F, 0xFE, 0x1C, 0x78, 0x3E, 0x6C, 0x77, 0xEE, 0x77, 0xCE, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x1F, 0xF8,
|
||||||
|
// U+6700 (最)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x10, 0x18, 0x1F, 0xF8, 0x10, 0x18, 0x1F, 0xF8, 0x00, 0x00, 0x7F, 0xFE, 0x33, 0x00, 0x3F, 0xFE, 0x33, 0x6C, 0x3F, 0x6C, 0x33, 0x38, 0x7F, 0xBC, 0x7F, 0xFE,
|
||||||
|
// U+6709 (有)
|
||||||
|
0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x00, 0x0F, 0xF8, 0x1F, 0xF8, 0x3C, 0x18, 0x7C, 0x18, 0x6F, 0xF8, 0x0C, 0x18, 0x0F, 0xF8, 0x0C, 0x18, 0x0C, 0x18, 0x0C, 0x78,
|
||||||
|
// U+672A (未)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x3F, 0xFC, 0x3F, 0xFC, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x07, 0xE0, 0x0F, 0xF0, 0x1D, 0xB8, 0x39, 0x9C, 0x71, 0x8E, 0x61, 0x86,
|
||||||
|
// U+672B (末)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x3F, 0xFC, 0x3F, 0xFC, 0x07, 0xE0, 0x0F, 0xF0, 0x1D, 0xB8, 0x39, 0x9C, 0x71, 0x8E, 0x61, 0x86,
|
||||||
|
// U+672C (本)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x07, 0xE0, 0x0F, 0xF0, 0x0D, 0xB0, 0x1D, 0xB8, 0x19, 0x98, 0x31, 0x8C, 0x7F, 0xFE, 0x4F, 0xF6, 0x01, 0x80, 0x01, 0x80,
|
||||||
|
// U+673A (机)
|
||||||
|
0x00, 0x00, 0x18, 0x00, 0x19, 0xF8, 0x19, 0xF8, 0x7F, 0x98, 0x7F, 0x98, 0x19, 0x98, 0x3D, 0x98, 0x3F, 0x98, 0x3F, 0x98, 0x7B, 0x98, 0x79, 0x98, 0x59, 0x9A, 0x1B, 0x9B, 0x1B, 0x1F, 0x1F, 0x0E,
|
||||||
|
// U+6761 (条)
|
||||||
|
0x00, 0x00, 0x06, 0x00, 0x07, 0x10, 0x0F, 0xF8, 0x1C, 0x30, 0x77, 0xE0, 0x23, 0xC0, 0x0F, 0xF0, 0x7E, 0x7E, 0x79, 0x9E, 0x1F, 0xF8, 0x3F, 0xFC, 0x01, 0x80, 0x1D, 0xB0, 0x39, 0x98, 0x73, 0x8C,
|
||||||
|
// U+677E (松)
|
||||||
|
0x00, 0x00, 0x18, 0x08, 0x18, 0xD8, 0x18, 0xD8, 0x7D, 0xCC, 0xFF, 0x8C, 0x19, 0xA6, 0x3B, 0x66, 0x3F, 0x62, 0x3E, 0x60, 0x7E, 0xC8, 0x78, 0xCC, 0xD8, 0xCC, 0x59, 0x8C, 0x1B, 0xFE, 0x1B, 0xFE,
|
||||||
|
// U+67E5 (查)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x0F, 0xF0, 0x39, 0xBC, 0x71, 0x8E, 0x7F, 0xFE, 0x18, 0x18, 0x18, 0x18, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x00, 0x00, 0x7F, 0xFE,
|
||||||
|
// U+680F (栏)
|
||||||
|
0x00, 0x00, 0x18, 0x8C, 0x19, 0xCC, 0x18, 0xCC, 0x7C, 0xD8, 0x7F, 0xFE, 0x19, 0xFE, 0x38, 0x00, 0x3C, 0x00, 0x3D, 0xFC, 0x7D, 0xFC, 0x78, 0x00, 0x58, 0x00, 0x18, 0x00, 0x1B, 0xFE, 0x1B, 0xFE,
|
||||||
|
// U+68C0 (检)
|
||||||
|
0x00, 0x00, 0x10, 0x60, 0x10, 0x60, 0x10, 0xF0, 0x79, 0xD8, 0x7F, 0x8C, 0x37, 0x8E, 0x3F, 0xFA, 0x3C, 0x00, 0x7F, 0x66, 0x73, 0x64, 0xF1, 0x2C, 0x51, 0xAC, 0x11, 0x18, 0x13, 0xFE, 0x17, 0xFE,
|
||||||
|
// U+6A21 (模)
|
||||||
|
0x00, 0x00, 0x10, 0xD8, 0x30, 0xD8, 0x33, 0xFE, 0x78, 0xD8, 0x7F, 0xFE, 0x3B, 0x06, 0x3B, 0xFE, 0x3F, 0x06, 0x7F, 0x06, 0x7F, 0xFE, 0x70, 0x60, 0xF7, 0xFE, 0x30, 0xF8, 0x31, 0xDC, 0x37, 0x8E,
|
||||||
|
// U+6A2A (横)
|
||||||
|
0x00, 0x00, 0x30, 0xC8, 0x30, 0xDC, 0x33, 0xFE, 0x78, 0xD8, 0x7C, 0xDC, 0x37, 0xFE, 0x38, 0x60, 0x3B, 0xFE, 0x7F, 0x26, 0x77, 0xFE, 0xF3, 0x26, 0xF3, 0xFE, 0x30, 0x88, 0x31, 0xDC, 0x37, 0x8E,
|
||||||
|
// U+6B21 (次)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x41, 0x80, 0x73, 0x80, 0x3B, 0xFE, 0x13, 0xFE, 0x06, 0x66, 0x06, 0x6C, 0x06, 0x6C, 0x18, 0xE0, 0x18, 0xE0, 0x30, 0xF0, 0x71, 0xF0, 0x63, 0x98, 0x67, 0x1E, 0x0E, 0x0E,
|
||||||
|
// U+6B63 (正)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x19, 0x80, 0x19, 0xFC, 0x19, 0xFC, 0x19, 0x80, 0x19, 0x80, 0x19, 0x80, 0x19, 0x80, 0x7F, 0xFE, 0x7F, 0xFE,
|
||||||
|
// U+6B64 (此)
|
||||||
|
0x00, 0x00, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x06, 0x60, 0x36, 0x64, 0x37, 0x6E, 0x37, 0xFC, 0x36, 0x70, 0x36, 0x60, 0x36, 0x60, 0x36, 0x60, 0x36, 0x62, 0x36, 0x63, 0x3F, 0xE3, 0x7F, 0x7E,
|
||||||
|
// U+6BB5 (段)
|
||||||
|
0x00, 0x00, 0x06, 0x00, 0x3E, 0xF8, 0x38, 0xF8, 0x30, 0xC8, 0x3E, 0xC8, 0x3C, 0xCC, 0x31, 0x8E, 0x3D, 0x80, 0x3F, 0xFC, 0x30, 0xCC, 0x32, 0xCC, 0x7E, 0x78, 0xF8, 0x78, 0x30, 0xFC, 0x33, 0xCE,
|
||||||
|
// U+6BD4 (比)
|
||||||
|
0x00, 0x00, 0x30, 0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x30, 0xC4, 0x30, 0xCE, 0x3E, 0xDC, 0x3F, 0xF8, 0x30, 0xF0, 0x30, 0xC0, 0x30, 0xC0, 0x30, 0xC6, 0x30, 0xC6, 0x36, 0xC6, 0x3E, 0xC6, 0x7C, 0xFE,
|
||||||
|
// U+6CD5 (法)
|
||||||
|
0x00, 0x00, 0x20, 0x60, 0x38, 0x60, 0x18, 0x60, 0x03, 0xFE, 0x03, 0xFC, 0x60, 0x60, 0x70, 0x60, 0x17, 0xFE, 0x03, 0xFE, 0x08, 0xC0, 0x18, 0xD8, 0x19, 0x9C, 0x31, 0x8C, 0x33, 0xFE, 0x63, 0xF6,
|
||||||
|
// U+6D45 (浅)
|
||||||
|
0x00, 0x00, 0x20, 0xD0, 0x30, 0xF8, 0x18, 0xD8, 0x00, 0xFE, 0x07, 0xFE, 0x67, 0xC0, 0x70, 0xC2, 0x31, 0xFE, 0x07, 0xFE, 0x16, 0xE6, 0x18, 0x6C, 0x30, 0x78, 0x30, 0x72, 0x71, 0xF2, 0x67, 0xBE,
|
||||||
|
// U+6D4F (浏)
|
||||||
|
0x00, 0x00, 0x02, 0x06, 0x63, 0x06, 0x33, 0x16, 0x1F, 0xF6, 0x07, 0xF6, 0x41, 0x96, 0x67, 0x96, 0x37, 0x96, 0x03, 0x96, 0x03, 0x96, 0x33, 0x96, 0x33, 0x96, 0x36, 0xD6, 0x6E, 0xC6, 0x6C, 0x0E,
|
||||||
|
// U+6D88 (消)
|
||||||
|
0x00, 0x00, 0x20, 0x60, 0x73, 0x66, 0x3B, 0x66, 0x03, 0xEC, 0x01, 0x60, 0x63, 0xFE, 0x73, 0xFE, 0x13, 0x06, 0x03, 0xFE, 0x13, 0xFE, 0x1B, 0x06, 0x3B, 0xFE, 0x33, 0xFE, 0x73, 0x06, 0x63, 0x1C,
|
||||||
|
// U+6DF1 (深)
|
||||||
|
0x00, 0x00, 0x20, 0x00, 0x77, 0xFE, 0x1F, 0xFE, 0x06, 0x96, 0x01, 0x98, 0x63, 0x0C, 0x77, 0x0E, 0x36, 0x64, 0x07, 0xFE, 0x07, 0xFE, 0x18, 0xF0, 0x31, 0xF8, 0x33, 0x7C, 0x6F, 0x6E, 0x64, 0x66,
|
||||||
|
// U+6E08 (済)
|
||||||
|
0x00, 0x00, 0x00, 0x40, 0x70, 0x60, 0x3F, 0xFE, 0x17, 0xFE, 0x01, 0xB8, 0x40, 0xF0, 0x77, 0xFE, 0x37, 0x0E, 0x03, 0x0C, 0x13, 0xFC, 0x13, 0x0C, 0x33, 0x0C, 0x33, 0xFC, 0x66, 0x0C, 0x6E, 0x0C,
|
||||||
|
// U+6E90 (源)
|
||||||
|
0x00, 0x00, 0x20, 0x00, 0x7F, 0xFE, 0x1F, 0xFE, 0x06, 0x30, 0x07, 0xFE, 0x67, 0x86, 0x77, 0x86, 0x37, 0xFE, 0x07, 0x86, 0x17, 0xFE, 0x1E, 0x30, 0x36, 0xBC, 0x3D, 0xB6, 0x6D, 0xB6, 0x7F, 0x32,
|
||||||
|
// U+70B9 (点)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0xFE, 0x01, 0xFC, 0x01, 0x80, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x00, 0x00, 0x36, 0x4C, 0x36, 0x6E, 0x66, 0x66,
|
||||||
|
// U+70ED (热)
|
||||||
|
0x00, 0x00, 0x18, 0x40, 0x18, 0x60, 0x7C, 0xE0, 0x7F, 0xFC, 0x18, 0xCC, 0x1E, 0xCC, 0x7F, 0xCC, 0x79, 0xCC, 0x18, 0xFE, 0x19, 0xBF, 0x7B, 0x0E, 0x32, 0x00, 0x36, 0x4C, 0x36, 0x6E, 0x66, 0x66,
|
||||||
|
// U+7121 (無)
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0x18, 0x00, 0x3F, 0xFE, 0x7F, 0xFC, 0x7A, 0xD8, 0x1A, 0xD8, 0x7F, 0xFE, 0x3F, 0xFC, 0x1A, 0xD8, 0x1A, 0xD8, 0x7F, 0xFE, 0x7F, 0xFE, 0x36, 0x4C, 0x36, 0x6E, 0x66, 0x66,
|
||||||
|
// U+7248 (版)
|
||||||
|
0x00, 0x00, 0x2C, 0x0E, 0x6C, 0xFE, 0x6D, 0xF0, 0x6D, 0x80, 0x7F, 0x80, 0x7F, 0xFE, 0x61, 0xFE, 0x61, 0xE6, 0x7D, 0xE6, 0x7D, 0xAC, 0x65, 0xBC, 0x65, 0xB8, 0x65, 0x98, 0x67, 0xBC, 0xC7, 0xEE,
|
||||||
|
// U+7279 (特)
|
||||||
|
0x00, 0x00, 0x18, 0x30, 0x18, 0x30, 0x79, 0xFE, 0x7D, 0xFC, 0x7C, 0x30, 0x5B, 0xFF, 0x5B, 0xFE, 0x58, 0x0C, 0x1F, 0xFE, 0x7F, 0xFE, 0x78, 0x8C, 0x59, 0xCC, 0x18, 0xCC, 0x18, 0x4C, 0x18, 0x3C,
|
||||||
|
// U+72B6 (状)
|
||||||
|
0x00, 0x00, 0x08, 0x60, 0x0C, 0x6C, 0x4C, 0x6C, 0x6C, 0x66, 0x7C, 0x60, 0x3F, 0xFE, 0x0F, 0xFE, 0x0C, 0x60, 0x1C, 0x70, 0x3C, 0xF0, 0x7C, 0xF8, 0x6C, 0xD8, 0x0D, 0x9C, 0x0F, 0x8C, 0x0F, 0x07,
|
||||||
|
// U+72ED (狭)
|
||||||
|
0x00, 0x00, 0x48, 0x20, 0x6C, 0x60, 0x3B, 0xFE, 0x3B, 0xFE, 0x79, 0x64, 0x79, 0xE6, 0x19, 0xEC, 0x19, 0xEC, 0x3F, 0xFE, 0x7F, 0xFE, 0xEC, 0x70, 0x48, 0xF8, 0x08, 0xD8, 0x19, 0x8C, 0x7F, 0x0E,
|
||||||
|
// U+7387 (率)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x03, 0x00, 0x77, 0x6C, 0x3F, 0xD8, 0x01, 0xC0, 0x1B, 0x78, 0x7F, 0xFE, 0x67, 0xB6, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80,
|
||||||
|
// U+73B0 (现)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7D, 0xFC, 0x7F, 0xFC, 0x19, 0xA4, 0x19, 0xA4, 0x19, 0xA4, 0x7D, 0xA4, 0x7D, 0xE4, 0x19, 0xE4, 0x19, 0x74, 0x18, 0x70, 0x1E, 0xF2, 0x7C, 0xF2, 0x63, 0xB2, 0x07, 0x3E,
|
||||||
|
// U+73FE (現)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7D, 0x86, 0x19, 0x86, 0x19, 0xFE, 0x19, 0x86, 0x7D, 0xFE, 0x7D, 0x86, 0x19, 0x86, 0x19, 0xFE, 0x1E, 0xD8, 0x7E, 0xD8, 0xF9, 0xD8, 0x41, 0x9B, 0x07, 0x1F,
|
||||||
|
// U+7528 (用)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x1F, 0xFC, 0x3F, 0xFC, 0x31, 0x8C, 0x31, 0x8C, 0x3F, 0xFC, 0x3F, 0xFC, 0x31, 0x8C, 0x31, 0x8C, 0x3F, 0xFC, 0x3F, 0xFC, 0x31, 0x8C, 0x71, 0x8C, 0x61, 0x8C, 0xE1, 0xBC,
|
||||||
|
// U+7535 (电)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x3F, 0xFC, 0x3F, 0xFC, 0x31, 0x8C, 0x31, 0x8C, 0x3F, 0xFC, 0x3F, 0xFC, 0x31, 0x8C, 0x31, 0x8C, 0x3F, 0xFC, 0x3F, 0xFE, 0x21, 0x83, 0x01, 0x86, 0x01, 0xFE,
|
||||||
|
// U+753B (画)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x6D, 0xB6, 0x6D, 0xB6, 0x6F, 0xF6, 0x6D, 0xB6, 0x6D, 0xB6, 0x6F, 0xF6, 0x60, 0x06, 0x7F, 0xFE, 0x7F, 0xFE,
|
||||||
|
// U+7565 (略)
|
||||||
|
0x00, 0x00, 0x00, 0x60, 0x3C, 0x60, 0x7E, 0xFE, 0x7E, 0xFE, 0x7F, 0xEC, 0x7F, 0x7C, 0x7E, 0x38, 0x7E, 0xFC, 0x7F, 0xEF, 0x7F, 0xFF, 0x7E, 0xFE, 0x7E, 0xC6, 0x7E, 0xC6, 0x60, 0xFE, 0x00, 0xFE,
|
||||||
|
// U+767D (白)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x03, 0x80, 0x03, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC,
|
||||||
|
// U+767E (百)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x03, 0x80, 0x1F, 0xFC, 0x1F, 0xFC, 0x18, 0x0C, 0x18, 0x0C, 0x1F, 0xFC, 0x1F, 0xFC, 0x18, 0x0C, 0x18, 0x0C, 0x1F, 0xFC, 0x1F, 0xFC,
|
||||||
|
// U+7684 (的)
|
||||||
|
0x00, 0x00, 0x18, 0x60, 0x18, 0x60, 0x18, 0x60, 0x7E, 0xFE, 0x7E, 0xFE, 0x67, 0x86, 0x67, 0x86, 0x7E, 0x66, 0x7E, 0x66, 0x66, 0x36, 0x66, 0x36, 0x66, 0x06, 0x7E, 0x06, 0x7E, 0x0C, 0x60, 0x7C,
|
||||||
|
// U+76EE (目)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC,
|
||||||
|
// U+7720 (眠)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7D, 0xFE, 0x7D, 0xFE, 0x6D, 0x06, 0x6D, 0xFE, 0x7D, 0xFE, 0x6D, 0x30, 0x6D, 0x30, 0x7D, 0xFE, 0x7D, 0xFE, 0x6D, 0x18, 0x7D, 0x18, 0x7D, 0x1B, 0x63, 0xFF, 0x43, 0xEE,
|
||||||
|
// U+77ED (短)
|
||||||
|
0x00, 0x00, 0x20, 0x00, 0x21, 0xFE, 0x7D, 0xFE, 0x7E, 0x00, 0x79, 0xFE, 0x59, 0xFE, 0x19, 0x86, 0xFF, 0x86, 0x7F, 0xFE, 0x18, 0xFC, 0x1C, 0xCC, 0x3C, 0xCC, 0x36, 0xCC, 0x64, 0x58, 0x63, 0xFF,
|
||||||
|
// U+7801 (码)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFC, 0x7D, 0xFC, 0x30, 0x8C, 0x31, 0x8C, 0x31, 0x8C, 0x7D, 0x8C, 0x7D, 0xFE, 0xED, 0xFE, 0x6C, 0x06, 0x6C, 0x06, 0x2F, 0xFE, 0x3D, 0xF6, 0x3C, 0x06, 0x20, 0x1C,
|
||||||
|
// U+786E (确)
|
||||||
|
0x00, 0x00, 0x00, 0xC0, 0x7E, 0xC0, 0x7D, 0xFC, 0x31, 0x98, 0x33, 0xB8, 0x33, 0xFE, 0x7D, 0xB6, 0x7D, 0xB6, 0xED, 0xFE, 0x6D, 0xB6, 0x6D, 0xB6, 0x2D, 0xFE, 0x3F, 0x36, 0x3F, 0x36, 0x26, 0x3E,
|
||||||
|
// U+78BA (確)
|
||||||
|
0x00, 0x00, 0x00, 0x30, 0x7C, 0x30, 0x7F, 0xFE, 0x33, 0xFE, 0x33, 0xFE, 0x30, 0xD8, 0x3D, 0xFE, 0x7F, 0x98, 0x6F, 0xFE, 0xED, 0x98, 0x6D, 0x98, 0x2D, 0xFE, 0x3D, 0x98, 0x3D, 0x98, 0x21, 0xFE,
|
||||||
|
// U+793A (示)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x3F, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x19, 0x90, 0x19, 0x98, 0x39, 0x9C, 0x31, 0x8C, 0x61, 0x8E, 0x61, 0x86, 0x0F, 0x80,
|
||||||
|
// U+7981 (禁)
|
||||||
|
0x00, 0x00, 0x08, 0x10, 0x1C, 0x38, 0x7F, 0xFE, 0x1C, 0x38, 0x3E, 0x7C, 0x7B, 0xF6, 0x49, 0xB6, 0x18, 0x30, 0x1F, 0xFC, 0x00, 0x00, 0x7F, 0xFE, 0x3F, 0xFE, 0x19, 0x98, 0x39, 0x9C, 0x77, 0x8E,
|
||||||
|
// U+7A7A (空)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x66, 0x66, 0x6E, 0x76, 0x3C, 0x3C, 0x78, 0x0E, 0x3F, 0xFC, 0x1F, 0xF8, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE,
|
||||||
|
// U+7AD6 (竖)
|
||||||
|
0x00, 0x00, 0x04, 0x00, 0x25, 0xFC, 0x65, 0xFC, 0x64, 0xDC, 0x64, 0xF8, 0x64, 0x70, 0x64, 0xF8, 0x67, 0xFE, 0x25, 0x8E, 0x01, 0x80, 0x7F, 0xFE, 0x3F, 0xFC, 0x0C, 0x30, 0x0C, 0x30, 0x7F, 0xFE,
|
||||||
|
// U+7AE0 (章)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x3F, 0xFC, 0x0C, 0x30, 0x7F, 0xFE, 0x00, 0x00, 0x3F, 0xFC, 0x30, 0x0C, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x01, 0x80, 0x7F, 0xFE, 0x01, 0x80,
|
||||||
|
// U+7AEF (端)
|
||||||
|
0x00, 0x00, 0x10, 0x20, 0x33, 0x36, 0x1B, 0x36, 0x7F, 0xFE, 0x7F, 0xFE, 0x4C, 0x00, 0x6F, 0xFE, 0x6F, 0xFE, 0x6C, 0x60, 0x6B, 0xFE, 0x2B, 0xFE, 0x3F, 0x5E, 0x7F, 0x5E, 0x73, 0x5E, 0x03, 0x5E,
|
||||||
|
// U+7BC4 (範)
|
||||||
|
0x00, 0x00, 0x10, 0x60, 0x38, 0xE0, 0x7F, 0xFE, 0x6D, 0x98, 0x49, 0x10, 0x7F, 0xFE, 0x0C, 0xFE, 0x7F, 0xC6, 0x6B, 0xC6, 0x7F, 0xC6, 0x6B, 0xDE, 0x7F, 0xC8, 0x08, 0xC2, 0x7F, 0xC3, 0x08, 0x7E,
|
||||||
|
// U+7C4D (籍)
|
||||||
|
0x00, 0x00, 0x18, 0x60, 0x38, 0x60, 0x3F, 0xFE, 0x6D, 0xD8, 0x7C, 0xEC, 0x7F, 0xFE, 0x3C, 0x6C, 0x7E, 0x6C, 0x19, 0xFE, 0x7E, 0x00, 0x1C, 0xFC, 0x3E, 0xC4, 0x7E, 0xFC, 0x7A, 0xC4, 0x58, 0xFC,
|
||||||
|
// U+7D22 (索)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x3F, 0xFC, 0x7F, 0xFE, 0x01, 0x80, 0x7F, 0xFE, 0x67, 0x86, 0x67, 0x26, 0x7C, 0x76, 0x1F, 0xD0, 0x0F, 0x18, 0x3F, 0xFC, 0x3F, 0x86, 0x0D, 0xB0, 0x19, 0x9C, 0x77, 0x8E,
|
||||||
|
// U+7D27 (紧)
|
||||||
|
0x00, 0x00, 0x0D, 0xFC, 0x2D, 0xFC, 0x6C, 0xCC, 0x6C, 0x78, 0x6C, 0x70, 0x6D, 0xFC, 0x0F, 0xDE, 0x0E, 0x60, 0x1F, 0xE0, 0x1F, 0x98, 0x1F, 0x1C, 0x3F, 0xFE, 0x3F, 0x86, 0x1D, 0xB8, 0x79, 0x9C,
|
||||||
|
// U+7D42 (終)
|
||||||
|
0x00, 0x00, 0x18, 0x60, 0x18, 0xE0, 0x30, 0xFE, 0x3D, 0xFC, 0x6F, 0xDC, 0xFB, 0x78, 0x78, 0x70, 0x3C, 0xF8, 0x7F, 0xCE, 0x7F, 0xE6, 0x44, 0x78, 0x7C, 0x18, 0x7E, 0xC0, 0x5E, 0xF0, 0xD8, 0x3C,
|
||||||
|
// U+7D9A (続)
|
||||||
|
0x00, 0x00, 0x10, 0x30, 0x10, 0x30, 0x33, 0xFE, 0x2C, 0x30, 0x7D, 0xFE, 0xF8, 0x00, 0x78, 0x00, 0x3F, 0xFE, 0x7F, 0x02, 0x7F, 0x5A, 0x64, 0xD8, 0x7C, 0xD8, 0x7C, 0xDB, 0x7F, 0x9B, 0x5B, 0x9E,
|
||||||
|
// U+7E26 (縦)
|
||||||
|
0x00, 0x00, 0x11, 0xA6, 0x31, 0xE6, 0x33, 0x34, 0x2E, 0x3C, 0x69, 0xFE, 0xF9, 0xFE, 0x73, 0x18, 0x3F, 0x78, 0x7F, 0x7E, 0x7D, 0x7E, 0x45, 0x78, 0x5D, 0x78, 0x55, 0x78, 0x55, 0xF8, 0xD1, 0xDF,
|
||||||
|
// U+7EBF (线)
|
||||||
|
0x00, 0x00, 0x18, 0x68, 0x18, 0x7C, 0x18, 0x64, 0x30, 0x7E, 0x37, 0xFE, 0x7D, 0xE0, 0x7C, 0x66, 0x58, 0xFE, 0x33, 0xF8, 0x7F, 0x36, 0x7C, 0x3E, 0x60, 0x3C, 0x0E, 0x38, 0x7C, 0xFA, 0x73, 0xDE,
|
||||||
|
// U+7EC8 (终)
|
||||||
|
0x00, 0x00, 0x18, 0x60, 0x18, 0xE0, 0x30, 0xFE, 0x31, 0xFC, 0x67, 0xDC, 0x6F, 0x78, 0xF8, 0x70, 0x58, 0xFC, 0x33, 0xCE, 0x7F, 0x66, 0x7C, 0x78, 0x40, 0x18, 0x0D, 0xC0, 0xFE, 0xF8, 0x70, 0x3C,
|
||||||
|
// U+7EDC (络)
|
||||||
|
0x00, 0x00, 0x10, 0x40, 0x18, 0xC0, 0x38, 0xFE, 0x31, 0xFC, 0x77, 0xCC, 0x7F, 0xF8, 0xFC, 0x70, 0x78, 0xFC, 0x33, 0xCE, 0x7F, 0xFE, 0x7D, 0xFC, 0x41, 0x84, 0x0D, 0x84, 0x7D, 0xFC, 0x71, 0xFC,
|
||||||
|
// U+7EE7 (继)
|
||||||
|
0x00, 0x00, 0x10, 0x10, 0x1B, 0x12, 0x33, 0xD6, 0x33, 0x56, 0x67, 0x5C, 0x6F, 0x5C, 0xFB, 0xFE, 0x7B, 0x38, 0x33, 0x3C, 0x7F, 0x7E, 0x7F, 0xD6, 0x43, 0xD0, 0x0F, 0x10, 0x7F, 0xFE, 0x63, 0xFF,
|
||||||
|
// U+7EED (续)
|
||||||
|
0x00, 0x00, 0x18, 0x20, 0x18, 0x70, 0x31, 0xFC, 0x30, 0x20, 0x6F, 0xFE, 0x7D, 0xFE, 0x78, 0xF6, 0x11, 0xF4, 0x3F, 0xB0, 0x7C, 0xB0, 0x73, 0xFE, 0x07, 0xFE, 0x3C, 0xF8, 0x71, 0xCC, 0x43, 0x86,
|
||||||
|
// U+7EF4 (维)
|
||||||
|
0x00, 0x00, 0x18, 0xD0, 0x18, 0xD8, 0x30, 0xD8, 0x35, 0xFE, 0x6F, 0xFE, 0x7F, 0x98, 0xFB, 0xFC, 0x5B, 0xFE, 0x31, 0x98, 0x7D, 0xFC, 0x79, 0xFE, 0x01, 0x98, 0x1F, 0x98, 0x7D, 0xFF, 0x61, 0xFE,
|
||||||
|
// U+7F51 (网)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x63, 0x16, 0x63, 0x36, 0x7B, 0xF6, 0x6E, 0xF6, 0x6E, 0x76, 0x66, 0x76, 0x67, 0x76, 0x6F, 0xFE, 0x7D, 0xDE, 0x79, 0x86, 0x60, 0x86, 0x60, 0x3E,
|
||||||
|
// U+7F6E (置)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFE, 0x36, 0x66, 0x3F, 0xFE, 0x01, 0x80, 0x7F, 0xFE, 0x03, 0x80, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x1F, 0xF8, 0x18, 0x18, 0x7F, 0xFE,
|
||||||
|
// U+7FFB (翻)
|
||||||
|
0x00, 0x00, 0x0E, 0x00, 0x7E, 0xFE, 0x6A, 0x3E, 0x2E, 0x36, 0x7F, 0xBE, 0x1C, 0xFE, 0x3F, 0xFE, 0x7B, 0x76, 0x48, 0x36, 0x7F, 0x7E, 0x6B, 0xFE, 0x7F, 0xBE, 0x6B, 0x36, 0x6B, 0x36, 0x7F, 0x7E,
|
||||||
|
// U+81EA (自)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x1F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x0C, 0x3F, 0xFC, 0x3F, 0xFC,
|
||||||
|
// U+8272 (色)
|
||||||
|
0x00, 0x00, 0x06, 0x00, 0x0F, 0xE0, 0x0F, 0xF0, 0x18, 0x60, 0x38, 0xC0, 0x7F, 0xFC, 0x7F, 0xFC, 0x31, 0x8C, 0x31, 0x8C, 0x3F, 0xFC, 0x3F, 0xFC, 0x30, 0x02, 0x30, 0x03, 0x38, 0x06, 0x1F, 0xFE,
|
||||||
|
// U+8282 (节)
|
||||||
|
0x00, 0x00, 0x0C, 0x30, 0x0C, 0x30, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x30, 0x00, 0x00, 0x7F, 0xFC, 0x3F, 0xFC, 0x06, 0x0C, 0x06, 0x0C, 0x06, 0x0C, 0x06, 0x0C, 0x06, 0x7C, 0x06, 0x38, 0x06, 0x00,
|
||||||
|
// U+8303 (范)
|
||||||
|
0x00, 0x00, 0x0C, 0x30, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x30, 0x24, 0x20, 0x3B, 0xFC, 0x1F, 0xFC, 0x03, 0x0C, 0x73, 0x0C, 0x3B, 0x3C, 0x0F, 0x3C, 0x0F, 0x00, 0x1B, 0x02, 0x33, 0x86, 0x71, 0xFE,
|
||||||
|
// U+843D (落)
|
||||||
|
0x00, 0x00, 0x0C, 0x30, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0xB0, 0x31, 0xC0, 0x3B, 0xFC, 0x0F, 0x9C, 0x67, 0xF8, 0x74, 0xF8, 0x33, 0xFE, 0x07, 0x8E, 0x1B, 0xFC, 0x3B, 0x0C, 0x33, 0x0C, 0x63, 0xFC,
|
||||||
|
// U+8535 (蔵)
|
||||||
|
0x00, 0x00, 0x0C, 0x30, 0x7F, 0xFE, 0x0E, 0x7C, 0x0C, 0x2C, 0x00, 0x7C, 0x7F, 0xFE, 0x60, 0x30, 0x7F, 0xF4, 0x7B, 0x3C, 0x7F, 0xBC, 0x78, 0xB8, 0x7F, 0xB8, 0x7B, 0x3A, 0x7F, 0xFB, 0x60, 0xEF,
|
||||||
|
// U+85CF (藏)
|
||||||
|
0x00, 0x00, 0x0C, 0x30, 0x7F, 0xFE, 0x0E, 0x7E, 0x04, 0x36, 0x7F, 0xFE, 0x7F, 0xFE, 0x78, 0x30, 0x7F, 0xF6, 0x1E, 0x96, 0x1F, 0xFC, 0xFE, 0x5C, 0x7F, 0xDC, 0x7E, 0x9A, 0x5F, 0xFB, 0x50, 0x7E,
|
||||||
|
// U+884C (行)
|
||||||
|
0x00, 0x00, 0x0C, 0x00, 0x19, 0xFE, 0x31, 0xFE, 0x60, 0x00, 0x4C, 0x00, 0x1C, 0x00, 0x1B, 0xFE, 0x3B, 0xFE, 0x78, 0x18, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78,
|
||||||
|
// U+88C1 (裁)
|
||||||
|
0x00, 0x00, 0x0C, 0x60, 0x3F, 0x6C, 0x7F, 0xEE, 0x0C, 0x64, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0x60, 0x0E, 0x26, 0x7F, 0xBC, 0x19, 0x3C, 0x3F, 0xB8, 0x7F, 0x3A, 0x53, 0xFB, 0x1E, 0xFA, 0x3C, 0xCE,
|
||||||
|
// U+898B (見)
|
||||||
|
0x00, 0x00, 0x1F, 0xF8, 0x1F, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x1F, 0xF8, 0x1F, 0xF8, 0x0E, 0x60, 0x0C, 0x62, 0x3C, 0x66, 0x78, 0x7E,
|
||||||
|
// U+8996 (視)
|
||||||
|
0x00, 0x00, 0x10, 0x00, 0x19, 0xFE, 0x19, 0x86, 0x7F, 0x86, 0x7D, 0xFE, 0x0D, 0x86, 0x19, 0xFE, 0x3D, 0x86, 0x7D, 0x86, 0xFF, 0xFE, 0x58, 0xF8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xDB, 0x1B, 0x9F,
|
||||||
|
// U+89A7 (覧)
|
||||||
|
0x00, 0x00, 0x00, 0x60, 0x7F, 0x60, 0x7F, 0x7E, 0x7F, 0xD0, 0x7F, 0xD8, 0x6C, 0x9C, 0x7F, 0x0C, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x06, 0x62, 0x1C, 0x66,
|
||||||
|
// U+89C8 (览)
|
||||||
|
0x00, 0x00, 0x06, 0x60, 0x26, 0x60, 0x26, 0xFE, 0x26, 0xFE, 0x27, 0xD8, 0x27, 0x98, 0x06, 0x08, 0x1F, 0xF8, 0x1F, 0xF8, 0x19, 0x98, 0x19, 0x98, 0x19, 0xD8, 0x13, 0xC0, 0x0E, 0xC6, 0x7C, 0xFE,
|
||||||
|
// U+8A00 (言)
|
||||||
|
0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x1F, 0xF8,
|
||||||
|
// U+8A08 (計)
|
||||||
|
0x00, 0x00, 0x10, 0x10, 0x18, 0x30, 0x08, 0x30, 0x7F, 0x30, 0x00, 0x30, 0x7E, 0x30, 0x01, 0xFF, 0x00, 0xFE, 0x7E, 0x30, 0x00, 0x30, 0x3E, 0x30, 0x26, 0x30, 0x26, 0x30, 0x36, 0x30, 0x3E, 0x30,
|
||||||
|
// U+8A2D (設)
|
||||||
|
0x00, 0x00, 0x10, 0x00, 0x18, 0xF8, 0x18, 0xF8, 0x7E, 0xC8, 0x01, 0x88, 0x7F, 0x8E, 0x03, 0x00, 0x01, 0xFC, 0x7F, 0xFE, 0x01, 0x8C, 0x7C, 0xCC, 0x64, 0xF8, 0x64, 0x70, 0x6C, 0xF8, 0x7F, 0xFE,
|
||||||
|
// U+8A66 (試)
|
||||||
|
0x00, 0x00, 0x30, 0x1C, 0x30, 0x1E, 0x10, 0x1E, 0x7D, 0xFE, 0x03, 0xFF, 0x7C, 0x18, 0x00, 0x18, 0x03, 0xF8, 0x7D, 0xD8, 0x00, 0x98, 0x7C, 0x98, 0x6C, 0x98, 0x6D, 0xFA, 0x6F, 0xEF, 0x7E, 0x0E,
|
||||||
|
// U+8A8D (認)
|
||||||
|
0x00, 0x00, 0x30, 0x00, 0x31, 0xFE, 0x19, 0xB6, 0xFF, 0xB6, 0x01, 0xE6, 0x7D, 0x66, 0x00, 0xC6, 0x01, 0xDC, 0x7D, 0xA0, 0x00, 0x74, 0x7D, 0xF6, 0x67, 0xD6, 0x67, 0xCE, 0x6F, 0xCE, 0x7E, 0xFC,
|
||||||
|
// U+8A9E (語)
|
||||||
|
0x00, 0x00, 0x10, 0x00, 0x19, 0xFE, 0x18, 0x60, 0xFE, 0x60, 0x01, 0xFC, 0x7C, 0x4C, 0x00, 0xCC, 0x7F, 0xFF, 0x7C, 0x00, 0x00, 0xFC, 0x7D, 0xFE, 0x65, 0x86, 0x65, 0x86, 0x6D, 0xFE, 0x7D, 0xFE,
|
||||||
|
// U+8AAD (読)
|
||||||
|
0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x13, 0xFE, 0xFC, 0x30, 0x01, 0xFE, 0x7C, 0x00, 0x00, 0x00, 0x03, 0xFE, 0x7F, 0x02, 0x03, 0xDA, 0x7C, 0xD8, 0x6C, 0xD8, 0x6C, 0xDA, 0x6D, 0xDB, 0x7F, 0x9E,
|
||||||
|
// U+8BA4 (认)
|
||||||
|
0x00, 0x00, 0x00, 0x20, 0x30, 0x30, 0x18, 0x20, 0x08, 0x60, 0x00, 0x60, 0x78, 0x60, 0x78, 0x70, 0x18, 0x70, 0x18, 0x70, 0x18, 0x70, 0x18, 0x78, 0x1E, 0xD8, 0x1E, 0xCC, 0x1D, 0x8E, 0x33, 0x86,
|
||||||
|
// U+8BB0 (记)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x31, 0xFC, 0x3B, 0xFE, 0x18, 0x06, 0x00, 0x06, 0x78, 0x06, 0x78, 0x06, 0x19, 0xFE, 0x19, 0xFE, 0x19, 0x80, 0x19, 0x80, 0x19, 0x82, 0x1F, 0x83, 0x1D, 0x86, 0x39, 0xFE,
|
||||||
|
// U+8BBE (设)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x31, 0xF8, 0x19, 0xF8, 0x09, 0x98, 0x01, 0x98, 0x73, 0x0E, 0xF3, 0x00, 0x13, 0xFC, 0x13, 0xFC, 0x11, 0x8C, 0x11, 0x9C, 0x1C, 0xF8, 0x1C, 0x70, 0x39, 0xF8, 0x37, 0xDE,
|
||||||
|
// U+8BD5 (试)
|
||||||
|
0x00, 0x00, 0x00, 0x34, 0x30, 0x3E, 0x18, 0x36, 0x0B, 0xFE, 0x07, 0xFF, 0x70, 0x18, 0x78, 0x18, 0x1B, 0xD8, 0x1B, 0xF8, 0x19, 0x98, 0x19, 0x98, 0x19, 0x98, 0x1D, 0xFB, 0x3F, 0xFF, 0x33, 0x0E,
|
||||||
|
// U+8BED (语)
|
||||||
|
0x00, 0x00, 0x23, 0xFC, 0x37, 0xFE, 0x38, 0xC0, 0x19, 0xF8, 0x03, 0xFC, 0x70, 0xCC, 0x70, 0xCC, 0x17, 0xFF, 0x17, 0xFE, 0x11, 0xFC, 0x13, 0xFE, 0x17, 0x06, 0x1F, 0x06, 0x3B, 0xFE, 0x33, 0xFE,
|
||||||
|
// U+8BEF (误)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x33, 0xFE, 0x3B, 0xFE, 0x1B, 0x06, 0x03, 0xFE, 0x73, 0xFE, 0xF8, 0x00, 0x1B, 0xFE, 0x19, 0xFC, 0x18, 0x60, 0x1B, 0xFF, 0x1F, 0xFE, 0x1C, 0xF8, 0x3D, 0xDC, 0x37, 0x8E,
|
||||||
|
// U+8BFB (读)
|
||||||
|
0x00, 0x00, 0x00, 0x60, 0x30, 0x70, 0x3B, 0xFE, 0x18, 0x60, 0x03, 0xFE, 0x73, 0xFE, 0xF1, 0xB6, 0x10, 0xF4, 0x13, 0x30, 0x11, 0xB0, 0x17, 0xFE, 0x1F, 0xFE, 0x1C, 0xF8, 0x39, 0xCC, 0x37, 0x86,
|
||||||
|
// U+8D25 (败)
|
||||||
|
0x00, 0x00, 0x00, 0x20, 0x7F, 0x60, 0x7F, 0x60, 0x6B, 0x7E, 0x7B, 0xFE, 0x7B, 0xCC, 0x7B, 0xCC, 0x7B, 0xCC, 0x7B, 0x6C, 0x7B, 0x78, 0x7B, 0x38, 0x18, 0x38, 0x3E, 0x38, 0x36, 0x7C, 0x63, 0xC6,
|
||||||
|
// U+8D77 (起)
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0x08, 0xFE, 0x7F, 0x7E, 0x7E, 0x06, 0x08, 0x06, 0x7F, 0x7E, 0x7F, 0xFE, 0x0C, 0xC0, 0x6C, 0xC0, 0x6F, 0xC2, 0x6F, 0xC6, 0x7C, 0x7E, 0x7C, 0x3C, 0x7C, 0x00, 0xCF, 0xFF,
|
||||||
|
// U+8D85 (超)
|
||||||
|
0x00, 0x00, 0x18, 0x00, 0x18, 0xFE, 0x7F, 0xFE, 0x3E, 0x66, 0x18, 0x66, 0x7E, 0xDE, 0x7F, 0xC0, 0x08, 0x80, 0x68, 0xFE, 0x6F, 0xC6, 0x6E, 0xC6, 0x78, 0xFE, 0x78, 0x00, 0x7C, 0x00, 0xCF, 0xFF,
|
||||||
|
// U+8DDD (距)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x67, 0x80, 0x67, 0x80, 0x7F, 0xFC, 0x3D, 0xFC, 0x19, 0x84, 0x7F, 0x84, 0x7F, 0xFC, 0x79, 0xFC, 0x79, 0x80, 0x7F, 0x80, 0x7F, 0xFE, 0x71, 0xFF,
|
||||||
|
// U+8DF3 (跳)
|
||||||
|
0x00, 0x00, 0x00, 0xD8, 0x7C, 0xD8, 0x7F, 0xDA, 0x67, 0xDE, 0x65, 0xDE, 0x7D, 0xDC, 0x18, 0xD8, 0x78, 0xD8, 0x7C, 0xDC, 0x7F, 0xDE, 0x7B, 0xDA, 0x78, 0xD8, 0x7F, 0x9A, 0xFD, 0x9B, 0x43, 0x1E,
|
||||||
|
// U+8EE2 (転)
|
||||||
|
0x00, 0x00, 0x18, 0x00, 0x18, 0x7E, 0x7F, 0xFE, 0x7E, 0x00, 0x18, 0x00, 0x7F, 0x00, 0x7B, 0xFF, 0x7F, 0xFE, 0x7B, 0x30, 0x7F, 0x60, 0x18, 0x6C, 0x7E, 0x6C, 0xFF, 0xC6, 0x19, 0xFE, 0x19, 0xFE,
|
||||||
|
// U+8F6C (转)
|
||||||
|
0x00, 0x00, 0x18, 0x30, 0x18, 0x30, 0xFF, 0xFE, 0x7E, 0xFE, 0x38, 0x60, 0x39, 0xFF, 0x69, 0xFE, 0x7C, 0xE0, 0x7E, 0xFC, 0x08, 0xFE, 0x0E, 0x0C, 0x7E, 0x58, 0x7C, 0xF8, 0x08, 0x38, 0x08, 0x1C,
|
||||||
|
// U+8F7D (载)
|
||||||
|
0x00, 0x00, 0x0C, 0x20, 0x0C, 0x6C, 0x7F, 0xEE, 0x0C, 0x64, 0x7F, 0xFE, 0x1C, 0x70, 0x18, 0x36, 0x7F, 0xFC, 0x36, 0x3C, 0x36, 0x3C, 0x7F, 0xB8, 0x06, 0x38, 0x07, 0xFB, 0x7F, 0xFA, 0x76, 0xEE,
|
||||||
|
// U+8F93 (输)
|
||||||
|
0x00, 0x00, 0x30, 0x30, 0x30, 0x70, 0x7C, 0xF8, 0x7F, 0x8E, 0x23, 0xFE, 0x78, 0x00, 0x78, 0x0E, 0x7F, 0xFE, 0x7F, 0x7E, 0x1B, 0xFE, 0x1F, 0x7E, 0x7F, 0xFE, 0x7B, 0x7E, 0x1B, 0x6E, 0x1B, 0x66,
|
||||||
|
// U+8FB9 (边)
|
||||||
|
0x00, 0x00, 0x20, 0x40, 0x70, 0xC0, 0x38, 0xC0, 0x1F, 0xFE, 0x03, 0xFE, 0x00, 0xC6, 0xF0, 0xC6, 0x70, 0xC6, 0x31, 0xC6, 0x31, 0x86, 0x33, 0x8E, 0x37, 0x3C, 0x3E, 0x3C, 0x7E, 0x00, 0xEF, 0xFF,
|
||||||
|
// U+8FBC (込)
|
||||||
|
0x00, 0x00, 0x20, 0x00, 0x71, 0x80, 0x38, 0xC0, 0x18, 0x60, 0x00, 0x60, 0x00, 0xF0, 0x70, 0xF0, 0x78, 0xF0, 0x19, 0x98, 0x19, 0x98, 0x1B, 0x0C, 0x1F, 0x0E, 0x3A, 0x02, 0x7E, 0x02, 0x67, 0xFE,
|
||||||
|
// U+8FD4 (返)
|
||||||
|
0x00, 0x00, 0x00, 0x04, 0x61, 0xFE, 0x73, 0xFC, 0x3B, 0x00, 0x13, 0xFC, 0x03, 0xFC, 0x73, 0x0C, 0x73, 0xCC, 0x33, 0xF8, 0x36, 0x78, 0x36, 0x78, 0x3F, 0xEC, 0x3D, 0x84, 0x7E, 0x02, 0xEF, 0xFE,
|
||||||
|
// U+8FDB (进)
|
||||||
|
0x00, 0x00, 0x20, 0x98, 0x71, 0x98, 0x39, 0x98, 0x1F, 0xFE, 0x03, 0xFE, 0x01, 0x98, 0x71, 0x98, 0x7F, 0xFE, 0x1F, 0xFE, 0x19, 0x98, 0x19, 0x98, 0x1B, 0x18, 0x3B, 0x18, 0x7E, 0x02, 0x67, 0xFE,
|
||||||
|
// U+8FDE (连)
|
||||||
|
0x00, 0x00, 0x20, 0xC0, 0x70, 0xC0, 0x37, 0xFE, 0x1B, 0xFC, 0x03, 0x30, 0x03, 0x30, 0x77, 0xFE, 0x73, 0xFC, 0x30, 0x30, 0x37, 0xFE, 0x37, 0xFE, 0x30, 0x30, 0x38, 0x30, 0x7E, 0x30, 0xEF, 0xFF,
|
||||||
|
// U+8FFD (追)
|
||||||
|
0x00, 0x00, 0x00, 0x60, 0x60, 0x60, 0x33, 0xFC, 0x1B, 0x0C, 0x03, 0x0C, 0x03, 0xFC, 0x7B, 0x00, 0x7B, 0x00, 0x1B, 0xFE, 0x1B, 0x0E, 0x1B, 0x06, 0x1B, 0x0E, 0x1B, 0xFE, 0x7C, 0x00, 0x67, 0xFF,
|
||||||
|
// U+9000 (退)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x63, 0xFC, 0x33, 0x0C, 0x3B, 0x0C, 0x13, 0xFC, 0x03, 0x0C, 0x7B, 0xFC, 0x7B, 0x6E, 0x1B, 0x7E, 0x1B, 0x38, 0x1B, 0x5C, 0x1B, 0xCE, 0x3B, 0x04, 0x7E, 0x00, 0x67, 0xFE,
|
||||||
|
// U+9001 (送)
|
||||||
|
0x00, 0x00, 0x21, 0x0C, 0x71, 0x9C, 0x31, 0x98, 0x1F, 0xFE, 0x03, 0xFC, 0x00, 0x60, 0x70, 0x60, 0x77, 0xFE, 0x37, 0xFE, 0x30, 0xF0, 0x31, 0xD8, 0x33, 0x8C, 0x3F, 0x04, 0x7E, 0x02, 0xEF, 0xFF,
|
||||||
|
// U+9002 (适)
|
||||||
|
0x00, 0x00, 0x00, 0x0C, 0x63, 0xFC, 0x73, 0xF8, 0x30, 0x60, 0x07, 0xFE, 0x07, 0xFE, 0xF0, 0x60, 0x70, 0x60, 0x33, 0xFC, 0x33, 0x0C, 0x33, 0x0C, 0x33, 0xFC, 0x38, 0x00, 0x7E, 0x02, 0x67, 0xFE,
|
||||||
|
// U+9006 (逆)
|
||||||
|
0x00, 0x00, 0x03, 0x0C, 0x63, 0x9C, 0x71, 0x98, 0x37, 0xFE, 0x07, 0xFE, 0x06, 0x66, 0x76, 0x66, 0x76, 0x66, 0x17, 0xFE, 0x17, 0xFE, 0x10, 0x60, 0x10, 0xC0, 0x3B, 0x80, 0x7F, 0x00, 0x67, 0xFE,
|
||||||
|
// U+9009 (选)
|
||||||
|
0x00, 0x00, 0x00, 0x60, 0x61, 0x60, 0x73, 0xF0, 0x3B, 0xFE, 0x16, 0x60, 0x00, 0x60, 0x77, 0xFE, 0x77, 0xFE, 0x11, 0x90, 0x11, 0x90, 0x13, 0x93, 0x17, 0x1E, 0x1E, 0x1E, 0x3E, 0x00, 0x67, 0xFE,
|
||||||
|
// U+901A (通)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x63, 0xFE, 0x70, 0x9C, 0x39, 0xF8, 0x18, 0xF0, 0x03, 0xFE, 0x73, 0x66, 0xFB, 0xFE, 0x1A, 0x66, 0x1B, 0xFE, 0x1B, 0x66, 0x1A, 0x66, 0x3A, 0x7E, 0x7E, 0x0E, 0x67, 0xFE,
|
||||||
|
// U+9032 (進)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x61, 0xB0, 0x33, 0x30, 0x3F, 0xFE, 0x07, 0xFC, 0x0F, 0x30, 0x7F, 0xFE, 0x73, 0x30, 0x13, 0x30, 0x13, 0xFE, 0x13, 0x30, 0x13, 0xFE, 0x3B, 0xFE, 0x7E, 0x00, 0x67, 0xFE,
|
||||||
|
// U+9078 (選)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x67, 0xFE, 0x36, 0xF6, 0x1F, 0xFE, 0x06, 0x72, 0x07, 0xFE, 0x71, 0x98, 0x77, 0xFE, 0x11, 0x98, 0x11, 0x98, 0x1F, 0xFE, 0x11, 0x98, 0x3F, 0x0E, 0x7E, 0x06, 0x67, 0xFE,
|
||||||
|
// U+90E8 (部)
|
||||||
|
0x00, 0x00, 0x0C, 0x00, 0x0C, 0x3E, 0x7F, 0xBE, 0x7F, 0xA6, 0x33, 0x2C, 0x13, 0x2C, 0x7F, 0xAC, 0x7F, 0xEC, 0x00, 0x26, 0x3F, 0x26, 0x7F, 0xA6, 0x61, 0xA6, 0x61, 0xBE, 0x7F, 0xB8, 0x7F, 0xA0,
|
||||||
|
// U+914D (配)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0x7E, 0x14, 0x06, 0x7F, 0x06, 0x7F, 0x06, 0x77, 0x7E, 0x77, 0x7E, 0x77, 0x46, 0x63, 0x40, 0x63, 0x40, 0x7F, 0x42, 0x63, 0x43, 0x7F, 0x62, 0x63, 0x7E,
|
||||||
|
// U+91CD (重)
|
||||||
|
0x00, 0x00, 0x01, 0xF8, 0x3F, 0xF8, 0x01, 0x80, 0x7F, 0xFE, 0x01, 0x80, 0x3F, 0xFC, 0x31, 0x8C, 0x3F, 0xFC, 0x31, 0x8C, 0x31, 0x8C, 0x3F, 0xFC, 0x01, 0x80, 0x3F, 0xFC, 0x01, 0x80, 0x7F, 0xFE,
|
||||||
|
// U+91CF (量)
|
||||||
|
0x00, 0x00, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x18, 0x18, 0x1F, 0xF8, 0x7F, 0xFE, 0x7F, 0xFE, 0x3F, 0xFC, 0x31, 0x8C, 0x3F, 0xFC, 0x3F, 0xFC, 0x1F, 0xFC, 0x3F, 0xFC, 0x3F, 0xFC, 0x7F, 0xFE,
|
||||||
|
// U+9488 (针)
|
||||||
|
0x00, 0x00, 0x10, 0x30, 0x30, 0x30, 0x7E, 0x30, 0x7E, 0x30, 0xC0, 0x30, 0x7E, 0x30, 0x3F, 0xFE, 0x19, 0xFE, 0x7C, 0x30, 0x7E, 0x30, 0x18, 0x30, 0x18, 0x30, 0x1A, 0x30, 0x1E, 0x30, 0x3C, 0x30,
|
||||||
|
// U+949F (钟)
|
||||||
|
0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x3E, 0x30, 0x7F, 0xFE, 0xC1, 0xFE, 0x7F, 0xB6, 0x3D, 0xB6, 0x19, 0xB6, 0x7D, 0xB6, 0x7F, 0xFE, 0x19, 0xFE, 0x19, 0x32, 0x1A, 0x30, 0x1E, 0x30, 0x3C, 0x30,
|
||||||
|
// U+94AE (钮)
|
||||||
|
0x00, 0x00, 0x30, 0x00, 0x31, 0xFE, 0x7F, 0xFE, 0x7C, 0x66, 0x40, 0x64, 0x7C, 0x64, 0x3C, 0x6C, 0x19, 0xFC, 0x7D, 0xFC, 0x7E, 0xCC, 0x18, 0xCC, 0x18, 0xCC, 0x1E, 0xCC, 0x1E, 0xCC, 0x3B, 0xFF,
|
||||||
|
// U+9519 (错)
|
||||||
|
0x00, 0x00, 0x30, 0xC8, 0x30, 0xC8, 0x7F, 0xFE, 0x7D, 0xFE, 0x40, 0xC8, 0x7F, 0xFE, 0x3F, 0xFF, 0x18, 0x00, 0x7D, 0xFE, 0x7F, 0x8E, 0x19, 0x86, 0x19, 0xFE, 0x1D, 0x8E, 0x1F, 0x8E, 0x39, 0xFE,
|
||||||
|
// U+952E (键)
|
||||||
|
0x00, 0x00, 0x20, 0x18, 0x20, 0x18, 0x7F, 0xFE, 0x79, 0x9E, 0xC3, 0x7F, 0x7B, 0x1E, 0x3B, 0xFE, 0x31, 0x98, 0x31, 0xFE, 0x7F, 0x98, 0x33, 0x98, 0x33, 0x7E, 0x3F, 0x98, 0x3F, 0xF8, 0x36, 0x7F,
|
||||||
|
// U+952F (锯)
|
||||||
|
0x00, 0x00, 0x10, 0x00, 0x31, 0xFE, 0x3D, 0x86, 0x7D, 0x86, 0xC1, 0xFE, 0x7D, 0xB8, 0x3D, 0x90, 0x19, 0xFE, 0x7D, 0xFE, 0x7F, 0x10, 0x1B, 0xFE, 0x1B, 0xC6, 0x1F, 0xC6, 0x1F, 0xC6, 0x3E, 0xFE,
|
||||||
|
// U+9577 (長)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x1F, 0xFC, 0x18, 0x00, 0x1C, 0x00, 0x1F, 0xF8, 0x1C, 0x00, 0x1F, 0xF8, 0x18, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x18, 0xDC, 0x18, 0xF8, 0x18, 0x70, 0x1F, 0xBC, 0x3F, 0x1E,
|
||||||
|
// U+957F (长)
|
||||||
|
0x00, 0x00, 0x0C, 0x08, 0x0C, 0x1C, 0x0C, 0x38, 0x0C, 0xE0, 0x0F, 0xC0, 0x0D, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x0C, 0xC0, 0x0C, 0xC0, 0x0C, 0x60, 0x0C, 0x70, 0x0C, 0xB8, 0x1F, 0x9E, 0x1F, 0x0E,
|
||||||
|
// U+958B (開)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x63, 0xC6, 0x7F, 0xFE, 0x63, 0xC6, 0x63, 0xC6, 0x7F, 0xFE, 0x60, 0x06, 0x6F, 0xF6, 0x66, 0x46, 0x66, 0xE6, 0x6F, 0xF6, 0x66, 0x46, 0x66, 0x46, 0x6C, 0x5E,
|
||||||
|
// U+9593 (間)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x63, 0xC6, 0x7F, 0xFE, 0x63, 0xC6, 0x63, 0xC6, 0x7F, 0xFE, 0x60, 0x06, 0x67, 0xE6, 0x66, 0x66, 0x66, 0x66, 0x67, 0xE6, 0x66, 0x66, 0x67, 0xE6, 0x66, 0x3E,
|
||||||
|
// U+95F4 (间)
|
||||||
|
0x00, 0x00, 0x20, 0x00, 0x37, 0xFE, 0x1B, 0xFE, 0x10, 0x06, 0x60, 0x06, 0x67, 0xE6, 0x66, 0x66, 0x64, 0x66, 0x67, 0xE6, 0x66, 0x66, 0x64, 0x66, 0x66, 0x66, 0x67, 0xE6, 0x60, 0x06, 0x60, 0x0E,
|
||||||
|
// U+9605 (阅)
|
||||||
|
0x00, 0x00, 0x20, 0x00, 0x37, 0xFE, 0x1B, 0xFE, 0x06, 0x66, 0x66, 0x66, 0x63, 0xC6, 0x6F, 0xF6, 0x6C, 0x36, 0x6C, 0x36, 0x6F, 0xF6, 0x63, 0xC6, 0x66, 0xC6, 0x66, 0xDE, 0x7C, 0xF6, 0x68, 0x1E,
|
||||||
|
// U+9664 (除)
|
||||||
|
0x00, 0x00, 0x00, 0x30, 0x7C, 0x70, 0x7C, 0xF8, 0x6C, 0xDC, 0x7B, 0x8E, 0x7F, 0xFF, 0x7B, 0xFC, 0x6C, 0x30, 0x6F, 0xFE, 0x6F, 0xFE, 0x7C, 0x30, 0x79, 0xBC, 0x63, 0x36, 0x63, 0x36, 0x66, 0xF2,
|
||||||
|
// U+9690 (隐)
|
||||||
|
0x00, 0x00, 0x00, 0xC0, 0x7C, 0xC0, 0x7D, 0xF8, 0x6F, 0x18, 0x6B, 0xFE, 0x78, 0x06, 0x78, 0x06, 0x6D, 0xFE, 0x6C, 0x06, 0x6F, 0xFE, 0x7C, 0x60, 0x7E, 0xF4, 0x63, 0xB6, 0x67, 0x8E, 0x66, 0xFA,
|
||||||
|
// U+9694 (隔)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7D, 0xFE, 0x6D, 0xFE, 0x79, 0x86, 0x79, 0x86, 0x79, 0xFE, 0x6C, 0x00, 0x6F, 0xFE, 0x6F, 0xCE, 0x7F, 0x5E, 0x7B, 0xFE, 0x63, 0x36, 0x63, 0x36, 0x63, 0x36,
|
||||||
|
// U+96A0 (隠)
|
||||||
|
0x00, 0x00, 0x00, 0x7C, 0x7F, 0xFC, 0x7D, 0x66, 0x6F, 0x6C, 0x79, 0xAC, 0x7B, 0xFE, 0x78, 0x06, 0x6F, 0xFE, 0x6C, 0x06, 0x6F, 0xFE, 0x7C, 0xE0, 0x7C, 0x70, 0x63, 0x8C, 0x66, 0x9E, 0x64, 0xFA,
|
||||||
|
// U+96FB (電)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x01, 0x80, 0x7F, 0xFE, 0x61, 0x86, 0x7F, 0xFE, 0x7F, 0xFE, 0x1D, 0xB8, 0x3F, 0xFC, 0x31, 0x8C, 0x3F, 0xFC, 0x31, 0x8C, 0x3F, 0xFE, 0x31, 0x83, 0x11, 0x86,
|
||||||
|
// U+9762 (面)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x01, 0x80, 0x3F, 0xFC, 0x7F, 0xFE, 0x66, 0x66, 0x66, 0x66, 0x67, 0xE6, 0x66, 0x66, 0x66, 0x66, 0x67, 0xE6, 0x66, 0x66, 0x7F, 0xFE, 0x7F, 0xFE,
|
||||||
|
// U+983B (頻)
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0x2C, 0xFE, 0x6F, 0x7E, 0x6C, 0x30, 0x6C, 0xFE, 0x7F, 0xC6, 0xFF, 0xFE, 0x08, 0xC6, 0x3B, 0xC6, 0x6B, 0xFE, 0x6F, 0xC6, 0x4E, 0xFE, 0x0C, 0x00, 0x1C, 0x6C, 0x79, 0xE6,
|
||||||
|
// U+9875 (页)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x03, 0x00, 0x1F, 0xF8, 0x3F, 0xFC, 0x30, 0x0C, 0x31, 0x8C, 0x31, 0x8C, 0x31, 0x8C, 0x31, 0x8C, 0x31, 0x8C, 0x13, 0x70, 0x0F, 0x3C, 0x7C, 0x0E,
|
||||||
|
// U+987A (顺)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x67, 0xFE, 0x7E, 0xFE, 0x7E, 0x30, 0x7E, 0xFE, 0x7E, 0xFE, 0x7E, 0xD6, 0x7E, 0xF6, 0x7E, 0xF6, 0x7E, 0xF6, 0x7E, 0xF6, 0x7E, 0xF6, 0x7E, 0x38, 0x5E, 0x7C, 0xC7, 0xE6,
|
||||||
|
// U+9891 (频)
|
||||||
|
0x00, 0x00, 0x0C, 0x00, 0x2C, 0xFE, 0x6F, 0x7E, 0x6C, 0x10, 0x6C, 0xFE, 0x7F, 0xC6, 0xFF, 0xD6, 0x08, 0xDE, 0x3B, 0xDE, 0x6B, 0xD6, 0x6F, 0xD6, 0x4E, 0xF6, 0x0C, 0x38, 0x1C, 0x7C, 0x79, 0xE6,
|
||||||
|
// U+989D (额)
|
||||||
|
0x00, 0x00, 0x18, 0x00, 0x1C, 0xFE, 0x7F, 0x7E, 0x43, 0x30, 0x7B, 0xFE, 0x3F, 0xC6, 0x66, 0xD6, 0x7C, 0xD6, 0x1E, 0xD6, 0x7F, 0xD6, 0x63, 0xF6, 0x3E, 0xF6, 0x32, 0x38, 0x32, 0x6C, 0x3F, 0xE6,
|
||||||
|
// U+9F50 (齐)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE, 0x7F, 0xFC, 0x0E, 0x70, 0x07, 0xE0, 0x07, 0xE0, 0x7F, 0xFE, 0x7C, 0x1E, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0x18, 0x30, 0x38, 0x30,
|
||||||
|
// U+9F7F (齿)
|
||||||
|
0x00, 0x00, 0x01, 0x80, 0x19, 0x80, 0x19, 0xFC, 0x19, 0x80, 0x19, 0x80, 0x7F, 0xFE, 0x7F, 0xFE, 0x31, 0x86, 0x31, 0x86, 0x33, 0xC6, 0x33, 0xE6, 0x36, 0x7E, 0x3C, 0x3E, 0x3F, 0xFE, 0x3F, 0xFE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binary search for codepoint in lookup table.
|
||||||
|
* @return Index of glyph, or -1 if not found
|
||||||
|
*/
|
||||||
|
inline int16_t findCjkUiGlyphIndex(uint16_t cp) {
|
||||||
|
int16_t lo = 0;
|
||||||
|
int16_t hi = CJK_UI_FONT_GLYPH_COUNT - 1;
|
||||||
|
|
||||||
|
while (lo <= hi) {
|
||||||
|
int16_t mid = (lo + hi) / 2;
|
||||||
|
uint16_t midCp = pgm_read_word(&CJK_UI_CODEPOINTS[mid]);
|
||||||
|
|
||||||
|
if (midCp == cp) {
|
||||||
|
return mid;
|
||||||
|
} else if (midCp < cp) {
|
||||||
|
lo = mid + 1;
|
||||||
|
} else {
|
||||||
|
hi = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get glyph bitmap for a codepoint from built-in CJK UI font.
|
||||||
|
* @param cp Unicode codepoint
|
||||||
|
* @return Pointer to glyph bitmap in PROGMEM, or nullptr if not available
|
||||||
|
*/
|
||||||
|
inline const uint8_t* getCjkUiGlyph(uint32_t cp) {
|
||||||
|
if (cp > 0xFFFF) return nullptr; // Only BMP supported
|
||||||
|
|
||||||
|
int16_t idx = findCjkUiGlyphIndex(static_cast<uint16_t>(cp));
|
||||||
|
if (idx < 0) return nullptr;
|
||||||
|
|
||||||
|
return &CJK_UI_FONT_DATA[idx * CJK_UI_FONT_BYTES_PER_CHAR];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a codepoint has a glyph in the built-in CJK UI font.
|
||||||
|
*/
|
||||||
|
inline bool hasCjkUiGlyph(uint32_t cp) {
|
||||||
|
if (cp > 0xFFFF) return false;
|
||||||
|
return findCjkUiGlyphIndex(static_cast<uint16_t>(cp)) >= 0;
|
||||||
|
}
|
||||||
2491
lib/GfxRenderer/cjk_ui_font_20.h
Normal file
2491
lib/GfxRenderer/cjk_ui_font_20.h
Normal file
File diff suppressed because it is too large
Load Diff
2963
lib/GfxRenderer/cjk_ui_font_22.h
Normal file
2963
lib/GfxRenderer/cjk_ui_font_22.h
Normal file
File diff suppressed because it is too large
Load Diff
2963
lib/GfxRenderer/cjk_ui_font_24.h
Normal file
2963
lib/GfxRenderer/cjk_ui_font_24.h
Normal file
File diff suppressed because it is too large
Load Diff
1031
lib/I18n/I18n.cpp
Normal file
1031
lib/I18n/I18n.cpp
Normal file
File diff suppressed because it is too large
Load Diff
350
lib/I18n/I18n.h
Normal file
350
lib/I18n/I18n.h
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internationalization (i18n) system for CrossPoint Reader
|
||||||
|
* Supports English and Chinese UI languages
|
||||||
|
*/
|
||||||
|
|
||||||
|
// String IDs - organized by category
|
||||||
|
enum class StrId : uint16_t {
|
||||||
|
// === Boot/Sleep ===
|
||||||
|
CROSSPOINT, // "CrossPoint"
|
||||||
|
BOOTING, // "BOOTING" / "启动中"
|
||||||
|
SLEEPING, // "SLEEPING" / "休眠中"
|
||||||
|
ENTERING_SLEEP, // "Entering Sleep..." / "进入休眠..."
|
||||||
|
|
||||||
|
// === Home Menu ===
|
||||||
|
BROWSE_FILES, // "Browse Files" / "浏览文件"
|
||||||
|
FILE_TRANSFER, // "File Transfer" / "文件传输"
|
||||||
|
SETTINGS_TITLE, // "Settings" / "设置"
|
||||||
|
CALIBRE_LIBRARY, // "Calibre Library" / "Calibre书库"
|
||||||
|
CONTINUE_READING, // "Continue Reading" / "继续阅读"
|
||||||
|
NO_OPEN_BOOK, // "No open book" / "无打开的书籍"
|
||||||
|
START_READING, // "Start reading below" / "从下方开始阅读"
|
||||||
|
|
||||||
|
// === File Browser ===
|
||||||
|
BOOKS, // "Books" / "书籍"
|
||||||
|
NO_BOOKS_FOUND, // "No books found" / "未找到书籍"
|
||||||
|
|
||||||
|
// === Reader ===
|
||||||
|
SELECT_CHAPTER, // "Select Chapter" / "选择章节"
|
||||||
|
NO_CHAPTERS, // "No chapters" / "无章节"
|
||||||
|
END_OF_BOOK, // "End of book" / "已到书末"
|
||||||
|
EMPTY_CHAPTER, // "Empty chapter" / "空章节"
|
||||||
|
INDEXING, // "Indexing..." / "索引中..."
|
||||||
|
MEMORY_ERROR, // "Memory error" / "内存错误"
|
||||||
|
PAGE_LOAD_ERROR, // "Page load error" / "页面加载错误"
|
||||||
|
EMPTY_FILE, // "Empty file" / "空文件"
|
||||||
|
OUT_OF_BOUNDS, // "Out of bounds" / "超出范围"
|
||||||
|
LOADING, // "Loading..." / "加载中..."
|
||||||
|
LOAD_XTC_FAILED, // "Failed to load XTC" / "加载XTC失败"
|
||||||
|
LOAD_TXT_FAILED, // "Failed to load TXT" / "加载TXT失败"
|
||||||
|
LOAD_EPUB_FAILED, // "Failed to load EPUB" / "加载EPUB失败"
|
||||||
|
SD_CARD_ERROR, // "SD card error" / "SD卡错误"
|
||||||
|
|
||||||
|
// === Network ===
|
||||||
|
WIFI_NETWORKS, // "WiFi Networks" / "WiFi网络"
|
||||||
|
NO_NETWORKS, // "No networks found" / "未找到网络"
|
||||||
|
NETWORKS_FOUND, // "%zu networks found" / "找到%zu个网络"
|
||||||
|
SCANNING, // "Scanning..." / "扫描中..."
|
||||||
|
CONNECTING, // "Connecting..." / "连接中..."
|
||||||
|
CONNECTED, // "Connected!" / "已连接!"
|
||||||
|
CONNECTION_FAILED, // "Connection Failed" / "连接失败"
|
||||||
|
CONNECTION_TIMEOUT, // "Connection timeout" / "连接超时"
|
||||||
|
FORGET_NETWORK, // "Forget Network?" / "忘记网络?"
|
||||||
|
SAVE_PASSWORD, // "Save password for next time?" / "保存密码?"
|
||||||
|
REMOVE_PASSWORD, // "Remove saved password?" / "删除已保存密码?"
|
||||||
|
PRESS_OK_SCAN, // "Press OK to scan again" / "按确定重新扫描"
|
||||||
|
PRESS_ANY_CONTINUE, // "Press any button to continue" / "按任意键继续"
|
||||||
|
SELECT_HINT, // "LEFT/RIGHT: Select | OK: Confirm" / "左/右:选择 | 确定:确认"
|
||||||
|
HOW_CONNECT, // "How would you like to connect?" / "选择连接方式"
|
||||||
|
JOIN_NETWORK, // "Join a Network" / "加入网络"
|
||||||
|
CREATE_HOTSPOT, // "Create Hotspot" / "创建热点"
|
||||||
|
JOIN_DESC, // "Connect to an existing WiFi network" / "连接到现有WiFi网络"
|
||||||
|
HOTSPOT_DESC, // "Create a WiFi network others can join" /
|
||||||
|
// "创建WiFi网络供他人连接"
|
||||||
|
STARTING_HOTSPOT, // "Starting Hotspot..." / "启动热点中..."
|
||||||
|
HOTSPOT_MODE, // "Hotspot Mode" / "热点模式"
|
||||||
|
CONNECT_WIFI_HINT, // "Connect your device to this WiFi network" /
|
||||||
|
// "将设备连接到此WiFi"
|
||||||
|
OPEN_URL_HINT, // "Open this URL in your browser" / "在浏览器中打开此URL"
|
||||||
|
OR_HTTP_PREFIX, // "or http://" / "或 http://"
|
||||||
|
SCAN_QR_HINT, // "or scan QR code with your phone:" / "或用手机扫描二维码:"
|
||||||
|
CALIBRE_WIRELESS, // "Calibre Wireless" / "Calibre无线连接"
|
||||||
|
CALIBRE_WEB_URL, // "Calibre Web URL" / "Calibre Web地址"
|
||||||
|
CONNECT_WIRELESS, // "Connect as Wireless Device" / "作为无线设备连接"
|
||||||
|
NETWORK_LEGEND, // "* = Encrypted | + = Saved" / "* = 加密 | + = 已保存"
|
||||||
|
MAC_ADDRESS, // "MAC address:" / "MAC地址:"
|
||||||
|
CHECKING_WIFI, // "Checking WiFi..." / "检查WiFi..."
|
||||||
|
ENTER_WIFI_PASSWORD, // "Enter WiFi Password" / "输入WiFi密码"
|
||||||
|
ENTER_TEXT, // "Enter Text" / "输入文字"
|
||||||
|
|
||||||
|
// === Calibre Wireless ===
|
||||||
|
CALIBRE_DISCOVERING, // "Discovering Calibre..." / "正在搜索Calibre..."
|
||||||
|
CALIBRE_CONNECTING_TO, // "Connecting to " / "正在连接到 "
|
||||||
|
CALIBRE_CONNECTED_TO, // "Connected to " / "已连接到 "
|
||||||
|
CALIBRE_WAITING_COMMANDS, // "Waiting for commands..." / "等待指令中..."
|
||||||
|
CONNECTION_FAILED_RETRYING, // "(Connection failed, retrying)" / "(连接失败,重试中)"
|
||||||
|
CALIBRE_DISCONNECTED, // "Calibre disconnected" / "Calibre已断开"
|
||||||
|
CALIBRE_WAITING_TRANSFER, // "Waiting for transfer..." / "等待传输中..."
|
||||||
|
CALIBRE_TRANSFER_HINT, // Transfer hint text
|
||||||
|
CALIBRE_RECEIVING, // "Receiving: " / "正在接收: "
|
||||||
|
CALIBRE_RECEIVED, // "Received: " / "已接收: "
|
||||||
|
CALIBRE_WAITING_MORE, // "Waiting for more..." / "等待更多内容..."
|
||||||
|
CALIBRE_FAILED_CREATE_FILE, // "Failed to create file" / "创建文件失败"
|
||||||
|
CALIBRE_PASSWORD_REQUIRED, // "Password required" / "需要密码"
|
||||||
|
CALIBRE_TRANSFER_INTERRUPTED, // "Transfer interrupted" / "传输中断"
|
||||||
|
|
||||||
|
// === Settings Categories ===
|
||||||
|
CAT_DISPLAY, // "Display" / "显示"
|
||||||
|
CAT_READER, // "Reader" / "阅读"
|
||||||
|
CAT_CONTROLS, // "Controls" / "控制"
|
||||||
|
CAT_SYSTEM, // "System" / "系统"
|
||||||
|
|
||||||
|
// === Settings ===
|
||||||
|
SLEEP_SCREEN, // "Sleep Screen" / "休眠屏幕"
|
||||||
|
SLEEP_COVER_MODE, // "Sleep Screen Cover Mode" / "封面显示模式"
|
||||||
|
STATUS_BAR, // "Status Bar" / "状态栏"
|
||||||
|
HIDE_BATTERY, // "Hide Battery %" / "隐藏电量百分比"
|
||||||
|
EXTRA_SPACING, // "Extra Paragraph Spacing" / "段落额外间距"
|
||||||
|
TEXT_AA, // "Text Anti-Aliasing" / "文字抗锯齿"
|
||||||
|
SHORT_PWR_BTN, // "Short Power Button Click" / "电源键短按"
|
||||||
|
ORIENTATION, // "Reading Orientation" / "阅读方向"
|
||||||
|
FRONT_BTN_LAYOUT, // "Front Button Layout" / "前置按钮布局"
|
||||||
|
SIDE_BTN_LAYOUT, // "Side Button Layout (reader)" / "侧边按钮布局"
|
||||||
|
LONG_PRESS_SKIP, // "Long-press Chapter Skip" / "长按跳转章节"
|
||||||
|
FONT_FAMILY, // "Reader Font Family" / "阅读字体"
|
||||||
|
EXT_READER_FONT, // "External Reader Font" / "阅读器字体"
|
||||||
|
EXT_CHINESE_FONT, // "Reader Font" / "阅读器字体"
|
||||||
|
EXT_UI_FONT, // "External UI Font" / "UI字体"
|
||||||
|
FONT_SIZE, // "Reader Font Size" / "字体大小"
|
||||||
|
LINE_SPACING, // "Reader Line Spacing" / "行间距"
|
||||||
|
ASCII_LETTER_SPACING, // "ASCII Letter Spacing" / "ASCII 字母间距"
|
||||||
|
ASCII_DIGIT_SPACING, // "ASCII Digit Spacing" / "ASCII 数字间距"
|
||||||
|
CJK_SPACING, // "CJK Spacing" / "汉字间距"
|
||||||
|
COLOR_MODE, // "Color Mode" / "颜色模式"
|
||||||
|
SCREEN_MARGIN, // "Reader Screen Margin" / "页面边距"
|
||||||
|
PARA_ALIGNMENT, // "Reader Paragraph Alignment" / "段落对齐"
|
||||||
|
HYPHENATION, // "Hyphenation" / "连字符"
|
||||||
|
TIME_TO_SLEEP, // "Time to Sleep" / "休眠时间"
|
||||||
|
REFRESH_FREQ, // "Refresh Frequency" / "刷新频率"
|
||||||
|
CALIBRE_SETTINGS, // "Calibre Settings" / "Calibre设置"
|
||||||
|
KOREADER_SYNC, // "KOReader Sync" / "KOReader同步"
|
||||||
|
CHECK_UPDATES, // "Check for updates" / "检查更新"
|
||||||
|
LANGUAGE, // "Language" / "语言"
|
||||||
|
SELECT_WALLPAPER, // "Select Wallpaper" / "选择壁纸"
|
||||||
|
CLEAR_READING_CACHE, // "Clear Reading Cache" / "清理阅读缓存"
|
||||||
|
|
||||||
|
// === Calibre Settings ===
|
||||||
|
CALIBRE, // "Calibre" / "Calibre"
|
||||||
|
|
||||||
|
// === KOReader Settings ===
|
||||||
|
USERNAME, // "Username" / "用户名"
|
||||||
|
PASSWORD, // "Password" / "密码"
|
||||||
|
SYNC_SERVER_URL, // "Sync Server URL" / "同步服务器地址"
|
||||||
|
DOCUMENT_MATCHING, // "Document Matching" / "文档匹配"
|
||||||
|
AUTHENTICATE, // "Authenticate" / "认证"
|
||||||
|
KOREADER_USERNAME, // "KOReader Username" / "KOReader用户名"
|
||||||
|
KOREADER_PASSWORD, // "KOReader Password" / "KOReader密码"
|
||||||
|
FILENAME, // "Filename" / "文件名"
|
||||||
|
BINARY, // "Binary" / "二进制"
|
||||||
|
SET_CREDENTIALS_FIRST, // "Set credentials first" / "请先设置凭据"
|
||||||
|
|
||||||
|
// === KOReader Auth ===
|
||||||
|
WIFI_CONN_FAILED, // "WiFi connection failed" / "WiFi连接失败"
|
||||||
|
AUTHENTICATING, // "Authenticating..." / "认证中..."
|
||||||
|
AUTH_SUCCESS, // "Successfully authenticated!" / "认证成功!"
|
||||||
|
KOREADER_AUTH, // "KOReader Auth" / "KOReader认证"
|
||||||
|
SYNC_READY, // "KOReader sync is ready to use" / "KOReader同步已就绪"
|
||||||
|
AUTH_FAILED, // "Authentication Failed" / "认证失败"
|
||||||
|
DONE, // "Done" / "完成"
|
||||||
|
|
||||||
|
// === Clear Cache ===
|
||||||
|
CLEAR_CACHE_WARNING_1, // "This will clear all cached book data." / "这将清除所有缓存的书籍数据。"
|
||||||
|
CLEAR_CACHE_WARNING_2, // "All reading progress will be lost!" / "所有阅读进度将丢失!"
|
||||||
|
CLEAR_CACHE_WARNING_3, // "Books will need to be re-indexed" / "书籍需要重新索引"
|
||||||
|
CLEAR_CACHE_WARNING_4, // "when opened again." / "当再次打开时。"
|
||||||
|
CLEARING_CACHE, // "Clearing cache..." / "清理缓存中..."
|
||||||
|
CACHE_CLEARED, // "Cache Cleared" / "缓存已清理"
|
||||||
|
ITEMS_REMOVED, // "items removed" / "项已删除"
|
||||||
|
FAILED_LOWER, // "failed" / "失败"
|
||||||
|
CLEAR_CACHE_FAILED, // "Failed to clear cache" / "清理缓存失败"
|
||||||
|
CHECK_SERIAL_OUTPUT, // "Check serial output for details" / "查看串口输出了解详情"
|
||||||
|
|
||||||
|
// Setting Values
|
||||||
|
DARK, // "Dark" / "深色"
|
||||||
|
LIGHT, // "Light" / "浅色"
|
||||||
|
CUSTOM, // "Custom" / "自定义"
|
||||||
|
COVER, // "Cover" / "封面"
|
||||||
|
NONE, // "None" / "无"
|
||||||
|
FIT, // "Fit" / "适应"
|
||||||
|
CROP, // "Crop" / "裁剪"
|
||||||
|
NO_PROGRESS, // "No Progress" / "无进度"
|
||||||
|
FULL, // "Full" / "完整"
|
||||||
|
NEVER, // "Never" / "从不"
|
||||||
|
IN_READER, // "In Reader" / "阅读时"
|
||||||
|
ALWAYS, // "Always" / "始终"
|
||||||
|
IGNORE, // "Ignore" / "忽略"
|
||||||
|
SLEEP, // "Sleep" / "休眠"
|
||||||
|
PAGE_TURN, // "Page Turn" / "翻页"
|
||||||
|
PORTRAIT, // "Portrait" / "竖屏"
|
||||||
|
LANDSCAPE_CW, // "Landscape CW" / "横屏顺时针"
|
||||||
|
INVERTED, // "Inverted" / "倒置"
|
||||||
|
LANDSCAPE_CCW, // "Landscape CCW" / "横屏逆时针"
|
||||||
|
FRONT_LAYOUT_BCLR, // "Bck, Cnfrm, Lft, Rght" / "返回, 确认, 左, 右"
|
||||||
|
FRONT_LAYOUT_LRBC, // "Lft, Rght, Bck, Cnfrm" / "左, 右, 返回, 确认"
|
||||||
|
FRONT_LAYOUT_LBCR, // "Lft, Bck, Cnfrm, Rght" / "左, 返回, 确认, 右"
|
||||||
|
PREV_NEXT, // "Prev/Next" / "上一页/下一页"
|
||||||
|
NEXT_PREV, // "Next/Prev" / "下一页/上一页"
|
||||||
|
BOOKERLY, // "Bookerly"
|
||||||
|
NOTO_SANS, // "Noto Sans"
|
||||||
|
OPEN_DYSLEXIC, // "Open Dyslexic"
|
||||||
|
SMALL, // "Small" / "小"
|
||||||
|
MEDIUM, // "Medium" / "中"
|
||||||
|
LARGE, // "Large" / "大"
|
||||||
|
X_LARGE, // "X Large" / "特大"
|
||||||
|
TIGHT, // "Tight" / "紧凑"
|
||||||
|
NORMAL, // "Normal" / "正常"
|
||||||
|
WIDE, // "Wide" / "宽松"
|
||||||
|
JUSTIFY, // "Justify" / "两端对齐"
|
||||||
|
LEFT, // "Left" / "左对齐"
|
||||||
|
CENTER, // "Center" / "居中"
|
||||||
|
RIGHT, // "Right" / "右对齐"
|
||||||
|
MIN_1, // "1 min" / "1分钟"
|
||||||
|
MIN_5, // "5 min" / "5分钟"
|
||||||
|
MIN_10, // "10 min" / "10分钟"
|
||||||
|
MIN_15, // "15 min" / "15分钟"
|
||||||
|
MIN_30, // "30 min" / "30分钟"
|
||||||
|
PAGES_1, // "1 page" / "1页"
|
||||||
|
PAGES_5, // "5 pages" / "5页"
|
||||||
|
PAGES_10, // "10 pages" / "10页"
|
||||||
|
PAGES_15, // "15 pages" / "15页"
|
||||||
|
PAGES_30, // "30 pages" / "30页"
|
||||||
|
|
||||||
|
// === OTA Update ===
|
||||||
|
UPDATE, // "Update" / "更新"
|
||||||
|
CHECKING_UPDATE, // "Checking for update..." / "检查更新中..."
|
||||||
|
NEW_UPDATE, // "New update available!" / "有新版本可用!"
|
||||||
|
CURRENT_VERSION, // "Current Version: " / "当前版本: "
|
||||||
|
NEW_VERSION, // "New Version: " / "新版本: "
|
||||||
|
UPDATING, // "Updating..." / "更新中..."
|
||||||
|
NO_UPDATE, // "No update available" / "已是最新版本"
|
||||||
|
UPDATE_FAILED, // "Update failed" / "更新失败"
|
||||||
|
UPDATE_COMPLETE, // "Update complete" / "更新完成"
|
||||||
|
POWER_ON_HINT, // "Press and hold power button to turn back on" /
|
||||||
|
// "长按电源键开机"
|
||||||
|
|
||||||
|
// === Font Selection ===
|
||||||
|
EXTERNAL_FONT, // "External Font" / "外置字体"
|
||||||
|
BUILTIN_DISABLED, // "Built-in (Disabled)" / "内置(已禁用)"
|
||||||
|
|
||||||
|
// === OPDS Browser ===
|
||||||
|
NO_ENTRIES, // "No entries found" / "无条目"
|
||||||
|
DOWNLOADING, // "Downloading..." / "下载中..."
|
||||||
|
DOWNLOAD_FAILED, // "Download failed" / "下载失败"
|
||||||
|
ERROR, // "Error:" / "错误:"
|
||||||
|
UNNAMED, // "Unnamed" / "未命名"
|
||||||
|
NO_SERVER_URL, // "No server URL configured" / "未配置服务器地址"
|
||||||
|
FETCH_FEED_FAILED, // "Failed to fetch feed" / "获取订阅失败"
|
||||||
|
PARSE_FEED_FAILED, // "Failed to parse feed" / "解析订阅失败"
|
||||||
|
NETWORK_PREFIX, // "Network: " / "网络: "
|
||||||
|
IP_ADDRESS_PREFIX, // "IP Address: " / "IP地址: "
|
||||||
|
SCAN_QR_WIFI_HINT, // "or scan QR code with your phone to connect to Wifi." /
|
||||||
|
// "或用手机扫描二维码连接WiFi"
|
||||||
|
|
||||||
|
// === Buttons ===
|
||||||
|
BACK, // "« Back" / "« 返回"
|
||||||
|
EXIT, // "« Exit" / "« 退出"
|
||||||
|
HOME, // "« Home" / "« 主页"
|
||||||
|
SAVE, // "« Save" / "« 保存"
|
||||||
|
SELECT, // "Select" / "选择"
|
||||||
|
TOGGLE, // "Toggle" / "切换"
|
||||||
|
CONFIRM, // "Confirm" / "确定"
|
||||||
|
CANCEL, // "Cancel" / "取消"
|
||||||
|
CONNECT, // "Connect" / "连接"
|
||||||
|
OPEN, // "Open" / "打开"
|
||||||
|
DOWNLOAD, // "Download" / "下载"
|
||||||
|
RETRY, // "Retry" / "重试"
|
||||||
|
YES, // "Yes" / "是"
|
||||||
|
NO, // "No" / "否"
|
||||||
|
ON, // "ON" / "开"
|
||||||
|
OFF, // "OFF" / "关"
|
||||||
|
SET, // "Set" / "已设置"
|
||||||
|
NOT_SET, // "Not Set" / "未设置"
|
||||||
|
DIR_LEFT, // "Left" / "左"
|
||||||
|
DIR_RIGHT, // "Right" / "右"
|
||||||
|
DIR_UP, // "Up" / "上"
|
||||||
|
DIR_DOWN, // "Down" / "下"
|
||||||
|
CAPS_ON, // "CAPS" / "大写"
|
||||||
|
CAPS_OFF, // "caps" / "小写"
|
||||||
|
OK_BUTTON, // "OK" / "确定"
|
||||||
|
|
||||||
|
// === Languages ===
|
||||||
|
ENGLISH, // "English"
|
||||||
|
CHINESE_SIMPLIFIED, // "简体中文"
|
||||||
|
JAPANESE, // "日本語"
|
||||||
|
|
||||||
|
// Sentinel - must be last
|
||||||
|
_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
// Language enum
|
||||||
|
enum class Language : uint8_t {
|
||||||
|
ENGLISH = 0,
|
||||||
|
CHINESE_SIMPLIFIED = 1,
|
||||||
|
JAPANESE = 2,
|
||||||
|
_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
class I18n {
|
||||||
|
public:
|
||||||
|
static I18n &getInstance();
|
||||||
|
|
||||||
|
// Disable copy
|
||||||
|
I18n(const I18n &) = delete;
|
||||||
|
I18n &operator=(const I18n &) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get localized string by ID
|
||||||
|
*/
|
||||||
|
const char *get(StrId id) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand operator for get()
|
||||||
|
*/
|
||||||
|
const char *operator[](StrId id) const { return get(id); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get/Set current language
|
||||||
|
*/
|
||||||
|
Language getLanguage() const { return _language; }
|
||||||
|
void setLanguage(Language lang);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save/Load language setting
|
||||||
|
*/
|
||||||
|
void saveSettings();
|
||||||
|
void loadSettings();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all unique characters used in a specific language
|
||||||
|
* Returns a sorted string of unique characters
|
||||||
|
*/
|
||||||
|
static const char *getCharacterSet(Language lang);
|
||||||
|
|
||||||
|
// String arrays (public for static_assert access)
|
||||||
|
static const char *const STRINGS_EN[];
|
||||||
|
static const char *const STRINGS_ZH[];
|
||||||
|
static const char *const STRINGS_JA[];
|
||||||
|
|
||||||
|
private:
|
||||||
|
I18n() : _language(Language::ENGLISH) {}
|
||||||
|
|
||||||
|
Language _language;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convenience macros
|
||||||
|
#define TR(id) I18n::getInstance().get(StrId::id)
|
||||||
|
#define I18N I18n::getInstance()
|
||||||
@ -1 +1 @@
|
|||||||
Subproject commit bd4e6707503ab9c97d13ee0d8f8c69e9ff03cd12
|
Subproject commit 6fa905c7b977df254c3642c35c6277e4e588abf8
|
||||||
271
scripts/generate_cjk_ui_font.py
Normal file
271
scripts/generate_cjk_ui_font.py
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate CJK UI font header file for CrossPoint Reader.
|
||||||
|
Uses Source Han Sans (思源黑体) to generate bitmap font data.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 generate_cjk_ui_font.py --size 26 --font /path/to/SourceHanSansSC-Medium.otf
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
except ImportError:
|
||||||
|
print("Error: PIL/Pillow not installed. Run: pip3 install Pillow")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# UI characters needed (extracted from I18n strings + common punctuation)
|
||||||
|
UI_CHARS = """
|
||||||
|
!#$%&'()*+,-./0123456789:;<=>?@
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`
|
||||||
|
abcdefghijklmnopqrstuvwxyz{|}~
|
||||||
|
启动中休眠进入浏览文件传输设置书库继续阅读无打开的籍从下方开始
|
||||||
|
未找到选择章节空已末索引内存错误页面加载
|
||||||
|
网络扫描连接失败超时忘记保存密码删除按确定重新任意键
|
||||||
|
左右上确认加热点现有供他人
|
||||||
|
此址或手机二维码地检查输入文字
|
||||||
|
正在搜寻等待指令断更多容创建需要中
|
||||||
|
屏幕封显示模式状态栏隐藏电量百分比段落额外间距抗锯齿
|
||||||
|
电源短阅读方向前置按钮布局侧边长跳转字体大小行
|
||||||
|
颜色边距对齐时间刷新频率语言壁纸清理缓存
|
||||||
|
深浅自定义适应裁剪度完整从不始终忽略翻竖横顺逆针
|
||||||
|
返确左右上一下紧凑正常宽松两端居分钟
|
||||||
|
版本新可用当前中检查数据成功信息
|
||||||
|
外内禁停全最后退出主保切换取消打回重试是否关
|
||||||
|
大写小决
|
||||||
|
英简繁體日本語
|
||||||
|
个令信先写制力务卡去受同名命壁多容待得必指控搜收数断服期析步母汉清理符等简系纸统缓获要解订需颜
|
||||||
|
ファイル一覧転送定ライブラリ続読開本ありません下書始
|
||||||
|
見つかりませんを章なし終わり空のインデックスメモリエラー
|
||||||
|
ページ込み範囲外失敗しました
|
||||||
|
ネットワークスキャン接続完了タイムアウト忘れるパスワード
|
||||||
|
押して再任意ボタン行方法参加ホットスポット作成既存
|
||||||
|
デバイスブラウザこのURLまたはスマホQRコードをして
|
||||||
|
無線ケーブル送受信待機
|
||||||
|
画面カバー非表示常にベル配置向きボン前後サイド押し飛ばし
|
||||||
|
フォントサイズ幅余白揃え分
|
||||||
|
アップート利チェック新しいバージョン現更失敗完成功
|
||||||
|
フ使用可中壊オフオンセ解除左右上回戻キャンセル再度はいいいえ
|
||||||
|
検機決漢紙題
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Extract unique characters
|
||||||
|
def get_unique_chars(text):
|
||||||
|
chars = set()
|
||||||
|
for c in text:
|
||||||
|
if c.strip() and ord(c) >= 0x20:
|
||||||
|
chars.add(c)
|
||||||
|
return sorted(chars, key=ord)
|
||||||
|
|
||||||
|
def generate_font_header(font_path, pixel_size, output_path):
|
||||||
|
"""Generate CJK UI font header file."""
|
||||||
|
|
||||||
|
# Calculate font point size (approximately pixel_size * 0.7)
|
||||||
|
pt_size = int(pixel_size * 0.7)
|
||||||
|
|
||||||
|
try:
|
||||||
|
font = ImageFont.truetype(font_path, pt_size)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading font: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
chars = get_unique_chars(UI_CHARS)
|
||||||
|
print(f"Generating {pixel_size}x{pixel_size} font with {len(chars)} characters...")
|
||||||
|
|
||||||
|
# Collect glyph data
|
||||||
|
codepoints = []
|
||||||
|
widths = []
|
||||||
|
bitmaps = []
|
||||||
|
|
||||||
|
for char in chars:
|
||||||
|
cp = ord(char)
|
||||||
|
|
||||||
|
# Create image for character
|
||||||
|
img = Image.new('1', (pixel_size, pixel_size), 0)
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
|
# Get character bounding box
|
||||||
|
try:
|
||||||
|
bbox = font.getbbox(char)
|
||||||
|
if bbox:
|
||||||
|
char_width = bbox[2] - bbox[0]
|
||||||
|
char_height = bbox[3] - bbox[1]
|
||||||
|
else:
|
||||||
|
char_width = pixel_size // 2
|
||||||
|
char_height = pixel_size
|
||||||
|
except:
|
||||||
|
char_width = pixel_size // 2
|
||||||
|
char_height = pixel_size
|
||||||
|
|
||||||
|
# Center character in cell
|
||||||
|
x = (pixel_size - char_width) // 2
|
||||||
|
y = (pixel_size - char_height) // 2 - (bbox[1] if bbox else 0)
|
||||||
|
|
||||||
|
# Draw character
|
||||||
|
draw.text((x, y), char, font=font, fill=1)
|
||||||
|
|
||||||
|
# Convert to bytes
|
||||||
|
bytes_per_row = (pixel_size + 7) // 8
|
||||||
|
bitmap_bytes = []
|
||||||
|
for row in range(pixel_size):
|
||||||
|
for byte_idx in range(bytes_per_row):
|
||||||
|
byte_val = 0
|
||||||
|
for bit in range(8):
|
||||||
|
px = byte_idx * 8 + bit
|
||||||
|
if px < pixel_size:
|
||||||
|
pixel = img.getpixel((px, row))
|
||||||
|
if pixel:
|
||||||
|
byte_val |= (1 << (7 - bit))
|
||||||
|
bitmap_bytes.append(byte_val)
|
||||||
|
|
||||||
|
codepoints.append(cp)
|
||||||
|
# Calculate advance width
|
||||||
|
if cp < 0x80:
|
||||||
|
# ASCII: use actual width + small padding
|
||||||
|
widths.append(min(char_width + 2, pixel_size))
|
||||||
|
else:
|
||||||
|
# CJK: use full width
|
||||||
|
widths.append(pixel_size)
|
||||||
|
bitmaps.append(bitmap_bytes)
|
||||||
|
|
||||||
|
# Generate header file
|
||||||
|
bytes_per_row = (pixel_size + 7) // 8
|
||||||
|
bytes_per_char = bytes_per_row * pixel_size
|
||||||
|
|
||||||
|
with open(output_path, 'w') as f:
|
||||||
|
f.write(f'''/**
|
||||||
|
* Auto-generated CJK UI font data (optimized - UI characters only)
|
||||||
|
* Font: 思源黑体-Medium
|
||||||
|
* Size: {pt_size}pt
|
||||||
|
* Dimensions: {pixel_size}x{pixel_size}
|
||||||
|
* Characters: {len(chars)}
|
||||||
|
* Total size: {len(chars) * bytes_per_char} bytes ({len(chars) * bytes_per_char / 1024:.1f} KB)
|
||||||
|
*
|
||||||
|
* This is a sparse font containing only UI-required CJK characters.
|
||||||
|
* Uses a lookup table for codepoint -> glyph index mapping.
|
||||||
|
* Supports proportional spacing for English characters.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
namespace CjkUiFont{pixel_size} {{
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <pgmspace.h>
|
||||||
|
|
||||||
|
// Font parameters
|
||||||
|
static constexpr uint8_t CJK_UI_FONT_WIDTH = {pixel_size};
|
||||||
|
static constexpr uint8_t CJK_UI_FONT_HEIGHT = {pixel_size};
|
||||||
|
static constexpr uint8_t CJK_UI_FONT_BYTES_PER_ROW = {bytes_per_row};
|
||||||
|
static constexpr uint8_t CJK_UI_FONT_BYTES_PER_CHAR = {bytes_per_char};
|
||||||
|
static constexpr uint16_t CJK_UI_FONT_GLYPH_COUNT = {len(chars)};
|
||||||
|
|
||||||
|
// Codepoint lookup table (sorted for binary search)
|
||||||
|
static const uint16_t CJK_UI_CODEPOINTS[] PROGMEM = {{
|
||||||
|
''')
|
||||||
|
|
||||||
|
# Write codepoints
|
||||||
|
for i, cp in enumerate(codepoints):
|
||||||
|
if i % 16 == 0:
|
||||||
|
f.write(' ')
|
||||||
|
f.write(f'0x{cp:04X}, ')
|
||||||
|
if (i + 1) % 16 == 0:
|
||||||
|
f.write('\n')
|
||||||
|
if len(codepoints) % 16 != 0:
|
||||||
|
f.write('\n')
|
||||||
|
f.write('};\n\n')
|
||||||
|
|
||||||
|
# Write widths
|
||||||
|
f.write('// Glyph width table (actual advance width for proportional spacing)\n')
|
||||||
|
f.write('static const uint8_t CJK_UI_GLYPH_WIDTHS[] PROGMEM = {\n')
|
||||||
|
for i, w in enumerate(widths):
|
||||||
|
if i % 16 == 0:
|
||||||
|
f.write(' ')
|
||||||
|
f.write(f'{w:3}, ')
|
||||||
|
if (i + 1) % 16 == 0:
|
||||||
|
f.write('\n')
|
||||||
|
if len(widths) % 16 != 0:
|
||||||
|
f.write('\n')
|
||||||
|
f.write('};\n\n')
|
||||||
|
|
||||||
|
# Write bitmap data
|
||||||
|
f.write('// Glyph bitmap data\n')
|
||||||
|
f.write('static const uint8_t CJK_UI_GLYPHS[] PROGMEM = {\n')
|
||||||
|
for i, bitmap in enumerate(bitmaps):
|
||||||
|
f.write(f' // U+{codepoints[i]:04X} ({chr(codepoints[i])})\n ')
|
||||||
|
for j, b in enumerate(bitmap):
|
||||||
|
f.write(f'0x{b:02X}, ')
|
||||||
|
if (j + 1) % 16 == 0 and j < len(bitmap) - 1:
|
||||||
|
f.write('\n ')
|
||||||
|
f.write('\n')
|
||||||
|
f.write('};\n\n')
|
||||||
|
|
||||||
|
# Write lookup functions
|
||||||
|
f.write('''// Binary search for codepoint
|
||||||
|
inline int findGlyphIndex(uint16_t codepoint) {
|
||||||
|
int low = 0;
|
||||||
|
int high = CJK_UI_FONT_GLYPH_COUNT - 1;
|
||||||
|
while (low <= high) {
|
||||||
|
int mid = (low + high) / 2;
|
||||||
|
uint16_t midCp = pgm_read_word(&CJK_UI_CODEPOINTS[mid]);
|
||||||
|
if (midCp == codepoint) return mid;
|
||||||
|
if (midCp < codepoint) low = mid + 1;
|
||||||
|
else high = mid - 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool hasCjkUiGlyph(uint32_t codepoint) {
|
||||||
|
if (codepoint > 0xFFFF) return false;
|
||||||
|
return findGlyphIndex(static_cast<uint16_t>(codepoint)) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const uint8_t* getCjkUiGlyph(uint32_t codepoint) {
|
||||||
|
if (codepoint > 0xFFFF) return nullptr;
|
||||||
|
int idx = findGlyphIndex(static_cast<uint16_t>(codepoint));
|
||||||
|
if (idx < 0) return nullptr;
|
||||||
|
return &CJK_UI_GLYPHS[idx * CJK_UI_FONT_BYTES_PER_CHAR];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint8_t getCjkUiGlyphWidth(uint32_t codepoint) {
|
||||||
|
if (codepoint > 0xFFFF) return 0;
|
||||||
|
int idx = findGlyphIndex(static_cast<uint16_t>(codepoint));
|
||||||
|
if (idx < 0) return 0;
|
||||||
|
return pgm_read_byte(&CJK_UI_GLYPH_WIDTHS[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace CjkUiFont''' + str(pixel_size) + '\n')
|
||||||
|
|
||||||
|
print(f"Generated: {output_path}")
|
||||||
|
print(f" - {len(chars)} characters")
|
||||||
|
print(f" - {len(chars) * bytes_per_char} bytes bitmap data")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='Generate CJK UI font header')
|
||||||
|
parser.add_argument('--size', type=int, default=26, help='Pixel size (default: 26)')
|
||||||
|
parser.add_argument('--font', type=str, required=True, help='Path to Source Han Sans font file')
|
||||||
|
parser.add_argument('--output', type=str, help='Output path (default: lib/GfxRenderer/cjk_ui_font_SIZE.h)')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
script_dir = Path(__file__).parent
|
||||||
|
project_root = script_dir.parent
|
||||||
|
|
||||||
|
if args.output:
|
||||||
|
output_path = Path(args.output)
|
||||||
|
else:
|
||||||
|
output_path = project_root / 'lib' / 'GfxRenderer' / f'cjk_ui_font_{args.size}.h'
|
||||||
|
|
||||||
|
if not Path(args.font).exists():
|
||||||
|
print(f"Error: Font file not found: {args.font}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if generate_font_header(args.font, args.size, output_path):
|
||||||
|
print("Success!")
|
||||||
|
else:
|
||||||
|
print("Failed!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@ -1,5 +1,6 @@
|
|||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
|
|
||||||
|
#include <FontManager.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
@ -196,6 +197,16 @@ int CrossPointSettings::getRefreshFrequency() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int CrossPointSettings::getReaderFontId() const {
|
int CrossPointSettings::getReaderFontId() const {
|
||||||
|
// Check if external font is enabled - if so, return a unique ID based on font index
|
||||||
|
// This ensures cache invalidation when external font changes
|
||||||
|
FontManager &fm = FontManager::getInstance();
|
||||||
|
if (fm.isExternalFontEnabled()) {
|
||||||
|
// Return a unique negative ID based on external font index
|
||||||
|
// Using negative values to avoid collision with built-in font IDs
|
||||||
|
return -(fm.getSelectedIndex() + 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to built-in font selection
|
||||||
switch (fontFamily) {
|
switch (fontFamily) {
|
||||||
case BOOKERLY:
|
case BOOKERLY:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -72,6 +72,8 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
|||||||
return InputManager::BTN_BACK;
|
return InputManager::BTN_BACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MappedInputManager::update() { inputManager.update(); }
|
||||||
|
|
||||||
bool MappedInputManager::wasPressed(const Button button) const { return inputManager.wasPressed(mapButton(button)); }
|
bool MappedInputManager::wasPressed(const Button button) const { return inputManager.wasPressed(mapButton(button)); }
|
||||||
|
|
||||||
bool MappedInputManager::wasReleased(const Button button) const { return inputManager.wasReleased(mapButton(button)); }
|
bool MappedInputManager::wasReleased(const Button button) const { return inputManager.wasReleased(mapButton(button)); }
|
||||||
|
|||||||
@ -15,6 +15,7 @@ class MappedInputManager {
|
|||||||
|
|
||||||
explicit MappedInputManager(InputManager& inputManager) : inputManager(inputManager) {}
|
explicit MappedInputManager(InputManager& inputManager) : inputManager(inputManager) {}
|
||||||
|
|
||||||
|
void update(); // Update button state (call before wasPressed/wasReleased)
|
||||||
bool wasPressed(Button button) const;
|
bool wasPressed(Button button) const;
|
||||||
bool wasReleased(Button button) const;
|
bool wasReleased(Button button) const;
|
||||||
bool isPressed(Button button) const;
|
bool isPressed(Button button) const;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "BootActivity.h"
|
#include "BootActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
#include "images/CrossLarge.h"
|
#include "images/CrossLarge.h"
|
||||||
@ -13,8 +14,8 @@ void BootActivity::onEnter() {
|
|||||||
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawImage(CrossLarge, (pageWidth + 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
renderer.drawImage(CrossLarge, (pageWidth + 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, TR(CROSSPOINT), true, EpdFontFamily::BOLD);
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING");
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, TR(BOOTING));
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION);
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <Txt.h>
|
#include <Txt.h>
|
||||||
#include <Xtc.h>
|
#include <Xtc.h>
|
||||||
@ -14,7 +15,7 @@
|
|||||||
|
|
||||||
void SleepActivity::onEnter() {
|
void SleepActivity::onEnter() {
|
||||||
Activity::onEnter();
|
Activity::onEnter();
|
||||||
renderPopup("Entering Sleep...");
|
renderPopup(TR(ENTERING_SLEEP));
|
||||||
|
|
||||||
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::BLANK) {
|
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::BLANK) {
|
||||||
return renderBlankSleepScreen();
|
return renderBlankSleepScreen();
|
||||||
@ -125,8 +126,8 @@ void SleepActivity::renderDefaultSleepScreen() const {
|
|||||||
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawImage(CrossLarge, (pageWidth + 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
renderer.drawImage(CrossLarge, (pageWidth + 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, TR(CROSSPOINT), true, EpdFontFamily::BOLD);
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, TR(SLEEPING));
|
||||||
|
|
||||||
// Make sleep screen dark unless light is selected in settings
|
// Make sleep screen dark unless light is selected in settings
|
||||||
if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) {
|
if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <OpdsStream.h>
|
#include <OpdsStream.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ void OpdsBookBrowserActivity::onEnter() {
|
|||||||
currentPath = OPDS_ROOT_PATH;
|
currentPath = OPDS_ROOT_PATH;
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
errorMessage.clear();
|
errorMessage.clear();
|
||||||
statusMessage = "Checking WiFi...";
|
statusMessage = TR(CHECKING_WIFI);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&OpdsBookBrowserActivity::taskTrampoline, "OpdsBookBrowserTask",
|
xTaskCreate(&OpdsBookBrowserActivity::taskTrampoline, "OpdsBookBrowserTask",
|
||||||
@ -82,7 +83,7 @@ void OpdsBookBrowserActivity::loop() {
|
|||||||
// WiFi connected - just retry fetching the feed
|
// WiFi connected - just retry fetching the feed
|
||||||
Serial.printf("[%lu] [OPDS] Retry: WiFi connected, retrying fetch\n", millis());
|
Serial.printf("[%lu] [OPDS] Retry: WiFi connected, retrying fetch\n", millis());
|
||||||
state = BrowserState::LOADING;
|
state = BrowserState::LOADING;
|
||||||
statusMessage = "Loading...";
|
statusMessage = TR(LOADING);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
fetchFeed(currentPath);
|
fetchFeed(currentPath);
|
||||||
} else {
|
} else {
|
||||||
@ -172,11 +173,11 @@ void OpdsBookBrowserActivity::render() const {
|
|||||||
const auto pageWidth = renderer.getScreenWidth();
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Calibre Library", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(CALIBRE_LIBRARY), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
if (state == BrowserState::CHECK_WIFI) {
|
if (state == BrowserState::CHECK_WIFI) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, statusMessage.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, statusMessage.c_str());
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), "", "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
@ -184,23 +185,23 @@ void OpdsBookBrowserActivity::render() const {
|
|||||||
|
|
||||||
if (state == BrowserState::LOADING) {
|
if (state == BrowserState::LOADING) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, statusMessage.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, statusMessage.c_str());
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), "", "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == BrowserState::ERROR) {
|
if (state == BrowserState::ERROR) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Error:");
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, TR(ERROR));
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, errorMessage.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, errorMessage.c_str());
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Retry", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(RETRY), "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == BrowserState::DOWNLOADING) {
|
if (state == BrowserState::DOWNLOADING) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 40, "Downloading...");
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 40, TR(DOWNLOADING));
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 10, statusMessage.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 10, statusMessage.c_str());
|
||||||
if (downloadTotal > 0) {
|
if (downloadTotal > 0) {
|
||||||
const int barWidth = pageWidth - 100;
|
const int barWidth = pageWidth - 100;
|
||||||
@ -215,15 +216,15 @@ void OpdsBookBrowserActivity::render() const {
|
|||||||
|
|
||||||
// Browsing state
|
// Browsing state
|
||||||
// Show appropriate button hint based on selected entry type
|
// Show appropriate button hint based on selected entry type
|
||||||
const char* confirmLabel = "Open";
|
const char* confirmLabel = TR(OPEN);
|
||||||
if (!entries.empty() && entries[selectorIndex].type == OpdsEntryType::BOOK) {
|
if (!entries.empty() && entries[selectorIndex].type == OpdsEntryType::BOOK) {
|
||||||
confirmLabel = "Download";
|
confirmLabel = TR(DOWNLOAD);
|
||||||
}
|
}
|
||||||
const auto labels = mappedInput.mapLabels("« Back", confirmLabel, "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), confirmLabel, "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
if (entries.empty()) {
|
if (entries.empty()) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "No entries found");
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, TR(NO_ENTRIES));
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -258,7 +259,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) {
|
|||||||
const char* serverUrl = SETTINGS.opdsServerUrl;
|
const char* serverUrl = SETTINGS.opdsServerUrl;
|
||||||
if (strlen(serverUrl) == 0) {
|
if (strlen(serverUrl) == 0) {
|
||||||
state = BrowserState::ERROR;
|
state = BrowserState::ERROR;
|
||||||
errorMessage = "No server URL configured";
|
errorMessage = TR(NO_SERVER_URL);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -272,7 +273,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) {
|
|||||||
OpdsParserStream stream{parser};
|
OpdsParserStream stream{parser};
|
||||||
if (!HttpDownloader::fetchUrl(url, stream)) {
|
if (!HttpDownloader::fetchUrl(url, stream)) {
|
||||||
state = BrowserState::ERROR;
|
state = BrowserState::ERROR;
|
||||||
errorMessage = "Failed to fetch feed";
|
errorMessage = TR(FETCH_FEED_FAILED);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -280,7 +281,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) {
|
|||||||
|
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
state = BrowserState::ERROR;
|
state = BrowserState::ERROR;
|
||||||
errorMessage = "Failed to parse feed";
|
errorMessage = TR(PARSE_FEED_FAILED);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -291,7 +292,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) {
|
|||||||
|
|
||||||
if (entries.empty()) {
|
if (entries.empty()) {
|
||||||
state = BrowserState::ERROR;
|
state = BrowserState::ERROR;
|
||||||
errorMessage = "No entries found";
|
errorMessage = TR(NO_ENTRIES);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -306,7 +307,7 @@ void OpdsBookBrowserActivity::navigateToEntry(const OpdsEntry& entry) {
|
|||||||
currentPath = entry.href;
|
currentPath = entry.href;
|
||||||
|
|
||||||
state = BrowserState::LOADING;
|
state = BrowserState::LOADING;
|
||||||
statusMessage = "Loading...";
|
statusMessage = TR(LOADING);
|
||||||
entries.clear();
|
entries.clear();
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@ -324,7 +325,7 @@ void OpdsBookBrowserActivity::navigateBack() {
|
|||||||
navigationHistory.pop_back();
|
navigationHistory.pop_back();
|
||||||
|
|
||||||
state = BrowserState::LOADING;
|
state = BrowserState::LOADING;
|
||||||
statusMessage = "Loading...";
|
statusMessage = TR(LOADING);
|
||||||
entries.clear();
|
entries.clear();
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@ -371,7 +372,7 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else {
|
} else {
|
||||||
state = BrowserState::ERROR;
|
state = BrowserState::ERROR;
|
||||||
errorMessage = "Download failed";
|
errorMessage = TR(DOWNLOAD_FAILED);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -380,7 +381,7 @@ void OpdsBookBrowserActivity::checkAndConnectWifi() {
|
|||||||
// Already connected? Verify connection is valid by checking IP
|
// Already connected? Verify connection is valid by checking IP
|
||||||
if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) {
|
if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) {
|
||||||
state = BrowserState::LOADING;
|
state = BrowserState::LOADING;
|
||||||
statusMessage = "Loading...";
|
statusMessage = TR(LOADING);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
fetchFeed(currentPath);
|
fetchFeed(currentPath);
|
||||||
return;
|
return;
|
||||||
@ -404,7 +405,7 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) {
|
|||||||
if (connected) {
|
if (connected) {
|
||||||
Serial.printf("[%lu] [OPDS] WiFi connected via selection, fetching feed\n", millis());
|
Serial.printf("[%lu] [OPDS] WiFi connected via selection, fetching feed\n", millis());
|
||||||
state = BrowserState::LOADING;
|
state = BrowserState::LOADING;
|
||||||
statusMessage = "Loading...";
|
statusMessage = TR(LOADING);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
fetchFeed(currentPath);
|
fetchFeed(currentPath);
|
||||||
} else {
|
} else {
|
||||||
@ -414,7 +415,7 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) {
|
|||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
WiFi.mode(WIFI_OFF);
|
WiFi.mode(WIFI_OFF);
|
||||||
state = BrowserState::ERROR;
|
state = BrowserState::ERROR;
|
||||||
errorMessage = "WiFi connection failed";
|
errorMessage = TR(CONNECTION_FAILED);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <Bitmap.h>
|
#include <Bitmap.h>
|
||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <Xtc.h>
|
#include <Xtc.h>
|
||||||
|
|
||||||
@ -477,7 +478,7 @@ void HomeActivity::render() {
|
|||||||
const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
|
const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
|
||||||
if (coverRendered) {
|
if (coverRendered) {
|
||||||
// Draw box behind "Continue Reading" text (inverted when selected: black box instead of white)
|
// Draw box behind "Continue Reading" text (inverted when selected: black box instead of white)
|
||||||
const char* continueText = "Continue Reading";
|
const char* continueText = TR(CONTINUE_READING);
|
||||||
const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText);
|
const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText);
|
||||||
constexpr int continuePadding = 6;
|
constexpr int continuePadding = 6;
|
||||||
const int continueBoxWidth = continueTextWidth + continuePadding * 2;
|
const int continueBoxWidth = continueTextWidth + continuePadding * 2;
|
||||||
@ -488,22 +489,22 @@ void HomeActivity::render() {
|
|||||||
renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, !bookSelected);
|
renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, !bookSelected);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, !bookSelected);
|
renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, !bookSelected);
|
||||||
} else {
|
} else {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, continueY, "Continue Reading", !bookSelected);
|
renderer.drawCenteredText(UI_10_FONT_ID, continueY, TR(CONTINUE_READING), !bookSelected);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No book to continue reading
|
// No book to continue reading
|
||||||
const int y =
|
const int y =
|
||||||
bookY + (bookHeight - renderer.getLineHeight(UI_12_FONT_ID) - renderer.getLineHeight(UI_10_FONT_ID)) / 2;
|
bookY + (bookHeight - renderer.getLineHeight(UI_12_FONT_ID) - renderer.getLineHeight(UI_10_FONT_ID)) / 2;
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, y, "No open book");
|
renderer.drawCenteredText(UI_12_FONT_ID, y, TR(NO_OPEN_BOOK));
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, y + renderer.getLineHeight(UI_12_FONT_ID), "Start reading below");
|
renderer.drawCenteredText(UI_10_FONT_ID, y + renderer.getLineHeight(UI_12_FONT_ID), TR(START_READING));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Bottom menu tiles ---
|
// --- Bottom menu tiles ---
|
||||||
// Build menu items dynamically
|
// Build menu items dynamically
|
||||||
std::vector<const char*> menuItems = {"My Library", "File Transfer", "Settings"};
|
std::vector<const char*> menuItems = {TR(BROWSE_FILES), TR(FILE_TRANSFER), TR(SETTINGS_TITLE)};
|
||||||
if (hasOpdsUrl) {
|
if (hasOpdsUrl) {
|
||||||
// Insert Calibre Library after My Library
|
// Insert Calibre Library after My Library
|
||||||
menuItems.insert(menuItems.begin() + 1, "Calibre Library");
|
menuItems.insert(menuItems.begin() + 1, TR(CALIBRE_LIBRARY));
|
||||||
}
|
}
|
||||||
|
|
||||||
const int menuTileWidth = pageWidth - 2 * margin;
|
const int menuTileWidth = pageWidth - 2 * margin;
|
||||||
@ -541,7 +542,7 @@ void HomeActivity::render() {
|
|||||||
renderer.drawText(UI_10_FONT_ID, textX, textY, label, !selected);
|
renderer.drawText(UI_10_FONT_ID, textX, textY, label, !selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels("", "Select", "Up", "Down");
|
const auto labels = mappedInput.mapLabels("", TR(SELECT), TR(DIR_UP), TR(DIR_DOWN));
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
const bool showBatteryPercentage =
|
const bool showBatteryPercentage =
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "MyLibraryActivity.h"
|
#include "MyLibraryActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -303,7 +304,7 @@ void MyLibraryActivity::render() const {
|
|||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
// Draw tab bar
|
// Draw tab bar
|
||||||
std::vector<TabInfo> tabs = {{"Recent", currentTab == Tab::Recent}, {"Files", currentTab == Tab::Files}};
|
std::vector<TabInfo> tabs = {{TR(BOOKS), currentTab == Tab::Recent}, {TR(BROWSE_FILES), currentTab == Tab::Files}};
|
||||||
ScreenComponents::drawTabBar(renderer, TAB_BAR_Y, tabs);
|
ScreenComponents::drawTabBar(renderer, TAB_BAR_Y, tabs);
|
||||||
|
|
||||||
// Draw content based on current tab
|
// Draw content based on current tab
|
||||||
@ -323,7 +324,7 @@ void MyLibraryActivity::render() const {
|
|||||||
renderer.drawSideButtonHints(UI_10_FONT_ID, ">", "<");
|
renderer.drawSideButtonHints(UI_10_FONT_ID, ">", "<");
|
||||||
|
|
||||||
// Draw bottom button hints
|
// Draw bottom button hints
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Open", "<", ">");
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(OPEN), "<", ">");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
@ -335,7 +336,7 @@ void MyLibraryActivity::renderRecentTab() const {
|
|||||||
const int bookCount = static_cast<int>(bookTitles.size());
|
const int bookCount = static_cast<int>(bookTitles.size());
|
||||||
|
|
||||||
if (bookCount == 0) {
|
if (bookCount == 0) {
|
||||||
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No recent books");
|
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, TR(NO_BOOKS_FOUND));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +360,7 @@ void MyLibraryActivity::renderFilesTab() const {
|
|||||||
const int fileCount = static_cast<int>(files.size());
|
const int fileCount = static_cast<int>(files.size());
|
||||||
|
|
||||||
if (fileCount == 0) {
|
if (fileCount == 0) {
|
||||||
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No books found");
|
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, TR(NO_BOOKS_FOUND));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ void CalibreWirelessActivity::onEnter() {
|
|||||||
stateMutex = xSemaphoreCreateMutex();
|
stateMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
state = WirelessState::DISCOVERING;
|
state = WirelessState::DISCOVERING;
|
||||||
statusMessage = "Discovering Calibre...";
|
statusMessage = TR(CALIBRE_DISCOVERING);
|
||||||
errorMessage.clear();
|
errorMessage.clear();
|
||||||
calibreHostname.clear();
|
calibreHostname.clear();
|
||||||
calibreHost.clear();
|
calibreHost.clear();
|
||||||
@ -220,7 +221,7 @@ void CalibreWirelessActivity::listenForDiscovery() {
|
|||||||
if (calibrePort > 0) {
|
if (calibrePort > 0) {
|
||||||
// Connect to Calibre's TCP server - try main port first, then alt port
|
// Connect to Calibre's TCP server - try main port first, then alt port
|
||||||
setState(WirelessState::CONNECTING);
|
setState(WirelessState::CONNECTING);
|
||||||
setStatus("Connecting to " + calibreHostname + "...");
|
setStatus(std::string(TR(CALIBRE_CONNECTING_TO)) + calibreHostname + "...");
|
||||||
|
|
||||||
// Small delay before connecting
|
// Small delay before connecting
|
||||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
@ -242,11 +243,11 @@ void CalibreWirelessActivity::listenForDiscovery() {
|
|||||||
|
|
||||||
if (connected) {
|
if (connected) {
|
||||||
setState(WirelessState::WAITING);
|
setState(WirelessState::WAITING);
|
||||||
setStatus("Connected to " + calibreHostname + "\nWaiting for commands...");
|
setStatus(std::string(TR(CALIBRE_CONNECTED_TO)) + calibreHostname + "\n" + TR(CALIBRE_WAITING_COMMANDS));
|
||||||
} else {
|
} else {
|
||||||
// Don't set error yet, keep trying discovery
|
// Don't set error yet, keep trying discovery
|
||||||
setState(WirelessState::DISCOVERING);
|
setState(WirelessState::DISCOVERING);
|
||||||
setStatus("Discovering Calibre...\n(Connection failed, retrying)");
|
setStatus(std::string(TR(CALIBRE_DISCOVERING)) + "\n" + TR(CONNECTION_FAILED_RETRYING));
|
||||||
calibrePort = 0;
|
calibrePort = 0;
|
||||||
calibreAltPort = 0;
|
calibreAltPort = 0;
|
||||||
}
|
}
|
||||||
@ -258,7 +259,7 @@ void CalibreWirelessActivity::listenForDiscovery() {
|
|||||||
void CalibreWirelessActivity::handleTcpClient() {
|
void CalibreWirelessActivity::handleTcpClient() {
|
||||||
if (!tcpClient.connected()) {
|
if (!tcpClient.connected()) {
|
||||||
setState(WirelessState::DISCONNECTED);
|
setState(WirelessState::DISCONNECTED);
|
||||||
setStatus("Calibre disconnected");
|
setStatus(TR(CALIBRE_DISCONNECTED));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,9 +456,7 @@ void CalibreWirelessActivity::handleCommand(const OpCode opcode, const std::stri
|
|||||||
|
|
||||||
void CalibreWirelessActivity::handleGetInitializationInfo(const std::string& data) {
|
void CalibreWirelessActivity::handleGetInitializationInfo(const std::string& data) {
|
||||||
setState(WirelessState::WAITING);
|
setState(WirelessState::WAITING);
|
||||||
setStatus("Connected to " + calibreHostname +
|
setStatus(std::string(TR(CALIBRE_CONNECTED_TO)) + calibreHostname + "\n" + TR(CALIBRE_WAITING_TRANSFER) + "\n\n" + TR(CALIBRE_TRANSFER_HINT));
|
||||||
"\nWaiting for transfer...\n\nIf transfer fails, enable\n'Ignore free space' in Calibre's\nSmartDevice "
|
|
||||||
"plugin settings.");
|
|
||||||
|
|
||||||
// Build response with device capabilities
|
// Build response with device capabilities
|
||||||
// Format must match what Calibre expects from a smart device
|
// Format must match what Calibre expects from a smart device
|
||||||
@ -589,11 +588,11 @@ void CalibreWirelessActivity::handleSendBook(const std::string& data) {
|
|||||||
bytesReceived = 0;
|
bytesReceived = 0;
|
||||||
|
|
||||||
setState(WirelessState::RECEIVING);
|
setState(WirelessState::RECEIVING);
|
||||||
setStatus("Receiving: " + filename);
|
setStatus(std::string(TR(CALIBRE_RECEIVING)) + filename);
|
||||||
|
|
||||||
// Open file for writing
|
// Open file for writing
|
||||||
if (!SdMan.openFileForWrite("CAL", currentFilename.c_str(), currentFile)) {
|
if (!SdMan.openFileForWrite("CAL", currentFilename.c_str(), currentFile)) {
|
||||||
setError("Failed to create file");
|
setError(TR(CALIBRE_FAILED_CREATE_FILE));
|
||||||
sendJsonResponse(OpCode::ERROR, "{\"message\":\"Failed to create file\"}");
|
sendJsonResponse(OpCode::ERROR, "{\"message\":\"Failed to create file\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -625,7 +624,7 @@ void CalibreWirelessActivity::handleDisplayMessage(const std::string& data) {
|
|||||||
// Calibre may send messages to display
|
// Calibre may send messages to display
|
||||||
// Check messageKind - 1 means password error
|
// Check messageKind - 1 means password error
|
||||||
if (data.find("\"messageKind\":1") != std::string::npos) {
|
if (data.find("\"messageKind\":1") != std::string::npos) {
|
||||||
setError("Password required");
|
setError(TR(CALIBRE_PASSWORD_REQUIRED));
|
||||||
}
|
}
|
||||||
sendJsonResponse(OpCode::OK, "{}");
|
sendJsonResponse(OpCode::OK, "{}");
|
||||||
}
|
}
|
||||||
@ -634,7 +633,7 @@ void CalibreWirelessActivity::handleNoop(const std::string& data) {
|
|||||||
// Check for ejecting flag
|
// Check for ejecting flag
|
||||||
if (data.find("\"ejecting\":true") != std::string::npos) {
|
if (data.find("\"ejecting\":true") != std::string::npos) {
|
||||||
setState(WirelessState::DISCONNECTED);
|
setState(WirelessState::DISCONNECTED);
|
||||||
setStatus("Calibre disconnected");
|
setStatus(TR(CALIBRE_DISCONNECTED));
|
||||||
}
|
}
|
||||||
sendJsonResponse(OpCode::NOOP, "{}");
|
sendJsonResponse(OpCode::NOOP, "{}");
|
||||||
}
|
}
|
||||||
@ -646,7 +645,7 @@ void CalibreWirelessActivity::receiveBinaryData() {
|
|||||||
if (!tcpClient.connected()) {
|
if (!tcpClient.connected()) {
|
||||||
currentFile.close();
|
currentFile.close();
|
||||||
inBinaryMode = false;
|
inBinaryMode = false;
|
||||||
setError("Transfer interrupted");
|
setError(TR(CALIBRE_TRANSFER_INTERRUPTED));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -668,7 +667,7 @@ void CalibreWirelessActivity::receiveBinaryData() {
|
|||||||
inBinaryMode = false;
|
inBinaryMode = false;
|
||||||
|
|
||||||
setState(WirelessState::WAITING);
|
setState(WirelessState::WAITING);
|
||||||
setStatus("Received: " + currentFilename + "\nWaiting for more...");
|
setStatus(std::string(TR(CALIBRE_RECEIVED)) + currentFilename + "\n" + TR(CALIBRE_WAITING_MORE));
|
||||||
|
|
||||||
// Send OK to acknowledge completion
|
// Send OK to acknowledge completion
|
||||||
sendJsonResponse(OpCode::OK, "{}");
|
sendJsonResponse(OpCode::OK, "{}");
|
||||||
@ -683,11 +682,11 @@ void CalibreWirelessActivity::render() const {
|
|||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
// Draw header
|
// Draw header
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 30, "Calibre Wireless", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 30, TR(CALIBRE_WIRELESS), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
// Draw IP address
|
// Draw IP address
|
||||||
const std::string ipAddr = WiFi.localIP().toString().c_str();
|
const std::string ipAddr = WiFi.localIP().toString().c_str();
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 60, ("IP: " + ipAddr).c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, 60, (std::string(TR(IP_ADDRESS_PREFIX)) + ipAddr).c_str());
|
||||||
|
|
||||||
// Draw status message
|
// Draw status message
|
||||||
int statusY = pageHeight / 2 - 40;
|
int statusY = pageHeight / 2 - 40;
|
||||||
@ -720,7 +719,7 @@ void CalibreWirelessActivity::render() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw button hints
|
// Draw button hints
|
||||||
const auto labels = mappedInput.mapLabels("Back", "", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), "", "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <DNSServer.h>
|
#include <DNSServer.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <esp_task_wdt.h>
|
#include <esp_task_wdt.h>
|
||||||
#include <qrcode.h>
|
#include <qrcode.h>
|
||||||
@ -329,6 +330,9 @@ void CrossPointWebServerActivity::loop() {
|
|||||||
// Yield and check for exit button every 64 iterations
|
// Yield and check for exit button every 64 iterations
|
||||||
if ((i & 0x3F) == 0x3F) {
|
if ((i & 0x3F) == 0x3F) {
|
||||||
yield();
|
yield();
|
||||||
|
// CRITICAL: Must call update() before wasPressed() to refresh button state
|
||||||
|
// Otherwise button presses during the loop will be missed
|
||||||
|
mappedInput.update();
|
||||||
// Check for exit button inside loop for responsiveness
|
// Check for exit button inside loop for responsiveness
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
onGoBack();
|
onGoBack();
|
||||||
@ -369,7 +373,7 @@ void CrossPointWebServerActivity::render() const {
|
|||||||
} else if (state == WebServerActivityState::AP_STARTING) {
|
} else if (state == WebServerActivityState::AP_STARTING) {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Starting Hotspot...", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, TR(STARTING_HOTSPOT), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -400,21 +404,20 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
|||||||
// Use consistent line spacing
|
// Use consistent line spacing
|
||||||
constexpr int LINE_SPACING = 28; // Space between lines
|
constexpr int LINE_SPACING = 28; // Space between lines
|
||||||
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(FILE_TRANSFER), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
if (isApMode) {
|
if (isApMode) {
|
||||||
// AP mode display - center the content block
|
// AP mode display - center the content block
|
||||||
int startY = 55;
|
int startY = 55;
|
||||||
|
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, "Hotspot Mode", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, startY, TR(HOTSPOT_MODE), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
std::string ssidInfo = "Network: " + connectedSSID;
|
std::string ssidInfo = std::string(TR(NETWORK_PREFIX)) + connectedSSID;
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ssidInfo.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ssidInfo.c_str());
|
||||||
|
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 2, "Connect your device to this WiFi network");
|
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 2, TR(CONNECT_WIFI_HINT));
|
||||||
|
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3,
|
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, TR(SCAN_QR_WIFI_HINT));
|
||||||
"or scan QR code with your phone to connect to Wifi.");
|
|
||||||
// Show QR code for URL
|
// Show QR code for URL
|
||||||
const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;";
|
const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;";
|
||||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig);
|
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig);
|
||||||
@ -425,24 +428,24 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
|||||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str(), true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str(), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
// Show IP address as fallback
|
// Show IP address as fallback
|
||||||
std::string ipUrl = "or http://" + connectedIP + "/";
|
std::string ipUrl = std::string(TR(OR_HTTP_PREFIX)) + connectedIP + "/";
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, ipUrl.c_str());
|
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, ipUrl.c_str());
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "Open this URL in your browser");
|
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, TR(OPEN_URL_HINT));
|
||||||
|
|
||||||
// Show QR code for URL
|
// Show QR code for URL
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "or scan QR code with your phone:");
|
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, TR(SCAN_QR_HINT));
|
||||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 7, hostnameUrl);
|
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 7, hostnameUrl);
|
||||||
} else {
|
} else {
|
||||||
// STA mode display (original behavior)
|
// STA mode display (original behavior)
|
||||||
const int startY = 65;
|
const int startY = 65;
|
||||||
|
|
||||||
std::string ssidInfo = "Network: " + connectedSSID;
|
std::string ssidInfo = std::string(TR(NETWORK_PREFIX)) + connectedSSID;
|
||||||
if (ssidInfo.length() > 28) {
|
if (ssidInfo.length() > 28) {
|
||||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||||
}
|
}
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, ssidInfo.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, startY, ssidInfo.c_str());
|
||||||
|
|
||||||
std::string ipInfo = "IP Address: " + connectedIP;
|
std::string ipInfo = std::string(TR(IP_ADDRESS_PREFIX)) + connectedIP;
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ipInfo.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ipInfo.c_str());
|
||||||
|
|
||||||
// Show web server URL prominently
|
// Show web server URL prominently
|
||||||
@ -450,16 +453,16 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
|||||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 2, webInfo.c_str(), true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 2, webInfo.c_str(), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
// Also show hostname URL
|
// Also show hostname URL
|
||||||
std::string hostnameUrl = std::string("or http://") + AP_HOSTNAME + ".local/";
|
std::string hostnameUrl = std::string(TR(OR_HTTP_PREFIX)) + AP_HOSTNAME + ".local/";
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str());
|
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str());
|
||||||
|
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, "Open this URL in your browser");
|
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, TR(OPEN_URL_HINT));
|
||||||
|
|
||||||
// Show QR code for URL
|
// Show QR code for URL
|
||||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, webInfo);
|
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, webInfo);
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "or scan QR code with your phone:");
|
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, TR(SCAN_QR_HINT));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels("« Exit", "", "", "");
|
const auto labels = mappedInput.mapLabels(TR(EXIT), "", "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,13 @@
|
|||||||
#include "NetworkModeSelectionActivity.h"
|
#include "NetworkModeSelectionActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int MENU_ITEM_COUNT = 2;
|
constexpr int MENU_ITEM_COUNT = 2;
|
||||||
const char* MENU_ITEMS[MENU_ITEM_COUNT] = {"Join a Network", "Create Hotspot"};
|
|
||||||
const char* MENU_DESCRIPTIONS[MENU_ITEM_COUNT] = {"Connect to an existing WiFi network",
|
|
||||||
"Create a WiFi network others can join"};
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void NetworkModeSelectionActivity::taskTrampoline(void* param) {
|
void NetworkModeSelectionActivity::taskTrampoline(void* param) {
|
||||||
@ -97,10 +95,15 @@ void NetworkModeSelectionActivity::render() const {
|
|||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
// Draw header
|
// Draw header
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(FILE_TRANSFER), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
// Draw subtitle
|
// Draw subtitle
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 50, "How would you like to connect?");
|
renderer.drawCenteredText(UI_10_FONT_ID, 50, TR(HOW_CONNECT));
|
||||||
|
|
||||||
|
// Menu items and descriptions
|
||||||
|
const char* menuItems[] = {TR(JOIN_NETWORK), TR(CREATE_HOTSPOT)};
|
||||||
|
const char* menuDescs[] = {TR(JOIN_DESC), TR(HOTSPOT_DESC)};
|
||||||
|
constexpr int MENU_ITEM_COUNT = 2;
|
||||||
|
|
||||||
// Draw menu items centered on screen
|
// Draw menu items centered on screen
|
||||||
constexpr int itemHeight = 50; // Height for each menu item (including description)
|
constexpr int itemHeight = 50; // Height for each menu item (including description)
|
||||||
@ -117,12 +120,12 @@ void NetworkModeSelectionActivity::render() const {
|
|||||||
|
|
||||||
// Draw text: black=false (white text) when selected (on black background)
|
// Draw text: black=false (white text) when selected (on black background)
|
||||||
// black=true (black text) when not selected (on white background)
|
// black=true (black text) when not selected (on white background)
|
||||||
renderer.drawText(UI_10_FONT_ID, 30, itemY, MENU_ITEMS[i], /*black=*/!isSelected);
|
renderer.drawText(UI_10_FONT_ID, 30, itemY, menuItems[i], /*black=*/!isSelected);
|
||||||
renderer.drawText(SMALL_FONT_ID, 30, itemY + 22, MENU_DESCRIPTIONS[i], /*black=*/!isSelected);
|
renderer.drawText(SMALL_FONT_ID, 30, itemY + 22, menuDescs[i], /*black=*/!isSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw help text at bottom
|
// Draw help text at bottom
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(SELECT), "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "WifiSelectionActivity.h"
|
#include "WifiSelectionActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
@ -199,7 +200,7 @@ void WifiSelectionActivity::selectNetwork(const int index) {
|
|||||||
// Don't allow screen updates while changing activity
|
// Don't allow screen updates while changing activity
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
enterNewActivity(new KeyboardEntryActivity(
|
enterNewActivity(new KeyboardEntryActivity(
|
||||||
renderer, mappedInput, "Enter WiFi Password",
|
renderer, mappedInput, TR(ENTER_WIFI_PASSWORD),
|
||||||
"", // No initial text
|
"", // No initial text
|
||||||
50, // Y position
|
50, // Y position
|
||||||
64, // Max password length
|
64, // Max password length
|
||||||
@ -266,9 +267,9 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status == WL_CONNECT_FAILED || status == WL_NO_SSID_AVAIL) {
|
if (status == WL_CONNECT_FAILED || status == WL_NO_SSID_AVAIL) {
|
||||||
connectionError = "Connection failed";
|
connectionError = TR(CONNECTION_FAILED);
|
||||||
if (status == WL_NO_SSID_AVAIL) {
|
if (status == WL_NO_SSID_AVAIL) {
|
||||||
connectionError = "Network not found";
|
connectionError = TR(NO_NETWORKS);
|
||||||
}
|
}
|
||||||
state = WifiSelectionState::CONNECTION_FAILED;
|
state = WifiSelectionState::CONNECTION_FAILED;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@ -278,7 +279,7 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
|||||||
// Check for timeout
|
// Check for timeout
|
||||||
if (millis() - connectionStartTime > CONNECTION_TIMEOUT_MS) {
|
if (millis() - connectionStartTime > CONNECTION_TIMEOUT_MS) {
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
connectionError = "Connection timeout";
|
connectionError = TR(CONNECTION_TIMEOUT);
|
||||||
state = WifiSelectionState::CONNECTION_FAILED;
|
state = WifiSelectionState::CONNECTION_FAILED;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
@ -513,14 +514,14 @@ void WifiSelectionActivity::renderNetworkList() const {
|
|||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
// Draw header
|
// Draw header
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "WiFi Networks", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(WIFI_NETWORKS), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
if (networks.empty()) {
|
if (networks.empty()) {
|
||||||
// No networks found or scan failed
|
// No networks found or scan failed
|
||||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||||
const auto top = (pageHeight - height) / 2;
|
const auto top = (pageHeight - height) / 2;
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top, "No networks found");
|
renderer.drawCenteredText(UI_10_FONT_ID, top, TR(NO_NETWORKS));
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press OK to scan again");
|
renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, TR(PRESS_OK_SCAN));
|
||||||
} else {
|
} else {
|
||||||
// Calculate how many networks we can display
|
// Calculate how many networks we can display
|
||||||
constexpr int startY = 60;
|
constexpr int startY = 60;
|
||||||
@ -584,8 +585,8 @@ void WifiSelectionActivity::renderNetworkList() const {
|
|||||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 105, cachedMacAddress.c_str());
|
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 105, cachedMacAddress.c_str());
|
||||||
|
|
||||||
// Draw help text
|
// Draw help text
|
||||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved");
|
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, TR(NETWORK_LEGEND));
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Connect", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(CONNECT), "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -595,13 +596,13 @@ void WifiSelectionActivity::renderConnecting() const {
|
|||||||
const auto top = (pageHeight - height) / 2;
|
const auto top = (pageHeight - height) / 2;
|
||||||
|
|
||||||
if (state == WifiSelectionState::SCANNING) {
|
if (state == WifiSelectionState::SCANNING) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top, "Scanning...");
|
renderer.drawCenteredText(UI_10_FONT_ID, top, TR(SCANNING));
|
||||||
} else {
|
} else {
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Connecting...", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, TR(CONNECTING), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
std::string ssidInfo = "to " + selectedSSID;
|
std::string ssidInfo = std::string(TR(NETWORK_PREFIX)) + selectedSSID;
|
||||||
if (ssidInfo.length() > 25) {
|
if (ssidInfo.length() > 28) {
|
||||||
ssidInfo.replace(22, ssidInfo.length() - 22, "...");
|
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||||
}
|
}
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str());
|
||||||
}
|
}
|
||||||
@ -612,18 +613,18 @@ void WifiSelectionActivity::renderConnected() const {
|
|||||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||||
const auto top = (pageHeight - height * 4) / 2;
|
const auto top = (pageHeight - height * 4) / 2;
|
||||||
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 30, "Connected!", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, top - 30, TR(CONNECTED), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
std::string ssidInfo = "Network: " + selectedSSID;
|
std::string ssidInfo = std::string(TR(NETWORK_PREFIX)) + selectedSSID;
|
||||||
if (ssidInfo.length() > 28) {
|
if (ssidInfo.length() > 28) {
|
||||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||||
}
|
}
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 10, ssidInfo.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, top + 10, ssidInfo.c_str());
|
||||||
|
|
||||||
const std::string ipInfo = "IP Address: " + connectedIP;
|
const std::string ipInfo = std::string(TR(IP_ADDRESS_PREFIX)) + connectedIP;
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, ipInfo.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, ipInfo.c_str());
|
||||||
|
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "Press any button to continue");
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, TR(PRESS_ANY_CONTINUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiSelectionActivity::renderSavePrompt() const {
|
void WifiSelectionActivity::renderSavePrompt() const {
|
||||||
@ -632,15 +633,15 @@ void WifiSelectionActivity::renderSavePrompt() const {
|
|||||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||||
const auto top = (pageHeight - height * 3) / 2;
|
const auto top = (pageHeight - height * 3) / 2;
|
||||||
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Connected!", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, TR(CONNECTED), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
std::string ssidInfo = "Network: " + selectedSSID;
|
std::string ssidInfo = std::string(TR(NETWORK_PREFIX)) + selectedSSID;
|
||||||
if (ssidInfo.length() > 28) {
|
if (ssidInfo.length() > 28) {
|
||||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||||
}
|
}
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str());
|
||||||
|
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, "Save password for next time?");
|
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, TR(SAVE_PASSWORD));
|
||||||
|
|
||||||
// Draw Yes/No buttons
|
// Draw Yes/No buttons
|
||||||
const int buttonY = top + 80;
|
const int buttonY = top + 80;
|
||||||
@ -651,19 +652,19 @@ void WifiSelectionActivity::renderSavePrompt() const {
|
|||||||
|
|
||||||
// Draw "Yes" button
|
// Draw "Yes" button
|
||||||
if (savePromptSelection == 0) {
|
if (savePromptSelection == 0) {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX, buttonY, "[Yes]");
|
renderer.drawText(UI_10_FONT_ID, startX, buttonY, (std::string("[") + TR(YES) + "]").c_str());
|
||||||
} else {
|
} else {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, "Yes");
|
renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, TR(YES));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw "No" button
|
// Draw "No" button
|
||||||
if (savePromptSelection == 1) {
|
if (savePromptSelection == 1) {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, "[No]");
|
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, (std::string("[") + TR(NO) + "]").c_str());
|
||||||
} else {
|
} else {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, "No");
|
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, TR(NO));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "LEFT/RIGHT: Select | OK: Confirm");
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, TR(SELECT_HINT));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiSelectionActivity::renderConnectionFailed() const {
|
void WifiSelectionActivity::renderConnectionFailed() const {
|
||||||
@ -671,9 +672,9 @@ void WifiSelectionActivity::renderConnectionFailed() const {
|
|||||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||||
const auto top = (pageHeight - height * 2) / 2;
|
const auto top = (pageHeight - height * 2) / 2;
|
||||||
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 20, "Connection Failed", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, top - 20, TR(CONNECTION_FAILED), true, EpdFontFamily::BOLD);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 20, connectionError.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, top + 20, connectionError.c_str());
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "Press any button to continue");
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, TR(PRESS_ANY_CONTINUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiSelectionActivity::renderForgetPrompt() const {
|
void WifiSelectionActivity::renderForgetPrompt() const {
|
||||||
@ -682,15 +683,15 @@ void WifiSelectionActivity::renderForgetPrompt() const {
|
|||||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||||
const auto top = (pageHeight - height * 3) / 2;
|
const auto top = (pageHeight - height * 3) / 2;
|
||||||
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Forget Network?", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, TR(FORGET_NETWORK), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
std::string ssidInfo = "Network: " + selectedSSID;
|
std::string ssidInfo = std::string(TR(NETWORK_PREFIX)) + selectedSSID;
|
||||||
if (ssidInfo.length() > 28) {
|
if (ssidInfo.length() > 28) {
|
||||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||||
}
|
}
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str());
|
||||||
|
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, "Remove saved password?");
|
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, TR(REMOVE_PASSWORD));
|
||||||
|
|
||||||
// Draw Yes/No buttons
|
// Draw Yes/No buttons
|
||||||
const int buttonY = top + 80;
|
const int buttonY = top + 80;
|
||||||
@ -701,17 +702,17 @@ void WifiSelectionActivity::renderForgetPrompt() const {
|
|||||||
|
|
||||||
// Draw "Yes" button
|
// Draw "Yes" button
|
||||||
if (forgetPromptSelection == 0) {
|
if (forgetPromptSelection == 0) {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX, buttonY, "[Yes]");
|
renderer.drawText(UI_10_FONT_ID, startX, buttonY, (std::string("[") + TR(YES) + "]").c_str());
|
||||||
} else {
|
} else {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, "Yes");
|
renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, TR(YES));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw "No" button
|
// Draw "No" button
|
||||||
if (forgetPromptSelection == 1) {
|
if (forgetPromptSelection == 1) {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, "[No]");
|
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, (std::string("[") + TR(NO) + "]").c_str());
|
||||||
} else {
|
} else {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, "No");
|
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, TR(NO));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "LEFT/RIGHT: Select | OK: Confirm");
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, TR(SELECT_HINT));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <Epub/Page.h>
|
#include <Epub/Page.h>
|
||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
@ -258,7 +259,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
// Show end of book screen
|
// Show end of book screen
|
||||||
if (currentSpineIndex == epub->getSpineItemsCount()) {
|
if (currentSpineIndex == epub->getSpineItemsCount()) {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "End of book", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, TR(END_OF_BOOK), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -303,7 +304,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
// Always show "Indexing..." text first
|
// Always show "Indexing..." text first
|
||||||
{
|
{
|
||||||
renderer.fillRect(boxXNoBar, boxY, boxWidthNoBar, boxHeightNoBar, false);
|
renderer.fillRect(boxXNoBar, boxY, boxWidthNoBar, boxHeightNoBar, false);
|
||||||
renderer.drawText(UI_12_FONT_ID, boxXNoBar + boxMargin, boxY + boxMargin, "Indexing...");
|
renderer.drawText(UI_12_FONT_ID, boxXNoBar + boxMargin, boxY + boxMargin, TR(INDEXING));
|
||||||
renderer.drawRect(boxXNoBar + 5, boxY + 5, boxWidthNoBar - 10, boxHeightNoBar - 10);
|
renderer.drawRect(boxXNoBar + 5, boxY + 5, boxWidthNoBar - 10, boxHeightNoBar - 10);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
pagesUntilFullRefresh = 0;
|
pagesUntilFullRefresh = 0;
|
||||||
@ -312,7 +313,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
// Setup callback - only called for chapters >= 50KB, redraws with progress bar
|
// Setup callback - only called for chapters >= 50KB, redraws with progress bar
|
||||||
auto progressSetup = [this, boxXWithBar, boxWidthWithBar, boxHeightWithBar, barX, barY] {
|
auto progressSetup = [this, boxXWithBar, boxWidthWithBar, boxHeightWithBar, barX, barY] {
|
||||||
renderer.fillRect(boxXWithBar, boxY, boxWidthWithBar, boxHeightWithBar, false);
|
renderer.fillRect(boxXWithBar, boxY, boxWidthWithBar, boxHeightWithBar, false);
|
||||||
renderer.drawText(UI_12_FONT_ID, boxXWithBar + boxMargin, boxY + boxMargin, "Indexing...");
|
renderer.drawText(UI_12_FONT_ID, boxXWithBar + boxMargin, boxY + boxMargin, TR(INDEXING));
|
||||||
renderer.drawRect(boxXWithBar + 5, boxY + 5, boxWidthWithBar - 10, boxHeightWithBar - 10);
|
renderer.drawRect(boxXWithBar + 5, boxY + 5, boxWidthWithBar - 10, boxHeightWithBar - 10);
|
||||||
renderer.drawRect(barX, barY, barWidth, barHeight);
|
renderer.drawRect(barX, barY, barWidth, barHeight);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
@ -347,7 +348,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
|
|
||||||
if (section->pageCount == 0) {
|
if (section->pageCount == 0) {
|
||||||
Serial.printf("[%lu] [ERS] No pages to render\n", millis());
|
Serial.printf("[%lu] [ERS] No pages to render\n", millis());
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Empty chapter", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, TR(EMPTY_CHAPTER), true, EpdFontFamily::BOLD);
|
||||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
@ -355,7 +356,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
|
|
||||||
if (section->currentPage < 0 || section->currentPage >= section->pageCount) {
|
if (section->currentPage < 0 || section->currentPage >= section->pageCount) {
|
||||||
Serial.printf("[%lu] [ERS] Page out of bounds: %d (max %d)\n", millis(), section->currentPage, section->pageCount);
|
Serial.printf("[%lu] [ERS] Page out of bounds: %d (max %d)\n", millis(), section->currentPage, section->pageCount);
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Out of bounds", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, TR(OUT_OF_BOUNDS), true, EpdFontFamily::BOLD);
|
||||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
@ -478,8 +479,8 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in
|
|||||||
std::string title;
|
std::string title;
|
||||||
int titleWidth;
|
int titleWidth;
|
||||||
if (tocIndex == -1) {
|
if (tocIndex == -1) {
|
||||||
title = "Unnamed";
|
title = TR(UNNAMED);
|
||||||
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, "Unnamed");
|
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, TR(UNNAMED));
|
||||||
} else {
|
} else {
|
||||||
const auto tocItem = epub->getTocItem(tocIndex);
|
const auto tocItem = epub->getTocItem(tocIndex);
|
||||||
title = tocItem.title;
|
title = tocItem.title;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "TxtReaderActivity.h"
|
#include "TxtReaderActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
@ -206,7 +207,7 @@ void TxtReaderActivity::buildPageIndex() {
|
|||||||
|
|
||||||
// Draw initial progress box
|
// Draw initial progress box
|
||||||
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false);
|
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false);
|
||||||
renderer.drawText(UI_12_FONT_ID, boxX + boxMargin, boxY + boxMargin, "Indexing...");
|
renderer.drawText(UI_12_FONT_ID, boxX + boxMargin, boxY + boxMargin, TR(INDEXING));
|
||||||
renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10);
|
renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10);
|
||||||
renderer.drawRect(barX, barY, barWidth, barHeight);
|
renderer.drawRect(barX, barY, barWidth, barHeight);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
@ -384,14 +385,14 @@ void TxtReaderActivity::renderScreen() {
|
|||||||
// Initialize reader if not done
|
// Initialize reader if not done
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Indexing...", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, TR(INDEXING), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
initializeReader();
|
initializeReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pageOffsets.empty()) {
|
if (pageOffsets.empty()) {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Empty file", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, TR(EMPTY_FILE), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
@ -169,7 +170,7 @@ void XtcReaderActivity::renderScreen() {
|
|||||||
if (currentPage >= xtc->getPageCount()) {
|
if (currentPage >= xtc->getPageCount()) {
|
||||||
// Show end of book screen
|
// Show end of book screen
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "End of book", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, TR(END_OF_BOOK), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -198,7 +199,7 @@ void XtcReaderActivity::renderPage() {
|
|||||||
if (!pageBuffer) {
|
if (!pageBuffer) {
|
||||||
Serial.printf("[%lu] [XTR] Failed to allocate page buffer (%lu bytes)\n", millis(), pageBufferSize);
|
Serial.printf("[%lu] [XTR] Failed to allocate page buffer (%lu bytes)\n", millis(), pageBufferSize);
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Memory error", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, TR(MEMORY_ERROR), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -209,7 +210,7 @@ void XtcReaderActivity::renderPage() {
|
|||||||
Serial.printf("[%lu] [XTR] Failed to load page %lu\n", millis(), currentPage);
|
Serial.printf("[%lu] [XTR] Failed to load page %lu\n", millis(), currentPage);
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Page load error", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 300, TR(PAGE_LOAD_ERROR), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "CalibreSettingsActivity.h"
|
#include "CalibreSettingsActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -12,11 +13,6 @@
|
|||||||
#include "activities/util/KeyboardEntryActivity.h"
|
#include "activities/util/KeyboardEntryActivity.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr int MENU_ITEMS = 2;
|
|
||||||
const char* menuNames[MENU_ITEMS] = {"Calibre Web URL", "Connect as Wireless Device"};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void CalibreSettingsActivity::taskTrampoline(void* param) {
|
void CalibreSettingsActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<CalibreSettingsActivity*>(param);
|
auto* self = static_cast<CalibreSettingsActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
@ -27,7 +23,7 @@ void CalibreSettingsActivity::onEnter() {
|
|||||||
|
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
selectedIndex = 0;
|
selectedIndex = 0;
|
||||||
updateRequired = true;
|
updateRequired = false; // Don't trigger render immediately to avoid race with parent activity
|
||||||
|
|
||||||
xTaskCreate(&CalibreSettingsActivity::taskTrampoline, "CalibreSettingsTask",
|
xTaskCreate(&CalibreSettingsActivity::taskTrampoline, "CalibreSettingsTask",
|
||||||
4096, // Stack size
|
4096, // Stack size
|
||||||
@ -67,23 +63,24 @@ void CalibreSettingsActivity::loop() {
|
|||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
selectedIndex = (selectedIndex + MENU_ITEMS - 1) % MENU_ITEMS;
|
selectedIndex = (selectedIndex + 2 - 1) % 2;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||||
selectedIndex = (selectedIndex + 1) % MENU_ITEMS;
|
selectedIndex = (selectedIndex + 1) % 2;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalibreSettingsActivity::handleSelection() {
|
void CalibreSettingsActivity::handleSelection() {
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
// Don't hold mutex while creating subactivities to avoid race conditions
|
||||||
|
// between parent and child rendering tasks
|
||||||
|
|
||||||
if (selectedIndex == 0) {
|
if (selectedIndex == 0) {
|
||||||
// Calibre Web URL
|
// Calibre Web URL
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new KeyboardEntryActivity(
|
enterNewActivity(new KeyboardEntryActivity(
|
||||||
renderer, mappedInput, "Calibre Web URL", SETTINGS.opdsServerUrl, 10,
|
renderer, mappedInput, TR(CALIBRE_WEB_URL), SETTINGS.opdsServerUrl, 10,
|
||||||
127, // maxLength
|
127, // maxLength
|
||||||
false, // not password
|
false, // not password
|
||||||
[this](const std::string& url) {
|
[this](const std::string& url) {
|
||||||
@ -119,11 +116,14 @@ void CalibreSettingsActivity::handleSelection() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xSemaphoreGive(renderingMutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalibreSettingsActivity::displayTaskLoop() {
|
void CalibreSettingsActivity::displayTaskLoop() {
|
||||||
|
// Wait for parent activity's rendering to complete (screen refresh takes ~422ms)
|
||||||
|
// Wait 500ms to be safe and avoid race conditions with parent activity
|
||||||
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||||
|
updateRequired = true;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired && !subActivity) {
|
if (updateRequired && !subActivity) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -141,13 +141,14 @@ void CalibreSettingsActivity::render() {
|
|||||||
const auto pageWidth = renderer.getScreenWidth();
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
|
||||||
// Draw header
|
// Draw header
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Calibre", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(CALIBRE), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
// Draw selection highlight
|
// Draw selection highlight
|
||||||
renderer.fillRect(0, 60 + selectedIndex * 30 - 2, pageWidth - 1, 30);
|
renderer.fillRect(0, 60 + selectedIndex * 30 - 2, pageWidth - 1, 30);
|
||||||
|
|
||||||
// Draw menu items
|
// Draw menu items
|
||||||
for (int i = 0; i < MENU_ITEMS; i++) {
|
const char* menuNames[2] = {TR(CALIBRE_WEB_URL), TR(CONNECT_WIRELESS)};
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
const int settingY = 60 + i * 30;
|
const int settingY = 60 + i * 30;
|
||||||
const bool isSelected = (i == selectedIndex);
|
const bool isSelected = (i == selectedIndex);
|
||||||
|
|
||||||
@ -155,14 +156,14 @@ void CalibreSettingsActivity::render() {
|
|||||||
|
|
||||||
// Draw status for URL setting
|
// Draw status for URL setting
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
const char* status = (strlen(SETTINGS.opdsServerUrl) > 0) ? "[Set]" : "[Not Set]";
|
const char* status = (strlen(SETTINGS.opdsServerUrl) > 0) ? TR(SET) : TR(NOT_SET);
|
||||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
|
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
|
||||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected);
|
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw button hints
|
// Draw button hints
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(SELECT), "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
|||||||
@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include "CalibreSettingsActivity.h"
|
#include "CalibreSettingsActivity.h"
|
||||||
#include "ClearCacheActivity.h"
|
#include "ClearCacheActivity.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
|
#include "FontSelectActivity.h"
|
||||||
#include "KOReaderSettingsActivity.h"
|
#include "KOReaderSettingsActivity.h"
|
||||||
|
#include "LanguageSelectActivity.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "OtaUpdateActivity.h"
|
#include "OtaUpdateActivity.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
@ -51,7 +52,11 @@ void CategorySettingsActivity::loop() {
|
|||||||
// Handle actions with early return
|
// Handle actions with early return
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
toggleCurrentSetting();
|
toggleCurrentSetting();
|
||||||
updateRequired = true;
|
// Only update if we didn't enter a subactivity
|
||||||
|
// If we entered a subactivity, it will handle its own rendering
|
||||||
|
if (!subActivity) {
|
||||||
|
updateRequired = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,38 +100,45 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
|||||||
SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step;
|
SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step;
|
||||||
}
|
}
|
||||||
} else if (setting.type == SettingType::ACTION) {
|
} else if (setting.type == SettingType::ACTION) {
|
||||||
if (strcmp(setting.name, "KOReader Sync") == 0) {
|
// 创建新的 Activity 但不持有 mutex
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
// 注意:不能在持有 mutex 的情况下调用 enterNewActivity
|
||||||
exitActivity();
|
// 因为新 Activity 的 onEnter 会创建自己的任务,可能导致 FreeRTOS 冲突
|
||||||
|
|
||||||
|
if (setting.nameId == StrId::KOREADER_SYNC) {
|
||||||
enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] {
|
enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
xSemaphoreGive(renderingMutex);
|
} else if (setting.nameId == StrId::CALIBRE_SETTINGS) {
|
||||||
} else if (strcmp(setting.name, "Calibre Settings") == 0) {
|
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
||||||
exitActivity();
|
|
||||||
enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {
|
enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
xSemaphoreGive(renderingMutex);
|
} else if (setting.nameId == StrId::CLEAR_READING_CACHE) {
|
||||||
} else if (strcmp(setting.name, "Clear Cache") == 0) {
|
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
||||||
exitActivity();
|
|
||||||
enterNewActivity(new ClearCacheActivity(renderer, mappedInput, [this] {
|
enterNewActivity(new ClearCacheActivity(renderer, mappedInput, [this] {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
xSemaphoreGive(renderingMutex);
|
} else if (setting.nameId == StrId::CHECK_UPDATES) {
|
||||||
} else if (strcmp(setting.name, "Check for updates") == 0) {
|
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
||||||
exitActivity();
|
|
||||||
enterNewActivity(new OtaUpdateActivity(renderer, mappedInput, [this] {
|
enterNewActivity(new OtaUpdateActivity(renderer, mappedInput, [this] {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
xSemaphoreGive(renderingMutex);
|
} else if (setting.nameId == StrId::EXT_UI_FONT) {
|
||||||
|
enterNewActivity(new FontSelectActivity(renderer, mappedInput, FontSelectActivity::SelectMode::UI, [this] {
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
}));
|
||||||
|
} else if (setting.nameId == StrId::EXT_READER_FONT) {
|
||||||
|
enterNewActivity(new FontSelectActivity(renderer, mappedInput, FontSelectActivity::SelectMode::Reader, [this] {
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
}));
|
||||||
|
} else if (setting.nameId == StrId::LANGUAGE) {
|
||||||
|
enterNewActivity(new LanguageSelectActivity(renderer, mappedInput, [this] {
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
@ -137,6 +149,8 @@ void CategorySettingsActivity::toggleCurrentSetting() {
|
|||||||
|
|
||||||
void CategorySettingsActivity::displayTaskLoop() {
|
void CategorySettingsActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// CRITICAL: Check both updateRequired AND subActivity atomically
|
||||||
|
// This prevents race condition where parent and child render simultaneously
|
||||||
if (updateRequired && !subActivity) {
|
if (updateRequired && !subActivity) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
@ -163,17 +177,17 @@ void CategorySettingsActivity::render() const {
|
|||||||
const int settingY = 60 + i * 30; // 30 pixels between settings
|
const int settingY = 60 + i * 30; // 30 pixels between settings
|
||||||
const bool isSelected = (i == selectedSettingIndex);
|
const bool isSelected = (i == selectedSettingIndex);
|
||||||
|
|
||||||
// Draw setting name
|
// Draw setting name (translated)
|
||||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected);
|
renderer.drawText(UI_10_FONT_ID, 20, settingY, I18N.get(settingsList[i].nameId), !isSelected);
|
||||||
|
|
||||||
// Draw value based on setting type
|
// Draw value based on setting type
|
||||||
std::string valueText;
|
std::string valueText;
|
||||||
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
||||||
const bool value = SETTINGS.*(settingsList[i].valuePtr);
|
const bool value = SETTINGS.*(settingsList[i].valuePtr);
|
||||||
valueText = value ? "ON" : "OFF";
|
valueText = value ? TR(ON) : TR(OFF);
|
||||||
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
|
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
|
||||||
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
|
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
|
||||||
valueText = settingsList[i].enumValues[value];
|
valueText = I18N.get(settingsList[i].enumValues[value]);
|
||||||
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
||||||
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
||||||
}
|
}
|
||||||
@ -186,7 +200,7 @@ void CategorySettingsActivity::render() const {
|
|||||||
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
||||||
pageHeight - 60, CROSSPOINT_VERSION);
|
pageHeight - 60, CROSSPOINT_VERSION);
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Toggle", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(TOGGLE), "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -14,10 +15,10 @@ class CrossPointSettings;
|
|||||||
enum class SettingType { TOGGLE, ENUM, ACTION, VALUE };
|
enum class SettingType { TOGGLE, ENUM, ACTION, VALUE };
|
||||||
|
|
||||||
struct SettingInfo {
|
struct SettingInfo {
|
||||||
const char* name;
|
StrId nameId;
|
||||||
SettingType type;
|
SettingType type;
|
||||||
uint8_t CrossPointSettings::* valuePtr;
|
uint8_t CrossPointSettings::* valuePtr;
|
||||||
std::vector<std::string> enumValues;
|
std::vector<StrId> enumValues;
|
||||||
|
|
||||||
struct ValueRange {
|
struct ValueRange {
|
||||||
uint8_t min;
|
uint8_t min;
|
||||||
@ -26,18 +27,18 @@ struct SettingInfo {
|
|||||||
};
|
};
|
||||||
ValueRange valueRange;
|
ValueRange valueRange;
|
||||||
|
|
||||||
static SettingInfo Toggle(const char* name, uint8_t CrossPointSettings::* ptr) {
|
static SettingInfo Toggle(StrId nameId, uint8_t CrossPointSettings::* ptr) {
|
||||||
return {name, SettingType::TOGGLE, ptr};
|
return {nameId, SettingType::TOGGLE, ptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
static SettingInfo Enum(const char* name, uint8_t CrossPointSettings::* ptr, std::vector<std::string> values) {
|
static SettingInfo Enum(StrId nameId, uint8_t CrossPointSettings::* ptr, std::vector<StrId> values) {
|
||||||
return {name, SettingType::ENUM, ptr, std::move(values)};
|
return {nameId, SettingType::ENUM, ptr, std::move(values)};
|
||||||
}
|
}
|
||||||
|
|
||||||
static SettingInfo Action(const char* name) { return {name, SettingType::ACTION, nullptr}; }
|
static SettingInfo Action(StrId nameId) { return {nameId, SettingType::ACTION, nullptr}; }
|
||||||
|
|
||||||
static SettingInfo Value(const char* name, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange) {
|
static SettingInfo Value(StrId nameId, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange) {
|
||||||
return {name, SettingType::VALUE, ptr, {}, valueRange};
|
return {nameId, SettingType::VALUE, ptr, {}, valueRange};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
@ -17,7 +18,7 @@ void ClearCacheActivity::onEnter() {
|
|||||||
|
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
state = WARNING;
|
state = WARNING;
|
||||||
updateRequired = true;
|
updateRequired = false; // Don't trigger render immediately to avoid race with parent activity
|
||||||
|
|
||||||
xTaskCreate(&ClearCacheActivity::taskTrampoline, "ClearCacheActivityTask",
|
xTaskCreate(&ClearCacheActivity::taskTrampoline, "ClearCacheActivityTask",
|
||||||
4096, // Stack size
|
4096, // Stack size
|
||||||
@ -30,6 +31,21 @@ void ClearCacheActivity::onEnter() {
|
|||||||
void ClearCacheActivity::onExit() {
|
void ClearCacheActivity::onExit() {
|
||||||
ActivityWithSubactivity::onExit();
|
ActivityWithSubactivity::onExit();
|
||||||
|
|
||||||
|
// Set exit flag to prevent clearCache from accessing mutex after deletion
|
||||||
|
isExiting = true;
|
||||||
|
|
||||||
|
// Wait for clearCache task to complete (max 10 seconds)
|
||||||
|
if (clearCacheTaskHandle) {
|
||||||
|
for (int i = 0; i < 1000 && clearCacheTaskHandle != nullptr; i++) {
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
// Force delete if still running (shouldn't happen)
|
||||||
|
if (clearCacheTaskHandle) {
|
||||||
|
vTaskDelete(clearCacheTaskHandle);
|
||||||
|
clearCacheTaskHandle = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -41,8 +57,15 @@ void ClearCacheActivity::onExit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ClearCacheActivity::displayTaskLoop() {
|
void ClearCacheActivity::displayTaskLoop() {
|
||||||
|
// Wait for parent activity's rendering to complete (screen refresh takes ~422ms)
|
||||||
|
// Wait 500ms to be safe and avoid race conditions with parent activity
|
||||||
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||||
|
updateRequired = true;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
// CRITICAL: Check both updateRequired AND subActivity atomically
|
||||||
|
// This prevents race condition where parent and child render simultaneously
|
||||||
|
if (updateRequired && !subActivity) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
render();
|
render();
|
||||||
@ -56,46 +79,46 @@ void ClearCacheActivity::render() {
|
|||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Clear Cache", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(CLEAR_READING_CACHE), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
if (state == WARNING) {
|
if (state == WARNING) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 60, "This will clear all cached book data.", true);
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 60, TR(CLEAR_CACHE_WARNING_1), true);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 30, "All reading progress will be lost!", true,
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 30, TR(CLEAR_CACHE_WARNING_2), true,
|
||||||
EpdFontFamily::BOLD);
|
EpdFontFamily::BOLD);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, "Books will need to be re-indexed", true);
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, TR(CLEAR_CACHE_WARNING_3), true);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 30, "when opened again.", true);
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 30, TR(CLEAR_CACHE_WARNING_4), true);
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels("« Cancel", "Clear", "", "");
|
const auto labels = mappedInput.mapLabels(TR(CANCEL), TR(CONFIRM), "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == CLEARING) {
|
if (state == CLEARING) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Clearing cache...", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, TR(CLEARING_CACHE), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == SUCCESS) {
|
if (state == SUCCESS) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Cache Cleared", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, TR(CACHE_CLEARED), true, EpdFontFamily::BOLD);
|
||||||
String resultText = String(clearedCount) + " items removed";
|
String resultText = String(clearedCount) + " " + TR(ITEMS_REMOVED);
|
||||||
if (failedCount > 0) {
|
if (failedCount > 0) {
|
||||||
resultText += ", " + String(failedCount) + " failed";
|
resultText += ", " + String(failedCount) + " " + TR(FAILED_LOWER);
|
||||||
}
|
}
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, resultText.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, resultText.c_str());
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), "", "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == FAILED) {
|
if (state == FAILED) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Failed to clear cache", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, TR(CLEAR_CACHE_FAILED), true, EpdFontFamily::BOLD);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, "Check serial output for details");
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, TR(CHECK_SERIAL_OUTPUT));
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), "", "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
@ -105,13 +128,23 @@ void ClearCacheActivity::render() {
|
|||||||
void ClearCacheActivity::clearCache() {
|
void ClearCacheActivity::clearCache() {
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] Clearing cache...\n", millis());
|
Serial.printf("[%lu] [CLEAR_CACHE] Clearing cache...\n", millis());
|
||||||
|
|
||||||
|
// Check if exiting before starting
|
||||||
|
if (isExiting) {
|
||||||
|
Serial.printf("[%lu] [CLEAR_CACHE] Aborted: activity is exiting\n", millis());
|
||||||
|
clearCacheTaskHandle = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Open .crosspoint directory
|
// Open .crosspoint directory
|
||||||
auto root = SdMan.open("/.crosspoint");
|
auto root = SdMan.open("/.crosspoint");
|
||||||
if (!root || !root.isDirectory()) {
|
if (!root || !root.isDirectory()) {
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] Failed to open cache directory\n", millis());
|
Serial.printf("[%lu] [CLEAR_CACHE] Failed to open cache directory\n", millis());
|
||||||
if (root) root.close();
|
if (root) root.close();
|
||||||
state = FAILED;
|
if (!isExiting) {
|
||||||
updateRequired = true;
|
state = FAILED;
|
||||||
|
updateRequired = true;
|
||||||
|
}
|
||||||
|
clearCacheTaskHandle = nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +154,15 @@ void ClearCacheActivity::clearCache() {
|
|||||||
|
|
||||||
// Iterate through all entries in the directory
|
// Iterate through all entries in the directory
|
||||||
for (auto file = root.openNextFile(); file; file = root.openNextFile()) {
|
for (auto file = root.openNextFile(); file; file = root.openNextFile()) {
|
||||||
|
// Check if exiting during iteration
|
||||||
|
if (isExiting) {
|
||||||
|
file.close();
|
||||||
|
root.close();
|
||||||
|
Serial.printf("[%lu] [CLEAR_CACHE] Aborted during iteration\n", millis());
|
||||||
|
clearCacheTaskHandle = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
file.getName(name, sizeof(name));
|
file.getName(name, sizeof(name));
|
||||||
String itemName(name);
|
String itemName(name);
|
||||||
|
|
||||||
@ -145,21 +187,30 @@ void ClearCacheActivity::clearCache() {
|
|||||||
|
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] Cache cleared: %d removed, %d failed\n", millis(), clearedCount, failedCount);
|
Serial.printf("[%lu] [CLEAR_CACHE] Cache cleared: %d removed, %d failed\n", millis(), clearedCount, failedCount);
|
||||||
|
|
||||||
state = SUCCESS;
|
// Only update state if not exiting
|
||||||
updateRequired = true;
|
if (!isExiting) {
|
||||||
|
state = SUCCESS;
|
||||||
|
updateRequired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCacheTaskHandle = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearCacheActivity::loop() {
|
void ClearCacheActivity::loop() {
|
||||||
if (state == WARNING) {
|
if (state == WARNING) {
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
Serial.printf("[%lu] [CLEAR_CACHE] User confirmed, starting cache clear\n", millis());
|
Serial.printf("[%lu] [CLEAR_CACHE] User confirmed, starting cache clear\n", millis());
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
|
||||||
state = CLEARING;
|
state = CLEARING;
|
||||||
xSemaphoreGive(renderingMutex);
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
|
||||||
|
|
||||||
clearCache();
|
// Run clearCache in a separate task to avoid blocking loop()
|
||||||
|
xTaskCreate(
|
||||||
|
[](void* param) {
|
||||||
|
auto* self = static_cast<ClearCacheActivity*>(param);
|
||||||
|
self->clearCache();
|
||||||
|
vTaskDelete(nullptr);
|
||||||
|
},
|
||||||
|
"ClearCacheTask", 4096, this, 1, &clearCacheTaskHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
|
|||||||
@ -23,8 +23,10 @@ class ClearCacheActivity final : public ActivityWithSubactivity {
|
|||||||
|
|
||||||
State state = WARNING;
|
State state = WARNING;
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
|
TaskHandle_t clearCacheTaskHandle = nullptr; // Track clearCache task
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
|
bool isExiting = false; // Flag to prevent new operations during exit
|
||||||
const std::function<void()> goBack;
|
const std::function<void()> goBack;
|
||||||
|
|
||||||
int clearedCount = 0;
|
int clearedCount = 0;
|
||||||
|
|||||||
164
src/activities/settings/FontSelectActivity.cpp
Normal file
164
src/activities/settings/FontSelectActivity.cpp
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
#include "FontSelectActivity.h"
|
||||||
|
|
||||||
|
#include <FontManager.h>
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
|
#include "MappedInputManager.h"
|
||||||
|
#include "fontIds.h"
|
||||||
|
|
||||||
|
void FontSelectActivity::onEnter() {
|
||||||
|
ActivityWithSubactivity::onEnter();
|
||||||
|
|
||||||
|
// Wait for parent activity's rendering to complete (screen refresh takes ~422ms)
|
||||||
|
// Wait 500ms to be safe and avoid race conditions with parent activity
|
||||||
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
// Scan fonts
|
||||||
|
FontMgr.scanFonts();
|
||||||
|
|
||||||
|
// Total items = 1 (Built-in) + external font count
|
||||||
|
totalItems = 1 + FontMgr.getFontCount();
|
||||||
|
|
||||||
|
// Set current selection based on mode
|
||||||
|
int currentFont = (mode == SelectMode::Reader) ? FontMgr.getSelectedIndex()
|
||||||
|
: FontMgr.getUiSelectedIndex();
|
||||||
|
if (currentFont < 0) {
|
||||||
|
selectedIndex = 0; // Built-in
|
||||||
|
} else {
|
||||||
|
selectedIndex = currentFont + 1; // External font index + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步渲染,不使用后台任务
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontSelectActivity::onExit() {
|
||||||
|
ActivityWithSubactivity::onExit();
|
||||||
|
// 不需要清理任务和 mutex,因为我们不再使用它们
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontSelectActivity::loop() {
|
||||||
|
if (subActivity) {
|
||||||
|
subActivity->loop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
|
onBack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
|
handleSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needsRender = false;
|
||||||
|
|
||||||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
|
selectedIndex = (selectedIndex + totalItems - 1) % totalItems;
|
||||||
|
needsRender = true;
|
||||||
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||||
|
selectedIndex = (selectedIndex + 1) % totalItems;
|
||||||
|
needsRender = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步渲染
|
||||||
|
if (needsRender) {
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontSelectActivity::handleSelection() {
|
||||||
|
Serial.printf("[FONT_SELECT] handleSelection: mode=%d, selectedIndex=%d\n",
|
||||||
|
static_cast<int>(mode), selectedIndex);
|
||||||
|
|
||||||
|
if (selectedIndex == 0) {
|
||||||
|
// Select Built-in (disable external font)
|
||||||
|
if (mode == SelectMode::Reader) {
|
||||||
|
Serial.printf("[FONT_SELECT] Disabling reader font\n");
|
||||||
|
FontMgr.selectFont(-1);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[FONT_SELECT] Disabling UI font\n");
|
||||||
|
FontMgr.selectUiFont(-1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Select external font
|
||||||
|
if (mode == SelectMode::Reader) {
|
||||||
|
Serial.printf("[FONT_SELECT] Selecting reader font index %d\n", selectedIndex - 1);
|
||||||
|
FontMgr.selectFont(selectedIndex - 1);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[FONT_SELECT] Selecting UI font index %d\n", selectedIndex - 1);
|
||||||
|
FontMgr.selectUiFont(selectedIndex - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[FONT_SELECT] After selection: readerIndex=%d, uiIndex=%d\n",
|
||||||
|
FontMgr.getSelectedIndex(), FontMgr.getUiSelectedIndex());
|
||||||
|
|
||||||
|
// Return to previous page
|
||||||
|
onBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontSelectActivity::render() {
|
||||||
|
renderer.clearScreen();
|
||||||
|
|
||||||
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
constexpr int rowHeight = 30;
|
||||||
|
|
||||||
|
// Title
|
||||||
|
const char *title = (mode == SelectMode::Reader) ? TR(EXT_CHINESE_FONT)
|
||||||
|
: TR(EXT_UI_FONT);
|
||||||
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, title, true,
|
||||||
|
EpdFontFamily::BOLD);
|
||||||
|
|
||||||
|
// Current selected font marker
|
||||||
|
int currentFont = (mode == SelectMode::Reader) ? FontMgr.getSelectedIndex()
|
||||||
|
: FontMgr.getUiSelectedIndex();
|
||||||
|
|
||||||
|
// Draw options
|
||||||
|
for (int i = 0; i < totalItems && i < 20; i++) { // Max 20 items
|
||||||
|
const int itemY = 60 + i * rowHeight;
|
||||||
|
const bool isSelected = (i == selectedIndex);
|
||||||
|
const bool isCurrent =
|
||||||
|
(i == 0 && currentFont < 0) || (i > 0 && i - 1 == currentFont);
|
||||||
|
|
||||||
|
// Draw selection highlight
|
||||||
|
if (isSelected) {
|
||||||
|
renderer.fillRect(0, itemY - 2, pageWidth - 1, rowHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw text
|
||||||
|
if (i == 0) {
|
||||||
|
// Built-in option
|
||||||
|
renderer.drawText(UI_10_FONT_ID, 20, itemY, TR(BUILTIN_DISABLED),
|
||||||
|
!isSelected);
|
||||||
|
} else {
|
||||||
|
// External font
|
||||||
|
const FontInfo *info = FontMgr.getFontInfo(i - 1);
|
||||||
|
if (info) {
|
||||||
|
char label[64];
|
||||||
|
snprintf(label, sizeof(label), "%s (%dpt)", info->name, info->size);
|
||||||
|
renderer.drawText(UI_10_FONT_ID, 20, itemY, label, !isSelected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw current selection marker
|
||||||
|
if (isCurrent) {
|
||||||
|
const char* marker = TR(ON);
|
||||||
|
const auto width = renderer.getTextWidth(UI_10_FONT_ID, marker);
|
||||||
|
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, itemY,
|
||||||
|
marker, !isSelected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button hints
|
||||||
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(SELECT), "", "");
|
||||||
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3,
|
||||||
|
labels.btn4);
|
||||||
|
|
||||||
|
renderer.displayBuffer();
|
||||||
|
}
|
||||||
33
src/activities/settings/FontSelectActivity.h
Normal file
33
src/activities/settings/FontSelectActivity.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "activities/ActivityWithSubactivity.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Font selection page
|
||||||
|
* Shows available external fonts and allows user to select
|
||||||
|
* Uses synchronous rendering (no background task) to avoid FreeRTOS conflicts
|
||||||
|
*/
|
||||||
|
class FontSelectActivity final : public ActivityWithSubactivity {
|
||||||
|
public:
|
||||||
|
enum class SelectMode { Reader, UI };
|
||||||
|
explicit FontSelectActivity(GfxRenderer &renderer,
|
||||||
|
MappedInputManager &mappedInput, SelectMode mode,
|
||||||
|
const std::function<void()> &onBack)
|
||||||
|
: ActivityWithSubactivity("FontSelect", renderer, mappedInput),
|
||||||
|
mode(mode), onBack(onBack) {}
|
||||||
|
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SelectMode mode;
|
||||||
|
int selectedIndex = 0; // 0 = Built-in, 1+ = external fonts
|
||||||
|
int totalItems = 1; // At least "Built-in" option
|
||||||
|
const std::function<void()> onBack;
|
||||||
|
|
||||||
|
void render();
|
||||||
|
void handleSelection();
|
||||||
|
};
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#include "KOReaderAuthActivity.h"
|
#include "KOReaderAuthActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
#include "KOReaderCredentialStore.h"
|
#include "KOReaderCredentialStore.h"
|
||||||
@ -20,7 +21,7 @@ void KOReaderAuthActivity::onWifiSelectionComplete(const bool success) {
|
|||||||
if (!success) {
|
if (!success) {
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
state = FAILED;
|
state = FAILED;
|
||||||
errorMessage = "WiFi connection failed";
|
errorMessage = TR(WIFI_CONN_FAILED);
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
@ -28,7 +29,7 @@ void KOReaderAuthActivity::onWifiSelectionComplete(const bool success) {
|
|||||||
|
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
state = AUTHENTICATING;
|
state = AUTHENTICATING;
|
||||||
statusMessage = "Authenticating...";
|
statusMessage = TR(AUTHENTICATING);
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ void KOReaderAuthActivity::performAuthentication() {
|
|||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (result == KOReaderSyncClient::OK) {
|
if (result == KOReaderSyncClient::OK) {
|
||||||
state = SUCCESS;
|
state = SUCCESS;
|
||||||
statusMessage = "Successfully authenticated!";
|
statusMessage = TR(AUTH_SUCCESS);
|
||||||
} else {
|
} else {
|
||||||
state = FAILED;
|
state = FAILED;
|
||||||
errorMessage = KOReaderSyncClient::errorString(result);
|
errorMessage = KOReaderSyncClient::errorString(result);
|
||||||
@ -68,7 +69,7 @@ void KOReaderAuthActivity::onEnter() {
|
|||||||
// Check if already connected
|
// Check if already connected
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
state = AUTHENTICATING;
|
state = AUTHENTICATING;
|
||||||
statusMessage = "Authenticating...";
|
statusMessage = TR(AUTHENTICATING);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
// Perform authentication in a separate task
|
// Perform authentication in a separate task
|
||||||
@ -123,7 +124,7 @@ void KOReaderAuthActivity::render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Auth", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(KOREADER_AUTH), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
if (state == AUTHENTICATING) {
|
if (state == AUTHENTICATING) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, statusMessage.c_str(), true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, 300, statusMessage.c_str(), true, EpdFontFamily::BOLD);
|
||||||
@ -132,20 +133,20 @@ void KOReaderAuthActivity::render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state == SUCCESS) {
|
if (state == SUCCESS) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Success!", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, 280, TR(AUTH_SUCCESS), true, EpdFontFamily::BOLD);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, "KOReader sync is ready to use");
|
renderer.drawCenteredText(UI_10_FONT_ID, 320, TR(SYNC_READY));
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels("Done", "", "", "");
|
const auto labels = mappedInput.mapLabels(TR(DONE), "", "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == FAILED) {
|
if (state == FAILED) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Authentication Failed", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, 280, TR(AUTH_FAILED), true, EpdFontFamily::BOLD);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, errorMessage.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, 320, errorMessage.c_str());
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels("Back", "", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), "", "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "KOReaderSettingsActivity.h"
|
#include "KOReaderSettingsActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@ -10,11 +11,6 @@
|
|||||||
#include "activities/util/KeyboardEntryActivity.h"
|
#include "activities/util/KeyboardEntryActivity.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr int MENU_ITEMS = 5;
|
|
||||||
const char* menuNames[MENU_ITEMS] = {"Username", "Password", "Sync Server URL", "Document Matching", "Authenticate"};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void KOReaderSettingsActivity::taskTrampoline(void* param) {
|
void KOReaderSettingsActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<KOReaderSettingsActivity*>(param);
|
auto* self = static_cast<KOReaderSettingsActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
@ -25,7 +21,7 @@ void KOReaderSettingsActivity::onEnter() {
|
|||||||
|
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
selectedIndex = 0;
|
selectedIndex = 0;
|
||||||
updateRequired = true;
|
updateRequired = false; // Don't trigger render immediately to avoid race with parent activity
|
||||||
|
|
||||||
xTaskCreate(&KOReaderSettingsActivity::taskTrampoline, "KOReaderSettingsTask",
|
xTaskCreate(&KOReaderSettingsActivity::taskTrampoline, "KOReaderSettingsTask",
|
||||||
4096, // Stack size
|
4096, // Stack size
|
||||||
@ -65,23 +61,24 @@ void KOReaderSettingsActivity::loop() {
|
|||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
selectedIndex = (selectedIndex + MENU_ITEMS - 1) % MENU_ITEMS;
|
selectedIndex = (selectedIndex + 5 - 1) % 5;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||||
selectedIndex = (selectedIndex + 1) % MENU_ITEMS;
|
selectedIndex = (selectedIndex + 1) % 5;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderSettingsActivity::handleSelection() {
|
void KOReaderSettingsActivity::handleSelection() {
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
// Don't hold mutex while creating subactivities to avoid race conditions
|
||||||
|
// between parent and child rendering tasks
|
||||||
|
|
||||||
if (selectedIndex == 0) {
|
if (selectedIndex == 0) {
|
||||||
// Username
|
// Username
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new KeyboardEntryActivity(
|
enterNewActivity(new KeyboardEntryActivity(
|
||||||
renderer, mappedInput, "KOReader Username", KOREADER_STORE.getUsername(), 10,
|
renderer, mappedInput, TR(KOREADER_USERNAME), KOREADER_STORE.getUsername(), 10,
|
||||||
64, // maxLength
|
64, // maxLength
|
||||||
false, // not password
|
false, // not password
|
||||||
[this](const std::string& username) {
|
[this](const std::string& username) {
|
||||||
@ -98,7 +95,7 @@ void KOReaderSettingsActivity::handleSelection() {
|
|||||||
// Password
|
// Password
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new KeyboardEntryActivity(
|
enterNewActivity(new KeyboardEntryActivity(
|
||||||
renderer, mappedInput, "KOReader Password", KOREADER_STORE.getPassword(), 10,
|
renderer, mappedInput, TR(KOREADER_PASSWORD), KOREADER_STORE.getPassword(), 10,
|
||||||
64, // maxLength
|
64, // maxLength
|
||||||
false, // show characters
|
false, // show characters
|
||||||
[this](const std::string& password) {
|
[this](const std::string& password) {
|
||||||
@ -117,7 +114,7 @@ void KOReaderSettingsActivity::handleSelection() {
|
|||||||
const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl;
|
const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl;
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new KeyboardEntryActivity(
|
enterNewActivity(new KeyboardEntryActivity(
|
||||||
renderer, mappedInput, "Sync Server URL", prefillUrl, 10,
|
renderer, mappedInput, TR(SYNC_SERVER_URL), prefillUrl, 10,
|
||||||
128, // maxLength - URLs can be long
|
128, // maxLength - URLs can be long
|
||||||
false, // not password
|
false, // not password
|
||||||
[this](const std::string& url) {
|
[this](const std::string& url) {
|
||||||
@ -144,7 +141,6 @@ void KOReaderSettingsActivity::handleSelection() {
|
|||||||
// Authenticate
|
// Authenticate
|
||||||
if (!KOREADER_STORE.hasCredentials()) {
|
if (!KOREADER_STORE.hasCredentials()) {
|
||||||
// Can't authenticate without credentials - just show message briefly
|
// Can't authenticate without credentials - just show message briefly
|
||||||
xSemaphoreGive(renderingMutex);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
exitActivity();
|
exitActivity();
|
||||||
@ -153,11 +149,14 @@ void KOReaderSettingsActivity::handleSelection() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
xSemaphoreGive(renderingMutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderSettingsActivity::displayTaskLoop() {
|
void KOReaderSettingsActivity::displayTaskLoop() {
|
||||||
|
// Wait for parent activity's rendering to complete (screen refresh takes ~422ms)
|
||||||
|
// Wait 500ms to be safe and avoid race conditions with parent activity
|
||||||
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||||
|
updateRequired = true;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired && !subActivity) {
|
if (updateRequired && !subActivity) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -175,13 +174,14 @@ void KOReaderSettingsActivity::render() {
|
|||||||
const auto pageWidth = renderer.getScreenWidth();
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
|
||||||
// Draw header
|
// Draw header
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Sync", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(KOREADER_SYNC), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
// Draw selection highlight
|
// Draw selection highlight
|
||||||
renderer.fillRect(0, 60 + selectedIndex * 30 - 2, pageWidth - 1, 30);
|
renderer.fillRect(0, 60 + selectedIndex * 30 - 2, pageWidth - 1, 30);
|
||||||
|
|
||||||
// Draw menu items
|
// Draw menu items
|
||||||
for (int i = 0; i < MENU_ITEMS; i++) {
|
const char* menuNames[5] = {TR(USERNAME), TR(PASSWORD), TR(SYNC_SERVER_URL), TR(DOCUMENT_MATCHING), TR(AUTHENTICATE)};
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
const int settingY = 60 + i * 30;
|
const int settingY = 60 + i * 30;
|
||||||
const bool isSelected = (i == selectedIndex);
|
const bool isSelected = (i == selectedIndex);
|
||||||
|
|
||||||
@ -190,15 +190,15 @@ void KOReaderSettingsActivity::render() {
|
|||||||
// Draw status for each item
|
// Draw status for each item
|
||||||
const char* status = "";
|
const char* status = "";
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
status = KOREADER_STORE.getUsername().empty() ? "[Not Set]" : "[Set]";
|
status = KOREADER_STORE.getUsername().empty() ? TR(NOT_SET) : TR(SET);
|
||||||
} else if (i == 1) {
|
} else if (i == 1) {
|
||||||
status = KOREADER_STORE.getPassword().empty() ? "[Not Set]" : "[Set]";
|
status = KOREADER_STORE.getPassword().empty() ? TR(NOT_SET) : TR(SET);
|
||||||
} else if (i == 2) {
|
} else if (i == 2) {
|
||||||
status = KOREADER_STORE.getServerUrl().empty() ? "[Not Set]" : "[Set]";
|
status = KOREADER_STORE.getServerUrl().empty() ? TR(NOT_SET) : TR(SET);
|
||||||
} else if (i == 3) {
|
} else if (i == 3) {
|
||||||
status = KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME ? "[Filename]" : "[Binary]";
|
status = KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME ? TR(FILENAME) : TR(BINARY);
|
||||||
} else if (i == 4) {
|
} else if (i == 4) {
|
||||||
status = KOREADER_STORE.hasCredentials() ? "" : "[Set credentials first]";
|
status = KOREADER_STORE.hasCredentials() ? "" : TR(SET_CREDENTIALS_FIRST);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
|
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
|
||||||
@ -206,7 +206,7 @@ void KOReaderSettingsActivity::render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw button hints
|
// Draw button hints
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(SELECT), "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
|||||||
145
src/activities/settings/LanguageSelectActivity.cpp
Normal file
145
src/activities/settings/LanguageSelectActivity.cpp
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
#include "LanguageSelectActivity.h"
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
|
#include "MappedInputManager.h"
|
||||||
|
#include "fontIds.h"
|
||||||
|
|
||||||
|
void LanguageSelectActivity::taskTrampoline(void *param) {
|
||||||
|
auto *self = static_cast<LanguageSelectActivity *>(param);
|
||||||
|
self->displayTaskLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LanguageSelectActivity::onEnter() {
|
||||||
|
ActivityWithSubactivity::onEnter();
|
||||||
|
|
||||||
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
|
// Set current selection based on current language
|
||||||
|
selectedIndex = static_cast<int>(I18N.getLanguage());
|
||||||
|
|
||||||
|
updateRequired = false; // Don't trigger render immediately to avoid race with parent activity
|
||||||
|
|
||||||
|
xTaskCreate(&LanguageSelectActivity::taskTrampoline, "LanguageSelectTask",
|
||||||
|
4096, this, 1, &displayTaskHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LanguageSelectActivity::onExit() {
|
||||||
|
ActivityWithSubactivity::onExit();
|
||||||
|
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
if (displayTaskHandle) {
|
||||||
|
vTaskDelete(displayTaskHandle);
|
||||||
|
displayTaskHandle = nullptr;
|
||||||
|
}
|
||||||
|
vSemaphoreDelete(renderingMutex);
|
||||||
|
renderingMutex = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LanguageSelectActivity::loop() {
|
||||||
|
if (subActivity) {
|
||||||
|
subActivity->loop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
|
onBack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
|
handleSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
|
selectedIndex = (selectedIndex + totalItems - 1) % totalItems;
|
||||||
|
updateRequired = true;
|
||||||
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||||
|
selectedIndex = (selectedIndex + 1) % totalItems;
|
||||||
|
updateRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LanguageSelectActivity::handleSelection() {
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
|
||||||
|
// Set the selected language (setLanguage internally calls saveSettings)
|
||||||
|
I18N.setLanguage(static_cast<Language>(selectedIndex));
|
||||||
|
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
|
||||||
|
// Return to previous page
|
||||||
|
onBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LanguageSelectActivity::displayTaskLoop() {
|
||||||
|
// Wait for parent activity's rendering to complete (screen refresh takes ~422ms)
|
||||||
|
// Wait 500ms to be safe and avoid race conditions with parent activity
|
||||||
|
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||||
|
updateRequired = true;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (updateRequired && !subActivity) {
|
||||||
|
updateRequired = false;
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
render();
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
}
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LanguageSelectActivity::render() {
|
||||||
|
renderer.clearScreen();
|
||||||
|
|
||||||
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
constexpr int rowHeight = 30;
|
||||||
|
|
||||||
|
// Title
|
||||||
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(LANGUAGE), true,
|
||||||
|
EpdFontFamily::BOLD);
|
||||||
|
|
||||||
|
// Current language marker
|
||||||
|
const int currentLang = static_cast<int>(I18N.getLanguage());
|
||||||
|
|
||||||
|
// Language names in their native language
|
||||||
|
const char *langNames[] = {
|
||||||
|
"English",
|
||||||
|
"\xE7\xAE\x80\xE4\xBD\x93\xE4\xB8\xAD\xE6\x96\x87", // 简体中文
|
||||||
|
"\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" // 日本語
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draw options
|
||||||
|
for (int i = 0; i < totalItems && i < 10; i++) { // Max 10 items
|
||||||
|
const int itemY = 60 + i * rowHeight;
|
||||||
|
const bool isSelected = (i == selectedIndex);
|
||||||
|
const bool isCurrent = (i == currentLang);
|
||||||
|
|
||||||
|
// Draw selection highlight
|
||||||
|
if (isSelected) {
|
||||||
|
renderer.fillRect(0, itemY - 2, pageWidth - 1, rowHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw language name
|
||||||
|
renderer.drawText(UI_10_FONT_ID, 20, itemY, langNames[i], !isSelected);
|
||||||
|
|
||||||
|
// Draw current selection marker
|
||||||
|
if (isCurrent) {
|
||||||
|
const char *marker = "[ON]";
|
||||||
|
const auto width = renderer.getTextWidth(UI_10_FONT_ID, marker);
|
||||||
|
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, itemY, marker,
|
||||||
|
!isSelected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button hints
|
||||||
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(SELECT), "", "");
|
||||||
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3,
|
||||||
|
labels.btn4);
|
||||||
|
|
||||||
|
renderer.displayBuffer();
|
||||||
|
}
|
||||||
39
src/activities/settings/LanguageSelectActivity.h
Normal file
39
src/activities/settings/LanguageSelectActivity.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "../ActivityWithSubactivity.h"
|
||||||
|
|
||||||
|
class MappedInputManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity for selecting UI language
|
||||||
|
*/
|
||||||
|
class LanguageSelectActivity final : public ActivityWithSubactivity {
|
||||||
|
public:
|
||||||
|
explicit LanguageSelectActivity(GfxRenderer &renderer,
|
||||||
|
MappedInputManager &mappedInput,
|
||||||
|
const std::function<void()> &onBack)
|
||||||
|
: ActivityWithSubactivity("LanguageSelect", renderer, mappedInput), onBack(onBack) {}
|
||||||
|
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void taskTrampoline(void *param);
|
||||||
|
void displayTaskLoop();
|
||||||
|
void render();
|
||||||
|
void handleSelection();
|
||||||
|
|
||||||
|
std::function<void()> onBack;
|
||||||
|
int selectedIndex = 0;
|
||||||
|
static constexpr int totalItems = 3; // English, 简体中文, 日本語
|
||||||
|
|
||||||
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
volatile bool updateRequired = false;
|
||||||
|
};
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#include "OtaUpdateActivity.h"
|
#include "OtaUpdateActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
@ -127,27 +128,27 @@ void OtaUpdateActivity::render() {
|
|||||||
const auto pageWidth = renderer.getScreenWidth();
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Update", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(UPDATE), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
if (state == CHECKING_FOR_UPDATE) {
|
if (state == CHECKING_FOR_UPDATE) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Checking for update...", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, 300, TR(CHECKING_UPDATE), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == WAITING_CONFIRMATION) {
|
if (state == WAITING_CONFIRMATION) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 200, "New update available!", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, 200, TR(NEW_UPDATE), true, EpdFontFamily::BOLD);
|
||||||
renderer.drawText(UI_10_FONT_ID, 20, 250, "Current Version: " CROSSPOINT_VERSION);
|
renderer.drawText(UI_10_FONT_ID, 20, 250, (std::string(TR(CURRENT_VERSION)) + CROSSPOINT_VERSION).c_str());
|
||||||
renderer.drawText(UI_10_FONT_ID, 20, 270, ("New Version: " + updater.getLatestVersion()).c_str());
|
renderer.drawText(UI_10_FONT_ID, 20, 270, (std::string(TR(NEW_VERSION)) + updater.getLatestVersion()).c_str());
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels("Cancel", "Update", "", "");
|
const auto labels = mappedInput.mapLabels(TR(CANCEL), TR(UPDATE), "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == UPDATE_IN_PROGRESS) {
|
if (state == UPDATE_IN_PROGRESS) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 310, "Updating...", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, 310, TR(UPDATING), true, EpdFontFamily::BOLD);
|
||||||
renderer.drawRect(20, 350, pageWidth - 40, 50);
|
renderer.drawRect(20, 350, pageWidth - 40, 50);
|
||||||
renderer.fillRect(24, 354, static_cast<int>(updaterProgress * static_cast<float>(pageWidth - 44)), 42);
|
renderer.fillRect(24, 354, static_cast<int>(updaterProgress * static_cast<float>(pageWidth - 44)), 42);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 420,
|
renderer.drawCenteredText(UI_10_FONT_ID, 420,
|
||||||
@ -160,20 +161,20 @@ void OtaUpdateActivity::render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state == NO_UPDATE) {
|
if (state == NO_UPDATE) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, "No update available", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, 300, TR(NO_UPDATE), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == FAILED) {
|
if (state == FAILED) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update failed", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, 300, TR(UPDATE_FAILED), true, EpdFontFamily::BOLD);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == FINISHED) {
|
if (state == FINISHED) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update complete", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_10_FONT_ID, 300, TR(UPDATE_COMPLETE), true, EpdFontFamily::BOLD);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, 350, "Press and hold power button to turn back on");
|
renderer.drawCenteredText(UI_10_FONT_ID, 350, TR(POWER_ON_HINT));
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
state = SHUTTING_DOWN;
|
state = SHUTTING_DOWN;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -2,54 +2,55 @@
|
|||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
#include "CategorySettingsActivity.h"
|
#include "CategorySettingsActivity.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int displaySettingsCount = 5;
|
constexpr int displaySettingsCount = 6;
|
||||||
const SettingInfo displaySettings[displaySettingsCount] = {
|
const SettingInfo displaySettings[displaySettingsCount] = {
|
||||||
// Should match with SLEEP_SCREEN_MODE
|
// Should match with SLEEP_SCREEN_MODE
|
||||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
SettingInfo::Enum(StrId::SLEEP_SCREEN, &CrossPointSettings::sleepScreen, {StrId::DARK, StrId::LIGHT, StrId::CUSTOM, StrId::COVER, StrId::NONE}),
|
||||||
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
|
SettingInfo::Enum(StrId::SLEEP_COVER_MODE, &CrossPointSettings::sleepScreenCoverMode, {StrId::FIT, StrId::CROP}),
|
||||||
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
|
SettingInfo::Enum(StrId::STATUS_BAR, &CrossPointSettings::statusBar, {StrId::NONE, StrId::NO_PROGRESS, StrId::FULL}),
|
||||||
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
SettingInfo::Enum(StrId::HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage, {StrId::NEVER, StrId::IN_READER, StrId::ALWAYS}),
|
||||||
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
SettingInfo::Enum(StrId::REFRESH_FREQ, &CrossPointSettings::refreshFrequency,
|
||||||
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"})};
|
{StrId::PAGES_1, StrId::PAGES_5, StrId::PAGES_10, StrId::PAGES_15, StrId::PAGES_30}),
|
||||||
|
SettingInfo::Action(StrId::EXT_UI_FONT)};
|
||||||
|
|
||||||
constexpr int readerSettingsCount = 9;
|
constexpr int readerSettingsCount = 9;
|
||||||
const SettingInfo readerSettings[readerSettingsCount] = {
|
const SettingInfo readerSettings[readerSettingsCount] = {
|
||||||
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}),
|
SettingInfo::Action(StrId::EXT_READER_FONT),
|
||||||
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}),
|
SettingInfo::Enum(StrId::FONT_SIZE, &CrossPointSettings::fontSize, {StrId::SMALL, StrId::MEDIUM, StrId::LARGE, StrId::X_LARGE}),
|
||||||
SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}),
|
SettingInfo::Enum(StrId::LINE_SPACING, &CrossPointSettings::lineSpacing, {StrId::TIGHT, StrId::NORMAL, StrId::WIDE}),
|
||||||
SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}),
|
SettingInfo::Value(StrId::SCREEN_MARGIN, &CrossPointSettings::screenMargin, {5, 40, 5}),
|
||||||
SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
|
SettingInfo::Enum(StrId::PARA_ALIGNMENT, &CrossPointSettings::paragraphAlignment,
|
||||||
{"Justify", "Left", "Center", "Right"}),
|
{StrId::JUSTIFY, StrId::LEFT, StrId::CENTER, StrId::RIGHT}),
|
||||||
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled),
|
SettingInfo::Toggle(StrId::HYPHENATION, &CrossPointSettings::hyphenationEnabled),
|
||||||
SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation,
|
SettingInfo::Enum(StrId::ORIENTATION, &CrossPointSettings::orientation,
|
||||||
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
|
{StrId::PORTRAIT, StrId::LANDSCAPE_CW, StrId::INVERTED, StrId::LANDSCAPE_CCW}),
|
||||||
SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing),
|
SettingInfo::Toggle(StrId::EXTRA_SPACING, &CrossPointSettings::extraParagraphSpacing),
|
||||||
SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing)};
|
SettingInfo::Toggle(StrId::TEXT_AA, &CrossPointSettings::textAntiAliasing)};
|
||||||
|
|
||||||
constexpr int controlsSettingsCount = 4;
|
constexpr int controlsSettingsCount = 4;
|
||||||
const SettingInfo controlsSettings[controlsSettingsCount] = {
|
const SettingInfo controlsSettings[controlsSettingsCount] = {
|
||||||
SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout,
|
SettingInfo::Enum(StrId::FRONT_BTN_LAYOUT, &CrossPointSettings::frontButtonLayout,
|
||||||
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}),
|
{StrId::FRONT_LAYOUT_BCLR, StrId::FRONT_LAYOUT_LRBC, StrId::FRONT_LAYOUT_LBCR}),
|
||||||
SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout,
|
SettingInfo::Enum(StrId::SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout,
|
||||||
{"Prev, Next", "Next, Prev"}),
|
{StrId::PREV_NEXT, StrId::NEXT_PREV}),
|
||||||
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip),
|
SettingInfo::Toggle(StrId::LONG_PRESS_SKIP, &CrossPointSettings::longPressChapterSkip),
|
||||||
SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"})};
|
SettingInfo::Enum(StrId::SHORT_PWR_BTN, &CrossPointSettings::shortPwrBtn, {StrId::IGNORE, StrId::SLEEP, StrId::PAGE_TURN})};
|
||||||
|
|
||||||
constexpr int systemSettingsCount = 5;
|
constexpr int systemSettingsCount = 6;
|
||||||
const SettingInfo systemSettings[systemSettingsCount] = {
|
const SettingInfo systemSettings[systemSettingsCount] = {
|
||||||
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
|
SettingInfo::Enum(StrId::TIME_TO_SLEEP, &CrossPointSettings::sleepTimeout,
|
||||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
{StrId::MIN_1, StrId::MIN_5, StrId::MIN_10, StrId::MIN_15, StrId::MIN_30}),
|
||||||
SettingInfo::Action("KOReader Sync"), SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
|
SettingInfo::Action(StrId::LANGUAGE),
|
||||||
SettingInfo::Action("Check for updates")};
|
SettingInfo::Action(StrId::KOREADER_SYNC), SettingInfo::Action(StrId::CALIBRE_SETTINGS), SettingInfo::Action(StrId::CLEAR_READING_CACHE),
|
||||||
|
SettingInfo::Action(StrId::CHECK_UPDATES)};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void SettingsActivity::taskTrampoline(void* param) {
|
void SettingsActivity::taskTrampoline(void* param) {
|
||||||
@ -131,6 +132,11 @@ void SettingsActivity::enterCategory(int categoryIndex) {
|
|||||||
const SettingInfo* settingsList = nullptr;
|
const SettingInfo* settingsList = nullptr;
|
||||||
int settingsCount = 0;
|
int settingsCount = 0;
|
||||||
|
|
||||||
|
// Category StrIds for dynamic translation
|
||||||
|
static constexpr StrId categoryStrIds[categoryCount] = {
|
||||||
|
StrId::CAT_DISPLAY, StrId::CAT_READER, StrId::CAT_CONTROLS, StrId::CAT_SYSTEM
|
||||||
|
};
|
||||||
|
|
||||||
switch (categoryIndex) {
|
switch (categoryIndex) {
|
||||||
case 0: // Display
|
case 0: // Display
|
||||||
settingsList = displaySettings;
|
settingsList = displaySettings;
|
||||||
@ -150,7 +156,7 @@ void SettingsActivity::enterCategory(int categoryIndex) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, categoryNames[categoryIndex], settingsList,
|
enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, I18N.get(categoryStrIds[categoryIndex]), settingsList,
|
||||||
settingsCount, [this] {
|
settingsCount, [this] {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@ -177,17 +183,22 @@ void SettingsActivity::render() const {
|
|||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
// Draw header
|
// Draw header
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, 15, TR(SETTINGS_TITLE), true, EpdFontFamily::BOLD);
|
||||||
|
|
||||||
// Draw selection
|
// Draw selection
|
||||||
renderer.fillRect(0, 60 + selectedCategoryIndex * 30 - 2, pageWidth - 1, 30);
|
renderer.fillRect(0, 60 + selectedCategoryIndex * 30 - 2, pageWidth - 1, 30);
|
||||||
|
|
||||||
|
// Category StrIds for dynamic translation
|
||||||
|
static constexpr StrId categoryStrIds[categoryCount] = {
|
||||||
|
StrId::CAT_DISPLAY, StrId::CAT_READER, StrId::CAT_CONTROLS, StrId::CAT_SYSTEM
|
||||||
|
};
|
||||||
|
|
||||||
// Draw all categories
|
// Draw all categories
|
||||||
for (int i = 0; i < categoryCount; i++) {
|
for (int i = 0; i < categoryCount; i++) {
|
||||||
const int categoryY = 60 + i * 30; // 30 pixels between categories
|
const int categoryY = 60 + i * 30; // 30 pixels between categories
|
||||||
|
|
||||||
// Draw category name
|
// Draw category name (dynamically translated)
|
||||||
renderer.drawText(UI_10_FONT_ID, 20, categoryY, categoryNames[i], i != selectedCategoryIndex);
|
renderer.drawText(UI_10_FONT_ID, 20, categoryY, I18N.get(categoryStrIds[i]), i != selectedCategoryIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw version text above button hints
|
// Draw version text above button hints
|
||||||
@ -195,7 +206,7 @@ void SettingsActivity::render() const {
|
|||||||
pageHeight - 60, CROSSPOINT_VERSION);
|
pageHeight - 60, CROSSPOINT_VERSION);
|
||||||
|
|
||||||
// Draw help text
|
// Draw help text
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(SELECT), "", "");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
// Always use standard refresh for settings screen
|
// Always use standard refresh for settings screen
|
||||||
|
|||||||
@ -20,7 +20,6 @@ class SettingsActivity final : public ActivityWithSubactivity {
|
|||||||
const std::function<void()> onGoHome;
|
const std::function<void()> onGoHome;
|
||||||
|
|
||||||
static constexpr int categoryCount = 4;
|
static constexpr int categoryCount = 4;
|
||||||
static const char* categoryNames[categoryCount];
|
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#include "KeyboardEntryActivity.h"
|
#include "KeyboardEntryActivity.h"
|
||||||
|
|
||||||
|
#include <I18n.h>
|
||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
@ -342,11 +344,11 @@ void KeyboardEntryActivity::render() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw help text
|
// Draw help text
|
||||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "Left", "Right");
|
const auto labels = mappedInput.mapLabels(TR(BACK), TR(SELECT), TR(DIR_LEFT), TR(DIR_RIGHT));
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
// Draw side button hints for Up/Down navigation
|
// Draw side button hints for Up/Down navigation
|
||||||
renderer.drawSideButtonHints(UI_10_FONT_ID, "Up", "Down");
|
renderer.drawSideButtonHints(UI_10_FONT_ID, TR(DIR_UP), TR(DIR_DOWN));
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
#include <InputManager.h>
|
#include <InputManager.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
|
#include <FontManager.h>
|
||||||
|
#include <I18n.h>
|
||||||
#include <builtinFonts/all.h>
|
#include <builtinFonts/all.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -322,6 +324,13 @@ void setup() {
|
|||||||
SETTINGS.loadFromFile();
|
SETTINGS.loadFromFile();
|
||||||
KOREADER_STORE.loadFromFile();
|
KOREADER_STORE.loadFromFile();
|
||||||
|
|
||||||
|
// Initialize FontManager - scan fonts and load user's font selection
|
||||||
|
FontMgr.scanFonts();
|
||||||
|
FontMgr.loadSettings();
|
||||||
|
|
||||||
|
// Initialize I18n - load language settings
|
||||||
|
I18N.loadSettings();
|
||||||
|
|
||||||
if (!isWakeupAfterFlashing()) {
|
if (!isWakeupAfterFlashing()) {
|
||||||
// For normal wakeups (not immediately after flashing), verify long press
|
// For normal wakeups (not immediately after flashing), verify long press
|
||||||
verifyWakeupLongPress();
|
verifyWakeupLongPress();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user