@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.
package/README.md CHANGED
@@ -244,7 +244,7 @@ Transform({
244
244
 
245
245
  ### CustomDraw
246
246
 
247
- 自定义绘制组件,提供原生 Canvas 上下文进行自定义绘制。
247
+ 自定义绘制组件,提供受控的绘制上下文。原生 Canvas API 可通过 `ctx.canvas` 访问,`save`/`restore`/transform 由 `CustomDrawContext` 管理。
248
248
 
249
249
  ```typescript
250
250
  CustomDraw({
@@ -252,12 +252,12 @@ CustomDraw({
252
252
  height: 100,
253
253
  draw: (ctx, options) => {
254
254
  // 使用原生 Canvas API 进行绘制
255
- ctx.fillStyle = "#667eea";
256
- ctx.fillRect(10, 10, 180, 80);
257
- ctx.fillStyle = "#ffffff";
258
- ctx.font = "bold 16px sans-serif";
259
- ctx.textAlign = "center";
260
- ctx.fillText("Custom Draw", 100, 60);
255
+ ctx.canvas.fillStyle = "#667eea";
256
+ ctx.canvas.fillRect(10, 10, 180, 80);
257
+ ctx.canvas.fillStyle = "#ffffff";
258
+ ctx.canvas.font = "bold 16px sans-serif";
259
+ ctx.canvas.textAlign = "center";
260
+ ctx.canvas.fillText("Custom Draw", 100, 60);
261
261
  },
262
262
  });
263
263
  ```
@@ -479,20 +479,40 @@ function buildFontString(font) {
479
479
  }
480
480
  //#endregion
481
481
  //#region src/layout/utils/measure.ts
482
+ const MEASURE_CACHE_LIMIT = 256;
482
483
  function createCanvasMeasureContext(ctx) {
484
+ const cache = /* @__PURE__ */ new Map();
485
+ let lastFontString = null;
483
486
  return { measureText(text, font) {
484
- ctx.font = buildFontString(font);
485
- ctx.textBaseline = "middle";
487
+ const fontString = buildFontString(font);
488
+ const key = fontString + "\0" + text;
489
+ const hit = cache.get(key);
490
+ if (hit !== void 0) {
491
+ cache.delete(key);
492
+ cache.set(key, hit);
493
+ return hit;
494
+ }
495
+ if (fontString !== lastFontString) {
496
+ ctx.font = fontString;
497
+ ctx.textBaseline = "middle";
498
+ lastFontString = fontString;
499
+ }
486
500
  const metrics = ctx.measureText(text);
487
501
  const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
488
502
  const fontSize = font.size || 16;
489
- return {
503
+ const result = {
490
504
  width: metrics.width,
491
505
  height: height || fontSize,
492
506
  offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
493
507
  ascent: metrics.actualBoundingBoxAscent,
494
508
  descent: metrics.actualBoundingBoxDescent
495
509
  };
510
+ if (cache.size >= MEASURE_CACHE_LIMIT) {
511
+ const oldest = cache.keys().next().value;
512
+ if (oldest !== void 0) cache.delete(oldest);
513
+ }
514
+ cache.set(key, result);
515
+ return result;
496
516
  } };
497
517
  }
498
518
  function wrapText(ctx, text, maxWidth, font) {
@@ -769,7 +789,14 @@ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
769
789
  }
770
790
  if (layoutElement.type === "richtext") {
771
791
  const lineHeight = layoutElement.lineHeight ?? 1.2;
772
- let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight);
792
+ const elementStyle = {
793
+ font: layoutElement.font,
794
+ color: layoutElement.color,
795
+ background: layoutElement.background,
796
+ underline: layoutElement.underline,
797
+ strikethrough: layoutElement.strikethrough
798
+ };
799
+ let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight, elementStyle);
773
800
  if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
774
801
  lines = lines.slice(0, layoutElement.maxLines);
775
802
  if (layoutElement.ellipsis && lines.length > 0) {
@@ -1168,121 +1195,88 @@ function renderBox(ctx, node) {
1168
1195
  if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
1169
1196
  }
1170
1197
  //#endregion
1171
- //#region src/render/components/ProxiedCanvasContext.ts
1172
- /**
1173
- * ProxiedCanvasContext - Canvas 上下文代理类
1174
- *
1175
- * 该类提供对真实 CanvasRenderingContext2D 的代理,有以下功能:
1176
- * 1. 管理 save/restore 的平衡(计数器)
1177
- * 2. 追踪相对变换而不是绝对变换
1178
- * 3. 在析构时自动恢复所有未恢复的状态
1179
- * 4. 转发所有其他 Canvas API 调用
1180
- */
1181
- var ProxiedCanvasContext = class {
1182
- /**
1183
- * 真实的 Canvas 上下文
1184
- */
1185
- ctx;
1186
- /**
1187
- * 基础变换矩阵(初始化时设置,保持不变)
1188
- */
1198
+ //#region src/render/components/CustomDrawContext.ts
1199
+ function cloneMatrix(matrix) {
1200
+ return new DOMMatrix([
1201
+ matrix.a,
1202
+ matrix.b,
1203
+ matrix.c,
1204
+ matrix.d,
1205
+ matrix.e,
1206
+ matrix.f
1207
+ ]);
1208
+ }
1209
+ function toRelativeMatrix(transform) {
1210
+ if (transform === void 0) return new DOMMatrix();
1211
+ if (transform instanceof DOMMatrix) return cloneMatrix(transform);
1212
+ return new DOMMatrix(transform);
1213
+ }
1214
+ var ManagedCustomDrawContext = class {
1215
+ canvas;
1189
1216
  baseTransform;
1190
- /**
1191
- * 相对变换矩阵(用户通过 setTransform 设置)
1192
- */
1193
1217
  relativeTransform;
1194
- /**
1195
- * save/restore 计数器
1196
- */
1218
+ transformStack = [];
1197
1219
  saveCount = 0;
1198
- /**
1199
- * 构造函数
1200
- * @param ctx 真实的 CanvasRenderingContext2D
1201
- * @param baseTransform 初始的基础变换矩阵
1202
- */
1203
- constructor(ctx, baseTransform) {
1204
- this.ctx = ctx;
1205
- this.baseTransform = baseTransform;
1220
+ constructor(canvas, baseTransform) {
1221
+ this.canvas = canvas;
1222
+ this.baseTransform = cloneMatrix(baseTransform);
1206
1223
  this.relativeTransform = new DOMMatrix();
1207
1224
  }
1208
- /**
1209
- * save() - 保存当前状态并增加计数
1210
- */
1211
1225
  save() {
1212
1226
  this.saveCount++;
1213
- this.ctx.save();
1227
+ this.transformStack.push(cloneMatrix(this.relativeTransform));
1228
+ this.canvas.save();
1214
1229
  }
1215
- /**
1216
- * restore() - 恢复上一个状态并减少计数
1217
- */
1218
1230
  restore() {
1219
- if (this.saveCount > 0) {
1220
- this.saveCount--;
1221
- this.ctx.restore();
1222
- }
1231
+ if (this.saveCount === 0) return;
1232
+ this.saveCount--;
1233
+ this.relativeTransform = this.transformStack.pop() ?? new DOMMatrix();
1234
+ this.canvas.restore();
1235
+ this.applyRelativeTransform();
1223
1236
  }
1224
- /**
1225
- * setTransform() - 设置相对变换
1226
- */
1227
- setTransform(...args) {
1228
- let matrix;
1229
- if (args.length === 1 && args[0] instanceof DOMMatrix) matrix = args[0];
1230
- else if (args.length === 6) matrix = new DOMMatrix([
1231
- args[0],
1232
- args[1],
1233
- args[2],
1234
- args[3],
1235
- args[4],
1236
- args[5]
1237
- ]);
1238
- else return;
1239
- this.relativeTransform = matrix;
1240
- const actualTransform = this.baseTransform.multiply(matrix);
1241
- this.ctx.setTransform(actualTransform);
1242
- }
1243
- /**
1244
- * getTransform() - 返回相对变换(而不是绝对变换)
1245
- */
1246
1237
  getTransform() {
1247
- return this.relativeTransform;
1238
+ return cloneMatrix(this.relativeTransform);
1239
+ }
1240
+ setTransform(transform) {
1241
+ this.relativeTransform = toRelativeMatrix(transform);
1242
+ this.applyRelativeTransform();
1243
+ }
1244
+ resetTransform() {
1245
+ this.relativeTransform = new DOMMatrix();
1246
+ this.applyRelativeTransform();
1247
+ }
1248
+ translate(x, y) {
1249
+ this.relativeTransform = this.relativeTransform.translate(x, y);
1250
+ this.applyRelativeTransform();
1251
+ }
1252
+ rotate(angle) {
1253
+ this.relativeTransform = this.relativeTransform.rotate(angle * 180 / Math.PI);
1254
+ this.applyRelativeTransform();
1255
+ }
1256
+ scale(x, y) {
1257
+ this.relativeTransform = this.relativeTransform.scale(x, y ?? x);
1258
+ this.applyRelativeTransform();
1259
+ }
1260
+ transform(a, b, c, d, e, f) {
1261
+ this.relativeTransform = this.relativeTransform.multiply(new DOMMatrix([
1262
+ a,
1263
+ b,
1264
+ c,
1265
+ d,
1266
+ e,
1267
+ f
1268
+ ]));
1269
+ this.applyRelativeTransform();
1248
1270
  }
1249
- /**
1250
- * 析构函数级的清理 - 自动恢复所有未恢复的 save
1251
- */
1252
1271
  destroy() {
1253
- while (this.saveCount > 0) {
1254
- console.log("destroy restore", this.saveCount);
1255
- this.saveCount--;
1256
- this.ctx.restore();
1257
- }
1272
+ while (this.saveCount > 0) this.restore();
1273
+ }
1274
+ applyRelativeTransform() {
1275
+ this.canvas.setTransform(this.baseTransform.multiply(this.relativeTransform));
1258
1276
  }
1259
1277
  };
1260
- function createProxiedCanvasContext(ctx, baseTransform) {
1261
- const proxy = new ProxiedCanvasContext(ctx, baseTransform);
1262
- return new Proxy(proxy, {
1263
- get(target, prop, receiver) {
1264
- if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return Reflect.get(target, prop, receiver).bind(proxy);
1265
- const ownValue = Reflect.get(target, prop, receiver);
1266
- if (ownValue !== void 0) return ownValue;
1267
- const contextValue = target.ctx[prop];
1268
- if (typeof contextValue === "function") return contextValue.bind(target.ctx);
1269
- return contextValue;
1270
- },
1271
- set(target, prop, value, _receiver) {
1272
- target.ctx[prop] = value;
1273
- return true;
1274
- },
1275
- has(target, prop) {
1276
- if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return true;
1277
- return prop in target.ctx;
1278
- },
1279
- ownKeys(target) {
1280
- return Reflect.ownKeys(target.ctx);
1281
- },
1282
- getOwnPropertyDescriptor(target, prop) {
1283
- return Reflect.getOwnPropertyDescriptor(target.ctx, prop);
1284
- }
1285
- });
1278
+ function createCustomDrawContext(canvas, baseTransform) {
1279
+ return new ManagedCustomDrawContext(canvas, baseTransform);
1286
1280
  }
1287
1281
  //#endregion
1288
1282
  //#region src/render/components/customDraw.ts
@@ -1294,7 +1288,7 @@ function renderCustomDraw(ctx, node) {
1294
1288
  const element = node.element;
1295
1289
  ctx.save();
1296
1290
  ctx.translate(node.layout.x, node.layout.y);
1297
- const proxyCtx = createProxiedCanvasContext(ctx, ctx.getTransform());
1291
+ const customCtx = createCustomDrawContext(ctx, ctx.getTransform());
1298
1292
  const inner = () => {
1299
1293
  if (node.children && node.children.length > 0) {
1300
1294
  ctx.save();
@@ -1303,12 +1297,12 @@ function renderCustomDraw(ctx, node) {
1303
1297
  ctx.restore();
1304
1298
  }
1305
1299
  };
1306
- element.draw(proxyCtx, {
1300
+ element.draw(customCtx, {
1307
1301
  inner,
1308
1302
  width: node.layout.contentWidth,
1309
1303
  height: node.layout.contentHeight
1310
1304
  });
1311
- proxyCtx.destroy();
1305
+ customCtx.destroy();
1312
1306
  ctx.restore();
1313
1307
  }
1314
1308
  //#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 };
@@ -478,20 +478,40 @@ function buildFontString(font) {
478
478
  }
479
479
  //#endregion
480
480
  //#region src/layout/utils/measure.ts
481
+ const MEASURE_CACHE_LIMIT = 256;
481
482
  function createCanvasMeasureContext(ctx) {
483
+ const cache = /* @__PURE__ */ new Map();
484
+ let lastFontString = null;
482
485
  return { measureText(text, font) {
483
- ctx.font = buildFontString(font);
484
- ctx.textBaseline = "middle";
486
+ const fontString = buildFontString(font);
487
+ const key = fontString + "\0" + text;
488
+ const hit = cache.get(key);
489
+ if (hit !== void 0) {
490
+ cache.delete(key);
491
+ cache.set(key, hit);
492
+ return hit;
493
+ }
494
+ if (fontString !== lastFontString) {
495
+ ctx.font = fontString;
496
+ ctx.textBaseline = "middle";
497
+ lastFontString = fontString;
498
+ }
485
499
  const metrics = ctx.measureText(text);
486
500
  const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
487
501
  const fontSize = font.size || 16;
488
- return {
502
+ const result = {
489
503
  width: metrics.width,
490
504
  height: height || fontSize,
491
505
  offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
492
506
  ascent: metrics.actualBoundingBoxAscent,
493
507
  descent: metrics.actualBoundingBoxDescent
494
508
  };
509
+ if (cache.size >= MEASURE_CACHE_LIMIT) {
510
+ const oldest = cache.keys().next().value;
511
+ if (oldest !== void 0) cache.delete(oldest);
512
+ }
513
+ cache.set(key, result);
514
+ return result;
495
515
  } };
496
516
  }
497
517
  function wrapText(ctx, text, maxWidth, font) {
@@ -768,7 +788,14 @@ function computeLayoutImpl(element, ctx, constraints, x = 0, y = 0) {
768
788
  }
769
789
  if (layoutElement.type === "richtext") {
770
790
  const lineHeight = layoutElement.lineHeight ?? 1.2;
771
- let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight);
791
+ const elementStyle = {
792
+ font: layoutElement.font,
793
+ color: layoutElement.color,
794
+ background: layoutElement.background,
795
+ underline: layoutElement.underline,
796
+ strikethrough: layoutElement.strikethrough
797
+ };
798
+ let lines = wrapRichText(ctx, layoutElement.spans, contentWidth, lineHeight, elementStyle);
772
799
  if (layoutElement.maxLines && lines.length > layoutElement.maxLines) {
773
800
  lines = lines.slice(0, layoutElement.maxLines);
774
801
  if (layoutElement.ellipsis && lines.length > 0) {
@@ -1167,121 +1194,88 @@ function renderBox(ctx, node) {
1167
1194
  if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
1168
1195
  }
1169
1196
  //#endregion
1170
- //#region src/render/components/ProxiedCanvasContext.ts
1171
- /**
1172
- * ProxiedCanvasContext - Canvas 上下文代理类
1173
- *
1174
- * 该类提供对真实 CanvasRenderingContext2D 的代理,有以下功能:
1175
- * 1. 管理 save/restore 的平衡(计数器)
1176
- * 2. 追踪相对变换而不是绝对变换
1177
- * 3. 在析构时自动恢复所有未恢复的状态
1178
- * 4. 转发所有其他 Canvas API 调用
1179
- */
1180
- var ProxiedCanvasContext = class {
1181
- /**
1182
- * 真实的 Canvas 上下文
1183
- */
1184
- ctx;
1185
- /**
1186
- * 基础变换矩阵(初始化时设置,保持不变)
1187
- */
1197
+ //#region src/render/components/CustomDrawContext.ts
1198
+ function cloneMatrix(matrix) {
1199
+ return new DOMMatrix([
1200
+ matrix.a,
1201
+ matrix.b,
1202
+ matrix.c,
1203
+ matrix.d,
1204
+ matrix.e,
1205
+ matrix.f
1206
+ ]);
1207
+ }
1208
+ function toRelativeMatrix(transform) {
1209
+ if (transform === void 0) return new DOMMatrix();
1210
+ if (transform instanceof DOMMatrix) return cloneMatrix(transform);
1211
+ return new DOMMatrix(transform);
1212
+ }
1213
+ var ManagedCustomDrawContext = class {
1214
+ canvas;
1188
1215
  baseTransform;
1189
- /**
1190
- * 相对变换矩阵(用户通过 setTransform 设置)
1191
- */
1192
1216
  relativeTransform;
1193
- /**
1194
- * save/restore 计数器
1195
- */
1217
+ transformStack = [];
1196
1218
  saveCount = 0;
1197
- /**
1198
- * 构造函数
1199
- * @param ctx 真实的 CanvasRenderingContext2D
1200
- * @param baseTransform 初始的基础变换矩阵
1201
- */
1202
- constructor(ctx, baseTransform) {
1203
- this.ctx = ctx;
1204
- this.baseTransform = baseTransform;
1219
+ constructor(canvas, baseTransform) {
1220
+ this.canvas = canvas;
1221
+ this.baseTransform = cloneMatrix(baseTransform);
1205
1222
  this.relativeTransform = new DOMMatrix();
1206
1223
  }
1207
- /**
1208
- * save() - 保存当前状态并增加计数
1209
- */
1210
1224
  save() {
1211
1225
  this.saveCount++;
1212
- this.ctx.save();
1226
+ this.transformStack.push(cloneMatrix(this.relativeTransform));
1227
+ this.canvas.save();
1213
1228
  }
1214
- /**
1215
- * restore() - 恢复上一个状态并减少计数
1216
- */
1217
1229
  restore() {
1218
- if (this.saveCount > 0) {
1219
- this.saveCount--;
1220
- this.ctx.restore();
1221
- }
1230
+ if (this.saveCount === 0) return;
1231
+ this.saveCount--;
1232
+ this.relativeTransform = this.transformStack.pop() ?? new DOMMatrix();
1233
+ this.canvas.restore();
1234
+ this.applyRelativeTransform();
1222
1235
  }
1223
- /**
1224
- * setTransform() - 设置相对变换
1225
- */
1226
- setTransform(...args) {
1227
- let matrix;
1228
- if (args.length === 1 && args[0] instanceof DOMMatrix) matrix = args[0];
1229
- else if (args.length === 6) matrix = new DOMMatrix([
1230
- args[0],
1231
- args[1],
1232
- args[2],
1233
- args[3],
1234
- args[4],
1235
- args[5]
1236
- ]);
1237
- else return;
1238
- this.relativeTransform = matrix;
1239
- const actualTransform = this.baseTransform.multiply(matrix);
1240
- this.ctx.setTransform(actualTransform);
1241
- }
1242
- /**
1243
- * getTransform() - 返回相对变换(而不是绝对变换)
1244
- */
1245
1236
  getTransform() {
1246
- return this.relativeTransform;
1237
+ return cloneMatrix(this.relativeTransform);
1238
+ }
1239
+ setTransform(transform) {
1240
+ this.relativeTransform = toRelativeMatrix(transform);
1241
+ this.applyRelativeTransform();
1242
+ }
1243
+ resetTransform() {
1244
+ this.relativeTransform = new DOMMatrix();
1245
+ this.applyRelativeTransform();
1246
+ }
1247
+ translate(x, y) {
1248
+ this.relativeTransform = this.relativeTransform.translate(x, y);
1249
+ this.applyRelativeTransform();
1250
+ }
1251
+ rotate(angle) {
1252
+ this.relativeTransform = this.relativeTransform.rotate(angle * 180 / Math.PI);
1253
+ this.applyRelativeTransform();
1254
+ }
1255
+ scale(x, y) {
1256
+ this.relativeTransform = this.relativeTransform.scale(x, y ?? x);
1257
+ this.applyRelativeTransform();
1258
+ }
1259
+ transform(a, b, c, d, e, f) {
1260
+ this.relativeTransform = this.relativeTransform.multiply(new DOMMatrix([
1261
+ a,
1262
+ b,
1263
+ c,
1264
+ d,
1265
+ e,
1266
+ f
1267
+ ]));
1268
+ this.applyRelativeTransform();
1247
1269
  }
1248
- /**
1249
- * 析构函数级的清理 - 自动恢复所有未恢复的 save
1250
- */
1251
1270
  destroy() {
1252
- while (this.saveCount > 0) {
1253
- console.log("destroy restore", this.saveCount);
1254
- this.saveCount--;
1255
- this.ctx.restore();
1256
- }
1271
+ while (this.saveCount > 0) this.restore();
1272
+ }
1273
+ applyRelativeTransform() {
1274
+ this.canvas.setTransform(this.baseTransform.multiply(this.relativeTransform));
1257
1275
  }
1258
1276
  };
1259
- function createProxiedCanvasContext(ctx, baseTransform) {
1260
- const proxy = new ProxiedCanvasContext(ctx, baseTransform);
1261
- return new Proxy(proxy, {
1262
- get(target, prop, receiver) {
1263
- if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return Reflect.get(target, prop, receiver).bind(proxy);
1264
- const ownValue = Reflect.get(target, prop, receiver);
1265
- if (ownValue !== void 0) return ownValue;
1266
- const contextValue = target.ctx[prop];
1267
- if (typeof contextValue === "function") return contextValue.bind(target.ctx);
1268
- return contextValue;
1269
- },
1270
- set(target, prop, value, _receiver) {
1271
- target.ctx[prop] = value;
1272
- return true;
1273
- },
1274
- has(target, prop) {
1275
- if (prop === "save" || prop === "restore" || prop === "setTransform" || prop === "getTransform" || prop === "destroy") return true;
1276
- return prop in target.ctx;
1277
- },
1278
- ownKeys(target) {
1279
- return Reflect.ownKeys(target.ctx);
1280
- },
1281
- getOwnPropertyDescriptor(target, prop) {
1282
- return Reflect.getOwnPropertyDescriptor(target.ctx, prop);
1283
- }
1284
- });
1277
+ function createCustomDrawContext(canvas, baseTransform) {
1278
+ return new ManagedCustomDrawContext(canvas, baseTransform);
1285
1279
  }
1286
1280
  //#endregion
1287
1281
  //#region src/render/components/customDraw.ts
@@ -1293,7 +1287,7 @@ function renderCustomDraw(ctx, node) {
1293
1287
  const element = node.element;
1294
1288
  ctx.save();
1295
1289
  ctx.translate(node.layout.x, node.layout.y);
1296
- const proxyCtx = createProxiedCanvasContext(ctx, ctx.getTransform());
1290
+ const customCtx = createCustomDrawContext(ctx, ctx.getTransform());
1297
1291
  const inner = () => {
1298
1292
  if (node.children && node.children.length > 0) {
1299
1293
  ctx.save();
@@ -1302,12 +1296,12 @@ function renderCustomDraw(ctx, node) {
1302
1296
  ctx.restore();
1303
1297
  }
1304
1298
  };
1305
- element.draw(proxyCtx, {
1299
+ element.draw(customCtx, {
1306
1300
  inner,
1307
1301
  width: node.layout.contentWidth,
1308
1302
  height: node.layout.contentHeight
1309
1303
  });
1310
- proxyCtx.destroy();
1304
+ customCtx.destroy();
1311
1305
  ctx.restore();
1312
1306
  }
1313
1307
  //#endregion