modern-text 1.6.1 → 1.6.3

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 CHANGED
@@ -1,1766 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const modernPath2d = require('modern-path2d');
4
- const modernIdoc = require('modern-idoc');
5
- const modernFont = require('modern-font');
6
- const diff = require('diff');
7
-
8
- function parseColor(ctx, source, box) {
9
- if (typeof source === "string" && source.startsWith("linear-gradient")) {
10
- const { x0, y0, x1, y1, stops } = parseCssLinearGradient(source, box.left, box.top, box.width, box.height);
11
- const gradient = ctx.createLinearGradient(x0, y0, x1, y1);
12
- stops.forEach((stop) => gradient.addColorStop(stop.offset, stop.color));
13
- return gradient;
14
- }
15
- return source;
16
- }
17
- function uploadColor(style, box, ctx) {
18
- if (style?.color) {
19
- style.color = parseColor(ctx, style.color, box);
20
- }
21
- if (style?.backgroundColor) {
22
- style.backgroundColor = parseColor(ctx, style.backgroundColor, box);
23
- }
24
- if (style?.textStrokeColor) {
25
- style.textStrokeColor = parseColor(ctx, style.textStrokeColor, box);
26
- }
27
- }
28
- function parseCssLinearGradient(css, x, y, width, height) {
29
- const str = css.match(/linear-gradient\((.+)\)$/)?.[1] ?? "";
30
- const first = str.split(",")[0];
31
- const cssDeg = first.includes("deg") ? first : "0deg";
32
- const matched = str.replace(cssDeg, "").matchAll(/(#|rgba|rgb)(.+?) ([\d.]+%)/gi);
33
- const deg = Number(cssDeg.replace("deg", "")) || 0;
34
- const rad = deg * Math.PI / 180;
35
- const offsetX = width * Math.sin(rad);
36
- const offsetY = height * Math.cos(rad);
37
- return {
38
- x0: x + width / 2 - offsetX,
39
- y0: y + height / 2 + offsetY,
40
- x1: x + width / 2 + offsetX,
41
- y1: y + height / 2 - offsetY,
42
- stops: Array.from(matched).map((res) => {
43
- let color = res[2];
44
- if (color.startsWith("(")) {
45
- color = color.split(",").length > 3 ? `rgba${color}` : `rgb${color}`;
46
- } else {
47
- color = `#${color}`;
48
- }
49
- return {
50
- offset: Number(res[3].replace("%", "")) / 100,
51
- color
52
- };
53
- })
54
- };
55
- }
56
-
57
- function drawPath(options) {
58
- const { ctx, path, fontSize, clipRect } = options;
59
- ctx.save();
60
- ctx.beginPath();
61
- const pathStyle = path.style;
62
- const style = {
63
- ...pathStyle,
64
- fill: options.color ?? pathStyle.fill,
65
- stroke: options.textStrokeColor ?? pathStyle.stroke,
66
- strokeWidth: options.textStrokeWidth ? options.textStrokeWidth * fontSize : pathStyle.strokeWidth,
67
- strokeLinecap: "round",
68
- strokeLinejoin: "round",
69
- shadowOffsetX: (options.shadowOffsetX ?? 0) * fontSize,
70
- shadowOffsetY: (options.shadowOffsetY ?? 0) * fontSize,
71
- shadowBlur: (options.shadowBlur ?? 0) * fontSize,
72
- shadowColor: options.shadowColor
73
- };
74
- if (clipRect) {
75
- ctx.rect(clipRect.left, clipRect.top, clipRect.width, clipRect.height);
76
- ctx.clip();
77
- ctx.beginPath();
78
- }
79
- path.drawTo(ctx, style);
80
- ctx.restore();
81
- }
82
-
83
- function setupView(ctx, pixelRatio, boundingBox) {
84
- const { left, top, width, height } = boundingBox;
85
- const view = ctx.canvas;
86
- view.dataset.viewBox = String(`${left} ${top} ${width} ${height}`);
87
- view.dataset.pixelRatio = String(pixelRatio);
88
- const canvasWidth = width;
89
- const canvasHeight = height;
90
- view.width = Math.max(1, Math.ceil(canvasWidth * pixelRatio));
91
- view.height = Math.max(1, Math.ceil(canvasHeight * pixelRatio));
92
- view.style.width = `${canvasWidth}px`;
93
- view.style.height = `${canvasHeight}px`;
94
- ctx.clearRect(0, 0, view.width, view.height);
95
- ctx.scale(pixelRatio, pixelRatio);
96
- ctx.translate(-left, -top);
97
- }
98
-
99
- function uploadColors(ctx, text) {
100
- const { paragraphs, computedStyle: style, glyphBox } = text;
101
- uploadColor(style, glyphBox, ctx);
102
- paragraphs.forEach((paragraph) => {
103
- uploadColor(paragraph.computedStyle, paragraph.lineBox, ctx);
104
- paragraph.fragments.forEach((fragment) => {
105
- uploadColor(fragment.computedStyle, fragment.inlineBox, ctx);
106
- });
107
- });
108
- }
109
-
110
- const set1 = /* @__PURE__ */ new Set(["\xA9", "\xAE", "\xF7"]);
111
- const set2 = /* @__PURE__ */ new Set([
112
- "\u2014",
113
- "\u2026",
114
- "\u201C",
115
- "\u201D",
116
- "\uFE4F",
117
- "\uFE4B",
118
- "\uFE4C",
119
- "\u2018",
120
- "\u2019",
121
- "\u02DC"
122
- ]);
123
- const fsSelectionMap = {
124
- 1: "italic",
125
- 32: "bold"
126
- };
127
- const macStyleMap = {
128
- 1: "italic",
129
- 2: "bold"
130
- };
131
- const fontWeightMap = {
132
- 100: -0.2,
133
- 200: -0.1,
134
- 300: 0,
135
- 400: 0,
136
- normal: 0,
137
- 500: 0.1,
138
- 600: 0.2,
139
- 700: 0.3,
140
- bold: 0.3,
141
- 800: 0.4,
142
- 900: 0.5
143
- };
144
- class Character {
145
- constructor(content, index, parent) {
146
- this.content = content;
147
- this.index = index;
148
- this.parent = parent;
149
- }
150
- path = new modernPath2d.Path2D();
151
- lineBox = new modernPath2d.BoundingBox();
152
- inlineBox = new modernPath2d.BoundingBox();
153
- glyphBox;
154
- advanceWidth = 0;
155
- advanceHeight = 0;
156
- underlinePosition = 0;
157
- underlineThickness = 0;
158
- strikeoutPosition = 0;
159
- strikeoutSize = 0;
160
- ascender = 0;
161
- descender = 0;
162
- typoAscender = 0;
163
- typoDescender = 0;
164
- typoLineGap = 0;
165
- winAscent = 0;
166
- winDescent = 0;
167
- xHeight = 0;
168
- capHeight = 0;
169
- baseline = 0;
170
- centerDiviation = 0;
171
- fontStyle;
172
- get compatibleGlyphBox() {
173
- const size = this.computedStyle.fontSize * 0.8;
174
- return this.glyphBox ?? (this.isVertical ? new modernPath2d.BoundingBox(
175
- this.lineBox.left + this.lineBox.width / 2 - size / 2,
176
- this.lineBox.top,
177
- size,
178
- this.lineBox.height
179
- ) : new modernPath2d.BoundingBox(
180
- this.lineBox.left,
181
- this.lineBox.top + this.lineBox.height / 2 - size / 2,
182
- this.lineBox.width,
183
- size
184
- ));
185
- }
186
- get center() {
187
- return this.compatibleGlyphBox.center;
188
- }
189
- get computedStyle() {
190
- return this.parent.computedStyle;
191
- }
192
- get isVertical() {
193
- return this.computedStyle.writingMode.includes("vertical");
194
- }
195
- get fontSize() {
196
- return this.computedStyle.fontSize;
197
- }
198
- get fontHeight() {
199
- return this.fontSize * this.computedStyle.lineHeight;
200
- }
201
- _getFontSFNT(fonts) {
202
- const fontFamily = this.computedStyle.fontFamily;
203
- const _fonts = fonts ?? modernFont.fonts;
204
- const font = fontFamily ? _fonts.get(fontFamily) : _fonts.fallbackFont;
205
- return font?.getSFNT();
206
- }
207
- updateGlyph(sfnt = this._getFontSFNT()) {
208
- if (!sfnt) {
209
- return this;
210
- }
211
- const { hhea, os2, post, head } = sfnt;
212
- const unitsPerEm = head.unitsPerEm;
213
- const ascender = hhea.ascent;
214
- const descender = hhea.descent;
215
- const { content, computedStyle, isVertical } = this;
216
- const { fontSize } = computedStyle;
217
- const rate = unitsPerEm / fontSize;
218
- const advanceWidth = sfnt.getAdvanceWidth(content, fontSize);
219
- const advanceHeight = (ascender + Math.abs(descender)) / rate;
220
- const baseline = ascender / rate;
221
- this.advanceWidth = advanceWidth;
222
- this.advanceHeight = advanceHeight;
223
- this.inlineBox.width = isVertical ? advanceHeight : advanceWidth;
224
- this.inlineBox.height = isVertical ? advanceWidth : advanceHeight;
225
- this.underlinePosition = (ascender - post.underlinePosition) / rate;
226
- this.underlineThickness = post.underlineThickness / rate;
227
- this.strikeoutPosition = (ascender - os2.yStrikeoutPosition) / rate;
228
- this.strikeoutSize = os2.yStrikeoutSize / rate;
229
- this.ascender = ascender / rate;
230
- this.descender = descender / rate;
231
- this.typoAscender = os2.sTypoAscender / rate;
232
- this.typoDescender = os2.sTypoDescender / rate;
233
- this.typoLineGap = os2.sTypoLineGap / rate;
234
- this.winAscent = os2.usWinAscent / rate;
235
- this.winDescent = os2.usWinDescent / rate;
236
- this.xHeight = os2.sxHeight / rate;
237
- this.capHeight = os2.sCapHeight / rate;
238
- this.baseline = baseline;
239
- this.centerDiviation = advanceHeight / 2 - baseline;
240
- this.fontStyle = fsSelectionMap[os2.fsSelection] ?? macStyleMap[head.macStyle];
241
- return this;
242
- }
243
- update(fonts) {
244
- const sfnt = this._getFontSFNT(fonts);
245
- if (!sfnt) {
246
- return this;
247
- }
248
- this.updateGlyph(sfnt);
249
- const {
250
- isVertical,
251
- content,
252
- computedStyle: style,
253
- baseline,
254
- inlineBox,
255
- ascender,
256
- descender,
257
- typoAscender,
258
- fontStyle,
259
- advanceWidth,
260
- advanceHeight
261
- } = this;
262
- const { left, top } = inlineBox;
263
- const needsItalic = style.fontStyle === "italic" && fontStyle !== "italic";
264
- let x = left;
265
- let y = top + baseline;
266
- let glyphIndex;
267
- const path = new modernPath2d.Path2D();
268
- if (isVertical) {
269
- x += (advanceHeight - advanceWidth) / 2;
270
- if (Math.abs(advanceWidth - advanceHeight) > 0.1) {
271
- y -= (ascender - typoAscender) / (ascender + Math.abs(descender)) * advanceHeight;
272
- }
273
- glyphIndex = void 0;
274
- }
275
- if (isVertical && !set1.has(content) && (content.codePointAt(0) <= 256 || set2.has(content))) {
276
- path.addCommands(
277
- sfnt.getPathCommands(
278
- content,
279
- x,
280
- top + baseline - (advanceHeight - advanceWidth) / 2,
281
- style.fontSize
282
- )
283
- );
284
- const point = {
285
- y: top - (advanceHeight - advanceWidth) / 2 + advanceHeight / 2,
286
- x: x + advanceWidth / 2
287
- };
288
- if (needsItalic) {
289
- this._italic(
290
- path,
291
- isVertical ? {
292
- x: point.x,
293
- y: top - (advanceHeight - advanceWidth) / 2 + baseline
294
- } : void 0
295
- );
296
- }
297
- path.rotate(90, point);
298
- } else {
299
- if (glyphIndex !== void 0) {
300
- path.addCommands(
301
- sfnt.glyphs.get(glyphIndex).getPathCommands(x, y, style.fontSize)
302
- );
303
- if (needsItalic) {
304
- this._italic(
305
- path,
306
- isVertical ? {
307
- x: x + advanceWidth / 2,
308
- y: top + typoAscender / (ascender + Math.abs(descender)) * advanceHeight
309
- } : void 0
310
- );
311
- }
312
- } else {
313
- path.addCommands(sfnt.getPathCommands(content, x, y, style.fontSize));
314
- if (needsItalic) {
315
- this._italic(
316
- path,
317
- isVertical ? { x: x + advanceHeight / 2, y } : void 0
318
- );
319
- }
320
- }
321
- }
322
- const fontWeight = style.fontWeight ?? 400;
323
- if (fontWeight in fontWeightMap && ((fontWeight === 700 || fontWeight === "bold") && fontStyle !== "bold")) {
324
- path.bold(fontWeightMap[fontWeight] * style.fontSize * 0.05);
325
- }
326
- path.style = {
327
- fill: style.color,
328
- stroke: style.textStrokeWidth ? style.textStrokeColor : "none",
329
- strokeWidth: style.textStrokeWidth ? style.textStrokeWidth * style.fontSize * 0.03 : 0
330
- };
331
- this.path = path;
332
- this.glyphBox = this.getGlyphBoundingBox();
333
- return this;
334
- }
335
- _italic(path, startPoint) {
336
- path.skew(-0.24, 0, startPoint || {
337
- y: this.inlineBox.top + this.baseline,
338
- x: this.inlineBox.left + this.inlineBox.width / 2
339
- });
340
- }
341
- getGlyphMinMax(min, max, withStyle) {
342
- if (this.path.curves[0]?.curves.length) {
343
- return this.path.getMinMax(min, max, withStyle);
344
- } else {
345
- return void 0;
346
- }
347
- }
348
- getGlyphBoundingBox(withStyle) {
349
- const minMax = this.getGlyphMinMax(void 0, void 0, withStyle);
350
- if (!minMax) {
351
- return void 0;
352
- }
353
- const { min, max } = minMax;
354
- return new modernPath2d.BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
355
- }
356
- drawTo(ctx, config = {}) {
357
- const style = this.computedStyle;
358
- const options = {
359
- ctx,
360
- path: this.path,
361
- fontSize: style.fontSize,
362
- color: style.color,
363
- ...config
364
- };
365
- if (this.glyphBox) {
366
- drawPath(options);
367
- } else {
368
- ctx.save();
369
- ctx.beginPath();
370
- const pathStyle = this.path.style;
371
- const _style = {
372
- ...pathStyle,
373
- fill: options.color ?? pathStyle.fill,
374
- stroke: options.textStrokeColor ?? pathStyle.stroke,
375
- strokeWidth: options.textStrokeWidth ? options.textStrokeWidth * options.fontSize : pathStyle.strokeWidth,
376
- shadowOffsetX: (options.shadowOffsetX ?? 0) * options.fontSize,
377
- shadowOffsetY: (options.shadowOffsetY ?? 0) * options.fontSize,
378
- shadowBlur: (options.shadowBlur ?? 0) * options.fontSize,
379
- shadowColor: options.shadowColor
380
- };
381
- modernPath2d.setCanvasContext(ctx, _style);
382
- ctx.font = `${options.fontSize}px ${options.fontFamily}`;
383
- if (this.isVertical) {
384
- ctx.textBaseline = "middle";
385
- ctx.fillText(this.content, this.inlineBox.left, this.inlineBox.top + this.inlineBox.height / 2);
386
- } else {
387
- ctx.textBaseline = "alphabetic";
388
- ctx.fillText(this.content, this.inlineBox.left, this.inlineBox.top + this.baseline);
389
- }
390
- ctx.restore();
391
- }
392
- }
393
- }
394
-
395
- function createSVGLoader() {
396
- const loaded = /* @__PURE__ */ new Map();
397
- async function load(svg) {
398
- if (!loaded.has(svg)) {
399
- loaded.set(svg, svg);
400
- try {
401
- loaded.set(svg, await fetch(svg).then((rep) => rep.text()));
402
- } catch (err) {
403
- console.warn(err);
404
- loaded.delete(svg);
405
- }
406
- }
407
- }
408
- function needsLoad(source) {
409
- return source.startsWith("http://") || source.startsWith("https://") || source.startsWith("blob://");
410
- }
411
- return {
412
- loaded,
413
- needsLoad,
414
- load
415
- };
416
- }
417
-
418
- function createSVGParser(loader) {
419
- const parsed = /* @__PURE__ */ new Map();
420
- function parse(svg) {
421
- let result = parsed.get(svg);
422
- if (!result) {
423
- const dom = modernPath2d.svgToDom(
424
- loader.needsLoad(svg) ? loader.loaded.get(svg) ?? svg : svg
425
- );
426
- const pathSet = modernPath2d.svgToPath2DSet(dom);
427
- result = { dom, pathSet };
428
- parsed.set(svg, result);
429
- }
430
- return result;
431
- }
432
- return {
433
- parsed,
434
- parse
435
- };
436
- }
437
-
438
- function parseValueNumber(value, ctx) {
439
- if (typeof value === "number") {
440
- return value;
441
- } else {
442
- if (value.endsWith("%")) {
443
- value = value.substring(0, value.length - 1);
444
- return Math.ceil(Number(value) / 100 * ctx.total);
445
- } else if (value.endsWith("rem")) {
446
- value = value.substring(0, value.length - 3);
447
- return Number(value) * ctx.fontSize;
448
- } else if (value.endsWith("em")) {
449
- value = value.substring(0, value.length - 2);
450
- return Number(value) * ctx.fontSize;
451
- } else {
452
- return Number(value);
453
- }
454
- }
455
- }
456
- function parseColormap(colormap) {
457
- return modernIdoc.isNone(colormap) ? {} : colormap;
458
- }
459
- function isEqualObject(obj1, obj2) {
460
- const keys1 = Object.keys(obj1);
461
- const keys2 = Object.keys(obj2);
462
- const keys = Array.from(/* @__PURE__ */ new Set([...keys1, ...keys2]));
463
- return keys.every((key) => isEqualValue(obj1[key], obj2[key]));
464
- }
465
- function isEqualValue(val1, val2) {
466
- const typeof1 = typeof val1;
467
- const typeof2 = typeof val2;
468
- if (typeof1 === typeof2) {
469
- if (typeof1 === "object") {
470
- return isEqualObject(val1, val2);
471
- }
472
- return val1 === val2;
473
- }
474
- return false;
475
- }
476
- function hexToRgb(hex) {
477
- const cleanHex = hex.startsWith("#") ? hex.slice(1) : hex;
478
- const isValidHex = /^(?:[0-9A-F]{3}|[0-9A-F]{6})$/i.test(cleanHex);
479
- if (!isValidHex)
480
- return null;
481
- const fullHex = cleanHex.length === 3 ? cleanHex.split("").map((char) => char + char).join("") : cleanHex;
482
- const r = Number.parseInt(fullHex.slice(0, 2), 16);
483
- const g = Number.parseInt(fullHex.slice(2, 4), 16);
484
- const b = Number.parseInt(fullHex.slice(4, 6), 16);
485
- return `rgb(${r}, ${g}, ${b})`;
486
- }
487
- function filterEmpty(val) {
488
- if (!val)
489
- return val;
490
- const res = {};
491
- for (const key in val) {
492
- if (val[key] !== "" && val[key] !== void 0) {
493
- res[key] = val[key];
494
- }
495
- }
496
- return res;
497
- }
498
-
499
- class Fragment {
500
- constructor(content, style = {}, parent) {
501
- this.content = content;
502
- this.style = style;
503
- this.parent = parent;
504
- this.updateComputedStyle().initCharacters();
505
- }
506
- inlineBox = new modernPath2d.BoundingBox();
507
- get computedContent() {
508
- const style = this.computedStyle;
509
- return style.textTransform === "uppercase" ? this.content.toUpperCase() : style.textTransform === "lowercase" ? this.content.toLowerCase() : this.content;
510
- }
511
- updateComputedStyle() {
512
- this.computedStyle = {
513
- ...this.parent.computedStyle,
514
- ...filterEmpty(this.style)
515
- };
516
- return this;
517
- }
518
- initCharacters() {
519
- const characters = [];
520
- let index = 0;
521
- for (const c of this.computedContent) {
522
- characters.push(new Character(c, index++, this));
523
- }
524
- this.characters = characters;
525
- return this;
526
- }
527
- }
528
-
529
- class Paragraph {
530
- constructor(style, parentStyle) {
531
- this.style = style;
532
- this.parentStyle = parentStyle;
533
- this.updateComputedStyle();
534
- }
535
- lineBox = new modernPath2d.BoundingBox();
536
- fragments = [];
537
- updateComputedStyle() {
538
- this.computedStyle = {
539
- ...filterEmpty(this.parentStyle),
540
- ...filterEmpty(this.style)
541
- };
542
- return this;
543
- }
544
- addFragment(content, style) {
545
- const fragment = new Fragment(content, style, this);
546
- this.fragments.push(fragment);
547
- return fragment;
548
- }
549
- }
550
-
551
- function definePlugin(options) {
552
- return options;
553
- }
554
-
555
- class Measurer {
556
- static notZeroStyles = /* @__PURE__ */ new Set([
557
- "width",
558
- "height"
559
- ]);
560
- static pxStyles = /* @__PURE__ */ new Set([
561
- "width",
562
- "height",
563
- "fontSize",
564
- "letterSpacing",
565
- "textStrokeWidth",
566
- "textIndent",
567
- "shadowOffsetX",
568
- "shadowOffsetY",
569
- "shadowBlur",
570
- "margin",
571
- "marginLeft",
572
- "marginTop",
573
- "marginRight",
574
- "marginBottom",
575
- "padding",
576
- "paddingLeft",
577
- "paddingTop",
578
- "paddingRight",
579
- "paddingBottom"
580
- ]);
581
- _toDOMStyle(style) {
582
- const _style = {};
583
- for (const key in style) {
584
- const value = style[key];
585
- if (Measurer.notZeroStyles.has(key) && value === 0) ; else if (typeof value === "number" && Measurer.pxStyles.has(key)) {
586
- _style[key] = `${value}px`;
587
- } else {
588
- _style[key] = value;
589
- }
590
- }
591
- return _style;
592
- }
593
- /**
594
- * <section style="...">
595
- * <ul>
596
- * <li style="...">
597
- * <span style="...">...</span>
598
- * <span>...</span>
599
- * </li>
600
- * </ul>
601
- * </section>
602
- */
603
- createDOM(paragraphs, rootStyle) {
604
- const dom = document.createElement("section");
605
- const style = { ...rootStyle };
606
- const isHorizontal = rootStyle.writingMode.includes("horizontal");
607
- switch (rootStyle.textAlign) {
608
- case "start":
609
- case "left":
610
- style.justifyContent = "flex-start";
611
- break;
612
- case "center":
613
- style.justifyContent = "center";
614
- break;
615
- case "end":
616
- case "right":
617
- style.justifyContent = "flex-end";
618
- break;
619
- }
620
- switch (rootStyle.verticalAlign) {
621
- case "top":
622
- style.alignItems = "flex-start";
623
- break;
624
- case "middle":
625
- style.alignItems = "center";
626
- break;
627
- case "bottom":
628
- style.alignItems = "flex-end";
629
- break;
630
- }
631
- const isFlex = Boolean(style.justifyContent || style.alignItems);
632
- Object.assign(dom.style, {
633
- ...this._toDOMStyle({
634
- ...style,
635
- boxSizing: style.boxSizing ?? "border-box",
636
- display: style.display ?? (isFlex ? "inline-flex" : void 0),
637
- width: style.width ?? "max-content",
638
- height: style.height ?? "max-content"
639
- }),
640
- whiteSpace: "pre-wrap",
641
- wordBreak: "break-all"
642
- });
643
- const ul = document.createElement("ul");
644
- Object.assign(ul.style, {
645
- verticalAlign: "inherit",
646
- listStyleType: "inherit",
647
- padding: "0",
648
- margin: "0",
649
- width: isFlex && isHorizontal ? "100%" : void 0,
650
- height: isFlex && !isHorizontal ? "100%" : void 0
651
- });
652
- paragraphs.forEach((paragraph) => {
653
- const li = document.createElement("li");
654
- Object.assign(li.style, {
655
- verticalAlign: "inherit",
656
- ...this._toDOMStyle(paragraph.style)
657
- });
658
- paragraph.fragments.forEach((fragment) => {
659
- const span = document.createElement("span");
660
- Object.assign(span.style, {
661
- verticalAlign: "inherit",
662
- ...this._toDOMStyle(fragment.style)
663
- });
664
- span.appendChild(document.createTextNode(fragment.content));
665
- li.appendChild(span);
666
- });
667
- ul.appendChild(li);
668
- });
669
- dom.appendChild(ul);
670
- return dom;
671
- }
672
- measureDOMText(text) {
673
- const range = document.createRange();
674
- range.selectNodeContents(text);
675
- const data = text.data ?? "";
676
- let offset = 0;
677
- return Array.from(data).map((char) => {
678
- const start = offset += data.substring(offset).indexOf(char);
679
- const end = start + char.length;
680
- offset += char.length;
681
- range.setStart(text, Math.max(start, 0));
682
- range.setEnd(text, end);
683
- const rects = range.getClientRects?.() ?? [range.getBoundingClientRect()];
684
- let rect = rects[rects.length - 1];
685
- if (rects.length > 1 && rect.width < 2) {
686
- rect = rects[rects.length - 2];
687
- }
688
- const content = range.toString();
689
- if (content !== "" && rect && rect.width + rect.height !== 0) {
690
- return {
691
- content,
692
- top: rect.top,
693
- left: rect.left,
694
- height: rect.height,
695
- width: rect.width
696
- };
697
- }
698
- return void 0;
699
- }).filter(Boolean);
700
- }
701
- measureDOM(dom) {
702
- const paragraphs = [];
703
- const fragments = [];
704
- const characters = [];
705
- dom.querySelectorAll("li").forEach((pDOM, paragraphIndex) => {
706
- const pBox = pDOM.getBoundingClientRect();
707
- paragraphs.push({
708
- paragraphIndex,
709
- left: pBox.left,
710
- top: pBox.top,
711
- width: pBox.width,
712
- height: pBox.height
713
- });
714
- pDOM.querySelectorAll(":scope > *").forEach((fDOM, fragmentIndex) => {
715
- const fBox = fDOM.getBoundingClientRect();
716
- fragments.push({
717
- paragraphIndex,
718
- fragmentIndex,
719
- left: fBox.left,
720
- top: fBox.top,
721
- width: fBox.width,
722
- height: fBox.height
723
- });
724
- let characterIndex = 0;
725
- if (!fDOM.children.length && fDOM.firstChild instanceof window.Text) {
726
- this.measureDOMText(fDOM.firstChild).forEach((char) => {
727
- characters.push({
728
- ...char,
729
- newParagraphIndex: -1,
730
- paragraphIndex,
731
- fragmentIndex,
732
- characterIndex: characterIndex++,
733
- textWidth: -1,
734
- textHeight: -1
735
- });
736
- });
737
- } else {
738
- fDOM.querySelectorAll(":scope > *").forEach((cDOM) => {
739
- if (cDOM.firstChild instanceof window.Text) {
740
- this.measureDOMText(cDOM.firstChild).forEach((char) => {
741
- characters.push({
742
- ...char,
743
- newParagraphIndex: -1,
744
- paragraphIndex,
745
- fragmentIndex,
746
- characterIndex: characterIndex++,
747
- textWidth: -1,
748
- textHeight: -1
749
- });
750
- });
751
- }
752
- });
753
- }
754
- });
755
- });
756
- return {
757
- paragraphs,
758
- fragments,
759
- characters
760
- };
761
- }
762
- measureParagraphDOM(paragraphs, dom) {
763
- const rect = dom.getBoundingClientRect();
764
- const measured = this.measureDOM(dom);
765
- measured.paragraphs.forEach((p) => {
766
- const _p = paragraphs[p.paragraphIndex];
767
- _p.lineBox.left = p.left - rect.left;
768
- _p.lineBox.top = p.top - rect.top;
769
- _p.lineBox.width = p.width;
770
- _p.lineBox.height = p.height;
771
- });
772
- measured.fragments.forEach((f) => {
773
- const _f = paragraphs[f.paragraphIndex].fragments[f.fragmentIndex];
774
- _f.inlineBox.left = f.left - rect.left;
775
- _f.inlineBox.top = f.top - rect.top;
776
- _f.inlineBox.width = f.width;
777
- _f.inlineBox.height = f.height;
778
- });
779
- const results = [];
780
- let i = 0;
781
- measured.characters.forEach((character) => {
782
- const { paragraphIndex, fragmentIndex, characterIndex } = character;
783
- results.push({
784
- ...character,
785
- newParagraphIndex: paragraphIndex,
786
- left: character.left - rect.left,
787
- top: character.top - rect.top
788
- });
789
- const item = paragraphs[paragraphIndex].fragments[fragmentIndex].characters[characterIndex];
790
- const { fontHeight, isVertical } = item;
791
- const result = results[i];
792
- item.inlineBox.left = result.left;
793
- item.inlineBox.top = result.top;
794
- item.inlineBox.width = result.width;
795
- item.inlineBox.height = result.height;
796
- if (isVertical) {
797
- item.lineBox.left = result.left + (result.width - fontHeight) / 2;
798
- item.lineBox.top = result.top;
799
- item.lineBox.width = fontHeight;
800
- item.lineBox.height = result.height;
801
- } else {
802
- item.lineBox.left = result.left;
803
- item.lineBox.top = result.top + (result.height - fontHeight) / 2;
804
- item.lineBox.width = result.width;
805
- item.lineBox.height = fontHeight;
806
- }
807
- i++;
808
- });
809
- return {
810
- paragraphs,
811
- boundingBox: new modernPath2d.BoundingBox(0, 0, rect.width, rect.height)
812
- };
813
- }
814
- measure(paragraphs, rootStyle, dom) {
815
- let destory;
816
- if (!dom) {
817
- dom = this.createDOM(paragraphs, rootStyle);
818
- Object.assign(dom.style, {
819
- position: "fixed",
820
- visibility: "hidden"
821
- });
822
- document.body.appendChild(dom);
823
- destory = () => dom?.parentNode?.removeChild(dom);
824
- }
825
- const result = this.measureParagraphDOM(paragraphs, dom);
826
- destory?.();
827
- return result;
828
- }
829
- }
830
-
831
- function background() {
832
- const pathSet = new modernPath2d.Path2DSet();
833
- const loader = createSVGLoader();
834
- const parser = createSVGParser(loader);
835
- return {
836
- name: "background",
837
- pathSet,
838
- load: async (text) => {
839
- const { backgroundImage } = text.style;
840
- if (backgroundImage && loader.needsLoad(backgroundImage)) {
841
- await loader.load(backgroundImage);
842
- }
843
- },
844
- update: (text) => {
845
- pathSet.paths.length = 0;
846
- const { style, lineBox, isVertical } = text;
847
- const {
848
- backgroundImage,
849
- backgroundSize,
850
- backgroundColormap
851
- } = style;
852
- if (modernIdoc.isNone(backgroundImage))
853
- return;
854
- const { pathSet: imagePathSet } = parser.parse(backgroundImage);
855
- const imageBox = imagePathSet.getBoundingBox(true);
856
- let x, y, width, height;
857
- if (isVertical) {
858
- ({ x: y, y: x, width: height, height: width } = lineBox);
859
- } else {
860
- ({ x, y, width, height } = lineBox);
861
- }
862
- const colormap = parseColormap(backgroundColormap ?? "none");
863
- const paths = imagePathSet.paths.map((p) => {
864
- const cloned = p.clone();
865
- if (cloned.style.fill && cloned.style.fill in colormap) {
866
- cloned.style.fill = colormap[cloned.style.fill];
867
- }
868
- if (cloned.style.stroke && cloned.style.stroke in colormap) {
869
- cloned.style.stroke = colormap[cloned.style.stroke];
870
- }
871
- return cloned;
872
- });
873
- let scaleX;
874
- let scaleY;
875
- if (backgroundSize === "rigid") {
876
- scaleX = Math.max(text.fontSize * 5 / imageBox.width);
877
- scaleY = scaleX;
878
- const dist = new modernPath2d.Vector2();
879
- dist.x = imageBox.width - width / scaleX;
880
- dist.y = imageBox.height - height / scaleY;
881
- paths.forEach((path) => {
882
- path.applyTransform((p) => {
883
- const hasX = p.x > imageBox.left + imageBox.width / 2;
884
- const hasY = p.y > imageBox.top + imageBox.height / 2;
885
- if (hasX) {
886
- p.x -= dist.x;
887
- }
888
- if (hasY) {
889
- p.y -= dist.y;
890
- }
891
- });
892
- });
893
- } else {
894
- scaleX = width / imageBox.width;
895
- scaleY = height / imageBox.height;
896
- }
897
- const transform = new modernPath2d.Matrix3();
898
- transform.translate(-imageBox.x, -imageBox.y);
899
- transform.scale(scaleX, scaleY);
900
- if (isVertical) {
901
- transform.translate(-width / 2, -height / 2);
902
- transform.rotate(-Math.PI / 2);
903
- transform.translate(height / 2, width / 2);
904
- }
905
- transform.translate(x, y);
906
- paths.forEach((path) => {
907
- path.applyTransform((p) => {
908
- p.applyMatrix3(transform);
909
- });
910
- });
911
- pathSet.paths.push(...paths);
912
- },
913
- renderOrder: -2,
914
- render: (ctx, text) => {
915
- const { boundingBox, computedStyle: style } = text;
916
- if (!modernIdoc.isNone(style.backgroundColor)) {
917
- ctx.fillStyle = style.backgroundColor;
918
- ctx.fillRect(...boundingBox.array);
919
- }
920
- pathSet.paths.forEach((path) => {
921
- drawPath({ ctx, path, fontSize: style.fontSize });
922
- if (text.debug) {
923
- const box = new modernPath2d.Path2DSet([path]).getBoundingBox();
924
- if (box) {
925
- ctx.strokeRect(box.x, box.y, box.width, box.height);
926
- }
927
- }
928
- });
929
- text.paragraphs.forEach((p) => {
930
- const { lineBox, style: style2 } = p;
931
- if (!modernIdoc.isNone(style2.backgroundColor)) {
932
- ctx.fillStyle = style2.backgroundColor;
933
- ctx.fillRect(...lineBox.array);
934
- }
935
- p.fragments.forEach((f) => {
936
- const { inlineBox, style: style3 } = f;
937
- if (!modernIdoc.isNone(style3.backgroundColor)) {
938
- ctx.fillStyle = style3.backgroundColor;
939
- ctx.fillRect(...inlineBox.array);
940
- }
941
- });
942
- });
943
- }
944
- };
945
- }
946
-
947
- function getHighlightStyle(style) {
948
- const {
949
- highlight: highlight2,
950
- highlightImage,
951
- highlightReferImage,
952
- highlightColormap,
953
- highlightLine,
954
- highlightSize,
955
- highlightThickness
956
- } = style;
957
- return {
958
- image: highlight2?.image ?? highlightImage ?? "none",
959
- referImage: highlight2?.referImage ?? highlightReferImage ?? "none",
960
- colormap: highlight2?.colormap ?? highlightColormap ?? "none",
961
- line: highlight2?.line ?? highlightLine ?? "none",
962
- size: highlight2?.size ?? highlightSize ?? "cover",
963
- thickness: highlight2?.thickness ?? highlightThickness ?? "100%"
964
- };
965
- }
966
- function highlight() {
967
- const pathSet = new modernPath2d.Path2DSet();
968
- const clipRects = [];
969
- const loader = createSVGLoader();
970
- const parser = createSVGParser(loader);
971
- return definePlugin({
972
- name: "highlight",
973
- pathSet,
974
- load: async (text) => {
975
- const set = /* @__PURE__ */ new Set();
976
- text.forEachCharacter((character) => {
977
- const { computedStyle: style } = character;
978
- const { image, referImage } = getHighlightStyle(style);
979
- if (image && loader.needsLoad(image)) {
980
- set.add(image);
981
- }
982
- if (referImage && loader.needsLoad(referImage)) {
983
- set.add(referImage);
984
- }
985
- });
986
- await Promise.all(Array.from(set).map((src) => loader.load(src)));
987
- },
988
- update: (text) => {
989
- clipRects.length = 0;
990
- pathSet.paths.length = 0;
991
- let groups = [];
992
- let group;
993
- let prevHighlight;
994
- text.forEachCharacter((character) => {
995
- const {
996
- computedStyle: style
997
- } = character;
998
- const highlight2 = getHighlightStyle(style);
999
- const {
1000
- image,
1001
- colormap,
1002
- line,
1003
- size,
1004
- thickness
1005
- } = highlight2;
1006
- if (!modernIdoc.isNone(image)) {
1007
- const { inlineBox, isVertical } = character;
1008
- const { fontSize } = style;
1009
- if ((!prevHighlight || isEqualValue(prevHighlight.image, image) && isEqualValue(prevHighlight.colormap, colormap) && isEqualValue(prevHighlight.line, line) && isEqualValue(prevHighlight.size, size) && isEqualValue(prevHighlight.thickness, thickness)) && group?.length && (isVertical ? group[0].inlineBox.left === inlineBox.left : group[0].inlineBox.top === inlineBox.top) && group[0].fontSize === fontSize) {
1010
- group.push(character);
1011
- } else {
1012
- group = [];
1013
- group.push(character);
1014
- groups.push(group);
1015
- }
1016
- } else {
1017
- if (group?.length) {
1018
- group = [];
1019
- groups.push(group);
1020
- }
1021
- }
1022
- prevHighlight = highlight2;
1023
- });
1024
- groups = groups.filter((characters) => characters.length);
1025
- for (let i = 0; i < groups.length; i++) {
1026
- const characters = groups[i];
1027
- const char = characters[0];
1028
- const groupBox = modernPath2d.BoundingBox.from(...characters.map((c) => c.compatibleGlyphBox));
1029
- if (!groupBox.height || !groupBox.width) {
1030
- continue;
1031
- }
1032
- const {
1033
- computedStyle: style,
1034
- isVertical,
1035
- inlineBox,
1036
- compatibleGlyphBox: glyphBox,
1037
- strikeoutPosition,
1038
- underlinePosition
1039
- } = char;
1040
- const { fontSize } = style;
1041
- const {
1042
- image,
1043
- referImage,
1044
- colormap,
1045
- line,
1046
- size,
1047
- thickness
1048
- } = getHighlightStyle(style);
1049
- const _thickness = parseValueNumber(thickness, { fontSize, total: groupBox.width }) / groupBox.width;
1050
- const _colormap = parseColormap(colormap);
1051
- const { pathSet: imagePathSet, dom: imageDom } = parser.parse(image);
1052
- const imageBox = imagePathSet.getBoundingBox(true);
1053
- const styleScale = fontSize / imageBox.width * 2;
1054
- const targetBox = new modernPath2d.BoundingBox().copy(groupBox);
1055
- if (isVertical) {
1056
- targetBox.width = groupBox.height;
1057
- targetBox.height = groupBox.width;
1058
- targetBox.left = groupBox.left + groupBox.width;
1059
- }
1060
- const rawWidth = Math.floor(targetBox.width);
1061
- let userWidth = rawWidth;
1062
- if (size !== "cover") {
1063
- userWidth = parseValueNumber(size, { fontSize, total: groupBox.width }) || rawWidth;
1064
- targetBox.width = userWidth;
1065
- }
1066
- const hasReferImage = !modernIdoc.isNone(referImage) && modernIdoc.isNone(line);
1067
- if (hasReferImage) {
1068
- imageBox.copy(
1069
- parser.parse(referImage).pathSet.getBoundingBox(true)
1070
- );
1071
- } else {
1072
- let _line;
1073
- if (modernIdoc.isNone(line)) {
1074
- if (imageBox.width / imageBox.height > 4) {
1075
- _line = "underline";
1076
- const viewBox = imageDom.getAttribute("viewBox");
1077
- if (viewBox) {
1078
- const [_x, y, _w, h] = viewBox.split(" ").map((v) => Number(v));
1079
- const viewCenter = y + h / 2;
1080
- if (imageBox.y < viewCenter && imageBox.y + imageBox.height > viewCenter) {
1081
- _line = "line-through";
1082
- } else if (imageBox.y + imageBox.height < viewCenter) {
1083
- _line = "overline";
1084
- } else {
1085
- _line = "underline";
1086
- }
1087
- }
1088
- } else {
1089
- _line = "outline";
1090
- }
1091
- } else {
1092
- _line = line;
1093
- }
1094
- switch (_line) {
1095
- case "outline": {
1096
- const paddingX = targetBox.width * 0.2;
1097
- const paddingY = targetBox.height * 0.2;
1098
- if (isVertical) {
1099
- targetBox.x -= paddingY / 2;
1100
- targetBox.y -= paddingX / 2;
1101
- targetBox.x -= targetBox.height;
1102
- } else {
1103
- targetBox.x -= paddingX / 2;
1104
- targetBox.y -= paddingY / 2;
1105
- }
1106
- targetBox.width += paddingX;
1107
- targetBox.height += paddingY;
1108
- break;
1109
- }
1110
- case "overline":
1111
- targetBox.height = imageBox.height * styleScale;
1112
- if (isVertical) {
1113
- targetBox.x = inlineBox.left + inlineBox.width;
1114
- } else {
1115
- targetBox.y = inlineBox.top;
1116
- }
1117
- break;
1118
- case "line-through":
1119
- targetBox.height = imageBox.height * styleScale;
1120
- if (isVertical) {
1121
- targetBox.x = inlineBox.left + inlineBox.width - strikeoutPosition + targetBox.height / 2;
1122
- } else {
1123
- targetBox.y = inlineBox.top + strikeoutPosition - targetBox.height / 2;
1124
- }
1125
- break;
1126
- case "underline":
1127
- targetBox.height = imageBox.height * styleScale;
1128
- if (isVertical) {
1129
- targetBox.x = glyphBox.left + glyphBox.width - underlinePosition;
1130
- } else {
1131
- targetBox.y = inlineBox.top + underlinePosition;
1132
- }
1133
- break;
1134
- }
1135
- }
1136
- const transform = new modernPath2d.Matrix3();
1137
- transform.translate(-imageBox.x, -imageBox.y);
1138
- transform.scale(targetBox.width / imageBox.width, targetBox.height / imageBox.height);
1139
- if (isVertical) {
1140
- const tx = targetBox.width / 2;
1141
- const ty = targetBox.height / 2;
1142
- if (!hasReferImage) {
1143
- transform.translate(-tx, -ty);
1144
- }
1145
- transform.rotate(-Math.PI / 2);
1146
- if (!hasReferImage) {
1147
- transform.translate(ty, tx);
1148
- }
1149
- }
1150
- transform.translate(targetBox.x, targetBox.y);
1151
- for (let i2 = 0; i2 < Math.ceil(rawWidth / userWidth); i2++) {
1152
- const _transform = transform.clone();
1153
- if (isVertical) {
1154
- _transform.translate(0, i2 * targetBox.width);
1155
- } else {
1156
- _transform.translate(i2 * targetBox.width, 0);
1157
- }
1158
- imagePathSet.paths.forEach((originalPath) => {
1159
- const path = originalPath.clone().applyTransform(_transform);
1160
- if (path.style.strokeWidth)
1161
- path.style.strokeWidth *= styleScale * _thickness;
1162
- if (path.style.strokeMiterlimit)
1163
- path.style.strokeMiterlimit *= styleScale;
1164
- if (path.style.strokeDashoffset)
1165
- path.style.strokeDashoffset *= styleScale;
1166
- if (path.style.strokeDasharray)
1167
- path.style.strokeDasharray = path.style.strokeDasharray.map((v) => v * styleScale);
1168
- if (path.style.fill && path.style.fill in _colormap) {
1169
- path.style.fill = _colormap[path.style.fill];
1170
- }
1171
- if (path.style.stroke && path.style.stroke in _colormap) {
1172
- path.style.stroke = _colormap[path.style.stroke];
1173
- }
1174
- pathSet.paths.push(path);
1175
- if (rawWidth !== userWidth) {
1176
- if (isVertical) {
1177
- clipRects[pathSet.paths.length - 1] = new modernPath2d.BoundingBox(
1178
- groupBox.left - groupBox.width * 2,
1179
- groupBox.top,
1180
- groupBox.width * 4,
1181
- groupBox.height
1182
- );
1183
- } else {
1184
- clipRects[pathSet.paths.length - 1] = new modernPath2d.BoundingBox(
1185
- groupBox.left,
1186
- groupBox.top - groupBox.height * 2,
1187
- groupBox.width,
1188
- groupBox.height * 4
1189
- );
1190
- }
1191
- }
1192
- });
1193
- }
1194
- }
1195
- },
1196
- renderOrder: -1,
1197
- getBoundingBox: () => {
1198
- const boundingBoxs = [];
1199
- pathSet.paths.forEach((path, index) => {
1200
- const clipRect = clipRects[index];
1201
- let box = path.getBoundingBox();
1202
- if (clipRect) {
1203
- const x = Math.max(box.x, clipRect.x);
1204
- const y = Math.max(box.y, clipRect.y);
1205
- const right = Math.min(box.right, clipRect.right);
1206
- const bottom = Math.min(box.bottom, clipRect.bottom);
1207
- box = new modernPath2d.BoundingBox(x, y, right - x, bottom - y);
1208
- }
1209
- boundingBoxs.push(box);
1210
- });
1211
- return modernPath2d.BoundingBox.from(...boundingBoxs);
1212
- },
1213
- render: (ctx, text) => {
1214
- pathSet.paths.forEach((path, index) => {
1215
- drawPath({
1216
- ctx,
1217
- path,
1218
- fontSize: text.computedStyle.fontSize,
1219
- clipRect: clipRects[index]
1220
- });
1221
- if (text.debug) {
1222
- const box = new modernPath2d.Path2DSet([path]).getBoundingBox();
1223
- if (box) {
1224
- ctx.strokeRect(box.x, box.y, box.width, box.height);
1225
- }
1226
- }
1227
- });
1228
- }
1229
- });
1230
- }
1231
-
1232
- function genDisc(r, color) {
1233
- return `<svg width="${r * 2}" height="${r * 2}" xmlns="http://www.w3.org/2000/svg">
1234
- <circle cx="${r}" cy="${r}" r="${r}" fill="${color}" />
1235
- </svg>`;
1236
- }
1237
- function listStyle() {
1238
- const pathSet = new modernPath2d.Path2DSet();
1239
- return definePlugin({
1240
- name: "listStyle",
1241
- pathSet,
1242
- update: (text) => {
1243
- pathSet.paths.length = 0;
1244
- const { paragraphs, isVertical, fontSize } = text;
1245
- const padding = fontSize * 0.45;
1246
- paragraphs.forEach((paragraph) => {
1247
- const {
1248
- computedStyle: style
1249
- } = paragraph;
1250
- const {
1251
- color,
1252
- listStyleImage,
1253
- listStyleColormap,
1254
- listStyleSize,
1255
- listStyleType
1256
- } = style;
1257
- const colormap = parseColormap(listStyleColormap);
1258
- let size = listStyleSize;
1259
- let image;
1260
- if (!modernIdoc.isNone(listStyleImage)) {
1261
- image = listStyleImage;
1262
- } else if (!modernIdoc.isNone(listStyleType)) {
1263
- const r = fontSize * 0.38 / 2;
1264
- size = size === "cover" ? r * 2 : size;
1265
- switch (listStyleType) {
1266
- case "disc":
1267
- image = genDisc(r, String(color));
1268
- break;
1269
- }
1270
- }
1271
- if (!image) {
1272
- return;
1273
- }
1274
- const imagePathSet = modernPath2d.svgToPath2DSet(image);
1275
- const imageBox = imagePathSet.getBoundingBox();
1276
- const char = paragraph.fragments[0]?.characters[0];
1277
- if (!char) {
1278
- return;
1279
- }
1280
- const { inlineBox } = char;
1281
- const scale = size === "cover" ? 1 : parseValueNumber(size, { total: fontSize, fontSize }) / fontSize;
1282
- const m = new modernPath2d.Matrix3();
1283
- if (isVertical) {
1284
- const _scale = fontSize / imageBox.height * scale;
1285
- m.translate(-imageBox.left, -imageBox.top).rotate(Math.PI / 2).scale(_scale, _scale).translate(
1286
- inlineBox.left + (inlineBox.width - imageBox.height * _scale) / 2,
1287
- inlineBox.top - padding
1288
- );
1289
- } else {
1290
- const _scale = fontSize / imageBox.height * scale;
1291
- m.translate(-imageBox.left, -imageBox.top).scale(_scale, _scale).translate(
1292
- inlineBox.left - imageBox.width * _scale - padding,
1293
- inlineBox.top + (inlineBox.height - imageBox.height * _scale) / 2
1294
- );
1295
- }
1296
- pathSet.paths.push(...imagePathSet.paths.map((p) => {
1297
- const path = p.clone();
1298
- path.applyTransform(m);
1299
- if (path.style.fill && path.style.fill in colormap) {
1300
- path.style.fill = colormap[path.style.fill];
1301
- }
1302
- if (path.style.stroke && path.style.stroke in colormap) {
1303
- path.style.stroke = colormap[path.style.stroke];
1304
- }
1305
- return path;
1306
- }));
1307
- });
1308
- }
1309
- });
1310
- }
1311
-
1312
- function outline() {
1313
- return {
1314
- name: "outline"
1315
- // TODO
1316
- };
1317
- }
1318
-
1319
- const tempV1 = new modernPath2d.Vector2();
1320
- const tempM1 = new modernPath2d.Matrix3();
1321
- const tempM2 = new modernPath2d.Matrix3();
1322
- function render() {
1323
- return definePlugin({
1324
- name: "render",
1325
- getBoundingBox: (text) => {
1326
- const { characters, fontSize, effects } = text;
1327
- const boxes = [];
1328
- characters.forEach((character) => {
1329
- effects?.forEach((style) => {
1330
- if (!character.glyphBox) {
1331
- return;
1332
- }
1333
- const aabb = character.glyphBox.clone();
1334
- const m = getTransform2D(text, style);
1335
- tempV1.set(aabb.left, aabb.top);
1336
- tempV1.applyMatrix3(m);
1337
- aabb.left = tempV1.x;
1338
- aabb.top = tempV1.y;
1339
- tempV1.set(aabb.right, aabb.bottom);
1340
- tempV1.applyMatrix3(m);
1341
- aabb.width = tempV1.x - aabb.left;
1342
- aabb.height = tempV1.y - aabb.top;
1343
- const shadowOffsetX = (style.shadowOffsetX ?? 0) * fontSize;
1344
- const shadowOffsetY = (style.shadowOffsetY ?? 0) * fontSize;
1345
- const textStrokeWidth = Math.max(0.1, style.textStrokeWidth ?? 0) * fontSize;
1346
- aabb.left += shadowOffsetX - textStrokeWidth;
1347
- aabb.top += shadowOffsetY - textStrokeWidth;
1348
- aabb.width += textStrokeWidth * 2;
1349
- aabb.height += textStrokeWidth * 2;
1350
- boxes.push(aabb);
1351
- });
1352
- });
1353
- return boxes.length ? modernPath2d.BoundingBox.from(...boxes) : void 0;
1354
- },
1355
- render: (ctx, text) => {
1356
- const { paragraphs, glyphBox, effects } = text;
1357
- if (effects) {
1358
- effects.forEach((style) => {
1359
- uploadColor(style, glyphBox, ctx);
1360
- ctx.save();
1361
- const [a, c, e, b, d, f] = getTransform2D(text, style).transpose().elements;
1362
- ctx.transform(a, b, c, d, e, f);
1363
- text.forEachCharacter((character) => {
1364
- character.drawTo(ctx, style);
1365
- });
1366
- ctx.restore();
1367
- });
1368
- } else {
1369
- paragraphs.forEach((paragraph) => {
1370
- paragraph.fragments.forEach((fragment) => {
1371
- fragment.characters.forEach((character) => {
1372
- character.drawTo(ctx);
1373
- });
1374
- });
1375
- });
1376
- }
1377
- if (text.debug) {
1378
- paragraphs.forEach((paragraph) => {
1379
- ctx.strokeRect(paragraph.lineBox.x, paragraph.lineBox.y, paragraph.lineBox.width, paragraph.lineBox.height);
1380
- });
1381
- }
1382
- }
1383
- });
1384
- }
1385
- function getTransform2D(text, style) {
1386
- const { fontSize, glyphBox } = text;
1387
- const translateX = (style.translateX ?? 0) * fontSize;
1388
- const translateY = (style.translateY ?? 0) * fontSize;
1389
- const PI_2 = Math.PI * 2;
1390
- const skewX = (style.skewX ?? 0) / 360 * PI_2;
1391
- const skewY = (style.skewY ?? 0) / 360 * PI_2;
1392
- const { left, top, width, height } = glyphBox;
1393
- const centerX = left + width / 2;
1394
- const centerY = top + height / 2;
1395
- tempM1.identity();
1396
- tempM2.makeTranslation(translateX, translateY);
1397
- tempM1.multiply(tempM2);
1398
- tempM2.makeTranslation(centerX, centerY);
1399
- tempM1.multiply(tempM2);
1400
- tempM2.set(1, Math.tan(skewX), 0, Math.tan(skewY), 1, 0, 0, 0, 1);
1401
- tempM1.multiply(tempM2);
1402
- tempM2.makeTranslation(-centerX, -centerY);
1403
- tempM1.multiply(tempM2);
1404
- return tempM1.clone();
1405
- }
1406
-
1407
- function textDecoration() {
1408
- const pathSet = new modernPath2d.Path2DSet();
1409
- return definePlugin({
1410
- name: "textDecoration",
1411
- pathSet,
1412
- update: (text) => {
1413
- pathSet.paths.length = 0;
1414
- const groups = [];
1415
- let group;
1416
- let prevStyle;
1417
- text.forEachCharacter((character) => {
1418
- const {
1419
- computedStyle: style,
1420
- isVertical,
1421
- inlineBox,
1422
- underlinePosition,
1423
- underlineThickness,
1424
- strikeoutPosition,
1425
- strikeoutSize
1426
- } = character;
1427
- const { color, textDecoration: textDecoration2, writingMode } = style;
1428
- if (!modernIdoc.isNone(textDecoration2)) {
1429
- let flag = false;
1430
- if (prevStyle?.textDecoration === textDecoration2 && prevStyle?.writingMode === writingMode && prevStyle?.color === color && (isVertical ? group[0].inlineBox.left === inlineBox.left : group[0].inlineBox.top === inlineBox.top)) {
1431
- switch (textDecoration2) {
1432
- case "underline":
1433
- if (group[0].underlinePosition === underlinePosition && group[0].underlineThickness === underlineThickness) {
1434
- flag = true;
1435
- }
1436
- break;
1437
- case "line-through":
1438
- if (group[0].strikeoutPosition === strikeoutPosition && group[0].strikeoutSize === strikeoutSize) {
1439
- flag = true;
1440
- }
1441
- break;
1442
- }
1443
- }
1444
- if (flag) {
1445
- group.push(character);
1446
- } else {
1447
- group = [];
1448
- group.push(character);
1449
- groups.push(group);
1450
- }
1451
- prevStyle = style;
1452
- } else {
1453
- prevStyle = void 0;
1454
- }
1455
- });
1456
- groups.forEach((group2) => {
1457
- const {
1458
- computedStyle: style,
1459
- isVertical,
1460
- underlinePosition,
1461
- underlineThickness,
1462
- strikeoutPosition,
1463
- strikeoutSize
1464
- } = group2[0];
1465
- const {
1466
- color,
1467
- textDecoration: textDecoration2
1468
- } = style;
1469
- const inlineBox = modernPath2d.BoundingBox.from(...group2.map((c) => c.inlineBox));
1470
- const { left, top, width, height } = inlineBox;
1471
- let position = isVertical ? left + width : top;
1472
- const direction = isVertical ? -1 : 1;
1473
- let thickness = 0;
1474
- switch (textDecoration2) {
1475
- case "overline":
1476
- thickness = underlineThickness * 2;
1477
- break;
1478
- case "underline":
1479
- position += direction * underlinePosition;
1480
- thickness = underlineThickness * 2;
1481
- break;
1482
- case "line-through":
1483
- position += direction * strikeoutPosition;
1484
- thickness = strikeoutSize * 2;
1485
- break;
1486
- }
1487
- position -= thickness;
1488
- let path;
1489
- if (isVertical) {
1490
- path = new modernPath2d.Path2D([
1491
- { type: "M", x: position, y: top },
1492
- { type: "L", x: position, y: top + height },
1493
- { type: "L", x: position + thickness, y: top + height },
1494
- { type: "L", x: position + thickness, y: top },
1495
- { type: "Z" }
1496
- ], {
1497
- fill: color
1498
- });
1499
- } else {
1500
- path = new modernPath2d.Path2D([
1501
- { type: "M", x: left, y: position },
1502
- { type: "L", x: left + width, y: position },
1503
- { type: "L", x: left + width, y: position + thickness },
1504
- { type: "L", x: left, y: position + thickness },
1505
- { type: "Z" }
1506
- ], {
1507
- fill: color
1508
- });
1509
- }
1510
- pathSet.paths.push(path);
1511
- });
1512
- },
1513
- render: (ctx, text) => {
1514
- const { effects, computedStyle: style } = text;
1515
- if (effects) {
1516
- effects.forEach((effectStyle) => {
1517
- ctx.save();
1518
- const [a, c, e, b, d, f] = getTransform2D(text, effectStyle).transpose().elements;
1519
- ctx.transform(a, b, c, d, e, f);
1520
- pathSet.paths.forEach((path) => {
1521
- drawPath({
1522
- ctx,
1523
- path,
1524
- fontSize: style.fontSize,
1525
- ...effectStyle
1526
- });
1527
- });
1528
- ctx.restore();
1529
- });
1530
- } else {
1531
- pathSet.paths.forEach((path) => {
1532
- drawPath({
1533
- ctx,
1534
- path,
1535
- fontSize: style.fontSize
1536
- });
1537
- });
1538
- }
1539
- }
1540
- });
1541
- }
1542
-
1543
- var __defProp$1 = Object.defineProperty;
1544
- var __decorateClass$1 = (decorators, target, key, kind) => {
1545
- var result = void 0 ;
1546
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
1547
- if (decorator = decorators[i])
1548
- result = (decorator(target, key, result) ) || result;
1549
- if (result) __defProp$1(target, key, result);
1550
- return result;
1551
- };
1552
- const textDefaultStyle = modernIdoc.getDefaultStyle();
1553
- class Text extends modernIdoc.EventEmitter {
1554
- needsUpdate = true;
1555
- computedStyle = { ...textDefaultStyle };
1556
- paragraphs = [];
1557
- lineBox = new modernPath2d.BoundingBox();
1558
- rawGlyphBox = new modernPath2d.BoundingBox();
1559
- glyphBox = new modernPath2d.BoundingBox();
1560
- pathBox = new modernPath2d.BoundingBox();
1561
- boundingBox = new modernPath2d.BoundingBox();
1562
- measurer = new Measurer();
1563
- plugins = /* @__PURE__ */ new Map();
1564
- get fontSize() {
1565
- return this.computedStyle.fontSize;
1566
- }
1567
- get isVertical() {
1568
- return this.computedStyle.writingMode.includes("vertical");
1569
- }
1570
- get characters() {
1571
- return this.paragraphs.flatMap((p) => p.fragments.flatMap((f) => f.characters));
1572
- }
1573
- constructor(options = {}) {
1574
- super();
1575
- this.set(options);
1576
- }
1577
- set(options = {}) {
1578
- this.debug = options.debug ?? false;
1579
- this.content = modernIdoc.normalizeTextContent(options.content ?? "");
1580
- this.effects = options.effects;
1581
- this.style = options.style ?? {};
1582
- this.measureDOM = options.measureDOM;
1583
- this.fonts = options.fonts;
1584
- this.use(background()).use(outline()).use(listStyle()).use(textDecoration()).use(highlight()).use(render());
1585
- (options.plugins ?? []).forEach((plugin) => {
1586
- this.use(plugin);
1587
- });
1588
- this.updateParagraphs();
1589
- }
1590
- onUpdateProperty(key, newValue, oldValue, declaration) {
1591
- this.emit("updateProperty", key, newValue, oldValue, declaration);
1592
- }
1593
- use(plugin) {
1594
- this.plugins.set(plugin.name, plugin);
1595
- return this;
1596
- }
1597
- forEachCharacter(handle) {
1598
- this.paragraphs.forEach((p, paragraphIndex) => {
1599
- p.fragments.forEach((f, fragmentIndex) => {
1600
- f.characters.forEach((c, characterIndex) => {
1601
- handle(c, { paragraphIndex, fragmentIndex, characterIndex });
1602
- });
1603
- });
1604
- });
1605
- return this;
1606
- }
1607
- async load() {
1608
- await Promise.all(Array.from(this.plugins.values()).map((p) => p.load?.(this)));
1609
- }
1610
- updateParagraphs() {
1611
- this.computedStyle = { ...textDefaultStyle, ...this.style };
1612
- const { content, computedStyle: style } = this;
1613
- const paragraphs = [];
1614
- content.forEach((p) => {
1615
- const { fragments, ...pStyle } = p;
1616
- const paragraph = new Paragraph(pStyle, style);
1617
- fragments.forEach((f) => {
1618
- const { content: content2, ...fStyle } = f;
1619
- if (content2 !== void 0) {
1620
- paragraph.addFragment(content2, fStyle);
1621
- }
1622
- });
1623
- paragraphs.push(paragraph);
1624
- });
1625
- this.paragraphs = paragraphs;
1626
- return this;
1627
- }
1628
- createDOM() {
1629
- this.updateParagraphs();
1630
- return this.measurer.createDOM(this.paragraphs, this.computedStyle);
1631
- }
1632
- measure(dom = this.measureDOM) {
1633
- const old = {
1634
- paragraphs: this.paragraphs,
1635
- lineBox: this.lineBox,
1636
- rawGlyphBox: this.rawGlyphBox,
1637
- glyphBox: this.glyphBox,
1638
- pathBox: this.pathBox,
1639
- boundingBox: this.boundingBox
1640
- };
1641
- this.updateParagraphs();
1642
- const result = this.measurer.measure(this.paragraphs, this.computedStyle, dom);
1643
- this.paragraphs = result.paragraphs;
1644
- this.lineBox = result.boundingBox;
1645
- this.characters.forEach((c) => {
1646
- c.update(this.fonts);
1647
- });
1648
- this.rawGlyphBox = this.getGlyphBox();
1649
- Array.from(this.plugins.values()).sort((a, b) => (a.updateOrder ?? 0) - (b.updateOrder ?? 0)).forEach((plugin) => {
1650
- plugin.update?.(this);
1651
- });
1652
- this.glyphBox = this.getGlyphBox();
1653
- this.updatePathBox().updateBoundingBox();
1654
- for (const key in old) {
1655
- result[key] = this[key];
1656
- this[key] = old[key];
1657
- }
1658
- this.emit("measure", { text: this, result });
1659
- return result;
1660
- }
1661
- getGlyphBox() {
1662
- const min = modernPath2d.Vector2.MAX;
1663
- const max = modernPath2d.Vector2.MIN;
1664
- this.characters.forEach((c) => {
1665
- if (!c.getGlyphMinMax(min, max)) {
1666
- const { inlineBox: glyphBox } = c;
1667
- const { left, top, width, height } = glyphBox;
1668
- const a = new modernPath2d.Vector2(left, top);
1669
- const b = new modernPath2d.Vector2(left + width, top + height);
1670
- min.min(a, b);
1671
- max.max(a, b);
1672
- }
1673
- });
1674
- return new modernPath2d.BoundingBox(
1675
- min.x,
1676
- min.y,
1677
- max.x - min.x,
1678
- max.y - min.y
1679
- );
1680
- }
1681
- updatePathBox() {
1682
- this.pathBox = modernPath2d.BoundingBox.from(
1683
- this.glyphBox,
1684
- ...Array.from(this.plugins.values()).map((plugin) => {
1685
- return plugin.getBoundingBox ? plugin.getBoundingBox(this) : plugin.pathSet?.getBoundingBox();
1686
- }).filter(Boolean)
1687
- );
1688
- return this;
1689
- }
1690
- updateBoundingBox() {
1691
- this.boundingBox = modernPath2d.BoundingBox.from(
1692
- this.rawGlyphBox,
1693
- this.lineBox,
1694
- this.pathBox
1695
- );
1696
- return this;
1697
- }
1698
- requestUpdate() {
1699
- this.needsUpdate = true;
1700
- return this;
1701
- }
1702
- update(dom = this.measureDOM) {
1703
- const result = this.measure(dom);
1704
- for (const key in result) {
1705
- this[key] = result[key];
1706
- }
1707
- this.emit("update", { text: this });
1708
- this.needsUpdate = false;
1709
- return this;
1710
- }
1711
- render(options) {
1712
- const { view, pixelRatio = 2 } = options;
1713
- const ctx = view.getContext("2d");
1714
- if (!ctx) {
1715
- return;
1716
- }
1717
- if (this.needsUpdate) {
1718
- this.update();
1719
- }
1720
- setupView(ctx, pixelRatio, this.boundingBox);
1721
- uploadColors(ctx, this);
1722
- Array.from(this.plugins.values()).sort((a, b) => (a.renderOrder ?? 0) - (b.renderOrder ?? 0)).forEach((plugin) => {
1723
- if (plugin.render) {
1724
- plugin.render?.(ctx, this);
1725
- } else if (plugin.pathSet) {
1726
- const style = this.computedStyle;
1727
- plugin.pathSet.paths.forEach((path) => {
1728
- drawPath({
1729
- ctx,
1730
- path,
1731
- fontSize: style.fontSize
1732
- });
1733
- });
1734
- }
1735
- });
1736
- this.emit("render", { text: this, view, pixelRatio });
1737
- options.onContext?.(ctx);
1738
- }
1739
- toString() {
1740
- return this.content.flatMap((p) => p.fragments.map((f) => f.content)).join("");
1741
- }
1742
- }
1743
- __decorateClass$1([
1744
- modernIdoc.property()
1745
- ], Text.prototype, "debug");
1746
- __decorateClass$1([
1747
- modernIdoc.property()
1748
- ], Text.prototype, "content");
1749
- __decorateClass$1([
1750
- modernIdoc.property()
1751
- ], Text.prototype, "effects");
1752
- __decorateClass$1([
1753
- modernIdoc.property()
1754
- ], Text.prototype, "style");
1755
- __decorateClass$1([
1756
- modernIdoc.property()
1757
- ], Text.prototype, "measureDOM");
1758
- __decorateClass$1([
1759
- modernIdoc.property()
1760
- ], Text.prototype, "fonts");
3
+ const Text = require('./shared/modern-text.Dr7A9mkS.cjs');
4
+ require('modern-idoc');
5
+ require('modern-path2d');
6
+ require('modern-font');
1761
7
 
1762
8
  function measureText(options, load) {
1763
- const text = new Text(options);
9
+ const text = new Text.Text(options);
1764
10
  if (load) {
1765
11
  return text.load().then(() => {
1766
12
  return text.measure();
@@ -1770,7 +16,7 @@ function measureText(options, load) {
1770
16
  }
1771
17
 
1772
18
  function renderText(options, load) {
1773
- const text = new Text(options);
19
+ const text = new Text.Text(options);
1774
20
  if (load) {
1775
21
  return text.load().then(() => {
1776
22
  return text.render(options);
@@ -1779,551 +25,33 @@ function renderText(options, load) {
1779
25
  return text.render(options);
1780
26
  }
1781
27
 
1782
- var __defProp = Object.defineProperty;
1783
- var __decorateClass = (decorators, target, key, kind) => {
1784
- var result = void 0 ;
1785
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
1786
- if (decorator = decorators[i])
1787
- result = (decorator(target, key, result) ) || result;
1788
- if (result) __defProp(target, key, result);
1789
- return result;
1790
- };
1791
- function normalizeStyle(style) {
1792
- const newStyle = {};
1793
- for (const key in style) {
1794
- if (key !== "id" && style[key] !== void 0 && style[key] !== "") {
1795
- newStyle[key] = style[key];
1796
- }
1797
- }
1798
- return newStyle;
1799
- }
1800
- function textContentToCharStyles(textContent) {
1801
- return textContent.flatMap((p) => {
1802
- const { fragments } = p;
1803
- const res = fragments.flatMap((f) => {
1804
- const { content, ...fStyle } = f;
1805
- return Array.from(modernIdoc.normalizeCRLF(content)).map(() => ({ ...fStyle }));
1806
- });
1807
- if (modernIdoc.isCRLF(modernIdoc.normalizeCRLF(fragments[fragments.length - 1]?.content ?? ""))) {
1808
- return res;
1809
- }
1810
- return [...res, {}];
1811
- });
1812
- }
1813
- function isEqualStyle(style1, style2) {
1814
- const keys1 = Object.keys(style1);
1815
- const keys2 = Object.keys(style2);
1816
- const keys = Array.from(/* @__PURE__ */ new Set([...keys1, ...keys2]));
1817
- return !keys.length || keys.every((key) => style1[key] === style2[key]);
1818
- }
1819
- const emojiRE = /[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC2\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g;
1820
- function parseHTML(html) {
1821
- const template = document.createElement("template");
1822
- template.innerHTML = html;
1823
- return template.content.cloneNode(true);
1824
- }
1825
- class TextEditor extends HTMLElement {
1826
- static observedAttributes = [
1827
- "width",
1828
- "height"
1829
- ];
1830
- static register() {
1831
- customElements.define("text-editor", this);
1832
- }
1833
- _text = new Text();
1834
- get text() {
1835
- return this._text;
1836
- }
1837
- set text(value) {
1838
- if (value instanceof Text) {
1839
- this._text = value;
1840
- } else {
1841
- this._text.set(value);
1842
- }
1843
- this.setTextInput(this.getTextValue());
1844
- this.render();
1845
- }
1846
- pixelRatio = 2;
1847
- composition = false;
1848
- selection = [0, 0];
1849
- prevSelection = [0, 0];
1850
- _oldText = "";
1851
- $preview;
1852
- $textInput;
1853
- $cursor;
1854
- get selectionMinMax() {
1855
- return {
1856
- min: Math.min(...this.selection),
1857
- max: Math.max(...this.selection)
1858
- };
1859
- }
1860
- get selectedCharacters() {
1861
- const { min, max } = this.selectionMinMax;
1862
- return this.selectableCharacters.filter((_char, index) => {
1863
- return index >= min && index < max;
1864
- });
1865
- }
1866
- get selectableCharacters() {
1867
- const paragraphs = [];
1868
- this.text?.paragraphs.forEach((p, paragraphIndex) => {
1869
- p.fragments.forEach((f, fragmentIndex) => {
1870
- f.characters.forEach((c) => {
1871
- if (!paragraphs[paragraphIndex])
1872
- paragraphs[paragraphIndex] = [];
1873
- if (!paragraphs[paragraphIndex][fragmentIndex])
1874
- paragraphs[paragraphIndex][fragmentIndex] = [];
1875
- paragraphs[paragraphIndex][fragmentIndex].push(c);
1876
- });
1877
- });
1878
- });
1879
- const toSelectableCharacter = (c) => {
1880
- return {
1881
- color: c.computedStyle.color,
1882
- left: c.lineBox.left - this.text.boundingBox.left,
1883
- top: c.lineBox.top - this.text.boundingBox.top,
1884
- width: c.lineBox.width,
1885
- height: c.lineBox.height,
1886
- content: c.content
1887
- };
1888
- };
1889
- const map = [];
1890
- let pos = 0;
1891
- paragraphs.forEach((p) => {
1892
- if (p.length === 1 && p[0].length === 1 && modernIdoc.isCRLF(p[0][0].content)) {
1893
- const c = p[0][0];
1894
- map[pos] = {
1895
- ...toSelectableCharacter(c),
1896
- isCrlf: true
1897
- };
1898
- } else {
1899
- p.forEach((f) => {
1900
- f.forEach((c) => {
1901
- map[pos] = {
1902
- ...toSelectableCharacter(c)
1903
- };
1904
- pos++;
1905
- if (!modernIdoc.isCRLF(c.content)) {
1906
- map[pos] = {
1907
- ...toSelectableCharacter(c),
1908
- content: " ",
1909
- isLastSelected: true
1910
- };
1911
- }
1912
- });
1913
- });
1914
- }
1915
- pos++;
1916
- });
1917
- if (map[0]) {
1918
- map[0].isFirst = true;
1919
- }
1920
- if (map[map.length - 1]) {
1921
- map[map.length - 1].isLast = true;
1922
- }
1923
- return map;
1924
- }
1925
- get cursorPosition() {
1926
- let left = 0;
1927
- let top = 0;
1928
- const char = this.selectableCharacters[this.selectionMinMax.min];
1929
- if (char?.isLastSelected) {
1930
- if (this.text.isVertical) {
1931
- top += char?.height ?? 0;
1932
- } else {
1933
- left += char?.width ?? 0;
1934
- }
1935
- }
1936
- left += char?.left ?? 0;
1937
- top += char?.top ?? 0;
1938
- return {
1939
- color: char?.color,
1940
- left,
1941
- top,
1942
- height: char?.height ?? 0,
1943
- width: char?.width ?? 0
1944
- };
1945
- }
1946
- constructor() {
1947
- super();
1948
- const shadowRoot = this.attachShadow({ mode: "open" });
1949
- shadowRoot.appendChild(
1950
- parseHTML(`
1951
- <style>
1952
- :host {
1953
- position: absolute;
1954
- width: 0;
1955
- height: 0;
1956
- }
1957
-
1958
- .preview {
1959
- position: absolute;
1960
- left: 0;
1961
- top: 0;
1962
- --selection-color: rgba(var(--color, 0, 0, 0), 0.4);
1963
- }
1964
-
1965
- .text-input {
1966
- position: absolute;
1967
- opacity: 0;
1968
- caret-color: transparent;
1969
- left: 0;
1970
- top: 0;
1971
- width: 100%;
1972
- height: 100%;
1973
- padding: 0;
1974
- border: 0;
1975
- }
1976
-
1977
- .cursor {
1978
- position: absolute;
1979
- left: 0;
1980
- top: 0;
1981
- }
1982
-
1983
- .cursor.blink {
1984
- animation: cursor-blink 1s steps(2, start) infinite;
1985
- }
1986
-
1987
- @keyframes cursor-blink {
1988
- 100% {
1989
- display: none;
1990
- }
1991
- }
1992
- </style>
1993
-
1994
- <canvas class="preview"></canvas>
1995
-
1996
- <textarea class="text-input" autofocus></textarea>
1997
-
1998
- <div class="cursor blink"></div>
1999
- `)
2000
- );
2001
- this.$preview = shadowRoot.querySelector(".preview");
2002
- this.$textInput = shadowRoot.querySelector(".text-input");
2003
- this.$cursor = shadowRoot.querySelector(".cursor");
2004
- this.$textInput.addEventListener("compositionstart", () => this.composition = true);
2005
- this.$textInput.addEventListener("compositionend", () => this.composition = false);
2006
- this.$textInput.addEventListener("keydown", this.onKeydown.bind(this));
2007
- this.$textInput.addEventListener("input", this.onInput.bind(this));
2008
- this.$textInput.addEventListener("blur", this.onBlur.bind(this));
2009
- this.$textInput.addEventListener("mousedown", this.onMousedown.bind(this));
2010
- }
2011
- getTextValue() {
2012
- return modernIdoc.textContentToString(
2013
- this.getContentValue(
2014
- this.text.content
2015
- )
2016
- );
2017
- }
2018
- getContentValue(content, newString = modernIdoc.textContentToString(content), oldString = newString) {
2019
- newString = modernIdoc.normalizeCRLF(newString);
2020
- newString = newString.replace(emojiRE, (emoji) => {
2021
- if (Array.from(emoji).length > 1) {
2022
- return "?";
2023
- }
2024
- return emoji;
2025
- });
2026
- oldString = modernIdoc.normalizeCRLF(oldString);
2027
- const oldStyles = textContentToCharStyles(content);
2028
- const styles = [];
2029
- let styleIndex = 0;
2030
- let oldStyleIndex = 0;
2031
- let prevOldStyle = {};
2032
- const changes = diff.diffChars(oldString, newString);
2033
- changes.forEach((change) => {
2034
- const chars = Array.from(change.value);
2035
- if (change.removed) {
2036
- oldStyleIndex += chars.length;
2037
- } else {
2038
- chars.forEach(() => {
2039
- if (change.added) {
2040
- styles[styleIndex] = { ...prevOldStyle };
2041
- } else {
2042
- prevOldStyle = normalizeStyle(oldStyles[oldStyleIndex]);
2043
- styles[styleIndex] = { ...prevOldStyle };
2044
- oldStyleIndex++;
2045
- }
2046
- styleIndex++;
2047
- });
2048
- }
2049
- });
2050
- let charIndex = 0;
2051
- const newContents = [];
2052
- modernIdoc.normalizeTextContent(newString).forEach((p, pI) => {
2053
- const { fragments: _, ...pStyle } = content[pI] ?? {};
2054
- let newParagraph = { ...pStyle, fragments: [] };
2055
- let newFragment;
2056
- p.fragments.forEach((f) => {
2057
- Array.from(f.content).forEach((char) => {
2058
- const style = styles[charIndex] ?? {};
2059
- if (newFragment) {
2060
- const { content: _2, ..._style } = newFragment;
2061
- if (isEqualStyle(style, _style)) {
2062
- newFragment.content += char;
2063
- } else {
2064
- newParagraph.fragments.push(newFragment);
2065
- newFragment = { ...style, content: char };
2066
- }
2067
- } else {
2068
- newFragment = { ...style, content: char };
2069
- }
2070
- charIndex++;
2071
- });
2072
- });
2073
- if (!modernIdoc.isCRLF(p.fragments[p.fragments.length - 1]?.content ?? "")) {
2074
- charIndex++;
2075
- }
2076
- if (newFragment) {
2077
- newParagraph.fragments.push(newFragment);
2078
- }
2079
- if (newParagraph.fragments.length) {
2080
- newContents.push(newParagraph);
2081
- newParagraph = { ...pStyle, fragments: [] };
2082
- }
2083
- });
2084
- return newContents;
2085
- }
2086
- setTextInput(newText) {
2087
- this.$textInput.value = newText;
2088
- this._oldText = newText;
2089
- }
2090
- onInput() {
2091
- const newText = this.$textInput.value;
2092
- this.text.content = this.getContentValue(
2093
- this.text.content,
2094
- newText,
2095
- this._oldText
2096
- );
2097
- this._oldText = newText;
2098
- this.updateSelection();
2099
- this.render();
2100
- }
2101
- _timer;
2102
- onKeydown(e) {
2103
- e.stopPropagation();
2104
- switch (e.key) {
2105
- case "Escape":
2106
- return this.$textInput.blur();
2107
- }
2108
- this.updateSelection();
2109
- this.render();
2110
- setTimeout(() => {
2111
- this.updateSelection();
2112
- this.render();
2113
- }, 0);
2114
- setTimeout(() => {
2115
- this.updateSelection();
2116
- this.render();
2117
- }, 100);
2118
- }
2119
- onBlur() {
2120
- this.emit("done", this.text);
2121
- }
2122
- findNearest(options) {
2123
- const { x, y, xWeight = 1, yWeight = 1 } = options;
2124
- const char = this.selectableCharacters.reduce(
2125
- (prev, current, index) => {
2126
- const diff = Math.abs(current.left + current.width / 2 - x) * xWeight + Math.abs(current.top + current.height / 2 - y) * yWeight;
2127
- if (diff < prev.diff) {
2128
- return {
2129
- diff,
2130
- index,
2131
- value: current
2132
- };
2133
- }
2134
- return prev;
2135
- },
2136
- {
2137
- diff: Number.MAX_SAFE_INTEGER,
2138
- index: -1,
2139
- value: void 0
2140
- }
2141
- );
2142
- if (char?.value) {
2143
- const middleX = char.value.left + char.value.width / 2;
2144
- if (x > middleX && !char.value.isCrlf && !char.value.isLastSelected) {
2145
- return char.index + 1;
2146
- }
2147
- return char.index;
2148
- }
2149
- return -1;
2150
- }
2151
- updateSelection() {
2152
- if (this.composition) {
2153
- this.selection = this.prevSelection;
2154
- } else {
2155
- let count = 0;
2156
- const _selection = [];
2157
- this.selectableCharacters.forEach((char, index) => {
2158
- if (count <= this.$textInput.selectionStart) {
2159
- _selection[0] = index;
2160
- } else if (count <= this.$textInput.selectionEnd) {
2161
- _selection[1] = index;
2162
- }
2163
- count += char.content.length;
2164
- });
2165
- this.selection = _selection;
2166
- this.prevSelection = this.selection;
2167
- }
2168
- }
2169
- updateDOMSelection() {
2170
- let start = 0;
2171
- let end = 0;
2172
- this.selectableCharacters.forEach((char, index) => {
2173
- if (index < this.selectionMinMax.min) {
2174
- start += char.content.length;
2175
- end = start;
2176
- } else if (index < this.selectionMinMax.max) {
2177
- end += char.content.length;
2178
- }
2179
- });
2180
- this.$textInput.selectionStart = start;
2181
- this.$textInput.selectionEnd = end;
2182
- }
2183
- onMousedown(e) {
2184
- e.preventDefault();
2185
- e.stopPropagation();
2186
- this.$textInput.focus();
2187
- const index = this.findNearest({ x: e.offsetX, y: e.offsetY });
2188
- this.selection = [index, index];
2189
- this.updateDOMSelection();
2190
- this.render();
2191
- const onMousemove = (e2) => {
2192
- this.selection[1] = this.findNearest({ x: e2.offsetX, y: e2.offsetY });
2193
- this.updateDOMSelection();
2194
- this.render();
2195
- };
2196
- const onMouseup = () => {
2197
- document.removeEventListener("mousemove", onMousemove);
2198
- document.removeEventListener("mouseup", onMouseup);
2199
- };
2200
- document.addEventListener("mousemove", onMousemove);
2201
- document.addEventListener("mouseup", onMouseup);
2202
- }
2203
- render() {
2204
- const isVertical = this.text.isVertical;
2205
- this.text.style = {
2206
- justifyContent: "center",
2207
- alignItems: "center",
2208
- textAlign: "center",
2209
- color: "#FFFFFFFF",
2210
- ...this.text.style
2211
- };
2212
- this.text.style.width = this.width;
2213
- this.text.style.height = this.height;
2214
- this.text.requestUpdate();
2215
- let ctx;
2216
- this.text.render({
2217
- pixelRatio: this.pixelRatio,
2218
- view: this.$preview,
2219
- onContext: (_ctx) => ctx = _ctx
2220
- });
2221
- const selectedCharacters = this.selectedCharacters;
2222
- if (ctx) {
2223
- const boxesGroups = {};
2224
- selectedCharacters.forEach((char) => {
2225
- if (char.isLastSelected) {
2226
- return;
2227
- }
2228
- const key = isVertical ? char.left : char.top;
2229
- if (!boxesGroups[key]) {
2230
- boxesGroups[key] = [];
2231
- }
2232
- boxesGroups[key].push({
2233
- x: char.left,
2234
- y: char.top,
2235
- w: char.width,
2236
- h: char.height
2237
- });
2238
- });
2239
- ctx.fillStyle = getComputedStyle(this.$preview).getPropertyValue("--selection-color");
2240
- Object.values(boxesGroups).forEach((boxes) => {
2241
- const min = {
2242
- x: Math.min(...boxes.map((v) => v.x)),
2243
- y: Math.min(...boxes.map((v) => v.y))
2244
- };
2245
- const max = {
2246
- x: Math.max(...boxes.map((v) => v.x + v.w)),
2247
- y: Math.max(...boxes.map((v) => v.y + v.h))
2248
- };
2249
- ctx.fillRect(
2250
- min.x + this.text.boundingBox.left,
2251
- min.y + this.text.boundingBox.top,
2252
- max.x - min.x,
2253
- max.y - min.y
2254
- );
2255
- });
2256
- }
2257
- if (selectedCharacters.length === 0) {
2258
- this.$cursor.style.visibility = "visible";
2259
- const cursorPosition = this.cursorPosition;
2260
- this.$cursor.style.backgroundColor = cursorPosition.color ?? "rgba(var(--color)";
2261
- this.$cursor.style.left = `${cursorPosition.left}px`;
2262
- this.$cursor.style.top = `${cursorPosition.top}px`;
2263
- this.$cursor.style.height = isVertical ? "1px" : `${cursorPosition.height}px`;
2264
- this.$cursor.style.width = isVertical ? `${cursorPosition.width}px` : "1px";
2265
- } else {
2266
- this.$cursor.style.visibility = "hidden";
2267
- }
2268
- this.$cursor.classList.remove("blink");
2269
- if (this._timer) {
2270
- clearTimeout(this._timer);
2271
- }
2272
- this._timer = setTimeout(() => this.$cursor.classList.add("blink"), 500);
2273
- this.emit("rendered", this.text);
2274
- }
2275
- requestUpdate() {
2276
- this.render();
2277
- }
2278
- attributeChangedCallback(name, _oldValue, newValue) {
2279
- this[name] = newValue;
2280
- }
2281
- emit(type, detail) {
2282
- return this.dispatchEvent(
2283
- new CustomEvent(type, {
2284
- bubbles: true,
2285
- cancelable: true,
2286
- composed: true,
2287
- detail
2288
- })
2289
- );
2290
- }
2291
- }
2292
- __decorateClass([
2293
- modernIdoc.property()
2294
- ], TextEditor.prototype, "width");
2295
- __decorateClass([
2296
- modernIdoc.property()
2297
- ], TextEditor.prototype, "height");
2298
-
2299
- exports.Character = Character;
2300
- exports.Fragment = Fragment;
2301
- exports.Measurer = Measurer;
2302
- exports.Paragraph = Paragraph;
2303
- exports.Text = Text;
2304
- exports.TextEditor = TextEditor;
2305
- exports.background = background;
2306
- exports.createSVGLoader = createSVGLoader;
2307
- exports.createSVGParser = createSVGParser;
2308
- exports.definePlugin = definePlugin;
2309
- exports.drawPath = drawPath;
2310
- exports.filterEmpty = filterEmpty;
2311
- exports.getHighlightStyle = getHighlightStyle;
2312
- exports.getTransform2D = getTransform2D;
2313
- exports.hexToRgb = hexToRgb;
2314
- exports.highlight = highlight;
2315
- exports.isEqualObject = isEqualObject;
2316
- exports.isEqualValue = isEqualValue;
2317
- exports.listStyle = listStyle;
28
+ exports.Character = Text.Character;
29
+ exports.Fragment = Text.Fragment;
30
+ exports.Measurer = Text.Measurer;
31
+ exports.Paragraph = Text.Paragraph;
32
+ exports.Text = Text.Text;
33
+ exports.background = Text.background;
34
+ exports.createSVGLoader = Text.createSVGLoader;
35
+ exports.createSVGParser = Text.createSVGParser;
36
+ exports.definePlugin = Text.definePlugin;
37
+ exports.drawPath = Text.drawPath;
38
+ exports.filterEmpty = Text.filterEmpty;
39
+ exports.getHighlightStyle = Text.getHighlightStyle;
40
+ exports.getTransform2D = Text.getTransform2D;
41
+ exports.hexToRgb = Text.hexToRgb;
42
+ exports.highlight = Text.highlight;
43
+ exports.isEqualObject = Text.isEqualObject;
44
+ exports.isEqualValue = Text.isEqualValue;
45
+ exports.listStyle = Text.listStyle;
46
+ exports.outline = Text.outline;
47
+ exports.parseColor = Text.parseColor;
48
+ exports.parseColormap = Text.parseColormap;
49
+ exports.parseValueNumber = Text.parseValueNumber;
50
+ exports.render = Text.render;
51
+ exports.setupView = Text.setupView;
52
+ exports.textDecoration = Text.textDecoration;
53
+ exports.textDefaultStyle = Text.textDefaultStyle;
54
+ exports.uploadColor = Text.uploadColor;
55
+ exports.uploadColors = Text.uploadColors;
2318
56
  exports.measureText = measureText;
2319
- exports.outline = outline;
2320
- exports.parseColor = parseColor;
2321
- exports.parseColormap = parseColormap;
2322
- exports.parseValueNumber = parseValueNumber;
2323
- exports.render = render;
2324
57
  exports.renderText = renderText;
2325
- exports.setupView = setupView;
2326
- exports.textDecoration = textDecoration;
2327
- exports.textDefaultStyle = textDefaultStyle;
2328
- exports.uploadColor = uploadColor;
2329
- exports.uploadColors = uploadColors;