declare-render 1.0.2 → 1.0.4

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.
@@ -0,0 +1,252 @@
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
+ });
Binary file
Binary file
@@ -0,0 +1,411 @@
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "declare-render",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "You can declare canvas shapes by JSON format.",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -23,6 +23,10 @@
23
23
  },
24
24
  "type": "module",
25
25
  "scripts": {
26
- "rebuild:canvas": "pnpm rebuild canvas"
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
31
  }
28
32
  }
@@ -1,9 +1,16 @@
1
- import { ContainerRenderData } from "./container-renderer";
2
- import { ImgRenderData } from "./img-renderer";
3
- import { TextRenderData } from "./text-renderer/types";
1
+ import type {
2
+ ContainerRenderData,
3
+ ImgRenderData,
4
+ ShapeRenderData,
5
+ TextRenderData,
6
+ } from "../types";
4
7
 
5
8
  export abstract class BaseRender<
6
- T extends TextRenderData | ImgRenderData | ContainerRenderData,
9
+ T extends
10
+ | TextRenderData
11
+ | ImgRenderData
12
+ | ContainerRenderData
13
+ | ShapeRenderData,
7
14
  > {
8
15
  protected data!: T;
9
16
  abstract get container(): { x1: number; x2: number; y1: number; y2: number };