declare-render 1.0.3 → 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/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 +128 -83
- package/src/canvas-renderers/shape-render/index.ts +279 -0
- package/src/index.ts +4 -3
- package/src/types.ts +94 -5
- package/tsconfig.json +5 -3
- package/examples/sunscreen-guide.ts +0 -206
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ContainerRenderData,
|
|
3
3
|
ImgRenderData,
|
|
4
|
+
ShapeRenderData,
|
|
4
5
|
TextRenderData,
|
|
5
6
|
} from "../types";
|
|
6
7
|
|
|
7
8
|
export abstract class BaseRender<
|
|
8
|
-
T extends
|
|
9
|
+
T extends
|
|
10
|
+
| TextRenderData
|
|
11
|
+
| ImgRenderData
|
|
12
|
+
| ContainerRenderData
|
|
13
|
+
| ShapeRenderData,
|
|
9
14
|
> {
|
|
10
15
|
protected data!: T;
|
|
11
16
|
abstract get container(): { x1: number; x2: number; y1: number; y2: number };
|
|
@@ -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,135 +11,179 @@ 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
|
-
private ctx: CanvasRenderingContext2D
|
|
18
|
-
private
|
|
19
|
+
private ctx: CanvasRenderingContext2D;
|
|
20
|
+
private layers: Array<
|
|
21
|
+
ImgRender | TextRender | ShapeRender | ContainerRenderer
|
|
22
|
+
> = [];
|
|
19
23
|
|
|
20
24
|
constructor(ctx: CanvasRenderingContext2D, data: ContainerRenderData) {
|
|
21
|
-
super()
|
|
22
|
-
this.ctx = ctx
|
|
23
|
-
this.data = cloneDeep(data)
|
|
24
|
-
this.ctx.patternQuality =
|
|
25
|
-
this.ctx.quality =
|
|
25
|
+
super();
|
|
26
|
+
this.ctx = ctx;
|
|
27
|
+
this.data = cloneDeep(data);
|
|
28
|
+
this.ctx.patternQuality = "best";
|
|
29
|
+
this.ctx.quality = "best";
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
get container() {
|
|
29
|
-
return this.getContainerCoordinates()
|
|
33
|
+
return this.getContainerCoordinates();
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
private
|
|
33
|
-
|
|
36
|
+
private createLayer = (
|
|
37
|
+
layerData:
|
|
38
|
+
| ImgRenderData
|
|
39
|
+
| TextRenderData
|
|
40
|
+
| ShapeRenderData
|
|
41
|
+
| ContainerRenderData,
|
|
42
|
+
) => {
|
|
43
|
+
switch (layerData.type) {
|
|
34
44
|
case RendererType.TEXT: {
|
|
35
|
-
return new TextRender(this.ctx,
|
|
45
|
+
return new TextRender(this.ctx, layerData, {
|
|
46
|
+
inFlexFlow: isUndefined(layerData.x) || isUndefined(layerData.y),
|
|
47
|
+
});
|
|
36
48
|
}
|
|
37
49
|
case RendererType.IMG:
|
|
38
|
-
return new ImgRender(this.ctx,
|
|
50
|
+
return new ImgRender(this.ctx, layerData);
|
|
51
|
+
case RendererType.SHAPE:
|
|
52
|
+
return new ShapeRender(this.ctx, layerData);
|
|
39
53
|
case RendererType.CONTAINER:
|
|
40
|
-
return new ContainerRenderer(this.ctx,
|
|
54
|
+
return new ContainerRenderer(this.ctx, layerData);
|
|
41
55
|
default:
|
|
42
|
-
throw new Error("[Renderer] unknown
|
|
56
|
+
throw new Error("[Renderer] unknown layer type");
|
|
43
57
|
}
|
|
44
|
-
}
|
|
58
|
+
};
|
|
45
59
|
|
|
46
|
-
// TODO center the
|
|
60
|
+
// TODO center the layers, vertical center, left align, wrap the layers;
|
|
47
61
|
public layout = async () => {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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;
|
|
58
78
|
}
|
|
59
79
|
|
|
60
|
-
if (isNumber(
|
|
61
|
-
|
|
80
|
+
if (isNumber(layerY)) {
|
|
81
|
+
layerY += this.data.y;
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
// justify
|
|
65
|
-
if (isUndefined(
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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;
|
|
80
114
|
} else {
|
|
81
|
-
|
|
82
|
-
|
|
115
|
+
layerX = layerX || x2 + gapX;
|
|
116
|
+
layerY = layerY || y1;
|
|
83
117
|
}
|
|
84
118
|
}
|
|
85
119
|
|
|
86
|
-
const
|
|
120
|
+
const currentLayer = this.createLayer({
|
|
121
|
+
...layerData,
|
|
122
|
+
x: layerX,
|
|
123
|
+
y: layerY,
|
|
124
|
+
});
|
|
87
125
|
|
|
88
|
-
await
|
|
89
|
-
|
|
90
|
-
|
|
126
|
+
await currentLayer.layout();
|
|
127
|
+
|
|
128
|
+
layoutedLayers.push(currentLayer);
|
|
91
129
|
}
|
|
92
130
|
|
|
93
131
|
// align items
|
|
94
|
-
if (this.data.itemAlign ===
|
|
95
|
-
const squares =
|
|
96
|
-
const { x1, x2, y1, y2 } = r.container
|
|
97
|
-
return { ...r.container, width: x2 - x1, height: y2 - y1,
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const maxWidth = Math.max(...squares.map(s => s.width))
|
|
101
|
-
const maxHeight = Math.max(...squares.map(s => s.height))
|
|
102
|
-
|
|
103
|
-
squares.forEach(s => {
|
|
104
|
-
if (this.data.direction ===
|
|
105
|
-
const offset = (maxWidth - s.width) / 2
|
|
106
|
-
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);
|
|
107
145
|
} else {
|
|
108
|
-
const offset = (maxHeight - s.height) / 2
|
|
109
|
-
s.
|
|
146
|
+
const offset = (maxHeight - s.height) / 2;
|
|
147
|
+
s.layer.setPosition(s.x1, s.y1 + offset);
|
|
110
148
|
}
|
|
111
|
-
})
|
|
149
|
+
});
|
|
112
150
|
}
|
|
113
151
|
|
|
114
|
-
this.
|
|
115
|
-
return this
|
|
116
|
-
}
|
|
152
|
+
this.layers = layoutedLayers;
|
|
153
|
+
return this;
|
|
154
|
+
};
|
|
117
155
|
|
|
118
|
-
// TODO rotate the whole
|
|
119
|
-
public draw = async() => {
|
|
120
|
-
if (!this.
|
|
121
|
-
return this
|
|
156
|
+
// TODO rotate the whole layer
|
|
157
|
+
public draw = async () => {
|
|
158
|
+
if (!this.layers.length) {
|
|
159
|
+
return this;
|
|
122
160
|
}
|
|
123
161
|
|
|
124
|
-
const pickedOne = this.
|
|
125
|
-
await pickedOne.draw()
|
|
162
|
+
const pickedOne = this.layers.shift()!;
|
|
163
|
+
await pickedOne.draw();
|
|
126
164
|
|
|
127
|
-
await this.draw()
|
|
165
|
+
await this.draw();
|
|
128
166
|
|
|
129
|
-
return this
|
|
130
|
-
}
|
|
167
|
+
return this;
|
|
168
|
+
};
|
|
131
169
|
|
|
132
|
-
// TODO handle the
|
|
170
|
+
// TODO handle the layers wrapping, add gap;
|
|
133
171
|
private getContainerCoordinates = () => {
|
|
134
|
-
if (!this.
|
|
135
|
-
|
|
136
|
-
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;
|
|
137
182
|
return {
|
|
138
183
|
x1: firstChild.container.x1,
|
|
139
184
|
y1: firstChild.container.y1,
|
|
140
185
|
x2: lastChild.container.x2,
|
|
141
186
|
y2: lastChild.container.y2,
|
|
142
|
-
}
|
|
143
|
-
}
|
|
187
|
+
};
|
|
188
|
+
};
|
|
144
189
|
}
|
|
@@ -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,8 +24,8 @@ export class Renderer {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
draw = async () => {
|
|
26
|
-
if (!this.schema.
|
|
27
|
-
throw new Error("[Renderer] empty canvas with no
|
|
27
|
+
if (!this.schema.layers.length) {
|
|
28
|
+
throw new Error("[Renderer] empty canvas with no layers");
|
|
28
29
|
}
|
|
29
30
|
const container = new ContainerRenderer(this.ctx, {
|
|
30
31
|
id: this.schema.id,
|
|
@@ -33,7 +34,7 @@ export class Renderer {
|
|
|
33
34
|
y: 0,
|
|
34
35
|
width: this.canvas.width,
|
|
35
36
|
height: this.canvas.height,
|
|
36
|
-
|
|
37
|
+
layers: this.schema.layers,
|
|
37
38
|
});
|
|
38
39
|
await container.layout().then((d) => d.draw());
|
|
39
40
|
return this.toBuffer();
|