mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2025-12-18 07:07:41 +03:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
063a1df851 | ||
|
|
d429966dd4 | ||
|
|
c78f2a9840 | ||
|
|
11f01d3a41 | ||
|
|
973d372521 | ||
|
|
67da8139b3 | ||
|
|
c287aa03a4 | ||
|
|
5d68c8b305 | ||
|
|
def7abbd60 | ||
|
|
9ad8111ce7 | ||
|
|
57d1939be7 | ||
|
|
012992f904 | ||
|
|
c262f222de | ||
|
|
449b3ca161 | ||
|
|
6989035ef8 | ||
|
|
108cf57202 | ||
|
|
a640fbecf8 | ||
|
|
7a5719b46d | ||
|
|
8c3576e397 | ||
|
|
fdb5634ea6 | ||
|
|
5cabba7712 | ||
|
|
a86d405fb0 | ||
|
|
e4b5dc0e6a | ||
|
|
dfc74f94c2 | ||
|
|
3518cbb56d | ||
|
|
8994953254 | ||
|
|
ead39fd04b | ||
|
|
5a7381a0eb | ||
|
|
f69fc90b5c | ||
|
|
5bae283838 | ||
|
|
c7a32fe41f | ||
|
|
d450f362d1 | ||
|
|
6ddcf9b592 | ||
|
|
492c6fd23e | ||
|
|
7c852cf7d1 | ||
|
|
69f357998e | ||
|
|
09f68a3d03 | ||
|
|
7ec7efcb47 | ||
|
|
45af2d0e81 | ||
|
|
0926e9e6e4 | ||
|
|
02b157c02b | ||
|
|
07cc589e59 | ||
|
|
b743a1ca8e | ||
|
|
2ed8017aa2 | ||
|
|
de453fed1d | ||
|
|
c715c18bf7 | ||
|
|
9fa697ae0c | ||
|
|
79294f6b8f | ||
|
|
eceffaa289 | ||
|
|
bb151caee7 | ||
|
|
dd6e649d74 | ||
|
|
ad8cee12ab | ||
|
|
4ecfdea1a1 | ||
|
|
5ed2fe391d |
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
github: [daveallie]
|
||||
ko_fi: daveallie
|
||||
54
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
54
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
name: Bug Report
|
||||
description: Report an issue or unexpected behavior
|
||||
title: "Short, descriptive title of the issue"
|
||||
labels: ["bug", "triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to report this bug! Please fill out the details below.
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Affected Version
|
||||
description: What version of the project/library are you using? (e.g., v1.2.3, master branch commit SHA)
|
||||
placeholder: Ex. v1.2.3
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Describe the Bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Clearly list the steps necessary to reproduce the unexpected behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Select '...'
|
||||
3. Crash
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant Log Output/Screenshots
|
||||
description: If applicable, error messages, or log output to help explain your problem. You can drag and drop images here.
|
||||
render: shell
|
||||
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
## Summary
|
||||
|
||||
* **What is the goal of this PR?** (e.g., Fixes a bug in the user authentication module, Implements the new feature for
|
||||
file uploading.)
|
||||
* **What changes are included?**
|
||||
|
||||
## Additional Context
|
||||
|
||||
* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on).
|
||||
43
.github/workflows/ci.yml
vendored
Normal file
43
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
name: CI
|
||||
'on':
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pip
|
||||
~/.platformio/.cache
|
||||
key: ${{ runner.os }}-pio
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: pip install --upgrade platformio
|
||||
|
||||
- name: Install clang-format-21
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 21
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-format-21
|
||||
|
||||
- name: Run cppcheck
|
||||
run: pio check --fail-on-defect medium --fail-on-defect high
|
||||
|
||||
- name: Run clang-format
|
||||
run: PATH="/usr/lib/llvm-21/bin:$PATH" ./bin/clang-format-fix && git diff --exit-code || (echo "Please run 'bin/clang-format-fix' to fix formatting issues" && exit 1)
|
||||
|
||||
- name: Build CrossPoint
|
||||
run: pio run
|
||||
40
.github/workflows/release.yml
vendored
Normal file
40
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: Compile Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pip
|
||||
~/.platformio/.cache
|
||||
key: ${{ runner.os }}-pio
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: pip install --upgrade platformio
|
||||
|
||||
- name: Build CrossPoint
|
||||
run: pio run -e gh_release
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CrossPoint-${{ github.ref_name }}
|
||||
path: |
|
||||
.pio/build/gh_release/bootloader.bin
|
||||
.pio/build/gh_release/firmware.bin
|
||||
.pio/build/gh_release/firmware.elf
|
||||
.pio/build/gh_release/firmware.map
|
||||
.pio/build/gh_release/partitions.bin
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
.pio
|
||||
.idea
|
||||
.DS_Store
|
||||
.vscode
|
||||
lib/EpdFont/fontsrc
|
||||
|
||||
52
README.md
52
README.md
@ -6,10 +6,7 @@ Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller.
|
||||
CrossPoint Reader is a purpose-built firmware designed to be a drop-in, fully open-source replacement for the official
|
||||
Xteink firmware. It aims to match or improve upon the standard EPUB reading experience.
|
||||
|
||||
// TODO include some images
|
||||
|
||||
I look at the [**diy-esp32-epub-reader** by atomic14](https://github.com/atomic14/diy-esp32-epub-reader) project a lot
|
||||
when making CrossPoint and a handful of lessons and some direct source code comes directly from that repo.
|
||||

|
||||
|
||||
## Motivation
|
||||
|
||||
@ -39,7 +36,34 @@ This project is **not affiliated with Xteink**; it's built as a community projec
|
||||
- [ ] WiFi connectivity
|
||||
- [ ] BLE connectivity
|
||||
|
||||
## Getting Started
|
||||
## Installing
|
||||
|
||||
### Web (latest firmware)
|
||||
|
||||
1. Connect your Xteink X4 to your computer via USB-C
|
||||
2. Go to https://xteink.dve.al/ and click "Flash CrossPoint firmware"
|
||||
|
||||
To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap
|
||||
back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug.
|
||||
|
||||
### Web (specific firmware version)
|
||||
|
||||
1. Connect your Xteink X4 to your computer via USB-C
|
||||
2. Download the `firmware.bin` file from the release of your choice via the [releases page](https://github.com/daveallie/crosspoint-reader/releases)
|
||||
3. Go to https://xteink.dve.al/ and flash the firmware file using the "OTA fast flash controls" section
|
||||
|
||||
To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap
|
||||
back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug.
|
||||
|
||||
### Manual
|
||||
|
||||
See [Development](#development) below.
|
||||
|
||||
## Usage
|
||||
|
||||
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@ -61,24 +85,12 @@ git submodule update --init --recursive
|
||||
|
||||
### Flashing your device
|
||||
|
||||
#### Command line
|
||||
|
||||
Connect your Xteink X4 to your computer via USB-C and run the following command.
|
||||
|
||||
```sh
|
||||
pio run --target upload
|
||||
```
|
||||
|
||||
#### Web
|
||||
|
||||
1. Connect your Xteink X4 to your computer via USB-C
|
||||
2. Download the `firmware.bin` file from the latest release via the [releases page](https://github.com/daveallie/crosspoint-reader/releases)
|
||||
3. Go to https://xteink.dve.al/ and flash the firmware file using the "OTA fast flash controls" section
|
||||
4. Press the reset button on the Xteink X4 to restart the device
|
||||
|
||||
To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap
|
||||
back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug.
|
||||
|
||||
## Internals
|
||||
|
||||
CrossPoint Reader is pretty aggressive about caching data down to the SD card to minimise RAM usage. The ESP32-C3 only
|
||||
@ -119,6 +131,9 @@ EPUB file will reset the reading progress.
|
||||
|
||||
Contributions are very welcome!
|
||||
|
||||
If you're looking for a way to help out, take a look at the [ideas discussion board](https://github.com/daveallie/crosspoint-reader/discussions/categories/ideas).
|
||||
If there's something there you'd like to work on, leave a comment so that we can avoid duplicated effort.
|
||||
|
||||
### To submit a contribution:
|
||||
|
||||
1. Fork the repo
|
||||
@ -129,3 +144,6 @@ Contributions are very welcome!
|
||||
---
|
||||
|
||||
CrossPoint Reader is **not affiliated with Xteink or any manufacturer of the X4 hardware**.
|
||||
|
||||
Huge shoutout to [**diy-esp32-epub-reader** by atomic14](https://github.com/atomic14/diy-esp32-epub-reader), which was a project I took a lot of inspiration from as I
|
||||
was making CrossPoint.
|
||||
|
||||
78
USER_GUIDE.md
Normal file
78
USER_GUIDE.md
Normal file
@ -0,0 +1,78 @@
|
||||
# CrossPoint User Guide
|
||||
|
||||
Welcome to the **CrossPoint** firmware. This guide outlines the hardware controls, navigation, and reading features of
|
||||
the device.
|
||||
|
||||
## 1. Hardware Overview
|
||||
|
||||
The device utilises the standard buttons on the Xtink X4 in the same layout:
|
||||
|
||||
### Button Layout
|
||||
| Location | Buttons |
|
||||
|-----------------|--------------------------------------------|
|
||||
| **Bottom Edge** | **Back**, **Confirm**, **Left**, **Right** |
|
||||
| **Right Side** | **Power**, **Volume Up**, **Volume Down** |
|
||||
|
||||
---
|
||||
|
||||
## 2. Power & Startup
|
||||
|
||||
### Power On / Off
|
||||
|
||||
To turn the device on or off, **press and hold the Power button for 1 full second**.
|
||||
|
||||
### First Launch
|
||||
|
||||
Upon turning the device on for the first time, you will be placed on the **Book Selection Screen** (File Browser).
|
||||
|
||||
> **Note:** On subsequent restarts, the firmware will automatically reopen the last book you were reading.
|
||||
|
||||
---
|
||||
|
||||
## 3. Book Selection
|
||||
|
||||
The Home Screen acts as a folder and file browser.
|
||||
|
||||
* **Navigate List:** Use **Left** (or **Volume Up**), or **Right** (or **Volume Down**) to move the selection cursor up
|
||||
and down through folders and books.
|
||||
* **Open Selection:** Press **Confirm** to open a folder or read a selected book.
|
||||
|
||||
---
|
||||
|
||||
## 4. Reading Mode
|
||||
|
||||
Once you have opened a book, the button layout changes to facilitate reading.
|
||||
|
||||
### Page Turning
|
||||
| Action | Buttons |
|
||||
|-------------------|--------------------------------------|
|
||||
| **Previous Page** | Press **Left** _or_ **Volume Up** |
|
||||
| **Next Page** | Press **Right** _or_ **Volume Down** |
|
||||
|
||||
### Chapter Navigation
|
||||
* **Next Chapter:** Press and **hold** the **Right** (or **Volume Down**) button briefly, then release.
|
||||
* **Previous Chapter:** Press and **hold** the **Left** (or **Volume Up**) button briefly, then release.
|
||||
|
||||
### System Navigation
|
||||
* **Return to Home:** Press **Back** to close the book and return to the Book Selection screen.
|
||||
* **Chapter Menu:** Press **Confirm** to open the Table of Contents/Chapter Selection screen.
|
||||
|
||||
---
|
||||
|
||||
## 5. Chapter Selection Screen
|
||||
|
||||
Accessible by pressing **Confirm** while inside a book.
|
||||
|
||||
1. Use **Left** (or **Volume Up**), or **Right** (or **Volume Down**) to highlight the desired chapter.
|
||||
2. Press **Confirm** to jump to that chapter.
|
||||
3. *Alternatively, press **Back** to cancel and return to your current page.*
|
||||
|
||||
---
|
||||
|
||||
## 6. Current Limitations & Roadmap
|
||||
|
||||
Please note that this firmware is currently in active development. The following features are **not yet supported** but
|
||||
are planned for future updates:
|
||||
|
||||
* **Images:** Embedded images in e-books will not render.
|
||||
* **Text Formatting:** There are currently no settings to adjust font type, size, line spacing, or margins.
|
||||
19
docs/comparison.md
Normal file
19
docs/comparison.md
Normal file
@ -0,0 +1,19 @@
|
||||
# CrossPoint vs XTOS
|
||||
|
||||
Below is like for like comparison of CrossPoint (version 0.5.1) and XTOS (version 3.1.1). CrossPoint is on the left,
|
||||
XTOS is on the right. CrossPoint does not currently support all features of XTOS, so this comparison is just of key
|
||||
features which both firmwares support.
|
||||
|
||||
## EPUB reading
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Menus
|
||||
|
||||

|
||||
|
||||

|
||||
BIN
docs/images/comparison/chapter-menu.jpg
Normal file
BIN
docs/images/comparison/chapter-menu.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
docs/images/comparison/menu.jpg
Normal file
BIN
docs/images/comparison/menu.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
docs/images/comparison/reading-1.jpg
Normal file
BIN
docs/images/comparison/reading-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
BIN
docs/images/comparison/reading-2.jpg
Normal file
BIN
docs/images/comparison/reading-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
BIN
docs/images/comparison/reading-3.jpg
Normal file
BIN
docs/images/comparison/reading-3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/images/cover.jpg
Normal file
BIN
docs/images/cover.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
@ -2,12 +2,9 @@
|
||||
|
||||
#include <Utf8.h>
|
||||
|
||||
EpdFont::EpdFont(const EpdFontData* data) { this->data = data; }
|
||||
|
||||
inline int min(const int a, const int b) { return a < b ? a : b; }
|
||||
inline int max(const int a, const int b) { return a < b ? b : a; }
|
||||
|
||||
// TODO: Text properties??
|
||||
void EpdFont::getTextBounds(const char* string, const int startX, const int startY, int* minX, int* minY, int* maxX,
|
||||
int* maxY) const {
|
||||
*minX = startX;
|
||||
|
||||
@ -6,7 +6,7 @@ class EpdFont {
|
||||
|
||||
public:
|
||||
const EpdFontData* data;
|
||||
explicit EpdFont(const EpdFontData* data);
|
||||
explicit EpdFont(const EpdFontData* data) : data(data) {}
|
||||
~EpdFont() = default;
|
||||
void getTextDimensions(const char* string, int* w, int* h) const;
|
||||
bool hasPrintableChars(const char* string) const;
|
||||
|
||||
@ -6,13 +6,13 @@
|
||||
|
||||
/// Font data stored PER GLYPH
|
||||
typedef struct {
|
||||
uint8_t width; ///< Bitmap dimensions in pixels
|
||||
uint8_t height; ///< Bitmap dimensions in pixels
|
||||
uint8_t advanceX; ///< Distance to advance cursor (x axis)
|
||||
int16_t left; ///< X dist from cursor pos to UL corner
|
||||
int16_t top; ///< Y dist from cursor pos to UL corner
|
||||
uint16_t compressedSize; ///< Size of the zlib-compressed font data.
|
||||
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap
|
||||
uint8_t width; ///< Bitmap dimensions in pixels
|
||||
uint8_t height; ///< Bitmap dimensions in pixels
|
||||
uint8_t advanceX; ///< Distance to advance cursor (x axis)
|
||||
int16_t left; ///< X dist from cursor pos to UL corner
|
||||
int16_t top; ///< Y dist from cursor pos to UL corner
|
||||
uint16_t dataLength; ///< Size of the font data.
|
||||
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap
|
||||
} EpdGlyph;
|
||||
|
||||
/// Glyph interval structure
|
||||
@ -28,8 +28,8 @@ typedef struct {
|
||||
const EpdGlyph* glyph; ///< Glyph array
|
||||
const EpdUnicodeInterval* intervals; ///< Valid unicode intervals for this font
|
||||
uint32_t intervalCount; ///< Number of unicode intervals.
|
||||
bool compressed; ///< Does this font use compressed glyph bitmaps?
|
||||
uint8_t advanceY; ///< Newline distance (y axis)
|
||||
int ascender; ///< Maximal height of a glyph above the base line
|
||||
int descender; ///< Maximal height of a glyph below the base line
|
||||
bool is2Bit;
|
||||
} EpdFontData;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4033
lib/EpdFont/builtinFonts/bookerly_2b.h
Normal file
4033
lib/EpdFont/builtinFonts/bookerly_2b.h
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4291
lib/EpdFont/builtinFonts/bookerly_bold_2b.h
Normal file
4291
lib/EpdFont/builtinFonts/bookerly_bold_2b.h
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4454
lib/EpdFont/builtinFonts/bookerly_bold_italic_2b.h
Normal file
4454
lib/EpdFont/builtinFonts/bookerly_bold_italic_2b.h
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4169
lib/EpdFont/builtinFonts/bookerly_italic_2b.h
Normal file
4169
lib/EpdFont/builtinFonts/bookerly_italic_2b.h
Normal file
File diff suppressed because it is too large
Load Diff
1041
lib/EpdFont/builtinFonts/pixelarial14.h
Normal file
1041
lib/EpdFont/builtinFonts/pixelarial14.h
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -7,20 +7,20 @@ import math
|
||||
import argparse
|
||||
from collections import namedtuple
|
||||
|
||||
# From: https://github.com/vroland/epdiy
|
||||
# Originally from https://github.com/vroland/epdiy
|
||||
|
||||
parser = argparse.ArgumentParser(description="Generate a header file from a font to be used with epdiy.")
|
||||
parser.add_argument("name", action="store", help="name of the font.")
|
||||
parser.add_argument("size", type=int, help="font size to use.")
|
||||
parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority.")
|
||||
parser.add_argument("--compress", dest="compress", action="store_true", help="compress glyph bitmaps.")
|
||||
parser.add_argument("--2bit", dest="is2Bit", action="store_true", help="generate 2-bit greyscale bitmap instead of 1-bit black and white.")
|
||||
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
|
||||
args = parser.parse_args()
|
||||
|
||||
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "compressed_size", "data_offset", "code_point"])
|
||||
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "code_point"])
|
||||
|
||||
font_stack = [freetype.Face(f) for f in args.fontstack]
|
||||
compress = args.compress
|
||||
is2Bit = args.is2Bit
|
||||
size = args.size
|
||||
font_name = args.name
|
||||
|
||||
@ -54,7 +54,7 @@ intervals = [
|
||||
# (0x0370, 0x03FF),
|
||||
### Cyrillic ###
|
||||
# Russian, Ukrainian, Bulgarian, etc.
|
||||
# (0x0400, 0x04FF),
|
||||
(0x0400, 0x04FF),
|
||||
### Math Symbols (common subset) ###
|
||||
# General math operators
|
||||
(0x2200, 0x22FF),
|
||||
@ -148,19 +148,18 @@ for i_start, i_end in unvalidated_intervals:
|
||||
intervals.append((start, i_end))
|
||||
|
||||
for face in font_stack:
|
||||
# shift by 6 bytes, because sizes are given as 6-bit fractions
|
||||
# the display has about 150 dpi.
|
||||
face.set_char_size(size << 6, size << 6, 150, 150)
|
||||
|
||||
total_size = 0
|
||||
total_packed = 0
|
||||
all_glyphs = []
|
||||
|
||||
for i_start, i_end in intervals:
|
||||
for code_point in range(i_start, i_end + 1):
|
||||
face = load_glyph(code_point)
|
||||
bitmap = face.glyph.bitmap
|
||||
pixels = []
|
||||
|
||||
# Build out 4-bit greyscale bitmap
|
||||
pixels4g = []
|
||||
px = 0
|
||||
for i, v in enumerate(bitmap.buffer):
|
||||
y = i / bitmap.width
|
||||
@ -169,31 +168,92 @@ for i_start, i_end in intervals:
|
||||
px = (v >> 4)
|
||||
else:
|
||||
px = px | (v & 0xF0)
|
||||
pixels.append(px);
|
||||
pixels4g.append(px);
|
||||
px = 0
|
||||
# eol
|
||||
if x == bitmap.width - 1 and bitmap.width % 2 > 0:
|
||||
pixels.append(px)
|
||||
pixels4g.append(px)
|
||||
px = 0
|
||||
|
||||
packed = bytes(pixels);
|
||||
total_packed += len(packed)
|
||||
compressed = packed
|
||||
if compress:
|
||||
compressed = zlib.compress(packed)
|
||||
if is2Bit:
|
||||
# 0 = white, 15 black, 8+ dark grey, 7- light grey
|
||||
# Downsample to 2-bit bitmap
|
||||
pixels2b = []
|
||||
px = 0
|
||||
pitch = (bitmap.width // 2) + (bitmap.width % 2)
|
||||
for y in range(bitmap.rows):
|
||||
for x in range(bitmap.width):
|
||||
px = px << 2
|
||||
bm = pixels4g[y * pitch + (x // 2)]
|
||||
bm = (bm >> ((x % 2) * 4)) & 0xF
|
||||
|
||||
if bm == 15:
|
||||
px += 3
|
||||
elif bm >= 8:
|
||||
px += 2
|
||||
elif bm > 0:
|
||||
px += 1
|
||||
|
||||
if (y * bitmap.width + x) % 4 == 3:
|
||||
pixels2b.append(px)
|
||||
px = 0
|
||||
if (bitmap.width * bitmap.rows) % 4 != 0:
|
||||
px = px << (4 - (bitmap.width * bitmap.rows) % 4) * 2
|
||||
pixels2b.append(px)
|
||||
|
||||
# for y in range(bitmap.rows):
|
||||
# line = ''
|
||||
# for x in range(bitmap.width):
|
||||
# pixelPosition = y * bitmap.width + x
|
||||
# byte = pixels2b[pixelPosition // 4]
|
||||
# bit_index = (3 - (pixelPosition % 4)) * 2
|
||||
# line += '#' if ((byte >> bit_index) & 3) > 0 else '.'
|
||||
# print(line)
|
||||
# print('')
|
||||
else:
|
||||
# Downsample to 1-bit bitmap - treat any non-zero as black
|
||||
pixelsbw = []
|
||||
px = 0
|
||||
pitch = (bitmap.width // 2) + (bitmap.width % 2)
|
||||
for y in range(bitmap.rows):
|
||||
for x in range(bitmap.width):
|
||||
px = px << 1
|
||||
bm = pixels4g[y * pitch + (x // 2)]
|
||||
px += 1 if ((x & 1) == 0 and bm & 0xF > 0) or ((x & 1) == 1 and bm & 0xF0 > 0) else 0
|
||||
|
||||
if (y * bitmap.width + x) % 8 == 7:
|
||||
pixelsbw.append(px)
|
||||
px = 0
|
||||
if (bitmap.width * bitmap.rows) % 8 != 0:
|
||||
px = px << (8 - (bitmap.width * bitmap.rows) % 8)
|
||||
pixelsbw.append(px)
|
||||
|
||||
# for y in range(bitmap.rows):
|
||||
# line = ''
|
||||
# for x in range(bitmap.width):
|
||||
# pixelPosition = y * bitmap.width + x
|
||||
# byte = pixelsbw[pixelPosition // 8]
|
||||
# bit_index = 7 - (pixelPosition % 8)
|
||||
# line += '#' if (byte >> bit_index) & 1 else '.'
|
||||
# print(line)
|
||||
# print('')
|
||||
|
||||
pixels = pixels2b if is2Bit else pixelsbw
|
||||
|
||||
# Build output data
|
||||
packed = bytes(pixels)
|
||||
glyph = GlyphProps(
|
||||
width = bitmap.width,
|
||||
height = bitmap.rows,
|
||||
advance_x = norm_floor(face.glyph.advance.x),
|
||||
left = face.glyph.bitmap_left,
|
||||
top = face.glyph.bitmap_top,
|
||||
compressed_size = len(compressed),
|
||||
data_length = len(packed),
|
||||
data_offset = total_size,
|
||||
code_point = code_point,
|
||||
)
|
||||
total_size += len(compressed)
|
||||
all_glyphs.append((glyph, compressed))
|
||||
total_size += len(packed)
|
||||
all_glyphs.append((glyph, packed))
|
||||
|
||||
# pipe seems to be a good heuristic for the "real" descender
|
||||
face = load_glyph(ord('|'))
|
||||
@ -201,11 +261,11 @@ face = load_glyph(ord('|'))
|
||||
glyph_data = []
|
||||
glyph_props = []
|
||||
for index, glyph in enumerate(all_glyphs):
|
||||
props, compressed = glyph
|
||||
glyph_data.extend([b for b in compressed])
|
||||
props, packed = glyph
|
||||
glyph_data.extend([b for b in packed])
|
||||
glyph_props.append(props)
|
||||
|
||||
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n * compressed: {compress}\n */")
|
||||
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("#pragma once")
|
||||
print("#include \"EpdFontData.h\"\n")
|
||||
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
|
||||
@ -230,8 +290,8 @@ print(f" {font_name}Bitmaps,")
|
||||
print(f" {font_name}Glyphs,")
|
||||
print(f" {font_name}Intervals,")
|
||||
print(f" {len(intervals)},")
|
||||
print(f" {1 if compress else 0},")
|
||||
print(f" {norm_ceil(face.size.height)},")
|
||||
print(f" {norm_ceil(face.size.ascender)},")
|
||||
print(f" {norm_floor(face.size.descender)},")
|
||||
print(f" {'true' if is2Bit else 'false'},")
|
||||
print("};")
|
||||
|
||||
@ -1,129 +0,0 @@
|
||||
#pragma once
|
||||
#include <EpdFontFamily.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <Utf8.h>
|
||||
#include <miniz.h>
|
||||
|
||||
inline int min(const int a, const int b) { return a < b ? a : b; }
|
||||
inline int max(const int a, const int b) { return a > b ? a : b; }
|
||||
|
||||
static tinfl_decompressor decomp;
|
||||
|
||||
template <typename Renderable>
|
||||
class EpdFontRenderer {
|
||||
Renderable* renderer;
|
||||
void renderChar(uint32_t cp, int* x, const int* y, uint16_t color, EpdFontStyle style = REGULAR);
|
||||
|
||||
public:
|
||||
const EpdFontFamily* fontFamily;
|
||||
explicit EpdFontRenderer(const EpdFontFamily* fontFamily, Renderable* renderer)
|
||||
: fontFamily(fontFamily), renderer(renderer) {}
|
||||
~EpdFontRenderer() = default;
|
||||
void renderString(const char* string, int* x, int* y, uint16_t color, EpdFontStyle style = REGULAR);
|
||||
};
|
||||
|
||||
inline int uncompress(uint8_t* dest, size_t uncompressedSize, const uint8_t* source, size_t sourceSize) {
|
||||
if (uncompressedSize == 0 || dest == nullptr || sourceSize == 0 || source == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
tinfl_init(&decomp);
|
||||
|
||||
// we know everything will fit into the buffer.
|
||||
const tinfl_status decomp_status =
|
||||
tinfl_decompress(&decomp, source, &sourceSize, dest, dest, &uncompressedSize,
|
||||
TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
|
||||
if (decomp_status != TINFL_STATUS_DONE) {
|
||||
return decomp_status;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Renderable>
|
||||
void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const uint16_t color,
|
||||
const EpdFontStyle style) {
|
||||
// cannot draw a NULL / empty string
|
||||
if (string == nullptr || *string == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
// no printable characters
|
||||
if (!fontFamily->hasPrintableChars(string, style)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t cp;
|
||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
|
||||
renderChar(cp, x, y, color, style);
|
||||
}
|
||||
|
||||
*y += fontFamily->getData(style)->advanceY;
|
||||
}
|
||||
|
||||
template <typename Renderable>
|
||||
void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, uint16_t color,
|
||||
const EpdFontStyle style) {
|
||||
const EpdGlyph* glyph = fontFamily->getGlyph(cp, style);
|
||||
if (!glyph) {
|
||||
// TODO: Replace with fallback glyph property?
|
||||
glyph = fontFamily->getGlyph('?', style);
|
||||
}
|
||||
|
||||
// no glyph?
|
||||
if (!glyph) {
|
||||
Serial.printf("No glyph for codepoint %d\n", cp);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t offset = glyph->dataOffset;
|
||||
const uint8_t width = glyph->width;
|
||||
const uint8_t height = glyph->height;
|
||||
const int left = glyph->left;
|
||||
|
||||
const int byteWidth = width / 2 + width % 2;
|
||||
const unsigned long bitmapSize = byteWidth * height;
|
||||
const uint8_t* bitmap = nullptr;
|
||||
|
||||
if (fontFamily->getData(style)->compressed) {
|
||||
auto* tmpBitmap = static_cast<uint8_t*>(malloc(bitmapSize));
|
||||
if (tmpBitmap == nullptr && bitmapSize) {
|
||||
Serial.println("Failed to allocate memory for decompression buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
uncompress(tmpBitmap, bitmapSize, &fontFamily->getData(style)->bitmap[offset], glyph->compressedSize);
|
||||
bitmap = tmpBitmap;
|
||||
} else {
|
||||
bitmap = &fontFamily->getData(style)->bitmap[offset];
|
||||
}
|
||||
|
||||
if (bitmap != nullptr) {
|
||||
for (int localY = 0; localY < height; localY++) {
|
||||
int yy = *y - glyph->top + localY;
|
||||
const int startPos = *x + left;
|
||||
bool byteComplete = startPos % 2;
|
||||
int localX = max(0, -startPos);
|
||||
const int maxX = startPos + width;
|
||||
|
||||
for (int xx = startPos; xx < maxX; xx++) {
|
||||
uint8_t bm = bitmap[localY * byteWidth + localX / 2];
|
||||
if ((localX & 1) == 0) {
|
||||
bm = bm & 0xF;
|
||||
} else {
|
||||
bm = bm >> 4;
|
||||
}
|
||||
|
||||
if (bm) {
|
||||
renderer->drawPixel(xx, yy, color);
|
||||
}
|
||||
byteComplete = !byteComplete;
|
||||
localX++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fontFamily->getData(style)->compressed) {
|
||||
free(const_cast<uint8_t*>(bitmap));
|
||||
}
|
||||
}
|
||||
|
||||
*x += glyph->advanceX;
|
||||
}
|
||||
@ -1,159 +0,0 @@
|
||||
#include "EpdRenderer.h"
|
||||
|
||||
#include "builtinFonts/babyblue.h"
|
||||
#include "builtinFonts/bookerly.h"
|
||||
#include "builtinFonts/bookerly_bold.h"
|
||||
#include "builtinFonts/bookerly_bold_italic.h"
|
||||
#include "builtinFonts/bookerly_italic.h"
|
||||
#include "builtinFonts/ubuntu_10.h"
|
||||
#include "builtinFonts/ubuntu_bold_10.h"
|
||||
|
||||
EpdRenderer::EpdRenderer(XteinkDisplay* display) {
|
||||
const auto bookerlyFontFamily = new EpdFontFamily(new EpdFont(&bookerly), new EpdFont(&bookerly_bold),
|
||||
new EpdFont(&bookerly_italic), new EpdFont(&bookerly_bold_italic));
|
||||
const auto ubuntuFontFamily = new EpdFontFamily(new EpdFont(&ubuntu_10), new EpdFont(&ubuntu_bold_10));
|
||||
|
||||
this->display = display;
|
||||
this->regularFontRenderer = new EpdFontRenderer<XteinkDisplay>(bookerlyFontFamily, display);
|
||||
this->smallFontRenderer = new EpdFontRenderer<XteinkDisplay>(new EpdFontFamily(new EpdFont(&babyblue)), display);
|
||||
this->uiFontRenderer = new EpdFontRenderer<XteinkDisplay>(ubuntuFontFamily, display);
|
||||
|
||||
this->marginTop = 11;
|
||||
this->marginBottom = 30;
|
||||
this->marginLeft = 10;
|
||||
this->marginRight = 10;
|
||||
this->lineCompression = 0.95f;
|
||||
}
|
||||
|
||||
int EpdRenderer::getTextWidth(const char* text, const EpdFontStyle style) const {
|
||||
int w = 0, h = 0;
|
||||
|
||||
regularFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
int EpdRenderer::getUiTextWidth(const char* text, const EpdFontStyle style) const {
|
||||
int w = 0, h = 0;
|
||||
|
||||
uiFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
int EpdRenderer::getSmallTextWidth(const char* text, const EpdFontStyle style) const {
|
||||
int w = 0, h = 0;
|
||||
|
||||
smallFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
void EpdRenderer::drawText(const int x, const int y, const char* text, const uint16_t color,
|
||||
const EpdFontStyle style) const {
|
||||
int ypos = y + getLineHeight() + marginTop;
|
||||
int xpos = x + marginLeft;
|
||||
regularFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawUiText(const int x, const int y, const char* text, const uint16_t color,
|
||||
const EpdFontStyle style) const {
|
||||
int ypos = y + uiFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
|
||||
int xpos = x + marginLeft;
|
||||
uiFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const uint16_t color,
|
||||
const EpdFontStyle style) const {
|
||||
int ypos = y + smallFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
|
||||
int xpos = x + marginLeft;
|
||||
smallFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text, const int width, const int height,
|
||||
const EpdFontStyle style) const {
|
||||
const size_t length = text.length();
|
||||
// fit the text into the box
|
||||
int start = 0;
|
||||
int end = 1;
|
||||
int ypos = 0;
|
||||
while (true) {
|
||||
if (end >= length) {
|
||||
drawText(x, y + ypos, text.substr(start, length - start).c_str(), 1, style);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ypos + getLineHeight() >= height) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (text[end - 1] == '\n') {
|
||||
drawText(x, y + ypos, text.substr(start, end - start).c_str(), 1, style);
|
||||
ypos += getLineHeight();
|
||||
start = end;
|
||||
end = start + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (getTextWidth(text.substr(start, end - start).c_str(), style) > width) {
|
||||
drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), 1, style);
|
||||
ypos += getLineHeight();
|
||||
start = end - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
end++;
|
||||
}
|
||||
}
|
||||
|
||||
void EpdRenderer::drawLine(int x1, int y1, int x2, int y2, uint16_t color) const {
|
||||
display->drawLine(x1 + marginLeft, y1 + marginTop, x2 + marginLeft, y2 + marginTop,
|
||||
color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawRect(const int x, const int y, const int width, const int height, const uint16_t color) const {
|
||||
display->drawRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
}
|
||||
|
||||
void EpdRenderer::fillRect(const int x, const int y, const int width, const int height, const uint16_t color) const {
|
||||
display->fillRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawCircle(const int x, const int y, const int radius, const uint16_t color) const {
|
||||
display->drawCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
}
|
||||
|
||||
void EpdRenderer::fillCircle(const int x, const int y, const int radius, const uint16_t color) const {
|
||||
display->fillCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height,
|
||||
const bool invert, const bool mirrorY) const {
|
||||
drawImageNoMargin(bitmap, x + marginLeft, y + marginTop, width, height, invert, mirrorY);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawImageNoMargin(const uint8_t bitmap[], const int x, const int y, const int width, const int height,
|
||||
const bool invert, const bool mirrorY) const {
|
||||
display->drawImage(bitmap, x, y, width, height, invert, mirrorY);
|
||||
}
|
||||
|
||||
void EpdRenderer::clearScreen(const bool black) const {
|
||||
Serial.println("Clearing screen");
|
||||
display->fillScreen(black ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
}
|
||||
|
||||
void EpdRenderer::flushDisplay(const bool partialUpdate) const { display->display(partialUpdate); }
|
||||
|
||||
void EpdRenderer::flushArea(const int x, const int y, const int width, const int height) const {
|
||||
display->displayWindow(x + marginLeft, y + marginTop, width, height);
|
||||
}
|
||||
|
||||
int EpdRenderer::getPageWidth() const { return display->width() - marginLeft - marginRight; }
|
||||
|
||||
int EpdRenderer::getPageHeight() const { return display->height() - marginTop - marginBottom; }
|
||||
|
||||
int EpdRenderer::getSpaceWidth() const { return regularFontRenderer->fontFamily->getGlyph(' ', REGULAR)->advanceX; }
|
||||
|
||||
int EpdRenderer::getLineHeight() const {
|
||||
return regularFontRenderer->fontFamily->getData(REGULAR)->advanceY * lineCompression;
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <GxEPD2_BW.h>
|
||||
|
||||
#include <EpdFontRenderer.hpp>
|
||||
|
||||
#define XteinkDisplay GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT>
|
||||
|
||||
class EpdRenderer {
|
||||
XteinkDisplay* display;
|
||||
EpdFontRenderer<XteinkDisplay>* regularFontRenderer;
|
||||
EpdFontRenderer<XteinkDisplay>* smallFontRenderer;
|
||||
EpdFontRenderer<XteinkDisplay>* uiFontRenderer;
|
||||
int marginTop;
|
||||
int marginBottom;
|
||||
int marginLeft;
|
||||
int marginRight;
|
||||
float lineCompression;
|
||||
|
||||
public:
|
||||
explicit EpdRenderer(XteinkDisplay* display);
|
||||
~EpdRenderer() = default;
|
||||
int getTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
|
||||
int getUiTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
|
||||
int getSmallTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
|
||||
void drawText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
|
||||
void drawUiText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
|
||||
void drawSmallText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
|
||||
void drawTextBox(int x, int y, const std::string& text, int width, int height, EpdFontStyle style = REGULAR) const;
|
||||
void drawLine(int x1, int y1, int x2, int y2, uint16_t color = 1) const;
|
||||
void drawRect(int x, int y, int width, int height, uint16_t color = 1) const;
|
||||
void fillRect(int x, int y, int width, int height, uint16_t color = 1) const;
|
||||
void drawCircle(int x, int y, int radius, uint16_t color = 1) const;
|
||||
void fillCircle(int x, int y, int radius, uint16_t color = 1) const;
|
||||
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false,
|
||||
bool mirrorY = false) const;
|
||||
void drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false,
|
||||
bool mirrorY = false) const;
|
||||
void clearScreen(bool black = false) const;
|
||||
void flushDisplay(bool partialUpdate = true) const;
|
||||
void flushArea(int x, int y, int width, int height) const;
|
||||
|
||||
int getPageWidth() const;
|
||||
int getPageHeight() const;
|
||||
int getSpaceWidth() const;
|
||||
int getLineHeight() const;
|
||||
// set margins
|
||||
void setMarginTop(const int newMarginTop) { this->marginTop = newMarginTop; }
|
||||
void setMarginBottom(const int newMarginBottom) { this->marginBottom = newMarginBottom; }
|
||||
void setMarginLeft(const int newMarginLeft) { this->marginLeft = newMarginLeft; }
|
||||
void setMarginRight(const int newMarginRight) { this->marginRight = newMarginRight; }
|
||||
};
|
||||
@ -6,249 +6,179 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
bool Epub::findContentOpfFile(const ZipFile& zip, std::string& contentOpfFile) {
|
||||
// open up the meta data to find where the content.opf file lives
|
||||
size_t s;
|
||||
const auto metaInfo = zip.readTextFileToMemory("META-INF/container.xml", &s);
|
||||
if (!metaInfo) {
|
||||
Serial.println("Could not find META-INF/container.xml");
|
||||
#include "Epub/FsHelpers.h"
|
||||
#include "Epub/parsers/ContainerParser.h"
|
||||
#include "Epub/parsers/ContentOpfParser.h"
|
||||
#include "Epub/parsers/TocNcxParser.h"
|
||||
|
||||
bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
|
||||
const auto containerPath = "META-INF/container.xml";
|
||||
size_t containerSize;
|
||||
|
||||
// Get file size without loading it all into heap
|
||||
if (!getItemSize(containerPath, &containerSize)) {
|
||||
Serial.printf("[%lu] [EBP] Could not find or size META-INF/container.xml\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse the meta data
|
||||
tinyxml2::XMLDocument metaDataDoc;
|
||||
const auto result = metaDataDoc.Parse(metaInfo);
|
||||
free(metaInfo);
|
||||
ContainerParser containerParser(containerSize);
|
||||
|
||||
if (result != tinyxml2::XML_SUCCESS) {
|
||||
Serial.printf("Could not parse META-INF/container.xml. Error: %d\n", result);
|
||||
if (!containerParser.setup()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto container = metaDataDoc.FirstChildElement("container");
|
||||
if (!container) {
|
||||
Serial.println("Could not find container element in META-INF/container.xml");
|
||||
// Stream read (reusing your existing stream logic)
|
||||
if (!readItemContentsToStream(containerPath, containerParser, 512)) {
|
||||
Serial.printf("[%lu] [EBP] Could not read META-INF/container.xml\n", millis());
|
||||
containerParser.teardown();
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto rootfiles = container->FirstChildElement("rootfiles");
|
||||
if (!rootfiles) {
|
||||
Serial.println("Could not find rootfiles element in META-INF/container.xml");
|
||||
// Extract the result
|
||||
if (containerParser.fullPath.empty()) {
|
||||
Serial.printf("[%lu] [EBP] Could not find valid rootfile in container.xml\n", millis());
|
||||
containerParser.teardown();
|
||||
return false;
|
||||
}
|
||||
|
||||
// find the root file that has the media-type="application/oebps-package+xml"
|
||||
auto rootfile = rootfiles->FirstChildElement("rootfile");
|
||||
while (rootfile) {
|
||||
const char* mediaType = rootfile->Attribute("media-type");
|
||||
if (mediaType && strcmp(mediaType, "application/oebps-package+xml") == 0) {
|
||||
const char* full_path = rootfile->Attribute("full-path");
|
||||
if (full_path) {
|
||||
contentOpfFile = full_path;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
rootfile = rootfile->NextSiblingElement("rootfile");
|
||||
}
|
||||
*contentOpfFile = std::move(containerParser.fullPath);
|
||||
|
||||
Serial.println("Could not get path to content.opf file");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
|
||||
// read in the content.opf file and parse it
|
||||
auto contents = zip.readTextFileToMemory(content_opf_file.c_str());
|
||||
|
||||
// parse the contents
|
||||
tinyxml2::XMLDocument doc;
|
||||
auto result = doc.Parse(contents);
|
||||
free(contents);
|
||||
|
||||
if (result != tinyxml2::XML_SUCCESS) {
|
||||
Serial.printf("Error parsing content.opf - %s\n", tinyxml2::XMLDocument::ErrorIDToName(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto package = doc.FirstChildElement("package");
|
||||
if (!package) package = doc.FirstChildElement("opf:package");
|
||||
|
||||
if (!package) {
|
||||
Serial.println("Could not find package element in content.opf");
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the metadata - title and cover image
|
||||
auto metadata = package->FirstChildElement("metadata");
|
||||
if (!metadata) metadata = package->FirstChildElement("opf:metadata");
|
||||
if (!metadata) {
|
||||
Serial.println("Missing metadata");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto titleEl = metadata->FirstChildElement("dc:title");
|
||||
if (!titleEl) {
|
||||
Serial.println("Missing title");
|
||||
return false;
|
||||
}
|
||||
this->title = titleEl->GetText();
|
||||
|
||||
auto cover = metadata->FirstChildElement("meta");
|
||||
if (!cover) cover = metadata->FirstChildElement("opf:meta");
|
||||
while (cover && cover->Attribute("name") && strcmp(cover->Attribute("name"), "cover") != 0) {
|
||||
cover = cover->NextSiblingElement("meta");
|
||||
}
|
||||
if (!cover) {
|
||||
Serial.println("Missing cover");
|
||||
}
|
||||
auto coverItem = cover ? cover->Attribute("content") : nullptr;
|
||||
|
||||
// read the manifest and spine
|
||||
// the manifest gives us the names of the files
|
||||
// the spine gives us the order of the files
|
||||
// we can then read the files in the order they are in the spine
|
||||
auto manifest = package->FirstChildElement("manifest");
|
||||
if (!manifest) manifest = package->FirstChildElement("opf:manifest");
|
||||
if (!manifest) {
|
||||
Serial.println("Missing manifest");
|
||||
return false;
|
||||
}
|
||||
|
||||
// create a mapping from id to file name
|
||||
auto item = manifest->FirstChildElement("item");
|
||||
if (!item) item = manifest->FirstChildElement("opf:item");
|
||||
std::map<std::string, std::string> items;
|
||||
|
||||
while (item) {
|
||||
std::string itemId = item->Attribute("id");
|
||||
std::string href = contentBasePath + item->Attribute("href");
|
||||
|
||||
// grab the cover image
|
||||
if (coverItem && itemId == coverItem) {
|
||||
coverImageItem = href;
|
||||
}
|
||||
|
||||
// grab the ncx file
|
||||
if (itemId == "ncx" || itemId == "ncxtoc") {
|
||||
tocNcxItem = href;
|
||||
}
|
||||
|
||||
items[itemId] = href;
|
||||
auto nextItem = item->NextSiblingElement("item");
|
||||
if (!nextItem) nextItem = item->NextSiblingElement("opf:item");
|
||||
item = nextItem;
|
||||
}
|
||||
|
||||
// find the spine
|
||||
auto spineEl = package->FirstChildElement("spine");
|
||||
if (!spineEl) spineEl = package->FirstChildElement("opf:spine");
|
||||
if (!spineEl) {
|
||||
Serial.println("Missing spine");
|
||||
return false;
|
||||
}
|
||||
|
||||
// read the spine
|
||||
auto itemref = spineEl->FirstChildElement("itemref");
|
||||
if (!itemref) itemref = spineEl->FirstChildElement("opf:itemref");
|
||||
while (itemref) {
|
||||
auto id = itemref->Attribute("idref");
|
||||
if (items.find(id) != items.end()) {
|
||||
spine.emplace_back(id, items[id]);
|
||||
}
|
||||
auto nextItemRef = itemref->NextSiblingElement("itemref");
|
||||
if (!nextItemRef) nextItemRef = itemref->NextSiblingElement("opf:itemref");
|
||||
itemref = nextItemRef;
|
||||
}
|
||||
containerParser.teardown();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Epub::parseTocNcxFile(const ZipFile& zip) {
|
||||
bool Epub::parseContentOpf(const std::string& contentOpfFilePath) {
|
||||
size_t contentOpfSize;
|
||||
if (!getItemSize(contentOpfFilePath, &contentOpfSize)) {
|
||||
Serial.printf("[%lu] [EBP] Could not get size of content.opf\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
ContentOpfParser opfParser(getBasePath(), contentOpfSize);
|
||||
|
||||
if (!opfParser.setup()) {
|
||||
Serial.printf("[%lu] [EBP] Could not setup content.opf parser\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!readItemContentsToStream(contentOpfFilePath, opfParser, 1024)) {
|
||||
Serial.printf("[%lu] [EBP] Could not read content.opf\n", millis());
|
||||
opfParser.teardown();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Grab data from opfParser into epub
|
||||
title = opfParser.title;
|
||||
if (!opfParser.coverItemId.empty() && opfParser.items.count(opfParser.coverItemId) > 0) {
|
||||
coverImageItem = opfParser.items.at(opfParser.coverItemId);
|
||||
}
|
||||
|
||||
if (!opfParser.tocNcxPath.empty()) {
|
||||
tocNcxItem = opfParser.tocNcxPath;
|
||||
}
|
||||
|
||||
for (auto& spineRef : opfParser.spineRefs) {
|
||||
if (opfParser.items.count(spineRef)) {
|
||||
spine.emplace_back(spineRef, opfParser.items.at(spineRef));
|
||||
}
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [EBP] Successfully parsed content.opf\n", millis());
|
||||
|
||||
opfParser.teardown();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Epub::parseTocNcxFile() {
|
||||
// the ncx file should have been specified in the content.opf file
|
||||
if (tocNcxItem.empty()) {
|
||||
Serial.println("No ncx file specified");
|
||||
Serial.printf("[%lu] [EBP] No ncx file specified\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto ncxData = zip.readTextFileToMemory(tocNcxItem.c_str());
|
||||
if (!ncxData) {
|
||||
Serial.printf("Could not find %s\n", tocNcxItem.c_str());
|
||||
size_t tocSize;
|
||||
if (!getItemSize(tocNcxItem, &tocSize)) {
|
||||
Serial.printf("[%lu] [EBP] Could not get size of toc ncx\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse the Toc contents
|
||||
tinyxml2::XMLDocument doc;
|
||||
const auto result = doc.Parse(ncxData);
|
||||
free(ncxData);
|
||||
TocNcxParser ncxParser(contentBasePath, tocSize);
|
||||
|
||||
if (result != tinyxml2::XML_SUCCESS) {
|
||||
Serial.printf("Error parsing toc %s\n", tinyxml2::XMLDocument::ErrorIDToName(result));
|
||||
if (!ncxParser.setup()) {
|
||||
Serial.printf("[%lu] [EBP] Could not setup toc ncx parser\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto ncx = doc.FirstChildElement("ncx");
|
||||
if (!ncx) {
|
||||
Serial.println("Could not find first child ncx in toc");
|
||||
if (!readItemContentsToStream(tocNcxItem, ncxParser, 1024)) {
|
||||
Serial.printf("[%lu] [EBP] Could not read toc ncx stream\n", millis());
|
||||
ncxParser.teardown();
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto navMap = ncx->FirstChildElement("navMap");
|
||||
if (!navMap) {
|
||||
Serial.println("Could not find navMap child in ncx");
|
||||
return false;
|
||||
}
|
||||
this->toc = std::move(ncxParser.toc);
|
||||
|
||||
recursivelyParseNavMap(navMap->FirstChildElement("navPoint"));
|
||||
Serial.printf("[%lu] [EBP] Parsed %d TOC items\n", millis(), this->toc.size());
|
||||
|
||||
ncxParser.teardown();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Epub::recursivelyParseNavMap(tinyxml2::XMLElement* element) {
|
||||
// Fills toc map
|
||||
while (element) {
|
||||
std::string navTitle = element->FirstChildElement("navLabel")->FirstChildElement("text")->FirstChild()->Value();
|
||||
const auto content = element->FirstChildElement("content");
|
||||
std::string href = contentBasePath + content->Attribute("src");
|
||||
// split the href on the # to get the href and the anchor
|
||||
const size_t pos = href.find('#');
|
||||
std::string anchor;
|
||||
|
||||
if (pos != std::string::npos) {
|
||||
anchor = href.substr(pos + 1);
|
||||
href = href.substr(0, pos);
|
||||
}
|
||||
|
||||
toc.emplace_back(navTitle, href, anchor, 0);
|
||||
|
||||
tinyxml2::XMLElement* nestedNavPoint = element->FirstChildElement("navPoint");
|
||||
if (nestedNavPoint) {
|
||||
recursivelyParseNavMap(nestedNavPoint);
|
||||
}
|
||||
element = element->NextSiblingElement("navPoint");
|
||||
}
|
||||
}
|
||||
|
||||
// load in the meta data for the epub file
|
||||
bool Epub::load() {
|
||||
Serial.printf("[%lu] [EBP] Loading ePub: %s\n", millis(), filepath.c_str());
|
||||
ZipFile zip("/sd" + filepath);
|
||||
|
||||
std::string contentOpfFile;
|
||||
if (!findContentOpfFile(zip, contentOpfFile)) {
|
||||
Serial.println("Could not open ePub");
|
||||
std::string contentOpfFilePath;
|
||||
if (!findContentOpfFile(&contentOpfFilePath)) {
|
||||
Serial.printf("[%lu] [EBP] Could not find content.opf in zip\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
contentBasePath = contentOpfFile.substr(0, contentOpfFile.find_last_of('/') + 1);
|
||||
Serial.printf("[%lu] [EBP] Found content.opf at: %s\n", millis(), contentOpfFilePath.c_str());
|
||||
|
||||
if (!parseContentOpf(zip, contentOpfFile)) {
|
||||
contentBasePath = contentOpfFilePath.substr(0, contentOpfFilePath.find_last_of('/') + 1);
|
||||
|
||||
if (!parseContentOpf(contentOpfFilePath)) {
|
||||
Serial.printf("[%lu] [EBP] Could not parse content.opf\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parseTocNcxFile(zip)) {
|
||||
if (!parseTocNcxFile()) {
|
||||
Serial.printf("[%lu] [EBP] Could not parse toc\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
// determine size of spine items
|
||||
size_t spineItemsCount = getSpineItemsCount();
|
||||
size_t spineItemsSize = 0;
|
||||
for (size_t i = 0; i < spineItemsCount; i++) {
|
||||
std::string spineItem = getSpineItem(i);
|
||||
size_t s = 0;
|
||||
getItemSize(spineItem, &s);
|
||||
spineItemsSize += s;
|
||||
cumulativeSpineItemSize.emplace_back(spineItemsSize);
|
||||
}
|
||||
Serial.printf("[%lu] [EBP] Book size: %u\n", millis(), spineItemsSize);
|
||||
|
||||
Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Epub::clearCache() const { SD.rmdir(cachePath.c_str()); }
|
||||
bool Epub::clearCache() const {
|
||||
if (!SD.exists(cachePath.c_str())) {
|
||||
Serial.printf("[%lu] [EPB] Cache does not exist, no action needed\n", millis());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!FsHelpers::removeDir(cachePath.c_str())) {
|
||||
Serial.printf("[%lu] [EPB] Failed to clear cache\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [EPB] Cache cleared successfully\n", millis());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Epub::setupCacheDir() const {
|
||||
if (SD.exists(cachePath.c_str())) {
|
||||
@ -308,37 +238,40 @@ std::string normalisePath(const std::string& path) {
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t* Epub::getItemContents(const std::string& itemHref, size_t* size) const {
|
||||
uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, bool trailingNullByte) const {
|
||||
const ZipFile zip("/sd" + filepath);
|
||||
const std::string path = normalisePath(itemHref);
|
||||
|
||||
const auto content = zip.readFileToMemory(path.c_str(), size);
|
||||
const auto content = zip.readFileToMemory(path.c_str(), size, trailingNullByte);
|
||||
if (!content) {
|
||||
Serial.printf("Failed to read item %s\n", path.c_str());
|
||||
Serial.printf("[%lu] [EBP] Failed to read item %s\n", millis(), path.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
char* Epub::getTextItemContents(const std::string& itemHref, size_t* size) const {
|
||||
bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, const size_t chunkSize) const {
|
||||
const ZipFile zip("/sd" + filepath);
|
||||
const std::string path = normalisePath(itemHref);
|
||||
|
||||
const auto content = zip.readTextFileToMemory(path.c_str(), size);
|
||||
if (!content) {
|
||||
Serial.printf("Failed to read item %s\n", path.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
return zip.readFileToStream(path.c_str(), out, chunkSize);
|
||||
}
|
||||
|
||||
return content;
|
||||
bool Epub::getItemSize(const std::string& itemHref, size_t* size) const {
|
||||
const ZipFile zip("/sd" + filepath);
|
||||
const std::string path = normalisePath(itemHref);
|
||||
|
||||
return zip.getInflatedFileSize(path.c_str(), size);
|
||||
}
|
||||
|
||||
int Epub::getSpineItemsCount() const { return spine.size(); }
|
||||
|
||||
size_t Epub::getCumulativeSpineItemSize(const int spineIndex) const { return cumulativeSpineItemSize.at(spineIndex); }
|
||||
|
||||
std::string& Epub::getSpineItem(const int spineIndex) {
|
||||
if (spineIndex < 0 || spineIndex >= spine.size()) {
|
||||
Serial.printf("getSpineItem index:%d is out of range\n", spineIndex);
|
||||
Serial.printf("[%lu] [EBP] getSpineItem index:%d is out of range\n", millis(), spineIndex);
|
||||
return spine.at(0).second;
|
||||
}
|
||||
|
||||
@ -347,7 +280,7 @@ std::string& Epub::getSpineItem(const int spineIndex) {
|
||||
|
||||
EpubTocEntry& Epub::getTocItem(const int tocTndex) {
|
||||
if (tocTndex < 0 || tocTndex >= toc.size()) {
|
||||
Serial.printf("getTocItem index:%d is out of range\n", tocTndex);
|
||||
Serial.printf("[%lu] [EBP] getTocItem index:%d is out of range\n", millis(), tocTndex);
|
||||
return toc.at(0);
|
||||
}
|
||||
|
||||
@ -366,7 +299,7 @@ int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println("Section not found");
|
||||
Serial.printf("[%lu] [EBP] Section not found\n", millis());
|
||||
// not found - default to the start of the book
|
||||
return 0;
|
||||
}
|
||||
@ -380,7 +313,17 @@ int Epub::getTocIndexForSpineIndex(const int spineIndex) const {
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println("TOC item not found");
|
||||
// not found - default to first item
|
||||
return 0;
|
||||
Serial.printf("[%lu] [EBP] TOC item not found\n", millis());
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t Epub::getBookSize() const { return getCumulativeSpineItemSize(getSpineItemsCount() - 1); }
|
||||
|
||||
// Calculate progress in book
|
||||
uint8_t Epub::calculateProgress(const int currentSpineIndex, const float currentSpineRead) {
|
||||
size_t prevChapterSize = (currentSpineIndex >= 1) ? getCumulativeSpineItemSize(currentSpineIndex - 1) : 0;
|
||||
size_t curChapterSize = getCumulativeSpineItemSize(currentSpineIndex) - prevChapterSize;
|
||||
size_t bookSize = getBookSize();
|
||||
size_t sectionProgSize = currentSpineRead * curChapterSize;
|
||||
return round(static_cast<float>(prevChapterSize + sectionProgSize) / bookSize * 100.0);
|
||||
}
|
||||
|
||||
@ -1,22 +1,13 @@
|
||||
#pragma once
|
||||
#include <HardwareSerial.h>
|
||||
#include <tinyxml2.h>
|
||||
#include <Print.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class ZipFile;
|
||||
#include "Epub/EpubTocEntry.h"
|
||||
|
||||
class EpubTocEntry {
|
||||
public:
|
||||
std::string title;
|
||||
std::string href;
|
||||
std::string anchor;
|
||||
int level;
|
||||
EpubTocEntry(std::string title, std::string href, std::string anchor, const int level)
|
||||
: title(std::move(title)), href(std::move(href)), anchor(std::move(anchor)), level(level) {}
|
||||
};
|
||||
class ZipFile;
|
||||
|
||||
class Epub {
|
||||
// the title read from the EPUB meta data
|
||||
@ -29,6 +20,8 @@ class Epub {
|
||||
std::string filepath;
|
||||
// the spine of the EPUB file
|
||||
std::vector<std::pair<std::string, std::string>> spine;
|
||||
// the file size of the spine items (proxy to book progress)
|
||||
std::vector<size_t> cumulativeSpineItemSize;
|
||||
// the toc of the EPUB file
|
||||
std::vector<EpubTocEntry> toc;
|
||||
// the base path for items in the EPUB file
|
||||
@ -36,11 +29,9 @@ class Epub {
|
||||
// Uniq cache key based on filepath
|
||||
std::string cachePath;
|
||||
|
||||
// find the path for the content.opf file
|
||||
static bool findContentOpfFile(const ZipFile& zip, std::string& contentOpfFile);
|
||||
bool parseContentOpf(ZipFile& zip, std::string& content_opf_file);
|
||||
bool parseTocNcxFile(const ZipFile& zip);
|
||||
void recursivelyParseNavMap(tinyxml2::XMLElement* element);
|
||||
bool findContentOpfFile(std::string* contentOpfFile) const;
|
||||
bool parseContentOpf(const std::string& contentOpfFilePath);
|
||||
bool parseTocNcxFile();
|
||||
|
||||
public:
|
||||
explicit Epub(std::string filepath, const std::string& cacheDir) : filepath(std::move(filepath)) {
|
||||
@ -50,18 +41,24 @@ class Epub {
|
||||
~Epub() = default;
|
||||
std::string& getBasePath() { return contentBasePath; }
|
||||
bool load();
|
||||
void clearCache() const;
|
||||
bool clearCache() const;
|
||||
void setupCacheDir() const;
|
||||
const std::string& getCachePath() const;
|
||||
const std::string& getPath() const;
|
||||
const std::string& getTitle() const;
|
||||
const std::string& getCoverImageItem() const;
|
||||
uint8_t* getItemContents(const std::string& itemHref, size_t* size = nullptr) const;
|
||||
char* getTextItemContents(const std::string& itemHref, size_t* size = nullptr) const;
|
||||
uint8_t* readItemContentsToBytes(const std::string& itemHref, size_t* size = nullptr,
|
||||
bool trailingNullByte = false) const;
|
||||
bool readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize) const;
|
||||
bool getItemSize(const std::string& itemHref, size_t* size) const;
|
||||
std::string& getSpineItem(int spineIndex);
|
||||
int getSpineItemsCount() const;
|
||||
EpubTocEntry& getTocItem(int tocTndex);
|
||||
size_t getCumulativeSpineItemSize(const int spineIndex) const;
|
||||
EpubTocEntry& getTocItem(int tocIndex);
|
||||
int getTocItemsCount() const;
|
||||
int getSpineIndexForTocIndex(int tocIndex) const;
|
||||
int getTocIndexForSpineIndex(int spineIndex) const;
|
||||
|
||||
size_t getBookSize() const;
|
||||
uint8_t calculateProgress(const int currentSpineIndex, const float currentSpineRead);
|
||||
};
|
||||
|
||||
@ -1,181 +0,0 @@
|
||||
#include "EpubHtmlParser.h"
|
||||
|
||||
#include <EpdRenderer.h>
|
||||
#include <HardwareSerial.h>
|
||||
|
||||
#include "Page.h"
|
||||
#include "htmlEntities.h"
|
||||
|
||||
const char* HEADER_TAGS[] = {"h1", "h2", "h3", "h4", "h5", "h6"};
|
||||
constexpr int NUM_HEADER_TAGS = sizeof(HEADER_TAGS) / sizeof(HEADER_TAGS[0]);
|
||||
|
||||
const char* BLOCK_TAGS[] = {"p", "li", "div", "br"};
|
||||
constexpr int NUM_BLOCK_TAGS = sizeof(BLOCK_TAGS) / sizeof(BLOCK_TAGS[0]);
|
||||
|
||||
const char* BOLD_TAGS[] = {"b"};
|
||||
constexpr int NUM_BOLD_TAGS = sizeof(BOLD_TAGS) / sizeof(BOLD_TAGS[0]);
|
||||
|
||||
const char* ITALIC_TAGS[] = {"i"};
|
||||
constexpr int NUM_ITALIC_TAGS = sizeof(ITALIC_TAGS) / sizeof(ITALIC_TAGS[0]);
|
||||
|
||||
const char* IMAGE_TAGS[] = {"img"};
|
||||
constexpr int NUM_IMAGE_TAGS = sizeof(IMAGE_TAGS) / sizeof(IMAGE_TAGS[0]);
|
||||
|
||||
const char* SKIP_TAGS[] = {"head", "table"};
|
||||
constexpr int NUM_SKIP_TAGS = sizeof(SKIP_TAGS) / sizeof(SKIP_TAGS[0]);
|
||||
|
||||
// given the start and end of a tag, check to see if it matches a known tag
|
||||
bool matches(const char* tag_name, const char* possible_tags[], const int possible_tag_count) {
|
||||
for (int i = 0; i < possible_tag_count; i++) {
|
||||
if (strcmp(tag_name, possible_tags[i]) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// start a new text block if needed
|
||||
void EpubHtmlParser::startNewTextBlock(const BLOCK_STYLE style) {
|
||||
if (currentTextBlock) {
|
||||
// already have a text block running and it is empty - just reuse it
|
||||
if (currentTextBlock->isEmpty()) {
|
||||
currentTextBlock->set_style(style);
|
||||
return;
|
||||
}
|
||||
|
||||
currentTextBlock->finish();
|
||||
makePages();
|
||||
delete currentTextBlock;
|
||||
}
|
||||
currentTextBlock = new TextBlock(style);
|
||||
}
|
||||
|
||||
bool EpubHtmlParser::VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute) {
|
||||
const char* tag_name = element.Name();
|
||||
if (matches(tag_name, IMAGE_TAGS, NUM_IMAGE_TAGS)) {
|
||||
const char* src = element.Attribute("src");
|
||||
if (src) {
|
||||
// don't leave an empty text block in the list
|
||||
// const BLOCK_STYLE style = currentTextBlock->get_style();
|
||||
if (currentTextBlock->isEmpty()) {
|
||||
delete currentTextBlock;
|
||||
currentTextBlock = nullptr;
|
||||
}
|
||||
// TODO: Fix this
|
||||
// blocks.push_back(new ImageBlock(m_base_path + src));
|
||||
// start a new text block - with the same style as before
|
||||
// startNewTextBlock(style);
|
||||
} else {
|
||||
// ESP_LOGE(TAG, "Could not find src attribute");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (matches(tag_name, SKIP_TAGS, NUM_SKIP_TAGS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Serial.printf("Text: %s\n", element.GetText());
|
||||
|
||||
if (matches(tag_name, HEADER_TAGS, NUM_HEADER_TAGS)) {
|
||||
insideBoldTag = true;
|
||||
startNewTextBlock(CENTER_ALIGN);
|
||||
} else if (matches(tag_name, BLOCK_TAGS, NUM_BLOCK_TAGS)) {
|
||||
if (strcmp(tag_name, "br") == 0) {
|
||||
startNewTextBlock(currentTextBlock->get_style());
|
||||
} else {
|
||||
startNewTextBlock(JUSTIFIED);
|
||||
}
|
||||
} else if (matches(tag_name, BOLD_TAGS, NUM_BOLD_TAGS)) {
|
||||
insideBoldTag = true;
|
||||
} else if (matches(tag_name, ITALIC_TAGS, NUM_ITALIC_TAGS)) {
|
||||
insideItalicTag = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// Visit a text node.
|
||||
bool EpubHtmlParser::Visit(const tinyxml2::XMLText& text) {
|
||||
const char* content = text.Value();
|
||||
currentTextBlock->addSpan(replaceHtmlEntities(content), insideBoldTag, insideItalicTag);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EpubHtmlParser::VisitExit(const tinyxml2::XMLElement& element) {
|
||||
const char* tag_name = element.Name();
|
||||
if (matches(tag_name, HEADER_TAGS, NUM_HEADER_TAGS)) {
|
||||
insideBoldTag = false;
|
||||
} else if (matches(tag_name, BLOCK_TAGS, NUM_BLOCK_TAGS)) {
|
||||
// nothing to do
|
||||
} else if (matches(tag_name, BOLD_TAGS, NUM_BOLD_TAGS)) {
|
||||
insideBoldTag = false;
|
||||
} else if (matches(tag_name, ITALIC_TAGS, NUM_ITALIC_TAGS)) {
|
||||
insideItalicTag = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EpubHtmlParser::parseAndBuildPages() {
|
||||
startNewTextBlock(JUSTIFIED);
|
||||
tinyxml2::XMLDocument doc(false, tinyxml2::COLLAPSE_WHITESPACE);
|
||||
|
||||
const tinyxml2::XMLError result = doc.LoadFile(filepath);
|
||||
if (result != tinyxml2::XML_SUCCESS) {
|
||||
Serial.printf("Failed to load file, Error: %s\n", tinyxml2::XMLDocument::ErrorIDToName(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
doc.Accept(this);
|
||||
if (currentTextBlock) {
|
||||
makePages();
|
||||
completePageFn(currentPage);
|
||||
currentPage = nullptr;
|
||||
delete currentTextBlock;
|
||||
currentTextBlock = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EpubHtmlParser::makePages() {
|
||||
if (!currentTextBlock) {
|
||||
Serial.println("!! No text block to make pages for !!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentPage) {
|
||||
currentPage = new Page();
|
||||
}
|
||||
|
||||
const int lineHeight = renderer.getLineHeight();
|
||||
const int pageHeight = renderer.getPageHeight();
|
||||
|
||||
// Long running task, make sure to let other things happen
|
||||
vTaskDelay(1);
|
||||
|
||||
if (currentTextBlock->getType() == TEXT_BLOCK) {
|
||||
const auto lines = currentTextBlock->splitIntoLines(renderer);
|
||||
|
||||
for (const auto line : lines) {
|
||||
if (currentPage->nextY + lineHeight > pageHeight) {
|
||||
completePageFn(currentPage);
|
||||
currentPage = new Page();
|
||||
}
|
||||
|
||||
currentPage->elements.push_back(new PageLine(line, currentPage->nextY));
|
||||
currentPage->nextY += lineHeight;
|
||||
}
|
||||
// TODO: Fix spacing between paras
|
||||
// add some extra line between blocks
|
||||
currentPage->nextY += lineHeight / 2;
|
||||
}
|
||||
// TODO: Image block support
|
||||
// if (block->getType() == BlockType::IMAGE_BLOCK) {
|
||||
// ImageBlock *imageBlock = (ImageBlock *)block;
|
||||
// if (y + imageBlock->height > page_height) {
|
||||
// pages.push_back(new Page());
|
||||
// y = 0;
|
||||
// }
|
||||
// pages.back()->elements.push_back(new PageImage(imageBlock, y));
|
||||
// y += imageBlock->height;
|
||||
// }
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "blocks/TextBlock.h"
|
||||
|
||||
class Page;
|
||||
class EpdRenderer;
|
||||
|
||||
class EpubHtmlParser final : public tinyxml2::XMLVisitor {
|
||||
const char* filepath;
|
||||
EpdRenderer& renderer;
|
||||
std::function<void(Page*)> completePageFn;
|
||||
|
||||
bool insideBoldTag = false;
|
||||
bool insideItalicTag = false;
|
||||
TextBlock* currentTextBlock = nullptr;
|
||||
Page* currentPage = nullptr;
|
||||
|
||||
void startNewTextBlock(BLOCK_STYLE style);
|
||||
void makePages();
|
||||
|
||||
// xml parser callbacks
|
||||
bool VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute) override;
|
||||
bool Visit(const tinyxml2::XMLText& text) override;
|
||||
bool VisitExit(const tinyxml2::XMLElement& element) override;
|
||||
// xml parser callbacks
|
||||
public:
|
||||
explicit EpubHtmlParser(const char* filepath, EpdRenderer& renderer, const std::function<void(Page*)>& completePageFn)
|
||||
: filepath(filepath), renderer(renderer), completePageFn(completePageFn) {}
|
||||
~EpubHtmlParser() override = default;
|
||||
bool parseAndBuildPages();
|
||||
};
|
||||
13
lib/Epub/Epub/EpubTocEntry.h
Normal file
13
lib/Epub/Epub/EpubTocEntry.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
class EpubTocEntry {
|
||||
public:
|
||||
std::string title;
|
||||
std::string href;
|
||||
std::string anchor;
|
||||
int level;
|
||||
EpubTocEntry(std::string title, std::string href, std::string anchor, const int level)
|
||||
: title(std::move(title)), href(std::move(href)), anchor(std::move(anchor)), level(level) {}
|
||||
};
|
||||
36
lib/Epub/Epub/FsHelpers.cpp
Normal file
36
lib/Epub/Epub/FsHelpers.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include "FsHelpers.h"
|
||||
|
||||
#include <SD.h>
|
||||
|
||||
bool FsHelpers::removeDir(const char* path) {
|
||||
// 1. Open the directory
|
||||
File dir = SD.open(path);
|
||||
if (!dir) {
|
||||
return false;
|
||||
}
|
||||
if (!dir.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
File file = dir.openNextFile();
|
||||
while (file) {
|
||||
String filePath = path;
|
||||
if (!filePath.endsWith("/")) {
|
||||
filePath += "/";
|
||||
}
|
||||
filePath += file.name();
|
||||
|
||||
if (file.isDirectory()) {
|
||||
if (!removeDir(filePath.c_str())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!SD.remove(filePath.c_str())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
file = dir.openNextFile();
|
||||
}
|
||||
|
||||
return SD.rmdir(path);
|
||||
}
|
||||
6
lib/Epub/Epub/FsHelpers.h
Normal file
6
lib/Epub/Epub/FsHelpers.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
class FsHelpers {
|
||||
public:
|
||||
static bool removeDir(const char* path);
|
||||
};
|
||||
@ -3,48 +3,58 @@
|
||||
#include <HardwareSerial.h>
|
||||
#include <Serialization.h>
|
||||
|
||||
void PageLine::render(EpdRenderer& renderer) { block->render(renderer, 0, yPos); }
|
||||
namespace {
|
||||
constexpr uint8_t PAGE_FILE_VERSION = 3;
|
||||
}
|
||||
|
||||
void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); }
|
||||
|
||||
void PageLine::serialize(std::ostream& os) {
|
||||
serialization::writePod(os, xPos);
|
||||
serialization::writePod(os, yPos);
|
||||
|
||||
// serialize TextBlock pointed to by PageLine
|
||||
block->serialize(os);
|
||||
}
|
||||
|
||||
PageLine* PageLine::deserialize(std::istream& is) {
|
||||
int32_t yPos;
|
||||
std::unique_ptr<PageLine> PageLine::deserialize(std::istream& is) {
|
||||
int16_t xPos;
|
||||
int16_t yPos;
|
||||
serialization::readPod(is, xPos);
|
||||
serialization::readPod(is, yPos);
|
||||
|
||||
const auto tb = TextBlock::deserialize(is);
|
||||
return new PageLine(tb, yPos);
|
||||
auto tb = TextBlock::deserialize(is);
|
||||
return std::unique_ptr<PageLine>(new PageLine(std::move(tb), xPos, yPos));
|
||||
}
|
||||
|
||||
void Page::render(EpdRenderer& renderer) const {
|
||||
const auto start = millis();
|
||||
for (const auto element : elements) {
|
||||
element->render(renderer);
|
||||
void Page::render(GfxRenderer& renderer, const int fontId) const {
|
||||
for (auto& element : elements) {
|
||||
element->render(renderer, fontId);
|
||||
}
|
||||
Serial.printf("Rendered page elements (%u) in %dms\n", elements.size(), millis() - start);
|
||||
}
|
||||
|
||||
void Page::serialize(std::ostream& os) const {
|
||||
serialization::writePod(os, nextY);
|
||||
serialization::writePod(os, PAGE_FILE_VERSION);
|
||||
|
||||
const uint32_t count = elements.size();
|
||||
serialization::writePod(os, count);
|
||||
|
||||
for (auto* el : elements) {
|
||||
for (const auto& el : elements) {
|
||||
// Only PageLine exists currently
|
||||
serialization::writePod(os, static_cast<uint8_t>(TAG_PageLine));
|
||||
static_cast<PageLine*>(el)->serialize(os);
|
||||
el->serialize(os);
|
||||
}
|
||||
}
|
||||
|
||||
Page* Page::deserialize(std::istream& is) {
|
||||
auto* page = new Page();
|
||||
std::unique_ptr<Page> Page::deserialize(std::istream& is) {
|
||||
uint8_t version;
|
||||
serialization::readPod(is, version);
|
||||
if (version != PAGE_FILE_VERSION) {
|
||||
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
serialization::readPod(is, page->nextY);
|
||||
auto page = std::unique_ptr<Page>(new Page());
|
||||
|
||||
uint32_t count;
|
||||
serialization::readPod(is, count);
|
||||
@ -54,10 +64,11 @@ Page* Page::deserialize(std::istream& is) {
|
||||
serialization::readPod(is, tag);
|
||||
|
||||
if (tag == TAG_PageLine) {
|
||||
auto* pl = PageLine::deserialize(is);
|
||||
page->elements.push_back(pl);
|
||||
auto pl = PageLine::deserialize(is);
|
||||
page->elements.push_back(std::move(pl));
|
||||
} else {
|
||||
throw std::runtime_error("Unknown PageElement tag");
|
||||
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown tag %u\n", millis(), tag);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
#pragma once
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "blocks/TextBlock.h"
|
||||
|
||||
enum PageElementTag : uint8_t {
|
||||
@ -8,36 +11,31 @@ enum PageElementTag : uint8_t {
|
||||
// represents something that has been added to a page
|
||||
class PageElement {
|
||||
public:
|
||||
int yPos;
|
||||
explicit PageElement(const int yPos) : yPos(yPos) {}
|
||||
int16_t xPos;
|
||||
int16_t yPos;
|
||||
explicit PageElement(const int16_t xPos, const int16_t yPos) : xPos(xPos), yPos(yPos) {}
|
||||
virtual ~PageElement() = default;
|
||||
virtual void render(EpdRenderer& renderer) = 0;
|
||||
virtual void render(GfxRenderer& renderer, int fontId) = 0;
|
||||
virtual void serialize(std::ostream& os) = 0;
|
||||
};
|
||||
|
||||
// a line from a block element
|
||||
class PageLine final : public PageElement {
|
||||
const TextBlock* block;
|
||||
std::shared_ptr<TextBlock> block;
|
||||
|
||||
public:
|
||||
PageLine(const TextBlock* block, const int yPos) : PageElement(yPos), block(block) {}
|
||||
~PageLine() override { delete block; }
|
||||
void render(EpdRenderer& renderer) override;
|
||||
PageLine(std::shared_ptr<TextBlock> block, const int16_t xPos, const int16_t yPos)
|
||||
: PageElement(xPos, yPos), block(std::move(block)) {}
|
||||
void render(GfxRenderer& renderer, int fontId) override;
|
||||
void serialize(std::ostream& os) override;
|
||||
static PageLine* deserialize(std::istream& is);
|
||||
static std::unique_ptr<PageLine> deserialize(std::istream& is);
|
||||
};
|
||||
|
||||
class Page {
|
||||
public:
|
||||
int nextY = 0;
|
||||
// the list of block index and line numbers on this page
|
||||
std::vector<PageElement*> elements;
|
||||
void render(EpdRenderer& renderer) const;
|
||||
~Page() {
|
||||
for (const auto element : elements) {
|
||||
delete element;
|
||||
}
|
||||
}
|
||||
std::vector<std::shared_ptr<PageElement>> elements;
|
||||
void render(GfxRenderer& renderer, int fontId) const;
|
||||
void serialize(std::ostream& os) const;
|
||||
static Page* deserialize(std::istream& is);
|
||||
static std::unique_ptr<Page> deserialize(std::istream& is);
|
||||
};
|
||||
|
||||
171
lib/Epub/Epub/ParsedText.cpp
Normal file
171
lib/Epub/Epub/ParsedText.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
#include "ParsedText.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
constexpr int MAX_COST = std::numeric_limits<int>::max();
|
||||
|
||||
void ParsedText::addWord(std::string word, const EpdFontStyle fontStyle) {
|
||||
if (word.empty()) return;
|
||||
|
||||
words.push_back(std::move(word));
|
||||
wordStyles.push_back(fontStyle);
|
||||
}
|
||||
|
||||
// Consumes data to minimize memory usage
|
||||
void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fontId, const int horizontalMargin,
|
||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine) {
|
||||
if (words.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t totalWordCount = words.size();
|
||||
const int pageWidth = renderer.getScreenWidth() - horizontalMargin;
|
||||
const int spaceWidth = renderer.getSpaceWidth(fontId);
|
||||
// width of 1em to indent first line of paragraph if Extra Spacing is enabled
|
||||
const int indentWidth = (!extraParagraphSpacing) ? 1 * renderer.getTextWidth(fontId, "m", REGULAR) : 0;
|
||||
|
||||
std::vector<uint16_t> wordWidths;
|
||||
wordWidths.reserve(totalWordCount);
|
||||
|
||||
auto wordsIt = words.begin();
|
||||
auto wordStylesIt = wordStyles.begin();
|
||||
|
||||
while (wordsIt != words.end()) {
|
||||
wordWidths.push_back(renderer.getTextWidth(fontId, wordsIt->c_str(), *wordStylesIt));
|
||||
|
||||
std::advance(wordsIt, 1);
|
||||
std::advance(wordStylesIt, 1);
|
||||
}
|
||||
|
||||
// DP table to store the minimum badness (cost) of lines starting at index i
|
||||
std::vector<int> dp(totalWordCount);
|
||||
// 'ans[i]' stores the index 'j' of the *last word* in the optimal line starting at 'i'
|
||||
std::vector<size_t> ans(totalWordCount);
|
||||
|
||||
// Base Case
|
||||
dp[totalWordCount - 1] = 0;
|
||||
ans[totalWordCount - 1] = totalWordCount - 1;
|
||||
|
||||
for (int i = totalWordCount - 2; i >= 0; --i) {
|
||||
int currlen = -spaceWidth + indentWidth;
|
||||
dp[i] = MAX_COST;
|
||||
|
||||
for (size_t j = i; j < totalWordCount; ++j) {
|
||||
// Current line length: previous width + space + current word width
|
||||
currlen += wordWidths[j] + spaceWidth;
|
||||
|
||||
if (currlen > pageWidth) {
|
||||
break;
|
||||
}
|
||||
|
||||
int cost;
|
||||
if (j == totalWordCount - 1) {
|
||||
cost = 0; // Last line
|
||||
} else {
|
||||
const int remainingSpace = pageWidth - currlen;
|
||||
// Use long long for the square to prevent overflow
|
||||
const long long cost_ll = static_cast<long long>(remainingSpace) * remainingSpace + dp[j + 1];
|
||||
|
||||
if (cost_ll > MAX_COST) {
|
||||
cost = MAX_COST;
|
||||
} else {
|
||||
cost = static_cast<int>(cost_ll);
|
||||
}
|
||||
}
|
||||
|
||||
if (cost < dp[i]) {
|
||||
dp[i] = cost;
|
||||
ans[i] = j; // j is the index of the last word in this optimal line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stores the index of the word that starts the next line (last_word_index + 1)
|
||||
std::vector<size_t> lineBreakIndices;
|
||||
size_t currentWordIndex = 0;
|
||||
constexpr size_t MAX_LINES = 1000;
|
||||
|
||||
while (currentWordIndex < totalWordCount) {
|
||||
if (lineBreakIndices.size() >= MAX_LINES) {
|
||||
break;
|
||||
}
|
||||
|
||||
size_t nextBreakIndex = ans[currentWordIndex] + 1;
|
||||
lineBreakIndices.push_back(nextBreakIndex);
|
||||
|
||||
currentWordIndex = nextBreakIndex;
|
||||
}
|
||||
|
||||
// Initialize iterators for consumption
|
||||
auto wordStartIt = words.begin();
|
||||
auto wordStyleStartIt = wordStyles.begin();
|
||||
size_t wordWidthIndex = 0;
|
||||
|
||||
size_t lastBreakAt = 0;
|
||||
for (const size_t lineBreak : lineBreakIndices) {
|
||||
const size_t lineWordCount = lineBreak - lastBreakAt;
|
||||
|
||||
// Calculate end iterators for the range to splice
|
||||
auto wordEndIt = wordStartIt;
|
||||
auto wordStyleEndIt = wordStyleStartIt;
|
||||
std::advance(wordEndIt, lineWordCount);
|
||||
std::advance(wordStyleEndIt, lineWordCount);
|
||||
|
||||
// Calculate total word width for this line
|
||||
int lineWordWidthSum = 0;
|
||||
for (size_t i = 0; i < lineWordCount; ++i) {
|
||||
lineWordWidthSum += wordWidths[wordWidthIndex + i];
|
||||
}
|
||||
|
||||
// Calculate spacing
|
||||
int spareSpace = pageWidth - lineWordWidthSum;
|
||||
if (wordWidthIndex == 0) {
|
||||
spareSpace -= indentWidth;
|
||||
}
|
||||
|
||||
int spacing = spaceWidth;
|
||||
const bool isLastLine = lineBreak == totalWordCount;
|
||||
|
||||
if (style == TextBlock::JUSTIFIED && !isLastLine && lineWordCount >= 2) {
|
||||
spacing = spareSpace / (lineWordCount - 1);
|
||||
}
|
||||
|
||||
// Calculate initial x position
|
||||
uint16_t xpos = (wordWidthIndex == 0) ? indentWidth : 0;
|
||||
|
||||
if (style == TextBlock::RIGHT_ALIGN) {
|
||||
xpos = spareSpace - (lineWordCount - 1) * spaceWidth;
|
||||
} else if (style == TextBlock::CENTER_ALIGN) {
|
||||
xpos = (spareSpace - (lineWordCount - 1) * spaceWidth) / 2;
|
||||
}
|
||||
|
||||
// Pre-calculate X positions for words
|
||||
std::list<uint16_t> lineXPos;
|
||||
for (size_t i = 0; i < lineWordCount; ++i) {
|
||||
const uint16_t currentWordWidth = wordWidths[wordWidthIndex + i];
|
||||
lineXPos.push_back(xpos);
|
||||
xpos += currentWordWidth + spacing;
|
||||
}
|
||||
|
||||
// *** CRITICAL STEP: CONSUME DATA USING SPLICE ***
|
||||
std::list<std::string> lineWords;
|
||||
lineWords.splice(lineWords.begin(), words, wordStartIt, wordEndIt);
|
||||
std::list<EpdFontStyle> lineWordStyles;
|
||||
lineWordStyles.splice(lineWordStyles.begin(), wordStyles, wordStyleStartIt, wordStyleEndIt);
|
||||
|
||||
processLine(
|
||||
std::make_shared<TextBlock>(std::move(lineWords), std::move(lineXPos), std::move(lineWordStyles), style));
|
||||
|
||||
// Update pointers/indices for the next line
|
||||
wordStartIt = wordEndIt;
|
||||
wordStyleStartIt = wordStyleEndIt;
|
||||
wordWidthIndex += lineWordCount;
|
||||
lastBreakAt = lineBreak;
|
||||
}
|
||||
}
|
||||
32
lib/Epub/Epub/ParsedText.h
Normal file
32
lib/Epub/Epub/ParsedText.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <EpdFontFamily.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "blocks/TextBlock.h"
|
||||
|
||||
class GfxRenderer;
|
||||
|
||||
class ParsedText {
|
||||
std::list<std::string> words;
|
||||
std::list<EpdFontStyle> wordStyles;
|
||||
TextBlock::BLOCK_STYLE style;
|
||||
bool extraParagraphSpacing;
|
||||
|
||||
public:
|
||||
explicit ParsedText(const TextBlock::BLOCK_STYLE style, const bool extraParagraphSpacing)
|
||||
: style(style), extraParagraphSpacing(extraParagraphSpacing) {}
|
||||
~ParsedText() = default;
|
||||
|
||||
void addWord(std::string word, EpdFontStyle fontStyle);
|
||||
void setStyle(const TextBlock::BLOCK_STYLE style) { this->style = style; }
|
||||
TextBlock::BLOCK_STYLE getStyle() const { return style; }
|
||||
bool isEmpty() const { return words.empty(); }
|
||||
void layoutAndExtractLines(const GfxRenderer& renderer, int fontId, int horizontalMargin,
|
||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine);
|
||||
};
|
||||
@ -1,29 +1,49 @@
|
||||
#include "Section.h"
|
||||
|
||||
#include <EpdRenderer.h>
|
||||
#include <SD.h>
|
||||
#include <Serialization.h>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "EpubHtmlParser.h"
|
||||
#include "FsHelpers.h"
|
||||
#include "Page.h"
|
||||
#include "parsers/ChapterHtmlSlimParser.h"
|
||||
|
||||
void Section::onPageComplete(const Page* page) {
|
||||
Serial.printf("Page %d complete\n", pageCount);
|
||||
namespace {
|
||||
constexpr uint8_t SECTION_FILE_VERSION = 5;
|
||||
}
|
||||
|
||||
void Section::onPageComplete(std::unique_ptr<Page> page) {
|
||||
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
||||
// TODO can this be removed?
|
||||
SD.open(filePath.c_str(), FILE_WRITE).close();
|
||||
|
||||
std::ofstream outputFile("/sd" + filePath);
|
||||
page->serialize(outputFile);
|
||||
outputFile.close();
|
||||
|
||||
Serial.printf("[%lu] [SCT] Page %d processed\n", millis(), pageCount);
|
||||
|
||||
pageCount++;
|
||||
delete page;
|
||||
}
|
||||
|
||||
bool Section::hasCache() {
|
||||
void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
||||
const int marginRight, const int marginBottom, const int marginLeft,
|
||||
const bool extraParagraphSpacing) const {
|
||||
std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str());
|
||||
serialization::writePod(outputFile, SECTION_FILE_VERSION);
|
||||
serialization::writePod(outputFile, fontId);
|
||||
serialization::writePod(outputFile, lineCompression);
|
||||
serialization::writePod(outputFile, marginTop);
|
||||
serialization::writePod(outputFile, marginRight);
|
||||
serialization::writePod(outputFile, marginBottom);
|
||||
serialization::writePod(outputFile, marginLeft);
|
||||
serialization::writePod(outputFile, extraParagraphSpacing);
|
||||
serialization::writePod(outputFile, pageCount);
|
||||
outputFile.close();
|
||||
}
|
||||
|
||||
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
||||
const int marginRight, const int marginBottom, const int marginLeft,
|
||||
const bool extraParagraphSpacing) {
|
||||
if (!SD.exists(cachePath.c_str())) {
|
||||
return false;
|
||||
}
|
||||
@ -33,14 +53,43 @@ bool Section::hasCache() {
|
||||
return false;
|
||||
}
|
||||
|
||||
File sectionFile = SD.open(sectionFilePath.c_str(), FILE_READ);
|
||||
uint8_t pageCountBytes[2] = {0, 0};
|
||||
sectionFile.read(pageCountBytes, 2);
|
||||
sectionFile.close();
|
||||
std::ifstream inputFile(("/sd" + sectionFilePath).c_str());
|
||||
|
||||
pageCount = pageCountBytes[0] + (pageCountBytes[1] << 8);
|
||||
Serial.printf("Loaded cache: %d pages\n", pageCount);
|
||||
// Match parameters
|
||||
{
|
||||
uint8_t version;
|
||||
serialization::readPod(inputFile, version);
|
||||
if (version != SECTION_FILE_VERSION) {
|
||||
inputFile.close();
|
||||
Serial.printf("[%lu] [SCT] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||
clearCache();
|
||||
return false;
|
||||
}
|
||||
|
||||
int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft;
|
||||
float fileLineCompression;
|
||||
bool fileExtraParagraphSpacing;
|
||||
serialization::readPod(inputFile, fileFontId);
|
||||
serialization::readPod(inputFile, fileLineCompression);
|
||||
serialization::readPod(inputFile, fileMarginTop);
|
||||
serialization::readPod(inputFile, fileMarginRight);
|
||||
serialization::readPod(inputFile, fileMarginBottom);
|
||||
serialization::readPod(inputFile, fileMarginLeft);
|
||||
serialization::readPod(inputFile, fileExtraParagraphSpacing);
|
||||
|
||||
if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop ||
|
||||
marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft ||
|
||||
extraParagraphSpacing != fileExtraParagraphSpacing) {
|
||||
inputFile.close();
|
||||
Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis());
|
||||
clearCache();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
serialization::readPod(inputFile, pageCount);
|
||||
inputFile.close();
|
||||
Serial.printf("[%lu] [SCT] Deserialization succeeded: %d pages\n", millis(), pageCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -49,69 +98,69 @@ void Section::setupCacheDir() const {
|
||||
SD.mkdir(cachePath.c_str());
|
||||
}
|
||||
|
||||
void Section::clearCache() const { SD.rmdir(cachePath.c_str()); }
|
||||
// Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem)
|
||||
bool Section::clearCache() const {
|
||||
if (!SD.exists(cachePath.c_str())) {
|
||||
Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Section::persistPageDataToSD() {
|
||||
size_t size = 0;
|
||||
auto localPath = epub->getSpineItem(spineIndex);
|
||||
|
||||
const auto html = epub->getItemContents(epub->getSpineItem(spineIndex), &size);
|
||||
if (!html) {
|
||||
Serial.println("Failed to read item contents");
|
||||
if (!FsHelpers::removeDir(cachePath.c_str())) {
|
||||
Serial.printf("[%lu] [SCT] Failed to clear cache\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Would love to stream this through an XML visitor
|
||||
Serial.printf("[%lu] [SCT] Cache cleared successfully\n", millis());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop,
|
||||
const int marginRight, const int marginBottom, const int marginLeft,
|
||||
const bool extraParagraphSpacing) {
|
||||
const auto localPath = epub->getSpineItem(spineIndex);
|
||||
|
||||
// TODO: Should we get rid of this file all together?
|
||||
// It currently saves us a bit of memory by allowing for all the inflation bits to be released
|
||||
// before loading the XML parser
|
||||
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html";
|
||||
File f = SD.open(tmpHtmlPath.c_str(), FILE_WRITE);
|
||||
const auto written = f.write(html, size);
|
||||
File f = SD.open(tmpHtmlPath.c_str(), FILE_WRITE, true);
|
||||
bool success = epub->readItemContentsToStream(localPath, f, 1024);
|
||||
f.close();
|
||||
free(html);
|
||||
|
||||
Serial.printf("Wrote %d bytes to %s\n", written, tmpHtmlPath.c_str());
|
||||
|
||||
if (size != written) {
|
||||
Serial.println("Failed to inflate section contents to SD");
|
||||
SD.remove(tmpHtmlPath.c_str());
|
||||
if (!success) {
|
||||
Serial.printf("[%lu] [SCT] Failed to stream item contents to temp file\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [SCT] Streamed temp HTML to %s\n", millis(), tmpHtmlPath.c_str());
|
||||
|
||||
const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath;
|
||||
auto visitor =
|
||||
EpubHtmlParser(sdTmpHtmlPath.c_str(), renderer, [this](const Page* page) { this->onPageComplete(page); });
|
||||
|
||||
// TODO: Come back and see if mem used here can be lowered?
|
||||
const bool success = visitor.parseAndBuildPages();
|
||||
ChapterHtmlSlimParser visitor(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight,
|
||||
marginBottom, marginLeft, extraParagraphSpacing,
|
||||
[this](std::unique_ptr<Page> page) { this->onPageComplete(std::move(page)); });
|
||||
success = visitor.parseAndBuildPages();
|
||||
|
||||
SD.remove(tmpHtmlPath.c_str());
|
||||
if (!success) {
|
||||
Serial.println("Failed to parse and build pages");
|
||||
Serial.printf("[%lu] [SCT] Failed to parse XML and build pages\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
File sectionFile = SD.open((cachePath + "/section.bin").c_str(), FILE_WRITE, true);
|
||||
const uint8_t pageCountBytes[2] = {static_cast<uint8_t>(pageCount & 0xFF),
|
||||
static_cast<uint8_t>((pageCount >> 8) & 0xFF)};
|
||||
sectionFile.write(pageCountBytes, 2);
|
||||
sectionFile.close();
|
||||
writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft, extraParagraphSpacing);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Section::renderPage() {
|
||||
if (0 <= currentPage && currentPage < pageCount) {
|
||||
const auto filePath = "/sd" + cachePath + "/page_" + std::to_string(currentPage) + ".bin";
|
||||
std::ifstream inputFile(filePath);
|
||||
const Page* p = Page::deserialize(inputFile);
|
||||
inputFile.close();
|
||||
p->render(renderer);
|
||||
delete p;
|
||||
} else if (pageCount == 0) {
|
||||
Serial.println("No pages to render");
|
||||
const int width = renderer.getTextWidth("Empty chapter", BOLD);
|
||||
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Empty chapter", 1, BOLD);
|
||||
} else {
|
||||
Serial.printf("Page out of bounds: %d (max %d)\n", currentPage, pageCount);
|
||||
const int width = renderer.getTextWidth("Out of bounds", BOLD);
|
||||
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Out of bounds", 1, BOLD);
|
||||
std::unique_ptr<Page> Section::loadPageFromSD() const {
|
||||
const auto filePath = "/sd" + cachePath + "/page_" + std::to_string(currentPage) + ".bin";
|
||||
if (!SD.exists(filePath.c_str() + 3)) {
|
||||
Serial.printf("[%lu] [SCT] Page file does not exist: %s\n", millis(), filePath.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::ifstream inputFile(filePath);
|
||||
auto page = Page::deserialize(inputFile);
|
||||
inputFile.close();
|
||||
return page;
|
||||
}
|
||||
|
||||
@ -1,29 +1,35 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
#include "Epub.h"
|
||||
|
||||
class Page;
|
||||
class EpdRenderer;
|
||||
class GfxRenderer;
|
||||
|
||||
class Section {
|
||||
Epub* epub;
|
||||
std::shared_ptr<Epub> epub;
|
||||
const int spineIndex;
|
||||
EpdRenderer& renderer;
|
||||
GfxRenderer& renderer;
|
||||
std::string cachePath;
|
||||
|
||||
void onPageComplete(const Page* page);
|
||||
void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||
int marginLeft, bool extraParagraphSpacing) const;
|
||||
void onPageComplete(std::unique_ptr<Page> page);
|
||||
|
||||
public:
|
||||
int pageCount = 0;
|
||||
int currentPage = 0;
|
||||
|
||||
explicit Section(Epub* epub, const int spineIndex, EpdRenderer& renderer)
|
||||
explicit Section(const std::shared_ptr<Epub>& epub, const int spineIndex, GfxRenderer& renderer)
|
||||
: epub(epub), spineIndex(spineIndex), renderer(renderer) {
|
||||
cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex);
|
||||
}
|
||||
~Section() = default;
|
||||
bool hasCache();
|
||||
bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||
int marginLeft, bool extraParagraphSpacing);
|
||||
void setupCacheDir() const;
|
||||
void clearCache() const;
|
||||
bool persistPageDataToSD();
|
||||
void renderPage();
|
||||
bool clearCache() const;
|
||||
bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||
int marginLeft, bool extraParagraphSpacing);
|
||||
std::unique_ptr<Page> loadPageFromSD() const;
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
class EpdRenderer;
|
||||
class GfxRenderer;
|
||||
|
||||
typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
|
||||
|
||||
@ -8,7 +8,7 @@ typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
|
||||
class Block {
|
||||
public:
|
||||
virtual ~Block() = default;
|
||||
virtual void layout(EpdRenderer& renderer) = 0;
|
||||
virtual void layout(GfxRenderer& renderer) = 0;
|
||||
virtual BlockType getType() = 0;
|
||||
virtual bool isEmpty() = 0;
|
||||
virtual void finish() {}
|
||||
|
||||
@ -1,209 +1,19 @@
|
||||
#include "TextBlock.h"
|
||||
|
||||
#include <EpdRenderer.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <Serialization.h>
|
||||
|
||||
static bool isWhitespace(const char c) { return c == ' ' || c == '\r' || c == '\n'; }
|
||||
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
||||
auto wordIt = words.begin();
|
||||
auto wordStylesIt = wordStyles.begin();
|
||||
auto wordXposIt = wordXpos.begin();
|
||||
|
||||
// move past anything that should be considered part of a work
|
||||
static int skipWord(const std::string& text, int index, const int length) {
|
||||
while (index < length && !isWhitespace(text[index])) {
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// skip past any white space characters
|
||||
static int skipWhitespace(const std::string& html, int index, const int length) {
|
||||
while (index < length && isWhitespace(html[index])) {
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void TextBlock::addSpan(const std::string& span, const bool is_bold, const bool is_italic) {
|
||||
// adding a span to text block
|
||||
// make a copy of the text as we'll modify it
|
||||
const int length = span.length();
|
||||
// const auto text = new char[length + 1];
|
||||
// strcpy(text, span);
|
||||
// work out where each word is in the span
|
||||
int index = 0;
|
||||
while (index < length) {
|
||||
// skip past any whitespace to the start of a word
|
||||
index = skipWhitespace(span, index, length);
|
||||
const int wordStart = index;
|
||||
// find the end of the word
|
||||
index = skipWord(span, index, length);
|
||||
const int wordLength = index - wordStart;
|
||||
if (wordLength > 0) {
|
||||
words.push_back(span.substr(wordStart, wordLength));
|
||||
wordStyles.push_back((is_bold ? BOLD_SPAN : 0) | (is_italic ? ITALIC_SPAN : 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
|
||||
const int totalWordCount = words.size();
|
||||
const int pageWidth = renderer.getPageWidth();
|
||||
const int spaceWidth = renderer.getSpaceWidth();
|
||||
|
||||
words.shrink_to_fit();
|
||||
wordStyles.shrink_to_fit();
|
||||
wordXpos.reserve(totalWordCount);
|
||||
|
||||
// measure each word
|
||||
uint16_t wordWidths[totalWordCount];
|
||||
for (int i = 0; i < words.size(); i++) {
|
||||
// measure the word
|
||||
EpdFontStyle fontStyle = REGULAR;
|
||||
if (wordStyles[i] & BOLD_SPAN) {
|
||||
if (wordStyles[i] & ITALIC_SPAN) {
|
||||
fontStyle = BOLD_ITALIC;
|
||||
} else {
|
||||
fontStyle = BOLD;
|
||||
}
|
||||
} else if (wordStyles[i] & ITALIC_SPAN) {
|
||||
fontStyle = ITALIC;
|
||||
}
|
||||
const int width = renderer.getTextWidth(words[i].c_str(), fontStyle);
|
||||
wordWidths[i] = width;
|
||||
}
|
||||
renderer.drawText(fontId, *wordXposIt + x, y, wordIt->c_str(), true, *wordStylesIt);
|
||||
|
||||
// now apply the dynamic programming algorithm to find the best line breaks
|
||||
// DP table in which dp[i] represents cost of line starting with word words[i]
|
||||
int dp[totalWordCount];
|
||||
|
||||
// Array in which ans[i] store index of last word in line starting with word
|
||||
// word[i]
|
||||
size_t ans[totalWordCount];
|
||||
|
||||
// If only one word is present then only one line is required. Cost of last
|
||||
// line is zero. Hence cost of this line is zero. Ending point is also n-1 as
|
||||
// single word is present
|
||||
dp[totalWordCount - 1] = 0;
|
||||
ans[totalWordCount - 1] = totalWordCount - 1;
|
||||
|
||||
// Make each word first word of line by iterating over each index in arr.
|
||||
for (int i = totalWordCount - 2; i >= 0; i--) {
|
||||
int currlen = -1;
|
||||
dp[i] = INT_MAX;
|
||||
|
||||
// Variable to store possible minimum cost of line.
|
||||
int cost;
|
||||
|
||||
// Keep on adding words in current line by iterating from starting word upto
|
||||
// last word in arr.
|
||||
for (int j = i; j < totalWordCount; j++) {
|
||||
// Update the width of the words in current line + the space between two
|
||||
// words.
|
||||
currlen += wordWidths[j] + spaceWidth;
|
||||
|
||||
// If we're bigger than the current pagewidth then we can't add more words
|
||||
if (currlen > pageWidth) break;
|
||||
|
||||
// if we've run out of words then this is last line and the cost should be
|
||||
// 0 Otherwise the cost is the sqaure of the left over space + the costs
|
||||
// of all the previous lines
|
||||
if (j == totalWordCount - 1)
|
||||
cost = 0;
|
||||
else
|
||||
cost = (pageWidth - currlen) * (pageWidth - currlen) + dp[j + 1];
|
||||
|
||||
// Check if this arrangement gives minimum cost for line starting with
|
||||
// word words[i].
|
||||
if (cost < dp[i]) {
|
||||
dp[i] = cost;
|
||||
ans[i] = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can now iterate through the answer to find the line break positions
|
||||
std::list<uint16_t> lineBreaks;
|
||||
for (size_t i = 0; i < totalWordCount;) {
|
||||
i = ans[i] + 1;
|
||||
if (i > totalWordCount) {
|
||||
break;
|
||||
}
|
||||
lineBreaks.push_back(i);
|
||||
// Text too big, just exit
|
||||
if (lineBreaks.size() > 1000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::list<TextBlock*> lines;
|
||||
|
||||
// With the line breaks calculated we can now position the words along the
|
||||
// line
|
||||
int startWord = 0;
|
||||
for (const auto lineBreak : lineBreaks) {
|
||||
const int lineWordCount = lineBreak - startWord;
|
||||
|
||||
int lineWordWidthSum = 0;
|
||||
for (int i = startWord; i < lineBreak; i++) {
|
||||
lineWordWidthSum += wordWidths[i];
|
||||
}
|
||||
|
||||
// Calculate spacing between words
|
||||
const uint16_t spareSpace = pageWidth - lineWordWidthSum;
|
||||
uint16_t spacing = spaceWidth;
|
||||
// evenly space words if using justified style, not the last line, and at
|
||||
// least 2 words
|
||||
if (style == JUSTIFIED && lineBreak != lineBreaks.back() && lineWordCount >= 2) {
|
||||
spacing = spareSpace / (lineWordCount - 1);
|
||||
}
|
||||
|
||||
uint16_t xpos = 0;
|
||||
if (style == RIGHT_ALIGN) {
|
||||
xpos = spareSpace - (lineWordCount - 1) * spaceWidth;
|
||||
} else if (style == CENTER_ALIGN) {
|
||||
xpos = (spareSpace - (lineWordCount - 1) * spaceWidth) / 2;
|
||||
}
|
||||
|
||||
for (int i = startWord; i < lineBreak; i++) {
|
||||
wordXpos[i] = xpos;
|
||||
xpos += wordWidths[i] + spacing;
|
||||
}
|
||||
|
||||
std::vector<std::string> lineWords;
|
||||
std::vector<uint16_t> lineXPos;
|
||||
std::vector<uint8_t> lineWordStyles;
|
||||
lineWords.reserve(lineWordCount);
|
||||
lineXPos.reserve(lineWordCount);
|
||||
lineWordStyles.reserve(lineWordCount);
|
||||
|
||||
for (int i = startWord; i < lineBreak; i++) {
|
||||
lineWords.push_back(words[i]);
|
||||
lineXPos.push_back(wordXpos[i]);
|
||||
lineWordStyles.push_back(wordStyles[i]);
|
||||
}
|
||||
const auto textLine = new TextBlock(lineWords, lineXPos, lineWordStyles, style);
|
||||
lines.push_back(textLine);
|
||||
startWord = lineBreak;
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
void TextBlock::render(const EpdRenderer& renderer, const int x, const int y) const {
|
||||
for (int i = 0; i < words.size(); i++) {
|
||||
// get the style
|
||||
const uint8_t wordStyle = wordStyles[i];
|
||||
// render the word
|
||||
EpdFontStyle fontStyle = REGULAR;
|
||||
if (wordStyles[i] & BOLD_SPAN) {
|
||||
if (wordStyles[i] & ITALIC_SPAN) {
|
||||
fontStyle = BOLD_ITALIC;
|
||||
} else {
|
||||
fontStyle = BOLD;
|
||||
}
|
||||
|
||||
} else if (wordStyles[i] & ITALIC_SPAN) {
|
||||
fontStyle = ITALIC;
|
||||
}
|
||||
renderer.drawText(x + wordXpos[i], y, words[i].c_str(), 1, fontStyle);
|
||||
std::advance(wordIt, 1);
|
||||
std::advance(wordStylesIt, 1);
|
||||
std::advance(wordXposIt, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,11 +37,11 @@ void TextBlock::serialize(std::ostream& os) const {
|
||||
serialization::writePod(os, style);
|
||||
}
|
||||
|
||||
TextBlock* TextBlock::deserialize(std::istream& is) {
|
||||
std::unique_ptr<TextBlock> TextBlock::deserialize(std::istream& is) {
|
||||
uint32_t wc, xc, sc;
|
||||
std::vector<std::string> words;
|
||||
std::vector<uint16_t> wordXpos;
|
||||
std::vector<uint8_t> wordStyles;
|
||||
std::list<std::string> words;
|
||||
std::list<uint16_t> wordXpos;
|
||||
std::list<EpdFontStyle> wordStyles;
|
||||
BLOCK_STYLE style;
|
||||
|
||||
// words
|
||||
@ -252,5 +62,5 @@ TextBlock* TextBlock::deserialize(std::istream& is) {
|
||||
// style
|
||||
serialization::readPod(is, style);
|
||||
|
||||
return new TextBlock(words, wordXpos, wordStyles, style);
|
||||
return std::unique_ptr<TextBlock>(new TextBlock(std::move(words), std::move(wordXpos), std::move(wordStyles), style));
|
||||
}
|
||||
|
||||
@ -1,50 +1,40 @@
|
||||
#pragma once
|
||||
#include <EpdFontFamily.h>
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Block.h"
|
||||
|
||||
enum SPAN_STYLE : uint8_t {
|
||||
BOLD_SPAN = 1,
|
||||
ITALIC_SPAN = 2,
|
||||
};
|
||||
|
||||
enum BLOCK_STYLE : uint8_t {
|
||||
JUSTIFIED = 0,
|
||||
LEFT_ALIGN = 1,
|
||||
CENTER_ALIGN = 2,
|
||||
RIGHT_ALIGN = 3,
|
||||
};
|
||||
|
||||
// represents a block of words in the html document
|
||||
class TextBlock final : public Block {
|
||||
// pointer to each word
|
||||
std::vector<std::string> words;
|
||||
// x position of each word
|
||||
std::vector<uint16_t> wordXpos;
|
||||
// the styles of each word
|
||||
std::vector<uint8_t> wordStyles;
|
||||
public:
|
||||
enum BLOCK_STYLE : uint8_t {
|
||||
JUSTIFIED = 0,
|
||||
LEFT_ALIGN = 1,
|
||||
CENTER_ALIGN = 2,
|
||||
RIGHT_ALIGN = 3,
|
||||
};
|
||||
|
||||
// the style of the block - left, center, right aligned
|
||||
private:
|
||||
std::list<std::string> words;
|
||||
std::list<uint16_t> wordXpos;
|
||||
std::list<EpdFontStyle> wordStyles;
|
||||
BLOCK_STYLE style;
|
||||
|
||||
public:
|
||||
void addSpan(const std::string& span, bool is_bold, bool is_italic);
|
||||
explicit TextBlock(const BLOCK_STYLE style) : style(style) {}
|
||||
explicit TextBlock(const std::vector<std::string>& words, const std::vector<uint16_t>& word_xpos,
|
||||
// the styles of each word
|
||||
const std::vector<uint8_t>& word_styles, const BLOCK_STYLE style)
|
||||
: words(words), wordXpos(word_xpos), wordStyles(word_styles), style(style) {}
|
||||
explicit TextBlock(std::list<std::string> words, std::list<uint16_t> word_xpos, std::list<EpdFontStyle> word_styles,
|
||||
const BLOCK_STYLE style)
|
||||
: words(std::move(words)), wordXpos(std::move(word_xpos)), wordStyles(std::move(word_styles)), style(style) {}
|
||||
~TextBlock() override = default;
|
||||
void set_style(const BLOCK_STYLE style) { this->style = style; }
|
||||
BLOCK_STYLE get_style() const { return style; }
|
||||
void setStyle(const BLOCK_STYLE style) { this->style = style; }
|
||||
BLOCK_STYLE getStyle() const { return style; }
|
||||
bool isEmpty() override { return words.empty(); }
|
||||
void layout(EpdRenderer& renderer) override {};
|
||||
void layout(GfxRenderer& renderer) override {};
|
||||
// given a renderer works out where to break the words into lines
|
||||
std::list<TextBlock*> splitIntoLines(const EpdRenderer& renderer);
|
||||
void render(const EpdRenderer& renderer, int x, int y) const;
|
||||
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
|
||||
BlockType getType() override { return TEXT_BLOCK; }
|
||||
void serialize(std::ostream& os) const;
|
||||
static TextBlock* deserialize(std::istream& is);
|
||||
static std::unique_ptr<TextBlock> deserialize(std::istream& is);
|
||||
};
|
||||
|
||||
281
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
Normal file
281
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
Normal file
@ -0,0 +1,281 @@
|
||||
#include "ChapterHtmlSlimParser.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <expat.h>
|
||||
|
||||
#include "../Page.h"
|
||||
#include "../htmlEntities.h"
|
||||
|
||||
const char* HEADER_TAGS[] = {"h1", "h2", "h3", "h4", "h5", "h6"};
|
||||
constexpr int NUM_HEADER_TAGS = sizeof(HEADER_TAGS) / sizeof(HEADER_TAGS[0]);
|
||||
|
||||
const char* BLOCK_TAGS[] = {"p", "li", "div", "br"};
|
||||
constexpr int NUM_BLOCK_TAGS = sizeof(BLOCK_TAGS) / sizeof(BLOCK_TAGS[0]);
|
||||
|
||||
const char* BOLD_TAGS[] = {"b"};
|
||||
constexpr int NUM_BOLD_TAGS = sizeof(BOLD_TAGS) / sizeof(BOLD_TAGS[0]);
|
||||
|
||||
const char* ITALIC_TAGS[] = {"i"};
|
||||
constexpr int NUM_ITALIC_TAGS = sizeof(ITALIC_TAGS) / sizeof(ITALIC_TAGS[0]);
|
||||
|
||||
const char* IMAGE_TAGS[] = {"img"};
|
||||
constexpr int NUM_IMAGE_TAGS = sizeof(IMAGE_TAGS) / sizeof(IMAGE_TAGS[0]);
|
||||
|
||||
const char* SKIP_TAGS[] = {"head", "table"};
|
||||
constexpr int NUM_SKIP_TAGS = sizeof(SKIP_TAGS) / sizeof(SKIP_TAGS[0]);
|
||||
|
||||
bool isWhitespace(const char c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t'; }
|
||||
|
||||
// given the start and end of a tag, check to see if it matches a known tag
|
||||
bool matches(const char* tag_name, const char* possible_tags[], const int possible_tag_count) {
|
||||
for (int i = 0; i < possible_tag_count; i++) {
|
||||
if (strcmp(tag_name, possible_tags[i]) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// start a new text block if needed
|
||||
void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::BLOCK_STYLE style) {
|
||||
if (currentTextBlock) {
|
||||
// already have a text block running and it is empty - just reuse it
|
||||
if (currentTextBlock->isEmpty()) {
|
||||
currentTextBlock->setStyle(style);
|
||||
return;
|
||||
}
|
||||
|
||||
makePages();
|
||||
}
|
||||
currentTextBlock.reset(new ParsedText(style, extraParagraphSpacing));
|
||||
}
|
||||
|
||||
void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
||||
auto* self = static_cast<ChapterHtmlSlimParser*>(userData);
|
||||
(void)atts;
|
||||
|
||||
// Middle of skip
|
||||
if (self->skipUntilDepth < self->depth) {
|
||||
self->depth += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS)) {
|
||||
// TODO: Start processing image tags
|
||||
self->skipUntilDepth = self->depth;
|
||||
self->depth += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (matches(name, SKIP_TAGS, NUM_SKIP_TAGS)) {
|
||||
// start skip
|
||||
self->skipUntilDepth = self->depth;
|
||||
self->depth += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (matches(name, HEADER_TAGS, NUM_HEADER_TAGS)) {
|
||||
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
||||
self->boldUntilDepth = min(self->boldUntilDepth, self->depth);
|
||||
} else if (matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS)) {
|
||||
if (strcmp(name, "br") == 0) {
|
||||
self->startNewTextBlock(self->currentTextBlock->getStyle());
|
||||
} else {
|
||||
self->startNewTextBlock(TextBlock::JUSTIFIED);
|
||||
}
|
||||
} else if (matches(name, BOLD_TAGS, NUM_BOLD_TAGS)) {
|
||||
self->boldUntilDepth = min(self->boldUntilDepth, self->depth);
|
||||
} else if (matches(name, ITALIC_TAGS, NUM_ITALIC_TAGS)) {
|
||||
self->italicUntilDepth = min(self->italicUntilDepth, self->depth);
|
||||
}
|
||||
|
||||
self->depth += 1;
|
||||
}
|
||||
|
||||
void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char* s, const int len) {
|
||||
auto* self = static_cast<ChapterHtmlSlimParser*>(userData);
|
||||
|
||||
// Middle of skip
|
||||
if (self->skipUntilDepth < self->depth) {
|
||||
return;
|
||||
}
|
||||
|
||||
EpdFontStyle fontStyle = REGULAR;
|
||||
if (self->boldUntilDepth < self->depth && self->italicUntilDepth < self->depth) {
|
||||
fontStyle = BOLD_ITALIC;
|
||||
} else if (self->boldUntilDepth < self->depth) {
|
||||
fontStyle = BOLD;
|
||||
} else if (self->italicUntilDepth < self->depth) {
|
||||
fontStyle = ITALIC;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (isWhitespace(s[i])) {
|
||||
// Currently looking at whitespace, if there's anything in the partWordBuffer, flush it
|
||||
if (self->partWordBufferIndex > 0) {
|
||||
self->partWordBuffer[self->partWordBufferIndex] = '\0';
|
||||
self->currentTextBlock->addWord(std::move(replaceHtmlEntities(self->partWordBuffer)), fontStyle);
|
||||
self->partWordBufferIndex = 0;
|
||||
}
|
||||
// Skip the whitespace char
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
self->partWordBuffer[self->partWordBufferIndex] = '\0';
|
||||
self->currentTextBlock->addWord(std::move(replaceHtmlEntities(self->partWordBuffer)), fontStyle);
|
||||
self->partWordBufferIndex = 0;
|
||||
}
|
||||
|
||||
self->partWordBuffer[self->partWordBufferIndex++] = s[i];
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* name) {
|
||||
auto* self = static_cast<ChapterHtmlSlimParser*>(userData);
|
||||
(void)name;
|
||||
|
||||
if (self->partWordBufferIndex > 0) {
|
||||
// Only flush out part word buffer if we're closing a block tag or are at the top of the HTML file.
|
||||
// We don't want to flush out content when closing inline tags like <span>.
|
||||
// Currently this also flushes out on closing <b> and <i> tags, but they are line tags so that shouldn't happen,
|
||||
// text styling needs to be overhauled to fix it.
|
||||
const bool shouldBreakText =
|
||||
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;
|
||||
|
||||
if (shouldBreakText) {
|
||||
EpdFontStyle fontStyle = REGULAR;
|
||||
if (self->boldUntilDepth < self->depth && self->italicUntilDepth < self->depth) {
|
||||
fontStyle = BOLD_ITALIC;
|
||||
} else if (self->boldUntilDepth < self->depth) {
|
||||
fontStyle = BOLD;
|
||||
} else if (self->italicUntilDepth < self->depth) {
|
||||
fontStyle = ITALIC;
|
||||
}
|
||||
|
||||
self->partWordBuffer[self->partWordBufferIndex] = '\0';
|
||||
self->currentTextBlock->addWord(std::move(replaceHtmlEntities(self->partWordBuffer)), fontStyle);
|
||||
self->partWordBufferIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
self->depth -= 1;
|
||||
|
||||
// Leaving skip
|
||||
if (self->skipUntilDepth == self->depth) {
|
||||
self->skipUntilDepth = INT_MAX;
|
||||
}
|
||||
|
||||
// Leaving bold
|
||||
if (self->boldUntilDepth == self->depth) {
|
||||
self->boldUntilDepth = INT_MAX;
|
||||
}
|
||||
|
||||
// Leaving italic
|
||||
if (self->italicUntilDepth == self->depth) {
|
||||
self->italicUntilDepth = INT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
startNewTextBlock(TextBlock::JUSTIFIED);
|
||||
|
||||
const XML_Parser parser = XML_ParserCreate(nullptr);
|
||||
int done;
|
||||
|
||||
if (!parser) {
|
||||
Serial.printf("[%lu] [EHP] Couldn't allocate memory for parser\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
XML_SetUserData(parser, this);
|
||||
XML_SetElementHandler(parser, startElement, endElement);
|
||||
XML_SetCharacterDataHandler(parser, characterData);
|
||||
|
||||
FILE* file = fopen(filepath, "r");
|
||||
if (!file) {
|
||||
Serial.printf("[%lu] [EHP] Couldn't open file %s\n", millis(), filepath);
|
||||
XML_ParserFree(parser);
|
||||
return false;
|
||||
}
|
||||
|
||||
do {
|
||||
void* const buf = XML_GetBuffer(parser, 1024);
|
||||
if (!buf) {
|
||||
Serial.printf("[%lu] [EHP] Couldn't allocate memory for buffer\n", millis());
|
||||
XML_ParserFree(parser);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t len = fread(buf, 1, 1024, file);
|
||||
|
||||
if (ferror(file)) {
|
||||
Serial.printf("[%lu] [EHP] File read error\n", millis());
|
||||
XML_ParserFree(parser);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
done = feof(file);
|
||||
|
||||
if (XML_ParseBuffer(parser, static_cast<int>(len), done) == XML_STATUS_ERROR) {
|
||||
Serial.printf("[%lu] [EHP] Parse error at line %lu:\n%s\n", millis(), XML_GetCurrentLineNumber(parser),
|
||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||
XML_ParserFree(parser);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
} while (!done);
|
||||
|
||||
XML_ParserFree(parser);
|
||||
fclose(file);
|
||||
|
||||
// Process last page if there is still text
|
||||
if (currentTextBlock) {
|
||||
makePages();
|
||||
completePageFn(std::move(currentPage));
|
||||
currentPage.reset();
|
||||
currentTextBlock.reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChapterHtmlSlimParser::addLineToPage(std::shared_ptr<TextBlock> line) {
|
||||
const int lineHeight = renderer.getLineHeight(fontId) * lineCompression;
|
||||
const int pageHeight = GfxRenderer::getScreenHeight() - marginTop - marginBottom;
|
||||
|
||||
if (currentPageNextY + lineHeight > pageHeight) {
|
||||
completePageFn(std::move(currentPage));
|
||||
currentPage.reset(new Page());
|
||||
currentPageNextY = marginTop;
|
||||
}
|
||||
|
||||
currentPage->elements.push_back(std::make_shared<PageLine>(line, marginLeft, currentPageNextY));
|
||||
currentPageNextY += lineHeight;
|
||||
}
|
||||
|
||||
void ChapterHtmlSlimParser::makePages() {
|
||||
if (!currentTextBlock) {
|
||||
Serial.printf("[%lu] [EHP] !! No text block to make pages for !!\n", millis());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentPage) {
|
||||
currentPage.reset(new Page());
|
||||
currentPageNextY = marginTop;
|
||||
}
|
||||
|
||||
const int lineHeight = renderer.getLineHeight(fontId) * lineCompression;
|
||||
currentTextBlock->layoutAndExtractLines(
|
||||
renderer, fontId, marginLeft + marginRight,
|
||||
[this](const std::shared_ptr<TextBlock>& textBlock) { addLineToPage(textBlock); });
|
||||
// Extra paragraph spacing if enabled
|
||||
if (extraParagraphSpacing) {
|
||||
currentPageNextY += lineHeight / 2;
|
||||
}
|
||||
}
|
||||
65
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
Normal file
65
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
Normal file
@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <expat.h>
|
||||
|
||||
#include <climits>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "../ParsedText.h"
|
||||
#include "../blocks/TextBlock.h"
|
||||
|
||||
class Page;
|
||||
class GfxRenderer;
|
||||
|
||||
#define MAX_WORD_SIZE 200
|
||||
|
||||
class ChapterHtmlSlimParser {
|
||||
const char* filepath;
|
||||
GfxRenderer& renderer;
|
||||
std::function<void(std::unique_ptr<Page>)> completePageFn;
|
||||
int depth = 0;
|
||||
int skipUntilDepth = INT_MAX;
|
||||
int boldUntilDepth = INT_MAX;
|
||||
int italicUntilDepth = INT_MAX;
|
||||
// buffer for building up words from characters, will auto break if longer than this
|
||||
// leave one char at end for null pointer
|
||||
char partWordBuffer[MAX_WORD_SIZE + 1] = {};
|
||||
int partWordBufferIndex = 0;
|
||||
std::unique_ptr<ParsedText> currentTextBlock = nullptr;
|
||||
std::unique_ptr<Page> currentPage = nullptr;
|
||||
int16_t currentPageNextY = 0;
|
||||
int fontId;
|
||||
float lineCompression;
|
||||
int marginTop;
|
||||
int marginRight;
|
||||
int marginBottom;
|
||||
int marginLeft;
|
||||
bool extraParagraphSpacing;
|
||||
|
||||
void startNewTextBlock(TextBlock::BLOCK_STYLE style);
|
||||
void makePages();
|
||||
// XML callbacks
|
||||
static void XMLCALL startElement(void* userData, const XML_Char* name, const XML_Char** atts);
|
||||
static void XMLCALL characterData(void* userData, const XML_Char* s, int len);
|
||||
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
||||
|
||||
public:
|
||||
explicit ChapterHtmlSlimParser(const char* filepath, GfxRenderer& renderer, const int fontId,
|
||||
const float lineCompression, const int marginTop, const int marginRight,
|
||||
const int marginBottom, const int marginLeft, const bool extraParagraphSpacing,
|
||||
const std::function<void(std::unique_ptr<Page>)>& completePageFn)
|
||||
: filepath(filepath),
|
||||
renderer(renderer),
|
||||
fontId(fontId),
|
||||
lineCompression(lineCompression),
|
||||
marginTop(marginTop),
|
||||
marginRight(marginRight),
|
||||
marginBottom(marginBottom),
|
||||
marginLeft(marginLeft),
|
||||
extraParagraphSpacing(extraParagraphSpacing),
|
||||
completePageFn(completePageFn) {}
|
||||
~ChapterHtmlSlimParser() = default;
|
||||
bool parseAndBuildPages();
|
||||
void addLineToPage(std::shared_ptr<TextBlock> line);
|
||||
};
|
||||
96
lib/Epub/Epub/parsers/ContainerParser.cpp
Normal file
96
lib/Epub/Epub/parsers/ContainerParser.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "ContainerParser.h"
|
||||
|
||||
#include <HardwareSerial.h>
|
||||
|
||||
bool ContainerParser::setup() {
|
||||
parser = XML_ParserCreate(nullptr);
|
||||
if (!parser) {
|
||||
Serial.printf("[%lu] [CTR] Couldn't allocate memory for parser\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
XML_SetUserData(parser, this);
|
||||
XML_SetElementHandler(parser, startElement, endElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ContainerParser::teardown() {
|
||||
if (parser) {
|
||||
XML_ParserFree(parser);
|
||||
parser = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t ContainerParser::write(const uint8_t data) { return write(&data, 1); }
|
||||
|
||||
size_t ContainerParser::write(const uint8_t* buffer, const size_t size) {
|
||||
if (!parser) return 0;
|
||||
|
||||
const uint8_t* currentBufferPos = buffer;
|
||||
auto remainingInBuffer = size;
|
||||
|
||||
while (remainingInBuffer > 0) {
|
||||
void* const buf = XML_GetBuffer(parser, 1024);
|
||||
if (!buf) {
|
||||
Serial.printf("[%lu] [CTR] Couldn't allocate buffer\n", millis());
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto toRead = remainingInBuffer < 1024 ? remainingInBuffer : 1024;
|
||||
memcpy(buf, currentBufferPos, toRead);
|
||||
|
||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
||||
Serial.printf("[%lu] [CTR] Parse error: %s\n", millis(), XML_ErrorString(XML_GetErrorCode(parser)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
currentBufferPos += toRead;
|
||||
remainingInBuffer -= toRead;
|
||||
remainingSize -= toRead;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void XMLCALL ContainerParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
||||
auto* self = static_cast<ContainerParser*>(userData);
|
||||
|
||||
// Simple state tracking to ensure we are looking at the valid schema structure
|
||||
if (self->state == START && strcmp(name, "container") == 0) {
|
||||
self->state = IN_CONTAINER;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_CONTAINER && strcmp(name, "rootfiles") == 0) {
|
||||
self->state = IN_ROOTFILES;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_ROOTFILES && strcmp(name, "rootfile") == 0) {
|
||||
const char* mediaType = nullptr;
|
||||
const char* path = nullptr;
|
||||
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "media-type") == 0) {
|
||||
mediaType = atts[i + 1];
|
||||
} else if (strcmp(atts[i], "full-path") == 0) {
|
||||
path = atts[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this is the standard OEBPS package
|
||||
if (mediaType && path && strcmp(mediaType, "application/oebps-package+xml") == 0) {
|
||||
self->fullPath = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL ContainerParser::endElement(void* userData, const XML_Char* name) {
|
||||
auto* self = static_cast<ContainerParser*>(userData);
|
||||
|
||||
if (self->state == IN_ROOTFILES && strcmp(name, "rootfiles") == 0) {
|
||||
self->state = IN_CONTAINER;
|
||||
} else if (self->state == IN_CONTAINER && strcmp(name, "container") == 0) {
|
||||
self->state = START;
|
||||
}
|
||||
}
|
||||
32
lib/Epub/Epub/parsers/ContainerParser.h
Normal file
32
lib/Epub/Epub/parsers/ContainerParser.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include <Print.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "expat.h"
|
||||
|
||||
class ContainerParser final : public Print {
|
||||
enum ParserState {
|
||||
START,
|
||||
IN_CONTAINER,
|
||||
IN_ROOTFILES,
|
||||
};
|
||||
|
||||
size_t remainingSize;
|
||||
XML_Parser parser = nullptr;
|
||||
ParserState state = START;
|
||||
|
||||
static void startElement(void* userData, const XML_Char* name, const XML_Char** atts);
|
||||
static void endElement(void* userData, const XML_Char* name);
|
||||
|
||||
public:
|
||||
std::string fullPath;
|
||||
|
||||
explicit ContainerParser(const size_t xmlSize) : remainingSize(xmlSize) {}
|
||||
|
||||
bool setup();
|
||||
bool teardown();
|
||||
|
||||
size_t write(uint8_t) override;
|
||||
size_t write(const uint8_t* buffer, size_t size) override;
|
||||
};
|
||||
191
lib/Epub/Epub/parsers/ContentOpfParser.cpp
Normal file
191
lib/Epub/Epub/parsers/ContentOpfParser.cpp
Normal file
@ -0,0 +1,191 @@
|
||||
#include "ContentOpfParser.h"
|
||||
|
||||
#include <HardwareSerial.h>
|
||||
#include <ZipFile.h>
|
||||
|
||||
namespace {
|
||||
constexpr const char MEDIA_TYPE_NCX[] = "application/x-dtbncx+xml";
|
||||
}
|
||||
|
||||
bool ContentOpfParser::setup() {
|
||||
parser = XML_ParserCreate(nullptr);
|
||||
if (!parser) {
|
||||
Serial.printf("[%lu] [COF] Couldn't allocate memory for parser\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
XML_SetUserData(parser, this);
|
||||
XML_SetElementHandler(parser, startElement, endElement);
|
||||
XML_SetCharacterDataHandler(parser, characterData);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ContentOpfParser::teardown() {
|
||||
if (parser) {
|
||||
XML_ParserFree(parser);
|
||||
parser = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t ContentOpfParser::write(const uint8_t data) { return write(&data, 1); }
|
||||
|
||||
size_t ContentOpfParser::write(const uint8_t* buffer, const size_t size) {
|
||||
if (!parser) return 0;
|
||||
|
||||
const uint8_t* currentBufferPos = buffer;
|
||||
auto remainingInBuffer = size;
|
||||
|
||||
while (remainingInBuffer > 0) {
|
||||
void* const buf = XML_GetBuffer(parser, 1024);
|
||||
|
||||
if (!buf) {
|
||||
Serial.printf("[%lu] [COF] Couldn't allocate memory for buffer\n", millis());
|
||||
XML_ParserFree(parser);
|
||||
parser = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto toRead = remainingInBuffer < 1024 ? remainingInBuffer : 1024;
|
||||
memcpy(buf, currentBufferPos, toRead);
|
||||
|
||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
||||
Serial.printf("[%lu] [COF] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
|
||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||
XML_ParserFree(parser);
|
||||
parser = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
currentBufferPos += toRead;
|
||||
remainingInBuffer -= toRead;
|
||||
remainingSize -= toRead;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
||||
auto* self = static_cast<ContentOpfParser*>(userData);
|
||||
(void)atts;
|
||||
|
||||
if (self->state == START && (strcmp(name, "package") == 0 || strcmp(name, "opf:package") == 0)) {
|
||||
self->state = IN_PACKAGE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_PACKAGE && (strcmp(name, "metadata") == 0 || strcmp(name, "opf:metadata") == 0)) {
|
||||
self->state = IN_METADATA;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_METADATA && strcmp(name, "dc:title") == 0) {
|
||||
self->state = IN_BOOK_TITLE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_PACKAGE && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
|
||||
self->state = IN_MANIFEST;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_PACKAGE && (strcmp(name, "spine") == 0 || strcmp(name, "opf:spine") == 0)) {
|
||||
self->state = IN_SPINE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_METADATA && (strcmp(name, "meta") == 0 || strcmp(name, "opf:meta") == 0)) {
|
||||
bool isCover = false;
|
||||
std::string coverItemId;
|
||||
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "name") == 0 && strcmp(atts[i + 1], "cover") == 0) {
|
||||
isCover = true;
|
||||
} else if (strcmp(atts[i], "content") == 0) {
|
||||
coverItemId = atts[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (isCover) {
|
||||
self->coverItemId = coverItemId;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_MANIFEST && (strcmp(name, "item") == 0 || strcmp(name, "opf:item") == 0)) {
|
||||
std::string itemId;
|
||||
std::string href;
|
||||
std::string mediaType;
|
||||
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "id") == 0) {
|
||||
itemId = atts[i + 1];
|
||||
} else if (strcmp(atts[i], "href") == 0) {
|
||||
href = self->baseContentPath + atts[i + 1];
|
||||
} else if (strcmp(atts[i], "media-type") == 0) {
|
||||
mediaType = atts[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
self->items[itemId] = href;
|
||||
|
||||
if (mediaType == MEDIA_TYPE_NCX) {
|
||||
if (self->tocNcxPath.empty()) {
|
||||
self->tocNcxPath = href;
|
||||
} else {
|
||||
Serial.printf("[%lu] [COF] Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s\n", millis(),
|
||||
href.c_str());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_SPINE && (strcmp(name, "itemref") == 0 || strcmp(name, "opf:itemref") == 0)) {
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "idref") == 0) {
|
||||
self->spineRefs.emplace_back(atts[i + 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL ContentOpfParser::characterData(void* userData, const XML_Char* s, const int len) {
|
||||
auto* self = static_cast<ContentOpfParser*>(userData);
|
||||
|
||||
if (self->state == IN_BOOK_TITLE) {
|
||||
self->title.append(s, len);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL ContentOpfParser::endElement(void* userData, const XML_Char* name) {
|
||||
auto* self = static_cast<ContentOpfParser*>(userData);
|
||||
(void)name;
|
||||
|
||||
if (self->state == IN_SPINE && (strcmp(name, "spine") == 0 || strcmp(name, "opf:spine") == 0)) {
|
||||
self->state = IN_PACKAGE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_MANIFEST && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
|
||||
self->state = IN_PACKAGE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_BOOK_TITLE && strcmp(name, "dc:title") == 0) {
|
||||
self->state = IN_METADATA;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_METADATA && (strcmp(name, "metadata") == 0 || strcmp(name, "opf:metadata") == 0)) {
|
||||
self->state = IN_PACKAGE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_PACKAGE && (strcmp(name, "package") == 0 || strcmp(name, "opf:package") == 0)) {
|
||||
self->state = START;
|
||||
return;
|
||||
}
|
||||
}
|
||||
43
lib/Epub/Epub/parsers/ContentOpfParser.h
Normal file
43
lib/Epub/Epub/parsers/ContentOpfParser.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <Print.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "Epub.h"
|
||||
#include "expat.h"
|
||||
|
||||
class ContentOpfParser final : public Print {
|
||||
enum ParserState {
|
||||
START,
|
||||
IN_PACKAGE,
|
||||
IN_METADATA,
|
||||
IN_BOOK_TITLE,
|
||||
IN_MANIFEST,
|
||||
IN_SPINE,
|
||||
};
|
||||
|
||||
const std::string& baseContentPath;
|
||||
size_t remainingSize;
|
||||
XML_Parser parser = nullptr;
|
||||
ParserState state = START;
|
||||
|
||||
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 endElement(void* userData, const XML_Char* name);
|
||||
|
||||
public:
|
||||
std::string title;
|
||||
std::string tocNcxPath;
|
||||
std::string coverItemId;
|
||||
std::map<std::string, std::string> items;
|
||||
std::vector<std::string> spineRefs;
|
||||
|
||||
explicit ContentOpfParser(const std::string& baseContentPath, const size_t xmlSize)
|
||||
: baseContentPath(baseContentPath), remainingSize(xmlSize) {}
|
||||
|
||||
bool setup();
|
||||
bool teardown();
|
||||
|
||||
size_t write(uint8_t) override;
|
||||
size_t write(const uint8_t* buffer, size_t size) override;
|
||||
};
|
||||
165
lib/Epub/Epub/parsers/TocNcxParser.cpp
Normal file
165
lib/Epub/Epub/parsers/TocNcxParser.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
#include "TocNcxParser.h"
|
||||
|
||||
#include <HardwareSerial.h>
|
||||
|
||||
bool TocNcxParser::setup() {
|
||||
parser = XML_ParserCreate(nullptr);
|
||||
if (!parser) {
|
||||
Serial.printf("[%lu] [TOC] Couldn't allocate memory for parser\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
XML_SetUserData(parser, this);
|
||||
XML_SetElementHandler(parser, startElement, endElement);
|
||||
XML_SetCharacterDataHandler(parser, characterData);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TocNcxParser::teardown() {
|
||||
if (parser) {
|
||||
XML_ParserFree(parser);
|
||||
parser = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t TocNcxParser::write(const uint8_t data) { return write(&data, 1); }
|
||||
|
||||
size_t TocNcxParser::write(const uint8_t* buffer, const size_t size) {
|
||||
if (!parser) return 0;
|
||||
|
||||
const uint8_t* currentBufferPos = buffer;
|
||||
auto remainingInBuffer = size;
|
||||
|
||||
while (remainingInBuffer > 0) {
|
||||
void* const buf = XML_GetBuffer(parser, 1024);
|
||||
if (!buf) {
|
||||
Serial.printf("[%lu] [TOC] Couldn't allocate memory for buffer\n", millis());
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto toRead = remainingInBuffer < 1024 ? remainingInBuffer : 1024;
|
||||
memcpy(buf, currentBufferPos, toRead);
|
||||
|
||||
if (XML_ParseBuffer(parser, static_cast<int>(toRead), remainingSize == toRead) == XML_STATUS_ERROR) {
|
||||
Serial.printf("[%lu] [TOC] Parse error at line %lu: %s\n", millis(), XML_GetCurrentLineNumber(parser),
|
||||
XML_ErrorString(XML_GetErrorCode(parser)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
currentBufferPos += toRead;
|
||||
remainingInBuffer -= toRead;
|
||||
remainingSize -= toRead;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void XMLCALL TocNcxParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
||||
// NOTE: We rely on navPoint label and content coming before any nested navPoints, this will be fine:
|
||||
// <navPoint>
|
||||
// <navLabel><text>Chapter 1</text></navLabel>
|
||||
// <content src="ch1.html"/>
|
||||
// <navPoint> ...nested... </navPoint>
|
||||
// </navPoint>
|
||||
//
|
||||
// This will NOT:
|
||||
// <navPoint>
|
||||
// <navPoint> ...nested... </navPoint>
|
||||
// <navLabel><text>Chapter 1</text></navLabel>
|
||||
// <content src="ch1.html"/>
|
||||
// </navPoint>
|
||||
|
||||
auto* self = static_cast<TocNcxParser*>(userData);
|
||||
|
||||
if (self->state == START && strcmp(name, "ncx") == 0) {
|
||||
self->state = IN_NCX;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_NCX && strcmp(name, "navMap") == 0) {
|
||||
self->state = IN_NAV_MAP;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handles both top-level and nested navPoints
|
||||
if ((self->state == IN_NAV_MAP || self->state == IN_NAV_POINT) && strcmp(name, "navPoint") == 0) {
|
||||
self->state = IN_NAV_POINT;
|
||||
self->currentDepth++;
|
||||
|
||||
self->currentLabel.clear();
|
||||
self->currentSrc.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_NAV_POINT && strcmp(name, "navLabel") == 0) {
|
||||
self->state = IN_NAV_LABEL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_NAV_LABEL && strcmp(name, "text") == 0) {
|
||||
self->state = IN_NAV_LABEL_TEXT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_NAV_POINT && strcmp(name, "content") == 0) {
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "src") == 0) {
|
||||
self->currentSrc = atts[i + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL TocNcxParser::characterData(void* userData, const XML_Char* s, const int len) {
|
||||
auto* self = static_cast<TocNcxParser*>(userData);
|
||||
if (self->state == IN_NAV_LABEL_TEXT) {
|
||||
self->currentLabel.append(s, len);
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL TocNcxParser::endElement(void* userData, const XML_Char* name) {
|
||||
auto* self = static_cast<TocNcxParser*>(userData);
|
||||
|
||||
if (self->state == IN_NAV_LABEL_TEXT && strcmp(name, "text") == 0) {
|
||||
self->state = IN_NAV_LABEL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_NAV_LABEL && strcmp(name, "navLabel") == 0) {
|
||||
self->state = IN_NAV_POINT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_NAV_POINT && strcmp(name, "navPoint") == 0) {
|
||||
self->currentDepth--;
|
||||
if (self->currentDepth == 0) {
|
||||
self->state = IN_NAV_MAP;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_NAV_POINT && strcmp(name, "content") == 0) {
|
||||
// At this point (end of content tag), we likely have both Label (from previous tags) and Src.
|
||||
// This is the safest place to push the data, assuming <navLabel> always comes before <content>.
|
||||
// NCX spec says navLabel comes before content.
|
||||
if (!self->currentLabel.empty() && !self->currentSrc.empty()) {
|
||||
std::string href = self->baseContentPath + self->currentSrc;
|
||||
std::string anchor;
|
||||
|
||||
const size_t pos = href.find('#');
|
||||
if (pos != std::string::npos) {
|
||||
anchor = href.substr(pos + 1);
|
||||
href = href.substr(0, pos);
|
||||
}
|
||||
|
||||
// Push to vector
|
||||
self->toc.emplace_back(self->currentLabel, href, anchor, self->currentDepth);
|
||||
|
||||
// Clear them so we don't re-add them if there are weird XML structures
|
||||
self->currentLabel.clear();
|
||||
self->currentSrc.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
37
lib/Epub/Epub/parsers/TocNcxParser.h
Normal file
37
lib/Epub/Epub/parsers/TocNcxParser.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include <Print.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Epub/EpubTocEntry.h"
|
||||
#include "expat.h"
|
||||
|
||||
class TocNcxParser final : public Print {
|
||||
enum ParserState { START, IN_NCX, IN_NAV_MAP, IN_NAV_POINT, IN_NAV_LABEL, IN_NAV_LABEL_TEXT, IN_CONTENT };
|
||||
|
||||
const std::string& baseContentPath;
|
||||
size_t remainingSize;
|
||||
XML_Parser parser = nullptr;
|
||||
ParserState state = START;
|
||||
|
||||
std::string currentLabel;
|
||||
std::string currentSrc;
|
||||
size_t currentDepth = 0;
|
||||
|
||||
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 endElement(void* userData, const XML_Char* name);
|
||||
|
||||
public:
|
||||
std::vector<EpubTocEntry> toc;
|
||||
|
||||
explicit TocNcxParser(const std::string& baseContentPath, const size_t xmlSize)
|
||||
: baseContentPath(baseContentPath), remainingSize(xmlSize) {}
|
||||
|
||||
bool setup();
|
||||
bool teardown();
|
||||
|
||||
size_t write(uint8_t) override;
|
||||
size_t write(const uint8_t* buffer, size_t size) override;
|
||||
};
|
||||
329
lib/GfxRenderer/GfxRenderer.cpp
Normal file
329
lib/GfxRenderer/GfxRenderer.cpp
Normal file
@ -0,0 +1,329 @@
|
||||
#include "GfxRenderer.h"
|
||||
|
||||
#include <Utf8.h>
|
||||
|
||||
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
||||
|
||||
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
||||
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||
|
||||
// Early return if no framebuffer is set
|
||||
if (!frameBuffer) {
|
||||
Serial.printf("[%lu] [GFX] !! No framebuffer\n", millis());
|
||||
return;
|
||||
}
|
||||
|
||||
// Rotate coordinates: portrait (480x800) -> landscape (800x480)
|
||||
// Rotation: 90 degrees clockwise
|
||||
const int rotatedX = y;
|
||||
const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
||||
|
||||
// Bounds checking (portrait: 480x800)
|
||||
if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 ||
|
||||
rotatedY >= EInkDisplay::DISPLAY_HEIGHT) {
|
||||
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d)\n", millis(), x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate byte position and bit position
|
||||
const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
|
||||
const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first
|
||||
|
||||
if (state) {
|
||||
frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit
|
||||
} else {
|
||||
frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit
|
||||
}
|
||||
}
|
||||
|
||||
int GfxRenderer::getTextWidth(const int fontId, const char* text, const EpdFontStyle style) const {
|
||||
if (fontMap.count(fontId) == 0) {
|
||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int w = 0, h = 0;
|
||||
fontMap.at(fontId).getTextDimensions(text, &w, &h, style);
|
||||
return w;
|
||||
}
|
||||
|
||||
void GfxRenderer::drawCenteredText(const int fontId, const int y, const char* text, const bool black,
|
||||
const EpdFontStyle style) const {
|
||||
const int x = (getScreenWidth() - getTextWidth(fontId, text, style)) / 2;
|
||||
drawText(fontId, x, y, text, black, style);
|
||||
}
|
||||
|
||||
void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black,
|
||||
const EpdFontStyle style) const {
|
||||
const int yPos = y + getLineHeight(fontId);
|
||||
int xpos = x;
|
||||
|
||||
// cannot draw a NULL / empty string
|
||||
if (text == nullptr || *text == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fontMap.count(fontId) == 0) {
|
||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
||||
return;
|
||||
}
|
||||
const auto font = fontMap.at(fontId);
|
||||
|
||||
// no printable characters
|
||||
if (!font.hasPrintableChars(text, style)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t cp;
|
||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||
renderChar(font, cp, &xpos, &yPos, black, style);
|
||||
}
|
||||
}
|
||||
|
||||
void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const {
|
||||
if (x1 == x2) {
|
||||
if (y2 < y1) {
|
||||
std::swap(y1, y2);
|
||||
}
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
drawPixel(x1, y, state);
|
||||
}
|
||||
} else if (y1 == y2) {
|
||||
if (x2 < x1) {
|
||||
std::swap(x1, x2);
|
||||
}
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
drawPixel(x, y1, state);
|
||||
}
|
||||
} else {
|
||||
// TODO: Implement
|
||||
Serial.printf("[%lu] [GFX] Line drawing not supported\n", millis());
|
||||
}
|
||||
}
|
||||
|
||||
void GfxRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||
drawLine(x, y, x + width - 1, y, state);
|
||||
drawLine(x + width - 1, y, x + width - 1, y + height - 1, state);
|
||||
drawLine(x + width - 1, y + height - 1, x, y + height - 1, state);
|
||||
drawLine(x, y, x, y + height - 1, state);
|
||||
}
|
||||
|
||||
void GfxRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||
for (int fillY = y; fillY < y + height; fillY++) {
|
||||
drawLine(x, fillY, x + width - 1, fillY, state);
|
||||
}
|
||||
}
|
||||
|
||||
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
||||
// Flip X and Y for portrait mode
|
||||
einkDisplay.drawImage(bitmap, y, x, height, width);
|
||||
}
|
||||
|
||||
void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); }
|
||||
|
||||
void GfxRenderer::invertScreen() const {
|
||||
uint8_t* buffer = einkDisplay.getFrameBuffer();
|
||||
for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) {
|
||||
buffer[i] = ~buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
void GfxRenderer::displayBuffer(const EInkDisplay::RefreshMode refreshMode) const {
|
||||
einkDisplay.displayBuffer(refreshMode);
|
||||
}
|
||||
|
||||
void GfxRenderer::displayWindow(const int x, const int y, const int width, const int height) const {
|
||||
// Rotate coordinates from portrait (480x800) to landscape (800x480)
|
||||
// Rotation: 90 degrees clockwise
|
||||
// Portrait coordinates: (x, y) with dimensions (width, height)
|
||||
// Landscape coordinates: (rotatedX, rotatedY) with dimensions (rotatedWidth, rotatedHeight)
|
||||
|
||||
const int rotatedX = y;
|
||||
const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x - width + 1;
|
||||
const int rotatedWidth = height;
|
||||
const int rotatedHeight = width;
|
||||
|
||||
einkDisplay.displayWindow(rotatedX, rotatedY, rotatedWidth, rotatedHeight);
|
||||
}
|
||||
|
||||
// Note: Internal driver treats screen in command orientation, this library treats in portrait orientation
|
||||
int GfxRenderer::getScreenWidth() { return EInkDisplay::DISPLAY_HEIGHT; }
|
||||
int GfxRenderer::getScreenHeight() { return EInkDisplay::DISPLAY_WIDTH; }
|
||||
|
||||
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
||||
if (fontMap.count(fontId) == 0) {
|
||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return fontMap.at(fontId).getGlyph(' ', REGULAR)->advanceX;
|
||||
}
|
||||
|
||||
int GfxRenderer::getLineHeight(const int fontId) const {
|
||||
if (fontMap.count(fontId) == 0) {
|
||||
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return fontMap.at(fontId).getData(REGULAR)->advanceY;
|
||||
}
|
||||
|
||||
uint8_t* GfxRenderer::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
|
||||
|
||||
size_t GfxRenderer::getBufferSize() { return EInkDisplay::BUFFER_SIZE; }
|
||||
|
||||
void GfxRenderer::grayscaleRevert() const { einkDisplay.grayscaleRevert(); }
|
||||
|
||||
void GfxRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); }
|
||||
|
||||
void GfxRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); }
|
||||
|
||||
void GfxRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }
|
||||
|
||||
void GfxRenderer::freeBwBufferChunks() {
|
||||
for (auto& bwBufferChunk : bwBufferChunks) {
|
||||
if (bwBufferChunk) {
|
||||
free(bwBufferChunk);
|
||||
bwBufferChunk = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be called before grayscale buffers are populated.
|
||||
* A `restoreBwBuffer` call should always follow the grayscale render if this method was called.
|
||||
* Uses chunked allocation to avoid needing 48KB of contiguous memory.
|
||||
*/
|
||||
void GfxRenderer::storeBwBuffer() {
|
||||
const uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||
|
||||
// Allocate and copy each chunk
|
||||
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
||||
// Check if any chunks are already allocated
|
||||
if (bwBufferChunks[i]) {
|
||||
Serial.printf("[%lu] [GFX] !! BW buffer chunk %zu already stored - this is likely a bug, freeing chunk\n",
|
||||
millis(), i);
|
||||
free(bwBufferChunks[i]);
|
||||
bwBufferChunks[i] = nullptr;
|
||||
}
|
||||
|
||||
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
||||
bwBufferChunks[i] = static_cast<uint8_t*>(malloc(BW_BUFFER_CHUNK_SIZE));
|
||||
|
||||
if (!bwBufferChunks[i]) {
|
||||
Serial.printf("[%lu] [GFX] !! Failed to allocate BW buffer chunk %zu (%zu bytes)\n", millis(), i,
|
||||
BW_BUFFER_CHUNK_SIZE);
|
||||
// Free previously allocated chunks
|
||||
freeBwBufferChunks();
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(bwBufferChunks[i], frameBuffer + offset, BW_BUFFER_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [GFX] Stored BW buffer in %zu chunks (%zu bytes each)\n", millis(), BW_BUFFER_NUM_CHUNKS,
|
||||
BW_BUFFER_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This can only be called if `storeBwBuffer` was called prior to the grayscale render.
|
||||
* It should be called to restore the BW buffer state after grayscale rendering is complete.
|
||||
* Uses chunked restoration to match chunked storage.
|
||||
*/
|
||||
void GfxRenderer::restoreBwBuffer() {
|
||||
// Check if any all chunks are allocated
|
||||
bool missingChunks = false;
|
||||
for (const auto& bwBufferChunk : bwBufferChunks) {
|
||||
if (!bwBufferChunk) {
|
||||
missingChunks = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (missingChunks) {
|
||||
freeBwBufferChunks();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
||||
// Check if chunk is missing
|
||||
if (!bwBufferChunks[i]) {
|
||||
Serial.printf("[%lu] [GFX] !! BW buffer chunks not stored - this is likely a bug\n", millis());
|
||||
freeBwBufferChunks();
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
||||
memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
einkDisplay.cleanupGrayscaleBuffers(frameBuffer);
|
||||
|
||||
freeBwBufferChunks();
|
||||
Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis());
|
||||
}
|
||||
|
||||
void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y,
|
||||
const bool pixelState, const EpdFontStyle style) const {
|
||||
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
|
||||
if (!glyph) {
|
||||
// TODO: Replace with fallback glyph property?
|
||||
glyph = fontFamily.getGlyph('?', style);
|
||||
}
|
||||
|
||||
// no glyph?
|
||||
if (!glyph) {
|
||||
Serial.printf("[%lu] [GFX] No glyph for codepoint %d\n", millis(), cp);
|
||||
return;
|
||||
}
|
||||
|
||||
const int is2Bit = fontFamily.getData(style)->is2Bit;
|
||||
const uint32_t offset = glyph->dataOffset;
|
||||
const uint8_t width = glyph->width;
|
||||
const uint8_t height = glyph->height;
|
||||
const int left = glyph->left;
|
||||
|
||||
const uint8_t* bitmap = nullptr;
|
||||
bitmap = &fontFamily.getData(style)->bitmap[offset];
|
||||
|
||||
if (bitmap != nullptr) {
|
||||
for (int glyphY = 0; glyphY < height; glyphY++) {
|
||||
const int screenY = *y - glyph->top + glyphY;
|
||||
for (int glyphX = 0; glyphX < width; glyphX++) {
|
||||
const int pixelPosition = glyphY * width + glyphX;
|
||||
const int screenX = *x + left + glyphX;
|
||||
|
||||
if (is2Bit) {
|
||||
const uint8_t byte = bitmap[pixelPosition / 4];
|
||||
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
|
||||
// the direct bit from the font is 0 -> white, 1 -> light gray, 2 -> dark gray, 3 -> black
|
||||
// we swap this to better match the way images and screen think about colors:
|
||||
// 0 -> black, 1 -> dark grey, 2 -> light grey, 3 -> white
|
||||
const uint8_t bmpVal = 3 - (byte >> bit_index) & 0x3;
|
||||
|
||||
if (renderMode == BW && bmpVal < 3) {
|
||||
// Black (also paints over the grays in BW mode)
|
||||
drawPixel(screenX, screenY, pixelState);
|
||||
} else if (renderMode == GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
|
||||
// Light gray (also mark the MSB if it's going to be a dark gray too)
|
||||
// We have to flag pixels in reverse for the gray buffers, as 0 leave alone, 1 update
|
||||
drawPixel(screenX, screenY, false);
|
||||
} else if (renderMode == GRAYSCALE_LSB && bmpVal == 1) {
|
||||
// Dark gray
|
||||
drawPixel(screenX, screenY, false);
|
||||
}
|
||||
} else {
|
||||
const uint8_t byte = bitmap[pixelPosition / 8];
|
||||
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
||||
|
||||
if ((byte >> bit_index) & 1) {
|
||||
drawPixel(screenX, screenY, pixelState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*x += glyph->advanceX;
|
||||
}
|
||||
68
lib/GfxRenderer/GfxRenderer.h
Normal file
68
lib/GfxRenderer/GfxRenderer.h
Normal file
@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <EInkDisplay.h>
|
||||
#include <EpdFontFamily.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
class GfxRenderer {
|
||||
public:
|
||||
enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
||||
|
||||
private:
|
||||
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory
|
||||
static constexpr size_t BW_BUFFER_NUM_CHUNKS = EInkDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
||||
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == EInkDisplay::BUFFER_SIZE,
|
||||
"BW buffer chunking does not line up with display buffer size");
|
||||
|
||||
EInkDisplay& einkDisplay;
|
||||
RenderMode renderMode;
|
||||
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
||||
std::map<int, EpdFontFamily> fontMap;
|
||||
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
||||
EpdFontStyle style) const;
|
||||
void freeBwBufferChunks();
|
||||
|
||||
public:
|
||||
explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW) {}
|
||||
~GfxRenderer() = default;
|
||||
|
||||
// Setup
|
||||
void insertFont(int fontId, EpdFontFamily font);
|
||||
|
||||
// Screen ops
|
||||
static int getScreenWidth();
|
||||
static int getScreenHeight();
|
||||
void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
||||
// EXPERIMENTAL: Windowed update - display only a rectangular region (portrait coordinates)
|
||||
void displayWindow(int x, int y, int width, int height) const;
|
||||
void invertScreen() const;
|
||||
void clearScreen(uint8_t color = 0xFF) const;
|
||||
|
||||
// Drawing
|
||||
void drawPixel(int x, int y, bool state = true) const;
|
||||
void drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
|
||||
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
||||
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
||||
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||
|
||||
// Text
|
||||
int getTextWidth(int fontId, const char* text, EpdFontStyle style = REGULAR) const;
|
||||
void drawCenteredText(int fontId, int y, const char* text, bool black = true, EpdFontStyle style = REGULAR) const;
|
||||
void drawText(int fontId, int x, int y, const char* text, bool black = true, EpdFontStyle style = REGULAR) const;
|
||||
int getSpaceWidth(int fontId) const;
|
||||
int getLineHeight(int fontId) const;
|
||||
|
||||
// Grayscale functions
|
||||
void setRenderMode(const RenderMode mode) { this->renderMode = mode; }
|
||||
void copyGrayscaleLsbBuffers() const;
|
||||
void copyGrayscaleMsbBuffers() const;
|
||||
void displayGrayBuffer() const;
|
||||
void storeBwBuffer();
|
||||
void restoreBwBuffer();
|
||||
|
||||
// Low level functions
|
||||
uint8_t* getFrameBuffer() const;
|
||||
static size_t getBufferSize();
|
||||
void grayscaleRevert() const;
|
||||
};
|
||||
@ -3,93 +3,107 @@
|
||||
#include <HardwareSerial.h>
|
||||
#include <miniz.h>
|
||||
|
||||
int libzInflateOneShot(const uint8_t* inputBuff, const size_t compSize, uint8_t* outputBuff, const size_t uncompSize) {
|
||||
mz_stream pStream = {
|
||||
.next_in = inputBuff,
|
||||
.avail_in = compSize,
|
||||
.total_in = 0,
|
||||
.next_out = outputBuff,
|
||||
.avail_out = uncompSize,
|
||||
.total_out = 0,
|
||||
};
|
||||
bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf, const size_t inflatedSize) {
|
||||
// Setup inflator
|
||||
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||
if (!inflator) {
|
||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for inflator\n", millis());
|
||||
return false;
|
||||
}
|
||||
memset(inflator, 0, sizeof(tinfl_decompressor));
|
||||
tinfl_init(inflator);
|
||||
|
||||
int status = 0;
|
||||
status = mz_inflateInit2(&pStream, -MZ_DEFAULT_WINDOW_BITS);
|
||||
size_t inBytes = deflatedSize;
|
||||
size_t outBytes = inflatedSize;
|
||||
const tinfl_status status = tinfl_decompress(inflator, inputBuf, &inBytes, nullptr, outputBuf, &outBytes,
|
||||
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
|
||||
free(inflator);
|
||||
|
||||
if (status != MZ_OK) {
|
||||
Serial.printf("inflateInit2 failed: %d\n", status);
|
||||
return status;
|
||||
if (status != TINFL_STATUS_DONE) {
|
||||
Serial.printf("[%lu] [ZIP] tinfl_decompress() failed with status %d\n", millis(), status);
|
||||
return false;
|
||||
}
|
||||
|
||||
status = mz_inflate(&pStream, MZ_FINISH);
|
||||
if (status != MZ_STREAM_END) {
|
||||
Serial.printf("inflate failed: %d\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
status = mz_inflateEnd(&pStream);
|
||||
if (status != MZ_OK) {
|
||||
Serial.printf("inflateEnd failed: %d\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
return status;
|
||||
return true;
|
||||
}
|
||||
|
||||
char* ZipFile::readTextFileToMemory(const char* filename, size_t* size) const {
|
||||
const auto data = readFileToMemory(filename, size, true);
|
||||
return data ? reinterpret_cast<char*>(data) : nullptr;
|
||||
}
|
||||
|
||||
uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, bool trailingNullByte) const {
|
||||
bool ZipFile::loadFileStat(const char* filename, mz_zip_archive_file_stat* fileStat) const {
|
||||
mz_zip_archive zipArchive = {};
|
||||
const bool status = mz_zip_reader_init_file(&zipArchive, filePath.c_str(), 0);
|
||||
|
||||
if (!status) {
|
||||
Serial.printf("mz_zip_reader_init_file() failed!\nError %s\n", mz_zip_get_error_string(zipArchive.m_last_error));
|
||||
return nullptr;
|
||||
Serial.printf("[%lu] [ZIP] mz_zip_reader_init_file() failed! Error: %s\n", millis(),
|
||||
mz_zip_get_error_string(zipArchive.m_last_error));
|
||||
return false;
|
||||
}
|
||||
|
||||
// find the file
|
||||
mz_uint32 fileIndex = 0;
|
||||
if (!mz_zip_reader_locate_file_v2(&zipArchive, filename, nullptr, 0, &fileIndex)) {
|
||||
Serial.printf("Could not find file %s\n", filename);
|
||||
Serial.printf("[%lu] [ZIP] Could not find file %s\n", millis(), filename);
|
||||
mz_zip_reader_end(&zipArchive);
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
mz_zip_archive_file_stat fileStat;
|
||||
if (!mz_zip_reader_file_stat(&zipArchive, fileIndex, &fileStat)) {
|
||||
Serial.printf("mz_zip_reader_file_stat() failed!\nError %s\n", mz_zip_get_error_string(zipArchive.m_last_error));
|
||||
if (!mz_zip_reader_file_stat(&zipArchive, fileIndex, fileStat)) {
|
||||
Serial.printf("[%lu] [ZIP] mz_zip_reader_file_stat() failed! Error: %s\n", millis(),
|
||||
mz_zip_get_error_string(zipArchive.m_last_error));
|
||||
mz_zip_reader_end(&zipArchive);
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
mz_zip_reader_end(&zipArchive);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t pLocalHeader[30];
|
||||
uint64_t fileOffset = fileStat.m_local_header_ofs;
|
||||
long ZipFile::getDataOffset(const mz_zip_archive_file_stat& fileStat) const {
|
||||
constexpr auto localHeaderSize = 30;
|
||||
|
||||
// Reopen the file to manual read out delated bytes
|
||||
FILE* file = fopen(filePath.c_str(), "rb");
|
||||
uint8_t pLocalHeader[localHeaderSize];
|
||||
const uint64_t fileOffset = fileStat.m_local_header_ofs;
|
||||
|
||||
FILE* file = fopen(filePath.c_str(), "r");
|
||||
fseek(file, fileOffset, SEEK_SET);
|
||||
const size_t read = fread(pLocalHeader, 1, localHeaderSize, file);
|
||||
fclose(file);
|
||||
|
||||
const size_t read = fread(pLocalHeader, 1, 30, file);
|
||||
if (read != 30) {
|
||||
Serial.println("Something went wrong reading the local header");
|
||||
fclose(file);
|
||||
return nullptr;
|
||||
if (read != localHeaderSize) {
|
||||
Serial.printf("[%lu] [ZIP] Something went wrong reading the local header\n", millis());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pLocalHeader[0] + (pLocalHeader[1] << 8) + (pLocalHeader[2] << 16) + (pLocalHeader[3] << 24) !=
|
||||
0x04034b50 /* MZ_ZIP_LOCAL_DIR_HEADER_SIG */) {
|
||||
Serial.println("Not a valid zip file header");
|
||||
fclose(file);
|
||||
return nullptr;
|
||||
Serial.printf("[%lu] [ZIP] Not a valid zip file header\n", millis());
|
||||
return -1;
|
||||
}
|
||||
|
||||
const uint16_t filenameLength = pLocalHeader[26] + (pLocalHeader[27] << 8);
|
||||
const uint16_t extraOffset = pLocalHeader[28] + (pLocalHeader[29] << 8);
|
||||
fileOffset += 30 + filenameLength + extraOffset;
|
||||
return fileOffset + localHeaderSize + filenameLength + extraOffset;
|
||||
}
|
||||
|
||||
bool ZipFile::getInflatedFileSize(const char* filename, size_t* size) const {
|
||||
mz_zip_archive_file_stat fileStat;
|
||||
if (!loadFileStat(filename, &fileStat)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*size = static_cast<size_t>(fileStat.m_uncomp_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const bool trailingNullByte) const {
|
||||
mz_zip_archive_file_stat fileStat;
|
||||
if (!loadFileStat(filename, &fileStat)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const long fileOffset = getDataOffset(fileStat);
|
||||
if (fileOffset < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FILE* file = fopen(filePath.c_str(), "rb");
|
||||
fseek(file, fileOffset, SEEK_SET);
|
||||
|
||||
const auto deflatedDataSize = static_cast<size_t>(fileStat.m_comp_size);
|
||||
@ -97,44 +111,214 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, bool trai
|
||||
const auto dataSize = trailingNullByte ? inflatedDataSize + 1 : inflatedDataSize;
|
||||
const auto data = static_cast<uint8_t*>(malloc(dataSize));
|
||||
|
||||
if (!fileStat.m_method) {
|
||||
if (fileStat.m_method == MZ_NO_COMPRESSION) {
|
||||
// no deflation, just read content
|
||||
const size_t dataRead = fread(data, 1, inflatedDataSize, file);
|
||||
fclose(file);
|
||||
|
||||
if (dataRead != inflatedDataSize) {
|
||||
Serial.println("Failed to read data");
|
||||
Serial.printf("[%lu] [ZIP] Failed to read data\n", millis());
|
||||
free(data);
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
|
||||
// Continue out of block with data set
|
||||
} else if (fileStat.m_method == MZ_DEFLATED) {
|
||||
// Read out deflated content from file
|
||||
const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize));
|
||||
if (deflatedData == nullptr) {
|
||||
Serial.println("Failed to allocate memory for decompression buffer");
|
||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for decompression buffer\n", millis());
|
||||
fclose(file);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const size_t dataRead = fread(deflatedData, 1, deflatedDataSize, file);
|
||||
fclose(file);
|
||||
|
||||
if (dataRead != deflatedDataSize) {
|
||||
Serial.printf("Failed to read data, expected %d got %d\n", deflatedDataSize, dataRead);
|
||||
Serial.printf("[%lu] [ZIP] Failed to read data, expected %d got %d\n", millis(), deflatedDataSize, dataRead);
|
||||
free(deflatedData);
|
||||
free(data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int result = libzInflateOneShot(deflatedData, deflatedDataSize, data, inflatedDataSize);
|
||||
bool success = inflateOneShot(deflatedData, deflatedDataSize, data, inflatedDataSize);
|
||||
free(deflatedData);
|
||||
if (result != MZ_OK) {
|
||||
Serial.println("Failed to inflate file");
|
||||
|
||||
if (!success) {
|
||||
Serial.printf("[%lu] [ZIP] Failed to inflate file\n", millis());
|
||||
free(data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Continue out of block with data set
|
||||
} else {
|
||||
Serial.printf("[%lu] [ZIP] Unsupported compression method\n", millis());
|
||||
fclose(file);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (trailingNullByte) {
|
||||
data[inflatedDataSize] = '\0';
|
||||
}
|
||||
if (size) {
|
||||
*size = inflatedDataSize;
|
||||
}
|
||||
if (trailingNullByte) data[inflatedDataSize] = '\0';
|
||||
if (size) *size = inflatedDataSize;
|
||||
return data;
|
||||
}
|
||||
|
||||
bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t chunkSize) const {
|
||||
mz_zip_archive_file_stat fileStat;
|
||||
if (!loadFileStat(filename, &fileStat)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const long fileOffset = getDataOffset(fileStat);
|
||||
if (fileOffset < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FILE* file = fopen(filePath.c_str(), "rb");
|
||||
fseek(file, fileOffset, SEEK_SET);
|
||||
|
||||
const auto deflatedDataSize = static_cast<size_t>(fileStat.m_comp_size);
|
||||
const auto inflatedDataSize = static_cast<size_t>(fileStat.m_uncomp_size);
|
||||
|
||||
if (fileStat.m_method == MZ_NO_COMPRESSION) {
|
||||
// no deflation, just read content
|
||||
const auto buffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
if (!buffer) {
|
||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for buffer\n", millis());
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t remaining = inflatedDataSize;
|
||||
while (remaining > 0) {
|
||||
const size_t dataRead = fread(buffer, 1, remaining < chunkSize ? remaining : chunkSize, file);
|
||||
if (dataRead == 0) {
|
||||
Serial.printf("[%lu] [ZIP] Could not read more bytes\n", millis());
|
||||
free(buffer);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
out.write(buffer, dataRead);
|
||||
remaining -= dataRead;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
free(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fileStat.m_method == MZ_DEFLATED) {
|
||||
// Setup inflator
|
||||
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||
if (!inflator) {
|
||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for inflator\n", millis());
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
memset(inflator, 0, sizeof(tinfl_decompressor));
|
||||
tinfl_init(inflator);
|
||||
|
||||
// Setup file read buffer
|
||||
const auto fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
if (!fileReadBuffer) {
|
||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for zip file read buffer\n", millis());
|
||||
free(inflator);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto outputBuffer = static_cast<uint8_t*>(malloc(TINFL_LZ_DICT_SIZE));
|
||||
if (!outputBuffer) {
|
||||
Serial.printf("[%lu] [ZIP] Failed to allocate memory for dictionary\n", millis());
|
||||
free(inflator);
|
||||
free(fileReadBuffer);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
memset(outputBuffer, 0, TINFL_LZ_DICT_SIZE);
|
||||
|
||||
size_t fileRemainingBytes = deflatedDataSize;
|
||||
size_t processedOutputBytes = 0;
|
||||
size_t fileReadBufferFilledBytes = 0;
|
||||
size_t fileReadBufferCursor = 0;
|
||||
size_t outputCursor = 0; // Current offset in the circular dictionary
|
||||
|
||||
while (true) {
|
||||
// Load more compressed bytes when needed
|
||||
if (fileReadBufferCursor >= fileReadBufferFilledBytes) {
|
||||
if (fileRemainingBytes == 0) {
|
||||
// Should not be hit, but a safe protection
|
||||
break; // EOF
|
||||
}
|
||||
|
||||
fileReadBufferFilledBytes =
|
||||
fread(fileReadBuffer, 1, fileRemainingBytes < chunkSize ? fileRemainingBytes : chunkSize, file);
|
||||
fileRemainingBytes -= fileReadBufferFilledBytes;
|
||||
fileReadBufferCursor = 0;
|
||||
|
||||
if (fileReadBufferFilledBytes == 0) {
|
||||
// Bad read
|
||||
break; // EOF
|
||||
}
|
||||
}
|
||||
|
||||
// Available bytes in fileReadBuffer to process
|
||||
size_t inBytes = fileReadBufferFilledBytes - fileReadBufferCursor;
|
||||
// Space remaining in outputBuffer
|
||||
size_t outBytes = TINFL_LZ_DICT_SIZE - outputCursor;
|
||||
|
||||
const tinfl_status status = tinfl_decompress(inflator, fileReadBuffer + fileReadBufferCursor, &inBytes,
|
||||
outputBuffer, outputBuffer + outputCursor, &outBytes,
|
||||
fileRemainingBytes > 0 ? TINFL_FLAG_HAS_MORE_INPUT : 0);
|
||||
|
||||
// Update input position
|
||||
fileReadBufferCursor += inBytes;
|
||||
|
||||
// Write output chunk
|
||||
if (outBytes > 0) {
|
||||
processedOutputBytes += outBytes;
|
||||
if (out.write(outputBuffer + outputCursor, outBytes) != outBytes) {
|
||||
Serial.printf("[%lu] [ZIP] Failed to write all output bytes to stream\n", millis());
|
||||
fclose(file);
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
free(inflator);
|
||||
return false;
|
||||
}
|
||||
// Update output position in buffer (with wraparound)
|
||||
outputCursor = (outputCursor + outBytes) & (TINFL_LZ_DICT_SIZE - 1);
|
||||
}
|
||||
|
||||
if (status < 0) {
|
||||
Serial.printf("[%lu] [ZIP] tinfl_decompress() failed with status %d\n", millis(), status);
|
||||
fclose(file);
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
free(inflator);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status == TINFL_STATUS_DONE) {
|
||||
Serial.printf("[%lu] [ZIP] Decompressed %d bytes into %d bytes\n", millis(), deflatedDataSize,
|
||||
inflatedDataSize);
|
||||
fclose(file);
|
||||
free(inflator);
|
||||
free(fileReadBuffer);
|
||||
free(outputBuffer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, EOF reached without TINFL_STATUS_DONE
|
||||
Serial.printf("[%lu] [ZIP] Unexpected EOF\n", millis());
|
||||
fclose(file);
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
free(inflator);
|
||||
return false;
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [ZIP] Unsupported compression method\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
#pragma once
|
||||
#include <Print.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "miniz.h"
|
||||
|
||||
class ZipFile {
|
||||
std::string filePath;
|
||||
bool loadFileStat(const char* filename, mz_zip_archive_file_stat* fileStat) const;
|
||||
long getDataOffset(const mz_zip_archive_file_stat& fileStat) const;
|
||||
|
||||
public:
|
||||
explicit ZipFile(std::string filePath) : filePath(std::move(filePath)) {}
|
||||
~ZipFile() = default;
|
||||
char* readTextFileToMemory(const char* filename, size_t* size = nullptr) const;
|
||||
bool getInflatedFileSize(const char* filename, size_t* size) const;
|
||||
uint8_t* readFileToMemory(const char* filename, size_t* size = nullptr, bool trailingNullByte = false) const;
|
||||
bool readFileToStream(const char* filename, Print& out, size_t chunkSize) const;
|
||||
};
|
||||
|
||||
17
lib/expat/.gitignore
vendored
Normal file
17
lib/expat/.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
Makefile
|
||||
.libs
|
||||
*.lo
|
||||
Debug
|
||||
Debug-w
|
||||
Release
|
||||
Release-w
|
||||
expat.ncb
|
||||
expat.opt
|
||||
expat.plg
|
||||
Debug_static
|
||||
Debug-w_static
|
||||
Release_static
|
||||
Release-w_static
|
||||
expat_static.plg
|
||||
expatw.plg
|
||||
expatw_static.plg
|
||||
87
lib/expat/Makefile.am
Normal file
87
lib/expat/Makefile.am
Normal file
@ -0,0 +1,87 @@
|
||||
#
|
||||
# __ __ _
|
||||
# ___\ \/ /_ __ __ _| |_
|
||||
# / _ \\ /| '_ \ / _` | __|
|
||||
# | __// \| |_) | (_| | |_
|
||||
# \___/_/\_\ .__/ \__,_|\__|
|
||||
# |_| XML parser
|
||||
#
|
||||
# Copyright (c) 2017-2024 Sebastian Pipping <sebastian@pipping.org>
|
||||
# Copyright (c) 2017 Tomasz Kłoczko <kloczek@fedoraproject.org>
|
||||
# Copyright (c) 2019 David Loffredo <loffredo@steptools.com>
|
||||
# Licensed under the MIT license:
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the
|
||||
# following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
# USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
include_HEADERS = \
|
||||
../expat_config.h \
|
||||
expat.h \
|
||||
expat_external.h
|
||||
|
||||
lib_LTLIBRARIES = libexpat.la
|
||||
if WITH_TESTS
|
||||
noinst_LTLIBRARIES = libtestpat.la
|
||||
endif
|
||||
|
||||
libexpat_la_LDFLAGS = \
|
||||
@AM_LDFLAGS@ \
|
||||
@LIBM@ \
|
||||
-no-undefined \
|
||||
-version-info @LIBCURRENT@:@LIBREVISION@:@LIBAGE@
|
||||
|
||||
libexpat_la_SOURCES = \
|
||||
xmlparse.c \
|
||||
xmltok.c \
|
||||
xmlrole.c
|
||||
|
||||
if WITH_TESTS
|
||||
libtestpat_la_CPPFLAGS = -DXML_TESTING
|
||||
|
||||
libtestpat_la_SOURCES = $(libexpat_la_SOURCES)
|
||||
endif
|
||||
|
||||
doc_DATA = \
|
||||
../AUTHORS \
|
||||
../Changes
|
||||
|
||||
install-data-hook:
|
||||
cd "$(DESTDIR)$(docdir)" && $(am__mv) Changes changelog
|
||||
|
||||
uninstall-local:
|
||||
$(RM) "$(DESTDIR)$(docdir)/changelog"
|
||||
|
||||
EXTRA_DIST = \
|
||||
ascii.h \
|
||||
asciitab.h \
|
||||
expat_external.h \
|
||||
expat.h \
|
||||
iasciitab.h \
|
||||
internal.h \
|
||||
latin1tab.h \
|
||||
libexpat.def.cmake \
|
||||
nametab.h \
|
||||
siphash.h \
|
||||
utf8tab.h \
|
||||
winconfig.h \
|
||||
xmlrole.h \
|
||||
xmltok.h \
|
||||
xmltok_impl.c \
|
||||
xmltok_impl.h \
|
||||
xmltok_ns.c
|
||||
123
lib/expat/ascii.h
Normal file
123
lib/expat/ascii.h
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 1999-2000 Thai Open Source Software Center Ltd
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2007 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2017 Sebastian Pipping <sebastian@pipping.org>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#define ASCII_A 0x41
|
||||
#define ASCII_B 0x42
|
||||
#define ASCII_C 0x43
|
||||
#define ASCII_D 0x44
|
||||
#define ASCII_E 0x45
|
||||
#define ASCII_F 0x46
|
||||
#define ASCII_G 0x47
|
||||
#define ASCII_H 0x48
|
||||
#define ASCII_I 0x49
|
||||
#define ASCII_J 0x4A
|
||||
#define ASCII_K 0x4B
|
||||
#define ASCII_L 0x4C
|
||||
#define ASCII_M 0x4D
|
||||
#define ASCII_N 0x4E
|
||||
#define ASCII_O 0x4F
|
||||
#define ASCII_P 0x50
|
||||
#define ASCII_Q 0x51
|
||||
#define ASCII_R 0x52
|
||||
#define ASCII_S 0x53
|
||||
#define ASCII_T 0x54
|
||||
#define ASCII_U 0x55
|
||||
#define ASCII_V 0x56
|
||||
#define ASCII_W 0x57
|
||||
#define ASCII_X 0x58
|
||||
#define ASCII_Y 0x59
|
||||
#define ASCII_Z 0x5A
|
||||
|
||||
#define ASCII_a 0x61
|
||||
#define ASCII_b 0x62
|
||||
#define ASCII_c 0x63
|
||||
#define ASCII_d 0x64
|
||||
#define ASCII_e 0x65
|
||||
#define ASCII_f 0x66
|
||||
#define ASCII_g 0x67
|
||||
#define ASCII_h 0x68
|
||||
#define ASCII_i 0x69
|
||||
#define ASCII_j 0x6A
|
||||
#define ASCII_k 0x6B
|
||||
#define ASCII_l 0x6C
|
||||
#define ASCII_m 0x6D
|
||||
#define ASCII_n 0x6E
|
||||
#define ASCII_o 0x6F
|
||||
#define ASCII_p 0x70
|
||||
#define ASCII_q 0x71
|
||||
#define ASCII_r 0x72
|
||||
#define ASCII_s 0x73
|
||||
#define ASCII_t 0x74
|
||||
#define ASCII_u 0x75
|
||||
#define ASCII_v 0x76
|
||||
#define ASCII_w 0x77
|
||||
#define ASCII_x 0x78
|
||||
#define ASCII_y 0x79
|
||||
#define ASCII_z 0x7A
|
||||
|
||||
#define ASCII_0 0x30
|
||||
#define ASCII_1 0x31
|
||||
#define ASCII_2 0x32
|
||||
#define ASCII_3 0x33
|
||||
#define ASCII_4 0x34
|
||||
#define ASCII_5 0x35
|
||||
#define ASCII_6 0x36
|
||||
#define ASCII_7 0x37
|
||||
#define ASCII_8 0x38
|
||||
#define ASCII_9 0x39
|
||||
|
||||
#define ASCII_TAB 0x09
|
||||
#define ASCII_SPACE 0x20
|
||||
#define ASCII_EXCL 0x21
|
||||
#define ASCII_QUOT 0x22
|
||||
#define ASCII_AMP 0x26
|
||||
#define ASCII_APOS 0x27
|
||||
#define ASCII_MINUS 0x2D
|
||||
#define ASCII_PERIOD 0x2E
|
||||
#define ASCII_COLON 0x3A
|
||||
#define ASCII_SEMI 0x3B
|
||||
#define ASCII_LT 0x3C
|
||||
#define ASCII_EQUALS 0x3D
|
||||
#define ASCII_GT 0x3E
|
||||
#define ASCII_LSQB 0x5B
|
||||
#define ASCII_RSQB 0x5D
|
||||
#define ASCII_UNDERSCORE 0x5F
|
||||
#define ASCII_LPAREN 0x28
|
||||
#define ASCII_RPAREN 0x29
|
||||
#define ASCII_FF 0x0C
|
||||
#define ASCII_SLASH 0x2F
|
||||
#define ASCII_HASH 0x23
|
||||
#define ASCII_PIPE 0x7C
|
||||
#define ASCII_COMMA 0x2C
|
||||
66
lib/expat/asciitab.h
Normal file
66
lib/expat/asciitab.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2017 Sebastian Pipping <sebastian@pipping.org>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* 0x00 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x04 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x08 */ BT_NONXML, BT_S, BT_LF, BT_NONXML,
|
||||
/* 0x0C */ BT_NONXML, BT_CR, BT_NONXML, BT_NONXML,
|
||||
/* 0x10 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x14 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x18 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x1C */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x20 */ BT_S, BT_EXCL, BT_QUOT, BT_NUM,
|
||||
/* 0x24 */ BT_OTHER, BT_PERCNT, BT_AMP, BT_APOS,
|
||||
/* 0x28 */ BT_LPAR, BT_RPAR, BT_AST, BT_PLUS,
|
||||
/* 0x2C */ BT_COMMA, BT_MINUS, BT_NAME, BT_SOL,
|
||||
/* 0x30 */ BT_DIGIT, BT_DIGIT, BT_DIGIT, BT_DIGIT,
|
||||
/* 0x34 */ BT_DIGIT, BT_DIGIT, BT_DIGIT, BT_DIGIT,
|
||||
/* 0x38 */ BT_DIGIT, BT_DIGIT, BT_COLON, BT_SEMI,
|
||||
/* 0x3C */ BT_LT, BT_EQUALS, BT_GT, BT_QUEST,
|
||||
/* 0x40 */ BT_OTHER, BT_HEX, BT_HEX, BT_HEX,
|
||||
/* 0x44 */ BT_HEX, BT_HEX, BT_HEX, BT_NMSTRT,
|
||||
/* 0x48 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x4C */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x50 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x54 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x58 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_LSQB,
|
||||
/* 0x5C */ BT_OTHER, BT_RSQB, BT_OTHER, BT_NMSTRT,
|
||||
/* 0x60 */ BT_OTHER, BT_HEX, BT_HEX, BT_HEX,
|
||||
/* 0x64 */ BT_HEX, BT_HEX, BT_HEX, BT_NMSTRT,
|
||||
/* 0x68 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x6C */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x70 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x74 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x78 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_OTHER,
|
||||
/* 0x7C */ BT_VERBAR, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
1031
lib/expat/expat.h
Normal file
1031
lib/expat/expat.h
Normal file
File diff suppressed because it is too large
Load Diff
0
lib/expat/expat_config.h
Normal file
0
lib/expat/expat_config.h
Normal file
163
lib/expat/expat_external.h
Normal file
163
lib/expat/expat_external.h
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2000-2004 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2001-2002 Greg Stein <gstein@users.sourceforge.net>
|
||||
Copyright (c) 2002-2006 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2016 Cristian Rodríguez <crrodriguez@opensuse.org>
|
||||
Copyright (c) 2016-2019 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk>
|
||||
Copyright (c) 2018 Yury Gribov <tetra2005@gmail.com>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef Expat_External_INCLUDED
|
||||
#define Expat_External_INCLUDED 1
|
||||
|
||||
/* External API definitions */
|
||||
|
||||
/* Expat tries very hard to make the API boundary very specifically
|
||||
defined. There are two macros defined to control this boundary;
|
||||
each of these can be defined before including this header to
|
||||
achieve some different behavior, but doing so it not recommended or
|
||||
tested frequently.
|
||||
|
||||
XMLCALL - The calling convention to use for all calls across the
|
||||
"library boundary." This will default to cdecl, and
|
||||
try really hard to tell the compiler that's what we
|
||||
want.
|
||||
|
||||
XMLIMPORT - Whatever magic is needed to note that a function is
|
||||
to be imported from a dynamically loaded library
|
||||
(.dll, .so, or .sl, depending on your platform).
|
||||
|
||||
The XMLCALL macro was added in Expat 1.95.7. The only one which is
|
||||
expected to be directly useful in client code is XMLCALL.
|
||||
|
||||
Note that on at least some Unix versions, the Expat library must be
|
||||
compiled with the cdecl calling convention as the default since
|
||||
system headers may assume the cdecl convention.
|
||||
*/
|
||||
#ifndef XMLCALL
|
||||
#if defined(_MSC_VER)
|
||||
#define XMLCALL __cdecl
|
||||
#elif defined(__GNUC__) && defined(__i386) && !defined(__INTEL_COMPILER)
|
||||
#define XMLCALL __attribute__((cdecl))
|
||||
#else
|
||||
/* For any platform which uses this definition and supports more than
|
||||
one calling convention, we need to extend this definition to
|
||||
declare the convention used on that platform, if it's possible to
|
||||
do so.
|
||||
|
||||
If this is the case for your platform, please file a bug report
|
||||
with information on how to identify your platform via the C
|
||||
pre-processor and how to specify the same calling convention as the
|
||||
platform's malloc() implementation.
|
||||
*/
|
||||
#define XMLCALL
|
||||
#endif
|
||||
#endif /* not defined XMLCALL */
|
||||
|
||||
#if !defined(XML_STATIC) && !defined(XMLIMPORT)
|
||||
#ifndef XML_BUILDING_EXPAT
|
||||
/* using Expat from an application */
|
||||
|
||||
#if defined(_MSC_VER) && !defined(__BEOS__) && !defined(__CYGWIN__)
|
||||
#define XMLIMPORT __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif /* not defined XML_STATIC */
|
||||
|
||||
#ifndef XML_ENABLE_VISIBILITY
|
||||
#define XML_ENABLE_VISIBILITY 0
|
||||
#endif
|
||||
|
||||
#if !defined(XMLIMPORT) && XML_ENABLE_VISIBILITY
|
||||
#define XMLIMPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
/* If we didn't define it above, define it away: */
|
||||
#ifndef XMLIMPORT
|
||||
#define XMLIMPORT
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96))
|
||||
#define XML_ATTR_MALLOC __attribute__((__malloc__))
|
||||
#else
|
||||
#define XML_ATTR_MALLOC
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
|
||||
#define XML_ATTR_ALLOC_SIZE(x) __attribute__((__alloc_size__(x)))
|
||||
#else
|
||||
#define XML_ATTR_ALLOC_SIZE(x)
|
||||
#endif
|
||||
|
||||
#define XMLPARSEAPI(type) XMLIMPORT type XMLCALL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef XML_UNICODE_WCHAR_T
|
||||
#ifndef XML_UNICODE
|
||||
#define XML_UNICODE
|
||||
#endif
|
||||
#if defined(__SIZEOF_WCHAR_T__) && (__SIZEOF_WCHAR_T__ != 2)
|
||||
#error "sizeof(wchar_t) != 2; Need -fshort-wchar for both Expat and libc"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef XML_UNICODE /* Information is UTF-16 encoded. */
|
||||
#ifdef XML_UNICODE_WCHAR_T
|
||||
typedef wchar_t XML_Char;
|
||||
typedef wchar_t XML_LChar;
|
||||
#else
|
||||
typedef unsigned short XML_Char;
|
||||
typedef char XML_LChar;
|
||||
#endif /* XML_UNICODE_WCHAR_T */
|
||||
#else /* Information is UTF-8 encoded. */
|
||||
typedef char XML_Char;
|
||||
typedef char XML_LChar;
|
||||
#endif /* XML_UNICODE */
|
||||
|
||||
#ifdef XML_LARGE_SIZE /* Use large integers for file/stream positions. */
|
||||
typedef long long XML_Index;
|
||||
typedef unsigned long long XML_Size;
|
||||
#else
|
||||
typedef long XML_Index;
|
||||
typedef unsigned long XML_Size;
|
||||
#endif /* XML_LARGE_SIZE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* not Expat_External_INCLUDED */
|
||||
67
lib/expat/iasciitab.h
Normal file
67
lib/expat/iasciitab.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2017 Sebastian Pipping <sebastian@pipping.org>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* Like asciitab.h, except that 0xD has code BT_S rather than BT_CR */
|
||||
/* 0x00 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x04 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x08 */ BT_NONXML, BT_S, BT_LF, BT_NONXML,
|
||||
/* 0x0C */ BT_NONXML, BT_S, BT_NONXML, BT_NONXML,
|
||||
/* 0x10 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x14 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x18 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x1C */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0x20 */ BT_S, BT_EXCL, BT_QUOT, BT_NUM,
|
||||
/* 0x24 */ BT_OTHER, BT_PERCNT, BT_AMP, BT_APOS,
|
||||
/* 0x28 */ BT_LPAR, BT_RPAR, BT_AST, BT_PLUS,
|
||||
/* 0x2C */ BT_COMMA, BT_MINUS, BT_NAME, BT_SOL,
|
||||
/* 0x30 */ BT_DIGIT, BT_DIGIT, BT_DIGIT, BT_DIGIT,
|
||||
/* 0x34 */ BT_DIGIT, BT_DIGIT, BT_DIGIT, BT_DIGIT,
|
||||
/* 0x38 */ BT_DIGIT, BT_DIGIT, BT_COLON, BT_SEMI,
|
||||
/* 0x3C */ BT_LT, BT_EQUALS, BT_GT, BT_QUEST,
|
||||
/* 0x40 */ BT_OTHER, BT_HEX, BT_HEX, BT_HEX,
|
||||
/* 0x44 */ BT_HEX, BT_HEX, BT_HEX, BT_NMSTRT,
|
||||
/* 0x48 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x4C */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x50 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x54 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x58 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_LSQB,
|
||||
/* 0x5C */ BT_OTHER, BT_RSQB, BT_OTHER, BT_NMSTRT,
|
||||
/* 0x60 */ BT_OTHER, BT_HEX, BT_HEX, BT_HEX,
|
||||
/* 0x64 */ BT_HEX, BT_HEX, BT_HEX, BT_NMSTRT,
|
||||
/* 0x68 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x6C */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x70 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x74 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0x78 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_OTHER,
|
||||
/* 0x7C */ BT_VERBAR, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
187
lib/expat/internal.h
Normal file
187
lib/expat/internal.h
Normal file
@ -0,0 +1,187 @@
|
||||
/* internal.h
|
||||
|
||||
Internal definitions used by Expat. This is not needed to compile
|
||||
client code.
|
||||
|
||||
The following calling convention macros are defined for frequently
|
||||
called functions:
|
||||
|
||||
FASTCALL - Used for those internal functions that have a simple
|
||||
body and a low number of arguments and local variables.
|
||||
|
||||
PTRCALL - Used for functions called though function pointers.
|
||||
|
||||
PTRFASTCALL - Like PTRCALL, but for low number of arguments.
|
||||
|
||||
inline - Used for selected internal functions for which inlining
|
||||
may improve performance on some platforms.
|
||||
|
||||
Note: Use of these macros is based on judgement, not hard rules,
|
||||
and therefore subject to change.
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 2002-2003 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2002-2006 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net>
|
||||
Copyright (c) 2016-2025 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2018 Yury Gribov <tetra2005@gmail.com>
|
||||
Copyright (c) 2019 David Loffredo <loffredo@steptools.com>
|
||||
Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
|
||||
Copyright (c) 2024 Taichi Haradaguchi <20001722@ymail.ne.jp>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#if defined(__GNUC__) && defined(__i386__) && !defined(__MINGW32__)
|
||||
/* We'll use this version by default only where we know it helps.
|
||||
|
||||
regparm() generates warnings on Solaris boxes. See SF bug #692878.
|
||||
|
||||
Instability reported with egcs on a RedHat Linux 7.3.
|
||||
Let's comment out:
|
||||
#define FASTCALL __attribute__((stdcall, regparm(3)))
|
||||
and let's try this:
|
||||
*/
|
||||
#define FASTCALL __attribute__((regparm(3)))
|
||||
#define PTRFASTCALL __attribute__((regparm(3)))
|
||||
#endif
|
||||
|
||||
/* Using __fastcall seems to have an unexpected negative effect under
|
||||
MS VC++, especially for function pointers, so we won't use it for
|
||||
now on that platform. It may be reconsidered for a future release
|
||||
if it can be made more effective.
|
||||
Likely reason: __fastcall on Windows is like stdcall, therefore
|
||||
the compiler cannot perform stack optimizations for call clusters.
|
||||
*/
|
||||
|
||||
/* Make sure all of these are defined if they aren't already. */
|
||||
|
||||
#ifndef FASTCALL
|
||||
#define FASTCALL
|
||||
#endif
|
||||
|
||||
#ifndef PTRCALL
|
||||
#define PTRCALL
|
||||
#endif
|
||||
|
||||
#ifndef PTRFASTCALL
|
||||
#define PTRFASTCALL
|
||||
#endif
|
||||
|
||||
#ifndef XML_MIN_SIZE
|
||||
#if !defined(__cplusplus) && !defined(inline)
|
||||
#ifdef __GNUC__
|
||||
#define inline __inline
|
||||
#endif /* __GNUC__ */
|
||||
#endif
|
||||
#endif /* XML_MIN_SIZE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define inline inline
|
||||
#else
|
||||
#ifndef inline
|
||||
#define inline
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <limits.h> // ULONG_MAX
|
||||
#include <stddef.h> // size_t
|
||||
|
||||
#if defined(_WIN32) && (!defined(__USE_MINGW_ANSI_STDIO) || (1 - __USE_MINGW_ANSI_STDIO - 1 == 0))
|
||||
#define EXPAT_FMT_ULL(midpart) "%" midpart "I64u"
|
||||
#if defined(_WIN64) // Note: modifiers "td" and "zu" do not work for MinGW
|
||||
#define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "I64d"
|
||||
#define EXPAT_FMT_SIZE_T(midpart) "%" midpart "I64u"
|
||||
#else
|
||||
#define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d"
|
||||
#define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u"
|
||||
#endif
|
||||
#else
|
||||
#define EXPAT_FMT_ULL(midpart) "%" midpart "llu"
|
||||
#if !defined(ULONG_MAX)
|
||||
#error Compiler did not define ULONG_MAX for us
|
||||
#elif ULONG_MAX == 18446744073709551615u // 2^64-1
|
||||
#define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "ld"
|
||||
#define EXPAT_FMT_SIZE_T(midpart) "%" midpart "lu"
|
||||
#elif defined(EMSCRIPTEN) // 32bit mode Emscripten
|
||||
#define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "ld"
|
||||
#define EXPAT_FMT_SIZE_T(midpart) "%" midpart "zu"
|
||||
#else
|
||||
#define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d"
|
||||
#define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef UNUSED_P
|
||||
#define UNUSED_P(p) (void)p
|
||||
#endif
|
||||
|
||||
/* NOTE BEGIN If you ever patch these defaults to greater values
|
||||
for non-attack XML payload in your environment,
|
||||
please file a bug report with libexpat. Thank you!
|
||||
*/
|
||||
#define EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT 100.0f
|
||||
#define EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT 8388608 // 8 MiB, 2^23
|
||||
|
||||
#define EXPAT_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION_DEFAULT 100.0f
|
||||
#define EXPAT_ALLOC_TRACKER_ACTIVATION_THRESHOLD_DEFAULT 67108864 // 64 MiB, 2^26
|
||||
|
||||
// NOTE: If function expat_alloc was user facing, EXPAT_MALLOC_ALIGNMENT would
|
||||
// have to take sizeof(long double) into account
|
||||
#define EXPAT_MALLOC_ALIGNMENT sizeof(long long) // largest parser (sub)member
|
||||
#define EXPAT_MALLOC_PADDING ((EXPAT_MALLOC_ALIGNMENT) - sizeof(size_t))
|
||||
|
||||
/* NOTE END */
|
||||
|
||||
#include "expat.h" // so we can use type XML_Parser below
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void _INTERNAL_trim_to_complete_utf8_characters(const char* from, const char** fromLimRef);
|
||||
|
||||
#if defined(XML_GE) && XML_GE == 1
|
||||
unsigned long long testingAccountingGetCountBytesDirect(XML_Parser parser);
|
||||
unsigned long long testingAccountingGetCountBytesIndirect(XML_Parser parser);
|
||||
const char* unsignedCharToPrintable(unsigned char c);
|
||||
#endif
|
||||
|
||||
extern
|
||||
#if !defined(XML_TESTING)
|
||||
const
|
||||
#endif
|
||||
XML_Bool g_reparseDeferralEnabledDefault; // written ONLY in runtests.c
|
||||
#if defined(XML_TESTING)
|
||||
void* expat_malloc(XML_Parser parser, size_t size, int sourceLine);
|
||||
void expat_free(XML_Parser parser, void* ptr, int sourceLine);
|
||||
void* expat_realloc(XML_Parser parser, void* ptr, size_t size, int sourceLine);
|
||||
extern unsigned int g_bytesScanned; // used for testing only
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
66
lib/expat/latin1tab.h
Normal file
66
lib/expat/latin1tab.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2017 Sebastian Pipping <sebastian@pipping.org>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* 0x80 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0x84 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0x88 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0x8C */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0x90 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0x94 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0x98 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0x9C */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0xA0 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0xA4 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0xA8 */ BT_OTHER, BT_OTHER, BT_NMSTRT, BT_OTHER,
|
||||
/* 0xAC */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0xB0 */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0xB4 */ BT_OTHER, BT_NMSTRT, BT_OTHER, BT_NAME,
|
||||
/* 0xB8 */ BT_OTHER, BT_OTHER, BT_NMSTRT, BT_OTHER,
|
||||
/* 0xBC */ BT_OTHER, BT_OTHER, BT_OTHER, BT_OTHER,
|
||||
/* 0xC0 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xC4 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xC8 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xCC */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xD0 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xD4 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_OTHER,
|
||||
/* 0xD8 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xDC */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xE0 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xE4 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xE8 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xEC */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xF0 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xF4 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_OTHER,
|
||||
/* 0xF8 */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
/* 0xFC */ BT_NMSTRT, BT_NMSTRT, BT_NMSTRT, BT_NMSTRT,
|
||||
84
lib/expat/libexpat.def.cmake
Normal file
84
lib/expat/libexpat.def.cmake
Normal file
@ -0,0 +1,84 @@
|
||||
; DEF file for MS VC++
|
||||
|
||||
EXPORTS
|
||||
XML_DefaultCurrent @1
|
||||
XML_ErrorString @2
|
||||
XML_ExpatVersion @3
|
||||
XML_ExpatVersionInfo @4
|
||||
XML_ExternalEntityParserCreate @5
|
||||
XML_GetBase @6
|
||||
XML_GetBuffer @7
|
||||
XML_GetCurrentByteCount @8
|
||||
XML_GetCurrentByteIndex @9
|
||||
XML_GetCurrentColumnNumber @10
|
||||
XML_GetCurrentLineNumber @11
|
||||
XML_GetErrorCode @12
|
||||
XML_GetIdAttributeIndex @13
|
||||
XML_GetInputContext @14
|
||||
XML_GetSpecifiedAttributeCount @15
|
||||
XML_Parse @16
|
||||
XML_ParseBuffer @17
|
||||
XML_ParserCreate @18
|
||||
XML_ParserCreateNS @19
|
||||
XML_ParserCreate_MM @20
|
||||
XML_ParserFree @21
|
||||
XML_SetAttlistDeclHandler @22
|
||||
XML_SetBase @23
|
||||
XML_SetCdataSectionHandler @24
|
||||
XML_SetCharacterDataHandler @25
|
||||
XML_SetCommentHandler @26
|
||||
XML_SetDefaultHandler @27
|
||||
XML_SetDefaultHandlerExpand @28
|
||||
XML_SetDoctypeDeclHandler @29
|
||||
XML_SetElementDeclHandler @30
|
||||
XML_SetElementHandler @31
|
||||
XML_SetEncoding @32
|
||||
XML_SetEndCdataSectionHandler @33
|
||||
XML_SetEndDoctypeDeclHandler @34
|
||||
XML_SetEndElementHandler @35
|
||||
XML_SetEndNamespaceDeclHandler @36
|
||||
XML_SetEntityDeclHandler @37
|
||||
XML_SetExternalEntityRefHandler @38
|
||||
XML_SetExternalEntityRefHandlerArg @39
|
||||
XML_SetNamespaceDeclHandler @40
|
||||
XML_SetNotStandaloneHandler @41
|
||||
XML_SetNotationDeclHandler @42
|
||||
XML_SetParamEntityParsing @43
|
||||
XML_SetProcessingInstructionHandler @44
|
||||
XML_SetReturnNSTriplet @45
|
||||
XML_SetStartCdataSectionHandler @46
|
||||
XML_SetStartDoctypeDeclHandler @47
|
||||
XML_SetStartElementHandler @48
|
||||
XML_SetStartNamespaceDeclHandler @49
|
||||
XML_SetUnknownEncodingHandler @50
|
||||
XML_SetUnparsedEntityDeclHandler @51
|
||||
XML_SetUserData @52
|
||||
XML_SetXmlDeclHandler @53
|
||||
XML_UseParserAsHandlerArg @54
|
||||
; added with version 1.95.3
|
||||
XML_ParserReset @55
|
||||
XML_SetSkippedEntityHandler @56
|
||||
; added with version 1.95.5
|
||||
XML_GetFeatureList @57
|
||||
XML_UseForeignDTD @58
|
||||
; added with version 1.95.6
|
||||
XML_FreeContentModel @59
|
||||
XML_MemMalloc @60
|
||||
XML_MemRealloc @61
|
||||
XML_MemFree @62
|
||||
; added with version 1.95.8
|
||||
XML_StopParser @63
|
||||
XML_ResumeParser @64
|
||||
XML_GetParsingStatus @65
|
||||
; added with version 2.1.1
|
||||
@_EXPAT_COMMENT_ATTR_INFO@ XML_GetAttributeInfo @66
|
||||
XML_SetHashSalt @67
|
||||
; internal @68 removed with version 2.3.1
|
||||
; added with version 2.4.0
|
||||
@_EXPAT_COMMENT_DTD_OR_GE@ XML_SetBillionLaughsAttackProtectionActivationThreshold @69
|
||||
@_EXPAT_COMMENT_DTD_OR_GE@ XML_SetBillionLaughsAttackProtectionMaximumAmplification @70
|
||||
; added with version 2.6.0
|
||||
XML_SetReparseDeferralEnabled @71
|
||||
; added with version 2.7.2
|
||||
@_EXPAT_COMMENT_DTD_OR_GE@ XML_SetAllocTrackerMaximumAmplification @72
|
||||
@_EXPAT_COMMENT_DTD_OR_GE@ XML_SetAllocTrackerActivationThreshold @73
|
||||
11
lib/expat/library.json
Normal file
11
lib/expat/library.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "expat",
|
||||
"version": "2.7.3",
|
||||
"build": {
|
||||
"srcFilter": [
|
||||
"+<xmlparse.c>",
|
||||
"+<xmlrole.c>",
|
||||
"+<xmltok.c>"
|
||||
]
|
||||
}
|
||||
}
|
||||
102
lib/expat/nametab.h
Normal file
102
lib/expat/nametab.h
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2017 Sebastian Pipping <sebastian@pipping.org>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
static const unsigned namingBitmap[] = {
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x04000000,
|
||||
0x87FFFFFE, 0x07FFFFFE, 0x00000000, 0x00000000, 0xFF7FFFFF, 0xFF7FFFFF, 0xFFFFFFFF, 0x7FF3FFFF, 0xFFFFFDFE,
|
||||
0x7FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE00F, 0xFC31FFFF, 0x00FFFFFF, 0x00000000, 0xFFFF0000, 0xFFFFFFFF,
|
||||
0xFFFFFFFF, 0xF80001FF, 0x00000003, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFD740,
|
||||
0xFFFFFFFB, 0x547F7FFF, 0x000FFFFD, 0xFFFFDFFE, 0xFFFFFFFF, 0xDFFEFFFF, 0xFFFFFFFF, 0xFFFF0003, 0xFFFFFFFF,
|
||||
0xFFFF199F, 0x033FCFFF, 0x00000000, 0xFFFE0000, 0x027FFFFF, 0xFFFFFFFE, 0x0000007F, 0x00000000, 0xFFFF0000,
|
||||
0x000707FF, 0x00000000, 0x07FFFFFE, 0x000007FE, 0xFFFE0000, 0xFFFFFFFF, 0x7CFFFFFF, 0x002F7FFF, 0x00000060,
|
||||
0xFFFFFFE0, 0x23FFFFFF, 0xFF000000, 0x00000003, 0xFFF99FE0, 0x03C5FDFF, 0xB0000000, 0x00030003, 0xFFF987E0,
|
||||
0x036DFDFF, 0x5E000000, 0x001C0000, 0xFFFBAFE0, 0x23EDFDFF, 0x00000000, 0x00000001, 0xFFF99FE0, 0x23CDFDFF,
|
||||
0xB0000000, 0x00000003, 0xD63DC7E0, 0x03BFC718, 0x00000000, 0x00000000, 0xFFFDDFE0, 0x03EFFDFF, 0x00000000,
|
||||
0x00000003, 0xFFFDDFE0, 0x03EFFDFF, 0x40000000, 0x00000003, 0xFFFDDFE0, 0x03FFFDFF, 0x00000000, 0x00000003,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFE, 0x000D7FFF, 0x0000003F, 0x00000000, 0xFEF02596,
|
||||
0x200D6CAE, 0x0000001F, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFEFF, 0x000003FF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFF003F,
|
||||
0x007FFFFF, 0x0007DAED, 0x50000000, 0x82315001, 0x002C62AB, 0x40000000, 0xF580C900, 0x00000007, 0x02010800,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x03FFFFFF, 0x3F3FFFFF,
|
||||
0xFFFFFFFF, 0xAAFF3F3F, 0x3FFFFFFF, 0xFFFFFFFF, 0x5FDFFFFF, 0x0FCF1FDC, 0x1FDC1FFF, 0x00000000, 0x00004C40,
|
||||
0x00000000, 0x00000000, 0x00000007, 0x00000000, 0x00000000, 0x00000000, 0x00000080, 0x000003FE, 0xFFFFFFFE,
|
||||
0xFFFFFFFF, 0x001FFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0x07FFFFFF, 0xFFFFFFE0, 0x00001FFF, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
0x0000003F, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000000F,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x07FF6000, 0x87FFFFFE, 0x07FFFFFE, 0x00000000, 0x00800000, 0xFF7FFFFF,
|
||||
0xFF7FFFFF, 0x00FFFFFF, 0x00000000, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xF80001FF, 0x00030003, 0x00000000,
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0x0000003F, 0x00000003, 0xFFFFD7C0, 0xFFFFFFFB, 0x547F7FFF, 0x000FFFFD, 0xFFFFDFFE,
|
||||
0xFFFFFFFF, 0xDFFEFFFF, 0xFFFFFFFF, 0xFFFF007B, 0xFFFFFFFF, 0xFFFF199F, 0x033FCFFF, 0x00000000, 0xFFFE0000,
|
||||
0x027FFFFF, 0xFFFFFFFE, 0xFFFE007F, 0xBBFFFFFB, 0xFFFF0016, 0x000707FF, 0x00000000, 0x07FFFFFE, 0x0007FFFF,
|
||||
0xFFFF03FF, 0xFFFFFFFF, 0x7CFFFFFF, 0xFFEF7FFF, 0x03FF3DFF, 0xFFFFFFEE, 0xF3FFFFFF, 0xFF1E3FFF, 0x0000FFCF,
|
||||
0xFFF99FEE, 0xD3C5FDFF, 0xB080399F, 0x0003FFCF, 0xFFF987E4, 0xD36DFDFF, 0x5E003987, 0x001FFFC0, 0xFFFBAFEE,
|
||||
0xF3EDFDFF, 0x00003BBF, 0x0000FFC1, 0xFFF99FEE, 0xF3CDFDFF, 0xB0C0398F, 0x0000FFC3, 0xD63DC7EC, 0xC3BFC718,
|
||||
0x00803DC7, 0x0000FF80, 0xFFFDDFEE, 0xC3EFFDFF, 0x00603DDF, 0x0000FFC3, 0xFFFDDFEC, 0xC3EFFDFF, 0x40603DDF,
|
||||
0x0000FFC3, 0xFFFDDFEC, 0xC3FFFDFF, 0x00803DCF, 0x0000FFC3, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0xFFFFFFFE, 0x07FF7FFF, 0x03FF7FFF, 0x00000000, 0xFEF02596, 0x3BFF6CAE, 0x03FF3F5F, 0x00000000, 0x03000000,
|
||||
0xC2A003FF, 0xFFFFFEFF, 0xFFFE03FF, 0xFEBF0FDF, 0x02FE3FFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x1FFF0000, 0x00000002, 0x000000A0, 0x003EFFFE, 0xFFFFFFFE,
|
||||
0xFFFFFFFF, 0x661FFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0x77FFFFFF,
|
||||
};
|
||||
static const unsigned char nmstrtPages[] = {
|
||||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x00, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x13, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
static const unsigned char namePages[] = {
|
||||
0x19, 0x03, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x00, 0x00, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x10, 0x11, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x13, 0x26, 0x14, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
379
lib/expat/siphash.h
Normal file
379
lib/expat/siphash.h
Normal file
@ -0,0 +1,379 @@
|
||||
/* ==========================================================================
|
||||
* siphash.h - SipHash-2-4 in a single header file
|
||||
* --------------------------------------------------------------------------
|
||||
* Derived by William Ahern from the reference implementation[1] published[2]
|
||||
* by Jean-Philippe Aumasson and Daniel J. Berstein.
|
||||
* Minimal changes by Sebastian Pipping and Victor Stinner on top, see below.
|
||||
* Licensed under the CC0 Public Domain Dedication license.
|
||||
*
|
||||
* 1. https://www.131002.net/siphash/siphash24.c
|
||||
* 2. https://www.131002.net/siphash/
|
||||
* --------------------------------------------------------------------------
|
||||
* HISTORY:
|
||||
*
|
||||
* 2020-10-03 (Sebastian Pipping)
|
||||
* - Drop support for Visual Studio 9.0/2008 and earlier
|
||||
*
|
||||
* 2019-08-03 (Sebastian Pipping)
|
||||
* - Mark part of sip24_valid as to be excluded from clang-format
|
||||
* - Re-format code using clang-format 9
|
||||
*
|
||||
* 2018-07-08 (Anton Maklakov)
|
||||
* - Add "fall through" markers for GCC's -Wimplicit-fallthrough
|
||||
*
|
||||
* 2017-11-03 (Sebastian Pipping)
|
||||
* - Hide sip_tobin and sip_binof unless SIPHASH_TOBIN macro is defined
|
||||
*
|
||||
* 2017-07-25 (Vadim Zeitlin)
|
||||
* - Fix use of SIPHASH_MAIN macro
|
||||
*
|
||||
* 2017-07-05 (Sebastian Pipping)
|
||||
* - Use _SIP_ULL macro to not require a C++11 compiler if compiled as C++
|
||||
* - Add const qualifiers at two places
|
||||
* - Ensure <=80 characters line length (assuming tab width 4)
|
||||
*
|
||||
* 2017-06-23 (Victor Stinner)
|
||||
* - Address Win64 compile warnings
|
||||
*
|
||||
* 2017-06-18 (Sebastian Pipping)
|
||||
* - Clarify license note in the header
|
||||
* - Address C89 issues:
|
||||
* - Stop using inline keyword (and let compiler decide)
|
||||
* - Replace _Bool by int
|
||||
* - Turn macro siphash24 into a function
|
||||
* - Address invalid conversion (void pointer) by explicit cast
|
||||
* - Address lack of stdint.h for Visual Studio 2003 to 2008
|
||||
* - Always expose sip24_valid (for self-tests)
|
||||
*
|
||||
* 2012-11-04 - Born. (William Ahern)
|
||||
* --------------------------------------------------------------------------
|
||||
* USAGE:
|
||||
*
|
||||
* SipHash-2-4 takes as input two 64-bit words as the key, some number of
|
||||
* message bytes, and outputs a 64-bit word as the message digest. This
|
||||
* implementation employs two data structures: a struct sipkey for
|
||||
* representing the key, and a struct siphash for representing the hash
|
||||
* state.
|
||||
*
|
||||
* For converting a 16-byte unsigned char array to a key, use either the
|
||||
* macro sip_keyof or the routine sip_tokey. The former instantiates a
|
||||
* compound literal key, while the latter requires a key object as a
|
||||
* parameter.
|
||||
*
|
||||
* unsigned char secret[16];
|
||||
* arc4random_buf(secret, sizeof secret);
|
||||
* struct sipkey *key = sip_keyof(secret);
|
||||
*
|
||||
* For hashing a message, use either the convenience macro siphash24 or the
|
||||
* routines sip24_init, sip24_update, and sip24_final.
|
||||
*
|
||||
* struct siphash state;
|
||||
* void *msg;
|
||||
* size_t len;
|
||||
* uint64_t hash;
|
||||
*
|
||||
* sip24_init(&state, key);
|
||||
* sip24_update(&state, msg, len);
|
||||
* hash = sip24_final(&state);
|
||||
*
|
||||
* or
|
||||
*
|
||||
* hash = siphash24(msg, len, key);
|
||||
*
|
||||
* To convert the 64-bit hash value to a canonical 8-byte little-endian
|
||||
* binary representation, use either the macro sip_binof or the routine
|
||||
* sip_tobin. The former instantiates and returns a compound literal array,
|
||||
* while the latter requires an array object as a parameter.
|
||||
* --------------------------------------------------------------------------
|
||||
* NOTES:
|
||||
*
|
||||
* o Neither sip_keyof, sip_binof, nor siphash24 will work with compilers
|
||||
* lacking compound literal support. Instead, you must use the lower-level
|
||||
* interfaces which take as parameters the temporary state objects.
|
||||
*
|
||||
* o Uppercase macros may evaluate parameters more than once. Lowercase
|
||||
* macros should not exhibit any such side effects.
|
||||
* ==========================================================================
|
||||
*/
|
||||
#ifndef SIPHASH_H
|
||||
#define SIPHASH_H
|
||||
|
||||
#include <stddef.h> /* size_t */
|
||||
#include <stdint.h> /* uint64_t uint32_t uint8_t */
|
||||
|
||||
/*
|
||||
* Workaround to not require a C++11 compiler for using ULL suffix
|
||||
* if this code is included and compiled as C++; related GCC warning is:
|
||||
* warning: use of C++11 long long integer constant [-Wlong-long]
|
||||
*/
|
||||
#define SIP_ULL(high, low) ((((uint64_t)high) << 32) | (low))
|
||||
|
||||
#define SIP_ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
|
||||
|
||||
#define SIP_U32TO8_LE(p, v) \
|
||||
(p)[0] = (uint8_t)((v) >> 0); \
|
||||
(p)[1] = (uint8_t)((v) >> 8); \
|
||||
(p)[2] = (uint8_t)((v) >> 16); \
|
||||
(p)[3] = (uint8_t)((v) >> 24);
|
||||
|
||||
#define SIP_U64TO8_LE(p, v) \
|
||||
SIP_U32TO8_LE((p) + 0, (uint32_t)((v) >> 0)); \
|
||||
SIP_U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));
|
||||
|
||||
#define SIP_U8TO64_LE(p) \
|
||||
(((uint64_t)((p)[0]) << 0) | ((uint64_t)((p)[1]) << 8) | ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \
|
||||
((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))
|
||||
|
||||
#define SIPHASH_INITIALIZER {0, 0, 0, 0, {0}, 0, 0}
|
||||
|
||||
struct siphash {
|
||||
uint64_t v0, v1, v2, v3;
|
||||
|
||||
unsigned char buf[8], *p;
|
||||
uint64_t c;
|
||||
}; /* struct siphash */
|
||||
|
||||
#define SIP_KEYLEN 16
|
||||
|
||||
struct sipkey {
|
||||
uint64_t k[2];
|
||||
}; /* struct sipkey */
|
||||
|
||||
#define sip_keyof(k) sip_tokey(&(struct sipkey){{0}}, (k))
|
||||
|
||||
static struct sipkey* sip_tokey(struct sipkey* key, const void* src) {
|
||||
key->k[0] = SIP_U8TO64_LE((const unsigned char*)src);
|
||||
key->k[1] = SIP_U8TO64_LE((const unsigned char*)src + 8);
|
||||
return key;
|
||||
} /* sip_tokey() */
|
||||
|
||||
#ifdef SIPHASH_TOBIN
|
||||
|
||||
#define sip_binof(v) sip_tobin((unsigned char[8]){0}, (v))
|
||||
|
||||
static void* sip_tobin(void* dst, uint64_t u64) {
|
||||
SIP_U64TO8_LE((unsigned char*)dst, u64);
|
||||
return dst;
|
||||
} /* sip_tobin() */
|
||||
|
||||
#endif /* SIPHASH_TOBIN */
|
||||
|
||||
static void sip_round(struct siphash* H, const int rounds) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < rounds; i++) {
|
||||
H->v0 += H->v1;
|
||||
H->v1 = SIP_ROTL(H->v1, 13);
|
||||
H->v1 ^= H->v0;
|
||||
H->v0 = SIP_ROTL(H->v0, 32);
|
||||
|
||||
H->v2 += H->v3;
|
||||
H->v3 = SIP_ROTL(H->v3, 16);
|
||||
H->v3 ^= H->v2;
|
||||
|
||||
H->v0 += H->v3;
|
||||
H->v3 = SIP_ROTL(H->v3, 21);
|
||||
H->v3 ^= H->v0;
|
||||
|
||||
H->v2 += H->v1;
|
||||
H->v1 = SIP_ROTL(H->v1, 17);
|
||||
H->v1 ^= H->v2;
|
||||
H->v2 = SIP_ROTL(H->v2, 32);
|
||||
}
|
||||
} /* sip_round() */
|
||||
|
||||
static struct siphash* sip24_init(struct siphash* H, const struct sipkey* key) {
|
||||
H->v0 = SIP_ULL(0x736f6d65U, 0x70736575U) ^ key->k[0];
|
||||
H->v1 = SIP_ULL(0x646f7261U, 0x6e646f6dU) ^ key->k[1];
|
||||
H->v2 = SIP_ULL(0x6c796765U, 0x6e657261U) ^ key->k[0];
|
||||
H->v3 = SIP_ULL(0x74656462U, 0x79746573U) ^ key->k[1];
|
||||
|
||||
H->p = H->buf;
|
||||
H->c = 0;
|
||||
|
||||
return H;
|
||||
} /* sip24_init() */
|
||||
|
||||
#define sip_endof(a) (&(a)[sizeof(a) / sizeof *(a)])
|
||||
|
||||
static struct siphash* sip24_update(struct siphash* H, const void* src, size_t len) {
|
||||
const unsigned char *p = (const unsigned char*)src, *pe = p + len;
|
||||
uint64_t m;
|
||||
|
||||
do {
|
||||
while (p < pe && H->p < sip_endof(H->buf)) *H->p++ = *p++;
|
||||
|
||||
if (H->p < sip_endof(H->buf)) break;
|
||||
|
||||
m = SIP_U8TO64_LE(H->buf);
|
||||
H->v3 ^= m;
|
||||
sip_round(H, 2);
|
||||
H->v0 ^= m;
|
||||
|
||||
H->p = H->buf;
|
||||
H->c += 8;
|
||||
} while (p < pe);
|
||||
|
||||
return H;
|
||||
} /* sip24_update() */
|
||||
|
||||
static uint64_t sip24_final(struct siphash* H) {
|
||||
const char left = (char)(H->p - H->buf);
|
||||
uint64_t b = (H->c + left) << 56;
|
||||
|
||||
switch (left) {
|
||||
case 7:
|
||||
b |= (uint64_t)H->buf[6] << 48;
|
||||
/* fall through */
|
||||
case 6:
|
||||
b |= (uint64_t)H->buf[5] << 40;
|
||||
/* fall through */
|
||||
case 5:
|
||||
b |= (uint64_t)H->buf[4] << 32;
|
||||
/* fall through */
|
||||
case 4:
|
||||
b |= (uint64_t)H->buf[3] << 24;
|
||||
/* fall through */
|
||||
case 3:
|
||||
b |= (uint64_t)H->buf[2] << 16;
|
||||
/* fall through */
|
||||
case 2:
|
||||
b |= (uint64_t)H->buf[1] << 8;
|
||||
/* fall through */
|
||||
case 1:
|
||||
b |= (uint64_t)H->buf[0] << 0;
|
||||
/* fall through */
|
||||
case 0:
|
||||
break;
|
||||
}
|
||||
|
||||
H->v3 ^= b;
|
||||
sip_round(H, 2);
|
||||
H->v0 ^= b;
|
||||
H->v2 ^= 0xff;
|
||||
sip_round(H, 4);
|
||||
|
||||
return H->v0 ^ H->v1 ^ H->v2 ^ H->v3;
|
||||
} /* sip24_final() */
|
||||
|
||||
static uint64_t siphash24(const void* src, size_t len, const struct sipkey* key) {
|
||||
struct siphash state = SIPHASH_INITIALIZER;
|
||||
return sip24_final(sip24_update(sip24_init(&state, key), src, len));
|
||||
} /* siphash24() */
|
||||
|
||||
/*
|
||||
* SipHash-2-4 output with
|
||||
* k = 00 01 02 ...
|
||||
* and
|
||||
* in = (empty string)
|
||||
* in = 00 (1 byte)
|
||||
* in = 00 01 (2 bytes)
|
||||
* in = 00 01 02 (3 bytes)
|
||||
* ...
|
||||
* in = 00 01 02 ... 3e (63 bytes)
|
||||
*/
|
||||
static int sip24_valid(void) {
|
||||
/* clang-format off */
|
||||
static const unsigned char vectors[64][8] = {
|
||||
{ 0x31, 0x0e, 0x0e, 0xdd, 0x47, 0xdb, 0x6f, 0x72, },
|
||||
{ 0xfd, 0x67, 0xdc, 0x93, 0xc5, 0x39, 0xf8, 0x74, },
|
||||
{ 0x5a, 0x4f, 0xa9, 0xd9, 0x09, 0x80, 0x6c, 0x0d, },
|
||||
{ 0x2d, 0x7e, 0xfb, 0xd7, 0x96, 0x66, 0x67, 0x85, },
|
||||
{ 0xb7, 0x87, 0x71, 0x27, 0xe0, 0x94, 0x27, 0xcf, },
|
||||
{ 0x8d, 0xa6, 0x99, 0xcd, 0x64, 0x55, 0x76, 0x18, },
|
||||
{ 0xce, 0xe3, 0xfe, 0x58, 0x6e, 0x46, 0xc9, 0xcb, },
|
||||
{ 0x37, 0xd1, 0x01, 0x8b, 0xf5, 0x00, 0x02, 0xab, },
|
||||
{ 0x62, 0x24, 0x93, 0x9a, 0x79, 0xf5, 0xf5, 0x93, },
|
||||
{ 0xb0, 0xe4, 0xa9, 0x0b, 0xdf, 0x82, 0x00, 0x9e, },
|
||||
{ 0xf3, 0xb9, 0xdd, 0x94, 0xc5, 0xbb, 0x5d, 0x7a, },
|
||||
{ 0xa7, 0xad, 0x6b, 0x22, 0x46, 0x2f, 0xb3, 0xf4, },
|
||||
{ 0xfb, 0xe5, 0x0e, 0x86, 0xbc, 0x8f, 0x1e, 0x75, },
|
||||
{ 0x90, 0x3d, 0x84, 0xc0, 0x27, 0x56, 0xea, 0x14, },
|
||||
{ 0xee, 0xf2, 0x7a, 0x8e, 0x90, 0xca, 0x23, 0xf7, },
|
||||
{ 0xe5, 0x45, 0xbe, 0x49, 0x61, 0xca, 0x29, 0xa1, },
|
||||
{ 0xdb, 0x9b, 0xc2, 0x57, 0x7f, 0xcc, 0x2a, 0x3f, },
|
||||
{ 0x94, 0x47, 0xbe, 0x2c, 0xf5, 0xe9, 0x9a, 0x69, },
|
||||
{ 0x9c, 0xd3, 0x8d, 0x96, 0xf0, 0xb3, 0xc1, 0x4b, },
|
||||
{ 0xbd, 0x61, 0x79, 0xa7, 0x1d, 0xc9, 0x6d, 0xbb, },
|
||||
{ 0x98, 0xee, 0xa2, 0x1a, 0xf2, 0x5c, 0xd6, 0xbe, },
|
||||
{ 0xc7, 0x67, 0x3b, 0x2e, 0xb0, 0xcb, 0xf2, 0xd0, },
|
||||
{ 0x88, 0x3e, 0xa3, 0xe3, 0x95, 0x67, 0x53, 0x93, },
|
||||
{ 0xc8, 0xce, 0x5c, 0xcd, 0x8c, 0x03, 0x0c, 0xa8, },
|
||||
{ 0x94, 0xaf, 0x49, 0xf6, 0xc6, 0x50, 0xad, 0xb8, },
|
||||
{ 0xea, 0xb8, 0x85, 0x8a, 0xde, 0x92, 0xe1, 0xbc, },
|
||||
{ 0xf3, 0x15, 0xbb, 0x5b, 0xb8, 0x35, 0xd8, 0x17, },
|
||||
{ 0xad, 0xcf, 0x6b, 0x07, 0x63, 0x61, 0x2e, 0x2f, },
|
||||
{ 0xa5, 0xc9, 0x1d, 0xa7, 0xac, 0xaa, 0x4d, 0xde, },
|
||||
{ 0x71, 0x65, 0x95, 0x87, 0x66, 0x50, 0xa2, 0xa6, },
|
||||
{ 0x28, 0xef, 0x49, 0x5c, 0x53, 0xa3, 0x87, 0xad, },
|
||||
{ 0x42, 0xc3, 0x41, 0xd8, 0xfa, 0x92, 0xd8, 0x32, },
|
||||
{ 0xce, 0x7c, 0xf2, 0x72, 0x2f, 0x51, 0x27, 0x71, },
|
||||
{ 0xe3, 0x78, 0x59, 0xf9, 0x46, 0x23, 0xf3, 0xa7, },
|
||||
{ 0x38, 0x12, 0x05, 0xbb, 0x1a, 0xb0, 0xe0, 0x12, },
|
||||
{ 0xae, 0x97, 0xa1, 0x0f, 0xd4, 0x34, 0xe0, 0x15, },
|
||||
{ 0xb4, 0xa3, 0x15, 0x08, 0xbe, 0xff, 0x4d, 0x31, },
|
||||
{ 0x81, 0x39, 0x62, 0x29, 0xf0, 0x90, 0x79, 0x02, },
|
||||
{ 0x4d, 0x0c, 0xf4, 0x9e, 0xe5, 0xd4, 0xdc, 0xca, },
|
||||
{ 0x5c, 0x73, 0x33, 0x6a, 0x76, 0xd8, 0xbf, 0x9a, },
|
||||
{ 0xd0, 0xa7, 0x04, 0x53, 0x6b, 0xa9, 0x3e, 0x0e, },
|
||||
{ 0x92, 0x59, 0x58, 0xfc, 0xd6, 0x42, 0x0c, 0xad, },
|
||||
{ 0xa9, 0x15, 0xc2, 0x9b, 0xc8, 0x06, 0x73, 0x18, },
|
||||
{ 0x95, 0x2b, 0x79, 0xf3, 0xbc, 0x0a, 0xa6, 0xd4, },
|
||||
{ 0xf2, 0x1d, 0xf2, 0xe4, 0x1d, 0x45, 0x35, 0xf9, },
|
||||
{ 0x87, 0x57, 0x75, 0x19, 0x04, 0x8f, 0x53, 0xa9, },
|
||||
{ 0x10, 0xa5, 0x6c, 0xf5, 0xdf, 0xcd, 0x9a, 0xdb, },
|
||||
{ 0xeb, 0x75, 0x09, 0x5c, 0xcd, 0x98, 0x6c, 0xd0, },
|
||||
{ 0x51, 0xa9, 0xcb, 0x9e, 0xcb, 0xa3, 0x12, 0xe6, },
|
||||
{ 0x96, 0xaf, 0xad, 0xfc, 0x2c, 0xe6, 0x66, 0xc7, },
|
||||
{ 0x72, 0xfe, 0x52, 0x97, 0x5a, 0x43, 0x64, 0xee, },
|
||||
{ 0x5a, 0x16, 0x45, 0xb2, 0x76, 0xd5, 0x92, 0xa1, },
|
||||
{ 0xb2, 0x74, 0xcb, 0x8e, 0xbf, 0x87, 0x87, 0x0a, },
|
||||
{ 0x6f, 0x9b, 0xb4, 0x20, 0x3d, 0xe7, 0xb3, 0x81, },
|
||||
{ 0xea, 0xec, 0xb2, 0xa3, 0x0b, 0x22, 0xa8, 0x7f, },
|
||||
{ 0x99, 0x24, 0xa4, 0x3c, 0xc1, 0x31, 0x57, 0x24, },
|
||||
{ 0xbd, 0x83, 0x8d, 0x3a, 0xaf, 0xbf, 0x8d, 0xb7, },
|
||||
{ 0x0b, 0x1a, 0x2a, 0x32, 0x65, 0xd5, 0x1a, 0xea, },
|
||||
{ 0x13, 0x50, 0x79, 0xa3, 0x23, 0x1c, 0xe6, 0x60, },
|
||||
{ 0x93, 0x2b, 0x28, 0x46, 0xe4, 0xd7, 0x06, 0x66, },
|
||||
{ 0xe1, 0x91, 0x5f, 0x5c, 0xb1, 0xec, 0xa4, 0x6c, },
|
||||
{ 0xf3, 0x25, 0x96, 0x5c, 0xa1, 0x6d, 0x62, 0x9f, },
|
||||
{ 0x57, 0x5f, 0xf2, 0x8e, 0x60, 0x38, 0x1b, 0xe5, },
|
||||
{ 0x72, 0x45, 0x06, 0xeb, 0x4c, 0x32, 0x8a, 0x95, }
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
unsigned char in[64];
|
||||
struct sipkey k;
|
||||
size_t i;
|
||||
|
||||
sip_tokey(&k,
|
||||
"\000\001\002\003\004\005\006\007\010\011"
|
||||
"\012\013\014\015\016\017");
|
||||
|
||||
for (i = 0; i < sizeof in; ++i) {
|
||||
in[i] = (unsigned char)i;
|
||||
|
||||
if (siphash24(in, i, &k) != SIP_U8TO64_LE(vectors[i])) return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
} /* sip24_valid() */
|
||||
|
||||
#ifdef SIPHASH_MAIN
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
const int ok = sip24_valid();
|
||||
|
||||
if (ok)
|
||||
puts("OK");
|
||||
else
|
||||
puts("FAIL");
|
||||
|
||||
return !ok;
|
||||
} /* main() */
|
||||
|
||||
#endif /* SIPHASH_MAIN */
|
||||
|
||||
#endif /* SIPHASH_H */
|
||||
66
lib/expat/utf8tab.h
Normal file
66
lib/expat/utf8tab.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2017 Sebastian Pipping <sebastian@pipping.org>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* 0x80 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0x84 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0x88 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0x8C */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0x90 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0x94 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0x98 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0x9C */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0xA0 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0xA4 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0xA8 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0xAC */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0xB0 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0xB4 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0xB8 */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0xBC */ BT_TRAIL, BT_TRAIL, BT_TRAIL, BT_TRAIL,
|
||||
/* 0xC0 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2,
|
||||
/* 0xC4 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2,
|
||||
/* 0xC8 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2,
|
||||
/* 0xCC */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2,
|
||||
/* 0xD0 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2,
|
||||
/* 0xD4 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2,
|
||||
/* 0xD8 */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2,
|
||||
/* 0xDC */ BT_LEAD2, BT_LEAD2, BT_LEAD2, BT_LEAD2,
|
||||
/* 0xE0 */ BT_LEAD3, BT_LEAD3, BT_LEAD3, BT_LEAD3,
|
||||
/* 0xE4 */ BT_LEAD3, BT_LEAD3, BT_LEAD3, BT_LEAD3,
|
||||
/* 0xE8 */ BT_LEAD3, BT_LEAD3, BT_LEAD3, BT_LEAD3,
|
||||
/* 0xEC */ BT_LEAD3, BT_LEAD3, BT_LEAD3, BT_LEAD3,
|
||||
/* 0xF0 */ BT_LEAD4, BT_LEAD4, BT_LEAD4, BT_LEAD4,
|
||||
/* 0xF4 */ BT_LEAD4, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0xF8 */ BT_NONXML, BT_NONXML, BT_NONXML, BT_NONXML,
|
||||
/* 0xFC */ BT_NONXML, BT_NONXML, BT_MALFORM, BT_MALFORM,
|
||||
48
lib/expat/winconfig.h
Normal file
48
lib/expat/winconfig.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2002 Greg Stein <gstein@users.sourceforge.net>
|
||||
Copyright (c) 2005 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2017-2023 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2023 Orgad Shaneh <orgad.shaneh@audiocodes.com>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef WINCONFIG_H
|
||||
#define WINCONFIG_H
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#undef WIN32_LEAN_AND_MEAN
|
||||
|
||||
#include <memory.h>
|
||||
#include <string.h>
|
||||
|
||||
#endif /* ndef WINCONFIG_H */
|
||||
8210
lib/expat/xmlparse.c
Normal file
8210
lib/expat/xmlparse.c
Normal file
File diff suppressed because it is too large
Load Diff
1108
lib/expat/xmlrole.c
Normal file
1108
lib/expat/xmlrole.c
Normal file
File diff suppressed because it is too large
Load Diff
134
lib/expat/xmlrole.h
Normal file
134
lib/expat/xmlrole.h
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2002 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2017-2025 Sebastian Pipping <sebastian@pipping.org>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef XmlRole_INCLUDED
|
||||
#define XmlRole_INCLUDED 1
|
||||
|
||||
#include "xmltok.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum {
|
||||
XML_ROLE_ERROR = -1,
|
||||
XML_ROLE_NONE = 0,
|
||||
XML_ROLE_XML_DECL,
|
||||
XML_ROLE_INSTANCE_START,
|
||||
XML_ROLE_DOCTYPE_NONE,
|
||||
XML_ROLE_DOCTYPE_NAME,
|
||||
XML_ROLE_DOCTYPE_SYSTEM_ID,
|
||||
XML_ROLE_DOCTYPE_PUBLIC_ID,
|
||||
XML_ROLE_DOCTYPE_INTERNAL_SUBSET,
|
||||
XML_ROLE_DOCTYPE_CLOSE,
|
||||
XML_ROLE_GENERAL_ENTITY_NAME,
|
||||
XML_ROLE_PARAM_ENTITY_NAME,
|
||||
XML_ROLE_ENTITY_NONE,
|
||||
XML_ROLE_ENTITY_VALUE,
|
||||
XML_ROLE_ENTITY_SYSTEM_ID,
|
||||
XML_ROLE_ENTITY_PUBLIC_ID,
|
||||
XML_ROLE_ENTITY_COMPLETE,
|
||||
XML_ROLE_ENTITY_NOTATION_NAME,
|
||||
XML_ROLE_NOTATION_NONE,
|
||||
XML_ROLE_NOTATION_NAME,
|
||||
XML_ROLE_NOTATION_SYSTEM_ID,
|
||||
XML_ROLE_NOTATION_NO_SYSTEM_ID,
|
||||
XML_ROLE_NOTATION_PUBLIC_ID,
|
||||
XML_ROLE_ATTRIBUTE_NAME,
|
||||
XML_ROLE_ATTRIBUTE_TYPE_CDATA,
|
||||
XML_ROLE_ATTRIBUTE_TYPE_ID,
|
||||
XML_ROLE_ATTRIBUTE_TYPE_IDREF,
|
||||
XML_ROLE_ATTRIBUTE_TYPE_IDREFS,
|
||||
XML_ROLE_ATTRIBUTE_TYPE_ENTITY,
|
||||
XML_ROLE_ATTRIBUTE_TYPE_ENTITIES,
|
||||
XML_ROLE_ATTRIBUTE_TYPE_NMTOKEN,
|
||||
XML_ROLE_ATTRIBUTE_TYPE_NMTOKENS,
|
||||
XML_ROLE_ATTRIBUTE_ENUM_VALUE,
|
||||
XML_ROLE_ATTRIBUTE_NOTATION_VALUE,
|
||||
XML_ROLE_ATTLIST_NONE,
|
||||
XML_ROLE_ATTLIST_ELEMENT_NAME,
|
||||
XML_ROLE_IMPLIED_ATTRIBUTE_VALUE,
|
||||
XML_ROLE_REQUIRED_ATTRIBUTE_VALUE,
|
||||
XML_ROLE_DEFAULT_ATTRIBUTE_VALUE,
|
||||
XML_ROLE_FIXED_ATTRIBUTE_VALUE,
|
||||
XML_ROLE_ELEMENT_NONE,
|
||||
XML_ROLE_ELEMENT_NAME,
|
||||
XML_ROLE_CONTENT_ANY,
|
||||
XML_ROLE_CONTENT_EMPTY,
|
||||
XML_ROLE_CONTENT_PCDATA,
|
||||
XML_ROLE_GROUP_OPEN,
|
||||
XML_ROLE_GROUP_CLOSE,
|
||||
XML_ROLE_GROUP_CLOSE_REP,
|
||||
XML_ROLE_GROUP_CLOSE_OPT,
|
||||
XML_ROLE_GROUP_CLOSE_PLUS,
|
||||
XML_ROLE_GROUP_CHOICE,
|
||||
XML_ROLE_GROUP_SEQUENCE,
|
||||
XML_ROLE_CONTENT_ELEMENT,
|
||||
XML_ROLE_CONTENT_ELEMENT_REP,
|
||||
XML_ROLE_CONTENT_ELEMENT_OPT,
|
||||
XML_ROLE_CONTENT_ELEMENT_PLUS,
|
||||
XML_ROLE_PI,
|
||||
XML_ROLE_COMMENT,
|
||||
#ifdef XML_DTD
|
||||
XML_ROLE_TEXT_DECL,
|
||||
XML_ROLE_IGNORE_SECT,
|
||||
XML_ROLE_INNER_PARAM_ENTITY_REF,
|
||||
#endif /* XML_DTD */
|
||||
XML_ROLE_PARAM_ENTITY_REF
|
||||
};
|
||||
|
||||
typedef struct prolog_state {
|
||||
int(PTRCALL* handler)(struct prolog_state* state, int tok, const char* ptr, const char* end, const ENCODING* enc);
|
||||
unsigned level;
|
||||
int role_none;
|
||||
#ifdef XML_DTD
|
||||
unsigned includeLevel;
|
||||
int documentEntity;
|
||||
int inEntityValue;
|
||||
#endif /* XML_DTD */
|
||||
} PROLOG_STATE;
|
||||
|
||||
void XmlPrologStateInit(PROLOG_STATE* state);
|
||||
#ifdef XML_DTD
|
||||
void XmlPrologStateInitExternalEntity(PROLOG_STATE* state);
|
||||
#endif /* XML_DTD */
|
||||
|
||||
#define XmlTokenRole(state, tok, ptr, end, enc) (((state)->handler)(state, tok, ptr, end, enc))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* not XmlRole_INCLUDED */
|
||||
1489
lib/expat/xmltok.c
Normal file
1489
lib/expat/xmltok.c
Normal file
File diff suppressed because it is too large
Load Diff
288
lib/expat/xmltok.h
Normal file
288
lib/expat/xmltok.h
Normal file
@ -0,0 +1,288 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2002-2005 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2016-2024 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef XmlTok_INCLUDED
|
||||
#define XmlTok_INCLUDED 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* The following token may be returned by XmlContentTok */
|
||||
#define XML_TOK_TRAILING_RSQB \
|
||||
-5 /* ] or ]] at the end of the scan; might be \
|
||||
start of illegal ]]> sequence */
|
||||
/* The following tokens may be returned by both XmlPrologTok and
|
||||
XmlContentTok.
|
||||
*/
|
||||
#define XML_TOK_NONE -4 /* The string to be scanned is empty */
|
||||
#define XML_TOK_TRAILING_CR \
|
||||
-3 /* A CR at the end of the scan; \
|
||||
might be part of CRLF sequence */
|
||||
#define XML_TOK_PARTIAL_CHAR -2 /* only part of a multibyte sequence */
|
||||
#define XML_TOK_PARTIAL -1 /* only part of a token */
|
||||
#define XML_TOK_INVALID 0
|
||||
|
||||
/* The following tokens are returned by XmlContentTok; some are also
|
||||
returned by XmlAttributeValueTok, XmlEntityTok, XmlCdataSectionTok.
|
||||
*/
|
||||
#define XML_TOK_START_TAG_WITH_ATTS 1
|
||||
#define XML_TOK_START_TAG_NO_ATTS 2
|
||||
#define XML_TOK_EMPTY_ELEMENT_WITH_ATTS 3 /* empty element tag <e/> */
|
||||
#define XML_TOK_EMPTY_ELEMENT_NO_ATTS 4
|
||||
#define XML_TOK_END_TAG 5
|
||||
#define XML_TOK_DATA_CHARS 6
|
||||
#define XML_TOK_DATA_NEWLINE 7
|
||||
#define XML_TOK_CDATA_SECT_OPEN 8
|
||||
#define XML_TOK_ENTITY_REF 9
|
||||
#define XML_TOK_CHAR_REF 10 /* numeric character reference */
|
||||
|
||||
/* The following tokens may be returned by both XmlPrologTok and
|
||||
XmlContentTok.
|
||||
*/
|
||||
#define XML_TOK_PI 11 /* processing instruction */
|
||||
#define XML_TOK_XML_DECL 12 /* XML decl or text decl */
|
||||
#define XML_TOK_COMMENT 13
|
||||
#define XML_TOK_BOM 14 /* Byte order mark */
|
||||
|
||||
/* The following tokens are returned only by XmlPrologTok */
|
||||
#define XML_TOK_PROLOG_S 15
|
||||
#define XML_TOK_DECL_OPEN 16 /* <!foo */
|
||||
#define XML_TOK_DECL_CLOSE 17 /* > */
|
||||
#define XML_TOK_NAME 18
|
||||
#define XML_TOK_NMTOKEN 19
|
||||
#define XML_TOK_POUND_NAME 20 /* #name */
|
||||
#define XML_TOK_OR 21 /* | */
|
||||
#define XML_TOK_PERCENT 22
|
||||
#define XML_TOK_OPEN_PAREN 23
|
||||
#define XML_TOK_CLOSE_PAREN 24
|
||||
#define XML_TOK_OPEN_BRACKET 25
|
||||
#define XML_TOK_CLOSE_BRACKET 26
|
||||
#define XML_TOK_LITERAL 27
|
||||
#define XML_TOK_PARAM_ENTITY_REF 28
|
||||
#define XML_TOK_INSTANCE_START 29
|
||||
|
||||
/* The following occur only in element type declarations */
|
||||
#define XML_TOK_NAME_QUESTION 30 /* name? */
|
||||
#define XML_TOK_NAME_ASTERISK 31 /* name* */
|
||||
#define XML_TOK_NAME_PLUS 32 /* name+ */
|
||||
#define XML_TOK_COND_SECT_OPEN 33 /* <![ */
|
||||
#define XML_TOK_COND_SECT_CLOSE 34 /* ]]> */
|
||||
#define XML_TOK_CLOSE_PAREN_QUESTION 35 /* )? */
|
||||
#define XML_TOK_CLOSE_PAREN_ASTERISK 36 /* )* */
|
||||
#define XML_TOK_CLOSE_PAREN_PLUS 37 /* )+ */
|
||||
#define XML_TOK_COMMA 38
|
||||
|
||||
/* The following token is returned only by XmlAttributeValueTok */
|
||||
#define XML_TOK_ATTRIBUTE_VALUE_S 39
|
||||
|
||||
/* The following token is returned only by XmlCdataSectionTok */
|
||||
#define XML_TOK_CDATA_SECT_CLOSE 40
|
||||
|
||||
/* With namespace processing this is returned by XmlPrologTok for a
|
||||
name with a colon.
|
||||
*/
|
||||
#define XML_TOK_PREFIXED_NAME 41
|
||||
|
||||
#ifdef XML_DTD
|
||||
#define XML_TOK_IGNORE_SECT 42
|
||||
#endif /* XML_DTD */
|
||||
|
||||
#ifdef XML_DTD
|
||||
#define XML_N_STATES 4
|
||||
#else /* not XML_DTD */
|
||||
#define XML_N_STATES 3
|
||||
#endif /* not XML_DTD */
|
||||
|
||||
#define XML_PROLOG_STATE 0
|
||||
#define XML_CONTENT_STATE 1
|
||||
#define XML_CDATA_SECTION_STATE 2
|
||||
#ifdef XML_DTD
|
||||
#define XML_IGNORE_SECTION_STATE 3
|
||||
#endif /* XML_DTD */
|
||||
|
||||
#define XML_N_LITERAL_TYPES 2
|
||||
#define XML_ATTRIBUTE_VALUE_LITERAL 0
|
||||
#define XML_ENTITY_VALUE_LITERAL 1
|
||||
|
||||
/* The size of the buffer passed to XmlUtf8Encode must be at least this. */
|
||||
#define XML_UTF8_ENCODE_MAX 4
|
||||
/* The size of the buffer passed to XmlUtf16Encode must be at least this. */
|
||||
#define XML_UTF16_ENCODE_MAX 2
|
||||
|
||||
typedef struct position {
|
||||
/* first line and first column are 0 not 1 */
|
||||
XML_Size lineNumber;
|
||||
XML_Size columnNumber;
|
||||
} POSITION;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
const char* valuePtr;
|
||||
const char* valueEnd;
|
||||
char normalized;
|
||||
} ATTRIBUTE;
|
||||
|
||||
struct encoding;
|
||||
typedef struct encoding ENCODING;
|
||||
|
||||
typedef int(PTRCALL* SCANNER)(const ENCODING*, const char*, const char*, const char**);
|
||||
|
||||
enum XML_Convert_Result {
|
||||
XML_CONVERT_COMPLETED = 0,
|
||||
XML_CONVERT_INPUT_INCOMPLETE = 1,
|
||||
XML_CONVERT_OUTPUT_EXHAUSTED = 2 /* and therefore potentially input remaining as well */
|
||||
};
|
||||
|
||||
struct encoding {
|
||||
SCANNER scanners[XML_N_STATES];
|
||||
SCANNER literalScanners[XML_N_LITERAL_TYPES];
|
||||
int(PTRCALL* nameMatchesAscii)(const ENCODING*, const char*, const char*, const char*);
|
||||
int(PTRFASTCALL* nameLength)(const ENCODING*, const char*);
|
||||
const char*(PTRFASTCALL* skipS)(const ENCODING*, const char*);
|
||||
int(PTRCALL* getAtts)(const ENCODING* enc, const char* ptr, int attsMax, ATTRIBUTE* atts);
|
||||
int(PTRFASTCALL* charRefNumber)(const ENCODING* enc, const char* ptr);
|
||||
int(PTRCALL* predefinedEntityName)(const ENCODING*, const char*, const char*);
|
||||
void(PTRCALL* updatePosition)(const ENCODING*, const char* ptr, const char* end, POSITION*);
|
||||
int(PTRCALL* isPublicId)(const ENCODING* enc, const char* ptr, const char* end, const char** badPtr);
|
||||
enum XML_Convert_Result(PTRCALL* utf8Convert)(const ENCODING* enc, const char** fromP, const char* fromLim,
|
||||
char** toP, const char* toLim);
|
||||
enum XML_Convert_Result(PTRCALL* utf16Convert)(const ENCODING* enc, const char** fromP, const char* fromLim,
|
||||
unsigned short** toP, const unsigned short* toLim);
|
||||
int minBytesPerChar;
|
||||
char isUtf8;
|
||||
char isUtf16;
|
||||
};
|
||||
|
||||
/* Scan the string starting at ptr until the end of the next complete
|
||||
token, but do not scan past eptr. Return an integer giving the
|
||||
type of token.
|
||||
|
||||
Return XML_TOK_NONE when ptr == eptr; nextTokPtr will not be set.
|
||||
|
||||
Return XML_TOK_PARTIAL when the string does not contain a complete
|
||||
token; nextTokPtr will not be set.
|
||||
|
||||
Return XML_TOK_INVALID when the string does not start a valid
|
||||
token; nextTokPtr will be set to point to the character which made
|
||||
the token invalid.
|
||||
|
||||
Otherwise the string starts with a valid token; nextTokPtr will be
|
||||
set to point to the character following the end of that token.
|
||||
|
||||
Each data character counts as a single token, but adjacent data
|
||||
characters may be returned together. Similarly for characters in
|
||||
the prolog outside literals, comments and processing instructions.
|
||||
*/
|
||||
|
||||
#define XmlTok(enc, state, ptr, end, nextTokPtr) (((enc)->scanners[state])(enc, ptr, end, nextTokPtr))
|
||||
|
||||
#define XmlPrologTok(enc, ptr, end, nextTokPtr) XmlTok(enc, XML_PROLOG_STATE, ptr, end, nextTokPtr)
|
||||
|
||||
#define XmlContentTok(enc, ptr, end, nextTokPtr) XmlTok(enc, XML_CONTENT_STATE, ptr, end, nextTokPtr)
|
||||
|
||||
#define XmlCdataSectionTok(enc, ptr, end, nextTokPtr) XmlTok(enc, XML_CDATA_SECTION_STATE, ptr, end, nextTokPtr)
|
||||
|
||||
#ifdef XML_DTD
|
||||
|
||||
#define XmlIgnoreSectionTok(enc, ptr, end, nextTokPtr) XmlTok(enc, XML_IGNORE_SECTION_STATE, ptr, end, nextTokPtr)
|
||||
|
||||
#endif /* XML_DTD */
|
||||
|
||||
/* This is used for performing a 2nd-level tokenization on the content
|
||||
of a literal that has already been returned by XmlTok.
|
||||
*/
|
||||
#define XmlLiteralTok(enc, literalType, ptr, end, nextTokPtr) \
|
||||
(((enc)->literalScanners[literalType])(enc, ptr, end, nextTokPtr))
|
||||
|
||||
#define XmlAttributeValueTok(enc, ptr, end, nextTokPtr) \
|
||||
XmlLiteralTok(enc, XML_ATTRIBUTE_VALUE_LITERAL, ptr, end, nextTokPtr)
|
||||
|
||||
#define XmlEntityValueTok(enc, ptr, end, nextTokPtr) XmlLiteralTok(enc, XML_ENTITY_VALUE_LITERAL, ptr, end, nextTokPtr)
|
||||
|
||||
#define XmlNameMatchesAscii(enc, ptr1, end1, ptr2) (((enc)->nameMatchesAscii)(enc, ptr1, end1, ptr2))
|
||||
|
||||
#define XmlNameLength(enc, ptr) (((enc)->nameLength)(enc, ptr))
|
||||
|
||||
#define XmlSkipS(enc, ptr) (((enc)->skipS)(enc, ptr))
|
||||
|
||||
#define XmlGetAttributes(enc, ptr, attsMax, atts) (((enc)->getAtts)(enc, ptr, attsMax, atts))
|
||||
|
||||
#define XmlCharRefNumber(enc, ptr) (((enc)->charRefNumber)(enc, ptr))
|
||||
|
||||
#define XmlPredefinedEntityName(enc, ptr, end) (((enc)->predefinedEntityName)(enc, ptr, end))
|
||||
|
||||
#define XmlUpdatePosition(enc, ptr, end, pos) (((enc)->updatePosition)(enc, ptr, end, pos))
|
||||
|
||||
#define XmlIsPublicId(enc, ptr, end, badPtr) (((enc)->isPublicId)(enc, ptr, end, badPtr))
|
||||
|
||||
#define XmlUtf8Convert(enc, fromP, fromLim, toP, toLim) (((enc)->utf8Convert)(enc, fromP, fromLim, toP, toLim))
|
||||
|
||||
#define XmlUtf16Convert(enc, fromP, fromLim, toP, toLim) (((enc)->utf16Convert)(enc, fromP, fromLim, toP, toLim))
|
||||
|
||||
typedef struct {
|
||||
ENCODING initEnc;
|
||||
const ENCODING** encPtr;
|
||||
} INIT_ENCODING;
|
||||
|
||||
int XmlParseXmlDecl(int isGeneralTextEntity, const ENCODING* enc, const char* ptr, const char* end, const char** badPtr,
|
||||
const char** versionPtr, const char** versionEndPtr, const char** encodingNamePtr,
|
||||
const ENCODING** namedEncodingPtr, int* standalonePtr);
|
||||
|
||||
int XmlInitEncoding(INIT_ENCODING* p, const ENCODING** encPtr, const char* name);
|
||||
const ENCODING* XmlGetUtf8InternalEncoding(void);
|
||||
const ENCODING* XmlGetUtf16InternalEncoding(void);
|
||||
int FASTCALL XmlUtf8Encode(int charNumber, char* buf);
|
||||
int FASTCALL XmlUtf16Encode(int charNumber, unsigned short* buf);
|
||||
int XmlSizeOfUnknownEncoding(void);
|
||||
|
||||
typedef int(XMLCALL* CONVERTER)(void* userData, const char* p);
|
||||
|
||||
ENCODING* XmlInitUnknownEncoding(void* mem, const int* table, CONVERTER convert, void* userData);
|
||||
|
||||
int XmlParseXmlDeclNS(int isGeneralTextEntity, const ENCODING* enc, const char* ptr, const char* end,
|
||||
const char** badPtr, const char** versionPtr, const char** versionEndPtr,
|
||||
const char** encodingNamePtr, const ENCODING** namedEncodingPtr, int* standalonePtr);
|
||||
|
||||
int XmlInitEncodingNS(INIT_ENCODING* p, const ENCODING** encPtr, const char* name);
|
||||
const ENCODING* XmlGetUtf8InternalEncodingNS(void);
|
||||
const ENCODING* XmlGetUtf16InternalEncodingNS(void);
|
||||
ENCODING* XmlInitUnknownEncodingNS(void* mem, const int* table, CONVERTER convert, void* userData);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* not XmlTok_INCLUDED */
|
||||
1719
lib/expat/xmltok_impl.c
Normal file
1719
lib/expat/xmltok_impl.c
Normal file
File diff suppressed because it is too large
Load Diff
74
lib/expat/xmltok_impl.h
Normal file
74
lib/expat/xmltok_impl.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2017-2019 Sebastian Pipping <sebastian@pipping.org>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
enum {
|
||||
BT_NONXML, /* e.g. noncharacter-FFFF */
|
||||
BT_MALFORM, /* illegal, with regard to encoding */
|
||||
BT_LT, /* less than = "<" */
|
||||
BT_AMP, /* ampersand = "&" */
|
||||
BT_RSQB, /* right square bracket = "[" */
|
||||
BT_LEAD2, /* lead byte of a 2-byte UTF-8 character */
|
||||
BT_LEAD3, /* lead byte of a 3-byte UTF-8 character */
|
||||
BT_LEAD4, /* lead byte of a 4-byte UTF-8 character */
|
||||
BT_TRAIL, /* trailing unit, e.g. second 16-bit unit of a 4-byte char. */
|
||||
BT_CR, /* carriage return = "\r" */
|
||||
BT_LF, /* line feed = "\n" */
|
||||
BT_GT, /* greater than = ">" */
|
||||
BT_QUOT, /* quotation character = "\"" */
|
||||
BT_APOS, /* apostrophe = "'" */
|
||||
BT_EQUALS, /* equal sign = "=" */
|
||||
BT_QUEST, /* question mark = "?" */
|
||||
BT_EXCL, /* exclamation mark = "!" */
|
||||
BT_SOL, /* solidus, slash = "/" */
|
||||
BT_SEMI, /* semicolon = ";" */
|
||||
BT_NUM, /* number sign = "#" */
|
||||
BT_LSQB, /* left square bracket = "[" */
|
||||
BT_S, /* white space, e.g. "\t", " "[, "\r"] */
|
||||
BT_NMSTRT, /* non-hex name start letter = "G".."Z" + "g".."z" + "_" */
|
||||
BT_COLON, /* colon = ":" */
|
||||
BT_HEX, /* hex letter = "A".."F" + "a".."f" */
|
||||
BT_DIGIT, /* digit = "0".."9" */
|
||||
BT_NAME, /* dot and middle dot = "." + chr(0xb7) */
|
||||
BT_MINUS, /* minus = "-" */
|
||||
BT_OTHER, /* known not to be a name or name start character */
|
||||
BT_NONASCII, /* might be a name or name start character */
|
||||
BT_PERCNT, /* percent sign = "%" */
|
||||
BT_LPAR, /* left parenthesis = "(" */
|
||||
BT_RPAR, /* right parenthesis = "(" */
|
||||
BT_AST, /* asterisk = "*" */
|
||||
BT_PLUS, /* plus sign = "+" */
|
||||
BT_COMMA, /* comma = "," */
|
||||
BT_VERBAR /* vertical bar = "|" */
|
||||
};
|
||||
|
||||
#include <stddef.h>
|
||||
98
lib/expat/xmltok_ns.c
Normal file
98
lib/expat/xmltok_ns.c
Normal file
@ -0,0 +1,98 @@
|
||||
/* This file is included!
|
||||
__ __ _
|
||||
___\ \/ /_ __ __ _| |_
|
||||
/ _ \\ /| '_ \ / _` | __|
|
||||
| __// \| |_) | (_| | |_
|
||||
\___/_/\_\ .__/ \__,_|\__|
|
||||
|_| XML parser
|
||||
|
||||
Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
|
||||
Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net>
|
||||
Copyright (c) 2002 Greg Stein <gstein@users.sourceforge.net>
|
||||
Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2002-2006 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2017-2021 Sebastian Pipping <sebastian@pipping.org>
|
||||
Licensed under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifdef XML_TOK_NS_C
|
||||
|
||||
const ENCODING* NS(XmlGetUtf8InternalEncoding)(void) { return &ns(internal_utf8_encoding).enc; }
|
||||
|
||||
const ENCODING* NS(XmlGetUtf16InternalEncoding)(void) {
|
||||
#if BYTEORDER == 1234
|
||||
return &ns(internal_little2_encoding).enc;
|
||||
#elif BYTEORDER == 4321
|
||||
return &ns(internal_big2_encoding).enc;
|
||||
#else
|
||||
const short n = 1;
|
||||
return (*(const char*)&n ? &ns(internal_little2_encoding).enc : &ns(internal_big2_encoding).enc);
|
||||
#endif
|
||||
}
|
||||
|
||||
static const ENCODING* const NS(encodings)[] = {
|
||||
&ns(latin1_encoding).enc, &ns(ascii_encoding).enc, &ns(utf8_encoding).enc, &ns(big2_encoding).enc,
|
||||
&ns(big2_encoding).enc, &ns(little2_encoding).enc, &ns(utf8_encoding).enc /* NO_ENC */
|
||||
};
|
||||
|
||||
static int PTRCALL NS(initScanProlog)(const ENCODING* enc, const char* ptr, const char* end, const char** nextTokPtr) {
|
||||
return initScan(NS(encodings), (const INIT_ENCODING*)enc, XML_PROLOG_STATE, ptr, end, nextTokPtr);
|
||||
}
|
||||
|
||||
static int PTRCALL NS(initScanContent)(const ENCODING* enc, const char* ptr, const char* end, const char** nextTokPtr) {
|
||||
return initScan(NS(encodings), (const INIT_ENCODING*)enc, XML_CONTENT_STATE, ptr, end, nextTokPtr);
|
||||
}
|
||||
|
||||
int NS(XmlInitEncoding)(INIT_ENCODING* p, const ENCODING** encPtr, const char* name) {
|
||||
int i = getEncodingIndex(name);
|
||||
if (i == UNKNOWN_ENC) return 0;
|
||||
SET_INIT_ENC_INDEX(p, i);
|
||||
p->initEnc.scanners[XML_PROLOG_STATE] = NS(initScanProlog);
|
||||
p->initEnc.scanners[XML_CONTENT_STATE] = NS(initScanContent);
|
||||
p->initEnc.updatePosition = initUpdatePosition;
|
||||
p->encPtr = encPtr;
|
||||
*encPtr = &(p->initEnc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const ENCODING* NS(findEncoding)(const ENCODING* enc, const char* ptr, const char* end) {
|
||||
#define ENCODING_MAX 128
|
||||
char buf[ENCODING_MAX] = "";
|
||||
char* p = buf;
|
||||
int i;
|
||||
XmlUtf8Convert(enc, &ptr, end, &p, p + ENCODING_MAX - 1);
|
||||
if (ptr != end) return 0;
|
||||
*p = 0;
|
||||
if (streqci(buf, KW_UTF_16) && enc->minBytesPerChar == 2) return enc;
|
||||
i = getEncodingIndex(buf);
|
||||
if (i == UNKNOWN_ENC) return 0;
|
||||
return NS(encodings)[i];
|
||||
}
|
||||
|
||||
int NS(XmlParseXmlDecl)(int isGeneralTextEntity, const ENCODING* enc, const char* ptr, const char* end,
|
||||
const char** badPtr, const char** versionPtr, const char** versionEndPtr,
|
||||
const char** encodingName, const ENCODING** encoding, int* standalone) {
|
||||
return doParseXmlDecl(NS(findEncoding), isGeneralTextEntity, enc, ptr, end, badPtr, versionPtr, versionEndPtr,
|
||||
encodingName, encoding, standalone);
|
||||
}
|
||||
|
||||
#endif /* XML_TOK_NS_C */
|
||||
File diff suppressed because it is too large
Load Diff
1013
lib/miniz/miniz.h
1013
lib/miniz/miniz.h
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
Subproject commit 8224d278c58e76abf781c2e015f28a09419f27b2
|
||||
Subproject commit 98a5aa1f8969ccd317c9b45bf0fa84b6c82e167f
|
||||
@ -1,37 +1,50 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
[platformio]
|
||||
crosspoint_version = 0.6.0
|
||||
default_envs = default
|
||||
|
||||
[env:esp32-c3-devkitm-1]
|
||||
[base]
|
||||
platform = espressif32
|
||||
board = esp32-c3-devkitm-1
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
upload_speed = 921600
|
||||
check_tool = cppcheck
|
||||
check_skip_packages = yes
|
||||
check_severity = medium, high
|
||||
|
||||
board_upload.flash_size = 16MB
|
||||
board_upload.maximum_size = 16777216
|
||||
board_upload.offset_address = 0x10000
|
||||
|
||||
build_flags =
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1
|
||||
-DEINK_DISPLAY_SINGLE_BUFFER_MODE=1
|
||||
# https://libexpat.github.io/doc/api/latest/#XML_GE
|
||||
-DXML_GE=0
|
||||
-DXML_CONTEXT_BYTES=1024
|
||||
-std=c++2a
|
||||
|
||||
; Board configuration
|
||||
board_build.flash_mode = dio
|
||||
board_build.flash_size = 16MB
|
||||
board_build.partitions = partitions.csv
|
||||
|
||||
build_flags =
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1
|
||||
|
||||
; Libraries
|
||||
lib_deps =
|
||||
zinggjm/GxEPD2@^1.6.5
|
||||
https://github.com/leethomason/tinyxml2.git#11.0.0
|
||||
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor
|
||||
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
|
||||
EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay
|
||||
|
||||
[env:default]
|
||||
extends = base
|
||||
build_flags =
|
||||
${base.build_flags}
|
||||
-DCROSSPOINT_VERSION=\"${platformio.crosspoint_version}-dev\"
|
||||
|
||||
[env:gh_release]
|
||||
extends = base
|
||||
build_flags =
|
||||
${base.build_flags}
|
||||
-DCROSSPOINT_VERSION=\"${platformio.crosspoint_version}\"
|
||||
|
||||
67
src/CrossPointSettings.cpp
Normal file
67
src/CrossPointSettings.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "CrossPointSettings.h"
|
||||
|
||||
#include <HardwareSerial.h>
|
||||
#include <SD.h>
|
||||
#include <Serialization.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
|
||||
// Initialize the static instance
|
||||
CrossPointSettings CrossPointSettings::instance;
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||
constexpr uint8_t SETTINGS_COUNT = 2;
|
||||
constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin";
|
||||
} // namespace
|
||||
|
||||
bool CrossPointSettings::saveToFile() const {
|
||||
// Make sure the directory exists
|
||||
SD.mkdir("/.crosspoint");
|
||||
|
||||
std::ofstream outputFile(SETTINGS_FILE);
|
||||
serialization::writePod(outputFile, SETTINGS_FILE_VERSION);
|
||||
serialization::writePod(outputFile, SETTINGS_COUNT);
|
||||
serialization::writePod(outputFile, whiteSleepScreen);
|
||||
serialization::writePod(outputFile, extraParagraphSpacing);
|
||||
outputFile.close();
|
||||
|
||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CrossPointSettings::loadFromFile() {
|
||||
if (!SD.exists(SETTINGS_FILE + 3)) { // +3 to skip "/sd" prefix
|
||||
Serial.printf("[%lu] [CPS] Settings file does not exist, using defaults\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ifstream inputFile(SETTINGS_FILE);
|
||||
|
||||
uint8_t version;
|
||||
serialization::readPod(inputFile, version);
|
||||
if (version != SETTINGS_FILE_VERSION) {
|
||||
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||
inputFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t fileSettingsCount = 0;
|
||||
serialization::readPod(inputFile, fileSettingsCount);
|
||||
|
||||
// load settings that exist
|
||||
switch (fileSettingsCount) {
|
||||
case 1:
|
||||
serialization::readPod(inputFile, whiteSleepScreen);
|
||||
break;
|
||||
case 2:
|
||||
serialization::readPod(inputFile, whiteSleepScreen);
|
||||
serialization::readPod(inputFile, extraParagraphSpacing);
|
||||
break;
|
||||
}
|
||||
|
||||
inputFile.close();
|
||||
Serial.printf("[%lu] [CPS] Settings loaded from file\n", millis());
|
||||
return true;
|
||||
}
|
||||
34
src/CrossPointSettings.h
Normal file
34
src/CrossPointSettings.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
|
||||
class CrossPointSettings {
|
||||
private:
|
||||
// Private constructor for singleton
|
||||
CrossPointSettings() = default;
|
||||
|
||||
// Static instance
|
||||
static CrossPointSettings instance;
|
||||
|
||||
public:
|
||||
// Delete copy constructor and assignment
|
||||
CrossPointSettings(const CrossPointSettings&) = delete;
|
||||
CrossPointSettings& operator=(const CrossPointSettings&) = delete;
|
||||
|
||||
// Sleep screen settings
|
||||
uint8_t whiteSleepScreen = 0;
|
||||
|
||||
// Text rendering settings
|
||||
uint8_t extraParagraphSpacing = 1;
|
||||
|
||||
~CrossPointSettings() = default;
|
||||
|
||||
// Get singleton instance
|
||||
static CrossPointSettings& getInstance() { return instance; }
|
||||
|
||||
bool saveToFile() const;
|
||||
bool loadFromFile();
|
||||
};
|
||||
|
||||
// Helper macro to access settings
|
||||
#define SETTINGS CrossPointSettings::getInstance()
|
||||
@ -6,12 +6,16 @@
|
||||
|
||||
#include <fstream>
|
||||
|
||||
constexpr uint8_t STATE_VERSION = 1;
|
||||
namespace {
|
||||
constexpr uint8_t STATE_FILE_VERSION = 1;
|
||||
constexpr char STATE_FILE[] = "/sd/.crosspoint/state.bin";
|
||||
} // namespace
|
||||
|
||||
CrossPointState CrossPointState::instance;
|
||||
|
||||
bool CrossPointState::saveToFile() const {
|
||||
std::ofstream outputFile(STATE_FILE);
|
||||
serialization::writePod(outputFile, STATE_VERSION);
|
||||
serialization::writePod(outputFile, STATE_FILE_VERSION);
|
||||
serialization::writeString(outputFile, openEpubPath);
|
||||
outputFile.close();
|
||||
return true;
|
||||
@ -22,8 +26,8 @@ bool CrossPointState::loadFromFile() {
|
||||
|
||||
uint8_t version;
|
||||
serialization::readPod(inputFile, version);
|
||||
if (version != STATE_VERSION) {
|
||||
Serial.printf("CrossPointState: Unknown version %u\n", version);
|
||||
if (version != STATE_FILE_VERSION) {
|
||||
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||
inputFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3,11 +3,20 @@
|
||||
#include <string>
|
||||
|
||||
class CrossPointState {
|
||||
// Static instance
|
||||
static CrossPointState instance;
|
||||
|
||||
public:
|
||||
std::string openEpubPath;
|
||||
~CrossPointState() = default;
|
||||
|
||||
// Get singleton instance
|
||||
static CrossPointState& getInstance() { return instance; }
|
||||
|
||||
bool saveToFile() const;
|
||||
|
||||
bool loadFromFile();
|
||||
};
|
||||
|
||||
// Helper macro to access settings
|
||||
#define APP_STATE CrossPointState::getInstance()
|
||||
|
||||
18
src/activities/Activity.h
Normal file
18
src/activities/Activity.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <InputManager.h>
|
||||
|
||||
class GfxRenderer;
|
||||
|
||||
class Activity {
|
||||
protected:
|
||||
GfxRenderer& renderer;
|
||||
InputManager& inputManager;
|
||||
|
||||
public:
|
||||
explicit Activity(GfxRenderer& renderer, InputManager& inputManager)
|
||||
: renderer(renderer), inputManager(inputManager) {}
|
||||
virtual ~Activity() = default;
|
||||
virtual void onEnter() {}
|
||||
virtual void onExit() {}
|
||||
virtual void loop() {}
|
||||
};
|
||||
21
src/activities/ActivityWithSubactivity.cpp
Normal file
21
src/activities/ActivityWithSubactivity.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "ActivityWithSubactivity.h"
|
||||
|
||||
void ActivityWithSubactivity::exitActivity() {
|
||||
if (subActivity) {
|
||||
subActivity->onExit();
|
||||
subActivity.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityWithSubactivity::enterNewActivity(Activity* activity) {
|
||||
subActivity.reset(activity);
|
||||
subActivity->onEnter();
|
||||
}
|
||||
|
||||
void ActivityWithSubactivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityWithSubactivity::onExit() { exitActivity(); }
|
||||
17
src/activities/ActivityWithSubactivity.h
Normal file
17
src/activities/ActivityWithSubactivity.h
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
#include "Activity.h"
|
||||
|
||||
class ActivityWithSubactivity : public Activity {
|
||||
protected:
|
||||
std::unique_ptr<Activity> subActivity = nullptr;
|
||||
void exitActivity();
|
||||
void enterNewActivity(Activity* activity);
|
||||
|
||||
public:
|
||||
explicit ActivityWithSubactivity(GfxRenderer& renderer, InputManager& inputManager)
|
||||
: Activity(renderer, inputManager) {}
|
||||
void loop() override;
|
||||
void onExit() override;
|
||||
};
|
||||
18
src/activities/boot_sleep/BootActivity.cpp
Normal file
18
src/activities/boot_sleep/BootActivity.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include "BootActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "images/CrossLarge.h"
|
||||
|
||||
void BootActivity::onEnter() {
|
||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||
|
||||
renderer.clearScreen();
|
||||
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
||||
renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION);
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
8
src/activities/boot_sleep/BootActivity.h
Normal file
8
src/activities/boot_sleep/BootActivity.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "../Activity.h"
|
||||
|
||||
class BootActivity final : public Activity {
|
||||
public:
|
||||
explicit BootActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {}
|
||||
void onEnter() override;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user