Skip to content

Commit 92bd238

Browse files
committed
Fix XTG/XTH page header structure causing broken device rendering
- Remove invalid version field at offset 0x04 that shifted width/height - Add missing colorMode, compression, dataSize fields per spec - Fix container header flags as individual bytes (not uint32)
1 parent e3e6826 commit 92bd238

File tree

3 files changed

+55
-28
lines changed

3 files changed

+55
-28
lines changed

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,19 @@ Example `settings.json`:
9898

9999
## XTC/XTCH Format
100100

101-
- **XTC**: 1-bit monochrome pages (fast rendering, smaller files)
102-
- **XTCH**: 2-bit grayscale pages (4 levels, better image quality)
101+
Native binary ebook format for Xteink e-readers. Stores pre-rendered bitmap pages optimized for the device's e-paper display.
102+
103+
| Extension | Container | Page Format | Bit Depth | Description |
104+
|-----------|-----------|-------------|-----------|-------------|
105+
| `.xtc` | XTC | XTG | 1-bit | Monochrome, fast rendering, smaller files |
106+
| `.xtch` | XTCH | XTH | 2-bit | 4-level grayscale, better image quality |
107+
108+
### Xteink X4 Specifics
109+
110+
- **Display**: 480x800 e-paper (4.3")
111+
- **XTG (1-bit)**: Row-major scan, 8 pixels per byte, MSB = leftmost pixel
112+
- **XTH (2-bit)**: Vertical scan order (columns right-to-left), optimized for e-paper refresh
113+
- **Grayscale LUT**: Non-linear mapping (0=white, 1=dark gray, 2=light gray, 3=black)
103114

104115
Both formats include:
105116
- Document metadata (title, author)

cli/encoder.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,17 @@ function encodeXTG(data, width, height) {
2323
header[2] = 0x47; // G
2424
header[3] = 0x00;
2525

26-
// Version
27-
view.setUint16(4, 1, true);
28-
29-
// Dimensions
30-
view.setUint16(6, width, true);
31-
view.setUint16(8, height, true);
26+
// Dimensions (per XTG spec - no version field!)
27+
view.setUint16(4, width, true); // offset 0x04
28+
view.setUint16(6, height, true); // offset 0x06
29+
header[8] = 0; // colorMode = 0 (monochrome)
30+
header[9] = 0; // compression = 0 (uncompressed)
3231

3332
// Bitmap: 8 pixels per byte, MSB = leftmost
3433
const rowBytes = Math.ceil(width / 8);
34+
const dataSize = rowBytes * height;
35+
view.setUint32(10, dataSize, true); // offset 0x0A (dataSize)
36+
// md5 at 0x0E left as zeros (optional)
3537
const bitmap = new Uint8Array(rowBytes * height);
3638

3739
for (let y = 0; y < height; y++) {
@@ -76,15 +78,17 @@ function encodeXTH(data, width, height) {
7678
header[2] = 0x48; // H
7779
header[3] = 0x00;
7880

79-
// Version
80-
view.setUint16(4, 1, true);
81-
82-
// Dimensions
83-
view.setUint16(6, width, true);
84-
view.setUint16(8, height, true);
81+
// Dimensions (per XTH spec - no version field!)
82+
view.setUint16(4, width, true); // offset 0x04
83+
view.setUint16(6, height, true); // offset 0x06
84+
header[8] = 0; // colorMode = 0
85+
header[9] = 0; // compression = 0
8586

8687
// Two bit planes, vertical scan, columns right-to-left
8788
const colBytes = Math.ceil(height / 8);
89+
const dataSize = colBytes * width * 2; // Two bit planes
90+
view.setUint32(10, dataSize, true); // offset 0x0A (dataSize)
91+
// md5 at 0x0E left as zeros (optional)
8892
const plane0 = new Uint8Array(colBytes * width); // bit 0
8993
const plane1 = new Uint8Array(colBytes * width); // bit 1
9094

@@ -171,7 +175,11 @@ function buildXTCContainer(pages, metadata, toc, width, height, isHQ) {
171175
}
172176
view.setUint16(4, 1, true); // Version
173177
view.setUint16(6, pages.length, true); // Page count
174-
view.setUint32(8, 0, true); // Flags
178+
// Individual flag bytes per XTC spec
179+
bytes[8] = 0; // readDirection (0 = L→R)
180+
bytes[9] = 1; // hasMetadata
181+
bytes[10] = 0; // hasThumbnails
182+
bytes[11] = toc.length > 0 ? 1 : 0; // hasChapters
175183
view.setUint32(12, 1, true); // Current page (1-indexed)
176184

177185
// Use BigInt for 64-bit values

web/app.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,15 +1497,17 @@ function encodeXTG(imageData) {
14971497
header[2] = 0x47; // G
14981498
header[3] = 0x00;
14991499

1500-
// Version
1501-
view.setUint16(4, 1, true);
1502-
1503-
// Dimensions
1504-
view.setUint16(6, width, true);
1505-
view.setUint16(8, height, true);
1500+
// Dimensions (per XTG spec - no version field!)
1501+
view.setUint16(4, width, true); // offset 0x04
1502+
view.setUint16(6, height, true); // offset 0x06
1503+
header[8] = 0; // colorMode = 0 (monochrome)
1504+
header[9] = 0; // compression = 0 (uncompressed)
15061505

15071506
// Bitmap: 8 pixels per byte, MSB = leftmost
15081507
var rowBytes = Math.ceil(width / 8);
1508+
var dataSize = rowBytes * height;
1509+
view.setUint32(10, dataSize, true); // offset 0x0A (dataSize)
1510+
// md5 at 0x0E left as zeros (optional)
15091511
var bitmap = new Uint8Array(rowBytes * height);
15101512

15111513
for (var y = 0; y < height; y++) {
@@ -1546,15 +1548,17 @@ function encodeXTH(imageData) {
15461548
header[2] = 0x48; // H
15471549
header[3] = 0x00;
15481550

1549-
// Version
1550-
view.setUint16(4, 1, true);
1551-
1552-
// Dimensions
1553-
view.setUint16(6, width, true);
1554-
view.setUint16(8, height, true);
1551+
// Dimensions (per XTH spec - no version field!)
1552+
view.setUint16(4, width, true); // offset 0x04
1553+
view.setUint16(6, height, true); // offset 0x06
1554+
header[8] = 0; // colorMode = 0
1555+
header[9] = 0; // compression = 0
15551556

15561557
// Two bit planes, vertical scan, columns right-to-left
15571558
var colBytes = Math.ceil(height / 8);
1559+
var dataSize = colBytes * width * 2; // Two bit planes
1560+
view.setUint32(10, dataSize, true); // offset 0x0A (dataSize)
1561+
// md5 at 0x0E left as zeros (optional)
15581562
var plane0 = new Uint8Array(colBytes * width); // bit 0
15591563
var plane1 = new Uint8Array(colBytes * width); // bit 1
15601564

@@ -1630,7 +1634,11 @@ function buildXTCContainer(pages, isHQ) {
16301634
}
16311635
view.setUint16(4, 1, true); // Version
16321636
view.setUint16(6, pages.length, true); // Page count
1633-
view.setUint32(8, 0, true); // Flags
1637+
// Individual flag bytes per XTC spec
1638+
bytes[8] = 0; // readDirection (0 = L→R)
1639+
bytes[9] = 1; // hasMetadata
1640+
bytes[10] = 0; // hasThumbnails
1641+
bytes[11] = currentToc.length > 0 ? 1 : 0; // hasChapters
16341642
view.setUint32(12, 1, true); // Current page (1-indexed)
16351643

16361644
// Use BigInt for 64-bit values

0 commit comments

Comments
 (0)