Compare commits
73
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
567fa6e1e2 | ||
|
|
da4d3b5ea5 | ||
|
|
172916afd4 | ||
|
|
ebcd813ff6 | ||
|
|
712c566664 | ||
|
|
5894ae5afe | ||
|
|
8c1c80787a | ||
|
|
140fcb9db5 | ||
|
|
e0b6b9b28a | ||
|
|
83315b6179 | ||
|
|
8e0d2bece2 | ||
|
|
4848a77e1b | ||
|
|
49190cca6d | ||
|
|
e9c2fe1c87 | ||
|
|
dd1741bf0b | ||
|
|
51c5c3c0aa | ||
|
|
5e24895f6d | ||
|
|
e2ca0e94ca | ||
|
|
a4b9a43ca1 | ||
|
|
c73fca26f5 | ||
|
|
dfd7b615dc | ||
|
|
aca6dceaa8 | ||
|
|
6ca75c4653 | ||
|
|
1b9c8ab545 | ||
|
|
bf6cf83577 | ||
|
|
3a761b18af | ||
|
|
13f0ebed96 | ||
|
|
0bc0baa966 | ||
|
|
5d369df6be | ||
|
|
b8ebcf5867 | ||
|
|
e858ebbe88 | ||
|
|
9224bc3f8c | ||
|
|
67a679ab41 | ||
|
|
7a53342f9d | ||
|
|
3ce11f14ce | ||
|
|
47ef92e8fd | ||
|
|
e3d6e32609 | ||
|
|
d399afb53d | ||
|
|
838993259d | ||
|
|
cc74039cab | ||
|
|
87d6c032a5 | ||
|
|
c9b5462370 | ||
|
|
e548bfc0e1 | ||
|
|
73c30748d8 | ||
|
|
6d68466891 | ||
|
|
8824c87490 | ||
|
|
5fef99c641 | ||
|
|
7a792a5384 | ||
|
|
f69cddf2cc | ||
|
|
7185e5d287 | ||
|
|
12940cc546 | ||
|
|
21277e03eb | ||
|
|
4eef2b5793 | ||
|
|
5a55fa1c6e | ||
|
|
c98ba142e8 | ||
|
|
c1c94c0112 | ||
|
|
eb84bcee7c | ||
|
|
d45f355e87 | ||
|
|
56ec3dfb6d | ||
|
|
e517945aaa | ||
|
|
489220832f | ||
|
|
3ee10b31ab | ||
|
|
a946c83a07 | ||
|
|
847786e342 | ||
|
|
c2fb8ce55d | ||
|
|
ed05554d74 | ||
|
|
9a9dc044ce | ||
|
|
1c027ce2cd | ||
|
|
49f97b69ca | ||
|
|
14643d0225 | ||
|
|
fecd1849b9 | ||
|
|
2040e088e7 | ||
|
|
65d23910a3 |
@@ -0,0 +1,26 @@
|
||||
name: "PR Formatting"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
permissions:
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
title-check:
|
||||
name: Title Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check PR Title
|
||||
uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -4,3 +4,8 @@
|
||||
.vscode
|
||||
lib/EpdFont/fontsrc
|
||||
*.generated.h
|
||||
.vs
|
||||
build
|
||||
**/__pycache__/
|
||||
/compile_commands.json
|
||||
/.cache
|
||||
|
||||
@@ -41,7 +41,9 @@ This project is **not affiliated with Xteink**; it's built as a community projec
|
||||
- [ ] Full UTF support
|
||||
- [x] Screen rotation
|
||||
|
||||
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
||||
Multi-language support: Read EPUBs in various languages, including English, Spanish, French, German, Italian, Portuguese, Russian, Ukrainian, Polish, Swedish, Norwegian, [and more](./USER_GUIDE.md#supported-languages).
|
||||
|
||||
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
||||
|
||||
## Installing
|
||||
|
||||
|
||||
+78
-8
@@ -2,6 +2,27 @@
|
||||
|
||||
Welcome to the **CrossPoint** firmware. This guide outlines the hardware controls, navigation, and reading features of the device.
|
||||
|
||||
- [CrossPoint User Guide](#crosspoint-user-guide)
|
||||
- [1. Hardware Overview](#1-hardware-overview)
|
||||
- [Button Layout](#button-layout)
|
||||
- [2. Power \& Startup](#2-power--startup)
|
||||
- [Power On / Off](#power-on--off)
|
||||
- [First Launch](#first-launch)
|
||||
- [3. Screens](#3-screens)
|
||||
- [3.1 Home Screen](#31-home-screen)
|
||||
- [3.2 Book Selection](#32-book-selection)
|
||||
- [3.3 Reading Mode](#33-reading-mode)
|
||||
- [3.4 File Upload Screen](#34-file-upload-screen)
|
||||
- [3.5 Settings](#35-settings)
|
||||
- [3.6 Sleep Screen](#36-sleep-screen)
|
||||
- [4. Reading Mode](#4-reading-mode)
|
||||
- [Page Turning](#page-turning)
|
||||
- [Chapter Navigation](#chapter-navigation)
|
||||
- [System Navigation](#system-navigation)
|
||||
- [5. Chapter Selection Screen](#5-chapter-selection-screen)
|
||||
- [6. Current Limitations \& Roadmap](#6-current-limitations--roadmap)
|
||||
|
||||
|
||||
## 1. Hardware Overview
|
||||
|
||||
The device utilises the standard buttons on the Xtink X4 (in the same layout as the manufacturer firmware, by default):
|
||||
@@ -20,9 +41,10 @@ Button layout can be customized in **[Settings](#35-settings)**.
|
||||
|
||||
### Power On / Off
|
||||
|
||||
To turn the device on or off, **press and hold the Power button for half a second**. In **[Settings](#35-settings)** you can configure the power button to trigger on a short press instead of a long one.
|
||||
To turn the device on or off, **press and hold the Power button for approximately half a second**.
|
||||
In **[Settings](#35-settings)** you can configure the power button to turn the device off with a short press instead of a long one.
|
||||
|
||||
To reboot the device (for example if it's frozen, or after a firmware update), press and release the Reset button, and then hold the Power button for a few seconds.
|
||||
To reboot the device (for example if it's frozen, or after a firmware update), press and release the Reset button, and then quickly press and hold the Power button for a few seconds.
|
||||
|
||||
### First Launch
|
||||
|
||||
@@ -59,22 +81,49 @@ See the [webserver docs](./docs/webserver.md) for more information on how to con
|
||||
> [!TIP]
|
||||
> Advanced users can also manage files programmatically or via the command line using `curl`. See the [webserver docs](./docs/webserver.md) for details.
|
||||
|
||||
### 3.4.1 Calibre Wireless Transfers
|
||||
|
||||
CrossPoint supports sending books from Calibre using the CrossPoint Reader device plugin.
|
||||
|
||||
1. Install the plugin in Calibre:
|
||||
- Head to https://github.com/crosspoint-reader/calibre-plugins/releases to download the latest version of the crosspoint_reader plugin.
|
||||
- Download the zip file.
|
||||
- Open Calibre → Preferences → Plugins → Load plugin from file → Select the zip file.
|
||||
2. On the device: File Transfer → Connect to Calibre → Join a network.
|
||||
3. Make sure your computer is on the same WiFi network.
|
||||
4. In Calibre, click "Send to device" to transfer books.
|
||||
|
||||
### 3.5 Settings
|
||||
|
||||
The Settings screen allows you to configure the device's behavior. There are a few settings you can adjust:
|
||||
- **Sleep Screen**: Which sleep screen to display when the device sleeps:
|
||||
- "Dark" (default) - The default dark sleep screen
|
||||
- "Dark" (default) - The default dark Crosspoint logo sleep screen
|
||||
- "Light" - The same default sleep screen, on a white background
|
||||
- "Custom" - Custom images from the SD card, see [Sleep Screen](#36-sleep-screen) below for more information
|
||||
- "Custom" - Custom images from the SD card; see [Sleep Screen](#36-sleep-screen) below for more information
|
||||
- "Cover" - The book cover image (Note: this is experimental and may not work as expected)
|
||||
- "Blank" - A blank screen
|
||||
- "None" - A blank screen
|
||||
- **Sleep Screen Cover Mode**: How to display the book cover when "Cover" sleep screen is selected:
|
||||
- "Fit" (default) - Scale the image down to fit centered on the screen, padding with white borders as necessary
|
||||
- "Crop" - Scale the image down and crop as necessary to try to to fill the screen (Note: this is experimental and may not work as expected)
|
||||
- **Sleep Screen Cover Filter**: What filter will be applied to the book cover when "Cover" sleep screen is selected
|
||||
- "None" (default) - The cover image will be converted to a grayscale image and displayed as it is
|
||||
- "Contrast" - The image will be displayed as a black & white image without grayscale conversion
|
||||
- "Inverted" - The image will be inverted as in white&black and will be displayed without grayscale conversion
|
||||
- **Status Bar**: Configure the status bar displayed while reading:
|
||||
- "None" - No status bar
|
||||
- "No Progress" - Show status bar without reading progress
|
||||
- "Full" - Show status bar with reading progress
|
||||
- **Hide Battery %**: Configure where to suppress the battery pecentage display in the status bar; the battery icon will still be shown:
|
||||
- "Never" - Always show battery percentage (default)
|
||||
- "In Reader" - Show battery percentage everywhere except in reading mode
|
||||
- "Always" - Always hide battery percentage
|
||||
- **Extra Paragraph Spacing**: If enabled, vertical space will be added between paragraphs in the book. If disabled, paragraphs will not have vertical space between them, but will have first-line indentation.
|
||||
- **Short Power Button Click**: Whether to trigger the power button on a short press or a long press.
|
||||
- **Reading Orientation**: Set the screen orientation for reading:
|
||||
- **Text Anti-Aliasing**: Whether to show smooth grey edges (anti-aliasing) on text in reading mode. Note this slows down page turns slightly.
|
||||
- **Short Power Button Click**: Controls the effect of a short click of the power button:
|
||||
- "Ignore" - Require a long press to turn off the device
|
||||
- "Sleep" - A short press powers the device off
|
||||
- "Page Turn" - A short press in reading mode turns to the next page; a long press turns the device off
|
||||
- **Reading Orientation**: Set the screen orientation for reading EPUB files:
|
||||
- "Portrait" (default) - Standard portrait orientation
|
||||
- "Landscape CW" - Landscape, rotated clockwise
|
||||
- "Inverted" - Portrait, upside down
|
||||
@@ -83,16 +132,23 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
||||
- Back, Confirm, Left, Right (default)
|
||||
- Left, Right, Back, Confirm
|
||||
- Left, Back, Confirm, Right
|
||||
- **Side Button Layout**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||
- Back, Confirm, Right, Left
|
||||
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skip to the next/previous chapter.
|
||||
- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
|
||||
- "Page Scroll" - Long-pressing scrolls a page up/down
|
||||
- Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||
- **Reader Font Family**: Choose the font used for reading:
|
||||
- "Bookerly" (default) - Amazon's reading font
|
||||
- "Noto Sans" - Google's sans-serif font
|
||||
- "Open Dyslexic" - Font designed for readers with dyslexia
|
||||
- **Reader Font Size**: Adjust the text size for reading; options are "Small", "Medium", "Large", or "X Large".
|
||||
- **Reader Line Spacing**: Adjust the spacing between lines; options are "Tight", "Normal", or "Wide".
|
||||
- **Reader Screen Margin**: Controls the screen margins in reader mode between 5 and 40 pixels in 5 pixel increments.
|
||||
- **Reader Paragraph Alignment**: Set the alignment of paragraphs; options are "Justified" (default), "Left", "Center", or "Right".
|
||||
- **Time to Sleep**: Set the duration of inactivity before the device automatically goes to sleep.
|
||||
- **Refresh Frequency**: Set how often the screen does a full refresh while reading to reduce ghosting.
|
||||
- **OPDS Browser**: Configure OPDS server settings for browsing and downloading books. Set the server URL (for Calibre Content Server, add `/opds` to the end), and optionally configure username and password for servers requiring authentication. Note: Only HTTP Basic authentication is supported. If using Calibre Content Server with authentication enabled, you must set it to use Basic authentication instead of the default Digest authentication.
|
||||
- **Check for updates**: Check for firmware updates over WiFi.
|
||||
|
||||
### 3.6 Sleep Screen
|
||||
@@ -124,15 +180,29 @@ Once you have opened a book, the button layout changes to facilitate reading.
|
||||
|
||||
The role of the volume (side) buttons can be swapped in **[Settings](#35-settings)**.
|
||||
|
||||
If the **Short Power Button Click** setting is set to "Page Turn", you can also turn to the next page by briefly pressing the Power button.
|
||||
|
||||
### 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.
|
||||
|
||||
This feature can be disabled in **[Settings](#35-settings)** to help avoid changing chapters by mistake.
|
||||
|
||||
|
||||
### System Navigation
|
||||
* **Return to Book Selection:** Press **Back** to close the book and return to the **[Book Selection](#32-book-selection)** screen.
|
||||
* **Return to Home:** Press and **hold** the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
|
||||
* **Chapter Menu:** Press **Confirm** to open the **[Table of Contents/Chapter Selection](#5-chapter-selection-screen)**.
|
||||
|
||||
### Supported Languages
|
||||
|
||||
CrossPoint renders text using the following Unicode character blocks, enabling support for a wide range of languages:
|
||||
|
||||
* **Latin Script (Basic, Supplement, Extended-A):** Covers English, German, French, Spanish, Portuguese, Italian, Dutch, Swedish, Norwegian, Danish, Finnish, Polish, Czech, Hungarian, Romanian, Slovak, Slovenian, Turkish, and others.
|
||||
* **Cyrillic Script (Standard and Extended):** Covers Russian, Ukrainian, Belarusian, Bulgarian, Serbian, Macedonian, Kazakh, Kyrgyz, Mongolian, and others.
|
||||
|
||||
What is not supported: Chinese, Japanese, Korean, Vietnamese, Hebrew, Arabic and Farsi.
|
||||
|
||||
---
|
||||
|
||||
## 5. Chapter Selection Screen
|
||||
|
||||
@@ -15,5 +15,6 @@ fi
|
||||
# Additionally exclude files in 'lib/EpdFont/builtinFonts/' as they are script-generated.
|
||||
git ls-files --exclude-standard ${GIT_LS_FILES_FLAGS} \
|
||||
| grep -E '\.(c|cpp|h|hpp)$' \
|
||||
| grep -v -E '^lib/EpdFont/builtinFonts/' \
|
||||
| grep -v -E '^lib/CrossPointFont/builtinFonts/' \
|
||||
| grep -v -E '^lib/CrossPointFont/Group5' \
|
||||
| xargs -r clang-format -style=file -i
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Hypher Binary Tries
|
||||
|
||||
CrossPoint embeds the exact binary automata produced by
|
||||
[Typst's `hypher`](https://github.com/typst/hypher).
|
||||
|
||||
## File layout
|
||||
|
||||
Each `.bin` blob is a single self-contained automaton:
|
||||
|
||||
```
|
||||
uint32_t root_addr_be; // big-endian offset of the root node
|
||||
uint8_t levels[]; // shared "levels" tape (dist/score pairs)
|
||||
uint8_t nodes[]; // node records packed back-to-back
|
||||
```
|
||||
|
||||
The size of the `levels` tape is implicit. Individual nodes reference slices
|
||||
inside that tape via 12-bit offsets, so no additional pointers are required.
|
||||
|
||||
### Node encoding
|
||||
|
||||
Every node starts with a single control byte:
|
||||
|
||||
- Bit 7 – set when the node stores scores (`levels`).
|
||||
- Bits 5-6 – stride of the target deltas (1, 2, or 3 bytes, big-endian).
|
||||
- Bits 0-4 – transition count (values ≥ 31 spill into an extra byte).
|
||||
|
||||
If the `levels` flag is set, two more bytes follow. Together they encode a
|
||||
12-bit offset into the global `levels` tape and a 4-bit length. Each byte in the
|
||||
levels tape packs a distance/score pair as `dist * 10 + score`, where `dist`
|
||||
counts how many UTF-8 bytes we advanced since the previous digit.
|
||||
|
||||
After the optional levels header come the transition labels (one byte per edge)
|
||||
followed by the signed target deltas. Targets are stored as relative offsets
|
||||
from the current node address. Deltas up to ±128 fit in a single byte, larger
|
||||
distances grow to 2 or 3 bytes. The runtime walks the transitions with a simple
|
||||
linear scan and materializes the absolute address by adding the decoded delta
|
||||
to the current node’s base.
|
||||
|
||||
## Embedding blobs into the firmware
|
||||
|
||||
The helper script `scripts/generate_hyphenation_trie.py` acts as a thin
|
||||
wrapper: it reads the hypher-generated `.bin` files, formats them as `constexpr`
|
||||
byte arrays, and emits headers under
|
||||
`lib/Epub/Epub/hyphenation/generated/`. Each header defines the raw data plus a
|
||||
`SerializedHyphenationPatterns` descriptor so the reader can keep the automaton
|
||||
in flash.
|
||||
|
||||
To refresh the firmware assets after updating the `.bin` files, run:
|
||||
|
||||
```
|
||||
./scripts/generate_hyphenation_trie.py \
|
||||
--input lib/Epub/Epub/hyphenation/tries/en.bin \
|
||||
--output lib/Epub/Epub/hyphenation/generated/hyph-en.trie.h
|
||||
|
||||
./scripts/generate_hyphenation_trie.py \
|
||||
--input lib/Epub/Epub/hyphenation/tries/fr.bin \
|
||||
--output lib/Epub/Epub/hyphenation/generated/hyph-fr.trie.h
|
||||
|
||||
./scripts/generate_hyphenation_trie.py \
|
||||
--input lib/Epub/Epub/hyphenation/tries/de.bin \
|
||||
--output lib/Epub/Epub/hyphenation/generated/hyph-de.trie.h
|
||||
|
||||
./scripts/generate_hyphenation_trie.py \
|
||||
--input lib/Epub/Epub/hyphenation/tries/ru.bin \
|
||||
--output lib/Epub/Epub/hyphenation/generated/hyph-ru.trie.h
|
||||
```
|
||||
@@ -0,0 +1,57 @@
|
||||
# Troubleshooting
|
||||
|
||||
This document show most common issues and possible solutions while using the device features.
|
||||
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Cannot See the Device on the Network](#cannot-see-the-device-on-the-network)
|
||||
- [Connection Drops or Times Out](#connection-drops-or-times-out)
|
||||
- [Upload Fails](#upload-fails)
|
||||
- [Saved Password Not Working](#saved-password-not-working)
|
||||
|
||||
### Cannot See the Device on the Network
|
||||
|
||||
**Problem:** Browser shows "Cannot connect" or "Site can't be reached"
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Verify both devices are on the **same WiFi network**
|
||||
- Check your computer/phone WiFi settings
|
||||
- Confirm the CrossPoint Reader shows "Connected" status
|
||||
2. Double-check the IP address
|
||||
- Make sure you typed it correctly
|
||||
- Include `http://` at the beginning
|
||||
3. Try disabling VPN if you're using one
|
||||
4. Some networks have "client isolation" enabled - check with your network administrator
|
||||
|
||||
### Connection Drops or Times Out
|
||||
|
||||
**Problem:** WiFi connection is unstable
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Move closer to the WiFi router
|
||||
2. Check signal strength on the device (should be at least `||` or better)
|
||||
3. Avoid interference from other devices
|
||||
4. Try a different WiFi network if available
|
||||
|
||||
### Upload Fails
|
||||
|
||||
**Problem:** File upload doesn't complete or shows an error
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Ensure the file is a valid `.epub` file
|
||||
2. Check that the SD card has enough free space
|
||||
3. Try uploading a smaller file first to test
|
||||
4. Refresh the browser page and try again
|
||||
|
||||
### Saved Password Not Working
|
||||
|
||||
**Problem:** Device fails to connect with saved credentials
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. When connection fails, you'll be prompted to "Forget Network"
|
||||
2. Select **Yes** to remove the saved password
|
||||
3. Reconnect and enter the password again
|
||||
4. Choose to save the new password
|
||||
@@ -0,0 +1,331 @@
|
||||
# Webserver Endpoints
|
||||
|
||||
This document describes all HTTP and WebSocket endpoints available on the CrossPoint Reader webserver.
|
||||
|
||||
- [Webserver Endpoints](#webserver-endpoints)
|
||||
- [Overview](#overview)
|
||||
- [HTTP Endpoints](#http-endpoints)
|
||||
- [GET `/` - Home Page](#get----home-page)
|
||||
- [GET `/files` - File Browser Page](#get-files---file-browser-page)
|
||||
- [GET `/api/status` - Device Status](#get-apistatus---device-status)
|
||||
- [GET `/api/files` - List Files](#get-apifiles---list-files)
|
||||
- [POST `/upload` - Upload File](#post-upload---upload-file)
|
||||
- [POST `/mkdir` - Create Folder](#post-mkdir---create-folder)
|
||||
- [POST `/delete` - Delete File or Folder](#post-delete---delete-file-or-folder)
|
||||
- [WebSocket Endpoint](#websocket-endpoint)
|
||||
- [Port 81 - Fast Binary Upload](#port-81---fast-binary-upload)
|
||||
- [Network Modes](#network-modes)
|
||||
- [Station Mode (STA)](#station-mode-sta)
|
||||
- [Access Point Mode (AP)](#access-point-mode-ap)
|
||||
- [Notes](#notes)
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
The CrossPoint Reader exposes a webserver for file management and device monitoring:
|
||||
|
||||
- **HTTP Server**: Port 80
|
||||
- **WebSocket Server**: Port 81 (for fast binary uploads)
|
||||
|
||||
---
|
||||
|
||||
## HTTP Endpoints
|
||||
|
||||
### GET `/` - Home Page
|
||||
|
||||
Serves the home page HTML interface.
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
curl http://crosspoint.local/
|
||||
```
|
||||
|
||||
**Response:** HTML page (200 OK)
|
||||
|
||||
---
|
||||
|
||||
### GET `/files` - File Browser Page
|
||||
|
||||
Serves the file browser HTML interface.
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
curl http://crosspoint.local/files
|
||||
```
|
||||
|
||||
**Response:** HTML page (200 OK)
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/status` - Device Status
|
||||
|
||||
Returns JSON with device status information.
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
curl http://crosspoint.local/api/status
|
||||
```
|
||||
|
||||
**Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"ip": "192.168.1.100",
|
||||
"mode": "STA",
|
||||
"rssi": -45,
|
||||
"freeHeap": 123456,
|
||||
"uptime": 3600
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
| ---------- | ------ | --------------------------------------------------------- |
|
||||
| `version` | string | CrossPoint firmware version |
|
||||
| `ip` | string | Device IP address |
|
||||
| `mode` | string | `"STA"` (connected to WiFi) or `"AP"` (access point mode) |
|
||||
| `rssi` | number | WiFi signal strength in dBm (0 in AP mode) |
|
||||
| `freeHeap` | number | Free heap memory in bytes |
|
||||
| `uptime` | number | Seconds since device boot |
|
||||
|
||||
---
|
||||
|
||||
### GET `/api/files` - List Files
|
||||
|
||||
Returns a JSON array of files and folders in the specified directory.
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
# List root directory
|
||||
curl http://crosspoint.local/api/files
|
||||
|
||||
# List specific directory
|
||||
curl "http://crosspoint.local/api/files?path=/Books"
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
| --------- | -------- | ------- | ---------------------- |
|
||||
| `path` | No | `/` | Directory path to list |
|
||||
|
||||
**Response (200 OK):**
|
||||
```json
|
||||
[
|
||||
{"name": "MyBook.epub", "size": 1234567, "isDirectory": false, "isEpub": true},
|
||||
{"name": "Notes", "size": 0, "isDirectory": true, "isEpub": false},
|
||||
{"name": "document.pdf", "size": 54321, "isDirectory": false, "isEpub": false}
|
||||
]
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
| ------------- | ------- | ---------------------------------------- |
|
||||
| `name` | string | File or folder name |
|
||||
| `size` | number | Size in bytes (0 for directories) |
|
||||
| `isDirectory` | boolean | `true` if the item is a folder |
|
||||
| `isEpub` | boolean | `true` if the file has `.epub` extension |
|
||||
|
||||
**Notes:**
|
||||
- Hidden files (starting with `.`) are automatically filtered out
|
||||
- System folders (`System Volume Information`, `XTCache`) are hidden
|
||||
|
||||
---
|
||||
|
||||
### POST `/upload` - Upload File
|
||||
|
||||
Uploads a file to the SD card via multipart form data.
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
# Upload to root directory
|
||||
curl -X POST -F "file=@mybook.epub" http://crosspoint.local/upload
|
||||
|
||||
# Upload to specific directory
|
||||
curl -X POST -F "file=@mybook.epub" "http://crosspoint.local/upload?path=/Books"
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
| --------- | -------- | ------- | ------------------------------- |
|
||||
| `path` | No | `/` | Target directory for the upload |
|
||||
|
||||
**Response (200 OK):**
|
||||
```
|
||||
File uploaded successfully: mybook.epub
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
|
||||
| Status | Body | Cause |
|
||||
| ------ | ----------------------------------------------- | --------------------------- |
|
||||
| 400 | `Failed to create file on SD card` | Cannot create file |
|
||||
| 400 | `Failed to write to SD card - disk may be full` | Write error during upload |
|
||||
| 400 | `Failed to write final data to SD card` | Error flushing final buffer |
|
||||
| 400 | `Upload aborted` | Client aborted the upload |
|
||||
| 400 | `Unknown error during upload` | Unspecified error |
|
||||
|
||||
**Notes:**
|
||||
- Existing files with the same name will be overwritten
|
||||
- Uses a 4KB buffer for efficient SD card writes
|
||||
|
||||
---
|
||||
|
||||
### POST `/mkdir` - Create Folder
|
||||
|
||||
Creates a new folder on the SD card.
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
curl -X POST -d "name=NewFolder&path=/" http://crosspoint.local/mkdir
|
||||
```
|
||||
|
||||
**Form Parameters:**
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
| --------- | -------- | ------- | ---------------------------- |
|
||||
| `name` | Yes | - | Name of the folder to create |
|
||||
| `path` | No | `/` | Parent directory path |
|
||||
|
||||
**Response (200 OK):**
|
||||
```
|
||||
Folder created: NewFolder
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
|
||||
| Status | Body | Cause |
|
||||
| ------ | ----------------------------- | ----------------------------- |
|
||||
| 400 | `Missing folder name` | `name` parameter not provided |
|
||||
| 400 | `Folder name cannot be empty` | Empty folder name |
|
||||
| 400 | `Folder already exists` | Folder with same name exists |
|
||||
| 500 | `Failed to create folder` | SD card error |
|
||||
|
||||
---
|
||||
|
||||
### POST `/delete` - Delete File or Folder
|
||||
|
||||
Deletes a file or folder from the SD card.
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
# Delete a file
|
||||
curl -X POST -d "path=/Books/mybook.epub&type=file" http://crosspoint.local/delete
|
||||
|
||||
# Delete an empty folder
|
||||
curl -X POST -d "path=/OldFolder&type=folder" http://crosspoint.local/delete
|
||||
```
|
||||
|
||||
**Form Parameters:**
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
| --------- | -------- | ------- | -------------------------------- |
|
||||
| `path` | Yes | - | Path to the item to delete |
|
||||
| `type` | No | `file` | Type of item: `file` or `folder` |
|
||||
|
||||
**Response (200 OK):**
|
||||
```
|
||||
Deleted successfully
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
|
||||
| Status | Body | Cause |
|
||||
| ------ | --------------------------------------------- | ----------------------------- |
|
||||
| 400 | `Missing path` | `path` parameter not provided |
|
||||
| 400 | `Cannot delete root directory` | Attempted to delete `/` |
|
||||
| 400 | `Folder is not empty. Delete contents first.` | Non-empty folder |
|
||||
| 403 | `Cannot delete system files` | Hidden file (starts with `.`) |
|
||||
| 403 | `Cannot delete protected items` | Protected system folder |
|
||||
| 404 | `Item not found` | Path does not exist |
|
||||
| 500 | `Failed to delete item` | SD card error |
|
||||
|
||||
**Protected Items:**
|
||||
- Files/folders starting with `.`
|
||||
- `System Volume Information`
|
||||
- `XTCache`
|
||||
|
||||
---
|
||||
|
||||
## WebSocket Endpoint
|
||||
|
||||
### Port 81 - Fast Binary Upload
|
||||
|
||||
A WebSocket endpoint for high-speed binary file uploads. More efficient than HTTP multipart for large files.
|
||||
|
||||
**Connection:**
|
||||
```
|
||||
ws://crosspoint.local:81/
|
||||
```
|
||||
|
||||
**Protocol:**
|
||||
|
||||
1. **Client** sends TEXT message: `START:<filename>:<size>:<path>`
|
||||
2. **Server** responds with TEXT: `READY`
|
||||
3. **Client** sends BINARY messages with file data chunks
|
||||
4. **Server** sends TEXT progress updates: `PROGRESS:<received>:<total>`
|
||||
5. **Server** sends TEXT when complete: `DONE` or `ERROR:<message>`
|
||||
|
||||
**Example Session:**
|
||||
|
||||
```
|
||||
Client -> "START:mybook.epub:1234567:/Books"
|
||||
Server -> "READY"
|
||||
Client -> [binary chunk 1]
|
||||
Client -> [binary chunk 2]
|
||||
Server -> "PROGRESS:65536:1234567"
|
||||
Client -> [binary chunk 3]
|
||||
...
|
||||
Server -> "PROGRESS:1234567:1234567"
|
||||
Server -> "DONE"
|
||||
```
|
||||
|
||||
**Error Messages:**
|
||||
|
||||
| Message | Cause |
|
||||
| --------------------------------- | ---------------------------------- |
|
||||
| `ERROR:Failed to create file` | Cannot create file on SD card |
|
||||
| `ERROR:Invalid START format` | Malformed START message |
|
||||
| `ERROR:No upload in progress` | Binary data received without START |
|
||||
| `ERROR:Write failed - disk full?` | SD card write error |
|
||||
|
||||
**Example with `websocat`:**
|
||||
```bash
|
||||
# Interactive session
|
||||
websocat ws://crosspoint.local:81
|
||||
|
||||
# Then type:
|
||||
START:mybook.epub:1234567:/Books
|
||||
# Wait for READY, then send binary data
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Progress updates are sent every 64KB or at completion
|
||||
- Disconnection during upload will delete the incomplete file
|
||||
- Existing files with the same name will be overwritten
|
||||
|
||||
---
|
||||
|
||||
## Network Modes
|
||||
|
||||
The device can operate in two network modes:
|
||||
|
||||
### Station Mode (STA)
|
||||
- Device connects to an existing WiFi network
|
||||
- IP address assigned by router/DHCP
|
||||
- `mode` field in `/api/status` returns `"STA"`
|
||||
- `rssi` field shows signal strength
|
||||
|
||||
### Access Point Mode (AP)
|
||||
- Device creates its own WiFi hotspot
|
||||
- Default IP is typically `192.168.4.1`
|
||||
- `mode` field in `/api/status` returns `"AP"`
|
||||
- `rssi` field returns `0`
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- These examples use `crosspoint.local`. If your network does not support mDNS or the address does not resolve, replace it with the specific **IP Address** displayed on your device screen (e.g., `http://192.168.1.102/`).
|
||||
- All paths on the SD card start with `/`
|
||||
- Trailing slashes are automatically stripped (except for root `/`)
|
||||
- The webserver uses chunked transfer encoding for file listings
|
||||
+2
-83
@@ -172,89 +172,7 @@ This is useful for organizing your ebooks by genre, author, or series.
|
||||
|
||||
## Command Line File Management
|
||||
|
||||
For power users, you can manage files directly from your terminal using `curl` while the device is in File Upload mode.
|
||||
|
||||
### Uploading a File
|
||||
To upload a file to the root directory, use the following command:
|
||||
```bash
|
||||
curl -F "file=@book.epub" "http://crosspoint.local/upload?path=/"
|
||||
```
|
||||
|
||||
* **`-F "file=@filename"`**: Points to the local file on your computer.
|
||||
* **`path=/`**: The destination folder on the device SD card.
|
||||
|
||||
### Deleting a File
|
||||
|
||||
To delete a specific file, provide the full path on the SD card:
|
||||
|
||||
```bash
|
||||
curl -F "path=/folder/file.epub" "http://crosspoint.local/delete"
|
||||
```
|
||||
|
||||
### Advanced Flags
|
||||
|
||||
For more reliable transfers of large EPUB files, consider adding these flags:
|
||||
|
||||
* `-#`: Shows a simple progress bar.
|
||||
* `--connect-timeout 30`: Limits how long curl waits to establish a connection (in seconds).
|
||||
* `--max-time 300`: Sets a maximum duration for the entire transfer (5 minutes).
|
||||
|
||||
> [!NOTE]
|
||||
> These examples use `crosspoint.local`. If your network does not support mDNS or the address does not resolve, replace it with the specific **IP Address** displayed on your device screen (e.g., `http://192.168.1.102/`).
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cannot See the Device on the Network
|
||||
|
||||
**Problem:** Browser shows "Cannot connect" or "Site can't be reached"
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Verify both devices are on the **same WiFi network**
|
||||
- Check your computer/phone WiFi settings
|
||||
- Confirm the CrossPoint Reader shows "Connected" status
|
||||
2. Double-check the IP address
|
||||
- Make sure you typed it correctly
|
||||
- Include `http://` at the beginning
|
||||
3. Try disabling VPN if you're using one
|
||||
4. Some networks have "client isolation" enabled - check with your network administrator
|
||||
|
||||
### Connection Drops or Times Out
|
||||
|
||||
**Problem:** WiFi connection is unstable
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Move closer to the WiFi router
|
||||
2. Check signal strength on the device (should be at least `||` or better)
|
||||
3. Avoid interference from other devices
|
||||
4. Try a different WiFi network if available
|
||||
|
||||
### Upload Fails
|
||||
|
||||
**Problem:** File upload doesn't complete or shows an error
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Ensure the file is a valid `.epub` file
|
||||
2. Check that the SD card has enough free space
|
||||
3. Try uploading a smaller file first to test
|
||||
4. Refresh the browser page and try again
|
||||
|
||||
### Saved Password Not Working
|
||||
|
||||
**Problem:** Device fails to connect with saved credentials
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. When connection fails, you'll be prompted to "Forget Network"
|
||||
2. Select **Yes** to remove the saved password
|
||||
3. Reconnect and enter the password again
|
||||
4. Choose to save the new password
|
||||
|
||||
---
|
||||
For power users, you can manage files directly from your terminal using `curl` while the device is in File Upload mode a detailed documentation can be found [here](./webserver-endpoints.md).
|
||||
|
||||
## Security Notes
|
||||
|
||||
@@ -303,4 +221,5 @@ Your uploaded files will be immediately available in the file browser!
|
||||
## Related Documentation
|
||||
|
||||
- [User Guide](../USER_GUIDE.md) - General device operation
|
||||
- [Troubleshooting](./troubleshooting.md) - Troubleshooting
|
||||
- [README](../README.md) - Project overview and features
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
#include "CrossPointFont.h"
|
||||
|
||||
#include <Utf8.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#define FONT_SCALE 2
|
||||
|
||||
namespace {
|
||||
// Number of set bits from 0->15
|
||||
uint8_t bitCount[] = {
|
||||
0, // 0b0000,
|
||||
1, // 0b0001,
|
||||
1, // 0b0010,
|
||||
2, // 0b0011,
|
||||
1, // 0b0100,
|
||||
2, // 0b0101,
|
||||
2, // 0b0110,
|
||||
3, // 0b0111,
|
||||
1, // 0b1000,
|
||||
2, // 0b1001,
|
||||
2, // 0b1010,
|
||||
3, // 0b1011,
|
||||
2, // 0b1100,
|
||||
3, // 0b1101,
|
||||
3, // 0b1110,
|
||||
4, // 0b1111,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void CrossPointFont::getTextBounds(const char* string, const Style style, const int startX, const int startY, int* minX,
|
||||
int* minY, int* maxX, int* maxY) const {
|
||||
*minX = startX;
|
||||
*minY = startY;
|
||||
*maxX = startX;
|
||||
*maxY = startY;
|
||||
|
||||
if (*string == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
int cursorX = startX;
|
||||
const int cursorY = startY;
|
||||
uint32_t cp;
|
||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
|
||||
const CrossPointFontGlyph* glyph = getGlyph(cp, style);
|
||||
|
||||
if (!glyph) {
|
||||
glyph = getGlyph(REPLACEMENT_GLYPH, style);
|
||||
}
|
||||
|
||||
if (!glyph) {
|
||||
// TODO: Better handle this?
|
||||
continue;
|
||||
}
|
||||
|
||||
*minX = std::min(*minX, cursorX + glyph->xOffset / FONT_SCALE);
|
||||
*maxX = std::max(*maxX, cursorX + (glyph->xOffset + glyph->width) / FONT_SCALE);
|
||||
*minY = std::min(*minY, cursorY + (glyph->yOffset - glyph->height) / FONT_SCALE);
|
||||
*maxY = std::max(*maxY, cursorY + glyph->yOffset / FONT_SCALE);
|
||||
cursorX += glyph->xAdvance / FONT_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
void CrossPointFont::getTextDimensions(const char* string, const Style style, int* w, int* h) const {
|
||||
int minX = 0, minY = 0, maxX = 0, maxY = 0;
|
||||
|
||||
getTextBounds(string, style, 0, 0, &minX, &minY, &maxX, &maxY);
|
||||
|
||||
*w = maxX - minX;
|
||||
*h = maxY - minY;
|
||||
}
|
||||
|
||||
uint8_t CrossPointFont::styleGroup(const Style style) const {
|
||||
if (style == REGULAR) return 0;
|
||||
|
||||
if (data.header.styles == 0b0001) {
|
||||
// Only have regular font, show regular
|
||||
return 0;
|
||||
}
|
||||
if (data.header.styles == 0b0011) {
|
||||
// Only have bold and regular font
|
||||
// Show bold if style is bold or bold_italic
|
||||
return style == BOLD || style == BOLD_ITALIC ? 1 : 0;
|
||||
}
|
||||
if (data.header.styles == 0b0101) {
|
||||
// Only have italic and regular font
|
||||
// Show italic if style is italic or bold_italic
|
||||
return style == ITALIC || style == BOLD_ITALIC ? 1 : 0;
|
||||
}
|
||||
if (data.header.styles == 0b1001) {
|
||||
// Only have bold_italic and regular font
|
||||
// Show bold_italic if style is any non-regular
|
||||
return style == BOLD_ITALIC || style == BOLD || style == ITALIC ? 1 : 0;
|
||||
}
|
||||
if (data.header.styles == 0b0111) {
|
||||
// Have all but bold_italic
|
||||
// Show bold if style is bold_italic, otherwise show the requested style
|
||||
return style == BOLD_ITALIC ? 1 : style;
|
||||
}
|
||||
if (data.header.styles == 0b1011) {
|
||||
// Have all but italic
|
||||
// Show bold_italic if style is italic, otherwise show the requested style
|
||||
return style == ITALIC ? 2 : style;
|
||||
}
|
||||
if (data.header.styles == 0b1101) {
|
||||
// Have all but bold
|
||||
// Show bold_italic if style is bold, otherwise show the requested style
|
||||
return style == BOLD ? 2 : style;
|
||||
}
|
||||
if (data.header.styles == 0b1111) {
|
||||
return style;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const CrossPointFontGlyph* CrossPointFont::getGlyph(const uint32_t cp, const Style style) const {
|
||||
const CrossPointFontUnicodeInterval* intervals = data.intervals;
|
||||
const int count = data.header.intervalCount;
|
||||
|
||||
if (count == 0) return nullptr;
|
||||
|
||||
// Binary search for O(log n) lookup instead of O(n)
|
||||
// Critical for Korean fonts with many unicode intervals
|
||||
int left = 0;
|
||||
int right = count - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const int mid = left + (right - left) / 2;
|
||||
const CrossPointFontUnicodeInterval* interval = &intervals[mid];
|
||||
|
||||
if (cp < interval->first) {
|
||||
right = mid - 1;
|
||||
} else if (cp > interval->last) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Found: cp >= interval->first && cp <= interval->last
|
||||
const uint32_t index =
|
||||
interval->offset + (cp - interval->first) * bitCount[data.header.styles] + styleGroup(style);
|
||||
return &data.glyphs[index];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "CrossPointFontFormat.h"
|
||||
#include "Group5/Group5.h"
|
||||
|
||||
class CrossPointFont {
|
||||
public:
|
||||
enum Style : uint8_t { REGULAR = 0, BOLD = 1, ITALIC = 2, BOLD_ITALIC = 3 };
|
||||
|
||||
CrossPointFontData data;
|
||||
explicit CrossPointFont(void* rawData) {
|
||||
data.header = *static_cast<CrossPointFontHeader*>(rawData);
|
||||
data.intervals = (CrossPointFontUnicodeInterval*)(static_cast<uint8_t*>(rawData) + sizeof(CrossPointFontHeader));
|
||||
data.glyphs = (CrossPointFontGlyph*)((uint8_t*)data.intervals +
|
||||
sizeof(CrossPointFontUnicodeInterval) * data.header.intervalCount);
|
||||
data.bitmap = (uint8_t*)data.glyphs + sizeof(CrossPointFontGlyph) * data.header.glyphCount;
|
||||
}
|
||||
|
||||
~CrossPointFont() = default;
|
||||
void getTextDimensions(const char* string, Style style, int* w, int* h) const;
|
||||
const CrossPointFontGlyph* getGlyph(uint32_t cp, Style style) const;
|
||||
|
||||
private:
|
||||
void getTextBounds(const char* string, Style style, int startX, int startY, int* minX, int* minY, int* maxX,
|
||||
int* maxY) const;
|
||||
uint8_t styleGroup(Style style) const;
|
||||
};
|
||||
|
||||
// TODO: CrossPointFontSmall
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
// 16-bit marker at the start of a CrossPoint font file
|
||||
// (CrossPoint Font Format)
|
||||
#define CPF_FONT_MARKER 0xCFF1
|
||||
#define CPF_FONT_MARKER_SMALL 0xCFF2
|
||||
|
||||
// Font info per large character (glyph)
|
||||
typedef struct {
|
||||
uint32_t bitmapOffset; /// Offset to compressed bitmap data for this glyph
|
||||
uint16_t width; /// bitmap width in pixels
|
||||
uint16_t height; /// bitmap height in pixels
|
||||
uint16_t xAdvance; /// total width in pixels (bitmap + padding)
|
||||
int16_t xOffset; /// left padding to upper left corner
|
||||
int16_t yOffset; /// top padding to upper left corner
|
||||
} CrossPointFontGlyph;
|
||||
|
||||
// Font info per small character (glyph)
|
||||
typedef struct {
|
||||
uint32_t bitmapOffset; /// Offset to compressed bitmap data for this glyph
|
||||
uint8_t width; /// bitmap width in pixels
|
||||
uint8_t height; /// bitmap height in pixels
|
||||
uint8_t xAdvance; /// total width in pixels (bitmap + padding)
|
||||
int8_t xOffset; /// left padding to upper left corner
|
||||
int16_t yOffset; /// top padding to upper left corner
|
||||
} CrossPointFontSmallGlyph;
|
||||
|
||||
/// Glyph interval structure
|
||||
typedef struct {
|
||||
uint32_t first; /// The first unicode code point of the interval
|
||||
uint32_t last; /// The last unicode code point of the interval
|
||||
uint32_t offset; /// Index of the first code point into the glyph array
|
||||
} CrossPointFontUnicodeInterval;
|
||||
|
||||
typedef struct {
|
||||
uint16_t u16Marker; /// CPF_FONT_MARKER / CPF_FONT_MARKER_SMALL
|
||||
uint16_t height; /// Newline distance (y axis)
|
||||
uint16_t ascender; /// Maximal height of a glyph above the base line
|
||||
uint8_t styles; /// Regular = 0x01, Bold = 0x02, Italic = 0x04, BoldItalic = 0x08, can be OR'd together
|
||||
uint16_t intervalCount; /// Number of unicode intervals.
|
||||
uint32_t glyphCount; /// Number of total glyphs across all styles
|
||||
} CrossPointFontHeader;
|
||||
|
||||
/// Data stored for FONT AS A WHOLE
|
||||
typedef struct {
|
||||
CrossPointFontHeader header;
|
||||
CrossPointFontUnicodeInterval* intervals; /// Valid unicode intervals for this font
|
||||
CrossPointFontGlyph* glyphs; /// Glyph array
|
||||
uint8_t* bitmap; /// Glyph bitmaps, concatenated
|
||||
} CrossPointFontData;
|
||||
|
||||
typedef struct {
|
||||
CrossPointFontHeader header;
|
||||
CrossPointFontUnicodeInterval* intervals; /// Valid unicode intervals for this font
|
||||
CrossPointFontSmallGlyph* glyphs; /// Glyph array
|
||||
uint8_t* bitmap; /// Glyph bitmaps, concatenated
|
||||
} CrossPointFontDataSmall;
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "g5enc.inl"
|
||||
#include "g5dec.inl"
|
||||
//
|
||||
// Group5 1-bit image compression library
|
||||
// Written by Larry Bank (bitbank@pobox.com)
|
||||
// Decoder C++ wrapper functions
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2024 BitBank Software, Inc.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
int G5DECODER::init(int iWidth, int iHeight, uint8_t *pData, int iDataSize)
|
||||
{
|
||||
return g5_decode_init(&_g5dec, iWidth, iHeight, pData, iDataSize);
|
||||
} /* init() */
|
||||
|
||||
int G5DECODER::decodeLine(uint8_t *pOut)
|
||||
{
|
||||
return g5_decode_line(&_g5dec, pOut);
|
||||
} /* decodeLine() */
|
||||
|
||||
//
|
||||
// Encoder C++ wrapper functions
|
||||
//
|
||||
int G5ENCODER::init(int iWidth, int iHeight, uint8_t *pOut, int iOutSize)
|
||||
{
|
||||
return g5_encode_init(&_g5enc, iWidth, iHeight, pOut, iOutSize);
|
||||
} /* init() */
|
||||
|
||||
int G5ENCODER::encodeLine(uint8_t *pPixels)
|
||||
{
|
||||
return g5_encode_encodeLine(&_g5enc, pPixels);
|
||||
} /* encodeLine() */
|
||||
|
||||
int G5ENCODER::size()
|
||||
{
|
||||
return g5_encode_getOutSize(&_g5enc);
|
||||
} /* size() */
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
#ifndef __GROUP5__
|
||||
#define __GROUP5__
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#ifdef __AVR__
|
||||
#include <avr/pgmspace.h>
|
||||
#endif
|
||||
//
|
||||
// Group5 1-bit image compression library
|
||||
// Written by Larry Bank (bitbank@pobox.com)
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2024 BitBank Software, Inc.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// The name "Group5" is derived from the CCITT Group4 standard
|
||||
// This code is based on a lot of the good ideas from CCITT T.6
|
||||
// for FAX image compression, but modified to work in a very
|
||||
// constrained environment. The Huffman tables for horizontal
|
||||
// mode have been replaced with a simple 2-bit flag followed by
|
||||
// short or long counts of a fixed length. The short codes are
|
||||
// always 3 bits (run lengths 0-7) and the long codes are the
|
||||
// number of bits needed to encode the width of the image.
|
||||
// For example, if a 320 pixel wide image is being compressed,
|
||||
// the longest horizontal run needed is 320, which requires 9
|
||||
// bits to encode. The 2 prefix bits have the following meaning:
|
||||
// 00 = short, short (3+3 bits)
|
||||
// 01 = short, long (3+N bits)
|
||||
// 10 = long, short (N+3 bits)
|
||||
// 11 = long, long (N+N bits)
|
||||
// The rest of the code works identically to Group4 2D FAX
|
||||
//
|
||||
// Caution - this is the maximum number of color changes per line
|
||||
// The default value is set low to work embedded systems with little RAM
|
||||
// for font compression, this is plenty since each line of a character should have
|
||||
// a maximum of 7 color changes
|
||||
// You can define this in your compiler macros to override the default vlaue
|
||||
//
|
||||
#ifndef MAX_IMAGE_FLIPS
|
||||
#ifdef __AVR__
|
||||
#define MAX_IMAGE_FLIPS 32
|
||||
#else
|
||||
#define MAX_IMAGE_FLIPS 512
|
||||
#endif // __AVR__
|
||||
#endif
|
||||
// Horizontal prefix bits
|
||||
enum {
|
||||
HORIZ_SHORT_SHORT=0,
|
||||
HORIZ_SHORT_LONG,
|
||||
HORIZ_LONG_SHORT,
|
||||
HORIZ_LONG_LONG
|
||||
};
|
||||
|
||||
// Return code for encoder and decoder
|
||||
enum {
|
||||
G5_SUCCESS = 0,
|
||||
G5_INVALID_PARAMETER,
|
||||
G5_DECODE_ERROR,
|
||||
G5_UNSUPPORTED_FEATURE,
|
||||
G5_ENCODE_COMPLETE,
|
||||
G5_DECODE_COMPLETE,
|
||||
G5_NOT_INITIALIZED,
|
||||
G5_DATA_OVERFLOW,
|
||||
G5_MAX_FLIPS_EXCEEDED
|
||||
};
|
||||
//
|
||||
// Decoder state
|
||||
//
|
||||
typedef struct g5_dec_image_tag
|
||||
{
|
||||
int iWidth, iHeight; // image size
|
||||
int iError;
|
||||
int y; // last y value drawn
|
||||
int iVLCSize;
|
||||
int iHLen; // length of 'long' horizontal codes for this image
|
||||
int iPitch; // width in bytes of output buffer
|
||||
uint32_t u32Accum; // fractional scaling accumulator
|
||||
uint32_t ulBitOff, ulBits; // vlc decode variables
|
||||
uint8_t *pSrc, *pBuf; // starting & current buffer pointer
|
||||
int16_t *pCur, *pRef; // current state of current vs reference flips
|
||||
int16_t CurFlips[MAX_IMAGE_FLIPS];
|
||||
int16_t RefFlips[MAX_IMAGE_FLIPS];
|
||||
} G5DECIMAGE;
|
||||
|
||||
// Due to unaligned memory causing an exception, we have to do these macros the slow way
|
||||
#ifdef __AVR__
|
||||
// assume PROGMEM as the source of data
|
||||
inline uint32_t TIFFMOTOLONG(uint8_t *p)
|
||||
{
|
||||
uint32_t u32 = pgm_read_dword(p);
|
||||
return __builtin_bswap32(u32);
|
||||
}
|
||||
#else
|
||||
#define TIFFMOTOLONG(p) (((uint32_t)(*p)<<24UL) + ((uint32_t)(*(p+1))<<16UL) + ((uint32_t)(*(p+2))<<8UL) + (uint32_t)(*(p+3)))
|
||||
#endif // __AVR__
|
||||
|
||||
#define TOP_BIT 0x80000000
|
||||
#define MAX_VALUE 0xffffffff
|
||||
// Must be a 32-bit target processor
|
||||
#define REGISTER_WIDTH 32
|
||||
#define BIGUINT uint32_t
|
||||
|
||||
//
|
||||
// G5 Encoder
|
||||
//
|
||||
|
||||
typedef struct g5_buffered_bits
|
||||
{
|
||||
unsigned char *pBuf; // buffer pointer
|
||||
uint32_t ulBits; // buffered bits
|
||||
uint32_t ulBitOff; // current bit offset
|
||||
uint32_t ulDataSize; // available data
|
||||
} G5_BUFFERED_BITS;
|
||||
|
||||
//
|
||||
// Encoder state
|
||||
//
|
||||
typedef struct g5_enc_image_tag
|
||||
{
|
||||
int iWidth, iHeight; // image size
|
||||
int iError;
|
||||
int y; // last y encoded
|
||||
int iOutSize;
|
||||
int iDataSize; // generated output size
|
||||
uint8_t *pOutBuf;
|
||||
int16_t *pCur, *pRef; // pointers to swap current and reference lines
|
||||
G5_BUFFERED_BITS bb;
|
||||
int16_t CurFlips[MAX_IMAGE_FLIPS];
|
||||
int16_t RefFlips[MAX_IMAGE_FLIPS];
|
||||
} G5ENCIMAGE;
|
||||
|
||||
#ifdef __cplusplus
|
||||
//
|
||||
// The G5 classes wrap portable C code which does the actual work
|
||||
//
|
||||
class G5ENCODER
|
||||
{
|
||||
public:
|
||||
int init(int iWidth, int iHeight, uint8_t *pOut, int iOutSize);
|
||||
int encodeLine(uint8_t *pPixels);
|
||||
int size();
|
||||
|
||||
private:
|
||||
G5ENCIMAGE _g5enc;
|
||||
};
|
||||
class G5DECODER
|
||||
{
|
||||
public:
|
||||
int init(int iWidth, int iHeight, uint8_t *pData, int iDataSize);
|
||||
int decodeLine(uint8_t *pOut);
|
||||
|
||||
private:
|
||||
G5DECIMAGE _g5dec;
|
||||
};
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // __GROUP5__
|
||||
@@ -0,0 +1,346 @@
|
||||
//
|
||||
// Group5
|
||||
// A 1-bpp image decoder
|
||||
//
|
||||
// Written by Larry Bank (bitbank@pobox.com)
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2024 BitBank Software, Inc.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#include "Group5.h"
|
||||
|
||||
/*
|
||||
The code tree that follows has: bit_length, decode routine
|
||||
These codes are for Group 4 (MMR) decoding
|
||||
|
||||
01 = vertneg1, 11h = vert1, 20h = horiz, 30h = pass, 12h = vert2
|
||||
02 = vertneg2, 13h = vert3, 03 = vertneg3, 90h = trash
|
||||
*/
|
||||
|
||||
static const uint8_t code_table[128] =
|
||||
{0x90, 0, 0x40, 0, /* trash, uncompr mode - codes 0 and 1 */
|
||||
3, 7, /* V(-3) pos = 2 */
|
||||
0x13, 7, /* V(3) pos = 3 */
|
||||
2, 6, 2, 6, /* V(-2) pos = 4,5 */
|
||||
0x12, 6, 0x12, 6, /* V(2) pos = 6,7 */
|
||||
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4, /* pass pos = 8->F */
|
||||
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4,
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, /* horiz pos = 10->1F */
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
|
||||
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
|
||||
/* V(-1) pos = 20->2F */
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
1, 3, 1, 3, 1, 3, 1, 3,
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3, /* V(1) pos = 30->3F */
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
|
||||
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3};
|
||||
|
||||
static int g5_decode_init(G5DECIMAGE *pImage, int iWidth, int iHeight, uint8_t *pData, int iDataSize)
|
||||
{
|
||||
if (pImage == NULL || iWidth < 1 || iHeight < 1 || pData == NULL || iDataSize < 1)
|
||||
return G5_INVALID_PARAMETER;
|
||||
|
||||
pImage->iVLCSize = iDataSize;
|
||||
pImage->pSrc = pData;
|
||||
pImage->ulBitOff = 0;
|
||||
pImage->y = 0;
|
||||
pImage->ulBits = TIFFMOTOLONG(pData); // preload the first 32 bits of data
|
||||
pImage->iWidth = iWidth;
|
||||
pImage->iHeight = iHeight;
|
||||
return G5_SUCCESS;
|
||||
|
||||
} /* g5_decode_init() */
|
||||
|
||||
static void G5DrawLine(G5DECIMAGE *pPage, int16_t *pCurFlips, uint8_t *pOut)
|
||||
{
|
||||
int x, len, run;
|
||||
uint8_t lBit, rBit, *p;
|
||||
int iStart = 0, xright = pPage->iWidth;
|
||||
uint8_t *pDest;
|
||||
|
||||
iStart = 0;
|
||||
|
||||
pDest = pOut;
|
||||
len = (xright+7)>>3; // number of bytes to generate
|
||||
for (x=0; x<len; x++) {
|
||||
pOut[x] = 0xff; // start with white and only draw the black runs
|
||||
}
|
||||
x = 0;
|
||||
while (x < xright) { // while the scaled x is within the window bounds
|
||||
x = *pCurFlips++; // black starting point
|
||||
run = *pCurFlips++ - x; // get the black run
|
||||
x -= iStart;
|
||||
if (x >= xright || run == 0)
|
||||
break;
|
||||
if ((x + run) > 0) { /* If the run is visible, draw it */
|
||||
if (x < 0) {
|
||||
run += x; /* draw only visible part of run */
|
||||
x = 0;
|
||||
}
|
||||
if ((x + run) > xright) { /* Don't let it go off right edge */
|
||||
run = xright - x;
|
||||
}
|
||||
/* Draw this run */
|
||||
lBit = 0xff << (8 - (x & 7));
|
||||
rBit = 0xff >> ((x + run) & 7);
|
||||
len = ((x+run)>>3) - (x >> 3);
|
||||
p = &pDest[x >> 3];
|
||||
if (len == 0) {
|
||||
lBit |= rBit;
|
||||
*p &= lBit;
|
||||
} else {
|
||||
*p++ &= lBit;
|
||||
while (len > 1) {
|
||||
*p++ = 0;
|
||||
len--;
|
||||
}
|
||||
*p = rBit;
|
||||
}
|
||||
} // visible run
|
||||
} /* while drawing line */
|
||||
} /* G5DrawLine() */
|
||||
//
|
||||
// Initialize internal structures to decode the image
|
||||
//
|
||||
static void Decode_Begin(G5DECIMAGE *pPage)
|
||||
{
|
||||
int i, xsize;
|
||||
int16_t *CurFlips, *RefFlips;
|
||||
|
||||
xsize = pPage->iWidth;
|
||||
|
||||
RefFlips = pPage->RefFlips;
|
||||
CurFlips = pPage->CurFlips;
|
||||
|
||||
/* Seed the current and reference line with XSIZE for V(0) codes */
|
||||
for (i=0; i<MAX_IMAGE_FLIPS-2; i++) {
|
||||
RefFlips[i] = xsize;
|
||||
CurFlips[i] = xsize;
|
||||
}
|
||||
/* Prefill both current and reference lines with 7fff to prevent it from
|
||||
walking off the end if the data gets bunged and the current X is > XSIZE
|
||||
3-16-94 */
|
||||
CurFlips[i] = RefFlips[i] = 0x7fff;
|
||||
CurFlips[i+1] = RefFlips[i+1] = 0x7fff;
|
||||
|
||||
pPage->pCur = CurFlips;
|
||||
pPage->pRef = RefFlips;
|
||||
pPage->pBuf = pPage->pSrc;
|
||||
pPage->ulBits = TIFFMOTOLONG(pPage->pSrc); // load 32 bits to start
|
||||
pPage->ulBitOff = 0;
|
||||
// Calculate the number of bits needed for a long horizontal code
|
||||
#ifdef __AVR__
|
||||
pPage->iHLen = 16 - __builtin_clz(pPage->iWidth);
|
||||
#else
|
||||
pPage->iHLen = 32 - __builtin_clz(pPage->iWidth);
|
||||
#endif
|
||||
} /* Decode_Begin() */
|
||||
//
|
||||
// Decode a single line of G5 data (private function)
|
||||
//
|
||||
static int DecodeLine(G5DECIMAGE *pPage)
|
||||
{
|
||||
signed int a0, a0_p, b1;
|
||||
int16_t *pCur, *pRef, *RefFlips, *CurFlips;
|
||||
int xsize, tot_run=0, tot_run1 = 0;
|
||||
int32_t sCode;
|
||||
uint32_t lBits;
|
||||
uint32_t ulBits, ulBitOff;
|
||||
uint8_t *pBuf/*, *pBufEnd*/;
|
||||
uint32_t u32HMask, u32HLen; // horizontal code mask and length
|
||||
|
||||
pCur = CurFlips = pPage->pCur;
|
||||
pRef = RefFlips = pPage->pRef;
|
||||
ulBits = pPage->ulBits;
|
||||
ulBitOff = pPage->ulBitOff;
|
||||
pBuf = pPage->pBuf;
|
||||
// pBufEnd = &pPage->pSrc[pPage->iVLCSize];
|
||||
u32HLen = pPage->iHLen;
|
||||
u32HMask = (1 << u32HLen) - 1;
|
||||
a0 = -1;
|
||||
xsize = pPage->iWidth;
|
||||
|
||||
while (a0 < xsize) { /* Decode this line */
|
||||
if (ulBitOff > (REGISTER_WIDTH-8)) { // need at least 7 unused bits
|
||||
pBuf += (ulBitOff >> 3);
|
||||
ulBitOff &= 7;
|
||||
ulBits = TIFFMOTOLONG(pBuf);
|
||||
}
|
||||
if ((int32_t)(ulBits << ulBitOff) < 0) { /* V(0) code is the most frequent case (1 bit) */
|
||||
a0 = *pRef++;
|
||||
ulBitOff++; // length = 1 bit
|
||||
*pCur++ = a0;
|
||||
} else { /* Slower method for the less frequence codes */
|
||||
lBits = (ulBits >> ((REGISTER_WIDTH - 8) - ulBitOff)) & 0xfe; /* Only the first 7 bits are useful */
|
||||
sCode = code_table[lBits]; /* Get the code type as an 8-bit value */
|
||||
ulBitOff += code_table[lBits+1]; /* Get the code length */
|
||||
switch (sCode) {
|
||||
case 1: /* V(-1) */
|
||||
case 2: /* V(-2) */
|
||||
case 3: /* V(-3) */
|
||||
a0 = *pRef - sCode; /* A0 = B1 - x */
|
||||
*pCur++ = a0;
|
||||
if (pRef == RefFlips) {
|
||||
pRef += 2;
|
||||
}
|
||||
pRef--;
|
||||
while (a0 >= *pRef) {
|
||||
pRef += 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x11: /* V(1) */
|
||||
case 0x12: /* V(2) */
|
||||
case 0x13: /* V(3) */
|
||||
a0 = *pRef++; /* A0 = B1 */
|
||||
b1 = a0;
|
||||
a0 += sCode & 7; /* A0 = B1 + x */
|
||||
if (b1 != xsize && a0 < xsize) {
|
||||
while (a0 >= *pRef) {
|
||||
pRef += 2;
|
||||
}
|
||||
}
|
||||
if (a0 > xsize) {
|
||||
a0 = xsize;
|
||||
}
|
||||
*pCur++ = a0;
|
||||
break;
|
||||
|
||||
case 0x20: /* Horizontal codes */
|
||||
if (ulBitOff > (REGISTER_WIDTH-16)) { // need at least 16 unused bits
|
||||
pBuf += (ulBitOff >> 3);
|
||||
ulBitOff &= 7;
|
||||
ulBits = TIFFMOTOLONG(pBuf);
|
||||
}
|
||||
a0_p = a0;
|
||||
if (a0 < 0) {
|
||||
a0_p = 0;
|
||||
}
|
||||
lBits = (ulBits >> ((REGISTER_WIDTH - 2) - ulBitOff)) & 0x3; // get 2-bit prefix for code type
|
||||
// There are 4 possible horizontal cases: short/short, short/long, long/short, long/long
|
||||
// These are encoded in a 2-bit prefix code, followed by 3 bits for short or N bits for long code
|
||||
// N is the log base 2 of the image width (e.g. 320 pixels requires 9 bits)
|
||||
ulBitOff += 2;
|
||||
switch (lBits) {
|
||||
case HORIZ_SHORT_SHORT:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
|
||||
ulBitOff += 3;
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
|
||||
ulBitOff += 3;
|
||||
break;
|
||||
case HORIZ_SHORT_LONG:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
|
||||
ulBitOff += 3;
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
|
||||
ulBitOff += u32HLen;
|
||||
break;
|
||||
case HORIZ_LONG_SHORT:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
|
||||
ulBitOff += u32HLen;
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length
|
||||
ulBitOff += 3;
|
||||
break;
|
||||
case HORIZ_LONG_LONG:
|
||||
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
|
||||
ulBitOff += u32HLen;
|
||||
if (ulBitOff > (REGISTER_WIDTH-16)) { // need at least 16 unused bits
|
||||
pBuf += (ulBitOff >> 3);
|
||||
ulBitOff &= 7;
|
||||
ulBits = TIFFMOTOLONG(pBuf);
|
||||
}
|
||||
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length
|
||||
ulBitOff += u32HLen;
|
||||
break;
|
||||
} // switch on lBits
|
||||
a0 = a0_p + tot_run;
|
||||
*pCur++ = a0;
|
||||
a0 += tot_run1;
|
||||
if (a0 < xsize) {
|
||||
while (a0 >= *pRef) {
|
||||
pRef += 2;
|
||||
}
|
||||
}
|
||||
*pCur++ = a0;
|
||||
break;
|
||||
|
||||
case 0x30: /* Pass code */
|
||||
pRef++; /* A0 = B2, iRef+=2 */
|
||||
a0 = *pRef++;
|
||||
break;
|
||||
|
||||
default: /* ERROR */
|
||||
pPage->iError = G5_DECODE_ERROR;
|
||||
goto pilreadg5z;
|
||||
} /* switch */
|
||||
} /* Slow climb */
|
||||
}
|
||||
/*--- Convert flips data into run lengths ---*/
|
||||
*pCur++ = xsize; /* Terminate the line properly */
|
||||
*pCur++ = xsize;
|
||||
pilreadg5z:
|
||||
// Save the current VLC decoder state
|
||||
pPage->ulBits = ulBits;
|
||||
pPage->ulBitOff = ulBitOff;
|
||||
pPage->pBuf = pBuf;
|
||||
return pPage->iError;
|
||||
} /* DecodeLine() */
|
||||
//
|
||||
// Decompress the VLC data
|
||||
//
|
||||
static int g5_decode_line(G5DECIMAGE *pPage, uint8_t *pOut)
|
||||
{
|
||||
int rc;
|
||||
uint8_t *pBufEnd;
|
||||
int16_t *t1;
|
||||
|
||||
if (pPage == NULL || pOut == NULL)
|
||||
return G5_INVALID_PARAMETER;
|
||||
if (pPage->y >= pPage->iHeight)
|
||||
return G5_DECODE_COMPLETE;
|
||||
|
||||
pPage->iError = G5_SUCCESS;
|
||||
|
||||
if (pPage->y == 0) { // first time through
|
||||
Decode_Begin(pPage);
|
||||
}
|
||||
pBufEnd = &pPage->pSrc[pPage->iVLCSize];
|
||||
|
||||
if (pPage->pBuf >= pBufEnd) { // read past the end, error
|
||||
pPage->iError = G5_DECODE_ERROR;
|
||||
return G5_DECODE_ERROR;
|
||||
}
|
||||
rc = DecodeLine(pPage);
|
||||
if (rc == G5_SUCCESS) {
|
||||
// Draw the current line
|
||||
G5DrawLine(pPage, pPage->pCur, pOut);
|
||||
/*--- Swap current and reference lines ---*/
|
||||
t1 = pPage->pRef;
|
||||
pPage->pRef = pPage->pCur;
|
||||
pPage->pCur = t1;
|
||||
pPage->y++;
|
||||
if (pPage->y >= pPage->iHeight) {
|
||||
pPage->iError = G5_DECODE_COMPLETE;
|
||||
}
|
||||
} else {
|
||||
pPage->iError = rc;
|
||||
}
|
||||
return pPage->iError;
|
||||
} /* Decode() */
|
||||
@@ -0,0 +1,315 @@
|
||||
//
|
||||
// G5 Encoder
|
||||
// A 1-bpp image encoding library
|
||||
//
|
||||
// Written by Larry Bank (bitbank@pobox.com)
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2024 BitBank Software, Inc.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#include "Group5.h"
|
||||
|
||||
/* Number of consecutive 1 bits in a byte from MSB to LSB */
|
||||
static uint8_t bitcount[256] =
|
||||
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0-15 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 16-31 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 32-47 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 48-63 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 64-79 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 80-95 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 96-111 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 112-127 */
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 128-143 */
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 144-159 */
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 160-175 */
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 176-191 */
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 192-207 */
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 208-223 */
|
||||
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, /* 224-239 */
|
||||
4,4,4,4,4,4,4,4,5,5,5,5,6,6,7,8}; /* 240-255 */
|
||||
|
||||
/* Table of vertical codes for G5 encoding */
|
||||
/* code followed by length, starting with v(-3) */
|
||||
static const uint8_t vtable[14] =
|
||||
{3,7, /* V(-3) = 0000011 */
|
||||
3,6, /* V(-2) = 000011 */
|
||||
3,3, /* V(-1) = 011 */
|
||||
1,1, /* V(0) = 1 */
|
||||
2,3, /* V(1) = 010 */
|
||||
2,6, /* V(2) = 000010 */
|
||||
2,7}; /* V(3) = 0000010 */
|
||||
|
||||
|
||||
static void G5ENCInsertCode(G5_BUFFERED_BITS *bb, BIGUINT ulCode, int iLen)
|
||||
{
|
||||
if ((bb->ulBitOff + iLen) > REGISTER_WIDTH) { // need to write data
|
||||
bb->ulBits |= (ulCode >> (bb->ulBitOff + iLen - REGISTER_WIDTH)); // partial bits on first word
|
||||
*(BIGUINT *)bb->pBuf = __builtin_bswap32(bb->ulBits);
|
||||
bb->pBuf += sizeof(BIGUINT);
|
||||
bb->ulBits = ulCode << ((REGISTER_WIDTH*2) - (bb->ulBitOff + iLen));
|
||||
bb->ulBitOff += iLen - REGISTER_WIDTH;
|
||||
} else {
|
||||
bb->ulBits |= (ulCode << (REGISTER_WIDTH - bb->ulBitOff - iLen));
|
||||
bb->ulBitOff += iLen;
|
||||
}
|
||||
} /* G5ENCInsertCode() */
|
||||
//
|
||||
// Flush any buffered bits to the output
|
||||
//
|
||||
static void G5ENCFlushBits(G5_BUFFERED_BITS *bb)
|
||||
{
|
||||
while (bb->ulBitOff >= 8)
|
||||
{
|
||||
*bb->pBuf++ = (unsigned char) (bb->ulBits >> (REGISTER_WIDTH - 8));
|
||||
bb->ulBits <<= 8;
|
||||
bb->ulBitOff -= 8;
|
||||
}
|
||||
if (bb->ulBitOff) { // partial byte?
|
||||
*bb->pBuf++ = (unsigned char) (bb->ulBits >> (REGISTER_WIDTH - 8));
|
||||
}
|
||||
bb->ulBitOff = 0;
|
||||
bb->ulBits = 0;
|
||||
} /* G5ENCFlushBits() */
|
||||
//
|
||||
// Initialize the compressor
|
||||
// This must be called before adding data to the output
|
||||
//
|
||||
static int g5_encode_init(G5ENCIMAGE *pImage, int iWidth, int iHeight, uint8_t *pOut, int iOutSize)
|
||||
{
|
||||
int iError = G5_SUCCESS;
|
||||
|
||||
if (pImage == NULL || iHeight <= 0)
|
||||
return G5_INVALID_PARAMETER;
|
||||
pImage->iWidth = iWidth; // image size
|
||||
pImage->iHeight = iHeight;
|
||||
pImage->pCur = pImage->CurFlips;
|
||||
pImage->pRef = pImage->RefFlips;
|
||||
pImage->pOutBuf = pOut; // optional output buffer
|
||||
pImage->iOutSize = iOutSize; // output buffer pre-allocated size
|
||||
pImage->iDataSize = 0; // no data yet
|
||||
pImage->y = 0;
|
||||
for (int i=0; i<MAX_IMAGE_FLIPS; i++) {
|
||||
pImage->RefFlips[i] = iWidth;
|
||||
pImage->CurFlips[i] = iWidth;
|
||||
}
|
||||
pImage->bb.pBuf = pImage->pOutBuf;
|
||||
pImage->bb.ulBits = 0;
|
||||
pImage->bb.ulBitOff = 0;
|
||||
pImage->iError = iError;
|
||||
return iError;
|
||||
} /* g5_encode_init() */
|
||||
//
|
||||
// Internal function to convert uncompressed 1-bit per pixel data
|
||||
// into the run-end data needed to feed the G5 encoder
|
||||
//
|
||||
static int G5ENCEncodeLine(unsigned char *buf, int xsize, int16_t *pDest)
|
||||
{
|
||||
int iCount, xborder;
|
||||
uint8_t i, c;
|
||||
int8_t cBits;
|
||||
int iLen;
|
||||
int16_t x;
|
||||
int16_t *pLimit = pDest + (MAX_IMAGE_FLIPS-4);
|
||||
|
||||
xborder = xsize;
|
||||
iCount = (xsize + 7) >> 3; /* Number of bytes per line */
|
||||
cBits = 8;
|
||||
iLen = 0; /* Current run length */
|
||||
x = 0;
|
||||
|
||||
c = *buf++; /* Get the first byte to start */
|
||||
iCount--;
|
||||
while (iCount >=0) {
|
||||
if (pDest >= pLimit) return G5_MAX_FLIPS_EXCEEDED;
|
||||
i = bitcount[c]; /* Get the number of consecutive bits */
|
||||
iLen += i; /* Add this length to total run length */
|
||||
c <<= i;
|
||||
cBits -= i; /* Minus the number in a byte */
|
||||
if (cBits <= 0)
|
||||
{
|
||||
iLen += cBits; /* Adjust length */
|
||||
cBits = 8;
|
||||
c = *buf++; /* Get another data byte */
|
||||
iCount--;
|
||||
continue; /* Keep doing white until color change */
|
||||
}
|
||||
c = ~c; /* flip color to count black pixels */
|
||||
/* Store the white run length */
|
||||
xborder -= iLen;
|
||||
if (xborder < 0)
|
||||
{
|
||||
iLen += xborder; /* Make sure run length is not past end */
|
||||
break;
|
||||
}
|
||||
x += iLen;
|
||||
*pDest++ = x;
|
||||
iLen = 0;
|
||||
doblack:
|
||||
i = bitcount[c]; /* Get consecutive bits */
|
||||
iLen += i; /* Add to total run length */
|
||||
c <<= i;
|
||||
cBits -= i;
|
||||
if (cBits <= 0)
|
||||
{
|
||||
iLen += cBits; /* Adjust length */
|
||||
cBits = 8;
|
||||
c = *buf++; /* Get another data byte */
|
||||
c = ~c; /* Flip color to find black */
|
||||
iCount--;
|
||||
if (iCount < 0)
|
||||
break;
|
||||
goto doblack;
|
||||
}
|
||||
/* Store the black run length */
|
||||
c = ~c; /* Flip color again to find white pixels */
|
||||
xborder -= iLen;
|
||||
if (xborder < 0)
|
||||
{
|
||||
iLen += xborder; /* Make sure run length is not past end */
|
||||
break;
|
||||
}
|
||||
x += iLen;
|
||||
*pDest++ = x;
|
||||
iLen = 0;
|
||||
} /* while */
|
||||
|
||||
if (pDest >= pLimit) return G5_MAX_FLIPS_EXCEEDED;
|
||||
*pDest++ = xsize;
|
||||
*pDest++ = xsize; // Store a few more XSIZE to end the line
|
||||
*pDest++ = xsize; // so that the compressor doesn't go past
|
||||
*pDest++ = xsize; // the end of the line
|
||||
return G5_SUCCESS;
|
||||
} /* G5ENCEncodeLine() */
|
||||
//
|
||||
// Compress a line of pixels and add it to the output
|
||||
// the input format is expected to be MSB (most significant bit) first
|
||||
// for example, pixel 0 is in byte 0 at bit 7 (0x80)
|
||||
// Returns G5ENC_SUCCESS for each line if all is well and G5ENC_IMAGE_COMPLETE
|
||||
// for the last line
|
||||
//
|
||||
static int g5_encode_encodeLine(G5ENCIMAGE *pImage, uint8_t *pPixels)
|
||||
{
|
||||
int16_t a0, a0_c, b2, a1;
|
||||
int dx, run1, run2;
|
||||
int xsize, iErr, iHighWater;
|
||||
int iCur, iRef, iLen;
|
||||
int iHLen; // number of bits for long horizontal codes
|
||||
int16_t *CurFlips, *RefFlips;
|
||||
G5_BUFFERED_BITS bb;
|
||||
|
||||
if (pImage == NULL || pPixels == NULL)
|
||||
return G5_INVALID_PARAMETER;
|
||||
iHighWater = pImage->iOutSize - 32;
|
||||
iHLen = 32 - __builtin_clz(pImage->iWidth);
|
||||
memcpy(&bb, &pImage->bb, sizeof(G5_BUFFERED_BITS)); // keep local copy
|
||||
CurFlips = pImage->pCur;
|
||||
RefFlips = pImage->pRef;
|
||||
xsize = pImage->iWidth; /* For performance reasons */
|
||||
|
||||
// Convert the incoming line of pixels into run-end data
|
||||
iErr = G5ENCEncodeLine(pPixels, pImage->iWidth, CurFlips);
|
||||
if (iErr != G5_SUCCESS) return iErr; // exceeded the maximum number of color changes
|
||||
/* Encode this line as G5 */
|
||||
a0 = a0_c = 0;
|
||||
iCur = iRef = 0;
|
||||
while (a0 < xsize) {
|
||||
b2 = RefFlips[iRef+1];
|
||||
a1 = CurFlips[iCur];
|
||||
if (b2 < a1) { /* Is b2 to the left of a1? */
|
||||
/* yes, do pass mode */
|
||||
a0 = b2;
|
||||
iRef += 2;
|
||||
G5ENCInsertCode(&bb, 1, 4); /* Pass code = 0001 */
|
||||
} else { /* Try vertical and horizontal mode */
|
||||
dx = RefFlips[iRef] - a1; /* b1 - a1 */
|
||||
if (dx > 3 || dx < -3) { /* Horizontal mode */
|
||||
G5ENCInsertCode(&bb, 1, 3); /* Horizontal code = 001 */
|
||||
run1 = CurFlips[iCur] - a0;
|
||||
run2 = CurFlips[iCur+1] - CurFlips[iCur];
|
||||
if (run1 < 8) {
|
||||
if (run2 < 8) { // short, short
|
||||
G5ENCInsertCode(&bb, HORIZ_SHORT_SHORT, 2); /* short, short = 00 */
|
||||
G5ENCInsertCode(&bb, run1, 3);
|
||||
G5ENCInsertCode(&bb, run2, 3);
|
||||
} else { // short, long
|
||||
G5ENCInsertCode(&bb, HORIZ_SHORT_LONG, 2); /* short, long = 01 */
|
||||
G5ENCInsertCode(&bb, run1, 3);
|
||||
G5ENCInsertCode(&bb, run2, iHLen);
|
||||
}
|
||||
} else { // first run is long
|
||||
if (run2 < 8) { // long, short
|
||||
G5ENCInsertCode(&bb, HORIZ_LONG_SHORT, 2); /* long, short = 10 */
|
||||
G5ENCInsertCode(&bb, run1, iHLen);
|
||||
G5ENCInsertCode(&bb, run2, 3);
|
||||
} else { // long, long
|
||||
G5ENCInsertCode(&bb, HORIZ_LONG_LONG, 2); /* long, long = 11 */
|
||||
G5ENCInsertCode(&bb, run1, iHLen);
|
||||
G5ENCInsertCode(&bb, run2, iHLen);
|
||||
}
|
||||
}
|
||||
a0 = CurFlips[iCur+1]; /* a0 = a2 */
|
||||
if (a0 != xsize) {
|
||||
iCur += 2; /* Skip two color flips */
|
||||
while (RefFlips[iRef] != xsize && RefFlips[iRef] <= a0) {
|
||||
iRef += 2;
|
||||
}
|
||||
}
|
||||
} else { /* Vertical mode */
|
||||
dx = (dx + 3) * 2; /* Convert to index table */
|
||||
G5ENCInsertCode(&bb, vtable[dx], vtable[dx+1]);
|
||||
a0 = a1;
|
||||
a0_c = 1-a0_c;
|
||||
if (a0 != xsize) {
|
||||
if (iRef != 0) {
|
||||
iRef -= 2;
|
||||
}
|
||||
iRef++; /* Skip a color change in cur and ref */
|
||||
iCur++;
|
||||
while (RefFlips[iRef] <= a0 && RefFlips[iRef] != xsize) {
|
||||
iRef += 2;
|
||||
}
|
||||
}
|
||||
} /* vertical mode */
|
||||
} /* horiz/vert mode */
|
||||
} /* while x < xsize */
|
||||
iLen = (int)(bb.pBuf-pImage->pOutBuf);
|
||||
if (iLen >= iHighWater) { // not enough space
|
||||
pImage->iError = iErr = G5_DATA_OVERFLOW; // we don't have a better error
|
||||
return iErr;
|
||||
}
|
||||
if (pImage->y == pImage->iHeight-1) { // last line of image
|
||||
G5ENCFlushBits(&bb); // output the final buffered bits
|
||||
// wrap up final output
|
||||
pImage->iDataSize = 1 + (int)(bb.pBuf-pImage->pOutBuf);
|
||||
iErr = G5_ENCODE_COMPLETE;
|
||||
}
|
||||
pImage->pCur = RefFlips; // swap current and reference lines
|
||||
pImage->pRef = CurFlips;
|
||||
pImage->y++;
|
||||
memcpy(&pImage->bb, &bb, sizeof(bb));
|
||||
return iErr;
|
||||
} /* g5_encode_encodeLine() */
|
||||
//
|
||||
// Returns the number of bytes of G5 created by the encoder
|
||||
//
|
||||
static int g5_encode_getOutSize(G5ENCIMAGE *pImage)
|
||||
{
|
||||
int iSize = 0;
|
||||
if (pImage != NULL)
|
||||
iSize = pImage->iDataSize;
|
||||
return iSize;
|
||||
} /* g5_encode_getOutSize() */
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <builtinFonts/bookerly_12.h>
|
||||
#include <builtinFonts/bookerly_14.h>
|
||||
#include <builtinFonts/bookerly_16.h>
|
||||
#include <builtinFonts/bookerly_18.h>
|
||||
#include <builtinFonts/notosans_8.h>
|
||||
#include <builtinFonts/notosans_12.h>
|
||||
#include <builtinFonts/notosans_14.h>
|
||||
#include <builtinFonts/notosans_16.h>
|
||||
#include <builtinFonts/notosans_18.h>
|
||||
#include <builtinFonts/opendyslexic_8.h>
|
||||
#include <builtinFonts/opendyslexic_10.h>
|
||||
#include <builtinFonts/opendyslexic_12.h>
|
||||
#include <builtinFonts/opendyslexic_14.h>
|
||||
#include <builtinFonts/ubuntu_10.h>
|
||||
#include <builtinFonts/ubuntu_12.h>
|
||||
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
File diff suppressed because it is too large
Load Diff
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "// The contents of this file are generated by ./lib/CrossPointFont/builtinFonts/build-font-ids.sh"
|
||||
echo "#pragma once"
|
||||
echo ""
|
||||
|
||||
echo "#define BOOKERLY_12_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./bookerly_12.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define BOOKERLY_14_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./bookerly_14.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define BOOKERLY_16_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./bookerly_16.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define BOOKERLY_18_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./bookerly_18.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define NOTOSANS_12_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./notosans_12.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define NOTOSANS_14_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./notosans_14.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define NOTOSANS_16_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./notosans_16.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define NOTOSANS_18_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./notosans_18.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define OPENDYSLEXIC_8_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./opendyslexic_8.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define OPENDYSLEXIC_10_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./opendyslexic_10.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define OPENDYSLEXIC_12_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./opendyslexic_12.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define OPENDYSLEXIC_14_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./opendyslexic_14.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define UI_10_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./ubuntu_10.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define UI_12_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./ubuntu_12.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
echo "#define SMALL_FONT_ID ($(ruby -rdigest -e 'puts Digest::SHA256.hexdigest(File.read("./notosans_8.h")).to_i(16) % (2 ** 32) - (2 ** 31)'))"
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
BOOKERLY_FONT_SIZES=(12 14 16 18)
|
||||
NOTOSANS_FONT_SIZES=(12 14 16 18)
|
||||
OPENDYSLEXIC_FONT_SIZES=(8 10 12 14)
|
||||
|
||||
for size in ${BOOKERLY_FONT_SIZES[@]}; do
|
||||
font_name="bookerly_${size}"
|
||||
font_path_prefix="./source/Bookerly/Bookerly-"
|
||||
output_path="./${font_name}.h"
|
||||
../fontconvert/fontconvert "${font_path_prefix}Regular.ttf" -b "${font_path_prefix}Bold.ttf" -i "${font_path_prefix}Italic.ttf" -bi "${font_path_prefix}BoldItalic.ttf" -o $output_path -p $size
|
||||
done
|
||||
|
||||
for size in ${NOTOSANS_FONT_SIZES[@]}; do
|
||||
font_name="notosans_${size}"
|
||||
font_path_prefix="./source/NotoSans/NotoSans-"
|
||||
output_path="./${font_name}.h"
|
||||
../fontconvert/fontconvert "${font_path_prefix}Regular.ttf" -b "${font_path_prefix}Bold.ttf" -i "${font_path_prefix}Italic.ttf" -bi "${font_path_prefix}BoldItalic.ttf" -o $output_path -p $size
|
||||
done
|
||||
|
||||
for size in ${OPENDYSLEXIC_FONT_SIZES[@]}; do
|
||||
font_name="opendyslexic_${size}"
|
||||
font_path_prefix="./source/OpenDyslexic/OpenDyslexic-"
|
||||
output_path="./${font_name}.h"
|
||||
../fontconvert/fontconvert "${font_path_prefix}Regular.otf" -b "${font_path_prefix}Bold.otf" -i "${font_path_prefix}Italic.otf" -bi "${font_path_prefix}BoldItalic.otf" -o $output_path -p $size
|
||||
done
|
||||
|
||||
UI_FONT_SIZES=(10 12)
|
||||
UI_FONT_STYLES=("Regular" "Bold")
|
||||
|
||||
for size in ${UI_FONT_SIZES[@]}; do
|
||||
font_name="ubuntu_${size}"
|
||||
font_path_prefix="./source/Ubuntu/Ubuntu-"
|
||||
output_path="./${font_name}.h"
|
||||
../fontconvert/fontconvert "${font_path_prefix}Regular.ttf" -b "${font_path_prefix}Bold.ttf" -o $output_path -p $size
|
||||
done
|
||||
|
||||
../fontconvert/fontconvert ./source/NotoSans/NotoSans-Regular.ttf -o ./notosans_8.h -p 8
|
||||
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
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
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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
fontconvert
|
||||
@@ -0,0 +1,12 @@
|
||||
all: fontconvert
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -I/usr/local/include/freetype2 -I/usr/include/freetype2 -I/usr/include -I/opt/homebrew/include/freetype2
|
||||
LIBS = -L/opt/homebrew/lib -lfreetype
|
||||
|
||||
fontconvert: main.c ../Group5/Group5.h
|
||||
$(CC) $(CFLAGS) main.c $(LIBS) -o fontconvert
|
||||
strip fontconvert
|
||||
|
||||
clean:
|
||||
rm -f fontconvert
|
||||
@@ -0,0 +1,553 @@
|
||||
//
|
||||
// TrueType to CrossPoint font converter
|
||||
// Copyright (c) 2024 BitBank Software, inc.
|
||||
// Written by Larry Bank, adapted by Dave Allie for CrossPoint
|
||||
// August 31, 2024
|
||||
// The CrossPoint font format is a losslessly compressed bitmap font of a single point size with multiple variants
|
||||
// This was built entirely on the back of Larry Bank's bb_font format.
|
||||
// The data is compressed with a compression scheme based on CCITT T.6
|
||||
// The font structure includes overall size, per-character glyph info and then the
|
||||
// compressed image data at the end.
|
||||
// The font file format is designed to allow both dynamic loading of font data from
|
||||
// external memory/disk or compiling the data as const into a progarm.
|
||||
//
|
||||
// Example usage:
|
||||
// ./fontconvert <regular.ttf> [-b <bold.ttf>] [-i <italic.ttf>] [-bi <bold-italic.ttf>] -p <pt size> -o <out.cpf>
|
||||
// ./fontconvert <regular.ttf> [-b <bold.ttf>] [-i <italic.ttf>] [-bi <bold-italic.ttf>] -p <pt size> -o <out.h>
|
||||
//
|
||||
// This code requires the freetype library
|
||||
// found here: www.freetype.org
|
||||
//
|
||||
|
||||
#ifndef ARDUINO
|
||||
|
||||
#include <ctype.h>
|
||||
#include <ft2build.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../CrossPointFontFormat.h"
|
||||
|
||||
#include FT_GLYPH_H
|
||||
#include FT_MODULE_H
|
||||
#include FT_TRUETYPE_DRIVER_H
|
||||
#include "../Group5/g5enc.inl" // Group5 image compression library
|
||||
G5ENCIMAGE g5enc; // Group5 encoder state
|
||||
|
||||
#define DPI 150 // Approximate resolution of common displays
|
||||
#define OUTBUF_SIZE 1048576 // 1MB
|
||||
#define MAX_INTERVALS 65536
|
||||
#define FONT_SCALE_FACTOR 2
|
||||
// TODO: Re-enable small font
|
||||
// Disabled small font generation to get this working, but want to re-enable
|
||||
#define SMALL_FONT_ENABLED 0
|
||||
|
||||
uint32_t raw_intervals[][2] = {
|
||||
/* Basic Latin */
|
||||
// ASCII letters, digits, punctuation, control characters
|
||||
{0x0000, 0x007F},
|
||||
/* Latin-1 Supplement */
|
||||
// Accented characters for Western European languages
|
||||
{0x0080, 0x00FF},
|
||||
/* Latin Extended-A */
|
||||
// Eastern European and Baltic languages
|
||||
{0x0100, 0x017F},
|
||||
/* General Punctuation (core subset) */
|
||||
// Smart quotes, en dash, em dash, ellipsis, NO-BREAK SPACE
|
||||
{0x2000, 0x206F},
|
||||
/* Basic Symbols From "Latin-1 + Misc" */
|
||||
// dashes, quotes, prime marks
|
||||
{0x2010, 0x203A},
|
||||
// misc punctuation
|
||||
{0x2040, 0x205F},
|
||||
// common currency symbols
|
||||
{0x20A0, 0x20CF},
|
||||
/* Combining Diacritical Marks (minimal subset) */
|
||||
// Needed for proper rendering of many extended Latin languages
|
||||
{0x0300, 0x036F},
|
||||
/* Greek & Coptic */
|
||||
// Used in science, maths, philosophy, some academic texts
|
||||
// {0x0370, 0x03FF},
|
||||
/* Cyrillic */
|
||||
// Russian, Ukrainian, Bulgarian, etc.
|
||||
{0x0400, 0x04FF},
|
||||
/* Math Symbols (common subset) */
|
||||
// Superscripts and Subscripts
|
||||
{0x2070, 0x209F},
|
||||
// General math operators
|
||||
{0x2200, 0x22FF},
|
||||
// Arrows
|
||||
{0x2190, 0x21FF},
|
||||
/* CJK */
|
||||
// Core Unified Ideographs
|
||||
// {0x4E00, 0x9FFF},
|
||||
// Extension A
|
||||
// {0x3400, 0x4DBF},
|
||||
// Extension B
|
||||
// {0x20000, 0x2A6DF},
|
||||
// Extension C–F
|
||||
// {0x2A700, 0x2EBEF},
|
||||
// Extension G
|
||||
// {0x30000, 0x3134F},
|
||||
// Hiragana
|
||||
// {0x3040, 0x309F},
|
||||
// Katakana
|
||||
// {0x30A0, 0x30FF},
|
||||
// Katakana Phonetic Extensions
|
||||
// {0x31F0, 0x31FF},
|
||||
// Halfwidth Katakana
|
||||
// {0xFF60, 0xFF9F},
|
||||
// Hangul Syllables
|
||||
// {0xAC00, 0xD7AF},
|
||||
// Hangul Jamo
|
||||
// {0x1100, 0x11FF},
|
||||
// Hangul Compatibility Jamo
|
||||
// {0x3130, 0x318F},
|
||||
// Hangul Jamo Extended-A
|
||||
// {0xA960, 0xA97F},
|
||||
// Hangul Jamo Extended-B
|
||||
// {0xD7B0, 0xD7FF},
|
||||
// CJK Radicals Supplement
|
||||
// {0x2E80, 0x2EFF},
|
||||
// Kangxi Radicals
|
||||
// {0x2F00, 0x2FDF},
|
||||
// CJK Symbols and Punctuation
|
||||
// {0x3000, 0x303F},
|
||||
// CJK Compatibility Forms
|
||||
// {0xFE30, 0xFE4F},
|
||||
// CJK Compatibility Ideographs
|
||||
// {0xF900, 0xFAFF},
|
||||
/* Specials */
|
||||
// Replacement Character
|
||||
{0xFFFD, 0xFFFD},
|
||||
};
|
||||
|
||||
//
|
||||
// Comparison function for qsort
|
||||
//
|
||||
int compareIntervals(const void* a, const void* b) {
|
||||
const uint32_t* ia = (uint32_t*)a;
|
||||
const uint32_t* ib = (uint32_t*)b;
|
||||
if (ia[0] < ib[0]) return -1;
|
||||
if (ia[0] > ib[0]) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Sort and merge adjacent intervals
|
||||
// Returns the number of intervals after merging
|
||||
//
|
||||
int sortAndMergeIntervals(uint32_t intervals[][2], const int count) {
|
||||
int merged_count = 0;
|
||||
|
||||
// Sort intervals by start value
|
||||
qsort(intervals, count, sizeof(uint32_t) * 2, compareIntervals);
|
||||
|
||||
// Merge overlapping/adjacent intervals
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (merged_count > 0 && intervals[i][0] <= intervals[merged_count - 1][1] + 1) {
|
||||
// Merge with previous interval
|
||||
if (intervals[i][1] > intervals[merged_count - 1][1]) {
|
||||
intervals[merged_count - 1][1] = intervals[i][1];
|
||||
}
|
||||
} else {
|
||||
// Add as new interval
|
||||
if (merged_count != i) {
|
||||
intervals[merged_count][0] = intervals[i][0];
|
||||
intervals[merged_count][1] = intervals[i][1];
|
||||
}
|
||||
merged_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return merged_count;
|
||||
}
|
||||
|
||||
//
|
||||
// Create the comments and const array boilerplate for the hex data bytes
|
||||
//
|
||||
void StartHexFile(FILE* f, int iLen, const char* fname, int size) {
|
||||
int i, j;
|
||||
char szTemp[256];
|
||||
fprintf(f, "#pragma once\n\n");
|
||||
fprintf(f, "//\n// Created with fontconvert, written by Larry Bank, updated for CrossPoint by Dave Allie\n");
|
||||
fprintf(f, "// Point size: %d (scaled %dx)\n", size, FONT_SCALE_FACTOR);
|
||||
fprintf(f, "// compressed font data size = %d bytes\n//\n", iLen);
|
||||
|
||||
strcpy(szTemp, fname);
|
||||
i = strlen(szTemp);
|
||||
if (szTemp[i - 2] == '.') szTemp[i - 2] = 0; // get the leaf name for the data
|
||||
j = i;
|
||||
// go backwards to get rid trim off just the leaf name
|
||||
while (j > 0 && szTemp[j] != '/') {
|
||||
j--;
|
||||
}
|
||||
if (szTemp[j] == '/') j++;
|
||||
fprintf(f, "static const uint8_t %s[] = {\n", &szTemp[j]);
|
||||
} /* StartHexFile() */
|
||||
|
||||
//
|
||||
// Add N bytes of hex data to the output
|
||||
// The data will be arranged in rows of 16 bytes each
|
||||
//
|
||||
void AddHexBytes(FILE* f, void* pData, int iLen, int bLast) {
|
||||
static int iCount = 0; // number of bytes processed so far
|
||||
int i;
|
||||
uint8_t* s = (uint8_t*)pData;
|
||||
for (i = 0; i < iLen; i++) { // process the given data
|
||||
fprintf(f, "0x%02x", *s++);
|
||||
iCount++;
|
||||
if (i < iLen - 1 || !bLast) fprintf(f, ",");
|
||||
if ((iCount & 15) == 0) fprintf(f, "\n"); // next row of 16
|
||||
}
|
||||
if (bLast) {
|
||||
fprintf(f, "};\n");
|
||||
}
|
||||
} /* AddHexBytes() */
|
||||
|
||||
int loadCodePoint(FT_Face face, uint32_t code_point, CrossPointFontGlyph* pGlyphs, uint8_t* pBitmap,
|
||||
uint32_t* glyph_index, uint32_t* iOffset) {
|
||||
uint8_t* s;
|
||||
int iPitch, err;
|
||||
FT_Glyph glyph;
|
||||
|
||||
// MONO renderer provides clean image with perfect crop
|
||||
// (no wasted pixels) via bitmap struct.
|
||||
if ((err = FT_Load_Char(face, code_point, FT_LOAD_TARGET_MONO))) {
|
||||
printf("Error %d loading char U+%04X\n", err, code_point);
|
||||
(*glyph_index)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((err = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO))) {
|
||||
printf("Error %d rendering char U+%04X\n", err, code_point);
|
||||
(*glyph_index)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((err = FT_Get_Glyph(face->glyph, &glyph))) {
|
||||
printf("Error %d getting glyph U+%04X\n", err, code_point);
|
||||
(*glyph_index)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
FT_Bitmap* bitmap = &face->glyph->bitmap;
|
||||
FT_BitmapGlyphRec* g = (FT_BitmapGlyphRec*)glyph;
|
||||
|
||||
// TODO: Restore small font
|
||||
if (0 /* bSmallFont */) {
|
||||
#if SMALL_FONT_ENABLED == 1
|
||||
printf("Stubbed\n");
|
||||
return 1;
|
||||
#else
|
||||
printf("Small font has been disabled\n");
|
||||
return 1;
|
||||
#endif
|
||||
} else {
|
||||
pGlyphs[*glyph_index].bitmapOffset = *iOffset;
|
||||
pGlyphs[*glyph_index].width = bitmap->width;
|
||||
pGlyphs[*glyph_index].height = bitmap->rows;
|
||||
pGlyphs[*glyph_index].xAdvance = (face->glyph->advance.x >> 6);
|
||||
pGlyphs[*glyph_index].xOffset = g->left;
|
||||
pGlyphs[*glyph_index].yOffset = g->top;
|
||||
}
|
||||
s = bitmap->buffer;
|
||||
iPitch = bitmap->pitch;
|
||||
|
||||
g5_encode_init(&g5enc, bitmap->width, bitmap->rows, &pBitmap[*iOffset], OUTBUF_SIZE - *iOffset);
|
||||
for (int y = 0; y < bitmap->rows; y++) {
|
||||
g5_encode_encodeLine(&g5enc, &s[y * iPitch]);
|
||||
} // for y
|
||||
int iLen = g5_encode_getOutSize(&g5enc);
|
||||
*iOffset += iLen;
|
||||
|
||||
FT_Done_Glyph(glyph);
|
||||
(*glyph_index)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int i, err, size = 0;
|
||||
uint32_t iLen, iOffset = 0;
|
||||
FILE* fOut;
|
||||
// TrueType library structures
|
||||
FT_Library library;
|
||||
char* regularFaceFile;
|
||||
char* boldFaceFile = NULL;
|
||||
char* italicFaceFile = NULL;
|
||||
char* boldItalicFaceFile = NULL;
|
||||
const char* outputFile = NULL;
|
||||
FT_Face faceRegular;
|
||||
FT_Face faceBold;
|
||||
FT_Face faceItalic;
|
||||
FT_Face faceBoldItalic;
|
||||
|
||||
int bSmallFont = 0; // indicates if we're creating a normal or small font file
|
||||
CrossPointFontUnicodeInterval* pIntervals;
|
||||
CrossPointFontGlyph* pGlyphs;
|
||||
#if SMALL_FONT_ENABLED == 1
|
||||
CrossPointFontSmallGlyph* pSmallGlyphs;
|
||||
#endif
|
||||
uint8_t* pBitmap;
|
||||
CrossPointFontHeader epdFontHeader;
|
||||
|
||||
int bHFile; // flag indicating if the output will be a .H file of hex data
|
||||
|
||||
// Process intervals
|
||||
uint32_t intervals[MAX_INTERVALS][2];
|
||||
int intervalCount = sizeof(raw_intervals) / sizeof(raw_intervals[0]);
|
||||
uint32_t totalGlyphs = 0;
|
||||
|
||||
if (argc < 6 || argc % 2 == 1) {
|
||||
printf(
|
||||
"Usage: %s <regular.ttf> [-b <bold.ttf>] [-i <italic.ttf>] [-bi <bold-italic.ttf>] -p point_size -o <out.cpf "
|
||||
"or out.h>\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
regularFaceFile = argv[1];
|
||||
for (int i = 2; i < argc; i += 2) {
|
||||
if (strcmp(argv[i], "-b") == 0) {
|
||||
// Bold font
|
||||
boldFaceFile = argv[i + 1];
|
||||
} else if (strcmp(argv[i], "-i") == 0) {
|
||||
// Italic font
|
||||
italicFaceFile = argv[i + 1];
|
||||
} else if (strcmp(argv[i], "-bi") == 0) {
|
||||
// Bold-Italic font
|
||||
boldItalicFaceFile = argv[i + 1];
|
||||
} else if (strcmp(argv[i], "-p") == 0) {
|
||||
// Point size
|
||||
size = atoi(argv[i + 1]);
|
||||
} else if (strcmp(argv[i], "-o") == 0) {
|
||||
// Output file
|
||||
outputFile = argv[i + 1];
|
||||
// output an H file?
|
||||
bHFile = outputFile[strlen(outputFile) - 1] == 'H' || outputFile[strlen(outputFile) - 1] == 'h';
|
||||
} else {
|
||||
printf("Unknown argument: %s\n", argv[i]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!outputFile) {
|
||||
printf("No output file specified\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (size <= 0) {
|
||||
printf("Invalid point size: %d\n", size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size = size * FONT_SCALE_FACTOR;
|
||||
bSmallFont = (size < 60) && SMALL_FONT_ENABLED == 1; // Glyph info can fit in signed 8-bit values
|
||||
int fontVariants = 1; // Always at least one variant we treat as regular
|
||||
if (boldFaceFile) fontVariants += 1;
|
||||
if (italicFaceFile) fontVariants += 1;
|
||||
if (boldItalicFaceFile) fontVariants += 1;
|
||||
|
||||
// Copy and sort/merge intervals
|
||||
if (intervalCount > MAX_INTERVALS) {
|
||||
printf("Error: too many intervals (max %d)\n", MAX_INTERVALS);
|
||||
return 1;
|
||||
}
|
||||
for (i = 0; i < intervalCount; i++) {
|
||||
intervals[i][0] = raw_intervals[i][0];
|
||||
intervals[i][1] = raw_intervals[i][1];
|
||||
}
|
||||
intervalCount = sortAndMergeIntervals(intervals, intervalCount);
|
||||
|
||||
// Calculate total number of glyphs
|
||||
for (i = 0; i < intervalCount; i++) {
|
||||
totalGlyphs += intervals[i][1] - intervals[i][0] + 1;
|
||||
}
|
||||
totalGlyphs *= fontVariants;
|
||||
|
||||
printf("Processed intervals: %d, total glyphs: %u\n", intervalCount, totalGlyphs);
|
||||
|
||||
// Allocate memory for intervals
|
||||
pIntervals = (CrossPointFontUnicodeInterval*)malloc(intervalCount * sizeof(CrossPointFontUnicodeInterval));
|
||||
if (!pIntervals) {
|
||||
printf("Error allocating memory for interval data\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Allocate memory for glyphs
|
||||
if (bSmallFont) {
|
||||
#if SMALL_FONT_ENABLED == 1
|
||||
pSmallGlyphs = (CrossPointFontSmallGlyph*)malloc(totalGlyphs * sizeof(CrossPointFontSmallGlyph));
|
||||
if (!pSmallGlyphs) {
|
||||
printf("Error allocating memory for glyph data\n");
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
printf("Small font has been disabled\n");
|
||||
return 1;
|
||||
#endif
|
||||
} else {
|
||||
pGlyphs = (CrossPointFontGlyph*)malloc(totalGlyphs * sizeof(CrossPointFontGlyph));
|
||||
if (!pGlyphs) {
|
||||
printf("Error allocating memory for glyph data\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
pBitmap = (uint8_t*)malloc(OUTBUF_SIZE); // Enough to hold the output
|
||||
if (!pBitmap) {
|
||||
printf("Error allocating memory for bitmap data\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Init FreeType lib, load font
|
||||
if ((err = FT_Init_FreeType(&library))) {
|
||||
printf("FreeType init error: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Use TrueType engine version 35, without subpixel rendering.
|
||||
// This improves clarity of fonts since this library does not
|
||||
// support rendering multiple levels of gray in a glyph.
|
||||
// See https://github.com/adafruit/Adafruit-GFX-Library/issues/103
|
||||
FT_UInt interpreter_version = TT_INTERPRETER_VERSION_35;
|
||||
FT_Property_Set(library, "truetype", "interpreter-version", &interpreter_version);
|
||||
|
||||
if ((err = FT_New_Face(library, regularFaceFile, 0, &faceRegular))) {
|
||||
printf("Font load error: %d\n", err);
|
||||
FT_Done_FreeType(library);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (italicFaceFile && (err = FT_New_Face(library, italicFaceFile, 0, &faceItalic))) {
|
||||
printf("Font load error: %d\n", err);
|
||||
FT_Done_FreeType(library);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (boldFaceFile && (err = FT_New_Face(library, boldFaceFile, 0, &faceBold))) {
|
||||
printf("Font load error: %d\n", err);
|
||||
FT_Done_FreeType(library);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (boldItalicFaceFile && (err = FT_New_Face(library, boldItalicFaceFile, 0, &faceBoldItalic))) {
|
||||
printf("Font load error: %d\n", err);
|
||||
FT_Done_FreeType(library);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Shift the size left by 6 because the library uses '26dot6' fixed-point format
|
||||
FT_Set_Char_Size(faceRegular, size << 6, 0, DPI, 0);
|
||||
if (boldFaceFile) FT_Set_Char_Size(faceBold, size << 6, 0, DPI, 0);
|
||||
if (italicFaceFile) FT_Set_Char_Size(faceItalic, size << 6, 0, DPI, 0);
|
||||
if (boldItalicFaceFile) FT_Set_Char_Size(faceBoldItalic, size << 6, 0, DPI, 0);
|
||||
|
||||
// Build intervals with offsets and process glyphs
|
||||
uint32_t glyph_index = 0;
|
||||
for (int iInterval = 0; iInterval < intervalCount; iInterval++) {
|
||||
const uint32_t intervalStart = intervals[iInterval][0];
|
||||
const uint32_t intervalEnd = intervals[iInterval][1];
|
||||
|
||||
// Store interval with offset
|
||||
pIntervals[iInterval].first = intervalStart;
|
||||
pIntervals[iInterval].last = intervalEnd;
|
||||
pIntervals[iInterval].offset = glyph_index;
|
||||
|
||||
// Process each glyph in this interval
|
||||
// Load the codepoint for each style variant
|
||||
for (uint32_t codePoint = intervalStart; codePoint <= intervalEnd; codePoint++) {
|
||||
loadCodePoint(faceRegular, codePoint, pGlyphs, pBitmap, &glyph_index, &iOffset);
|
||||
if (boldFaceFile) loadCodePoint(faceBold, codePoint, pGlyphs, pBitmap, &glyph_index, &iOffset);
|
||||
if (italicFaceFile) loadCodePoint(faceItalic, codePoint, pGlyphs, pBitmap, &glyph_index, &iOffset);
|
||||
if (boldItalicFaceFile) loadCodePoint(faceBoldItalic, codePoint, pGlyphs, pBitmap, &glyph_index, &iOffset);
|
||||
} // for each code point in interval
|
||||
} // for each interval
|
||||
|
||||
// Try to create the output file
|
||||
fOut = fopen(outputFile, "w+b");
|
||||
if (!fOut) {
|
||||
printf("Error creating output file: %s\n", outputFile);
|
||||
return 1;
|
||||
}
|
||||
|
||||
epdFontHeader.height = faceRegular->size->metrics.height >> 6;
|
||||
epdFontHeader.ascender = faceRegular->size->metrics.ascender >> 6;
|
||||
epdFontHeader.styles = 0b0001;
|
||||
if (boldFaceFile) epdFontHeader.styles |= 0b0010;
|
||||
if (italicFaceFile) epdFontHeader.styles |= 0b0100;
|
||||
if (boldItalicFaceFile) epdFontHeader.styles |= 0b1000;
|
||||
epdFontHeader.intervalCount = intervalCount;
|
||||
epdFontHeader.glyphCount = totalGlyphs;
|
||||
|
||||
// Write the file header
|
||||
if (bSmallFont) {
|
||||
#if SMALL_FONT_ENABLED == 1
|
||||
epdFontHeader.u16Marker = CPF_FONT_MARKER_SMALL;
|
||||
if (faceRegular->size->metrics.height == 0) {
|
||||
// No face height info, assume fixed width and get from a glyph.
|
||||
epdFontHeader.height = pSmallGlyphs[0].height;
|
||||
}
|
||||
|
||||
iLen = sizeof(CrossPointFontHeader) + intervalCount * sizeof(CrossPointFontUnicodeInterval) +
|
||||
totalGlyphs * sizeof(CrossPointFontSmallGlyph) + iOffset;
|
||||
if (bHFile) { // create an H file of hex values
|
||||
StartHexFile(fOut, iLen, outputFile, size);
|
||||
AddHexBytes(fOut, &epdFontHeader, sizeof(CrossPointFontHeader), 0);
|
||||
// Write the intervals
|
||||
AddHexBytes(fOut, pIntervals, sizeof(CrossPointFontUnicodeInterval) * intervalCount, 0);
|
||||
// Write the glyph table
|
||||
AddHexBytes(fOut, pSmallGlyphs, sizeof(CrossPointFontSmallGlyph) * totalGlyphs, 0);
|
||||
// Write the compressed bitmap data
|
||||
AddHexBytes(fOut, pBitmap, iOffset, 1);
|
||||
} else {
|
||||
fwrite(&epdFontHeader, 1, sizeof(CrossPointFontHeader), fOut);
|
||||
// Write the intervals
|
||||
fwrite(pIntervals, 1, intervalCount * sizeof(CrossPointFontUnicodeInterval), fOut);
|
||||
// Write the glyph table
|
||||
fwrite(pSmallGlyphs, 1, totalGlyphs * sizeof(CrossPointFontSmallGlyph), fOut);
|
||||
// Write the compressed bitmap data
|
||||
fwrite(pBitmap, 1, iOffset, fOut);
|
||||
}
|
||||
#else
|
||||
printf("Small font has been disabled\n");
|
||||
return 1;
|
||||
#endif
|
||||
} else {
|
||||
epdFontHeader.u16Marker = CPF_FONT_MARKER;
|
||||
if (faceRegular->size->metrics.height == 0) {
|
||||
// No face height info, assume fixed width and get from a glyph.
|
||||
epdFontHeader.height = pGlyphs[0].height;
|
||||
}
|
||||
|
||||
iLen = sizeof(CrossPointFontHeader) + intervalCount * sizeof(CrossPointFontUnicodeInterval) +
|
||||
totalGlyphs * sizeof(CrossPointFontGlyph) + iOffset;
|
||||
if (bHFile) { // create an H file of hex values
|
||||
StartHexFile(fOut, iLen, outputFile, size);
|
||||
AddHexBytes(fOut, &epdFontHeader, sizeof(CrossPointFontHeader), 0);
|
||||
// Write the intervals
|
||||
AddHexBytes(fOut, pIntervals, sizeof(CrossPointFontUnicodeInterval) * intervalCount, 0);
|
||||
// Write the glyph table
|
||||
AddHexBytes(fOut, pGlyphs, sizeof(CrossPointFontGlyph) * totalGlyphs, 0);
|
||||
// Write the compressed bitmap data
|
||||
AddHexBytes(fOut, pBitmap, iOffset, 1);
|
||||
} else {
|
||||
fwrite(&epdFontHeader, 1, sizeof(CrossPointFontHeader), fOut);
|
||||
// Write the intervals
|
||||
fwrite(pIntervals, 1, intervalCount * sizeof(CrossPointFontUnicodeInterval), fOut);
|
||||
// Write the glyph table
|
||||
fwrite(pGlyphs, 1, totalGlyphs * sizeof(CrossPointFontGlyph), fOut);
|
||||
// Write the compressed bitmap data
|
||||
fwrite(pBitmap, 1, iOffset, fOut);
|
||||
}
|
||||
} // large fonts
|
||||
fflush(fOut);
|
||||
fclose(fOut); // done!
|
||||
FT_Done_FreeType(library);
|
||||
printf("Success!\nFont file size: %d bytes (%d glyphs)\n", iLen, totalGlyphs);
|
||||
|
||||
return 0;
|
||||
} /* main() */
|
||||
|
||||
#endif
|
||||
@@ -1,85 +0,0 @@
|
||||
#include "EpdFont.h"
|
||||
|
||||
#include <Utf8.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
void EpdFont::getTextBounds(const char* string, const int startX, const int startY, int* minX, int* minY, int* maxX,
|
||||
int* maxY) const {
|
||||
*minX = startX;
|
||||
*minY = startY;
|
||||
*maxX = startX;
|
||||
*maxY = startY;
|
||||
|
||||
if (*string == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
int cursorX = startX;
|
||||
const int cursorY = startY;
|
||||
uint32_t cp;
|
||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
|
||||
const EpdGlyph* glyph = getGlyph(cp);
|
||||
|
||||
if (!glyph) {
|
||||
// TODO: Replace with fallback glyph property?
|
||||
glyph = getGlyph('?');
|
||||
}
|
||||
|
||||
if (!glyph) {
|
||||
// TODO: Better handle this?
|
||||
continue;
|
||||
}
|
||||
|
||||
*minX = std::min(*minX, cursorX + glyph->left);
|
||||
*maxX = std::max(*maxX, cursorX + glyph->left + glyph->width);
|
||||
*minY = std::min(*minY, cursorY + glyph->top - glyph->height);
|
||||
*maxY = std::max(*maxY, cursorY + glyph->top);
|
||||
cursorX += glyph->advanceX;
|
||||
}
|
||||
}
|
||||
|
||||
void EpdFont::getTextDimensions(const char* string, int* w, int* h) const {
|
||||
int minX = 0, minY = 0, maxX = 0, maxY = 0;
|
||||
|
||||
getTextBounds(string, 0, 0, &minX, &minY, &maxX, &maxY);
|
||||
|
||||
*w = maxX - minX;
|
||||
*h = maxY - minY;
|
||||
}
|
||||
|
||||
bool EpdFont::hasPrintableChars(const char* string) const {
|
||||
int w = 0, h = 0;
|
||||
|
||||
getTextDimensions(string, &w, &h);
|
||||
|
||||
return w > 0 || h > 0;
|
||||
}
|
||||
|
||||
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
|
||||
const EpdUnicodeInterval* intervals = data->intervals;
|
||||
const int count = data->intervalCount;
|
||||
|
||||
if (count == 0) return nullptr;
|
||||
|
||||
// Binary search for O(log n) lookup instead of O(n)
|
||||
// Critical for Korean fonts with many unicode intervals
|
||||
int left = 0;
|
||||
int right = count - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const int mid = left + (right - left) / 2;
|
||||
const EpdUnicodeInterval* interval = &intervals[mid];
|
||||
|
||||
if (cp < interval->first) {
|
||||
right = mid - 1;
|
||||
} else if (cp > interval->last) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Found: cp >= interval->first && cp <= interval->last
|
||||
return &data->glyph[interval->offset + (cp - interval->first)];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
class EpdFont {
|
||||
void getTextBounds(const char* string, int startX, int startY, int* minX, int* minY, int* maxX, int* maxY) const;
|
||||
|
||||
public:
|
||||
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;
|
||||
|
||||
const EpdGlyph* getGlyph(uint32_t cp) const;
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
// From
|
||||
// https://github.com/vroland/epdiy/blob/c61e9e923ce2418150d54f88cea5d196cdc40c54/src/epd_internals.h
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
/// 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 dataLength; ///< Size of the font data.
|
||||
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap
|
||||
} EpdGlyph;
|
||||
|
||||
/// Glyph interval structure
|
||||
typedef struct {
|
||||
uint32_t first; ///< The first unicode code point of the interval
|
||||
uint32_t last; ///< The last unicode code point of the interval
|
||||
uint32_t offset; ///< Index of the first code point into the glyph array
|
||||
} EpdUnicodeInterval;
|
||||
|
||||
/// Data stored for FONT AS A WHOLE
|
||||
typedef struct {
|
||||
const uint8_t* bitmap; ///< Glyph bitmaps, concatenated
|
||||
const EpdGlyph* glyph; ///< Glyph array
|
||||
const EpdUnicodeInterval* intervals; ///< Valid unicode intervals for this font
|
||||
uint32_t intervalCount; ///< Number of unicode intervals.
|
||||
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;
|
||||
@@ -1,37 +0,0 @@
|
||||
#include "EpdFontFamily.h"
|
||||
|
||||
const EpdFont* EpdFontFamily::getFont(const Style style) const {
|
||||
if (style == BOLD && bold) {
|
||||
return bold;
|
||||
}
|
||||
if (style == ITALIC && italic) {
|
||||
return italic;
|
||||
}
|
||||
if (style == BOLD_ITALIC) {
|
||||
if (boldItalic) {
|
||||
return boldItalic;
|
||||
}
|
||||
if (bold) {
|
||||
return bold;
|
||||
}
|
||||
if (italic) {
|
||||
return italic;
|
||||
}
|
||||
}
|
||||
|
||||
return regular;
|
||||
}
|
||||
|
||||
void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const Style style) const {
|
||||
getFont(style)->getTextDimensions(string, w, h);
|
||||
}
|
||||
|
||||
bool EpdFontFamily::hasPrintableChars(const char* string, const Style style) const {
|
||||
return getFont(style)->hasPrintableChars(string);
|
||||
}
|
||||
|
||||
const EpdFontData* EpdFontFamily::getData(const Style style) const { return getFont(style)->data; }
|
||||
|
||||
const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const Style style) const {
|
||||
return getFont(style)->getGlyph(cp);
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
#include "EpdFont.h"
|
||||
|
||||
class EpdFontFamily {
|
||||
public:
|
||||
enum Style : uint8_t { REGULAR = 0, BOLD = 1, ITALIC = 2, BOLD_ITALIC = 3 };
|
||||
|
||||
explicit EpdFontFamily(const EpdFont* regular, const EpdFont* bold = nullptr, const EpdFont* italic = nullptr,
|
||||
const EpdFont* boldItalic = nullptr)
|
||||
: regular(regular), bold(bold), italic(italic), boldItalic(boldItalic) {}
|
||||
~EpdFontFamily() = default;
|
||||
void getTextDimensions(const char* string, int* w, int* h, Style style = REGULAR) const;
|
||||
bool hasPrintableChars(const char* string, Style style = REGULAR) const;
|
||||
const EpdFontData* getData(Style style = REGULAR) const;
|
||||
const EpdGlyph* getGlyph(uint32_t cp, Style style = REGULAR) const;
|
||||
|
||||
private:
|
||||
const EpdFont* regular;
|
||||
const EpdFont* bold;
|
||||
const EpdFont* italic;
|
||||
const EpdFont* boldItalic;
|
||||
|
||||
const EpdFont* getFont(Style style) const;
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <builtinFonts/bookerly_12_bold.h>
|
||||
#include <builtinFonts/bookerly_12_bolditalic.h>
|
||||
#include <builtinFonts/bookerly_12_italic.h>
|
||||
#include <builtinFonts/bookerly_12_regular.h>
|
||||
#include <builtinFonts/bookerly_14_bold.h>
|
||||
#include <builtinFonts/bookerly_14_bolditalic.h>
|
||||
#include <builtinFonts/bookerly_14_italic.h>
|
||||
#include <builtinFonts/bookerly_14_regular.h>
|
||||
#include <builtinFonts/bookerly_16_bold.h>
|
||||
#include <builtinFonts/bookerly_16_bolditalic.h>
|
||||
#include <builtinFonts/bookerly_16_italic.h>
|
||||
#include <builtinFonts/bookerly_16_regular.h>
|
||||
#include <builtinFonts/bookerly_18_bold.h>
|
||||
#include <builtinFonts/bookerly_18_bolditalic.h>
|
||||
#include <builtinFonts/bookerly_18_italic.h>
|
||||
#include <builtinFonts/bookerly_18_regular.h>
|
||||
#include <builtinFonts/notosans_8_regular.h>
|
||||
#include <builtinFonts/notosans_12_bold.h>
|
||||
#include <builtinFonts/notosans_12_bolditalic.h>
|
||||
#include <builtinFonts/notosans_12_italic.h>
|
||||
#include <builtinFonts/notosans_12_regular.h>
|
||||
#include <builtinFonts/notosans_14_bold.h>
|
||||
#include <builtinFonts/notosans_14_bolditalic.h>
|
||||
#include <builtinFonts/notosans_14_italic.h>
|
||||
#include <builtinFonts/notosans_14_regular.h>
|
||||
#include <builtinFonts/notosans_16_bold.h>
|
||||
#include <builtinFonts/notosans_16_bolditalic.h>
|
||||
#include <builtinFonts/notosans_16_italic.h>
|
||||
#include <builtinFonts/notosans_16_regular.h>
|
||||
#include <builtinFonts/notosans_18_bold.h>
|
||||
#include <builtinFonts/notosans_18_bolditalic.h>
|
||||
#include <builtinFonts/notosans_18_italic.h>
|
||||
#include <builtinFonts/notosans_18_regular.h>
|
||||
#include <builtinFonts/opendyslexic_10_bold.h>
|
||||
#include <builtinFonts/opendyslexic_10_bolditalic.h>
|
||||
#include <builtinFonts/opendyslexic_10_italic.h>
|
||||
#include <builtinFonts/opendyslexic_10_regular.h>
|
||||
#include <builtinFonts/opendyslexic_12_bold.h>
|
||||
#include <builtinFonts/opendyslexic_12_bolditalic.h>
|
||||
#include <builtinFonts/opendyslexic_12_italic.h>
|
||||
#include <builtinFonts/opendyslexic_12_regular.h>
|
||||
#include <builtinFonts/opendyslexic_14_bold.h>
|
||||
#include <builtinFonts/opendyslexic_14_bolditalic.h>
|
||||
#include <builtinFonts/opendyslexic_14_italic.h>
|
||||
#include <builtinFonts/opendyslexic_14_regular.h>
|
||||
#include <builtinFonts/opendyslexic_8_bold.h>
|
||||
#include <builtinFonts/opendyslexic_8_bolditalic.h>
|
||||
#include <builtinFonts/opendyslexic_8_italic.h>
|
||||
#include <builtinFonts/opendyslexic_8_regular.h>
|
||||
#include <builtinFonts/ubuntu_10_bold.h>
|
||||
#include <builtinFonts/ubuntu_10_regular.h>
|
||||
#include <builtinFonts/ubuntu_12_bold.h>
|
||||
#include <builtinFonts/ubuntu_12_regular.h>
|
||||
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
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
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
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
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
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
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
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
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
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
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
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
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
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user