@chenyomi/leafer-htmltext-editor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/dist/TextEditTool/index.d.ts +17 -0
- package/dist/TextEditTool/index.d.ts.map +1 -0
- package/dist/TextEditTool/index.js +138 -0
- package/dist/TextEditTool/utils.d.ts +8 -0
- package/dist/TextEditTool/utils.d.ts.map +1 -0
- package/dist/TextEditTool/utils.js +173 -0
- package/dist/TextEditor.d.ts +27 -0
- package/dist/TextEditor.d.ts.map +1 -0
- package/dist/TextEditor.js +166 -0
- package/dist/esm/TextEditTool/index.d.ts +17 -0
- package/dist/esm/TextEditTool/index.d.ts.map +1 -0
- package/dist/esm/TextEditTool/index.js +135 -0
- package/dist/esm/TextEditTool/utils.d.ts +8 -0
- package/dist/esm/TextEditTool/utils.d.ts.map +1 -0
- package/dist/esm/TextEditTool/utils.js +165 -0
- package/dist/esm/TextEditor.d.ts +27 -0
- package/dist/esm/TextEditor.d.ts.map +1 -0
- package/dist/esm/TextEditor.js +163 -0
- package/dist/esm/fonts/font.d.ts +17 -0
- package/dist/esm/fonts/font.d.ts.map +1 -0
- package/dist/esm/fonts/font.js +68 -0
- package/dist/esm/fonts/utils.d.ts +9 -0
- package/dist/esm/fonts/utils.d.ts.map +1 -0
- package/dist/esm/fonts/utils.js +170 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +284 -0
- package/dist/fonts/font.d.ts +17 -0
- package/dist/fonts/font.d.ts.map +1 -0
- package/dist/fonts/font.js +72 -0
- package/dist/fonts/utils.d.ts +9 -0
- package/dist/fonts/utils.d.ts.map +1 -0
- package/dist/fonts/utils.js +180 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +79 -0
- package/dist/index.js +92 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +289 -0
- package/package.json +55 -0
- package/src/TextEditTool/index.ts +145 -0
- package/src/TextEditTool/utils.ts +216 -0
- package/src/TextEditor.ts +200 -0
- package/src/fonts/font.ts +86 -0
- package/src/fonts/utils.ts +232 -0
- package/src/index.ts +92 -0
- package/src/utils.ts +331 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { Text, Box, Path } from 'leafer-ui'
|
|
2
|
+
import { HTMLText } from '@leafer-in/html' // 导入 html 插件
|
|
3
|
+
export function getArcRadius(fontSize: number, curveAmount: number): number {
|
|
4
|
+
if (curveAmount === 0) return Infinity
|
|
5
|
+
const theta = (330 * fontSize * 72) / 96
|
|
6
|
+
const radius = theta / Math.abs(curveAmount)
|
|
7
|
+
// 根据正负仅决定方向,不影响半径大小
|
|
8
|
+
return radius
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const handleShowCurve = (element: any, op: boolean) => {
|
|
12
|
+
const box = element.findOne('Box')
|
|
13
|
+
box && element.remove(box)
|
|
14
|
+
const text = element.findOne('HTMLText') as any
|
|
15
|
+
// 下一帧再生成新路径
|
|
16
|
+
const { boxBounds, x, y } = text
|
|
17
|
+
const { width } = boxBounds
|
|
18
|
+
if (!text.curveAmount) {
|
|
19
|
+
// 当移动到0的时候显示原来的文字
|
|
20
|
+
text.opacity = 1
|
|
21
|
+
// 这个为了解决其他地方有隐藏了这个的情况下
|
|
22
|
+
text.visible = true
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
text.text = text.text.replace(/\u200B/g, '').replace(/<br\s*\/?>/gi, '\n') // 把 <br> 转换成换行符
|
|
26
|
+
text.opacity = 0
|
|
27
|
+
const radius = getArcRadius(text.fontSize, text.curveAmount)
|
|
28
|
+
const C = 2 * Math.PI * radius
|
|
29
|
+
const dis = (text.text as string).length + 2
|
|
30
|
+
const tW = width / dis
|
|
31
|
+
let startW = 0
|
|
32
|
+
if (C > tW * (text.text as string).length) {
|
|
33
|
+
startW = (C - tW * (text.text as string).length) / 2
|
|
34
|
+
}
|
|
35
|
+
const startWPr = (startW * 360) / C
|
|
36
|
+
const group = new Box({
|
|
37
|
+
x: 0,
|
|
38
|
+
y: 0,
|
|
39
|
+
editable: false,
|
|
40
|
+
resizeChildren: true
|
|
41
|
+
})
|
|
42
|
+
let path
|
|
43
|
+
const offsetK = text.fontSize * text.lineHeight.value || 0
|
|
44
|
+
const offsetY = text.curveAmount > 0 ? radius + offsetK : -radius
|
|
45
|
+
if (text.curveAmount > 0) {
|
|
46
|
+
path = `G ${x + width / 2} ${offsetY} ${radius} ${radius} 90 ${startWPr} ${360 - startWPr} 0`
|
|
47
|
+
} else {
|
|
48
|
+
const a = 180 - startWPr
|
|
49
|
+
path = `G ${x + width / 2} ${offsetY} ${radius} ${radius} 90 ${a} ${-a} 1`
|
|
50
|
+
}
|
|
51
|
+
const pathLine = new Path({
|
|
52
|
+
x: 0,
|
|
53
|
+
y: 0,
|
|
54
|
+
motionPath: true,
|
|
55
|
+
editable: false,
|
|
56
|
+
path: path
|
|
57
|
+
})
|
|
58
|
+
// 给Box添加新的Path轨迹
|
|
59
|
+
group.add(pathLine)
|
|
60
|
+
// op如果内部编辑输入的时候显示预测轨迹
|
|
61
|
+
group.set({
|
|
62
|
+
opacity: op ? 0.2 : 1
|
|
63
|
+
})
|
|
64
|
+
Array.from(text.text as string).forEach((ch, i) => {
|
|
65
|
+
const str = new HTMLText({
|
|
66
|
+
text: ch,
|
|
67
|
+
around: text.curveAmount > 0 ? 'bottom' : 'top',
|
|
68
|
+
fontSize: text.fontSize,
|
|
69
|
+
fontFamily: text.fontFamily,
|
|
70
|
+
fontWeight: text.fontWeight,
|
|
71
|
+
motion: i * tW + tW / 2,
|
|
72
|
+
textDecoration: text.textDecoration,
|
|
73
|
+
textCase: text.textCase,
|
|
74
|
+
textAlign: text.textAlign,
|
|
75
|
+
motionRotation: text.curveAmount > 0 ? 3 : -2,
|
|
76
|
+
fill: text.fill
|
|
77
|
+
})
|
|
78
|
+
group.add(str)
|
|
79
|
+
})
|
|
80
|
+
element.add(group)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// base 映射:只需把普通字符 -> 上标/下标(任意已有的上/下标字符)列出来一次
|
|
84
|
+
const baseSuperscript: Record<string, string> = {
|
|
85
|
+
'0': '⁰','1': '¹','2': '²','3': '³','4': '⁴','5': '⁵','6': '⁶','7': '⁷','8': '⁸','9': '⁹',
|
|
86
|
+
a: 'ᵃ', b: 'ᵇ', c: 'ᶜ', d: 'ᵈ', e: 'ᵉ', f: 'ᶠ', g: 'ᵍ', h: 'ʰ', i: 'ⁱ', j: 'ʲ', k: 'ᵏ',
|
|
87
|
+
l: 'ˡ', m: 'ᵐ', n: 'ⁿ', o: 'ᵒ', p: 'ᵖ', r: 'ʳ', s: 'ˢ', t: 'ᵗ', u: 'ᵘ', v: 'ᵛ', w: 'ʷ',
|
|
88
|
+
x: 'ˣ', y: 'ʸ', z: 'ᶻ',
|
|
89
|
+
A: 'ᴬ', B: 'ᴮ', D: 'ᴰ', E: 'ᴱ', G: 'ᴳ', H: 'ᴴ', I: 'ᴵ', J: 'ᴶ', K: 'ᴷ', L: 'ᴸ', M: 'ᴹ',
|
|
90
|
+
N: 'ᴺ', O: 'ᴼ', P: 'ᴾ', R: 'ᴿ', T: 'ᵀ', U: 'ᵁ', W: 'ᵂ',
|
|
91
|
+
'+': '⁺', '-': '⁻', '=': '⁼', '(': '⁽', ')': '⁾'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const baseSubscript: Record<string, string> = {
|
|
95
|
+
'0': '₀','1': '₁','2': '₂','3': '₃','4': '₄','5': '₅','6': '₆','7': '₇','8': '₈','9': '₉',
|
|
96
|
+
a: 'ₐ', e: 'ₑ', h: 'ₕ', i: 'ᵢ', j: 'ⱼ', k: 'ₖ', l: 'ₗ', m: 'ₘ', n: 'ₙ', o: 'ₒ', p: 'ₚ',
|
|
97
|
+
r: 'ᵣ', s: 'ₛ', t: 'ₜ', u: 'ᵤ', v: 'ᵥ', x: 'ₓ',
|
|
98
|
+
'+': '₊', '-': '₋', '=': '₌', '(': '₍', ')': '₎',
|
|
99
|
+
// 常见希腊字母的下标形式(如需要可加入)
|
|
100
|
+
'β': 'ᵦ', 'γ': 'ᵧ', 'ρ': 'ᵨ', 'φ': 'ᵩ', 'χ': 'ᵪ'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// helper: 从 base 映射中收集所有已知上标/下标字符(用于反向映射)
|
|
104
|
+
function invertMap(map: Record<string, string>): Record<string, string> {
|
|
105
|
+
const inv: Record<string, string> = {}
|
|
106
|
+
for (const k in map) {
|
|
107
|
+
inv[map[k]] = map[k] // key: 上/下标字符 -> value: 同样的上/下标字符(作为标准形)
|
|
108
|
+
}
|
|
109
|
+
return inv
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const knownSupers = invertMap(baseSuperscript) // '¹' -> '¹', 'ᵃ'->'ᵃ' ...
|
|
113
|
+
const knownSubs = invertMap(baseSubscript) // '₁' -> '₁', 'ₐ'->'ₐ' ...
|
|
114
|
+
|
|
115
|
+
// 生成最终的“鲁棒”映射:接受普通字符、上标字符、下标字符,统一输出目标形式
|
|
116
|
+
function buildSuperscriptMap(
|
|
117
|
+
baseSup: Record<string, string>,
|
|
118
|
+
baseSub: Record<string, string>
|
|
119
|
+
): Record<string, string> {
|
|
120
|
+
const out: Record<string, string> = {}
|
|
121
|
+
|
|
122
|
+
// 普通字符 -> 上标
|
|
123
|
+
for (const k in baseSup) out[k] = baseSup[k]
|
|
124
|
+
|
|
125
|
+
// 已知的下标字符(如 '₁')也映射到对应的上标(如果我们在 baseSup 找到相同的 base char)
|
|
126
|
+
// 为了做到这一点,我们需要找到下标字符对应的 base 字符:遍历 baseSub 找到哪些 base 的下标字符
|
|
127
|
+
for (const base in baseSub) {
|
|
128
|
+
const subChar = baseSub[base]
|
|
129
|
+
// 如果 base 在 baseSup 有对应的上标,则把 subChar -> 那个上标
|
|
130
|
+
if (base in baseSup) out[subChar] = baseSup[base]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 如果输入已经是上标(例如 '¹'),也把它映射为标准上标(自身)
|
|
134
|
+
for (const supChar in knownSupers) out[supChar] = supChar
|
|
135
|
+
|
|
136
|
+
return out
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildSubscriptMap(
|
|
140
|
+
baseSup: Record<string, string>,
|
|
141
|
+
baseSub: Record<string, string>
|
|
142
|
+
): Record<string, string> {
|
|
143
|
+
const out: Record<string, string> = {}
|
|
144
|
+
|
|
145
|
+
// 普通字符 -> 下标
|
|
146
|
+
for (const k in baseSub) out[k] = baseSub[k]
|
|
147
|
+
|
|
148
|
+
// 已知的上标字符也映射到对应的下标(如果 baseSup 有同名 base)
|
|
149
|
+
for (const base in baseSup) {
|
|
150
|
+
const supChar = baseSup[base]
|
|
151
|
+
if (base in baseSub) out[supChar] = baseSub[base]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 已经是下标的字符映射到自身
|
|
155
|
+
for (const subChar in knownSubs) out[subChar] = subChar
|
|
156
|
+
|
|
157
|
+
return out
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const superscriptMap = buildSuperscriptMap(baseSuperscript, baseSubscript)
|
|
161
|
+
const subscriptMap = buildSubscriptMap(baseSuperscript, baseSubscript)
|
|
162
|
+
|
|
163
|
+
// 转换函数(逐字符替换;可以根据需要改成群组替换)
|
|
164
|
+
export function toSuperscript(input: string): string {
|
|
165
|
+
let out = ''
|
|
166
|
+
for (const ch of input) {
|
|
167
|
+
out += superscriptMap[ch] ?? ch
|
|
168
|
+
}
|
|
169
|
+
return out
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function toSubscript(input: string): string {
|
|
173
|
+
let out = ''
|
|
174
|
+
for (const ch of input) {
|
|
175
|
+
out += subscriptMap[ch] ?? ch
|
|
176
|
+
}
|
|
177
|
+
return out
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
export const superscriptMapVal = Object.values(superscriptMap)
|
|
182
|
+
|
|
183
|
+
export const subscriptMapVal = Object.values(subscriptMap)
|
|
184
|
+
|
|
185
|
+
// 构建 normalMap:所有上标、下标字符 → 对应的普通 base 字符
|
|
186
|
+
function buildNormalMap(
|
|
187
|
+
baseSup: Record<string, string>,
|
|
188
|
+
baseSub: Record<string, string>
|
|
189
|
+
): Record<string, string> {
|
|
190
|
+
const normal: Record<string, string> = {}
|
|
191
|
+
|
|
192
|
+
// 上标的 value → key(普通)
|
|
193
|
+
for (const base in baseSup) {
|
|
194
|
+
const supChar = baseSup[base]
|
|
195
|
+
normal[supChar] = base
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 下标的 value → key(普通)
|
|
199
|
+
for (const base in baseSub) {
|
|
200
|
+
const subChar = baseSub[base]
|
|
201
|
+
normal[subChar] = base
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return normal
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const normalMap = buildNormalMap(baseSuperscript, baseSubscript)
|
|
208
|
+
|
|
209
|
+
export function toNormal(input: string): string {
|
|
210
|
+
let out = ''
|
|
211
|
+
for (const ch of input) {
|
|
212
|
+
out += normalMap[ch] ?? ch
|
|
213
|
+
}
|
|
214
|
+
return out
|
|
215
|
+
}
|
|
216
|
+
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { IText, IEventListenerId } from "@leafer-in/interface";
|
|
2
|
+
import { Matrix, PointerEvent } from "@leafer-ui/core";
|
|
3
|
+
import { InnerEditor, registerInnerEditor } from "@leafer-in/editor";
|
|
4
|
+
import { handleShowCurve } from "./TextEditTool/utils";
|
|
5
|
+
import { updataHtmlText } from "./utils";
|
|
6
|
+
import { fetchFontAsBase64 } from "./fonts/utils";
|
|
7
|
+
import { defaultFonts } from "./fonts/font";
|
|
8
|
+
|
|
9
|
+
@registerInnerEditor()
|
|
10
|
+
export class TextEditor extends InnerEditor {
|
|
11
|
+
public static quill: any;
|
|
12
|
+
|
|
13
|
+
public get tag() {
|
|
14
|
+
return "TextEditor";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public quill: any;
|
|
18
|
+
declare public editTarget: IText;
|
|
19
|
+
|
|
20
|
+
public editDom: HTMLDivElement | undefined;
|
|
21
|
+
public config = {
|
|
22
|
+
selectAll: false,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
public eventIds: IEventListenerId[] = [];
|
|
26
|
+
|
|
27
|
+
protected selectText:
|
|
28
|
+
| { start: number; end: number; text: string }
|
|
29
|
+
| undefined;
|
|
30
|
+
protected inBody: boolean | undefined;
|
|
31
|
+
protected isHTMLText: boolean | undefined;
|
|
32
|
+
|
|
33
|
+
public onLoad(): void {
|
|
34
|
+
const { editor } = this;
|
|
35
|
+
const { config } = editor.app;
|
|
36
|
+
const text = this.editTarget;
|
|
37
|
+
const { scaleX, scaleY } = text.worldTransform;
|
|
38
|
+
const zoomScale = Math.max(Math.abs(scaleX), Math.abs(scaleY));
|
|
39
|
+
|
|
40
|
+
this.quill = TextEditor.quill;
|
|
41
|
+
|
|
42
|
+
this.isHTMLText = !(text instanceof Text);
|
|
43
|
+
config.keyEvent = false;
|
|
44
|
+
|
|
45
|
+
const element = document.querySelector("#textInnerEditor");
|
|
46
|
+
if (!element || !(element instanceof HTMLDivElement)) {
|
|
47
|
+
console.error("Cannot find #textInnerEditor element or it's not a div");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const div = (this.editDom = element);
|
|
52
|
+
const { style } = div;
|
|
53
|
+
style.visibility = "visible";
|
|
54
|
+
|
|
55
|
+
// 获取外部box的宽高
|
|
56
|
+
if (
|
|
57
|
+
text.data?.canChangeBox &&
|
|
58
|
+
text.parent?.width !== undefined &&
|
|
59
|
+
text.parent?.height !== undefined
|
|
60
|
+
) {
|
|
61
|
+
style.width = text.parent.width * zoomScale + "px";
|
|
62
|
+
style.height = text.parent.height * zoomScale + "px";
|
|
63
|
+
} else {
|
|
64
|
+
style.width = "auto";
|
|
65
|
+
style.height = "auto";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
style.outline = "solid #8499EF";
|
|
69
|
+
|
|
70
|
+
// 初始化文本样式
|
|
71
|
+
if (text.data?.textData?.fontSize) {
|
|
72
|
+
div.style.fontSize = `${text.data.textData.fontSize * zoomScale}px`;
|
|
73
|
+
}
|
|
74
|
+
if (text.data?.textData?.fontFamily) {
|
|
75
|
+
div.style.fontFamily = `${text.data.textData.fontFamily}`;
|
|
76
|
+
}
|
|
77
|
+
if (text.data?.textData?.lineHeight) {
|
|
78
|
+
div.style.lineHeight = text.data.textData.lineHeight;
|
|
79
|
+
}
|
|
80
|
+
if (text.data?.textData?.letterSpacing) {
|
|
81
|
+
div.style.letterSpacing = `${text.data.textData.letterSpacing}px`;
|
|
82
|
+
}
|
|
83
|
+
if (text.data?.textData?.textShadow) {
|
|
84
|
+
div.style.textShadow = `${text.data.textData.textShadow}`;
|
|
85
|
+
} else {
|
|
86
|
+
div.style.textShadow = "none";
|
|
87
|
+
}
|
|
88
|
+
if (text.data?.textData?.alignContent) {
|
|
89
|
+
const qlEditor: any = div.querySelector(".ql-editor");
|
|
90
|
+
if (qlEditor) {
|
|
91
|
+
qlEditor.style.alignContent = `${text.data.textData.alignContent}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 加载文本到编辑器
|
|
96
|
+
if (this.quill) {
|
|
97
|
+
this.quill.clipboard.dangerouslyPasteHTML(text.text);
|
|
98
|
+
|
|
99
|
+
if (text.parent?.children?.[0]?.tag?.includes("Shape")) {
|
|
100
|
+
const parentWidth = text.parent.width;
|
|
101
|
+
if (parentWidth !== undefined) {
|
|
102
|
+
style.width = parentWidth * zoomScale + "px";
|
|
103
|
+
style.left = "0px";
|
|
104
|
+
}
|
|
105
|
+
this.quill.formatLine(0, this.quill.getLength(), "align", "center");
|
|
106
|
+
} else {
|
|
107
|
+
this.quill.setSelection(0, this.quill.getLength() - 1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.quill.on("text-change", this.onInput);
|
|
111
|
+
this.quill.on("selection-change", this.onSelectionChange);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
localStorage.removeItem("selection-change");
|
|
115
|
+
|
|
116
|
+
this.eventIds = [
|
|
117
|
+
// 点击空白关闭
|
|
118
|
+
editor.app.on_(PointerEvent.DOWN, (e: PointerEvent) => {
|
|
119
|
+
let { target } = e.origin;
|
|
120
|
+
let find: boolean = false;
|
|
121
|
+
while (target) {
|
|
122
|
+
if (target === div) find = true;
|
|
123
|
+
target = target.parentElement;
|
|
124
|
+
}
|
|
125
|
+
if (!find) {
|
|
126
|
+
editor.closeInnerEditor();
|
|
127
|
+
editor.cancel();
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private onSelectionChange = async (e: any) => {
|
|
134
|
+
e && localStorage.setItem("selection-change", JSON.stringify(e));
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
private onInput = async () => {
|
|
138
|
+
// 主要是更新编辑内容
|
|
139
|
+
updataHtmlText(this.editTarget);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
public onUpdate() {
|
|
143
|
+
const { editTarget: text } = this;
|
|
144
|
+
if (!text.parent?.__local) return;
|
|
145
|
+
|
|
146
|
+
const { scaleX, scaleY } = text.worldTransform;
|
|
147
|
+
const zoomScale = Math.max(Math.abs(scaleX), Math.abs(scaleY));
|
|
148
|
+
|
|
149
|
+
// layout
|
|
150
|
+
const local = text.parent.__local as any;
|
|
151
|
+
let width = local.width || 0;
|
|
152
|
+
let height = local.height || 0;
|
|
153
|
+
width *= zoomScale;
|
|
154
|
+
height *= zoomScale;
|
|
155
|
+
|
|
156
|
+
const { x, y } = this.inBody
|
|
157
|
+
? text.app.clientBounds
|
|
158
|
+
: (text.app.tree?.clientBounds || { x: 0, y: 0 });
|
|
159
|
+
const { a, b, c, d, e, f } = new Matrix(text.worldTransform)
|
|
160
|
+
.scale(1 / zoomScale)
|
|
161
|
+
.translateInner(0, 0);
|
|
162
|
+
|
|
163
|
+
if (this.editDom) {
|
|
164
|
+
const { style } = this.editDom;
|
|
165
|
+
style.transform = `matrix(${a},${b},${c},${d},${e},${f})`;
|
|
166
|
+
style.left = x + "px";
|
|
167
|
+
style.top = y + "px";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 打开内部或者编辑内部文本编辑强制隐藏
|
|
171
|
+
text.set({
|
|
172
|
+
visible: false,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private isUpdatingPoints = false;
|
|
177
|
+
|
|
178
|
+
public onUnload(): void {
|
|
179
|
+
const { editTarget: text, editor, editDom: dom } = this;
|
|
180
|
+
|
|
181
|
+
if (text) {
|
|
182
|
+
this.onInput();
|
|
183
|
+
editor.off_(this.eventIds);
|
|
184
|
+
|
|
185
|
+
if (this.editDom) {
|
|
186
|
+
this.editDom.style.visibility = "hidden";
|
|
187
|
+
}
|
|
188
|
+
this.eventIds = [] as any;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
text.set({
|
|
192
|
+
visible: true,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (this.quill) {
|
|
196
|
+
this.quill.off("text-change", this.onInput);
|
|
197
|
+
this.quill.off("selection-change", this.onSelectionChange);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const defaultFonts = [
|
|
2
|
+
{ code: 'Roboto', name: `"Roboto", sans-serif`, url: '/fonts/Roboto.woff2' },
|
|
3
|
+
{ code: 'Roboto Mono', name: `"Roboto Mono", monospace`, url: '/fonts/RobotoMono.woff2' },
|
|
4
|
+
{ code: 'Inter', name: `"Inter", sans-serif`, url: '/fonts/Inter.woff2' },
|
|
5
|
+
{ code: 'Open Sans', name: `"Open Sans", sans-serif`, url: '/fonts/OpenSans.woff2' },
|
|
6
|
+
{ code: 'Montserrat', name: `"Montserrat", sans-serif`, url: '/fonts/Montserrat.woff2' },
|
|
7
|
+
{ code: 'Roboto Condensed', name: `"Roboto Condensed", sans-serif`, url: '/fonts/RobotoCondensed.woff2' },
|
|
8
|
+
{ code: 'Arimo', name: `"Arimo", sans-serif`, url: '/fonts/Arimo.woff2' },
|
|
9
|
+
{ code: 'Noto Sans', name: `"Noto Sans", sans-serif`, url: '/fonts/NotoSans.woff2' },
|
|
10
|
+
{ code: 'Noto Sans Symbols', name: `"Noto Sans Symbols", sans-serif`, url: '/fonts/NotoSansSymbols.woff2' },
|
|
11
|
+
{ code: 'Merriweather', name: `"Merriweather", serif`, url: '/fonts/Merriweather.woff2' },
|
|
12
|
+
{ code: 'Playfair Display', name: `"Playfair Display", serif`, url: '/fonts/PlayfairDisplay.woff2' },
|
|
13
|
+
{ code: 'Noto Serif', name: `"Noto Serif", serif`, url: '/fonts/NotoSerif.woff2' },
|
|
14
|
+
{ code: 'Lato', name: `"Lato", sans-serif`, url: '/fonts/Lato.woff2' },
|
|
15
|
+
{ code: 'Spectral', name: `"Spectral", serif`, url: '/fonts/Spectral.woff2' },
|
|
16
|
+
{ code: 'Dancing Script', name: `"Dancing Script", cursive`, url: '/fonts/DancingScript.woff2' },
|
|
17
|
+
|
|
18
|
+
{ code: 'Noto Sans Simplified Chinese', name: `"Noto Sans SC", sans-serif`, url: '/fonts/NotoSansSimplifiedChinese.woff2' },
|
|
19
|
+
{ code: 'Noto Serif Simplified Chinese', name: `"Noto Serif SC", serif`, url: '/fonts/NotoSerifSimplifiedChinese.woff2' },
|
|
20
|
+
{ code: 'Noto Sans Traditional Chinese', name: `"Noto Sans TC", sans-serif`, url: '/fonts/NotoSansTraditionalChinese.woff2' },
|
|
21
|
+
{ code: 'Noto Sans Hong Kong', name: `"Noto Sans HK", sans-serif`, url: '/fonts/NotoSansHongKong.woff2' },
|
|
22
|
+
{ code: 'Noto Serif Traditional Chinese', name: `"Noto Serif TC", serif`, url: '/fonts/NotoSerifTraditionalChinese.woff2' },
|
|
23
|
+
{ code: 'Noto Serif Hong Kong', name: `"Noto Serif HK", serif`, url: '/fonts/NotoSerifHongKong.woff2' },
|
|
24
|
+
{ code: 'Noto Sans Japanese', name: `"Noto Sans JP", sans-serif`, url: '/fonts/NotoSansJapanese.woff2' },
|
|
25
|
+
{ code: 'Noto Sans Korean', name: `"Noto Sans KR", sans-serif`, url: '/fonts/NotoSansKorean.woff2' },
|
|
26
|
+
|
|
27
|
+
{ code: 'Poppins', name: `"Poppins", sans-serif`, url: '/fonts/Poppins.woff2' }
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const FONT_KEY = 'OPEN_FONTS';
|
|
31
|
+
const FONT_VERSION_KEY = 'OPEN_FONTS_VERSION';
|
|
32
|
+
|
|
33
|
+
// 简单的字体管理类
|
|
34
|
+
export class FontManager {
|
|
35
|
+
private fontList: any[] = [];
|
|
36
|
+
private skipLoadFonts: any[] = defaultFonts.map((value) => value.name);
|
|
37
|
+
private loadFonts: any[] = defaultFonts;
|
|
38
|
+
|
|
39
|
+
constructor() {
|
|
40
|
+
this.initFonts();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 初始化部分字体
|
|
45
|
+
*/
|
|
46
|
+
async initFonts() {
|
|
47
|
+
let list: any[] = [];
|
|
48
|
+
|
|
49
|
+
if (typeof localStorage !== 'undefined') {
|
|
50
|
+
if (localStorage.getItem(FONT_VERSION_KEY) !== '1') {
|
|
51
|
+
localStorage.removeItem(FONT_KEY);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let localFonts: any[] = [];
|
|
55
|
+
try {
|
|
56
|
+
const storedFonts = localStorage.getItem(FONT_KEY);
|
|
57
|
+
localFonts = storedFonts ? JSON.parse(storedFonts) : [];
|
|
58
|
+
} catch {
|
|
59
|
+
localFonts = [];
|
|
60
|
+
localStorage.removeItem(FONT_KEY);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (localFonts.length > 0) {
|
|
64
|
+
list.push(...localFonts);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.fontList = defaultFonts.concat(list);
|
|
69
|
+
return list;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getFontList() {
|
|
73
|
+
return this.fontList;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getSkipLoadFonts() {
|
|
77
|
+
return this.skipLoadFonts;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getLoadFonts() {
|
|
81
|
+
return this.loadFonts;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 导出单例实例
|
|
86
|
+
export const fontManager = new FontManager();
|