@codehz/draw-call 0.5.3 → 0.6.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.
@@ -475,20 +475,40 @@ function buildFontString(font) {
475
475
  }
476
476
  //#endregion
477
477
  //#region src/layout/utils/measure.ts
478
+ const MEASURE_CACHE_LIMIT = 256;
478
479
  function createCanvasMeasureContext(ctx) {
480
+ const cache = /* @__PURE__ */ new Map();
481
+ let lastFontString = null;
479
482
  return { measureText(text, font) {
480
- ctx.font = buildFontString(font);
481
- ctx.textBaseline = "middle";
483
+ const fontString = buildFontString(font);
484
+ const key = fontString + "\0" + text;
485
+ const hit = cache.get(key);
486
+ if (hit !== void 0) {
487
+ cache.delete(key);
488
+ cache.set(key, hit);
489
+ return hit;
490
+ }
491
+ if (fontString !== lastFontString) {
492
+ ctx.font = fontString;
493
+ ctx.textBaseline = "middle";
494
+ lastFontString = fontString;
495
+ }
482
496
  const metrics = ctx.measureText(text);
483
497
  const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
484
498
  const fontSize = font.size || 16;
485
- return {
499
+ const result = {
486
500
  width: metrics.width,
487
501
  height: height || fontSize,
488
502
  offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
489
503
  ascent: metrics.actualBoundingBoxAscent,
490
504
  descent: metrics.actualBoundingBoxDescent
491
505
  };
506
+ if (cache.size >= MEASURE_CACHE_LIMIT) {
507
+ const oldest = cache.keys().next().value;
508
+ if (oldest !== void 0) cache.delete(oldest);
509
+ }
510
+ cache.set(key, result);
511
+ return result;
492
512
  } };
493
513
  }
494
514
  function wrapText(ctx, text, maxWidth, font) {
@@ -765,7 +785,14 @@ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
765
785
  }
766
786
  if (layoutElement.type === "richtext") {
767
787
  const lineHeight = layoutElement.lineHeight ?? 1.2;
768
- let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight);
788
+ const elementStyle = {
789
+ font: layoutElement.font,
790
+ color: layoutElement.color,
791
+ background: layoutElement.background,
792
+ underline: layoutElement.underline,
793
+ strikethrough: layoutElement.strikethrough
794
+ };
795
+ let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight, elementStyle);
769
796
  if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
770
797
  lines = lines.slice(0, layoutElement.maxLines);
771
798
  if (layoutElement.ellipsis && lines.length > 0) {
@@ -1164,121 +1191,88 @@ function renderBox(ctx, node) {
1164
1191
  if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
1165
1192
  }
1166
1193
  //#endregion
1167
- //#region src/render/components/ProxiedCanvasContext.ts
1168
- /**
1169
- * ProxiedCanvasContext - Canvas 上下文代理类
1170
- *
1171
- * 该类提供对真实 CanvasRenderingContext2D 的代理,有以下功能:
1172
- * 1. 管理 save/restore 的平衡(计数器)
1173
- * 2. 追踪相对变换而不是绝对变换
1174
- * 3. 在析构时自动恢复所有未恢复的状态
1175
- * 4. 转发所有其他 Canvas API 调用
1176
- */
1177
- var ProxiedCanvasContext = class {
1178
- /**
1179
- * 真实的 Canvas 上下文
1180
- */
1181
- ctx;
1182
- /**
1183
- * 基础变换矩阵(初始化时设置,保持不变)
1184
- */
1194
+ //#region src/render/components/CustomDrawContext.ts
1195
+ function cloneMatrix(matrix) {
1196
+ return new _napi_rs_canvas.DOMMatrix([
1197
+ matrix.a,
1198
+ matrix.b,
1199
+ matrix.c,
1200
+ matrix.d,
1201
+ matrix.e,
1202
+ matrix.f
1203
+ ]);
1204
+ }
1205
+ function toRelativeMatrix(transform) {
1206
+ if (transform === void 0) return new _napi_rs_canvas.DOMMatrix();
1207
+ if (transform instanceof _napi_rs_canvas.DOMMatrix) return cloneMatrix(transform);
1208
+ return new _napi_rs_canvas.DOMMatrix(transform);
1209
+ }
1210
+ var ManagedCustomDrawContext = class {
1211
+ canvas;
1185
1212
  baseTransform;
1186
- /**
1187
- * 相对变换矩阵(用户通过 setTransform 设置)
1188
- */
1189
1213
  relativeTransform;
1190
- /**
1191
- * save/restore 计数器
1192
- */
1214
+ transformStack = [];
1193
1215
  saveCount = 0;
1194
- /**
1195
- * 构造函数
1196
- * @param ctx 真实的 CanvasRenderingContext2D
1197
- * @param baseTransform 初始的基础变换矩阵
1198
- */
1199
- constructor(ctx, baseTransform) {
1200
- this.ctx = ctx;
1201
- this.baseTransform = baseTransform;
1216
+ constructor(canvas, baseTransform) {
1217
+ this.canvas = canvas;
1218
+ this.baseTransform = cloneMatrix(baseTransform);
1202
1219
  this.relativeTransform = new _napi_rs_canvas.DOMMatrix();
1203
1220
  }
1204
- /**
1205
- * save() - 保存当前状态并增加计数
1206
- */
1207
1221
  save() {
1208
1222
  this.saveCount++;
1209
- this.ctx.save();
1223
+ this.transformStack.push(cloneMatrix(this.relativeTransform));
1224
+ this.canvas.save();
1210
1225
  }
1211
- /**
1212
- * restore() - 恢复上一个状态并减少计数
1213
- */
1214
1226
  restore() {
1215
- if (this.saveCount > 0) {
1216
- this.saveCount--;
1217
- this.ctx.restore();
1218
- }
1227
+ if (this.saveCount === 0) return;
1228
+ this.saveCount--;
1229
+ this.relativeTransform = this.transformStack.pop() ?? new _napi_rs_canvas.DOMMatrix();
1230
+ this.canvas.restore();
1231
+ this.applyRelativeTransform();
1219
1232
  }
1220
- /**
1221
- * setTransform() - 设置相对变换
1222
- */
1223
- setTransform(...args) {
1224
- let matrix;
1225
- if (args.length === 1 && args[0] instanceof _napi_rs_canvas.DOMMatrix) matrix = args[0];
1226
- else if (args.length === 6) matrix = new _napi_rs_canvas.DOMMatrix([
1227
- args[0],
1228
- args[1],
1229
- args[2],
1230
- args[3],
1231
- args[4],
1232
- args[5]
1233
- ]);
1234
- else return;
1235
- this.relativeTransform = matrix;
1236
- const actualTransform = this.baseTransform.multiply(matrix);
1237
- this.ctx.setTransform(actualTransform);
1238
- }
1239
- /**
1240
- * getTransform() - 返回相对变换(而不是绝对变换)
1241
- */
1242
1233
  getTransform() {
1243
- return this.relativeTransform;
1234
+ return cloneMatrix(this.relativeTransform);
1235
+ }
1236
+ setTransform(transform) {
1237
+ this.relativeTransform = toRelativeMatrix(transform);
1238
+ this.applyRelativeTransform();
1239
+ }
1240
+ resetTransform() {
1241
+ this.relativeTransform = new _napi_rs_canvas.DOMMatrix();
1242
+ this.applyRelativeTransform();
1243
+ }
1244
+ translate(x, y) {
1245
+ this.relativeTransform = this.relativeTransform.translate(x, y);
1246
+ this.applyRelativeTransform();
1247
+ }
1248
+ rotate(angle) {
1249
+ this.relativeTransform = this.relativeTransform.rotate(angle * 180 / Math.PI);
1250
+ this.applyRelativeTransform();
1251
+ }
1252
+ scale(x, y) {
1253
+ this.relativeTransform = this.relativeTransform.scale(x, y ?? x);
1254
+ this.applyRelativeTransform();
1255
+ }
1256
+ transform(a, b, c, d, e, f) {
1257
+ this.relativeTransform = this.relativeTransform.multiply(new _napi_rs_canvas.DOMMatrix([
1258
+ a,
1259
+ b,
1260
+ c,
1261
+ d,
1262
+ e,
1263
+ f
1264
+ ]));
1265
+ this.applyRelativeTransform();
1244
1266
  }
1245
- /**
1246
- * 析构函数级的清理 - 自动恢复所有未恢复的 save
1247
- */
1248
1267
  destroy() {
1249
- while (this.saveCount > 0) {
1250
- console.log("destroy restore", this.saveCount);
1251
- this.saveCount--;
1252
- this.ctx.restore();
1253
- }
1268
+ while (this.saveCount > 0) this.restore();
1269
+ }
1270
+ applyRelativeTransform() {
1271
+ this.canvas.setTransform(this.baseTransform.multiply(this.relativeTransform));
1254
1272
  }
1255
1273
  };
1256
- function createProxiedCanvasContext(ctx, baseTransform) {
1257
- const proxy = new ProxiedCanvasContext(ctx, baseTransform);
1258
- return new Proxy(proxy, {
1259
- get(target, prop, receiver) {
1260
- if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return Reflect.get(target, prop, receiver).bind(proxy);
1261
- const ownValue = Reflect.get(target, prop, receiver);
1262
- if (ownValue !== void 0) return ownValue;
1263
- const contextValue = target.ctx[prop];
1264
- if (typeof contextValue === "function") return contextValue.bind(target.ctx);
1265
- return contextValue;
1266
- },
1267
- set(target, prop, value, _receiver) {
1268
- target.ctx[prop] = value;
1269
- return true;
1270
- },
1271
- has(target, prop) {
1272
- if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return true;
1273
- return prop in target.ctx;
1274
- },
1275
- ownKeys(target) {
1276
- return Reflect.ownKeys(target.ctx);
1277
- },
1278
- getOwnPropertyDescriptor(target, prop) {
1279
- return Reflect.getOwnPropertyDescriptor(target.ctx, prop);
1280
- }
1281
- });
1274
+ function createCustomDrawContext(canvas, baseTransform) {
1275
+ return new ManagedCustomDrawContext(canvas, baseTransform);
1282
1276
  }
1283
1277
  //#endregion
1284
1278
  //#region src/render/components/customDraw.ts
@@ -1290,7 +1284,7 @@ function renderCustomDraw(ctx, node) {
1290
1284
  const element = node.element;
1291
1285
  ctx.save();
1292
1286
  ctx.translate(node.layout.x, node.layout.y);
1293
- const proxyCtx = createProxiedCanvasContext(ctx, ctx.getTransform());
1287
+ const customCtx = createCustomDrawContext(ctx, ctx.getTransform());
1294
1288
  const inner = () => {
1295
1289
  if (node.children && node.children.length > 0) {
1296
1290
  ctx.save();
@@ -1299,12 +1293,12 @@ function renderCustomDraw(ctx, node) {
1299
1293
  ctx.restore();
1300
1294
  }
1301
1295
  };
1302
- element.draw(proxyCtx, {
1296
+ element.draw(customCtx, {
1303
1297
  inner,
1304
1298
  width: node.layout.contentWidth,
1305
1299
  height: node.layout.contentHeight
1306
1300
  });
1307
- proxyCtx.destroy();
1301
+ customCtx.destroy();
1308
1302
  ctx.restore();
1309
1303
  }
1310
1304
  //#endregion
@@ -1,4 +1,4 @@
1
- import { Canvas } from "@napi-rs/canvas";
1
+ import { Canvas, DOMMatrix } from "@napi-rs/canvas";
2
2
 
3
3
  //#region src/types/base.d.ts
4
4
  type Size = number | `${number}%` | "auto" | "fill";
@@ -152,7 +152,19 @@ interface LayoutConstraints {
152
152
  //#endregion
153
153
  //#region src/types/components.d.ts
154
154
  type ElementType = "box" | "text" | "richtext" | "image" | "svg" | "stack" | "transform" | "customdraw";
155
- interface ProxiedCanvasContextOptions {
155
+ interface CustomDrawContext {
156
+ readonly canvas: CanvasRenderingContext2D;
157
+ save(): void;
158
+ restore(): void;
159
+ getTransform(): DOMMatrix;
160
+ setTransform(transform?: DOMMatrix | [number, number, number, number, number, number]): void;
161
+ resetTransform(): void;
162
+ translate(x: number, y: number): void;
163
+ rotate(angle: number): void;
164
+ scale(x: number, y?: number): void;
165
+ transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
166
+ }
167
+ interface CustomDrawOptions {
156
168
  inner?: () => void;
157
169
  width: number;
158
170
  height: number;
@@ -357,7 +369,7 @@ interface TransformElement extends ElementBase, TransformProps {
357
369
  type: "transform";
358
370
  }
359
371
  interface CustomDrawProps extends LayoutProps {
360
- draw: (ctx: CanvasRenderingContext2D, options: ProxiedCanvasContextOptions) => void;
372
+ draw: (ctx: CustomDrawContext, options: CustomDrawOptions) => void;
361
373
  children?: Element;
362
374
  }
363
375
  interface CustomDrawElement extends ElementBase, CustomDrawProps {
@@ -451,4 +463,4 @@ declare function printLayout(node: LayoutNode): void;
451
463
  */
452
464
  declare function layoutToString(node: LayoutNode, _indent?: string): string;
453
465
  //#endregion
454
- 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 LayoutElement, 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 };
466
+ export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, CustomDraw, type CustomDrawContext, type CustomDrawElement, type CustomDrawOptions, type CustomDrawProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutElement, type LayoutNode, type LayoutProps, type LinearGradientDescriptor, type MeasureContext, 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 };
@@ -1,4 +1,4 @@
1
- import { Canvas } from "@napi-rs/canvas";
1
+ import { Canvas, DOMMatrix } from "@napi-rs/canvas";
2
2
 
3
3
  //#region src/types/base.d.ts
4
4
  type Size = number | `${number}%` | "auto" | "fill";
@@ -152,7 +152,19 @@ interface LayoutConstraints {
152
152
  //#endregion
153
153
  //#region src/types/components.d.ts
154
154
  type ElementType = "box" | "text" | "richtext" | "image" | "svg" | "stack" | "transform" | "customdraw";
155
- interface ProxiedCanvasContextOptions {
155
+ interface CustomDrawContext {
156
+ readonly canvas: CanvasRenderingContext2D;
157
+ save(): void;
158
+ restore(): void;
159
+ getTransform(): DOMMatrix;
160
+ setTransform(transform?: DOMMatrix | [number, number, number, number, number, number]): void;
161
+ resetTransform(): void;
162
+ translate(x: number, y: number): void;
163
+ rotate(angle: number): void;
164
+ scale(x: number, y?: number): void;
165
+ transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
166
+ }
167
+ interface CustomDrawOptions {
156
168
  inner?: () => void;
157
169
  width: number;
158
170
  height: number;
@@ -357,7 +369,7 @@ interface TransformElement extends ElementBase, TransformProps {
357
369
  type: "transform";
358
370
  }
359
371
  interface CustomDrawProps extends LayoutProps {
360
- draw: (ctx: CanvasRenderingContext2D, options: ProxiedCanvasContextOptions) => void;
372
+ draw: (ctx: CustomDrawContext, options: CustomDrawOptions) => void;
361
373
  children?: Element;
362
374
  }
363
375
  interface CustomDrawElement extends ElementBase, CustomDrawProps {
@@ -451,4 +463,4 @@ declare function printLayout(node: LayoutNode): void;
451
463
  */
452
464
  declare function layoutToString(node: LayoutNode, _indent?: string): string;
453
465
  //#endregion
454
- 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 LayoutElement, 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 };
466
+ export { type AlignItems, type AlignSelf, type Border, type Bounds, Box, type BoxElement, type BoxProps, type CanvasOptions, type Color, type ColorStop, type ContainerLayoutProps, CustomDraw, type CustomDrawContext, type CustomDrawElement, type CustomDrawOptions, type CustomDrawProps, type DrawCallCanvas, type Element, type FlexDirection, type FontProps, type GradientDescriptor, Image, type JustifyContent, type LayoutElement, type LayoutNode, type LayoutProps, type LinearGradientDescriptor, type MeasureContext, 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 };
@@ -474,20 +474,40 @@ function buildFontString(font) {
474
474
  }
475
475
  //#endregion
476
476
  //#region src/layout/utils/measure.ts
477
+ const MEASURE_CACHE_LIMIT = 256;
477
478
  function createCanvasMeasureContext(ctx) {
479
+ const cache = /* @__PURE__ */ new Map();
480
+ let lastFontString = null;
478
481
  return { measureText(text, font) {
479
- ctx.font = buildFontString(font);
480
- ctx.textBaseline = "middle";
482
+ const fontString = buildFontString(font);
483
+ const key = fontString + "\0" + text;
484
+ const hit = cache.get(key);
485
+ if (hit !== void 0) {
486
+ cache.delete(key);
487
+ cache.set(key, hit);
488
+ return hit;
489
+ }
490
+ if (fontString !== lastFontString) {
491
+ ctx.font = fontString;
492
+ ctx.textBaseline = "middle";
493
+ lastFontString = fontString;
494
+ }
481
495
  const metrics = ctx.measureText(text);
482
496
  const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
483
497
  const fontSize = font.size || 16;
484
- return {
498
+ const result = {
485
499
  width: metrics.width,
486
500
  height: height || fontSize,
487
501
  offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
488
502
  ascent: metrics.actualBoundingBoxAscent,
489
503
  descent: metrics.actualBoundingBoxDescent
490
504
  };
505
+ if (cache.size >= MEASURE_CACHE_LIMIT) {
506
+ const oldest = cache.keys().next().value;
507
+ if (oldest !== void 0) cache.delete(oldest);
508
+ }
509
+ cache.set(key, result);
510
+ return result;
491
511
  } };
492
512
  }
493
513
  function wrapText(ctx, text, maxWidth, font) {
@@ -764,7 +784,14 @@ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
764
784
  }
765
785
  if (layoutElement.type === "richtext") {
766
786
  const lineHeight = layoutElement.lineHeight ?? 1.2;
767
- let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight);
787
+ const elementStyle = {
788
+ font: layoutElement.font,
789
+ color: layoutElement.color,
790
+ background: layoutElement.background,
791
+ underline: layoutElement.underline,
792
+ strikethrough: layoutElement.strikethrough
793
+ };
794
+ let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight, elementStyle);
768
795
  if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
769
796
  lines = lines.slice(0, layoutElement.maxLines);
770
797
  if (layoutElement.ellipsis && lines.length > 0) {
@@ -1163,121 +1190,88 @@ function renderBox(ctx, node) {
1163
1190
  if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
1164
1191
  }
1165
1192
  //#endregion
1166
- //#region src/render/components/ProxiedCanvasContext.ts
1167
- /**
1168
- * ProxiedCanvasContext - Canvas 上下文代理类
1169
- *
1170
- * 该类提供对真实 CanvasRenderingContext2D 的代理,有以下功能:
1171
- * 1. 管理 save/restore 的平衡(计数器)
1172
- * 2. 追踪相对变换而不是绝对变换
1173
- * 3. 在析构时自动恢复所有未恢复的状态
1174
- * 4. 转发所有其他 Canvas API 调用
1175
- */
1176
- var ProxiedCanvasContext = class {
1177
- /**
1178
- * 真实的 Canvas 上下文
1179
- */
1180
- ctx;
1181
- /**
1182
- * 基础变换矩阵(初始化时设置,保持不变)
1183
- */
1193
+ //#region src/render/components/CustomDrawContext.ts
1194
+ function cloneMatrix(matrix) {
1195
+ return new DOMMatrix([
1196
+ matrix.a,
1197
+ matrix.b,
1198
+ matrix.c,
1199
+ matrix.d,
1200
+ matrix.e,
1201
+ matrix.f
1202
+ ]);
1203
+ }
1204
+ function toRelativeMatrix(transform) {
1205
+ if (transform === void 0) return new DOMMatrix();
1206
+ if (transform instanceof DOMMatrix) return cloneMatrix(transform);
1207
+ return new DOMMatrix(transform);
1208
+ }
1209
+ var ManagedCustomDrawContext = class {
1210
+ canvas;
1184
1211
  baseTransform;
1185
- /**
1186
- * 相对变换矩阵(用户通过 setTransform 设置)
1187
- */
1188
1212
  relativeTransform;
1189
- /**
1190
- * save/restore 计数器
1191
- */
1213
+ transformStack = [];
1192
1214
  saveCount = 0;
1193
- /**
1194
- * 构造函数
1195
- * @param ctx 真实的 CanvasRenderingContext2D
1196
- * @param baseTransform 初始的基础变换矩阵
1197
- */
1198
- constructor(ctx, baseTransform) {
1199
- this.ctx = ctx;
1200
- this.baseTransform = baseTransform;
1215
+ constructor(canvas, baseTransform) {
1216
+ this.canvas = canvas;
1217
+ this.baseTransform = cloneMatrix(baseTransform);
1201
1218
  this.relativeTransform = new DOMMatrix();
1202
1219
  }
1203
- /**
1204
- * save() - 保存当前状态并增加计数
1205
- */
1206
1220
  save() {
1207
1221
  this.saveCount++;
1208
- this.ctx.save();
1222
+ this.transformStack.push(cloneMatrix(this.relativeTransform));
1223
+ this.canvas.save();
1209
1224
  }
1210
- /**
1211
- * restore() - 恢复上一个状态并减少计数
1212
- */
1213
1225
  restore() {
1214
- if (this.saveCount > 0) {
1215
- this.saveCount--;
1216
- this.ctx.restore();
1217
- }
1226
+ if (this.saveCount === 0) return;
1227
+ this.saveCount--;
1228
+ this.relativeTransform = this.transformStack.pop() ?? new DOMMatrix();
1229
+ this.canvas.restore();
1230
+ this.applyRelativeTransform();
1218
1231
  }
1219
- /**
1220
- * setTransform() - 设置相对变换
1221
- */
1222
- setTransform(...args) {
1223
- let matrix;
1224
- if (args.length === 1 && args[0] instanceof DOMMatrix) matrix = args[0];
1225
- else if (args.length === 6) matrix = new DOMMatrix([
1226
- args[0],
1227
- args[1],
1228
- args[2],
1229
- args[3],
1230
- args[4],
1231
- args[5]
1232
- ]);
1233
- else return;
1234
- this.relativeTransform = matrix;
1235
- const actualTransform = this.baseTransform.multiply(matrix);
1236
- this.ctx.setTransform(actualTransform);
1237
- }
1238
- /**
1239
- * getTransform() - 返回相对变换(而不是绝对变换)
1240
- */
1241
1232
  getTransform() {
1242
- return this.relativeTransform;
1233
+ return cloneMatrix(this.relativeTransform);
1234
+ }
1235
+ setTransform(transform) {
1236
+ this.relativeTransform = toRelativeMatrix(transform);
1237
+ this.applyRelativeTransform();
1238
+ }
1239
+ resetTransform() {
1240
+ this.relativeTransform = new DOMMatrix();
1241
+ this.applyRelativeTransform();
1242
+ }
1243
+ translate(x, y) {
1244
+ this.relativeTransform = this.relativeTransform.translate(x, y);
1245
+ this.applyRelativeTransform();
1246
+ }
1247
+ rotate(angle) {
1248
+ this.relativeTransform = this.relativeTransform.rotate(angle * 180 / Math.PI);
1249
+ this.applyRelativeTransform();
1250
+ }
1251
+ scale(x, y) {
1252
+ this.relativeTransform = this.relativeTransform.scale(x, y ?? x);
1253
+ this.applyRelativeTransform();
1254
+ }
1255
+ transform(a, b, c, d, e, f) {
1256
+ this.relativeTransform = this.relativeTransform.multiply(new DOMMatrix([
1257
+ a,
1258
+ b,
1259
+ c,
1260
+ d,
1261
+ e,
1262
+ f
1263
+ ]));
1264
+ this.applyRelativeTransform();
1243
1265
  }
1244
- /**
1245
- * 析构函数级的清理 - 自动恢复所有未恢复的 save
1246
- */
1247
1266
  destroy() {
1248
- while (this.saveCount > 0) {
1249
- console.log("destroy restore", this.saveCount);
1250
- this.saveCount--;
1251
- this.ctx.restore();
1252
- }
1267
+ while (this.saveCount > 0) this.restore();
1268
+ }
1269
+ applyRelativeTransform() {
1270
+ this.canvas.setTransform(this.baseTransform.multiply(this.relativeTransform));
1253
1271
  }
1254
1272
  };
1255
- function createProxiedCanvasContext(ctx, baseTransform) {
1256
- const proxy = new ProxiedCanvasContext(ctx, baseTransform);
1257
- return new Proxy(proxy, {
1258
- get(target, prop, receiver) {
1259
- if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return Reflect.get(target, prop, receiver).bind(proxy);
1260
- const ownValue = Reflect.get(target, prop, receiver);
1261
- if (ownValue !== void 0) return ownValue;
1262
- const contextValue = target.ctx[prop];
1263
- if (typeof contextValue === "function") return contextValue.bind(target.ctx);
1264
- return contextValue;
1265
- },
1266
- set(target, prop, value, _receiver) {
1267
- target.ctx[prop] = value;
1268
- return true;
1269
- },
1270
- has(target, prop) {
1271
- if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return true;
1272
- return prop in target.ctx;
1273
- },
1274
- ownKeys(target) {
1275
- return Reflect.ownKeys(target.ctx);
1276
- },
1277
- getOwnPropertyDescriptor(target, prop) {
1278
- return Reflect.getOwnPropertyDescriptor(target.ctx, prop);
1279
- }
1280
- });
1273
+ function createCustomDrawContext(canvas, baseTransform) {
1274
+ return new ManagedCustomDrawContext(canvas, baseTransform);
1281
1275
  }
1282
1276
  //#endregion
1283
1277
  //#region src/render/components/customDraw.ts
@@ -1289,7 +1283,7 @@ function renderCustomDraw(ctx, node) {
1289
1283
  const element = node.element;
1290
1284
  ctx.save();
1291
1285
  ctx.translate(node.layout.x, node.layout.y);
1292
- const proxyCtx = createProxiedCanvasContext(ctx, ctx.getTransform());
1286
+ const customCtx = createCustomDrawContext(ctx, ctx.getTransform());
1293
1287
  const inner = () => {
1294
1288
  if (node.children && node.children.length > 0) {
1295
1289
  ctx.save();
@@ -1298,12 +1292,12 @@ function renderCustomDraw(ctx, node) {
1298
1292
  ctx.restore();
1299
1293
  }
1300
1294
  };
1301
- element.draw(proxyCtx, {
1295
+ element.draw(customCtx, {
1302
1296
  inner,
1303
1297
  width: node.layout.contentWidth,
1304
1298
  height: node.layout.contentHeight
1305
1299
  });
1306
- proxyCtx.destroy();
1300
+ customCtx.destroy();
1307
1301
  ctx.restore();
1308
1302
  }
1309
1303
  //#endregion