107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
/**
|
|
* Repair Korean text that acadrust emits as Latin-1 mojibake of CP949/EUC-KR.
|
|
*
|
|
* Older Korean DWGs store strings in code page 949. When those bytes are
|
|
* decoded as ISO-8859-1/Windows-1252, Hangul becomes garbled Latin
|
|
* (e.g. "½Ã°ø½Ã" instead of "시공시"). Unicode-correct drawings
|
|
* (already containing Hangul syllables) are left unchanged.
|
|
*
|
|
* Apply after parse_dwg, before Viewer2D.load().
|
|
*/
|
|
|
|
const HANGUL_SYLLABLE = /[\uAC00-\uD7A3]/;
|
|
const HANGUL_JAMO = /[\u1100-\u11FF\u3130-\u318F]/;
|
|
|
|
/** Labels tried in order; first supported decoder wins. */
|
|
const KOREAN_LABELS = ['cp949', 'windows-949', 'euc-kr'] as const;
|
|
|
|
let koreanDecoder: TextDecoder | null | undefined;
|
|
|
|
function getKoreanDecoder(): TextDecoder | null {
|
|
if (koreanDecoder !== undefined) return koreanDecoder;
|
|
for (const label of KOREAN_LABELS) {
|
|
try {
|
|
koreanDecoder = new TextDecoder(label);
|
|
return koreanDecoder;
|
|
} catch {
|
|
/* label not available in this runtime */
|
|
}
|
|
}
|
|
koreanDecoder = null;
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* If `s` looks like CP949 bytes misread as Latin-1, return the Hangul
|
|
* form; otherwise return `s` unchanged.
|
|
*/
|
|
export function fixCp949Mojibake(s: string): string {
|
|
if (!s) return s;
|
|
// Already real Hangul → leave alone (e.g. BasicSample.dwg).
|
|
if (HANGUL_SYLLABLE.test(s) || HANGUL_JAMO.test(s)) return s;
|
|
|
|
let hasHigh = false;
|
|
const bytes = new Uint8Array(s.length);
|
|
for (let i = 0; i < s.length; i++) {
|
|
const code = s.charCodeAt(i);
|
|
if (code > 255) return s; // true Unicode beyond Latin-1
|
|
if (code > 127) hasHigh = true;
|
|
bytes[i] = code;
|
|
}
|
|
if (!hasHigh) return s; // pure ASCII
|
|
|
|
const decoder = getKoreanDecoder();
|
|
if (!decoder) return s;
|
|
|
|
let fixed: string;
|
|
try {
|
|
fixed = decoder.decode(bytes);
|
|
} catch {
|
|
return s;
|
|
}
|
|
|
|
// Accept only when we gain Hangul and the decode is clean.
|
|
if (
|
|
(HANGUL_SYLLABLE.test(fixed) || HANGUL_JAMO.test(fixed)) &&
|
|
!fixed.includes('\uFFFD')
|
|
) {
|
|
return fixed;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* Deep-walk a CadParseResult (or any JSON-like tree) and fix string fields
|
|
* in place. Returns the same object for chaining.
|
|
*/
|
|
export function fixDwgKoreanText<T>(root: T): T {
|
|
if (root == null || typeof root !== 'object') return root;
|
|
const seen = new WeakSet<object>();
|
|
|
|
const walk = (value: unknown): void => {
|
|
if (value == null || typeof value !== 'object') return;
|
|
if (seen.has(value as object)) return;
|
|
seen.add(value as object);
|
|
|
|
if (Array.isArray(value)) {
|
|
for (let i = 0; i < value.length; i++) {
|
|
const item = value[i];
|
|
if (typeof item === 'string') value[i] = fixCp949Mojibake(item);
|
|
else walk(item);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const obj = value as Record<string, unknown>;
|
|
for (const key of Object.keys(obj)) {
|
|
if (key === 'imageDataUrl') continue;
|
|
const item = obj[key];
|
|
if (typeof item === 'string') obj[key] = fixCp949Mojibake(item);
|
|
else walk(item);
|
|
}
|
|
};
|
|
|
|
walk(root);
|
|
return root;
|
|
}
|