/* * 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; })();