modern-text 2.0.9 → 2.1.1
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/README.md +27 -18
- package/dist/deformations/index.cjs +1 -1
- package/dist/deformations/index.d.cts +2 -2
- package/dist/deformations/index.d.mts +2 -2
- package/dist/deformations/index.d.ts +2 -2
- package/dist/deformations/index.mjs +1 -1
- package/dist/index.cjs +3 -4
- package/dist/index.d.cts +20 -21
- package/dist/index.d.mts +20 -21
- package/dist/index.d.ts +20 -21
- package/dist/index.js +4 -4
- package/dist/index.mjs +3 -3
- package/dist/shared/{modern-text.CKFvdNej.cjs → modern-text.BAnUZM0U.cjs} +506 -495
- package/dist/shared/{modern-text.rc-b3qK8.mjs → modern-text.BdF52nXP.mjs} +506 -494
- package/dist/shared/{modern-text.CwEJ2EZq.d.ts → modern-text.CGZmjNV0.d.ts} +1 -1
- package/dist/shared/{modern-text.K8BlyK2j.mjs → modern-text.CdKp2H-o.mjs} +13 -4
- package/dist/shared/{modern-text.DPlppHEM.d.mts → modern-text.DG27jpKj.d.cts} +111 -125
- package/dist/shared/{modern-text.DPlppHEM.d.ts → modern-text.DG27jpKj.d.mts} +111 -125
- package/dist/shared/{modern-text.DPlppHEM.d.cts → modern-text.DG27jpKj.d.ts} +111 -125
- package/dist/shared/{modern-text.BzOupiZm.d.mts → modern-text.DNXfZmiT.d.mts} +1 -1
- package/dist/shared/{modern-text.Dmjryk8u.d.cts → modern-text.DzBaDwbG.d.cts} +1 -1
- package/dist/shared/{modern-text.DQWdaYoZ.cjs → modern-text._10DVHum.cjs} +13 -4
- package/dist/web-components/index.cjs +2 -2
- package/dist/web-components/index.d.cts +1 -1
- package/dist/web-components/index.d.mts +1 -1
- package/dist/web-components/index.d.ts +1 -1
- package/dist/web-components/index.mjs +2 -2
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fonts } from 'modern-font';
|
|
2
2
|
import { isNone, isGradient, normalizeGradient, clearUndef, Reactivable, normalizeText, getDefaultStyle, property } from 'modern-idoc';
|
|
3
3
|
import { svgToDom, svgToPath2DSet, Path2DSet, Transform2D, setCanvasContext, Path2D, BoundingBox, Vector2 } from 'modern-path2d';
|
|
4
|
-
import { a as definePlugin, b as deformationPlugin } from './modern-text.
|
|
4
|
+
import { a as definePlugin, b as deformationPlugin } from './modern-text.CdKp2H-o.mjs';
|
|
5
5
|
|
|
6
6
|
function createSvgLoader() {
|
|
7
7
|
const loaded = /* @__PURE__ */ new Map();
|
|
@@ -145,15 +145,21 @@ class Canvas2DRenderer {
|
|
|
145
145
|
this.context = context;
|
|
146
146
|
}
|
|
147
147
|
pixelRatio = window?.devicePixelRatio || 1;
|
|
148
|
+
// 子区域(平铺)渲染:只把 boundingBox 内相对偏移 (x,y)、尺寸 (width,height) 的一块
|
|
149
|
+
// 画到画布上(其余字形被画布边界裁掉)。用于超大文字按 GPU 上限分块栅格。不设则画整段。
|
|
150
|
+
region;
|
|
148
151
|
_setupView = () => {
|
|
149
152
|
const pixelRatio = this.pixelRatio;
|
|
150
153
|
const ctx = this.context;
|
|
151
|
-
const
|
|
154
|
+
const bb = this.text.boundingBox;
|
|
155
|
+
const region = this.region;
|
|
156
|
+
const left = bb.left + (region?.x ?? 0);
|
|
157
|
+
const top = bb.top + (region?.y ?? 0);
|
|
158
|
+
const canvasWidth = region?.width ?? bb.width;
|
|
159
|
+
const canvasHeight = region?.height ?? bb.height;
|
|
152
160
|
const view = ctx.canvas;
|
|
153
|
-
view.dataset.viewBox = String(`${left} ${top} ${
|
|
161
|
+
view.dataset.viewBox = String(`${left} ${top} ${canvasWidth} ${canvasHeight}`);
|
|
154
162
|
view.dataset.pixelRatio = String(pixelRatio);
|
|
155
|
-
const canvasWidth = width;
|
|
156
|
-
const canvasHeight = height;
|
|
157
163
|
view.width = Math.max(1, Math.ceil(canvasWidth * pixelRatio));
|
|
158
164
|
view.height = Math.max(1, Math.ceil(canvasHeight * pixelRatio));
|
|
159
165
|
view.style.width = `${canvasWidth}px`;
|
|
@@ -431,47 +437,196 @@ const fontWeightMap = {
|
|
|
431
437
|
800: 0.4,
|
|
432
438
|
900: 0.5
|
|
433
439
|
};
|
|
440
|
+
const GLYPH_CACHE_CAP = 4096;
|
|
441
|
+
const glyphCache = /* @__PURE__ */ new Map();
|
|
442
|
+
function glyphCacheSet(key, tmpl) {
|
|
443
|
+
glyphCache.set(key, tmpl);
|
|
444
|
+
if (glyphCache.size > GLYPH_CACHE_CAP) {
|
|
445
|
+
glyphCache.delete(glyphCache.keys().next().value);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
let _sfntIdCounter = 0;
|
|
449
|
+
const _sfntIds = /* @__PURE__ */ new WeakMap();
|
|
450
|
+
function sfntId(sfnt) {
|
|
451
|
+
let id = _sfntIds.get(sfnt);
|
|
452
|
+
if (id === void 0) {
|
|
453
|
+
id = ++_sfntIdCounter;
|
|
454
|
+
_sfntIds.set(sfnt, id);
|
|
455
|
+
}
|
|
456
|
+
return id;
|
|
457
|
+
}
|
|
458
|
+
const FONT_METRICS_CAP = 256;
|
|
459
|
+
const fontMetricsCache = /* @__PURE__ */ new Map();
|
|
460
|
+
function getFontMetrics(sfnt, fontSize) {
|
|
461
|
+
const key = `${sfntId(sfnt)}|${fontSize}`;
|
|
462
|
+
const cached = fontMetricsCache.get(key);
|
|
463
|
+
if (cached) {
|
|
464
|
+
return cached;
|
|
465
|
+
}
|
|
466
|
+
const { hhea, os2, post, head } = sfnt;
|
|
467
|
+
const unitsPerEm = head.unitsPerEm;
|
|
468
|
+
const ascender = hhea.ascent;
|
|
469
|
+
const descender = hhea.descent;
|
|
470
|
+
const rate = unitsPerEm / fontSize;
|
|
471
|
+
const advanceHeight = (ascender + Math.abs(descender)) / rate;
|
|
472
|
+
const baseline = ascender / rate;
|
|
473
|
+
const m = {
|
|
474
|
+
sfnt,
|
|
475
|
+
unitsPerEm,
|
|
476
|
+
advanceHeight,
|
|
477
|
+
baseline,
|
|
478
|
+
ascender: ascender / rate,
|
|
479
|
+
descender: descender / rate,
|
|
480
|
+
underlinePosition: (ascender - post.underlinePosition) / rate,
|
|
481
|
+
underlineThickness: post.underlineThickness / rate,
|
|
482
|
+
strikeoutPosition: (ascender - os2.yStrikeoutPosition) / rate,
|
|
483
|
+
strikeoutSize: os2.yStrikeoutSize / rate,
|
|
484
|
+
typoAscender: os2.sTypoAscender / rate,
|
|
485
|
+
typoDescender: os2.sTypoDescender / rate,
|
|
486
|
+
typoLineGap: os2.sTypoLineGap / rate,
|
|
487
|
+
winAscent: os2.usWinAscent / rate,
|
|
488
|
+
winDescent: os2.usWinDescent / rate,
|
|
489
|
+
xHeight: os2.version > 1 ? os2.sxHeight / rate : 0,
|
|
490
|
+
capHeight: os2.version > 1 ? os2.sCapHeight / rate : 0,
|
|
491
|
+
centerDiviation: advanceHeight / 2 - baseline,
|
|
492
|
+
fontStyle: fsSelectionMap[os2.fsSelection] ?? macStyleMap[head.macStyle]
|
|
493
|
+
};
|
|
494
|
+
if (fontMetricsCache.size >= FONT_METRICS_CAP) {
|
|
495
|
+
fontMetricsCache.delete(fontMetricsCache.keys().next().value);
|
|
496
|
+
}
|
|
497
|
+
fontMetricsCache.set(key, m);
|
|
498
|
+
return m;
|
|
499
|
+
}
|
|
434
500
|
class Character {
|
|
435
501
|
constructor(content, index, parent) {
|
|
436
502
|
this.content = content;
|
|
437
503
|
this.index = index;
|
|
438
504
|
this.parent = parent;
|
|
439
505
|
}
|
|
440
|
-
path
|
|
441
|
-
|
|
506
|
+
// 惰性 path:布局阶段(measure)只算度量 + glyphBox,不构造逐字定位 path
|
|
507
|
+
// (3681 字逐字 clone+平移占 Text.update 主成本)。path 仅在真正渲染/命中时才按需构建。
|
|
508
|
+
_path;
|
|
509
|
+
_lazyPath;
|
|
510
|
+
get path() {
|
|
511
|
+
if (this._path) {
|
|
512
|
+
return this._path;
|
|
513
|
+
}
|
|
514
|
+
const lazy = this._lazyPath;
|
|
515
|
+
if (lazy) {
|
|
516
|
+
const { x, y } = lazy;
|
|
517
|
+
const p = lazy.tmpl.path.clone().setMeta(this);
|
|
518
|
+
p.applyTransform((pt) => {
|
|
519
|
+
pt.x += x;
|
|
520
|
+
pt.y += y;
|
|
521
|
+
});
|
|
522
|
+
p.style = lazy.style;
|
|
523
|
+
this._path = p;
|
|
524
|
+
this._lazyPath = void 0;
|
|
525
|
+
return p;
|
|
526
|
+
}
|
|
527
|
+
const empty = new Path2D().setMeta(this);
|
|
528
|
+
this._path = empty;
|
|
529
|
+
return empty;
|
|
530
|
+
}
|
|
531
|
+
set path(value) {
|
|
532
|
+
this._path = value;
|
|
533
|
+
this._lazyPath = void 0;
|
|
534
|
+
}
|
|
442
535
|
inlineBox = new BoundingBox();
|
|
536
|
+
// lineBox(行高/列厚那条 strip)完全由 inlineBox + fontHeight 推出,不再逐字存一个 BoundingBox。
|
|
537
|
+
// 横排:与 inlineBox 同列、在内容盒上下居中、高=fontHeight;竖排:inline/block 轴交换。
|
|
538
|
+
get lineBox() {
|
|
539
|
+
const ib = this.inlineBox;
|
|
540
|
+
const fh = this.fontHeight;
|
|
541
|
+
return this.isVertical ? new BoundingBox(ib.left + (ib.width - fh) / 2, ib.top, fh, ib.height) : new BoundingBox(ib.left, ib.top + (ib.height - fh) / 2, ib.width, fh);
|
|
542
|
+
}
|
|
443
543
|
glyphBox;
|
|
444
544
|
advanceWidth = 0;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
545
|
+
// 字偶距(kerning):与「行内逻辑前一字符」之间的水平调整(px,通常为负=拉近)。
|
|
546
|
+
// 由 Measurer._applyKerning 在横排测量时按字形对填充;行首/换字体/竖排时为 0。
|
|
547
|
+
// 断行与定位都计入它,使纯/混排拉丁文本的行宽、换行点与浏览器渲染一致。
|
|
548
|
+
kerningBefore = 0;
|
|
549
|
+
// 本字符首字形在其 sfnt 内的索引(逐字),供相邻字符查字偶距。
|
|
550
|
+
_glyphIndex;
|
|
551
|
+
// 字体度量 flyweight(按 (sfnt,fontSize) 共享);下面所有度量 getter 都从这里推导,不逐字存。
|
|
552
|
+
_metrics;
|
|
553
|
+
// 度量 getter —— 零实例存储,从共享 _metrics 推导(advanceWidth 是唯一逐字存的度量)。
|
|
554
|
+
get advanceHeight() {
|
|
555
|
+
return this._metrics?.advanceHeight ?? 0;
|
|
556
|
+
}
|
|
557
|
+
get baseline() {
|
|
558
|
+
return this._metrics?.baseline ?? 0;
|
|
559
|
+
}
|
|
560
|
+
get ascender() {
|
|
561
|
+
return this._metrics?.ascender ?? 0;
|
|
562
|
+
}
|
|
563
|
+
get descender() {
|
|
564
|
+
return this._metrics?.descender ?? 0;
|
|
565
|
+
}
|
|
566
|
+
get underlinePosition() {
|
|
567
|
+
return this._metrics?.underlinePosition ?? 0;
|
|
568
|
+
}
|
|
569
|
+
get underlineThickness() {
|
|
570
|
+
return this._metrics?.underlineThickness ?? 0;
|
|
571
|
+
}
|
|
572
|
+
get strikeoutPosition() {
|
|
573
|
+
return this._metrics?.strikeoutPosition ?? 0;
|
|
574
|
+
}
|
|
575
|
+
get strikeoutSize() {
|
|
576
|
+
return this._metrics?.strikeoutSize ?? 0;
|
|
577
|
+
}
|
|
578
|
+
get typoAscender() {
|
|
579
|
+
return this._metrics?.typoAscender ?? 0;
|
|
580
|
+
}
|
|
581
|
+
get typoDescender() {
|
|
582
|
+
return this._metrics?.typoDescender ?? 0;
|
|
583
|
+
}
|
|
584
|
+
get typoLineGap() {
|
|
585
|
+
return this._metrics?.typoLineGap ?? 0;
|
|
586
|
+
}
|
|
587
|
+
get winAscent() {
|
|
588
|
+
return this._metrics?.winAscent ?? 0;
|
|
589
|
+
}
|
|
590
|
+
get winDescent() {
|
|
591
|
+
return this._metrics?.winDescent ?? 0;
|
|
592
|
+
}
|
|
593
|
+
get xHeight() {
|
|
594
|
+
return this._metrics?.xHeight ?? 0;
|
|
595
|
+
}
|
|
596
|
+
get capHeight() {
|
|
597
|
+
return this._metrics?.capHeight ?? 0;
|
|
598
|
+
}
|
|
599
|
+
get centerDiviation() {
|
|
600
|
+
return this._metrics?.centerDiviation ?? 0;
|
|
601
|
+
}
|
|
602
|
+
get fontStyle() {
|
|
603
|
+
return this._metrics?.fontStyle;
|
|
604
|
+
}
|
|
605
|
+
// 增量布局:把已测量的字形整体沿 y 平移 dy(用于未变段落因前序段落高度变化而下移)。
|
|
606
|
+
// 同步移动所有盒(inline/line/glyph)与 path(惰性偏移或已构建几何),保持与全量重排一致。
|
|
607
|
+
translateY(dy) {
|
|
608
|
+
if (!dy) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
this.inlineBox.top += dy;
|
|
612
|
+
if (this.glyphBox) {
|
|
613
|
+
this.glyphBox.top += dy;
|
|
614
|
+
}
|
|
615
|
+
if (this._lazyPath) {
|
|
616
|
+
this._lazyPath.y += dy;
|
|
617
|
+
} else if (this._path) {
|
|
618
|
+
this._path.applyTransform((p) => {
|
|
619
|
+
p.y += dy;
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
462
623
|
get compatibleGlyphBox() {
|
|
624
|
+
if (this.glyphBox) {
|
|
625
|
+
return this.glyphBox;
|
|
626
|
+
}
|
|
463
627
|
const size = this.computedStyle.fontSize * 0.8;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
this.lineBox.top,
|
|
467
|
-
size,
|
|
468
|
-
this.lineBox.height
|
|
469
|
-
) : new BoundingBox(
|
|
470
|
-
this.lineBox.left,
|
|
471
|
-
this.lineBox.top + this.lineBox.height / 2 - size / 2,
|
|
472
|
-
this.lineBox.width,
|
|
473
|
-
size
|
|
474
|
-
));
|
|
628
|
+
const lb = this.lineBox;
|
|
629
|
+
return this.isVertical ? new BoundingBox(lb.left + lb.width / 2 - size / 2, lb.top, size, lb.height) : new BoundingBox(lb.left, lb.top + lb.height / 2 - size / 2, lb.width, size);
|
|
475
630
|
}
|
|
476
631
|
get center() {
|
|
477
632
|
return this.compatibleGlyphBox.center;
|
|
@@ -508,44 +663,30 @@ class Character {
|
|
|
508
663
|
if (!sfnt) {
|
|
509
664
|
return this;
|
|
510
665
|
}
|
|
511
|
-
const { hhea, os2, post, head } = sfnt;
|
|
512
|
-
const unitsPerEm = head.unitsPerEm;
|
|
513
|
-
const ascender = hhea.ascent;
|
|
514
|
-
const descender = hhea.descent;
|
|
515
666
|
const { content, computedStyle } = this;
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const baseline = ascender / rate;
|
|
521
|
-
this.advanceWidth = advanceWidth;
|
|
522
|
-
this.advanceHeight = advanceHeight;
|
|
523
|
-
this.underlinePosition = (ascender - post.underlinePosition) / rate;
|
|
524
|
-
this.underlineThickness = post.underlineThickness / rate;
|
|
525
|
-
this.strikeoutPosition = (ascender - os2.yStrikeoutPosition) / rate;
|
|
526
|
-
this.strikeoutSize = os2.yStrikeoutSize / rate;
|
|
527
|
-
this.ascender = ascender / rate;
|
|
528
|
-
this.descender = descender / rate;
|
|
529
|
-
this.typoAscender = os2.sTypoAscender / rate;
|
|
530
|
-
this.typoDescender = os2.sTypoDescender / rate;
|
|
531
|
-
this.typoLineGap = os2.sTypoLineGap / rate;
|
|
532
|
-
this.winAscent = os2.usWinAscent / rate;
|
|
533
|
-
this.winDescent = os2.usWinDescent / rate;
|
|
534
|
-
this.xHeight = os2.version > 1 ? os2.sxHeight / rate : 0;
|
|
535
|
-
this.capHeight = os2.version > 1 ? os2.sCapHeight / rate : 0;
|
|
536
|
-
this.baseline = baseline;
|
|
537
|
-
this.centerDiviation = advanceHeight / 2 - baseline;
|
|
538
|
-
this.fontStyle = fsSelectionMap[os2.fsSelection] ?? macStyleMap[head.macStyle];
|
|
667
|
+
const fontSize = computedStyle.fontSize;
|
|
668
|
+
this._metrics = getFontMetrics(sfnt, fontSize);
|
|
669
|
+
this.advanceWidth = sfnt.getAdvanceWidth(content, fontSize);
|
|
670
|
+
this._glyphIndex = sfnt.textToGlyphIndexes(content)[0];
|
|
539
671
|
return this;
|
|
540
672
|
}
|
|
673
|
+
// 与逻辑前一字符 prev 之间的字偶距(px)。仅当同一 sfnt(同字体、非缺字回退分叉)
|
|
674
|
+
// 且两侧字形索引均有效时返回非零;否则 0。kerning value 为 font units,按本字符字号转 px。
|
|
675
|
+
computeKerningBefore(prev) {
|
|
676
|
+
const m = this._metrics;
|
|
677
|
+
if (!prev || !m || prev._metrics?.sfnt !== m.sfnt || prev._glyphIndex === void 0 || this._glyphIndex === void 0) {
|
|
678
|
+
return 0;
|
|
679
|
+
}
|
|
680
|
+
const kv = m.sfnt.getKerningValue(prev._glyphIndex, this._glyphIndex);
|
|
681
|
+
return kv ? kv * this.computedStyle.fontSize / m.unitsPerEm : 0;
|
|
682
|
+
}
|
|
541
683
|
/**
|
|
542
684
|
* Populate glyph metrics only (advance width/height, ascender/descender,
|
|
543
685
|
* baseline, …) without building the glyph `path` or touching boxes.
|
|
544
686
|
*
|
|
545
|
-
* The
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
* recomputes the same metrics while building the path, so this is idempotent.
|
|
687
|
+
* The pure-JS `Measurer` must know advances *before* it can place characters,
|
|
688
|
+
* so it calls this ahead of layout. `update()` later recomputes the same metrics
|
|
689
|
+
* while building the path, so this is idempotent.
|
|
549
690
|
*/
|
|
550
691
|
measureGlyph(fonts) {
|
|
551
692
|
return this.updateGlyph(this._getFontSFNT(fonts));
|
|
@@ -556,10 +697,15 @@ class Character {
|
|
|
556
697
|
return this;
|
|
557
698
|
}
|
|
558
699
|
this.updateGlyph(sfnt);
|
|
700
|
+
const style = this.computedStyle;
|
|
701
|
+
const needsItalic = style.fontStyle === "italic" && this.fontStyle !== "italic";
|
|
702
|
+
if (!this.isVertical && !needsItalic && !style.textStrokeWidth) {
|
|
703
|
+
this._updateFromCache(sfnt, style);
|
|
704
|
+
return this;
|
|
705
|
+
}
|
|
559
706
|
const {
|
|
560
707
|
isVertical,
|
|
561
708
|
content,
|
|
562
|
-
computedStyle: style,
|
|
563
709
|
baseline,
|
|
564
710
|
inlineBox,
|
|
565
711
|
ascender,
|
|
@@ -570,7 +716,6 @@ class Character {
|
|
|
570
716
|
advanceHeight
|
|
571
717
|
} = this;
|
|
572
718
|
const { left, top } = inlineBox;
|
|
573
|
-
const needsItalic = style.fontStyle === "italic" && fontStyle !== "italic";
|
|
574
719
|
let x = left;
|
|
575
720
|
let y = top + baseline;
|
|
576
721
|
let glyphIndex;
|
|
@@ -640,6 +785,39 @@ class Character {
|
|
|
640
785
|
this.glyphBox = this.getGlyphBoundingBox();
|
|
641
786
|
return this;
|
|
642
787
|
}
|
|
788
|
+
// 横排非合成斜体的快路径:用字形模板缓存构建 path 与 glyphBox。
|
|
789
|
+
_updateFromCache(sfnt, style) {
|
|
790
|
+
const content = this.content;
|
|
791
|
+
const fontSize = style.fontSize;
|
|
792
|
+
const x = this.inlineBox.left;
|
|
793
|
+
const y = this.inlineBox.top + this.baseline;
|
|
794
|
+
const fontWeight = style.fontWeight ?? 400;
|
|
795
|
+
const boldAmount = fontWeight in fontWeightMap && (fontWeight === 700 || fontWeight === "bold") && this.fontStyle !== "bold" ? fontWeightMap[fontWeight] * fontSize * 0.05 : 0;
|
|
796
|
+
const key = `${content}|${sfntId(sfnt)}|${fontSize}|${boldAmount}`;
|
|
797
|
+
let tmpl = glyphCache.get(key);
|
|
798
|
+
if (!tmpl) {
|
|
799
|
+
const tpath = new Path2D();
|
|
800
|
+
tpath.addCommands(sfnt.getPathCommands(content, 0, 0, fontSize));
|
|
801
|
+
if (boldAmount) {
|
|
802
|
+
tpath.bold(boldAmount);
|
|
803
|
+
}
|
|
804
|
+
let glyphBox;
|
|
805
|
+
if (tpath.curves[0]?.curves.length) {
|
|
806
|
+
const { min, max } = tpath.getMinMax(void 0, void 0, true);
|
|
807
|
+
glyphBox = new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
808
|
+
}
|
|
809
|
+
tmpl = { path: tpath, glyphBox };
|
|
810
|
+
glyphCacheSet(key, tmpl);
|
|
811
|
+
}
|
|
812
|
+
this._path = void 0;
|
|
813
|
+
this._lazyPath = {
|
|
814
|
+
tmpl,
|
|
815
|
+
x,
|
|
816
|
+
y,
|
|
817
|
+
style: clearUndef({ fill: style.color, fillRule: "nonzero" })
|
|
818
|
+
};
|
|
819
|
+
this.glyphBox = tmpl.glyphBox ? new BoundingBox(tmpl.glyphBox.left + x, tmpl.glyphBox.top + y, tmpl.glyphBox.width, tmpl.glyphBox.height) : void 0;
|
|
820
|
+
}
|
|
643
821
|
_italic(path, startPoint) {
|
|
644
822
|
path.skew(-0.24, 0, startPoint || {
|
|
645
823
|
y: this.inlineBox.top + this.baseline,
|
|
@@ -647,6 +825,19 @@ class Character {
|
|
|
647
825
|
});
|
|
648
826
|
}
|
|
649
827
|
getGlyphMinMax(min, max, withStyle) {
|
|
828
|
+
if (this._path === void 0 && this._lazyPath !== void 0) {
|
|
829
|
+
const gb = this.glyphBox;
|
|
830
|
+
if (!gb) {
|
|
831
|
+
return void 0;
|
|
832
|
+
}
|
|
833
|
+
const tl = new Vector2(gb.left, gb.top);
|
|
834
|
+
const br = new Vector2(gb.left + gb.width, gb.top + gb.height);
|
|
835
|
+
const rMin = min ?? new Vector2(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
|
|
836
|
+
const rMax = max ?? new Vector2(Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);
|
|
837
|
+
rMin.clampMin(tl, br);
|
|
838
|
+
rMax.clampMax(tl, br);
|
|
839
|
+
return { min: rMin, max: rMax };
|
|
840
|
+
}
|
|
650
841
|
if (this.path.curves[0]?.curves.length) {
|
|
651
842
|
return this.path.getMinMax(min, max, withStyle);
|
|
652
843
|
} else {
|
|
@@ -711,6 +902,18 @@ class Paragraph {
|
|
|
711
902
|
fragments = [];
|
|
712
903
|
fill;
|
|
713
904
|
outline;
|
|
905
|
+
// 增量布局缓存(见 Text._update / Measurer):
|
|
906
|
+
// _layoutDirty=true 时须全量重排该段;为 false(复用未变段)时,measurer 仅按 dy 平移。
|
|
907
|
+
// _layoutTop/_layoutHeight/_layoutRight 记录上次测量的绝对 y 顶/高/右,供平移与并集复用。
|
|
908
|
+
// _glyphBox 缓存该段字形包围盒(绝对坐标),平移时随段一起移,免去逐字 union。
|
|
909
|
+
_layoutDirty = true;
|
|
910
|
+
_layoutTop = 0;
|
|
911
|
+
_layoutHeight = 0;
|
|
912
|
+
_layoutRight = 0;
|
|
913
|
+
_glyphBox;
|
|
914
|
+
// 本段是否「有效测量」:含字符却所有字形 advance 为 0(字体未就绪时的退化测量)则为 false,
|
|
915
|
+
// 不可作为增量复用基准,须下次重测,避免把字体就绪前的零宽布局固化。
|
|
916
|
+
_layoutValid = false;
|
|
714
917
|
update() {
|
|
715
918
|
this.computedStyle = {
|
|
716
919
|
...clearUndef(this.parent.computedStyle),
|
|
@@ -724,381 +927,25 @@ class Paragraph {
|
|
|
724
927
|
}
|
|
725
928
|
}
|
|
726
929
|
|
|
727
|
-
let sharedContainer;
|
|
728
|
-
function getSharedContainer() {
|
|
729
|
-
if (sharedContainer?.isConnected) {
|
|
730
|
-
return sharedContainer;
|
|
731
|
-
}
|
|
732
|
-
const container = document.createElement("div");
|
|
733
|
-
container.dataset.modernText = "measurer";
|
|
734
|
-
Object.assign(container.style, {
|
|
735
|
-
position: "fixed",
|
|
736
|
-
left: "0",
|
|
737
|
-
top: "0",
|
|
738
|
-
visibility: "hidden",
|
|
739
|
-
pointerEvents: "none"
|
|
740
|
-
});
|
|
741
|
-
document.body.appendChild(container);
|
|
742
|
-
sharedContainer = container;
|
|
743
|
-
return container;
|
|
744
|
-
}
|
|
745
|
-
class DomMeasurer {
|
|
746
|
-
static notZeroStyles = /* @__PURE__ */ new Set([
|
|
747
|
-
"width",
|
|
748
|
-
"height"
|
|
749
|
-
]);
|
|
750
|
-
static pxStyles = /* @__PURE__ */ new Set([
|
|
751
|
-
"width",
|
|
752
|
-
"height",
|
|
753
|
-
"fontSize",
|
|
754
|
-
"letterSpacing",
|
|
755
|
-
"textStrokeWidth",
|
|
756
|
-
"textIndent",
|
|
757
|
-
"shadowOffsetX",
|
|
758
|
-
"shadowOffsetY",
|
|
759
|
-
"shadowBlur",
|
|
760
|
-
"margin",
|
|
761
|
-
"marginLeft",
|
|
762
|
-
"marginTop",
|
|
763
|
-
"marginRight",
|
|
764
|
-
"marginBottom",
|
|
765
|
-
"padding",
|
|
766
|
-
"paddingLeft",
|
|
767
|
-
"paddingTop",
|
|
768
|
-
"paddingRight",
|
|
769
|
-
"paddingBottom"
|
|
770
|
-
]);
|
|
771
|
-
_styleCache = /* @__PURE__ */ new WeakMap();
|
|
772
|
-
_mountedDom;
|
|
773
|
-
_mountedSignature;
|
|
774
|
-
_toDomStyle(style) {
|
|
775
|
-
const cached = this._styleCache.get(style);
|
|
776
|
-
if (cached) {
|
|
777
|
-
return cached;
|
|
778
|
-
}
|
|
779
|
-
const domStyle = {};
|
|
780
|
-
const { notZeroStyles, pxStyles } = DomMeasurer;
|
|
781
|
-
for (const key in style) {
|
|
782
|
-
const value = style[key];
|
|
783
|
-
if (notZeroStyles.has(key) && value === 0) {
|
|
784
|
-
continue;
|
|
785
|
-
}
|
|
786
|
-
domStyle[key] = typeof value === "number" && pxStyles.has(key) ? `${value}px` : value;
|
|
787
|
-
}
|
|
788
|
-
this._styleCache.set(style, domStyle);
|
|
789
|
-
return domStyle;
|
|
790
|
-
}
|
|
791
|
-
_resolveRootStyles(rootStyle) {
|
|
792
|
-
const style = { ...rootStyle };
|
|
793
|
-
const isHorizontal = rootStyle.writingMode.includes("horizontal");
|
|
794
|
-
switch (rootStyle.textAlign) {
|
|
795
|
-
case "start":
|
|
796
|
-
case "left":
|
|
797
|
-
style.justifyContent = "flex-start";
|
|
798
|
-
break;
|
|
799
|
-
case "center":
|
|
800
|
-
style.justifyContent = "center";
|
|
801
|
-
break;
|
|
802
|
-
case "end":
|
|
803
|
-
case "right":
|
|
804
|
-
style.justifyContent = "flex-end";
|
|
805
|
-
break;
|
|
806
|
-
}
|
|
807
|
-
switch (rootStyle.verticalAlign) {
|
|
808
|
-
case "top":
|
|
809
|
-
style.alignItems = "flex-start";
|
|
810
|
-
break;
|
|
811
|
-
case "middle":
|
|
812
|
-
style.alignItems = "center";
|
|
813
|
-
break;
|
|
814
|
-
case "bottom":
|
|
815
|
-
style.alignItems = "flex-end";
|
|
816
|
-
break;
|
|
817
|
-
}
|
|
818
|
-
const isFlex = Boolean(style.justifyContent || style.alignItems);
|
|
819
|
-
return {
|
|
820
|
-
section: {
|
|
821
|
-
...this._toDomStyle({
|
|
822
|
-
...style,
|
|
823
|
-
boxSizing: style.boxSizing ?? "border-box",
|
|
824
|
-
display: style.display ?? (isFlex ? "inline-flex" : void 0),
|
|
825
|
-
width: style.width ?? "max-content",
|
|
826
|
-
height: style.height ?? "max-content"
|
|
827
|
-
}),
|
|
828
|
-
whiteSpace: "pre-wrap",
|
|
829
|
-
wordBreak: "break-all"
|
|
830
|
-
},
|
|
831
|
-
ul: {
|
|
832
|
-
verticalAlign: "inherit",
|
|
833
|
-
listStyleType: "inherit",
|
|
834
|
-
padding: "0",
|
|
835
|
-
margin: "0",
|
|
836
|
-
width: isFlex && isHorizontal ? "100%" : void 0,
|
|
837
|
-
height: isFlex && !isHorizontal ? "100%" : void 0
|
|
838
|
-
}
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
_applyRootStyle(section, ul, rootStyle) {
|
|
842
|
-
const styles = this._resolveRootStyles(rootStyle);
|
|
843
|
-
section.removeAttribute("style");
|
|
844
|
-
Object.assign(section.style, styles.section);
|
|
845
|
-
ul.removeAttribute("style");
|
|
846
|
-
Object.assign(ul.style, styles.ul);
|
|
847
|
-
}
|
|
848
|
-
_applyLiStyle(li, paragraph) {
|
|
849
|
-
li.removeAttribute("style");
|
|
850
|
-
li.style.verticalAlign = "inherit";
|
|
851
|
-
Object.assign(li.style, this._toDomStyle(paragraph.style));
|
|
852
|
-
}
|
|
853
|
-
_applySpanStyle(span, fragment) {
|
|
854
|
-
span.removeAttribute("style");
|
|
855
|
-
span.style.verticalAlign = "inherit";
|
|
856
|
-
Object.assign(span.style, this._toDomStyle(fragment.style));
|
|
857
|
-
}
|
|
858
|
-
_applyFragmentContent(span, fragment) {
|
|
859
|
-
const textNode = span.firstChild;
|
|
860
|
-
if (textNode) {
|
|
861
|
-
if (textNode.data !== fragment.content) {
|
|
862
|
-
textNode.data = fragment.content;
|
|
863
|
-
}
|
|
864
|
-
} else {
|
|
865
|
-
span.appendChild(document.createTextNode(fragment.content));
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
_hide(element) {
|
|
869
|
-
element.style.visibility = "hidden";
|
|
870
|
-
}
|
|
871
|
-
_signature(paragraphs) {
|
|
872
|
-
let sig = `${paragraphs.length}`;
|
|
873
|
-
for (let i = 0; i < paragraphs.length; i++) {
|
|
874
|
-
sig += `:${paragraphs[i].fragments.length}`;
|
|
875
|
-
}
|
|
876
|
-
return sig;
|
|
877
|
-
}
|
|
878
|
-
/**
|
|
879
|
-
* <section style="...">
|
|
880
|
-
* <ul>
|
|
881
|
-
* <li style="...">
|
|
882
|
-
* <span style="...">...</span>
|
|
883
|
-
* <span>...</span>
|
|
884
|
-
* </li>
|
|
885
|
-
* </ul>
|
|
886
|
-
* </section>
|
|
887
|
-
*/
|
|
888
|
-
createDom(paragraphs, rootStyle) {
|
|
889
|
-
const section = document.createElement("section");
|
|
890
|
-
const ul = document.createElement("ul");
|
|
891
|
-
this._applyRootStyle(section, ul, rootStyle);
|
|
892
|
-
paragraphs.forEach((paragraph) => {
|
|
893
|
-
const li = document.createElement("li");
|
|
894
|
-
this._applyLiStyle(li, paragraph);
|
|
895
|
-
paragraph.fragments.forEach((fragment) => {
|
|
896
|
-
const span = document.createElement("span");
|
|
897
|
-
this._applySpanStyle(span, fragment);
|
|
898
|
-
this._applyFragmentContent(span, fragment);
|
|
899
|
-
li.appendChild(span);
|
|
900
|
-
});
|
|
901
|
-
ul.appendChild(li);
|
|
902
|
-
});
|
|
903
|
-
section.appendChild(ul);
|
|
904
|
-
return section;
|
|
905
|
-
}
|
|
906
|
-
_patchDom(dom, paragraphs, rootStyle, hidden) {
|
|
907
|
-
const ul = dom.firstChild;
|
|
908
|
-
this._applyRootStyle(dom, ul, rootStyle);
|
|
909
|
-
if (hidden) {
|
|
910
|
-
this._hide(dom);
|
|
911
|
-
}
|
|
912
|
-
const lis = ul.children;
|
|
913
|
-
for (let i = 0; i < paragraphs.length; i++) {
|
|
914
|
-
const li = lis[i];
|
|
915
|
-
this._applyLiStyle(li, paragraphs[i]);
|
|
916
|
-
const fragments = paragraphs[i].fragments;
|
|
917
|
-
const spans = li.children;
|
|
918
|
-
for (let j = 0; j < fragments.length; j++) {
|
|
919
|
-
const span = spans[j];
|
|
920
|
-
this._applySpanStyle(span, fragments[j]);
|
|
921
|
-
this._applyFragmentContent(span, fragments[j]);
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
measureDomText(text) {
|
|
926
|
-
const data = text.data ?? "";
|
|
927
|
-
const results = [];
|
|
928
|
-
if (!data.length) {
|
|
929
|
-
return results;
|
|
930
|
-
}
|
|
931
|
-
const range = document.createRange();
|
|
932
|
-
let i = 0;
|
|
933
|
-
while (i < data.length) {
|
|
934
|
-
const cp = data.codePointAt(i);
|
|
935
|
-
const end = i + (cp > 65535 ? 2 : 1);
|
|
936
|
-
range.setStart(text, i);
|
|
937
|
-
range.setEnd(text, end);
|
|
938
|
-
const rects = range.getClientRects?.();
|
|
939
|
-
let rect;
|
|
940
|
-
if (rects && rects.length) {
|
|
941
|
-
rect = rects[rects.length - 1];
|
|
942
|
-
if (rects.length > 1 && rect.width < 2) {
|
|
943
|
-
rect = rects[rects.length - 2];
|
|
944
|
-
}
|
|
945
|
-
} else {
|
|
946
|
-
rect = range.getBoundingClientRect();
|
|
947
|
-
}
|
|
948
|
-
if (rect && rect.width + rect.height !== 0) {
|
|
949
|
-
results.push({
|
|
950
|
-
content: data.slice(i, end),
|
|
951
|
-
top: rect.top,
|
|
952
|
-
left: rect.left,
|
|
953
|
-
width: rect.width,
|
|
954
|
-
height: rect.height
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
i = end;
|
|
958
|
-
}
|
|
959
|
-
return results;
|
|
960
|
-
}
|
|
961
|
-
measureDom(dom) {
|
|
962
|
-
const paragraphs = [];
|
|
963
|
-
const fragments = [];
|
|
964
|
-
const characters = [];
|
|
965
|
-
dom.querySelectorAll("li").forEach((pDom, paragraphIndex) => {
|
|
966
|
-
const pBox = pDom.getBoundingClientRect();
|
|
967
|
-
paragraphs.push({
|
|
968
|
-
paragraphIndex,
|
|
969
|
-
left: pBox.x,
|
|
970
|
-
top: pBox.y,
|
|
971
|
-
width: pBox.width,
|
|
972
|
-
height: pBox.height
|
|
973
|
-
});
|
|
974
|
-
pDom.querySelectorAll(":scope > *").forEach((fDom, fragmentIndex) => {
|
|
975
|
-
const fBox = fDom.getBoundingClientRect();
|
|
976
|
-
fragments.push({
|
|
977
|
-
paragraphIndex,
|
|
978
|
-
fragmentIndex,
|
|
979
|
-
left: fBox.x,
|
|
980
|
-
top: fBox.y,
|
|
981
|
-
width: fBox.width,
|
|
982
|
-
height: fBox.height
|
|
983
|
-
});
|
|
984
|
-
let characterIndex = 0;
|
|
985
|
-
const pushChars = (textNode) => {
|
|
986
|
-
this.measureDomText(textNode).forEach((char) => {
|
|
987
|
-
characters.push({
|
|
988
|
-
...char,
|
|
989
|
-
newParagraphIndex: -1,
|
|
990
|
-
paragraphIndex,
|
|
991
|
-
fragmentIndex,
|
|
992
|
-
characterIndex: characterIndex++,
|
|
993
|
-
textWidth: -1,
|
|
994
|
-
textHeight: -1
|
|
995
|
-
});
|
|
996
|
-
});
|
|
997
|
-
};
|
|
998
|
-
if (!fDom.children.length && fDom.firstChild instanceof window.Text) {
|
|
999
|
-
pushChars(fDom.firstChild);
|
|
1000
|
-
} else {
|
|
1001
|
-
fDom.querySelectorAll(":scope > *").forEach((cDom) => {
|
|
1002
|
-
if (cDom.firstChild instanceof window.Text) {
|
|
1003
|
-
pushChars(cDom.firstChild);
|
|
1004
|
-
}
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
});
|
|
1008
|
-
});
|
|
1009
|
-
return { paragraphs, fragments, characters };
|
|
1010
|
-
}
|
|
1011
|
-
measureParagraphDom(paragraphs, dom) {
|
|
1012
|
-
const rect = dom.getBoundingClientRect();
|
|
1013
|
-
const measured = this.measureDom(dom);
|
|
1014
|
-
measured.paragraphs.forEach((p) => {
|
|
1015
|
-
const box = paragraphs[p.paragraphIndex].lineBox;
|
|
1016
|
-
box.left = p.left - rect.left;
|
|
1017
|
-
box.top = p.top - rect.top;
|
|
1018
|
-
box.width = p.width;
|
|
1019
|
-
box.height = p.height;
|
|
1020
|
-
});
|
|
1021
|
-
measured.fragments.forEach((f) => {
|
|
1022
|
-
const box = paragraphs[f.paragraphIndex].fragments[f.fragmentIndex].inlineBox;
|
|
1023
|
-
box.left = f.left - rect.left;
|
|
1024
|
-
box.top = f.top - rect.top;
|
|
1025
|
-
box.width = f.width;
|
|
1026
|
-
box.height = f.height;
|
|
1027
|
-
});
|
|
1028
|
-
const results = [];
|
|
1029
|
-
measured.characters.forEach((character) => {
|
|
1030
|
-
const { paragraphIndex, fragmentIndex, characterIndex } = character;
|
|
1031
|
-
const left = character.left - rect.left;
|
|
1032
|
-
const top = character.top - rect.top;
|
|
1033
|
-
results.push({
|
|
1034
|
-
...character,
|
|
1035
|
-
newParagraphIndex: paragraphIndex,
|
|
1036
|
-
left,
|
|
1037
|
-
top
|
|
1038
|
-
});
|
|
1039
|
-
const item = paragraphs[paragraphIndex].fragments[fragmentIndex].characters[characterIndex];
|
|
1040
|
-
const { fontHeight, isVertical, inlineBox, lineBox } = item;
|
|
1041
|
-
inlineBox.left = left;
|
|
1042
|
-
inlineBox.top = top;
|
|
1043
|
-
inlineBox.width = character.width;
|
|
1044
|
-
inlineBox.height = character.height;
|
|
1045
|
-
if (isVertical) {
|
|
1046
|
-
lineBox.left = left + (character.width - fontHeight) / 2;
|
|
1047
|
-
lineBox.top = top;
|
|
1048
|
-
lineBox.width = fontHeight;
|
|
1049
|
-
lineBox.height = character.height;
|
|
1050
|
-
} else {
|
|
1051
|
-
lineBox.left = left;
|
|
1052
|
-
lineBox.top = top + (character.height - fontHeight) / 2;
|
|
1053
|
-
lineBox.width = character.width;
|
|
1054
|
-
lineBox.height = fontHeight;
|
|
1055
|
-
}
|
|
1056
|
-
});
|
|
1057
|
-
return {
|
|
1058
|
-
paragraphs,
|
|
1059
|
-
boundingBox: new BoundingBox(0, 0, rect.width, rect.height)
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
measure(paragraphs, rootStyle, dom) {
|
|
1063
|
-
return this.measureParagraphDom(paragraphs, dom ?? this._mount(paragraphs, rootStyle));
|
|
1064
|
-
}
|
|
1065
|
-
_mount(paragraphs, rootStyle) {
|
|
1066
|
-
const signature = this._signature(paragraphs);
|
|
1067
|
-
if (this._mountedDom?.isConnected && this._mountedSignature === signature) {
|
|
1068
|
-
this._patchDom(this._mountedDom, paragraphs, rootStyle, true);
|
|
1069
|
-
return this._mountedDom;
|
|
1070
|
-
}
|
|
1071
|
-
this._unmount();
|
|
1072
|
-
const dom = this.createDom(paragraphs, rootStyle);
|
|
1073
|
-
this._hide(dom);
|
|
1074
|
-
getSharedContainer().appendChild(dom);
|
|
1075
|
-
this._mountedDom = dom;
|
|
1076
|
-
this._mountedSignature = signature;
|
|
1077
|
-
return dom;
|
|
1078
|
-
}
|
|
1079
|
-
_unmount() {
|
|
1080
|
-
this._mountedDom?.parentNode?.removeChild(this._mountedDom);
|
|
1081
|
-
this._mountedDom = void 0;
|
|
1082
|
-
this._mountedSignature = void 0;
|
|
1083
|
-
}
|
|
1084
|
-
dispose() {
|
|
1085
|
-
this._unmount();
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
930
|
function side(style, name, edge) {
|
|
1090
931
|
return style[`${name}${edge}`] ?? style[name] ?? 0;
|
|
1091
932
|
}
|
|
1092
|
-
class
|
|
933
|
+
class Measurer {
|
|
1093
934
|
measure(paragraphs, rootStyle, _dom, fonts$1) {
|
|
1094
935
|
const _fonts = fonts$1 ?? fonts;
|
|
1095
936
|
for (const paragraph of paragraphs) {
|
|
937
|
+
if (!paragraph._layoutDirty) {
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
1096
940
|
for (const fragment of paragraph.fragments) {
|
|
1097
941
|
for (const character of fragment.characters) {
|
|
1098
942
|
character.measureGlyph(_fonts);
|
|
1099
943
|
}
|
|
1100
944
|
}
|
|
1101
945
|
}
|
|
946
|
+
if (!rootStyle.writingMode.includes("vertical")) {
|
|
947
|
+
this._applyKerning(paragraphs);
|
|
948
|
+
}
|
|
1102
949
|
return rootStyle.writingMode.includes("vertical") ? this._measureVertical(paragraphs, rootStyle) : this._measureHorizontal(paragraphs, rootStyle);
|
|
1103
950
|
}
|
|
1104
951
|
_rootPadding(rootStyle) {
|
|
@@ -1132,18 +979,48 @@ class FontMeasurer {
|
|
|
1132
979
|
y += mTop + pTop;
|
|
1133
980
|
const paraTop = y;
|
|
1134
981
|
let paraRight = liLeft;
|
|
982
|
+
if (!paragraph._layoutDirty) {
|
|
983
|
+
const dy = paraTop - paragraph._layoutTop;
|
|
984
|
+
if (dy) {
|
|
985
|
+
for (const fragment of paragraph.fragments) {
|
|
986
|
+
for (const c of fragment.characters) {
|
|
987
|
+
c.translateY(dy);
|
|
988
|
+
}
|
|
989
|
+
fragment.inlineBox.top += dy;
|
|
990
|
+
}
|
|
991
|
+
paragraph.lineBox.top += dy;
|
|
992
|
+
if (paragraph._glyphBox) {
|
|
993
|
+
paragraph._glyphBox.top += dy;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
paragraph._layoutTop = paraTop;
|
|
997
|
+
paraRight = paragraph._layoutRight;
|
|
998
|
+
y = paraTop + paragraph.lineBox.height;
|
|
999
|
+
if (paraRight > maxRight) {
|
|
1000
|
+
maxRight = paraRight;
|
|
1001
|
+
}
|
|
1002
|
+
y += pBottom + mBottom;
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1135
1005
|
const lines = this._breakLines(paragraph, liAvail);
|
|
1136
1006
|
const align = pStyle.textAlign;
|
|
1137
1007
|
const indent = pStyle.textIndent ?? 0;
|
|
1008
|
+
let hasChars = false;
|
|
1009
|
+
let anyAdvance = false;
|
|
1138
1010
|
for (let i = 0; i < lines.length; i++) {
|
|
1139
1011
|
const line = lines[i];
|
|
1140
1012
|
const lineIndent = i === 0 ? indent : 0;
|
|
1141
1013
|
let lineHeight = pStyle.fontSize * pStyle.lineHeight;
|
|
1142
1014
|
let contentWidth = 0;
|
|
1015
|
+
let firstW = true;
|
|
1143
1016
|
for (const c of line) {
|
|
1144
1017
|
if (c.fontHeight > lineHeight) {
|
|
1145
1018
|
lineHeight = c.fontHeight;
|
|
1146
1019
|
}
|
|
1020
|
+
if (!firstW) {
|
|
1021
|
+
contentWidth += c.kerningBefore;
|
|
1022
|
+
}
|
|
1023
|
+
firstW = false;
|
|
1147
1024
|
contentWidth += this._advance(c);
|
|
1148
1025
|
}
|
|
1149
1026
|
let x = liLeft + lineIndent;
|
|
@@ -1155,18 +1032,22 @@ class FontMeasurer {
|
|
|
1155
1032
|
x += slack;
|
|
1156
1033
|
}
|
|
1157
1034
|
}
|
|
1035
|
+
let firstInLine = true;
|
|
1158
1036
|
for (const c of line) {
|
|
1037
|
+
hasChars = true;
|
|
1038
|
+
if (c.advanceWidth > 0) {
|
|
1039
|
+
anyAdvance = true;
|
|
1040
|
+
}
|
|
1041
|
+
if (!firstInLine) {
|
|
1042
|
+
x += c.kerningBefore;
|
|
1043
|
+
}
|
|
1044
|
+
firstInLine = false;
|
|
1159
1045
|
const adv = c.advanceWidth;
|
|
1160
1046
|
const contentHeight = c.advanceHeight;
|
|
1161
|
-
const fontHeight = c.fontHeight;
|
|
1162
1047
|
c.inlineBox.left = x;
|
|
1163
1048
|
c.inlineBox.top = y + (lineHeight - contentHeight) / 2;
|
|
1164
1049
|
c.inlineBox.width = adv;
|
|
1165
1050
|
c.inlineBox.height = contentHeight;
|
|
1166
|
-
c.lineBox.left = x;
|
|
1167
|
-
c.lineBox.top = c.inlineBox.top + (contentHeight - fontHeight) / 2;
|
|
1168
|
-
c.lineBox.width = adv;
|
|
1169
|
-
c.lineBox.height = fontHeight;
|
|
1170
1051
|
x += this._advance(c);
|
|
1171
1052
|
}
|
|
1172
1053
|
if (x > paraRight) {
|
|
@@ -1184,6 +1065,11 @@ class FontMeasurer {
|
|
|
1184
1065
|
paragraph.lineBox.top = paraTop;
|
|
1185
1066
|
paragraph.lineBox.width = liAvail === Infinity ? paraRight - liLeft : liAvail;
|
|
1186
1067
|
paragraph.lineBox.height = y - paraTop;
|
|
1068
|
+
paragraph._layoutTop = paraTop;
|
|
1069
|
+
paragraph._layoutHeight = paragraph.lineBox.height;
|
|
1070
|
+
paragraph._layoutRight = paraRight;
|
|
1071
|
+
paragraph._glyphBox = void 0;
|
|
1072
|
+
paragraph._layoutValid = !hasChars || anyAdvance;
|
|
1187
1073
|
y += pBottom + mBottom;
|
|
1188
1074
|
}
|
|
1189
1075
|
const contentBottom = y + rootPad.bottom;
|
|
@@ -1207,9 +1093,8 @@ class FontMeasurer {
|
|
|
1207
1093
|
* flow top→bottom. It is the horizontal layout with the inline and block axes
|
|
1208
1094
|
* swapped — the inline (down-column) advance is `advanceWidth` (CJK upright = em;
|
|
1209
1095
|
* Latin is rotated, so its advance ≈ its width), the cross-axis content box is
|
|
1210
|
-
* `advanceHeight`, and the column thickness is `fontHeight`. The lineBox
|
|
1211
|
-
*
|
|
1212
|
-
* stays accurate even though inlineBox carries the content-box rounding residual.
|
|
1096
|
+
* `advanceHeight`, and the column thickness is `fontHeight`. The lineBox stays
|
|
1097
|
+
* accurate even though inlineBox carries the content-box rounding residual.
|
|
1213
1098
|
*
|
|
1214
1099
|
* v1: `vertical-rl` only; no per-paragraph margin/padding or block alignment.
|
|
1215
1100
|
*/
|
|
@@ -1243,15 +1128,10 @@ class FontMeasurer {
|
|
|
1243
1128
|
for (const c of column.chars) {
|
|
1244
1129
|
const advance = c.advanceWidth;
|
|
1245
1130
|
const contentWidth = c.advanceHeight;
|
|
1246
|
-
const fontHeight = c.fontHeight;
|
|
1247
1131
|
c.inlineBox.top = y;
|
|
1248
1132
|
c.inlineBox.height = advance;
|
|
1249
1133
|
c.inlineBox.width = contentWidth;
|
|
1250
1134
|
c.inlineBox.left = colLeft + (column.thickness - contentWidth) / 2;
|
|
1251
|
-
c.lineBox.left = c.inlineBox.left + (contentWidth - fontHeight) / 2;
|
|
1252
|
-
c.lineBox.top = y;
|
|
1253
|
-
c.lineBox.width = fontHeight;
|
|
1254
|
-
c.lineBox.height = advance;
|
|
1255
1135
|
y += this._advance(c);
|
|
1256
1136
|
}
|
|
1257
1137
|
if (y > maxBottom) {
|
|
@@ -1298,9 +1178,13 @@ class FontMeasurer {
|
|
|
1298
1178
|
flush();
|
|
1299
1179
|
continue;
|
|
1300
1180
|
}
|
|
1301
|
-
|
|
1181
|
+
const kern = current.length > 0 ? c.kerningBefore : 0;
|
|
1182
|
+
if (avail !== Infinity && current.length > 0 && width + kern + c.advanceWidth > avail) {
|
|
1302
1183
|
flush();
|
|
1303
1184
|
}
|
|
1185
|
+
if (current.length > 0) {
|
|
1186
|
+
width += c.kerningBefore;
|
|
1187
|
+
}
|
|
1304
1188
|
current.push(c);
|
|
1305
1189
|
width += this._advance(c);
|
|
1306
1190
|
}
|
|
@@ -1308,6 +1192,22 @@ class FontMeasurer {
|
|
|
1308
1192
|
flush();
|
|
1309
1193
|
return lines;
|
|
1310
1194
|
}
|
|
1195
|
+
// 横排:为每段(仅 dirty 段)填充相邻字形的字偶距。clean 段沿用上次结果(kern 是行内水平量,
|
|
1196
|
+
// 不随段落 y 平移变化),契合增量布局。
|
|
1197
|
+
_applyKerning(paragraphs) {
|
|
1198
|
+
for (const paragraph of paragraphs) {
|
|
1199
|
+
if (!paragraph._layoutDirty) {
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
let prev;
|
|
1203
|
+
for (const fragment of paragraph.fragments) {
|
|
1204
|
+
for (const c of fragment.characters) {
|
|
1205
|
+
c.kerningBefore = c.computeKerningBefore(prev);
|
|
1206
|
+
prev = c;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1311
1211
|
_unionInto(target, boxes) {
|
|
1312
1212
|
const used = boxes.filter((b) => b.width !== 0 || b.height !== 0);
|
|
1313
1213
|
if (!used.length) {
|
|
@@ -1326,7 +1226,6 @@ class FontMeasurer {
|
|
|
1326
1226
|
fragment.inlineBox.top += dy;
|
|
1327
1227
|
for (const character of fragment.characters) {
|
|
1328
1228
|
character.inlineBox.top += dy;
|
|
1329
|
-
character.lineBox.top += dy;
|
|
1330
1229
|
}
|
|
1331
1230
|
}
|
|
1332
1231
|
}
|
|
@@ -1829,19 +1728,37 @@ function outlinePlugin() {
|
|
|
1829
1728
|
|
|
1830
1729
|
function renderPlugin() {
|
|
1831
1730
|
const pathSet = new Path2DSet();
|
|
1731
|
+
let chars = [];
|
|
1732
|
+
let built = [];
|
|
1733
|
+
Object.defineProperty(pathSet, "paths", {
|
|
1734
|
+
configurable: true,
|
|
1735
|
+
enumerable: true,
|
|
1736
|
+
get() {
|
|
1737
|
+
if (built === null) {
|
|
1738
|
+
built = chars.map((c) => c.path);
|
|
1739
|
+
}
|
|
1740
|
+
return built;
|
|
1741
|
+
},
|
|
1742
|
+
set(value) {
|
|
1743
|
+
built = value;
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1832
1746
|
return definePlugin({
|
|
1833
1747
|
name: "render",
|
|
1834
1748
|
pathSet,
|
|
1835
1749
|
update: (text) => {
|
|
1836
|
-
|
|
1750
|
+
const next = [];
|
|
1837
1751
|
const { paragraphs } = text;
|
|
1838
1752
|
paragraphs.forEach((paragraph) => {
|
|
1839
1753
|
paragraph.fragments.forEach((fragment) => {
|
|
1840
1754
|
fragment.characters.forEach((character) => {
|
|
1841
|
-
|
|
1755
|
+
next.push(character);
|
|
1842
1756
|
});
|
|
1843
1757
|
});
|
|
1844
1758
|
});
|
|
1759
|
+
chars = next;
|
|
1760
|
+
built = null;
|
|
1761
|
+
pathSet._lazyCount = next.length;
|
|
1845
1762
|
},
|
|
1846
1763
|
getBoundingBox: (text) => {
|
|
1847
1764
|
const { characters, computedEffects, fontSize } = text;
|
|
@@ -2093,11 +2010,24 @@ class Text extends Reactivable {
|
|
|
2093
2010
|
glyphBox = new BoundingBox();
|
|
2094
2011
|
pathBox = new BoundingBox();
|
|
2095
2012
|
boundingBox = new BoundingBox();
|
|
2096
|
-
measurer = new
|
|
2013
|
+
measurer = new Measurer();
|
|
2097
2014
|
plugins = /* @__PURE__ */ new Map();
|
|
2098
2015
|
pathSets = [];
|
|
2099
2016
|
_paragraphs = [];
|
|
2100
2017
|
_cachedCharacters;
|
|
2018
|
+
// 增量布局开关与上一帧快照:未变段落按内容键复用,仅重排受影响段、平移后续段。
|
|
2019
|
+
incrementalLayout = true;
|
|
2020
|
+
_prevParagraphs = [];
|
|
2021
|
+
_prevContentKeys = [];
|
|
2022
|
+
_prevStyleKey = "";
|
|
2023
|
+
// 上次测量所用的 fonts 引用:从 undefined→tree.fonts 的赋值会改变字形度量,须作废增量基准
|
|
2024
|
+
// (否则会复用「fonts 未就绪」时测得的零宽字形)。
|
|
2025
|
+
_prevFonts;
|
|
2026
|
+
_prevFontsSet = false;
|
|
2027
|
+
// _update 计算出的待提交快照;仅在 measure() 真正完成测量后才提交到 _prev*(见 measure),
|
|
2028
|
+
// 否则构造函数里的 _update(只建树不测量)会让首测误把未测量段当作可复用。
|
|
2029
|
+
_pendingContentKeys = [];
|
|
2030
|
+
_pendingStyleKey = "";
|
|
2101
2031
|
_pluginsByUpdateOrder = [];
|
|
2102
2032
|
_pluginsByRenderOrder = [];
|
|
2103
2033
|
_renderer;
|
|
@@ -2151,12 +2081,6 @@ class Text extends Reactivable {
|
|
|
2151
2081
|
outline,
|
|
2152
2082
|
deformation
|
|
2153
2083
|
} = normalizeText(options);
|
|
2154
|
-
if (options.measurer && typeof options.measurer !== "string") {
|
|
2155
|
-
this.measurer = options.measurer;
|
|
2156
|
-
} else {
|
|
2157
|
-
const kind = options.measurer ?? "font";
|
|
2158
|
-
this.measurer = kind === "font" ? new FontMeasurer() : new DomMeasurer();
|
|
2159
|
-
}
|
|
2160
2084
|
this.debug = options.debug ?? false;
|
|
2161
2085
|
this.content = content;
|
|
2162
2086
|
this.effects = effects;
|
|
@@ -2196,10 +2120,13 @@ class Text extends Reactivable {
|
|
|
2196
2120
|
}
|
|
2197
2121
|
async load() {
|
|
2198
2122
|
this._update();
|
|
2199
|
-
await Promise.all([
|
|
2123
|
+
const [decoded] = await Promise.all([
|
|
2200
2124
|
this._decodeFonts(),
|
|
2201
2125
|
...Array.from(this.plugins.values()).map((p) => p.load?.(this))
|
|
2202
2126
|
]);
|
|
2127
|
+
if (decoded > 0) {
|
|
2128
|
+
this._prevParagraphs = [];
|
|
2129
|
+
}
|
|
2203
2130
|
}
|
|
2204
2131
|
/**
|
|
2205
2132
|
* Eagerly decode the fonts this text uses, off the main thread — WOFF tables
|
|
@@ -2219,12 +2146,15 @@ class Text extends Reactivable {
|
|
|
2219
2146
|
}
|
|
2220
2147
|
}
|
|
2221
2148
|
entries.add(fonts$1.fallbackFont);
|
|
2222
|
-
await Promise.all(Array.from(entries, async (entry) => {
|
|
2149
|
+
const decoded = await Promise.all(Array.from(entries, async (entry) => {
|
|
2223
2150
|
const font = entry?.getFont();
|
|
2224
2151
|
if (font && typeof font.createSFNTAsync === "function" && !font._sfnt) {
|
|
2225
2152
|
font._sfnt = await font.createSFNTAsync();
|
|
2153
|
+
return 1;
|
|
2226
2154
|
}
|
|
2155
|
+
return 0;
|
|
2227
2156
|
}));
|
|
2157
|
+
return decoded.reduce((a, b) => a + b, 0);
|
|
2228
2158
|
}
|
|
2229
2159
|
/**
|
|
2230
2160
|
* Coerce numeric style fields to finite numbers.
|
|
@@ -2257,44 +2187,70 @@ class Text extends Reactivable {
|
|
|
2257
2187
|
}
|
|
2258
2188
|
return style;
|
|
2259
2189
|
}
|
|
2190
|
+
_buildParagraph(p, pIndex) {
|
|
2191
|
+
const { fragments, fill: pFill, outline: pOutline, ...pStyle } = p;
|
|
2192
|
+
const paragraph = new Paragraph(pStyle, pIndex, this);
|
|
2193
|
+
paragraph.fill = pFill;
|
|
2194
|
+
paragraph.outline = pOutline;
|
|
2195
|
+
fragments.forEach((f, fIndex) => {
|
|
2196
|
+
const { content, fill: fFill, outline: fOutline, ...fStyle } = f;
|
|
2197
|
+
if (content !== void 0) {
|
|
2198
|
+
paragraph.fragments.push(
|
|
2199
|
+
new Fragment(content, fStyle, fFill, fOutline, fIndex, paragraph)
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2203
|
+
return paragraph;
|
|
2204
|
+
}
|
|
2205
|
+
// 仅当根级样式/填充/描边/特效/形变未变、且非竖排、非块级垂直对齐位移时,才允许复用未变段落
|
|
2206
|
+
// (这些会改变各段 computedStyle 或全局位移,复用会得到错误结果)。
|
|
2207
|
+
_canReuseLayout(styleKey) {
|
|
2208
|
+
if (!this.incrementalLayout || !this._prevParagraphs.length) {
|
|
2209
|
+
return false;
|
|
2210
|
+
}
|
|
2211
|
+
if (styleKey !== this._prevStyleKey) {
|
|
2212
|
+
return false;
|
|
2213
|
+
}
|
|
2214
|
+
if (!this._prevFontsSet || this.fonts !== this._prevFonts) {
|
|
2215
|
+
return false;
|
|
2216
|
+
}
|
|
2217
|
+
const cs = this.computedStyle;
|
|
2218
|
+
if (cs.writingMode.includes("vertical")) {
|
|
2219
|
+
return false;
|
|
2220
|
+
}
|
|
2221
|
+
if (typeof cs.height === "number" && (cs.verticalAlign === "middle" || cs.verticalAlign === "bottom")) {
|
|
2222
|
+
return false;
|
|
2223
|
+
}
|
|
2224
|
+
return true;
|
|
2225
|
+
}
|
|
2260
2226
|
_update() {
|
|
2261
2227
|
this.computedStyle = this._normalizeComputedStyle({ ...textDefaultStyle, ...this.style });
|
|
2262
2228
|
this.computedFill = this.fill ? { ...this.fill } : void 0;
|
|
2263
2229
|
this.computedOutline = this.outline ? { ...this.outline } : void 0;
|
|
2264
2230
|
this.computedEffects = this.effects?.map((v) => v) ?? [];
|
|
2265
|
-
const
|
|
2266
|
-
this.
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
);
|
|
2284
|
-
}
|
|
2285
|
-
});
|
|
2286
|
-
paragraphs.push(paragraph);
|
|
2287
|
-
});
|
|
2231
|
+
const styleKey = JSON.stringify([this.style, this.fill, this.outline, this.effects, this.deformation]);
|
|
2232
|
+
const reuse = this._canReuseLayout(styleKey);
|
|
2233
|
+
const content = this.content;
|
|
2234
|
+
const contentKeys = Array.from({ length: content.length });
|
|
2235
|
+
const paragraphs = Array.from({ length: content.length });
|
|
2236
|
+
for (let i = 0; i < content.length; i++) {
|
|
2237
|
+
const key = JSON.stringify(content[i]);
|
|
2238
|
+
contentKeys[i] = key;
|
|
2239
|
+
const prev = this._prevParagraphs[i];
|
|
2240
|
+
if (reuse && prev && prev._layoutValid && this._prevContentKeys[i] === key) {
|
|
2241
|
+
prev._layoutDirty = false;
|
|
2242
|
+
paragraphs[i] = prev;
|
|
2243
|
+
} else {
|
|
2244
|
+
const para = this._buildParagraph(content[i], i);
|
|
2245
|
+
para._layoutDirty = true;
|
|
2246
|
+
paragraphs[i] = para;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2288
2249
|
this.paragraphs = paragraphs;
|
|
2250
|
+
this._pendingContentKeys = contentKeys;
|
|
2251
|
+
this._pendingStyleKey = styleKey;
|
|
2289
2252
|
return this;
|
|
2290
2253
|
}
|
|
2291
|
-
createDom() {
|
|
2292
|
-
this._update();
|
|
2293
|
-
if (!this.measurer.createDom) {
|
|
2294
|
-
throw new Error("current measurer does not support createDom()");
|
|
2295
|
-
}
|
|
2296
|
-
return this.measurer.createDom(this.paragraphs, this.computedStyle);
|
|
2297
|
-
}
|
|
2298
2254
|
measure(dom = this.measureDom) {
|
|
2299
2255
|
const old = {
|
|
2300
2256
|
paragraphs: this.paragraphs,
|
|
@@ -2309,13 +2265,27 @@ class Text extends Reactivable {
|
|
|
2309
2265
|
const result = this.measurer.measure(this.paragraphs, this.computedStyle, dom, this.fonts);
|
|
2310
2266
|
this.paragraphs = result.paragraphs;
|
|
2311
2267
|
this.lineBox = result.boundingBox;
|
|
2312
|
-
const
|
|
2313
|
-
for (let
|
|
2314
|
-
|
|
2268
|
+
const paragraphs = this.paragraphs;
|
|
2269
|
+
for (let pi = 0; pi < paragraphs.length; pi++) {
|
|
2270
|
+
const paragraph = paragraphs[pi];
|
|
2271
|
+
if (!paragraph._layoutDirty) {
|
|
2272
|
+
continue;
|
|
2273
|
+
}
|
|
2274
|
+
for (const fragment of paragraph.fragments) {
|
|
2275
|
+
const chars = fragment.characters;
|
|
2276
|
+
for (let i = 0; i < chars.length; i++) {
|
|
2277
|
+
chars[i].update(this.fonts);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2315
2280
|
}
|
|
2316
|
-
const glyphBox = this.
|
|
2281
|
+
const glyphBox = this._computeGlyphBox();
|
|
2317
2282
|
this.rawGlyphBox = glyphBox;
|
|
2318
2283
|
this.glyphBox = glyphBox;
|
|
2284
|
+
this._prevParagraphs = this.paragraphs;
|
|
2285
|
+
this._prevContentKeys = this._pendingContentKeys;
|
|
2286
|
+
this._prevStyleKey = this._pendingStyleKey;
|
|
2287
|
+
this._prevFonts = this.fonts;
|
|
2288
|
+
this._prevFontsSet = true;
|
|
2319
2289
|
const updatePlugins = this._pluginsByUpdateOrder;
|
|
2320
2290
|
for (let i = 0; i < updatePlugins.length; i++) {
|
|
2321
2291
|
updatePlugins[i].update?.(this);
|
|
@@ -2324,8 +2294,10 @@ class Text extends Reactivable {
|
|
|
2324
2294
|
this.pathSets.length = 0;
|
|
2325
2295
|
for (let i = 0; i < renderPlugins.length; i++) {
|
|
2326
2296
|
const plugin = renderPlugins[i];
|
|
2327
|
-
|
|
2328
|
-
|
|
2297
|
+
const ps = plugin.pathSet;
|
|
2298
|
+
const count = ps?._lazyCount ?? ps?.paths.length;
|
|
2299
|
+
if (ps && count) {
|
|
2300
|
+
this.pathSets.push(ps);
|
|
2329
2301
|
}
|
|
2330
2302
|
}
|
|
2331
2303
|
this._updateInlineBox()._updatePathBox()._updateBoundingBox();
|
|
@@ -2362,6 +2334,45 @@ class Text extends Reactivable {
|
|
|
2362
2334
|
max.y - min.y
|
|
2363
2335
|
);
|
|
2364
2336
|
}
|
|
2337
|
+
// 单段字形包围盒(绝对坐标),逻辑与 getGlyphBox 等价但只覆盖一段;空段返回 undefined。
|
|
2338
|
+
_paragraphGlyphBox(paragraph) {
|
|
2339
|
+
const min = Vector2.MAX;
|
|
2340
|
+
const max = Vector2.MIN;
|
|
2341
|
+
const fragments = paragraph.fragments;
|
|
2342
|
+
for (let fi = 0; fi < fragments.length; fi++) {
|
|
2343
|
+
const chars = fragments[fi].characters;
|
|
2344
|
+
for (let i = 0; i < chars.length; i++) {
|
|
2345
|
+
const character = chars[i];
|
|
2346
|
+
if (character.getGlyphMinMax(min, max)) {
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
const { left, top, width, height } = character.inlineBox;
|
|
2350
|
+
min.clampMin(new Vector2(left, top), new Vector2(left + width, top + height));
|
|
2351
|
+
max.clampMax(new Vector2(left, top), new Vector2(left + width, top + height));
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
if (!Number.isFinite(min.x) || !Number.isFinite(min.y) || !Number.isFinite(max.x) || !Number.isFinite(max.y)) {
|
|
2355
|
+
return void 0;
|
|
2356
|
+
}
|
|
2357
|
+
return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
|
|
2358
|
+
}
|
|
2359
|
+
// 增量版整体字形盒:仅重排段重算并缓存 _glyphBox,未变段复用已平移的缓存,再并集。
|
|
2360
|
+
_computeGlyphBox() {
|
|
2361
|
+
const paragraphs = this.paragraphs;
|
|
2362
|
+
const boxes = [];
|
|
2363
|
+
for (let pi = 0; pi < paragraphs.length; pi++) {
|
|
2364
|
+
const paragraph = paragraphs[pi];
|
|
2365
|
+
let pbox = paragraph._glyphBox;
|
|
2366
|
+
if (paragraph._layoutDirty || !pbox) {
|
|
2367
|
+
pbox = this._paragraphGlyphBox(paragraph);
|
|
2368
|
+
paragraph._glyphBox = pbox;
|
|
2369
|
+
}
|
|
2370
|
+
if (pbox) {
|
|
2371
|
+
boxes.push(pbox);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
return boxes.length ? BoundingBox.from(...boxes) : new BoundingBox(0, 0, 0, 0);
|
|
2375
|
+
}
|
|
2365
2376
|
_updateInlineBox() {
|
|
2366
2377
|
const boxes = [];
|
|
2367
2378
|
const paragraphs = this._paragraphs;
|
|
@@ -2421,6 +2432,7 @@ class Text extends Reactivable {
|
|
|
2421
2432
|
}
|
|
2422
2433
|
const renderer = this._getRenderer(ctx);
|
|
2423
2434
|
renderer.pixelRatio = pixelRatio;
|
|
2435
|
+
renderer.region = options.region;
|
|
2424
2436
|
renderer.setup();
|
|
2425
2437
|
const renderPlugins = this._pluginsByRenderOrder;
|
|
2426
2438
|
for (let i = 0; i < renderPlugins.length; i++) {
|
|
@@ -2474,4 +2486,4 @@ __decorateClass([
|
|
|
2474
2486
|
property({ internal: true })
|
|
2475
2487
|
], Text.prototype, "fonts");
|
|
2476
2488
|
|
|
2477
|
-
export { Canvas2DRenderer as C,
|
|
2489
|
+
export { Canvas2DRenderer as C, Fragment as F, Measurer as M, Paragraph as P, Text as T, Character as a, backgroundPlugin as b, createSvgLoader as c, createSvgParser as d, getHighlightStyle as e, isEqualValue as f, getEffectTransform2D as g, highlightPlugin as h, isEqualObject as i, parseTransformOrigin as j, parseValueNumber as k, listStylePlugin as l, textDefaultStyle as m, outlinePlugin as o, parseColormap as p, renderPlugin as r, textDecorationPlugin as t };
|