/** * MTEXT / TEXT 的文字内容解析。 * * MTEXT 的正文里混着 AutoCAD 的格式码(\P 换行、\H 字高、\S 堆叠、{} 分组……), * 直接画出来会满屏乱码,所以这里把它拆成「行 → 文字段(run)」的结构, * 每个 run 带自己的字高倍数、宽度因子、倾斜、粗斜体、颜色和下划线。 */ /** %%d 之类的控制码(TEXT 与 MTEXT 通用) */ function replaceSpecial(s) { return s .replace(/%%[dD]/g, '°') // 度 .replace(/%%[cC]/g, '∅') // 直径 ⌀ .replace(/%%[pP]/g, '±') // 正负 .replace(/%%%/g, '%') } /** \U+00B0 形式的 Unicode 转义 */ function replaceUnicode(s) { return s.replace(/\\U\+([0-9A-Fa-f]{4})/g, (_, h) => String.fromCharCode(parseInt(h, 16))) } const BASE_STYLE = { hFactor: 1, // 相对于实体 textHeight 的倍数 wFactor: 1, oblique: 0, bold: false, italic: false, font: null, color: null, // ACI 索引或 0xRRGGBB(带 trueColor 标记) underline: false, overline: false, strike: false, rise: 0, // 相对基线的抬升(堆叠分数用),单位为字高倍数 } /** * 解析 MTEXT 正文。 * @returns {{lines: Array>, columnBreaks: number[]}} */ export function parseMText(raw) { const lines = [[]] if (!raw) return { lines } const text = replaceUnicode(raw) const stack = [] let st = { ...BASE_STYLE } let buf = '' const flush = () => { if (!buf) return lines[lines.length - 1].push({ ...st, text: replaceSpecial(buf) }) buf = '' } const newLine = () => { flush(); lines.push([]) } // 读到分号为止的参数 let i = 0 const readArg = () => { let out = '' while (i < text.length && text[i] !== ';') { out += text[i]; i++ } i++ // 吃掉 ; return out } while (i < text.length) { const ch = text[i] if (ch === '\\') { const c = text[i + 1] i += 2 switch (c) { case 'P': newLine(); break case 'X': newLine(); break case '~': buf += ' '; break case '\\': buf += '\\'; break case '{': buf += '{'; break case '}': buf += '}'; break case 'L': flush(); st = { ...st, underline: true }; break case 'l': flush(); st = { ...st, underline: false }; break case 'O': flush(); st = { ...st, overline: true }; break case 'o': flush(); st = { ...st, overline: false }; break case 'K': flush(); st = { ...st, strike: true }; break case 'k': flush(); st = { ...st, strike: false }; break case 'H': { flush() const a = readArg() const rel = /x$/i.test(a) const v = parseFloat(a) if (isFinite(v)) st = { ...st, hFactor: rel ? st.hFactor * v : v } break } case 'W': { flush(); const v = parseFloat(readArg()); if (isFinite(v) && v > 0) st = { ...st, wFactor: v }; break } case 'Q': { flush(); const v = parseFloat(readArg()); if (isFinite(v)) st = { ...st, oblique: (v * Math.PI) / 180 }; break } case 'T': { readArg(); break } // 字间距,忽略 case 'A': { readArg(); break } // 对齐,交由整体对齐处理 case 'p': { readArg(); break } // 段落属性,忽略 case 'C': { flush(); const v = parseInt(readArg(), 10); if (isFinite(v)) st = { ...st, color: { aci: v } }; break } case 'c': { flush(); const v = parseInt(readArg(), 10); if (isFinite(v)) st = { ...st, color: { rgb: bgrToRgb(v) } }; break } case 'F': case 'f': { flush() const a = readArg() const parts = a.split('|') const next = { ...st, font: parts[0] || null } for (const p of parts.slice(1)) { if (/^b1$/i.test(p)) next.bold = true else if (/^b0$/i.test(p)) next.bold = false else if (/^i1$/i.test(p)) next.italic = true else if (/^i0$/i.test(p)) next.italic = false } st = next break } case 'S': { // 堆叠分数: 上^下; 上/下; 上#下; flush() let a = '' while (i < text.length && text[i] !== ';') { a += text[i]; i++ } i++ const m = a.match(/^(.*?)([\^\/#])(.*)$/) if (m) { const upper = replaceSpecial(m[1]), lower = replaceSpecial(m[3]) const small = { ...st, hFactor: st.hFactor * 0.65 } const arr = lines[lines.length - 1] if (upper) arr.push({ ...small, text: upper, rise: 0.55 }) if (m[2] !== '^' && upper && lower) arr.push({ ...small, text: '/', rise: 0 }) if (lower) arr.push({ ...small, text: lower, rise: -0.25 }) } else if (a) { buf += replaceSpecial(a) } break } default: // 未知转义,原样保留字符 if (c != null) buf += c } } else if (ch === '{') { flush(); stack.push({ ...st }); i++ } else if (ch === '}') { flush(); if (stack.length) st = stack.pop(); i++ } else if (ch === '\n') { newLine(); i++ } else if (ch === '\r') { i++ } else { buf += ch; i++ } } flush() return { lines } } /** DXF 的 true color 在 MTEXT 里是 BGR 顺序 */ function bgrToRgb(v) { const b = (v >> 16) & 0xff, g = (v >> 8) & 0xff, r = v & 0xff return (r << 16) | (g << 8) | b } /** 单行 TEXT / ATTRIB 的内容处理 */ export function parseSimpleText(raw) { if (!raw) return '' return replaceSpecial(replaceUnicode(String(raw))) .replace(/%%[uUoO]/g, '') // 下划线/上划线开关,画不出来就去掉标记 }