/** * 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(root: T): T { if (root == null || typeof root !== 'object') return root; const seen = new WeakSet(); 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; 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; }