Files
Leantime/dev/DWGViewer/js/mtext.js

164 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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<Array<Run>>, 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, '') // 下划线/上划线开关,画不出来就去掉标记
}