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