OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)

This commit is contained in:
wangruiguo
2026-09-03 18:49:20 +08:00
commit d647428529
3501 changed files with 1988906 additions and 0 deletions

163
dev/DWGViewer/js/mtext.js Normal file
View 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, '') // 下划线/上划线开关,画不出来就去掉标记
}