declare-render 1.0.3 → 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.
- package/examples/arrow-test.ts +252 -0
- package/examples/output/arrow-test.png +0 -0
- package/examples/output/shape-test.png +0 -0
- package/examples/shape-test.ts +411 -0
- package/package.json +6 -2
- package/src/canvas-renderers/base-renderer.ts +6 -1
- package/src/canvas-renderers/container-renderer/index.ts +11 -7
- package/src/canvas-renderers/shape-render/index.ts +279 -0
- package/src/index.ts +3 -2
- package/src/types.ts +92 -3
- package/tsconfig.json +5 -3
- package/examples/sunscreen-guide.ts +0 -206
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
ChildRenderers,
|
|
3
3
|
ImgRenderData,
|
|
4
4
|
RendererType,
|
|
5
|
+
ShapeRenderData,
|
|
5
6
|
TextRenderData,
|
|
6
7
|
} from "../../types";
|
|
7
8
|
import type { ContainerRenderData } from "../../types";
|
|
@@ -10,12 +11,13 @@ import { cloneDeep, isNumber, isObject, isUndefined } from "lodash-es";
|
|
|
10
11
|
import { BaseRender } from "../base-renderer";
|
|
11
12
|
import { ImgRender } from "../img-renderer/index";
|
|
12
13
|
import { TextRender } from "../text-renderer/index";
|
|
14
|
+
import { ShapeRender } from "../shape-render/index";
|
|
13
15
|
|
|
14
16
|
export type { ContainerRenderData } from "../../types";
|
|
15
17
|
|
|
16
18
|
export class ContainerRenderer extends BaseRender<ContainerRenderData> {
|
|
17
19
|
private ctx: CanvasRenderingContext2D
|
|
18
|
-
private renderers: Array<ImgRender | TextRender | ContainerRenderer> = []
|
|
20
|
+
private renderers: Array<ImgRender | TextRender | ShapeRender | ContainerRenderer> = []
|
|
19
21
|
|
|
20
22
|
constructor(ctx: CanvasRenderingContext2D, data: ContainerRenderData) {
|
|
21
23
|
super()
|
|
@@ -29,13 +31,15 @@ export class ContainerRenderer extends BaseRender<ContainerRenderData> {
|
|
|
29
31
|
return this.getContainerCoordinates()
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
private createRenderer = (renderData: ImgRenderData | TextRenderData | ContainerRenderData) => {
|
|
34
|
+
private createRenderer = (renderData: ImgRenderData | TextRenderData | ShapeRenderData | ContainerRenderData) => {
|
|
33
35
|
switch (renderData.type) {
|
|
34
36
|
case RendererType.TEXT: {
|
|
35
37
|
return new TextRender(this.ctx, renderData, { inFlexFlow: isUndefined(renderData.x) || isUndefined(renderData.y) })
|
|
36
38
|
}
|
|
37
39
|
case RendererType.IMG:
|
|
38
40
|
return new ImgRender(this.ctx, renderData)
|
|
41
|
+
case RendererType.SHAPE:
|
|
42
|
+
return new ShapeRender(this.ctx, renderData)
|
|
39
43
|
case RendererType.CONTAINER:
|
|
40
44
|
return new ContainerRenderer(this.ctx, renderData)
|
|
41
45
|
default:
|
|
@@ -45,10 +49,10 @@ export class ContainerRenderer extends BaseRender<ContainerRenderData> {
|
|
|
45
49
|
|
|
46
50
|
// TODO center the renderers, vertical center, left align, wrap the renderers;
|
|
47
51
|
public layout = async () => {
|
|
48
|
-
const layoutedRenderers = [] as Array<ImgRender | TextRender | ContainerRenderer>
|
|
52
|
+
const layoutedRenderers = [] as Array<ImgRender | TextRender | ShapeRender | ContainerRenderer>
|
|
49
53
|
|
|
50
54
|
for (const index in this.data.renderers) {
|
|
51
|
-
const renderData = cloneDeep(this.data.renderers[index]) as ImgRenderData | TextRenderData | ContainerRenderData
|
|
55
|
+
const renderData = cloneDeep(this.data.renderers[index]) as ImgRenderData | TextRenderData | ShapeRenderData | ContainerRenderData
|
|
52
56
|
|
|
53
57
|
let renderX = renderData.x
|
|
54
58
|
let renderY = renderData.y
|
|
@@ -63,7 +67,7 @@ export class ContainerRenderer extends BaseRender<ContainerRenderData> {
|
|
|
63
67
|
|
|
64
68
|
// justify
|
|
65
69
|
if (isUndefined(renderX) || isUndefined(renderY)) {
|
|
66
|
-
const preRenderer = layoutedRenderers.at(-1) as undefined | ImgRender | TextRender | ContainerRenderer
|
|
70
|
+
const preRenderer = layoutedRenderers.at(-1) as undefined | ImgRender | TextRender | ShapeRender | ContainerRenderer
|
|
67
71
|
const { x2, y1, x1, y2 } = preRenderer?.container || { x1: this.data.x, x2: this.data.x, y1: this.data.y, y2: this.data.y }
|
|
68
72
|
|
|
69
73
|
const gapX = !preRenderer ?
|
|
@@ -132,8 +136,8 @@ export class ContainerRenderer extends BaseRender<ContainerRenderData> {
|
|
|
132
136
|
// TODO handle the renderers wrapping, add gap;
|
|
133
137
|
private getContainerCoordinates = () => {
|
|
134
138
|
if (!this.renderers.length) throw new Error("can not get container coordinates before layouted")
|
|
135
|
-
const firstChild = this.renderers.at(0) as ImgRender | TextRender
|
|
136
|
-
const lastChild = this.renderers.at(-1) as ImgRender | TextRender
|
|
139
|
+
const firstChild = this.renderers.at(0) as ImgRender | TextRender | ShapeRender
|
|
140
|
+
const lastChild = this.renderers.at(-1) as ImgRender | TextRender | ShapeRender
|
|
137
141
|
return {
|
|
138
142
|
x1: firstChild.container.x1,
|
|
139
143
|
y1: firstChild.container.y1,
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { BaseRender } from "../base-renderer";
|
|
2
|
+
import type { ShapeRenderData, ShapeCommand } from "../../types";
|
|
3
|
+
import { cloneDeep, isNumber } from "lodash-es";
|
|
4
|
+
import { type CanvasRenderingContext2D } from "canvas";
|
|
5
|
+
|
|
6
|
+
export type { ShapeRenderData } from "../../types";
|
|
7
|
+
|
|
8
|
+
export class ShapeRender extends BaseRender<ShapeRenderData> {
|
|
9
|
+
private ctx: CanvasRenderingContext2D;
|
|
10
|
+
private computedWidth: number = 0;
|
|
11
|
+
private computedHeight: number = 0;
|
|
12
|
+
|
|
13
|
+
constructor(ctx: CanvasRenderingContext2D, data: ShapeRenderData) {
|
|
14
|
+
super();
|
|
15
|
+
this.ctx = ctx;
|
|
16
|
+
this.data = cloneDeep(data);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get container() {
|
|
20
|
+
return this.getContainerCoordinates();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private applyStyle() {
|
|
24
|
+
const style = this.data.style;
|
|
25
|
+
if (!style) return;
|
|
26
|
+
|
|
27
|
+
if (style.fillStyle) {
|
|
28
|
+
this.ctx.fillStyle = style.fillStyle;
|
|
29
|
+
}
|
|
30
|
+
if (style.strokeStyle) {
|
|
31
|
+
this.ctx.strokeStyle = style.strokeStyle;
|
|
32
|
+
}
|
|
33
|
+
if (isNumber(style.lineWidth)) {
|
|
34
|
+
this.ctx.lineWidth = style.lineWidth;
|
|
35
|
+
}
|
|
36
|
+
if (style.lineCap) {
|
|
37
|
+
this.ctx.lineCap = style.lineCap;
|
|
38
|
+
}
|
|
39
|
+
if (style.lineJoin) {
|
|
40
|
+
this.ctx.lineJoin = style.lineJoin;
|
|
41
|
+
}
|
|
42
|
+
if (isNumber(style.miterLimit)) {
|
|
43
|
+
this.ctx.miterLimit = style.miterLimit;
|
|
44
|
+
}
|
|
45
|
+
if (style.lineDash) {
|
|
46
|
+
this.ctx.setLineDash(style.lineDash);
|
|
47
|
+
}
|
|
48
|
+
if (isNumber(style.lineDashOffset)) {
|
|
49
|
+
this.ctx.lineDashOffset = style.lineDashOffset;
|
|
50
|
+
}
|
|
51
|
+
if (isNumber(style.globalAlpha)) {
|
|
52
|
+
this.ctx.globalAlpha = style.globalAlpha;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private applyShadow() {
|
|
57
|
+
if (this.data.shadow) {
|
|
58
|
+
this.ctx.shadowBlur = this.data.shadow.blur;
|
|
59
|
+
this.ctx.shadowColor = this.data.shadow.color;
|
|
60
|
+
this.ctx.shadowOffsetX = this.data.shadow.X;
|
|
61
|
+
this.ctx.shadowOffsetY = this.data.shadow.Y;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private executeCommand(cmd: ShapeCommand, offsetX: number, offsetY: number) {
|
|
66
|
+
switch (cmd.type) {
|
|
67
|
+
case "rect":
|
|
68
|
+
this.ctx.rect(cmd.x + offsetX, cmd.y + offsetY, cmd.width, cmd.height);
|
|
69
|
+
break;
|
|
70
|
+
case "fillRect":
|
|
71
|
+
this.ctx.fillRect(
|
|
72
|
+
cmd.x + offsetX,
|
|
73
|
+
cmd.y + offsetY,
|
|
74
|
+
cmd.width,
|
|
75
|
+
cmd.height,
|
|
76
|
+
);
|
|
77
|
+
break;
|
|
78
|
+
case "strokeRect":
|
|
79
|
+
this.ctx.strokeRect(
|
|
80
|
+
cmd.x + offsetX,
|
|
81
|
+
cmd.y + offsetY,
|
|
82
|
+
cmd.width,
|
|
83
|
+
cmd.height,
|
|
84
|
+
);
|
|
85
|
+
break;
|
|
86
|
+
case "clearRect":
|
|
87
|
+
this.ctx.clearRect(
|
|
88
|
+
cmd.x + offsetX,
|
|
89
|
+
cmd.y + offsetY,
|
|
90
|
+
cmd.width,
|
|
91
|
+
cmd.height,
|
|
92
|
+
);
|
|
93
|
+
break;
|
|
94
|
+
case "beginPath":
|
|
95
|
+
this.ctx.beginPath();
|
|
96
|
+
break;
|
|
97
|
+
case "closePath":
|
|
98
|
+
this.ctx.closePath();
|
|
99
|
+
break;
|
|
100
|
+
case "moveTo":
|
|
101
|
+
this.ctx.moveTo(cmd.x + offsetX, cmd.y + offsetY);
|
|
102
|
+
break;
|
|
103
|
+
case "lineTo":
|
|
104
|
+
this.ctx.lineTo(cmd.x + offsetX, cmd.y + offsetY);
|
|
105
|
+
break;
|
|
106
|
+
case "arc":
|
|
107
|
+
this.ctx.arc(
|
|
108
|
+
cmd.x + offsetX,
|
|
109
|
+
cmd.y + offsetY,
|
|
110
|
+
cmd.radius,
|
|
111
|
+
cmd.startAngle,
|
|
112
|
+
cmd.endAngle,
|
|
113
|
+
cmd.counterclockwise || false,
|
|
114
|
+
);
|
|
115
|
+
break;
|
|
116
|
+
case "arcTo":
|
|
117
|
+
this.ctx.arcTo(
|
|
118
|
+
cmd.x1 + offsetX,
|
|
119
|
+
cmd.y1 + offsetY,
|
|
120
|
+
cmd.x2 + offsetX,
|
|
121
|
+
cmd.y2 + offsetY,
|
|
122
|
+
cmd.radius,
|
|
123
|
+
);
|
|
124
|
+
break;
|
|
125
|
+
case "quadraticCurveTo":
|
|
126
|
+
this.ctx.quadraticCurveTo(
|
|
127
|
+
cmd.cp1x + offsetX,
|
|
128
|
+
cmd.cp1y + offsetY,
|
|
129
|
+
cmd.x + offsetX,
|
|
130
|
+
cmd.y + offsetY,
|
|
131
|
+
);
|
|
132
|
+
break;
|
|
133
|
+
case "bezierCurveTo":
|
|
134
|
+
this.ctx.bezierCurveTo(
|
|
135
|
+
cmd.cp1x + offsetX,
|
|
136
|
+
cmd.cp1y + offsetY,
|
|
137
|
+
cmd.cp2x + offsetX,
|
|
138
|
+
cmd.cp2y + offsetY,
|
|
139
|
+
cmd.x + offsetX,
|
|
140
|
+
cmd.y + offsetY,
|
|
141
|
+
);
|
|
142
|
+
break;
|
|
143
|
+
case "fill":
|
|
144
|
+
this.ctx.fill();
|
|
145
|
+
break;
|
|
146
|
+
case "stroke":
|
|
147
|
+
this.ctx.stroke();
|
|
148
|
+
break;
|
|
149
|
+
case "fillAndStroke":
|
|
150
|
+
this.ctx.fill();
|
|
151
|
+
this.ctx.stroke();
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private computeBounds(): { width: number; height: number } {
|
|
157
|
+
if (this.data.width && this.data.height) {
|
|
158
|
+
return { width: this.data.width, height: this.data.height };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!this.data.shapes || this.data.shapes.length === 0) {
|
|
162
|
+
return { width: 0, height: 0 };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let minX = Infinity;
|
|
166
|
+
let minY = Infinity;
|
|
167
|
+
let maxX = -Infinity;
|
|
168
|
+
let maxY = -Infinity;
|
|
169
|
+
|
|
170
|
+
for (const cmd of this.data.shapes) {
|
|
171
|
+
switch (cmd.type) {
|
|
172
|
+
case "rect":
|
|
173
|
+
case "fillRect":
|
|
174
|
+
case "strokeRect":
|
|
175
|
+
case "clearRect":
|
|
176
|
+
minX = Math.min(minX, cmd.x);
|
|
177
|
+
minY = Math.min(minY, cmd.y);
|
|
178
|
+
maxX = Math.max(maxX, cmd.x + cmd.width);
|
|
179
|
+
maxY = Math.max(maxY, cmd.y + cmd.height);
|
|
180
|
+
break;
|
|
181
|
+
case "moveTo":
|
|
182
|
+
case "lineTo":
|
|
183
|
+
minX = Math.min(minX, cmd.x);
|
|
184
|
+
minY = Math.min(minY, cmd.y);
|
|
185
|
+
maxX = Math.max(maxX, cmd.x);
|
|
186
|
+
maxY = Math.max(maxY, cmd.y);
|
|
187
|
+
break;
|
|
188
|
+
case "arc":
|
|
189
|
+
minX = Math.min(minX, cmd.x - cmd.radius);
|
|
190
|
+
minY = Math.min(minY, cmd.y - cmd.radius);
|
|
191
|
+
maxX = Math.max(maxX, cmd.x + cmd.radius);
|
|
192
|
+
maxY = Math.max(maxY, cmd.y + cmd.radius);
|
|
193
|
+
break;
|
|
194
|
+
case "arcTo":
|
|
195
|
+
minX = Math.min(minX, cmd.x1, cmd.x2);
|
|
196
|
+
minY = Math.min(minY, cmd.y1, cmd.y2);
|
|
197
|
+
maxX = Math.max(maxX, cmd.x1, cmd.x2);
|
|
198
|
+
maxY = Math.max(maxY, cmd.y1, cmd.y2);
|
|
199
|
+
break;
|
|
200
|
+
case "quadraticCurveTo":
|
|
201
|
+
case "bezierCurveTo":
|
|
202
|
+
minX = Math.min(minX, cmd.cp1x, cmd.x);
|
|
203
|
+
minY = Math.min(minY, cmd.cp1y, cmd.y);
|
|
204
|
+
maxX = Math.max(maxX, cmd.cp1x, cmd.x);
|
|
205
|
+
maxY = Math.max(maxY, cmd.cp1y, cmd.y);
|
|
206
|
+
if (cmd.type === "bezierCurveTo") {
|
|
207
|
+
minX = Math.min(minX, cmd.cp2x);
|
|
208
|
+
minY = Math.min(minY, cmd.cp2y);
|
|
209
|
+
maxX = Math.max(maxX, cmd.cp2x);
|
|
210
|
+
maxY = Math.max(maxY, cmd.cp2y);
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (minX === Infinity) {
|
|
217
|
+
return { width: 0, height: 0 };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
width: maxX - minX,
|
|
222
|
+
height: maxY - minY,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
public layout = async () => {
|
|
227
|
+
const bounds = this.computeBounds();
|
|
228
|
+
this.computedWidth = this.data.width ?? bounds.width;
|
|
229
|
+
this.computedHeight = this.data.height ?? bounds.height;
|
|
230
|
+
return this;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
public draw = async () => {
|
|
234
|
+
const { x, y, rotate } = this.data;
|
|
235
|
+
const offsetX = x;
|
|
236
|
+
const offsetY = y;
|
|
237
|
+
|
|
238
|
+
const drawImpl = () => {
|
|
239
|
+
this.ctx.save();
|
|
240
|
+
this.applyStyle();
|
|
241
|
+
this.applyShadow();
|
|
242
|
+
|
|
243
|
+
for (const cmd of this.data.shapes) {
|
|
244
|
+
this.executeCommand(cmd, offsetX, offsetY);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
this.ctx.restore();
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
if (!rotate) {
|
|
251
|
+
drawImpl();
|
|
252
|
+
} else {
|
|
253
|
+
const { computedWidth, computedHeight } = this;
|
|
254
|
+
const centerX = x + computedWidth / 2;
|
|
255
|
+
const centerY = y + computedHeight / 2;
|
|
256
|
+
|
|
257
|
+
this.ctx.save();
|
|
258
|
+
this.ctx.translate(centerX, centerY);
|
|
259
|
+
this.ctx.rotate((rotate * Math.PI) / 180);
|
|
260
|
+
this.ctx.translate(-centerX, -centerY);
|
|
261
|
+
|
|
262
|
+
drawImpl();
|
|
263
|
+
|
|
264
|
+
this.ctx.restore();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return this;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
public getContainerCoordinates = () => {
|
|
271
|
+
const { x, y } = this.data;
|
|
272
|
+
return {
|
|
273
|
+
x1: x,
|
|
274
|
+
y1: y,
|
|
275
|
+
x2: x + this.computedWidth,
|
|
276
|
+
y2: y + this.computedHeight,
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { ContainerRenderer } from "./canvas-renderers/container-renderer/index";
|
|
|
5
5
|
export type {
|
|
6
6
|
ContainerRenderData,
|
|
7
7
|
ImgRenderData,
|
|
8
|
+
ShapeRenderData,
|
|
8
9
|
TextRenderData,
|
|
9
10
|
RenderData,
|
|
10
11
|
} from "./types";
|
|
@@ -23,7 +24,7 @@ export class Renderer {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
draw = async () => {
|
|
26
|
-
if (!this.schema.
|
|
27
|
+
if (!this.schema.layers.length) {
|
|
27
28
|
throw new Error("[Renderer] empty canvas with no renderers");
|
|
28
29
|
}
|
|
29
30
|
const container = new ContainerRenderer(this.ctx, {
|
|
@@ -33,7 +34,7 @@ export class Renderer {
|
|
|
33
34
|
y: 0,
|
|
34
35
|
width: this.canvas.width,
|
|
35
36
|
height: this.canvas.height,
|
|
36
|
-
renderers: this.schema.
|
|
37
|
+
renderers: this.schema.layers,
|
|
37
38
|
});
|
|
38
39
|
await container.layout().then((d) => d.draw());
|
|
39
40
|
return this.toBuffer();
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,7 @@ export enum RendererType {
|
|
|
4
4
|
CONTAINER = "container",
|
|
5
5
|
TEXT = "text",
|
|
6
6
|
IMG = "img",
|
|
7
|
+
SHAPE = "shape",
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
// ----- TypeScript types (primitives only) -----
|
|
@@ -70,13 +71,97 @@ export interface ContainerRenderData {
|
|
|
70
71
|
renderers: ChildRenderers;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
export
|
|
74
|
+
export interface ShapeRenderData {
|
|
75
|
+
id: string | number;
|
|
76
|
+
type: "shape";
|
|
77
|
+
x: number;
|
|
78
|
+
y: number;
|
|
79
|
+
width?: number;
|
|
80
|
+
height?: number;
|
|
81
|
+
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
|
+
};
|
|
93
|
+
shadow?: {
|
|
94
|
+
color: string;
|
|
95
|
+
blur: number;
|
|
96
|
+
X: number;
|
|
97
|
+
Y: number;
|
|
98
|
+
};
|
|
99
|
+
shapes: ShapeCommand[];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
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 }
|
|
111
|
+
| {
|
|
112
|
+
type: "arc";
|
|
113
|
+
x: number;
|
|
114
|
+
y: number;
|
|
115
|
+
radius: number;
|
|
116
|
+
startAngle: number;
|
|
117
|
+
endAngle: number;
|
|
118
|
+
counterclockwise?: boolean;
|
|
119
|
+
}
|
|
120
|
+
| {
|
|
121
|
+
type: "arcTo";
|
|
122
|
+
x1: number;
|
|
123
|
+
y1: number;
|
|
124
|
+
x2: number;
|
|
125
|
+
y2: number;
|
|
126
|
+
radius: number;
|
|
127
|
+
}
|
|
128
|
+
| {
|
|
129
|
+
type: "quadraticCurveTo";
|
|
130
|
+
cp1x: number;
|
|
131
|
+
cp1y: number;
|
|
132
|
+
x: number;
|
|
133
|
+
y: number;
|
|
134
|
+
}
|
|
135
|
+
| {
|
|
136
|
+
type: "bezierCurveTo";
|
|
137
|
+
cp1x: number;
|
|
138
|
+
cp1y: number;
|
|
139
|
+
cp2x: number;
|
|
140
|
+
cp2y: number;
|
|
141
|
+
x: number;
|
|
142
|
+
y: number;
|
|
143
|
+
}
|
|
144
|
+
| { type: "fill" }
|
|
145
|
+
| { type: "stroke" }
|
|
146
|
+
| { type: "fillAndStroke" };
|
|
147
|
+
|
|
148
|
+
export type ChildRenderers = (
|
|
149
|
+
| ImgRenderData
|
|
150
|
+
| TextRenderData
|
|
151
|
+
| ContainerRenderData
|
|
152
|
+
| ShapeRenderData
|
|
153
|
+
)[];
|
|
74
154
|
|
|
75
155
|
export interface RenderData {
|
|
76
156
|
id: string;
|
|
77
157
|
width: number;
|
|
78
158
|
height: number;
|
|
79
|
-
|
|
159
|
+
layers: (
|
|
160
|
+
| ImgRenderData
|
|
161
|
+
| TextRenderData
|
|
162
|
+
| ContainerRenderData
|
|
163
|
+
| ShapeRenderData
|
|
164
|
+
)[];
|
|
80
165
|
output?: {
|
|
81
166
|
type?: "png" | "jpg";
|
|
82
167
|
};
|
|
@@ -85,13 +170,17 @@ export interface RenderData {
|
|
|
85
170
|
// ----- String schema for AI (readable as string) -----
|
|
86
171
|
|
|
87
172
|
export const RENDER_DATA_SCHEMA = `
|
|
88
|
-
RenderData: { "id": string, "width": number, "height": number, "
|
|
173
|
+
RenderData: { "id": string, "width": number, "height": number, "layers": Array<TextRenderData | ImgRenderData | ContainerRenderData | ShapeRenderData>, "output"?: { "type"?: "png" | "jpg" } }
|
|
89
174
|
|
|
90
175
|
TextRenderData: { "id": string|number, "type": "text", "x": number, "y": number, "width": number, "height": number, "content": string, "style": { "fontName": string, "fontSize": number | { "max": number, "min": number }, "color": string, "align"?: "center"|"right", "verticalAlign"?: "center"|"top"|"bottom", "fontWeight"?: string, "verticalGap"?: number, "backgroundColor"?: string, "padding"?: number|{ "x": number, "y": number }, "border"?: { "color": string, "width"?: number }, "radius"?: number }, "rotate"?: number }
|
|
91
176
|
|
|
92
177
|
ImgRenderData: { "id": string, "type": "img", "x": number, "y": number, "width"?: number, "height"?: number, "url"?: string, "color"?: string, "objectFit": "contain"|"cover", "radius"?: number, "rotate"?: number, "globalAlpha"?: number, "shadow"?: { "color": string, "blur": number, "X": number, "Y": number } }
|
|
93
178
|
|
|
94
179
|
ContainerRenderData: { "id": string|number, "type": "container", "x": number, "y": number, "width": number, "height": number, "renderers": ChildRenderers[], "direction"?: "row"|"column", "gap"?: number|{ "x": number, "y": number }, "itemAlign"?: "center", "wrap"?: boolean }
|
|
180
|
+
|
|
181
|
+
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
|
+
|
|
183
|
+
ShapeCommand: { "type": "rect"|"fillRect"|"strokeRect"|"clearRect"|"beginPath"|"closePath"|"moveTo"|"lineTo"|"arc"|"arcTo"|"quadraticCurveTo"|"bezierCurveTo"|"fill"|"stroke"|"fillAndStroke", ...additional properties based on type }
|
|
95
184
|
`.trim();
|
|
96
185
|
|
|
97
186
|
// ----- Metrics (canvas-dependent) -----
|
package/tsconfig.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"target": "ES2020",
|
|
4
4
|
"module": "ESNext",
|
|
5
5
|
"lib": ["ES2020"],
|
|
6
|
-
"moduleResolution": "
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
7
|
"esModuleInterop": true,
|
|
8
8
|
"allowSyntheticDefaultImports": true,
|
|
9
9
|
"strict": true,
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
"forceConsistentCasingInFileNames": true,
|
|
12
12
|
"resolveJsonModule": true,
|
|
13
13
|
"isolatedModules": true,
|
|
14
|
-
"noEmit": true
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"types": ["node"],
|
|
16
|
+
"allowImportingTsExtensions": true
|
|
15
17
|
},
|
|
16
18
|
"ts-node": {
|
|
17
19
|
"esm": true,
|
|
18
20
|
"experimentalSpecifierResolution": "node"
|
|
19
21
|
},
|
|
20
|
-
"include": ["src/**/*"],
|
|
22
|
+
"include": ["src/**/*", "examples/**/*"],
|
|
21
23
|
"exclude": ["node_modules"]
|
|
22
24
|
}
|