Files
dwg-dxf-viewer-sample/src/viewer2d/fixDwgKoreanText.ts
T

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;
}