modern-text 0.2.2 → 0.2.4
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/dist/index.cjs +687 -629
- package/dist/index.d.cts +131 -72
- package/dist/index.d.mts +131 -72
- package/dist/index.d.ts +131 -72
- package/dist/index.js +2 -2
- package/dist/index.mjs +671 -631
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
import { BoundingBox, Point2D, parseSvgToDom, parseSvg, Matrix3
|
|
1
|
+
import { BoundingBox, Path2D, Point2D, parseSvgToDom, parseSvg, Matrix3 } from 'modern-path2d';
|
|
2
2
|
import { fonts, Woff, Ttf } from 'modern-font';
|
|
3
3
|
export * from 'modern-font';
|
|
4
4
|
|
|
5
|
-
class Feature {
|
|
6
|
-
constructor(_text) {
|
|
7
|
-
this._text = _text;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
class Deformer extends Feature {
|
|
12
|
-
deform() {
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
5
|
function parseColor(ctx, source, box) {
|
|
17
6
|
if (typeof source === "string" && source.startsWith("linear-gradient")) {
|
|
18
7
|
const { x0, y0, x1, y1, stops } = parseCssLinearGradient(source, box.left, box.top, box.width, box.height);
|
|
@@ -94,43 +83,66 @@ function drawPaths(options) {
|
|
|
94
83
|
});
|
|
95
84
|
}
|
|
96
85
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
86
|
+
function filterEmpty(val) {
|
|
87
|
+
if (!val)
|
|
88
|
+
return val;
|
|
89
|
+
const res = {};
|
|
90
|
+
for (const key in val) {
|
|
91
|
+
if (val[key] !== "" && val[key] !== void 0) {
|
|
92
|
+
res[key] = val[key];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return res;
|
|
96
|
+
}
|
|
97
|
+
function getRotationPoint(point, rotation) {
|
|
98
|
+
const { x, y } = point;
|
|
99
|
+
const sin = Math.sin(rotation);
|
|
100
|
+
const cos = Math.cos(rotation);
|
|
101
|
+
return {
|
|
102
|
+
x: x * cos - y * sin,
|
|
103
|
+
y: x * sin + y * cos
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function getSkewPoint(point, startPoint, skewX, skewY) {
|
|
107
|
+
const dx = point.x - startPoint.x;
|
|
108
|
+
const dy = point.y - startPoint.y;
|
|
109
|
+
return {
|
|
110
|
+
x: startPoint.x + (dx + Math.tan(skewX) * dy),
|
|
111
|
+
y: startPoint.y + (dy + Math.tan(skewY) * dx)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function getScalePoint(point, startPoint, scaleX, scaleY) {
|
|
115
|
+
const x = scaleX < 0 ? startPoint.x - point.x + startPoint.x : point.x;
|
|
116
|
+
const y = scaleY < 0 ? startPoint.y - point.y + startPoint.y : point.y;
|
|
117
|
+
return {
|
|
118
|
+
x: x * Math.abs(scaleX),
|
|
119
|
+
y: y * Math.abs(scaleY)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function getPointPosition(point, startPoint, rotation = 0, skewX = 0, skewY = 0, scaleX = 1, scaleY = 1) {
|
|
123
|
+
let points = Array.isArray(point) ? point : [point];
|
|
124
|
+
const _rotation = -rotation / 180 * Math.PI;
|
|
125
|
+
const { x, y } = startPoint;
|
|
126
|
+
if (scaleX !== 1 || scaleY !== 1) {
|
|
127
|
+
points = points.map((point2) => {
|
|
128
|
+
return getScalePoint(point2, startPoint, scaleX, scaleY);
|
|
116
129
|
});
|
|
117
|
-
return BoundingBox.from(...boxes);
|
|
118
130
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
effects.forEach((effect) => {
|
|
124
|
-
uploadColor(effect, boundingBox, ctx);
|
|
125
|
-
});
|
|
126
|
-
characters.forEach((character) => {
|
|
127
|
-
effects.forEach((effect) => {
|
|
128
|
-
character.drawTo(ctx, effect);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return this;
|
|
131
|
+
if (skewX || skewY) {
|
|
132
|
+
points = points.map((point2) => {
|
|
133
|
+
return getSkewPoint(point2, startPoint, skewX, skewY);
|
|
134
|
+
});
|
|
133
135
|
}
|
|
136
|
+
points = points.map((d) => {
|
|
137
|
+
const h = d.x - x;
|
|
138
|
+
const f = -(d.y - y);
|
|
139
|
+
d = getRotationPoint({ x: h, y: f }, _rotation);
|
|
140
|
+
return {
|
|
141
|
+
x: x + d.x,
|
|
142
|
+
y: y - d.y
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
return points[0];
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
var __defProp$4 = Object.defineProperty;
|
|
@@ -139,448 +151,149 @@ var __publicField$4 = (obj, key, value) => {
|
|
|
139
151
|
__defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
140
152
|
return value;
|
|
141
153
|
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
const set1 = /* @__PURE__ */ new Set(["\xA9", "\xAE", "\xF7"]);
|
|
155
|
+
const set2 = /* @__PURE__ */ new Set([
|
|
156
|
+
"\u2014",
|
|
157
|
+
"\u2026",
|
|
158
|
+
"\u201C",
|
|
159
|
+
"\u201D",
|
|
160
|
+
"\uFE4F",
|
|
161
|
+
"\uFE4B",
|
|
162
|
+
"\uFE4C",
|
|
163
|
+
"\u2018",
|
|
164
|
+
"\u2019",
|
|
165
|
+
"\u02DC"
|
|
166
|
+
]);
|
|
167
|
+
class Character {
|
|
168
|
+
constructor(content, index, parent) {
|
|
169
|
+
this.content = content;
|
|
170
|
+
this.index = index;
|
|
171
|
+
this.parent = parent;
|
|
172
|
+
__publicField$4(this, "boundingBox", new BoundingBox());
|
|
173
|
+
__publicField$4(this, "path", new Path2D());
|
|
174
|
+
__publicField$4(this, "textWidth", 0);
|
|
175
|
+
__publicField$4(this, "textHeight", 0);
|
|
146
176
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
177
|
+
get computedStyle() {
|
|
178
|
+
return this.parent.computedStyle;
|
|
179
|
+
}
|
|
180
|
+
get isVertical() {
|
|
181
|
+
return this.computedStyle.writingMode.includes("vertical");
|
|
182
|
+
}
|
|
183
|
+
get fontSize() {
|
|
184
|
+
return this.computedStyle.fontSize;
|
|
185
|
+
}
|
|
186
|
+
_font() {
|
|
187
|
+
const font = fonts.get(this.computedStyle.fontFamily)?.font;
|
|
188
|
+
if (font instanceof Woff || font instanceof Ttf) {
|
|
189
|
+
return font.sfnt;
|
|
150
190
|
}
|
|
151
|
-
|
|
152
|
-
const max = Point2D.MIN;
|
|
153
|
-
this.paths.forEach((path) => path.getMinMax(min, max));
|
|
154
|
-
return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
191
|
+
return void 0;
|
|
155
192
|
}
|
|
156
|
-
|
|
157
|
-
const {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
193
|
+
_updateGlyph(font) {
|
|
194
|
+
const { content, computedStyle, boundingBox, isVertical } = this;
|
|
195
|
+
const { left, top, height } = boundingBox;
|
|
196
|
+
const { fontSize } = computedStyle;
|
|
197
|
+
const { unitsPerEm, ascender, descender, os2, post } = font;
|
|
198
|
+
const rate = unitsPerEm / fontSize;
|
|
199
|
+
const glyphWidth = font.getAdvanceWidth(content, fontSize);
|
|
200
|
+
const glyphHeight = (ascender + Math.abs(descender)) / rate;
|
|
201
|
+
const baseline = ascender / rate;
|
|
202
|
+
const yStrikeoutPosition = (ascender - os2.yStrikeoutPosition) / rate;
|
|
203
|
+
const yStrikeoutSize = os2.yStrikeoutSize / rate;
|
|
204
|
+
const underlinePosition = (ascender - post.underlinePosition) / rate;
|
|
205
|
+
const underlineThickness = post.underlineThickness / rate;
|
|
206
|
+
this.glyphWidth = glyphWidth;
|
|
207
|
+
this.glyphHeight = glyphHeight;
|
|
208
|
+
this.underlinePosition = underlinePosition;
|
|
209
|
+
this.underlineThickness = underlineThickness;
|
|
210
|
+
this.yStrikeoutPosition = yStrikeoutPosition;
|
|
211
|
+
this.yStrikeoutSize = yStrikeoutSize;
|
|
212
|
+
this.baseline = baseline;
|
|
213
|
+
this.centerDiviation = 0.5 * height - baseline;
|
|
214
|
+
this.glyphBox = isVertical ? new BoundingBox(left, top, glyphHeight, glyphWidth) : new BoundingBox(left, top, glyphWidth, glyphHeight);
|
|
215
|
+
this.centerPoint = this.glyphBox.getCenterPoint();
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
_decoration() {
|
|
219
|
+
const { isVertical, underlinePosition, yStrikeoutPosition } = this;
|
|
220
|
+
const { textDecoration, fontSize } = this.computedStyle;
|
|
221
|
+
const { left, top, width, height } = this.boundingBox;
|
|
222
|
+
const lineWidth = 0.1 * fontSize;
|
|
223
|
+
let start;
|
|
224
|
+
switch (textDecoration) {
|
|
225
|
+
case "underline":
|
|
226
|
+
if (isVertical) {
|
|
227
|
+
start = left;
|
|
167
228
|
} else {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
229
|
+
start = top + underlinePosition;
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
case "line-through":
|
|
233
|
+
if (isVertical) {
|
|
234
|
+
start = left + width / 2;
|
|
235
|
+
} else {
|
|
236
|
+
start = top + yStrikeoutPosition;
|
|
171
237
|
}
|
|
238
|
+
break;
|
|
239
|
+
case "none":
|
|
240
|
+
default:
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
if (isVertical) {
|
|
244
|
+
return [
|
|
245
|
+
{ type: "M", x: start, y: top },
|
|
246
|
+
{ type: "L", x: start, y: top + height },
|
|
247
|
+
{ type: "L", x: start + lineWidth, y: top + height },
|
|
248
|
+
{ type: "L", x: start + lineWidth, y: top },
|
|
249
|
+
{ type: "Z" }
|
|
250
|
+
];
|
|
251
|
+
} else {
|
|
252
|
+
return [
|
|
253
|
+
{ type: "M", x: left, y: start },
|
|
254
|
+
{ type: "L", x: left + width, y: start },
|
|
255
|
+
{ type: "L", x: left + width, y: start + lineWidth },
|
|
256
|
+
{ type: "L", x: left, y: start + lineWidth },
|
|
257
|
+
{ type: "Z" }
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
_transform(commands, cb) {
|
|
262
|
+
return commands.map((rawCmd) => {
|
|
263
|
+
const cmd = { ...rawCmd };
|
|
264
|
+
switch (cmd.type) {
|
|
265
|
+
case "L":
|
|
266
|
+
case "M":
|
|
267
|
+
[cmd.x, cmd.y] = cb(cmd.x, cmd.y);
|
|
268
|
+
break;
|
|
269
|
+
case "Q":
|
|
270
|
+
[cmd.x1, cmd.y1] = cb(cmd.x1, cmd.y1);
|
|
271
|
+
[cmd.x, cmd.y] = cb(cmd.x, cmd.y);
|
|
272
|
+
break;
|
|
172
273
|
}
|
|
173
|
-
|
|
274
|
+
return cmd;
|
|
174
275
|
});
|
|
175
|
-
this.paths = groups.filter((characters2) => characters2.length).map((characters2) => {
|
|
176
|
-
return {
|
|
177
|
-
url: characters2[0].parent.highlight.url,
|
|
178
|
-
box: BoundingBox.from(...characters2.map((c) => c.glyphBox)),
|
|
179
|
-
baseline: Math.max(...characters2.map((c) => c.baseline))
|
|
180
|
-
};
|
|
181
|
-
}).map((group2) => this._parseGroup(group2, fontSize)).flat();
|
|
182
276
|
}
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const { x, y, width, height } = new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
190
|
-
const viewBox = svg.getAttribute("viewBox").split(" ").map(Number);
|
|
191
|
-
return {
|
|
192
|
-
paths,
|
|
193
|
-
box: new BoundingBox(x, y, width, height),
|
|
194
|
-
viewBox: new BoundingBox(...viewBox)
|
|
277
|
+
_italic(commands, startPoint) {
|
|
278
|
+
const { baseline, glyphWidth } = this;
|
|
279
|
+
const { left, top } = this.boundingBox;
|
|
280
|
+
const _startPoint = startPoint || {
|
|
281
|
+
y: top + baseline,
|
|
282
|
+
x: left + glyphWidth / 2
|
|
195
283
|
};
|
|
284
|
+
return this._transform(commands, (x, y) => {
|
|
285
|
+
const p = getSkewPoint({ x, y }, _startPoint, -0.24, 0);
|
|
286
|
+
return [p.x, p.y];
|
|
287
|
+
});
|
|
196
288
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (type === 0) {
|
|
203
|
-
const offset = {
|
|
204
|
-
x: groupBox.left - fontSize * 0.2,
|
|
205
|
-
y: groupBox.top
|
|
206
|
-
};
|
|
207
|
-
const scaleX = (groupBox.width + fontSize * 0.2 * 2) / box.width;
|
|
208
|
-
const scaleY = groupBox.height / box.height;
|
|
209
|
-
const m = new Matrix3().translate(-box.x, -box.y).scale(scaleX, scaleY).translate(offset.x, offset.y);
|
|
210
|
-
paths.forEach((original) => {
|
|
211
|
-
result.push(original.clone().transform(m));
|
|
212
|
-
});
|
|
213
|
-
} else if (type === 1) {
|
|
214
|
-
const scale = fontSize / box.width * 2;
|
|
215
|
-
const width = box.width * scale;
|
|
216
|
-
const length = Math.ceil(groupBox.width / width);
|
|
217
|
-
const totalWidth = width * length;
|
|
218
|
-
const offset = {
|
|
219
|
-
x: groupBox.left + (groupBox.width - totalWidth) / 2 + fontSize * 0.1,
|
|
220
|
-
y: groupBox.top + baseline + fontSize * 0.1
|
|
221
|
-
};
|
|
222
|
-
const m = new Matrix3().translate(-box.x, -box.y).scale(scale, scale).translate(offset.x, offset.y);
|
|
223
|
-
for (let i = 0; i < length; i++) {
|
|
224
|
-
const _m = m.clone().translate(i * width, 0);
|
|
225
|
-
paths.forEach((original) => {
|
|
226
|
-
result.push(original.clone().transform(_m));
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return result;
|
|
231
|
-
}
|
|
232
|
-
draw({ ctx }) {
|
|
233
|
-
drawPaths({
|
|
234
|
-
ctx,
|
|
235
|
-
paths: this.paths,
|
|
236
|
-
fontSize: this._text.computedStyle.fontSize,
|
|
237
|
-
fill: false
|
|
238
|
-
});
|
|
239
|
-
return this;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
class Measurer extends Feature {
|
|
244
|
-
_styleToDomStyle(style) {
|
|
245
|
-
const _style = { ...style };
|
|
246
|
-
for (const key in style) {
|
|
247
|
-
if ([
|
|
248
|
-
"width",
|
|
249
|
-
"height",
|
|
250
|
-
"fontSize",
|
|
251
|
-
"letterSpacing",
|
|
252
|
-
"textStrokeWidth",
|
|
253
|
-
"shadowOffsetX",
|
|
254
|
-
"shadowOffsetY",
|
|
255
|
-
"shadowBlur"
|
|
256
|
-
].includes(key)) {
|
|
257
|
-
_style[key] = `${style[key]}px`;
|
|
258
|
-
} else {
|
|
259
|
-
_style[key] = style[key];
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return _style;
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* <div style="...">
|
|
266
|
-
* <ul>
|
|
267
|
-
* <li style="...">
|
|
268
|
-
* <div>
|
|
269
|
-
* <span style="...">...</span>
|
|
270
|
-
* <span>...</span>
|
|
271
|
-
* </div>
|
|
272
|
-
* </li>
|
|
273
|
-
* </ul>
|
|
274
|
-
* </div>
|
|
275
|
-
*/
|
|
276
|
-
createDom() {
|
|
277
|
-
const { paragraphs, computedStyle: style } = this._text;
|
|
278
|
-
const documentFragment = document.createDocumentFragment();
|
|
279
|
-
const dom = document.createElement("div");
|
|
280
|
-
Object.assign(dom.style, this._styleToDomStyle(style));
|
|
281
|
-
dom.style.position = "absolute";
|
|
282
|
-
dom.style.visibility = "hidden";
|
|
283
|
-
const ul = document.createElement("ul");
|
|
284
|
-
ul.style.listStyle = "none";
|
|
285
|
-
ul.style.padding = "0";
|
|
286
|
-
ul.style.margin = "0";
|
|
287
|
-
paragraphs.forEach((paragraph) => {
|
|
288
|
-
const li = document.createElement("li");
|
|
289
|
-
const div = document.createElement("div");
|
|
290
|
-
Object.assign(li.style, this._styleToDomStyle(paragraph.style));
|
|
291
|
-
paragraph.fragments.forEach((fragment) => {
|
|
292
|
-
const span = document.createElement("span");
|
|
293
|
-
Object.assign(span.style, this._styleToDomStyle(fragment.style));
|
|
294
|
-
span.appendChild(document.createTextNode(fragment.content));
|
|
295
|
-
div.appendChild(span);
|
|
296
|
-
});
|
|
297
|
-
ul.appendChild(li);
|
|
298
|
-
li.appendChild(div);
|
|
299
|
-
});
|
|
300
|
-
dom.appendChild(ul);
|
|
301
|
-
documentFragment.appendChild(dom);
|
|
302
|
-
document.body.appendChild(documentFragment);
|
|
303
|
-
return {
|
|
304
|
-
dom,
|
|
305
|
-
destory: () => dom.parentNode?.removeChild(dom)
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
_measureDom(dom) {
|
|
309
|
-
const paragraphs = [];
|
|
310
|
-
const fragments = [];
|
|
311
|
-
const characters = [];
|
|
312
|
-
dom.querySelectorAll("li").forEach((li, paragraphIndex) => {
|
|
313
|
-
const pBox = li.getBoundingClientRect();
|
|
314
|
-
paragraphs.push({
|
|
315
|
-
paragraphIndex,
|
|
316
|
-
left: pBox.left,
|
|
317
|
-
top: pBox.top,
|
|
318
|
-
width: pBox.width,
|
|
319
|
-
height: pBox.height
|
|
320
|
-
});
|
|
321
|
-
li.querySelectorAll("span").forEach((span, fragmentIndex) => {
|
|
322
|
-
const fBox = li.getBoundingClientRect();
|
|
323
|
-
fragments.push({
|
|
324
|
-
paragraphIndex,
|
|
325
|
-
fragmentIndex,
|
|
326
|
-
left: fBox.left,
|
|
327
|
-
top: fBox.top,
|
|
328
|
-
width: fBox.width,
|
|
329
|
-
height: fBox.height
|
|
330
|
-
});
|
|
331
|
-
const text = span.firstChild;
|
|
332
|
-
if (text instanceof window.Text) {
|
|
333
|
-
const range = document.createRange();
|
|
334
|
-
range.selectNodeContents(text);
|
|
335
|
-
const len = text.data ? text.data.length : 0;
|
|
336
|
-
let i = 0;
|
|
337
|
-
for (; i <= len; ) {
|
|
338
|
-
range.setStart(text, Math.max(i - 1, 0));
|
|
339
|
-
range.setEnd(text, i);
|
|
340
|
-
const rects = range.getClientRects?.() ?? [range.getBoundingClientRect()];
|
|
341
|
-
let rect = rects[rects.length - 1];
|
|
342
|
-
if (rects.length > 1 && rect.width < 2) {
|
|
343
|
-
rect = rects[rects.length - 2];
|
|
344
|
-
}
|
|
345
|
-
const content = range.toString();
|
|
346
|
-
if (content !== "" && rect && rect.width + rect.height !== 0) {
|
|
347
|
-
characters.push({
|
|
348
|
-
content,
|
|
349
|
-
newParagraphIndex: -1,
|
|
350
|
-
paragraphIndex,
|
|
351
|
-
fragmentIndex,
|
|
352
|
-
characterIndex: i - 1,
|
|
353
|
-
top: rect.top,
|
|
354
|
-
left: rect.left,
|
|
355
|
-
height: rect.height,
|
|
356
|
-
width: rect.width,
|
|
357
|
-
textWidth: -1,
|
|
358
|
-
textHeight: -1
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
i++;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
});
|
|
366
|
-
return {
|
|
367
|
-
paragraphs,
|
|
368
|
-
fragments,
|
|
369
|
-
characters
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
measureDom(dom) {
|
|
373
|
-
const { paragraphs } = this._text;
|
|
374
|
-
const rect = dom.getBoundingClientRect();
|
|
375
|
-
const innerEl = dom.querySelector("ul");
|
|
376
|
-
const isVertical = window.getComputedStyle(dom).writingMode.includes("vertical");
|
|
377
|
-
const oldLineHeight = innerEl.style.lineHeight;
|
|
378
|
-
innerEl.style.lineHeight = "4000px";
|
|
379
|
-
const _paragraphs = [[]];
|
|
380
|
-
let fragments = _paragraphs[0];
|
|
381
|
-
const { characters: oldCharacters } = this._measureDom(dom);
|
|
382
|
-
if (oldCharacters.length > 0) {
|
|
383
|
-
fragments.push(oldCharacters[0]);
|
|
384
|
-
oldCharacters.reduce((prev, current) => {
|
|
385
|
-
const attr = isVertical ? "left" : "top";
|
|
386
|
-
if (Math.abs(current[attr] - prev[attr]) > 4e3 / 2) {
|
|
387
|
-
fragments = [];
|
|
388
|
-
_paragraphs.push(fragments);
|
|
389
|
-
}
|
|
390
|
-
fragments.push(current);
|
|
391
|
-
return current;
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
innerEl.style.lineHeight = oldLineHeight;
|
|
395
|
-
const measured = this._measureDom(dom);
|
|
396
|
-
measured.paragraphs.forEach((p) => {
|
|
397
|
-
const _p = paragraphs[p.paragraphIndex];
|
|
398
|
-
_p.boundingBox.left = p.left;
|
|
399
|
-
_p.boundingBox.top = p.top;
|
|
400
|
-
_p.boundingBox.width = p.width;
|
|
401
|
-
_p.boundingBox.height = p.height;
|
|
402
|
-
});
|
|
403
|
-
measured.fragments.forEach((f) => {
|
|
404
|
-
const _f = paragraphs[f.paragraphIndex].fragments[f.fragmentIndex];
|
|
405
|
-
_f.boundingBox.left = f.left;
|
|
406
|
-
_f.boundingBox.top = f.top;
|
|
407
|
-
_f.boundingBox.width = f.width;
|
|
408
|
-
_f.boundingBox.height = f.height;
|
|
409
|
-
});
|
|
410
|
-
const results = [];
|
|
411
|
-
let i = 0;
|
|
412
|
-
_paragraphs.forEach((oldCharacters2) => {
|
|
413
|
-
oldCharacters2.forEach((oldCharacter) => {
|
|
414
|
-
const character = measured.characters[i];
|
|
415
|
-
const { paragraphIndex, fragmentIndex, characterIndex } = character;
|
|
416
|
-
results.push({
|
|
417
|
-
...character,
|
|
418
|
-
newParagraphIndex: paragraphIndex,
|
|
419
|
-
textWidth: oldCharacter.width,
|
|
420
|
-
textHeight: oldCharacter.height,
|
|
421
|
-
left: character.left - rect.left,
|
|
422
|
-
top: character.top - rect.top
|
|
423
|
-
});
|
|
424
|
-
const item = paragraphs[paragraphIndex].fragments[fragmentIndex].characters[characterIndex];
|
|
425
|
-
item.boundingBox.left = results[i].left;
|
|
426
|
-
item.boundingBox.top = results[i].top;
|
|
427
|
-
item.boundingBox.width = results[i].width;
|
|
428
|
-
item.boundingBox.height = results[i].height;
|
|
429
|
-
item.textWidth = results[i].textWidth;
|
|
430
|
-
item.textHeight = results[i].textHeight;
|
|
431
|
-
i++;
|
|
432
|
-
});
|
|
433
|
-
});
|
|
434
|
-
return {
|
|
435
|
-
paragraphs,
|
|
436
|
-
boundingBox: new BoundingBox(0, 0, rect.width, rect.height)
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
measure(dom) {
|
|
440
|
-
let destory;
|
|
441
|
-
if (!dom) {
|
|
442
|
-
({ dom, destory } = this.createDom());
|
|
443
|
-
}
|
|
444
|
-
const result = this.measureDom(dom);
|
|
445
|
-
destory?.();
|
|
446
|
-
return result;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function filterEmpty(val) {
|
|
451
|
-
if (!val)
|
|
452
|
-
return val;
|
|
453
|
-
const res = {};
|
|
454
|
-
for (const key in val) {
|
|
455
|
-
if (val[key] !== "" && val[key] !== void 0) {
|
|
456
|
-
res[key] = val[key];
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
return res;
|
|
460
|
-
}
|
|
461
|
-
function getRotationPoint(point, rotation) {
|
|
462
|
-
const { x, y } = point;
|
|
463
|
-
const sin = Math.sin(rotation);
|
|
464
|
-
const cos = Math.cos(rotation);
|
|
465
|
-
return {
|
|
466
|
-
x: x * cos - y * sin,
|
|
467
|
-
y: x * sin + y * cos
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
function getSkewPoint(point, startPoint, skewX, skewY) {
|
|
471
|
-
const dx = point.x - startPoint.x;
|
|
472
|
-
const dy = point.y - startPoint.y;
|
|
473
|
-
return {
|
|
474
|
-
x: startPoint.x + (dx + Math.tan(skewX) * dy),
|
|
475
|
-
y: startPoint.y + (dy + Math.tan(skewY) * dx)
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
function getScalePoint(point, startPoint, scaleX, scaleY) {
|
|
479
|
-
const x = scaleX < 0 ? startPoint.x - point.x + startPoint.x : point.x;
|
|
480
|
-
const y = scaleY < 0 ? startPoint.y - point.y + startPoint.y : point.y;
|
|
481
|
-
return {
|
|
482
|
-
x: x * Math.abs(scaleX),
|
|
483
|
-
y: y * Math.abs(scaleY)
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
function getPointPosition(point, startPoint, rotation = 0, skewX = 0, skewY = 0, scaleX = 1, scaleY = 1) {
|
|
487
|
-
let points = Array.isArray(point) ? point : [point];
|
|
488
|
-
const _rotation = -rotation / 180 * Math.PI;
|
|
489
|
-
const { x, y } = startPoint;
|
|
490
|
-
if (scaleX !== 1 || scaleY !== 1) {
|
|
491
|
-
points = points.map((point2) => {
|
|
492
|
-
return getScalePoint(point2, startPoint, scaleX, scaleY);
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
if (skewX || skewY) {
|
|
496
|
-
points = points.map((point2) => {
|
|
497
|
-
return getSkewPoint(point2, startPoint, skewX, skewY);
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
points = points.map((d) => {
|
|
501
|
-
const h = d.x - x;
|
|
502
|
-
const f = -(d.y - y);
|
|
503
|
-
d = getRotationPoint({ x: h, y: f }, _rotation);
|
|
504
|
-
return {
|
|
505
|
-
x: x + d.x,
|
|
506
|
-
y: y - d.y
|
|
507
|
-
};
|
|
508
|
-
});
|
|
509
|
-
return points[0];
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
var __defProp$3 = Object.defineProperty;
|
|
513
|
-
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
514
|
-
var __publicField$3 = (obj, key, value) => {
|
|
515
|
-
__defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
516
|
-
return value;
|
|
517
|
-
};
|
|
518
|
-
const set1 = /* @__PURE__ */ new Set(["\xA9", "\xAE", "\xF7"]);
|
|
519
|
-
const set2 = /* @__PURE__ */ new Set([
|
|
520
|
-
"\u2014",
|
|
521
|
-
"\u2026",
|
|
522
|
-
"\u201C",
|
|
523
|
-
"\u201D",
|
|
524
|
-
"\uFE4F",
|
|
525
|
-
"\uFE4B",
|
|
526
|
-
"\uFE4C",
|
|
527
|
-
"\u2018",
|
|
528
|
-
"\u2019",
|
|
529
|
-
"\u02DC"
|
|
530
|
-
]);
|
|
531
|
-
class Character {
|
|
532
|
-
constructor(content, index, parent) {
|
|
533
|
-
this.content = content;
|
|
534
|
-
this.index = index;
|
|
535
|
-
this.parent = parent;
|
|
536
|
-
__publicField$3(this, "boundingBox", new BoundingBox());
|
|
537
|
-
__publicField$3(this, "path", new Path2D());
|
|
538
|
-
__publicField$3(this, "textWidth", 0);
|
|
539
|
-
__publicField$3(this, "textHeight", 0);
|
|
540
|
-
}
|
|
541
|
-
get computedStyle() {
|
|
542
|
-
return this.parent.computedStyle;
|
|
543
|
-
}
|
|
544
|
-
get isVertical() {
|
|
545
|
-
return this.computedStyle.writingMode.includes("vertical");
|
|
546
|
-
}
|
|
547
|
-
get fontSize() {
|
|
548
|
-
return this.computedStyle.fontSize;
|
|
549
|
-
}
|
|
550
|
-
_updateFont() {
|
|
551
|
-
const font = fonts.get(this.computedStyle.fontFamily)?.font;
|
|
552
|
-
if (font instanceof Woff || font instanceof Ttf) {
|
|
553
|
-
this.font = font.sfnt;
|
|
554
|
-
}
|
|
555
|
-
return this;
|
|
556
|
-
}
|
|
557
|
-
_updateGlyph(font) {
|
|
558
|
-
const { content, computedStyle, boundingBox, isVertical } = this;
|
|
559
|
-
const { left, top, height } = boundingBox;
|
|
560
|
-
const { fontSize } = computedStyle;
|
|
561
|
-
const { unitsPerEm, ascender, descender, os2, post } = font;
|
|
562
|
-
const rate = unitsPerEm / fontSize;
|
|
563
|
-
const glyphWidth = font.getAdvanceWidth(content, fontSize);
|
|
564
|
-
const glyphHeight = (ascender + Math.abs(descender)) / rate;
|
|
565
|
-
const baseline = ascender / rate;
|
|
566
|
-
const yStrikeoutPosition = (ascender - os2.yStrikeoutPosition) / rate;
|
|
567
|
-
const yStrikeoutSize = os2.yStrikeoutSize / rate;
|
|
568
|
-
const underlinePosition = (ascender - post.underlinePosition) / rate;
|
|
569
|
-
const underlineThickness = post.underlineThickness / rate;
|
|
570
|
-
this.glyphWidth = glyphWidth;
|
|
571
|
-
this.glyphHeight = glyphHeight;
|
|
572
|
-
this.underlinePosition = underlinePosition;
|
|
573
|
-
this.underlineThickness = underlineThickness;
|
|
574
|
-
this.yStrikeoutPosition = yStrikeoutPosition;
|
|
575
|
-
this.yStrikeoutSize = yStrikeoutSize;
|
|
576
|
-
this.baseline = baseline;
|
|
577
|
-
this.centerDiviation = 0.5 * height - baseline;
|
|
578
|
-
this.glyphBox = isVertical ? new BoundingBox(left, top, glyphHeight, glyphWidth) : new BoundingBox(left, top, glyphWidth, glyphHeight);
|
|
579
|
-
this.centerPoint = this.glyphBox.getCenterPoint();
|
|
580
|
-
return this;
|
|
289
|
+
_rotation90(commands, point) {
|
|
290
|
+
return this._transform(commands, (x, y) => {
|
|
291
|
+
const p = getPointPosition({ x, y }, point, 90);
|
|
292
|
+
return [p.x, p.y];
|
|
293
|
+
});
|
|
581
294
|
}
|
|
582
295
|
updatePath() {
|
|
583
|
-
const font = this.
|
|
296
|
+
const font = this._font();
|
|
584
297
|
if (!font) {
|
|
585
298
|
return this;
|
|
586
299
|
}
|
|
@@ -593,16 +306,14 @@ class Character {
|
|
|
593
306
|
computedStyle,
|
|
594
307
|
baseline,
|
|
595
308
|
glyphHeight,
|
|
596
|
-
glyphWidth
|
|
597
|
-
yStrikeoutPosition,
|
|
598
|
-
underlinePosition
|
|
309
|
+
glyphWidth
|
|
599
310
|
} = this._updateGlyph(font);
|
|
600
311
|
const { os2, ascender, descender } = font;
|
|
601
312
|
const usWinAscent = ascender;
|
|
602
313
|
const usWinDescent = descender;
|
|
603
314
|
const typoAscender = os2.sTypoAscender;
|
|
604
|
-
const { left, top
|
|
605
|
-
const { fontSize, fontStyle
|
|
315
|
+
const { left, top } = boundingBox;
|
|
316
|
+
const { fontSize, fontStyle } = computedStyle;
|
|
606
317
|
let x = left;
|
|
607
318
|
let y = top + baseline;
|
|
608
319
|
let glyphIndex;
|
|
@@ -621,192 +332,512 @@ class Character {
|
|
|
621
332
|
x: x + glyphWidth / 2
|
|
622
333
|
};
|
|
623
334
|
if (fontStyle === "italic") {
|
|
624
|
-
|
|
335
|
+
commands = this._italic(
|
|
625
336
|
commands,
|
|
626
337
|
isVertical ? {
|
|
627
338
|
x: point.x,
|
|
628
339
|
y: top - (glyphHeight - glyphWidth) / 2 + baseline
|
|
629
|
-
} :
|
|
340
|
+
} : void 0
|
|
630
341
|
);
|
|
631
342
|
}
|
|
632
|
-
|
|
343
|
+
commands = this._rotation90(commands, point);
|
|
633
344
|
} else {
|
|
634
345
|
if (glyphIndex !== void 0) {
|
|
635
346
|
commands = font.glyf.glyphs.get(glyphIndex).getPathCommands(x, y, fontSize);
|
|
636
347
|
if (fontStyle === "italic") {
|
|
637
|
-
|
|
348
|
+
commands = this._italic(
|
|
638
349
|
commands,
|
|
639
350
|
isVertical ? {
|
|
640
351
|
x: x + glyphWidth / 2,
|
|
641
352
|
y: top + typoAscender / (usWinAscent + Math.abs(usWinDescent)) * glyphHeight
|
|
642
|
-
} :
|
|
353
|
+
} : void 0
|
|
643
354
|
);
|
|
644
355
|
}
|
|
645
356
|
} else {
|
|
646
357
|
commands = font.getPathCommands(content, x, y, fontSize) ?? [];
|
|
647
358
|
if (fontStyle === "italic") {
|
|
648
|
-
|
|
359
|
+
commands = this._italic(
|
|
649
360
|
commands,
|
|
650
|
-
isVertical ? { x: x + glyphHeight / 2, y } :
|
|
361
|
+
isVertical ? { x: x + glyphHeight / 2, y } : void 0
|
|
651
362
|
);
|
|
652
363
|
}
|
|
653
364
|
}
|
|
654
365
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
case "
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
{ type: "L", x: left + width, y: start },
|
|
676
|
-
{ type: "L", x: left + width, y: start + len },
|
|
677
|
-
{ type: "L", x: left, y: start + len },
|
|
678
|
-
{ type: "Z" }
|
|
679
|
-
];
|
|
680
|
-
switch (textDecoration) {
|
|
681
|
-
case "underline":
|
|
682
|
-
commands.push(...create(top + underlinePosition, lineWidth));
|
|
366
|
+
commands.push(...this._decoration());
|
|
367
|
+
this.commands = commands;
|
|
368
|
+
this.path = new Path2D(commands);
|
|
369
|
+
return this;
|
|
370
|
+
}
|
|
371
|
+
update() {
|
|
372
|
+
this.updatePath();
|
|
373
|
+
return this;
|
|
374
|
+
}
|
|
375
|
+
getMinMax(min = Point2D.MAX, max = Point2D.MIN) {
|
|
376
|
+
let last = { x: 0, y: 0 };
|
|
377
|
+
this.commands.forEach((cmd) => {
|
|
378
|
+
switch (cmd.type) {
|
|
379
|
+
case "L":
|
|
380
|
+
case "M":
|
|
381
|
+
min.x = Math.min(min.x, cmd.x);
|
|
382
|
+
min.y = Math.min(min.y, cmd.y);
|
|
383
|
+
max.x = Math.max(max.x, cmd.x);
|
|
384
|
+
max.y = Math.max(max.y, cmd.y);
|
|
385
|
+
last = { x: cmd.x, y: cmd.y };
|
|
683
386
|
break;
|
|
684
|
-
case "
|
|
685
|
-
|
|
387
|
+
case "Q": {
|
|
388
|
+
const x1 = 0.5 * (last.x + cmd.x1);
|
|
389
|
+
const y1 = 0.5 * (last.y + cmd.y1);
|
|
390
|
+
const x2 = 0.5 * (last.x + cmd.x);
|
|
391
|
+
const y2 = 0.5 * (last.y + cmd.y);
|
|
392
|
+
min.x = Math.min(min.x, last.x, cmd.x, x1, x2);
|
|
393
|
+
min.y = Math.min(min.y, last.y, cmd.y, y1, y2);
|
|
394
|
+
max.x = Math.max(max.x, last.x, cmd.x, x1, x2);
|
|
395
|
+
max.y = Math.max(max.y, last.y, cmd.y, y1, y2);
|
|
396
|
+
last = { x: cmd.x, y: cmd.y };
|
|
686
397
|
break;
|
|
398
|
+
}
|
|
687
399
|
}
|
|
400
|
+
});
|
|
401
|
+
return { min, max };
|
|
402
|
+
}
|
|
403
|
+
drawTo(ctx, config = {}) {
|
|
404
|
+
drawPaths({
|
|
405
|
+
ctx,
|
|
406
|
+
paths: [this.path],
|
|
407
|
+
fontSize: this.computedStyle.fontSize,
|
|
408
|
+
color: this.computedStyle.color,
|
|
409
|
+
...config
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
var __defProp$3 = Object.defineProperty;
|
|
415
|
+
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
416
|
+
var __publicField$3 = (obj, key, value) => {
|
|
417
|
+
__defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
418
|
+
return value;
|
|
419
|
+
};
|
|
420
|
+
class Fragment {
|
|
421
|
+
constructor(content, style = {}, parent) {
|
|
422
|
+
this.content = content;
|
|
423
|
+
this.style = style;
|
|
424
|
+
this.parent = parent;
|
|
425
|
+
__publicField$3(this, "boundingBox", new BoundingBox());
|
|
426
|
+
__publicField$3(this, "highlight");
|
|
427
|
+
this.updateComputedStyle().initCharacters();
|
|
428
|
+
}
|
|
429
|
+
get computedContent() {
|
|
430
|
+
const style = this.computedStyle;
|
|
431
|
+
return style.textTransform === "uppercase" ? this.content.toUpperCase() : style.textTransform === "lowercase" ? this.content.toLowerCase() : this.content;
|
|
432
|
+
}
|
|
433
|
+
updateComputedStyle() {
|
|
434
|
+
this.computedStyle = {
|
|
435
|
+
...this.parent.computedStyle,
|
|
436
|
+
...filterEmpty(this.style)
|
|
437
|
+
};
|
|
438
|
+
return this;
|
|
439
|
+
}
|
|
440
|
+
initCharacters() {
|
|
441
|
+
const characters = [];
|
|
442
|
+
let index = 0;
|
|
443
|
+
for (const c of this.computedContent) {
|
|
444
|
+
characters.push(new Character(c, index++, this));
|
|
688
445
|
}
|
|
689
|
-
this.
|
|
446
|
+
this.characters = characters;
|
|
690
447
|
return this;
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
var __defProp$2 = Object.defineProperty;
|
|
452
|
+
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
453
|
+
var __publicField$2 = (obj, key, value) => {
|
|
454
|
+
__defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
455
|
+
return value;
|
|
456
|
+
};
|
|
457
|
+
class Paragraph {
|
|
458
|
+
constructor(style, parentStyle) {
|
|
459
|
+
this.style = style;
|
|
460
|
+
this.parentStyle = parentStyle;
|
|
461
|
+
__publicField$2(this, "boundingBox", new BoundingBox());
|
|
462
|
+
__publicField$2(this, "fragments", []);
|
|
463
|
+
this.updateComputedStyle();
|
|
464
|
+
}
|
|
465
|
+
updateComputedStyle() {
|
|
466
|
+
this.computedStyle = {
|
|
467
|
+
...filterEmpty(this.parentStyle),
|
|
468
|
+
...filterEmpty(this.style)
|
|
469
|
+
};
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
addFragment(content, style) {
|
|
473
|
+
const fragment = new Fragment(content, style, this);
|
|
474
|
+
this.fragments.push(fragment);
|
|
475
|
+
return fragment;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
class Feature {
|
|
480
|
+
constructor(_text) {
|
|
481
|
+
this._text = _text;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
class Deformer extends Feature {
|
|
486
|
+
deform() {
|
|
487
|
+
this._text.deformation?.();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
class Effector extends Feature {
|
|
492
|
+
getBoundingBox() {
|
|
493
|
+
const { characters, effects } = this._text;
|
|
494
|
+
const boxes = [];
|
|
495
|
+
characters.forEach((character) => {
|
|
496
|
+
const fontSize = character.computedStyle.fontSize;
|
|
497
|
+
effects?.forEach((effect) => {
|
|
498
|
+
const offsetX = (effect.offsetX ?? 0) * fontSize;
|
|
499
|
+
const offsetY = (effect.offsetY ?? 0) * fontSize;
|
|
500
|
+
const shadowOffsetX = (effect.shadowOffsetX ?? 0) * fontSize;
|
|
501
|
+
const shadowOffsetY = (effect.shadowOffsetY ?? 0) * fontSize;
|
|
502
|
+
const textStrokeWidth = Math.max(0.1, effect.textStrokeWidth ?? 0) * fontSize;
|
|
503
|
+
const aabb = character.boundingBox.clone();
|
|
504
|
+
aabb.left += offsetX + shadowOffsetX - textStrokeWidth;
|
|
505
|
+
aabb.top += offsetY + shadowOffsetY - textStrokeWidth;
|
|
506
|
+
aabb.width += textStrokeWidth * 2;
|
|
507
|
+
aabb.height += textStrokeWidth * 2;
|
|
508
|
+
boxes.push(aabb);
|
|
712
509
|
});
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
command[`x${arg}`] = pos.x;
|
|
727
|
-
command[`y${arg}`] = pos.y;
|
|
728
|
-
}
|
|
510
|
+
});
|
|
511
|
+
return BoundingBox.from(...boxes);
|
|
512
|
+
}
|
|
513
|
+
draw(options) {
|
|
514
|
+
const { ctx } = options;
|
|
515
|
+
const { effects, characters, boundingBox } = this._text;
|
|
516
|
+
if (effects) {
|
|
517
|
+
effects.forEach((effect) => {
|
|
518
|
+
uploadColor(effect, boundingBox, ctx);
|
|
519
|
+
});
|
|
520
|
+
characters.forEach((character) => {
|
|
521
|
+
effects.forEach((effect) => {
|
|
522
|
+
character.drawTo(ctx, effect);
|
|
729
523
|
});
|
|
730
524
|
});
|
|
731
525
|
}
|
|
732
|
-
}
|
|
733
|
-
update() {
|
|
734
|
-
this.updatePath();
|
|
735
526
|
return this;
|
|
736
527
|
}
|
|
737
|
-
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
var __defProp$1 = Object.defineProperty;
|
|
531
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
532
|
+
var __publicField$1 = (obj, key, value) => {
|
|
533
|
+
__defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
534
|
+
return value;
|
|
535
|
+
};
|
|
536
|
+
class Highlighter extends Feature {
|
|
537
|
+
constructor() {
|
|
538
|
+
super(...arguments);
|
|
539
|
+
__publicField$1(this, "paths", []);
|
|
540
|
+
}
|
|
541
|
+
getBoundingBox() {
|
|
542
|
+
if (!this.paths.length) {
|
|
543
|
+
return new BoundingBox();
|
|
544
|
+
}
|
|
545
|
+
const min = Point2D.MAX;
|
|
546
|
+
const max = Point2D.MIN;
|
|
547
|
+
this.paths.forEach((path) => path.getMinMax(min, max));
|
|
548
|
+
return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
549
|
+
}
|
|
550
|
+
highlight() {
|
|
551
|
+
const { characters, computedStyle: style } = this._text;
|
|
552
|
+
const fontSize = style.fontSize;
|
|
553
|
+
let group;
|
|
554
|
+
const groups = [];
|
|
555
|
+
let prevHighlight;
|
|
556
|
+
characters.forEach((character) => {
|
|
557
|
+
const highlight = character.parent.highlight;
|
|
558
|
+
if (highlight?.url) {
|
|
559
|
+
if (prevHighlight?.url === highlight.url) {
|
|
560
|
+
group.push(character);
|
|
561
|
+
} else {
|
|
562
|
+
group = [];
|
|
563
|
+
group.push(character);
|
|
564
|
+
groups.push(group);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
prevHighlight = highlight;
|
|
568
|
+
});
|
|
569
|
+
this.paths = groups.filter((characters2) => characters2.length).map((characters2) => {
|
|
570
|
+
return {
|
|
571
|
+
url: characters2[0].parent.highlight.url,
|
|
572
|
+
box: BoundingBox.from(...characters2.map((c) => c.glyphBox)),
|
|
573
|
+
baseline: Math.max(...characters2.map((c) => c.baseline))
|
|
574
|
+
};
|
|
575
|
+
}).map((group2) => this._parseGroup(group2, fontSize)).flat();
|
|
576
|
+
}
|
|
577
|
+
_parseSvg(url) {
|
|
578
|
+
const svg = parseSvgToDom(url);
|
|
579
|
+
const paths = parseSvg(svg);
|
|
580
|
+
const min = Point2D.MAX;
|
|
581
|
+
const max = Point2D.MIN;
|
|
582
|
+
paths.forEach((path) => path.getMinMax(min, max));
|
|
583
|
+
const { x, y, width, height } = new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
584
|
+
const viewBox = svg.getAttribute("viewBox").split(" ").map(Number);
|
|
585
|
+
return {
|
|
586
|
+
paths,
|
|
587
|
+
box: new BoundingBox(x, y, width, height),
|
|
588
|
+
viewBox: new BoundingBox(...viewBox)
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
_parseGroup(group, fontSize) {
|
|
592
|
+
const { url, box: groupBox, baseline } = group;
|
|
593
|
+
const { box, viewBox, paths } = this._parseSvg(url);
|
|
594
|
+
const result = [];
|
|
595
|
+
const type = box.height / viewBox.height > 0.3 ? 0 : 1;
|
|
596
|
+
if (type === 0) {
|
|
597
|
+
const offset = {
|
|
598
|
+
x: groupBox.left - fontSize * 0.2,
|
|
599
|
+
y: groupBox.top
|
|
600
|
+
};
|
|
601
|
+
const scaleX = (groupBox.width + fontSize * 0.2 * 2) / box.width;
|
|
602
|
+
const scaleY = groupBox.height / box.height;
|
|
603
|
+
const m = new Matrix3().translate(-box.x, -box.y).scale(scaleX, scaleY).translate(offset.x, offset.y);
|
|
604
|
+
paths.forEach((original) => {
|
|
605
|
+
result.push(original.clone().transform(m));
|
|
606
|
+
});
|
|
607
|
+
} else if (type === 1) {
|
|
608
|
+
const scale = fontSize / box.width * 2;
|
|
609
|
+
const width = box.width * scale;
|
|
610
|
+
const length = Math.ceil(groupBox.width / width);
|
|
611
|
+
const totalWidth = width * length;
|
|
612
|
+
const offset = {
|
|
613
|
+
x: groupBox.left + (groupBox.width - totalWidth) / 2 + fontSize * 0.1,
|
|
614
|
+
y: groupBox.top + baseline + fontSize * 0.1
|
|
615
|
+
};
|
|
616
|
+
const m = new Matrix3().translate(-box.x, -box.y).scale(scale, scale).translate(offset.x, offset.y);
|
|
617
|
+
for (let i = 0; i < length; i++) {
|
|
618
|
+
const _m = m.clone().translate(i * width, 0);
|
|
619
|
+
paths.forEach((original) => {
|
|
620
|
+
result.push(original.clone().transform(_m));
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return result;
|
|
625
|
+
}
|
|
626
|
+
draw({ ctx }) {
|
|
738
627
|
drawPaths({
|
|
739
628
|
ctx,
|
|
740
|
-
paths:
|
|
741
|
-
fontSize: this.computedStyle.fontSize,
|
|
742
|
-
|
|
743
|
-
...config
|
|
629
|
+
paths: this.paths,
|
|
630
|
+
fontSize: this._text.computedStyle.fontSize,
|
|
631
|
+
fill: false
|
|
744
632
|
});
|
|
633
|
+
return this;
|
|
745
634
|
}
|
|
746
635
|
}
|
|
747
636
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
637
|
+
class Measurer extends Feature {
|
|
638
|
+
_styleToDomStyle(style) {
|
|
639
|
+
const _style = { ...style };
|
|
640
|
+
for (const key in style) {
|
|
641
|
+
if ([
|
|
642
|
+
"width",
|
|
643
|
+
"height",
|
|
644
|
+
"fontSize",
|
|
645
|
+
"letterSpacing",
|
|
646
|
+
"textStrokeWidth",
|
|
647
|
+
"shadowOffsetX",
|
|
648
|
+
"shadowOffsetY",
|
|
649
|
+
"shadowBlur"
|
|
650
|
+
].includes(key)) {
|
|
651
|
+
_style[key] = `${style[key]}px`;
|
|
652
|
+
} else {
|
|
653
|
+
_style[key] = style[key];
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return _style;
|
|
766
657
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
658
|
+
/**
|
|
659
|
+
* <section style="...">
|
|
660
|
+
* <ul>
|
|
661
|
+
* <li style="...">
|
|
662
|
+
* <span style="...">...</span>
|
|
663
|
+
* <span>...</span>
|
|
664
|
+
* </li>
|
|
665
|
+
* </ul>
|
|
666
|
+
* </section>
|
|
667
|
+
*/
|
|
668
|
+
createDom() {
|
|
669
|
+
const { paragraphs, computedStyle } = this._text;
|
|
670
|
+
const documentFragment = document.createDocumentFragment();
|
|
671
|
+
const dom = document.createElement("section");
|
|
672
|
+
Object.assign(dom.style, {
|
|
673
|
+
...this._styleToDomStyle(computedStyle),
|
|
674
|
+
position: "absolute",
|
|
675
|
+
visibility: "hidden"
|
|
676
|
+
});
|
|
677
|
+
const ul = document.createElement("ul");
|
|
678
|
+
Object.assign(ul.style, {
|
|
679
|
+
listStyle: "none",
|
|
680
|
+
padding: "0",
|
|
681
|
+
margin: "0"
|
|
682
|
+
});
|
|
683
|
+
paragraphs.forEach((paragraph) => {
|
|
684
|
+
const li = document.createElement("li");
|
|
685
|
+
Object.assign(li.style, this._styleToDomStyle(paragraph.style));
|
|
686
|
+
paragraph.fragments.forEach((fragment) => {
|
|
687
|
+
const span = document.createElement("span");
|
|
688
|
+
Object.assign(span.style, this._styleToDomStyle(fragment.style));
|
|
689
|
+
span.appendChild(document.createTextNode(fragment.content));
|
|
690
|
+
li.appendChild(span);
|
|
691
|
+
});
|
|
692
|
+
ul.appendChild(li);
|
|
693
|
+
});
|
|
694
|
+
dom.appendChild(ul);
|
|
695
|
+
documentFragment.appendChild(dom);
|
|
696
|
+
document.body.appendChild(documentFragment);
|
|
697
|
+
return {
|
|
698
|
+
dom,
|
|
699
|
+
destory: () => dom.parentNode?.removeChild(dom)
|
|
771
700
|
};
|
|
772
|
-
return this;
|
|
773
701
|
}
|
|
774
|
-
|
|
702
|
+
_measureDom(dom) {
|
|
703
|
+
const paragraphs = [];
|
|
704
|
+
const fragments = [];
|
|
775
705
|
const characters = [];
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
706
|
+
dom.querySelectorAll("li").forEach((li, paragraphIndex) => {
|
|
707
|
+
const pBox = li.getBoundingClientRect();
|
|
708
|
+
paragraphs.push({
|
|
709
|
+
paragraphIndex,
|
|
710
|
+
left: pBox.left,
|
|
711
|
+
top: pBox.top,
|
|
712
|
+
width: pBox.width,
|
|
713
|
+
height: pBox.height
|
|
714
|
+
});
|
|
715
|
+
li.querySelectorAll("span").forEach((span, fragmentIndex) => {
|
|
716
|
+
const fBox = li.getBoundingClientRect();
|
|
717
|
+
fragments.push({
|
|
718
|
+
paragraphIndex,
|
|
719
|
+
fragmentIndex,
|
|
720
|
+
left: fBox.left,
|
|
721
|
+
top: fBox.top,
|
|
722
|
+
width: fBox.width,
|
|
723
|
+
height: fBox.height
|
|
724
|
+
});
|
|
725
|
+
const text = span.firstChild;
|
|
726
|
+
if (text instanceof window.Text) {
|
|
727
|
+
const range = document.createRange();
|
|
728
|
+
range.selectNodeContents(text);
|
|
729
|
+
const len = text.data ? text.data.length : 0;
|
|
730
|
+
let i = 0;
|
|
731
|
+
for (; i <= len; ) {
|
|
732
|
+
range.setStart(text, Math.max(i - 1, 0));
|
|
733
|
+
range.setEnd(text, i);
|
|
734
|
+
const rects = range.getClientRects?.() ?? [range.getBoundingClientRect()];
|
|
735
|
+
let rect = rects[rects.length - 1];
|
|
736
|
+
if (rects.length > 1 && rect.width < 2) {
|
|
737
|
+
rect = rects[rects.length - 2];
|
|
738
|
+
}
|
|
739
|
+
const content = range.toString();
|
|
740
|
+
if (content !== "" && rect && rect.width + rect.height !== 0) {
|
|
741
|
+
characters.push({
|
|
742
|
+
content,
|
|
743
|
+
newParagraphIndex: -1,
|
|
744
|
+
paragraphIndex,
|
|
745
|
+
fragmentIndex,
|
|
746
|
+
characterIndex: i - 1,
|
|
747
|
+
top: rect.top,
|
|
748
|
+
left: rect.left,
|
|
749
|
+
height: rect.height,
|
|
750
|
+
width: rect.width,
|
|
751
|
+
textWidth: -1,
|
|
752
|
+
textHeight: -1
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
i++;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
return {
|
|
761
|
+
paragraphs,
|
|
762
|
+
fragments,
|
|
763
|
+
characters
|
|
764
|
+
};
|
|
798
765
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
766
|
+
measureDom(dom) {
|
|
767
|
+
const { paragraphs } = this._text;
|
|
768
|
+
const rect = dom.getBoundingClientRect();
|
|
769
|
+
const innerEl = dom.querySelector("ul");
|
|
770
|
+
const isVertical = window.getComputedStyle(dom).writingMode.includes("vertical");
|
|
771
|
+
const oldLineHeight = innerEl.style.lineHeight;
|
|
772
|
+
innerEl.style.lineHeight = "4000px";
|
|
773
|
+
const _paragraphs = [[]];
|
|
774
|
+
let fragments = _paragraphs[0];
|
|
775
|
+
const { characters: oldCharacters } = this._measureDom(dom);
|
|
776
|
+
if (oldCharacters.length > 0) {
|
|
777
|
+
fragments.push(oldCharacters[0]);
|
|
778
|
+
oldCharacters.reduce((prev, current) => {
|
|
779
|
+
const attr = isVertical ? "left" : "top";
|
|
780
|
+
if (Math.abs(current[attr] - prev[attr]) > 4e3 / 2) {
|
|
781
|
+
fragments = [];
|
|
782
|
+
_paragraphs.push(fragments);
|
|
783
|
+
}
|
|
784
|
+
fragments.push(current);
|
|
785
|
+
return current;
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
innerEl.style.lineHeight = oldLineHeight;
|
|
789
|
+
const measured = this._measureDom(dom);
|
|
790
|
+
measured.paragraphs.forEach((p) => {
|
|
791
|
+
const _p = paragraphs[p.paragraphIndex];
|
|
792
|
+
_p.boundingBox.left = p.left;
|
|
793
|
+
_p.boundingBox.top = p.top;
|
|
794
|
+
_p.boundingBox.width = p.width;
|
|
795
|
+
_p.boundingBox.height = p.height;
|
|
796
|
+
});
|
|
797
|
+
measured.fragments.forEach((f) => {
|
|
798
|
+
const _f = paragraphs[f.paragraphIndex].fragments[f.fragmentIndex];
|
|
799
|
+
_f.boundingBox.left = f.left;
|
|
800
|
+
_f.boundingBox.top = f.top;
|
|
801
|
+
_f.boundingBox.width = f.width;
|
|
802
|
+
_f.boundingBox.height = f.height;
|
|
803
|
+
});
|
|
804
|
+
const results = [];
|
|
805
|
+
let i = 0;
|
|
806
|
+
_paragraphs.forEach((oldCharacters2) => {
|
|
807
|
+
oldCharacters2.forEach((oldCharacter) => {
|
|
808
|
+
const character = measured.characters[i];
|
|
809
|
+
const { paragraphIndex, fragmentIndex, characterIndex } = character;
|
|
810
|
+
results.push({
|
|
811
|
+
...character,
|
|
812
|
+
newParagraphIndex: paragraphIndex,
|
|
813
|
+
textWidth: oldCharacter.width,
|
|
814
|
+
textHeight: oldCharacter.height,
|
|
815
|
+
left: character.left - rect.left,
|
|
816
|
+
top: character.top - rect.top
|
|
817
|
+
});
|
|
818
|
+
const item = paragraphs[paragraphIndex].fragments[fragmentIndex].characters[characterIndex];
|
|
819
|
+
item.boundingBox.left = results[i].left;
|
|
820
|
+
item.boundingBox.top = results[i].top;
|
|
821
|
+
item.boundingBox.width = results[i].width;
|
|
822
|
+
item.boundingBox.height = results[i].height;
|
|
823
|
+
item.textWidth = results[i].textWidth;
|
|
824
|
+
item.textHeight = results[i].textHeight;
|
|
825
|
+
i++;
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
return {
|
|
829
|
+
paragraphs,
|
|
830
|
+
boundingBox: new BoundingBox(0, 0, rect.width, rect.height)
|
|
803
831
|
};
|
|
804
|
-
return this;
|
|
805
832
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
833
|
+
measure(dom) {
|
|
834
|
+
let destory;
|
|
835
|
+
if (!dom) {
|
|
836
|
+
({ dom, destory } = this.createDom());
|
|
837
|
+
}
|
|
838
|
+
const result = this.measureDom(dom);
|
|
839
|
+
destory?.();
|
|
840
|
+
return result;
|
|
810
841
|
}
|
|
811
842
|
}
|
|
812
843
|
|
|
@@ -865,6 +896,10 @@ class Parser extends Feature {
|
|
|
865
896
|
}
|
|
866
897
|
}
|
|
867
898
|
|
|
899
|
+
class Reflector extends Feature {
|
|
900
|
+
// TODO
|
|
901
|
+
}
|
|
902
|
+
|
|
868
903
|
class Renderer2D extends Feature {
|
|
869
904
|
setupView(options) {
|
|
870
905
|
const { ctx, pixelRatio } = options;
|
|
@@ -965,6 +1000,7 @@ class Text {
|
|
|
965
1000
|
__publicField(this, "style");
|
|
966
1001
|
__publicField(this, "effects");
|
|
967
1002
|
__publicField(this, "deformation");
|
|
1003
|
+
__publicField(this, "measureDom");
|
|
968
1004
|
__publicField(this, "needsUpdate", true);
|
|
969
1005
|
__publicField(this, "computedStyle", { ...defaultTextStyles });
|
|
970
1006
|
__publicField(this, "paragraphs", []);
|
|
@@ -976,19 +1012,23 @@ class Text {
|
|
|
976
1012
|
__publicField(this, "_effector", new Effector(this));
|
|
977
1013
|
__publicField(this, "_highlighter", new Highlighter(this));
|
|
978
1014
|
__publicField(this, "_renderer2D", new Renderer2D(this));
|
|
979
|
-
const { content = "", style = {}, effects, deformation } = options;
|
|
1015
|
+
const { content = "", style = {}, effects, deformation, measureDom } = options;
|
|
980
1016
|
this.content = content;
|
|
981
1017
|
this.style = style;
|
|
982
1018
|
this.effects = effects;
|
|
983
1019
|
this.deformation = deformation;
|
|
1020
|
+
this.measureDom = measureDom;
|
|
984
1021
|
}
|
|
985
1022
|
get characters() {
|
|
986
1023
|
return this.paragraphs.flatMap((p) => p.fragments.flatMap((f) => f.characters));
|
|
987
1024
|
}
|
|
988
|
-
measure(dom) {
|
|
1025
|
+
measure(dom = this.measureDom) {
|
|
989
1026
|
this.computedStyle = { ...defaultTextStyles, ...this.style };
|
|
1027
|
+
const old = this.paragraphs;
|
|
990
1028
|
this.paragraphs = this._parser.parse();
|
|
991
|
-
|
|
1029
|
+
const result = this._measurer.measure(dom);
|
|
1030
|
+
this.paragraphs = old;
|
|
1031
|
+
return result;
|
|
992
1032
|
}
|
|
993
1033
|
requestUpdate() {
|
|
994
1034
|
this.needsUpdate = true;
|
|
@@ -1005,7 +1045,7 @@ class Text {
|
|
|
1005
1045
|
this._highlighter.highlight();
|
|
1006
1046
|
const min = Point2D.MAX;
|
|
1007
1047
|
const max = Point2D.MIN;
|
|
1008
|
-
this.characters.forEach((c) => c.
|
|
1048
|
+
this.characters.forEach((c) => c.getMinMax(min, max));
|
|
1009
1049
|
this.renderBoundingBox = new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
1010
1050
|
return this;
|
|
1011
1051
|
}
|
|
@@ -1025,23 +1065,23 @@ class Text {
|
|
|
1025
1065
|
this._effector.getBoundingBox(),
|
|
1026
1066
|
this._highlighter.getBoundingBox()
|
|
1027
1067
|
);
|
|
1028
|
-
this._renderer2D.setupView({ pixelRatio, ctx });
|
|
1029
|
-
this._renderer2D.uploadColors({ ctx });
|
|
1030
|
-
this._highlighter.draw({ ctx });
|
|
1031
|
-
this._effector.draw({ ctx });
|
|
1032
1068
|
} else {
|
|
1033
1069
|
this.renderBoundingBox = BoundingBox.from(
|
|
1034
1070
|
this.boundingBox,
|
|
1035
1071
|
this.renderBoundingBox,
|
|
1036
1072
|
this._highlighter.getBoundingBox()
|
|
1037
1073
|
);
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1074
|
+
}
|
|
1075
|
+
this._renderer2D.setupView({ pixelRatio, ctx });
|
|
1076
|
+
this._renderer2D.uploadColors({ ctx });
|
|
1077
|
+
this._highlighter.draw({ ctx });
|
|
1078
|
+
if (this.effects?.length) {
|
|
1079
|
+
this._effector.draw({ ctx });
|
|
1080
|
+
} else {
|
|
1041
1081
|
this._renderer2D.draw({ ctx });
|
|
1042
1082
|
}
|
|
1043
1083
|
return this;
|
|
1044
1084
|
}
|
|
1045
1085
|
}
|
|
1046
1086
|
|
|
1047
|
-
export { Text, defaultTextStyles };
|
|
1087
|
+
export { Character, Deformer, Effector, Fragment, Highlighter, Measurer, Paragraph, Parser, Reflector, Renderer2D, Text, defaultTextStyles, drawPaths, filterEmpty, getPointPosition, getRotationPoint, getScalePoint, getSkewPoint, parseColor, uploadColor };
|