feat: implement OLE2Frame embedded image parsing and exact coordinate rendering
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user