@codehz/draw-call 0.2.0 → 0.2.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/render.mjs CHANGED
@@ -174,6 +174,23 @@ function measureBoxSize(element, ctx, availableWidth, measureChild) {
174
174
  };
175
175
  }
176
176
 
177
+ //#endregion
178
+ //#region src/layout/components/customDraw.ts
179
+ /**
180
+ * 测量 CustomDraw 元素的固有尺寸
181
+ */
182
+ function measureCustomDrawSize(element, ctx, availableWidth, measureChild) {
183
+ if (typeof element.width === "number" && typeof element.height === "number") return {
184
+ width: element.width,
185
+ height: element.height
186
+ };
187
+ if (element.children && measureChild) return measureChild(element.children, ctx, availableWidth);
188
+ return {
189
+ width: 0,
190
+ height: 0
191
+ };
192
+ }
193
+
177
194
  //#endregion
178
195
  //#region src/layout/components/image.ts
179
196
  /**
@@ -555,6 +572,17 @@ function measureTextSize(element, ctx, availableWidth) {
555
572
  };
556
573
  }
557
574
 
575
+ //#endregion
576
+ //#region src/layout/components/transform.ts
577
+ /**
578
+ * 测量 Transform 元素的固有尺寸
579
+ * Transform 不施加任何尺寸约束,直接透传子元素的测量结果
580
+ * 变换(rotate, scale 等)仅在渲染时应用,不影响固有尺寸
581
+ */
582
+ function measureTransformSize(element, ctx, availableWidth, measureIntrinsicSize) {
583
+ return measureIntrinsicSize(element.children, ctx, availableWidth);
584
+ }
585
+
558
586
  //#endregion
559
587
  //#region src/layout/components/index.ts
560
588
  /**
@@ -568,6 +596,8 @@ function measureIntrinsicSize(element, ctx, availableWidth) {
568
596
  case "stack": return measureStackSize(element, ctx, availableWidth, measureIntrinsicSize);
569
597
  case "image": return measureImageSize(element, ctx, availableWidth);
570
598
  case "svg": return measureSvgSize(element, ctx, availableWidth);
599
+ case "transform": return measureTransformSize(element, ctx, availableWidth, measureIntrinsicSize);
600
+ case "customdraw": return measureCustomDrawSize(element, ctx, availableWidth, measureIntrinsicSize);
571
601
  default: return {
572
602
  width: 0,
573
603
  height: 0
@@ -925,6 +955,54 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
925
955
  }
926
956
  if (isReverse) node.children.reverse();
927
957
  }
958
+ } else if (element.type === "transform") {
959
+ const child = element.children;
960
+ if (child) {
961
+ const childMargin = normalizeSpacing(child.margin);
962
+ const childNode = computeLayout(child, ctx, {
963
+ minWidth: 0,
964
+ maxWidth: contentWidth,
965
+ minHeight: 0,
966
+ maxHeight: contentHeight
967
+ }, contentX, contentY);
968
+ node.children.push(childNode);
969
+ if (element.width === void 0) {
970
+ const childOuterWidth = childNode.layout.width + childMargin.left + childMargin.right;
971
+ const actualWidth = childOuterWidth + padding.left + padding.right;
972
+ node.layout.width = actualWidth;
973
+ node.layout.contentWidth = childOuterWidth;
974
+ }
975
+ if (element.height === void 0) {
976
+ const childOuterHeight = childNode.layout.height + childMargin.top + childMargin.bottom;
977
+ const actualHeight = childOuterHeight + padding.top + padding.bottom;
978
+ node.layout.height = actualHeight;
979
+ node.layout.contentHeight = childOuterHeight;
980
+ }
981
+ }
982
+ } else if (element.type === "customdraw") {
983
+ const child = element.children;
984
+ if (child) {
985
+ const childMargin = normalizeSpacing(child.margin);
986
+ const childNode = computeLayout(child, ctx, {
987
+ minWidth: 0,
988
+ maxWidth: contentWidth,
989
+ minHeight: 0,
990
+ maxHeight: contentHeight
991
+ }, contentX, contentY);
992
+ node.children.push(childNode);
993
+ if (element.width === void 0) {
994
+ const childOuterWidth = childNode.layout.width + childMargin.left + childMargin.right;
995
+ const actualWidth = childOuterWidth + padding.left + padding.right;
996
+ node.layout.width = actualWidth;
997
+ node.layout.contentWidth = childOuterWidth;
998
+ }
999
+ if (element.height === void 0) {
1000
+ const childOuterHeight = childNode.layout.height + childMargin.top + childMargin.bottom;
1001
+ const actualHeight = childOuterHeight + padding.top + padding.bottom;
1002
+ node.layout.height = actualHeight;
1003
+ node.layout.contentHeight = childOuterHeight;
1004
+ }
1005
+ }
928
1006
  }
929
1007
  return node;
930
1008
  }
@@ -1033,6 +1111,163 @@ function renderBox(ctx, node) {
1033
1111
  if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
1034
1112
  }
1035
1113
 
1114
+ //#endregion
1115
+ //#region src/compat/DOMMatrix.ts
1116
+ const DOMMatrixCompat = (() => {
1117
+ if (typeof DOMMatrix !== "undefined") return DOMMatrix;
1118
+ try {
1119
+ return __require("@napi-rs/canvas").DOMMatrix;
1120
+ } catch {
1121
+ throw new Error("DOMMatrix is not available. In Node.js, install @napi-rs/canvas.");
1122
+ }
1123
+ })();
1124
+
1125
+ //#endregion
1126
+ //#region src/render/components/ProxiedCanvasContext.ts
1127
+ /**
1128
+ * ProxiedCanvasContext - Canvas 上下文代理类
1129
+ *
1130
+ * 该类提供对真实 CanvasRenderingContext2D 的代理,有以下功能:
1131
+ * 1. 管理 save/restore 的平衡(计数器)
1132
+ * 2. 追踪相对变换而不是绝对变换
1133
+ * 3. 在析构时自动恢复所有未恢复的状态
1134
+ * 4. 转发所有其他 Canvas API 调用
1135
+ */
1136
+ var ProxiedCanvasContext = class {
1137
+ /**
1138
+ * 真实的 Canvas 上下文
1139
+ */
1140
+ ctx;
1141
+ /**
1142
+ * 基础变换矩阵(初始化时设置,保持不变)
1143
+ */
1144
+ baseTransform;
1145
+ /**
1146
+ * 相对变换矩阵(用户通过 setTransform 设置)
1147
+ */
1148
+ relativeTransform;
1149
+ /**
1150
+ * save/restore 计数器
1151
+ */
1152
+ saveCount = 0;
1153
+ /**
1154
+ * 构造函数
1155
+ * @param ctx 真实的 CanvasRenderingContext2D
1156
+ * @param baseTransform 初始的基础变换矩阵
1157
+ */
1158
+ constructor(ctx, baseTransform) {
1159
+ this.ctx = ctx;
1160
+ this.baseTransform = baseTransform;
1161
+ this.relativeTransform = new DOMMatrixCompat();
1162
+ }
1163
+ /**
1164
+ * save() - 保存当前状态并增加计数
1165
+ */
1166
+ save() {
1167
+ this.saveCount++;
1168
+ this.ctx.save();
1169
+ }
1170
+ /**
1171
+ * restore() - 恢复上一个状态并减少计数
1172
+ */
1173
+ restore() {
1174
+ if (this.saveCount > 0) {
1175
+ this.saveCount--;
1176
+ this.ctx.restore();
1177
+ }
1178
+ }
1179
+ /**
1180
+ * setTransform() - 设置相对变换
1181
+ */
1182
+ setTransform(...args) {
1183
+ let matrix;
1184
+ if (args.length === 1 && args[0] instanceof DOMMatrixCompat) matrix = args[0];
1185
+ else if (args.length === 6) matrix = new DOMMatrixCompat([
1186
+ args[0],
1187
+ args[1],
1188
+ args[2],
1189
+ args[3],
1190
+ args[4],
1191
+ args[5]
1192
+ ]);
1193
+ else return;
1194
+ this.relativeTransform = matrix;
1195
+ const actualTransform = this.baseTransform.multiply(matrix);
1196
+ this.ctx.setTransform(actualTransform);
1197
+ }
1198
+ /**
1199
+ * getTransform() - 返回相对变换(而不是绝对变换)
1200
+ */
1201
+ getTransform() {
1202
+ return this.relativeTransform;
1203
+ }
1204
+ /**
1205
+ * 析构函数级的清理 - 自动恢复所有未恢复的 save
1206
+ */
1207
+ destroy() {
1208
+ while (this.saveCount > 0) {
1209
+ console.log("destroy restore", this.saveCount);
1210
+ this.saveCount--;
1211
+ this.ctx.restore();
1212
+ }
1213
+ }
1214
+ };
1215
+ function createProxiedCanvasContext(ctx, baseTransform) {
1216
+ const proxy = new ProxiedCanvasContext(ctx, baseTransform);
1217
+ return new Proxy(proxy, {
1218
+ get(target, prop, receiver) {
1219
+ if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return Reflect.get(target, prop, receiver).bind(proxy);
1220
+ const ownValue = Reflect.get(target, prop, receiver);
1221
+ if (ownValue !== void 0) return ownValue;
1222
+ const contextValue = target.ctx[prop];
1223
+ if (typeof contextValue === "function") return contextValue.bind(target.ctx);
1224
+ return contextValue;
1225
+ },
1226
+ set(target, prop, value, _receiver) {
1227
+ target.ctx[prop] = value;
1228
+ return true;
1229
+ },
1230
+ has(target, prop) {
1231
+ if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return true;
1232
+ return prop in target.ctx;
1233
+ },
1234
+ ownKeys(target) {
1235
+ return Reflect.ownKeys(target.ctx);
1236
+ },
1237
+ getOwnPropertyDescriptor(target, prop) {
1238
+ return Reflect.getOwnPropertyDescriptor(target.ctx, prop);
1239
+ }
1240
+ });
1241
+ }
1242
+
1243
+ //#endregion
1244
+ //#region src/render/components/customDraw.ts
1245
+ /**
1246
+ * 渲染 CustomDraw 组件
1247
+ * 提供自定义绘制回调,用户可以直接访问 Canvas 上下文并绘制自定义内容
1248
+ */
1249
+ function renderCustomDraw(ctx, node) {
1250
+ const element = node.element;
1251
+ ctx.save();
1252
+ ctx.translate(node.layout.x, node.layout.y);
1253
+ const proxyCtx = createProxiedCanvasContext(ctx, ctx.getTransform());
1254
+ const inner = () => {
1255
+ if (node.children && node.children.length > 0) {
1256
+ ctx.save();
1257
+ ctx.translate(-node.layout.x, -node.layout.y);
1258
+ renderNode(ctx, node.children[0]);
1259
+ ctx.restore();
1260
+ }
1261
+ };
1262
+ element.draw(proxyCtx, {
1263
+ inner,
1264
+ width: node.layout.contentWidth,
1265
+ height: node.layout.contentHeight
1266
+ });
1267
+ proxyCtx.destroy();
1268
+ ctx.restore();
1269
+ }
1270
+
1036
1271
  //#endregion
1037
1272
  //#region src/render/components/image.ts
1038
1273
  function renderImage(ctx, node) {
@@ -1170,17 +1405,6 @@ function renderRichText(ctx, node) {
1170
1405
  }
1171
1406
  }
1172
1407
 
1173
- //#endregion
1174
- //#region src/compat/DOMMatrix.ts
1175
- const DOMMatrixCompat = (() => {
1176
- if (typeof DOMMatrix !== "undefined") return DOMMatrix;
1177
- try {
1178
- return __require("@napi-rs/canvas").DOMMatrix;
1179
- } catch {
1180
- throw new Error("DOMMatrix is not available. In Node.js, install @napi-rs/canvas.");
1181
- }
1182
- })();
1183
-
1184
1408
  //#endregion
1185
1409
  //#region src/compat/Path2D.ts
1186
1410
  const Path2DCompat = (() => {
@@ -1256,8 +1480,14 @@ function applyTransform(base, transform) {
1256
1480
  }
1257
1481
  if (transform.scale !== void 0) if (typeof transform.scale === "number") result = result.scale(transform.scale);
1258
1482
  else result = result.scale(transform.scale[0], transform.scale[1]);
1259
- if (transform.skewX !== void 0) result = result.skewX(transform.skewX);
1260
- if (transform.skewY !== void 0) result = result.skewY(transform.skewY);
1483
+ if (transform.skewX !== void 0) {
1484
+ const degrees = transform.skewX * 180 / Math.PI;
1485
+ result = result.skewX(degrees);
1486
+ }
1487
+ if (transform.skewY !== void 0) {
1488
+ const degrees = transform.skewY * 180 / Math.PI;
1489
+ result = result.skewY(degrees);
1490
+ }
1261
1491
  return result;
1262
1492
  }
1263
1493
  function applyStroke(ctx, stroke, bounds) {
@@ -1493,6 +1723,82 @@ function renderText(ctx, node) {
1493
1723
  if (element.shadow) clearShadow$1(ctx);
1494
1724
  }
1495
1725
 
1726
+ //#endregion
1727
+ //#region src/render/components/transform.ts
1728
+ /**
1729
+ * 解析 Transform 值为 DOMMatrix
1730
+ * 支持三种格式:
1731
+ * - 数组: [a, b, c, d, e, f]
1732
+ * - DOMMatrix2DInit 对象: { a, b, c, d, e, f, ... }
1733
+ * - 简易对象: { translate, rotate, scale, skewX, skewY }
1734
+ */
1735
+ function parseTransformValue(transform) {
1736
+ if (transform === void 0) return new DOMMatrixCompat();
1737
+ if (Array.isArray(transform)) return new DOMMatrixCompat(transform);
1738
+ const hasDOMMatrixInit = "a" in transform || "b" in transform || "c" in transform || "d" in transform || "e" in transform || "f" in transform;
1739
+ const hasSimpleTransform = "translate" in transform || "rotate" in transform || "scale" in transform || "skewX" in transform || "skewY" in transform;
1740
+ if (hasDOMMatrixInit && !hasSimpleTransform) {
1741
+ const init = transform;
1742
+ return new DOMMatrixCompat([
1743
+ init.a ?? 1,
1744
+ init.b ?? 0,
1745
+ init.c ?? 0,
1746
+ init.d ?? 1,
1747
+ init.e ?? 0,
1748
+ init.f ?? 0
1749
+ ]);
1750
+ }
1751
+ const simpleObj = transform;
1752
+ let result = new DOMMatrixCompat();
1753
+ if (simpleObj.translate) result = result.translate(simpleObj.translate[0], simpleObj.translate[1]);
1754
+ if (simpleObj.rotate !== void 0) if (typeof simpleObj.rotate === "number") result = result.rotate(simpleObj.rotate);
1755
+ else {
1756
+ const [angle, cx, cy] = simpleObj.rotate;
1757
+ result = result.translate(cx, cy).rotate(angle).translate(-cx, -cy);
1758
+ }
1759
+ if (simpleObj.scale !== void 0) if (typeof simpleObj.scale === "number") result = result.scale(simpleObj.scale);
1760
+ else result = result.scale(simpleObj.scale[0], simpleObj.scale[1]);
1761
+ if (simpleObj.skewX !== void 0) result = result.skewX(simpleObj.skewX);
1762
+ if (simpleObj.skewY !== void 0) result = result.skewY(simpleObj.skewY);
1763
+ return result;
1764
+ }
1765
+ /**
1766
+ * 根据 transformOrigin 属性和子元素尺寸计算实际变换原点坐标
1767
+ */
1768
+ function resolveTransformOrigin(origin, childLayout) {
1769
+ if (origin === void 0) return [0, 0];
1770
+ const [xVal, yVal] = origin;
1771
+ const sizes = [childLayout.width, childLayout.height];
1772
+ const values = [xVal, yVal];
1773
+ const result = [0, 0];
1774
+ for (let i = 0; i < 2; i++) {
1775
+ const val = values[i];
1776
+ if (typeof val === "string") if (val.endsWith("%")) result[i] = parseFloat(val) / 100 * sizes[i];
1777
+ else result[i] = parseFloat(val);
1778
+ else result[i] = val;
1779
+ }
1780
+ return result;
1781
+ }
1782
+ /**
1783
+ * 渲染 Transform 组件及其子元素
1784
+ */
1785
+ function renderTransform(ctx, node) {
1786
+ const element = node.element;
1787
+ const { children } = node;
1788
+ if (!children || children.length === 0) return;
1789
+ const childNode = children[0];
1790
+ const [relativeOx, relativeOy] = resolveTransformOrigin(element.transformOrigin, childNode.layout);
1791
+ const ox = childNode.layout.x + relativeOx;
1792
+ const oy = childNode.layout.y + relativeOy;
1793
+ const targetMatrix = parseTransformValue(element.transform);
1794
+ const finalMatrix = new DOMMatrixCompat().translate(ox, oy).multiply(targetMatrix).translate(-ox, -oy);
1795
+ ctx.save();
1796
+ const composedTransform = ctx.getTransform().multiply(finalMatrix);
1797
+ ctx.setTransform(composedTransform);
1798
+ renderNode(ctx, childNode);
1799
+ ctx.restore();
1800
+ }
1801
+
1496
1802
  //#endregion
1497
1803
  //#region src/render/index.ts
1498
1804
  function renderNode(ctx, node) {
@@ -1524,6 +1830,12 @@ function renderNode(ctx, node) {
1524
1830
  case "svg":
1525
1831
  renderSvg(ctx, node);
1526
1832
  break;
1833
+ case "transform":
1834
+ renderTransform(ctx, node);
1835
+ break;
1836
+ case "customdraw":
1837
+ renderCustomDraw(ctx, node);
1838
+ break;
1527
1839
  }
1528
1840
  }
1529
1841