@codehz/draw-call 0.4.5 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/browser/index.cjs CHANGED
@@ -82,6 +82,19 @@ function normalizeBorderRadius(value) {
82
82
 
83
83
  //#endregion
84
84
  //#region src/layout/components/box.ts
85
+ /**
86
+ * 安全获取元素的 margin
87
+ * Transform 元素没有 margin,返回默认 spacing
88
+ */
89
+ function getElementMargin$2(element) {
90
+ if (element.type === "transform") return {
91
+ top: 0,
92
+ right: 0,
93
+ bottom: 0,
94
+ left: 0
95
+ };
96
+ return normalizeSpacing(element.margin);
97
+ }
85
98
  function calcEffectiveSize(element, padding, availableWidth) {
86
99
  return {
87
100
  width: typeof element.width === "number" ? element.width - padding.left - padding.right : availableWidth > 0 ? availableWidth : 0,
@@ -91,7 +104,7 @@ function calcEffectiveSize(element, padding, availableWidth) {
91
104
  function collectChildSizes(children, ctx, availableWidth, padding, measureChild) {
92
105
  const childSizes = [];
93
106
  for (const child of children) {
94
- const childMargin = normalizeSpacing(child.margin);
107
+ const childMargin = getElementMargin$2(child);
95
108
  const childSize = measureChild(child, ctx, availableWidth - padding.left - padding.right - childMargin.left - childMargin.right);
96
109
  childSizes.push({
97
110
  width: childSize.width,
@@ -160,7 +173,7 @@ function measureBoxSize(element, ctx, availableWidth, measureChild) {
160
173
  contentHeight = wrapped.height;
161
174
  } else for (let i = 0; i < children.length; i++) {
162
175
  const child = children[i];
163
- const childMargin = normalizeSpacing(child.margin);
176
+ const childMargin = getElementMargin$2(child);
164
177
  const childSize = measureChild(child, ctx, availableWidth - padding.left - padding.right - childMargin.left - childMargin.right);
165
178
  if (isRow) {
166
179
  contentWidth += childSize.width + childMargin.left + childMargin.right;
@@ -394,6 +407,19 @@ function wrapRichText(ctx, spans, maxWidth, lineHeightScale = 1.2, elementStyle
394
407
  //#endregion
395
408
  //#region src/layout/components/stack.ts
396
409
  /**
410
+ * 安全获取元素的 margin
411
+ * Transform 元素没有 margin,返回默认 spacing
412
+ */
413
+ function getElementMargin$1(element) {
414
+ if (element.type === "transform") return {
415
+ top: 0,
416
+ right: 0,
417
+ bottom: 0,
418
+ left: 0
419
+ };
420
+ return normalizeSpacing(element.margin);
421
+ }
422
+ /**
397
423
  * 测量 Stack 元素的固有尺寸
398
424
  */
399
425
  function measureStackSize(element, ctx, availableWidth, measureChild) {
@@ -402,7 +428,7 @@ function measureStackSize(element, ctx, availableWidth, measureChild) {
402
428
  let contentHeight = 0;
403
429
  const children = element.children ?? [];
404
430
  for (const child of children) {
405
- const childMargin = normalizeSpacing(child.margin);
431
+ const childMargin = getElementMargin$1(child);
406
432
  const childSize = measureChild(child, ctx, availableWidth - padding.left - padding.right - childMargin.left - childMargin.right);
407
433
  contentWidth = Math.max(contentWidth, childSize.width + childMargin.left + childMargin.right);
408
434
  contentHeight = Math.max(contentHeight, childSize.height + childMargin.top + childMargin.bottom);
@@ -641,18 +667,79 @@ function sizeNeedsParent(size) {
641
667
 
642
668
  //#endregion
643
669
  //#region src/layout/engine.ts
670
+ /**
671
+ * 类型守卫:检查 Element 是否为 LayoutElement(非 Transform)
672
+ * 由于 Transform 元素在 computeLayoutImpl 开始时被处理,
673
+ * 此时只应处理 LayoutElement
674
+ */
675
+ function assertLayoutElement(element) {
676
+ if (element.type === "transform") throw new Error("Transform elements should be handled at entry point");
677
+ }
678
+ /**
679
+ * 安全获取元素的 margin
680
+ * Transform 元素没有 margin,返回默认 spacing
681
+ */
682
+ function getElementMargin(element) {
683
+ if (element.type === "transform") return {
684
+ top: 0,
685
+ right: 0,
686
+ bottom: 0,
687
+ left: 0
688
+ };
689
+ return normalizeSpacing(element.margin);
690
+ }
691
+ /**
692
+ * 安全获取元素的布局属性(width, height, flex等)
693
+ * Transform 元素这些属性为 undefined
694
+ */
695
+ function getElementLayoutProps(element) {
696
+ if (element.type === "transform") return {
697
+ width: void 0,
698
+ height: void 0,
699
+ flex: void 0,
700
+ minWidth: void 0,
701
+ maxWidth: void 0,
702
+ minHeight: void 0,
703
+ maxHeight: void 0,
704
+ alignSelf: void 0
705
+ };
706
+ const le = element;
707
+ return {
708
+ width: le.width,
709
+ height: le.height,
710
+ flex: le.flex,
711
+ minWidth: le.minWidth,
712
+ maxWidth: le.maxWidth,
713
+ minHeight: le.minHeight,
714
+ maxHeight: le.maxHeight,
715
+ alignSelf: le.alignSelf
716
+ };
717
+ }
718
+ /**
719
+ * 布局计算主函数
720
+ * 内部使用 Element 类型以支持 Transform,外部通过 LayoutElement 约束类型
721
+ */
644
722
  function computeLayout(element, ctx, constraints, x = 0, y = 0) {
645
- const margin = normalizeSpacing(element.margin);
646
- const padding = normalizeSpacing("padding" in element ? element.padding : void 0);
723
+ return computeLayoutImpl(element, ctx, constraints, x, y);
724
+ }
725
+ /**
726
+ * 内部实现函数,处理所有元素类型包括 Transform
727
+ */
728
+ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
729
+ if (element.type === "transform") return computeLayoutImpl(element.children, ctx, constraints, x, y);
730
+ assertLayoutElement(element);
731
+ const layoutElement = element;
732
+ const margin = normalizeSpacing(layoutElement.margin);
733
+ const padding = normalizeSpacing("padding" in layoutElement ? layoutElement.padding : void 0);
647
734
  const availableWidth = constraints.maxWidth - margin.left - margin.right;
648
735
  const availableHeight = constraints.maxHeight - margin.top - margin.bottom;
649
- const intrinsic = measureIntrinsicSize(element, ctx, availableWidth);
650
- let width = constraints.minWidth === constraints.maxWidth && constraints.minWidth > 0 ? constraints.maxWidth - margin.left - margin.right : resolveSize(element.width, availableWidth, intrinsic.width);
651
- let height = constraints.minHeight === constraints.maxHeight && constraints.minHeight > 0 ? constraints.maxHeight - margin.top - margin.bottom : resolveSize(element.height, availableHeight, intrinsic.height);
652
- if (element.minWidth !== void 0) width = Math.max(width, element.minWidth);
653
- if (element.maxWidth !== void 0) width = Math.min(width, element.maxWidth);
654
- if (element.minHeight !== void 0) height = Math.max(height, element.minHeight);
655
- if (element.maxHeight !== void 0) height = Math.min(height, element.maxHeight);
736
+ const intrinsic = measureIntrinsicSize(layoutElement, ctx, availableWidth);
737
+ let width = constraints.minWidth === constraints.maxWidth && constraints.minWidth > 0 ? constraints.maxWidth - margin.left - margin.right : resolveSize(layoutElement.width, availableWidth, intrinsic.width);
738
+ let height = constraints.minHeight === constraints.maxHeight && constraints.minHeight > 0 ? constraints.maxHeight - margin.top - margin.bottom : resolveSize(layoutElement.height, availableHeight, intrinsic.height);
739
+ if (layoutElement.minWidth !== void 0) width = Math.max(width, layoutElement.minWidth);
740
+ if (layoutElement.maxWidth !== void 0) width = Math.min(width, layoutElement.maxWidth);
741
+ if (layoutElement.minHeight !== void 0) height = Math.max(height, layoutElement.minHeight);
742
+ if (layoutElement.maxHeight !== void 0) height = Math.min(height, layoutElement.maxHeight);
656
743
  const actualX = x + margin.left;
657
744
  const actualY = y + margin.top;
658
745
  const contentX = actualX + padding.left;
@@ -660,7 +747,7 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
660
747
  const contentWidth = width - padding.left - padding.right;
661
748
  const contentHeight = height - padding.top - padding.bottom;
662
749
  const node = {
663
- element,
750
+ element: layoutElement,
664
751
  layout: {
665
752
  x: actualX,
666
753
  y: actualY,
@@ -673,14 +760,14 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
673
760
  },
674
761
  children: []
675
762
  };
676
- if (element.type === "text") {
677
- const font = element.font ?? {};
678
- if (element.wrap && contentWidth > 0) {
679
- let { lines, offsets } = wrapText(ctx, element.content, contentWidth, font);
680
- if (element.maxLines && lines.length > element.maxLines) {
681
- lines = lines.slice(0, element.maxLines);
682
- offsets = offsets.slice(0, element.maxLines);
683
- if (element.ellipsis && lines.length > 0) {
763
+ if (layoutElement.type === "text") {
764
+ const font = layoutElement.font ?? {};
765
+ if (layoutElement.wrap && contentWidth > 0) {
766
+ let { lines, offsets } = wrapText(ctx, layoutElement.content, contentWidth, font);
767
+ if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
768
+ lines = lines.slice(0, layoutElement.maxLines);
769
+ offsets = offsets.slice(0, layoutElement.maxLines);
770
+ if (layoutElement.ellipsis && lines.length > 0) {
684
771
  const lastIdx = lines.length - 1;
685
772
  const truncated = truncateText(ctx, lines[lastIdx], contentWidth, font);
686
773
  lines[lastIdx] = truncated.text;
@@ -690,17 +777,17 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
690
777
  node.lines = lines;
691
778
  node.lineOffsets = offsets;
692
779
  } else {
693
- const { text, offset } = truncateText(ctx, element.content, contentWidth > 0 && element.ellipsis ? contentWidth : Infinity, font);
780
+ const { text, offset } = truncateText(ctx, layoutElement.content, contentWidth > 0 && layoutElement.ellipsis ? contentWidth : Infinity, font);
694
781
  node.lines = [text];
695
782
  node.lineOffsets = [offset];
696
783
  }
697
784
  }
698
- if (element.type === "richtext") {
699
- const lineHeight = element.lineHeight ?? 1.2;
700
- let lines = wrapRichText(ctx, element.spans, contentWidth, lineHeight);
701
- if (element.maxLines && lines.length > element.maxLines) {
702
- lines = lines.slice(0, element.maxLines);
703
- if (element.ellipsis && lines.length > 0) {
785
+ if (layoutElement.type === "richtext") {
786
+ const lineHeight = layoutElement.lineHeight ?? 1.2;
787
+ let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight);
788
+ if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
789
+ lines = lines.slice(0, layoutElement.maxLines);
790
+ if (layoutElement.ellipsis && lines.length > 0) {
704
791
  const lastLine = lines[lines.length - 1];
705
792
  if (lastLine.segments.length > 0) {
706
793
  const lastSeg = lastLine.segments[lastLine.segments.length - 1];
@@ -712,19 +799,19 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
712
799
  }
713
800
  node.richLines = lines;
714
801
  }
715
- if (element.type === "box" || element.type === "stack") {
716
- const children = element.children ?? [];
717
- if (element.type === "stack") {
718
- const stackAlign = element.align ?? "start";
719
- const stackJustify = element.justify ?? "start";
802
+ if (layoutElement.type === "box" || layoutElement.type === "stack") {
803
+ const children = layoutElement.children ?? [];
804
+ if (layoutElement.type === "stack") {
805
+ const stackAlign = layoutElement.align ?? "start";
806
+ const stackJustify = layoutElement.justify ?? "start";
720
807
  for (const child of children) {
721
- const childNode = computeLayout(child, ctx, {
808
+ const childNode = computeLayoutImpl(child, ctx, {
722
809
  minWidth: 0,
723
810
  maxWidth: contentWidth,
724
811
  minHeight: 0,
725
812
  maxHeight: contentHeight
726
813
  }, contentX, contentY);
727
- const childMargin = normalizeSpacing(child.margin);
814
+ const childMargin = getElementMargin(child);
728
815
  const childOuterWidth = childNode.layout.width + childMargin.left + childMargin.right;
729
816
  const childOuterHeight = childNode.layout.height + childMargin.top + childMargin.bottom;
730
817
  let offsetX = 0;
@@ -737,19 +824,21 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
737
824
  node.children.push(childNode);
738
825
  }
739
826
  } else {
740
- const direction = element.direction ?? "row";
741
- const justify = element.justify ?? "start";
742
- const align = element.align ?? "stretch";
743
- const gap = element.gap ?? 0;
744
- const wrap = element.wrap ?? false;
827
+ const boxElement = layoutElement;
828
+ const direction = boxElement.direction ?? "row";
829
+ const justify = boxElement.justify ?? "start";
830
+ const align = boxElement.align ?? "stretch";
831
+ const gap = boxElement.gap ?? 0;
832
+ const wrap = boxElement.wrap ?? false;
745
833
  const isRow = direction === "row" || direction === "row-reverse";
746
834
  const isReverse = direction === "row-reverse" || direction === "column-reverse";
747
835
  const getContentMainSize = () => isRow ? contentWidth : contentHeight;
748
836
  const getContentCrossSize = () => isRow ? contentHeight : contentWidth;
749
837
  const childInfos = [];
750
838
  for (const child of children) {
751
- const childMargin = normalizeSpacing(child.margin);
752
- const childFlex = child.flex ?? 0;
839
+ const childMargin = getElementMargin(child);
840
+ const childProps = getElementLayoutProps(child);
841
+ const childFlex = childProps.flex ?? 0;
753
842
  if (childFlex > 0) childInfos.push({
754
843
  element: child,
755
844
  width: 0,
@@ -759,10 +848,10 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
759
848
  });
760
849
  else {
761
850
  const size = measureIntrinsicSize(child, ctx, contentWidth - childMargin.left - childMargin.right);
762
- const shouldStretchWidth = !isRow && child.width === void 0 && align === "stretch";
763
- const shouldStretchHeight = isRow && child.height === void 0 && align === "stretch";
764
- let w = sizeNeedsParent(child.width) ? resolveSize(child.width, contentWidth - childMargin.left - childMargin.right, size.width) : resolveSize(child.width, 0, size.width);
765
- let h = sizeNeedsParent(child.height) ? resolveSize(child.height, contentHeight - childMargin.top - childMargin.bottom, size.height) : resolveSize(child.height, 0, size.height);
851
+ const shouldStretchWidth = !isRow && childProps.width === void 0 && align === "stretch";
852
+ const shouldStretchHeight = isRow && childProps.height === void 0 && align === "stretch";
853
+ let w = sizeNeedsParent(childProps.width) ? resolveSize(childProps.width, contentWidth - childMargin.left - childMargin.right, size.width) : resolveSize(childProps.width, 0, size.width);
854
+ let h = sizeNeedsParent(childProps.height) ? resolveSize(childProps.height, contentHeight - childMargin.top - childMargin.bottom, size.height) : resolveSize(childProps.height, 0, size.height);
766
855
  if (shouldStretchWidth && !wrap) w = contentWidth - childMargin.left - childMargin.right;
767
856
  if (shouldStretchHeight && !wrap) h = contentHeight - childMargin.top - childMargin.bottom;
768
857
  childInfos.push({
@@ -803,14 +892,15 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
803
892
  const availableForFlex = Math.max(0, mainAxisSize - totalFixed - totalGap);
804
893
  for (const info of lineInfos) if (info.flex > 0) {
805
894
  const flexSize = totalFlex > 0 ? availableForFlex * info.flex / totalFlex : 0;
895
+ const childProps = getElementLayoutProps(info.element);
806
896
  if (isRow) {
807
897
  info.width = flexSize;
808
898
  const size = measureIntrinsicSize(info.element, ctx, flexSize);
809
- info.height = sizeNeedsParent(info.element.height) ? resolveSize(info.element.height, contentHeight - info.margin.top - info.margin.bottom, size.height) : resolveSize(info.element.height, 0, size.height);
899
+ info.height = sizeNeedsParent(childProps.height) ? resolveSize(childProps.height, contentHeight - info.margin.top - info.margin.bottom, size.height) : resolveSize(childProps.height, 0, size.height);
810
900
  } else {
811
901
  info.height = flexSize;
812
902
  const size = measureIntrinsicSize(info.element, ctx, contentWidth - info.margin.left - info.margin.right);
813
- info.width = sizeNeedsParent(info.element.width) ? resolveSize(info.element.width, contentWidth - info.margin.left - info.margin.right, size.width) : resolveSize(info.element.width, 0, size.width);
903
+ info.width = sizeNeedsParent(childProps.width) ? resolveSize(childProps.width, contentWidth - info.margin.left - info.margin.right, size.width) : resolveSize(childProps.width, 0, size.width);
814
904
  }
815
905
  }
816
906
  }
@@ -861,17 +951,18 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
861
951
  const orderedInfos = isReverse ? [...lineInfos].reverse() : lineInfos;
862
952
  for (let i = 0; i < orderedInfos.length; i++) {
863
953
  const info = orderedInfos[i];
954
+ const childProps = getElementLayoutProps(info.element);
864
955
  const crossAxisSize = wrap ? lineCrossSize : getContentCrossSize();
865
956
  const childCrossSize = isRow ? info.height + info.margin.top + info.margin.bottom : info.width + info.margin.left + info.margin.right;
866
957
  let itemCrossOffset = 0;
867
- const effectiveAlign = info.element.alignSelf ?? align;
958
+ const effectiveAlign = childProps.alignSelf ?? align;
868
959
  if (effectiveAlign === "start") itemCrossOffset = 0;
869
960
  else if (effectiveAlign === "end") itemCrossOffset = crossAxisSize - childCrossSize;
870
961
  else if (effectiveAlign === "center") itemCrossOffset = (crossAxisSize - childCrossSize) / 2;
871
962
  else if (effectiveAlign === "stretch") {
872
963
  itemCrossOffset = 0;
873
- if (isRow && info.element.height === void 0) info.height = crossAxisSize - info.margin.top - info.margin.bottom;
874
- else if (!isRow && info.element.width === void 0) info.width = crossAxisSize - info.margin.left - info.margin.right;
964
+ if (isRow && childProps.height === void 0) info.height = crossAxisSize - info.margin.top - info.margin.bottom;
965
+ else if (!isRow && childProps.width === void 0) info.width = crossAxisSize - info.margin.left - info.margin.right;
875
966
  }
876
967
  const childX = isRow ? contentX + mainOffset + info.margin.left : contentX + crossOffset + itemCrossOffset + info.margin.left;
877
968
  const childY = isRow ? contentY + crossOffset + itemCrossOffset + info.margin.top : contentY + mainOffset + info.margin.top;
@@ -880,26 +971,29 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
880
971
  let minHeight = 0;
881
972
  let maxHeight = info.height;
882
973
  let shouldStretchCross = false;
883
- if (info.flex > 0) if (isRow) {
884
- minWidth = maxWidth = info.width;
885
- if (info.element.height === void 0 && align === "stretch") {
886
- minHeight = info.height;
887
- maxHeight = element.height !== void 0 ? info.height : Infinity;
888
- shouldStretchCross = true;
974
+ if (info.flex > 0) {
975
+ const childProps = getElementLayoutProps(info.element);
976
+ if (isRow) {
977
+ minWidth = maxWidth = info.width;
978
+ if (childProps.height === void 0 && align === "stretch") {
979
+ minHeight = info.height;
980
+ maxHeight = boxElement.height !== void 0 ? info.height : Infinity;
981
+ shouldStretchCross = true;
982
+ }
983
+ } else {
984
+ minHeight = maxHeight = info.height;
985
+ if (childProps.width === void 0 && align === "stretch") {
986
+ minWidth = info.width;
987
+ maxWidth = boxElement.width !== void 0 ? info.width : Infinity;
988
+ shouldStretchCross = true;
989
+ }
889
990
  }
890
991
  } else {
891
- minHeight = maxHeight = info.height;
892
- if (info.element.width === void 0 && align === "stretch") {
893
- minWidth = info.width;
894
- maxWidth = element.width !== void 0 ? info.width : Infinity;
895
- shouldStretchCross = true;
896
- }
897
- }
898
- else {
899
- if (!isRow && info.element.width === void 0 && align === "stretch") minWidth = maxWidth = crossAxisSize - info.margin.left - info.margin.right;
900
- if (isRow && info.element.height === void 0 && align === "stretch") minHeight = maxHeight = crossAxisSize - info.margin.top - info.margin.bottom;
992
+ const childProps = getElementLayoutProps(info.element);
993
+ if (!isRow && childProps.width === void 0 && align === "stretch") minWidth = maxWidth = crossAxisSize - info.margin.left - info.margin.right;
994
+ if (isRow && childProps.height === void 0 && align === "stretch") minHeight = maxHeight = crossAxisSize - info.margin.top - info.margin.bottom;
901
995
  }
902
- const childNode = computeLayout(info.element, ctx, {
996
+ const childNode = computeLayoutImpl(info.element, ctx, {
903
997
  minWidth,
904
998
  maxWidth,
905
999
  minHeight,
@@ -922,12 +1016,12 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
922
1016
  crossOffset += lineCrossSize;
923
1017
  if (lineIndex < lines.length - 1) crossOffset += gap;
924
1018
  }
925
- if (wrap && element.height === void 0 && isRow) {
1019
+ if (wrap && boxElement.height === void 0 && isRow) {
926
1020
  const actualContentHeight = crossOffset;
927
1021
  const actualHeight = actualContentHeight + padding.top + padding.bottom;
928
1022
  node.layout.height = actualHeight;
929
1023
  node.layout.contentHeight = actualContentHeight;
930
- } else if (wrap && element.width === void 0 && !isRow) {
1024
+ } else if (wrap && boxElement.width === void 0 && !isRow) {
931
1025
  const actualContentWidth = crossOffset;
932
1026
  const actualWidth = actualContentWidth + padding.left + padding.right;
933
1027
  node.layout.width = actualWidth;
@@ -936,7 +1030,7 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
936
1030
  if (!wrap) {
937
1031
  let maxChildCrossSize = 0;
938
1032
  for (const childNode of node.children) {
939
- const childMargin = normalizeSpacing(childNode.element.margin);
1033
+ const childMargin = getElementMargin(childNode.element);
940
1034
  if (isRow) {
941
1035
  const childOuterHeight = childNode.layout.height + childMargin.top + childMargin.bottom;
942
1036
  maxChildCrossSize = Math.max(maxChildCrossSize, childOuterHeight);
@@ -945,13 +1039,13 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
945
1039
  maxChildCrossSize = Math.max(maxChildCrossSize, childOuterWidth);
946
1040
  }
947
1041
  }
948
- if (isRow && element.height === void 0) {
1042
+ if (isRow && boxElement.height === void 0) {
949
1043
  const actualHeight = maxChildCrossSize + padding.top + padding.bottom;
950
1044
  if (actualHeight > node.layout.height) {
951
1045
  node.layout.height = actualHeight;
952
1046
  node.layout.contentHeight = maxChildCrossSize;
953
1047
  }
954
- } else if (!isRow && element.width === void 0) {
1048
+ } else if (!isRow && boxElement.width === void 0) {
955
1049
  const actualWidth = maxChildCrossSize + padding.left + padding.right;
956
1050
  if (actualWidth > node.layout.width) {
957
1051
  node.layout.width = actualWidth;
@@ -961,48 +1055,24 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
961
1055
  }
962
1056
  if (isReverse) node.children.reverse();
963
1057
  }
964
- } else if (element.type === "transform") {
965
- const child = element.children;
1058
+ } else if (layoutElement.type === "customdraw") {
1059
+ const child = layoutElement.children;
966
1060
  if (child) {
967
1061
  const childMargin = normalizeSpacing(child.margin);
968
- const childNode = computeLayout(child, ctx, {
1062
+ const childNode = computeLayoutImpl(child, ctx, {
969
1063
  minWidth: 0,
970
1064
  maxWidth: contentWidth,
971
1065
  minHeight: 0,
972
1066
  maxHeight: contentHeight
973
1067
  }, contentX, contentY);
974
1068
  node.children.push(childNode);
975
- if (element.width === void 0) {
1069
+ if (layoutElement.width === void 0) {
976
1070
  const childOuterWidth = childNode.layout.width + childMargin.left + childMargin.right;
977
1071
  const actualWidth = childOuterWidth + padding.left + padding.right;
978
1072
  node.layout.width = actualWidth;
979
1073
  node.layout.contentWidth = childOuterWidth;
980
1074
  }
981
- if (element.height === void 0) {
982
- const childOuterHeight = childNode.layout.height + childMargin.top + childMargin.bottom;
983
- const actualHeight = childOuterHeight + padding.top + padding.bottom;
984
- node.layout.height = actualHeight;
985
- node.layout.contentHeight = childOuterHeight;
986
- }
987
- }
988
- } else if (element.type === "customdraw") {
989
- const child = element.children;
990
- if (child) {
991
- const childMargin = normalizeSpacing(child.margin);
992
- const childNode = computeLayout(child, ctx, {
993
- minWidth: 0,
994
- maxWidth: contentWidth,
995
- minHeight: 0,
996
- maxHeight: contentHeight
997
- }, contentX, contentY);
998
- node.children.push(childNode);
999
- if (element.width === void 0) {
1000
- const childOuterWidth = childNode.layout.width + childMargin.left + childMargin.right;
1001
- const actualWidth = childOuterWidth + padding.left + padding.right;
1002
- node.layout.width = actualWidth;
1003
- node.layout.contentWidth = childOuterWidth;
1004
- }
1005
- if (element.height === void 0) {
1075
+ if (layoutElement.height === void 0) {
1006
1076
  const childOuterHeight = childNode.layout.height + childMargin.top + childMargin.bottom;
1007
1077
  const actualHeight = childOuterHeight + padding.top + padding.bottom;
1008
1078
  node.layout.height = actualHeight;
@@ -1833,8 +1903,17 @@ function renderNode(ctx, node) {
1833
1903
  function createCanvas(options) {
1834
1904
  const { width, height, pixelRatio = 1 } = options;
1835
1905
  const canvas = options.canvas ?? createRawCanvas(width * pixelRatio, height * pixelRatio);
1906
+ if (options.canvas) {
1907
+ canvas.width = width * pixelRatio;
1908
+ canvas.height = height * pixelRatio;
1909
+ if ("style" in canvas && options.updateStyles !== false) {
1910
+ canvas.style.width = `${width}px`;
1911
+ canvas.style.height = `${height}px`;
1912
+ }
1913
+ }
1836
1914
  const ctx = canvas.getContext("2d");
1837
1915
  if (!ctx) throw new Error("Failed to get 2d context");
1916
+ ctx.resetTransform();
1838
1917
  if (options.imageSmoothingEnabled !== void 0) ctx.imageSmoothingEnabled = options.imageSmoothingEnabled;
1839
1918
  if (options.imageSmoothingQuality !== void 0) ctx.imageSmoothingQuality = options.imageSmoothingQuality;
1840
1919
  if (pixelRatio !== 1) ctx.scale(pixelRatio, pixelRatio);
@@ -1857,7 +1936,7 @@ function createCanvas(options) {
1857
1936
  if (canvas.width !== contentWidth * pixelRatio || canvas.height !== contentHeight * pixelRatio) {
1858
1937
  canvas.width = contentWidth * pixelRatio;
1859
1938
  canvas.height = contentHeight * pixelRatio;
1860
- if ("style" in canvas) {
1939
+ if ("style" in canvas && options.updateStyles !== false) {
1861
1940
  canvas.style.width = `${contentWidth}px`;
1862
1941
  canvas.style.height = `${contentHeight}px`;
1863
1942
  }
@@ -2003,10 +2082,12 @@ function getElementType(element) {
2003
2082
  switch (element.type) {
2004
2083
  case "box": return "Box";
2005
2084
  case "text": return `Text "${element.content.slice(0, 20)}${element.content.length > 20 ? "..." : ""}"`;
2085
+ case "richtext": return "RichText";
2006
2086
  case "stack": return "Stack";
2007
2087
  case "image": return "Image";
2008
2088
  case "svg": return "Svg";
2009
- default: return element.type;
2089
+ case "transform": return "Transform";
2090
+ case "customdraw": return "CustomDraw";
2010
2091
  }
2011
2092
  }
2012
2093
  /**
@@ -363,7 +363,8 @@ interface CustomDrawProps extends LayoutProps {
363
363
  interface CustomDrawElement extends ElementBase, CustomDrawProps {
364
364
  type: "customdraw";
365
365
  }
366
- type Element = BoxElement | TextElement | RichTextElement | ImageElement | SvgElement | StackElement | TransformElement | CustomDrawElement;
366
+ type LayoutElement = BoxElement | TextElement | RichTextElement | ImageElement | SvgElement | StackElement | CustomDrawElement;
367
+ type Element = LayoutElement | TransformElement;
367
368
  //#endregion
368
369
  //#region node_modules/@napi-rs/canvas/index.d.ts
369
370
  interface CanvasRenderingContext2D$1 extends CanvasCompositing, CanvasDrawPath, CanvasFillStrokeStyles, CanvasFilters, CanvasImageData, CanvasImageSmoothing, CanvasPath, CanvasPathDrawingStyles, CanvasRect, CanvasSettings, CanvasShadowStyles, CanvasState, CanvasText, CanvasTextDrawingStyles, CanvasTransform, CanvasPDFAnnotations {}
@@ -916,20 +917,17 @@ interface CanvasOptions<T extends HTMLCanvasElement | OffscreenCanvas | Canvas =
916
917
  pixelRatio?: number;
917
918
  /** 根据内容调整画布大小 */
918
919
  fitContent?: boolean;
920
+ updateStyles?: boolean;
919
921
  imageSmoothingEnabled?: boolean;
920
922
  imageSmoothingQuality?: "low" | "medium" | "high";
921
923
  canvas?: T;
922
924
  }
923
- interface LayoutSize {
924
- width: number;
925
- height: number;
926
- }
927
- interface DrawCallCanvas<T extends HTMLCanvasElement | OffscreenCanvas | Canvas = HTMLCanvasElement | OffscreenCanvas | Canvas> {
925
+ interface DrawCallCanvas<T extends HTMLCanvasElement | OffscreenCanvas | Canvas = HTMLCanvasElement> {
928
926
  readonly width: number;
929
927
  readonly height: number;
930
928
  readonly pixelRatio: number;
931
929
  readonly canvas: T;
932
- render(element: Element): LayoutNode;
930
+ render(element: LayoutElement): LayoutNode;
933
931
  clear(): void;
934
932
  getContext(): CanvasRenderingContext2D;
935
933
  toDataURL(type?: string, quality?: number): string;
@@ -978,7 +976,11 @@ declare function Text(props: TextProps): TextElement;
978
976
  declare function Transform(props: TransformProps): TransformElement;
979
977
  //#endregion
980
978
  //#region src/layout/engine.d.ts
981
- declare function computeLayout(element: Element, ctx: MeasureContext, constraints: LayoutConstraints, x?: number, y?: number): LayoutNode;
979
+ /**
980
+ * 布局计算主函数
981
+ * 内部使用 Element 类型以支持 Transform,外部通过 LayoutElement 约束类型
982
+ */
983
+ declare function computeLayout(element: LayoutElement, ctx: MeasureContext, constraints: LayoutConstraints, x?: number, y?: number): LayoutNode;
982
984
  //#endregion
983
985
  //#region src/layout/utils/print.d.ts
984
986
  /**
@@ -993,4 +995,4 @@ declare function printLayout(node: LayoutNode): void;
993
995
  */
994
996
  declare function layoutToString(node: LayoutNode, _indent?: string): string;
995
997
  //#endregion
996
- export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, CustomDraw, type CustomDrawElement, type CustomDrawProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutNode, type LayoutProps, type LayoutSize, type LinearGradientDescriptor, type MeasureContext, type ProxiedCanvasContextOptions, type RadialGradientDescriptor, RichText, type RichTextElement, type RichTextProps, type RichTextSpan, type Shadow, type Size, type Spacing, Stack, type StackAlign, type StackElement, type StackProps, type StrokeProps, Svg, type SvgAlign, type SvgChild, type SvgCircleChild, type SvgElement, type SvgEllipseChild, type SvgGroupChild, type SvgLineChild, type SvgPathChild, type SvgPolygonChild, type SvgPolylineChild, type SvgProps, type SvgRectChild, type SvgStyleProps, type SvgTextChild, type SvgTransformProps, Text, type TextElement, type TextProps, Transform, type TransformElement, type TransformProps, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };
998
+ export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, CustomDraw, type CustomDrawElement, type CustomDrawProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutNode, type LayoutProps, type LinearGradientDescriptor, type MeasureContext, type ProxiedCanvasContextOptions, type RadialGradientDescriptor, RichText, type RichTextElement, type RichTextProps, type RichTextSpan, type Shadow, type Size, type Spacing, Stack, type StackAlign, type StackElement, type StackProps, type StrokeProps, Svg, type SvgAlign, type SvgChild, type SvgCircleChild, type SvgElement, type SvgEllipseChild, type SvgGroupChild, type SvgLineChild, type SvgPathChild, type SvgPolygonChild, type SvgPolylineChild, type SvgProps, type SvgRectChild, type SvgStyleProps, type SvgTextChild, type SvgTransformProps, Text, type TextElement, type TextProps, Transform, type TransformElement, type TransformProps, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };
@@ -363,7 +363,8 @@ interface CustomDrawProps extends LayoutProps {
363
363
  interface CustomDrawElement extends ElementBase, CustomDrawProps {
364
364
  type: "customdraw";
365
365
  }
366
- type Element = BoxElement | TextElement | RichTextElement | ImageElement | SvgElement | StackElement | TransformElement | CustomDrawElement;
366
+ type LayoutElement = BoxElement | TextElement | RichTextElement | ImageElement | SvgElement | StackElement | CustomDrawElement;
367
+ type Element = LayoutElement | TransformElement;
367
368
  //#endregion
368
369
  //#region node_modules/@napi-rs/canvas/index.d.ts
369
370
  interface CanvasRenderingContext2D$1 extends CanvasCompositing, CanvasDrawPath, CanvasFillStrokeStyles, CanvasFilters, CanvasImageData, CanvasImageSmoothing, CanvasPath, CanvasPathDrawingStyles, CanvasRect, CanvasSettings, CanvasShadowStyles, CanvasState, CanvasText, CanvasTextDrawingStyles, CanvasTransform, CanvasPDFAnnotations {}
@@ -916,20 +917,17 @@ interface CanvasOptions<T extends HTMLCanvasElement | OffscreenCanvas | Canvas =
916
917
  pixelRatio?: number;
917
918
  /** 根据内容调整画布大小 */
918
919
  fitContent?: boolean;
920
+ updateStyles?: boolean;
919
921
  imageSmoothingEnabled?: boolean;
920
922
  imageSmoothingQuality?: "low" | "medium" | "high";
921
923
  canvas?: T;
922
924
  }
923
- interface LayoutSize {
924
- width: number;
925
- height: number;
926
- }
927
- interface DrawCallCanvas<T extends HTMLCanvasElement | OffscreenCanvas | Canvas = HTMLCanvasElement | OffscreenCanvas | Canvas> {
925
+ interface DrawCallCanvas<T extends HTMLCanvasElement | OffscreenCanvas | Canvas = HTMLCanvasElement> {
928
926
  readonly width: number;
929
927
  readonly height: number;
930
928
  readonly pixelRatio: number;
931
929
  readonly canvas: T;
932
- render(element: Element): LayoutNode;
930
+ render(element: LayoutElement): LayoutNode;
933
931
  clear(): void;
934
932
  getContext(): CanvasRenderingContext2D;
935
933
  toDataURL(type?: string, quality?: number): string;
@@ -978,7 +976,11 @@ declare function Text(props: TextProps): TextElement;
978
976
  declare function Transform(props: TransformProps): TransformElement;
979
977
  //#endregion
980
978
  //#region src/layout/engine.d.ts
981
- declare function computeLayout(element: Element, ctx: MeasureContext, constraints: LayoutConstraints, x?: number, y?: number): LayoutNode;
979
+ /**
980
+ * 布局计算主函数
981
+ * 内部使用 Element 类型以支持 Transform,外部通过 LayoutElement 约束类型
982
+ */
983
+ declare function computeLayout(element: LayoutElement, ctx: MeasureContext, constraints: LayoutConstraints, x?: number, y?: number): LayoutNode;
982
984
  //#endregion
983
985
  //#region src/layout/utils/print.d.ts
984
986
  /**
@@ -993,4 +995,4 @@ declare function printLayout(node: LayoutNode): void;
993
995
  */
994
996
  declare function layoutToString(node: LayoutNode, _indent?: string): string;
995
997
  //#endregion
996
- export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, CustomDraw, type CustomDrawElement, type CustomDrawProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutNode, type LayoutProps, type LayoutSize, type LinearGradientDescriptor, type MeasureContext, type ProxiedCanvasContextOptions, type RadialGradientDescriptor, RichText, type RichTextElement, type RichTextProps, type RichTextSpan, type Shadow, type Size, type Spacing, Stack, type StackAlign, type StackElement, type StackProps, type StrokeProps, Svg, type SvgAlign, type SvgChild, type SvgCircleChild, type SvgElement, type SvgEllipseChild, type SvgGroupChild, type SvgLineChild, type SvgPathChild, type SvgPolygonChild, type SvgPolylineChild, type SvgProps, type SvgRectChild, type SvgStyleProps, type SvgTextChild, type SvgTransformProps, Text, type TextElement, type TextProps, Transform, type TransformElement, type TransformProps, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };
998
+ export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, CustomDraw, type CustomDrawElement, type CustomDrawProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutNode, type LayoutProps, type LinearGradientDescriptor, type MeasureContext, type ProxiedCanvasContextOptions, type RadialGradientDescriptor, RichText, type RichTextElement, type RichTextProps, type RichTextSpan, type Shadow, type Size, type Spacing, Stack, type StackAlign, type StackElement, type StackProps, type StrokeProps, Svg, type SvgAlign, type SvgChild, type SvgCircleChild, type SvgElement, type SvgEllipseChild, type SvgGroupChild, type SvgLineChild, type SvgPathChild, type SvgPolygonChild, type SvgPolylineChild, type SvgProps, type SvgRectChild, type SvgStyleProps, type SvgTextChild, type SvgTransformProps, Text, type TextElement, type TextProps, Transform, type TransformElement, type TransformProps, computeLayout, createCanvas, createCanvasMeasureContext, layoutToString, linearGradient, printLayout, radialGradient, svg };