OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
163
dev/DWGViewer/js/mtext.js
Normal file
163
dev/DWGViewer/js/mtext.js
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* 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, '') // 下划线/上划线开关,画不出来就去掉标记
|
||||
}
|
||||
Reference in New Issue
Block a user