|
|
|
|
@ -7,7 +7,7 @@ |
|
|
|
|
const HDR_SIZE = 23; |
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// BitReader — LSB-first, matching u8g2_font_decode_get_unsigned_bits exactly:
|
|
|
|
|
// BitReader - LSB-first, matching u8g2_font_decode_get_unsigned_bits exactly:
|
|
|
|
|
// val = *ptr >> bit_pos
|
|
|
|
|
// if (bit_pos + cnt >= 8): val |= *(ptr+1) << (8 - bit_pos); ptr++
|
|
|
|
|
// val &= (1 << cnt) - 1
|
|
|
|
|
@ -67,7 +67,7 @@ function parseHeader(font: Uint8Array) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// RLE decoder — matches fd_decode / u8g2_font_decode_glyph in the C source.
|
|
|
|
|
// RLE decoder - matches fd_decode / u8g2_font_decode_glyph in the C source.
|
|
|
|
|
//
|
|
|
|
|
// The bitstream is a sequence of (zeros-run, ones-run) pairs, repeated.
|
|
|
|
|
// Each pair:
|
|
|
|
|
@ -106,7 +106,7 @@ function decodeRLE( |
|
|
|
|
// Glyph result
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
export interface U8G2GlyphData { |
|
|
|
|
cols: Uint8Array; // column bytes (bit 0 = top row), length = w
|
|
|
|
|
cols: Uint8Array | Uint32Array; // column words (bit 0 = top row), length = w
|
|
|
|
|
w: number; |
|
|
|
|
h: number; |
|
|
|
|
dx: number; // horizontal advance
|
|
|
|
|
@ -115,7 +115,7 @@ export interface U8G2GlyphData { |
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Decode the glyph bitstream at byteOffset (after the record header bytes).
|
|
|
|
|
// Field order from C source (u8g2_font.c lines 590–625):
|
|
|
|
|
// Field order from C source (u8g2_font.c lines 590-625):
|
|
|
|
|
// glyph_width, glyph_height, x (signed), y (signed), delta_x (signed)
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
function decodeGlyphAt( |
|
|
|
|
@ -127,20 +127,21 @@ function decodeGlyphAt( |
|
|
|
|
|
|
|
|
|
const w = br.read(hdr.bitsPerW); |
|
|
|
|
const h = br.read(hdr.bitsPerH); |
|
|
|
|
/* x */ br.readSigned(hdr.bitsPerX); // x bearing — consumed but not needed
|
|
|
|
|
/* x */ br.readSigned(hdr.bitsPerX); // x bearing - consumed but not needed
|
|
|
|
|
const yB = br.readSigned(hdr.bitsPerY); |
|
|
|
|
const dx = br.readSigned(hdr.bitsPerDx); |
|
|
|
|
|
|
|
|
|
if (w === 0 || h === 0) { |
|
|
|
|
// Blank glyph (e.g. space) — advance is stored in dx
|
|
|
|
|
// Blank glyph (e.g. space) - advance is stored in dx
|
|
|
|
|
return { cols: new Uint8Array(1), w: 1, h: 0, dx: Math.max(dx, 1), yOff: 0 }; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Decode row-major horizontal bitmap
|
|
|
|
|
const bits = decodeRLE(br, w, h, hdr.m0, hdr.m1); |
|
|
|
|
|
|
|
|
|
// Convert row-major → column bytes (bit 0 = top row) for rasterizeText
|
|
|
|
|
const cols = new Uint8Array(w); |
|
|
|
|
// Convert row-major → column words (bit 0 = top row) for rasterizeText
|
|
|
|
|
// Must be Uint32Array - glyphs can be up to 32px tall; Uint8Array truncates rows 8+
|
|
|
|
|
const cols = new Uint32Array(w); |
|
|
|
|
for (let row = 0; row < h; row++) { |
|
|
|
|
for (let col = 0; col < w; col++) { |
|
|
|
|
if (bits[row * w + col]) cols[col] |= (1 << row); |
|
|
|
|
@ -161,12 +162,12 @@ function decodeGlyphAt( |
|
|
|
|
// Record layout (cp < 0x0100):
|
|
|
|
|
// [1 byte] encoding
|
|
|
|
|
// [1 byte] jump = byte distance from START of this record to next record
|
|
|
|
|
// [bitstream …]
|
|
|
|
|
// [bitstream ...]
|
|
|
|
|
//
|
|
|
|
|
// Record layout (cp >= 0x0100):
|
|
|
|
|
// [2 bytes BE] encoding
|
|
|
|
|
// [1 byte] jump (same semantics)
|
|
|
|
|
// [bitstream …]
|
|
|
|
|
// [bitstream ...]
|
|
|
|
|
//
|
|
|
|
|
// Unicode section is preceded by a lookup table (added in v2.23):
|
|
|
|
|
// pairs of [uint16 BE delta | uint16 BE last-encoding], terminated by last==0xFFFF
|
|
|
|
|
@ -178,7 +179,7 @@ export function u8g2Glyph(cp: number, font: Uint8Array): U8G2GlyphData | null { |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
if (cp >= 0x0100) { |
|
|
|
|
// ── Unicode block ────────────────────────────────────────────────────
|
|
|
|
|
// -- Unicode block ----------------------------------------------------
|
|
|
|
|
const tableBase = hdr.startUnicode; |
|
|
|
|
if (tableBase + 4 > font.length) return null; |
|
|
|
|
|
|
|
|
|
@ -214,7 +215,7 @@ export function u8g2Glyph(cp: number, font: Uint8Array): U8G2GlyphData | null { |
|
|
|
|
return null; |
|
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
// ── ASCII block (cp < 0x0100) ────────────────────────────────────────
|
|
|
|
|
// -- ASCII block (cp < 0x0100) ----------------------------------------
|
|
|
|
|
// Jump-start from the nearest block offset to avoid scanning from byte 0.
|
|
|
|
|
let recPos: number; |
|
|
|
|
if (cp >= 0x61) recPos = hdr.startLower; |
|
|
|
|
@ -265,7 +266,7 @@ export function rasterizeText( |
|
|
|
|
const pixels = new Uint8Array(totalW * cellHeight); |
|
|
|
|
|
|
|
|
|
// Centre the full font (ascent + |descent|) vertically in the cell.
|
|
|
|
|
// header byte 14 is descent of 'g' — stored as a negative signed value.
|
|
|
|
|
// header byte 14 is descent of 'g' - stored as a negative signed value.
|
|
|
|
|
const descent = font.FONT_DATA[14]! > 127 ? font.FONT_DATA[14]! - 256 : font.FONT_DATA[14]!; |
|
|
|
|
const fontH = hdr.ascentA - descent; // total font height in pixels
|
|
|
|
|
const topPad = Math.floor((cellHeight - fontH) / 2); |
|
|
|
|
@ -290,4 +291,4 @@ export function rasterizeText( |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return { pixels, width: totalW, height: cellHeight }; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|