@effing/canvas 0.43.0 → 0.45.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 +9 -5
- package/dist/index.js +761 -160
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
package/dist/index.js
CHANGED
|
@@ -139,8 +139,22 @@ var INHERITABLE_PROPS = [
|
|
|
139
139
|
"WebkitTextStrokeWidth",
|
|
140
140
|
"WebkitTextStrokeColor"
|
|
141
141
|
];
|
|
142
|
+
var CSS_NUMBER = /^[+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?$/;
|
|
143
|
+
var FONT_WEIGHT_KEYWORDS = /* @__PURE__ */ new Set(["normal", "bold", "bolder", "lighter"]);
|
|
144
|
+
function isValidFontWeight(weight) {
|
|
145
|
+
if (typeof weight === "string") {
|
|
146
|
+
const text = weight.trim();
|
|
147
|
+
if (FONT_WEIGHT_KEYWORDS.has(text.toLowerCase())) return true;
|
|
148
|
+
if (!CSS_NUMBER.test(text)) return false;
|
|
149
|
+
weight = Number(text);
|
|
150
|
+
}
|
|
151
|
+
return Number.isFinite(weight) && weight >= 1 && weight <= 1e3;
|
|
152
|
+
}
|
|
142
153
|
function resolveStyle(rawStyle, parentStyle) {
|
|
143
154
|
const style = { ...rawStyle };
|
|
155
|
+
if (style.fontWeight !== void 0 && !isValidFontWeight(style.fontWeight)) {
|
|
156
|
+
delete style.fontWeight;
|
|
157
|
+
}
|
|
144
158
|
for (const prop of INHERITABLE_PROPS) {
|
|
145
159
|
if (style[prop] === void 0 && parentStyle[prop] !== void 0) {
|
|
146
160
|
style[prop] = parentStyle[prop];
|
|
@@ -302,7 +316,9 @@ var LENGTH_STRING_PROPS = [
|
|
|
302
316
|
["transformOrigin", ""],
|
|
303
317
|
["clipPath", "px"],
|
|
304
318
|
["filter", "px"],
|
|
305
|
-
["backdropFilter", "px"]
|
|
319
|
+
["backdropFilter", "px"],
|
|
320
|
+
["boxShadow", "px"],
|
|
321
|
+
["textShadow", "px"]
|
|
306
322
|
];
|
|
307
323
|
function resolveLengthUnits(transform, viewportWidth, viewportHeight, fontSize, rootFontSize, suffix = "") {
|
|
308
324
|
return transform.replace(
|
|
@@ -386,7 +402,7 @@ function quoteFontFamilies(fontFamily) {
|
|
|
386
402
|
function setFont(ctx, fontSize, fontFamily, fontWeight = 400, fontStyle = "normal") {
|
|
387
403
|
ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${quoteFontFamilies(fontFamily)}`;
|
|
388
404
|
}
|
|
389
|
-
function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle,
|
|
405
|
+
function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, line, edge) {
|
|
390
406
|
const c = getScratchCtx();
|
|
391
407
|
const measure = (text) => {
|
|
392
408
|
setFont(c, fontSize, fontFamily, fontWeight, fontStyle);
|
|
@@ -417,7 +433,7 @@ function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHei
|
|
|
417
433
|
case "ideographic-ink":
|
|
418
434
|
case "text":
|
|
419
435
|
default:
|
|
420
|
-
targetAscent =
|
|
436
|
+
targetAscent = line.ascent;
|
|
421
437
|
break;
|
|
422
438
|
}
|
|
423
439
|
let targetDescent;
|
|
@@ -429,12 +445,11 @@ function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHei
|
|
|
429
445
|
case "ideographic-ink":
|
|
430
446
|
case "text":
|
|
431
447
|
default:
|
|
432
|
-
targetDescent =
|
|
448
|
+
targetDescent = line.descent;
|
|
433
449
|
break;
|
|
434
450
|
}
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
const underTrim = halfLeading + (fontDescent - targetDescent);
|
|
451
|
+
const overTrim = line.baseline - targetAscent;
|
|
452
|
+
const underTrim = line.lineHeight - line.baseline - targetDescent;
|
|
438
453
|
return {
|
|
439
454
|
overTrim: Math.max(0, overTrim),
|
|
440
455
|
underTrim: Math.max(0, underTrim)
|
|
@@ -444,9 +459,10 @@ function measureTrimMetrics(fontSize, fontFamily, fontWeight, fontStyle, lineHei
|
|
|
444
459
|
// src/jsx/text/native.ts
|
|
445
460
|
function toWeight(weight) {
|
|
446
461
|
if (typeof weight === "number") return weight;
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
462
|
+
const text = String(weight ?? "400").trim().toLowerCase();
|
|
463
|
+
if (text === "bold") return 700;
|
|
464
|
+
const parsed = text === "" ? NaN : Number(text);
|
|
465
|
+
return Number.isFinite(parsed) ? parsed : 400;
|
|
450
466
|
}
|
|
451
467
|
function toFontStyle(fontStyle) {
|
|
452
468
|
if (fontStyle?.startsWith("italic")) return "italic";
|
|
@@ -491,6 +507,17 @@ async function releaseParagraphs() {
|
|
|
491
507
|
if (pendingParagraphs < MAX_PENDING_PARAGRAPHS) return;
|
|
492
508
|
await new Promise((resolve) => setImmediate(resolve));
|
|
493
509
|
}
|
|
510
|
+
function halfLeadingLineBox(lineHeight, ascent, descent) {
|
|
511
|
+
const a = Math.round(ascent);
|
|
512
|
+
const d = Math.round(descent);
|
|
513
|
+
const leading = (lineHeight - a - d) * 64;
|
|
514
|
+
return {
|
|
515
|
+
lineHeight,
|
|
516
|
+
baseline: a + Math.floor(Math.trunc(leading / 2) / 64),
|
|
517
|
+
ascent: a,
|
|
518
|
+
descent: d
|
|
519
|
+
};
|
|
520
|
+
}
|
|
494
521
|
var graphemeSegmenter = new Intl.Segmenter(void 0, {
|
|
495
522
|
granularity: "grapheme"
|
|
496
523
|
});
|
|
@@ -562,6 +589,7 @@ function layoutTextNative(text, style, maxWidth, lineHeight, emojiEnabled) {
|
|
|
562
589
|
fontWeight: toWeight(fontWeight),
|
|
563
590
|
fontStyle: toFontStyle(fontStyle),
|
|
564
591
|
letterSpacing,
|
|
592
|
+
// Undefined for `normal`, which the paragraph sizes as Chrome does.
|
|
565
593
|
lineHeight,
|
|
566
594
|
textAlign: toTextAlign(style.textAlign),
|
|
567
595
|
noWrap,
|
|
@@ -574,12 +602,16 @@ function layoutTextNative(text, style, maxWidth, lineHeight, emojiEnabled) {
|
|
|
574
602
|
});
|
|
575
603
|
countParagraph();
|
|
576
604
|
const layout = paragraph.layout(width);
|
|
605
|
+
const box = halfLeadingLineBox(
|
|
606
|
+
layout.lineHeight,
|
|
607
|
+
layout.ascent,
|
|
608
|
+
layout.descent
|
|
609
|
+
);
|
|
577
610
|
const lines = layout.lines.length > 0 ? layout.lines : [
|
|
578
611
|
{
|
|
579
612
|
left: 0,
|
|
580
613
|
width: 0,
|
|
581
|
-
|
|
582
|
-
baseline: (layout.lineHeight + layout.ascent - layout.descent) / 2,
|
|
614
|
+
baseline: box.baseline,
|
|
583
615
|
startIndex: 0,
|
|
584
616
|
endIndex: 0,
|
|
585
617
|
hardBreak: true
|
|
@@ -587,8 +619,7 @@ function layoutTextNative(text, style, maxWidth, lineHeight, emojiEnabled) {
|
|
|
587
619
|
];
|
|
588
620
|
const toTextIndex = content.toTextIndex ?? ((index) => index);
|
|
589
621
|
const segments = lines.map((line, i) => ({
|
|
590
|
-
|
|
591
|
-
text: text.slice(toTextIndex(line.startIndex), toTextIndex(line.endIndex)).replace(/\n/g, ""),
|
|
622
|
+
text: text.slice(toTextIndex(line.startIndex), toTextIndex(line.endIndex)),
|
|
592
623
|
x: line.left,
|
|
593
624
|
y: line.baseline,
|
|
594
625
|
width: line.width,
|
|
@@ -604,21 +635,17 @@ function layoutTextNative(text, style, maxWidth, lineHeight, emojiEnabled) {
|
|
|
604
635
|
lineIndex: i
|
|
605
636
|
}));
|
|
606
637
|
const emoji = [];
|
|
607
|
-
layout.placeholders.forEach((
|
|
608
|
-
if (!
|
|
638
|
+
layout.placeholders.forEach((placed, i) => {
|
|
639
|
+
if (!placed) return;
|
|
609
640
|
emoji.push({
|
|
610
641
|
grapheme: content.emoji[i],
|
|
611
|
-
x:
|
|
612
|
-
y:
|
|
642
|
+
x: placed.x,
|
|
643
|
+
y: placed.y,
|
|
613
644
|
size: fontSize,
|
|
614
|
-
baseline: lines[
|
|
645
|
+
baseline: lines[placed.line].baseline
|
|
615
646
|
});
|
|
616
647
|
});
|
|
617
648
|
let height = lines.length * layout.lineHeight;
|
|
618
|
-
if (lineHeight === void 0) {
|
|
619
|
-
const noFontSize = Number.isFinite(styledFontSize) && styledFontSize <= 0;
|
|
620
|
-
height = noFontSize ? 0 : Math.ceil(height);
|
|
621
|
-
}
|
|
622
649
|
let paragraphOffsetY = 0;
|
|
623
650
|
const textBoxTrim = style.textBoxTrim;
|
|
624
651
|
if (textBoxTrim && textBoxTrim !== "none") {
|
|
@@ -627,10 +654,8 @@ function layoutTextNative(text, style, maxWidth, lineHeight, emojiEnabled) {
|
|
|
627
654
|
fontFamily,
|
|
628
655
|
fontWeight,
|
|
629
656
|
fontStyle,
|
|
630
|
-
|
|
631
|
-
style.textBoxEdge ?? "text"
|
|
632
|
-
layout.ascent,
|
|
633
|
-
layout.descent
|
|
657
|
+
box,
|
|
658
|
+
style.textBoxEdge ?? "text"
|
|
634
659
|
);
|
|
635
660
|
if (textBoxTrim === "trim-start" || textBoxTrim === "trim-both") {
|
|
636
661
|
for (const seg of segments) seg.y -= trim.overTrim;
|
|
@@ -639,7 +664,7 @@ function layoutTextNative(text, style, maxWidth, lineHeight, emojiEnabled) {
|
|
|
639
664
|
e.baseline -= trim.overTrim;
|
|
640
665
|
}
|
|
641
666
|
height -= trim.overTrim;
|
|
642
|
-
paragraphOffsetY
|
|
667
|
+
paragraphOffsetY -= trim.overTrim;
|
|
643
668
|
}
|
|
644
669
|
if (textBoxTrim === "trim-end" || textBoxTrim === "trim-both") {
|
|
645
670
|
height -= trim.underTrim;
|
|
@@ -649,6 +674,8 @@ function layoutTextNative(text, style, maxWidth, lineHeight, emojiEnabled) {
|
|
|
649
674
|
segments,
|
|
650
675
|
width: segments.reduce((w, s) => Math.max(w, s.width), 0),
|
|
651
676
|
height,
|
|
677
|
+
maxContentWidth: layout.maxIntrinsicWidth,
|
|
678
|
+
minContentWidth: layout.minIntrinsicWidth,
|
|
652
679
|
paragraph,
|
|
653
680
|
paragraphOffsetY,
|
|
654
681
|
emoji
|
|
@@ -660,15 +687,17 @@ function isWhiteSpaceOnly(text) {
|
|
|
660
687
|
return /^[ \t\n\r\f\v]*$/.test(text);
|
|
661
688
|
}
|
|
662
689
|
function collapseWhiteSpace(text, whiteSpace) {
|
|
690
|
+
const pieces = typeof text === "string" ? [text] : text;
|
|
691
|
+
return pieces.map((piece) => collapsePiece(piece, whiteSpace)).join("\n").replace(/\r\n/g, "\n").replace(FINAL_BREAK, "").replace(SEPARATORS, SEPARATOR_SPACE);
|
|
692
|
+
}
|
|
693
|
+
function collapsePiece(text, whiteSpace) {
|
|
663
694
|
let result = text.replace(/[\v\f]/g, "").replace(/\r\n/g, "\n");
|
|
664
|
-
if (whiteSpace
|
|
665
|
-
result = result.replace(/\r/g, "");
|
|
666
|
-
} else if (/[\t\n\r]| {2}|^ | $/.test(result)) {
|
|
695
|
+
if (whiteSpace !== "pre" && whiteSpace !== "pre-wrap" && /[\t\n\r]| {2}|^ | $/.test(result)) {
|
|
667
696
|
result = result.replace(/[\t\r]/g, " ").replace(/ *\n */g, "\n");
|
|
668
697
|
if (whiteSpace !== "pre-line") result = result.replace(/\n+/g, " ");
|
|
669
698
|
result = result.replace(/ {2,}/g, " ").replace(/^ | $/g, "");
|
|
670
699
|
}
|
|
671
|
-
return result
|
|
700
|
+
return result;
|
|
672
701
|
}
|
|
673
702
|
var FINAL_BREAK = /\n$/;
|
|
674
703
|
var SEPARATORS = /[\u2028\u2029]/g;
|
|
@@ -716,57 +745,189 @@ function resolveLineHeight(lineHeight, fontSize) {
|
|
|
716
745
|
}
|
|
717
746
|
return Number.isFinite(px) && px >= 0 ? px : void 0;
|
|
718
747
|
}
|
|
748
|
+
function toLayoutGrid(width) {
|
|
749
|
+
return Math.ceil(width * 64) / 64;
|
|
750
|
+
}
|
|
751
|
+
var graphemeSegmenter2 = new Intl.Segmenter(void 0, {
|
|
752
|
+
granularity: "grapheme"
|
|
753
|
+
});
|
|
719
754
|
var TextMeasure = class {
|
|
720
755
|
constructor(text, style, emojiEnabled) {
|
|
721
756
|
this.text = text;
|
|
722
757
|
this.style = style;
|
|
723
758
|
this.emojiEnabled = emojiEnabled;
|
|
724
|
-
this.measureStyle = { ...style, textOverflow: "clip" };
|
|
725
759
|
}
|
|
726
760
|
text;
|
|
727
761
|
style;
|
|
728
762
|
emojiEnabled;
|
|
729
|
-
/**
|
|
763
|
+
/**
|
|
764
|
+
* Yoga's measurements by width (Infinity for unbounded), and those at
|
|
765
|
+
* exactly a width.
|
|
766
|
+
*/
|
|
730
767
|
sizes = /* @__PURE__ */ new Map();
|
|
731
|
-
|
|
768
|
+
exactSizes = /* @__PURE__ */ new Map();
|
|
769
|
+
/**
|
|
770
|
+
* The heights reported to Yoga since the node was last marked dirty (see
|
|
771
|
+
* `settle`): Yoga may size the node, and its ancestors, from any of them,
|
|
772
|
+
* cached, without measuring it again.
|
|
773
|
+
*/
|
|
732
774
|
reported = /* @__PURE__ */ new Set();
|
|
733
775
|
pinnedHeight;
|
|
734
776
|
settled;
|
|
735
|
-
|
|
777
|
+
/**
|
|
778
|
+
* The text's intrinsic widths, from whichever layout came first: its
|
|
779
|
+
* max-content width, and a bound on its min-content width, which is the
|
|
780
|
+
* min-content width but where finding it takes more (see `widestGrapheme`).
|
|
781
|
+
*/
|
|
782
|
+
intrinsic;
|
|
736
783
|
/**
|
|
737
784
|
* Measure the text for Yoga.
|
|
738
785
|
*
|
|
739
786
|
* @param maxWidth - The width available, Infinity for unbounded
|
|
787
|
+
* @param exact - Whether the node is exactly `maxWidth` wide, whatever its
|
|
788
|
+
* text: it's then measured for its height there
|
|
740
789
|
*/
|
|
741
|
-
measure(maxWidth) {
|
|
742
|
-
|
|
790
|
+
measure(maxWidth, exact = false) {
|
|
791
|
+
const sizes = exact ? this.exactSizes : this.sizes;
|
|
792
|
+
let size = sizes.get(maxWidth);
|
|
743
793
|
if (!size) {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
maxWidth
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
width
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
794
|
+
let result = this.layOut(maxWidth);
|
|
795
|
+
let width;
|
|
796
|
+
if (exact) {
|
|
797
|
+
width = maxWidth;
|
|
798
|
+
} else if (result.maxContentWidth > maxWidth) {
|
|
799
|
+
width = this.minContentBound <= maxWidth ? maxWidth : Math.max(maxWidth, this.minContentWidth);
|
|
800
|
+
if (width > maxWidth) result = this.layOut(width);
|
|
801
|
+
} else {
|
|
802
|
+
width = Math.min(
|
|
803
|
+
toLayoutGrid(Math.min(result.width, result.maxContentWidth)),
|
|
804
|
+
maxWidth
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
size = { width, height: result.height };
|
|
808
|
+
sizes.set(maxWidth, size);
|
|
756
809
|
}
|
|
757
810
|
const height = this.pinnedHeight ?? size.height;
|
|
758
811
|
this.reported.add(height);
|
|
759
812
|
return { width: size.width, height };
|
|
760
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* The height of the text's lines at exactly `width`, as drawn there,
|
|
816
|
+
* whatever height later measurements are pinned to.
|
|
817
|
+
*/
|
|
818
|
+
heightAt(width) {
|
|
819
|
+
if (this.settled?.width === width) return this.settled.result.height;
|
|
820
|
+
let size = this.exactSizes.get(width);
|
|
821
|
+
if (!size) {
|
|
822
|
+
size = { width, height: this.layOut(width).height };
|
|
823
|
+
this.exactSizes.set(width, size);
|
|
824
|
+
}
|
|
825
|
+
return size.height;
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* The text's min-content width: as narrow as it gets, each run of it that
|
|
829
|
+
* can't break on a line of its own. As CSS has it, that's the widest word,
|
|
830
|
+
* or the widest line for text that doesn't wrap, and under
|
|
831
|
+
* `overflow-wrap: anywhere` (or `word-break: break-word`) and
|
|
832
|
+
* `word-break: break-all` the widest letter. Text truncated with an
|
|
833
|
+
* ellipsis (`text-overflow: ellipsis` without wrapping, or a line clamp)
|
|
834
|
+
* has none: it's drawn at whatever width it has, which CSS gives it in
|
|
835
|
+
* the `overflow: hidden` box an ellipsis needs.
|
|
836
|
+
*
|
|
837
|
+
* Never wider than the text's widest line, and on the layout grid (see
|
|
838
|
+
* `toLayoutGrid`), as the widths `measure` reports are.
|
|
839
|
+
*/
|
|
840
|
+
get minContentWidth() {
|
|
841
|
+
const intrinsic = this.intrinsic ?? this.measureIntrinsic();
|
|
842
|
+
intrinsic.min ??= Math.min(
|
|
843
|
+
toLayoutGrid(this.widestGrapheme()),
|
|
844
|
+
intrinsic.minBound
|
|
845
|
+
);
|
|
846
|
+
return intrinsic.min;
|
|
847
|
+
}
|
|
848
|
+
/** At least the text's min-content width, and found without more work. */
|
|
849
|
+
get minContentBound() {
|
|
850
|
+
return (this.intrinsic ?? this.measureIntrinsic()).minBound;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* The text's max-content width: its widest line, were no line to wrap, on
|
|
854
|
+
* the layout grid.
|
|
855
|
+
*/
|
|
856
|
+
get maxContentWidth() {
|
|
857
|
+
return (this.intrinsic ?? this.measureIntrinsic()).max;
|
|
858
|
+
}
|
|
859
|
+
/** Whether the text can't be narrower than its widest line. */
|
|
860
|
+
get unbreakable() {
|
|
861
|
+
return this.minContentBound >= this.maxContentWidth;
|
|
862
|
+
}
|
|
863
|
+
measureIntrinsic() {
|
|
864
|
+
this.layOut(Infinity);
|
|
865
|
+
return this.intrinsic;
|
|
866
|
+
}
|
|
867
|
+
layOut(width) {
|
|
868
|
+
const result = layoutText(this.text, this.style, width, this.emojiEnabled);
|
|
869
|
+
this.intrinsic ??= this.intrinsicOf(result);
|
|
870
|
+
return result;
|
|
871
|
+
}
|
|
872
|
+
/** The intrinsic widths of the text, from a layout of it at any width. */
|
|
873
|
+
intrinsicOf(result) {
|
|
874
|
+
const { style } = this;
|
|
875
|
+
const max = toLayoutGrid(result.maxContentWidth);
|
|
876
|
+
const noWrap = style.whiteSpace === "nowrap" || style.whiteSpace === "pre";
|
|
877
|
+
if (style.lineClamp !== void 0 && style.lineClamp >= 1 || noWrap && style.textOverflow === "ellipsis") {
|
|
878
|
+
return { max, minBound: 0, min: 0 };
|
|
879
|
+
}
|
|
880
|
+
const minBound = Math.min(
|
|
881
|
+
toLayoutGrid(
|
|
882
|
+
noWrap ? Math.min(result.minContentWidth, result.width) : result.minContentWidth
|
|
883
|
+
),
|
|
884
|
+
max
|
|
885
|
+
);
|
|
886
|
+
const anywhere = !noWrap && (style.overflowWrap === "anywhere" || style.wordBreak === "break-word");
|
|
887
|
+
return { max, minBound, min: anywhere ? void 0 : minBound };
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* The width of the text's widest grapheme cluster: its min-content where
|
|
891
|
+
* a line may break between any two. Each one is laid out once, as a word
|
|
892
|
+
* of its own.
|
|
893
|
+
*/
|
|
894
|
+
widestGrapheme() {
|
|
895
|
+
const text = applyTextTransform(
|
|
896
|
+
collapseWhiteSpace(this.text, this.style.whiteSpace),
|
|
897
|
+
this.style
|
|
898
|
+
);
|
|
899
|
+
const graphemes = new Set(
|
|
900
|
+
/^[\s!-\u02ff]*$/.test(text) ? text : Array.from(graphemeSegmenter2.segment(text), ({ segment }) => segment)
|
|
901
|
+
);
|
|
902
|
+
for (const grapheme of graphemes) {
|
|
903
|
+
if (/^\s+$/.test(grapheme)) graphemes.delete(grapheme);
|
|
904
|
+
}
|
|
905
|
+
if (graphemes.size === 0) return 0;
|
|
906
|
+
return layoutText(
|
|
907
|
+
[...graphemes].join(" "),
|
|
908
|
+
{
|
|
909
|
+
...this.style,
|
|
910
|
+
whiteSpace: "normal",
|
|
911
|
+
wordBreak: "normal",
|
|
912
|
+
overflowWrap: "normal",
|
|
913
|
+
textTransform: "none"
|
|
914
|
+
},
|
|
915
|
+
Infinity,
|
|
916
|
+
this.emojiEnabled
|
|
917
|
+
).minContentWidth;
|
|
918
|
+
}
|
|
761
919
|
/**
|
|
762
920
|
* Lay the text out for drawing at the node's final content width, and
|
|
763
|
-
* check it against the heights Yoga was given since the last
|
|
921
|
+
* check it against the heights Yoga was given since the node was last
|
|
922
|
+
* marked dirty: Yoga reuses a node's measurements until then, also when
|
|
923
|
+
* the node ends up at another width, so the heights of earlier layouts
|
|
924
|
+
* count too, not just those of the last.
|
|
764
925
|
*
|
|
765
926
|
* @param pin - Whether to pin later measurements to the drawn height when
|
|
766
927
|
* they disagree
|
|
767
928
|
* @returns Whether Yoga sized the node from another height. If `pin`, the
|
|
768
929
|
* node's measurements are now pinned to the drawn height, and the caller
|
|
769
|
-
*
|
|
930
|
+
* must mark the node dirty and compute the layout again.
|
|
770
931
|
*/
|
|
771
932
|
settle(width, pin) {
|
|
772
933
|
if (this.settled?.width !== width) {
|
|
@@ -777,20 +938,22 @@ var TextMeasure = class {
|
|
|
777
938
|
}
|
|
778
939
|
const { height } = this.settled.result;
|
|
779
940
|
const agrees = this.reported.size === 0 || this.reported.size === 1 && this.reported.has(height);
|
|
780
|
-
this.reported.clear();
|
|
781
941
|
if (agrees) return false;
|
|
782
|
-
if (pin)
|
|
942
|
+
if (pin) {
|
|
943
|
+
this.pinnedHeight = height;
|
|
944
|
+
this.reported.clear();
|
|
945
|
+
}
|
|
783
946
|
return true;
|
|
784
947
|
}
|
|
785
948
|
/**
|
|
786
949
|
* Drop the pinned height, so that Yoga sees the measured heights again.
|
|
787
950
|
*
|
|
788
|
-
* @returns Whether there was a pin
|
|
951
|
+
* @returns Whether there was a pin, for the caller to mark the node dirty
|
|
789
952
|
*/
|
|
790
953
|
unpin() {
|
|
791
954
|
const pinned = this.pinnedHeight !== void 0;
|
|
792
955
|
this.pinnedHeight = void 0;
|
|
793
|
-
this.reported.clear();
|
|
956
|
+
if (pinned) this.reported.clear();
|
|
794
957
|
return pinned;
|
|
795
958
|
}
|
|
796
959
|
/** The text as laid out by the last `settle`. */
|
|
@@ -1050,34 +1213,114 @@ function drawBorders(ctx, x, y, width, height, style, borderRadius) {
|
|
|
1050
1213
|
}
|
|
1051
1214
|
}
|
|
1052
1215
|
}
|
|
1053
|
-
var
|
|
1054
|
-
function drawBoxShadow(ctx, x, y, width, height,
|
|
1055
|
-
const
|
|
1056
|
-
if (
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
borderRadius.topLeft,
|
|
1063
|
-
borderRadius.topRight,
|
|
1064
|
-
borderRadius.bottomRight,
|
|
1065
|
-
borderRadius.bottomLeft
|
|
1066
|
-
];
|
|
1067
|
-
const margin = blur * 2 + Math.abs(offsetX) + Math.abs(offsetY);
|
|
1216
|
+
var NO_COLOR = "rgba(0, 0, 0, 0)";
|
|
1217
|
+
function drawBoxShadow(ctx, x, y, width, height, shadows, borderRadius) {
|
|
1218
|
+
const outer = shadows.filter((s) => !s.inset);
|
|
1219
|
+
if (outer.length === 0) return;
|
|
1220
|
+
const radii = clampRadii(borderRadius, width, height);
|
|
1221
|
+
let margin = 0;
|
|
1222
|
+
for (const s of outer) {
|
|
1223
|
+
margin = Math.max(margin, reach(s));
|
|
1224
|
+
}
|
|
1068
1225
|
ctx.save();
|
|
1069
1226
|
ctx.beginPath();
|
|
1070
1227
|
ctx.rect(x - margin, y - margin, width + margin * 2, height + margin * 2);
|
|
1071
|
-
ctx
|
|
1228
|
+
roundedRect(ctx, x, y, width, height, ...radii);
|
|
1072
1229
|
ctx.clip("evenodd");
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1230
|
+
for (let i = outer.length - 1; i >= 0; i--) {
|
|
1231
|
+
const s = outer[i];
|
|
1232
|
+
const w = width + s.spread * 2;
|
|
1233
|
+
const h = height + s.spread * 2;
|
|
1234
|
+
if (w <= 0 || h <= 0) continue;
|
|
1235
|
+
ctx.save();
|
|
1236
|
+
setShadowPaint(ctx, s);
|
|
1237
|
+
ctx.beginPath();
|
|
1238
|
+
roundedRect(
|
|
1239
|
+
ctx,
|
|
1240
|
+
x - s.spread + s.offsetX,
|
|
1241
|
+
y - s.spread + s.offsetY,
|
|
1242
|
+
w,
|
|
1243
|
+
h,
|
|
1244
|
+
...spreadRadii(radii, s.spread)
|
|
1245
|
+
);
|
|
1246
|
+
ctx.fill();
|
|
1247
|
+
ctx.restore();
|
|
1248
|
+
}
|
|
1249
|
+
ctx.restore();
|
|
1250
|
+
}
|
|
1251
|
+
function drawInsetBoxShadow(ctx, x, y, width, height, shadows, borderRadius, style) {
|
|
1252
|
+
const inset = shadows.filter((s) => s.inset);
|
|
1253
|
+
if (inset.length === 0) return;
|
|
1254
|
+
const bt = resolveBoxValue(style.borderTopWidth, width);
|
|
1255
|
+
const br = resolveBoxValue(style.borderRightWidth, width);
|
|
1256
|
+
const bb = resolveBoxValue(style.borderBottomWidth, width);
|
|
1257
|
+
const bl = resolveBoxValue(style.borderLeftWidth, width);
|
|
1258
|
+
const px = x + bl;
|
|
1259
|
+
const py = y + bt;
|
|
1260
|
+
const pw = width - bl - br;
|
|
1261
|
+
const ph = height - bt - bb;
|
|
1262
|
+
if (pw <= 0 || ph <= 0) return;
|
|
1263
|
+
const [tl, tr, brr, bll] = clampRadii(borderRadius, width, height);
|
|
1264
|
+
const inner = [
|
|
1265
|
+
Math.max(0, tl - Math.max(bt, bl)),
|
|
1266
|
+
Math.max(0, tr - Math.max(bt, br)),
|
|
1267
|
+
Math.max(0, brr - Math.max(bb, br)),
|
|
1268
|
+
Math.max(0, bll - Math.max(bb, bl))
|
|
1269
|
+
];
|
|
1270
|
+
ctx.save();
|
|
1076
1271
|
ctx.beginPath();
|
|
1077
|
-
ctx
|
|
1078
|
-
ctx.
|
|
1272
|
+
roundedRect(ctx, px, py, pw, ph, ...inner);
|
|
1273
|
+
ctx.clip();
|
|
1274
|
+
for (let i = inset.length - 1; i >= 0; i--) {
|
|
1275
|
+
const s = inset[i];
|
|
1276
|
+
const m = reach(s);
|
|
1277
|
+
ctx.save();
|
|
1278
|
+
setShadowPaint(ctx, s);
|
|
1279
|
+
ctx.beginPath();
|
|
1280
|
+
ctx.rect(px - m, py - m, pw + m * 2, ph + m * 2);
|
|
1281
|
+
const w = pw - s.spread * 2;
|
|
1282
|
+
const h = ph - s.spread * 2;
|
|
1283
|
+
if (w > 0 && h > 0) {
|
|
1284
|
+
roundedRect(
|
|
1285
|
+
ctx,
|
|
1286
|
+
px + s.spread + s.offsetX,
|
|
1287
|
+
py + s.spread + s.offsetY,
|
|
1288
|
+
w,
|
|
1289
|
+
h,
|
|
1290
|
+
...spreadRadii(inner, -s.spread)
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
ctx.fill("evenodd");
|
|
1294
|
+
ctx.restore();
|
|
1295
|
+
}
|
|
1079
1296
|
ctx.restore();
|
|
1080
1297
|
}
|
|
1298
|
+
function reach(s) {
|
|
1299
|
+
return s.blur * 2 + Math.abs(s.offsetX) + Math.abs(s.offsetY) + Math.abs(s.spread) + 1;
|
|
1300
|
+
}
|
|
1301
|
+
function setShadowPaint(ctx, s) {
|
|
1302
|
+
ctx.fillStyle = NO_COLOR;
|
|
1303
|
+
ctx.fillStyle = s.color;
|
|
1304
|
+
if (s.blur > 0) ctx.filter = `blur(${s.blur / 2}px)`;
|
|
1305
|
+
}
|
|
1306
|
+
function clampRadii(r, width, height) {
|
|
1307
|
+
const max = maxCornerRadius(width, height);
|
|
1308
|
+
return [
|
|
1309
|
+
Math.min(r.topLeft, max),
|
|
1310
|
+
Math.min(r.topRight, max),
|
|
1311
|
+
Math.min(r.bottomRight, max),
|
|
1312
|
+
Math.min(r.bottomLeft, max)
|
|
1313
|
+
];
|
|
1314
|
+
}
|
|
1315
|
+
function spreadRadii(radii, spread) {
|
|
1316
|
+
return radii.map((r) => spreadRadius(r, spread));
|
|
1317
|
+
}
|
|
1318
|
+
function spreadRadius(r, spread) {
|
|
1319
|
+
if (spread <= 0) return Math.max(0, r + spread);
|
|
1320
|
+
if (r >= spread) return r + spread;
|
|
1321
|
+
const ratio = r / spread;
|
|
1322
|
+
return r + spread * (1 + (ratio - 1) ** 3);
|
|
1323
|
+
}
|
|
1081
1324
|
|
|
1082
1325
|
// src/jsx/draw/clip-path.ts
|
|
1083
1326
|
var GEOMETRY_BOXES = /* @__PURE__ */ new Set([
|
|
@@ -1994,6 +2237,80 @@ async function drawImage(ctx, src, x, y, width, height, context, style) {
|
|
|
1994
2237
|
}
|
|
1995
2238
|
}
|
|
1996
2239
|
|
|
2240
|
+
// src/jsx/draw/shadow.ts
|
|
2241
|
+
var BOX_SHADOW = { maxLengths: 4, inset: true };
|
|
2242
|
+
var TEXT_SHADOW = { maxLengths: 3, inset: false };
|
|
2243
|
+
function parseBoxShadow(value, currentColor) {
|
|
2244
|
+
return parseShadowList(value, currentColor, BOX_SHADOW);
|
|
2245
|
+
}
|
|
2246
|
+
function parseTextShadow(value, currentColor) {
|
|
2247
|
+
return parseShadowList(value, currentColor, TEXT_SHADOW);
|
|
2248
|
+
}
|
|
2249
|
+
function parseShadowList(value, currentColor, syntax) {
|
|
2250
|
+
const text = value.trim();
|
|
2251
|
+
if (text === "" || text.toLowerCase() === "none") return [];
|
|
2252
|
+
const shadows = [];
|
|
2253
|
+
for (const item of splitTopLevel(text, ",")) {
|
|
2254
|
+
const shadow = parseShadow(item, currentColor, syntax);
|
|
2255
|
+
if (!shadow) return [];
|
|
2256
|
+
shadows.push(shadow);
|
|
2257
|
+
}
|
|
2258
|
+
return shadows;
|
|
2259
|
+
}
|
|
2260
|
+
var LENGTH = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)(px)?$/i;
|
|
2261
|
+
var NUMERIC = /^[+-]?\.?\d/;
|
|
2262
|
+
function parseShadow(item, currentColor, syntax) {
|
|
2263
|
+
const tokens = splitTopLevel(item, " ");
|
|
2264
|
+
const lengths = [];
|
|
2265
|
+
let lengthsEnded = false;
|
|
2266
|
+
let inset = false;
|
|
2267
|
+
let color;
|
|
2268
|
+
for (const token of tokens) {
|
|
2269
|
+
if (NUMERIC.test(token)) {
|
|
2270
|
+
const match = LENGTH.exec(token);
|
|
2271
|
+
if (!match || lengthsEnded) return null;
|
|
2272
|
+
lengths.push(parseFloat(match[1]));
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
if (lengths.length > 0) lengthsEnded = true;
|
|
2276
|
+
if (token.toLowerCase() === "inset") {
|
|
2277
|
+
if (!syntax.inset || inset) return null;
|
|
2278
|
+
inset = true;
|
|
2279
|
+
} else {
|
|
2280
|
+
if (color !== void 0) return null;
|
|
2281
|
+
color = token;
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
if (lengths.length < 2 || lengths.length > syntax.maxLengths) return null;
|
|
2285
|
+
const [offsetX, offsetY, blur = 0, spread = 0] = lengths;
|
|
2286
|
+
if (blur < 0) return null;
|
|
2287
|
+
if (color === void 0 || color.toLowerCase() === "currentcolor") {
|
|
2288
|
+
color = currentColor;
|
|
2289
|
+
}
|
|
2290
|
+
return { offsetX, offsetY, blur, spread, inset, color };
|
|
2291
|
+
}
|
|
2292
|
+
function splitTopLevel(text, separator) {
|
|
2293
|
+
const parts = [];
|
|
2294
|
+
let depth = 0;
|
|
2295
|
+
let current = "";
|
|
2296
|
+
for (const ch of text) {
|
|
2297
|
+
if (ch === "(") depth++;
|
|
2298
|
+
else if (ch === ")") depth = Math.max(0, depth - 1);
|
|
2299
|
+
const splits = depth === 0 && (separator === "," ? ch === "," : /\s/.test(ch));
|
|
2300
|
+
if (splits) {
|
|
2301
|
+
parts.push(current);
|
|
2302
|
+
current = "";
|
|
2303
|
+
} else {
|
|
2304
|
+
current += ch;
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
parts.push(current);
|
|
2308
|
+
if (separator === ",") {
|
|
2309
|
+
return parts.map((p) => p.trim());
|
|
2310
|
+
}
|
|
2311
|
+
return parts.filter((p) => p !== "");
|
|
2312
|
+
}
|
|
2313
|
+
|
|
1997
2314
|
// src/jsx/draw/svg/style.ts
|
|
1998
2315
|
function resolveCurrentColor(value, color) {
|
|
1999
2316
|
if (value?.toLowerCase() === "currentcolor") return color;
|
|
@@ -3103,12 +3420,12 @@ async function drawText(ctx, layout, offsetX, offsetY, textShadow, emojiStyle) {
|
|
|
3103
3420
|
if (!first) return;
|
|
3104
3421
|
const x = offsetX;
|
|
3105
3422
|
const y = offsetY + layout.paragraphOffsetY;
|
|
3106
|
-
const
|
|
3423
|
+
const shadows = textShadow ? parseTextShadow(textShadow, first.color) : [];
|
|
3107
3424
|
ctx.fillStyle = first.color;
|
|
3108
|
-
|
|
3425
|
+
for (let i = shadows.length - 1; i >= 0; i--) {
|
|
3109
3426
|
drawShadowPass(
|
|
3110
3427
|
ctx,
|
|
3111
|
-
|
|
3428
|
+
shadows[i],
|
|
3112
3429
|
getColorAlpha(first.color),
|
|
3113
3430
|
() => fillParagraph(ctx, paragraph, x, y)
|
|
3114
3431
|
);
|
|
@@ -3154,24 +3471,13 @@ async function drawEmoji(ctx, emoji, font, offsetX, offsetY, emojiStyle) {
|
|
|
3154
3471
|
ctx.textRendering = textRendering;
|
|
3155
3472
|
});
|
|
3156
3473
|
}
|
|
3157
|
-
function parseShadow(shadow) {
|
|
3158
|
-
const parts = shadow.match(
|
|
3159
|
-
/(-?\d+(?:\.\d+)?)\s*(?:px)?\s+(-?\d+(?:\.\d+)?)\s*(?:px)?\s+(-?\d+(?:\.\d+)?)\s*(?:px)?\s+(.*)/
|
|
3160
|
-
);
|
|
3161
|
-
if (!parts) return null;
|
|
3162
|
-
return {
|
|
3163
|
-
offsetX: parseFloat(parts[1]),
|
|
3164
|
-
offsetY: parseFloat(parts[2]),
|
|
3165
|
-
blur: parseFloat(parts[3]),
|
|
3166
|
-
color: parts[4].trim()
|
|
3167
|
-
};
|
|
3168
|
-
}
|
|
3169
3474
|
function getColorAlpha(color) {
|
|
3170
3475
|
const parsed = parseCssColor2(color);
|
|
3171
3476
|
return parsed ? parsed.alpha : 1;
|
|
3172
3477
|
}
|
|
3173
3478
|
function drawShadowPass(ctx, shadow, textAlpha, drawFn) {
|
|
3174
3479
|
ctx.save();
|
|
3480
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0)";
|
|
3175
3481
|
ctx.fillStyle = shadow.color;
|
|
3176
3482
|
if (textAlpha < 1) ctx.globalAlpha *= textAlpha;
|
|
3177
3483
|
if (shadow.blur > 0) ctx.filter = `blur(${shadow.blur / 2}px)`;
|
|
@@ -3261,9 +3567,8 @@ async function paintNode(ctx, node, x, y, opacity, renderContext, emojiStyle, gr
|
|
|
3261
3567
|
);
|
|
3262
3568
|
}
|
|
3263
3569
|
group.open = beginElementGroup(ctx, opacity, style.filter);
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
}
|
|
3570
|
+
const boxShadows = style.boxShadow ? parseBoxShadow(style.boxShadow, style.color ?? "black") : [];
|
|
3571
|
+
drawBoxShadow(ctx, x, y, width, height, boxShadows, borderRadius);
|
|
3267
3572
|
const isClipped = style.overflow === "hidden" || style.overflowX === "hidden" || style.overflowY === "hidden";
|
|
3268
3573
|
if (isClipped) {
|
|
3269
3574
|
applyClip(ctx, x, y, width, height, borderRadius);
|
|
@@ -3298,15 +3603,14 @@ async function paintNode(ctx, node, x, y, opacity, renderContext, emojiStyle, gr
|
|
|
3298
3603
|
} else {
|
|
3299
3604
|
const urlMatch = layer.match(/url\(["']?(.*?)["']?\)/);
|
|
3300
3605
|
if (urlMatch) {
|
|
3301
|
-
if (hasRadius(borderRadius)) {
|
|
3302
|
-
applyClip(ctx, x, y, width, height, borderRadius);
|
|
3303
|
-
}
|
|
3304
3606
|
const image = await cachedLoadImage(
|
|
3305
3607
|
renderContext.imageCache,
|
|
3306
3608
|
urlMatch[1],
|
|
3307
3609
|
renderContext.userAgent
|
|
3308
3610
|
);
|
|
3309
3611
|
const bgSize = style.backgroundSize;
|
|
3612
|
+
ctx.save();
|
|
3613
|
+
applyClip(ctx, x, y, width, height, borderRadius);
|
|
3310
3614
|
if (bgSize === "cover") {
|
|
3311
3615
|
const r = computeCover(
|
|
3312
3616
|
image.width,
|
|
@@ -3347,19 +3651,24 @@ async function paintNode(ctx, node, x, y, opacity, renderContext, emojiStyle, gr
|
|
|
3347
3651
|
tileW = image.width;
|
|
3348
3652
|
tileH = image.height;
|
|
3349
3653
|
}
|
|
3654
|
+
const drawable = tileW > 0 && tileH > 0 && Number.isFinite(tileW) && Number.isFinite(tileH);
|
|
3350
3655
|
const repeat = style.backgroundRepeat ?? "repeat";
|
|
3351
3656
|
const stepX = repeat === "repeat" || repeat === "repeat-x" ? tileW : width;
|
|
3352
3657
|
const stepY = repeat === "repeat" || repeat === "repeat-y" ? tileH : height;
|
|
3353
|
-
|
|
3354
|
-
for (let
|
|
3355
|
-
|
|
3658
|
+
if (drawable) {
|
|
3659
|
+
for (let ty = y; ty < y + height; ty += stepY) {
|
|
3660
|
+
for (let tx = x; tx < x + width; tx += stepX) {
|
|
3661
|
+
ctx.drawImage(image, tx, ty, tileW, tileH);
|
|
3662
|
+
}
|
|
3356
3663
|
}
|
|
3357
3664
|
}
|
|
3358
3665
|
}
|
|
3666
|
+
ctx.restore();
|
|
3359
3667
|
}
|
|
3360
3668
|
}
|
|
3361
3669
|
}
|
|
3362
3670
|
}
|
|
3671
|
+
drawInsetBoxShadow(ctx, x, y, width, height, boxShadows, borderRadius, style);
|
|
3363
3672
|
if (renderContext.debug) {
|
|
3364
3673
|
ctx.strokeStyle = "rgba(255, 0, 0, 0.5)";
|
|
3365
3674
|
ctx.lineWidth = 1;
|
|
@@ -3833,6 +4142,7 @@ import Yoga, {
|
|
|
3833
4142
|
MeasureMode,
|
|
3834
4143
|
Overflow,
|
|
3835
4144
|
PositionType,
|
|
4145
|
+
Unit,
|
|
3836
4146
|
Wrap
|
|
3837
4147
|
} from "yoga-layout";
|
|
3838
4148
|
function createYogaNode() {
|
|
@@ -3851,6 +4161,10 @@ function freeYogaNode(node) {
|
|
|
3851
4161
|
}
|
|
3852
4162
|
|
|
3853
4163
|
// src/jsx/style/properties.ts
|
|
4164
|
+
function toLayoutUnit(px) {
|
|
4165
|
+
if (!Number.isFinite(px)) return px;
|
|
4166
|
+
return Math.trunc(Math.round(px * 64 * 1e6) / 1e6) / 64;
|
|
4167
|
+
}
|
|
3854
4168
|
function applyStylesToYoga(node, style) {
|
|
3855
4169
|
if (style.display === "none") {
|
|
3856
4170
|
node.setDisplay(Display.None);
|
|
@@ -3928,14 +4242,14 @@ function applyStylesToYoga(node, style) {
|
|
|
3928
4242
|
node.setFlexShrink(style.flexShrink ?? 0);
|
|
3929
4243
|
if (style.flexBasis !== void 0) {
|
|
3930
4244
|
if (typeof style.flexBasis === "number") {
|
|
3931
|
-
node.setFlexBasis(style.flexBasis);
|
|
4245
|
+
node.setFlexBasis(toLayoutUnit(style.flexBasis));
|
|
3932
4246
|
} else if (String(style.flexBasis).endsWith("%")) {
|
|
3933
4247
|
node.setFlexBasis(String(style.flexBasis));
|
|
3934
4248
|
} else if (style.flexBasis === "auto") {
|
|
3935
4249
|
node.setFlexBasis("auto");
|
|
3936
4250
|
} else {
|
|
3937
4251
|
const n = parseFloat(String(style.flexBasis));
|
|
3938
|
-
if (!isNaN(n)) node.setFlexBasis(n);
|
|
4252
|
+
if (!isNaN(n)) node.setFlexBasis(toLayoutUnit(n));
|
|
3939
4253
|
}
|
|
3940
4254
|
}
|
|
3941
4255
|
applyDimension(node, "setWidth", style.width);
|
|
@@ -3962,16 +4276,20 @@ function applyStylesToYoga(node, style) {
|
|
|
3962
4276
|
applyEdgeValue(node, "setPadding", Edge.Bottom, style.paddingBottom);
|
|
3963
4277
|
applyEdgeValue(node, "setPadding", Edge.Left, style.paddingLeft);
|
|
3964
4278
|
if (style.borderTopWidth !== void 0)
|
|
3965
|
-
node.setBorder(Edge.Top, style.borderTopWidth);
|
|
4279
|
+
node.setBorder(Edge.Top, toLayoutUnit(style.borderTopWidth));
|
|
3966
4280
|
if (style.borderRightWidth !== void 0)
|
|
3967
|
-
node.setBorder(Edge.Right, style.borderRightWidth);
|
|
4281
|
+
node.setBorder(Edge.Right, toLayoutUnit(style.borderRightWidth));
|
|
3968
4282
|
if (style.borderBottomWidth !== void 0)
|
|
3969
|
-
node.setBorder(
|
|
4283
|
+
node.setBorder(
|
|
4284
|
+
Edge.Bottom,
|
|
4285
|
+
toLayoutUnit(style.borderBottomWidth)
|
|
4286
|
+
);
|
|
3970
4287
|
if (style.borderLeftWidth !== void 0)
|
|
3971
|
-
node.setBorder(Edge.Left, style.borderLeftWidth);
|
|
3972
|
-
if (style.rowGap !== void 0)
|
|
4288
|
+
node.setBorder(Edge.Left, toLayoutUnit(style.borderLeftWidth));
|
|
4289
|
+
if (style.rowGap !== void 0)
|
|
4290
|
+
node.setGap(Gutter.Row, toLayoutUnit(style.rowGap));
|
|
3973
4291
|
if (style.columnGap !== void 0)
|
|
3974
|
-
node.setGap(Gutter.Column, style.columnGap);
|
|
4292
|
+
node.setGap(Gutter.Column, toLayoutUnit(style.columnGap));
|
|
3975
4293
|
if (style.overflow === "hidden" || style.overflowX === "hidden" || style.overflowY === "hidden") {
|
|
3976
4294
|
node.setOverflow(Overflow.Hidden);
|
|
3977
4295
|
} else {
|
|
@@ -3987,7 +4305,7 @@ function applyDimension(node, setter, value) {
|
|
|
3987
4305
|
return;
|
|
3988
4306
|
}
|
|
3989
4307
|
if (typeof value === "number") {
|
|
3990
|
-
node[setter](value);
|
|
4308
|
+
node[setter](toLayoutUnit(value));
|
|
3991
4309
|
return;
|
|
3992
4310
|
}
|
|
3993
4311
|
const s = String(value);
|
|
@@ -3995,7 +4313,7 @@ function applyDimension(node, setter, value) {
|
|
|
3995
4313
|
node[setter](s);
|
|
3996
4314
|
} else {
|
|
3997
4315
|
const n = parseFloat(s);
|
|
3998
|
-
if (!isNaN(n)) node[setter](n);
|
|
4316
|
+
if (!isNaN(n)) node[setter](toLayoutUnit(n));
|
|
3999
4317
|
}
|
|
4000
4318
|
}
|
|
4001
4319
|
function applyEdgeValue(node, setter, edge, value) {
|
|
@@ -4005,7 +4323,7 @@ function applyEdgeValue(node, setter, edge, value) {
|
|
|
4005
4323
|
return;
|
|
4006
4324
|
}
|
|
4007
4325
|
if (typeof value === "number") {
|
|
4008
|
-
node[setter](edge, value);
|
|
4326
|
+
node[setter](edge, toLayoutUnit(value));
|
|
4009
4327
|
return;
|
|
4010
4328
|
}
|
|
4011
4329
|
const s = String(value);
|
|
@@ -4013,7 +4331,7 @@ function applyEdgeValue(node, setter, edge, value) {
|
|
|
4013
4331
|
node[setter](edge, s);
|
|
4014
4332
|
} else {
|
|
4015
4333
|
const n = parseFloat(s);
|
|
4016
|
-
if (!isNaN(n)) node[setter](edge, n);
|
|
4334
|
+
if (!isNaN(n)) node[setter](edge, toLayoutUnit(n));
|
|
4017
4335
|
}
|
|
4018
4336
|
}
|
|
4019
4337
|
|
|
@@ -4041,12 +4359,46 @@ async function buildLayoutTree(element, containerWidth, containerHeight, emojiEn
|
|
|
4041
4359
|
rootYogaNode.setHeight(containerHeight);
|
|
4042
4360
|
rootYogaNode.setFlexDirection(FlexDirection.Row);
|
|
4043
4361
|
rootYogaNode.insertChild(elementYogaNode, 0);
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
);
|
|
4362
|
+
let layouts = 0;
|
|
4363
|
+
const compute = () => {
|
|
4364
|
+
layouts++;
|
|
4365
|
+
rootYogaNode.calculateLayout(containerWidth, containerHeight);
|
|
4366
|
+
};
|
|
4367
|
+
const autoMinimums = collectAutoMinimums(elementNode);
|
|
4368
|
+
const layOut = () => {
|
|
4369
|
+
compute();
|
|
4370
|
+
for (const node of autoMinimums) node.autoMinimum.refreshed = false;
|
|
4371
|
+
for (let pass = 0; pass < MAX_AUTO_MINIMUM_RELAYOUTS && enforceAutoMinimums(autoMinimums); pass++) {
|
|
4372
|
+
compute();
|
|
4373
|
+
}
|
|
4374
|
+
};
|
|
4375
|
+
layOut();
|
|
4376
|
+
settleText(elementNode, layOut, renderContext.debug);
|
|
4377
|
+
const limit = RUNAWAY_SIZE * Math.max(containerWidth, containerHeight);
|
|
4378
|
+
for (let pass = 0; pass < MAX_RUNAWAY_RELAYOUTS; pass++) {
|
|
4379
|
+
const runaway2 = findRunawayItems(elementNode, limit);
|
|
4380
|
+
if (runaway2.length === 0) break;
|
|
4381
|
+
for (const item of runaway2) pinAtMinimum(item);
|
|
4382
|
+
layOut();
|
|
4383
|
+
settleText(elementNode, layOut, renderContext.debug);
|
|
4384
|
+
}
|
|
4385
|
+
const runaway = findRunawayItems(elementNode, limit);
|
|
4386
|
+
if (runaway.length > 0) {
|
|
4387
|
+
const lines = new Set(runaway.map((item) => item.parent));
|
|
4388
|
+
const lifted = [...lines].flatMap((line) => line.children).filter((node) => node.autoMinimum && liftAutoMinimum(node));
|
|
4389
|
+
if (lifted.length > 0) {
|
|
4390
|
+
layOut();
|
|
4391
|
+
settleText(elementNode, layOut, renderContext.debug);
|
|
4392
|
+
}
|
|
4393
|
+
if (renderContext.debug) {
|
|
4394
|
+
console.warn(
|
|
4395
|
+
"[@effing/canvas] Yoga sized flex items out of all proportion" + (lifted.length > 0 ? ": laid their text out without minimums" : "")
|
|
4396
|
+
);
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
if (renderContext.debug && layouts > 5) {
|
|
4400
|
+
console.warn(`[@effing/canvas] computed the layout ${layouts} times`);
|
|
4401
|
+
}
|
|
4050
4402
|
const elementLayout = extractLayout(elementNode, elementYogaNode);
|
|
4051
4403
|
freeYogaNode(rootYogaNode);
|
|
4052
4404
|
const tree = {
|
|
@@ -4071,20 +4423,9 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
4071
4423
|
yogaNode
|
|
4072
4424
|
};
|
|
4073
4425
|
}
|
|
4074
|
-
if (
|
|
4075
|
-
const text = String(element);
|
|
4076
|
-
|
|
4077
|
-
const textMeasure = new TextMeasure(text, style2, emojiEnabled);
|
|
4078
|
-
setTextMeasure(yogaNode, textMeasure);
|
|
4079
|
-
return {
|
|
4080
|
-
type: "text",
|
|
4081
|
-
style: style2,
|
|
4082
|
-
children: [],
|
|
4083
|
-
textContent: text,
|
|
4084
|
-
textMeasure,
|
|
4085
|
-
props: {},
|
|
4086
|
-
yogaNode
|
|
4087
|
-
};
|
|
4426
|
+
if (isText(element) || isTextRun(element)) {
|
|
4427
|
+
const text = isTextRun(element) ? textContentOf(element) : String(element);
|
|
4428
|
+
return buildTextNode(text, parentStyle, yogaNode, emojiEnabled);
|
|
4088
4429
|
}
|
|
4089
4430
|
const el = element;
|
|
4090
4431
|
const type = el.type;
|
|
@@ -4181,14 +4522,18 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
4181
4522
|
yogaNode
|
|
4182
4523
|
};
|
|
4183
4524
|
}
|
|
4184
|
-
const items = flattenChildren(props.children)
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4525
|
+
const items = flattenChildren(props.children).filter(
|
|
4526
|
+
(item) => !isTextRun(item) || !isBlankRun(item)
|
|
4527
|
+
);
|
|
4528
|
+
const onlyText = items.length === 1 && isTextRun(items[0]) ? items[0] : null;
|
|
4529
|
+
if (onlyText !== null) {
|
|
4189
4530
|
const childYogaNode = createTextYogaNode();
|
|
4190
|
-
const
|
|
4191
|
-
|
|
4531
|
+
const child = buildTextNode(
|
|
4532
|
+
textContentOf(onlyText),
|
|
4533
|
+
style,
|
|
4534
|
+
childYogaNode,
|
|
4535
|
+
emojiEnabled
|
|
4536
|
+
);
|
|
4192
4537
|
const jc = style.justifyContent;
|
|
4193
4538
|
if (!jc || jc === "flex-start") {
|
|
4194
4539
|
childYogaNode.setFlexGrow(1);
|
|
@@ -4198,28 +4543,29 @@ async function buildNode(element, parentStyle, yogaNode, viewportWidth, viewport
|
|
|
4198
4543
|
if (style.flexShrink === void 0) {
|
|
4199
4544
|
yogaNode.setFlexShrink(1);
|
|
4200
4545
|
}
|
|
4546
|
+
const textMeasure = child.textMeasure;
|
|
4547
|
+
child.autoMinimum = {
|
|
4548
|
+
textMeasure,
|
|
4549
|
+
column: !isRow(style),
|
|
4550
|
+
text: childYogaNode
|
|
4551
|
+
};
|
|
4201
4552
|
return {
|
|
4202
4553
|
type: tagName,
|
|
4203
4554
|
style,
|
|
4204
|
-
children: [
|
|
4205
|
-
{
|
|
4206
|
-
type: "text",
|
|
4207
|
-
style: childStyle,
|
|
4208
|
-
children: [],
|
|
4209
|
-
textContent,
|
|
4210
|
-
textMeasure,
|
|
4211
|
-
props: {},
|
|
4212
|
-
yogaNode: childYogaNode
|
|
4213
|
-
}
|
|
4214
|
-
],
|
|
4555
|
+
children: [child],
|
|
4215
4556
|
props,
|
|
4216
|
-
yogaNode
|
|
4557
|
+
yogaNode,
|
|
4558
|
+
autoMinimum: hasAutoMinimum(style, parentStyle) ? {
|
|
4559
|
+
textMeasure,
|
|
4560
|
+
style,
|
|
4561
|
+
column: !isRow(parentStyle),
|
|
4562
|
+
text: childYogaNode
|
|
4563
|
+
} : void 0
|
|
4217
4564
|
};
|
|
4218
4565
|
}
|
|
4219
4566
|
const children = [];
|
|
4220
4567
|
for (const rendered of items) {
|
|
4221
|
-
|
|
4222
|
-
const childYogaNode = isText(rendered) ? createTextYogaNode() : createYogaNode();
|
|
4568
|
+
const childYogaNode = isTextRun(rendered) ? createTextYogaNode() : createYogaNode();
|
|
4223
4569
|
yogaNode.insertChild(childYogaNode, children.length);
|
|
4224
4570
|
children.push(
|
|
4225
4571
|
await buildNode(
|
|
@@ -4275,11 +4621,248 @@ function settleText(root, relayout, debug) {
|
|
|
4275
4621
|
);
|
|
4276
4622
|
}
|
|
4277
4623
|
}
|
|
4624
|
+
function buildTextNode(text, parentStyle, yogaNode, emojiEnabled) {
|
|
4625
|
+
const style = resolveStyle(void 0, parentStyle);
|
|
4626
|
+
const textMeasure = new TextMeasure(text, style, emojiEnabled);
|
|
4627
|
+
setTextMeasure(yogaNode, textMeasure);
|
|
4628
|
+
return {
|
|
4629
|
+
type: "text",
|
|
4630
|
+
style,
|
|
4631
|
+
children: [],
|
|
4632
|
+
textContent: text,
|
|
4633
|
+
textMeasure,
|
|
4634
|
+
props: {},
|
|
4635
|
+
yogaNode
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
var isRow = (style) => style.flexDirection === void 0 || style.flexDirection === "row" || style.flexDirection === "row-reverse";
|
|
4639
|
+
function hasAutoMinimum(style, parentStyle) {
|
|
4640
|
+
const min = isRow(parentStyle) ? style.minWidth : style.minHeight;
|
|
4641
|
+
return style.position !== "absolute" && (min === void 0 || min === "auto") && ![style.overflow, style.overflowX, style.overflowY].some(
|
|
4642
|
+
(overflow) => overflow === "hidden" || overflow === "scroll" || overflow === "auto"
|
|
4643
|
+
);
|
|
4644
|
+
}
|
|
4645
|
+
var MAX_AUTO_MINIMUM_RELAYOUTS = 4;
|
|
4646
|
+
function collectAutoMinimums(root) {
|
|
4647
|
+
const nodes = [];
|
|
4648
|
+
const collect = (node) => {
|
|
4649
|
+
if (node.style.display === "none") return;
|
|
4650
|
+
if (node.autoMinimum) nodes.push(node);
|
|
4651
|
+
node.children.forEach(collect);
|
|
4652
|
+
};
|
|
4653
|
+
collect(root);
|
|
4654
|
+
return nodes;
|
|
4655
|
+
}
|
|
4656
|
+
function enforceAutoMinimums(nodes) {
|
|
4657
|
+
let enforced = false;
|
|
4658
|
+
for (const node of nodes) {
|
|
4659
|
+
const autoMinimum = node.autoMinimum;
|
|
4660
|
+
if (autoMinimum.lifted) continue;
|
|
4661
|
+
if (autoMinimum.column) {
|
|
4662
|
+
if (enforceAutoMinimumHeight(node)) enforced = true;
|
|
4663
|
+
continue;
|
|
4664
|
+
}
|
|
4665
|
+
if (autoMinimum.lift) continue;
|
|
4666
|
+
const { yogaNode } = node;
|
|
4667
|
+
const { textMeasure, style } = autoMinimum;
|
|
4668
|
+
const width = yogaNode.getComputedWidth();
|
|
4669
|
+
const tolerance = style ? 1 : 1e-3;
|
|
4670
|
+
const edges = style ? horizontalEdges(yogaNode, style) : 0;
|
|
4671
|
+
if (width + tolerance >= textMeasure.minContentBound + edges) continue;
|
|
4672
|
+
const minimum = autoMinimumWidth(node);
|
|
4673
|
+
if (minimum === void 0 || width + tolerance >= minimum) continue;
|
|
4674
|
+
autoMinimum.lift = holdAtMinimum(yogaNode, minimum, {
|
|
4675
|
+
atBasis: textMeasure.unbreakable && (!style || isAuto(style.width) && isAuto(style.flexBasis)),
|
|
4676
|
+
// The text in an element sizes the element's own flex basis, which a
|
|
4677
|
+
// pin would take from it.
|
|
4678
|
+
pin: style !== void 0,
|
|
4679
|
+
column: false
|
|
4680
|
+
});
|
|
4681
|
+
enforced = true;
|
|
4682
|
+
}
|
|
4683
|
+
return enforced;
|
|
4684
|
+
}
|
|
4685
|
+
function enforceAutoMinimumHeight(node) {
|
|
4686
|
+
const autoMinimum = node.autoMinimum;
|
|
4687
|
+
const { yogaNode } = node;
|
|
4688
|
+
const { style } = autoMinimum;
|
|
4689
|
+
const minimum = autoMinimumHeight(node);
|
|
4690
|
+
if (minimum === void 0) return false;
|
|
4691
|
+
const height = yogaNode.getComputedHeight();
|
|
4692
|
+
const tolerance = style ? 0.5 : 1e-3;
|
|
4693
|
+
const short = height + tolerance < minimum;
|
|
4694
|
+
const atBasis = !style || isAuto(style.height) && isAuto(style.flexBasis);
|
|
4695
|
+
if (autoMinimum.lift) {
|
|
4696
|
+
const { held } = autoMinimum;
|
|
4697
|
+
if (held === void 0) {
|
|
4698
|
+
if (!short) return false;
|
|
4699
|
+
} else if (Math.abs(held - minimum) < tolerance || // In a column that wraps, the heights decide the widths: an item is
|
|
4700
|
+
// only ever held higher there, or they could swing back and forth.
|
|
4701
|
+
minimum < held && yogaNode.getParent()?.getFlexWrap() !== Wrap.NoWrap) {
|
|
4702
|
+
if (height + (style ? 1 : 1e-3) >= held || autoMinimum.refreshed) {
|
|
4703
|
+
return false;
|
|
4704
|
+
}
|
|
4705
|
+
autoMinimum.text.markDirty();
|
|
4706
|
+
autoMinimum.refreshed = true;
|
|
4707
|
+
return true;
|
|
4708
|
+
}
|
|
4709
|
+
autoMinimum.lift();
|
|
4710
|
+
} else if (!short) {
|
|
4711
|
+
return false;
|
|
4712
|
+
} else if (atBasis) {
|
|
4713
|
+
autoMinimum.lift = holdAtMinimum(yogaNode, minimum, {
|
|
4714
|
+
atBasis: true,
|
|
4715
|
+
pin: false,
|
|
4716
|
+
column: true
|
|
4717
|
+
});
|
|
4718
|
+
return true;
|
|
4719
|
+
}
|
|
4720
|
+
autoMinimum.held = minimum;
|
|
4721
|
+
autoMinimum.lift = holdAtMinimum(yogaNode, minimum, {
|
|
4722
|
+
atBasis: false,
|
|
4723
|
+
pin: !atBasis,
|
|
4724
|
+
column: true
|
|
4725
|
+
});
|
|
4726
|
+
return true;
|
|
4727
|
+
}
|
|
4728
|
+
var isAuto = (value) => value === void 0 || value === "auto";
|
|
4729
|
+
function horizontalEdges(yogaNode, style) {
|
|
4730
|
+
const lengths = style && [
|
|
4731
|
+
style.paddingLeft,
|
|
4732
|
+
style.paddingRight,
|
|
4733
|
+
style.borderLeftWidth,
|
|
4734
|
+
style.borderRightWidth
|
|
4735
|
+
];
|
|
4736
|
+
if (lengths?.every((v) => v === void 0 || typeof v === "number")) {
|
|
4737
|
+
return lengths.reduce(
|
|
4738
|
+
(sum, v) => sum + toLayoutUnit(v ?? 0),
|
|
4739
|
+
0
|
|
4740
|
+
);
|
|
4741
|
+
}
|
|
4742
|
+
return yogaNode.getComputedPadding(Edge.Left) + yogaNode.getComputedPadding(Edge.Right) + yogaNode.getComputedBorder(Edge.Left) + yogaNode.getComputedBorder(Edge.Right);
|
|
4743
|
+
}
|
|
4744
|
+
function autoMinimumWidth(node) {
|
|
4745
|
+
const { yogaNode } = node;
|
|
4746
|
+
const { textMeasure, style } = node.autoMinimum;
|
|
4747
|
+
if (!style) return textMeasure.minContentWidth;
|
|
4748
|
+
const parent = yogaNode.getParent();
|
|
4749
|
+
return capped(
|
|
4750
|
+
textMeasure.minContentWidth + horizontalEdges(yogaNode, style),
|
|
4751
|
+
[style.width, style.maxWidth],
|
|
4752
|
+
parent ? parent.getComputedWidth() - horizontalEdges(parent) : NaN
|
|
4753
|
+
);
|
|
4754
|
+
}
|
|
4755
|
+
function autoMinimumHeight(node) {
|
|
4756
|
+
const { yogaNode } = node;
|
|
4757
|
+
const { textMeasure, style, text } = node.autoMinimum;
|
|
4758
|
+
const height = textMeasure.heightAt(text.getComputedWidth());
|
|
4759
|
+
if (!style) return height;
|
|
4760
|
+
const parent = yogaNode.getParent();
|
|
4761
|
+
return capped(
|
|
4762
|
+
height + verticalEdges(yogaNode),
|
|
4763
|
+
[style.height, style.maxHeight],
|
|
4764
|
+
parent ? parent.getComputedHeight() - verticalEdges(parent) : NaN
|
|
4765
|
+
);
|
|
4766
|
+
}
|
|
4767
|
+
function capped(minimum, sizes, parentContentSize) {
|
|
4768
|
+
for (const value of sizes) {
|
|
4769
|
+
if (isAuto(value)) continue;
|
|
4770
|
+
let cap;
|
|
4771
|
+
if (typeof value === "number") {
|
|
4772
|
+
cap = toLayoutUnit(value);
|
|
4773
|
+
} else if (value.endsWith("%") && Number.isFinite(parentContentSize)) {
|
|
4774
|
+
cap = parseFloat(value) / 100 * parentContentSize;
|
|
4775
|
+
} else {
|
|
4776
|
+
return void 0;
|
|
4777
|
+
}
|
|
4778
|
+
if (!Number.isFinite(cap)) return void 0;
|
|
4779
|
+
minimum = Math.min(minimum, cap);
|
|
4780
|
+
}
|
|
4781
|
+
return minimum;
|
|
4782
|
+
}
|
|
4783
|
+
var verticalEdges = (yogaNode) => yogaNode.getComputedPadding(Edge.Top) + yogaNode.getComputedPadding(Edge.Bottom) + yogaNode.getComputedBorder(Edge.Top) + yogaNode.getComputedBorder(Edge.Bottom);
|
|
4784
|
+
function holdAtMinimum(yogaNode, minimum, { atBasis, pin, column }) {
|
|
4785
|
+
const flexShrink = yogaNode.getFlexShrink();
|
|
4786
|
+
if (atBasis) {
|
|
4787
|
+
yogaNode.setFlexShrink(0);
|
|
4788
|
+
return () => yogaNode.setFlexShrink(flexShrink);
|
|
4789
|
+
}
|
|
4790
|
+
const wraps = yogaNode.getParent()?.getFlexWrap() !== Wrap.NoWrap;
|
|
4791
|
+
if (!pin || wraps) {
|
|
4792
|
+
const setMin = (value) => column ? yogaNode.setMinHeight(value) : yogaNode.setMinWidth(value);
|
|
4793
|
+
setMin(minimum);
|
|
4794
|
+
return () => setMin(void 0);
|
|
4795
|
+
}
|
|
4796
|
+
const flexGrow = yogaNode.getFlexGrow();
|
|
4797
|
+
const flexBasis = yogaNode.getFlexBasis();
|
|
4798
|
+
yogaNode.setFlexBasis(minimum);
|
|
4799
|
+
yogaNode.setFlexShrink(0);
|
|
4800
|
+
yogaNode.setFlexGrow(0);
|
|
4801
|
+
return () => {
|
|
4802
|
+
yogaNode.setFlexShrink(flexShrink);
|
|
4803
|
+
yogaNode.setFlexGrow(flexGrow);
|
|
4804
|
+
if (flexBasis.unit === Unit.Point) yogaNode.setFlexBasis(flexBasis.value);
|
|
4805
|
+
else if (flexBasis.unit === Unit.Percent)
|
|
4806
|
+
yogaNode.setFlexBasis(`${flexBasis.value}%`);
|
|
4807
|
+
else yogaNode.setFlexBasis("auto");
|
|
4808
|
+
};
|
|
4809
|
+
}
|
|
4810
|
+
function liftAutoMinimum(node) {
|
|
4811
|
+
const autoMinimum = node.autoMinimum;
|
|
4812
|
+
const { lift } = autoMinimum;
|
|
4813
|
+
lift?.();
|
|
4814
|
+
autoMinimum.lift = void 0;
|
|
4815
|
+
autoMinimum.lifted = true;
|
|
4816
|
+
return lift !== void 0;
|
|
4817
|
+
}
|
|
4818
|
+
var RUNAWAY_SIZE = 100;
|
|
4819
|
+
var MAX_RUNAWAY_RELAYOUTS = 2;
|
|
4820
|
+
var isRunaway = (size, limit) => !(Math.abs(size) <= limit);
|
|
4821
|
+
function findRunawayItems(root, limit) {
|
|
4822
|
+
const items = [];
|
|
4823
|
+
const visit = (node, parent) => {
|
|
4824
|
+
if (node.style.display === "none") return;
|
|
4825
|
+
const { yogaNode } = node;
|
|
4826
|
+
if (parent) {
|
|
4827
|
+
const row = [FlexDirection.Row, FlexDirection.RowReverse].includes(
|
|
4828
|
+
parent.yogaNode.getFlexDirection()
|
|
4829
|
+
);
|
|
4830
|
+
const size = (n) => row ? n.getComputedWidth() : n.getComputedHeight();
|
|
4831
|
+
if (yogaNode.getFlexShrink() > 0 && isRunaway(size(yogaNode), limit) && !isRunaway(size(parent.yogaNode), limit)) {
|
|
4832
|
+
items.push({ node, parent, row });
|
|
4833
|
+
return;
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
for (const child of node.children) visit(child, node);
|
|
4837
|
+
};
|
|
4838
|
+
visit(root);
|
|
4839
|
+
return items;
|
|
4840
|
+
}
|
|
4841
|
+
function pinAtMinimum({ node, parent, row }) {
|
|
4842
|
+
const { yogaNode } = node;
|
|
4843
|
+
const autoMinimum = node.autoMinimum;
|
|
4844
|
+
const auto = autoMinimum && autoMinimum.column !== row ? row ? autoMinimumWidth(node) : autoMinimumHeight(node) : 0;
|
|
4845
|
+
yogaNode.setFlexBasis(
|
|
4846
|
+
Math.max(minimumSize(yogaNode, parent.yogaNode, row), auto ?? 0)
|
|
4847
|
+
);
|
|
4848
|
+
yogaNode.setFlexShrink(0);
|
|
4849
|
+
}
|
|
4850
|
+
function minimumSize(yogaNode, parent, row) {
|
|
4851
|
+
const [start, end] = row ? [Edge.Left, Edge.Right] : [Edge.Top, Edge.Bottom];
|
|
4852
|
+
const edges = (node) => node.getComputedPadding(start) + node.getComputedPadding(end) + node.getComputedBorder(start) + node.getComputedBorder(end);
|
|
4853
|
+
const min = row ? yogaNode.getMinWidth() : yogaNode.getMinHeight();
|
|
4854
|
+
let minimum = 0;
|
|
4855
|
+
if (min.unit === Unit.Point) {
|
|
4856
|
+
minimum = min.value;
|
|
4857
|
+
} else if (min.unit === Unit.Percent) {
|
|
4858
|
+
const parentSize = row ? parent.getComputedWidth() : parent.getComputedHeight();
|
|
4859
|
+
minimum = min.value / 100 * (parentSize - edges(parent));
|
|
4860
|
+
}
|
|
4861
|
+
return Math.max(minimum, edges(yogaNode));
|
|
4862
|
+
}
|
|
4278
4863
|
function setTextMeasure(yogaNode, textMeasure) {
|
|
4279
4864
|
yogaNode.setMeasureFunc(
|
|
4280
|
-
(width, widthMode) => textMeasure.measure(
|
|
4281
|
-
widthMode === MeasureMode.Undefined || Number.isNaN(width) ? Infinity : width
|
|
4282
|
-
)
|
|
4865
|
+
(width, widthMode) => widthMode === MeasureMode.Undefined || Number.isNaN(width) ? textMeasure.measure(Infinity) : textMeasure.measure(width, widthMode === MeasureMode.Exactly)
|
|
4283
4866
|
);
|
|
4284
4867
|
}
|
|
4285
4868
|
var isText = (node) => typeof node === "string" || typeof node === "number";
|
|
@@ -4292,6 +4875,15 @@ function renderComponents(node) {
|
|
|
4292
4875
|
}
|
|
4293
4876
|
return node;
|
|
4294
4877
|
}
|
|
4878
|
+
var TextRun = class {
|
|
4879
|
+
pieces;
|
|
4880
|
+
constructor(...pieces) {
|
|
4881
|
+
this.pieces = pieces;
|
|
4882
|
+
}
|
|
4883
|
+
};
|
|
4884
|
+
var isTextRun = (node) => node instanceof TextRun;
|
|
4885
|
+
var textContentOf = (run) => run.pieces.length === 1 ? run.pieces[0] : run.pieces;
|
|
4886
|
+
var isBlankRun = (run) => run.pieces.length === 1 && isWhiteSpaceOnly(run.pieces[0]);
|
|
4295
4887
|
function flattenChildren(node, items = []) {
|
|
4296
4888
|
const rendered = renderComponents(node);
|
|
4297
4889
|
if (rendered === null || rendered === void 0 || typeof rendered === "boolean") {
|
|
@@ -4306,13 +4898,22 @@ function flattenChildren(node, items = []) {
|
|
|
4306
4898
|
return flattenChildren(el.props?.children, items);
|
|
4307
4899
|
}
|
|
4308
4900
|
const last = items[items.length - 1];
|
|
4309
|
-
if (isText(rendered)
|
|
4310
|
-
|
|
4311
|
-
|
|
4901
|
+
if (isText(rendered)) {
|
|
4902
|
+
if (isTextRun(last)) {
|
|
4903
|
+
last.pieces[last.pieces.length - 1] += String(rendered);
|
|
4904
|
+
} else {
|
|
4905
|
+
items.push(new TextRun(String(rendered)));
|
|
4906
|
+
}
|
|
4907
|
+
} else if (!isBreak(rendered)) {
|
|
4312
4908
|
items.push(rendered);
|
|
4909
|
+
} else if (!isHidden(rendered)) {
|
|
4910
|
+
if (isTextRun(last)) last.pieces.push("");
|
|
4911
|
+
else items.push(new TextRun("", ""));
|
|
4313
4912
|
}
|
|
4314
4913
|
return items;
|
|
4315
4914
|
}
|
|
4915
|
+
var isBreak = (node) => typeof node === "object" && node !== null && node.type === "br";
|
|
4916
|
+
var isHidden = (el) => el.props.style?.display === "none";
|
|
4316
4917
|
function floorToPixel(value) {
|
|
4317
4918
|
const rounded = Math.round(value);
|
|
4318
4919
|
return Math.abs(value - rounded) < 1e-4 ? rounded : Math.floor(value);
|