TP5 is a 2012 Windows application supplied with the Guangzhou-Tongda LED
destination signs fitted to Yutong buses. It has no preview, so building a
destination list is guess-and-check, and it only runs on Windows.
The bus never talks to TP5 — the sign controller reads a .td5 file off an SD
card, and that is the whole interface. So this replaces the software without
touching any hardware or protocol: it just has to write byte-correct .td5.
Formats reverse-engineered from the sample files and TP5(En).exe, then verified
byte-for-byte:
.td5 the file the bus reads. Fixed-layout binary; each destination block
carries a CRC-16/ARC over block[3..len] and a rand() block id, which
together looked like one 4-byte field because RAND_MAX is 0x7fff.
.tp5 the editable project. Line-based text, UTF-16BE hex strings.
.font the sign's own bitmap fonts, each glyph row XORed with its char code.
The app is one self-contained HTML file: live LED preview at the real sign size
with real scrolling, spreadsheet/CSV import, multi-page destinations, undoable
delete, and export to both .td5 and .tp5.
Verified:
- rebuilds a real 46,080-byte TP5 export byte-for-byte with a recomputed CRC
- all 36 stored CRCs verify against the implementation
- driven through its own UI, re-exporting the real file differs in 7 bytes,
all of them the export timestamp
- running on a real bus: signs and driver's controller both correct
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
29 lines
1.3 KiB
JavaScript
29 lines
1.3 KiB
JavaScript
/** Convert the shipped .font files into a JS module the app can embed. */
|
|
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
import { parseFont, fontToJSON } from '../src/codec/bitfont.mjs';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const srcDir = join(here, '../../yutongapp');
|
|
const outFile = join(here, '../src/fonts.js');
|
|
|
|
const fonts = {};
|
|
for (const f of readdirSync(srcDir).filter((f) => f.endsWith('.font')).sort()) {
|
|
const parsed = parseFont(readFileSync(join(srcDir, f), 'latin1'));
|
|
fonts[parsed.name] = fontToJSON(parsed);
|
|
const codes = [...parsed.glyphs.keys()];
|
|
console.log(
|
|
`${parsed.name.padEnd(9)} cell ${String(parsed.cellWidth).padStart(2)}x${parsed.height}` +
|
|
` ${String(parsed.glyphs.size).padStart(3)} glyphs codes ${Math.min(...codes)}..${Math.max(...codes)}`
|
|
);
|
|
}
|
|
|
|
writeFileSync(outFile,
|
|
'// Generated by tools/build-fonts.mjs from the .font files shipped with TP5.\n' +
|
|
'// Each glyph is [advance, ...rows]; rows are MSB-first, cellWidth bits wide.\n' +
|
|
'export const FONTS = ' + JSON.stringify(fonts) + ';\n');
|
|
|
|
const kb = (readFileSync(outFile).length / 1024).toFixed(0);
|
|
console.log(`\nwrote ${outFile} (${kb} KB, ${Object.keys(fonts).length} fonts)`);
|