@weasel-js/text 1.9.4 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/dist/{chunk-VZNK3C6F.js → chunk-Z6YRJ33J.js} +358 -695
- package/dist/chunk-Z6YRJ33J.js.map +1 -0
- package/dist/index.d.ts +511 -62
- package/dist/index.js +781 -21
- package/dist/index.js.map +1 -1
- package/dist/{test-seams-Czz9ua7X.d.ts → test-seams-CuLNcDew.d.ts} +191 -351
- package/dist/test-seams.d.ts +1 -1
- package/dist/test-seams.js +1 -1
- package/package.json +6 -5
- package/dist/chunk-VZNK3C6F.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { resolveScreenLength } from '@weasel-js/paint';
|
|
2
|
-
import { cssFamilyName, resolveFontVariant,
|
|
1
|
+
import { isSolidPaint, resolveScreenLength } from '@weasel-js/paint';
|
|
2
|
+
import { cssFamilyName, faceGeneration, resolveFontVariant, metricsOf, warnNoMetrics, resolveColorCluster, glyphIn, resolveGlyph } from '@weasel-js/font';
|
|
3
3
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __export = (target, all) => {
|
|
@@ -14,18 +14,24 @@ function tabAdvance(x, stop, min) {
|
|
|
14
14
|
const toNext = (Math.floor(x / stop) + 1) * stop - x;
|
|
15
15
|
return toNext < min ? toNext + stop : toNext;
|
|
16
16
|
}
|
|
17
|
-
function widthWithTabs(start, end, tabs, stopOf, flatWidth) {
|
|
17
|
+
function widthWithTabs(start, end, tabs, stopOf, flatWidth, piece) {
|
|
18
18
|
let x = 0;
|
|
19
19
|
let from = start;
|
|
20
|
+
const lay = (to) => {
|
|
21
|
+
if (to <= from) return;
|
|
22
|
+
piece?.(from, to, x);
|
|
23
|
+
x += flatWidth(from, to);
|
|
24
|
+
};
|
|
20
25
|
for (const t of tabs) {
|
|
21
26
|
if (t < start) continue;
|
|
22
27
|
if (t >= end) break;
|
|
23
|
-
|
|
28
|
+
lay(t);
|
|
24
29
|
const { stop, min } = stopOf(t);
|
|
25
30
|
x += tabAdvance(x, stop, min);
|
|
26
31
|
from = t + 1;
|
|
27
32
|
}
|
|
28
|
-
|
|
33
|
+
lay(end);
|
|
34
|
+
return x;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
// src/textStyle.ts
|
|
@@ -34,9 +40,9 @@ function resolveAlign(align, direction) {
|
|
|
34
40
|
if (align === "end") return direction === "rtl" ? "left" : "right";
|
|
35
41
|
return align;
|
|
36
42
|
}
|
|
37
|
-
var DEFAULT_FILL = {
|
|
43
|
+
var DEFAULT_FILL = { kind: "solid", color: "#000" };
|
|
38
44
|
function paintColor(p) {
|
|
39
|
-
return p
|
|
45
|
+
return isSolidPaint(p) ? p.color : "#000";
|
|
40
46
|
}
|
|
41
47
|
function defaultSelectionBackground(textColor) {
|
|
42
48
|
return `color-mix(in srgb, ${textColor} 25%, transparent)`;
|
|
@@ -51,7 +57,7 @@ var DEFAULT_TEXT_STYLE = {
|
|
|
51
57
|
lineHeight: 1.2,
|
|
52
58
|
tabSize: DEFAULT_TAB_SIZE,
|
|
53
59
|
wrap: false,
|
|
54
|
-
fill: DEFAULT_FILL,
|
|
60
|
+
fill: { paint: DEFAULT_FILL },
|
|
55
61
|
caretColor: paintColor(DEFAULT_FILL),
|
|
56
62
|
selectionBackground: defaultSelectionBackground(paintColor(DEFAULT_FILL)),
|
|
57
63
|
selectionColor: null,
|
|
@@ -69,12 +75,12 @@ function resolveTextStyle(style, paint, scale = 1) {
|
|
|
69
75
|
return fill === DEFAULT_TEXT_STYLE.fill && stroke === void 0 ? DEFAULT_TEXT_STYLE : {
|
|
70
76
|
...DEFAULT_TEXT_STYLE,
|
|
71
77
|
fill,
|
|
72
|
-
caretColor: paintColor(fill),
|
|
73
|
-
selectionBackground: defaultSelectionBackground(paintColor(fill)),
|
|
78
|
+
caretColor: paintColor(fill?.paint ?? null),
|
|
79
|
+
selectionBackground: defaultSelectionBackground(paintColor(fill?.paint ?? null)),
|
|
74
80
|
...stroke !== void 0 ? { stroke } : {}
|
|
75
81
|
};
|
|
76
82
|
}
|
|
77
|
-
const caretColor = style.caretColor ?? paintColor(fill);
|
|
83
|
+
const caretColor = style.caretColor ?? paintColor(fill?.paint ?? null);
|
|
78
84
|
let selectionBackground;
|
|
79
85
|
if (style.selectionBackground === "none") {
|
|
80
86
|
selectionBackground = null;
|
|
@@ -111,34 +117,6 @@ function fontString(s) {
|
|
|
111
117
|
return `${s.fontStyle} ${s.fontWeight} ${s.fontSize}px ${cssFamilyName(s.fontFamily)}`;
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
// src/layout/decorationMetrics.ts
|
|
115
|
-
var DECORATION_KINDS = ["underline", "strikethrough", "overline"];
|
|
116
|
-
var DEFAULT_DECORATION_METRICS = Object.freeze({
|
|
117
|
-
underline: Object.freeze({ offset: 0.1, thickness: 0.05 }),
|
|
118
|
-
strikethrough: Object.freeze({ offset: -0.3, thickness: 0.05 }),
|
|
119
|
-
overline: Object.freeze({ offset: -0.9, thickness: 0.05 })
|
|
120
|
-
});
|
|
121
|
-
var resolved = /* @__PURE__ */ new WeakMap();
|
|
122
|
-
function decorationMetrics(face) {
|
|
123
|
-
if (!face) return DEFAULT_DECORATION_METRICS;
|
|
124
|
-
let out = resolved.get(face);
|
|
125
|
-
if (!out) {
|
|
126
|
-
const underline = face.underline ?? DEFAULT_DECORATION_METRICS.underline;
|
|
127
|
-
out = Object.freeze({
|
|
128
|
-
underline,
|
|
129
|
-
strikethrough: face.strikethrough ?? DEFAULT_DECORATION_METRICS.strikethrough,
|
|
130
|
-
overline: Object.freeze({ offset: DEFAULT_DECORATION_METRICS.overline.offset, thickness: underline.thickness })
|
|
131
|
-
});
|
|
132
|
-
resolved.set(face, out);
|
|
133
|
-
}
|
|
134
|
-
return out;
|
|
135
|
-
}
|
|
136
|
-
function decorationRule(kind, baselineY, fontSize, face) {
|
|
137
|
-
const m = decorationMetrics(face)[kind];
|
|
138
|
-
const y0 = baselineY + fontSize * m.offset;
|
|
139
|
-
return { y0, y1: y0 + fontSize * m.thickness };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
120
|
// src/layout/lineBreak/lineBreakTable.ts
|
|
143
121
|
var lineBreakTable_exports = {};
|
|
144
122
|
__export(lineBreakTable_exports, {
|
|
@@ -518,6 +496,47 @@ function paragraphDirection(text, direction = "auto", engine = registered) {
|
|
|
518
496
|
}
|
|
519
497
|
return directionOfLevel(engine.baseLevel(engine.analyze(cps, "auto")));
|
|
520
498
|
}
|
|
499
|
+
var RTL_BLOCKS = [
|
|
500
|
+
[1424, 1535],
|
|
501
|
+
[1536, 1983],
|
|
502
|
+
[2144, 2303],
|
|
503
|
+
[64285, 65023],
|
|
504
|
+
[65136, 65279],
|
|
505
|
+
[67584, 69631],
|
|
506
|
+
[124928, 126975]
|
|
507
|
+
];
|
|
508
|
+
var RTL_BLOCK_NUMBERS = [
|
|
509
|
+
[1536, 1541],
|
|
510
|
+
[1632, 1644],
|
|
511
|
+
[1757, 1757],
|
|
512
|
+
[1776, 1785],
|
|
513
|
+
[2274, 2274],
|
|
514
|
+
[68912, 68921],
|
|
515
|
+
[69216, 69246]
|
|
516
|
+
];
|
|
517
|
+
var RTL_BLOCK_NEUTRALS = [
|
|
518
|
+
[1548, 1548],
|
|
519
|
+
[64830, 64831],
|
|
520
|
+
[65279, 65279]
|
|
521
|
+
];
|
|
522
|
+
var within = (ranges, cp) => ranges.some(([lo, hi]) => cp >= lo && cp <= hi);
|
|
523
|
+
var warnedLogicalRtl = false;
|
|
524
|
+
function warnRtlWithoutEngine(codePoints) {
|
|
525
|
+
if (warnedLogicalRtl) return;
|
|
526
|
+
for (const cp of codePoints) {
|
|
527
|
+
if (!within(RTL_BLOCKS, cp) || within(RTL_BLOCK_NUMBERS, cp) || within(RTL_BLOCK_NEUTRALS, cp)) continue;
|
|
528
|
+
warnedLogicalRtl = true;
|
|
529
|
+
console.warn(
|
|
530
|
+
// Naming the import in `from "<pkg>"` form would trip labkit's
|
|
531
|
+
// consumer smoke test, which greps dist for that exact shape.
|
|
532
|
+
"weasel layoutRuns: this text contains right-to-left characters but no `bidi` engine is registered, so it is being laid out in logical order \u2014 the glyphs will appear reversed. Import `bidi` out of the `@weasel-js/bidi` package and call `registerBidi(bidi)` once at startup."
|
|
533
|
+
);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function _resetBidiWarningForTests() {
|
|
538
|
+
warnedLogicalRtl = false;
|
|
539
|
+
}
|
|
521
540
|
|
|
522
541
|
// src/layout/bidiLines.ts
|
|
523
542
|
function analyzeParagraphs(engine, codePoints, isBreak, direction) {
|
|
@@ -583,17 +602,20 @@ function restoreRemoved(order, count, rtl) {
|
|
|
583
602
|
}
|
|
584
603
|
return out;
|
|
585
604
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
id = nextRefId++;
|
|
594
|
-
refIds.set(o, id);
|
|
605
|
+
function orderLine(paragraphs, start, count) {
|
|
606
|
+
const levels = new Array(count).fill(0);
|
|
607
|
+
if (paragraphs === null || count === 0) return { visual: levels.map((_, pos) => pos), levels };
|
|
608
|
+
const r = paragraphs.reorder(start, start + count);
|
|
609
|
+
for (let pos = 0; pos < count; pos++) {
|
|
610
|
+
const level = r.levels[pos];
|
|
611
|
+
if (typeof level === "number" && level >= 0) levels[pos] = level;
|
|
595
612
|
}
|
|
596
|
-
|
|
613
|
+
const kept = /* @__PURE__ */ new Set();
|
|
614
|
+
for (const at of r.order) {
|
|
615
|
+
const pos = at - start;
|
|
616
|
+
if (Number.isInteger(pos) && pos >= 0 && pos < count) kept.add(pos);
|
|
617
|
+
}
|
|
618
|
+
return { visual: restoreRemoved([...kept], count, (pos) => levels[pos] % 2 === 1), levels };
|
|
597
619
|
}
|
|
598
620
|
|
|
599
621
|
// src/measure/graphemes.ts
|
|
@@ -624,6 +646,123 @@ function graphemeCount(text) {
|
|
|
624
646
|
return graphemeEnds(text).length;
|
|
625
647
|
}
|
|
626
648
|
|
|
649
|
+
// src/layout/layoutEntries.ts
|
|
650
|
+
function measureRuns(runs, tabSize) {
|
|
651
|
+
const entries = [];
|
|
652
|
+
let prev;
|
|
653
|
+
let srcIndex = 0;
|
|
654
|
+
runs.forEach((run, runIndex) => {
|
|
655
|
+
const resolved = resolveFontVariant(run.fontFamily, run.fontWeight, run.fontStyle);
|
|
656
|
+
const metrics = metricsOf(resolved);
|
|
657
|
+
if (!metrics) {
|
|
658
|
+
warnNoMetrics(run.fontFamily, run.fontWeight, run.fontStyle);
|
|
659
|
+
srcIndex += run.srcMap?.length ?? run.text.length;
|
|
660
|
+
prev = void 0;
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const runBase = srcIndex;
|
|
664
|
+
const map = run.srcMap;
|
|
665
|
+
const clusterEnds = graphemeEnds(run.text);
|
|
666
|
+
let cluster = 0;
|
|
667
|
+
let clusterStart = 0;
|
|
668
|
+
let color = null;
|
|
669
|
+
let at = 0;
|
|
670
|
+
for (const ch of run.text) {
|
|
671
|
+
const cp = ch.codePointAt(0);
|
|
672
|
+
const fontSize = run.sizeMap ? run.sizeMap[at] : run.fontSize;
|
|
673
|
+
if (at === clusterStart) {
|
|
674
|
+
color = resolveColorCluster(
|
|
675
|
+
resolved,
|
|
676
|
+
run.fontFamily,
|
|
677
|
+
run.fontWeight,
|
|
678
|
+
run.fontStyle,
|
|
679
|
+
run.text.slice(at, clusterEnds[cluster])
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
const clusterEnd = at + ch.length === clusterEnds[cluster];
|
|
683
|
+
if (clusterEnd) clusterStart = clusterEnds[cluster++];
|
|
684
|
+
const tracking = clusterEnd ? run.letterSpacing : 0;
|
|
685
|
+
const srcStart = map ? runBase + map.starts[at] : runBase + at;
|
|
686
|
+
const srcEnd = map ? runBase + map.ends[at] : runBase + at + ch.length;
|
|
687
|
+
at += ch.length;
|
|
688
|
+
srcIndex = map ? runBase + map.length : srcEnd;
|
|
689
|
+
const kerningBefore = prev ? prev.metrics.kernOf(prev.cp, cp) * (prev.fontSize / prev.metrics.size) : 0;
|
|
690
|
+
const blank = {
|
|
691
|
+
run: runIndex,
|
|
692
|
+
cp,
|
|
693
|
+
fontSize,
|
|
694
|
+
lineSize: Math.max(run.fontSize, run.strutSize ?? 0),
|
|
695
|
+
// Optional on the authored run type; absent, every y would go NaN.
|
|
696
|
+
baselineShift: run.baselineShift ?? 0,
|
|
697
|
+
srcIndex: srcStart,
|
|
698
|
+
srcEnd,
|
|
699
|
+
clusterEnd,
|
|
700
|
+
resolved,
|
|
701
|
+
metrics,
|
|
702
|
+
font: null,
|
|
703
|
+
glyph: null,
|
|
704
|
+
advance: 0,
|
|
705
|
+
tracking: 0,
|
|
706
|
+
kerningBefore: 0,
|
|
707
|
+
drawsInk: false
|
|
708
|
+
};
|
|
709
|
+
const inked = (hit, kerning) => ({
|
|
710
|
+
...blank,
|
|
711
|
+
resolved: hit.resolved,
|
|
712
|
+
metrics: hit.metrics,
|
|
713
|
+
font: hit.font,
|
|
714
|
+
glyph: hit.glyph,
|
|
715
|
+
advance: hit.advance * (fontSize / hit.metrics.size),
|
|
716
|
+
tracking,
|
|
717
|
+
kerningBefore: kerning,
|
|
718
|
+
drawsInk: true
|
|
719
|
+
});
|
|
720
|
+
if (color) {
|
|
721
|
+
entries.push(clusterEnd ? inked(color, 0) : blank);
|
|
722
|
+
prev = void 0;
|
|
723
|
+
} else if (isHardLineBreak(cp)) {
|
|
724
|
+
entries.push(blank);
|
|
725
|
+
prev = void 0;
|
|
726
|
+
} else if (cp === 9 || cp === 32) {
|
|
727
|
+
const space = metrics.advanceOf(32);
|
|
728
|
+
const advance = space !== null ? space * (fontSize / metrics.size) : fontSize * 0.25;
|
|
729
|
+
if (cp === 9) {
|
|
730
|
+
entries.push({ ...blank, tab: { stop: tabSize * (advance + run.letterSpacing), min: advance / 2 } });
|
|
731
|
+
prev = void 0;
|
|
732
|
+
} else {
|
|
733
|
+
const own = glyphIn(resolved, 32);
|
|
734
|
+
prev = {
|
|
735
|
+
...blank,
|
|
736
|
+
font: own?.font ?? null,
|
|
737
|
+
glyph: own?.glyph ?? null,
|
|
738
|
+
advance,
|
|
739
|
+
tracking,
|
|
740
|
+
kerningBefore
|
|
741
|
+
};
|
|
742
|
+
entries.push(prev);
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
const hit = resolveGlyph(resolved, run.fontFamily, run.fontWeight, run.fontStyle, cp);
|
|
746
|
+
prev = hit ? inked(hit, kerningBefore) : blank;
|
|
747
|
+
entries.push(prev);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
return entries;
|
|
752
|
+
}
|
|
753
|
+
function mirrorEntries(entries, levels, mirror, runs) {
|
|
754
|
+
return entries.map((entry, pos) => {
|
|
755
|
+
if (levels[pos] % 2 === 0) return entry;
|
|
756
|
+
const cp = mirror(entry.cp);
|
|
757
|
+
if (cp === null || cp === entry.cp) return entry;
|
|
758
|
+
if (entry.font === null) return { ...entry, cp };
|
|
759
|
+
const run = runs[entry.run];
|
|
760
|
+
const hit = resolveGlyph(entry.resolved, run.fontFamily, run.fontWeight, run.fontStyle, cp, false);
|
|
761
|
+
if (!hit) return entry;
|
|
762
|
+
return { ...entry, cp, resolved: hit.resolved, metrics: hit.metrics, font: hit.font, glyph: hit.glyph };
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
627
766
|
// src/layout/lineBreak/wrapLines.ts
|
|
628
767
|
var hangs = (cp) => cp === 32 || cp === 9;
|
|
629
768
|
function wrapLines(cps, maxWidth, widthOf) {
|
|
@@ -660,681 +799,205 @@ function wrapLines(cps, maxWidth, widthOf) {
|
|
|
660
799
|
return lines;
|
|
661
800
|
}
|
|
662
801
|
|
|
663
|
-
// src/layout/
|
|
664
|
-
function
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
return
|
|
668
|
-
|
|
669
|
-
base: font.common.base,
|
|
670
|
-
...ascent !== void 0 && descent !== void 0 ? { ascent: ascent * size, descent: descent * size } : { ascent: font.common.base },
|
|
671
|
-
advanceOf: (cp) => font.charMap.get(cp)?.xadvance ?? null,
|
|
672
|
-
kernOf: (l, r) => font.kerningMap.get(l)?.get(r) ?? 0
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
function fillKey(p) {
|
|
676
|
-
if (p === null) return "none";
|
|
677
|
-
if ("color" in p) return `s:${p.color}:${p.opacity ?? 1}`;
|
|
678
|
-
return `nx:${refId(p)}`;
|
|
679
|
-
}
|
|
680
|
-
function sameFill(a, b) {
|
|
681
|
-
if (a === b) return true;
|
|
682
|
-
if (a !== null && b !== null && "color" in a && "color" in b) {
|
|
683
|
-
return a.color === b.color && (a.opacity ?? 1) === (b.opacity ?? 1);
|
|
684
|
-
}
|
|
685
|
-
return false;
|
|
686
|
-
}
|
|
687
|
-
function strokePaints(s) {
|
|
688
|
-
if (s?.paint === void 0) return false;
|
|
689
|
-
const w = s.width ?? 1;
|
|
690
|
-
return (typeof w === "number" ? w : w.px) > 0;
|
|
802
|
+
// src/layout/layoutLines.ts
|
|
803
|
+
function aboveBaseline(e, lineHeight) {
|
|
804
|
+
const k = e.lineSize / e.metrics.size;
|
|
805
|
+
const ascent = e.metrics.ascent * k;
|
|
806
|
+
if (e.metrics.descent === void 0) return ascent;
|
|
807
|
+
return (e.lineSize * lineHeight - ascent - e.metrics.descent * k) / 2 + ascent;
|
|
691
808
|
}
|
|
692
|
-
function
|
|
693
|
-
if (!strokePaints(s)) return "-";
|
|
694
|
-
const width = s.width ?? 1;
|
|
695
|
-
return [
|
|
696
|
-
// A screen-pixel width keys apart from the equal world-unit number: the two
|
|
697
|
-
// resolve to different ribbons, so they must not share a draw call.
|
|
698
|
-
fillKey(s.paint),
|
|
699
|
-
typeof width === "number" ? width : `px${width.px}`,
|
|
700
|
-
s.join ?? "miter",
|
|
701
|
-
s.cap ?? "butt",
|
|
702
|
-
s.miterLimit ?? "",
|
|
703
|
-
s.align ?? "center",
|
|
704
|
-
(s.dash ?? []).join(",")
|
|
705
|
-
].join(":");
|
|
706
|
-
}
|
|
707
|
-
function groupKey(family, weight, style, synthetic, fill, stroke, source, page) {
|
|
708
|
-
return `${family}|${weight}|${style}|${synthetic.bold ? 1 : 0}${synthetic.italic ? 1 : 0}|${fillKey(fill)}|${strokeKey(stroke)}|${source}|${page}`;
|
|
709
|
-
}
|
|
710
|
-
function getOrCreateGroup(ctx, run, resolved2, page, sourceOverride) {
|
|
711
|
-
const atlasFamily = resolved2.resolved.family;
|
|
712
|
-
const resolvedWeight = resolved2.resolved.weight;
|
|
713
|
-
const resolvedStyle = resolved2.resolved.style;
|
|
714
|
-
const source = sourceOverride ?? resolved2.source;
|
|
715
|
-
const key = groupKey(
|
|
716
|
-
atlasFamily,
|
|
717
|
-
resolvedWeight,
|
|
718
|
-
resolvedStyle,
|
|
719
|
-
resolved2.synthetic,
|
|
720
|
-
run.fill,
|
|
721
|
-
source === "outline" && strokePaints(run.stroke) ? run.stroke : void 0,
|
|
722
|
-
source,
|
|
723
|
-
page
|
|
724
|
-
);
|
|
725
|
-
let g = ctx.groups.get(key);
|
|
726
|
-
if (!g) {
|
|
727
|
-
g = {
|
|
728
|
-
family: atlasFamily,
|
|
729
|
-
weight: resolvedWeight,
|
|
730
|
-
style: resolvedStyle,
|
|
731
|
-
synthetic: { ...resolved2.synthetic },
|
|
732
|
-
source,
|
|
733
|
-
page,
|
|
734
|
-
fill: run.fill,
|
|
735
|
-
// Only the outline tier can paint it, and carrying it on an SDF group
|
|
736
|
-
// would be a promise the renderer cannot keep.
|
|
737
|
-
...source === "outline" && strokePaints(run.stroke) ? { stroke: run.stroke } : {},
|
|
738
|
-
quads: [],
|
|
739
|
-
glyphs: []
|
|
740
|
-
};
|
|
741
|
-
ctx.groups.set(key, g);
|
|
742
|
-
}
|
|
743
|
-
return g;
|
|
744
|
-
}
|
|
745
|
-
function resolveGlyph(run, font, resolved2, cp, warn = true) {
|
|
746
|
-
const direct = font.charMap.get(cp);
|
|
747
|
-
if (direct) return { glyph: direct, font, resolved: resolved2 };
|
|
748
|
-
const fallback = resolveGlyphFallback(run.fontFamily, run.fontWeight, run.fontStyle);
|
|
749
|
-
const face = fallback?.dynamicFace;
|
|
750
|
-
if (fallback && face) {
|
|
751
|
-
const glyph = face.requestGlyph(cp);
|
|
752
|
-
if (glyph.xadvance > 0 || glyph.width > 0) {
|
|
753
|
-
return { glyph, font: face.font, resolved: fallback };
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
if (warn) warnMissingGlyphOnce(resolved2.resolved.family, cp);
|
|
757
|
-
return null;
|
|
758
|
-
}
|
|
759
|
-
var RTL_BLOCKS = [
|
|
760
|
-
[1424, 1535],
|
|
761
|
-
[1536, 1983],
|
|
762
|
-
[2144, 2303],
|
|
763
|
-
[64285, 65023],
|
|
764
|
-
[65136, 65279],
|
|
765
|
-
[67584, 69631],
|
|
766
|
-
[124928, 126975]
|
|
767
|
-
];
|
|
768
|
-
var warnedLogicalRtl = false;
|
|
769
|
-
function warnLogicalRtlOnce() {
|
|
770
|
-
if (warnedLogicalRtl) return;
|
|
771
|
-
warnedLogicalRtl = true;
|
|
772
|
-
console.warn(
|
|
773
|
-
// Naming the import in `from "<pkg>"` form would trip labkit's
|
|
774
|
-
// consumer smoke test, which greps dist for that exact shape.
|
|
775
|
-
"weasel layoutRuns: this text contains right-to-left characters but no `bidi` engine is registered, so it is being laid out in logical order \u2014 the glyphs will appear reversed. Import `bidi` out of the `@weasel-js/bidi` package and call `registerBidi(bidi)` once at startup."
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
var warnedMissingGlyphs = /* @__PURE__ */ new Set();
|
|
779
|
-
function warnMissingGlyphOnce(family, cp) {
|
|
780
|
-
const key = `${family}|${cp}`;
|
|
781
|
-
if (warnedMissingGlyphs.has(key)) return;
|
|
782
|
-
warnedMissingGlyphs.add(key);
|
|
783
|
-
const ch = String.fromCodePoint(cp);
|
|
784
|
-
console.warn(
|
|
785
|
-
`weasel layoutRuns: no glyph for U+${cp.toString(16).toUpperCase().padStart(4, "0")} (${JSON.stringify(ch)}) in "${family}", and the dynamic tier could not rasterize it \u2014 skipping the character. Bake it into the atlas, or call registerCanvasFont("${family}") to serve missing codepoints from installed fonts.`
|
|
786
|
-
);
|
|
787
|
-
}
|
|
788
|
-
var warnedNoMetrics = /* @__PURE__ */ new Set();
|
|
789
|
-
function warnNoMetricsOnce(family, weight, style) {
|
|
790
|
-
const key = `${family}|${weight}|${style}`;
|
|
791
|
-
if (warnedNoMetrics.has(key)) return;
|
|
792
|
-
warnedNoMetrics.add(key);
|
|
793
|
-
console.warn(
|
|
794
|
-
`weasel layoutRuns: no metrics for "${family}" ${weight}/${style} \u2014 neither an atlas nor an outline face resolved, so every run set in it lays out as nothing. Register it with registerFont / registerCanvasFont / registerFontOutlines. If you did, check for a duplicated @weasel-js/font in node_modules \u2014 its registry is module state, so a second copy is a second, empty one.`
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
function layoutRuns(runs, opts) {
|
|
798
|
-
const ctx = { groups: /* @__PURE__ */ new Map() };
|
|
799
|
-
const bidi = opts.bidi ?? registeredBidi();
|
|
800
|
-
const direction = opts.direction ?? "auto";
|
|
801
|
-
const justify = opts.justify === true || opts.align === "justify";
|
|
802
|
-
const tabSize = opts.tabSize ?? DEFAULT_TAB_SIZE;
|
|
803
|
-
const lineSize = (e) => Math.max(e.run.fontSize, e.run.strutSize ?? 0);
|
|
804
|
-
const aboveBaseline = (e) => {
|
|
805
|
-
const k = lineSize(e) / e.metrics.size;
|
|
806
|
-
const ascent = e.metrics.ascent * k;
|
|
807
|
-
if (e.metrics.descent === void 0) return ascent;
|
|
808
|
-
return (lineSize(e) * opts.lineHeight - ascent - e.metrics.descent * k) / 2 + ascent;
|
|
809
|
-
};
|
|
810
|
-
const entries = [];
|
|
811
|
-
const closesCluster = [];
|
|
812
|
-
const runFaces = /* @__PURE__ */ new Map();
|
|
813
|
-
let prevCp;
|
|
814
|
-
let prevMetrics;
|
|
815
|
-
let prevFontSize;
|
|
816
|
-
let srcIndex = 0;
|
|
817
|
-
for (const run of runs) {
|
|
818
|
-
const resolved2 = resolveFontVariant(run.fontFamily, run.fontWeight, run.fontStyle);
|
|
819
|
-
runFaces.set(run, faceMetricsOf(resolved2));
|
|
820
|
-
const outlineFace = resolved2.outlineFace;
|
|
821
|
-
const font = resolved2.entry?.font ?? resolved2.dynamicFace?.font ?? null;
|
|
822
|
-
const metrics = outlineFace ? {
|
|
823
|
-
size: 1,
|
|
824
|
-
base: outlineFace.ascender,
|
|
825
|
-
ascent: outlineFace.faceMetrics?.ascent ?? outlineFace.ascender,
|
|
826
|
-
...outlineFace.faceMetrics?.descent !== void 0 ? { descent: outlineFace.faceMetrics.descent } : {},
|
|
827
|
-
advanceOf: (cp) => outlineFace.advanceOf(cp),
|
|
828
|
-
kernOf: (l, r) => outlineFace.kernOf(l, r)
|
|
829
|
-
} : font ? atlasMetrics(font) : void 0;
|
|
830
|
-
if (!metrics) {
|
|
831
|
-
if (!fontPending(run.fontFamily, run.fontWeight, run.fontStyle)) {
|
|
832
|
-
warnNoMetricsOnce(run.fontFamily, run.fontWeight, run.fontStyle);
|
|
833
|
-
}
|
|
834
|
-
srcIndex += run.srcMap?.length ?? run.text.length;
|
|
835
|
-
prevCp = void 0;
|
|
836
|
-
prevMetrics = void 0;
|
|
837
|
-
prevFontSize = void 0;
|
|
838
|
-
continue;
|
|
839
|
-
}
|
|
840
|
-
const runBase = srcIndex;
|
|
841
|
-
const map = run.srcMap;
|
|
842
|
-
const sizes = run.sizeMap;
|
|
843
|
-
let at = 0;
|
|
844
|
-
const runTracking = run.letterSpacing;
|
|
845
|
-
const clusterEnds = new Set(graphemeEnds(run.text));
|
|
846
|
-
for (const ch of [...run.text]) {
|
|
847
|
-
const cp = ch.codePointAt(0);
|
|
848
|
-
const fontSize = sizes ? sizes[at] : run.fontSize;
|
|
849
|
-
const scale = fontSize / metrics.size;
|
|
850
|
-
const clusterEnd = clusterEnds.has(at + ch.length);
|
|
851
|
-
closesCluster.push(clusterEnd);
|
|
852
|
-
const tracking = clusterEnd ? runTracking : 0;
|
|
853
|
-
const isNewline = isHardLineBreak(cp);
|
|
854
|
-
const isSpace = cp === 32;
|
|
855
|
-
const srcStart = map ? runBase + map.starts[at] : runBase + at;
|
|
856
|
-
const srcEnd = map ? runBase + map.ends[at] : runBase + at + ch.length;
|
|
857
|
-
at += ch.length;
|
|
858
|
-
srcIndex = map ? runBase + map.length : srcEnd;
|
|
859
|
-
if (isNewline) {
|
|
860
|
-
entries.push({
|
|
861
|
-
run,
|
|
862
|
-
font,
|
|
863
|
-
metrics,
|
|
864
|
-
glyph: { id: cp, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
|
|
865
|
-
// A newline consumes no advance, so it takes no tracking either.
|
|
866
|
-
cp,
|
|
867
|
-
advance: 0,
|
|
868
|
-
tracking: 0,
|
|
869
|
-
kerningBefore: 0,
|
|
870
|
-
isSpace: false,
|
|
871
|
-
isNewline: true,
|
|
872
|
-
drawsInk: false,
|
|
873
|
-
resolved: resolved2,
|
|
874
|
-
fontSize,
|
|
875
|
-
srcIndex: srcStart,
|
|
876
|
-
srcEnd,
|
|
877
|
-
flat: 0
|
|
878
|
-
});
|
|
879
|
-
prevCp = void 0;
|
|
880
|
-
prevMetrics = void 0;
|
|
881
|
-
prevFontSize = void 0;
|
|
882
|
-
continue;
|
|
883
|
-
}
|
|
884
|
-
const isTab = cp === 9;
|
|
885
|
-
if (isSpace || isTab) {
|
|
886
|
-
const spaceGlyph = resolved2.dynamicFace ? resolved2.dynamicFace.requestGlyph(32) : font?.charMap.get(32) ?? null;
|
|
887
|
-
const spaceAdvance = spaceGlyph ? spaceGlyph.xadvance : metrics.advanceOf(32);
|
|
888
|
-
const advance = spaceAdvance !== null ? spaceAdvance * scale : fontSize * 0.25;
|
|
889
|
-
if (isTab) {
|
|
890
|
-
entries.push({
|
|
891
|
-
run,
|
|
892
|
-
font,
|
|
893
|
-
metrics,
|
|
894
|
-
glyph: { id: cp, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
|
|
895
|
-
cp,
|
|
896
|
-
advance: 0,
|
|
897
|
-
tracking: 0,
|
|
898
|
-
kerningBefore: 0,
|
|
899
|
-
isSpace: false,
|
|
900
|
-
isNewline: false,
|
|
901
|
-
tab: { stop: tabSize * (advance + runTracking), min: advance / 2 },
|
|
902
|
-
drawsInk: false,
|
|
903
|
-
resolved: resolved2,
|
|
904
|
-
fontSize,
|
|
905
|
-
srcIndex: srcStart,
|
|
906
|
-
srcEnd,
|
|
907
|
-
flat: 0
|
|
908
|
-
});
|
|
909
|
-
prevCp = void 0;
|
|
910
|
-
prevMetrics = void 0;
|
|
911
|
-
prevFontSize = void 0;
|
|
912
|
-
continue;
|
|
913
|
-
}
|
|
914
|
-
let kerningBefore2 = 0;
|
|
915
|
-
if (prevCp !== void 0 && prevMetrics !== void 0 && prevFontSize !== void 0) {
|
|
916
|
-
kerningBefore2 = prevMetrics.kernOf(prevCp, cp) * (prevFontSize / prevMetrics.size);
|
|
917
|
-
}
|
|
918
|
-
entries.push({
|
|
919
|
-
run,
|
|
920
|
-
font,
|
|
921
|
-
metrics,
|
|
922
|
-
glyph: spaceGlyph ?? { id: 32, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
|
|
923
|
-
cp,
|
|
924
|
-
advance,
|
|
925
|
-
tracking,
|
|
926
|
-
kerningBefore: kerningBefore2,
|
|
927
|
-
isSpace: true,
|
|
928
|
-
isNewline: false,
|
|
929
|
-
drawsInk: false,
|
|
930
|
-
resolved: resolved2,
|
|
931
|
-
fontSize,
|
|
932
|
-
srcIndex: srcStart,
|
|
933
|
-
srcEnd,
|
|
934
|
-
flat: 0
|
|
935
|
-
});
|
|
936
|
-
prevCp = cp;
|
|
937
|
-
prevMetrics = metrics;
|
|
938
|
-
prevFontSize = fontSize;
|
|
939
|
-
continue;
|
|
940
|
-
}
|
|
941
|
-
if (outlineFace) {
|
|
942
|
-
const adv = metrics.advanceOf(cp);
|
|
943
|
-
if (adv === null) {
|
|
944
|
-
entries.push({
|
|
945
|
-
run,
|
|
946
|
-
font: null,
|
|
947
|
-
metrics,
|
|
948
|
-
glyph: null,
|
|
949
|
-
cp,
|
|
950
|
-
advance: 0,
|
|
951
|
-
tracking: 0,
|
|
952
|
-
kerningBefore: 0,
|
|
953
|
-
isSpace,
|
|
954
|
-
isNewline: false,
|
|
955
|
-
drawsInk: false,
|
|
956
|
-
resolved: resolved2,
|
|
957
|
-
fontSize,
|
|
958
|
-
srcIndex: srcStart,
|
|
959
|
-
srcEnd,
|
|
960
|
-
flat: 0
|
|
961
|
-
});
|
|
962
|
-
prevCp = cp;
|
|
963
|
-
prevMetrics = metrics;
|
|
964
|
-
prevFontSize = fontSize;
|
|
965
|
-
continue;
|
|
966
|
-
}
|
|
967
|
-
let kerningBefore2 = 0;
|
|
968
|
-
if (prevCp !== void 0 && prevMetrics !== void 0 && prevFontSize !== void 0) {
|
|
969
|
-
kerningBefore2 = prevMetrics.kernOf(prevCp, cp) * (prevFontSize / prevMetrics.size);
|
|
970
|
-
}
|
|
971
|
-
entries.push({
|
|
972
|
-
run,
|
|
973
|
-
font: null,
|
|
974
|
-
metrics,
|
|
975
|
-
glyph: null,
|
|
976
|
-
cp,
|
|
977
|
-
advance: adv * scale,
|
|
978
|
-
tracking,
|
|
979
|
-
kerningBefore: kerningBefore2,
|
|
980
|
-
isSpace,
|
|
981
|
-
isNewline: false,
|
|
982
|
-
drawsInk: !isSpace,
|
|
983
|
-
resolved: resolved2,
|
|
984
|
-
fontSize,
|
|
985
|
-
srcIndex: srcStart,
|
|
986
|
-
srcEnd,
|
|
987
|
-
flat: 0
|
|
988
|
-
});
|
|
989
|
-
prevCp = cp;
|
|
990
|
-
prevMetrics = metrics;
|
|
991
|
-
prevFontSize = fontSize;
|
|
992
|
-
continue;
|
|
993
|
-
}
|
|
994
|
-
const hit = resolved2.dynamicFace ? { glyph: resolved2.dynamicFace.requestGlyph(cp), font, resolved: resolved2 } : resolveGlyph(run, font, resolved2, cp);
|
|
995
|
-
if (!hit) {
|
|
996
|
-
entries.push({
|
|
997
|
-
run,
|
|
998
|
-
font: null,
|
|
999
|
-
metrics,
|
|
1000
|
-
glyph: null,
|
|
1001
|
-
cp,
|
|
1002
|
-
advance: 0,
|
|
1003
|
-
tracking: 0,
|
|
1004
|
-
kerningBefore: 0,
|
|
1005
|
-
isSpace,
|
|
1006
|
-
isNewline: false,
|
|
1007
|
-
drawsInk: false,
|
|
1008
|
-
resolved: resolved2,
|
|
1009
|
-
fontSize,
|
|
1010
|
-
srcIndex: srcStart,
|
|
1011
|
-
srcEnd,
|
|
1012
|
-
flat: 0
|
|
1013
|
-
});
|
|
1014
|
-
prevCp = cp;
|
|
1015
|
-
prevMetrics = metrics;
|
|
1016
|
-
prevFontSize = fontSize;
|
|
1017
|
-
continue;
|
|
1018
|
-
}
|
|
1019
|
-
const glyphFont = hit.font;
|
|
1020
|
-
const glyphMetrics = glyphFont === font ? metrics : atlasMetrics(glyphFont);
|
|
1021
|
-
const glyphScale = fontSize / glyphFont.info.size;
|
|
1022
|
-
let kerningBefore = 0;
|
|
1023
|
-
if (prevCp !== void 0 && prevMetrics !== void 0 && prevFontSize !== void 0) {
|
|
1024
|
-
kerningBefore = prevMetrics.kernOf(prevCp, cp) * (prevFontSize / prevMetrics.size);
|
|
1025
|
-
}
|
|
1026
|
-
entries.push({
|
|
1027
|
-
run,
|
|
1028
|
-
font: glyphFont,
|
|
1029
|
-
metrics: glyphMetrics,
|
|
1030
|
-
glyph: hit.glyph,
|
|
1031
|
-
cp,
|
|
1032
|
-
advance: hit.glyph.xadvance * glyphScale,
|
|
1033
|
-
tracking,
|
|
1034
|
-
kerningBefore,
|
|
1035
|
-
isSpace,
|
|
1036
|
-
isNewline: false,
|
|
1037
|
-
drawsInk: !isSpace,
|
|
1038
|
-
resolved: hit.resolved,
|
|
1039
|
-
fontSize,
|
|
1040
|
-
srcIndex: srcStart,
|
|
1041
|
-
srcEnd,
|
|
1042
|
-
flat: 0
|
|
1043
|
-
});
|
|
1044
|
-
prevCp = cp;
|
|
1045
|
-
prevMetrics = glyphMetrics;
|
|
1046
|
-
prevFontSize = fontSize;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
for (let i = 0; i < entries.length; i++) entries[i].flat = i;
|
|
1050
|
-
const paragraphs = bidi ? analyzeParagraphs(bidi, entries.map((e) => e.cp), (i) => entries[i].isNewline, direction) : null;
|
|
1051
|
-
if (!bidi && entries.some((e) => RTL_BLOCKS.some(([lo, hi]) => e.cp >= lo && e.cp <= hi))) {
|
|
1052
|
-
warnLogicalRtlOnce();
|
|
1053
|
-
}
|
|
1054
|
-
const openingWidth = (e) => e.advance + e.tracking;
|
|
809
|
+
function breakLines(entries, maxWidth, lineHeight) {
|
|
1055
810
|
const sum = new Float64Array(entries.length + 1);
|
|
1056
|
-
for (let k = 0; k < entries.length; k++) {
|
|
1057
|
-
const e = entries[k];
|
|
1058
|
-
sum[k + 1] = sum[k] + e.kerningBefore + e.advance + e.tracking;
|
|
1059
|
-
}
|
|
1060
|
-
const flatWidth = (start, end) => openingWidth(entries[start]) + sum[end] - sum[start + 1];
|
|
1061
811
|
const tabs = [];
|
|
1062
812
|
entries.forEach((e, k) => {
|
|
813
|
+
sum[k + 1] = sum[k] + e.kerningBefore + e.advance + e.tracking;
|
|
1063
814
|
if (e.tab) tabs.push(k);
|
|
1064
815
|
});
|
|
816
|
+
const flatWidth = (start, end) => entries[start].advance + entries[start].tracking + sum[end] - sum[start + 1];
|
|
1065
817
|
const widthOf = tabs.length === 0 ? flatWidth : (start, end) => widthWithTabs(start, end, tabs, (k) => entries[k].tab, flatWidth);
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
818
|
+
return wrapLines(entries.map((e) => e.cp), maxWidth, widthOf).map((span) => {
|
|
819
|
+
const line = {
|
|
820
|
+
start: span.start,
|
|
821
|
+
entries: [],
|
|
822
|
+
width: 0,
|
|
823
|
+
height: 0,
|
|
824
|
+
ascent: 0,
|
|
825
|
+
wrapped: span.wrapped,
|
|
826
|
+
srcEnd: 0
|
|
827
|
+
};
|
|
828
|
+
if (span.start === span.end) {
|
|
829
|
+
const brk = entries[span.end];
|
|
830
|
+
line.height = brk.lineSize * lineHeight;
|
|
831
|
+
line.ascent = aboveBaseline(brk, lineHeight);
|
|
832
|
+
line.srcEnd = brk.srcIndex;
|
|
833
|
+
return line;
|
|
1073
834
|
}
|
|
1074
|
-
for (let k =
|
|
835
|
+
for (let k = span.start; k < span.end; k++) {
|
|
1075
836
|
const e = entries[k];
|
|
1076
|
-
const opened = k >
|
|
837
|
+
const opened = k > span.start ? e : { ...e, kerningBefore: 0 };
|
|
1077
838
|
const placed = e.tab ? { ...opened, advance: tabAdvance(line.width, e.tab.stop, e.tab.min) } : opened;
|
|
1078
839
|
line.entries.push(placed);
|
|
1079
840
|
line.width += placed.kerningBefore + placed.advance + placed.tracking;
|
|
1080
|
-
line.height = Math.max(line.height, lineSize
|
|
1081
|
-
|
|
1082
|
-
lines.push(line);
|
|
1083
|
-
}
|
|
1084
|
-
function outlineFor(e) {
|
|
1085
|
-
const r0 = e.resolved.resolved;
|
|
1086
|
-
if (e.resolved.source === "outline") {
|
|
1087
|
-
return glyphOutline(r0.family, r0.weight, r0.style, e.cp);
|
|
841
|
+
line.height = Math.max(line.height, e.lineSize * lineHeight);
|
|
842
|
+
line.ascent = Math.max(line.ascent, aboveBaseline(placed, lineHeight));
|
|
1088
843
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
844
|
+
line.srcEnd = entries[span.end - 1].srcEnd;
|
|
845
|
+
return line;
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
function hungWidth(entries) {
|
|
849
|
+
let hung = 0;
|
|
850
|
+
for (let k = entries.length - 1; k >= 0 && hangs(entries[k].cp); k--) {
|
|
851
|
+
const e = entries[k];
|
|
852
|
+
hung += e.kerningBefore + e.advance + e.tracking;
|
|
1095
853
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
854
|
+
return hung;
|
|
855
|
+
}
|
|
856
|
+
function wordGaps(entries) {
|
|
857
|
+
const gaps = /* @__PURE__ */ new Set();
|
|
858
|
+
let first = 0;
|
|
859
|
+
while (first < entries.length && entries[first].cp === 32) first++;
|
|
860
|
+
let last = entries.length - 1;
|
|
861
|
+
while (last > first && entries[last].cp === 32) last--;
|
|
862
|
+
for (let k = first + 1; k < last; k++) if (entries[k].cp === 32) gaps.add(k);
|
|
863
|
+
return gaps;
|
|
864
|
+
}
|
|
865
|
+
function alignOffset(align, alignWidth, inkWidth) {
|
|
866
|
+
if (align === "left") return 0;
|
|
867
|
+
if (!Number.isFinite(alignWidth)) return align === "center" ? -inkWidth / 2 : -inkWidth;
|
|
868
|
+
const slack = alignWidth - inkWidth;
|
|
869
|
+
return align === "center" ? slack / 2 : slack;
|
|
870
|
+
}
|
|
871
|
+
function placeLine(line, y, order, align, alignWidth, justify) {
|
|
872
|
+
const { entries } = line;
|
|
873
|
+
const hung = hungWidth(entries);
|
|
874
|
+
const inkWidth = line.width - hung;
|
|
875
|
+
const slack = alignWidth - inkWidth;
|
|
876
|
+
const gaps = justify && line.wrapped && Number.isFinite(alignWidth) && slack > 0 ? wordGaps(entries) : /* @__PURE__ */ new Set();
|
|
877
|
+
const gapExtra = gaps.size > 0 ? slack / gaps.size : 0;
|
|
878
|
+
const spread = gapExtra * gaps.size;
|
|
879
|
+
const inkLeft = spread > 0 ? 0 : alignOffset(align, alignWidth, inkWidth);
|
|
880
|
+
const hangsLeft = hung > 0 && order.levels[entries.length - 1] % 2 === 1;
|
|
881
|
+
const left = hangsLeft ? inkLeft - hung : inkLeft;
|
|
882
|
+
const lineBaselineY = y + line.ascent;
|
|
883
|
+
const cells = new Array(entries.length);
|
|
884
|
+
const glyphs = [];
|
|
885
|
+
let penX = left;
|
|
886
|
+
let prevPos = -1;
|
|
887
|
+
for (const pos of order.visual) {
|
|
888
|
+
const e = entries[pos];
|
|
889
|
+
penX += prevPos < 0 ? 0 : pos === prevPos + 1 ? e.kerningBefore : prevPos === pos + 1 ? entries[prevPos].kerningBefore : 0;
|
|
890
|
+
prevPos = pos;
|
|
891
|
+
const step = e.advance + e.tracking + (gaps.has(pos) ? gapExtra : 0);
|
|
892
|
+
cells[pos] = {
|
|
893
|
+
srcIndex: e.srcIndex,
|
|
894
|
+
srcEnd: e.srcEnd,
|
|
895
|
+
cp: e.cp,
|
|
896
|
+
x: penX,
|
|
897
|
+
advance: step,
|
|
898
|
+
level: order.levels[pos],
|
|
899
|
+
drawsInk: e.drawsInk,
|
|
900
|
+
clusterEnd: e.clusterEnd
|
|
901
|
+
};
|
|
902
|
+
glyphs.push({
|
|
903
|
+
run: e.run,
|
|
904
|
+
cp: e.cp,
|
|
905
|
+
fontSize: e.fontSize,
|
|
906
|
+
resolved: e.resolved,
|
|
907
|
+
metrics: e.metrics,
|
|
908
|
+
font: e.font,
|
|
909
|
+
glyph: e.glyph,
|
|
910
|
+
x: penX,
|
|
911
|
+
// Positive raises, and y grows down, so the shift subtracts.
|
|
912
|
+
baselineY: lineBaselineY - e.baselineShift,
|
|
913
|
+
advance: e.advance,
|
|
914
|
+
step
|
|
915
|
+
});
|
|
916
|
+
penX += step;
|
|
1107
917
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
for (let k = line.entries.length - 1; k >= 0; k--) {
|
|
1115
|
-
const e = line.entries[k];
|
|
1116
|
-
if (!e.isSpace && !e.tab) break;
|
|
1117
|
-
hung += e.kerningBefore + e.advance + e.tracking;
|
|
1118
|
-
}
|
|
1119
|
-
const inkWidth = line.width - hung;
|
|
1120
|
-
const gaps = /* @__PURE__ */ new Set();
|
|
1121
|
-
let gapExtra = 0;
|
|
1122
|
-
if (justify && line.wrapped && Number.isFinite(alignWidth) && alignWidth > inkWidth) {
|
|
1123
|
-
let first = 0;
|
|
1124
|
-
while (first < line.entries.length && line.entries[first].isSpace) first++;
|
|
1125
|
-
let last = line.entries.length - 1;
|
|
1126
|
-
while (last > first && line.entries[last].isSpace) last--;
|
|
1127
|
-
for (let k = first + 1; k < last; k++) if (line.entries[k].isSpace) gaps.add(k);
|
|
1128
|
-
if (gaps.size > 0) gapExtra = (alignWidth - inkWidth) / gaps.size;
|
|
1129
|
-
}
|
|
1130
|
-
const spread = gapExtra * gaps.size;
|
|
1131
|
-
const align = resolveAlign(opts.align, direction !== "auto" ? direction : directionOfLevel(line.entries.length > 0 ? paragraphs?.baseLevel(line.entries[0].flat) ?? 0 : 0));
|
|
1132
|
-
const alignShift = (() => {
|
|
1133
|
-
if (spread > 0) return 0;
|
|
1134
|
-
if (align === "left") return 0;
|
|
1135
|
-
if (!Number.isFinite(alignWidth)) {
|
|
1136
|
-
return align === "center" ? -inkWidth / 2 : -inkWidth;
|
|
1137
|
-
}
|
|
1138
|
-
const slack = alignWidth - inkWidth;
|
|
1139
|
-
return align === "center" ? slack / 2 : slack;
|
|
1140
|
-
})();
|
|
1141
|
-
const lineX0 = alignShift;
|
|
1142
|
-
let lineAscent = 0;
|
|
1143
|
-
for (const e of line.entries) lineAscent = Math.max(lineAscent, aboveBaseline(e));
|
|
1144
|
-
if (line.entries.length === 0 && line.blank) lineAscent = aboveBaseline(line.blank);
|
|
1145
|
-
const lineBaselineY = penY + lineAscent;
|
|
1146
|
-
const cells = new Array(line.entries.length);
|
|
1147
|
-
const flatOf = line.entries.map((e) => e.flat);
|
|
1148
|
-
const posOfFlat = /* @__PURE__ */ new Map();
|
|
1149
|
-
flatOf.forEach((f, pos) => posOfFlat.set(f, pos));
|
|
1150
|
-
let visual = line.entries.map((_, k) => k);
|
|
1151
|
-
let levelOf = () => 0;
|
|
1152
|
-
if (paragraphs !== null && line.entries.length > 0) {
|
|
1153
|
-
const from = flatOf[0];
|
|
1154
|
-
const to = flatOf[flatOf.length - 1] + 1;
|
|
1155
|
-
const r = paragraphs.reorder(from, to);
|
|
1156
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1157
|
-
const ordered = [];
|
|
1158
|
-
for (const f of r.order) {
|
|
1159
|
-
const pos = posOfFlat.get(f);
|
|
1160
|
-
if (pos !== void 0 && !seen.has(pos)) {
|
|
1161
|
-
ordered.push(pos);
|
|
1162
|
-
seen.add(pos);
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
levelOf = (pos) => {
|
|
1166
|
-
const l = r.levels[flatOf[pos] - from];
|
|
1167
|
-
return typeof l === "number" && l >= 0 ? l : 0;
|
|
1168
|
-
};
|
|
1169
|
-
visual = restoreRemoved(ordered, line.entries.length, (pos) => levelOf(pos) % 2 === 1);
|
|
1170
|
-
}
|
|
1171
|
-
if (bidi) {
|
|
1172
|
-
for (let pos = 0; pos < line.entries.length; pos++) {
|
|
1173
|
-
if (levelOf(pos) % 2 === 0) continue;
|
|
1174
|
-
const e = line.entries[pos];
|
|
1175
|
-
const m = bidi.mirror(e.cp);
|
|
1176
|
-
if (m === null || m === e.cp) continue;
|
|
1177
|
-
if (e.font !== null) {
|
|
1178
|
-
const hit = resolveGlyph(e.run, e.font, e.resolved, m, false);
|
|
1179
|
-
if (!hit) continue;
|
|
1180
|
-
if (hit.font !== e.font) {
|
|
1181
|
-
e.font = hit.font;
|
|
1182
|
-
e.metrics = atlasMetrics(hit.font);
|
|
1183
|
-
e.resolved = hit.resolved;
|
|
1184
|
-
}
|
|
1185
|
-
e.glyph = hit.glyph;
|
|
1186
|
-
}
|
|
1187
|
-
e.cp = m;
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
const hangsLeft = hung > 0 && levelOf(line.entries.length - 1) % 2 === 1;
|
|
1191
|
-
const lineLeft = hangsLeft ? lineX0 - hung : lineX0;
|
|
1192
|
-
let penX = lineLeft;
|
|
1193
|
-
let prevPos = -1;
|
|
1194
|
-
for (const pos of visual) {
|
|
1195
|
-
const e = line.entries[pos];
|
|
1196
|
-
penX += prevPos < 0 ? 0 : pos === prevPos + 1 ? e.kerningBefore : prevPos === pos + 1 ? line.entries[prevPos].kerningBefore : 0;
|
|
1197
|
-
prevPos = pos;
|
|
1198
|
-
const step = e.advance + e.tracking + (gaps.has(pos) ? gapExtra : 0);
|
|
1199
|
-
const cell = {
|
|
1200
|
-
srcIndex: e.srcIndex,
|
|
1201
|
-
srcEnd: e.srcEnd,
|
|
1202
|
-
cp: e.cp,
|
|
1203
|
-
x: penX,
|
|
1204
|
-
advance: step,
|
|
1205
|
-
level: levelOf(pos),
|
|
1206
|
-
drawsInk: e.drawsInk,
|
|
1207
|
-
clusterEnd: closesCluster[e.flat]
|
|
1208
|
-
};
|
|
1209
|
-
cells[pos] = cell;
|
|
1210
|
-
const scale = e.fontSize / e.metrics.size;
|
|
1211
|
-
const baselineY = lineBaselineY - (e.run.baselineShift ?? 0);
|
|
1212
|
-
const decoFill = e.run.fill;
|
|
1213
|
-
if (decoFill !== null && (e.run.underline || e.run.strikethrough || e.run.overline)) {
|
|
1214
|
-
const face = runFaces.get(e.run);
|
|
1215
|
-
if (span !== null && span.underline === e.run.underline && span.strikethrough === e.run.strikethrough && span.overline === e.run.overline && span.fontSize === e.run.fontSize && span.face === face && span.baselineY === baselineY && sameFill(span.fill, e.run.fill)) {
|
|
1216
|
-
span.x1 = penX + step;
|
|
1217
|
-
} else {
|
|
1218
|
-
flushSpan();
|
|
1219
|
-
span = {
|
|
1220
|
-
underline: e.run.underline,
|
|
1221
|
-
strikethrough: e.run.strikethrough,
|
|
1222
|
-
overline: e.run.overline,
|
|
1223
|
-
fill: decoFill,
|
|
1224
|
-
// The run's size, not the glyph's: a small-caps word is one rule.
|
|
1225
|
-
fontSize: e.run.fontSize,
|
|
1226
|
-
face,
|
|
1227
|
-
baselineY,
|
|
1228
|
-
x0: penX,
|
|
1229
|
-
x1: penX + step
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
} else {
|
|
1233
|
-
flushSpan();
|
|
1234
|
-
}
|
|
1235
|
-
const outlineD = outlineFor(e);
|
|
1236
|
-
if (e.run.fill === null && !(outlineD !== null && strokePaints(e.run.stroke))) {
|
|
1237
|
-
penX += step;
|
|
1238
|
-
continue;
|
|
1239
|
-
}
|
|
1240
|
-
if (outlineD !== null) {
|
|
1241
|
-
const group2 = getOrCreateGroup(ctx, e.run, e.resolved, 0, "outline");
|
|
1242
|
-
group2.glyphs.push({
|
|
1243
|
-
d: outlineD,
|
|
1244
|
-
key: `${e.resolved.resolved.family}|${e.resolved.resolved.weight}|${e.resolved.resolved.style}|${e.cp}`,
|
|
1245
|
-
x: penX,
|
|
1246
|
-
baselineY,
|
|
1247
|
-
// Em space is unit-scale, so world units per em is just the size.
|
|
1248
|
-
scale: e.fontSize
|
|
1249
|
-
});
|
|
1250
|
-
penX += step;
|
|
1251
|
-
continue;
|
|
1252
|
-
}
|
|
1253
|
-
if (e.font === null || e.glyph === null || e.advance === 0 || e.glyph.width === 0 || e.glyph.page < 0) {
|
|
1254
|
-
penX += step;
|
|
1255
|
-
continue;
|
|
1256
|
-
}
|
|
1257
|
-
const group = getOrCreateGroup(ctx, e.run, e.resolved, e.glyph.page);
|
|
1258
|
-
const atlasW = e.font.common.scaleW;
|
|
1259
|
-
const atlasH = e.font.common.scaleH;
|
|
1260
|
-
const qx0 = penX + e.glyph.xoffset * scale;
|
|
1261
|
-
const qy0 = baselineY + (e.glyph.yoffset - e.metrics.base) * scale;
|
|
1262
|
-
const qx1 = qx0 + e.glyph.width * scale;
|
|
1263
|
-
const qy1 = qy0 + e.glyph.height * scale;
|
|
1264
|
-
const u0 = e.glyph.x / atlasW;
|
|
1265
|
-
const v0 = e.glyph.y / atlasH;
|
|
1266
|
-
const u1 = (e.glyph.x + e.glyph.width) / atlasW;
|
|
1267
|
-
const v1 = (e.glyph.y + e.glyph.height) / atlasH;
|
|
1268
|
-
group.quads.push({ x0: qx0, y0: qy0, x1: qx1, y1: qy1, u0, v0, u1, v1, baselineY });
|
|
1269
|
-
penX += step;
|
|
1270
|
-
}
|
|
1271
|
-
flushSpan();
|
|
1272
|
-
const lastCell = line.entries[line.entries.length - 1];
|
|
1273
|
-
const srcEnd = lastCell ? lastCell.srcEnd : line.blank?.srcIndex ?? 0;
|
|
1274
|
-
lineBoxes.push({
|
|
1275
|
-
x0: lineLeft,
|
|
1276
|
-
y0: penY,
|
|
1277
|
-
x1: lineLeft + line.width + spread,
|
|
1278
|
-
y1: penY + line.height,
|
|
918
|
+
return {
|
|
919
|
+
box: {
|
|
920
|
+
x0: left,
|
|
921
|
+
y0: y,
|
|
922
|
+
x1: left + line.width + spread,
|
|
923
|
+
y1: y + line.height,
|
|
1279
924
|
baselineY: lineBaselineY,
|
|
1280
925
|
cells,
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
}
|
|
926
|
+
glyphs,
|
|
927
|
+
srcEnd: line.srcEnd
|
|
928
|
+
},
|
|
929
|
+
inkWidth: inkWidth + spread
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// src/layout/layoutOpts.ts
|
|
934
|
+
function resolveLayoutOpts(opts) {
|
|
1286
935
|
return {
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
936
|
+
maxWidth: opts.maxWidth,
|
|
937
|
+
alignWidth: opts.alignWidth ?? opts.maxWidth,
|
|
938
|
+
lineHeight: opts.lineHeight,
|
|
939
|
+
align: opts.align,
|
|
940
|
+
justify: opts.justify === true || opts.align === "justify",
|
|
941
|
+
bidi: opts.bidi ?? registeredBidi(),
|
|
942
|
+
direction: opts.direction ?? "auto",
|
|
943
|
+
tabSize: opts.tabSize ?? DEFAULT_TAB_SIZE
|
|
1291
944
|
};
|
|
1292
945
|
}
|
|
946
|
+
|
|
947
|
+
// src/layout/layoutRuns.ts
|
|
948
|
+
function layoutRuns(runs, opts) {
|
|
949
|
+
const o = resolveLayoutOpts(opts);
|
|
950
|
+
const entries = measureRuns(runs, o.tabSize);
|
|
951
|
+
const cps = entries.map((e) => e.cp);
|
|
952
|
+
const { bidi } = o;
|
|
953
|
+
const paragraphs = bidi ? analyzeParagraphs(bidi, cps, (i) => isHardLineBreak(cps[i]), o.direction) : null;
|
|
954
|
+
if (!bidi) warnRtlWithoutEngine(cps);
|
|
955
|
+
const lines = [];
|
|
956
|
+
let y = 0;
|
|
957
|
+
let width = 0;
|
|
958
|
+
for (const line of breakLines(entries, o.maxWidth, o.lineHeight)) {
|
|
959
|
+
const order = orderLine(paragraphs, line.start, line.entries.length);
|
|
960
|
+
if (bidi) line.entries = mirrorEntries(line.entries, order.levels, (cp) => bidi.mirror(cp), runs);
|
|
961
|
+
const reads = o.direction !== "auto" ? o.direction : directionOfLevel(line.entries.length > 0 ? paragraphs?.baseLevel(line.start) ?? 0 : 0);
|
|
962
|
+
const placed = placeLine(line, y, order, resolveAlign(o.align, reads), o.alignWidth, o.justify);
|
|
963
|
+
lines.push(placed.box);
|
|
964
|
+
width = Math.max(width, placed.inkWidth);
|
|
965
|
+
y += line.height;
|
|
966
|
+
}
|
|
967
|
+
return { lines, bounds: { width, height: y } };
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/layout/refId.ts
|
|
971
|
+
var nextRefId = 1;
|
|
972
|
+
var refIds = /* @__PURE__ */ new WeakMap();
|
|
973
|
+
function refId(o) {
|
|
974
|
+
let id = refIds.get(o);
|
|
975
|
+
if (id === void 0) {
|
|
976
|
+
id = nextRefId++;
|
|
977
|
+
refIds.set(o, id);
|
|
978
|
+
}
|
|
979
|
+
return id;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/layout/layoutCache.ts
|
|
1293
983
|
var LAYOUT_CACHE_VARIANT_LIMIT = 8;
|
|
1294
984
|
var LAYOUT_CACHE_STRUCTURAL_LIMIT = 64;
|
|
1295
985
|
var cache = /* @__PURE__ */ new WeakMap();
|
|
1296
986
|
var structural = /* @__PURE__ */ new Map();
|
|
1297
987
|
var structuralGeneration = -1;
|
|
1298
|
-
function
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
for (const r of runs) sizes.add(r.fontSize);
|
|
1302
|
-
let n = 0;
|
|
1303
|
-
for (const size of sizes) if (size >= min) n++;
|
|
1304
|
-
return n;
|
|
1305
|
-
}
|
|
1306
|
-
function variantKey(runs, opts) {
|
|
1307
|
-
const alignWidth = opts.alignWidth ?? opts.maxWidth;
|
|
1308
|
-
const justify = opts.justify === true || opts.align === "justify";
|
|
1309
|
-
const bidi = opts.bidi ?? registeredBidi();
|
|
1310
|
-
return `${opts.maxWidth}|${alignWidth}|${opts.lineHeight}|${opts.align}|${justify ? "j" : ""}|${outlineBucket(runs, opts.outlineMinSize)}|${opts.direction ?? ""}|${bidi ? refId(bidi) : ""}|${opts.tabSize ?? ""}`;
|
|
1311
|
-
}
|
|
1312
|
-
function paintKey(p) {
|
|
1313
|
-
if (p === null) return "none";
|
|
1314
|
-
return "color" in p ? `s${p.color}:${p.opacity ?? 1}` : `#${refId(p)}`;
|
|
1315
|
-
}
|
|
1316
|
-
function strokeKey2(s) {
|
|
1317
|
-
if (!s?.paint) return "-";
|
|
1318
|
-
const width = s.width ?? 1;
|
|
1319
|
-
return [
|
|
1320
|
-
paintKey(s.paint),
|
|
1321
|
-
typeof width === "number" ? width : `px${width.px}`,
|
|
1322
|
-
s.join ?? "miter",
|
|
1323
|
-
s.cap ?? "butt",
|
|
1324
|
-
s.miterLimit ?? "",
|
|
1325
|
-
s.align ?? "center",
|
|
1326
|
-
(s.dash ?? []).join(",")
|
|
1327
|
-
].join(":");
|
|
988
|
+
function variantKey(opts) {
|
|
989
|
+
const o = resolveLayoutOpts(opts);
|
|
990
|
+
return `${o.maxWidth}|${o.alignWidth}|${o.lineHeight}|${o.align}|${o.justify ? "j" : ""}|${o.direction}|${o.bidi ? refId(o.bidi) : ""}|${o.tabSize}`;
|
|
1328
991
|
}
|
|
1329
992
|
function runsKey(runs) {
|
|
1330
993
|
let out = "";
|
|
1331
994
|
for (const r of runs) {
|
|
1332
|
-
out += `${r.text.length}:${r.text}|${r.fontFamily.length}:${r.fontFamily}|${r.fontSize}|${r.fontWeight}|${r.fontStyle}|${r.letterSpacing}|${r.
|
|
995
|
+
out += `${r.text.length}:${r.text}|${r.fontFamily.length}:${r.fontFamily}|${r.fontSize}|${r.fontWeight}|${r.fontStyle}|${r.letterSpacing}|${r.baselineShift}|${r.strutSize ?? ""}|${r.srcMap ? `${r.srcMap.length}:${r.srcMap.starts.join(",")}` : ""}|${r.sizeMap ? r.sizeMap.join(",") : ""}|`;
|
|
1333
996
|
}
|
|
1334
997
|
return out;
|
|
1335
998
|
}
|
|
1336
999
|
function cachedLayoutRuns(runs, opts) {
|
|
1337
|
-
const fonts =
|
|
1000
|
+
const fonts = faceGeneration();
|
|
1338
1001
|
let entry = cache.get(runs);
|
|
1339
1002
|
if (entry === void 0) {
|
|
1340
1003
|
entry = { generation: fonts, byVariant: /* @__PURE__ */ new Map() };
|
|
@@ -1343,7 +1006,7 @@ function cachedLayoutRuns(runs, opts) {
|
|
|
1343
1006
|
entry.generation = fonts;
|
|
1344
1007
|
entry.byVariant.clear();
|
|
1345
1008
|
}
|
|
1346
|
-
const key = variantKey(
|
|
1009
|
+
const key = variantKey(opts);
|
|
1347
1010
|
const hit = entry.byVariant.get(key);
|
|
1348
1011
|
if (hit !== void 0) return hit;
|
|
1349
1012
|
if (structuralGeneration !== fonts) {
|
|
@@ -1371,6 +1034,6 @@ function _resetLayoutCacheForTests() {
|
|
|
1371
1034
|
structuralGeneration = -1;
|
|
1372
1035
|
}
|
|
1373
1036
|
|
|
1374
|
-
export { BREAK_ALLOWED, BREAK_MANDATORY,
|
|
1375
|
-
//# sourceMappingURL=chunk-
|
|
1376
|
-
//# sourceMappingURL=chunk-
|
|
1037
|
+
export { BREAK_ALLOWED, BREAK_MANDATORY, DEFAULT_TAB_SIZE, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, NO_BREAK, _resetBidiWarningForTests, _resetLayoutCacheForTests, cachedLayoutRuns, fontString, graphemeCount, hangs, isHardLineBreak, layoutRuns, lineBreakOpportunities, paragraphDirection, registerBidi, resolveAlign, resolveTextStyle, tabAdvance, widthWithTabs, wrapLines };
|
|
1038
|
+
//# sourceMappingURL=chunk-Z6YRJ33J.js.map
|
|
1039
|
+
//# sourceMappingURL=chunk-Z6YRJ33J.js.map
|