mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 14:47:37 +03:00
Merge master into feature/sleep-bmp-selection
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
commit
c3ce6e11da
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,5 +4,8 @@
|
|||||||
.vscode
|
.vscode
|
||||||
lib/EpdFont/fontsrc
|
lib/EpdFont/fontsrc
|
||||||
*.generated.h
|
*.generated.h
|
||||||
|
.vs
|
||||||
build
|
build
|
||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
|
/compile_commands.json
|
||||||
|
/.cache
|
||||||
|
|||||||
@ -41,6 +41,8 @@ This project is **not affiliated with Xteink**; it's built as a community projec
|
|||||||
- [ ] Full UTF support
|
- [ ] Full UTF support
|
||||||
- [x] Screen rotation
|
- [x] Screen rotation
|
||||||
|
|
||||||
|
Multi-language support: Read EPUBs in various languages, including English, Spanish, French, German, Italian, Portuguese, Russian, Ukrainian, Polish, Swedish, Norwegian, [and more](./USER_GUIDE.md#supported-languages).
|
||||||
|
|
||||||
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|||||||
@ -2,6 +2,27 @@
|
|||||||
|
|
||||||
Welcome to the **CrossPoint** firmware. This guide outlines the hardware controls, navigation, and reading features of the device.
|
Welcome to the **CrossPoint** firmware. This guide outlines the hardware controls, navigation, and reading features of the device.
|
||||||
|
|
||||||
|
- [CrossPoint User Guide](#crosspoint-user-guide)
|
||||||
|
- [1. Hardware Overview](#1-hardware-overview)
|
||||||
|
- [Button Layout](#button-layout)
|
||||||
|
- [2. Power \& Startup](#2-power--startup)
|
||||||
|
- [Power On / Off](#power-on--off)
|
||||||
|
- [First Launch](#first-launch)
|
||||||
|
- [3. Screens](#3-screens)
|
||||||
|
- [3.1 Home Screen](#31-home-screen)
|
||||||
|
- [3.2 Book Selection](#32-book-selection)
|
||||||
|
- [3.3 Reading Mode](#33-reading-mode)
|
||||||
|
- [3.4 File Upload Screen](#34-file-upload-screen)
|
||||||
|
- [3.5 Settings](#35-settings)
|
||||||
|
- [3.6 Sleep Screen](#36-sleep-screen)
|
||||||
|
- [4. Reading Mode](#4-reading-mode)
|
||||||
|
- [Page Turning](#page-turning)
|
||||||
|
- [Chapter Navigation](#chapter-navigation)
|
||||||
|
- [System Navigation](#system-navigation)
|
||||||
|
- [5. Chapter Selection Screen](#5-chapter-selection-screen)
|
||||||
|
- [6. Current Limitations \& Roadmap](#6-current-limitations--roadmap)
|
||||||
|
|
||||||
|
|
||||||
## 1. Hardware Overview
|
## 1. Hardware Overview
|
||||||
|
|
||||||
The device utilises the standard buttons on the Xtink X4 (in the same layout as the manufacturer firmware, by default):
|
The device utilises the standard buttons on the Xtink X4 (in the same layout as the manufacturer firmware, by default):
|
||||||
@ -60,6 +81,18 @@ See the [webserver docs](./docs/webserver.md) for more information on how to con
|
|||||||
> [!TIP]
|
> [!TIP]
|
||||||
> Advanced users can also manage files programmatically or via the command line using `curl`. See the [webserver docs](./docs/webserver.md) for details.
|
> Advanced users can also manage files programmatically or via the command line using `curl`. See the [webserver docs](./docs/webserver.md) for details.
|
||||||
|
|
||||||
|
### 3.4.1 Calibre Wireless Transfers
|
||||||
|
|
||||||
|
CrossPoint supports sending books from Calibre using the CrossPoint Reader device plugin.
|
||||||
|
|
||||||
|
1. Install the plugin in Calibre:
|
||||||
|
- Head to https://github.com/crosspoint-reader/calibre-plugins/releases to download the latest version of the crosspoint_reader plugin.
|
||||||
|
- Download the zip file.
|
||||||
|
- Open Calibre → Preferences → Plugins → Load plugin from file → Select the zip file.
|
||||||
|
2. On the device: File Transfer → Connect to Calibre → Join a network.
|
||||||
|
3. Make sure your computer is on the same WiFi network.
|
||||||
|
4. In Calibre, click "Send to device" to transfer books.
|
||||||
|
|
||||||
### 3.5 Settings
|
### 3.5 Settings
|
||||||
|
|
||||||
The Settings screen allows you to configure the device's behavior. There are a few settings you can adjust:
|
The Settings screen allows you to configure the device's behavior. There are a few settings you can adjust:
|
||||||
@ -72,6 +105,10 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
|||||||
- **Sleep Screen Cover Mode**: How to display the book cover when "Cover" sleep screen is selected:
|
- **Sleep Screen Cover Mode**: How to display the book cover when "Cover" sleep screen is selected:
|
||||||
- "Fit" (default) - Scale the image down to fit centered on the screen, padding with white borders as necessary
|
- "Fit" (default) - Scale the image down to fit centered on the screen, padding with white borders as necessary
|
||||||
- "Crop" - Scale the image down and crop as necessary to try to to fill the screen (Note: this is experimental and may not work as expected)
|
- "Crop" - Scale the image down and crop as necessary to try to to fill the screen (Note: this is experimental and may not work as expected)
|
||||||
|
- **Sleep Screen Cover Filter**: What filter will be applied to the book cover when "Cover" sleep screen is selected
|
||||||
|
- "None" (default) - The cover image will be converted to a grayscale image and displayed as it is
|
||||||
|
- "Contrast" - The image will be displayed as a black & white image without grayscale conversion
|
||||||
|
- "Inverted" - The image will be inverted as in white&black and will be displayed without grayscale conversion
|
||||||
- **Status Bar**: Configure the status bar displayed while reading:
|
- **Status Bar**: Configure the status bar displayed while reading:
|
||||||
- "None" - No status bar
|
- "None" - No status bar
|
||||||
- "No Progress" - Show status bar without reading progress
|
- "No Progress" - Show status bar without reading progress
|
||||||
@ -95,6 +132,7 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
|||||||
- Back, Confirm, Left, Right (default)
|
- Back, Confirm, Left, Right (default)
|
||||||
- Left, Right, Back, Confirm
|
- Left, Right, Back, Confirm
|
||||||
- Left, Back, Confirm, Right
|
- Left, Back, Confirm, Right
|
||||||
|
- Back, Confirm, Right, Left
|
||||||
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||||
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skip to the next/previous chapter.
|
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skip to the next/previous chapter.
|
||||||
- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
|
- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
|
||||||
@ -110,7 +148,7 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
|||||||
- **Reader Paragraph Alignment**: Set the alignment of paragraphs; options are "Justified" (default), "Left", "Center", or "Right".
|
- **Reader Paragraph Alignment**: Set the alignment of paragraphs; options are "Justified" (default), "Left", "Center", or "Right".
|
||||||
- **Time to Sleep**: Set the duration of inactivity before the device automatically goes to sleep.
|
- **Time to Sleep**: Set the duration of inactivity before the device automatically goes to sleep.
|
||||||
- **Refresh Frequency**: Set how often the screen does a full refresh while reading to reduce ghosting.
|
- **Refresh Frequency**: Set how often the screen does a full refresh while reading to reduce ghosting.
|
||||||
- **Calibre Settings**: Set up integration for accessing a Calibre web library or connecting to Calibre as a wireless device.
|
- **OPDS Browser**: Configure OPDS server settings for browsing and downloading books. Set the server URL (for Calibre Content Server, add `/opds` to the end), and optionally configure username and password for servers requiring authentication. Note: Only HTTP Basic authentication is supported. If using Calibre Content Server with authentication enabled, you must set it to use Basic authentication instead of the default Digest authentication.
|
||||||
- **Check for updates**: Check for firmware updates over WiFi.
|
- **Check for updates**: Check for firmware updates over WiFi.
|
||||||
|
|
||||||
### 3.6 Sleep Screen
|
### 3.6 Sleep Screen
|
||||||
@ -156,6 +194,15 @@ This feature can be disabled in **[Settings](#35-settings)** to help avoid chang
|
|||||||
* **Return to Home:** Press and **hold** the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
|
* **Return to Home:** Press and **hold** the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
|
||||||
* **Chapter Menu:** Press **Confirm** to open the **[Table of Contents/Chapter Selection](#5-chapter-selection-screen)**.
|
* **Chapter Menu:** Press **Confirm** to open the **[Table of Contents/Chapter Selection](#5-chapter-selection-screen)**.
|
||||||
|
|
||||||
|
### Supported Languages
|
||||||
|
|
||||||
|
CrossPoint renders text using the following Unicode character blocks, enabling support for a wide range of languages:
|
||||||
|
|
||||||
|
* **Latin Script (Basic, Supplement, Extended-A):** Covers English, German, French, Spanish, Portuguese, Italian, Dutch, Swedish, Norwegian, Danish, Finnish, Polish, Czech, Hungarian, Romanian, Slovak, Slovenian, Turkish, and others.
|
||||||
|
* **Cyrillic Script (Standard and Extended):** Covers Russian, Ukrainian, Belarusian, Bulgarian, Serbian, Macedonian, Kazakh, Kyrgyz, Mongolian, and others.
|
||||||
|
|
||||||
|
What is not supported: Chinese, Japanese, Korean, Vietnamese, Hebrew, Arabic and Farsi.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Chapter Selection Screen
|
## 5. Chapter Selection Screen
|
||||||
|
|||||||
57
docs/troubleshooting.md
Normal file
57
docs/troubleshooting.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
This document show most common issues and possible solutions while using the device features.
|
||||||
|
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
- [Cannot See the Device on the Network](#cannot-see-the-device-on-the-network)
|
||||||
|
- [Connection Drops or Times Out](#connection-drops-or-times-out)
|
||||||
|
- [Upload Fails](#upload-fails)
|
||||||
|
- [Saved Password Not Working](#saved-password-not-working)
|
||||||
|
|
||||||
|
### Cannot See the Device on the Network
|
||||||
|
|
||||||
|
**Problem:** Browser shows "Cannot connect" or "Site can't be reached"
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. Verify both devices are on the **same WiFi network**
|
||||||
|
- Check your computer/phone WiFi settings
|
||||||
|
- Confirm the CrossPoint Reader shows "Connected" status
|
||||||
|
2. Double-check the IP address
|
||||||
|
- Make sure you typed it correctly
|
||||||
|
- Include `http://` at the beginning
|
||||||
|
3. Try disabling VPN if you're using one
|
||||||
|
4. Some networks have "client isolation" enabled - check with your network administrator
|
||||||
|
|
||||||
|
### Connection Drops or Times Out
|
||||||
|
|
||||||
|
**Problem:** WiFi connection is unstable
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. Move closer to the WiFi router
|
||||||
|
2. Check signal strength on the device (should be at least `||` or better)
|
||||||
|
3. Avoid interference from other devices
|
||||||
|
4. Try a different WiFi network if available
|
||||||
|
|
||||||
|
### Upload Fails
|
||||||
|
|
||||||
|
**Problem:** File upload doesn't complete or shows an error
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. Ensure the file is a valid `.epub` file
|
||||||
|
2. Check that the SD card has enough free space
|
||||||
|
3. Try uploading a smaller file first to test
|
||||||
|
4. Refresh the browser page and try again
|
||||||
|
|
||||||
|
### Saved Password Not Working
|
||||||
|
|
||||||
|
**Problem:** Device fails to connect with saved credentials
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. When connection fails, you'll be prompted to "Forget Network"
|
||||||
|
2. Select **Yes** to remove the saved password
|
||||||
|
3. Reconnect and enter the password again
|
||||||
|
4. Choose to save the new password
|
||||||
331
docs/webserver-endpoints.md
Normal file
331
docs/webserver-endpoints.md
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
# Webserver Endpoints
|
||||||
|
|
||||||
|
This document describes all HTTP and WebSocket endpoints available on the CrossPoint Reader webserver.
|
||||||
|
|
||||||
|
- [Webserver Endpoints](#webserver-endpoints)
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [HTTP Endpoints](#http-endpoints)
|
||||||
|
- [GET `/` - Home Page](#get----home-page)
|
||||||
|
- [GET `/files` - File Browser Page](#get-files---file-browser-page)
|
||||||
|
- [GET `/api/status` - Device Status](#get-apistatus---device-status)
|
||||||
|
- [GET `/api/files` - List Files](#get-apifiles---list-files)
|
||||||
|
- [POST `/upload` - Upload File](#post-upload---upload-file)
|
||||||
|
- [POST `/mkdir` - Create Folder](#post-mkdir---create-folder)
|
||||||
|
- [POST `/delete` - Delete File or Folder](#post-delete---delete-file-or-folder)
|
||||||
|
- [WebSocket Endpoint](#websocket-endpoint)
|
||||||
|
- [Port 81 - Fast Binary Upload](#port-81---fast-binary-upload)
|
||||||
|
- [Network Modes](#network-modes)
|
||||||
|
- [Station Mode (STA)](#station-mode-sta)
|
||||||
|
- [Access Point Mode (AP)](#access-point-mode-ap)
|
||||||
|
- [Notes](#notes)
|
||||||
|
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The CrossPoint Reader exposes a webserver for file management and device monitoring:
|
||||||
|
|
||||||
|
- **HTTP Server**: Port 80
|
||||||
|
- **WebSocket Server**: Port 81 (for fast binary uploads)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## HTTP Endpoints
|
||||||
|
|
||||||
|
### GET `/` - Home Page
|
||||||
|
|
||||||
|
Serves the home page HTML interface.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
curl http://crosspoint.local/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:** HTML page (200 OK)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### GET `/files` - File Browser Page
|
||||||
|
|
||||||
|
Serves the file browser HTML interface.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
curl http://crosspoint.local/files
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:** HTML page (200 OK)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### GET `/api/status` - Device Status
|
||||||
|
|
||||||
|
Returns JSON with device status information.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
curl http://crosspoint.local/api/status
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"ip": "192.168.1.100",
|
||||||
|
"mode": "STA",
|
||||||
|
"rssi": -45,
|
||||||
|
"freeHeap": 123456,
|
||||||
|
"uptime": 3600
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
| ---------- | ------ | --------------------------------------------------------- |
|
||||||
|
| `version` | string | CrossPoint firmware version |
|
||||||
|
| `ip` | string | Device IP address |
|
||||||
|
| `mode` | string | `"STA"` (connected to WiFi) or `"AP"` (access point mode) |
|
||||||
|
| `rssi` | number | WiFi signal strength in dBm (0 in AP mode) |
|
||||||
|
| `freeHeap` | number | Free heap memory in bytes |
|
||||||
|
| `uptime` | number | Seconds since device boot |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### GET `/api/files` - List Files
|
||||||
|
|
||||||
|
Returns a JSON array of files and folders in the specified directory.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
# List root directory
|
||||||
|
curl http://crosspoint.local/api/files
|
||||||
|
|
||||||
|
# List specific directory
|
||||||
|
curl "http://crosspoint.local/api/files?path=/Books"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
| --------- | -------- | ------- | ---------------------- |
|
||||||
|
| `path` | No | `/` | Directory path to list |
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"name": "MyBook.epub", "size": 1234567, "isDirectory": false, "isEpub": true},
|
||||||
|
{"name": "Notes", "size": 0, "isDirectory": true, "isEpub": false},
|
||||||
|
{"name": "document.pdf", "size": 54321, "isDirectory": false, "isEpub": false}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
| ------------- | ------- | ---------------------------------------- |
|
||||||
|
| `name` | string | File or folder name |
|
||||||
|
| `size` | number | Size in bytes (0 for directories) |
|
||||||
|
| `isDirectory` | boolean | `true` if the item is a folder |
|
||||||
|
| `isEpub` | boolean | `true` if the file has `.epub` extension |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Hidden files (starting with `.`) are automatically filtered out
|
||||||
|
- System folders (`System Volume Information`, `XTCache`) are hidden
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### POST `/upload` - Upload File
|
||||||
|
|
||||||
|
Uploads a file to the SD card via multipart form data.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
# Upload to root directory
|
||||||
|
curl -X POST -F "file=@mybook.epub" http://crosspoint.local/upload
|
||||||
|
|
||||||
|
# Upload to specific directory
|
||||||
|
curl -X POST -F "file=@mybook.epub" "http://crosspoint.local/upload?path=/Books"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
| --------- | -------- | ------- | ------------------------------- |
|
||||||
|
| `path` | No | `/` | Target directory for the upload |
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```
|
||||||
|
File uploaded successfully: mybook.epub
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Responses:**
|
||||||
|
|
||||||
|
| Status | Body | Cause |
|
||||||
|
| ------ | ----------------------------------------------- | --------------------------- |
|
||||||
|
| 400 | `Failed to create file on SD card` | Cannot create file |
|
||||||
|
| 400 | `Failed to write to SD card - disk may be full` | Write error during upload |
|
||||||
|
| 400 | `Failed to write final data to SD card` | Error flushing final buffer |
|
||||||
|
| 400 | `Upload aborted` | Client aborted the upload |
|
||||||
|
| 400 | `Unknown error during upload` | Unspecified error |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Existing files with the same name will be overwritten
|
||||||
|
- Uses a 4KB buffer for efficient SD card writes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### POST `/mkdir` - Create Folder
|
||||||
|
|
||||||
|
Creates a new folder on the SD card.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
curl -X POST -d "name=NewFolder&path=/" http://crosspoint.local/mkdir
|
||||||
|
```
|
||||||
|
|
||||||
|
**Form Parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
| --------- | -------- | ------- | ---------------------------- |
|
||||||
|
| `name` | Yes | - | Name of the folder to create |
|
||||||
|
| `path` | No | `/` | Parent directory path |
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```
|
||||||
|
Folder created: NewFolder
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Responses:**
|
||||||
|
|
||||||
|
| Status | Body | Cause |
|
||||||
|
| ------ | ----------------------------- | ----------------------------- |
|
||||||
|
| 400 | `Missing folder name` | `name` parameter not provided |
|
||||||
|
| 400 | `Folder name cannot be empty` | Empty folder name |
|
||||||
|
| 400 | `Folder already exists` | Folder with same name exists |
|
||||||
|
| 500 | `Failed to create folder` | SD card error |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### POST `/delete` - Delete File or Folder
|
||||||
|
|
||||||
|
Deletes a file or folder from the SD card.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
# Delete a file
|
||||||
|
curl -X POST -d "path=/Books/mybook.epub&type=file" http://crosspoint.local/delete
|
||||||
|
|
||||||
|
# Delete an empty folder
|
||||||
|
curl -X POST -d "path=/OldFolder&type=folder" http://crosspoint.local/delete
|
||||||
|
```
|
||||||
|
|
||||||
|
**Form Parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
| --------- | -------- | ------- | -------------------------------- |
|
||||||
|
| `path` | Yes | - | Path to the item to delete |
|
||||||
|
| `type` | No | `file` | Type of item: `file` or `folder` |
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```
|
||||||
|
Deleted successfully
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Responses:**
|
||||||
|
|
||||||
|
| Status | Body | Cause |
|
||||||
|
| ------ | --------------------------------------------- | ----------------------------- |
|
||||||
|
| 400 | `Missing path` | `path` parameter not provided |
|
||||||
|
| 400 | `Cannot delete root directory` | Attempted to delete `/` |
|
||||||
|
| 400 | `Folder is not empty. Delete contents first.` | Non-empty folder |
|
||||||
|
| 403 | `Cannot delete system files` | Hidden file (starts with `.`) |
|
||||||
|
| 403 | `Cannot delete protected items` | Protected system folder |
|
||||||
|
| 404 | `Item not found` | Path does not exist |
|
||||||
|
| 500 | `Failed to delete item` | SD card error |
|
||||||
|
|
||||||
|
**Protected Items:**
|
||||||
|
- Files/folders starting with `.`
|
||||||
|
- `System Volume Information`
|
||||||
|
- `XTCache`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WebSocket Endpoint
|
||||||
|
|
||||||
|
### Port 81 - Fast Binary Upload
|
||||||
|
|
||||||
|
A WebSocket endpoint for high-speed binary file uploads. More efficient than HTTP multipart for large files.
|
||||||
|
|
||||||
|
**Connection:**
|
||||||
|
```
|
||||||
|
ws://crosspoint.local:81/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Protocol:**
|
||||||
|
|
||||||
|
1. **Client** sends TEXT message: `START:<filename>:<size>:<path>`
|
||||||
|
2. **Server** responds with TEXT: `READY`
|
||||||
|
3. **Client** sends BINARY messages with file data chunks
|
||||||
|
4. **Server** sends TEXT progress updates: `PROGRESS:<received>:<total>`
|
||||||
|
5. **Server** sends TEXT when complete: `DONE` or `ERROR:<message>`
|
||||||
|
|
||||||
|
**Example Session:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Client -> "START:mybook.epub:1234567:/Books"
|
||||||
|
Server -> "READY"
|
||||||
|
Client -> [binary chunk 1]
|
||||||
|
Client -> [binary chunk 2]
|
||||||
|
Server -> "PROGRESS:65536:1234567"
|
||||||
|
Client -> [binary chunk 3]
|
||||||
|
...
|
||||||
|
Server -> "PROGRESS:1234567:1234567"
|
||||||
|
Server -> "DONE"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Messages:**
|
||||||
|
|
||||||
|
| Message | Cause |
|
||||||
|
| --------------------------------- | ---------------------------------- |
|
||||||
|
| `ERROR:Failed to create file` | Cannot create file on SD card |
|
||||||
|
| `ERROR:Invalid START format` | Malformed START message |
|
||||||
|
| `ERROR:No upload in progress` | Binary data received without START |
|
||||||
|
| `ERROR:Write failed - disk full?` | SD card write error |
|
||||||
|
|
||||||
|
**Example with `websocat`:**
|
||||||
|
```bash
|
||||||
|
# Interactive session
|
||||||
|
websocat ws://crosspoint.local:81
|
||||||
|
|
||||||
|
# Then type:
|
||||||
|
START:mybook.epub:1234567:/Books
|
||||||
|
# Wait for READY, then send binary data
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Progress updates are sent every 64KB or at completion
|
||||||
|
- Disconnection during upload will delete the incomplete file
|
||||||
|
- Existing files with the same name will be overwritten
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Network Modes
|
||||||
|
|
||||||
|
The device can operate in two network modes:
|
||||||
|
|
||||||
|
### Station Mode (STA)
|
||||||
|
- Device connects to an existing WiFi network
|
||||||
|
- IP address assigned by router/DHCP
|
||||||
|
- `mode` field in `/api/status` returns `"STA"`
|
||||||
|
- `rssi` field shows signal strength
|
||||||
|
|
||||||
|
### Access Point Mode (AP)
|
||||||
|
- Device creates its own WiFi hotspot
|
||||||
|
- Default IP is typically `192.168.4.1`
|
||||||
|
- `mode` field in `/api/status` returns `"AP"`
|
||||||
|
- `rssi` field returns `0`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- These examples use `crosspoint.local`. If your network does not support mDNS or the address does not resolve, replace it with the specific **IP Address** displayed on your device screen (e.g., `http://192.168.1.102/`).
|
||||||
|
- All paths on the SD card start with `/`
|
||||||
|
- Trailing slashes are automatically stripped (except for root `/`)
|
||||||
|
- The webserver uses chunked transfer encoding for file listings
|
||||||
@ -172,89 +172,7 @@ This is useful for organizing your ebooks by genre, author, or series.
|
|||||||
|
|
||||||
## Command Line File Management
|
## Command Line File Management
|
||||||
|
|
||||||
For power users, you can manage files directly from your terminal using `curl` while the device is in File Upload mode.
|
For power users, you can manage files directly from your terminal using `curl` while the device is in File Upload mode a detailed documentation can be found [here](./webserver-endpoints.md).
|
||||||
|
|
||||||
### Uploading a File
|
|
||||||
To upload a file to the root directory, use the following command:
|
|
||||||
```bash
|
|
||||||
curl -F "file=@book.epub" "http://crosspoint.local/upload?path=/"
|
|
||||||
```
|
|
||||||
|
|
||||||
* **`-F "file=@filename"`**: Points to the local file on your computer.
|
|
||||||
* **`path=/`**: The destination folder on the device SD card.
|
|
||||||
|
|
||||||
### Deleting a File
|
|
||||||
|
|
||||||
To delete a specific file, provide the full path on the SD card:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -F "path=/folder/file.epub" "http://crosspoint.local/delete"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Advanced Flags
|
|
||||||
|
|
||||||
For more reliable transfers of large EPUB files, consider adding these flags:
|
|
||||||
|
|
||||||
* `-#`: Shows a simple progress bar.
|
|
||||||
* `--connect-timeout 30`: Limits how long curl waits to establish a connection (in seconds).
|
|
||||||
* `--max-time 300`: Sets a maximum duration for the entire transfer (5 minutes).
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> These examples use `crosspoint.local`. If your network does not support mDNS or the address does not resolve, replace it with the specific **IP Address** displayed on your device screen (e.g., `http://192.168.1.102/`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Cannot See the Device on the Network
|
|
||||||
|
|
||||||
**Problem:** Browser shows "Cannot connect" or "Site can't be reached"
|
|
||||||
|
|
||||||
**Solutions:**
|
|
||||||
|
|
||||||
1. Verify both devices are on the **same WiFi network**
|
|
||||||
- Check your computer/phone WiFi settings
|
|
||||||
- Confirm the CrossPoint Reader shows "Connected" status
|
|
||||||
2. Double-check the IP address
|
|
||||||
- Make sure you typed it correctly
|
|
||||||
- Include `http://` at the beginning
|
|
||||||
3. Try disabling VPN if you're using one
|
|
||||||
4. Some networks have "client isolation" enabled - check with your network administrator
|
|
||||||
|
|
||||||
### Connection Drops or Times Out
|
|
||||||
|
|
||||||
**Problem:** WiFi connection is unstable
|
|
||||||
|
|
||||||
**Solutions:**
|
|
||||||
|
|
||||||
1. Move closer to the WiFi router
|
|
||||||
2. Check signal strength on the device (should be at least `||` or better)
|
|
||||||
3. Avoid interference from other devices
|
|
||||||
4. Try a different WiFi network if available
|
|
||||||
|
|
||||||
### Upload Fails
|
|
||||||
|
|
||||||
**Problem:** File upload doesn't complete or shows an error
|
|
||||||
|
|
||||||
**Solutions:**
|
|
||||||
|
|
||||||
1. Ensure the file is a valid `.epub` file
|
|
||||||
2. Check that the SD card has enough free space
|
|
||||||
3. Try uploading a smaller file first to test
|
|
||||||
4. Refresh the browser page and try again
|
|
||||||
|
|
||||||
### Saved Password Not Working
|
|
||||||
|
|
||||||
**Problem:** Device fails to connect with saved credentials
|
|
||||||
|
|
||||||
**Solutions:**
|
|
||||||
|
|
||||||
1. When connection fails, you'll be prompted to "Forget Network"
|
|
||||||
2. Select **Yes** to remove the saved password
|
|
||||||
3. Reconnect and enter the password again
|
|
||||||
4. Choose to save the new password
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security Notes
|
## Security Notes
|
||||||
|
|
||||||
@ -303,4 +221,5 @@ Your uploaded files will be immediately available in the file browser!
|
|||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
- [User Guide](../USER_GUIDE.md) - General device operation
|
- [User Guide](../USER_GUIDE.md) - General device operation
|
||||||
|
- [Troubleshooting](./troubleshooting.md) - Troubleshooting
|
||||||
- [README](../README.md) - Project overview and features
|
- [README](../README.md) - Project overview and features
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_12_bold
|
* name: bookerly_12_bold
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_12_bold 12 ../builtinFonts/source/Bookerly/Bookerly-Bold.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_12_bolditalic
|
* name: bookerly_12_bolditalic
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_12_bolditalic 12 ../builtinFonts/source/Bookerly/Bookerly-BoldItalic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_12_italic
|
* name: bookerly_12_italic
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_12_italic 12 ../builtinFonts/source/Bookerly/Bookerly-Italic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_12_regular
|
* name: bookerly_12_regular
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_12_regular 12 ../builtinFonts/source/Bookerly/Bookerly-Regular.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_14_bold
|
* name: bookerly_14_bold
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_14_bold 14 ../builtinFonts/source/Bookerly/Bookerly-Bold.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_14_bolditalic
|
* name: bookerly_14_bolditalic
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_14_bolditalic 14 ../builtinFonts/source/Bookerly/Bookerly-BoldItalic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_14_italic
|
* name: bookerly_14_italic
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_14_italic 14 ../builtinFonts/source/Bookerly/Bookerly-Italic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_14_regular
|
* name: bookerly_14_regular
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_14_regular 14 ../builtinFonts/source/Bookerly/Bookerly-Regular.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_16_bold
|
* name: bookerly_16_bold
|
||||||
* size: 16
|
* size: 16
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_16_bold 16 ../builtinFonts/source/Bookerly/Bookerly-Bold.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_16_bolditalic
|
* name: bookerly_16_bolditalic
|
||||||
* size: 16
|
* size: 16
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_16_bolditalic 16 ../builtinFonts/source/Bookerly/Bookerly-BoldItalic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_16_italic
|
* name: bookerly_16_italic
|
||||||
* size: 16
|
* size: 16
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_16_italic 16 ../builtinFonts/source/Bookerly/Bookerly-Italic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_16_regular
|
* name: bookerly_16_regular
|
||||||
* size: 16
|
* size: 16
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_16_regular 16 ../builtinFonts/source/Bookerly/Bookerly-Regular.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_18_bold
|
* name: bookerly_18_bold
|
||||||
* size: 18
|
* size: 18
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_18_bold 18 ../builtinFonts/source/Bookerly/Bookerly-Bold.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_18_bolditalic
|
* name: bookerly_18_bolditalic
|
||||||
* size: 18
|
* size: 18
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_18_bolditalic 18 ../builtinFonts/source/Bookerly/Bookerly-BoldItalic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_18_italic
|
* name: bookerly_18_italic
|
||||||
* size: 18
|
* size: 18
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_18_italic 18 ../builtinFonts/source/Bookerly/Bookerly-Italic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: bookerly_18_regular
|
* name: bookerly_18_regular
|
||||||
* size: 18
|
* size: 18
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py bookerly_18_regular 18 ../builtinFonts/source/Bookerly/Bookerly-Regular.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_12_bold
|
* name: notosans_12_bold
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_12_bold 12 ../builtinFonts/source/NotoSans/NotoSans-Bold.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_12_bolditalic
|
* name: notosans_12_bolditalic
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_12_bolditalic 12 ../builtinFonts/source/NotoSans/NotoSans-BoldItalic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_12_italic
|
* name: notosans_12_italic
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_12_italic 12 ../builtinFonts/source/NotoSans/NotoSans-Italic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_12_regular
|
* name: notosans_12_regular
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_12_regular 12 ../builtinFonts/source/NotoSans/NotoSans-Regular.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_14_bold
|
* name: notosans_14_bold
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_14_bold 14 ../builtinFonts/source/NotoSans/NotoSans-Bold.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_14_bolditalic
|
* name: notosans_14_bolditalic
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_14_bolditalic 14 ../builtinFonts/source/NotoSans/NotoSans-BoldItalic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_14_italic
|
* name: notosans_14_italic
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_14_italic 14 ../builtinFonts/source/NotoSans/NotoSans-Italic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_14_regular
|
* name: notosans_14_regular
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_14_regular 14 ../builtinFonts/source/NotoSans/NotoSans-Regular.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_16_bold
|
* name: notosans_16_bold
|
||||||
* size: 16
|
* size: 16
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_16_bold 16 ../builtinFonts/source/NotoSans/NotoSans-Bold.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_16_bolditalic
|
* name: notosans_16_bolditalic
|
||||||
* size: 16
|
* size: 16
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_16_bolditalic 16 ../builtinFonts/source/NotoSans/NotoSans-BoldItalic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_16_italic
|
* name: notosans_16_italic
|
||||||
* size: 16
|
* size: 16
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_16_italic 16 ../builtinFonts/source/NotoSans/NotoSans-Italic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_16_regular
|
* name: notosans_16_regular
|
||||||
* size: 16
|
* size: 16
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_16_regular 16 ../builtinFonts/source/NotoSans/NotoSans-Regular.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_18_bold
|
* name: notosans_18_bold
|
||||||
* size: 18
|
* size: 18
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_18_bold 18 ../builtinFonts/source/NotoSans/NotoSans-Bold.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_18_bolditalic
|
* name: notosans_18_bolditalic
|
||||||
* size: 18
|
* size: 18
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_18_bolditalic 18 ../builtinFonts/source/NotoSans/NotoSans-BoldItalic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_18_italic
|
* name: notosans_18_italic
|
||||||
* size: 18
|
* size: 18
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_18_italic 18 ../builtinFonts/source/NotoSans/NotoSans-Italic.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_18_regular
|
* name: notosans_18_regular
|
||||||
* size: 18
|
* size: 18
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py notosans_18_regular 18 ../builtinFonts/source/NotoSans/NotoSans-Regular.ttf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: notosans_8_regular
|
* name: notosans_8_regular
|
||||||
* size: 8
|
* size: 8
|
||||||
* mode: 1-bit
|
* mode: 1-bit
|
||||||
|
* Command used: fontconvert.py notosans_8_regular 8 ../builtinFonts/source/NotoSans/NotoSans-Regular.ttf
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_10_bold
|
* name: opendyslexic_10_bold
|
||||||
* size: 10
|
* size: 10
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_10_bold 10 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Bold.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_10_bolditalic
|
* name: opendyslexic_10_bolditalic
|
||||||
* size: 10
|
* size: 10
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_10_bolditalic 10 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-BoldItalic.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_10_italic
|
* name: opendyslexic_10_italic
|
||||||
* size: 10
|
* size: 10
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_10_italic 10 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Italic.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_10_regular
|
* name: opendyslexic_10_regular
|
||||||
* size: 10
|
* size: 10
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_10_regular 10 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Regular.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_12_bold
|
* name: opendyslexic_12_bold
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_12_bold 12 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Bold.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_12_bolditalic
|
* name: opendyslexic_12_bolditalic
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_12_bolditalic 12 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-BoldItalic.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_12_italic
|
* name: opendyslexic_12_italic
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_12_italic 12 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Italic.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_12_regular
|
* name: opendyslexic_12_regular
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_12_regular 12 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Regular.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_14_bold
|
* name: opendyslexic_14_bold
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_14_bold 14 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Bold.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_14_bolditalic
|
* name: opendyslexic_14_bolditalic
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_14_bolditalic 14 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-BoldItalic.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_14_italic
|
* name: opendyslexic_14_italic
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_14_italic 14 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Italic.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_14_regular
|
* name: opendyslexic_14_regular
|
||||||
* size: 14
|
* size: 14
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_14_regular 14 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Regular.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_8_bold
|
* name: opendyslexic_8_bold
|
||||||
* size: 8
|
* size: 8
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_8_bold 8 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Bold.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_8_bolditalic
|
* name: opendyslexic_8_bolditalic
|
||||||
* size: 8
|
* size: 8
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_8_bolditalic 8 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-BoldItalic.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_8_italic
|
* name: opendyslexic_8_italic
|
||||||
* size: 8
|
* size: 8
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_8_italic 8 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Italic.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: opendyslexic_8_regular
|
* name: opendyslexic_8_regular
|
||||||
* size: 8
|
* size: 8
|
||||||
* mode: 2-bit
|
* mode: 2-bit
|
||||||
|
* Command used: fontconvert.py opendyslexic_8_regular 8 ../builtinFonts/source/OpenDyslexic/OpenDyslexic-Regular.otf --2bit
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: ubuntu_10_bold
|
* name: ubuntu_10_bold
|
||||||
* size: 10
|
* size: 10
|
||||||
* mode: 1-bit
|
* mode: 1-bit
|
||||||
|
* Command used: fontconvert.py ubuntu_10_bold 10 ../builtinFonts/source/Ubuntu/Ubuntu-Bold.ttf
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: ubuntu_10_regular
|
* name: ubuntu_10_regular
|
||||||
* size: 10
|
* size: 10
|
||||||
* mode: 1-bit
|
* mode: 1-bit
|
||||||
|
* Command used: fontconvert.py ubuntu_10_regular 10 ../builtinFonts/source/Ubuntu/Ubuntu-Regular.ttf
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: ubuntu_12_bold
|
* name: ubuntu_12_bold
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 1-bit
|
* mode: 1-bit
|
||||||
|
* Command used: fontconvert.py ubuntu_12_bold 12 ../builtinFonts/source/Ubuntu/Ubuntu-Bold.ttf
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* name: ubuntu_12_regular
|
* name: ubuntu_12_regular
|
||||||
* size: 12
|
* size: 12
|
||||||
* mode: 1-bit
|
* mode: 1-bit
|
||||||
|
* Command used: fontconvert.py ubuntu_12_regular 12 ../builtinFonts/source/Ubuntu/Ubuntu-Regular.ttf
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFontData.h"
|
#include "EpdFontData.h"
|
||||||
|
|||||||
@ -270,9 +270,17 @@ for index, glyph in enumerate(all_glyphs):
|
|||||||
glyph_data.extend([b for b in packed])
|
glyph_data.extend([b for b in packed])
|
||||||
glyph_props.append(props)
|
glyph_props.append(props)
|
||||||
|
|
||||||
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n * mode: {'2-bit' if is2Bit else '1-bit'}\n */")
|
print(f"""/**
|
||||||
print("#pragma once")
|
* generated by fontconvert.py
|
||||||
print("#include \"EpdFontData.h\"\n")
|
* name: {font_name}
|
||||||
|
* size: {size}
|
||||||
|
* mode: {'2-bit' if is2Bit else '1-bit'}
|
||||||
|
* Command used: {' '.join(sys.argv)}
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "EpdFontData.h"
|
||||||
|
""")
|
||||||
|
|
||||||
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
|
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
|
||||||
for c in chunks(glyph_data, 16):
|
for c in chunks(glyph_data, 16):
|
||||||
print (" " + " ".join(f"0x{b:02X}," for b in c))
|
print (" " + " ".join(f"0x{b:02X}," for b in c))
|
||||||
|
|||||||
@ -226,6 +226,8 @@ bool Epub::load(const bool buildIfMissing) {
|
|||||||
Serial.printf("[%lu] [EBP] Cache not found, building spine/TOC cache\n", millis());
|
Serial.printf("[%lu] [EBP] Cache not found, building spine/TOC cache\n", millis());
|
||||||
setupCacheDir();
|
setupCacheDir();
|
||||||
|
|
||||||
|
const uint32_t indexingStart = millis();
|
||||||
|
|
||||||
// Begin building cache - stream entries to disk immediately
|
// Begin building cache - stream entries to disk immediately
|
||||||
if (!bookMetadataCache->beginWrite()) {
|
if (!bookMetadataCache->beginWrite()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not begin writing cache\n", millis());
|
Serial.printf("[%lu] [EBP] Could not begin writing cache\n", millis());
|
||||||
@ -233,6 +235,7 @@ bool Epub::load(const bool buildIfMissing) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OPF Pass
|
// OPF Pass
|
||||||
|
const uint32_t opfStart = millis();
|
||||||
BookMetadataCache::BookMetadata bookMetadata;
|
BookMetadataCache::BookMetadata bookMetadata;
|
||||||
if (!bookMetadataCache->beginContentOpfPass()) {
|
if (!bookMetadataCache->beginContentOpfPass()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not begin writing content.opf pass\n", millis());
|
Serial.printf("[%lu] [EBP] Could not begin writing content.opf pass\n", millis());
|
||||||
@ -246,8 +249,10 @@ bool Epub::load(const bool buildIfMissing) {
|
|||||||
Serial.printf("[%lu] [EBP] Could not end writing content.opf pass\n", millis());
|
Serial.printf("[%lu] [EBP] Could not end writing content.opf pass\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Serial.printf("[%lu] [EBP] OPF pass completed in %lu ms\n", millis(), millis() - opfStart);
|
||||||
|
|
||||||
// TOC Pass - try EPUB 3 nav first, fall back to NCX
|
// TOC Pass - try EPUB 3 nav first, fall back to NCX
|
||||||
|
const uint32_t tocStart = millis();
|
||||||
if (!bookMetadataCache->beginTocPass()) {
|
if (!bookMetadataCache->beginTocPass()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not begin writing toc pass\n", millis());
|
Serial.printf("[%lu] [EBP] Could not begin writing toc pass\n", millis());
|
||||||
return false;
|
return false;
|
||||||
@ -276,6 +281,7 @@ bool Epub::load(const bool buildIfMissing) {
|
|||||||
Serial.printf("[%lu] [EBP] Could not end writing toc pass\n", millis());
|
Serial.printf("[%lu] [EBP] Could not end writing toc pass\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Serial.printf("[%lu] [EBP] TOC pass completed in %lu ms\n", millis(), millis() - tocStart);
|
||||||
|
|
||||||
// Close the cache files
|
// Close the cache files
|
||||||
if (!bookMetadataCache->endWrite()) {
|
if (!bookMetadataCache->endWrite()) {
|
||||||
@ -284,10 +290,13 @@ bool Epub::load(const bool buildIfMissing) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build final book.bin
|
// Build final book.bin
|
||||||
|
const uint32_t buildStart = millis();
|
||||||
if (!bookMetadataCache->buildBookBin(filepath, bookMetadata)) {
|
if (!bookMetadataCache->buildBookBin(filepath, bookMetadata)) {
|
||||||
Serial.printf("[%lu] [EBP] Could not update mappings and sizes\n", millis());
|
Serial.printf("[%lu] [EBP] Could not update mappings and sizes\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Serial.printf("[%lu] [EBP] buildBookBin completed in %lu ms\n", millis(), millis() - buildStart);
|
||||||
|
Serial.printf("[%lu] [EBP] Total indexing completed in %lu ms\n", millis(), millis() - indexingStart);
|
||||||
|
|
||||||
if (!bookMetadataCache->cleanupTmpFiles()) {
|
if (!bookMetadataCache->cleanupTmpFiles()) {
|
||||||
Serial.printf("[%lu] [EBP] Could not cleanup tmp files - ignoring\n", millis());
|
Serial.printf("[%lu] [EBP] Could not cleanup tmp files - ignoring\n", millis());
|
||||||
@ -359,7 +368,7 @@ const std::string& Epub::getLanguage() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string Epub::getCoverBmpPath(bool cropped) const {
|
std::string Epub::getCoverBmpPath(bool cropped) const {
|
||||||
const auto coverFileName = "cover" + cropped ? "_crop" : "";
|
const auto coverFileName = std::string("cover") + (cropped ? "_crop" : "");
|
||||||
return cachePath + "/" + coverFileName + ".bmp";
|
return cachePath + "/" + coverFileName + ".bmp";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,7 +391,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
|||||||
|
|
||||||
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
||||||
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
||||||
Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image\n", millis());
|
Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image (%s mode)\n", millis(), cropped ? "cropped" : "fit");
|
||||||
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
||||||
|
|
||||||
FsFile coverJpg;
|
FsFile coverJpg;
|
||||||
@ -401,7 +410,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
|||||||
coverJpg.close();
|
coverJpg.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp);
|
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp, cropped);
|
||||||
coverJpg.close();
|
coverJpg.close();
|
||||||
coverBmp.close();
|
coverBmp.close();
|
||||||
SdMan.remove(coverJpgTempPath.c_str());
|
SdMan.remove(coverJpgTempPath.c_str());
|
||||||
|
|||||||
@ -40,7 +40,6 @@ bool BookMetadataCache::endContentOpfPass() {
|
|||||||
bool BookMetadataCache::beginTocPass() {
|
bool BookMetadataCache::beginTocPass() {
|
||||||
Serial.printf("[%lu] [BMC] Beginning toc pass\n", millis());
|
Serial.printf("[%lu] [BMC] Beginning toc pass\n", millis());
|
||||||
|
|
||||||
// Open spine file for reading
|
|
||||||
if (!SdMan.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) {
|
if (!SdMan.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -48,12 +47,41 @@ bool BookMetadataCache::beginTocPass() {
|
|||||||
spineFile.close();
|
spineFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (spineCount >= LARGE_SPINE_THRESHOLD) {
|
||||||
|
spineHrefIndex.clear();
|
||||||
|
spineHrefIndex.reserve(spineCount);
|
||||||
|
spineFile.seek(0);
|
||||||
|
for (int i = 0; i < spineCount; i++) {
|
||||||
|
auto entry = readSpineEntry(spineFile);
|
||||||
|
SpineHrefIndexEntry idx;
|
||||||
|
idx.hrefHash = fnvHash64(entry.href);
|
||||||
|
idx.hrefLen = static_cast<uint16_t>(entry.href.size());
|
||||||
|
idx.spineIndex = static_cast<int16_t>(i);
|
||||||
|
spineHrefIndex.push_back(idx);
|
||||||
|
}
|
||||||
|
std::sort(spineHrefIndex.begin(), spineHrefIndex.end(),
|
||||||
|
[](const SpineHrefIndexEntry& a, const SpineHrefIndexEntry& b) {
|
||||||
|
return a.hrefHash < b.hrefHash || (a.hrefHash == b.hrefHash && a.hrefLen < b.hrefLen);
|
||||||
|
});
|
||||||
|
spineFile.seek(0);
|
||||||
|
useSpineHrefIndex = true;
|
||||||
|
Serial.printf("[%lu] [BMC] Using fast index for %d spine items\n", millis(), spineCount);
|
||||||
|
} else {
|
||||||
|
useSpineHrefIndex = false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BookMetadataCache::endTocPass() {
|
bool BookMetadataCache::endTocPass() {
|
||||||
tocFile.close();
|
tocFile.close();
|
||||||
spineFile.close();
|
spineFile.close();
|
||||||
|
|
||||||
|
spineHrefIndex.clear();
|
||||||
|
spineHrefIndex.shrink_to_fit();
|
||||||
|
useSpineHrefIndex = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +152,18 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
// LUTs complete
|
// LUTs complete
|
||||||
// Loop through spines from spine file matching up TOC indexes, calculating cumulative size and writing to book.bin
|
// Loop through spines from spine file matching up TOC indexes, calculating cumulative size and writing to book.bin
|
||||||
|
|
||||||
|
// Build spineIndex->tocIndex mapping in one pass (O(n) instead of O(n*m))
|
||||||
|
std::vector<int16_t> spineToTocIndex(spineCount, -1);
|
||||||
|
tocFile.seek(0);
|
||||||
|
for (int j = 0; j < tocCount; j++) {
|
||||||
|
auto tocEntry = readTocEntry(tocFile);
|
||||||
|
if (tocEntry.spineIndex >= 0 && tocEntry.spineIndex < spineCount) {
|
||||||
|
if (spineToTocIndex[tocEntry.spineIndex] == -1) {
|
||||||
|
spineToTocIndex[tocEntry.spineIndex] = static_cast<int16_t>(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ZipFile zip(epubPath);
|
ZipFile zip(epubPath);
|
||||||
// Pre-open zip file to speed up size calculations
|
// Pre-open zip file to speed up size calculations
|
||||||
if (!zip.open()) {
|
if (!zip.open()) {
|
||||||
@ -133,31 +173,56 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
tocFile.close();
|
tocFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// TODO: For large ZIPs loading the all localHeaderOffsets will crash.
|
// NOTE: We intentionally skip calling loadAllFileStatSlims() here.
|
||||||
// However not having them loaded is extremely slow. Need a better solution here.
|
// For large EPUBs (2000+ chapters), pre-loading all ZIP central directory entries
|
||||||
// Perhaps only a cache of spine items or a better way to speedup lookups?
|
// into memory causes OOM crashes on ESP32-C3's limited ~380KB RAM.
|
||||||
if (!zip.loadAllFileStatSlims()) {
|
// Instead, for large books we use a one-pass batch lookup that scans the ZIP
|
||||||
Serial.printf("[%lu] [BMC] Could not load zip local header offsets for size calculations\n", millis());
|
// central directory once and matches against spine targets using hash comparison.
|
||||||
bookFile.close();
|
// This is O(n*log(m)) instead of O(n*m) while avoiding memory exhaustion.
|
||||||
spineFile.close();
|
// See: https://github.com/crosspoint-reader/crosspoint-reader/issues/134
|
||||||
tocFile.close();
|
|
||||||
zip.close();
|
std::vector<uint32_t> spineSizes;
|
||||||
return false;
|
bool useBatchSizes = false;
|
||||||
|
|
||||||
|
if (spineCount >= LARGE_SPINE_THRESHOLD) {
|
||||||
|
Serial.printf("[%lu] [BMC] Using batch size lookup for %d spine items\n", millis(), spineCount);
|
||||||
|
|
||||||
|
std::vector<ZipFile::SizeTarget> targets;
|
||||||
|
targets.reserve(spineCount);
|
||||||
|
|
||||||
|
spineFile.seek(0);
|
||||||
|
for (int i = 0; i < spineCount; i++) {
|
||||||
|
auto entry = readSpineEntry(spineFile);
|
||||||
|
std::string path = FsHelpers::normalisePath(entry.href);
|
||||||
|
|
||||||
|
ZipFile::SizeTarget t;
|
||||||
|
t.hash = ZipFile::fnvHash64(path.c_str(), path.size());
|
||||||
|
t.len = static_cast<uint16_t>(path.size());
|
||||||
|
t.index = static_cast<uint16_t>(i);
|
||||||
|
targets.push_back(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::sort(targets.begin(), targets.end(), [](const ZipFile::SizeTarget& a, const ZipFile::SizeTarget& b) {
|
||||||
|
return a.hash < b.hash || (a.hash == b.hash && a.len < b.len);
|
||||||
|
});
|
||||||
|
|
||||||
|
spineSizes.resize(spineCount, 0);
|
||||||
|
int matched = zip.fillUncompressedSizes(targets, spineSizes);
|
||||||
|
Serial.printf("[%lu] [BMC] Batch lookup matched %d/%d spine items\n", millis(), matched, spineCount);
|
||||||
|
|
||||||
|
targets.clear();
|
||||||
|
targets.shrink_to_fit();
|
||||||
|
|
||||||
|
useBatchSizes = true;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t cumSize = 0;
|
uint32_t cumSize = 0;
|
||||||
spineFile.seek(0);
|
spineFile.seek(0);
|
||||||
int lastSpineTocIndex = -1;
|
int lastSpineTocIndex = -1;
|
||||||
for (int i = 0; i < spineCount; i++) {
|
for (int i = 0; i < spineCount; i++) {
|
||||||
auto spineEntry = readSpineEntry(spineFile);
|
auto spineEntry = readSpineEntry(spineFile);
|
||||||
|
|
||||||
tocFile.seek(0);
|
spineEntry.tocIndex = spineToTocIndex[i];
|
||||||
for (int j = 0; j < tocCount; j++) {
|
|
||||||
auto tocEntry = readTocEntry(tocFile);
|
|
||||||
if (tocEntry.spineIndex == i) {
|
|
||||||
spineEntry.tocIndex = j;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not a huge deal if we don't fine a TOC entry for the spine entry, this is expected behaviour for EPUBs
|
// Not a huge deal if we don't fine a TOC entry for the spine entry, this is expected behaviour for EPUBs
|
||||||
// Logging here is for debugging
|
// Logging here is for debugging
|
||||||
@ -169,15 +234,24 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
|||||||
}
|
}
|
||||||
lastSpineTocIndex = spineEntry.tocIndex;
|
lastSpineTocIndex = spineEntry.tocIndex;
|
||||||
|
|
||||||
// Calculate size for cumulative size
|
|
||||||
size_t itemSize = 0;
|
size_t itemSize = 0;
|
||||||
|
if (useBatchSizes) {
|
||||||
|
itemSize = spineSizes[i];
|
||||||
|
if (itemSize == 0) {
|
||||||
const std::string path = FsHelpers::normalisePath(spineEntry.href);
|
const std::string path = FsHelpers::normalisePath(spineEntry.href);
|
||||||
if (zip.getInflatedFileSize(path.c_str(), &itemSize)) {
|
if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) {
|
||||||
cumSize += itemSize;
|
|
||||||
spineEntry.cumulativeSize = cumSize;
|
|
||||||
} else {
|
|
||||||
Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str());
|
Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const std::string path = FsHelpers::normalisePath(spineEntry.href);
|
||||||
|
if (!zip.getInflatedFileSize(path.c_str(), &itemSize)) {
|
||||||
|
Serial.printf("[%lu] [BMC] Warning: Could not get size for spine item: %s\n", millis(), path.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cumSize += itemSize;
|
||||||
|
spineEntry.cumulativeSize = cumSize;
|
||||||
|
|
||||||
// Write out spine data to book.bin
|
// Write out spine data to book.bin
|
||||||
writeSpineEntry(bookFile, spineEntry);
|
writeSpineEntry(bookFile, spineEntry);
|
||||||
@ -248,21 +322,38 @@ void BookMetadataCache::createTocEntry(const std::string& title, const std::stri
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int spineIndex = -1;
|
int16_t spineIndex = -1;
|
||||||
// find spine index
|
|
||||||
// TODO: This lookup is slow as need to scan through all items each time. We can't hold it all in memory due to size.
|
if (useSpineHrefIndex) {
|
||||||
// But perhaps we can load just the hrefs in a vector/list to do an index lookup?
|
uint64_t targetHash = fnvHash64(href);
|
||||||
|
uint16_t targetLen = static_cast<uint16_t>(href.size());
|
||||||
|
|
||||||
|
auto it =
|
||||||
|
std::lower_bound(spineHrefIndex.begin(), spineHrefIndex.end(), SpineHrefIndexEntry{targetHash, targetLen, 0},
|
||||||
|
[](const SpineHrefIndexEntry& a, const SpineHrefIndexEntry& b) {
|
||||||
|
return a.hrefHash < b.hrefHash || (a.hrefHash == b.hrefHash && a.hrefLen < b.hrefLen);
|
||||||
|
});
|
||||||
|
|
||||||
|
while (it != spineHrefIndex.end() && it->hrefHash == targetHash && it->hrefLen == targetLen) {
|
||||||
|
spineIndex = it->spineIndex;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spineIndex == -1) {
|
||||||
|
Serial.printf("[%lu] [BMC] createTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
spineFile.seek(0);
|
spineFile.seek(0);
|
||||||
for (int i = 0; i < spineCount; i++) {
|
for (int i = 0; i < spineCount; i++) {
|
||||||
auto spineEntry = readSpineEntry(spineFile);
|
auto spineEntry = readSpineEntry(spineFile);
|
||||||
if (spineEntry.href == href) {
|
if (spineEntry.href == href) {
|
||||||
spineIndex = i;
|
spineIndex = static_cast<int16_t>(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spineIndex == -1) {
|
if (spineIndex == -1) {
|
||||||
Serial.printf("[%lu] [BMC] addTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str());
|
Serial.printf("[%lu] [BMC] createTocEntry: Could not find spine item for TOC href %s\n", millis(), href.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TocEntry entry(title, href, anchor, level, spineIndex);
|
const TocEntry entry(title, href, anchor, level, spineIndex);
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class BookMetadataCache {
|
class BookMetadataCache {
|
||||||
public:
|
public:
|
||||||
@ -53,6 +55,27 @@ class BookMetadataCache {
|
|||||||
FsFile spineFile;
|
FsFile spineFile;
|
||||||
FsFile tocFile;
|
FsFile tocFile;
|
||||||
|
|
||||||
|
// Index for fast href→spineIndex lookup (used only for large EPUBs)
|
||||||
|
struct SpineHrefIndexEntry {
|
||||||
|
uint64_t hrefHash; // FNV-1a 64-bit hash
|
||||||
|
uint16_t hrefLen; // length for collision reduction
|
||||||
|
int16_t spineIndex;
|
||||||
|
};
|
||||||
|
std::vector<SpineHrefIndexEntry> spineHrefIndex;
|
||||||
|
bool useSpineHrefIndex = false;
|
||||||
|
|
||||||
|
static constexpr uint16_t LARGE_SPINE_THRESHOLD = 400;
|
||||||
|
|
||||||
|
// FNV-1a 64-bit hash function
|
||||||
|
static uint64_t fnvHash64(const std::string& s) {
|
||||||
|
uint64_t hash = 14695981039346656037ull;
|
||||||
|
for (char c : s) {
|
||||||
|
hash ^= static_cast<uint8_t>(c);
|
||||||
|
hash *= 1099511628211ull;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t writeSpineEntry(FsFile& file, const SpineEntry& entry) const;
|
uint32_t writeSpineEntry(FsFile& file, const SpineEntry& entry) const;
|
||||||
uint32_t writeTocEntry(FsFile& file, const TocEntry& entry) const;
|
uint32_t writeTocEntry(FsFile& file, const TocEntry& entry) const;
|
||||||
SpineEntry readSpineEntry(FsFile& file) const;
|
SpineEntry readSpineEntry(FsFile& file) const;
|
||||||
|
|||||||
@ -125,6 +125,8 @@ bool isExplicitHyphen(const uint32_t cp) {
|
|||||||
case 0xFE58: // small em dash
|
case 0xFE58: // small em dash
|
||||||
case 0xFE63: // small hyphen-minus
|
case 0xFE63: // small hyphen-minus
|
||||||
case 0xFF0D: // fullwidth hyphen-minus
|
case 0xFF0D: // fullwidth hyphen-minus
|
||||||
|
case 0x005F: // Underscore
|
||||||
|
case 0x2026: // Ellipsis
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "HyphenationCommon.h"
|
#include "HyphenationCommon.h"
|
||||||
#include "generated/hyph-de.trie.h"
|
#include "generated/hyph-de.trie.h"
|
||||||
#include "generated/hyph-en.trie.h"
|
#include "generated/hyph-en.trie.h"
|
||||||
|
#include "generated/hyph-es.trie.h"
|
||||||
#include "generated/hyph-fr.trie.h"
|
#include "generated/hyph-fr.trie.h"
|
||||||
#include "generated/hyph-ru.trie.h"
|
#include "generated/hyph-ru.trie.h"
|
||||||
|
|
||||||
@ -16,14 +17,16 @@ LanguageHyphenator englishHyphenator(en_us_patterns, isLatinLetter, toLowerLatin
|
|||||||
LanguageHyphenator frenchHyphenator(fr_patterns, isLatinLetter, toLowerLatin);
|
LanguageHyphenator frenchHyphenator(fr_patterns, isLatinLetter, toLowerLatin);
|
||||||
LanguageHyphenator germanHyphenator(de_patterns, isLatinLetter, toLowerLatin);
|
LanguageHyphenator germanHyphenator(de_patterns, isLatinLetter, toLowerLatin);
|
||||||
LanguageHyphenator russianHyphenator(ru_ru_patterns, isCyrillicLetter, toLowerCyrillic);
|
LanguageHyphenator russianHyphenator(ru_ru_patterns, isCyrillicLetter, toLowerCyrillic);
|
||||||
|
LanguageHyphenator spanishHyphenator(es_patterns, isLatinLetter, toLowerLatin);
|
||||||
|
|
||||||
using EntryArray = std::array<LanguageEntry, 4>;
|
using EntryArray = std::array<LanguageEntry, 5>;
|
||||||
|
|
||||||
const EntryArray& entries() {
|
const EntryArray& entries() {
|
||||||
static const EntryArray kEntries = {{{"english", "en", &englishHyphenator},
|
static const EntryArray kEntries = {{{"english", "en", &englishHyphenator},
|
||||||
{"french", "fr", &frenchHyphenator},
|
{"french", "fr", &frenchHyphenator},
|
||||||
{"german", "de", &germanHyphenator},
|
{"german", "de", &germanHyphenator},
|
||||||
{"russian", "ru", &russianHyphenator}}};
|
{"russian", "ru", &russianHyphenator},
|
||||||
|
{"spanish", "es", &spanishHyphenator}}};
|
||||||
return kEntries;
|
return kEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
734
lib/Epub/Epub/hyphenation/generated/hyph-es.trie.h
Normal file
734
lib/Epub/Epub/hyphenation/generated/hyph-es.trie.h
Normal file
@ -0,0 +1,734 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "../SerializedHyphenationTrie.h"
|
||||||
|
|
||||||
|
// Auto-generated by generate_hyphenation_trie.py. Do not edit manually.
|
||||||
|
alignas(4) constexpr uint8_t es_trie_data[] = {
|
||||||
|
0x00, 0x00, 0x34, 0xFC, 0x01, 0x04, 0x16, 0x02, 0x0E, 0x0C, 0x02, 0x16, 0x02, 0x0D, 0x0C, 0x22, 0x0F, 0x2C, 0x0F,
|
||||||
|
0x22, 0x0D, 0x2C, 0x0D, 0x0B, 0x16, 0x0B, 0x20, 0x15, 0x16, 0x15, 0x0C, 0x02, 0x0C, 0x17, 0x0E, 0x04, 0x2C, 0x05,
|
||||||
|
0x04, 0x0D, 0x04, 0x21, 0x04, 0x18, 0x0D, 0x04, 0x17, 0x04, 0x0D, 0x17, 0x04, 0x0E, 0x0D, 0x04, 0x0D, 0x21, 0x04,
|
||||||
|
0x0D, 0x21, 0x21, 0x0F, 0x0E, 0x0F, 0x0E, 0x0D, 0x0F, 0x0E, 0x17, 0x33, 0x33, 0x0C, 0x33, 0x16, 0x29, 0x29, 0x0C,
|
||||||
|
0x29, 0x16, 0x21, 0x0C, 0x21, 0x0E, 0x34, 0x0D, 0x3E, 0x36, 0x0D, 0x3F, 0x2B, 0x16, 0x0D, 0x3D, 0x3D, 0x0C, 0x3D,
|
||||||
|
0x16, 0x1F, 0x1F, 0x16, 0x2A, 0x2C, 0x0D, 0x0E, 0x0E, 0x21, 0x1F, 0x0C, 0x2A, 0x0D, 0x2A, 0x0B, 0x2A, 0x0B, 0x0C,
|
||||||
|
0x2A, 0x0B, 0x16, 0x37, 0x20, 0x0C, 0x20, 0x16, 0x35, 0x24, 0x47, 0x47, 0x0C, 0x47, 0x16, 0x20, 0x0B, 0x20, 0x0D,
|
||||||
|
0x0C, 0x20, 0x0D, 0x16, 0x20, 0x20, 0x03, 0x17, 0x0E, 0x0D, 0x23, 0x0E, 0x17, 0x17, 0x17, 0x21, 0x16, 0x0D, 0x18,
|
||||||
|
0x48, 0x49, 0x16, 0x0C, 0x0C, 0x16, 0x0C, 0x16, 0x2D, 0x2B, 0x0E, 0x0D, 0x2B, 0x0E, 0x17, 0x17, 0x2B, 0x34, 0x0B,
|
||||||
|
0x34, 0x0B, 0x0C, 0x34, 0x0B, 0x16, 0x21, 0x20, 0x0D, 0x21, 0x0E, 0x17, 0x20, 0x0D, 0x04, 0x0F, 0x19, 0x0C, 0x0D,
|
||||||
|
0x2E, 0x0F, 0x0E, 0x21, 0x17, 0x0E, 0x2D, 0x0E, 0x2B, 0x0E, 0x22, 0x17, 0x17, 0x0E, 0x22, 0x0D, 0x0E, 0x38, 0x19,
|
||||||
|
0x18, 0x03, 0x0C, 0x22, 0x0B, 0x0E, 0x22, 0x0B, 0x18, 0x40, 0x2A, 0x0C, 0x0C, 0x2A, 0x0C, 0x16, 0x18, 0x0D, 0x0C,
|
||||||
|
0x18, 0x0D, 0x0E, 0x2B, 0x21, 0x2B, 0x17, 0x2A, 0x16, 0x02, 0x33, 0x02, 0x33, 0x0C, 0x02, 0x33, 0x16, 0x35, 0x0E,
|
||||||
|
0x04, 0x0C, 0x20, 0x0C, 0x0C, 0x20, 0x0C, 0x16, 0x2B, 0x0E, 0x0E, 0x2B, 0x0E, 0x18, 0x04, 0x0D, 0x0E, 0x0D, 0x19,
|
||||||
|
0x0E, 0x41, 0x10, 0x2A, 0x20, 0x04, 0x0C, 0x0D, 0x03, 0x0E, 0x16, 0x0D, 0x0E, 0x18, 0x0F, 0x05, 0x0E, 0x07, 0x0E,
|
||||||
|
0xA0, 0x00, 0x51, 0xA0, 0x00, 0x71, 0xA0, 0x00, 0xC3, 0xA3, 0x00, 0x71, 0x74, 0x6E, 0x7A, 0xFD, 0xFD, 0xFD, 0xA1,
|
||||||
|
0x00, 0x71, 0x74, 0xF4, 0xA1, 0x00, 0x71, 0x6E, 0xEF, 0xA3, 0x00, 0x71, 0x74, 0x73, 0x6E, 0xEA, 0xEA, 0xEA, 0xA2,
|
||||||
|
0x00, 0x71, 0x7A, 0x73, 0xE1, 0xE1, 0xA0, 0x00, 0xA2, 0xB6, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68,
|
||||||
|
0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xD1, 0xFD, 0xFD, 0xFD,
|
||||||
|
0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xA0,
|
||||||
|
0x01, 0xD2, 0x21, 0xAD, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x6E, 0xFD, 0xA0, 0x05, 0xB1, 0xA0, 0x05, 0xC2, 0xA0, 0x05,
|
||||||
|
0xE2, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x27, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75,
|
||||||
|
0xC3, 0xEC, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xF5, 0x21, 0x6F, 0xF1, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0xA0, 0x05,
|
||||||
|
0x81, 0xA0, 0x06, 0x72, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0xA2, 0x05, 0x81, 0x6F, 0x61, 0xFA, 0xFD, 0xAE, 0x06,
|
||||||
|
0x31, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6C, 0x6D, 0x70, 0x71, 0x73, 0x74, 0x76, 0x7A, 0xED, 0xED, 0xF9, 0xED,
|
||||||
|
0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0x21, 0x6E, 0xE1, 0xA0, 0x06, 0x01, 0xA0, 0x06, 0x92,
|
||||||
|
0xA0, 0x06, 0x12, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xA0, 0x02, 0x51, 0x21, 0x61,
|
||||||
|
0xFD, 0x21, 0xAD, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x6F, 0xFD, 0x28, 0x68, 0x61, 0x65, 0x69, 0x6F,
|
||||||
|
0x75, 0xC3, 0x6C, 0xDA, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xE3, 0xFD, 0x44, 0x75, 0x62, 0x65, 0x6F, 0xFF, 0x65, 0xFF,
|
||||||
|
0x91, 0xFF, 0xC6, 0xFF, 0xEF, 0xA0, 0x04, 0x41, 0xA0, 0x04, 0x52, 0xA0, 0x04, 0x72, 0x25, 0xA1, 0xA9, 0xAD, 0xB3,
|
||||||
|
0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x27, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xEC, 0xEF, 0xEF, 0xEF, 0xEF,
|
||||||
|
0xEF, 0xF5, 0x21, 0x61, 0xF1, 0x21, 0x63, 0xFD, 0x21, 0x73, 0xFD, 0xD8, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66,
|
||||||
|
0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, 0x72, 0x69, 0x75,
|
||||||
|
0xFE, 0xC5, 0xFE, 0xC8, 0xFE, 0xCE, 0xFE, 0xC8, 0xFE, 0xD7, 0xFE, 0xDC, 0xFE, 0xC8, 0xFE, 0xC8, 0xFE, 0xC8, 0xFE,
|
||||||
|
0xDC, 0xFE, 0xC8, 0xFE, 0xE1, 0xFE, 0xC8, 0xFE, 0xC8, 0xFE, 0xEA, 0xFE, 0xC8, 0xFE, 0xC8, 0xFE, 0xC8, 0xFE, 0xC8,
|
||||||
|
0xFE, 0xC8, 0xFE, 0xF4, 0xFE, 0xF4, 0xFF, 0xC7, 0xFF, 0xFD, 0x41, 0x6C, 0xFF, 0x45, 0x21, 0x61, 0xFC, 0x21, 0x75,
|
||||||
|
0xFD, 0x41, 0x72, 0xFF, 0x3B, 0x22, 0x6E, 0x75, 0xF9, 0xFC, 0x41, 0x78, 0xFF, 0x32, 0x41, 0x78, 0xFF, 0x34, 0x21,
|
||||||
|
0xB3, 0xFC, 0x41, 0x6E, 0xFF, 0x27, 0xA0, 0x01, 0x52, 0x21, 0x64, 0xFD, 0xA0, 0x06, 0x43, 0x21, 0x61, 0xFD, 0x21,
|
||||||
|
0x65, 0xFA, 0x23, 0x6E, 0x70, 0x76, 0xF4, 0xFA, 0xFD, 0x21, 0x74, 0xEA, 0x21, 0x73, 0xFD, 0x21, 0x6E, 0xFA, 0x21,
|
||||||
|
0x69, 0xED, 0x21, 0x6C, 0xFD, 0x24, 0x61, 0x65, 0x69, 0x6F, 0xEA, 0xF4, 0xF7, 0xFD, 0x21, 0x6E, 0xF7, 0x25, 0x61,
|
||||||
|
0x6F, 0xC3, 0x75, 0x65, 0xBB, 0xC0, 0xC8, 0xCB, 0xFD, 0xA1, 0x00, 0x61, 0x69, 0xF5, 0xA0, 0x07, 0xB1, 0x21, 0x62,
|
||||||
|
0xFD, 0xA0, 0x00, 0xF1, 0x21, 0x68, 0xFD, 0x22, 0x69, 0x6F, 0xFA, 0xFA, 0x21, 0x74, 0xF5, 0x21, 0x6E, 0xFD, 0x21,
|
||||||
|
0x65, 0xFD, 0x24, 0x63, 0x73, 0x72, 0x74, 0xEF, 0xF2, 0xFD, 0xEC, 0xA2, 0x06, 0x01, 0x69, 0x65, 0xE0, 0xF7, 0xA0,
|
||||||
|
0x02, 0x91, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFA, 0x21, 0x65, 0xFD, 0x22, 0x65, 0x72, 0xF7, 0xFD, 0x21, 0x6E, 0xEF,
|
||||||
|
0x41, 0x6C, 0xFE, 0x6F, 0x22, 0x65, 0x75, 0xF9, 0xFC, 0x21, 0x74, 0xE3, 0x21, 0x73, 0xFD, 0x21, 0xB3, 0xFD, 0x21,
|
||||||
|
0xC3, 0xFD, 0x41, 0x63, 0xFE, 0x5A, 0x21, 0x73, 0xFC, 0x22, 0x65, 0x69, 0xFD, 0xF9, 0x21, 0x64, 0xCB, 0x21, 0x6E,
|
||||||
|
0xFD, 0x21, 0x65, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x61, 0xD3, 0x21, 0x69, 0xFD, 0x21, 0x6F, 0xB9, 0x21, 0x74, 0xFD,
|
||||||
|
0xA7, 0x07, 0x62, 0x63, 0x67, 0x70, 0x6C, 0x72, 0x78, 0x75, 0xBF, 0xCB, 0xD9, 0xE3, 0xF1, 0xF7, 0xFD, 0x42, 0x63,
|
||||||
|
0x74, 0xFF, 0xA2, 0xFF, 0xA2, 0x41, 0x63, 0xFF, 0x9B, 0x22, 0x69, 0x75, 0xF5, 0xFC, 0x41, 0x69, 0xFF, 0x92, 0x21,
|
||||||
|
0x63, 0xFC, 0x21, 0x69, 0xFD, 0x41, 0xA1, 0xFE, 0x0B, 0x21, 0xC3, 0xFC, 0x41, 0x73, 0xFF, 0x81, 0x21, 0x69, 0xFC,
|
||||||
|
0xA4, 0x07, 0x62, 0x64, 0x66, 0x74, 0x78, 0xE3, 0xEF, 0xF6, 0xFD, 0x41, 0x75, 0xFF, 0x8C, 0x21, 0x70, 0xFC, 0x41,
|
||||||
|
0x6F, 0xFD, 0xEB, 0xA2, 0x07, 0x62, 0x6D, 0x74, 0xF9, 0xFC, 0xA0, 0x00, 0xF2, 0x21, 0x69, 0xFD, 0xA0, 0x01, 0x32,
|
||||||
|
0x21, 0x72, 0xFD, 0x21, 0xA9, 0xFD, 0x43, 0x65, 0xC3, 0x74, 0xFF, 0xFA, 0xFF, 0xFD, 0xFF, 0x2A, 0x41, 0x6E, 0xFF,
|
||||||
|
0x20, 0x21, 0xAD, 0xFC, 0x23, 0x65, 0x69, 0xC3, 0xF9, 0xF9, 0xFD, 0x21, 0x64, 0xF9, 0xA3, 0x05, 0x02, 0x6B, 0x70,
|
||||||
|
0x72, 0xD9, 0xE5, 0xFD, 0xA0, 0x07, 0x62, 0xA0, 0x07, 0xA1, 0x21, 0x6C, 0xFD, 0x21, 0x75, 0xFD, 0xA1, 0x07, 0x82,
|
||||||
|
0x67, 0xFD, 0xA0, 0x07, 0x82, 0xC1, 0x07, 0x82, 0x70, 0xFE, 0xFD, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xF2, 0xF7,
|
||||||
|
0xF7, 0xFA, 0xF7, 0xA0, 0x01, 0xA1, 0x21, 0x62, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x75, 0xFD, 0x48, 0x68, 0x61, 0x65,
|
||||||
|
0x69, 0x6F, 0x75, 0xC3, 0x6E, 0xFE, 0xF2, 0xFF, 0x46, 0xFF, 0x7F, 0xFF, 0x95, 0xFF, 0xC6, 0xFF, 0xCF, 0xFF, 0xE9,
|
||||||
|
0xFF, 0xFD, 0xA0, 0x0A, 0x01, 0x21, 0x74, 0xFD, 0x21, 0x75, 0xFD, 0xA2, 0x00, 0x61, 0x6F, 0x61, 0xDE, 0xFD, 0xA0,
|
||||||
|
0x08, 0x12, 0xA0, 0x08, 0x33, 0xC2, 0x07, 0x82, 0x6D, 0x6E, 0xFD, 0x4D, 0xFD, 0x4D, 0xA0, 0x0B, 0x45, 0x23, 0xA1,
|
||||||
|
0xA9, 0xB3, 0xFD, 0xFD, 0xFD, 0x24, 0x61, 0x65, 0x6F, 0xC3, 0xF6, 0xF6, 0xF6, 0xF9, 0x21, 0x73, 0xF7, 0x21, 0x65,
|
||||||
|
0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xFD, 0xA1, 0x07, 0x82, 0x6E, 0xFD, 0xA0, 0x08, 0x63, 0xA0,
|
||||||
|
0x08, 0x92, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFA, 0xFD, 0xFD, 0xFD, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F,
|
||||||
|
0x75, 0xC3, 0xFF, 0xB9, 0xFF, 0xBC, 0xFF, 0xBF, 0xFF, 0xEA, 0xFF, 0x70, 0xFF, 0x70, 0xFF, 0xF5, 0x42, 0x73, 0x75,
|
||||||
|
0xFF, 0xEA, 0xFF, 0x96, 0xA0, 0x09, 0x91, 0x21, 0x68, 0xFD, 0xA1, 0x09, 0x81, 0x63, 0xFD, 0x21, 0x6F, 0xFB, 0x21,
|
||||||
|
0x69, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x65, 0xFD, 0xA2, 0x00, 0x61, 0x65, 0x69, 0xE2, 0xFD, 0xA0, 0x00, 0x61, 0xA0,
|
||||||
|
0x0C, 0xC3, 0x21, 0x75, 0xFD, 0xA0, 0x04, 0x91, 0x21, 0x74, 0xFD, 0x22, 0x64, 0x73, 0xF7, 0xFD, 0x22, 0x6E, 0x73,
|
||||||
|
0xF5, 0xF5, 0x21, 0x6F, 0xFB, 0x22, 0x74, 0x7A, 0xED, 0xED, 0x21, 0x6E, 0xFB, 0x21, 0x61, 0xFD, 0x21, 0x64, 0xFD,
|
||||||
|
0x43, 0x63, 0x65, 0x6E, 0xFF, 0xEF, 0xFD, 0x20, 0xFF, 0xFD, 0x21, 0x6E, 0xD8, 0x23, 0x65, 0x61, 0x69, 0xD8, 0xF3,
|
||||||
|
0xFD, 0x21, 0x6C, 0xF9, 0x41, 0x69, 0xFD, 0x1D, 0xA0, 0x04, 0xA2, 0xA0, 0x04, 0xC2, 0x25, 0xA1, 0xA9, 0xAD, 0xB3,
|
||||||
|
0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x27, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xB3, 0xEF, 0xEF, 0xEF, 0xEF,
|
||||||
|
0xEF, 0xF5, 0x22, 0x6C, 0x6F, 0xDC, 0xF1, 0xA2, 0x00, 0x61, 0x61, 0x69, 0xD4, 0xFB, 0xA0, 0x0D, 0x43, 0x21, 0x69,
|
||||||
|
0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x62, 0xF4, 0x21, 0xA1, 0xFD, 0x22, 0xC3, 0x61, 0xFD, 0xFA, 0x23,
|
||||||
|
0x66, 0x6D, 0x72, 0xEF, 0xF2, 0xFB, 0xA0, 0x0D, 0x73, 0x21, 0x62, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x72, 0xFD, 0xA0,
|
||||||
|
0x0D, 0x42, 0x21, 0x69, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x70, 0xFD, 0x22, 0xA1, 0xB3, 0xF1, 0xFD, 0x21, 0x70, 0xEF,
|
||||||
|
0x21, 0x6F, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x75, 0xFD, 0x21, 0x6D, 0xE3, 0x21, 0xA1, 0xFD, 0x22, 0x61, 0xC3, 0xFA,
|
||||||
|
0xFD, 0x21, 0x6C, 0xFB, 0x21, 0x73, 0xFD, 0x41, 0x70, 0xFE, 0x28, 0x21, 0x73, 0xFC, 0x21, 0x6C, 0xCB, 0x22, 0x69,
|
||||||
|
0x65, 0xFA, 0xFD, 0x45, 0x61, 0xC3, 0x65, 0x69, 0x68, 0xFF, 0xB0, 0xFF, 0xCF, 0xFF, 0xDD, 0xFF, 0xEE, 0xFF, 0xFB,
|
||||||
|
0x21, 0x6E, 0xF0, 0xA0, 0x06, 0xD2, 0x41, 0x69, 0xFB, 0xE3, 0xA0, 0x0E, 0x92, 0x21, 0x65, 0xFD, 0xC3, 0x0D, 0xB3,
|
||||||
|
0x63, 0x72, 0x6A, 0xFF, 0xF6, 0xFB, 0xD9, 0xFF, 0xFD, 0x21, 0x6F, 0xEE, 0x21, 0xAD, 0xFD, 0x43, 0x67, 0x69, 0xC3,
|
||||||
|
0xFB, 0xC7, 0xFF, 0xE8, 0xFF, 0xFD, 0xA0, 0x06, 0xB2, 0xA1, 0x0E, 0x92, 0x61, 0xDB, 0x22, 0x63, 0x72, 0xF8, 0xFB,
|
||||||
|
0x21, 0x65, 0xFB, 0x41, 0x72, 0xFB, 0xAD, 0xA1, 0x0E, 0x92, 0x73, 0xCA, 0x23, 0x65, 0x61, 0x69, 0xC5, 0xFB, 0xC5,
|
||||||
|
0x21, 0x61, 0xBE, 0xC6, 0x0D, 0xB3, 0x72, 0x6C, 0x61, 0x6D, 0x74, 0x6F, 0xFF, 0xD3, 0xFF, 0xEA, 0xFF, 0xED, 0xFF,
|
||||||
|
0xF6, 0xFF, 0xFD, 0xFB, 0x9A, 0xA0, 0x05, 0x71, 0x21, 0x6F, 0xFD, 0x21, 0x64, 0xFD, 0xC3, 0x05, 0x81, 0x64, 0x65,
|
||||||
|
0x75, 0xFC, 0x8E, 0xFF, 0x9D, 0xFF, 0xFD, 0xC1, 0x0E, 0x92, 0x6F, 0xFF, 0x91, 0x43, 0xA1, 0xA9, 0xB3, 0xFF, 0x8B,
|
||||||
|
0xFF, 0x8B, 0xFF, 0x8B, 0x45, 0x61, 0x65, 0x6C, 0x6F, 0xC3, 0xFF, 0x81, 0xFF, 0x81, 0xFF, 0xF0, 0xFF, 0x81, 0xFF,
|
||||||
|
0xF6, 0x42, 0x61, 0x6F, 0xFF, 0x71, 0xFF, 0x71, 0x41, 0x72, 0xFF, 0x8C, 0x21, 0x70, 0xFC, 0x41, 0x72, 0xFF, 0x63,
|
||||||
|
0x21, 0x65, 0xFC, 0x41, 0x61, 0xFB, 0x3B, 0x42, 0x6D, 0x74, 0xFB, 0x37, 0xFF, 0xFC, 0xC7, 0x0D, 0xB3, 0x6E, 0x67,
|
||||||
|
0x6C, 0x7A, 0x6D, 0x63, 0x73, 0xFF, 0xB4, 0xFF, 0x63, 0xFF, 0xD0, 0xFF, 0xE0, 0xFF, 0xEB, 0xFF, 0xF2, 0xFF, 0xF9,
|
||||||
|
0x41, 0x65, 0xFF, 0x5B, 0x41, 0x73, 0xFF, 0x8F, 0x21, 0x65, 0xFC, 0xA2, 0x0D, 0xB3, 0x70, 0x72, 0xF5, 0xFD, 0x42,
|
||||||
|
0x61, 0x65, 0xFF, 0x27, 0xFF, 0x39, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xFF, 0x20, 0xFF, 0x95, 0xFF, 0x20, 0xFF, 0x20,
|
||||||
|
0xA2, 0x0D, 0xB3, 0x72, 0x6C, 0xEC, 0xF3, 0xA0, 0x0D, 0xE3, 0xC1, 0x0D, 0xE3, 0x6E, 0xFA, 0xE8, 0xA0, 0x0E, 0x72,
|
||||||
|
0xA1, 0x05, 0x81, 0x69, 0xFD, 0xA1, 0x0D, 0xE3, 0x6E, 0xFB, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xEA, 0xEA, 0xED,
|
||||||
|
0xFB, 0xEA, 0x41, 0x76, 0xFF, 0x0D, 0x41, 0x6D, 0xFF, 0x09, 0x22, 0x65, 0x6F, 0xF8, 0xFC, 0x48, 0x68, 0x61, 0x65,
|
||||||
|
0x69, 0x6F, 0x75, 0xC3, 0x72, 0xFE, 0xD7, 0xFE, 0xE4, 0xFF, 0x23, 0xFF, 0x8D, 0xFF, 0xB0, 0xFF, 0xCB, 0xFF, 0xE8,
|
||||||
|
0xFF, 0xFB, 0x21, 0x74, 0xE7, 0x21, 0x73, 0xFD, 0x41, 0x70, 0xFB, 0xB0, 0x21, 0xBA, 0xFC, 0x22, 0x75, 0xC3, 0xF9,
|
||||||
|
0xFD, 0xA0, 0x01, 0x11, 0x21, 0x6E, 0xFD, 0x21, 0xAD, 0xFD, 0x22, 0x69, 0xC3, 0xFA, 0xFD, 0x21, 0x64, 0xFB, 0xA2,
|
||||||
|
0x04, 0xA2, 0x63, 0x72, 0xEA, 0xFD, 0x21, 0x6C, 0xE8, 0x21, 0x75, 0xFD, 0x21, 0x62, 0xFD, 0xA1, 0x04, 0xC2, 0x6D,
|
||||||
|
0xFD, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, 0xFB, 0xFD, 0xE3, 0xFD, 0xE3, 0xFD, 0xE3, 0xFD, 0xE3, 0x47, 0x68,
|
||||||
|
0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xFD, 0x94, 0xFD, 0xD0, 0xFD, 0xD0, 0xFD, 0xD0, 0xFF, 0xDB, 0xFD, 0xD0, 0xFF,
|
||||||
|
0xF0, 0x22, 0xA1, 0xAD, 0xB4, 0xB4, 0x24, 0x61, 0xC3, 0x65, 0x69, 0xAF, 0xFB, 0xAF, 0xAF, 0x21, 0x62, 0xF7, 0xC2,
|
||||||
|
0x01, 0x11, 0x61, 0x6F, 0xFF, 0xA3, 0xFF, 0xA3, 0x21, 0x62, 0xF7, 0x21, 0xAD, 0xFD, 0xA2, 0x04, 0x91, 0x69, 0xC3,
|
||||||
|
0xEE, 0xFD, 0x41, 0x74, 0xFA, 0x1F, 0x21, 0x72, 0xFC, 0x21, 0x6F, 0xFD, 0xA1, 0x0D, 0xB2, 0x62, 0xFD, 0x41, 0x72,
|
||||||
|
0xFE, 0x63, 0x21, 0x61, 0xFC, 0xA1, 0x0D, 0xB2, 0x74, 0xFD, 0xA0, 0x0D, 0xB2, 0xA0, 0x0E, 0xB2, 0x25, 0xA1, 0xA9,
|
||||||
|
0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x27, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xCD, 0xDE, 0xEA,
|
||||||
|
0xEF, 0xEF, 0xEF, 0xF5, 0x42, 0x65, 0x6F, 0xFF, 0x88, 0xFF, 0xF1, 0xC3, 0x00, 0x61, 0x61, 0x6F, 0x72, 0xFD, 0xF4,
|
||||||
|
0xFF, 0x3C, 0xFF, 0xF9, 0x41, 0x72, 0xFB, 0x6B, 0x21, 0x65, 0xFC, 0x41, 0xB3, 0xFB, 0x4A, 0x42, 0x6F, 0xC3, 0xFB,
|
||||||
|
0x46, 0xFF, 0xFC, 0x43, 0x72, 0x69, 0x73, 0xFB, 0x3C, 0xFF, 0xF2, 0xFF, 0xF9, 0x42, 0x73, 0x74, 0xFB, 0x32, 0xFB,
|
||||||
|
0x32, 0x41, 0xAD, 0xFB, 0x48, 0x22, 0x69, 0xC3, 0xF5, 0xFC, 0x21, 0x6D, 0xFB, 0x41, 0x6D, 0xFB, 0x1F, 0x21, 0x72,
|
||||||
|
0xFC, 0x21, 0xAD, 0xFD, 0x22, 0x69, 0xC3, 0xFA, 0xFD, 0x41, 0x76, 0xFB, 0x10, 0x21, 0xA1, 0xFC, 0xA0, 0x04, 0xE2,
|
||||||
|
0x21, 0x70, 0xFD, 0x23, 0x61, 0xC3, 0x75, 0xF3, 0xF7, 0xFD, 0x21, 0x72, 0xF9, 0x41, 0x69, 0xFB, 0x5E, 0x21, 0x64,
|
||||||
|
0xFC, 0x21, 0x6E, 0xFD, 0x41, 0xB1, 0xFA, 0xEF, 0x21, 0xC3, 0xFC, 0x21, 0xBA, 0xFD, 0x23, 0x6F, 0x75, 0xC3, 0xF3,
|
||||||
|
0xFA, 0xFD, 0x41, 0x73, 0xFF, 0x42, 0x21, 0xBA, 0xFC, 0x42, 0x75, 0xC3, 0xFA, 0xF7, 0xFF, 0xFD, 0x41, 0x67, 0xFA,
|
||||||
|
0xD3, 0x41, 0x7A, 0xFE, 0x14, 0x41, 0x6A, 0xFA, 0xC8, 0x23, 0xA9, 0xAD, 0xB3, 0xF4, 0xF8, 0xFC, 0x42, 0x61, 0xC3,
|
||||||
|
0xF9, 0x40, 0xFB, 0x35, 0x42, 0x6D, 0x74, 0xF9, 0x39, 0xF9, 0x39, 0x43, 0x7A, 0x6D, 0x73, 0xFF, 0xF2, 0xFA, 0xAF,
|
||||||
|
0xFF, 0xF9, 0x45, 0x65, 0xC3, 0x69, 0x6F, 0x71, 0xFF, 0xD5, 0xFF, 0xE1, 0xFF, 0xF6, 0xFF, 0xDD, 0xFA, 0xA5, 0x41,
|
||||||
|
0xAD, 0xFF, 0x76, 0x42, 0x69, 0xC3, 0xFF, 0x72, 0xFF, 0xFC, 0x43, 0xA1, 0xA9, 0xB3, 0xFA, 0x8A, 0xFA, 0x8A, 0xFA,
|
||||||
|
0x8A, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xFA, 0x80, 0xFF, 0xF6, 0xFA, 0x80, 0xFA, 0x80, 0x41, 0x65, 0xFA, 0xD8, 0x21,
|
||||||
|
0x72, 0xFC, 0x42, 0x6E, 0x74, 0xFA, 0xA1, 0xFA, 0x6C, 0xA0, 0x00, 0x40, 0x21, 0x74, 0xFD, 0x21, 0x65, 0xFD, 0x42,
|
||||||
|
0xA9, 0xAD, 0xFA, 0x94, 0xFF, 0xFD, 0x22, 0x65, 0xC3, 0xE9, 0xF9, 0x22, 0x61, 0x72, 0xE1, 0xFB, 0x41, 0x67, 0xFF,
|
||||||
|
0x42, 0x41, 0xBA, 0xFF, 0x28, 0x42, 0x75, 0xC3, 0xFF, 0x24, 0xFF, 0xFC, 0xCE, 0x07, 0x62, 0x62, 0x64, 0x66, 0x67,
|
||||||
|
0x63, 0x6A, 0x6C, 0x6E, 0x6D, 0x70, 0x65, 0x71, 0x7A, 0x73, 0xFF, 0x00, 0xFF, 0x1A, 0xFF, 0x27, 0xFF, 0x40, 0xFF,
|
||||||
|
0x57, 0xFF, 0x65, 0xFF, 0x97, 0xFF, 0xAB, 0xFF, 0xBC, 0xFF, 0xEC, 0xFF, 0xF1, 0xFF, 0x33, 0xFF, 0x33, 0xFF, 0xF9,
|
||||||
|
0xA0, 0x05, 0x02, 0x49, 0x6F, 0x63, 0x69, 0x66, 0x67, 0x76, 0x61, 0x73, 0x74, 0xF8, 0x8F, 0xFA, 0x0C, 0xFA, 0x71,
|
||||||
|
0xFA, 0x0C, 0xFA, 0x0C, 0xFA, 0x0C, 0xF8, 0x8F, 0xFA, 0x0C, 0xFA, 0x0C, 0x41, 0x64, 0xF8, 0x73, 0x21, 0x6E, 0xFC,
|
||||||
|
0x21, 0x69, 0xFD, 0xC3, 0x07, 0x62, 0x6E, 0x6D, 0x76, 0xFF, 0xDA, 0xFE, 0xDD, 0xFF, 0xFD, 0x41, 0x6E, 0xF9, 0xF7,
|
||||||
|
0x21, 0x65, 0xFC, 0x41, 0x61, 0xF9, 0xD3, 0x22, 0x69, 0x67, 0xF9, 0xFC, 0xC4, 0x07, 0x62, 0x62, 0x72, 0x63, 0x6A,
|
||||||
|
0xFE, 0xC1, 0xFF, 0xFB, 0xF9, 0xCA, 0xFA, 0x73, 0x42, 0xA1, 0xB3, 0xF9, 0xBB, 0xF9, 0xBB, 0x43, 0x61, 0xC3, 0x6F,
|
||||||
|
0xF9, 0xB4, 0xFF, 0xF9, 0xF9, 0xB4, 0x42, 0x63, 0x71, 0xFF, 0xF6, 0xF9, 0xAA, 0x42, 0x63, 0x71, 0xFF, 0xD0, 0xF9,
|
||||||
|
0xA3, 0x21, 0xAD, 0xF9, 0x22, 0x69, 0xC3, 0xEF, 0xFD, 0xC1, 0x05, 0x81, 0x74, 0xFC, 0x34, 0x41, 0x74, 0xFC, 0x2E,
|
||||||
|
0x21, 0xA1, 0xFC, 0x22, 0x61, 0xC3, 0xF3, 0xFD, 0x42, 0xA9, 0xB3, 0xF8, 0x05, 0xF8, 0x05, 0x48, 0x72, 0x61, 0x73,
|
||||||
|
0x6D, 0x65, 0xC3, 0x64, 0x66, 0xF7, 0xFE, 0xF7, 0xFE, 0xF7, 0xFE, 0xFF, 0x16, 0xF7, 0xFE, 0xFF, 0xF9, 0xF7, 0xFE,
|
||||||
|
0xF9, 0x7B, 0xC1, 0x05, 0x81, 0x72, 0xF7, 0xE5, 0x42, 0xAD, 0xA1, 0xFF, 0xFA, 0xF7, 0xDF, 0x43, 0x69, 0xC3, 0x74,
|
||||||
|
0xFF, 0xDA, 0xFF, 0xF9, 0xF9, 0x55, 0x41, 0xA1, 0xF9, 0x4E, 0x42, 0x61, 0xC3, 0xF9, 0x4A, 0xFF, 0xFC, 0x41, 0x7A,
|
||||||
|
0xF9, 0x40, 0x21, 0xAD, 0xFC, 0x22, 0x69, 0xC3, 0xF9, 0xFD, 0x21, 0x6C, 0xFB, 0x21, 0x69, 0xFD, 0xC5, 0x07, 0x62,
|
||||||
|
0x62, 0x6D, 0x6E, 0x73, 0x74, 0xFF, 0x95, 0xFF, 0xA7, 0xFF, 0xD9, 0xFF, 0xE7, 0xFF, 0xFD, 0x43, 0x61, 0x65, 0x6F,
|
||||||
|
0xF9, 0x1C, 0xF9, 0x1C, 0xF9, 0x1C, 0xC2, 0x07, 0x82, 0x62, 0x6D, 0xF9, 0x15, 0xFF, 0xF6, 0x45, 0xA1, 0xA9, 0xAD,
|
||||||
|
0xB3, 0xBA, 0xFF, 0xF7, 0xF9, 0xF0, 0xF9, 0xF0, 0xF9, 0xF0, 0xF9, 0xF0, 0x46, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3,
|
||||||
|
0xFE, 0xBD, 0xFE, 0xEA, 0xFF, 0x13, 0xFF, 0x2F, 0xFF, 0xCB, 0xFF, 0xF0, 0xA1, 0x00, 0x61, 0x65, 0xED, 0x43, 0x6E,
|
||||||
|
0x72, 0x73, 0xFE, 0xD2, 0xF8, 0xE1, 0xF8, 0xE1, 0xA0, 0x0C, 0x12, 0xA1, 0x0C, 0x12, 0x72, 0xFD, 0x21, 0x6E, 0xF8,
|
||||||
|
0x21, 0xB3, 0xFD, 0x23, 0x61, 0x6F, 0xC3, 0xF2, 0xF5, 0xFD, 0x41, 0x70, 0xF7, 0x45, 0x41, 0x6E, 0xF7, 0x41, 0x21,
|
||||||
|
0x65, 0xFC, 0x21, 0x64, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x22, 0x73, 0x74, 0xEC, 0xFD, 0x41, 0xA9, 0xF8,
|
||||||
|
0xBA, 0x21, 0x65, 0xD6, 0x21, 0x69, 0xFD, 0xC7, 0x0F, 0x93, 0x65, 0x6C, 0x64, 0x6E, 0x72, 0xC3, 0x6D, 0xFF, 0xBE,
|
||||||
|
0xF9, 0x7B, 0xFF, 0xD6, 0xFF, 0xF1, 0xF8, 0x9F, 0xFF, 0xF6, 0xFF, 0xFD, 0x41, 0xA1, 0xFC, 0xEB, 0x21, 0xC3, 0xFC,
|
||||||
|
0x42, 0x70, 0x74, 0xF8, 0xA9, 0xF7, 0x03, 0x22, 0x75, 0x65, 0xF6, 0xF9, 0x41, 0xA1, 0xF8, 0x74, 0x44, 0x61, 0xC3,
|
||||||
|
0x65, 0x6F, 0xF8, 0x70, 0xFF, 0xFC, 0xF8, 0x70, 0xF8, 0x70, 0x21, 0x74, 0xF3, 0x41, 0x65, 0xF6, 0xE3, 0x21, 0x75,
|
||||||
|
0xFC, 0x21, 0x6C, 0xFD, 0x41, 0x61, 0xFA, 0xF6, 0x41, 0x6E, 0xFC, 0xB6, 0x21, 0x65, 0xFC, 0x21, 0x6D, 0xFD, 0x41,
|
||||||
|
0x65, 0xF8, 0x4B, 0x23, 0x63, 0x69, 0x74, 0xEE, 0xF9, 0xFC, 0x41, 0x69, 0xF8, 0x66, 0x21, 0x6D, 0xFC, 0x21, 0xB3,
|
||||||
|
0xFD, 0x21, 0xC3, 0xFD, 0xC6, 0x0F, 0x93, 0x63, 0x73, 0x66, 0x6C, 0x72, 0x74, 0xFF, 0xB7, 0xFF, 0xCD, 0xFF, 0xD7,
|
||||||
|
0xFF, 0xEC, 0xFB, 0x06, 0xFF, 0xFD, 0x41, 0x75, 0xFC, 0x7F, 0x21, 0x63, 0xFC, 0x21, 0x65, 0xFD, 0x41, 0x6D, 0xFF,
|
||||||
|
0x57, 0x21, 0x65, 0xFC, 0x21, 0x6C, 0xAA, 0x21, 0x70, 0xFD, 0x41, 0x74, 0xFF, 0x4A, 0x41, 0x65, 0xF8, 0x29, 0x41,
|
||||||
|
0x6D, 0xF6, 0x7F, 0x21, 0xAD, 0xFC, 0x41, 0x75, 0xF8, 0x1E, 0x44, 0x61, 0x69, 0xC3, 0x72, 0xF8, 0x1A, 0xFF, 0xF5,
|
||||||
|
0xFF, 0xF9, 0xFF, 0xFC, 0x22, 0x70, 0x74, 0xE4, 0xF3, 0xA5, 0x0F, 0x93, 0x6A, 0x6C, 0x6D, 0x6E, 0x73, 0xCB, 0xD2,
|
||||||
|
0xD8, 0xDB, 0xFB, 0x41, 0x69, 0xFC, 0x36, 0x21, 0x70, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x63, 0xFD, 0x41, 0x63, 0xFA,
|
||||||
|
0x65, 0x21, 0x69, 0xFC, 0x41, 0xAD, 0xF7, 0xCF, 0x42, 0x69, 0xC3, 0xF7, 0xCB, 0xFF, 0xFC, 0x21, 0x64, 0xF9, 0xA3,
|
||||||
|
0x0F, 0x93, 0x63, 0x66, 0x72, 0xE8, 0xEF, 0xFD, 0x41, 0x62, 0xFA, 0xEF, 0xA1, 0x0F, 0x93, 0x72, 0xFC, 0x42, 0xA9,
|
||||||
|
0xB3, 0xF7, 0x9E, 0xF7, 0x9E, 0x42, 0x61, 0xC3, 0xF7, 0x97, 0xFF, 0xF9, 0x21, 0x74, 0xF9, 0x41, 0x74, 0xFF, 0x50,
|
||||||
|
0xA2, 0x0F, 0xC3, 0x73, 0x72, 0xF9, 0xFC, 0xA0, 0x0F, 0xC3, 0xC3, 0x0F, 0xC3, 0x6E, 0x6D, 0x72, 0xFD, 0x8F, 0xFA,
|
||||||
|
0x1F, 0xF7, 0x7F, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xEA, 0xF1, 0xF4, 0xF1, 0xF1, 0x41, 0x79, 0xF8, 0x11, 0x21,
|
||||||
|
0x61, 0xFC, 0x48, 0x69, 0x68, 0x61, 0x65, 0x6F, 0x75, 0xC3, 0x72, 0xFE, 0xC2, 0xF8, 0x91, 0xFF, 0x31, 0xFF, 0x82,
|
||||||
|
0xFF, 0xB1, 0xFF, 0xBE, 0xFF, 0xEE, 0xFF, 0xFD, 0x41, 0x74, 0xF8, 0x78, 0x21, 0x73, 0xFC, 0x41, 0x73, 0xF8, 0x71,
|
||||||
|
0x21, 0x65, 0xFC, 0x22, 0x65, 0x6F, 0xF6, 0xFD, 0x22, 0x62, 0x72, 0xD4, 0xFB, 0x41, 0x73, 0xFD, 0x21, 0x21, 0x61,
|
||||||
|
0xFC, 0x41, 0x61, 0xFD, 0x39, 0x21, 0x72, 0xFC, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x62,
|
||||||
|
0xFD, 0xA3, 0x00, 0x61, 0x75, 0x6F, 0x65, 0xE1, 0xEA, 0xFD, 0x41, 0x70, 0xF6, 0x09, 0x21, 0x6D, 0xFC, 0x41, 0x6A,
|
||||||
|
0xF6, 0x02, 0xA0, 0x05, 0x52, 0x21, 0x74, 0xFD, 0x21, 0xB3, 0xFD, 0x21, 0xC3, 0xFD, 0x22, 0x62, 0x6C, 0xF0, 0xFD,
|
||||||
|
0x22, 0x69, 0x6F, 0xE8, 0xFB, 0x21, 0x65, 0xFB, 0x21, 0x6C, 0xFD, 0xC1, 0x0E, 0xB2, 0x67, 0xF9, 0x8A, 0x41, 0x67,
|
||||||
|
0xF5, 0x63, 0xA1, 0x0E, 0xB2, 0x65, 0xFC, 0x41, 0xB1, 0xF9, 0x7B, 0xA1, 0x0E, 0xB2, 0xC3, 0xFC, 0x43, 0xA1, 0xA9,
|
||||||
|
0xB3, 0xF5, 0x51, 0xF5, 0x51, 0xF5, 0x51, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xF5, 0x47, 0xFF, 0xF6, 0xF5, 0x47, 0xF5,
|
||||||
|
0x47, 0x21, 0x74, 0xF3, 0xC2, 0x0E, 0xB2, 0x64, 0x6E, 0xFA, 0x38, 0xFF, 0xFD, 0xA0, 0x10, 0xD2, 0x25, 0xA1, 0xA9,
|
||||||
|
0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xF9, 0x3A, 0xFB,
|
||||||
|
0x1F, 0xFF, 0xB7, 0xFF, 0xC1, 0xFF, 0xCA, 0xFF, 0xE9, 0xFF, 0xF5, 0x21, 0x73, 0xEA, 0x41, 0x78, 0xF8, 0x7E, 0x21,
|
||||||
|
0xB3, 0xFC, 0x21, 0xC3, 0xFD, 0x22, 0x61, 0x69, 0xF3, 0xFD, 0xC2, 0x00, 0x61, 0x65, 0x72, 0xFF, 0x8C, 0xFF, 0xFB,
|
||||||
|
0x42, 0x61, 0x6F, 0xF6, 0x48, 0xF6, 0x48, 0x21, 0x65, 0xF9, 0x21, 0x6D, 0xFD, 0x41, 0x65, 0xF6, 0x3B, 0x21, 0x65,
|
||||||
|
0xFC, 0x21, 0x6D, 0xFD, 0x22, 0x75, 0x65, 0xF3, 0xFD, 0x41, 0x65, 0xF5, 0x60, 0x21, 0x72, 0xFC, 0x41, 0x6F, 0xF5,
|
||||||
|
0x59, 0x21, 0x72, 0xFC, 0x41, 0x72, 0xFB, 0x39, 0x21, 0x67, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x70, 0xFD, 0x41, 0x68,
|
||||||
|
0xFB, 0x2C, 0x21, 0x6F, 0xFC, 0x21, 0x63, 0xFD, 0x41, 0x69, 0xF6, 0x72, 0x21, 0x6E, 0xFC, 0x41, 0x72, 0xF6, 0x6B,
|
||||||
|
0x23, 0x6C, 0x6D, 0x65, 0xF2, 0xF9, 0xFC, 0x41, 0xB3, 0xFC, 0x0A, 0x42, 0x6F, 0xC3, 0xFC, 0x06, 0xFF, 0xFC, 0x21,
|
||||||
|
0x73, 0xF9, 0xA0, 0x05, 0x22, 0x21, 0x65, 0xFD, 0x42, 0x6A, 0x6E, 0xFF, 0xFD, 0xF9, 0x78, 0x21, 0x6F, 0xF9, 0x42,
|
||||||
|
0x65, 0x69, 0xFF, 0xFD, 0xF5, 0x0B, 0x24, 0x65, 0x61, 0x69, 0x74, 0xBC, 0xD4, 0xE6, 0xF9, 0x41, 0x74, 0xFF, 0xA2,
|
||||||
|
0xC4, 0x02, 0xB1, 0x62, 0x63, 0x6E, 0x74, 0xFF, 0x9B, 0xFF, 0xA2, 0xFF, 0xF3, 0xFF, 0xFC, 0x41, 0x74, 0xF6, 0xD3,
|
||||||
|
0x21, 0x73, 0xFC, 0xA1, 0x01, 0x82, 0x65, 0xFD, 0x42, 0x6F, 0xC3, 0xF8, 0xA2, 0xFA, 0x85, 0x41, 0x69, 0xF5, 0xE2,
|
||||||
|
0x21, 0x65, 0xFC, 0x41, 0xA9, 0xFD, 0x00, 0x42, 0x65, 0xC3, 0xFC, 0xFC, 0xFF, 0xFC, 0x41, 0x62, 0xF5, 0xB3, 0xA4,
|
||||||
|
0x09, 0xA3, 0x6D, 0x63, 0x6A, 0x72, 0xE3, 0xEE, 0xF5, 0xFC, 0x41, 0xAD, 0xFA, 0xC6, 0x42, 0x69, 0xC3, 0xFA, 0xC2,
|
||||||
|
0xFF, 0xFC, 0xA1, 0x09, 0xA3, 0x6D, 0xF9, 0xA0, 0x09, 0xA3, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xFC, 0x2F, 0xFE, 0xC3,
|
||||||
|
0xF4, 0x14, 0xF4, 0x14, 0xA1, 0x09, 0xA3, 0x6A, 0xF3, 0x43, 0x61, 0xC3, 0x65, 0xF4, 0x02, 0xF5, 0xF7, 0xF4, 0x02,
|
||||||
|
0x21, 0x72, 0xF6, 0x21, 0x65, 0xFD, 0xA1, 0x09, 0xA3, 0x6D, 0xFD, 0xA0, 0x09, 0xD3, 0xC1, 0x09, 0xD3, 0x6A, 0xF6,
|
||||||
|
0x40, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xF7, 0xF7, 0xF7, 0xFA, 0xF7, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75,
|
||||||
|
0xC3, 0xFF, 0x85, 0xFF, 0xA7, 0xFF, 0xBD, 0xFF, 0xC2, 0xFF, 0xD2, 0xFF, 0xE7, 0xFF, 0xF5, 0x41, 0x73, 0xF6, 0x3B,
|
||||||
|
0x42, 0x6C, 0x75, 0xF6, 0x37, 0xFF, 0xFC, 0x41, 0x6C, 0xF6, 0x30, 0x41, 0x72, 0xFF, 0x59, 0x41, 0x6D, 0xF6, 0x28,
|
||||||
|
0x44, 0xA1, 0xAD, 0xB3, 0xBA, 0xFF, 0xF4, 0xF6, 0x27, 0xFF, 0xF8, 0xFF, 0xFC, 0xC5, 0x01, 0x82, 0x61, 0xC3, 0x69,
|
||||||
|
0x6F, 0x75, 0xFF, 0xE0, 0xFF, 0xF3, 0xF6, 0x1A, 0xFF, 0xEB, 0xFF, 0xEF, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF,
|
||||||
|
0xA0, 0xFF, 0xA0, 0xFF, 0xA0, 0xFF, 0xA0, 0xFF, 0xA0, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xFF, 0xDE,
|
||||||
|
0xFF, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0xFF, 0xF0, 0x42, 0x6E, 0x78, 0xFF, 0x8E, 0xFF, 0xEA,
|
||||||
|
0xA0, 0x01, 0x82, 0x41, 0x72, 0xF5, 0x3F, 0x41, 0x72, 0xF5, 0x0B, 0x22, 0x61, 0x6F, 0xF8, 0xFC, 0x42, 0x6E, 0x70,
|
||||||
|
0xF4, 0xEA, 0xF4, 0xEA, 0x21, 0x65, 0xF9, 0x41, 0x70, 0xF4, 0xE0, 0x22, 0x61, 0x6F, 0xFC, 0xFC, 0x41, 0x61, 0xFA,
|
||||||
|
0xE0, 0x21, 0x75, 0xFC, 0x41, 0x6D, 0xFF, 0x00, 0x21, 0xA1, 0xFC, 0x41, 0x65, 0xF4, 0xBD, 0x22, 0xC3, 0x69, 0xF9,
|
||||||
|
0xFC, 0x41, 0x69, 0xFB, 0x63, 0x21, 0x6C, 0xFC, 0x42, 0x6D, 0x63, 0xF4, 0x9C, 0xF3, 0x1F, 0x22, 0x61, 0x69, 0xF6,
|
||||||
|
0xF9, 0x41, 0x61, 0xFE, 0xDD, 0x21, 0x67, 0xFC, 0x41, 0x6C, 0xF4, 0x89, 0x42, 0x61, 0x69, 0xFB, 0x45, 0xF4, 0xEA,
|
||||||
|
0x43, 0x63, 0x68, 0x6E, 0xF4, 0xEC, 0xFF, 0xD2, 0xF4, 0xFD, 0x21, 0x65, 0xF6, 0x24, 0x61, 0x65, 0x6C, 0x72, 0xE5,
|
||||||
|
0xE8, 0xEC, 0xFD, 0x41, 0x63, 0xF4, 0x85, 0x21, 0x65, 0xFC, 0x41, 0xB3, 0xF4, 0x72, 0x21, 0xC3, 0xFC, 0x41, 0x67,
|
||||||
|
0xF4, 0x5A, 0x21, 0x75, 0xFC, 0x22, 0x6D, 0x72, 0xF6, 0xFD, 0x41, 0x69, 0xF4, 0x6E, 0x41, 0x62, 0xF2, 0xCD, 0x21,
|
||||||
|
0x69, 0xFC, 0x21, 0x76, 0xFD, 0x21, 0x6F, 0xFD, 0xCC, 0x09, 0xA3, 0x62, 0x63, 0x64, 0x67, 0x6C, 0x6E, 0x70, 0x66,
|
||||||
|
0x72, 0x73, 0x74, 0x6D, 0xFF, 0x6B, 0xFF, 0x77, 0xFF, 0x7E, 0xFF, 0x87, 0xFF, 0x95, 0xFF, 0xA8, 0xFF, 0xCC, 0xFF,
|
||||||
|
0xD9, 0xFF, 0xEA, 0xFF, 0xEF, 0xFA, 0x67, 0xFF, 0xFD, 0xC1, 0x02, 0x91, 0x69, 0xF4, 0x16, 0x21, 0x63, 0xFA, 0x21,
|
||||||
|
0x69, 0xFD, 0x41, 0x64, 0xF4, 0x78, 0x22, 0x75, 0x65, 0xFC, 0xAC, 0x41, 0x6F, 0xFA, 0x27, 0x42, 0x63, 0x61, 0xFF,
|
||||||
|
0xFC, 0xF8, 0x70, 0x41, 0x76, 0xF2, 0x79, 0x21, 0xAD, 0xFC, 0x42, 0x69, 0xC3, 0xF4, 0x24, 0xFF, 0xFD, 0x21, 0x75,
|
||||||
|
0xF9, 0xC2, 0x02, 0x91, 0x61, 0x68, 0xFF, 0x7D, 0xFA, 0x12, 0xC6, 0x09, 0xA3, 0x66, 0x6C, 0x6E, 0x71, 0x78, 0x76,
|
||||||
|
0xFF, 0xCF, 0xFF, 0xD6, 0xFF, 0xDF, 0xFF, 0xF4, 0xFF, 0xF7, 0xFE, 0x17, 0x42, 0x6F, 0x61, 0xF2, 0x4A, 0xF2, 0x4A,
|
||||||
|
0x21, 0x75, 0xF9, 0x41, 0x6C, 0xFF, 0x2D, 0x21, 0x61, 0xFC, 0x21, 0x75, 0xFD, 0xC3, 0x09, 0xA3, 0x63, 0x67, 0x6E,
|
||||||
|
0xFF, 0xF3, 0xFF, 0xFD, 0xF3, 0xB3, 0x41, 0x73, 0xFB, 0x5F, 0x44, 0x74, 0x61, 0xC3, 0x65, 0xF3, 0xA3, 0xF2, 0x26,
|
||||||
|
0xF4, 0x1B, 0xF2, 0x26, 0x43, 0x6F, 0x61, 0x6C, 0xF2, 0x19, 0xF2, 0x19, 0xFF, 0xF3, 0x42, 0x63, 0x74, 0xF2, 0x0F,
|
||||||
|
0xF2, 0x0F, 0x21, 0x6E, 0xF9, 0x22, 0x75, 0x65, 0xEC, 0xFD, 0x41, 0x73, 0xF2, 0x00, 0x21, 0x6E, 0xFC, 0x21, 0x65,
|
||||||
|
0xFD, 0x41, 0x6F, 0xF8, 0x25, 0xA4, 0x09, 0xA3, 0x62, 0x63, 0x66, 0x70, 0xC8, 0xED, 0xF9, 0xFC, 0x41, 0x7A, 0xF1,
|
||||||
|
0xE7, 0x21, 0x69, 0xFC, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0xA1, 0x09, 0xA3, 0x74, 0xFD, 0x41, 0x6D, 0xF4, 0x2B,
|
||||||
|
0x21, 0x69, 0xFC, 0xA1, 0x09, 0xD3, 0x6E, 0xFD, 0x41, 0x74, 0xF4, 0x1F, 0x21, 0x69, 0xFC, 0xA1, 0x09, 0xD3, 0x64,
|
||||||
|
0xFD, 0x41, 0x69, 0xF4, 0x16, 0xA1, 0x09, 0xD3, 0x74, 0xFC, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, 0xE6, 0xFF,
|
||||||
|
0xF2, 0xFD, 0xC7, 0xFD, 0xC7, 0xFF, 0xFB, 0xA0, 0x0C, 0x13, 0x21, 0x67, 0xFD, 0x22, 0x63, 0x74, 0xFA, 0xFA, 0x21,
|
||||||
|
0x70, 0xF5, 0x22, 0x70, 0x6D, 0xF8, 0xFD, 0xA2, 0x05, 0x22, 0x6F, 0x75, 0xF0, 0xFB, 0xA0, 0x0A, 0x92, 0xA0, 0x0A,
|
||||||
|
0xB3, 0xA0, 0x0B, 0x13, 0x23, 0xA1, 0xA9, 0xB3, 0xFD, 0xFD, 0xFD, 0x24, 0x61, 0x65, 0x6F, 0xC3, 0xF6, 0xF6, 0xF6,
|
||||||
|
0xF9, 0xA1, 0x0A, 0xB3, 0x73, 0xF7, 0xA0, 0x0A, 0xE3, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD,
|
||||||
|
0xFD, 0x28, 0x72, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xCD, 0xD4, 0xD7, 0xED, 0xD7, 0xD7, 0xD7, 0xF5, 0x21,
|
||||||
|
0x72, 0xEF, 0x21, 0x65, 0xFD, 0x48, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0x74, 0xFD, 0xE7, 0xFE, 0x87, 0xFE,
|
||||||
|
0xE8, 0xFF, 0x11, 0xFF, 0x55, 0xFF, 0x6D, 0xFF, 0x93, 0xFF, 0xFD, 0x21, 0x6E, 0xE7, 0x58, 0x62, 0x63, 0x64, 0x66,
|
||||||
|
0x67, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x68, 0x61, 0x65,
|
||||||
|
0x69, 0xF2, 0x79, 0xF3, 0xD1, 0xF4, 0x53, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, 0xC4,
|
||||||
|
0xF4, 0x5A, 0xF7, 0x4E, 0xF4, 0x5A, 0xF9, 0xC2, 0xFB, 0x92, 0xFC, 0x33, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4, 0x5A, 0xF4,
|
||||||
|
0x5A, 0xF4, 0x5A, 0xFC, 0x53, 0xFC, 0xC1, 0xFD, 0xC4, 0xFF, 0xFD, 0x41, 0x63, 0xFC, 0x16, 0xA0, 0x0D, 0x22, 0x21,
|
||||||
|
0x72, 0xFD, 0x21, 0x6F, 0xFD, 0xC3, 0x00, 0x71, 0x2E, 0x69, 0x65, 0xF0, 0x3F, 0xFF, 0xF3, 0xFF, 0xFD, 0xC3, 0x00,
|
||||||
|
0x71, 0x2E, 0x7A, 0x73, 0xF0, 0x33, 0xF0, 0x39, 0xF0, 0x39, 0xC1, 0x00, 0x71, 0x2E, 0xF0, 0x27, 0xD6, 0x00, 0x81,
|
||||||
|
0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77,
|
||||||
|
0x78, 0x79, 0x7A, 0xF0, 0x21, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24,
|
||||||
|
0xF0, 0x24, 0xF3, 0xE6, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF3, 0xE6, 0xF0, 0x24, 0xF0, 0x24, 0xF0,
|
||||||
|
0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0xF0, 0x24, 0x41, 0x74, 0xF0, 0x69, 0x21, 0x70, 0xFC, 0xD7, 0x00, 0x91,
|
||||||
|
0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77,
|
||||||
|
0x78, 0x79, 0x7A, 0x65, 0xEF, 0xD5, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0,
|
||||||
|
0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01,
|
||||||
|
0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xF0, 0x01, 0xFF, 0xFD, 0x42, 0x6F, 0x70, 0xF3, 0xA8, 0xFF, 0xB1,
|
||||||
|
0x41, 0x6E, 0xFB, 0x50, 0xD8, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
|
||||||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x69, 0x6F, 0xEF, 0x82, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF,
|
||||||
|
0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE,
|
||||||
|
0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xEF, 0xAE, 0xFF,
|
||||||
|
0xF5, 0xFF, 0xFC, 0x41, 0x74, 0xF1, 0xF3, 0x21, 0x70, 0xFC, 0x41, 0x6F, 0xFC, 0x90, 0x21, 0x6C, 0xFC, 0x41, 0x74,
|
||||||
|
0xF0, 0x5B, 0x41, 0x6D, 0xFA, 0xEF, 0x41, 0x61, 0xEF, 0x9F, 0x21, 0x72, 0xFC, 0x21, 0x74, 0xFD, 0x25, 0x6D, 0x75,
|
||||||
|
0x72, 0x73, 0x6E, 0xE4, 0xEB, 0xEE, 0xF2, 0xFD, 0xA0, 0x02, 0x32, 0x21, 0x61, 0xFD, 0x41, 0x2E, 0xEF, 0x06, 0xA1,
|
||||||
|
0x02, 0x32, 0x73, 0xFC, 0x22, 0x6F, 0x61, 0xF1, 0xFB, 0x41, 0x64, 0xEF, 0x88, 0x23, 0x63, 0x67, 0x72, 0xEB, 0xF7,
|
||||||
|
0xFC, 0x21, 0x6F, 0xE1, 0x41, 0x75, 0xEF, 0x68, 0x21, 0x72, 0xFC, 0x42, 0x64, 0x73, 0xFF, 0xFD, 0xF2, 0xE9, 0x22,
|
||||||
|
0x6C, 0x61, 0xEF, 0xF9, 0x41, 0x6C, 0xEF, 0x64, 0x21, 0x61, 0xFC, 0xA0, 0x07, 0x51, 0x21, 0x61, 0xFD, 0x21, 0x65,
|
||||||
|
0xFD, 0xA1, 0x04, 0x72, 0x72, 0xFD, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, 0xFB, 0xEF, 0xD7, 0xEF, 0xD7, 0xEF,
|
||||||
|
0xD7, 0xEF, 0xD7, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xEF, 0xC1, 0xEF, 0xC4, 0xEF, 0xC4, 0xEF, 0xC4,
|
||||||
|
0xEF, 0xC4, 0xEF, 0xC4, 0xFF, 0xF0, 0x21, 0x69, 0xEA, 0x21, 0x74, 0xFD, 0x22, 0x66, 0x6E, 0xC3, 0xFD, 0x42, 0x68,
|
||||||
|
0x6F, 0xF2, 0x5F, 0xEF, 0xB4, 0x21, 0x6E, 0xF9, 0xA0, 0x06, 0xF3, 0xA0, 0x07, 0x23, 0x25, 0xA1, 0xA9, 0xAD, 0xB3,
|
||||||
|
0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x48, 0x72, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xF3, 0x4F, 0xF3, 0x26,
|
||||||
|
0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xF5, 0x21, 0x72, 0xE7, 0x21, 0x65, 0xFD, 0x41,
|
||||||
|
0x6C, 0xFA, 0x21, 0x41, 0x6F, 0xF2, 0x6E, 0x24, 0x61, 0x62, 0x63, 0x74, 0xC5, 0xF5, 0xF8, 0xFC, 0x41, 0x6F, 0xEF,
|
||||||
|
0x25, 0x21, 0x63, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0xA9, 0xFD,
|
||||||
|
0xDC, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77,
|
||||||
|
0x78, 0x79, 0x7A, 0x68, 0x6C, 0x72, 0x6F, 0x61, 0x75, 0x65, 0x69, 0xC3, 0xEE, 0x30, 0xEE, 0x33, 0xEE, 0x39, 0xEE,
|
||||||
|
0x33, 0xEE, 0x42, 0xEE, 0x47, 0xEE, 0x33, 0xEE, 0x33, 0xEE, 0x47, 0xFD, 0xF1, 0xEE, 0x4C, 0xEE, 0x33, 0xEE, 0x33,
|
||||||
|
0xFD, 0xFD, 0xEE, 0x33, 0xEE, 0x33, 0xEE, 0x33, 0xEE, 0x33, 0xFE, 0x09, 0xFE, 0x0F, 0xFE, 0x5B, 0xFE, 0xAE, 0xFF,
|
||||||
|
0x19, 0xFF, 0x3C, 0xFF, 0x54, 0xFF, 0x9A, 0xFF, 0xE1, 0xFF, 0xFD, 0x41, 0x74, 0xF2, 0xB2, 0x21, 0x6E, 0xFC, 0x21,
|
||||||
|
0x65, 0xFD, 0x21, 0x69, 0xFD, 0xA1, 0x04, 0xA2, 0x6D, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xF1,
|
||||||
|
0x95, 0xF1, 0xD1, 0xF1, 0xD1, 0xFF, 0xFB, 0xF1, 0xD1, 0xF1, 0xD1, 0xF1, 0xD7, 0x21, 0x61, 0xEA, 0xA0, 0x07, 0xC1,
|
||||||
|
0xA0, 0x07, 0xD2, 0xA0, 0x07, 0xF2, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0x27, 0x68,
|
||||||
|
0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xEC, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xF5, 0x21, 0x6F, 0xF1, 0x21, 0x74, 0xFD,
|
||||||
|
0x42, 0x61, 0x6F, 0xFF, 0xFD, 0xEE, 0xA8, 0x21, 0x6D, 0xF9, 0xA0, 0x05, 0x92, 0x21, 0x65, 0xFD, 0x21, 0x64, 0xFD,
|
||||||
|
0xA0, 0x09, 0x32, 0x21, 0x61, 0xFD, 0x22, 0x72, 0x6C, 0xF7, 0xFD, 0xA0, 0x02, 0x12, 0x21, 0x69, 0xFD, 0x21, 0x63,
|
||||||
|
0xFD, 0x21, 0x75, 0xFD, 0x41, 0x61, 0xEF, 0x4A, 0x22, 0x68, 0x6C, 0xF9, 0xFC, 0x22, 0x61, 0x65, 0xE0, 0xE0, 0x21,
|
||||||
|
0x68, 0xFB, 0x21, 0x74, 0xE3, 0x22, 0x63, 0x72, 0xFA, 0xFD, 0x23, 0xB3, 0xA1, 0xA9, 0xD6, 0xEB, 0xFB, 0x21, 0x6A,
|
||||||
|
0xC0, 0x21, 0x6C, 0xBD, 0x21, 0x74, 0xBA, 0x21, 0x6E, 0xFD, 0x22, 0x6C, 0x65, 0xF7, 0xFD, 0xA0, 0x02, 0x11, 0x21,
|
||||||
|
0x6A, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x69, 0xFF, 0xA6, 0x21, 0x6E, 0xFC, 0x21, 0x61, 0xFD, 0x41, 0x6D, 0xFF, 0x9C,
|
||||||
|
0x21, 0x61, 0xFC, 0x46, 0x64, 0x65, 0x69, 0x74, 0x67, 0x6E, 0xFF, 0x98, 0xFF, 0xD5, 0xFF, 0xE1, 0xFF, 0xEC, 0xFF,
|
||||||
|
0xF6, 0xFF, 0xFD, 0x42, 0x63, 0x7A, 0xFF, 0x82, 0xFF, 0x82, 0x41, 0x6E, 0xFF, 0x7B, 0x21, 0x65, 0xFC, 0x22, 0x65,
|
||||||
|
0x69, 0xF2, 0xFD, 0x21, 0x64, 0xFB, 0x41, 0x67, 0xFF, 0x6C, 0x21, 0x69, 0xFC, 0x41, 0x72, 0xFF, 0x65, 0x21, 0x74,
|
||||||
|
0xFC, 0x23, 0x65, 0x6C, 0x73, 0xEF, 0xF6, 0xFD, 0xA0, 0x09, 0x12, 0x21, 0x73, 0xFD, 0x41, 0x70, 0xFF, 0x51, 0x21,
|
||||||
|
0xBA, 0xFC, 0x23, 0x61, 0x75, 0xC3, 0xF6, 0xF9, 0xFD, 0x21, 0x6F, 0xDE, 0xC2, 0x09, 0x12, 0x63, 0x64, 0xFF, 0x91,
|
||||||
|
0xFF, 0x91, 0x23, 0xA1, 0xA9, 0xB3, 0xE0, 0xE0, 0xE0, 0x45, 0x61, 0xC3, 0x65, 0x6F, 0x6C, 0xFF, 0xF0, 0xFF, 0xF9,
|
||||||
|
0xFF, 0xD9, 0xFF, 0xD9, 0xFF, 0x81, 0x41, 0x69, 0xFF, 0x84, 0x21, 0x72, 0xFC, 0x41, 0x65, 0xFF, 0x6A, 0x21, 0x63,
|
||||||
|
0xFC, 0x42, 0xA1, 0xA9, 0xFF, 0x12, 0xFF, 0x12, 0x43, 0x61, 0xC3, 0x69, 0xFF, 0x0B, 0xFF, 0xF9, 0xFF, 0x0B, 0x41,
|
||||||
|
0xA9, 0xFF, 0x01, 0x42, 0x65, 0xC3, 0xFE, 0xFD, 0xFF, 0xFC, 0x41, 0x67, 0xFF, 0x0A, 0x21, 0x65, 0xFC, 0x4B, 0x72,
|
||||||
|
0x62, 0x63, 0x64, 0x6C, 0x70, 0x6E, 0x76, 0x78, 0x79, 0x73, 0xFF, 0x5A, 0xFF, 0x91, 0xFF, 0xA5, 0xFF, 0xAC, 0xFF,
|
||||||
|
0xBF, 0xFF, 0xD3, 0xFF, 0xDA, 0xFF, 0xE4, 0xFF, 0x49, 0xFF, 0xF2, 0xFF, 0xFD, 0xA0, 0x08, 0xC3, 0x21, 0x64, 0xFD,
|
||||||
|
0x21, 0x69, 0xFD, 0x21, 0x72, 0xF7, 0x22, 0x72, 0x6F, 0xFA, 0xFD, 0x22, 0x7A, 0x63, 0xEF, 0xEF, 0x21, 0x69, 0xFB,
|
||||||
|
0x21, 0x6C, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x72, 0xFD, 0x23, 0xA1, 0xA9, 0xB3, 0xDE, 0xDE, 0xDE, 0x22, 0x61, 0xC3,
|
||||||
|
0xD7, 0xF9, 0x23, 0x61, 0x65, 0x6F, 0xD2, 0xD2, 0xD2, 0x21, 0xAD, 0xF9, 0x22, 0x69, 0xC3, 0xF1, 0xFD, 0xA0, 0x08,
|
||||||
|
0xF2, 0x21, 0x73, 0xC0, 0x22, 0x61, 0x69, 0xFA, 0xFD, 0x21, 0x75, 0xFB, 0x21, 0xAD, 0xC6, 0x22, 0x69, 0xC3, 0xC3,
|
||||||
|
0xFD, 0x42, 0x76, 0x6E, 0xFF, 0xAD, 0xFF, 0xFB, 0x42, 0x61, 0x69, 0xED, 0xDD, 0xFF, 0xF9, 0x41, 0x6C, 0xFE, 0x80,
|
||||||
|
0x42, 0x72, 0x65, 0xFE, 0x7C, 0xFF, 0xFC, 0x21, 0x67, 0xF9, 0x41, 0x76, 0xFF, 0x91, 0x21, 0x69, 0xFC, 0x21, 0x73,
|
||||||
|
0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x72, 0xFD, 0x41, 0x6C, 0xFF, 0x7E, 0x21, 0x6C, 0xFC, 0x21, 0x6F,
|
||||||
|
0xFD, 0x21, 0x72, 0xFD, 0x41, 0x72, 0xFE, 0x52, 0x43, 0x61, 0x65, 0x74, 0xF1, 0xB9, 0xF1, 0xB9, 0xFF, 0xFC, 0x41,
|
||||||
|
0x73, 0xFF, 0xA0, 0x21, 0x65, 0xFC, 0x41, 0x6E, 0xFF, 0x5C, 0x21, 0x75, 0xFC, 0x21, 0xB3, 0xF9, 0x22, 0xC3, 0x6F,
|
||||||
|
0xFD, 0xF6, 0x4D, 0x62, 0x63, 0x66, 0x67, 0x68, 0x6C, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x79, 0x7A, 0xFF, 0x59, 0xFF,
|
||||||
|
0x6C, 0xFF, 0x85, 0xFF, 0x95, 0xFE, 0x37, 0xFF, 0xA7, 0xFF, 0xB9, 0xFF, 0xCC, 0xFF, 0xD9, 0xFF, 0xE0, 0xFF, 0xEE,
|
||||||
|
0xFF, 0xF5, 0xFF, 0xFB, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xFF, 0x25, 0xFF, 0x47, 0xFF, 0x25, 0xFF, 0x25, 0x21, 0x6A,
|
||||||
|
0xF3, 0x41, 0x6A, 0xFF, 0x43, 0x21, 0xA9, 0xFC, 0x41, 0xB1, 0xFD, 0xEF, 0x21, 0xC3, 0xFC, 0x21, 0xA9, 0xFD, 0x22,
|
||||||
|
0x65, 0xC3, 0xFA, 0xFD, 0x23, 0x65, 0xC3, 0x70, 0xE7, 0xEE, 0xFB, 0x41, 0x6E, 0xFD, 0xD9, 0x21, 0xA9, 0xFC, 0x22,
|
||||||
|
0x65, 0xC3, 0xF9, 0xFD, 0x21, 0x72, 0xFB, 0x21, 0x66, 0xFD, 0xC6, 0x02, 0x11, 0x6E, 0x72, 0x62, 0x64, 0x6D, 0x73,
|
||||||
|
0xFE, 0x04, 0xFE, 0x04, 0xFE, 0x04, 0xFE, 0x04, 0xFE, 0x04, 0xFE, 0x04, 0x46, 0x6E, 0x72, 0x62, 0x64, 0x6D, 0x73,
|
||||||
|
0xFD, 0xEF, 0xFD, 0xEF, 0xFD, 0xEF, 0xFD, 0xEF, 0xFD, 0xEF, 0xFD, 0xEF, 0x21, 0xA1, 0xED, 0xC6, 0x09, 0x12, 0x6E,
|
||||||
|
0x72, 0x62, 0x64, 0x6D, 0x73, 0xFE, 0x31, 0xFE, 0x31, 0xFE, 0x31, 0xFE, 0x31, 0xFE, 0x31, 0xFE, 0x31, 0x42, 0xA1,
|
||||||
|
0xB3, 0xFF, 0xEB, 0xFE, 0x1C, 0x44, 0x61, 0xC3, 0x65, 0x6F, 0xFE, 0x15, 0xFE, 0x35, 0xFE, 0x15, 0xFE, 0x15, 0x44,
|
||||||
|
0x6F, 0x61, 0xC3, 0x68, 0xFE, 0x08, 0xFF, 0xD7, 0xFF, 0xEC, 0xFF, 0xF3, 0x41, 0xA9, 0xFE, 0x85, 0x42, 0x65, 0xC3,
|
||||||
|
0xFE, 0x81, 0xFF, 0xFC, 0xA1, 0x05, 0x92, 0x75, 0xF9, 0x41, 0x66, 0xFD, 0x42, 0x42, 0x63, 0x71, 0xFD, 0x3E, 0xFD,
|
||||||
|
0x3E, 0x22, 0x69, 0x75, 0xF5, 0xF9, 0x41, 0x62, 0xFD, 0xCD, 0x21, 0x6D, 0xFC, 0x21, 0x6F, 0xFD, 0x41, 0x7A, 0xFD,
|
||||||
|
0x28, 0x42, 0x63, 0x6E, 0xFD, 0x75, 0xFF, 0xFC, 0x21, 0x61, 0xF9, 0x21, 0x72, 0xFD, 0x44, 0x61, 0x65, 0x69, 0x75,
|
||||||
|
0xFD, 0x17, 0xFF, 0xFD, 0xFD, 0x9C, 0xFD, 0x7B, 0x41, 0x69, 0xFD, 0x4D, 0x41, 0x69, 0xFD, 0x8B, 0x43, 0x62, 0x63,
|
||||||
|
0x6C, 0xFF, 0xF8, 0xFD, 0x5C, 0xFF, 0xFC, 0x41, 0x73, 0xFC, 0xF8, 0x41, 0x63, 0xFC, 0xF4, 0x22, 0x65, 0x75, 0xF8,
|
||||||
|
0xFC, 0x43, 0x61, 0x69, 0x72, 0xFF, 0xE9, 0xFD, 0x4F, 0xFF, 0xFB, 0x23, 0x63, 0x70, 0x74, 0xB6, 0xCA, 0xF6, 0x42,
|
||||||
|
0x63, 0x74, 0xFC, 0xF1, 0xFC, 0xEE, 0x4A, 0x6D, 0x6E, 0x6F, 0x61, 0xC3, 0x63, 0x71, 0x64, 0x73, 0x72, 0xFF, 0x07,
|
||||||
|
0xFF, 0x1D, 0xFD, 0x24, 0xFF, 0x20, 0xFF, 0x48, 0xFF, 0x74, 0xFF, 0x8C, 0xFF, 0x9C, 0xFF, 0xF2, 0xFF, 0xF9, 0x42,
|
||||||
|
0x72, 0x6F, 0xFD, 0x05, 0xFC, 0xF7, 0x42, 0x61, 0x6F, 0xFC, 0xFE, 0xFC, 0xFE, 0x22, 0x65, 0x69, 0xF2, 0xF9, 0x41,
|
||||||
|
0x74, 0xFC, 0xF2, 0x21, 0x72, 0xFC, 0x41, 0xA1, 0xFC, 0xDD, 0x42, 0x61, 0xC3, 0xFC, 0xD9, 0xFF, 0xFC, 0x42, 0x6E,
|
||||||
|
0x75, 0xFC, 0xE0, 0xFF, 0xF9, 0x41, 0xB3, 0xFD, 0x0D, 0x42, 0x6F, 0xC3, 0xFD, 0x09, 0xFF, 0xFC, 0x21, 0x69, 0xF9,
|
||||||
|
0x21, 0x73, 0xFD, 0x21, 0x75, 0xFD, 0x42, 0x67, 0x6E, 0xFF, 0x6E, 0xFC, 0x74, 0x41, 0x65, 0xFF, 0x75, 0x42, 0x6F,
|
||||||
|
0x72, 0xFC, 0xEE, 0xFF, 0xFC, 0x22, 0x61, 0x70, 0xEE, 0xF9, 0x41, 0x72, 0xFD, 0x0C, 0x41, 0x73, 0xFC, 0x9F, 0x21,
|
||||||
|
0x75, 0xFC, 0x44, 0x65, 0x6C, 0x6F, 0x72, 0xFC, 0x9B, 0xFF, 0x4C, 0xFF, 0xF5, 0xFF, 0xFD, 0x42, 0x63, 0x74, 0xFC,
|
||||||
|
0xEE, 0xFC, 0xEE, 0x21, 0x6E, 0xF9, 0x41, 0x63, 0xFC, 0x8C, 0x41, 0x72, 0xFC, 0x7D, 0xC1, 0x05, 0x92, 0x61, 0xFC,
|
||||||
|
0x97, 0x41, 0x72, 0xFC, 0x91, 0x24, 0x65, 0x61, 0x6C, 0x6F, 0xEE, 0xF2, 0xF6, 0xFC, 0x41, 0x62, 0xFC, 0x20, 0x21,
|
||||||
|
0x69, 0xFC, 0x41, 0x63, 0xFC, 0x5F, 0x41, 0x61, 0xFC, 0x58, 0x22, 0x65, 0x74, 0xF8, 0xFC, 0x42, 0x67, 0x72, 0xFD,
|
||||||
|
0xCE, 0xFC, 0x20, 0x41, 0x78, 0xFC, 0x05, 0x23, 0x65, 0x6F, 0x75, 0xF5, 0xFC, 0xE1, 0x41, 0x65, 0xFC, 0x95, 0x47,
|
||||||
|
0x63, 0x65, 0x66, 0x68, 0x73, 0x74, 0x76, 0xFF, 0xA4, 0xFF, 0xB8, 0xFF, 0xCD, 0xFF, 0xDA, 0xFF, 0xE5, 0xFF, 0xF5,
|
||||||
|
0xFF, 0xFC, 0x41, 0x6E, 0xFC, 0x31, 0x21, 0x65, 0xFC, 0x21, 0x74, 0xFD, 0x47, 0x64, 0x65, 0x67, 0x6C, 0x6D, 0x6E,
|
||||||
|
0x73, 0xFF, 0x30, 0xFF, 0x39, 0xFF, 0x47, 0xFF, 0x5F, 0xFF, 0x74, 0xFF, 0xE0, 0xFF, 0xFD, 0x43, 0x72, 0x73, 0x6E,
|
||||||
|
0xFC, 0x69, 0xFC, 0x69, 0xFC, 0x69, 0x21, 0x61, 0xF6, 0x41, 0x6C, 0xFC, 0x04, 0x21, 0x6C, 0xFC, 0x41, 0x61, 0xFD,
|
||||||
|
0xE7, 0x21, 0x74, 0xFC, 0xA0, 0x09, 0x53, 0x22, 0x63, 0x71, 0xFD, 0xFD, 0x22, 0x73, 0x69, 0xF5, 0xFB, 0xC1, 0x05,
|
||||||
|
0x92, 0x61, 0xFB, 0x98, 0x43, 0x6E, 0x72, 0x73, 0xFB, 0x92, 0xFF, 0xFA, 0xFB, 0x92, 0x43, 0x6E, 0x72, 0x73, 0xFB,
|
||||||
|
0x88, 0xFB, 0x88, 0xFB, 0x88, 0x42, 0xA9, 0xB3, 0xFF, 0xF6, 0xFB, 0x7E, 0x45, 0x72, 0x64, 0x65, 0xC3, 0x6D, 0xFB,
|
||||||
|
0x77, 0xFB, 0x77, 0xFF, 0xE5, 0xFF, 0xF9, 0xFB, 0x77, 0x42, 0x6E, 0x73, 0xFB, 0x67, 0xFB, 0x67, 0x42, 0xA1, 0xAD,
|
||||||
|
0xFB, 0x60, 0xFF, 0xC8, 0x45, 0x69, 0x61, 0x65, 0x6F, 0xC3, 0xFF, 0xE2, 0xFF, 0xF2, 0xFB, 0x59, 0xFB, 0x59, 0xFF,
|
||||||
|
0xF9, 0x41, 0xA1, 0xFB, 0x49, 0x42, 0x61, 0xC3, 0xFB, 0x45, 0xFF, 0xFC, 0x21, 0xB1, 0xF9, 0x41, 0x62, 0xFB, 0x9C,
|
||||||
|
0x47, 0x64, 0x65, 0x62, 0x73, 0x6E, 0xC3, 0x72, 0xFF, 0x81, 0xFF, 0x88, 0xFF, 0x9A, 0xFF, 0x8F, 0xFF, 0xDE, 0xFF,
|
||||||
|
0xF9, 0xFF, 0xFC, 0x46, 0xC3, 0x6F, 0x61, 0x65, 0x69, 0x75, 0xFB, 0x5A, 0xFC, 0x32, 0xFD, 0x07, 0xFE, 0x4E, 0xFF,
|
||||||
|
0x4B, 0xFF, 0xEA, 0x41, 0x69, 0xFB, 0x5F, 0x21, 0x74, 0xFC, 0x21, 0x73, 0xFD, 0x45, 0x63, 0x6E, 0x72, 0x73, 0x69,
|
||||||
|
0xFA, 0xCE, 0xF4, 0xA7, 0xFB, 0x01, 0xFF, 0xE3, 0xFF, 0xFD, 0xA0, 0x11, 0x72, 0x21, 0x63, 0xFD, 0x21, 0xA9, 0xFD,
|
||||||
|
0x22, 0x65, 0xC3, 0xFA, 0xFD, 0x21, 0x6C, 0xFB, 0x21, 0x65, 0xFD, 0xD8, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66,
|
||||||
|
0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x72, 0x65, 0x69,
|
||||||
|
0xE8, 0x5B, 0xE8, 0x5E, 0xE8, 0x64, 0xE8, 0x5E, 0xE8, 0x6D, 0xE8, 0x72, 0xE8, 0x5E, 0xE8, 0x5E, 0xE8, 0x5E, 0xE8,
|
||||||
|
0x5E, 0xE8, 0x72, 0xE8, 0x5E, 0xE8, 0x77, 0xE8, 0x5E, 0xE8, 0x5E, 0xE8, 0x80, 0xE8, 0x5E, 0xE8, 0x5E, 0xE8, 0x5E,
|
||||||
|
0xE8, 0x5E, 0xE8, 0x5E, 0xE8, 0x8A, 0xFF, 0xDC, 0xFF, 0xFD, 0x42, 0x6D, 0x72, 0xF4, 0x38, 0xF3, 0xDE, 0x41, 0x69,
|
||||||
|
0xF3, 0xD3, 0x43, 0x6C, 0x73, 0x74, 0xF9, 0xB2, 0xFF, 0xFC, 0xF9, 0xB2, 0x42, 0x6E, 0x74, 0xF9, 0xA8, 0xF9, 0xA8,
|
||||||
|
0x41, 0x69, 0xEB, 0x9B, 0x21, 0x72, 0xFC, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD,
|
||||||
|
0x21, 0x6D, 0xFD, 0xDA, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71,
|
||||||
|
0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, 0x72, 0x65, 0x69, 0x6F, 0x61, 0xE7, 0xDE, 0xE7, 0xE1, 0xE7, 0xE1,
|
||||||
|
0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7,
|
||||||
|
0xE1, 0xE7, 0xE1, 0xF7, 0xB7, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE7, 0xE1, 0xE8, 0x0D, 0xE8, 0x0D,
|
||||||
|
0xFF, 0xCE, 0xFF, 0xD9, 0xFF, 0xE3, 0xFF, 0xFD, 0xD7, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A,
|
||||||
|
0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x75, 0xE7, 0x8D, 0xE7, 0xB9,
|
||||||
|
0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7,
|
||||||
|
0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9, 0xE7, 0xB9,
|
||||||
|
0xE7, 0xB9, 0xF7, 0x41, 0xA0, 0x08, 0xB1, 0x21, 0x2E, 0xFD, 0x49, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0x2E,
|
||||||
|
0x73, 0xE8, 0x4E, 0xE8, 0x51, 0xE8, 0x51, 0xE8, 0x51, 0xE8, 0x51, 0xE8, 0x51, 0xE8, 0x57, 0xFF, 0xFA, 0xFF, 0xFD,
|
||||||
|
0x22, 0x2E, 0x73, 0xDE, 0xE1, 0x21, 0x61, 0xFB, 0x21, 0xAD, 0xFD, 0x23, 0x6F, 0x61, 0xC3, 0xD9, 0xF5, 0xFD, 0x21,
|
||||||
|
0x66, 0xF9, 0xD7, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71,
|
||||||
|
0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x61, 0xE7, 0x0E, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A,
|
||||||
|
0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7,
|
||||||
|
0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xE7, 0x3A, 0xFF, 0xFD, 0x41, 0x73,
|
||||||
|
0xFF, 0x84, 0x42, 0x2E, 0x65, 0xFF, 0x7D, 0xFF, 0xFC, 0x21, 0x6C, 0xF9, 0x42, 0x6F, 0x61, 0xFF, 0x95, 0xFF, 0xFD,
|
||||||
|
0x21, 0x6E, 0xF9, 0x41, 0x72, 0xF9, 0x23, 0x42, 0x65, 0x72, 0xFF, 0xFC, 0xE7, 0x37, 0x21, 0x74, 0xF9, 0x42, 0x6C,
|
||||||
|
0x73, 0xF8, 0x4D, 0xFF, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE7, 0x64, 0xE7, 0x67, 0xE7, 0x67,
|
||||||
|
0xE7, 0x67, 0xE7, 0x67, 0xE7, 0x67, 0xE7, 0x6D, 0x41, 0x6E, 0xF8, 0xFB, 0x21, 0x6F, 0xFC, 0x22, 0x6F, 0x72, 0xE3,
|
||||||
|
0xFD, 0x41, 0x63, 0xE7, 0x04, 0x21, 0x65, 0xFC, 0x41, 0x61, 0xEA, 0x8B, 0x22, 0x6E, 0x67, 0xF9, 0xFC, 0x41, 0x64,
|
||||||
|
0xF7, 0x46, 0x21, 0x72, 0xFC, 0x21, 0x61, 0xFD, 0xDB, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A,
|
||||||
|
0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, 0x72, 0x6F, 0x61, 0x65, 0x69, 0x75,
|
||||||
|
0xE6, 0x5D, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6,
|
||||||
|
0x60, 0xF6, 0x36, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60, 0xE6, 0x60,
|
||||||
|
0xE6, 0x60, 0xFE, 0xD0, 0xFF, 0x4F, 0xFF, 0xAC, 0xFF, 0xBD, 0xFF, 0xE1, 0xFF, 0xF1, 0xFF, 0xFD, 0x41, 0x2E, 0xE6,
|
||||||
|
0x0C, 0x42, 0x2E, 0x73, 0xE6, 0x08, 0xFF, 0xFC, 0x22, 0x6F, 0x61, 0xF9, 0xF9, 0x21, 0x6C, 0xFB, 0x42, 0x6F, 0x61,
|
||||||
|
0xE6, 0xD5, 0xE6, 0xD5, 0x21, 0x6E, 0xF9, 0x21, 0x61, 0xFD, 0x22, 0x65, 0x6D, 0xF0, 0xFD, 0x41, 0x65, 0xFE, 0x9F,
|
||||||
|
0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x65, 0xFA, 0x22, 0x6C, 0x69, 0xFA, 0xFD, 0x42, 0x6C,
|
||||||
|
0x62, 0xF7, 0x7C, 0xFF, 0xFB, 0x42, 0x63, 0x6F, 0xE6, 0x55, 0xE6, 0xEB, 0x21, 0x69, 0xF9, 0x41, 0x2E, 0xE8, 0xAA,
|
||||||
|
0x42, 0x2E, 0x73, 0xE8, 0xA6, 0xFF, 0xFC, 0x21, 0x61, 0xF9, 0xA1, 0x04, 0xA2, 0x6C, 0xFD, 0x47, 0x68, 0x61, 0x65,
|
||||||
|
0x69, 0x6F, 0x75, 0xC3, 0xE9, 0x79, 0xE9, 0xB5, 0xE9, 0xB5, 0xE9, 0xB5, 0xFF, 0xFB, 0xE9, 0xB5, 0xE9, 0xBB, 0x43,
|
||||||
|
0x61, 0x69, 0x6F, 0xF5, 0xB9, 0xFF, 0xEA, 0xE9, 0xB0, 0x42, 0x61, 0x74, 0xF5, 0xAF, 0xE6, 0xBD, 0x41, 0x72, 0xE6,
|
||||||
|
0x11, 0x21, 0x65, 0xFC, 0x46, 0x63, 0x6C, 0x6D, 0x70, 0x74, 0x78, 0xF1, 0xA5, 0xFF, 0xBC, 0xFF, 0xE8, 0xFF, 0xF2,
|
||||||
|
0xFF, 0xFD, 0xFF, 0x0D, 0xA0, 0x0A, 0x13, 0x21, 0x65, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0xAD, 0xFD, 0x21, 0xC3, 0xFD,
|
||||||
|
0xA1, 0x06, 0xF3, 0x63, 0xFD, 0x21, 0x69, 0xEC, 0x21, 0x6D, 0xFD, 0x21, 0xAD, 0xFD, 0x22, 0x69, 0xC3, 0xFA, 0xFD,
|
||||||
|
0x21, 0x61, 0xDE, 0x21, 0x69, 0xFD, 0xA2, 0x06, 0xF3, 0x6E, 0x78, 0xF5, 0xFD, 0xA0, 0x0A, 0x43, 0x21, 0x6F, 0xFD,
|
||||||
|
0x21, 0x6D, 0xFD, 0x21, 0x69, 0xFD, 0xA1, 0x07, 0x23, 0x6E, 0xFD, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xF6, 0xA6,
|
||||||
|
0xF6, 0xA6, 0xF6, 0xA6, 0xFF, 0xFB, 0xF6, 0xA6, 0x48, 0x72, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE9, 0xF3,
|
||||||
|
0xE9, 0xCA, 0xF6, 0x93, 0xF6, 0x93, 0xFF, 0xBF, 0xFF, 0xD8, 0xF6, 0x93, 0xFF, 0xF0, 0x21, 0x72, 0xE7, 0x42, 0x65,
|
||||||
|
0x6F, 0xFF, 0xFD, 0xE9, 0x19, 0x43, 0x64, 0x70, 0x73, 0xF0, 0xC5, 0xFF, 0xF9, 0xF1, 0x1F, 0x42, 0x6F, 0x65, 0xE9,
|
||||||
|
0x08, 0xF0, 0xB7, 0x42, 0x6C, 0x6D, 0xF6, 0x93, 0xFF, 0xF9, 0x5B, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A,
|
||||||
|
0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x75, 0x61, 0x65, 0x69, 0x6F,
|
||||||
|
0xE4, 0xDF, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4,
|
||||||
|
0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2,
|
||||||
|
0xE4, 0xE2, 0xE4, 0xE2, 0xE4, 0xE2, 0xFE, 0xF6, 0xFF, 0x10, 0xFF, 0x62, 0xFF, 0xE8, 0xFF, 0xF9, 0xD6, 0x00, 0x41,
|
||||||
|
0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77,
|
||||||
|
0x78, 0x79, 0x7A, 0xE4, 0x8D, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90,
|
||||||
|
0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4,
|
||||||
|
0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0xE4, 0x90, 0x41, 0x6C, 0xF5, 0xF5, 0xD7, 0x00, 0x41, 0x2E, 0x62, 0x63,
|
||||||
|
0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, 0x72,
|
||||||
|
0x69, 0xE4, 0x44, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47,
|
||||||
|
0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4, 0x47, 0xE4,
|
||||||
|
0x47, 0xE4, 0x47, 0xE4, 0x73, 0xE4, 0x73, 0xFF, 0xFC, 0xD6, 0x00, 0x81, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68,
|
||||||
|
0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xE3, 0xFC, 0xE3, 0xFF,
|
||||||
|
0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3,
|
||||||
|
0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF, 0xE3, 0xFF,
|
||||||
|
0xE3, 0xFF, 0x41, 0x75, 0xF3, 0x6B, 0x41, 0x66, 0xEF, 0x7D, 0xA0, 0x0D, 0x02, 0x21, 0x61, 0xFD, 0x21, 0x65, 0xFD,
|
||||||
|
0x21, 0x72, 0xFD, 0x21, 0xA1, 0xFD, 0x44, 0x6E, 0x70, 0x74, 0xC3, 0xFF, 0xED, 0xF5, 0x4D, 0xF5, 0x4D, 0xFF, 0xFD,
|
||||||
|
0x41, 0x61, 0xFC, 0x4E, 0x21, 0xAD, 0xFC, 0x21, 0xC3, 0xFD, 0x21, 0x67, 0xFD, 0xD9, 0x00, 0x41, 0x2E, 0x62, 0x63,
|
||||||
|
0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C,
|
||||||
|
0x65, 0x69, 0x6F, 0xE3, 0x86, 0xE3, 0x89, 0xE3, 0x8F, 0xE3, 0x89, 0xE3, 0x98, 0xE3, 0x9D, 0xE3, 0x89, 0xE3, 0x89,
|
||||||
|
0xE3, 0x89, 0xE3, 0x9D, 0xE3, 0x89, 0xE3, 0xA2, 0xE3, 0x89, 0xE3, 0x89, 0xE3, 0x89, 0xE3, 0xAB, 0xE3, 0x89, 0xE3,
|
||||||
|
0x89, 0xE3, 0x89, 0xE3, 0x89, 0xE3, 0x89, 0xFF, 0x8A, 0xFF, 0xCF, 0xFF, 0xE6, 0xFF, 0xFD, 0x42, 0x2E, 0x73, 0xE3,
|
||||||
|
0x38, 0xF4, 0x32, 0x21, 0x65, 0xF9, 0x21, 0x6C, 0xFD, 0x21, 0x62, 0xFD, 0x41, 0x2E, 0xE4, 0x07, 0x21, 0x65, 0xFC,
|
||||||
|
0x21, 0x74, 0xFD, 0x48, 0x6C, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE3, 0xAB, 0xE6, 0xEC, 0xE7, 0x28, 0xE7,
|
||||||
|
0x28, 0xE7, 0x28, 0xE7, 0x28, 0xE7, 0x28, 0xE7, 0x2E, 0x21, 0x61, 0xE7, 0x41, 0x6E, 0xE3, 0x8F, 0x21, 0x61, 0xFC,
|
||||||
|
0x47, 0x6F, 0x61, 0x6E, 0x67, 0x6C, 0x73, 0x74, 0xF3, 0xF5, 0xFF, 0xD0, 0xFF, 0xDA, 0xFF, 0xF6, 0xFF, 0xFD, 0xF4,
|
||||||
|
0xA8, 0xFC, 0x8B, 0xA0, 0x05, 0x51, 0x21, 0x61, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xFD, 0xA0, 0x02, 0xB2, 0xCC,
|
||||||
|
0x01, 0xA1, 0x68, 0x62, 0x63, 0x64, 0x66, 0x67, 0x6D, 0x70, 0x71, 0x73, 0x74, 0x76, 0xFF, 0xFD, 0xE4, 0xE9, 0xE4,
|
||||||
|
0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9, 0xE4, 0xE9,
|
||||||
|
0x41, 0x69, 0xE6, 0xCA, 0x44, 0x6E, 0x63, 0x6C, 0x78, 0xFF, 0xCF, 0xEE, 0x79, 0xFF, 0xD5, 0xFF, 0xFC, 0x41, 0x72,
|
||||||
|
0xE8, 0xA2, 0x21, 0x61, 0xFC, 0x21, 0x69, 0xFD, 0xA0, 0x01, 0x12, 0x21, 0x72, 0xFD, 0x21, 0x75, 0xFD, 0xA1, 0x04,
|
||||||
|
0xA2, 0x74, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE6, 0x54, 0xFF, 0xFB, 0xE6, 0x90, 0xE6, 0x90,
|
||||||
|
0xE6, 0x90, 0xE6, 0x90, 0xE6, 0x96, 0x21, 0x69, 0xEA, 0x41, 0x69, 0xE3, 0x9F, 0x44, 0x63, 0x6C, 0x6E, 0x72, 0xEE,
|
||||||
|
0x37, 0xFF, 0xD2, 0xFF, 0xF9, 0xFF, 0xFC, 0x41, 0x74, 0xE6, 0x62, 0x21, 0x6C, 0xFC, 0x43, 0x6E, 0x72, 0x74, 0xF4,
|
||||||
|
0x02, 0xFE, 0xA2, 0xF4, 0x02, 0xDB, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D,
|
||||||
|
0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x65, 0x61, 0x69, 0x75, 0x6F, 0xE2, 0x4B, 0xE2,
|
||||||
|
0x4E, 0xE2, 0x54, 0xE2, 0x4E, 0xE2, 0x5D, 0xE2, 0x62, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x62,
|
||||||
|
0xF2, 0x24, 0xE2, 0x67, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x70, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2, 0x4E, 0xE2,
|
||||||
|
0x4E, 0xE2, 0x4E, 0xFF, 0x50, 0xFF, 0xA0, 0xFF, 0xE2, 0xFF, 0xF3, 0xFF, 0xF6, 0xA0, 0x0B, 0x95, 0x21, 0x6E, 0xFD,
|
||||||
|
0x21, 0x69, 0xFD, 0x21, 0x72, 0xFD, 0xC3, 0x00, 0x71, 0x7A, 0x73, 0x65, 0xE1, 0xF1, 0xE1, 0xF1, 0xFF, 0xFD, 0x41,
|
||||||
|
0x74, 0xED, 0xA2, 0x42, 0x2E, 0x72, 0xE1, 0xDE, 0xFF, 0xFC, 0x43, 0x6D, 0x6E, 0x72, 0xF3, 0x81, 0xF3, 0x81, 0xF1,
|
||||||
|
0x88, 0x45, 0x63, 0x66, 0x6F, 0x74, 0x75, 0xED, 0x98, 0xED, 0x98, 0xFB, 0x31, 0xF3, 0x77, 0xF2, 0xA5, 0xD9, 0x00,
|
||||||
|
0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76,
|
||||||
|
0x77, 0x78, 0x79, 0x7A, 0x6F, 0x61, 0x65, 0xE1, 0xBA, 0xE1, 0xBD, 0xE1, 0xC3, 0xE1, 0xBD, 0xE1, 0xCC, 0xE1, 0xD1,
|
||||||
|
0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xD1, 0xE1, 0xBD, 0xE1, 0xD6, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1,
|
||||||
|
0xBD, 0xFF, 0xCF, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xBD, 0xE1, 0xBD, 0xFF, 0xDF, 0xFF, 0xE6, 0xFF, 0xF0,
|
||||||
|
0xC1, 0x0D, 0x22, 0x6F, 0xE2, 0x8F, 0x42, 0x63, 0x71, 0xFF, 0xFA, 0xF1, 0x1E, 0xC2, 0x00, 0x71, 0x2E, 0x69, 0xE1,
|
||||||
|
0x5F, 0xFF, 0xF9, 0xC2, 0x00, 0x71, 0x2E, 0x65, 0xE1, 0x56, 0xED, 0x24, 0x41, 0x74, 0xFE, 0xB9, 0x21, 0x63, 0xFC,
|
||||||
|
0x21, 0x6E, 0xFD, 0x41, 0x72, 0xE5, 0x49, 0xD8, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B,
|
||||||
|
0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x61, 0x75, 0xE1, 0x3F, 0xE1, 0x6B,
|
||||||
|
0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1,
|
||||||
|
0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B, 0xE1, 0x6B,
|
||||||
|
0xE1, 0x6B, 0xFF, 0xF9, 0xFF, 0xFC, 0x41, 0x70, 0xE2, 0xB2, 0x42, 0x6D, 0x74, 0xFF, 0xFC, 0xEC, 0xBA, 0xD7, 0x00,
|
||||||
|
0x91, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76,
|
||||||
|
0x77, 0x78, 0x79, 0x7A, 0x6F, 0xE0, 0xE9, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15,
|
||||||
|
0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1,
|
||||||
|
0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xE1, 0x15, 0xFF, 0xF9, 0x42, 0x61, 0x6F, 0xF1, 0x95, 0xF1,
|
||||||
|
0x95, 0x21, 0x74, 0xF9, 0x41, 0x61, 0xF4, 0x4F, 0x21, 0x69, 0xFC, 0x21, 0x6D, 0xFD, 0xA0, 0x10, 0x92, 0x21, 0x65,
|
||||||
|
0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0xA0, 0x10, 0xB2, 0x21, 0x72, 0xFD, 0x21, 0x64, 0xFD, 0x21, 0x6E, 0xFD,
|
||||||
|
0x21, 0x6F, 0xFD, 0x23, 0x65, 0x61, 0x70, 0xE2, 0xEE, 0xFD, 0x44, 0x64, 0x72, 0x6E, 0x74, 0xF1, 0x7E, 0xFF, 0xF9,
|
||||||
|
0xF1, 0x42, 0xF9, 0xFB, 0x41, 0x6E, 0xEB, 0x6F, 0x21, 0x6F, 0xFC, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xFD, 0x41, 0x65,
|
||||||
|
0xEC, 0x1B, 0xA0, 0x06, 0x31, 0x41, 0xB1, 0xE1, 0xF2, 0x21, 0xC3, 0xFC, 0x22, 0x2E, 0x65, 0xF6, 0xFD, 0xA1, 0x04,
|
||||||
|
0xA2, 0x73, 0xFB, 0x41, 0x61, 0xE6, 0x3D, 0x21, 0x74, 0xFC, 0x21, 0x61, 0xFD, 0xA1, 0x04, 0xA2, 0x6C, 0xFD, 0x41,
|
||||||
|
0x6F, 0xE6, 0x2E, 0xA1, 0x04, 0xC2, 0x73, 0xFC, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xE4, 0x2E, 0xE4, 0x2E, 0xFF,
|
||||||
|
0xFB, 0xE4, 0x2E, 0xE4, 0x2E, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE3, 0xDF, 0xE4, 0x1B, 0xE4, 0x1B,
|
||||||
|
0xFF, 0xD3, 0xE4, 0x1B, 0xFF, 0xE2, 0xFF, 0xF0, 0x21, 0x61, 0xEA, 0x23, 0x6E, 0x6C, 0x72, 0xA4, 0xA7, 0xFD, 0x41,
|
||||||
|
0x7A, 0xEB, 0xBB, 0x43, 0x63, 0x65, 0x72, 0xF1, 0x9A, 0xFF, 0xFC, 0xF1, 0x9A, 0x42, 0x71, 0x63, 0xE5, 0xE7, 0xFF,
|
||||||
|
0xAA, 0x41, 0x65, 0xFF, 0xA3, 0x42, 0x64, 0x74, 0xFD, 0x3A, 0xFF, 0xFC, 0xA2, 0x04, 0xA2, 0x72, 0x6E, 0xEE, 0xF9,
|
||||||
|
0x41, 0x65, 0xFD, 0x36, 0x21, 0x69, 0xFC, 0xA1, 0x04, 0xA2, 0x6D, 0xFD, 0xC1, 0x04, 0xA2, 0x72, 0xE1, 0x66, 0x41,
|
||||||
|
0x71, 0xE5, 0xBC, 0xA1, 0x04, 0xC2, 0x72, 0xFC, 0x41, 0x65, 0xE5, 0xB3, 0x21, 0x74, 0xFC, 0xA1, 0x04, 0xC2, 0x73,
|
||||||
|
0xFD, 0x45, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, 0xEF, 0xFF, 0xFB, 0xE3, 0xB0, 0xE3, 0xB0, 0xE3, 0xB0, 0x47, 0x68,
|
||||||
|
0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE3, 0x61, 0xFF, 0xC2, 0xE3, 0x9D, 0xE3, 0x9D, 0xFF, 0xD0, 0xFF, 0xD5, 0xFF,
|
||||||
|
0xF0, 0x21, 0x69, 0xEA, 0x41, 0x6F, 0xEA, 0x8B, 0xA1, 0x04, 0x52, 0x72, 0xFC, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F,
|
||||||
|
0x75, 0xC3, 0xE0, 0x80, 0xE0, 0x83, 0xFF, 0xFB, 0xE0, 0x83, 0xE0, 0x83, 0xE0, 0x83, 0xE0, 0x89, 0x21, 0x61, 0xEA,
|
||||||
|
0x21, 0x74, 0xFD, 0x41, 0x72, 0xFC, 0x7C, 0x21, 0x70, 0xFC, 0x41, 0x64, 0xFC, 0x75, 0x22, 0x6D, 0x6E, 0xF9, 0xFC,
|
||||||
|
0xA0, 0x0E, 0x13, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x70, 0xFD, 0xA0, 0x0E, 0x43, 0x21, 0x74, 0xFD, 0x21,
|
||||||
|
0x63, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xD8, 0x22, 0x6C, 0x73, 0xFA, 0xFD, 0x41, 0x2E, 0xE1, 0x38, 0x42, 0x2E,
|
||||||
|
0x73, 0xE1, 0x34, 0xFF, 0xFC, 0x42, 0x61, 0x73, 0xFF, 0xF9, 0xE1, 0xD0, 0x24, 0x69, 0x6F, 0x65, 0x74, 0xC9, 0xD7,
|
||||||
|
0xE9, 0xF9, 0x43, 0x6C, 0x72, 0x73, 0xFF, 0x8D, 0xFF, 0xB2, 0xFF, 0xF7, 0xDB, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64,
|
||||||
|
0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x6C, 0x72, 0x75,
|
||||||
|
0x65, 0x61, 0x69, 0x6F, 0xDF, 0x00, 0xDF, 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xDF,
|
||||||
|
0x03, 0xDF, 0x03, 0xDF, 0x03, 0xEE, 0xD9, 0xDF, 0x03, 0xDF, 0x03, 0xFD, 0xA1, 0xFD, 0xAA, 0xDF, 0x03, 0xDF, 0x03,
|
||||||
|
0xDF, 0x03, 0xDF, 0x03, 0xDF, 0x03, 0xFD, 0xC1, 0xFE, 0x17, 0xFE, 0x66, 0xFE, 0x95, 0xFF, 0x08, 0xFF, 0x13, 0xFF,
|
||||||
|
0xF6, 0x42, 0x6D, 0x72, 0xDF, 0x3C, 0xEA, 0x76, 0x42, 0x65, 0x69, 0xFC, 0xC6, 0xFF, 0xF9, 0xD7, 0x00, 0x41, 0x2E,
|
||||||
|
0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78,
|
||||||
|
0x79, 0x7A, 0x75, 0xDE, 0x9E, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1,
|
||||||
|
0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE,
|
||||||
|
0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xDE, 0xA1, 0xFF, 0xF9, 0xC2, 0x00, 0x71, 0x6E, 0x61, 0xDE, 0x5C, 0xEE,
|
||||||
|
0xD0, 0x41, 0xA1, 0xF0, 0xDB, 0x43, 0x61, 0xC3, 0x65, 0xF0, 0xD7, 0xFF, 0xFC, 0xF0, 0xD7, 0x21, 0x69, 0xF6, 0x21,
|
||||||
|
0x63, 0xFD, 0xA0, 0x0A, 0x72, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0xAD, 0xFD, 0x22, 0x69,
|
||||||
|
0xC3, 0xEE, 0xFD, 0x21, 0x6E, 0xFB, 0x42, 0x65, 0x72, 0xE2, 0x3D, 0xE9, 0xEC, 0x22, 0x69, 0x74, 0xF6, 0xF9, 0xA0,
|
||||||
|
0x0B, 0xB1, 0x23, 0xA1, 0xA9, 0xAD, 0xFD, 0xFD, 0xFD, 0x24, 0x61, 0xC3, 0x65, 0x6F, 0xF6, 0xF9, 0xF6, 0xF6, 0x43,
|
||||||
|
0x64, 0x6E, 0x72, 0xF5, 0xFA, 0xED, 0xB7, 0xFF, 0xF7, 0x41, 0x6D, 0xEF, 0xA6, 0xD9, 0x00, 0x41, 0x2E, 0x62, 0x63,
|
||||||
|
0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x72,
|
||||||
|
0x65, 0x61, 0x6F, 0xDD, 0xF5, 0xDD, 0xF8, 0xDD, 0xFE, 0xDD, 0xF8, 0xDE, 0x07, 0xDE, 0x0C, 0xDD, 0xF8, 0xDD, 0xF8,
|
||||||
|
0xDD, 0xF8, 0xDD, 0xF8, 0xFF, 0x9F, 0xDD, 0xF8, 0xDE, 0x11, 0xDD, 0xF8, 0xDD, 0xF8, 0xDE, 0x1A, 0xDD, 0xF8, 0xDD,
|
||||||
|
0xF8, 0xDD, 0xF8, 0xDD, 0xF8, 0xDD, 0xF8, 0xDE, 0x24, 0xFF, 0xDA, 0xFF, 0xF2, 0xFF, 0xFC, 0xC4, 0x00, 0x71, 0x74,
|
||||||
|
0x73, 0x6E, 0x61, 0xDD, 0xAD, 0xDD, 0xAD, 0xDD, 0xAD, 0xEE, 0x21, 0xA0, 0x00, 0xD1, 0x21, 0x2E, 0xFD, 0x22, 0x2E,
|
||||||
|
0x73, 0xFA, 0xFD, 0xA0, 0x03, 0x02, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0x22, 0x2E, 0x65, 0xEC, 0xFD, 0x21, 0x6C,
|
||||||
|
0xFB, 0x22, 0x2E, 0x73, 0xEF, 0xF2, 0x21, 0x6E, 0xED, 0x21, 0xB3, 0xFD, 0x21, 0x65, 0xEA, 0x21, 0x6E, 0xFD, 0x23,
|
||||||
|
0x61, 0xC3, 0x6F, 0xEF, 0xF7, 0xFD, 0x21, 0x6C, 0xF9, 0x21, 0x6C, 0xFD, 0x21, 0x73, 0xC9, 0x23, 0x2E, 0x61, 0x65,
|
||||||
|
0xC3, 0xC9, 0xFD, 0x21, 0x72, 0xF9, 0xC6, 0x00, 0x71, 0x7A, 0x73, 0x65, 0x61, 0x69, 0x6F, 0xDD, 0x57, 0xDD, 0x57,
|
||||||
|
0xFF, 0xBF, 0xFF, 0xD2, 0xFF, 0xF0, 0xFF, 0xFD, 0x41, 0x74, 0xDF, 0xF2, 0x21, 0x63, 0xFC, 0x41, 0x76, 0xDE, 0x67,
|
||||||
|
0x44, 0x6E, 0x2E, 0x73, 0x6C, 0xFF, 0xF9, 0xF5, 0xEC, 0xF5, 0xEF, 0xFF, 0xFC, 0x41, 0x65, 0xFA, 0x22, 0x41, 0x76,
|
||||||
|
0xE8, 0xEA, 0xA0, 0x0E, 0xD2, 0xA0, 0x0E, 0xF3, 0xA0, 0x0F, 0x23, 0x25, 0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFD, 0xFD,
|
||||||
|
0xFD, 0xFD, 0xFD, 0x27, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xEC, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xF5, 0x21,
|
||||||
|
0x6F, 0xF1, 0x21, 0x64, 0xFD, 0x44, 0x6C, 0x6D, 0x72, 0x75, 0xFF, 0xCF, 0xFA, 0x44, 0xFF, 0xD3, 0xFF, 0xFD, 0xA0,
|
||||||
|
0x0F, 0x52, 0xA1, 0x0F, 0x52, 0x73, 0xFD, 0x21, 0x61, 0xFB, 0xA1, 0x04, 0x52, 0x73, 0xFD, 0x47, 0x68, 0x61, 0x65,
|
||||||
|
0x69, 0x6F, 0x75, 0xC3, 0xDD, 0xE5, 0xFF, 0xFB, 0xDD, 0xE8, 0xDD, 0xE8, 0xDD, 0xE8, 0xDD, 0xE8, 0xDD, 0xEE, 0x21,
|
||||||
|
0x65, 0xEA, 0x21, 0x72, 0xFD, 0x42, 0x62, 0x63, 0xFF, 0xFD, 0xF4, 0xB1, 0xA0, 0x0F, 0xF3, 0xA1, 0x06, 0xF3, 0x72,
|
||||||
|
0xFD, 0x41, 0x72, 0xF9, 0xC6, 0xA1, 0x06, 0xF3, 0x6F, 0xFC, 0xA0, 0x10, 0x23, 0x41, 0x2E, 0xF7, 0x64, 0x42, 0x2E,
|
||||||
|
0x73, 0xF7, 0x60, 0xFF, 0xFC, 0x21, 0x74, 0xF9, 0x21, 0x69, 0xFD, 0xA2, 0x07, 0x23, 0x72, 0x76, 0xEC, 0xFD, 0x45,
|
||||||
|
0xA1, 0xA9, 0xAD, 0xB3, 0xBA, 0xFF, 0xF9, 0xEE, 0x03, 0xEE, 0x03, 0xEE, 0x03, 0xEE, 0x03, 0x48, 0x72, 0x68, 0x61,
|
||||||
|
0x65, 0x69, 0x6F, 0x75, 0xC3, 0xE1, 0x50, 0xE1, 0x27, 0xFF, 0xC7, 0xED, 0xF0, 0xFF, 0xD0, 0xED, 0xF0, 0xED, 0xF0,
|
||||||
|
0xFF, 0xF0, 0x21, 0x72, 0xE7, 0xC7, 0x07, 0xB1, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xDD, 0x6A, 0xDD, 0x6D,
|
||||||
|
0xDD, 0x6D, 0xDD, 0x6D, 0xDD, 0x6D, 0xDD, 0x6D, 0xDD, 0x73, 0x21, 0x61, 0xE8, 0x22, 0x65, 0x72, 0xE2, 0xFD, 0xA0,
|
||||||
|
0x11, 0x43, 0x21, 0x6E, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65,
|
||||||
|
0xFD, 0x21, 0x6D, 0xFD, 0x21, 0x61, 0xFD, 0x23, 0x70, 0x64, 0x72, 0xE0, 0xFD, 0xFD, 0xDA, 0x00, 0x41, 0x2E, 0x62,
|
||||||
|
0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79,
|
||||||
|
0x7A, 0x61, 0x65, 0x6F, 0x75, 0xDC, 0x19, 0xDC, 0x1C, 0xDC, 0x22, 0xDC, 0x1C, 0xDC, 0x2B, 0xDC, 0x30, 0xDC, 0x1C,
|
||||||
|
0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x30, 0xDC, 0x1C, 0xFE, 0x72, 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x1C, 0xFE,
|
||||||
|
0xC8, 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x1C, 0xDC, 0x1C, 0xFE, 0xE8, 0xFF, 0x26, 0xFF, 0x5F, 0xFF, 0xF9,
|
||||||
|
0x41, 0x65, 0xE1, 0x23, 0x41, 0x6E, 0xDF, 0x92, 0x21, 0x69, 0xFC, 0x22, 0x74, 0x64, 0xF5, 0xFD, 0x41, 0x6C, 0xDF,
|
||||||
|
0x86, 0x21, 0x65, 0xFC, 0x21, 0x75, 0xFD, 0x41, 0x62, 0xDF, 0x7C, 0x21, 0x6F, 0xFC, 0x41, 0x72, 0xDF, 0x75, 0x21,
|
||||||
|
0x61, 0xFC, 0x43, 0x63, 0x70, 0x74, 0xFF, 0xF6, 0xDF, 0x6E, 0xFF, 0xFD, 0x41, 0xA1, 0xDF, 0x8F, 0x21, 0xC3, 0xFC,
|
||||||
|
0x21, 0x6C, 0xFD, 0x24, 0x6E, 0x62, 0x6C, 0x74, 0xCF, 0xDB, 0xEC, 0xFD, 0x21, 0xA1, 0xBF, 0x21, 0xC3, 0xFD, 0x21,
|
||||||
|
0x65, 0xFD, 0x21, 0x63, 0xFD, 0x41, 0x2E, 0xE4, 0xB3, 0x42, 0x2E, 0x73, 0xE4, 0xAF, 0xFF, 0xFC, 0x22, 0x6F, 0x61,
|
||||||
|
0xF9, 0xF9, 0x21, 0x72, 0xFB, 0x23, 0x61, 0x6F, 0x65, 0xD8, 0xEA, 0xFD, 0x41, 0x73, 0xDE, 0x49, 0x21, 0x61, 0xFC,
|
||||||
|
0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x64, 0xE0, 0x00, 0x41, 0x6C, 0xDF, 0xFC, 0x41, 0x69, 0xE9, 0x65, 0x42,
|
||||||
|
0x63, 0x74, 0xDF, 0xF7, 0xFF, 0xFC, 0xA4, 0x0E, 0xB2, 0x6D, 0x6E, 0x74, 0x63, 0xEA, 0xED, 0xF1, 0xF9, 0x41, 0xBA,
|
||||||
|
0xE4, 0x87, 0x41, 0x75, 0xDF, 0xE5, 0xA2, 0x0E, 0xB2, 0xC3, 0x78, 0xF8, 0xFC, 0x41, 0x6E, 0xDF, 0xDA, 0x21, 0x61,
|
||||||
|
0xFC, 0x21, 0x69, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0xB3, 0xF0, 0x22, 0x6F, 0xC3, 0xED, 0xFD, 0x21,
|
||||||
|
0x69, 0xFB, 0x41, 0x2E, 0xDB, 0x9E, 0x42, 0x2E, 0x73, 0xDB, 0x9A, 0xFF, 0xFC, 0x22, 0x6F, 0x61, 0xF9, 0xF9, 0x41,
|
||||||
|
0xAD, 0xDF, 0xAF, 0x43, 0x69, 0xC3, 0x65, 0xDF, 0xAB, 0xFF, 0xFC, 0xDF, 0xAB, 0x41, 0xA1, 0xDF, 0xA1, 0x43, 0x61,
|
||||||
|
0xC3, 0x6F, 0xDF, 0x9D, 0xFF, 0xFC, 0xDF, 0x9D, 0x41, 0x61, 0xE4, 0x31, 0x21, 0x76, 0xFC, 0x41, 0x74, 0xDD, 0x80,
|
||||||
|
0x41, 0x69, 0xDF, 0x88, 0xA1, 0x0E, 0x92, 0x72, 0xFC, 0x41, 0x76, 0xDF, 0x7F, 0x45, 0x61, 0xC3, 0x65, 0x6F, 0x69,
|
||||||
|
0xDF, 0x7B, 0xDF, 0xF0, 0xDF, 0x7B, 0xFF, 0xF7, 0xFF, 0xFC, 0xC8, 0x0E, 0xB2, 0x62, 0x63, 0x64, 0x67, 0x6A, 0x6C,
|
||||||
|
0x73, 0x74, 0xFF, 0x9E, 0xFF, 0xA9, 0xFF, 0xB7, 0xFF, 0xC0, 0xFF, 0xCE, 0xFF, 0xDC, 0xFF, 0xDF, 0xFF, 0xF0, 0x41,
|
||||||
|
0x65, 0xDF, 0x49, 0x41, 0x69, 0xDD, 0x81, 0x21, 0x63, 0xFC, 0x21, 0x61, 0xFD, 0xA2, 0x0E, 0xB2, 0x63, 0x72, 0xF2,
|
||||||
|
0xFD, 0xC3, 0x0E, 0xB2, 0x72, 0x62, 0x73, 0xDF, 0x34, 0xE1, 0xB9, 0xE0, 0xFB, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F,
|
||||||
|
0x75, 0xC3, 0xDF, 0x28, 0xFF, 0x3B, 0xFF, 0x4E, 0xFF, 0xC4, 0xFF, 0xED, 0xFF, 0xF4, 0xE5, 0xE3, 0x21, 0x73, 0xEA,
|
||||||
|
0x42, 0x73, 0x6E, 0xFE, 0xFB, 0xFF, 0xFD, 0x41, 0x70, 0xE6, 0x22, 0xD8, 0x00, 0x91, 0x2E, 0x62, 0x63, 0x64, 0x66,
|
||||||
|
0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x61, 0x6F,
|
||||||
|
0xDA, 0x54, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA,
|
||||||
|
0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80,
|
||||||
|
0xDA, 0x80, 0xDA, 0x80, 0xDA, 0x80, 0xFF, 0xF5, 0xFF, 0xFC, 0x41, 0x68, 0xEC, 0xA2, 0x21, 0x63, 0xFC, 0xC2, 0x01,
|
||||||
|
0xF2, 0x2E, 0x73, 0xDA, 0x02, 0xFF, 0xFD, 0xC1, 0x01, 0xF2, 0x2E, 0xD9, 0xF9, 0xA0, 0x01, 0xF2, 0x42, 0x61, 0x72,
|
||||||
|
0xF6, 0xB8, 0xDB, 0x22, 0x41, 0x65, 0xDE, 0x04, 0x42, 0x61, 0x6D, 0xDE, 0x00, 0xE5, 0xAF, 0x44, 0x74, 0x6C, 0x63,
|
||||||
|
0x72, 0xFF, 0xEE, 0xFF, 0xF5, 0xEA, 0x58, 0xFF, 0xF9, 0x41, 0x75, 0xEC, 0x56, 0x41, 0x6F, 0xEC, 0x52, 0x22, 0x71,
|
||||||
|
0x63, 0xF8, 0xFC, 0x21, 0x6F, 0xFB, 0x41, 0x6C, 0xEA, 0x9C, 0x41, 0x70, 0xEB, 0x6A, 0x41, 0x62, 0xE5, 0x83, 0x21,
|
||||||
|
0x72, 0xFC, 0x41, 0x63, 0xDA, 0x91, 0x21, 0x69, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0xA9, 0xFD, 0xDC,
|
||||||
|
0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x74, 0x76, 0x77, 0x79,
|
||||||
|
0x72, 0x7A, 0x73, 0x6C, 0x78, 0x65, 0x69, 0x61, 0x6F, 0x75, 0xC3, 0xD9, 0xA2, 0xD9, 0xA5, 0xD9, 0xAB, 0xD9, 0xA5,
|
||||||
|
0xD9, 0xB4, 0xD9, 0xB9, 0xD9, 0xA5, 0xD9, 0xA5, 0xD9, 0xA5, 0xD9, 0xB9, 0xD9, 0xA5, 0xD9, 0xBE, 0xD9, 0xA5, 0xD9,
|
||||||
|
0xC7, 0xD9, 0xA5, 0xD9, 0xA5, 0xD9, 0xA5, 0xFF, 0x4E, 0xFF, 0xA0, 0xFF, 0xA9, 0xFF, 0xAF, 0xFF, 0xAF, 0xFF, 0xC4,
|
||||||
|
0xFF, 0xDE, 0xFF, 0xE1, 0xFF, 0xE5, 0xFF, 0xED, 0xFF, 0xFD, 0x42, 0x63, 0x64, 0xFF, 0x62, 0xF8, 0xFA, 0xD7, 0x00,
|
||||||
|
0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x71, 0x73, 0x74, 0x76, 0x77, 0x78,
|
||||||
|
0x79, 0x7A, 0x6C, 0x72, 0x69, 0xD9, 0x44, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47,
|
||||||
|
0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9,
|
||||||
|
0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x47, 0xD9, 0x73, 0xD9, 0x73, 0xFF, 0xF9, 0x41, 0x73, 0xFE, 0xF3, 0xD7, 0x00,
|
||||||
|
0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76,
|
||||||
|
0x77, 0x78, 0x79, 0x7A, 0x61, 0xD8, 0xF8, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB,
|
||||||
|
0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8,
|
||||||
|
0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xD8, 0xFB, 0xFF, 0xFC, 0x42, 0x6E, 0x72, 0xEA, 0x5D, 0xEA,
|
||||||
|
0x5D, 0xD8, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72,
|
||||||
|
0x73, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x65, 0x69, 0xD8, 0xA9, 0xD8, 0xAC, 0xD8, 0xB2, 0xD8, 0xAC, 0xD8, 0xBB,
|
||||||
|
0xD8, 0xC0, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xC0, 0xD8, 0xAC, 0xD8, 0xC5, 0xD8, 0xAC, 0xD8,
|
||||||
|
0xAC, 0xD8, 0xAC, 0xD8, 0xCE, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xAC, 0xD8, 0xAC, 0xFF, 0xF9, 0xF4, 0x61,
|
||||||
|
0xD6, 0x00, 0x41, 0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73,
|
||||||
|
0x74, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xD8, 0x5E, 0xD8, 0x61, 0xD8, 0x67, 0xD8, 0x61, 0xD8, 0x70, 0xD8, 0x75, 0xD8,
|
||||||
|
0x61, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x75, 0xD8, 0x61, 0xD8, 0x7A, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x61,
|
||||||
|
0xD8, 0x83, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x61, 0xD8, 0x61, 0x41, 0x6F, 0xF1, 0x80, 0xD7, 0x00, 0x41,
|
||||||
|
0x2E, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77,
|
||||||
|
0x78, 0x79, 0x7A, 0x6F, 0xD8, 0x15, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8,
|
||||||
|
0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18,
|
||||||
|
0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xD8, 0x18, 0xFF, 0xFC, 0xC1, 0x00, 0x41, 0x2E, 0xD7, 0xCD, 0x41,
|
||||||
|
0x73, 0xE8, 0xC1, 0xA0, 0x02, 0x82, 0x21, 0x2E, 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0x43, 0x65, 0x6F, 0x61, 0xF4,
|
||||||
|
0x80, 0xF4, 0x80, 0xFF, 0xFB, 0x21, 0x6C, 0xF6, 0x21, 0x65, 0xFD, 0x43, 0x65, 0x6F, 0x61, 0xF4, 0x70, 0xF4, 0x70,
|
||||||
|
0xF4, 0x70, 0x21, 0x6C, 0xF6, 0x21, 0x65, 0xFD, 0x21, 0x73, 0xFA, 0x21, 0x6F, 0xFD, 0xA0, 0x02, 0xD2, 0x21, 0x2E,
|
||||||
|
0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0x22, 0x6F, 0x61, 0xFB, 0xFB, 0x21, 0x63, 0xFB, 0x21, 0x69, 0xFD, 0x25, 0x6D,
|
||||||
|
0x74, 0x73, 0x6E, 0x72, 0xD1, 0xD1, 0xE1, 0xE7, 0xFD, 0x23, 0x65, 0x6F, 0x61, 0xE5, 0xE5, 0xE5, 0x21, 0x6C, 0xF9,
|
||||||
|
0x21, 0x73, 0xFD, 0x25, 0x6D, 0x74, 0x73, 0x6E, 0x6F, 0xB9, 0xB9, 0xC9, 0xCF, 0xFD, 0x46, 0x73, 0x69, 0x2E, 0x64,
|
||||||
|
0x6F, 0x72, 0xD7, 0x59, 0xFF, 0x92, 0xD7, 0x59, 0xFF, 0xDD, 0xFF, 0xC1, 0xFF, 0xF5, 0x41, 0x73, 0xFF, 0x86, 0x21,
|
||||||
|
0x6F, 0xFC, 0x45, 0x2E, 0x73, 0x6D, 0x69, 0x6E, 0xD7, 0x3F, 0xE8, 0x39, 0xFF, 0xFD, 0xFF, 0x78, 0xE8, 0x39, 0xA0,
|
||||||
|
0x02, 0xA3, 0x21, 0x2E, 0xFD, 0x23, 0x2E, 0x73, 0x69, 0xFA, 0xFD, 0xE3, 0x42, 0x6F, 0x61, 0xF3, 0xEA, 0xF3, 0xEA,
|
||||||
|
0x21, 0x63, 0xF9, 0x43, 0x65, 0x61, 0x69, 0xFF, 0xEF, 0xF3, 0xE0, 0xFF, 0xFD, 0x41, 0x6F, 0xF3, 0xD6, 0x22, 0x74,
|
||||||
|
0x6D, 0xF2, 0xFC, 0x41, 0x73, 0xFF, 0x76, 0x21, 0x65, 0xFC, 0x21, 0x6F, 0xF9, 0x41, 0x65, 0xFF, 0x6F, 0x21, 0x6C,
|
||||||
|
0xFC, 0x47, 0x61, 0x2E, 0x73, 0x74, 0x6D, 0x64, 0x62, 0xFF, 0xB5, 0xD6, 0xF4, 0xFF, 0xEA, 0xFF, 0xF3, 0xFF, 0xF6,
|
||||||
|
0xFF, 0x6D, 0xFF, 0xFD, 0x21, 0x6D, 0xE0, 0x42, 0x2E, 0x65, 0xD6, 0xDB, 0xFF, 0xFD, 0x21, 0x61, 0xF6, 0xA0, 0x02,
|
||||||
|
0xA2, 0x42, 0x2E, 0x73, 0xFF, 0xFD, 0xFF, 0xA2, 0x42, 0x2E, 0x73, 0xFF, 0x98, 0xFF, 0x9B, 0x23, 0x65, 0x6F, 0x61,
|
||||||
|
0xF2, 0xF2, 0xF9, 0x21, 0x6C, 0xF9, 0x21, 0x65, 0xFD, 0x23, 0x65, 0x6F, 0x61, 0xEC, 0xEC, 0xEC, 0x21, 0x6C, 0xF9,
|
||||||
|
0x21, 0x65, 0xFD, 0x21, 0x73, 0xFA, 0x21, 0x6F, 0xFD, 0x47, 0x61, 0x65, 0x6D, 0x74, 0x73, 0x6E, 0x6F, 0xFF, 0xC2,
|
||||||
|
0xFF, 0xC2, 0xFF, 0xEA, 0xFF, 0xF7, 0xFF, 0xF7, 0xFF, 0xFD, 0xFF, 0x08, 0x44, 0x6D, 0x74, 0x73, 0x6E, 0xFE, 0xDF,
|
||||||
|
0xFE, 0xDF, 0xFE, 0xEF, 0xFE, 0xF5, 0x41, 0x6F, 0xFE, 0xB6, 0x43, 0x6F, 0x61, 0x65, 0xF3, 0x41, 0xF3, 0x41, 0xF3,
|
||||||
|
0x41, 0x42, 0x2E, 0x6C, 0xD6, 0x6F, 0xFF, 0xF6, 0x21, 0x65, 0xF9, 0x41, 0x65, 0xE7, 0x5F, 0x44, 0x2E, 0x6D, 0x6C,
|
||||||
|
0x6E, 0xD6, 0x61, 0xFF, 0xFC, 0xFF, 0xE8, 0xFF, 0xE4, 0x21, 0x65, 0xF3, 0x46, 0x6C, 0x6E, 0x6F, 0x6D, 0x74, 0x73,
|
||||||
|
0xFE, 0xA9, 0xFF, 0xD4, 0xFE, 0x8A, 0xFF, 0xE9, 0xFF, 0xFD, 0xFF, 0xFD, 0x21, 0x6F, 0xED, 0x21, 0x64, 0xFD, 0x47,
|
||||||
|
0x73, 0x69, 0x62, 0x72, 0x64, 0x6F, 0x6E, 0xFF, 0x5D, 0xFE, 0x71, 0xFF, 0x64, 0xFF, 0x98, 0xFF, 0xAE, 0xFE, 0xA0,
|
||||||
|
0xFF, 0xFD, 0x41, 0x67, 0xFE, 0x9B, 0x21, 0x6F, 0xFC, 0x41, 0x63, 0xD6, 0x1E, 0x21, 0x69, 0xFC, 0x41, 0x65, 0xFF,
|
||||||
|
0x06, 0x21, 0x74, 0xFC, 0x45, 0x2E, 0x6C, 0x74, 0x6E, 0x73, 0xD6, 0x0D, 0xFF, 0xEF, 0xFF, 0xF6, 0xE7, 0x07, 0xFF,
|
||||||
|
0xFD, 0x45, 0xB1, 0xA9, 0xAD, 0xA1, 0xB3, 0xFE, 0x30, 0xFE, 0xA4, 0xFF, 0x09, 0xFF, 0xC5, 0xFF, 0xF0, 0xA0, 0x01,
|
||||||
|
0x72, 0xA0, 0x01, 0x92, 0x21, 0xB3, 0xFD, 0x22, 0x75, 0xC3, 0xF7, 0xFD, 0x21, 0x65, 0xF2, 0xA0, 0x02, 0x62, 0x21,
|
||||||
|
0x2E, 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0x21, 0x6F, 0xFB, 0x21, 0x65, 0xF5, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD,
|
||||||
|
0x21, 0x65, 0xFD, 0x23, 0x2E, 0x73, 0x6D, 0xE6, 0xE9, 0xFD, 0x22, 0x6F, 0x61, 0xE5, 0xF9, 0x21, 0x63, 0xFB, 0x21,
|
||||||
|
0x69, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0xB3, 0xFD, 0x21, 0x61, 0xD4, 0x21, 0xAD, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x67,
|
||||||
|
0xFD, 0x41, 0x67, 0xE1, 0x68, 0x23, 0xC3, 0x6F, 0x69, 0xED, 0xF9, 0xFC, 0xA0, 0x00, 0xC2, 0x21, 0x2E, 0xFD, 0x22,
|
||||||
|
0x2E, 0x73, 0xFA, 0xFD, 0x21, 0x65, 0xF8, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x23, 0x2E, 0x73,
|
||||||
|
0x6D, 0xE9, 0xEC, 0xFD, 0x44, 0x2E, 0x6F, 0x61, 0x74, 0xD5, 0x78, 0xFF, 0xE8, 0xFF, 0xF9, 0xF5, 0x24, 0x42, 0x6F,
|
||||||
|
0x61, 0xD9, 0x83, 0xD9, 0x83, 0x21, 0x74, 0xF9, 0x41, 0x6E, 0xF2, 0xAF, 0x43, 0x63, 0x74, 0x65, 0xE7, 0x07, 0xE7,
|
||||||
|
0x07, 0xFD, 0x93, 0x41, 0x74, 0xE6, 0xFD, 0x41, 0x69, 0xE5, 0x70, 0x41, 0x61, 0xD6, 0xF0, 0x21, 0xAD, 0xFC, 0x21,
|
||||||
|
0xC3, 0xFD, 0xA1, 0x04, 0xA2, 0x70, 0xFD, 0x47, 0x68, 0x61, 0x65, 0x69, 0x6F, 0x75, 0xC3, 0xD9, 0x07, 0xD9, 0x43,
|
||||||
|
0xFF, 0xFB, 0xD9, 0x43, 0xD9, 0x43, 0xD9, 0x43, 0xD9, 0x49, 0x21, 0x6F, 0xEA, 0x22, 0x6E, 0x74, 0xD4, 0xFD, 0xA0,
|
||||||
|
0x00, 0x91, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0xA0, 0x0F, 0x72, 0x21, 0x2E, 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD,
|
||||||
|
0x22, 0x6F, 0x61, 0xFB, 0xFB, 0xA0, 0x03, 0x32, 0x21, 0x2E, 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0xA0, 0x10, 0xF3,
|
||||||
|
0x21, 0x2E, 0xFD, 0x23, 0x61, 0x2E, 0x73, 0xF5, 0xFA, 0xFD, 0x21, 0x73, 0xEB, 0x22, 0x2E, 0x65, 0xE5, 0xFD, 0x21,
|
||||||
|
0x6C, 0xFB, 0x22, 0x65, 0x61, 0xEE, 0xFD, 0x22, 0x63, 0x64, 0xD3, 0xFB, 0x4D, 0x65, 0x61, 0x2E, 0x78, 0x6C, 0x73,
|
||||||
|
0x63, 0x6D, 0x6E, 0x70, 0x72, 0x6F, 0x69, 0xFE, 0xF1, 0xFE, 0xF6, 0xD4, 0xD5, 0xFF, 0x04, 0xFF, 0x3B, 0xFF, 0x60,
|
||||||
|
0xFF, 0x74, 0xFF, 0x77, 0xFF, 0x7B, 0xFF, 0x85, 0xFF, 0xB5, 0xFF, 0xC0, 0xFF, 0xFB, 0x42, 0x65, 0xC3, 0xFE, 0xC0,
|
||||||
|
0xFE, 0xC6, 0xA0, 0x03, 0x23, 0x21, 0x2E, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x67, 0xFD, 0x43, 0x2E,
|
||||||
|
0x73, 0x69, 0xD4, 0x97, 0xE5, 0x91, 0xFC, 0xD0, 0x21, 0x65, 0xF6, 0x41, 0x73, 0xFE, 0xB1, 0x44, 0x2E, 0x73, 0x69,
|
||||||
|
0x6E, 0xFE, 0xAA, 0xFE, 0xAD, 0xFF, 0xFC, 0xFE, 0xAD, 0x43, 0x2E, 0x74, 0x65, 0xD4, 0x79, 0xFF, 0xEC, 0xFF, 0xF3,
|
||||||
|
0xA0, 0x0C, 0xF1, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0xA0, 0x11, 0x22, 0x21, 0x6E, 0xFD, 0x21,
|
||||||
|
0x61, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x72, 0xFD, 0x23, 0x6F, 0x69, 0x65, 0xC7, 0xEB, 0xFD, 0x42,
|
||||||
|
0x6F, 0x72, 0xD4, 0x4A, 0xE0, 0x14, 0x45, 0x2E, 0x64, 0x66, 0x67, 0x74, 0xD4, 0x43, 0xFF, 0xF9, 0xF1, 0x94, 0xE5,
|
||||||
|
0xEC, 0xFA, 0x5A, 0x41, 0x65, 0xFC, 0x6C, 0x42, 0x6E, 0x73, 0xFE, 0x56, 0xFE, 0x56, 0x41, 0x6F, 0xFF, 0x9E, 0x41,
|
||||||
|
0x73, 0xFE, 0x48, 0x45, 0x2E, 0x73, 0x6D, 0x69, 0x6E, 0xFE, 0x44, 0xFE, 0x47, 0xFF, 0xF8, 0xFF, 0xFC, 0xFE, 0x47,
|
||||||
|
0x42, 0x61, 0x73, 0xFF, 0xF0, 0xFE, 0x37, 0x43, 0x2E, 0x73, 0x69, 0xFE, 0x2D, 0xFE, 0x30, 0xFF, 0x7F, 0x43, 0x73,
|
||||||
|
0x2E, 0x6E, 0xFE, 0x26, 0xFE, 0x23, 0xFE, 0x26, 0x23, 0xAD, 0xA9, 0xA1, 0xE5, 0xEC, 0xF6, 0x45, 0x6D, 0x2E, 0x73,
|
||||||
|
0x69, 0x6E, 0xFF, 0xC6, 0xFE, 0x12, 0xFE, 0x15, 0xFF, 0x64, 0xFE, 0x15, 0xA0, 0x03, 0x22, 0x21, 0x2E, 0xFD, 0x21,
|
||||||
|
0x65, 0xFD, 0x41, 0x65, 0xFF, 0x32, 0x42, 0x2E, 0x73, 0xFF, 0x2B, 0xFF, 0x2E, 0x23, 0x65, 0x61, 0x6F, 0xF9, 0xF9,
|
||||||
|
0xF9, 0x41, 0x73, 0xFF, 0x20, 0x21, 0x6F, 0xFC, 0x41, 0x68, 0xD7, 0xC2, 0xC2, 0x00, 0xD1, 0x2E, 0x73, 0xFD, 0xDC,
|
||||||
|
0xFD, 0xDF, 0x22, 0x6F, 0x61, 0xF7, 0xF7, 0x4C, 0x6F, 0xC3, 0x65, 0x61, 0x2E, 0x6D, 0x74, 0x6C, 0x73, 0x6E, 0x63,
|
||||||
|
0x69, 0xFF, 0x7B, 0xFF, 0xB5, 0xFF, 0xBC, 0xFF, 0x24, 0xD3, 0xAA, 0xFF, 0xD2, 0xFF, 0xD5, 0xFF, 0xE0, 0xFF, 0xD5,
|
||||||
|
0xFF, 0xEB, 0xFF, 0xEE, 0xFF, 0xFB, 0x41, 0x61, 0xFE, 0xFF, 0x43, 0x65, 0x6F, 0x61, 0xF0, 0x49, 0xF0, 0x49, 0xFB,
|
||||||
|
0xBA, 0x43, 0x2E, 0x61, 0x65, 0xFD, 0x9B, 0xFD, 0xA1, 0xFE, 0xED, 0x43, 0x2E, 0x73, 0x72, 0xFD, 0x91, 0xFD, 0x94,
|
||||||
|
0xFF, 0xF6, 0x48, 0x2E, 0x6D, 0x74, 0x6C, 0x6E, 0x6F, 0x61, 0x65, 0xD3, 0x63, 0xFC, 0xFE, 0xFC, 0xFE, 0xFF, 0xE2,
|
||||||
|
0xFC, 0xE6, 0xFF, 0xF6, 0xFD, 0x8D, 0xE3, 0xDD, 0xA0, 0x05, 0x41, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E,
|
||||||
|
0xFD, 0x41, 0x6E, 0xFD, 0x65, 0x21, 0xB3, 0xFC, 0x41, 0x65, 0xFE, 0xAD, 0x21, 0x6E, 0xFC, 0x22, 0xC3, 0x6F, 0xF6,
|
||||||
|
0xFD, 0x43, 0x74, 0x61, 0x69, 0xE4, 0xD8, 0xFF, 0xEA, 0xFF, 0xFB, 0x41, 0x72, 0xE4, 0xCE, 0x41, 0x64, 0xFE, 0xBA,
|
||||||
|
0x21, 0x61, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x63, 0xFD, 0x42, 0x72, 0x69, 0xE4,
|
||||||
|
0xB7, 0xFF, 0xFD, 0x41, 0x69, 0xE2, 0xB7, 0x41, 0x74, 0xED, 0x7B, 0x43, 0x64, 0x73, 0x74, 0xEA, 0xF2, 0xFF, 0xFC,
|
||||||
|
0xE4, 0xA8, 0x41, 0x73, 0xEC, 0xE8, 0x42, 0x2E, 0x65, 0xD2, 0xF0, 0xFF, 0xFC, 0xA0, 0x08, 0xF1, 0x21, 0x2E, 0xFD,
|
||||||
|
0x22, 0x2E, 0x73, 0xFA, 0xFD, 0x21, 0x6F, 0xFB, 0x21, 0x73, 0xFD, 0x21, 0xAD, 0xFD, 0x53, 0x61, 0x69, 0x73, 0x2E,
|
||||||
|
0x6D, 0x6E, 0x74, 0x72, 0x62, 0x64, 0x6F, 0x63, 0x65, 0x66, 0x67, 0x70, 0x75, 0x6C, 0xC3, 0xFE, 0x25, 0xFE, 0x38,
|
||||||
|
0xFE, 0x59, 0xD2, 0xD2, 0xFE, 0x81, 0xFE, 0x8F, 0xFE, 0x9F, 0xFF, 0x28, 0xFF, 0x4D, 0xFF, 0x6F, 0xFB, 0x0B, 0xFF,
|
||||||
|
0xA7, 0xFF, 0xB1, 0xFF, 0xC8, 0xFF, 0xB1, 0xFF, 0xCF, 0xFF, 0xD7, 0xFF, 0xE5, 0xFF, 0xFD, 0xA0, 0x01, 0xB2, 0x21,
|
||||||
|
0xA1, 0xFD, 0x43, 0xC3, 0x65, 0x73, 0xFF, 0xFD, 0xD2, 0xF0, 0xE3, 0x8C, 0x41, 0x65, 0xEB, 0xDA, 0x21, 0x6C, 0xFC,
|
||||||
|
0x41, 0x65, 0xE4, 0xF6, 0x21, 0x72, 0xFC, 0x21, 0x65, 0xFD, 0x43, 0x2E, 0x63, 0x74, 0xD2, 0x77, 0xFF, 0xF3, 0xFF,
|
||||||
|
0xFD, 0x41, 0x61, 0xD2, 0x6D, 0x21, 0x63, 0xFC, 0x21, 0x6F, 0xFD, 0x41, 0x6F, 0xD5, 0x4C, 0x43, 0x6F, 0x62, 0x69,
|
||||||
|
0xFD, 0xD5, 0xFF, 0xF9, 0xFF, 0xFC, 0xA0, 0x01, 0xB1, 0x21, 0x72, 0xFD, 0x21, 0x62, 0xFD, 0x21, 0x65, 0xFD, 0x43,
|
||||||
|
0x65, 0x6F, 0x72, 0xEC, 0xC5, 0xD6, 0x64, 0xDE, 0x0C, 0x45, 0x2E, 0x68, 0x64, 0x65, 0x74, 0xD2, 0x3F, 0xFF, 0xF3,
|
||||||
|
0xE3, 0xEC, 0xEB, 0xCF, 0xFF, 0xF6, 0xA0, 0x04, 0x13, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x6F, 0xFD, 0x21,
|
||||||
|
0x6D, 0xFD, 0x42, 0x2E, 0x65, 0xFC, 0x44, 0xFF, 0xFD, 0xA0, 0x03, 0xC2, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0x21,
|
||||||
|
0x61, 0xED, 0x22, 0x61, 0x65, 0xEA, 0xEA, 0x46, 0x73, 0x2E, 0x6E, 0x69, 0x62, 0x72, 0xFF, 0xE8, 0xFC, 0x2C, 0xFC,
|
||||||
|
0x2F, 0xFF, 0xF5, 0xFF, 0xF8, 0xFF, 0xFB, 0x45, 0x2E, 0x73, 0x6D, 0x69, 0x6E, 0xFC, 0x19, 0xFC, 0x1C, 0xFD, 0xCD,
|
||||||
|
0xFD, 0x6B, 0xFC, 0x1C, 0x42, 0x73, 0x61, 0xFC, 0x0C, 0xFF, 0xF0, 0x43, 0xA9, 0xA1, 0xAD, 0xFD, 0xD5, 0xFF, 0xD6,
|
||||||
|
0xFF, 0xF9, 0xA0, 0x02, 0xF3, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x6D, 0xFD, 0x43, 0x65,
|
||||||
|
0x61, 0x6F, 0xEE, 0x8D, 0xEE, 0x8D, 0xEE, 0x8D, 0x43, 0x2E, 0x73, 0x69, 0xFF, 0xA2, 0xFF, 0xA5, 0xFF, 0xA8, 0x21,
|
||||||
|
0x65, 0xF6, 0xA0, 0x03, 0xE3, 0x21, 0x2E, 0xFD, 0x21, 0x73, 0xFD, 0x24, 0x2E, 0x73, 0x69, 0x6E, 0xF7, 0xFA, 0xFD,
|
||||||
|
0xFA, 0x43, 0x2E, 0x74, 0x65, 0xFF, 0x83, 0xFF, 0xEB, 0xFF, 0xF7, 0x21, 0x6F, 0xEA, 0x41, 0x65, 0xFF, 0x7C, 0x21,
|
||||||
|
0x6E, 0xE0, 0x21, 0x73, 0xDA, 0x25, 0x2E, 0x73, 0x6D, 0x69, 0x6E, 0xD7, 0xDA, 0xF3, 0xFD, 0xDA, 0x22, 0x61, 0x73,
|
||||||
|
0xF5, 0xCF, 0x23, 0x2E, 0x73, 0x69, 0xC7, 0xCA, 0xCD, 0x23, 0x73, 0x2E, 0x6E, 0xC3, 0xC0, 0xC3, 0x23, 0xAD, 0xA9,
|
||||||
|
0xA1, 0xED, 0xF2, 0xF9, 0x45, 0x6D, 0x2E, 0x73, 0x69, 0x6E, 0xFF, 0xCE, 0xFF, 0xB2, 0xFF, 0xB5, 0xFF, 0xB8, 0xFF,
|
||||||
|
0xB5, 0x44, 0x6F, 0xC3, 0x65, 0x61, 0xFF, 0xC5, 0xFF, 0xE9, 0xFF, 0xF0, 0xFF, 0xAB, 0xA0, 0x10, 0x54, 0x21, 0x2E,
|
||||||
|
0xFD, 0x21, 0x65, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x23, 0x2E, 0x73, 0x6D, 0xEE, 0xF1,
|
||||||
|
0xFD, 0x21, 0x65, 0xF9, 0x42, 0x61, 0x6C, 0xFF, 0x82, 0xFF, 0xFD, 0x42, 0x2E, 0x73, 0xFF, 0x72, 0xFF, 0x75, 0x43,
|
||||||
|
0x2E, 0x61, 0x65, 0xFF, 0x6B, 0xFF, 0xF9, 0xFF, 0x71, 0x43, 0x2E, 0x73, 0x72, 0xFF, 0x61, 0xFF, 0x64, 0xFF, 0xF6,
|
||||||
|
0x43, 0x2E, 0x6F, 0x61, 0xFE, 0xEC, 0xFF, 0xF6, 0xFF, 0xE5, 0x47, 0x73, 0x6D, 0x6E, 0x74, 0x72, 0x62, 0x64, 0xFF,
|
||||||
|
0x5F, 0xFF, 0x69, 0xFE, 0xE5, 0xFF, 0x6C, 0xFF, 0xAB, 0xFF, 0xD4, 0xFF, 0xF6, 0x42, 0x2E, 0x65, 0xFB, 0x09, 0xFC,
|
||||||
|
0x5B, 0x21, 0x64, 0xF9, 0x21, 0x61, 0xFD, 0x21, 0x64, 0xFD, 0x45, 0x2E, 0x65, 0x61, 0x6D, 0x69, 0xFA, 0xF9, 0xFC,
|
||||||
|
0x4B, 0xFA, 0xFF, 0xFB, 0x10, 0xFF, 0xFD, 0x21, 0x72, 0xF0, 0x21, 0x6F, 0xFD, 0x4B, 0xC3, 0x65, 0x2E, 0x6D, 0x74,
|
||||||
|
0x6C, 0x73, 0x6E, 0x6F, 0x61, 0x69, 0xFE, 0xE1, 0xFE, 0xF7, 0xD0, 0xBF, 0xFA, 0x5A, 0xFA, 0x5A, 0xFE, 0xFA, 0xFA,
|
||||||
|
0x5A, 0xFA, 0x42, 0xFC, 0x35, 0xFF, 0xC4, 0xFF, 0xFD, 0x46, 0x2E, 0x6D, 0x74, 0x6C, 0x6E, 0x72, 0xD0, 0x9D, 0xFA,
|
||||||
|
0x38, 0xFA, 0x38, 0xFD, 0x1C, 0xFA, 0x20, 0xFA, 0xCC, 0x41, 0x61, 0xE1, 0x84, 0x21, 0x6C, 0xFC, 0x21, 0x64, 0xFD,
|
||||||
|
0x41, 0x6F, 0xFB, 0x7E, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x2E, 0xE3,
|
||||||
|
0x46, 0x42, 0x2E, 0x73, 0xE3, 0x42, 0xFF, 0xFC, 0x22, 0x6F, 0x61, 0xF9, 0xF9, 0x21, 0x69, 0xFB, 0x23, 0x64, 0x6D,
|
||||||
|
0x63, 0xD7, 0xEA, 0xFD, 0xA0, 0x00, 0x81, 0x21, 0x6F, 0xFD, 0x21, 0x64, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0xA1, 0xFD,
|
||||||
|
0x42, 0x6F, 0x72, 0xD4, 0x62, 0xDC, 0x11, 0xA0, 0x11, 0x92, 0x21, 0x6C, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD,
|
||||||
|
0x21, 0x72, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x61, 0xFD, 0x44, 0x61, 0x6F, 0x74, 0x75, 0xE0, 0xA2,
|
||||||
|
0xE9, 0x8F, 0xFF, 0xE1, 0xFF, 0xFD, 0x41, 0x6E, 0xE1, 0xC8, 0x42, 0x63, 0x72, 0xE1, 0xC4, 0xE1, 0xC4, 0x42, 0x6D,
|
||||||
|
0x72, 0xD9, 0x69, 0xD4, 0xC3, 0x41, 0x67, 0xD9, 0xBC, 0x42, 0x61, 0x6F, 0xD0, 0x9B, 0xD0, 0x9B, 0x43, 0x61, 0x65,
|
||||||
|
0x6F, 0xD0, 0x94, 0xD0, 0x94, 0xD0, 0x94, 0x21, 0x69, 0xF6, 0x44, 0x61, 0x69, 0x65, 0x6F, 0xD0, 0x87, 0xD0, 0x87,
|
||||||
|
0xD0, 0x87, 0xD0, 0x87, 0x44, 0x6A, 0x67, 0x6C, 0x6D, 0xFF, 0xDF, 0xD9, 0x97, 0xFF, 0xF0, 0xFF, 0xF3, 0x44, 0xA1,
|
||||||
|
0xA9, 0xB3, 0xAD, 0xFF, 0xC7, 0xFF, 0xCE, 0xD8, 0x5C, 0xFF, 0xF3, 0x41, 0x72, 0xDC, 0x2A, 0x21, 0x65, 0xFC, 0x41,
|
||||||
|
0x6D, 0xE2, 0x99, 0x21, 0x75, 0xFC, 0x41, 0x69, 0xD1, 0xE0, 0x44, 0x63, 0x67, 0x6C, 0x6D, 0xFF, 0xF2, 0xD9, 0x83,
|
||||||
|
0xFF, 0xF9, 0xFF, 0xFC, 0x41, 0x74, 0xD2, 0x2C, 0x21, 0xA9, 0xFC, 0x21, 0xC3, 0xFD, 0x41, 0x69, 0xD7, 0xE1, 0x21,
|
||||||
|
0x75, 0xFC, 0x43, 0x63, 0x67, 0x71, 0xD1, 0xB0, 0xFF, 0xF6, 0xFF, 0xFD, 0x43, 0x61, 0xC3, 0x6F, 0xD1, 0xA3, 0xD9,
|
||||||
|
0x2F, 0xD1, 0xA3, 0x41, 0xAD, 0xD1, 0x99, 0x43, 0x65, 0x69, 0xC3, 0xD1, 0x95, 0xD1, 0x95, 0xFF, 0xFC, 0x42, 0x69,
|
||||||
|
0x61, 0xD7, 0x0B, 0xD1, 0x8E, 0x44, 0xA1, 0xAD, 0xA9, 0xB3, 0xD1, 0x84, 0xD1, 0x84, 0xD1, 0x84, 0xD1, 0x84, 0x45,
|
||||||
|
0x61, 0xC3, 0x69, 0x65, 0x6F, 0xD1, 0x77, 0xFF, 0xF3, 0xD1, 0x77, 0xD1, 0x77, 0xD1, 0x77, 0x41, 0x6F, 0xD1, 0xE6,
|
||||||
|
0x25, 0x6A, 0x67, 0x6C, 0x6D, 0x74, 0xC0, 0xCE, 0xD8, 0xEC, 0xFC, 0x41, 0xB3, 0xD1, 0x58, 0x21, 0xC3, 0xFC, 0x21,
|
||||||
|
0x69, 0xFD, 0x41, 0x72, 0xFF, 0x7F, 0x41, 0xA9, 0xD1, 0x4D, 0x43, 0x63, 0x71, 0x73, 0xD1, 0x46, 0xD1, 0x46, 0xD6,
|
||||||
|
0x27, 0x22, 0xC3, 0x69, 0xF2, 0xF6, 0x41, 0x6D, 0xD1, 0xA5, 0x21, 0xA1, 0xFC, 0x22, 0x61, 0xC3, 0xF9, 0xFD, 0x41,
|
||||||
|
0x71, 0xD1, 0x2B, 0x21, 0x73, 0xFC, 0x41, 0x61, 0xD1, 0x35, 0x21, 0x6C, 0xFC, 0x47, 0x62, 0x6E, 0x63, 0x74, 0x67,
|
||||||
|
0x65, 0x70, 0xFF, 0xCC, 0xD8, 0xD5, 0xFF, 0xCF, 0xFF, 0xE1, 0xFF, 0xED, 0xFF, 0xF6, 0xFF, 0xFD, 0x43, 0x72, 0x74,
|
||||||
|
0x63, 0xD1, 0x07, 0xD1, 0x07, 0xD1, 0x07, 0x21, 0x61, 0xF6, 0x42, 0x62, 0x64, 0xD8, 0xB2, 0xFF, 0xFD, 0x41, 0x72,
|
||||||
|
0xD0, 0x12, 0xA0, 0x0D, 0xA1, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x6F, 0xFD, 0x48, 0xC3, 0x61, 0x65, 0x69,
|
||||||
|
0x6F, 0x75, 0x74, 0x70, 0xFE, 0xF9, 0xFF, 0x18, 0xFF, 0x36, 0xFF, 0x80, 0xFF, 0xC6, 0xFF, 0xE9, 0xFF, 0xF0, 0xFF,
|
||||||
|
0xFD, 0x41, 0x72, 0xFA, 0x54, 0x21, 0x74, 0xFC, 0x21, 0x63, 0xFD, 0x21, 0xA9, 0xFD, 0x22, 0x65, 0xC3, 0xFA, 0xFD,
|
||||||
|
0x4F, 0x6F, 0x73, 0x2E, 0x6D, 0x6E, 0x72, 0x64, 0x65, 0x61, 0xC3, 0x63, 0x74, 0x75, 0x78, 0x6C, 0xFC, 0x13, 0xFC,
|
||||||
|
0x2E, 0xCE, 0xA5, 0xFC, 0x46, 0xFC, 0x66, 0xFD, 0xE6, 0xFE, 0x08, 0xFE, 0x22, 0xFE, 0x48, 0xFE, 0x5B, 0xFE, 0x7D,
|
||||||
|
0xFE, 0x8A, 0xFE, 0x8E, 0xFF, 0xD5, 0xFF, 0xFB, 0x43, 0x2E, 0x73, 0x6D, 0xF8, 0x9B, 0xF8, 0x9E, 0xFA, 0x4F, 0xA0,
|
||||||
|
0x03, 0x53, 0x21, 0x2E, 0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0xA0, 0x03, 0x84, 0x21, 0x2E, 0xFD, 0x22, 0x2E, 0x73,
|
||||||
|
0xFA, 0xFD, 0x23, 0x65, 0x6F, 0x61, 0xF0, 0xF0, 0xFB, 0x22, 0x2E, 0x6C, 0xE3, 0xF9, 0x21, 0x65, 0xFB, 0x23, 0x65,
|
||||||
|
0x61, 0x6F, 0xE1, 0xE1, 0xE1, 0x23, 0x65, 0x6F, 0x61, 0xDA, 0xDA, 0xDA, 0x21, 0x6C, 0xF9, 0x24, 0x6D, 0x74, 0x6C,
|
||||||
|
0x65, 0xEC, 0xEC, 0xEF, 0xFD, 0x22, 0x2E, 0x6C, 0xC1, 0xED, 0x21, 0x73, 0xFB, 0x21, 0x6F, 0xFD, 0x23, 0x73, 0x6E,
|
||||||
|
0x6F, 0xEC, 0xFD, 0xFA, 0x21, 0x6F, 0xF9, 0x43, 0x73, 0x69, 0x6D, 0xF8, 0x40, 0xF9, 0x8F, 0xFF, 0xFD, 0x21, 0xA1,
|
||||||
|
0xF6, 0x43, 0x6F, 0x61, 0xC3, 0xF8, 0x33, 0xFF, 0x95, 0xFF, 0xFD, 0x41, 0x69, 0xF9, 0x78, 0x21, 0x74, 0xFC, 0x41,
|
||||||
|
0x73, 0xF8, 0xEC, 0x42, 0x2E, 0x65, 0xF8, 0xE5, 0xFF, 0xFC, 0x21, 0x6C, 0xF9, 0x43, 0x69, 0x61, 0x65, 0xFF, 0xEF,
|
||||||
|
0xFF, 0xFD, 0xF8, 0x1C, 0x41, 0x61, 0xEA, 0xAB, 0x42, 0x6F, 0x69, 0xCE, 0x5D, 0xCE, 0xBE, 0x21, 0x6D, 0xF9, 0x41,
|
||||||
|
0x69, 0xCE, 0xB4, 0x21, 0x6D, 0xFC, 0x21, 0xA1, 0xFD, 0x22, 0x61, 0xC3, 0xF3, 0xFD, 0x44, 0x6D, 0x74, 0x6C, 0x6F,
|
||||||
|
0xF6, 0xB8, 0xFF, 0xE3, 0xFF, 0xFB, 0xE7, 0x2D, 0xC3, 0x02, 0x91, 0x61, 0xC3, 0x65, 0xCF, 0xCC, 0xD7, 0x58, 0xCF,
|
||||||
|
0xCC, 0x21, 0x69, 0xF4, 0x21, 0x63, 0xFD, 0xC1, 0x05, 0x81, 0x61, 0xCE, 0x3D, 0x21, 0x69, 0xFA, 0x21, 0x63, 0xFD,
|
||||||
|
0x21, 0xAD, 0xFD, 0xA0, 0x02, 0xB1, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0xA9, 0xFD, 0x23, 0x62, 0x65, 0xC3,
|
||||||
|
0xF4, 0xFA, 0xFD, 0x21, 0x62, 0xED, 0x21, 0x6C, 0xEA, 0x21, 0x6D, 0xE7, 0x21, 0x69, 0xE7, 0x21, 0x70, 0xFD, 0x21,
|
||||||
|
0x73, 0xFD, 0x25, 0xAD, 0xA1, 0xA9, 0xBA, 0xB3, 0xEE, 0xF1, 0xE1, 0xF4, 0xFD, 0x22, 0x74, 0x69, 0xD0, 0xD0, 0x21,
|
||||||
|
0x6E, 0xCE, 0x21, 0x65, 0xFD, 0x22, 0x73, 0x72, 0xF5, 0xFD, 0x25, 0x69, 0xC3, 0x61, 0x65, 0x75, 0xCC, 0xE5, 0xD6,
|
||||||
|
0xFB, 0xD9, 0x41, 0x75, 0xEA, 0x4B, 0xA0, 0x0B, 0xE3, 0x22, 0x75, 0x74, 0xFD, 0xFD, 0x22, 0x73, 0x64, 0xFB, 0xF8,
|
||||||
|
0xA0, 0x0C, 0x63, 0x21, 0x72, 0xFD, 0xA0, 0x0C, 0x93, 0x21, 0x2E, 0xFD, 0x23, 0x6E, 0x6F, 0x6D, 0xEF, 0xF7, 0xFD,
|
||||||
|
0x41, 0x73, 0xEA, 0x44, 0x21, 0xA9, 0xFC, 0xA0, 0x0A, 0x12, 0x21, 0x72, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x73, 0xFD,
|
||||||
|
0xA0, 0x0C, 0x42, 0x21, 0x6F, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x65, 0xFD, 0x24, 0x69, 0xC3, 0x65,
|
||||||
|
0x72, 0xD7, 0xE2, 0xEE, 0xFD, 0x21, 0x72, 0xF7, 0x42, 0x65, 0x72, 0xFF, 0xFD, 0xCE, 0x2D, 0x41, 0x72, 0xFC, 0xB4,
|
||||||
|
0x21, 0x74, 0xFC, 0x21, 0x73, 0xFD, 0x21, 0x75, 0xFD, 0x41, 0x72, 0xCD, 0xC6, 0x21, 0x65, 0xFC, 0x21, 0x69, 0xFD,
|
||||||
|
0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x4A, 0x69, 0xC3, 0x68, 0x66, 0x6D, 0x74, 0x6F, 0x61, 0x64, 0x67, 0xFF, 0x2D,
|
||||||
|
0xFF, 0x3C, 0xFF, 0x7F, 0xFD, 0xF7, 0xFF, 0x8A, 0xFF, 0xDC, 0xE9, 0x9F, 0xE9, 0x9F, 0xFF, 0xED, 0xFF, 0xFD, 0x41,
|
||||||
|
0x65, 0xD8, 0x86, 0x43, 0x6E, 0x2E, 0x73, 0xD8, 0x7E, 0xF7, 0x21, 0xF7, 0x24, 0x42, 0x6F, 0x61, 0xFF, 0xF6, 0xF7,
|
||||||
|
0x1D, 0x42, 0x2E, 0x73, 0xF8, 0xC5, 0xF8, 0xC8, 0x22, 0x6F, 0x61, 0xF9, 0xF9, 0x41, 0x2E, 0xEE, 0x81, 0x21, 0x73,
|
||||||
|
0xFC, 0x21, 0x65, 0xFD, 0x44, 0x6E, 0x72, 0x2E, 0x73, 0xFF, 0xF1, 0xFF, 0xFD, 0xF7, 0x72, 0xF7, 0x75, 0x41, 0x61,
|
||||||
|
0xDE, 0x29, 0x41, 0x72, 0xF8, 0xA1, 0x42, 0x2E, 0x73, 0xF7, 0x5D, 0xF7, 0x60, 0x4A, 0x67, 0x64, 0x73, 0x6E, 0x62,
|
||||||
|
0x63, 0x61, 0x74, 0x65, 0x6F, 0xFE, 0x65, 0xFE, 0x84, 0xFE, 0xAB, 0xFF, 0x9A, 0xFF, 0xB9, 0xFF, 0xC7, 0xFF, 0xE4,
|
||||||
|
0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xF9, 0x41, 0x69, 0xFB, 0xFC, 0x21, 0x72, 0xFC, 0x21, 0x65, 0xFD, 0x41, 0x74, 0xFD,
|
||||||
|
0x68, 0xA0, 0x11, 0xB2, 0x42, 0x64, 0x74, 0xFF, 0xFD, 0xFC, 0x01, 0x21, 0x69, 0xF9, 0x21, 0x73, 0xFD, 0x21, 0x72,
|
||||||
|
0xFD, 0x21, 0x65, 0xFD, 0x21, 0x76, 0xFD, 0x21, 0x69, 0xFD, 0x23, 0x74, 0x6C, 0x6E, 0xDD, 0xE0, 0xFD, 0x5C, 0x62,
|
||||||
|
0x2E, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x70, 0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x78,
|
||||||
|
0x79, 0x7A, 0xC3, 0x6F, 0x61, 0x65, 0x69, 0x75, 0xCD, 0x5C, 0xDB, 0x8C, 0xDD, 0xF1, 0xE3, 0xC6, 0xE4, 0x43, 0xE5,
|
||||||
|
0xC4, 0xE7, 0x42, 0xE7, 0x94, 0xE7, 0xDD, 0xE8, 0x9B, 0xE9, 0xD6, 0xEA, 0x67, 0xED, 0x21, 0xED, 0x83, 0xEE, 0x2C,
|
||||||
|
0xF0, 0x08, 0xF2, 0x7F, 0xF2, 0xDD, 0xF3, 0x29, 0xF3, 0x78, 0xF3, 0xC3, 0xF4, 0x0C, 0xF6, 0x24, 0xF7, 0x4C, 0xF9,
|
||||||
|
0x4F, 0xFD, 0x7C, 0xFF, 0xB0, 0xFF, 0xF9,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr SerializedHyphenationPatterns es_patterns = {
|
||||||
|
es_trie_data,
|
||||||
|
sizeof(es_trie_data),
|
||||||
|
};
|
||||||
@ -40,6 +40,23 @@ bool matches(const char* tag_name, const char* possible_tags[], const int possib
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flush the contents of partWordBuffer to currentTextBlock
|
||||||
|
void ChapterHtmlSlimParser::flushPartWordBuffer() {
|
||||||
|
// determine font style
|
||||||
|
EpdFontFamily::Style fontStyle = EpdFontFamily::REGULAR;
|
||||||
|
if (boldUntilDepth < depth && italicUntilDepth < depth) {
|
||||||
|
fontStyle = EpdFontFamily::BOLD_ITALIC;
|
||||||
|
} else if (boldUntilDepth < depth) {
|
||||||
|
fontStyle = EpdFontFamily::BOLD;
|
||||||
|
} else if (italicUntilDepth < depth) {
|
||||||
|
fontStyle = EpdFontFamily::ITALIC;
|
||||||
|
}
|
||||||
|
// flush the buffer
|
||||||
|
partWordBuffer[partWordBufferIndex] = '\0';
|
||||||
|
currentTextBlock->addWord(partWordBuffer, fontStyle);
|
||||||
|
partWordBufferIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// start a new text block if needed
|
// start a new text block if needed
|
||||||
void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::Style style) {
|
void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::Style style) {
|
||||||
if (currentTextBlock) {
|
if (currentTextBlock) {
|
||||||
@ -67,39 +84,43 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
if (strcmp(name, "table") == 0) {
|
if (strcmp(name, "table") == 0) {
|
||||||
// Add placeholder text
|
// Add placeholder text
|
||||||
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
||||||
if (self->currentTextBlock) {
|
|
||||||
self->currentTextBlock->addWord("[Table omitted]", EpdFontFamily::ITALIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip table contents
|
self->italicUntilDepth = min(self->italicUntilDepth, self->depth);
|
||||||
self->skipUntilDepth = self->depth;
|
// Advance depth before processing character data (like you would for a element with text)
|
||||||
self->depth += 1;
|
self->depth += 1;
|
||||||
|
self->characterData(userData, "[Table omitted]", strlen("[Table omitted]"));
|
||||||
|
|
||||||
|
// Skip table contents (skip until parent as we pre-advanced depth above)
|
||||||
|
self->skipUntilDepth = self->depth - 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS)) {
|
if (matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS)) {
|
||||||
// TODO: Start processing image tags
|
// TODO: Start processing image tags
|
||||||
std::string alt;
|
std::string alt = "[Image]";
|
||||||
if (atts != nullptr) {
|
if (atts != nullptr) {
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
for (int i = 0; atts[i]; i += 2) {
|
||||||
if (strcmp(atts[i], "alt") == 0) {
|
if (strcmp(atts[i], "alt") == 0) {
|
||||||
|
if (strlen(atts[i + 1]) > 0) {
|
||||||
alt = "[Image: " + std::string(atts[i + 1]) + "]";
|
alt = "[Image: " + std::string(atts[i + 1]) + "]";
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Serial.printf("[%lu] [EHP] Image alt: %s\n", millis(), alt.c_str());
|
Serial.printf("[%lu] [EHP] Image alt: %s\n", millis(), alt.c_str());
|
||||||
|
|
||||||
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
||||||
self->italicUntilDepth = min(self->italicUntilDepth, self->depth);
|
self->italicUntilDepth = min(self->italicUntilDepth, self->depth);
|
||||||
|
// Advance depth before processing character data (like you would for a element with text)
|
||||||
self->depth += 1;
|
self->depth += 1;
|
||||||
self->characterData(userData, alt.c_str(), alt.length());
|
self->characterData(userData, alt.c_str(), alt.length());
|
||||||
|
|
||||||
} else {
|
// Skip table contents (skip until parent as we pre-advanced depth above)
|
||||||
// Skip for now
|
self->skipUntilDepth = self->depth - 1;
|
||||||
self->skipUntilDepth = self->depth;
|
|
||||||
self->depth += 1;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (matches(name, SKIP_TAGS, NUM_SKIP_TAGS)) {
|
if (matches(name, SKIP_TAGS, NUM_SKIP_TAGS)) {
|
||||||
// start skip
|
// start skip
|
||||||
@ -123,21 +144,43 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
if (matches(name, HEADER_TAGS, NUM_HEADER_TAGS)) {
|
if (matches(name, HEADER_TAGS, NUM_HEADER_TAGS)) {
|
||||||
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
||||||
self->boldUntilDepth = std::min(self->boldUntilDepth, self->depth);
|
self->boldUntilDepth = std::min(self->boldUntilDepth, self->depth);
|
||||||
} else if (matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS)) {
|
self->depth += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS)) {
|
||||||
if (strcmp(name, "br") == 0) {
|
if (strcmp(name, "br") == 0) {
|
||||||
|
if (self->partWordBufferIndex > 0) {
|
||||||
|
// flush word preceding <br/> to currentTextBlock before calling startNewTextBlock
|
||||||
|
self->flushPartWordBuffer();
|
||||||
|
}
|
||||||
self->startNewTextBlock(self->currentTextBlock->getStyle());
|
self->startNewTextBlock(self->currentTextBlock->getStyle());
|
||||||
} else {
|
self->depth += 1;
|
||||||
self->startNewTextBlock((TextBlock::Style)self->paragraphAlignment);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->startNewTextBlock(static_cast<TextBlock::Style>(self->paragraphAlignment));
|
||||||
if (strcmp(name, "li") == 0) {
|
if (strcmp(name, "li") == 0) {
|
||||||
self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR);
|
self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (matches(name, BOLD_TAGS, NUM_BOLD_TAGS)) {
|
self->depth += 1;
|
||||||
self->boldUntilDepth = std::min(self->boldUntilDepth, self->depth);
|
return;
|
||||||
} else if (matches(name, ITALIC_TAGS, NUM_ITALIC_TAGS)) {
|
|
||||||
self->italicUntilDepth = std::min(self->italicUntilDepth, self->depth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (matches(name, BOLD_TAGS, NUM_BOLD_TAGS)) {
|
||||||
|
self->boldUntilDepth = std::min(self->boldUntilDepth, self->depth);
|
||||||
|
self->depth += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches(name, ITALIC_TAGS, NUM_ITALIC_TAGS)) {
|
||||||
|
self->italicUntilDepth = std::min(self->italicUntilDepth, self->depth);
|
||||||
|
self->depth += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unprocessed tag, just increasing depth and continue forward
|
||||||
self->depth += 1;
|
self->depth += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,22 +192,11 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EpdFontFamily::Style fontStyle = EpdFontFamily::REGULAR;
|
|
||||||
if (self->boldUntilDepth < self->depth && self->italicUntilDepth < self->depth) {
|
|
||||||
fontStyle = EpdFontFamily::BOLD_ITALIC;
|
|
||||||
} else if (self->boldUntilDepth < self->depth) {
|
|
||||||
fontStyle = EpdFontFamily::BOLD;
|
|
||||||
} else if (self->italicUntilDepth < self->depth) {
|
|
||||||
fontStyle = EpdFontFamily::ITALIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
if (isWhitespace(s[i])) {
|
if (isWhitespace(s[i])) {
|
||||||
// Currently looking at whitespace, if there's anything in the partWordBuffer, flush it
|
// Currently looking at whitespace, if there's anything in the partWordBuffer, flush it
|
||||||
if (self->partWordBufferIndex > 0) {
|
if (self->partWordBufferIndex > 0) {
|
||||||
self->partWordBuffer[self->partWordBufferIndex] = '\0';
|
self->flushPartWordBuffer();
|
||||||
self->currentTextBlock->addWord(self->partWordBuffer, fontStyle);
|
|
||||||
self->partWordBufferIndex = 0;
|
|
||||||
}
|
}
|
||||||
// Skip the whitespace char
|
// Skip the whitespace char
|
||||||
continue;
|
continue;
|
||||||
@ -186,9 +218,7 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
|
|||||||
|
|
||||||
// If we're about to run out of space, then cut the word off and start a new one
|
// If we're about to run out of space, then cut the word off and start a new one
|
||||||
if (self->partWordBufferIndex >= MAX_WORD_SIZE) {
|
if (self->partWordBufferIndex >= MAX_WORD_SIZE) {
|
||||||
self->partWordBuffer[self->partWordBufferIndex] = '\0';
|
self->flushPartWordBuffer();
|
||||||
self->currentTextBlock->addWord(self->partWordBuffer, fontStyle);
|
|
||||||
self->partWordBufferIndex = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self->partWordBuffer[self->partWordBufferIndex++] = s[i];
|
self->partWordBuffer[self->partWordBufferIndex++] = s[i];
|
||||||
@ -216,21 +246,11 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
|
|||||||
// text styling needs to be overhauled to fix it.
|
// text styling needs to be overhauled to fix it.
|
||||||
const bool shouldBreakText =
|
const bool shouldBreakText =
|
||||||
matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS) || matches(name, HEADER_TAGS, NUM_HEADER_TAGS) ||
|
matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS) || matches(name, HEADER_TAGS, NUM_HEADER_TAGS) ||
|
||||||
matches(name, BOLD_TAGS, NUM_BOLD_TAGS) || matches(name, ITALIC_TAGS, NUM_ITALIC_TAGS) || self->depth == 1;
|
matches(name, BOLD_TAGS, NUM_BOLD_TAGS) || matches(name, ITALIC_TAGS, NUM_ITALIC_TAGS) ||
|
||||||
|
strcmp(name, "table") == 0 || matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS) || self->depth == 1;
|
||||||
|
|
||||||
if (shouldBreakText) {
|
if (shouldBreakText) {
|
||||||
EpdFontFamily::Style fontStyle = EpdFontFamily::REGULAR;
|
self->flushPartWordBuffer();
|
||||||
if (self->boldUntilDepth < self->depth && self->italicUntilDepth < self->depth) {
|
|
||||||
fontStyle = EpdFontFamily::BOLD_ITALIC;
|
|
||||||
} else if (self->boldUntilDepth < self->depth) {
|
|
||||||
fontStyle = EpdFontFamily::BOLD;
|
|
||||||
} else if (self->italicUntilDepth < self->depth) {
|
|
||||||
fontStyle = EpdFontFamily::ITALIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
self->partWordBuffer[self->partWordBufferIndex] = '\0';
|
|
||||||
self->currentTextBlock->addWord(self->partWordBuffer, fontStyle);
|
|
||||||
self->partWordBufferIndex = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ class ChapterHtmlSlimParser {
|
|||||||
bool hyphenationEnabled;
|
bool hyphenationEnabled;
|
||||||
|
|
||||||
void startNewTextBlock(TextBlock::Style style);
|
void startNewTextBlock(TextBlock::Style style);
|
||||||
|
void flushPartWordBuffer();
|
||||||
void makePages();
|
void makePages();
|
||||||
// XML callbacks
|
// XML callbacks
|
||||||
static void XMLCALL startElement(void* userData, const XML_Char* name, const XML_Char** atts);
|
static void XMLCALL startElement(void* userData, const XML_Char* name, const XML_Char** atts);
|
||||||
|
|||||||
@ -38,6 +38,9 @@ ContentOpfParser::~ContentOpfParser() {
|
|||||||
if (SdMan.exists((cachePath + itemCacheFile).c_str())) {
|
if (SdMan.exists((cachePath + itemCacheFile).c_str())) {
|
||||||
SdMan.remove((cachePath + itemCacheFile).c_str());
|
SdMan.remove((cachePath + itemCacheFile).c_str());
|
||||||
}
|
}
|
||||||
|
itemIndex.clear();
|
||||||
|
itemIndex.shrink_to_fit();
|
||||||
|
useItemIndex = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ContentOpfParser::write(const uint8_t data) { return write(&data, 1); }
|
size_t ContentOpfParser::write(const uint8_t data) { return write(&data, 1); }
|
||||||
@ -129,6 +132,15 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
"[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n",
|
"[%lu] [COF] Couldn't open temp items file for reading. This is probably going to be a fatal error.\n",
|
||||||
millis());
|
millis());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort item index for binary search if we have enough items
|
||||||
|
if (self->itemIndex.size() >= LARGE_SPINE_THRESHOLD) {
|
||||||
|
std::sort(self->itemIndex.begin(), self->itemIndex.end(), [](const ItemIndexEntry& a, const ItemIndexEntry& b) {
|
||||||
|
return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen);
|
||||||
|
});
|
||||||
|
self->useItemIndex = true;
|
||||||
|
Serial.printf("[%lu] [COF] Using fast index for %zu manifest items\n", millis(), self->itemIndex.size());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +192,15 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record index entry for fast lookup later
|
||||||
|
if (self->tempItemStore) {
|
||||||
|
ItemIndexEntry entry;
|
||||||
|
entry.idHash = fnvHash(itemId);
|
||||||
|
entry.idLen = static_cast<uint16_t>(itemId.size());
|
||||||
|
entry.fileOffset = static_cast<uint32_t>(self->tempItemStore.position());
|
||||||
|
self->itemIndex.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
// Write items down to SD card
|
// Write items down to SD card
|
||||||
serialization::writeString(self->tempItemStore, itemId);
|
serialization::writeString(self->tempItemStore, itemId);
|
||||||
serialization::writeString(self->tempItemStore, href);
|
serialization::writeString(self->tempItemStore, href);
|
||||||
@ -215,21 +236,52 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
for (int i = 0; atts[i]; i += 2) {
|
for (int i = 0; atts[i]; i += 2) {
|
||||||
if (strcmp(atts[i], "idref") == 0) {
|
if (strcmp(atts[i], "idref") == 0) {
|
||||||
const std::string idref = atts[i + 1];
|
const std::string idref = atts[i + 1];
|
||||||
// Resolve the idref to href using items map
|
std::string href;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
if (self->useItemIndex) {
|
||||||
|
// Fast path: binary search
|
||||||
|
uint32_t targetHash = fnvHash(idref);
|
||||||
|
uint16_t targetLen = static_cast<uint16_t>(idref.size());
|
||||||
|
|
||||||
|
auto it = std::lower_bound(self->itemIndex.begin(), self->itemIndex.end(),
|
||||||
|
ItemIndexEntry{targetHash, targetLen, 0},
|
||||||
|
[](const ItemIndexEntry& a, const ItemIndexEntry& b) {
|
||||||
|
return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for match (may need to check a few due to hash collisions)
|
||||||
|
while (it != self->itemIndex.end() && it->idHash == targetHash) {
|
||||||
|
self->tempItemStore.seek(it->fileOffset);
|
||||||
|
std::string itemId;
|
||||||
|
serialization::readString(self->tempItemStore, itemId);
|
||||||
|
if (itemId == idref) {
|
||||||
|
serialization::readString(self->tempItemStore, href);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Slow path: linear scan (for small manifests, keeps original behavior)
|
||||||
// TODO: This lookup is slow as need to scan through all items each time.
|
// TODO: This lookup is slow as need to scan through all items each time.
|
||||||
// It can take up to 200ms per item when getting to 1500 items.
|
// It can take up to 200ms per item when getting to 1500 items.
|
||||||
self->tempItemStore.seek(0);
|
self->tempItemStore.seek(0);
|
||||||
std::string itemId;
|
std::string itemId;
|
||||||
std::string href;
|
|
||||||
while (self->tempItemStore.available()) {
|
while (self->tempItemStore.available()) {
|
||||||
serialization::readString(self->tempItemStore, itemId);
|
serialization::readString(self->tempItemStore, itemId);
|
||||||
serialization::readString(self->tempItemStore, href);
|
serialization::readString(self->tempItemStore, href);
|
||||||
if (itemId == idref) {
|
if (itemId == idref) {
|
||||||
self->cache->createSpineEntry(href);
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (found && self->cache) {
|
||||||
|
self->cache->createSpineEntry(href);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Print.h>
|
#include <Print.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "Epub.h"
|
#include "Epub.h"
|
||||||
#include "expat.h"
|
#include "expat.h"
|
||||||
|
|
||||||
@ -28,6 +31,27 @@ class ContentOpfParser final : public Print {
|
|||||||
FsFile tempItemStore;
|
FsFile tempItemStore;
|
||||||
std::string coverItemId;
|
std::string coverItemId;
|
||||||
|
|
||||||
|
// Index for fast idref→href lookup (used only for large EPUBs)
|
||||||
|
struct ItemIndexEntry {
|
||||||
|
uint32_t idHash; // FNV-1a hash of itemId
|
||||||
|
uint16_t idLen; // length for collision reduction
|
||||||
|
uint32_t fileOffset; // offset in .items.bin
|
||||||
|
};
|
||||||
|
std::vector<ItemIndexEntry> itemIndex;
|
||||||
|
bool useItemIndex = false;
|
||||||
|
|
||||||
|
static constexpr uint16_t LARGE_SPINE_THRESHOLD = 400;
|
||||||
|
|
||||||
|
// FNV-1a hash function
|
||||||
|
static uint32_t fnvHash(const std::string& s) {
|
||||||
|
uint32_t hash = 2166136261u;
|
||||||
|
for (char c : s) {
|
||||||
|
hash ^= static_cast<uint8_t>(c);
|
||||||
|
hash *= 16777619u;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
static void startElement(void* userData, const XML_Char* name, const XML_Char** atts);
|
static void startElement(void* userData, const XML_Char* name, const XML_Char** atts);
|
||||||
static void characterData(void* userData, const XML_Char* s, int len);
|
static void characterData(void* userData, const XML_Char* s, int len);
|
||||||
static void endElement(void* userData, const XML_Char* name);
|
static void endElement(void* userData, const XML_Char* name);
|
||||||
|
|||||||
@ -10,19 +10,19 @@ void GfxRenderer::rotateCoordinates(const int x, const int y, int* rotatedX, int
|
|||||||
// Logical portrait (480x800) → panel (800x480)
|
// Logical portrait (480x800) → panel (800x480)
|
||||||
// Rotation: 90 degrees clockwise
|
// Rotation: 90 degrees clockwise
|
||||||
*rotatedX = y;
|
*rotatedX = y;
|
||||||
*rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
*rotatedY = HalDisplay::DISPLAY_HEIGHT - 1 - x;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LandscapeClockwise: {
|
case LandscapeClockwise: {
|
||||||
// Logical landscape (800x480) rotated 180 degrees (swap top/bottom and left/right)
|
// Logical landscape (800x480) rotated 180 degrees (swap top/bottom and left/right)
|
||||||
*rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - x;
|
*rotatedX = HalDisplay::DISPLAY_WIDTH - 1 - x;
|
||||||
*rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - y;
|
*rotatedY = HalDisplay::DISPLAY_HEIGHT - 1 - y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PortraitInverted: {
|
case PortraitInverted: {
|
||||||
// Logical portrait (480x800) → panel (800x480)
|
// Logical portrait (480x800) → panel (800x480)
|
||||||
// Rotation: 90 degrees counter-clockwise
|
// Rotation: 90 degrees counter-clockwise
|
||||||
*rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - y;
|
*rotatedX = HalDisplay::DISPLAY_WIDTH - 1 - y;
|
||||||
*rotatedY = x;
|
*rotatedY = x;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ void GfxRenderer::rotateCoordinates(const int x, const int y, int* rotatedX, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
||||||
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
uint8_t* frameBuffer = display.getFrameBuffer();
|
||||||
|
|
||||||
// Early return if no framebuffer is set
|
// Early return if no framebuffer is set
|
||||||
if (!frameBuffer) {
|
if (!frameBuffer) {
|
||||||
@ -49,14 +49,13 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
|||||||
rotateCoordinates(x, y, &rotatedX, &rotatedY);
|
rotateCoordinates(x, y, &rotatedX, &rotatedY);
|
||||||
|
|
||||||
// Bounds checking against physical panel dimensions
|
// Bounds checking against physical panel dimensions
|
||||||
if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 ||
|
if (rotatedX < 0 || rotatedX >= HalDisplay::DISPLAY_WIDTH || rotatedY < 0 || rotatedY >= HalDisplay::DISPLAY_HEIGHT) {
|
||||||
rotatedY >= EInkDisplay::DISPLAY_HEIGHT) {
|
|
||||||
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d) -> (%d, %d)\n", millis(), x, y, rotatedX, rotatedY);
|
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d) -> (%d, %d)\n", millis(), x, y, rotatedX, rotatedY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate byte position and bit position
|
// Calculate byte position and bit position
|
||||||
const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
|
const uint16_t byteIndex = rotatedY * HalDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
|
||||||
const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first
|
const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
@ -145,11 +144,26 @@ void GfxRenderer::fillRect(const int x, const int y, const int width, const int
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
||||||
// TODO: Rotate bits
|
|
||||||
int rotatedX = 0;
|
int rotatedX = 0;
|
||||||
int rotatedY = 0;
|
int rotatedY = 0;
|
||||||
rotateCoordinates(x, y, &rotatedX, &rotatedY);
|
rotateCoordinates(x, y, &rotatedX, &rotatedY);
|
||||||
einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height);
|
// Rotate origin corner
|
||||||
|
switch (orientation) {
|
||||||
|
case Portrait:
|
||||||
|
rotatedY = rotatedY - height;
|
||||||
|
break;
|
||||||
|
case PortraitInverted:
|
||||||
|
rotatedX = rotatedX - width;
|
||||||
|
break;
|
||||||
|
case LandscapeClockwise:
|
||||||
|
rotatedY = rotatedY - height;
|
||||||
|
rotatedX = rotatedX - width;
|
||||||
|
break;
|
||||||
|
case LandscapeCounterClockwise:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// TODO: Rotate bits
|
||||||
|
display.drawImage(bitmap, rotatedX, rotatedY, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
|
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
|
||||||
@ -384,22 +398,20 @@ void GfxRenderer::fillPolygon(const int* xPoints, const int* yPoints, int numPoi
|
|||||||
free(nodeX);
|
free(nodeX);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); }
|
void GfxRenderer::clearScreen(const uint8_t color) const { display.clearScreen(color); }
|
||||||
|
|
||||||
void GfxRenderer::invertScreen() const {
|
void GfxRenderer::invertScreen() const {
|
||||||
uint8_t* buffer = einkDisplay.getFrameBuffer();
|
uint8_t* buffer = display.getFrameBuffer();
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
Serial.printf("[%lu] [GFX] !! No framebuffer in invertScreen\n", millis());
|
Serial.printf("[%lu] [GFX] !! No framebuffer in invertScreen\n", millis());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) {
|
for (int i = 0; i < HalDisplay::BUFFER_SIZE; i++) {
|
||||||
buffer[i] = ~buffer[i];
|
buffer[i] = ~buffer[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::displayBuffer(const EInkDisplay::RefreshMode refreshMode) const {
|
void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const { display.displayBuffer(refreshMode); }
|
||||||
einkDisplay.displayBuffer(refreshMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth,
|
std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth,
|
||||||
const EpdFontFamily::Style style) const {
|
const EpdFontFamily::Style style) const {
|
||||||
@ -418,13 +430,13 @@ int GfxRenderer::getScreenWidth() const {
|
|||||||
case Portrait:
|
case Portrait:
|
||||||
case PortraitInverted:
|
case PortraitInverted:
|
||||||
// 480px wide in portrait logical coordinates
|
// 480px wide in portrait logical coordinates
|
||||||
return EInkDisplay::DISPLAY_HEIGHT;
|
return HalDisplay::DISPLAY_HEIGHT;
|
||||||
case LandscapeClockwise:
|
case LandscapeClockwise:
|
||||||
case LandscapeCounterClockwise:
|
case LandscapeCounterClockwise:
|
||||||
// 800px wide in landscape logical coordinates
|
// 800px wide in landscape logical coordinates
|
||||||
return EInkDisplay::DISPLAY_WIDTH;
|
return HalDisplay::DISPLAY_WIDTH;
|
||||||
}
|
}
|
||||||
return EInkDisplay::DISPLAY_HEIGHT;
|
return HalDisplay::DISPLAY_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GfxRenderer::getScreenHeight() const {
|
int GfxRenderer::getScreenHeight() const {
|
||||||
@ -432,13 +444,13 @@ int GfxRenderer::getScreenHeight() const {
|
|||||||
case Portrait:
|
case Portrait:
|
||||||
case PortraitInverted:
|
case PortraitInverted:
|
||||||
// 800px tall in portrait logical coordinates
|
// 800px tall in portrait logical coordinates
|
||||||
return EInkDisplay::DISPLAY_WIDTH;
|
return HalDisplay::DISPLAY_WIDTH;
|
||||||
case LandscapeClockwise:
|
case LandscapeClockwise:
|
||||||
case LandscapeCounterClockwise:
|
case LandscapeCounterClockwise:
|
||||||
// 480px tall in landscape logical coordinates
|
// 480px tall in landscape logical coordinates
|
||||||
return EInkDisplay::DISPLAY_HEIGHT;
|
return HalDisplay::DISPLAY_HEIGHT;
|
||||||
}
|
}
|
||||||
return EInkDisplay::DISPLAY_WIDTH;
|
return HalDisplay::DISPLAY_WIDTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
||||||
@ -638,17 +650,18 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* GfxRenderer::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
|
uint8_t* GfxRenderer::getFrameBuffer() const { return display.getFrameBuffer(); }
|
||||||
|
|
||||||
size_t GfxRenderer::getBufferSize() { return EInkDisplay::BUFFER_SIZE; }
|
size_t GfxRenderer::getBufferSize() { return HalDisplay::BUFFER_SIZE; }
|
||||||
|
|
||||||
void GfxRenderer::grayscaleRevert() const { einkDisplay.grayscaleRevert(); }
|
// unused
|
||||||
|
// void GfxRenderer::grayscaleRevert() const { display.grayscaleRevert(); }
|
||||||
|
|
||||||
void GfxRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); }
|
void GfxRenderer::copyGrayscaleLsbBuffers() const { display.copyGrayscaleLsbBuffers(display.getFrameBuffer()); }
|
||||||
|
|
||||||
void GfxRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); }
|
void GfxRenderer::copyGrayscaleMsbBuffers() const { display.copyGrayscaleMsbBuffers(display.getFrameBuffer()); }
|
||||||
|
|
||||||
void GfxRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }
|
void GfxRenderer::displayGrayBuffer() const { display.displayGrayBuffer(); }
|
||||||
|
|
||||||
void GfxRenderer::freeBwBufferChunks() {
|
void GfxRenderer::freeBwBufferChunks() {
|
||||||
for (auto& bwBufferChunk : bwBufferChunks) {
|
for (auto& bwBufferChunk : bwBufferChunks) {
|
||||||
@ -666,7 +679,7 @@ void GfxRenderer::freeBwBufferChunks() {
|
|||||||
* Returns true if buffer was stored successfully, false if allocation failed.
|
* Returns true if buffer was stored successfully, false if allocation failed.
|
||||||
*/
|
*/
|
||||||
bool GfxRenderer::storeBwBuffer() {
|
bool GfxRenderer::storeBwBuffer() {
|
||||||
const uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
const uint8_t* frameBuffer = display.getFrameBuffer();
|
||||||
if (!frameBuffer) {
|
if (!frameBuffer) {
|
||||||
Serial.printf("[%lu] [GFX] !! No framebuffer in storeBwBuffer\n", millis());
|
Serial.printf("[%lu] [GFX] !! No framebuffer in storeBwBuffer\n", millis());
|
||||||
return false;
|
return false;
|
||||||
@ -721,7 +734,7 @@ void GfxRenderer::restoreBwBuffer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
uint8_t* frameBuffer = display.getFrameBuffer();
|
||||||
if (!frameBuffer) {
|
if (!frameBuffer) {
|
||||||
Serial.printf("[%lu] [GFX] !! No framebuffer in restoreBwBuffer\n", millis());
|
Serial.printf("[%lu] [GFX] !! No framebuffer in restoreBwBuffer\n", millis());
|
||||||
freeBwBufferChunks();
|
freeBwBufferChunks();
|
||||||
@ -740,7 +753,7 @@ void GfxRenderer::restoreBwBuffer() {
|
|||||||
memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE);
|
memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
einkDisplay.cleanupGrayscaleBuffers(frameBuffer);
|
display.cleanupGrayscaleBuffers(frameBuffer);
|
||||||
|
|
||||||
freeBwBufferChunks();
|
freeBwBufferChunks();
|
||||||
Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis());
|
Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis());
|
||||||
@ -751,9 +764,9 @@ void GfxRenderer::restoreBwBuffer() {
|
|||||||
* Use this when BW buffer was re-rendered instead of stored/restored.
|
* Use this when BW buffer was re-rendered instead of stored/restored.
|
||||||
*/
|
*/
|
||||||
void GfxRenderer::cleanupGrayscaleWithFrameBuffer() const {
|
void GfxRenderer::cleanupGrayscaleWithFrameBuffer() const {
|
||||||
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
uint8_t* frameBuffer = display.getFrameBuffer();
|
||||||
if (frameBuffer) {
|
if (frameBuffer) {
|
||||||
einkDisplay.cleanupGrayscaleBuffers(frameBuffer);
|
display.cleanupGrayscaleBuffers(frameBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <EInkDisplay.h>
|
|
||||||
#include <EpdFontFamily.h>
|
#include <EpdFontFamily.h>
|
||||||
|
#include <HalDisplay.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
@ -21,11 +21,11 @@ class GfxRenderer {
|
|||||||
|
|
||||||
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 = 8000; // 8KB chunks to allow for non-contiguous memory
|
||||||
static constexpr size_t BW_BUFFER_NUM_CHUNKS = EInkDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
static constexpr size_t BW_BUFFER_NUM_CHUNKS = HalDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
||||||
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == EInkDisplay::BUFFER_SIZE,
|
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == HalDisplay::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;
|
HalDisplay& display;
|
||||||
RenderMode renderMode;
|
RenderMode renderMode;
|
||||||
Orientation orientation;
|
Orientation orientation;
|
||||||
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
||||||
@ -36,7 +36,7 @@ class GfxRenderer {
|
|||||||
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(HalDisplay& halDisplay) : display(halDisplay), renderMode(BW), orientation(Portrait) {}
|
||||||
~GfxRenderer() { freeBwBufferChunks(); }
|
~GfxRenderer() { freeBwBufferChunks(); }
|
||||||
|
|
||||||
static constexpr int VIEWABLE_MARGIN_TOP = 9;
|
static constexpr int VIEWABLE_MARGIN_TOP = 9;
|
||||||
@ -54,7 +54,7 @@ class GfxRenderer {
|
|||||||
// 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(HalDisplay::RefreshMode refreshMode = HalDisplay::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;
|
||||||
|
|||||||
@ -200,7 +200,7 @@ unsigned char JpegToBmpConverter::jpegReadCallback(unsigned char* pBuf, const un
|
|||||||
|
|
||||||
// Internal implementation with configurable target size and bit depth
|
// Internal implementation with configurable target size and bit depth
|
||||||
bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
||||||
bool oneBit) {
|
bool oneBit, bool crop) {
|
||||||
Serial.printf("[%lu] [JPG] Converting JPEG to %s BMP (target: %dx%d)\n", millis(), oneBit ? "1-bit" : "2-bit",
|
Serial.printf("[%lu] [JPG] Converting JPEG to %s BMP (target: %dx%d)\n", millis(), oneBit ? "1-bit" : "2-bit",
|
||||||
targetWidth, targetHeight);
|
targetWidth, targetHeight);
|
||||||
|
|
||||||
@ -242,8 +242,12 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
const float scaleToFitWidth = static_cast<float>(targetWidth) / imageInfo.m_width;
|
const float scaleToFitWidth = static_cast<float>(targetWidth) / imageInfo.m_width;
|
||||||
const float scaleToFitHeight = static_cast<float>(targetHeight) / imageInfo.m_height;
|
const float scaleToFitHeight = static_cast<float>(targetHeight) / imageInfo.m_height;
|
||||||
// We scale to the smaller dimension, so we can potentially crop later.
|
// We scale to the smaller dimension, so we can potentially crop later.
|
||||||
// TODO: ideally, we already crop here.
|
float scale = 1.0;
|
||||||
const float scale = (scaleToFitWidth > scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight;
|
if (crop) { // if we will crop, scale to the smaller dimension
|
||||||
|
scale = (scaleToFitWidth > scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight;
|
||||||
|
} else { // else, scale to the larger dimension to fit
|
||||||
|
scale = (scaleToFitWidth < scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight;
|
||||||
|
}
|
||||||
|
|
||||||
outWidth = static_cast<int>(imageInfo.m_width * scale);
|
outWidth = static_cast<int>(imageInfo.m_width * scale);
|
||||||
outHeight = static_cast<int>(imageInfo.m_height * scale);
|
outHeight = static_cast<int>(imageInfo.m_height * scale);
|
||||||
@ -550,8 +554,8 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Core function: Convert JPEG file to 2-bit BMP (uses default target size)
|
// Core function: Convert JPEG file to 2-bit BMP (uses default target size)
|
||||||
bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bool crop) {
|
||||||
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, TARGET_MAX_WIDTH, TARGET_MAX_HEIGHT, false);
|
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, TARGET_MAX_WIDTH, TARGET_MAX_HEIGHT, false, crop);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert with custom target size (for thumbnails, 2-bit)
|
// Convert with custom target size (for thumbnails, 2-bit)
|
||||||
|
|||||||
@ -8,10 +8,10 @@ class JpegToBmpConverter {
|
|||||||
static unsigned char jpegReadCallback(unsigned char* pBuf, unsigned char buf_size,
|
static unsigned char jpegReadCallback(unsigned char* pBuf, unsigned char buf_size,
|
||||||
unsigned char* pBytes_actually_read, void* pCallback_data);
|
unsigned char* pBytes_actually_read, void* pCallback_data);
|
||||||
static bool jpegFileToBmpStreamInternal(class FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
static bool jpegFileToBmpStreamInternal(class FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
||||||
bool oneBit);
|
bool oneBit, bool crop = true);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static bool jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut);
|
static bool jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bool crop = true);
|
||||||
// Convert with custom target size (for thumbnails)
|
// Convert with custom target size (for thumbnails)
|
||||||
static bool jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
|
static bool jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
|
||||||
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
|
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
|
||||||
|
|||||||
@ -33,10 +33,10 @@ std::string KOReaderDocumentId::calculateFromFilename(const std::string& filePat
|
|||||||
|
|
||||||
size_t KOReaderDocumentId::getOffset(int i) {
|
size_t KOReaderDocumentId::getOffset(int i) {
|
||||||
// Offset = 1024 << (2*i)
|
// Offset = 1024 << (2*i)
|
||||||
// For i = -1: 1024 >> 2 = 256
|
// For i = -1: KOReader uses a value of 0
|
||||||
// For i >= 0: 1024 << (2*i)
|
// For i >= 0: 1024 << (2*i)
|
||||||
if (i < 0) {
|
if (i < 0) {
|
||||||
return CHUNK_SIZE >> (-2 * i);
|
return 0;
|
||||||
}
|
}
|
||||||
return CHUNK_SIZE << (2 * i);
|
return CHUNK_SIZE << (2 * i);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,14 @@
|
|||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
OpdsParser::OpdsParser() {
|
||||||
|
parser = XML_ParserCreate(nullptr);
|
||||||
|
if (!parser) {
|
||||||
|
errorOccured = true;
|
||||||
|
Serial.printf("[%lu] [OPDS] Couldn't allocate memory for parser\n", millis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OpdsParser::~OpdsParser() {
|
OpdsParser::~OpdsParser() {
|
||||||
if (parser) {
|
if (parser) {
|
||||||
XML_StopParser(parser, XML_FALSE);
|
XML_StopParser(parser, XML_FALSE);
|
||||||
@ -14,13 +22,11 @@ OpdsParser::~OpdsParser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpdsParser::parse(const char* xmlData, const size_t length) {
|
size_t OpdsParser::write(uint8_t c) { return write(&c, 1); }
|
||||||
clear();
|
|
||||||
|
|
||||||
parser = XML_ParserCreate(nullptr);
|
size_t OpdsParser::write(const uint8_t* xmlData, const size_t length) {
|
||||||
if (!parser) {
|
if (errorOccured) {
|
||||||
Serial.printf("[%lu] [OPDS] Couldn't allocate memory for parser\n", millis());
|
return length;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XML_SetUserData(parser, this);
|
XML_SetUserData(parser, this);
|
||||||
@ -28,43 +34,48 @@ bool OpdsParser::parse(const char* xmlData, const size_t length) {
|
|||||||
XML_SetCharacterDataHandler(parser, characterData);
|
XML_SetCharacterDataHandler(parser, characterData);
|
||||||
|
|
||||||
// Parse in chunks to avoid large buffer allocations
|
// Parse in chunks to avoid large buffer allocations
|
||||||
const char* currentPos = xmlData;
|
const char* currentPos = reinterpret_cast<const char*>(xmlData);
|
||||||
size_t remaining = length;
|
size_t remaining = length;
|
||||||
constexpr size_t chunkSize = 1024;
|
constexpr size_t chunkSize = 1024;
|
||||||
|
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
void* const buf = XML_GetBuffer(parser, chunkSize);
|
void* const buf = XML_GetBuffer(parser, chunkSize);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
|
errorOccured = true;
|
||||||
Serial.printf("[%lu] [OPDS] Couldn't allocate memory for buffer\n", millis());
|
Serial.printf("[%lu] [OPDS] Couldn't allocate memory for buffer\n", millis());
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
parser = nullptr;
|
parser = nullptr;
|
||||||
return false;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t toRead = remaining < chunkSize ? remaining : chunkSize;
|
const size_t toRead = remaining < chunkSize ? remaining : chunkSize;
|
||||||
memcpy(buf, currentPos, toRead);
|
memcpy(buf, currentPos, toRead);
|
||||||
|
|
||||||
const bool isFinal = (remaining == toRead);
|
if (XML_ParseBuffer(parser, static_cast<int>(toRead), 0) == XML_STATUS_ERROR) {
|
||||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), isFinal) == XML_STATUS_ERROR) {
|
errorOccured = true;
|
||||||
Serial.printf("[%lu] [OPDS] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
|
Serial.printf("[%lu] [OPDS] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
|
||||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
parser = nullptr;
|
parser = nullptr;
|
||||||
return false;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPos += toRead;
|
currentPos += toRead;
|
||||||
remaining -= toRead;
|
remaining -= toRead;
|
||||||
}
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up parser
|
void OpdsParser::flush() {
|
||||||
|
if (XML_Parse(parser, nullptr, 0, XML_TRUE) != XML_STATUS_OK) {
|
||||||
|
errorOccured = true;
|
||||||
XML_ParserFree(parser);
|
XML_ParserFree(parser);
|
||||||
parser = nullptr;
|
parser = nullptr;
|
||||||
|
}
|
||||||
Serial.printf("[%lu] [OPDS] Parsed %zu entries\n", millis(), entries.size());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OpdsParser::error() const { return errorOccured; }
|
||||||
|
|
||||||
void OpdsParser::clear() {
|
void OpdsParser::clear() {
|
||||||
entries.clear();
|
entries.clear();
|
||||||
currentEntry = OpdsEntry{};
|
currentEntry = OpdsEntry{};
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <Print.h>
|
||||||
#include <expat.h>
|
#include <expat.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -42,28 +43,30 @@ using OpdsBook = OpdsEntry;
|
|||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
class OpdsParser {
|
class OpdsParser final : public Print {
|
||||||
public:
|
public:
|
||||||
OpdsParser() = default;
|
OpdsParser();
|
||||||
~OpdsParser();
|
~OpdsParser();
|
||||||
|
|
||||||
// Disable copy
|
// Disable copy
|
||||||
OpdsParser(const OpdsParser&) = delete;
|
OpdsParser(const OpdsParser&) = delete;
|
||||||
OpdsParser& operator=(const OpdsParser&) = delete;
|
OpdsParser& operator=(const OpdsParser&) = delete;
|
||||||
|
|
||||||
/**
|
size_t write(uint8_t) override;
|
||||||
* Parse an OPDS XML feed.
|
size_t write(const uint8_t*, size_t) override;
|
||||||
* @param xmlData Pointer to the XML data
|
|
||||||
* @param length Length of the XML data
|
void flush() override;
|
||||||
* @return true if parsing succeeded, false on error
|
|
||||||
*/
|
bool error() const;
|
||||||
bool parse(const char* xmlData, size_t length);
|
|
||||||
|
operator bool() { return !error(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the parsed entries (both navigation and book entries).
|
* Get the parsed entries (both navigation and book entries).
|
||||||
* @return Vector of OpdsEntry entries
|
* @return Vector of OpdsEntry entries
|
||||||
*/
|
*/
|
||||||
const std::vector<OpdsEntry>& getEntries() const { return entries; }
|
const std::vector<OpdsEntry>& getEntries() const& { return entries; }
|
||||||
|
std::vector<OpdsEntry> getEntries() && { return std::move(entries); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get only book entries (legacy compatibility).
|
* Get only book entries (legacy compatibility).
|
||||||
@ -96,4 +99,6 @@ class OpdsParser {
|
|||||||
bool inAuthor = false;
|
bool inAuthor = false;
|
||||||
bool inAuthorName = false;
|
bool inAuthorName = false;
|
||||||
bool inId = false;
|
bool inId = false;
|
||||||
|
|
||||||
|
bool errorOccured = false;
|
||||||
};
|
};
|
||||||
|
|||||||
15
lib/OpdsParser/OpdsStream.cpp
Normal file
15
lib/OpdsParser/OpdsStream.cpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#include "OpdsStream.h"
|
||||||
|
|
||||||
|
OpdsParserStream::OpdsParserStream(OpdsParser& parser) : parser(parser) {}
|
||||||
|
|
||||||
|
int OpdsParserStream::available() { return 0; }
|
||||||
|
|
||||||
|
int OpdsParserStream::peek() { abort(); }
|
||||||
|
|
||||||
|
int OpdsParserStream::read() { abort(); }
|
||||||
|
|
||||||
|
size_t OpdsParserStream::write(uint8_t c) { return parser.write(c); }
|
||||||
|
|
||||||
|
size_t OpdsParserStream::write(const uint8_t* buffer, size_t size) { return parser.write(buffer, size); }
|
||||||
|
|
||||||
|
OpdsParserStream::~OpdsParserStream() { parser.flush(); }
|
||||||
23
lib/OpdsParser/OpdsStream.h
Normal file
23
lib/OpdsParser/OpdsStream.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
#include "OpdsParser.h"
|
||||||
|
|
||||||
|
class OpdsParserStream : public Stream {
|
||||||
|
public:
|
||||||
|
explicit OpdsParserStream(OpdsParser& parser);
|
||||||
|
|
||||||
|
// That functions are not implimented for that stream
|
||||||
|
int available() override;
|
||||||
|
int peek() override;
|
||||||
|
int read() override;
|
||||||
|
|
||||||
|
virtual size_t write(uint8_t c) override;
|
||||||
|
virtual size_t write(const uint8_t* buffer, size_t size) override;
|
||||||
|
|
||||||
|
~OpdsParserStream() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
OpdsParser& parser;
|
||||||
|
};
|
||||||
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "Xtc.h"
|
#include "Xtc.h"
|
||||||
|
|
||||||
#include <FsHelpers.h>
|
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
|
||||||
@ -87,6 +86,15 @@ std::string Xtc::getTitle() const {
|
|||||||
return filepath.substr(lastSlash, lastDot - lastSlash);
|
return filepath.substr(lastSlash, lastDot - lastSlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Xtc::getAuthor() const {
|
||||||
|
if (!loaded || !parser) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get author from XTC metadata
|
||||||
|
return parser->getAuthor();
|
||||||
|
}
|
||||||
|
|
||||||
bool Xtc::hasChapters() const {
|
bool Xtc::hasChapters() const {
|
||||||
if (!loaded || !parser) {
|
if (!loaded || !parser) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -56,6 +56,7 @@ class Xtc {
|
|||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
std::string getTitle() const;
|
std::string getTitle() const;
|
||||||
|
std::string getAuthor() const;
|
||||||
bool hasChapters() const;
|
bool hasChapters() const;
|
||||||
const std::vector<xtc::ChapterInfo>& getChapters() const;
|
const std::vector<xtc::ChapterInfo>& getChapters() const;
|
||||||
|
|
||||||
|
|||||||
@ -47,8 +47,21 @@ XtcError XtcParser::open(const char* filepath) {
|
|||||||
return m_lastError;
|
return m_lastError;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read title if available
|
// Read title & author if available
|
||||||
readTitle();
|
if (m_header.hasMetadata) {
|
||||||
|
m_lastError = readTitle();
|
||||||
|
if (m_lastError != XtcError::OK) {
|
||||||
|
Serial.printf("[%lu] [XTC] Failed to read title: %s\n", millis(), errorToString(m_lastError));
|
||||||
|
m_file.close();
|
||||||
|
return m_lastError;
|
||||||
|
}
|
||||||
|
m_lastError = readAuthor();
|
||||||
|
if (m_lastError != XtcError::OK) {
|
||||||
|
Serial.printf("[%lu] [XTC] Failed to read author: %s\n", millis(), errorToString(m_lastError));
|
||||||
|
m_file.close();
|
||||||
|
return m_lastError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read page table
|
// Read page table
|
||||||
m_lastError = readPageTable();
|
m_lastError = readPageTable();
|
||||||
@ -124,24 +137,34 @@ XtcError XtcParser::readHeader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
XtcError XtcParser::readTitle() {
|
XtcError XtcParser::readTitle() {
|
||||||
// Title is usually at offset 0x38 (56) for 88-byte headers
|
constexpr auto titleOffset = 0x38;
|
||||||
// Read title as null-terminated UTF-8 string
|
if (!m_file.seek(titleOffset)) {
|
||||||
if (m_header.titleOffset == 0) {
|
|
||||||
m_header.titleOffset = 0x38; // Default offset
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_file.seek(m_header.titleOffset)) {
|
|
||||||
return XtcError::READ_ERROR;
|
return XtcError::READ_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
char titleBuf[128] = {0};
|
char titleBuf[128] = {0};
|
||||||
m_file.read(reinterpret_cast<uint8_t*>(titleBuf), sizeof(titleBuf) - 1);
|
m_file.read(titleBuf, sizeof(titleBuf) - 1);
|
||||||
m_title = titleBuf;
|
m_title = titleBuf;
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str());
|
Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str());
|
||||||
return XtcError::OK;
|
return XtcError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XtcError XtcParser::readAuthor() {
|
||||||
|
// Read author as null-terminated UTF-8 string with max length 64, directly following title
|
||||||
|
constexpr auto authorOffset = 0xB8;
|
||||||
|
if (!m_file.seek(authorOffset)) {
|
||||||
|
return XtcError::READ_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
char authorBuf[64] = {0};
|
||||||
|
m_file.read(authorBuf, sizeof(authorBuf) - 1);
|
||||||
|
m_author = authorBuf;
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [XTC] Author: %s\n", millis(), m_author.c_str());
|
||||||
|
return XtcError::OK;
|
||||||
|
}
|
||||||
|
|
||||||
XtcError XtcParser::readPageTable() {
|
XtcError XtcParser::readPageTable() {
|
||||||
if (m_header.pageTableOffset == 0) {
|
if (m_header.pageTableOffset == 0) {
|
||||||
Serial.printf("[%lu] [XTC] Page table offset is 0, cannot read\n", millis());
|
Serial.printf("[%lu] [XTC] Page table offset is 0, cannot read\n", millis());
|
||||||
|
|||||||
@ -67,8 +67,9 @@ class XtcParser {
|
|||||||
std::function<void(const uint8_t* data, size_t size, size_t offset)> callback,
|
std::function<void(const uint8_t* data, size_t size, size_t offset)> callback,
|
||||||
size_t chunkSize = 1024);
|
size_t chunkSize = 1024);
|
||||||
|
|
||||||
// Get title from metadata
|
// Get title/author from metadata
|
||||||
std::string getTitle() const { return m_title; }
|
std::string getTitle() const { return m_title; }
|
||||||
|
std::string getAuthor() const { return m_author; }
|
||||||
|
|
||||||
bool hasChapters() const { return m_hasChapters; }
|
bool hasChapters() const { return m_hasChapters; }
|
||||||
const std::vector<ChapterInfo>& getChapters() const { return m_chapters; }
|
const std::vector<ChapterInfo>& getChapters() const { return m_chapters; }
|
||||||
@ -86,6 +87,7 @@ class XtcParser {
|
|||||||
std::vector<PageInfo> m_pageTable;
|
std::vector<PageInfo> m_pageTable;
|
||||||
std::vector<ChapterInfo> m_chapters;
|
std::vector<ChapterInfo> m_chapters;
|
||||||
std::string m_title;
|
std::string m_title;
|
||||||
|
std::string m_author;
|
||||||
uint16_t m_defaultWidth;
|
uint16_t m_defaultWidth;
|
||||||
uint16_t m_defaultHeight;
|
uint16_t m_defaultHeight;
|
||||||
uint8_t m_bitDepth; // 1 = XTC/XTG (1-bit), 2 = XTCH/XTH (2-bit)
|
uint8_t m_bitDepth; // 1 = XTC/XTG (1-bit), 2 = XTCH/XTH (2-bit)
|
||||||
@ -96,6 +98,7 @@ class XtcParser {
|
|||||||
XtcError readHeader();
|
XtcError readHeader();
|
||||||
XtcError readPageTable();
|
XtcError readPageTable();
|
||||||
XtcError readTitle();
|
XtcError readTitle();
|
||||||
|
XtcError readAuthor();
|
||||||
XtcError readChapters();
|
XtcError readChapters();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -38,14 +38,16 @@ struct XtcHeader {
|
|||||||
uint8_t versionMajor; // 0x04: Format version major (typically 1) (together with minor = 1.0)
|
uint8_t versionMajor; // 0x04: Format version major (typically 1) (together with minor = 1.0)
|
||||||
uint8_t versionMinor; // 0x05: Format version minor (typically 0)
|
uint8_t versionMinor; // 0x05: Format version minor (typically 0)
|
||||||
uint16_t pageCount; // 0x06: Total page count
|
uint16_t pageCount; // 0x06: Total page count
|
||||||
uint32_t flags; // 0x08: Flags/reserved
|
uint8_t readDirection; // 0x08: Reading direction (0-2)
|
||||||
uint32_t headerSize; // 0x0C: Size of header section (typically 88)
|
uint8_t hasMetadata; // 0x09: Has metadata (0-1)
|
||||||
uint32_t reserved1; // 0x10: Reserved
|
uint8_t hasThumbnails; // 0x0A: Has thumbnails (0-1)
|
||||||
uint32_t tocOffset; // 0x14: TOC offset (0 if unused) - 4 bytes, not 8!
|
uint8_t hasChapters; // 0x0B: Has chapters (0-1)
|
||||||
|
uint32_t currentPage; // 0x0C: Current page (1-based) (0-65535)
|
||||||
|
uint64_t metadataOffset; // 0x10: Metadata offset (0 if unused)
|
||||||
uint64_t pageTableOffset; // 0x18: Page table offset
|
uint64_t pageTableOffset; // 0x18: Page table offset
|
||||||
uint64_t dataOffset; // 0x20: First page data offset
|
uint64_t dataOffset; // 0x20: First page data offset
|
||||||
uint64_t reserved2; // 0x28: Reserved
|
uint64_t thumbOffset; // 0x28: Thumbnail offset
|
||||||
uint32_t titleOffset; // 0x30: Title string offset
|
uint32_t chapterOffset; // 0x30: Chapter data offset
|
||||||
uint32_t padding; // 0x34: Padding to 56 bytes
|
uint32_t padding; // 0x34: Padding to 56 bytes
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <miniz.h>
|
#include <miniz.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf, const size_t inflatedSize) {
|
bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf, const size_t inflatedSize) {
|
||||||
// Setup inflator
|
// Setup inflator
|
||||||
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||||
@ -74,6 +76,10 @@ bool ZipFile::loadAllFileStatSlims() {
|
|||||||
file.seekCur(m + k);
|
file.seekCur(m + k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set cursor to start of central directory for sequential access
|
||||||
|
lastCentralDirPos = zipDetails.centralDirOffset;
|
||||||
|
lastCentralDirPosValid = true;
|
||||||
|
|
||||||
if (!wasOpen) {
|
if (!wasOpen) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@ -102,15 +108,35 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.seek(zipDetails.centralDirOffset);
|
// Phase 1: Try scanning from cursor position first
|
||||||
|
uint32_t startPos = lastCentralDirPosValid ? lastCentralDirPos : zipDetails.centralDirOffset;
|
||||||
|
uint32_t wrapPos = zipDetails.centralDirOffset;
|
||||||
|
bool wrapped = false;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
file.seek(startPos);
|
||||||
|
|
||||||
uint32_t sig;
|
uint32_t sig;
|
||||||
char itemName[256];
|
char itemName[256];
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
while (file.available()) {
|
while (true) {
|
||||||
file.read(&sig, 4);
|
uint32_t entryStart = file.position();
|
||||||
if (sig != 0x02014b50) break; // End of list
|
|
||||||
|
if (file.read(&sig, 4) != 4 || sig != 0x02014b50) {
|
||||||
|
// End of central directory
|
||||||
|
if (!wrapped && lastCentralDirPosValid && startPos != zipDetails.centralDirOffset) {
|
||||||
|
// Wrap around to beginning
|
||||||
|
file.seek(zipDetails.centralDirOffset);
|
||||||
|
wrapped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've wrapped and reached our start position, stop
|
||||||
|
if (wrapped && entryStart >= startPos) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
file.seekCur(6);
|
file.seekCur(6);
|
||||||
file.read(&fileStat->method, 2);
|
file.read(&fileStat->method, 2);
|
||||||
@ -123,15 +149,25 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) {
|
|||||||
file.read(&k, 2);
|
file.read(&k, 2);
|
||||||
file.seekCur(8);
|
file.seekCur(8);
|
||||||
file.read(&fileStat->localHeaderOffset, 4);
|
file.read(&fileStat->localHeaderOffset, 4);
|
||||||
|
|
||||||
|
if (nameLen < 256) {
|
||||||
file.read(itemName, nameLen);
|
file.read(itemName, nameLen);
|
||||||
itemName[nameLen] = '\0';
|
itemName[nameLen] = '\0';
|
||||||
|
|
||||||
if (strcmp(itemName, filename) == 0) {
|
if (strcmp(itemName, filename) == 0) {
|
||||||
|
// Found it! Update cursor to next entry
|
||||||
|
file.seekCur(m + k);
|
||||||
|
lastCentralDirPos = file.position();
|
||||||
|
lastCentralDirPosValid = true;
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Name too long, skip it
|
||||||
|
file.seekCur(nameLen);
|
||||||
|
}
|
||||||
|
|
||||||
// Skip the rest of this entry (extra field + comment)
|
// Skip extra field + comment
|
||||||
file.seekCur(m + k);
|
file.seekCur(m + k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,6 +289,8 @@ bool ZipFile::close() {
|
|||||||
if (file) {
|
if (file) {
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
lastCentralDirPos = 0;
|
||||||
|
lastCentralDirPosValid = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,6 +304,80 @@ bool ZipFile::getInflatedFileSize(const char* filename, size_t* size) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ZipFile::fillUncompressedSizes(std::vector<SizeTarget>& targets, std::vector<uint32_t>& sizes) {
|
||||||
|
if (targets.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wasOpen = isOpen();
|
||||||
|
if (!wasOpen && !open()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loadZipDetails()) {
|
||||||
|
if (!wasOpen) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seek(zipDetails.centralDirOffset);
|
||||||
|
|
||||||
|
int matched = 0;
|
||||||
|
uint32_t sig;
|
||||||
|
char itemName[256];
|
||||||
|
|
||||||
|
while (file.available()) {
|
||||||
|
file.read(&sig, 4);
|
||||||
|
if (sig != 0x02014b50) break;
|
||||||
|
|
||||||
|
file.seekCur(6);
|
||||||
|
uint16_t method;
|
||||||
|
file.read(&method, 2);
|
||||||
|
file.seekCur(8);
|
||||||
|
uint32_t compressedSize, uncompressedSize;
|
||||||
|
file.read(&compressedSize, 4);
|
||||||
|
file.read(&uncompressedSize, 4);
|
||||||
|
uint16_t nameLen, m, k;
|
||||||
|
file.read(&nameLen, 2);
|
||||||
|
file.read(&m, 2);
|
||||||
|
file.read(&k, 2);
|
||||||
|
file.seekCur(8);
|
||||||
|
uint32_t localHeaderOffset;
|
||||||
|
file.read(&localHeaderOffset, 4);
|
||||||
|
|
||||||
|
if (nameLen < 256) {
|
||||||
|
file.read(itemName, nameLen);
|
||||||
|
itemName[nameLen] = '\0';
|
||||||
|
|
||||||
|
uint64_t hash = fnvHash64(itemName, nameLen);
|
||||||
|
SizeTarget key = {hash, nameLen, 0};
|
||||||
|
|
||||||
|
auto it = std::lower_bound(targets.begin(), targets.end(), key, [](const SizeTarget& a, const SizeTarget& b) {
|
||||||
|
return a.hash < b.hash || (a.hash == b.hash && a.len < b.len);
|
||||||
|
});
|
||||||
|
|
||||||
|
while (it != targets.end() && it->hash == hash && it->len == nameLen) {
|
||||||
|
if (it->index < sizes.size()) {
|
||||||
|
sizes[it->index] = uncompressedSize;
|
||||||
|
matched++;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file.seekCur(nameLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seekCur(m + k);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wasOpen) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const bool trailingNullByte) {
|
uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const bool trailingNullByte) {
|
||||||
const bool wasOpen = isOpen();
|
const bool wasOpen = isOpen();
|
||||||
if (!wasOpen && !open()) {
|
if (!wasOpen && !open()) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class ZipFile {
|
class ZipFile {
|
||||||
public:
|
public:
|
||||||
@ -19,12 +20,33 @@ class ZipFile {
|
|||||||
bool isSet;
|
bool isSet;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Target for batch uncompressed size lookup (sorted by hash, then len)
|
||||||
|
struct SizeTarget {
|
||||||
|
uint64_t hash; // FNV-1a 64-bit hash of normalized path
|
||||||
|
uint16_t len; // Length of path for collision reduction
|
||||||
|
uint16_t index; // Caller's index (e.g. spine index)
|
||||||
|
};
|
||||||
|
|
||||||
|
// FNV-1a 64-bit hash computed from char buffer (no std::string allocation)
|
||||||
|
static uint64_t fnvHash64(const char* s, size_t len) {
|
||||||
|
uint64_t hash = 14695981039346656037ull;
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
hash ^= static_cast<uint8_t>(s[i]);
|
||||||
|
hash *= 1099511628211ull;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::string& filePath;
|
const std::string& filePath;
|
||||||
FsFile file;
|
FsFile file;
|
||||||
ZipDetails zipDetails = {0, 0, false};
|
ZipDetails zipDetails = {0, 0, false};
|
||||||
std::unordered_map<std::string, FileStatSlim> fileStatSlimCache;
|
std::unordered_map<std::string, FileStatSlim> fileStatSlimCache;
|
||||||
|
|
||||||
|
// Cursor for sequential central-dir scanning optimization
|
||||||
|
uint32_t lastCentralDirPos = 0;
|
||||||
|
bool lastCentralDirPosValid = false;
|
||||||
|
|
||||||
bool loadFileStatSlim(const char* filename, FileStatSlim* fileStat);
|
bool loadFileStatSlim(const char* filename, FileStatSlim* fileStat);
|
||||||
long getDataOffset(const FileStatSlim& fileStat);
|
long getDataOffset(const FileStatSlim& fileStat);
|
||||||
bool loadZipDetails();
|
bool loadZipDetails();
|
||||||
@ -39,6 +61,10 @@ class ZipFile {
|
|||||||
bool close();
|
bool close();
|
||||||
bool loadAllFileStatSlims();
|
bool loadAllFileStatSlims();
|
||||||
bool getInflatedFileSize(const char* filename, size_t* size);
|
bool getInflatedFileSize(const char* filename, size_t* size);
|
||||||
|
// Batch lookup: scan ZIP central dir once and fill sizes for matching targets.
|
||||||
|
// targets must be sorted by (hash, len). sizes[target.index] receives uncompressedSize.
|
||||||
|
// Returns number of targets matched.
|
||||||
|
int fillUncompressedSizes(std::vector<SizeTarget>& targets, std::vector<uint32_t>& sizes);
|
||||||
// Due to the memory required to run each of these, it is recommended to not preopen the zip file for multiple
|
// Due to the memory required to run each of these, it is recommended to not preopen the zip file for multiple
|
||||||
// These functions will open and close the zip as needed
|
// These functions will open and close the zip as needed
|
||||||
uint8_t* readFileToMemory(const char* filename, size_t* size = nullptr, bool trailingNullByte = false);
|
uint8_t* readFileToMemory(const char* filename, size_t* size = nullptr, bool trailingNullByte = false);
|
||||||
|
|||||||
51
lib/hal/HalDisplay.cpp
Normal file
51
lib/hal/HalDisplay.cpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#include <HalDisplay.h>
|
||||||
|
#include <HalGPIO.h>
|
||||||
|
|
||||||
|
#define SD_SPI_MISO 7
|
||||||
|
|
||||||
|
HalDisplay::HalDisplay() : einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY) {}
|
||||||
|
|
||||||
|
HalDisplay::~HalDisplay() {}
|
||||||
|
|
||||||
|
void HalDisplay::begin() { einkDisplay.begin(); }
|
||||||
|
|
||||||
|
void HalDisplay::clearScreen(uint8_t color) const { einkDisplay.clearScreen(color); }
|
||||||
|
|
||||||
|
void HalDisplay::drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
|
||||||
|
bool fromProgmem) const {
|
||||||
|
einkDisplay.drawImage(imageData, x, y, w, h, fromProgmem);
|
||||||
|
}
|
||||||
|
|
||||||
|
EInkDisplay::RefreshMode convertRefreshMode(HalDisplay::RefreshMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case HalDisplay::FULL_REFRESH:
|
||||||
|
return EInkDisplay::FULL_REFRESH;
|
||||||
|
case HalDisplay::HALF_REFRESH:
|
||||||
|
return EInkDisplay::HALF_REFRESH;
|
||||||
|
case HalDisplay::FAST_REFRESH:
|
||||||
|
default:
|
||||||
|
return EInkDisplay::FAST_REFRESH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HalDisplay::displayBuffer(HalDisplay::RefreshMode mode) { einkDisplay.displayBuffer(convertRefreshMode(mode)); }
|
||||||
|
|
||||||
|
void HalDisplay::refreshDisplay(HalDisplay::RefreshMode mode, bool turnOffScreen) {
|
||||||
|
einkDisplay.refreshDisplay(convertRefreshMode(mode), turnOffScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HalDisplay::deepSleep() { einkDisplay.deepSleep(); }
|
||||||
|
|
||||||
|
uint8_t* HalDisplay::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
|
||||||
|
|
||||||
|
void HalDisplay::copyGrayscaleBuffers(const uint8_t* lsbBuffer, const uint8_t* msbBuffer) {
|
||||||
|
einkDisplay.copyGrayscaleBuffers(lsbBuffer, msbBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HalDisplay::copyGrayscaleLsbBuffers(const uint8_t* lsbBuffer) { einkDisplay.copyGrayscaleLsbBuffers(lsbBuffer); }
|
||||||
|
|
||||||
|
void HalDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { einkDisplay.copyGrayscaleMsbBuffers(msbBuffer); }
|
||||||
|
|
||||||
|
void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); }
|
||||||
|
|
||||||
|
void HalDisplay::displayGrayBuffer() { einkDisplay.displayGrayBuffer(); }
|
||||||
52
lib/hal/HalDisplay.h
Normal file
52
lib/hal/HalDisplay.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <EInkDisplay.h>
|
||||||
|
|
||||||
|
class HalDisplay {
|
||||||
|
public:
|
||||||
|
// Constructor with pin configuration
|
||||||
|
HalDisplay();
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
~HalDisplay();
|
||||||
|
|
||||||
|
// Refresh modes
|
||||||
|
enum RefreshMode {
|
||||||
|
FULL_REFRESH, // Full refresh with complete waveform
|
||||||
|
HALF_REFRESH, // Half refresh (1720ms) - balanced quality and speed
|
||||||
|
FAST_REFRESH // Fast refresh using custom LUT
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize the display hardware and driver
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
// Display dimensions
|
||||||
|
static constexpr uint16_t DISPLAY_WIDTH = EInkDisplay::DISPLAY_WIDTH;
|
||||||
|
static constexpr uint16_t DISPLAY_HEIGHT = EInkDisplay::DISPLAY_HEIGHT;
|
||||||
|
static constexpr uint16_t DISPLAY_WIDTH_BYTES = DISPLAY_WIDTH / 8;
|
||||||
|
static constexpr uint32_t BUFFER_SIZE = DISPLAY_WIDTH_BYTES * DISPLAY_HEIGHT;
|
||||||
|
|
||||||
|
// Frame buffer operations
|
||||||
|
void clearScreen(uint8_t color = 0xFF) const;
|
||||||
|
void drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
|
||||||
|
bool fromProgmem = false) const;
|
||||||
|
|
||||||
|
void displayBuffer(RefreshMode mode = RefreshMode::FAST_REFRESH);
|
||||||
|
void refreshDisplay(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
|
||||||
|
|
||||||
|
// Power management
|
||||||
|
void deepSleep();
|
||||||
|
|
||||||
|
// Access to frame buffer
|
||||||
|
uint8_t* getFrameBuffer() const;
|
||||||
|
|
||||||
|
void copyGrayscaleBuffers(const uint8_t* lsbBuffer, const uint8_t* msbBuffer);
|
||||||
|
void copyGrayscaleLsbBuffers(const uint8_t* lsbBuffer);
|
||||||
|
void copyGrayscaleMsbBuffers(const uint8_t* msbBuffer);
|
||||||
|
void cleanupGrayscaleBuffers(const uint8_t* bwBuffer);
|
||||||
|
|
||||||
|
void displayGrayBuffer();
|
||||||
|
|
||||||
|
private:
|
||||||
|
EInkDisplay einkDisplay;
|
||||||
|
};
|
||||||
55
lib/hal/HalGPIO.cpp
Normal file
55
lib/hal/HalGPIO.cpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include <HalGPIO.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <esp_sleep.h>
|
||||||
|
|
||||||
|
void HalGPIO::begin() {
|
||||||
|
inputMgr.begin();
|
||||||
|
SPI.begin(EPD_SCLK, SPI_MISO, EPD_MOSI, EPD_CS);
|
||||||
|
pinMode(BAT_GPIO0, INPUT);
|
||||||
|
pinMode(UART0_RXD, INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HalGPIO::update() { inputMgr.update(); }
|
||||||
|
|
||||||
|
bool HalGPIO::isPressed(uint8_t buttonIndex) const { return inputMgr.isPressed(buttonIndex); }
|
||||||
|
|
||||||
|
bool HalGPIO::wasPressed(uint8_t buttonIndex) const { return inputMgr.wasPressed(buttonIndex); }
|
||||||
|
|
||||||
|
bool HalGPIO::wasAnyPressed() const { return inputMgr.wasAnyPressed(); }
|
||||||
|
|
||||||
|
bool HalGPIO::wasReleased(uint8_t buttonIndex) const { return inputMgr.wasReleased(buttonIndex); }
|
||||||
|
|
||||||
|
bool HalGPIO::wasAnyReleased() const { return inputMgr.wasAnyReleased(); }
|
||||||
|
|
||||||
|
unsigned long HalGPIO::getHeldTime() const { return inputMgr.getHeldTime(); }
|
||||||
|
|
||||||
|
void HalGPIO::startDeepSleep() {
|
||||||
|
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
||||||
|
// Ensure that the power button has been released to avoid immediately turning back on if you're holding it
|
||||||
|
while (inputMgr.isPressed(BTN_POWER)) {
|
||||||
|
delay(50);
|
||||||
|
inputMgr.update();
|
||||||
|
}
|
||||||
|
// Enter Deep Sleep
|
||||||
|
esp_deep_sleep_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
int HalGPIO::getBatteryPercentage() const {
|
||||||
|
static const BatteryMonitor battery = BatteryMonitor(BAT_GPIO0);
|
||||||
|
return battery.readPercentage();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HalGPIO::isUsbConnected() const {
|
||||||
|
// U0RXD/GPIO20 reads HIGH when USB is connected
|
||||||
|
return digitalRead(UART0_RXD) == HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HalGPIO::isWakeupByPowerButton() const {
|
||||||
|
const auto wakeupCause = esp_sleep_get_wakeup_cause();
|
||||||
|
const auto resetReason = esp_reset_reason();
|
||||||
|
if (isUsbConnected()) {
|
||||||
|
return wakeupCause == ESP_SLEEP_WAKEUP_GPIO;
|
||||||
|
} else {
|
||||||
|
return (wakeupCause == ESP_SLEEP_WAKEUP_UNDEFINED) && (resetReason == ESP_RST_POWERON);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
lib/hal/HalGPIO.h
Normal file
61
lib/hal/HalGPIO.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <BatteryMonitor.h>
|
||||||
|
#include <InputManager.h>
|
||||||
|
|
||||||
|
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
|
||||||
|
#define EPD_SCLK 8 // SPI Clock
|
||||||
|
#define EPD_MOSI 10 // SPI MOSI (Master Out Slave In)
|
||||||
|
#define EPD_CS 21 // Chip Select
|
||||||
|
#define EPD_DC 4 // Data/Command
|
||||||
|
#define EPD_RST 5 // Reset
|
||||||
|
#define EPD_BUSY 6 // Busy
|
||||||
|
|
||||||
|
#define SPI_MISO 7 // SPI MISO, shared between SD card and display (Master In Slave Out)
|
||||||
|
|
||||||
|
#define BAT_GPIO0 0 // Battery voltage
|
||||||
|
|
||||||
|
#define UART0_RXD 20 // Used for USB connection detection
|
||||||
|
|
||||||
|
class HalGPIO {
|
||||||
|
#if CROSSPOINT_EMULATED == 0
|
||||||
|
InputManager inputMgr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
HalGPIO() = default;
|
||||||
|
|
||||||
|
// Start button GPIO and setup SPI for screen and SD card
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
// Button input methods
|
||||||
|
void update();
|
||||||
|
bool isPressed(uint8_t buttonIndex) const;
|
||||||
|
bool wasPressed(uint8_t buttonIndex) const;
|
||||||
|
bool wasAnyPressed() const;
|
||||||
|
bool wasReleased(uint8_t buttonIndex) const;
|
||||||
|
bool wasAnyReleased() const;
|
||||||
|
unsigned long getHeldTime() const;
|
||||||
|
|
||||||
|
// Setup wake up GPIO and enter deep sleep
|
||||||
|
void startDeepSleep();
|
||||||
|
|
||||||
|
// Get battery percentage (range 0-100)
|
||||||
|
int getBatteryPercentage() const;
|
||||||
|
|
||||||
|
// Check if USB is connected
|
||||||
|
bool isUsbConnected() const;
|
||||||
|
|
||||||
|
// Check if wakeup was caused by power button press
|
||||||
|
bool isWakeupByPowerButton() const;
|
||||||
|
|
||||||
|
// Button indices
|
||||||
|
static constexpr uint8_t BTN_BACK = 0;
|
||||||
|
static constexpr uint8_t BTN_CONFIRM = 1;
|
||||||
|
static constexpr uint8_t BTN_LEFT = 2;
|
||||||
|
static constexpr uint8_t BTN_RIGHT = 3;
|
||||||
|
static constexpr uint8_t BTN_UP = 4;
|
||||||
|
static constexpr uint8_t BTN_DOWN = 5;
|
||||||
|
static constexpr uint8_t BTN_POWER = 6;
|
||||||
|
};
|
||||||
@ -2,7 +2,7 @@
|
|||||||
default_envs = default
|
default_envs = default
|
||||||
|
|
||||||
[crosspoint]
|
[crosspoint]
|
||||||
version = 0.14.0
|
version = 0.16.0
|
||||||
|
|
||||||
[base]
|
[base]
|
||||||
platform = espressif32 @ 6.12.0
|
platform = espressif32 @ 6.12.0
|
||||||
|
|||||||
@ -11,10 +11,18 @@
|
|||||||
// Initialize the static instance
|
// Initialize the static instance
|
||||||
CrossPointSettings CrossPointSettings::instance;
|
CrossPointSettings CrossPointSettings::instance;
|
||||||
|
|
||||||
|
void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) {
|
||||||
|
uint8_t tempValue;
|
||||||
|
serialization::readPod(file, tempValue);
|
||||||
|
if (tempValue < maxValue) {
|
||||||
|
member = tempValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||||
// Increment this when adding new persisted settings fields
|
// Increment this when adding new persisted settings fields
|
||||||
constexpr uint8_t SETTINGS_COUNT = 21;
|
constexpr uint8_t SETTINGS_COUNT = 24;
|
||||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -50,6 +58,9 @@ bool CrossPointSettings::saveToFile() const {
|
|||||||
serialization::writePod(outputFile, longPressChapterSkip);
|
serialization::writePod(outputFile, longPressChapterSkip);
|
||||||
serialization::writePod(outputFile, hyphenationEnabled);
|
serialization::writePod(outputFile, hyphenationEnabled);
|
||||||
serialization::writeString(outputFile, std::string(selectedSleepBmp));
|
serialization::writeString(outputFile, std::string(selectedSleepBmp));
|
||||||
|
serialization::writeString(outputFile, std::string(opdsUsername));
|
||||||
|
serialization::writeString(outputFile, std::string(opdsPassword));
|
||||||
|
serialization::writePod(outputFile, sleepScreenCoverFilter);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
|
|
||||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||||
@ -76,35 +87,35 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
// load settings that exist (support older files with fewer fields)
|
// load settings that exist (support older files with fewer fields)
|
||||||
uint8_t settingsRead = 0;
|
uint8_t settingsRead = 0;
|
||||||
do {
|
do {
|
||||||
serialization::readPod(inputFile, sleepScreen);
|
readAndValidate(inputFile, sleepScreen, SLEEP_SCREEN_MODE_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, extraParagraphSpacing);
|
serialization::readPod(inputFile, extraParagraphSpacing);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, shortPwrBtn);
|
readAndValidate(inputFile, shortPwrBtn, SHORT_PWRBTN_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, statusBar);
|
readAndValidate(inputFile, statusBar, STATUS_BAR_MODE_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, orientation);
|
readAndValidate(inputFile, orientation, ORIENTATION_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, frontButtonLayout);
|
readAndValidate(inputFile, frontButtonLayout, FRONT_BUTTON_LAYOUT_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, sideButtonLayout);
|
readAndValidate(inputFile, sideButtonLayout, SIDE_BUTTON_LAYOUT_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, fontFamily);
|
readAndValidate(inputFile, fontFamily, FONT_FAMILY_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, fontSize);
|
readAndValidate(inputFile, fontSize, FONT_SIZE_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, lineSpacing);
|
readAndValidate(inputFile, lineSpacing, LINE_COMPRESSION_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, paragraphAlignment);
|
readAndValidate(inputFile, paragraphAlignment, PARAGRAPH_ALIGNMENT_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, sleepTimeout);
|
readAndValidate(inputFile, sleepTimeout, SLEEP_TIMEOUT_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, refreshFrequency);
|
readAndValidate(inputFile, refreshFrequency, REFRESH_FREQUENCY_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, screenMargin);
|
serialization::readPod(inputFile, screenMargin);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, sleepScreenCoverMode);
|
readAndValidate(inputFile, sleepScreenCoverMode, SLEEP_SCREEN_COVER_MODE_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
{
|
{
|
||||||
std::string urlStr;
|
std::string urlStr;
|
||||||
@ -115,7 +126,7 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, textAntiAliasing);
|
serialization::readPod(inputFile, textAntiAliasing);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, hideBatteryPercentage);
|
readAndValidate(inputFile, hideBatteryPercentage, HIDE_BATTERY_PERCENTAGE_COUNT);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, longPressChapterSkip);
|
serialization::readPod(inputFile, longPressChapterSkip);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
@ -128,6 +139,22 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
selectedSleepBmp[sizeof(selectedSleepBmp) - 1] = '\0';
|
selectedSleepBmp[sizeof(selectedSleepBmp) - 1] = '\0';
|
||||||
}
|
}
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
{
|
||||||
|
std::string usernameStr;
|
||||||
|
serialization::readString(inputFile, usernameStr);
|
||||||
|
strncpy(opdsUsername, usernameStr.c_str(), sizeof(opdsUsername) - 1);
|
||||||
|
opdsUsername[sizeof(opdsUsername) - 1] = '\0';
|
||||||
|
}
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
{
|
||||||
|
std::string passwordStr;
|
||||||
|
serialization::readString(inputFile, passwordStr);
|
||||||
|
strncpy(opdsPassword, passwordStr.c_str(), sizeof(opdsPassword) - 1);
|
||||||
|
opdsPassword[sizeof(opdsPassword) - 1] = '\0';
|
||||||
|
}
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
readAndValidate(inputFile, sleepScreenCoverFilter, SLEEP_SCREEN_COVER_FILTER_COUNT);
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
} while (false);
|
} while (false);
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
|
|||||||
@ -15,53 +15,94 @@ class CrossPointSettings {
|
|||||||
CrossPointSettings(const CrossPointSettings&) = delete;
|
CrossPointSettings(const CrossPointSettings&) = delete;
|
||||||
CrossPointSettings& operator=(const CrossPointSettings&) = delete;
|
CrossPointSettings& operator=(const CrossPointSettings&) = delete;
|
||||||
|
|
||||||
// Should match with SettingsActivity text
|
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4, SLEEP_SCREEN_MODE_COUNT };
|
||||||
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 };
|
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT };
|
||||||
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1 };
|
enum SLEEP_SCREEN_COVER_FILTER {
|
||||||
|
NO_FILTER = 0,
|
||||||
|
BLACK_AND_WHITE = 1,
|
||||||
|
INVERTED_BLACK_AND_WHITE = 2,
|
||||||
|
SLEEP_SCREEN_COVER_FILTER_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
// Status bar display type enum
|
// Status bar display type enum
|
||||||
enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2 };
|
enum STATUS_BAR_MODE {
|
||||||
|
NONE = 0,
|
||||||
|
NO_PROGRESS = 1,
|
||||||
|
FULL = 2,
|
||||||
|
FULL_WITH_PROGRESS_BAR = 3,
|
||||||
|
ONLY_PROGRESS_BAR = 4,
|
||||||
|
STATUS_BAR_MODE_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
enum ORIENTATION {
|
enum ORIENTATION {
|
||||||
PORTRAIT = 0, // 480x800 logical coordinates (current default)
|
PORTRAIT = 0, // 480x800 logical coordinates (current default)
|
||||||
LANDSCAPE_CW = 1, // 800x480 logical coordinates, rotated 180° (swap top/bottom)
|
LANDSCAPE_CW = 1, // 800x480 logical coordinates, rotated 180° (swap top/bottom)
|
||||||
INVERTED = 2, // 480x800 logical coordinates, inverted
|
INVERTED = 2, // 480x800 logical coordinates, inverted
|
||||||
LANDSCAPE_CCW = 3 // 800x480 logical coordinates, native panel orientation
|
LANDSCAPE_CCW = 3, // 800x480 logical coordinates, native panel orientation
|
||||||
|
ORIENTATION_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
// Front button layout options
|
// Front button layout options
|
||||||
// Default: Back, Confirm, Left, Right
|
// Default: Back, Confirm, Left, Right
|
||||||
// Swapped: Left, Right, Back, Confirm
|
// Swapped: Left, Right, Back, Confirm
|
||||||
enum FRONT_BUTTON_LAYOUT { BACK_CONFIRM_LEFT_RIGHT = 0, LEFT_RIGHT_BACK_CONFIRM = 1, LEFT_BACK_CONFIRM_RIGHT = 2 };
|
enum FRONT_BUTTON_LAYOUT {
|
||||||
|
BACK_CONFIRM_LEFT_RIGHT = 0,
|
||||||
|
LEFT_RIGHT_BACK_CONFIRM = 1,
|
||||||
|
LEFT_BACK_CONFIRM_RIGHT = 2,
|
||||||
|
BACK_CONFIRM_RIGHT_LEFT = 3,
|
||||||
|
FRONT_BUTTON_LAYOUT_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
// Side button layout options
|
// Side button layout options
|
||||||
// Default: Previous, Next
|
// Default: Previous, Next
|
||||||
// Swapped: Next, Previous
|
// Swapped: Next, Previous
|
||||||
enum SIDE_BUTTON_LAYOUT { PREV_NEXT = 0, NEXT_PREV = 1 };
|
enum SIDE_BUTTON_LAYOUT { PREV_NEXT = 0, NEXT_PREV = 1, SIDE_BUTTON_LAYOUT_COUNT };
|
||||||
|
|
||||||
// Font family options
|
// Font family options
|
||||||
enum FONT_FAMILY { BOOKERLY = 0, NOTOSANS = 1, OPENDYSLEXIC = 2 };
|
enum FONT_FAMILY { BOOKERLY = 0, NOTOSANS = 1, OPENDYSLEXIC = 2, FONT_FAMILY_COUNT };
|
||||||
// Font size options
|
// Font size options
|
||||||
enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3 };
|
enum FONT_SIZE { SMALL = 0, MEDIUM = 1, LARGE = 2, EXTRA_LARGE = 3, FONT_SIZE_COUNT };
|
||||||
enum LINE_COMPRESSION { TIGHT = 0, NORMAL = 1, WIDE = 2 };
|
enum LINE_COMPRESSION { TIGHT = 0, NORMAL = 1, WIDE = 2, LINE_COMPRESSION_COUNT };
|
||||||
enum PARAGRAPH_ALIGNMENT { JUSTIFIED = 0, LEFT_ALIGN = 1, CENTER_ALIGN = 2, RIGHT_ALIGN = 3 };
|
enum PARAGRAPH_ALIGNMENT {
|
||||||
|
JUSTIFIED = 0,
|
||||||
|
LEFT_ALIGN = 1,
|
||||||
|
CENTER_ALIGN = 2,
|
||||||
|
RIGHT_ALIGN = 3,
|
||||||
|
PARAGRAPH_ALIGNMENT_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
// Auto-sleep timeout options (in minutes)
|
// Auto-sleep timeout options (in minutes)
|
||||||
enum SLEEP_TIMEOUT { SLEEP_1_MIN = 0, SLEEP_5_MIN = 1, SLEEP_10_MIN = 2, SLEEP_15_MIN = 3, SLEEP_30_MIN = 4 };
|
enum SLEEP_TIMEOUT {
|
||||||
|
SLEEP_1_MIN = 0,
|
||||||
|
SLEEP_5_MIN = 1,
|
||||||
|
SLEEP_10_MIN = 2,
|
||||||
|
SLEEP_15_MIN = 3,
|
||||||
|
SLEEP_30_MIN = 4,
|
||||||
|
SLEEP_TIMEOUT_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
// E-ink refresh frequency (pages between full refreshes)
|
// E-ink refresh frequency (pages between full refreshes)
|
||||||
enum REFRESH_FREQUENCY { REFRESH_1 = 0, REFRESH_5 = 1, REFRESH_10 = 2, REFRESH_15 = 3, REFRESH_30 = 4 };
|
enum REFRESH_FREQUENCY {
|
||||||
|
REFRESH_1 = 0,
|
||||||
|
REFRESH_5 = 1,
|
||||||
|
REFRESH_10 = 2,
|
||||||
|
REFRESH_15 = 3,
|
||||||
|
REFRESH_30 = 4,
|
||||||
|
REFRESH_FREQUENCY_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
// Short power button press actions
|
// Short power button press actions
|
||||||
enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2 };
|
enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2, SHORT_PWRBTN_COUNT };
|
||||||
|
|
||||||
// Hide battery percentage
|
// Hide battery percentage
|
||||||
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2 };
|
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2, HIDE_BATTERY_PERCENTAGE_COUNT };
|
||||||
|
|
||||||
// Sleep screen settings
|
// Sleep screen settings
|
||||||
uint8_t sleepScreen = DARK;
|
uint8_t sleepScreen = DARK;
|
||||||
// Sleep screen cover mode settings
|
// Sleep screen cover mode settings
|
||||||
uint8_t sleepScreenCoverMode = FIT;
|
uint8_t sleepScreenCoverMode = FIT;
|
||||||
|
// Sleep screen cover filter
|
||||||
|
uint8_t sleepScreenCoverFilter = NO_FILTER;
|
||||||
// Status bar settings
|
// Status bar settings
|
||||||
uint8_t statusBar = FULL;
|
uint8_t statusBar = FULL;
|
||||||
// Text rendering settings
|
// Text rendering settings
|
||||||
@ -90,6 +131,8 @@ class CrossPointSettings {
|
|||||||
uint8_t screenMargin = 5;
|
uint8_t screenMargin = 5;
|
||||||
// OPDS browser settings
|
// OPDS browser settings
|
||||||
char opdsServerUrl[128] = "";
|
char opdsServerUrl[128] = "";
|
||||||
|
char opdsUsername[64] = "";
|
||||||
|
char opdsPassword[64] = "";
|
||||||
// Hide battery percentage
|
// Hide battery percentage
|
||||||
uint8_t hideBatteryPercentage = HIDE_NEVER;
|
uint8_t hideBatteryPercentage = HIDE_NEVER;
|
||||||
// Long-press chapter skip on side buttons
|
// Long-press chapter skip on side buttons
|
||||||
|
|||||||
@ -2,87 +2,77 @@
|
|||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
|
|
||||||
decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button button) const {
|
namespace {
|
||||||
|
using ButtonIndex = uint8_t;
|
||||||
|
|
||||||
|
struct FrontLayoutMap {
|
||||||
|
ButtonIndex back;
|
||||||
|
ButtonIndex confirm;
|
||||||
|
ButtonIndex left;
|
||||||
|
ButtonIndex right;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SideLayoutMap {
|
||||||
|
ButtonIndex pageBack;
|
||||||
|
ButtonIndex pageForward;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Order matches CrossPointSettings::FRONT_BUTTON_LAYOUT.
|
||||||
|
constexpr FrontLayoutMap kFrontLayouts[] = {
|
||||||
|
{HalGPIO::BTN_BACK, HalGPIO::BTN_CONFIRM, HalGPIO::BTN_LEFT, HalGPIO::BTN_RIGHT},
|
||||||
|
{HalGPIO::BTN_LEFT, HalGPIO::BTN_RIGHT, HalGPIO::BTN_BACK, HalGPIO::BTN_CONFIRM},
|
||||||
|
{HalGPIO::BTN_CONFIRM, HalGPIO::BTN_LEFT, HalGPIO::BTN_BACK, HalGPIO::BTN_RIGHT},
|
||||||
|
{HalGPIO::BTN_BACK, HalGPIO::BTN_CONFIRM, HalGPIO::BTN_RIGHT, HalGPIO::BTN_LEFT},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Order matches CrossPointSettings::SIDE_BUTTON_LAYOUT.
|
||||||
|
constexpr SideLayoutMap kSideLayouts[] = {
|
||||||
|
{HalGPIO::BTN_UP, HalGPIO::BTN_DOWN},
|
||||||
|
{HalGPIO::BTN_DOWN, HalGPIO::BTN_UP},
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool MappedInputManager::mapButton(const Button button, bool (HalGPIO::*fn)(uint8_t) const) const {
|
||||||
const auto frontLayout = static_cast<CrossPointSettings::FRONT_BUTTON_LAYOUT>(SETTINGS.frontButtonLayout);
|
const auto frontLayout = static_cast<CrossPointSettings::FRONT_BUTTON_LAYOUT>(SETTINGS.frontButtonLayout);
|
||||||
const auto sideLayout = static_cast<CrossPointSettings::SIDE_BUTTON_LAYOUT>(SETTINGS.sideButtonLayout);
|
const auto sideLayout = static_cast<CrossPointSettings::SIDE_BUTTON_LAYOUT>(SETTINGS.sideButtonLayout);
|
||||||
|
const auto& front = kFrontLayouts[frontLayout];
|
||||||
|
const auto& side = kSideLayouts[sideLayout];
|
||||||
|
|
||||||
switch (button) {
|
switch (button) {
|
||||||
case Button::Back:
|
case Button::Back:
|
||||||
switch (frontLayout) {
|
return (gpio.*fn)(front.back);
|
||||||
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
|
||||||
return InputManager::BTN_LEFT;
|
|
||||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
|
||||||
return InputManager::BTN_CONFIRM;
|
|
||||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
|
||||||
default:
|
|
||||||
return InputManager::BTN_BACK;
|
|
||||||
}
|
|
||||||
case Button::Confirm:
|
case Button::Confirm:
|
||||||
switch (frontLayout) {
|
return (gpio.*fn)(front.confirm);
|
||||||
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
|
||||||
return InputManager::BTN_RIGHT;
|
|
||||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
|
||||||
return InputManager::BTN_LEFT;
|
|
||||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
|
||||||
default:
|
|
||||||
return InputManager::BTN_CONFIRM;
|
|
||||||
}
|
|
||||||
case Button::Left:
|
case Button::Left:
|
||||||
switch (frontLayout) {
|
return (gpio.*fn)(front.left);
|
||||||
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
|
||||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
|
||||||
return InputManager::BTN_BACK;
|
|
||||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
|
||||||
default:
|
|
||||||
return InputManager::BTN_LEFT;
|
|
||||||
}
|
|
||||||
case Button::Right:
|
case Button::Right:
|
||||||
switch (frontLayout) {
|
return (gpio.*fn)(front.right);
|
||||||
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
|
||||||
return InputManager::BTN_CONFIRM;
|
|
||||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
|
||||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
|
||||||
default:
|
|
||||||
return InputManager::BTN_RIGHT;
|
|
||||||
}
|
|
||||||
case Button::Up:
|
case Button::Up:
|
||||||
return InputManager::BTN_UP;
|
return (gpio.*fn)(HalGPIO::BTN_UP);
|
||||||
case Button::Down:
|
case Button::Down:
|
||||||
return InputManager::BTN_DOWN;
|
return (gpio.*fn)(HalGPIO::BTN_DOWN);
|
||||||
case Button::Power:
|
case Button::Power:
|
||||||
return InputManager::BTN_POWER;
|
return (gpio.*fn)(HalGPIO::BTN_POWER);
|
||||||
case Button::PageBack:
|
case Button::PageBack:
|
||||||
switch (sideLayout) {
|
return (gpio.*fn)(side.pageBack);
|
||||||
case CrossPointSettings::NEXT_PREV:
|
|
||||||
return InputManager::BTN_DOWN;
|
|
||||||
case CrossPointSettings::PREV_NEXT:
|
|
||||||
default:
|
|
||||||
return InputManager::BTN_UP;
|
|
||||||
}
|
|
||||||
case Button::PageForward:
|
case Button::PageForward:
|
||||||
switch (sideLayout) {
|
return (gpio.*fn)(side.pageForward);
|
||||||
case CrossPointSettings::NEXT_PREV:
|
|
||||||
return InputManager::BTN_UP;
|
|
||||||
case CrossPointSettings::PREV_NEXT:
|
|
||||||
default:
|
|
||||||
return InputManager::BTN_DOWN;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return InputManager::BTN_BACK;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MappedInputManager::wasPressed(const Button button) const { return inputManager.wasPressed(mapButton(button)); }
|
bool MappedInputManager::wasPressed(const Button button) const { return mapButton(button, &HalGPIO::wasPressed); }
|
||||||
|
|
||||||
bool MappedInputManager::wasReleased(const Button button) const { return inputManager.wasReleased(mapButton(button)); }
|
bool MappedInputManager::wasReleased(const Button button) const { return mapButton(button, &HalGPIO::wasReleased); }
|
||||||
|
|
||||||
bool MappedInputManager::isPressed(const Button button) const { return inputManager.isPressed(mapButton(button)); }
|
bool MappedInputManager::isPressed(const Button button) const { return mapButton(button, &HalGPIO::isPressed); }
|
||||||
|
|
||||||
bool MappedInputManager::wasAnyPressed() const { return inputManager.wasAnyPressed(); }
|
bool MappedInputManager::wasAnyPressed() const { return gpio.wasAnyPressed(); }
|
||||||
|
|
||||||
bool MappedInputManager::wasAnyReleased() const { return inputManager.wasAnyReleased(); }
|
bool MappedInputManager::wasAnyReleased() const { return gpio.wasAnyReleased(); }
|
||||||
|
|
||||||
unsigned long MappedInputManager::getHeldTime() const { return inputManager.getHeldTime(); }
|
unsigned long MappedInputManager::getHeldTime() const { return gpio.getHeldTime(); }
|
||||||
|
|
||||||
MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const char* confirm, const char* previous,
|
MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const char* confirm, const char* previous,
|
||||||
const char* next) const {
|
const char* next) const {
|
||||||
@ -93,6 +83,8 @@ MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const
|
|||||||
return {previous, next, back, confirm};
|
return {previous, next, back, confirm};
|
||||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
||||||
return {previous, back, confirm, next};
|
return {previous, back, confirm, next};
|
||||||
|
case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT:
|
||||||
|
return {back, confirm, next, previous};
|
||||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||||
default:
|
default:
|
||||||
return {back, confirm, previous, next};
|
return {back, confirm, previous, next};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <InputManager.h>
|
#include <HalGPIO.h>
|
||||||
|
|
||||||
class MappedInputManager {
|
class MappedInputManager {
|
||||||
public:
|
public:
|
||||||
@ -13,7 +13,7 @@ class MappedInputManager {
|
|||||||
const char* btn4;
|
const char* btn4;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit MappedInputManager(InputManager& inputManager) : inputManager(inputManager) {}
|
explicit MappedInputManager(HalGPIO& gpio) : gpio(gpio) {}
|
||||||
|
|
||||||
bool wasPressed(Button button) const;
|
bool wasPressed(Button button) const;
|
||||||
bool wasReleased(Button button) const;
|
bool wasReleased(Button button) const;
|
||||||
@ -24,6 +24,7 @@ class MappedInputManager {
|
|||||||
Labels mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const;
|
Labels mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
InputManager& inputManager;
|
HalGPIO& gpio;
|
||||||
decltype(InputManager::BTN_BACK) mapButton(Button button) const;
|
|
||||||
|
bool mapButton(Button button, bool (HalGPIO::*fn)(uint8_t) const) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,22 +7,23 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t RECENT_BOOKS_FILE_VERSION = 1;
|
constexpr uint8_t RECENT_BOOKS_FILE_VERSION = 2;
|
||||||
constexpr char RECENT_BOOKS_FILE[] = "/.crosspoint/recent.bin";
|
constexpr char RECENT_BOOKS_FILE[] = "/.crosspoint/recent.bin";
|
||||||
constexpr int MAX_RECENT_BOOKS = 10;
|
constexpr int MAX_RECENT_BOOKS = 10;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
RecentBooksStore RecentBooksStore::instance;
|
RecentBooksStore RecentBooksStore::instance;
|
||||||
|
|
||||||
void RecentBooksStore::addBook(const std::string& path) {
|
void RecentBooksStore::addBook(const std::string& path, const std::string& title, const std::string& author) {
|
||||||
// Remove existing entry if present
|
// Remove existing entry if present
|
||||||
auto it = std::find(recentBooks.begin(), recentBooks.end(), path);
|
auto it =
|
||||||
|
std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; });
|
||||||
if (it != recentBooks.end()) {
|
if (it != recentBooks.end()) {
|
||||||
recentBooks.erase(it);
|
recentBooks.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to front
|
// Add to front
|
||||||
recentBooks.insert(recentBooks.begin(), path);
|
recentBooks.insert(recentBooks.begin(), {path, title, author});
|
||||||
|
|
||||||
// Trim to max size
|
// Trim to max size
|
||||||
if (recentBooks.size() > MAX_RECENT_BOOKS) {
|
if (recentBooks.size() > MAX_RECENT_BOOKS) {
|
||||||
@ -46,7 +47,9 @@ bool RecentBooksStore::saveToFile() const {
|
|||||||
serialization::writePod(outputFile, count);
|
serialization::writePod(outputFile, count);
|
||||||
|
|
||||||
for (const auto& book : recentBooks) {
|
for (const auto& book : recentBooks) {
|
||||||
serialization::writeString(outputFile, book);
|
serialization::writeString(outputFile, book.path);
|
||||||
|
serialization::writeString(outputFile, book.title);
|
||||||
|
serialization::writeString(outputFile, book.author);
|
||||||
}
|
}
|
||||||
|
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
@ -63,11 +66,25 @@ bool RecentBooksStore::loadFromFile() {
|
|||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(inputFile, version);
|
serialization::readPod(inputFile, version);
|
||||||
if (version != RECENT_BOOKS_FILE_VERSION) {
|
if (version != RECENT_BOOKS_FILE_VERSION) {
|
||||||
|
if (version == 1) {
|
||||||
|
// Old version, just read paths
|
||||||
|
uint8_t count;
|
||||||
|
serialization::readPod(inputFile, count);
|
||||||
|
recentBooks.clear();
|
||||||
|
recentBooks.reserve(count);
|
||||||
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
|
std::string path;
|
||||||
|
serialization::readString(inputFile, path);
|
||||||
|
// Title and author will be empty, they will be filled when the book is
|
||||||
|
// opened again
|
||||||
|
recentBooks.push_back({path, "", ""});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Serial.printf("[%lu] [RBS] Deserialization failed: Unknown version %u\n", millis(), version);
|
Serial.printf("[%lu] [RBS] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
uint8_t count;
|
uint8_t count;
|
||||||
serialization::readPod(inputFile, count);
|
serialization::readPod(inputFile, count);
|
||||||
|
|
||||||
@ -75,12 +92,15 @@ bool RecentBooksStore::loadFromFile() {
|
|||||||
recentBooks.reserve(count);
|
recentBooks.reserve(count);
|
||||||
|
|
||||||
for (uint8_t i = 0; i < count; i++) {
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
std::string path;
|
std::string path, title, author;
|
||||||
serialization::readString(inputFile, path);
|
serialization::readString(inputFile, path);
|
||||||
recentBooks.push_back(path);
|
serialization::readString(inputFile, title);
|
||||||
|
serialization::readString(inputFile, author);
|
||||||
|
recentBooks.push_back({path, title, author});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
Serial.printf("[%lu] [RBS] Recent books loaded from file (%d entries)\n", millis(), count);
|
Serial.printf("[%lu] [RBS] Recent books loaded from file (%d entries)\n", millis(), recentBooks.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,19 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
struct RecentBook {
|
||||||
|
std::string path;
|
||||||
|
std::string title;
|
||||||
|
std::string author;
|
||||||
|
|
||||||
|
bool operator==(const RecentBook& other) const { return path == other.path; }
|
||||||
|
};
|
||||||
|
|
||||||
class RecentBooksStore {
|
class RecentBooksStore {
|
||||||
// Static instance
|
// Static instance
|
||||||
static RecentBooksStore instance;
|
static RecentBooksStore instance;
|
||||||
|
|
||||||
std::vector<std::string> recentBooks;
|
std::vector<RecentBook> recentBooks;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~RecentBooksStore() = default;
|
~RecentBooksStore() = default;
|
||||||
@ -14,11 +22,11 @@ class RecentBooksStore {
|
|||||||
// Get singleton instance
|
// Get singleton instance
|
||||||
static RecentBooksStore& getInstance() { return instance; }
|
static RecentBooksStore& getInstance() { return instance; }
|
||||||
|
|
||||||
// Add a book path to the recent list (moves to front if already exists)
|
// Add a book to the recent list (moves to front if already exists)
|
||||||
void addBook(const std::string& path);
|
void addBook(const std::string& path, const std::string& title, const std::string& author);
|
||||||
|
|
||||||
// Get the list of recent book paths (most recent first)
|
// Get the list of recent books (most recent first)
|
||||||
const std::vector<std::string>& getBooks() const { return recentBooks; }
|
const std::vector<RecentBook>& getBooks() const { return recentBooks; }
|
||||||
|
|
||||||
// Get the count of recent books
|
// Get the count of recent books
|
||||||
int getCount() const { return static_cast<int>(recentBooks.size()); }
|
int getCount() const { return static_cast<int>(recentBooks.size()); }
|
||||||
|
|||||||
@ -42,6 +42,17 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left,
|
|||||||
renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4);
|
renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScreenComponents::drawBookProgressBar(const GfxRenderer& renderer, const size_t bookProgress) {
|
||||||
|
int vieweableMarginTop, vieweableMarginRight, vieweableMarginBottom, vieweableMarginLeft;
|
||||||
|
renderer.getOrientedViewableTRBL(&vieweableMarginTop, &vieweableMarginRight, &vieweableMarginBottom,
|
||||||
|
&vieweableMarginLeft);
|
||||||
|
|
||||||
|
const int progressBarMaxWidth = renderer.getScreenWidth() - vieweableMarginLeft - vieweableMarginRight;
|
||||||
|
const int progressBarY = renderer.getScreenHeight() - vieweableMarginBottom - BOOK_PROGRESS_BAR_HEIGHT;
|
||||||
|
const int barWidth = progressBarMaxWidth * bookProgress / 100;
|
||||||
|
renderer.fillRect(vieweableMarginLeft, progressBarY, barWidth, BOOK_PROGRESS_BAR_HEIGHT, true);
|
||||||
|
}
|
||||||
|
|
||||||
int ScreenComponents::drawTabBar(const GfxRenderer& renderer, const int y, const std::vector<TabInfo>& tabs) {
|
int ScreenComponents::drawTabBar(const GfxRenderer& renderer, const int y, const std::vector<TabInfo>& tabs) {
|
||||||
constexpr int tabPadding = 20; // Horizontal padding between tabs
|
constexpr int tabPadding = 20; // Horizontal padding between tabs
|
||||||
constexpr int leftMargin = 20; // Left margin for first tab
|
constexpr int leftMargin = 20; // Left margin for first tab
|
||||||
|
|||||||
@ -13,7 +13,10 @@ struct TabInfo {
|
|||||||
|
|
||||||
class ScreenComponents {
|
class ScreenComponents {
|
||||||
public:
|
public:
|
||||||
|
static const int BOOK_PROGRESS_BAR_HEIGHT = 4;
|
||||||
|
|
||||||
static void drawBattery(const GfxRenderer& renderer, int left, int top, bool showPercentage = true);
|
static void drawBattery(const GfxRenderer& renderer, int left, int top, bool showPercentage = true);
|
||||||
|
static void drawBookProgressBar(const GfxRenderer& renderer, size_t bookProgress);
|
||||||
|
|
||||||
// Draw a horizontal tab bar with underline indicator for selected tab
|
// Draw a horizontal tab bar with underline indicator for selected tab
|
||||||
// Returns the height of the tab bar (for positioning content below)
|
// Returns the height of the tab bar (for positioning content below)
|
||||||
|
|||||||
@ -12,7 +12,7 @@ void BootActivity::onEnter() {
|
|||||||
const auto pageHeight = renderer.getScreenHeight();
|
const auto pageHeight = renderer.getScreenHeight();
|
||||||
|
|
||||||
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, "CrossPoint", true, EpdFontFamily::BOLD);
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING");
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING");
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION);
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user