dosllm / big5.js
HelloSun's picture
Add 小貓中文: Chinese questions and answers on the DOS screen (Big5, Zhuyin); v86 patch of XiaoMao; updated dev package
1cd0943 verified
Raw History Blame Contribute Delete
3.52 kB
/*
* Big5 <-> Unicode helpers (the encoding XiaoMao Chinese shows on the DOS screen).
* Decoding uses the browser's built-in TextDecoder('big5'); encoding is a reverse table built from it
* (lead A1..F9, trail 40..7E / A1..FE: the range XiaoMao's font covers). Characters outside Big5
* (Simplified Chinese, emoji, ...) cannot be shown by DOS and become '?'.
*/
(function () {
'use strict';
let dec = null;
try { dec = new TextDecoder('big5'); } catch (e) { dec = null; }
const toCode = new Map();
if (dec) {
const pair = new Uint8Array(2);
for (let lead = 0xA1; lead <= 0xF9; lead++) {
for (let trail = 0x40; trail <= 0xFE; trail++) {
if (trail > 0x7E && trail < 0xA1) continue;
pair[0] = lead; pair[1] = trail;
const s = dec.decode(pair);
if (s.length === 1 && s !== '\uFFFD' && !toCode.has(s)) toCode.set(s, (lead << 8) | trail); // lowest code wins
}
}
}
const isLead = (b) => b >= 0xA1 && b <= 0xF9;
const isTrail = (b) => (b >= 0x40 && b <= 0x7E) || (b >= 0xA1 && b <= 0xFE);
const Big5 = {
available: !!dec && toCode.size > 10000,
size: toCode.size,
hasHighBytes: (bytes) => bytes.some((b) => b >= 0x80),
// Big5 bytes -> JS string (NUL shown as space)
decode(bytes) { return dec ? dec.decode(Uint8Array.from(bytes, (b) => (b === 0 ? 0x20 : b))) : String.fromCharCode(...bytes); },
// JS string -> { bytes, lost }: ASCII as is, Big5 characters as two bytes, anything else '?'
encode(str) {
const out = []; const lost = [];
for (const ch of str) {
const cp = ch.codePointAt(0);
if (cp < 0x80) out.push(cp);
else if (toCode.has(ch)) { const c = toCode.get(ch); out.push(c >> 8, c & 255); }
else { out.push(0x3F); lost.push(ch); }
}
return { bytes: Uint8Array.from(out), lost };
},
// what the DOS screen can actually show for this text (lost characters -> '?')
displayable(str) { return Array.from(str, (ch) => (ch.codePointAt(0) < 0x80 || toCode.has(ch) ? ch : '?')).join(''); },
// JSON text from Gradio is ASCII with \uXXXX escapes: turn every non-ASCII escape (or raw non-ASCII
// character) into Big5 bytes (or '?'), keep every other escape as it is, return bytes.
jsonToBig5Bytes(text) {
const out = [];
const put = (cp) => {
const ch = String.fromCodePoint(cp);
if (cp < 0x80) out.push(cp);
else if (toCode.has(ch)) { const c = toCode.get(ch); out.push(c >> 8, c & 255); }
else out.push(0x3F);
};
for (let i = 0; i < text.length;) {
const ch = text[i];
if (ch === '\\' && i + 1 < text.length) {
if (text[i + 1] === 'u' && /^[0-9a-fA-F]{4}$/.test(text.substr(i + 2, 4))) {
let cp = parseInt(text.substr(i + 2, 4), 16); i += 6;
if (cp >= 0xD800 && cp <= 0xDBFF && text.substr(i, 2) === '\\u' && /^[dD][c-fC-F][0-9a-fA-F]{2}$/.test(text.substr(i + 2, 4))) {
cp = 0x10000 + ((cp - 0xD800) << 10) + (parseInt(text.substr(i + 2, 4), 16) - 0xDC00); i += 6;
}
if (cp < 0x80) { out.push(0x5C, 0x75, ...Array.from(cp.toString(16).padStart(4, '0'), (c) => c.charCodeAt(0))); } // keep \u00XX escapes of ASCII
else put(cp);
} else { out.push(0x5C, text.charCodeAt(i + 1) & 0x7F); i += 2; }
} else { const cp = text.codePointAt(i); i += cp > 0xFFFF ? 2 : 1; put(cp); }
}
return Uint8Array.from(out);
},
};
window.Big5 = Big5;
})();