declare-render 1.0.5 → 1.0.7

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.
Binary file
Binary file
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Declare-render examples: all test schemas in one file.
3
+ * Each schema is commented with what it demonstrates and which output file it produces.
4
+ */
5
+ import { Renderer } from "../src/index";
6
+ import { RenderData, RendererType, type ShapeRenderData } from "../src/types";
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import { fileURLToPath } from "url";
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Helper: create an arrow shape (used by arrow-test schema)
16
+ // ---------------------------------------------------------------------------
17
+ function createArrow(
18
+ id: string | number,
19
+ x: number,
20
+ y: number,
21
+ endX: number,
22
+ endY: number,
23
+ arrowLength: number = 20,
24
+ arrowAngle: number = Math.PI / 6,
25
+ ): ShapeRenderData {
26
+ const dx = endX - x;
27
+ const dy = endY - y;
28
+ const angle = Math.atan2(dy, dx);
29
+ const arrowX1 = endX - arrowLength * Math.cos(angle - arrowAngle);
30
+ const arrowY1 = endY - arrowLength * Math.sin(angle - arrowAngle);
31
+ const arrowX2 = endX - arrowLength * Math.cos(angle + arrowAngle);
32
+ const arrowY2 = endY - arrowLength * Math.sin(angle + arrowAngle);
33
+ const minX = Math.min(x, endX, arrowX1, arrowX2);
34
+ const minY = Math.min(y, endY, arrowY1, arrowY2);
35
+ const maxX = Math.max(x, endX, arrowX1, arrowX2);
36
+ const maxY = Math.max(y, endY, arrowY1, arrowY2);
37
+ const normalizedX = x - minX;
38
+ const normalizedY = y - minY;
39
+ const normalizedEndX = endX - minX;
40
+ const normalizedEndY = endY - minY;
41
+ const normalizedArrowX1 = arrowX1 - minX;
42
+ const normalizedArrowY1 = arrowY1 - minY;
43
+ const normalizedArrowX2 = arrowX2 - minX;
44
+ const normalizedArrowY2 = arrowY2 - minY;
45
+ return {
46
+ id,
47
+ type: RendererType.SHAPE,
48
+ x: minX,
49
+ y: minY,
50
+ width: maxX - minX,
51
+ height: maxY - minY,
52
+ style: {
53
+ strokeStyle: "#000000",
54
+ fillStyle: "#000000",
55
+ lineWidth: 3,
56
+ lineCap: "round",
57
+ lineJoin: "round",
58
+ },
59
+ shapes: [
60
+ { type: "beginPath" },
61
+ { type: "moveTo", x: normalizedX, y: normalizedY },
62
+ { type: "lineTo", x: normalizedEndX, y: normalizedEndY },
63
+ { type: "stroke" },
64
+ { type: "beginPath" },
65
+ { type: "moveTo", x: normalizedEndX, y: normalizedEndY },
66
+ { type: "lineTo", x: normalizedArrowX1, y: normalizedArrowY1 },
67
+ { type: "lineTo", x: normalizedArrowX2, y: normalizedArrowY2 },
68
+ { type: "closePath" },
69
+ { type: "fill" },
70
+ ],
71
+ };
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Schema 1: Shape test
76
+ // Purpose: Rectangles, circles (arc), triangles (lines), star, heart (bezier),
77
+ // smiley (multiple arcs), rotated rect, shadow, quadratic curves, dashed line,
78
+ // multiple shapes in one layer. Output: shape-test.png
79
+ // ---------------------------------------------------------------------------
80
+ const shapeTestSchema: RenderData = {
81
+ id: "shape-test",
82
+ width: 800,
83
+ height: 1200,
84
+ layers: [
85
+ {
86
+ id: "rect1",
87
+ type: RendererType.SHAPE,
88
+ x: 50,
89
+ y: 50,
90
+ width: 200,
91
+ height: 150,
92
+ style: { fillStyle: "#3498db", strokeStyle: "#2980b9", lineWidth: 3 },
93
+ shapes: [
94
+ { type: "fillRect", x: 0, y: 0, width: 200, height: 150 },
95
+ { type: "strokeRect", x: 0, y: 0, width: 200, height: 150 },
96
+ ],
97
+ } as ShapeRenderData,
98
+ {
99
+ id: "circle1",
100
+ type: RendererType.SHAPE,
101
+ x: 300,
102
+ y: 50,
103
+ width: 150,
104
+ height: 150,
105
+ style: { fillStyle: "#e74c3c", strokeStyle: "#c0392b", lineWidth: 2 },
106
+ shapes: [
107
+ { type: "beginPath" },
108
+ { type: "arc", x: 75, y: 75, radius: 70, startAngle: 0, endAngle: Math.PI * 2, counterclockwise: false },
109
+ { type: "fillAndStroke" },
110
+ ],
111
+ } as ShapeRenderData,
112
+ {
113
+ id: "triangle1",
114
+ type: RendererType.SHAPE,
115
+ x: 500,
116
+ y: 50,
117
+ width: 200,
118
+ height: 200,
119
+ style: { fillStyle: "#2ecc71", strokeStyle: "#27ae60", lineWidth: 4 },
120
+ shapes: [
121
+ { type: "beginPath" },
122
+ { type: "moveTo", x: 100, y: 0 },
123
+ { type: "lineTo", x: 200, y: 200 },
124
+ { type: "lineTo", x: 0, y: 200 },
125
+ { type: "closePath" },
126
+ { type: "fillAndStroke" },
127
+ ],
128
+ } as ShapeRenderData,
129
+ {
130
+ id: "star1",
131
+ type: RendererType.SHAPE,
132
+ x: 50,
133
+ y: 250,
134
+ width: 200,
135
+ height: 200,
136
+ style: { fillStyle: "#f39c12", strokeStyle: "#e67e22", lineWidth: 2 },
137
+ shapes: [
138
+ { type: "beginPath" },
139
+ { type: "moveTo", x: 100, y: 0 },
140
+ { type: "lineTo", x: 120, y: 70 },
141
+ { type: "lineTo", x: 200, y: 70 },
142
+ { type: "lineTo", x: 135, y: 115 },
143
+ { type: "lineTo", x: 160, y: 200 },
144
+ { type: "lineTo", x: 100, y: 150 },
145
+ { type: "lineTo", x: 40, y: 200 },
146
+ { type: "lineTo", x: 65, y: 115 },
147
+ { type: "lineTo", x: 0, y: 70 },
148
+ { type: "lineTo", x: 80, y: 70 },
149
+ { type: "closePath" },
150
+ { type: "fillAndStroke" },
151
+ ],
152
+ } as ShapeRenderData,
153
+ {
154
+ id: "heart1",
155
+ type: RendererType.SHAPE,
156
+ x: 300,
157
+ y: 250,
158
+ width: 200,
159
+ height: 180,
160
+ style: { fillStyle: "#e91e63", strokeStyle: "#c2185b", lineWidth: 2 },
161
+ shapes: [
162
+ { type: "beginPath" },
163
+ { type: "moveTo", x: 100, y: 60 },
164
+ { type: "bezierCurveTo", cp1x: 100, cp1y: 30, cp2x: 50, cp2y: 30, x: 50, y: 60 },
165
+ { type: "bezierCurveTo", cp1x: 50, cp1y: 100, cp2x: 100, cp2y: 140, x: 100, y: 180 },
166
+ { type: "bezierCurveTo", cp1x: 100, cp1y: 140, cp2x: 150, cp2y: 100, x: 150, y: 60 },
167
+ { type: "bezierCurveTo", cp1x: 150, cp1y: 30, cp2x: 100, cp2y: 30, x: 100, y: 60 },
168
+ { type: "fillAndStroke" },
169
+ ],
170
+ } as ShapeRenderData,
171
+ {
172
+ id: "smiley1",
173
+ type: RendererType.SHAPE,
174
+ x: 550,
175
+ y: 250,
176
+ width: 200,
177
+ height: 200,
178
+ style: { fillStyle: "#ffeb3b", strokeStyle: "#fbc02d", lineWidth: 3 },
179
+ shapes: [
180
+ { type: "beginPath" },
181
+ { type: "arc", x: 100, y: 100, radius: 80, startAngle: 0, endAngle: Math.PI * 2, counterclockwise: false },
182
+ { type: "fill" },
183
+ { type: "stroke" },
184
+ { type: "beginPath" },
185
+ { type: "arc", x: 70, y: 80, radius: 10, startAngle: 0, endAngle: Math.PI * 2, counterclockwise: false },
186
+ { type: "fill" },
187
+ { type: "beginPath" },
188
+ { type: "arc", x: 130, y: 80, radius: 10, startAngle: 0, endAngle: Math.PI * 2, counterclockwise: false },
189
+ { type: "fill" },
190
+ { type: "beginPath" },
191
+ { type: "arc", x: 100, y: 100, radius: 50, startAngle: 0, endAngle: Math.PI, counterclockwise: false },
192
+ { type: "stroke" },
193
+ ],
194
+ } as ShapeRenderData,
195
+ {
196
+ id: "rotated-rect",
197
+ type: RendererType.SHAPE,
198
+ x: 50,
199
+ y: 500,
200
+ width: 150,
201
+ height: 100,
202
+ rotate: 45,
203
+ style: { fillStyle: "#9b59b6", strokeStyle: "#8e44ad", lineWidth: 2 },
204
+ shapes: [
205
+ { type: "fillRect", x: 0, y: 0, width: 150, height: 100 },
206
+ { type: "strokeRect", x: 0, y: 0, width: 150, height: 100 },
207
+ ],
208
+ } as ShapeRenderData,
209
+ {
210
+ id: "shadow-shape",
211
+ type: RendererType.SHAPE,
212
+ x: 250,
213
+ y: 500,
214
+ width: 150,
215
+ height: 150,
216
+ style: { fillStyle: "#16a085", strokeStyle: "#138d75", lineWidth: 2 },
217
+ shadow: { color: "rgba(0, 0, 0, 0.5)", blur: 10, X: 5, Y: 5 },
218
+ shapes: [
219
+ { type: "beginPath" },
220
+ { type: "arc", x: 75, y: 75, radius: 60, startAngle: 0, endAngle: Math.PI * 2, counterclockwise: false },
221
+ { type: "fillAndStroke" },
222
+ ],
223
+ } as ShapeRenderData,
224
+ {
225
+ id: "curve-shape",
226
+ type: RendererType.SHAPE,
227
+ x: 450,
228
+ y: 500,
229
+ width: 200,
230
+ height: 150,
231
+ style: { fillStyle: "#34495e", strokeStyle: "#2c3e50", lineWidth: 3, lineCap: "round", lineJoin: "round" },
232
+ shapes: [
233
+ { type: "beginPath" },
234
+ { type: "moveTo", x: 0, y: 75 },
235
+ { type: "quadraticCurveTo", cp1x: 50, cp1y: 0, x: 100, y: 75 },
236
+ { type: "quadraticCurveTo", cp1x: 150, cp1y: 150, x: 200, y: 75 },
237
+ { type: "stroke" },
238
+ ],
239
+ } as ShapeRenderData,
240
+ {
241
+ id: "dashed-rect",
242
+ type: RendererType.SHAPE,
243
+ x: 50,
244
+ y: 700,
245
+ width: 200,
246
+ height: 150,
247
+ style: { strokeStyle: "#e74c3c", lineWidth: 4, lineDash: [10, 5], lineDashOffset: 0 },
248
+ shapes: [{ type: "strokeRect", x: 0, y: 0, width: 200, height: 150 }],
249
+ } as ShapeRenderData,
250
+ {
251
+ id: "multi-shape",
252
+ type: RendererType.SHAPE,
253
+ x: 300,
254
+ y: 700,
255
+ width: 300,
256
+ height: 200,
257
+ style: { fillStyle: "#95a5a6", strokeStyle: "#7f8c8d", lineWidth: 2 },
258
+ shapes: [
259
+ { type: "fillRect", x: 0, y: 0, width: 300, height: 200 },
260
+ { type: "beginPath" },
261
+ { type: "arc", x: 75, y: 75, radius: 50, startAngle: 0, endAngle: Math.PI * 2, counterclockwise: false },
262
+ { type: "fill" },
263
+ { type: "fillRect", x: 150, y: 25, width: 100, height: 100 },
264
+ { type: "beginPath" },
265
+ { type: "moveTo", x: 275, y: 25 },
266
+ { type: "lineTo", x: 300, y: 125 },
267
+ { type: "lineTo", x: 250, y: 125 },
268
+ { type: "closePath" },
269
+ { type: "fill" },
270
+ ],
271
+ } as ShapeRenderData,
272
+ ],
273
+ output: { type: "png" },
274
+ };
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Schema 2: Arrow test
278
+ // Purpose: Directional arrows (createArrow), curved arrow, circles/rects/triangles,
279
+ // arrows pointing to shapes, flow diagram. Output: arrow-test.png
280
+ // ---------------------------------------------------------------------------
281
+ const arrowTestSchema: RenderData = {
282
+ id: "arrow-test",
283
+ width: 800,
284
+ height: 600,
285
+ layers: [
286
+ createArrow("arrow1", 50, 100, 200, 100, 25),
287
+ createArrow("arrow2", 250, 50, 250, 200, 25),
288
+ createArrow("arrow3", 50, 300, 200, 200, 30),
289
+ createArrow("arrow4", 50, 400, 200, 500, 30),
290
+ {
291
+ id: "curved-arrow",
292
+ type: RendererType.SHAPE,
293
+ x: 300,
294
+ y: 100,
295
+ width: 200,
296
+ height: 200,
297
+ style: { strokeStyle: "#000000", fillStyle: "#000000", lineWidth: 3, lineCap: "round" },
298
+ shapes: [
299
+ { type: "beginPath" },
300
+ { type: "moveTo", x: 0, y: 0 },
301
+ { type: "bezierCurveTo", cp1x: 50, cp1y: 0, cp2x: 150, cp2y: 200, x: 200, y: 200 },
302
+ { type: "stroke" },
303
+ { type: "beginPath" },
304
+ { type: "moveTo", x: 200, y: 200 },
305
+ { type: "lineTo", x: 180, y: 185 },
306
+ { type: "lineTo", x: 185, y: 195 },
307
+ { type: "closePath" },
308
+ { type: "fill" },
309
+ ],
310
+ } as ShapeRenderData,
311
+ {
312
+ id: "circle1",
313
+ type: RendererType.SHAPE,
314
+ x: 550,
315
+ y: 50,
316
+ width: 100,
317
+ height: 100,
318
+ style: { fillStyle: "#000000", strokeStyle: "#000000", lineWidth: 2 },
319
+ shapes: [
320
+ { type: "beginPath" },
321
+ { type: "arc", x: 50, y: 50, radius: 45, startAngle: 0, endAngle: Math.PI * 2, counterclockwise: false },
322
+ { type: "fillAndStroke" },
323
+ ],
324
+ } as ShapeRenderData,
325
+ {
326
+ id: "rect1",
327
+ type: RendererType.SHAPE,
328
+ x: 550,
329
+ y: 200,
330
+ width: 150,
331
+ height: 100,
332
+ style: { fillStyle: "#000000", strokeStyle: "#000000", lineWidth: 2 },
333
+ shapes: [
334
+ { type: "fillRect", x: 0, y: 0, width: 150, height: 100 },
335
+ { type: "strokeRect", x: 0, y: 0, width: 150, height: 100 },
336
+ ],
337
+ } as ShapeRenderData,
338
+ {
339
+ id: "triangle1",
340
+ type: RendererType.SHAPE,
341
+ x: 550,
342
+ y: 350,
343
+ width: 150,
344
+ height: 150,
345
+ style: { fillStyle: "#000000", strokeStyle: "#000000", lineWidth: 2 },
346
+ shapes: [
347
+ { type: "beginPath" },
348
+ { type: "moveTo", x: 75, y: 0 },
349
+ { type: "lineTo", x: 150, y: 150 },
350
+ { type: "lineTo", x: 0, y: 150 },
351
+ { type: "closePath" },
352
+ { type: "fillAndStroke" },
353
+ ],
354
+ } as ShapeRenderData,
355
+ createArrow("arrow-to-circle", 300, 350, 550, 100, 30),
356
+ createArrow("arrow-to-rect", 300, 400, 550, 250, 30),
357
+ createArrow("arrow-to-triangle", 300, 450, 550, 425, 30),
358
+ createArrow("flow1", 50, 550, 150, 550, 20),
359
+ createArrow("flow2", 150, 550, 250, 550, 20),
360
+ createArrow("flow3", 250, 550, 350, 550, 20),
361
+ createArrow("flow4", 350, 550, 450, 550, 20),
362
+ ],
363
+ output: { type: "png" },
364
+ };
365
+
366
+ // ---------------------------------------------------------------------------
367
+ // Schema 3: Canvas-1 test
368
+ // Purpose: Minimal agent-style layout: circle + square + arrow line, with
369
+ // per-command style (fillStyle/strokeStyle on fill/stroke commands). Output: canvas-1-test.png
370
+ // ---------------------------------------------------------------------------
371
+ const canvas1Schema: RenderData = {
372
+ id: "canvas-1",
373
+ width: 400,
374
+ height: 300,
375
+ layers: [
376
+ {
377
+ id: "circle",
378
+ type: "shape",
379
+ x: 120,
380
+ y: 150,
381
+ shapes: [
382
+ { type: "beginPath" },
383
+ { type: "arc", x: 0, y: 0, radius: 40, startAngle: 0, endAngle: 6.283185307179586, counterclockwise: false },
384
+ { type: "closePath" },
385
+ { type: "fill", style: { fillStyle: "#4A90D9" } },
386
+ ],
387
+ },
388
+ {
389
+ id: "square",
390
+ type: "shape",
391
+ x: 280,
392
+ y: 150,
393
+ shapes: [
394
+ { type: "beginPath" },
395
+ { type: "rect", x: -40, y: -40, width: 80, height: 80 },
396
+ { type: "closePath" },
397
+ { type: "fill", style: { fillStyle: "#4A90D9" } },
398
+ ],
399
+ },
400
+ {
401
+ id: "arrow-line",
402
+ type: "shape",
403
+ x: 0,
404
+ y: 0,
405
+ shapes: [
406
+ { type: "beginPath" },
407
+ { type: "moveTo", x: 160, y: 150 },
408
+ { type: "lineTo", x: 240, y: 150 },
409
+ { type: "stroke", style: { strokeStyle: "#333333", lineWidth: 3 } },
410
+ { type: "beginPath" },
411
+ { type: "moveTo", x: 240, y: 150 },
412
+ { type: "lineTo", x: 230, y: 140 },
413
+ { type: "lineTo", x: 230, y: 160 },
414
+ { type: "closePath" },
415
+ { type: "fill", style: { strokeStyle: "#333333", lineWidth: 3 } },
416
+ ],
417
+ },
418
+ ],
419
+ };
420
+
421
+ // ---------------------------------------------------------------------------
422
+ // Run all tests: render each schema and write to output/<name>.png
423
+ // ---------------------------------------------------------------------------
424
+ const TESTS: { name: string; schema: RenderData }[] = [
425
+ { name: "shape-test", schema: shapeTestSchema },
426
+ { name: "arrow-test", schema: arrowTestSchema },
427
+ { name: "canvas-1-test", schema: canvas1Schema },
428
+ ];
429
+
430
+ async function main() {
431
+ const outputDir = path.join(__dirname, "output");
432
+ if (!fs.existsSync(outputDir)) {
433
+ fs.mkdirSync(outputDir, { recursive: true });
434
+ }
435
+
436
+ for (const { name, schema } of TESTS) {
437
+ try {
438
+ const renderer = new Renderer(schema);
439
+ const buffer = await renderer.draw();
440
+ const outputPath = path.join(outputDir, `${name}.png`);
441
+ fs.writeFileSync(outputPath, buffer);
442
+ console.log(`Rendered: ${outputPath}`);
443
+ } catch (error) {
444
+ console.error(`Error rendering ${name}:`, error);
445
+ if (error instanceof Error) console.error(error.stack);
446
+ process.exit(1);
447
+ }
448
+ }
449
+
450
+ console.log("All tests passed.");
451
+ }
452
+
453
+ main().catch((error) => {
454
+ console.error("Unhandled error:", error);
455
+ process.exit(1);
456
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "declare-render",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "You can declare canvas shapes by JSON format.",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -24,9 +24,6 @@
24
24
  "type": "module",
25
25
  "scripts": {
26
26
  "rebuild:canvas": "pnpm rebuild canvas",
27
- "test:shape": "tsx examples/shape-test.ts",
28
- "test:shape:ts-node": "NODE_OPTIONS='--loader ts-node/esm' node examples/shape-test.ts",
29
- "test:arrow": "tsx examples/arrow-test.ts",
30
- "test:arrow:ts-node": "NODE_OPTIONS='--loader ts-node/esm' node examples/arrow-test.ts"
27
+ "test": "tsx examples/test.ts"
31
28
  }
32
29
  }
@@ -1,5 +1,5 @@
1
1
  import { BaseRender } from "../base-renderer";
2
- import type { ShapeRenderData, ShapeCommand } from "../../types";
2
+ import type { ShapeRenderData, ShapeCommand, ShapeStyle } from "../../types";
3
3
  import { cloneDeep, isNumber } from "lodash-es";
4
4
  import { type CanvasRenderingContext2D } from "canvas";
5
5
 
@@ -20,8 +20,8 @@ export class ShapeRender extends BaseRender<ShapeRenderData> {
20
20
  return this.getContainerCoordinates();
21
21
  }
22
22
 
23
- private applyStyle() {
24
- const style = this.data.style;
23
+ /** Apply style to ctx. Used for layer style (inherit) and per-command override. */
24
+ private applyStyle(style: ShapeStyle | undefined) {
25
25
  if (!style) return;
26
26
 
27
27
  if (style.fillStyle) {
@@ -236,15 +236,17 @@ export class ShapeRender extends BaseRender<ShapeRenderData> {
236
236
  const offsetY = y;
237
237
 
238
238
  const drawImpl = () => {
239
- this.ctx.save();
240
- this.applyStyle();
241
239
  this.applyShadow();
242
240
 
241
+ this.applyStyle(this.data.style);
242
+ this.ctx.save();
243
243
  for (const cmd of this.data.shapes) {
244
+ if ("style" in cmd && cmd.style) {
245
+ this.applyStyle(cmd.style);
246
+ }
244
247
  this.executeCommand(cmd, offsetX, offsetY);
248
+ this.ctx.restore();
245
249
  }
246
-
247
- this.ctx.restore();
248
250
  };
249
251
 
250
252
  if (!rotate) {
package/src/index.ts CHANGED
@@ -6,6 +6,8 @@ export type {
6
6
  ContainerRenderData,
7
7
  ImgRenderData,
8
8
  ShapeRenderData,
9
+ ShapeStyle,
10
+ ShapeCommand,
9
11
  TextRenderData,
10
12
  RenderData,
11
13
  } from "./types";
package/src/types.ts CHANGED
@@ -71,6 +71,19 @@ export interface ContainerRenderData {
71
71
  layers: ChildRenderers;
72
72
  }
73
73
 
74
+ /** Style for shape layer or per-command override. Commands inherit layer style; command.style overrides for that command only. */
75
+ export interface ShapeStyle {
76
+ fillStyle?: string;
77
+ strokeStyle?: string;
78
+ lineWidth?: number;
79
+ lineCap?: "butt" | "round" | "square";
80
+ lineJoin?: "bevel" | "round" | "miter";
81
+ miterLimit?: number;
82
+ lineDash?: number[];
83
+ lineDashOffset?: number;
84
+ globalAlpha?: number;
85
+ }
86
+
74
87
  export interface ShapeRenderData {
75
88
  id: string | number;
76
89
  type: "shape";
@@ -79,17 +92,7 @@ export interface ShapeRenderData {
79
92
  width?: number;
80
93
  height?: number;
81
94
  rotate?: number;
82
- style?: {
83
- fillStyle?: string;
84
- strokeStyle?: string;
85
- lineWidth?: number;
86
- lineCap?: "butt" | "round" | "square";
87
- lineJoin?: "bevel" | "round" | "miter";
88
- miterLimit?: number;
89
- lineDash?: number[];
90
- lineDashOffset?: number;
91
- globalAlpha?: number;
92
- };
95
+ style?: ShapeStyle;
93
96
  shadow?: {
94
97
  color: string;
95
98
  blur: number;
@@ -100,14 +103,14 @@ export interface ShapeRenderData {
100
103
  }
101
104
 
102
105
  export type ShapeCommand =
103
- | { type: "rect"; x: number; y: number; width: number; height: number }
104
- | { type: "fillRect"; x: number; y: number; width: number; height: number }
105
- | { type: "strokeRect"; x: number; y: number; width: number; height: number }
106
- | { type: "clearRect"; x: number; y: number; width: number; height: number }
107
- | { type: "beginPath" }
108
- | { type: "closePath" }
109
- | { type: "moveTo"; x: number; y: number }
110
- | { type: "lineTo"; x: number; y: number }
106
+ | { type: "rect"; x: number; y: number; width: number; height: number; style?: ShapeStyle }
107
+ | { type: "fillRect"; x: number; y: number; width: number; height: number; style?: ShapeStyle }
108
+ | { type: "strokeRect"; x: number; y: number; width: number; height: number; style?: ShapeStyle }
109
+ | { type: "clearRect"; x: number; y: number; width: number; height: number; style?: ShapeStyle }
110
+ | { type: "beginPath"; style?: ShapeStyle }
111
+ | { type: "closePath"; style?: ShapeStyle }
112
+ | { type: "moveTo"; x: number; y: number; style?: ShapeStyle }
113
+ | { type: "lineTo"; x: number; y: number; style?: ShapeStyle }
111
114
  | {
112
115
  type: "arc";
113
116
  x: number;
@@ -116,6 +119,7 @@ export type ShapeCommand =
116
119
  startAngle: number;
117
120
  endAngle: number;
118
121
  counterclockwise?: boolean;
122
+ style?: ShapeStyle;
119
123
  }
120
124
  | {
121
125
  type: "arcTo";
@@ -124,6 +128,7 @@ export type ShapeCommand =
124
128
  x2: number;
125
129
  y2: number;
126
130
  radius: number;
131
+ style?: ShapeStyle;
127
132
  }
128
133
  | {
129
134
  type: "quadraticCurveTo";
@@ -131,6 +136,7 @@ export type ShapeCommand =
131
136
  cp1y: number;
132
137
  x: number;
133
138
  y: number;
139
+ style?: ShapeStyle;
134
140
  }
135
141
  | {
136
142
  type: "bezierCurveTo";
@@ -140,10 +146,11 @@ export type ShapeCommand =
140
146
  cp2y: number;
141
147
  x: number;
142
148
  y: number;
149
+ style?: ShapeStyle;
143
150
  }
144
- | { type: "fill" }
145
- | { type: "stroke" }
146
- | { type: "fillAndStroke" };
151
+ | { type: "fill"; style?: ShapeStyle }
152
+ | { type: "stroke"; style?: ShapeStyle }
153
+ | { type: "fillAndStroke"; style?: ShapeStyle };
147
154
 
148
155
  export type ChildRenderers = (
149
156
  | ImgRenderData
@@ -180,7 +187,7 @@ ContainerRenderData: { "id": string|number, "type": "container", "x": number, "y
180
187
 
181
188
  ShapeRenderData: { "id": string|number, "type": "shape", "x": number, "y": number, "width"?: number, "height"?: number, "rotate"?: number, "style"?: { "fillStyle"?: string, "strokeStyle"?: string, "lineWidth"?: number, "lineCap"?: "butt"|"round"|"square", "lineJoin"?: "bevel"|"round"|"miter", "miterLimit"?: number, "lineDash"?: number[], "lineDashOffset"?: number, "globalAlpha"?: number }, "shadow"?: { "color": string, "blur": number, "X": number, "Y": number }, "shapes": Array<ShapeCommand> }
182
189
 
183
- ShapeCommand: { "type": "rect"|"fillRect"|"strokeRect"|"clearRect"|"beginPath"|"closePath"|"moveTo"|"lineTo"|"arc"|"arcTo"|"quadraticCurveTo"|"bezierCurveTo"|"fill"|"stroke"|"fillAndStroke", ...additional properties based on type }
190
+ ShapeCommand: { "type": "rect"|"fillRect"|"strokeRect"|"clearRect"|"beginPath"|"closePath"|"moveTo"|"lineTo"|"arc"|"arcTo"|"quadraticCurveTo"|"bezierCurveTo"|"fill"|"stroke"|"fillAndStroke", "style"?: ShapeStyle (optional; overrides layer style for this command), ...additional properties based on type }
184
191
  `.trim();
185
192
 
186
193
  // ----- Metrics (canvas-dependent) -----
@@ -1,252 +0,0 @@
1
- import { Renderer } from "../src/index";
2
- import { RenderData, RendererType, type ShapeRenderData } from "../src/types";
3
- import * as fs from "fs";
4
- import * as path from "path";
5
- import { fileURLToPath } from "url";
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
-
10
- // Helper function to create an arrow shape
11
- function createArrow(
12
- id: string | number,
13
- x: number,
14
- y: number,
15
- endX: number,
16
- endY: number,
17
- arrowLength: number = 20,
18
- arrowAngle: number = Math.PI / 6, // 30 degrees
19
- ): ShapeRenderData {
20
- // Calculate arrow direction
21
- const dx = endX - x;
22
- const dy = endY - y;
23
- const angle = Math.atan2(dy, dx);
24
-
25
- // Calculate arrowhead points
26
- const arrowX1 = endX - arrowLength * Math.cos(angle - arrowAngle);
27
- const arrowY1 = endY - arrowLength * Math.sin(angle - arrowAngle);
28
- const arrowX2 = endX - arrowLength * Math.cos(angle + arrowAngle);
29
- const arrowY2 = endY - arrowLength * Math.sin(angle + arrowAngle);
30
-
31
- // Calculate bounds
32
- const minX = Math.min(x, endX, arrowX1, arrowX2);
33
- const minY = Math.min(y, endY, arrowY1, arrowY2);
34
- const maxX = Math.max(x, endX, arrowX1, arrowX2);
35
- const maxY = Math.max(y, endY, arrowY1, arrowY2);
36
-
37
- // Normalize coordinates relative to shape position
38
- const normalizedX = x - minX;
39
- const normalizedY = y - minY;
40
- const normalizedEndX = endX - minX;
41
- const normalizedEndY = endY - minY;
42
- const normalizedArrowX1 = arrowX1 - minX;
43
- const normalizedArrowY1 = arrowY1 - minY;
44
- const normalizedArrowX2 = arrowX2 - minX;
45
- const normalizedArrowY2 = arrowY2 - minY;
46
-
47
- return {
48
- id,
49
- type: RendererType.SHAPE,
50
- x: minX,
51
- y: minY,
52
- width: maxX - minX,
53
- height: maxY - minY,
54
- style: {
55
- strokeStyle: "#000000",
56
- fillStyle: "#000000",
57
- lineWidth: 3,
58
- lineCap: "round",
59
- lineJoin: "round",
60
- },
61
- shapes: [
62
- { type: "beginPath" },
63
- { type: "moveTo", x: normalizedX, y: normalizedY },
64
- { type: "lineTo", x: normalizedEndX, y: normalizedEndY },
65
- { type: "stroke" },
66
- // Arrowhead
67
- { type: "beginPath" },
68
- { type: "moveTo", x: normalizedEndX, y: normalizedEndY },
69
- { type: "lineTo", x: normalizedArrowX1, y: normalizedArrowY1 },
70
- { type: "lineTo", x: normalizedArrowX2, y: normalizedArrowY2 },
71
- { type: "closePath" },
72
- { type: "fill" },
73
- ],
74
- };
75
- }
76
-
77
- // Test file with black shapes including arrows
78
- const arrowTestSchema: RenderData = {
79
- id: "arrow-test",
80
- width: 800,
81
- height: 600,
82
- layers: [
83
- // Horizontal arrow pointing right
84
- createArrow("arrow1", 50, 100, 200, 100, 25),
85
-
86
- // Vertical arrow pointing down
87
- createArrow("arrow2", 250, 50, 250, 200, 25),
88
-
89
- // Diagonal arrow pointing up-right
90
- createArrow("arrow3", 50, 300, 200, 200, 30),
91
-
92
- // Diagonal arrow pointing down-right
93
- createArrow("arrow4", 50, 400, 200, 500, 30),
94
-
95
- // Curved arrow (using bezier curve)
96
- {
97
- id: "curved-arrow",
98
- type: RendererType.SHAPE,
99
- x: 300,
100
- y: 100,
101
- width: 200,
102
- height: 200,
103
- style: {
104
- strokeStyle: "#000000",
105
- fillStyle: "#000000",
106
- lineWidth: 3,
107
- lineCap: "round",
108
- },
109
- shapes: [
110
- { type: "beginPath" },
111
- { type: "moveTo", x: 0, y: 0 },
112
- {
113
- type: "bezierCurveTo",
114
- cp1x: 50,
115
- cp1y: 0,
116
- cp2x: 150,
117
- cp2y: 200,
118
- x: 200,
119
- y: 200,
120
- },
121
- { type: "stroke" },
122
- // Arrowhead at end
123
- { type: "beginPath" },
124
- { type: "moveTo", x: 200, y: 200 },
125
- { type: "lineTo", x: 180, y: 185 },
126
- { type: "lineTo", x: 185, y: 195 },
127
- { type: "closePath" },
128
- { type: "fill" },
129
- ],
130
- } as ShapeRenderData,
131
-
132
- // Simple black circle
133
- {
134
- id: "circle1",
135
- type: RendererType.SHAPE,
136
- x: 550,
137
- y: 50,
138
- width: 100,
139
- height: 100,
140
- style: {
141
- fillStyle: "#000000",
142
- strokeStyle: "#000000",
143
- lineWidth: 2,
144
- },
145
- shapes: [
146
- { type: "beginPath" },
147
- {
148
- type: "arc",
149
- x: 50,
150
- y: 50,
151
- radius: 45,
152
- startAngle: 0,
153
- endAngle: Math.PI * 2,
154
- counterclockwise: false,
155
- },
156
- { type: "fillAndStroke" },
157
- ],
158
- } as ShapeRenderData,
159
-
160
- // Black rectangle
161
- {
162
- id: "rect1",
163
- type: RendererType.SHAPE,
164
- x: 550,
165
- y: 200,
166
- width: 150,
167
- height: 100,
168
- style: {
169
- fillStyle: "#000000",
170
- strokeStyle: "#000000",
171
- lineWidth: 2,
172
- },
173
- shapes: [
174
- { type: "fillRect", x: 0, y: 0, width: 150, height: 100 },
175
- { type: "strokeRect", x: 0, y: 0, width: 150, height: 100 },
176
- ],
177
- } as ShapeRenderData,
178
-
179
- // Black triangle
180
- {
181
- id: "triangle1",
182
- type: RendererType.SHAPE,
183
- x: 550,
184
- y: 350,
185
- width: 150,
186
- height: 150,
187
- style: {
188
- fillStyle: "#000000",
189
- strokeStyle: "#000000",
190
- lineWidth: 2,
191
- },
192
- shapes: [
193
- { type: "beginPath" },
194
- { type: "moveTo", x: 75, y: 0 },
195
- { type: "lineTo", x: 150, y: 150 },
196
- { type: "lineTo", x: 0, y: 150 },
197
- { type: "closePath" },
198
- { type: "fillAndStroke" },
199
- ],
200
- } as ShapeRenderData,
201
-
202
- // Arrow pointing to the circle
203
- createArrow("arrow-to-circle", 300, 350, 550, 100, 30),
204
-
205
- // Arrow pointing to the rectangle
206
- createArrow("arrow-to-rect", 300, 400, 550, 250, 30),
207
-
208
- // Arrow pointing to the triangle
209
- createArrow("arrow-to-triangle", 300, 450, 550, 425, 30),
210
-
211
- // Multiple arrows forming a flow diagram
212
- createArrow("flow1", 50, 550, 150, 550, 20),
213
- createArrow("flow2", 150, 550, 250, 550, 20),
214
- createArrow("flow3", 250, 550, 350, 550, 20),
215
- createArrow("flow4", 350, 550, 450, 550, 20),
216
- ],
217
- output: {
218
- type: "png",
219
- },
220
- };
221
-
222
- async function main() {
223
- try {
224
- console.log("Creating renderer...");
225
- const renderer = new Renderer(arrowTestSchema);
226
- console.log("Drawing shapes...");
227
- const buffer = await renderer.draw();
228
- const outputPath = path.join(__dirname, "output", "arrow-test.png");
229
-
230
- // Ensure output directory exists
231
- const outputDir = path.dirname(outputPath);
232
- if (!fs.existsSync(outputDir)) {
233
- fs.mkdirSync(outputDir, { recursive: true });
234
- }
235
-
236
- console.log("Writing file...");
237
- fs.writeFileSync(outputPath, buffer);
238
- console.log(`Arrow test rendered successfully: ${outputPath}`);
239
- } catch (error) {
240
- console.error("Error rendering arrow test:");
241
- console.error(error);
242
- if (error instanceof Error) {
243
- console.error("Stack:", error.stack);
244
- }
245
- process.exit(1);
246
- }
247
- }
248
-
249
- main().catch((error) => {
250
- console.error("Unhandled error:", error);
251
- process.exit(1);
252
- });
@@ -1,411 +0,0 @@
1
- import { Renderer } from "../src/index";
2
- import { RenderData, RendererType, type ShapeRenderData } from "../src/types";
3
- import * as fs from "fs";
4
- import * as path from "path";
5
- import { fileURLToPath } from "url";
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
-
10
- // Test various canvas shapes
11
- const shapeTestSchema: RenderData = {
12
- id: "shape-test",
13
- width: 800,
14
- height: 1200,
15
- layers: [
16
- // Rectangle shapes
17
- {
18
- id: "rect1",
19
- type: RendererType.SHAPE,
20
- x: 50,
21
- y: 50,
22
- width: 200,
23
- height: 150,
24
- style: {
25
- fillStyle: "#3498db",
26
- strokeStyle: "#2980b9",
27
- lineWidth: 3,
28
- },
29
- shapes: [
30
- { type: "fillRect", x: 0, y: 0, width: 200, height: 150 },
31
- { type: "strokeRect", x: 0, y: 0, width: 200, height: 150 },
32
- ],
33
- } as ShapeRenderData,
34
-
35
- // Circle using arc
36
- {
37
- id: "circle1",
38
- type: RendererType.SHAPE,
39
- x: 300,
40
- y: 50,
41
- width: 150,
42
- height: 150,
43
- style: {
44
- fillStyle: "#e74c3c",
45
- strokeStyle: "#c0392b",
46
- lineWidth: 2,
47
- },
48
- shapes: [
49
- { type: "beginPath" },
50
- {
51
- type: "arc",
52
- x: 75,
53
- y: 75,
54
- radius: 70,
55
- startAngle: 0,
56
- endAngle: Math.PI * 2,
57
- counterclockwise: false,
58
- },
59
- { type: "fillAndStroke" },
60
- ],
61
- } as ShapeRenderData,
62
-
63
- // Triangle using lines
64
- {
65
- id: "triangle1",
66
- type: RendererType.SHAPE,
67
- x: 500,
68
- y: 50,
69
- width: 200,
70
- height: 200,
71
- style: {
72
- fillStyle: "#2ecc71",
73
- strokeStyle: "#27ae60",
74
- lineWidth: 4,
75
- },
76
- shapes: [
77
- { type: "beginPath" },
78
- { type: "moveTo", x: 100, y: 0 },
79
- { type: "lineTo", x: 200, y: 200 },
80
- { type: "lineTo", x: 0, y: 200 },
81
- { type: "closePath" },
82
- { type: "fillAndStroke" },
83
- ],
84
- } as ShapeRenderData,
85
-
86
- // Star shape using lines
87
- {
88
- id: "star1",
89
- type: RendererType.SHAPE,
90
- x: 50,
91
- y: 250,
92
- width: 200,
93
- height: 200,
94
- style: {
95
- fillStyle: "#f39c12",
96
- strokeStyle: "#e67e22",
97
- lineWidth: 2,
98
- },
99
- shapes: [
100
- { type: "beginPath" },
101
- { type: "moveTo", x: 100, y: 0 },
102
- { type: "lineTo", x: 120, y: 70 },
103
- { type: "lineTo", x: 200, y: 70 },
104
- { type: "lineTo", x: 135, y: 115 },
105
- { type: "lineTo", x: 160, y: 200 },
106
- { type: "lineTo", x: 100, y: 150 },
107
- { type: "lineTo", x: 40, y: 200 },
108
- { type: "lineTo", x: 65, y: 115 },
109
- { type: "lineTo", x: 0, y: 70 },
110
- { type: "lineTo", x: 80, y: 70 },
111
- { type: "closePath" },
112
- { type: "fillAndStroke" },
113
- ],
114
- } as ShapeRenderData,
115
-
116
- // Heart shape using bezier curves
117
- {
118
- id: "heart1",
119
- type: RendererType.SHAPE,
120
- x: 300,
121
- y: 250,
122
- width: 200,
123
- height: 180,
124
- style: {
125
- fillStyle: "#e91e63",
126
- strokeStyle: "#c2185b",
127
- lineWidth: 2,
128
- },
129
- shapes: [
130
- { type: "beginPath" },
131
- { type: "moveTo", x: 100, y: 60 },
132
- {
133
- type: "bezierCurveTo",
134
- cp1x: 100,
135
- cp1y: 30,
136
- cp2x: 50,
137
- cp2y: 30,
138
- x: 50,
139
- y: 60,
140
- },
141
- {
142
- type: "bezierCurveTo",
143
- cp1x: 50,
144
- cp1y: 100,
145
- cp2x: 100,
146
- cp2y: 140,
147
- x: 100,
148
- y: 180,
149
- },
150
- {
151
- type: "bezierCurveTo",
152
- cp1x: 100,
153
- cp1y: 140,
154
- cp2x: 150,
155
- cp2y: 100,
156
- x: 150,
157
- y: 60,
158
- },
159
- {
160
- type: "bezierCurveTo",
161
- cp1x: 150,
162
- cp1y: 30,
163
- cp2x: 100,
164
- cp2y: 30,
165
- x: 100,
166
- y: 60,
167
- },
168
- { type: "fillAndStroke" },
169
- ],
170
- } as ShapeRenderData,
171
-
172
- // Smiley face using arcs
173
- {
174
- id: "smiley1",
175
- type: RendererType.SHAPE,
176
- x: 550,
177
- y: 250,
178
- width: 200,
179
- height: 200,
180
- style: {
181
- fillStyle: "#ffeb3b",
182
- strokeStyle: "#fbc02d",
183
- lineWidth: 3,
184
- },
185
- shapes: [
186
- { type: "beginPath" },
187
- {
188
- type: "arc",
189
- x: 100,
190
- y: 100,
191
- radius: 80,
192
- startAngle: 0,
193
- endAngle: Math.PI * 2,
194
- counterclockwise: false,
195
- },
196
- { type: "fill" },
197
- { type: "stroke" },
198
- // Left eye
199
- { type: "beginPath" },
200
- {
201
- type: "arc",
202
- x: 70,
203
- y: 80,
204
- radius: 10,
205
- startAngle: 0,
206
- endAngle: Math.PI * 2,
207
- counterclockwise: false,
208
- },
209
- { type: "fill" },
210
- // Right eye
211
- { type: "beginPath" },
212
- {
213
- type: "arc",
214
- x: 130,
215
- y: 80,
216
- radius: 10,
217
- startAngle: 0,
218
- endAngle: Math.PI * 2,
219
- counterclockwise: false,
220
- },
221
- { type: "fill" },
222
- // Mouth
223
- { type: "beginPath" },
224
- {
225
- type: "arc",
226
- x: 100,
227
- y: 100,
228
- radius: 50,
229
- startAngle: 0,
230
- endAngle: Math.PI,
231
- counterclockwise: false,
232
- },
233
- { type: "stroke" },
234
- ],
235
- } as ShapeRenderData,
236
-
237
- // Rotated rectangle
238
- {
239
- id: "rotated-rect",
240
- type: RendererType.SHAPE,
241
- x: 50,
242
- y: 500,
243
- width: 150,
244
- height: 100,
245
- rotate: 45,
246
- style: {
247
- fillStyle: "#9b59b6",
248
- strokeStyle: "#8e44ad",
249
- lineWidth: 2,
250
- },
251
- shapes: [
252
- { type: "fillRect", x: 0, y: 0, width: 150, height: 100 },
253
- { type: "strokeRect", x: 0, y: 0, width: 150, height: 100 },
254
- ],
255
- } as ShapeRenderData,
256
-
257
- // Shape with shadow
258
- {
259
- id: "shadow-shape",
260
- type: RendererType.SHAPE,
261
- x: 250,
262
- y: 500,
263
- width: 150,
264
- height: 150,
265
- style: {
266
- fillStyle: "#16a085",
267
- strokeStyle: "#138d75",
268
- lineWidth: 2,
269
- },
270
- shadow: {
271
- color: "rgba(0, 0, 0, 0.5)",
272
- blur: 10,
273
- X: 5,
274
- Y: 5,
275
- },
276
- shapes: [
277
- { type: "beginPath" },
278
- {
279
- type: "arc",
280
- x: 75,
281
- y: 75,
282
- radius: 60,
283
- startAngle: 0,
284
- endAngle: Math.PI * 2,
285
- counterclockwise: false,
286
- },
287
- { type: "fillAndStroke" },
288
- ],
289
- } as ShapeRenderData,
290
-
291
- // Complex path with quadratic curves
292
- {
293
- id: "curve-shape",
294
- type: RendererType.SHAPE,
295
- x: 450,
296
- y: 500,
297
- width: 200,
298
- height: 150,
299
- style: {
300
- fillStyle: "#34495e",
301
- strokeStyle: "#2c3e50",
302
- lineWidth: 3,
303
- lineCap: "round",
304
- lineJoin: "round",
305
- },
306
- shapes: [
307
- { type: "beginPath" },
308
- { type: "moveTo", x: 0, y: 75 },
309
- {
310
- type: "quadraticCurveTo",
311
- cp1x: 50,
312
- cp1y: 0,
313
- x: 100,
314
- y: 75,
315
- },
316
- {
317
- type: "quadraticCurveTo",
318
- cp1x: 150,
319
- cp1y: 150,
320
- x: 200,
321
- y: 75,
322
- },
323
- { type: "stroke" },
324
- ],
325
- } as ShapeRenderData,
326
-
327
- // Dashed line rectangle
328
- {
329
- id: "dashed-rect",
330
- type: RendererType.SHAPE,
331
- x: 50,
332
- y: 700,
333
- width: 200,
334
- height: 150,
335
- style: {
336
- strokeStyle: "#e74c3c",
337
- lineWidth: 4,
338
- lineDash: [10, 5],
339
- lineDashOffset: 0,
340
- },
341
- shapes: [
342
- { type: "strokeRect", x: 0, y: 0, width: 200, height: 150 },
343
- ],
344
- } as ShapeRenderData,
345
-
346
- // Multiple shapes in one renderer (same style)
347
- {
348
- id: "multi-shape",
349
- type: RendererType.SHAPE,
350
- x: 300,
351
- y: 700,
352
- width: 300,
353
- height: 200,
354
- style: {
355
- fillStyle: "#95a5a6",
356
- strokeStyle: "#7f8c8d",
357
- lineWidth: 2,
358
- },
359
- shapes: [
360
- // Background rectangle
361
- { type: "fillRect", x: 0, y: 0, width: 300, height: 200 },
362
- // Circle
363
- { type: "beginPath" },
364
- {
365
- type: "arc",
366
- x: 75,
367
- y: 75,
368
- radius: 50,
369
- startAngle: 0,
370
- endAngle: Math.PI * 2,
371
- counterclockwise: false,
372
- },
373
- { type: "fill" },
374
- // Square
375
- { type: "fillRect", x: 150, y: 25, width: 100, height: 100 },
376
- // Triangle
377
- { type: "beginPath" },
378
- { type: "moveTo", x: 275, y: 25 },
379
- { type: "lineTo", x: 300, y: 125 },
380
- { type: "lineTo", x: 250, y: 125 },
381
- { type: "closePath" },
382
- { type: "fill" },
383
- ],
384
- } as ShapeRenderData,
385
- ],
386
- output: {
387
- type: "png",
388
- },
389
- };
390
-
391
- async function main() {
392
- try {
393
- const renderer = new Renderer(shapeTestSchema);
394
- const buffer = await renderer.draw();
395
- const outputPath = path.join(__dirname, "output", "shape-test.png");
396
-
397
- // Ensure output directory exists
398
- const outputDir = path.dirname(outputPath);
399
- if (!fs.existsSync(outputDir)) {
400
- fs.mkdirSync(outputDir, { recursive: true });
401
- }
402
-
403
- fs.writeFileSync(outputPath, buffer);
404
- console.log(`Shape test rendered successfully: ${outputPath}`);
405
- } catch (error) {
406
- console.error("Error rendering shape test:", error);
407
- process.exit(1);
408
- }
409
- }
410
-
411
- main();