declare-render 1.0.4 → 1.0.5
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 +6 -5
- package/package.json +1 -1
- package/src/canvas-renderers/container-renderer/index.ts +125 -84
- package/src/index.ts +2 -2
- package/src/types.ts +2 -2
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ const schema: RenderData = {
|
|
|
20
20
|
id: "my-canvas",
|
|
21
21
|
width: 800,
|
|
22
22
|
height: 600,
|
|
23
|
-
|
|
23
|
+
layers: [
|
|
24
24
|
{
|
|
25
25
|
id: "text-1",
|
|
26
26
|
type: "text",
|
|
@@ -45,7 +45,8 @@ const buffer = await renderer.draw();
|
|
|
45
45
|
|
|
46
46
|
## Features
|
|
47
47
|
|
|
48
|
-
- **Text
|
|
49
|
-
- **Image
|
|
50
|
-
- **Container
|
|
51
|
-
- **Multiple Output Formats**: PNG and JPG support
|
|
48
|
+
- **Text Layers**: Render text with various styling options
|
|
49
|
+
- **Image Layers**: Render images or solid color rectangles
|
|
50
|
+
- **Container Layers**: Organize layers with flexbox-like layouts
|
|
51
|
+
- **Multiple Output Formats**: PNG and JPG support
|
|
52
|
+
|
package/package.json
CHANGED
|
@@ -16,133 +16,174 @@ import { ShapeRender } from "../shape-render/index";
|
|
|
16
16
|
export type { ContainerRenderData } from "../../types";
|
|
17
17
|
|
|
18
18
|
export class ContainerRenderer extends BaseRender<ContainerRenderData> {
|
|
19
|
-
private ctx: CanvasRenderingContext2D
|
|
20
|
-
private
|
|
19
|
+
private ctx: CanvasRenderingContext2D;
|
|
20
|
+
private layers: Array<
|
|
21
|
+
ImgRender | TextRender | ShapeRender | ContainerRenderer
|
|
22
|
+
> = [];
|
|
21
23
|
|
|
22
24
|
constructor(ctx: CanvasRenderingContext2D, data: ContainerRenderData) {
|
|
23
|
-
super()
|
|
24
|
-
this.ctx = ctx
|
|
25
|
-
this.data = cloneDeep(data)
|
|
26
|
-
this.ctx.patternQuality =
|
|
27
|
-
this.ctx.quality =
|
|
25
|
+
super();
|
|
26
|
+
this.ctx = ctx;
|
|
27
|
+
this.data = cloneDeep(data);
|
|
28
|
+
this.ctx.patternQuality = "best";
|
|
29
|
+
this.ctx.quality = "best";
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
get container() {
|
|
31
|
-
return this.getContainerCoordinates()
|
|
33
|
+
return this.getContainerCoordinates();
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
+
private createLayer = (
|
|
37
|
+
layerData:
|
|
38
|
+
| ImgRenderData
|
|
39
|
+
| TextRenderData
|
|
40
|
+
| ShapeRenderData
|
|
41
|
+
| ContainerRenderData,
|
|
42
|
+
) => {
|
|
43
|
+
switch (layerData.type) {
|
|
36
44
|
case RendererType.TEXT: {
|
|
37
|
-
return new TextRender(this.ctx,
|
|
45
|
+
return new TextRender(this.ctx, layerData, {
|
|
46
|
+
inFlexFlow: isUndefined(layerData.x) || isUndefined(layerData.y),
|
|
47
|
+
});
|
|
38
48
|
}
|
|
39
49
|
case RendererType.IMG:
|
|
40
|
-
return new ImgRender(this.ctx,
|
|
50
|
+
return new ImgRender(this.ctx, layerData);
|
|
41
51
|
case RendererType.SHAPE:
|
|
42
|
-
return new ShapeRender(this.ctx,
|
|
52
|
+
return new ShapeRender(this.ctx, layerData);
|
|
43
53
|
case RendererType.CONTAINER:
|
|
44
|
-
return new ContainerRenderer(this.ctx,
|
|
54
|
+
return new ContainerRenderer(this.ctx, layerData);
|
|
45
55
|
default:
|
|
46
|
-
throw new Error("[Renderer] unknown
|
|
56
|
+
throw new Error("[Renderer] unknown layer type");
|
|
47
57
|
}
|
|
48
|
-
}
|
|
58
|
+
};
|
|
49
59
|
|
|
50
|
-
// TODO center the
|
|
60
|
+
// TODO center the layers, vertical center, left align, wrap the layers;
|
|
51
61
|
public layout = async () => {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
const layoutedLayers = [] as Array<
|
|
63
|
+
ImgRender | TextRender | ShapeRender | ContainerRenderer
|
|
64
|
+
>;
|
|
65
|
+
|
|
66
|
+
for (const index in this.data.layers) {
|
|
67
|
+
const layerData = cloneDeep(this.data.layers[index]) as
|
|
68
|
+
| ImgRenderData
|
|
69
|
+
| TextRenderData
|
|
70
|
+
| ShapeRenderData
|
|
71
|
+
| ContainerRenderData;
|
|
72
|
+
|
|
73
|
+
let layerX = layerData.x;
|
|
74
|
+
let layerY = layerData.y;
|
|
75
|
+
// 子 layer 的 x、y 相对容器的 x、y 进行定位
|
|
76
|
+
if (isNumber(layerX)) {
|
|
77
|
+
layerX += this.data.x;
|
|
62
78
|
}
|
|
63
79
|
|
|
64
|
-
if (isNumber(
|
|
65
|
-
|
|
80
|
+
if (isNumber(layerY)) {
|
|
81
|
+
layerY += this.data.y;
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
// justify
|
|
69
|
-
if (isUndefined(
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
if (isUndefined(layerX) || isUndefined(layerY)) {
|
|
86
|
+
const preLayer = layoutedLayers.at(-1) as
|
|
87
|
+
| undefined
|
|
88
|
+
| ImgRender
|
|
89
|
+
| TextRender
|
|
90
|
+
| ShapeRender
|
|
91
|
+
| ContainerRenderer;
|
|
92
|
+
const { x2, y1, x1, y2 } = preLayer?.container || {
|
|
93
|
+
x1: this.data.x,
|
|
94
|
+
x2: this.data.x,
|
|
95
|
+
y1: this.data.y,
|
|
96
|
+
y2: this.data.y,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const gapX = !preLayer
|
|
100
|
+
? 0
|
|
101
|
+
: isObject(this.data.gap)
|
|
102
|
+
? this.data.gap.x
|
|
103
|
+
: this.data.gap || 0;
|
|
104
|
+
|
|
105
|
+
const gapY = !preLayer
|
|
106
|
+
? 0
|
|
107
|
+
: isObject(this.data.gap)
|
|
108
|
+
? this.data.gap.y
|
|
109
|
+
: this.data.gap || 0;
|
|
110
|
+
|
|
111
|
+
if (this.data.direction === "column") {
|
|
112
|
+
layerX = layerX || x1;
|
|
113
|
+
layerY = layerY || y2 + gapY;
|
|
84
114
|
} else {
|
|
85
|
-
|
|
86
|
-
|
|
115
|
+
layerX = layerX || x2 + gapX;
|
|
116
|
+
layerY = layerY || y1;
|
|
87
117
|
}
|
|
88
118
|
}
|
|
89
119
|
|
|
90
|
-
const
|
|
120
|
+
const currentLayer = this.createLayer({
|
|
121
|
+
...layerData,
|
|
122
|
+
x: layerX,
|
|
123
|
+
y: layerY,
|
|
124
|
+
});
|
|
91
125
|
|
|
92
|
-
await
|
|
93
|
-
|
|
94
|
-
|
|
126
|
+
await currentLayer.layout();
|
|
127
|
+
|
|
128
|
+
layoutedLayers.push(currentLayer);
|
|
95
129
|
}
|
|
96
130
|
|
|
97
131
|
// align items
|
|
98
|
-
if (this.data.itemAlign ===
|
|
99
|
-
const squares =
|
|
100
|
-
const { x1, x2, y1, y2 } = r.container
|
|
101
|
-
return { ...r.container, width: x2 - x1, height: y2 - y1,
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
const maxWidth = Math.max(...squares.map(s => s.width))
|
|
105
|
-
const maxHeight = Math.max(...squares.map(s => s.height))
|
|
106
|
-
|
|
107
|
-
squares.forEach(s => {
|
|
108
|
-
if (this.data.direction ===
|
|
109
|
-
const offset = (maxWidth - s.width) / 2
|
|
110
|
-
s.
|
|
132
|
+
if (this.data.itemAlign === "center") {
|
|
133
|
+
const squares = layoutedLayers.map((r) => {
|
|
134
|
+
const { x1, x2, y1, y2 } = r.container;
|
|
135
|
+
return { ...r.container, width: x2 - x1, height: y2 - y1, layer: r };
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const maxWidth = Math.max(...squares.map((s) => s.width));
|
|
139
|
+
const maxHeight = Math.max(...squares.map((s) => s.height));
|
|
140
|
+
|
|
141
|
+
squares.forEach((s) => {
|
|
142
|
+
if (this.data.direction === "column") {
|
|
143
|
+
const offset = (maxWidth - s.width) / 2;
|
|
144
|
+
s.layer.setPosition(s.x1 + offset, s.y1);
|
|
111
145
|
} else {
|
|
112
|
-
const offset = (maxHeight - s.height) / 2
|
|
113
|
-
s.
|
|
146
|
+
const offset = (maxHeight - s.height) / 2;
|
|
147
|
+
s.layer.setPosition(s.x1, s.y1 + offset);
|
|
114
148
|
}
|
|
115
|
-
})
|
|
149
|
+
});
|
|
116
150
|
}
|
|
117
151
|
|
|
118
|
-
this.
|
|
119
|
-
return this
|
|
120
|
-
}
|
|
152
|
+
this.layers = layoutedLayers;
|
|
153
|
+
return this;
|
|
154
|
+
};
|
|
121
155
|
|
|
122
|
-
// TODO rotate the whole
|
|
123
|
-
public draw = async() => {
|
|
124
|
-
if (!this.
|
|
125
|
-
return this
|
|
156
|
+
// TODO rotate the whole layer
|
|
157
|
+
public draw = async () => {
|
|
158
|
+
if (!this.layers.length) {
|
|
159
|
+
return this;
|
|
126
160
|
}
|
|
127
161
|
|
|
128
|
-
const pickedOne = this.
|
|
129
|
-
await pickedOne.draw()
|
|
162
|
+
const pickedOne = this.layers.shift()!;
|
|
163
|
+
await pickedOne.draw();
|
|
130
164
|
|
|
131
|
-
await this.draw()
|
|
165
|
+
await this.draw();
|
|
132
166
|
|
|
133
|
-
return this
|
|
134
|
-
}
|
|
167
|
+
return this;
|
|
168
|
+
};
|
|
135
169
|
|
|
136
|
-
// TODO handle the
|
|
170
|
+
// TODO handle the layers wrapping, add gap;
|
|
137
171
|
private getContainerCoordinates = () => {
|
|
138
|
-
if (!this.
|
|
139
|
-
|
|
140
|
-
const
|
|
172
|
+
if (!this.layers.length)
|
|
173
|
+
throw new Error("can not get container coordinates before layouted");
|
|
174
|
+
const firstChild = this.layers.at(0) as
|
|
175
|
+
| ImgRender
|
|
176
|
+
| TextRender
|
|
177
|
+
| ShapeRender;
|
|
178
|
+
const lastChild = this.layers.at(-1) as
|
|
179
|
+
| ImgRender
|
|
180
|
+
| TextRender
|
|
181
|
+
| ShapeRender;
|
|
141
182
|
return {
|
|
142
183
|
x1: firstChild.container.x1,
|
|
143
184
|
y1: firstChild.container.y1,
|
|
144
185
|
x2: lastChild.container.x2,
|
|
145
186
|
y2: lastChild.container.y2,
|
|
146
|
-
}
|
|
147
|
-
}
|
|
187
|
+
};
|
|
188
|
+
};
|
|
148
189
|
}
|
package/src/index.ts
CHANGED
|
@@ -25,7 +25,7 @@ export class Renderer {
|
|
|
25
25
|
|
|
26
26
|
draw = async () => {
|
|
27
27
|
if (!this.schema.layers.length) {
|
|
28
|
-
throw new Error("[Renderer] empty canvas with no
|
|
28
|
+
throw new Error("[Renderer] empty canvas with no layers");
|
|
29
29
|
}
|
|
30
30
|
const container = new ContainerRenderer(this.ctx, {
|
|
31
31
|
id: this.schema.id,
|
|
@@ -34,7 +34,7 @@ export class Renderer {
|
|
|
34
34
|
y: 0,
|
|
35
35
|
width: this.canvas.width,
|
|
36
36
|
height: this.canvas.height,
|
|
37
|
-
|
|
37
|
+
layers: this.schema.layers,
|
|
38
38
|
});
|
|
39
39
|
await container.layout().then((d) => d.draw());
|
|
40
40
|
return this.toBuffer();
|
package/src/types.ts
CHANGED
|
@@ -68,7 +68,7 @@ export interface ContainerRenderData {
|
|
|
68
68
|
itemAlign?: "center";
|
|
69
69
|
gap?: number | { x: number; y: number };
|
|
70
70
|
wrap?: boolean;
|
|
71
|
-
|
|
71
|
+
layers: ChildRenderers;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export interface ShapeRenderData {
|
|
@@ -176,7 +176,7 @@ TextRenderData: { "id": string|number, "type": "text", "x": number, "y": number,
|
|
|
176
176
|
|
|
177
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 } }
|
|
178
178
|
|
|
179
|
-
ContainerRenderData: { "id": string|number, "type": "container", "x": number, "y": number, "width": number, "height": number, "
|
|
179
|
+
ContainerRenderData: { "id": string|number, "type": "container", "x": number, "y": number, "width": number, "height": number, "layers": ChildRenderers[], "direction"?: "row"|"column", "gap"?: number|{ "x": number, "y": number }, "itemAlign"?: "center", "wrap"?: boolean }
|
|
180
180
|
|
|
181
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
182
|
|