File size: 3,517 Bytes
1cd0943
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/*
 * 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;
})();