| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| const CP1252_HIGH = { |
| 0x20ac: 0x80, 0x201a: 0x82, 0x0192: 0x83, 0x201e: 0x84, 0x2026: 0x85, |
| 0x2020: 0x86, 0x2021: 0x87, 0x02c6: 0x88, 0x2030: 0x89, 0x0160: 0x8a, |
| 0x2039: 0x8b, 0x0152: 0x8c, 0x017d: 0x8e, 0x2018: 0x91, 0x2019: 0x92, |
| 0x201c: 0x93, 0x201d: 0x94, 0x2022: 0x95, 0x2013: 0x96, 0x2014: 0x97, |
| 0x02dc: 0x98, 0x2122: 0x99, 0x0161: 0x9a, 0x203a: 0x9b, 0x0153: 0x9c, |
| 0x017e: 0x9e, 0x0178: 0x9f, |
| }; |
|
|
| |
| const byteOf = (ch) => { |
| const cp = ch.codePointAt(0); |
| return cp < 0x100 ? cp : CP1252_HIGH[cp]; |
| }; |
|
|
| const seqLen = (b) => |
| b >= 0xc2 && b <= 0xdf ? 2 : b >= 0xe0 && b <= 0xef ? 3 : b >= 0xf0 && b <= 0xf4 ? 4 : 0; |
| const isCont = (b) => b >= 0x80 && b <= 0xbf; |
|
|
| const utf8 = new TextDecoder("utf-8", { fatal: true }); |
|
|
| |
| |
| function unmojibake(s, atEnd, keepResidue) { |
| let out = ""; |
| let i = 0; |
| while (i < s.length) { |
| const ch = s[i]; |
| const b = byteOf(ch); |
| if (b === undefined || b < 0x80) { |
| out += ch; |
| i++; |
| continue; |
| } |
| const need = seqLen(b); |
| const bytes = [b]; |
| let j = i + 1; |
| while (j < s.length && bytes.length < need) { |
| const cb = byteOf(s[j]); |
| if (cb === undefined || !isCont(cb)) break; |
| bytes.push(cb); |
| j++; |
| } |
| if (need && bytes.length === need) { |
| try { |
| const decoded = utf8.decode(new Uint8Array(bytes)); |
| if (decoded.codePointAt(0) > 0x9f) { |
| |
| out += decoded; |
| i = j; |
| continue; |
| } |
| } catch { |
| |
| } |
| } else if (need && j === s.length && !atEnd) { |
| return { out, hold: s.slice(i) }; |
| } |
| if (b === 0xe2 && bytes.length >= 2 && bytes[1] === 0x80) { |
| out += "β"; |
| i += 2; |
| continue; |
| } |
| if (keepResidue) out += ch; |
| i++; |
| } |
| return { out, hold: "" }; |
| } |
|
|
| export function fixMojibake(s) { |
| return unmojibake(s, true, true).out; |
| } |
|
|
| export function mojibakeFilter() { |
| let buf = ""; |
| return { |
| push(text) { |
| const { out, hold } = unmojibake(buf + text, false, false); |
| buf = hold; |
| return out; |
| }, |
| flush() { |
| const { out } = unmojibake(buf, true, false); |
| buf = ""; |
| return out; |
| }, |
| }; |
| } |
|
|