xpict 0.4.1 → 1.0.0
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 +272 -17
- package/dist/effects/blur.d.ts +2 -3
- package/dist/effects/blur.js +14 -4
- package/dist/effects/box-blur.d.ts +2 -0
- package/dist/effects/box-blur.js +61 -0
- package/dist/effects/grayscale.d.ts +2 -2
- package/dist/effects/grayscale.js +14 -4
- package/dist/effects/index.d.ts +1 -1
- package/dist/effects/index.js +1 -1
- package/dist/enums/index.d.ts +2 -0
- package/dist/enums/index.js +18 -0
- package/dist/enums/text-align.d.ts +5 -0
- package/dist/enums/text-align.js +9 -0
- package/dist/enums/text-baseline.d.ts +8 -0
- package/dist/enums/text-baseline.js +12 -0
- package/dist/index.d.ts +5 -7
- package/dist/index.js +27 -7
- package/dist/layers/circle-layer.d.ts +1 -1
- package/dist/layers/circle-layer.js +4 -18
- package/dist/layers/group-layer.js +0 -2
- package/dist/layers/image-layer.d.ts +5 -4
- package/dist/layers/image-layer.js +21 -17
- package/dist/layers/line-layer.d.ts +1 -1
- package/dist/layers/line-layer.js +6 -20
- package/dist/layers/rectangle-layer.d.ts +1 -1
- package/dist/layers/rectangle-layer.js +4 -18
- package/dist/layers/repeat-layer.js +0 -2
- package/dist/layers/text-layer.d.ts +7 -15
- package/dist/layers/text-layer.js +6 -40
- package/dist/render-context.d.ts +2 -2
- package/dist/template.d.ts +3 -3
- package/dist/template.js +22 -30
- package/dist/utils/font.d.ts +17 -0
- package/dist/utils/font.js +21 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/value-of-enum.d.ts +1 -0
- package/dist/utils/value-of-enum.js +2 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,35 +1,290 @@
|
|
|
1
1
|
# Xpict
|
|
2
2
|
|
|
3
|
-
Xpict
|
|
3
|
+
**Xpict** is a library built on top of `@napi-rs/canvas` designed to create templates for generating standardized images with data—which can be static or dynamic. It features support for conditional rendering, runtime-generated position axes, and dynamic text.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 📦 Installation
|
|
6
|
+
|
|
7
|
+
Installation is straightforward; just use your preferred package manager. Here is an example using NPM:
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
npm
|
|
10
|
+
npm i xpict
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 🚀 Usage
|
|
15
|
+
|
|
16
|
+
<a href="https://www.buymeacoffee.com/marcuth">
|
|
17
|
+
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" width="200">
|
|
18
|
+
</a>
|
|
19
|
+
|
|
20
|
+
### Template
|
|
21
|
+
|
|
22
|
+
The foundation for creating images in Xpict is the `Template`. This is where you define your image layers and encapsulate all assembly logic. There are two ways to define an initial `Template`.
|
|
23
|
+
|
|
24
|
+
**Using the config property (width, height, and fill):**
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import xpict from "xpict"
|
|
28
|
+
|
|
29
|
+
type RenderData = {} // Your data structure used in layer logic
|
|
30
|
+
|
|
31
|
+
const template = xpict.template<RenderData>({
|
|
32
|
+
config: {
|
|
33
|
+
width: 1280,
|
|
34
|
+
height: 720,
|
|
35
|
+
fill: "#020817" // optional
|
|
36
|
+
},
|
|
37
|
+
layers: [...]
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Using an image as a background:**
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import xpict from "xpict"
|
|
46
|
+
|
|
47
|
+
type RenderData = {} // Your data structure used in layer logic
|
|
48
|
+
|
|
49
|
+
const template = xpict.template<RenderData>({
|
|
50
|
+
imagePath: "path/to/background.png",
|
|
51
|
+
layers: [...]
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### Layers
|
|
59
|
+
|
|
60
|
+
Layers are another fundamental component of Xpict. They act as building blocks—sometimes functional, sometimes just wrappers—that organize code declaratively and provide instructions to `@napi-rs/canvas` on how to perform operations within our canvas context.
|
|
61
|
+
|
|
62
|
+
All axes (x, y) across all layer types support dynamic values:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
x: ({ data, index /* index is useful if you are inside a `RepeatLayer` loop */ }) => index * 110,
|
|
66
|
+
y: ({ data, index /* index is useful if you are inside a `RepeatLayer` loop */ }) => index * 50,
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Every layer also supports conditional rendering via the `when` property. If not defined, the layer renders by default.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
when: ({ data, index }) => false // must return a boolean
|
|
74
|
+
|
|
9
75
|
```
|
|
10
76
|
|
|
11
|
-
|
|
77
|
+
#### TextLayer
|
|
12
78
|
|
|
13
|
-
|
|
79
|
+
To insert text into the canvas, use `TextLayer`:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
14
82
|
import xpict from "xpict"
|
|
15
83
|
|
|
16
|
-
const
|
|
84
|
+
const myFont = xpict.utils.font({
|
|
85
|
+
name: "curse-casual",
|
|
86
|
+
filePath: "path/to/font.ttf",
|
|
87
|
+
color: "#ffffff",
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
type RenderData = {}
|
|
91
|
+
|
|
92
|
+
const template = xpict.template<RenderData>({
|
|
17
93
|
config: {
|
|
18
|
-
width:
|
|
19
|
-
height:
|
|
20
|
-
fill: "#
|
|
94
|
+
width: 1280,
|
|
95
|
+
height: 720,
|
|
96
|
+
fill: "#020817"
|
|
21
97
|
},
|
|
22
98
|
layers: [
|
|
23
|
-
xpict.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
99
|
+
xpict.text({
|
|
100
|
+
x: 640,
|
|
101
|
+
y: 360,
|
|
102
|
+
font: myFont({
|
|
103
|
+
size: 60,
|
|
104
|
+
color: "#ffffff" // optional, as it was previously defined in font config
|
|
105
|
+
}),
|
|
106
|
+
text: "Hello World",
|
|
107
|
+
align: "center", // optional
|
|
108
|
+
baseline: "middle", // optional
|
|
109
|
+
stroke: {
|
|
110
|
+
width: 4,
|
|
111
|
+
fill: "#000000"
|
|
112
|
+
} // optional
|
|
113
|
+
})
|
|
30
114
|
]
|
|
31
115
|
})
|
|
32
116
|
|
|
33
|
-
const image = await template.render({...}) // para renderizar a imagem passando os dados de entrada
|
|
34
117
|
```
|
|
35
118
|
|
|
119
|
+
**Using dynamic text:**
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
text: ({ data, index }) => data.myText
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
#### ImageLayer
|
|
129
|
+
|
|
130
|
+
To insert images into the canvas, use `ImageLayer`:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import xpict from "xpict"
|
|
134
|
+
|
|
135
|
+
const template = xpict.template<RenderData>({
|
|
136
|
+
config: {
|
|
137
|
+
width: 1280,
|
|
138
|
+
height: 720,
|
|
139
|
+
fill: "#020817"
|
|
140
|
+
},
|
|
141
|
+
layers: [
|
|
142
|
+
xpict.image({
|
|
143
|
+
src: "path/to/image.png",
|
|
144
|
+
x: 0,
|
|
145
|
+
y: 0,
|
|
146
|
+
}),
|
|
147
|
+
]
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Using a dynamic image:**
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
src: ({ data, index }) => data.myImagePath
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
#### RectangleLayer
|
|
162
|
+
|
|
163
|
+
To insert rectangles into the canvas, use `RectangleLayer`:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
xpict.rectangle({
|
|
167
|
+
x: 0,
|
|
168
|
+
y: 0,
|
|
169
|
+
fill: "#ffffff",
|
|
170
|
+
width: 150,
|
|
171
|
+
height: 50,
|
|
172
|
+
borderRadius: 5 // optional
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
#### CircleLayer
|
|
180
|
+
|
|
181
|
+
To insert circles into the canvas, use `CircleLayer`:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
xpict.circle({
|
|
185
|
+
x: 0,
|
|
186
|
+
y: 0,
|
|
187
|
+
fill: "#ffffff",
|
|
188
|
+
radius: 50,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
#### LineLayer
|
|
196
|
+
|
|
197
|
+
To insert lines into the canvas, use `LineLayer`:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
xpict.line({
|
|
201
|
+
from: { x: 0, y: 0 },
|
|
202
|
+
to: { x: 0, y: 50 },
|
|
203
|
+
color: "#ffffff",
|
|
204
|
+
width: 1
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
#### GroupLayer
|
|
212
|
+
|
|
213
|
+
If you want to group layers that share a common context, you can use `GroupLayer` to keep your code organized and apply an offset if desired:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
xpict.group({
|
|
217
|
+
x: 50,
|
|
218
|
+
y: 50,
|
|
219
|
+
layers: [...]
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
#### RepeatLayer
|
|
227
|
+
|
|
228
|
+
If you need to handle arrays to generate layers, use `RepeatLayer` to organize your code and loop logic:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
xpict.repeat({
|
|
232
|
+
each: ({ data }) => data.images,
|
|
233
|
+
x: ({ index }) => index * 50,
|
|
234
|
+
y: ({ index }) => index * 50,
|
|
235
|
+
layer: (image, { index, length }) => xpict.image({
|
|
236
|
+
x: 0,
|
|
237
|
+
y: 0,
|
|
238
|
+
src: image
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
### Image Transformations and Effects
|
|
247
|
+
|
|
248
|
+
If you want to manipulate an image or apply effects, you can do so using the `transform` property of `ImageLayer`, or via `flipX`, `flipY`, and `rotate`.
|
|
249
|
+
|
|
250
|
+
**Using transform:**
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
xpict.image({
|
|
254
|
+
x: 0,
|
|
255
|
+
y: 0,
|
|
256
|
+
src: "...",
|
|
257
|
+
transform: [
|
|
258
|
+
xpict.effects.blur(2.5),
|
|
259
|
+
({ canvas, ctx, data, index }) => {
|
|
260
|
+
// manipulate the `ctx` directly here to apply custom logic
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 🧪 Tests (Not included yet, CONTRIBUTE! :D)
|
|
270
|
+
|
|
271
|
+
Automated tests are located in `__tests__`. To run them:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
npm run test
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## 🤝 Contributing
|
|
279
|
+
|
|
280
|
+
Want to contribute? Follow these steps:
|
|
281
|
+
|
|
282
|
+
1. Fork the repository.
|
|
283
|
+
2. Create a new branch (`git checkout -b feature-new`).
|
|
284
|
+
3. Commit your changes (`git commit -m 'Add new feature'`).
|
|
285
|
+
4. Push to the branch (`git push origin feature-new`).
|
|
286
|
+
5. Open a Pull Request.
|
|
287
|
+
|
|
288
|
+
## 📝 License
|
|
289
|
+
|
|
290
|
+
This project is licensed under the MIT License.
|
package/dist/effects/blur.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export declare function blurEffect(sigma: number | boolean | sharp.BlurOptions): ImageTransformFunction<any>;
|
|
1
|
+
import { TransformOptions } from "../layers/image-layer";
|
|
2
|
+
export declare function blur<Data>(radius: number): ({ canvas, ctx }: TransformOptions<Data>) => void;
|
package/dist/effects/blur.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
exports.blur = blur;
|
|
4
|
+
const canvas_1 = require("@napi-rs/canvas");
|
|
5
|
+
function blur(radius) {
|
|
6
|
+
return ({ canvas, ctx }) => {
|
|
7
|
+
if (radius <= 0)
|
|
8
|
+
return;
|
|
9
|
+
const tempCanvas = (0, canvas_1.createCanvas)(canvas.width, canvas.height);
|
|
10
|
+
const tempCtx = tempCanvas.getContext("2d");
|
|
11
|
+
tempCtx.drawImage(canvas, 0, 0);
|
|
12
|
+
ctx.save();
|
|
13
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
14
|
+
ctx.filter = `blur(${radius}px)`;
|
|
15
|
+
ctx.drawImage(tempCanvas, 0, 0);
|
|
16
|
+
ctx.restore();
|
|
7
17
|
};
|
|
8
18
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.boxBlur = boxBlur;
|
|
4
|
+
function boxBlur(radius) {
|
|
5
|
+
return ({ canvas, ctx }) => {
|
|
6
|
+
const r = Math.floor(radius);
|
|
7
|
+
if (r <= 0)
|
|
8
|
+
return;
|
|
9
|
+
const width = canvas.width;
|
|
10
|
+
const height = canvas.height;
|
|
11
|
+
const imageData = ctx.getImageData(0, 0, width, height);
|
|
12
|
+
const data = imageData.data;
|
|
13
|
+
const sourceData = new Uint8ClampedArray(data);
|
|
14
|
+
for (let y = 0; y < height; y++) {
|
|
15
|
+
for (let x = 0; x < width; x++) {
|
|
16
|
+
let red = 0, green = 0, blue = 0, alpha = 0;
|
|
17
|
+
let count = 0;
|
|
18
|
+
for (let i = -r; i <= r; i++) {
|
|
19
|
+
const nx = x + i;
|
|
20
|
+
if (nx >= 0 && nx < width) {
|
|
21
|
+
const sourceIndex = (y * width + nx) * 4;
|
|
22
|
+
red += sourceData[sourceIndex];
|
|
23
|
+
green += sourceData[sourceIndex + 1];
|
|
24
|
+
blue += sourceData[sourceIndex + 2];
|
|
25
|
+
alpha += sourceData[sourceIndex + 3];
|
|
26
|
+
count++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const targetIndex = (y * width + x) * 4;
|
|
30
|
+
data[targetIndex] = red / count;
|
|
31
|
+
data[targetIndex + 1] = green / count;
|
|
32
|
+
data[targetIndex + 2] = blue / count;
|
|
33
|
+
data[targetIndex + 3] = alpha / count;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
sourceData.set(data);
|
|
37
|
+
for (let x = 0; x < width; x++) {
|
|
38
|
+
for (let y = 0; y < height; y++) {
|
|
39
|
+
let red = 0, green = 0, blue = 0, alpha = 0;
|
|
40
|
+
let count = 0;
|
|
41
|
+
for (let i = -r; i <= r; i++) {
|
|
42
|
+
const ny = y + i;
|
|
43
|
+
if (ny >= 0 && ny < height) {
|
|
44
|
+
const sourceIndex = (ny * width + x) * 4;
|
|
45
|
+
red += sourceData[sourceIndex];
|
|
46
|
+
green += sourceData[sourceIndex + 1];
|
|
47
|
+
blue += sourceData[sourceIndex + 2];
|
|
48
|
+
alpha += sourceData[sourceIndex + 3];
|
|
49
|
+
count++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const targetIndex = (y * width + x) * 4;
|
|
53
|
+
data[targetIndex] = red / count;
|
|
54
|
+
data[targetIndex + 1] = green / count;
|
|
55
|
+
data[targetIndex + 2] = blue / count;
|
|
56
|
+
data[targetIndex + 3] = alpha / count;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
ctx.putImageData(imageData, 0, 0);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function
|
|
1
|
+
import { TransformOptions } from "../layers/image-layer";
|
|
2
|
+
export declare function grayscale<Data>(amount?: number): ({ canvas, ctx }: TransformOptions<Data>) => void;
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
exports.grayscale = grayscale;
|
|
4
|
+
const canvas_1 = require("@napi-rs/canvas");
|
|
5
|
+
function grayscale(amount = 100) {
|
|
6
|
+
return ({ canvas, ctx }) => {
|
|
7
|
+
if (amount <= 0)
|
|
8
|
+
return;
|
|
9
|
+
const tempCanvas = (0, canvas_1.createCanvas)(canvas.width, canvas.height);
|
|
10
|
+
const tempCtx = tempCanvas.getContext("2d");
|
|
11
|
+
tempCtx.drawImage(canvas, 0, 0);
|
|
12
|
+
ctx.save();
|
|
13
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
14
|
+
ctx.filter = `grayscale(${amount}%)`;
|
|
15
|
+
ctx.drawImage(tempCanvas, 0, 0);
|
|
16
|
+
ctx.restore();
|
|
7
17
|
};
|
|
8
18
|
}
|
package/dist/effects/index.d.ts
CHANGED
package/dist/effects/index.js
CHANGED
|
@@ -16,4 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./blur"), exports);
|
|
18
18
|
__exportStar(require("./grayscale"), exports);
|
|
19
|
-
__exportStar(require("./
|
|
19
|
+
__exportStar(require("./box-blur"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./text-align"), exports);
|
|
18
|
+
__exportStar(require("./text-baseline"), exports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TextAlign = void 0;
|
|
4
|
+
var TextAlign;
|
|
5
|
+
(function (TextAlign) {
|
|
6
|
+
TextAlign["Left"] = "left";
|
|
7
|
+
TextAlign["Center"] = "center";
|
|
8
|
+
TextAlign["Right"] = "right";
|
|
9
|
+
})(TextAlign || (exports.TextAlign = TextAlign = {}));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TextBaseline = void 0;
|
|
4
|
+
var TextBaseline;
|
|
5
|
+
(function (TextBaseline) {
|
|
6
|
+
TextBaseline["Top"] = "top";
|
|
7
|
+
TextBaseline["Hanging"] = "hanging";
|
|
8
|
+
TextBaseline["Middle"] = "middle";
|
|
9
|
+
TextBaseline["Alphabetic"] = "alphabetic";
|
|
10
|
+
TextBaseline["Ideographic"] = "ideographic";
|
|
11
|
+
TextBaseline["Bottom"] = "bottom";
|
|
12
|
+
})(TextBaseline || (exports.TextBaseline = TextBaseline = {}));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { GroupLayer, RepeatLayer, GroupLayerOptions, ImageLayer, ImageLayerOptions, LineLayer, LineLayerOptions, RectangleLayer, RectangleLayerOptions, RepeatLayerOptions, TextLayer, TextLayerOptions, CircleLayerOptions, CircleLayer } from "./layers";
|
|
2
|
-
import { negativeEffect, blurEffect, grayscaleEffect } from "./effects";
|
|
3
2
|
import { Template, InputTemplateOptions } from "./template";
|
|
4
|
-
|
|
3
|
+
import * as utils from "./utils";
|
|
4
|
+
import * as effects from "./effects";
|
|
5
5
|
export * from "./effects";
|
|
6
|
+
export * from "./layers";
|
|
6
7
|
export * from "./template";
|
|
7
8
|
export * from "./utils";
|
|
8
9
|
export * from "./error";
|
|
@@ -15,10 +16,7 @@ declare const xpict: {
|
|
|
15
16
|
group<Data>(options: GroupLayerOptions<Data>): GroupLayer<Data>;
|
|
16
17
|
line<Data>(options: LineLayerOptions<Data>): LineLayer<Data>;
|
|
17
18
|
template<Data>(options: InputTemplateOptions<Data>): Template<Data>;
|
|
18
|
-
effects:
|
|
19
|
-
|
|
20
|
-
blur: typeof blurEffect;
|
|
21
|
-
grayscale: typeof grayscaleEffect;
|
|
22
|
-
};
|
|
19
|
+
effects: typeof effects;
|
|
20
|
+
utils: typeof utils;
|
|
23
21
|
};
|
|
24
22
|
export default xpict;
|
package/dist/index.js
CHANGED
|
@@ -10,15 +10,38 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
|
10
10
|
if (k2 === undefined) k2 = k;
|
|
11
11
|
o[k2] = m[k];
|
|
12
12
|
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
13
35
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
36
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
37
|
};
|
|
16
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
39
|
const layers_1 = require("./layers");
|
|
18
|
-
const effects_1 = require("./effects");
|
|
19
40
|
const template_1 = require("./template");
|
|
20
|
-
|
|
41
|
+
const utils = __importStar(require("./utils"));
|
|
42
|
+
const effects = __importStar(require("./effects"));
|
|
21
43
|
__exportStar(require("./effects"), exports);
|
|
44
|
+
__exportStar(require("./layers"), exports);
|
|
22
45
|
__exportStar(require("./template"), exports);
|
|
23
46
|
__exportStar(require("./utils"), exports);
|
|
24
47
|
__exportStar(require("./error"), exports);
|
|
@@ -47,10 +70,7 @@ const xpict = {
|
|
|
47
70
|
template(options) {
|
|
48
71
|
return new template_1.Template(options);
|
|
49
72
|
},
|
|
50
|
-
effects:
|
|
51
|
-
|
|
52
|
-
blur: effects_1.blurEffect,
|
|
53
|
-
grayscale: effects_1.grayscaleEffect,
|
|
54
|
-
},
|
|
73
|
+
effects: effects,
|
|
74
|
+
utils: utils,
|
|
55
75
|
};
|
|
56
76
|
exports.default = xpict;
|
|
@@ -10,5 +10,5 @@ export type CircleLayerOptions<Data> = {
|
|
|
10
10
|
export declare class CircleLayer<Data> extends Layer<Data> {
|
|
11
11
|
private readonly options;
|
|
12
12
|
constructor(options: CircleLayerOptions<Data>);
|
|
13
|
-
render({ context:
|
|
13
|
+
render({ context: renderContext, data, index, templateConfig }: RenderOptions<Data>): Promise<void>;
|
|
14
14
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CircleLayer = void 0;
|
|
4
|
-
const canvas_1 = require("canvas");
|
|
5
4
|
const layer_1 = require("./layer");
|
|
6
5
|
const resolve_axis_1 = require("../utils/resolve-axis");
|
|
7
6
|
class CircleLayer extends layer_1.Layer {
|
|
@@ -9,19 +8,15 @@ class CircleLayer extends layer_1.Layer {
|
|
|
9
8
|
super(options.when);
|
|
10
9
|
this.options = options;
|
|
11
10
|
}
|
|
12
|
-
async render({ context:
|
|
13
|
-
const
|
|
14
|
-
const metadata = await image.metadata();
|
|
15
|
-
const canvasWidth = metadata.width;
|
|
16
|
-
const canvasHeight = metadata.height;
|
|
17
|
-
const x = ctx.offsetX +
|
|
11
|
+
async render({ context: renderContext, data, index = 0, templateConfig }) {
|
|
12
|
+
const x = renderContext.offsetX +
|
|
18
13
|
(0, resolve_axis_1.resolveAxis)({
|
|
19
14
|
axis: this.options.x,
|
|
20
15
|
data: data,
|
|
21
16
|
index: index,
|
|
22
17
|
templateConfig: templateConfig,
|
|
23
18
|
});
|
|
24
|
-
const y =
|
|
19
|
+
const y = renderContext.offsetY +
|
|
25
20
|
(0, resolve_axis_1.resolveAxis)({
|
|
26
21
|
axis: this.options.y,
|
|
27
22
|
data: data,
|
|
@@ -29,21 +24,12 @@ class CircleLayer extends layer_1.Layer {
|
|
|
29
24
|
templateConfig: templateConfig,
|
|
30
25
|
});
|
|
31
26
|
const { radius, fill } = this.options;
|
|
32
|
-
const
|
|
33
|
-
const context = canvas.getContext("2d");
|
|
27
|
+
const context = renderContext.ctx;
|
|
34
28
|
context.beginPath();
|
|
35
29
|
context.arc(x, y, radius, 0, Math.PI * 2);
|
|
36
30
|
context.closePath();
|
|
37
31
|
context.fillStyle = fill;
|
|
38
32
|
context.fill();
|
|
39
|
-
const circleBuffer = canvas.toBuffer();
|
|
40
|
-
ctx.image = image.composite([
|
|
41
|
-
{
|
|
42
|
-
input: circleBuffer,
|
|
43
|
-
top: 0,
|
|
44
|
-
left: 0,
|
|
45
|
-
},
|
|
46
|
-
]);
|
|
47
33
|
}
|
|
48
34
|
}
|
|
49
35
|
exports.CircleLayer = CircleLayer;
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.GroupLayer = void 0;
|
|
4
4
|
const layer_1 = require("./layer");
|
|
5
5
|
const resolve_axis_1 = require("../utils/resolve-axis");
|
|
6
|
-
const commit_frame_1 = require("../utils/commit-frame");
|
|
7
6
|
class GroupLayer extends layer_1.Layer {
|
|
8
7
|
constructor(options) {
|
|
9
8
|
super(options.when);
|
|
@@ -36,7 +35,6 @@ class GroupLayer extends layer_1.Layer {
|
|
|
36
35
|
index: index,
|
|
37
36
|
templateConfig: templateConfig,
|
|
38
37
|
});
|
|
39
|
-
ctx.image = await (0, commit_frame_1.commitFrame)(ctx.image);
|
|
40
38
|
}
|
|
41
39
|
ctx.offsetX = prevX;
|
|
42
40
|
ctx.offsetY = prevY;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Canvas, SKRSContext2D } from "@napi-rs/canvas";
|
|
2
2
|
import { Layer, RenderOptions, WhenFunction } from "./layer";
|
|
3
3
|
import { Axis } from "../utils/resolve-axis";
|
|
4
4
|
export type TransformOptions<Data> = {
|
|
5
5
|
data: Data;
|
|
6
6
|
index: number;
|
|
7
|
-
|
|
7
|
+
canvas: Canvas;
|
|
8
|
+
ctx: SKRSContext2D;
|
|
8
9
|
};
|
|
9
|
-
export type ImageTransformFunction<Data> = (options: TransformOptions<Data>) =>
|
|
10
|
+
export type ImageTransformFunction<Data> = (options: TransformOptions<Data>) => void | Promise<void>;
|
|
10
11
|
export type ImageImageSrcOptions<Data> = {
|
|
11
12
|
data: Data;
|
|
12
13
|
index: number;
|
|
@@ -27,5 +28,5 @@ export type ImageLayerOptions<Data> = {
|
|
|
27
28
|
export declare class ImageLayer<Data> extends Layer<Data> {
|
|
28
29
|
private options;
|
|
29
30
|
constructor(options: ImageLayerOptions<Data>);
|
|
30
|
-
render({ context:
|
|
31
|
+
render({ context: renderContext, data, index, templateConfig }: RenderOptions<Data>): Promise<void>;
|
|
31
32
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.ImageLayer = void 0;
|
|
7
|
-
const
|
|
4
|
+
const canvas_1 = require("@napi-rs/canvas");
|
|
8
5
|
const layer_1 = require("./layer");
|
|
9
6
|
const resolve_axis_1 = require("../utils/resolve-axis");
|
|
10
7
|
const error_1 = require("../error");
|
|
@@ -13,7 +10,7 @@ class ImageLayer extends layer_1.Layer {
|
|
|
13
10
|
super(options.when);
|
|
14
11
|
this.options = options;
|
|
15
12
|
}
|
|
16
|
-
async render({ context:
|
|
13
|
+
async render({ context: renderContext, data, index = 0, templateConfig }) {
|
|
17
14
|
const localX = (0, resolve_axis_1.resolveAxis)({
|
|
18
15
|
axis: this.options.x,
|
|
19
16
|
data: data,
|
|
@@ -26,34 +23,41 @@ class ImageLayer extends layer_1.Layer {
|
|
|
26
23
|
index: index,
|
|
27
24
|
templateConfig: templateConfig,
|
|
28
25
|
});
|
|
29
|
-
const x =
|
|
30
|
-
const y =
|
|
26
|
+
const x = renderContext.offsetX + localX;
|
|
27
|
+
const y = renderContext.offsetY + localY;
|
|
31
28
|
const src = this.options.src;
|
|
32
29
|
const resolvedImageSource = typeof src === "string" ? src : src({ data: data, index: index });
|
|
30
|
+
const image = await (0, canvas_1.loadImage)(resolvedImageSource);
|
|
31
|
+
const localCanvas = (0, canvas_1.createCanvas)(image.width, image.height);
|
|
32
|
+
const localContext = localCanvas.getContext("2d");
|
|
33
33
|
try {
|
|
34
|
-
|
|
34
|
+
localContext.drawImage(image, 0, 0);
|
|
35
35
|
if (this.options.flipX) {
|
|
36
|
-
|
|
36
|
+
localContext.translate(image.width, 0);
|
|
37
|
+
localContext.scale(-1, 1);
|
|
37
38
|
}
|
|
38
39
|
if (this.options.flipY) {
|
|
39
|
-
|
|
40
|
+
localContext.translate(0, image.height);
|
|
41
|
+
localContext.scale(1, -1);
|
|
40
42
|
}
|
|
41
43
|
if (this.options.rotate !== undefined) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
localContext.translate(image.width / 2, image.height / 2);
|
|
45
|
+
localContext.rotate((this.options.rotate * Math.PI) / 180);
|
|
46
|
+
localContext.translate(-image.width / 2, -image.height / 2);
|
|
45
47
|
}
|
|
46
48
|
if (this.options.transform && this.options.transform.length > 0) {
|
|
47
49
|
for (const transform of this.options.transform) {
|
|
48
|
-
|
|
50
|
+
await transform({
|
|
49
51
|
data: data,
|
|
50
52
|
index: index,
|
|
51
|
-
|
|
53
|
+
canvas: localCanvas,
|
|
54
|
+
ctx: localContext,
|
|
52
55
|
});
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
|
-
const buffer =
|
|
56
|
-
|
|
58
|
+
const buffer = localCanvas.toBuffer("image/png");
|
|
59
|
+
const finalImage = await (0, canvas_1.loadImage)(buffer);
|
|
60
|
+
renderContext.ctx.drawImage(finalImage, x, y);
|
|
57
61
|
}
|
|
58
62
|
catch (error) {
|
|
59
63
|
throw new error_1.XpictError(`Failed to render image layer (${resolvedImageSource}): ${error.message}`);
|
|
@@ -16,5 +16,5 @@ export type LineLayerOptions<Data> = {
|
|
|
16
16
|
export declare class LineLayer<Data> extends Layer<Data> {
|
|
17
17
|
private readonly options;
|
|
18
18
|
constructor(options: LineLayerOptions<Data>);
|
|
19
|
-
render({ context:
|
|
19
|
+
render({ context: renderContext, data, index, templateConfig }: RenderOptions<Data>): Promise<void>;
|
|
20
20
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LineLayer = void 0;
|
|
4
|
-
const canvas_1 = require("canvas");
|
|
5
4
|
const layer_1 = require("./layer");
|
|
6
5
|
const resolve_axis_1 = require("../utils/resolve-axis");
|
|
7
6
|
class LineLayer extends layer_1.Layer {
|
|
@@ -9,41 +8,36 @@ class LineLayer extends layer_1.Layer {
|
|
|
9
8
|
super(options.when);
|
|
10
9
|
this.options = options;
|
|
11
10
|
}
|
|
12
|
-
async render({ context:
|
|
13
|
-
const
|
|
14
|
-
const metadata = await image.metadata();
|
|
15
|
-
const canvasWidth = metadata.width;
|
|
16
|
-
const canvasHeight = metadata.height;
|
|
17
|
-
const x1 = ctx.offsetX +
|
|
11
|
+
async render({ context: renderContext, data, index = 0, templateConfig }) {
|
|
12
|
+
const x1 = renderContext.offsetX +
|
|
18
13
|
(0, resolve_axis_1.resolveAxis)({
|
|
19
14
|
axis: this.options.from.x,
|
|
20
15
|
data: data,
|
|
21
16
|
index: index,
|
|
22
17
|
templateConfig: templateConfig,
|
|
23
18
|
});
|
|
24
|
-
const y1 =
|
|
19
|
+
const y1 = renderContext.offsetY +
|
|
25
20
|
(0, resolve_axis_1.resolveAxis)({
|
|
26
21
|
axis: this.options.from.y,
|
|
27
22
|
data: data,
|
|
28
23
|
index: index,
|
|
29
24
|
templateConfig: templateConfig,
|
|
30
25
|
});
|
|
31
|
-
const x2 =
|
|
26
|
+
const x2 = renderContext.offsetX +
|
|
32
27
|
(0, resolve_axis_1.resolveAxis)({
|
|
33
28
|
axis: this.options.to.x,
|
|
34
29
|
data: data,
|
|
35
30
|
index: index,
|
|
36
31
|
templateConfig: templateConfig,
|
|
37
32
|
});
|
|
38
|
-
const y2 =
|
|
33
|
+
const y2 = renderContext.offsetY +
|
|
39
34
|
(0, resolve_axis_1.resolveAxis)({
|
|
40
35
|
axis: this.options.to.y,
|
|
41
36
|
data: data,
|
|
42
37
|
index: index,
|
|
43
38
|
templateConfig: templateConfig,
|
|
44
39
|
});
|
|
45
|
-
const
|
|
46
|
-
const context = canvas.getContext("2d");
|
|
40
|
+
const context = renderContext.ctx;
|
|
47
41
|
context.strokeStyle = this.options.color;
|
|
48
42
|
context.lineWidth = this.options.width;
|
|
49
43
|
context.lineCap = "round";
|
|
@@ -51,14 +45,6 @@ class LineLayer extends layer_1.Layer {
|
|
|
51
45
|
context.moveTo(x1, y1);
|
|
52
46
|
context.lineTo(x2, y2);
|
|
53
47
|
context.stroke();
|
|
54
|
-
const lineBuffer = canvas.toBuffer();
|
|
55
|
-
ctx.image = image.composite([
|
|
56
|
-
{
|
|
57
|
-
input: lineBuffer,
|
|
58
|
-
top: 0,
|
|
59
|
-
left: 0,
|
|
60
|
-
},
|
|
61
|
-
]);
|
|
62
48
|
}
|
|
63
49
|
}
|
|
64
50
|
exports.LineLayer = LineLayer;
|
|
@@ -12,5 +12,5 @@ export type RectangleLayerOptions<Data> = {
|
|
|
12
12
|
export declare class RectangleLayer<Data> extends Layer<Data> {
|
|
13
13
|
private readonly options;
|
|
14
14
|
constructor(options: RectangleLayerOptions<Data>);
|
|
15
|
-
render({ context:
|
|
15
|
+
render({ context: renderContext, data, index, templateConfig }: RenderOptions<Data>): Promise<void>;
|
|
16
16
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RectangleLayer = void 0;
|
|
4
|
-
const canvas_1 = require("canvas");
|
|
5
4
|
const layer_1 = require("./layer");
|
|
6
5
|
const resolve_axis_1 = require("../utils/resolve-axis");
|
|
7
6
|
class RectangleLayer extends layer_1.Layer {
|
|
@@ -9,11 +8,7 @@ class RectangleLayer extends layer_1.Layer {
|
|
|
9
8
|
super(options.when);
|
|
10
9
|
this.options = options;
|
|
11
10
|
}
|
|
12
|
-
async render({ context:
|
|
13
|
-
const image = ctx.image;
|
|
14
|
-
const metadata = await image.metadata();
|
|
15
|
-
const canvasWidth = metadata.width;
|
|
16
|
-
const canvasHeight = metadata.height;
|
|
11
|
+
async render({ context: renderContext, data, index = 0, templateConfig }) {
|
|
17
12
|
const localX = (0, resolve_axis_1.resolveAxis)({
|
|
18
13
|
axis: this.options.x,
|
|
19
14
|
data: data,
|
|
@@ -26,11 +21,10 @@ class RectangleLayer extends layer_1.Layer {
|
|
|
26
21
|
index: index,
|
|
27
22
|
templateConfig: templateConfig,
|
|
28
23
|
});
|
|
29
|
-
const x =
|
|
30
|
-
const y =
|
|
24
|
+
const x = renderContext.offsetX + localX;
|
|
25
|
+
const y = renderContext.offsetY + localY;
|
|
31
26
|
const { width, height, fill, borderRadius } = this.options;
|
|
32
|
-
const
|
|
33
|
-
const context = canvas.getContext("2d");
|
|
27
|
+
const context = renderContext.ctx;
|
|
34
28
|
context.fillStyle = fill;
|
|
35
29
|
if (borderRadius && borderRadius > 0) {
|
|
36
30
|
const r = Math.min(borderRadius, width / 2, height / 2);
|
|
@@ -50,14 +44,6 @@ class RectangleLayer extends layer_1.Layer {
|
|
|
50
44
|
else {
|
|
51
45
|
context.fillRect(x, y, width, height);
|
|
52
46
|
}
|
|
53
|
-
const rectangleBuffer = canvas.toBuffer();
|
|
54
|
-
ctx.image = image.composite([
|
|
55
|
-
{
|
|
56
|
-
input: rectangleBuffer,
|
|
57
|
-
top: 0,
|
|
58
|
-
left: 0,
|
|
59
|
-
},
|
|
60
|
-
]);
|
|
61
47
|
}
|
|
62
48
|
}
|
|
63
49
|
exports.RectangleLayer = RectangleLayer;
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RepeatLayer = void 0;
|
|
4
4
|
const layer_1 = require("./layer");
|
|
5
5
|
const resolve_axis_1 = require("../utils/resolve-axis");
|
|
6
|
-
const commit_frame_1 = require("../utils/commit-frame");
|
|
7
6
|
class RepeatLayer extends layer_1.Layer {
|
|
8
7
|
constructor(options) {
|
|
9
8
|
super(options.when);
|
|
@@ -38,7 +37,6 @@ class RepeatLayer extends layer_1.Layer {
|
|
|
38
37
|
index: index,
|
|
39
38
|
templateConfig: templateConfig,
|
|
40
39
|
});
|
|
41
|
-
ctx.image = await (0, commit_frame_1.commitFrame)(ctx.image);
|
|
42
40
|
ctx.offsetX = prevX;
|
|
43
41
|
ctx.offsetY = prevY;
|
|
44
42
|
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { Layer, RenderOptions, WhenFunction } from "./layer";
|
|
2
2
|
import { Axis } from "../utils/resolve-axis";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
name?: string;
|
|
7
|
-
filePath?: string;
|
|
8
|
-
};
|
|
9
|
-
export type TextAnchor = "top-left" | "top-center" | "top-right" | "middle-left" | "middle-center" | "middle-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
|
3
|
+
import { ValueOfEnum } from "../utils/value-of-enum";
|
|
4
|
+
import { TextBaseline, TextAlign } from "../enums";
|
|
5
|
+
import { Font } from "../utils/font";
|
|
10
6
|
export type Stroke = {
|
|
11
7
|
fill: string;
|
|
12
8
|
width: number;
|
|
@@ -16,17 +12,13 @@ export type TextFunctionOptions<Data> = {
|
|
|
16
12
|
index: number;
|
|
17
13
|
};
|
|
18
14
|
export type StringFunction<Data> = (options: TextFunctionOptions<Data>) => string;
|
|
19
|
-
export type TextAlign = "left" | "center" | "right";
|
|
20
|
-
export type TextBaseline = "top" | "hanging" | "middle" | "alphabetic" | "ideographic" | "bottom";
|
|
21
15
|
export type TextLayerOptions<Data> = {
|
|
22
16
|
text: StringFunction<Data> | string;
|
|
23
|
-
font:
|
|
17
|
+
font: Font;
|
|
24
18
|
x: Axis<Data>;
|
|
25
19
|
y: Axis<Data>;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
align?: TextAlign;
|
|
29
|
-
baseline?: TextBaseline;
|
|
20
|
+
align?: ValueOfEnum<TextAlign>;
|
|
21
|
+
baseline?: ValueOfEnum<TextBaseline>;
|
|
30
22
|
stroke?: Stroke;
|
|
31
23
|
rotation?: number;
|
|
32
24
|
when?: WhenFunction<Data>;
|
|
@@ -34,5 +26,5 @@ export type TextLayerOptions<Data> = {
|
|
|
34
26
|
export declare class TextLayer<Data> extends Layer<Data> {
|
|
35
27
|
private readonly options;
|
|
36
28
|
constructor(options: TextLayerOptions<Data>);
|
|
37
|
-
render({ context:
|
|
29
|
+
render({ context: renderContext, data, index, templateConfig }: RenderOptions<Data>): Promise<void>;
|
|
38
30
|
}
|
|
@@ -1,43 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TextLayer = void 0;
|
|
4
|
-
const canvas_1 = require("canvas");
|
|
5
4
|
const layer_1 = require("./layer");
|
|
6
5
|
const resolve_axis_1 = require("../utils/resolve-axis");
|
|
7
|
-
const anchorOffsets = {
|
|
8
|
-
"top-left": (w, h) => ({ x: 0, y: 0 }),
|
|
9
|
-
"top-center": (w, h) => ({ x: -w / 2, y: 0 }),
|
|
10
|
-
"top-right": (w, h) => ({ x: -w, y: 0 }),
|
|
11
|
-
"middle-left": (w, h) => ({ x: 0, y: -h / 2 }),
|
|
12
|
-
"middle-center": (w, h) => ({ x: -w / 2, y: -h / 2 }),
|
|
13
|
-
"middle-right": (w, h) => ({ x: -w, y: -h / 2 }),
|
|
14
|
-
"bottom-left": (w, h) => ({ x: 0, y: -h }),
|
|
15
|
-
"bottom-center": (w, h) => ({ x: -w / 2, y: -h }),
|
|
16
|
-
"bottom-right": (w, h) => ({ x: -w, y: -h }),
|
|
17
|
-
};
|
|
18
6
|
class TextLayer extends layer_1.Layer {
|
|
19
7
|
constructor(options) {
|
|
20
8
|
super(options.when);
|
|
21
9
|
this.options = options;
|
|
22
10
|
}
|
|
23
|
-
async render({ context:
|
|
11
|
+
async render({ context: renderContext, data, index = 0, templateConfig }) {
|
|
24
12
|
var _a, _b;
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const height = imageMetadata.height;
|
|
28
|
-
const { text, font, x: initialX, y: initialY, backgroundColor = "transparent", anchor = "top-left", stroke, rotation = 0, align, baseline, } = this.options;
|
|
29
|
-
if (!font.name) {
|
|
30
|
-
throw new Error("Font name is required");
|
|
31
|
-
}
|
|
32
|
-
if (font.filePath) {
|
|
33
|
-
(0, canvas_1.registerFont)(font.filePath, { family: font.name });
|
|
34
|
-
}
|
|
35
|
-
const canvas = (0, canvas_1.createCanvas)(width, height);
|
|
36
|
-
const context = canvas.getContext("2d");
|
|
37
|
-
if (backgroundColor !== "transparent") {
|
|
38
|
-
context.fillStyle = backgroundColor;
|
|
39
|
-
context.fillRect(0, 0, width, height);
|
|
40
|
-
}
|
|
13
|
+
const { text, font, x: initialX, y: initialY, stroke, rotation = 0, align, baseline } = this.options;
|
|
14
|
+
const context = renderContext.ctx;
|
|
41
15
|
if (align) {
|
|
42
16
|
context.textAlign = align;
|
|
43
17
|
}
|
|
@@ -47,10 +21,6 @@ class TextLayer extends layer_1.Layer {
|
|
|
47
21
|
context.font = `${font.size}px ${(_a = font.name) !== null && _a !== void 0 ? _a : "Arial"}`;
|
|
48
22
|
context.fillStyle = (_b = font.color) !== null && _b !== void 0 ? _b : "#000";
|
|
49
23
|
const content = typeof text === "string" ? text : text({ data: data, index: index });
|
|
50
|
-
const metrics = context.measureText(content);
|
|
51
|
-
const textWidth = metrics.width;
|
|
52
|
-
const textHeight = font.size;
|
|
53
|
-
const offset = anchorOffsets[anchor](textWidth, textHeight);
|
|
54
24
|
const localX = (0, resolve_axis_1.resolveAxis)({
|
|
55
25
|
axis: initialX,
|
|
56
26
|
data: data,
|
|
@@ -63,12 +33,10 @@ class TextLayer extends layer_1.Layer {
|
|
|
63
33
|
index: index,
|
|
64
34
|
templateConfig: templateConfig,
|
|
65
35
|
});
|
|
66
|
-
const x =
|
|
67
|
-
const y =
|
|
68
|
-
const adjustedX = x + offset.x;
|
|
69
|
-
const adjustedY = y + offset.y;
|
|
36
|
+
const x = renderContext.offsetX + localX;
|
|
37
|
+
const y = renderContext.offsetY + localY;
|
|
70
38
|
context.save();
|
|
71
|
-
context.translate(
|
|
39
|
+
context.translate(x, y);
|
|
72
40
|
context.rotate((rotation * Math.PI) / 180);
|
|
73
41
|
if (stroke) {
|
|
74
42
|
context.strokeStyle = stroke.fill;
|
|
@@ -78,8 +46,6 @@ class TextLayer extends layer_1.Layer {
|
|
|
78
46
|
}
|
|
79
47
|
context.fillText(content, 0, 0);
|
|
80
48
|
context.restore();
|
|
81
|
-
const buffer = canvas.toBuffer();
|
|
82
|
-
ctx.image = ctx.image.composite([{ input: buffer, top: 0, left: 0 }]);
|
|
83
49
|
}
|
|
84
50
|
}
|
|
85
51
|
exports.TextLayer = TextLayer;
|
package/dist/render-context.d.ts
CHANGED
package/dist/template.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Canvas } from "@napi-rs/canvas";
|
|
2
2
|
import { Layer } from "./layers/layer";
|
|
3
3
|
export type TemplateConfig = {
|
|
4
4
|
width: number;
|
|
5
5
|
height: number;
|
|
6
|
-
fill?:
|
|
6
|
+
fill?: string;
|
|
7
7
|
};
|
|
8
8
|
export type InputTemplateOptions<Data> = {
|
|
9
9
|
imagePath?: undefined;
|
|
@@ -22,5 +22,5 @@ export type TemplateOptions<Data> = {
|
|
|
22
22
|
export declare class Template<Data> {
|
|
23
23
|
readonly options: TemplateOptions<Data>;
|
|
24
24
|
constructor(options: InputTemplateOptions<Data>);
|
|
25
|
-
render(data: Data): Promise<
|
|
25
|
+
render(data: Data): Promise<Canvas>;
|
|
26
26
|
}
|
package/dist/template.js
CHANGED
|
@@ -1,44 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.Template = void 0;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const transparentFill = {
|
|
10
|
-
r: 0,
|
|
11
|
-
g: 0,
|
|
12
|
-
b: 0,
|
|
13
|
-
alpha: 0,
|
|
14
|
-
};
|
|
4
|
+
const canvas_1 = require("@napi-rs/canvas");
|
|
5
|
+
const error_1 = require("./error");
|
|
15
6
|
class Template {
|
|
16
7
|
constructor(options) {
|
|
17
8
|
this.options = options;
|
|
18
9
|
}
|
|
19
10
|
async render(data) {
|
|
20
|
-
let
|
|
11
|
+
let canvas;
|
|
21
12
|
if (this.options.config) {
|
|
22
13
|
const { width, height, fill } = this.options.config;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
});
|
|
14
|
+
canvas = (0, canvas_1.createCanvas)(width, height);
|
|
15
|
+
if (fill) {
|
|
16
|
+
const ctx = canvas.getContext("2d");
|
|
17
|
+
ctx.fillStyle = fill;
|
|
18
|
+
ctx.fillRect(0, 0, width, height);
|
|
19
|
+
}
|
|
31
20
|
}
|
|
32
|
-
else {
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
else if (this.options.imagePath) {
|
|
22
|
+
const backgroundImage = await (0, canvas_1.loadImage)(this.options.imagePath);
|
|
23
|
+
canvas = (0, canvas_1.createCanvas)(backgroundImage.width, backgroundImage.height);
|
|
35
24
|
this.options.config = {
|
|
36
|
-
width:
|
|
37
|
-
height:
|
|
25
|
+
width: backgroundImage.width,
|
|
26
|
+
height: backgroundImage.height,
|
|
38
27
|
};
|
|
39
28
|
}
|
|
40
|
-
|
|
41
|
-
|
|
29
|
+
else {
|
|
30
|
+
throw new error_1.XpictError("No config or imagePath provided");
|
|
31
|
+
}
|
|
32
|
+
const ctx = canvas.getContext("2d");
|
|
33
|
+
const renderContext = {
|
|
34
|
+
ctx: ctx,
|
|
42
35
|
offsetX: 0,
|
|
43
36
|
offsetY: 0,
|
|
44
37
|
};
|
|
@@ -46,13 +39,12 @@ class Template {
|
|
|
46
39
|
if (!layer.shouldRender(data))
|
|
47
40
|
continue;
|
|
48
41
|
await layer.render({
|
|
49
|
-
context:
|
|
42
|
+
context: renderContext,
|
|
50
43
|
data: data,
|
|
51
44
|
templateConfig: this.options.config,
|
|
52
45
|
});
|
|
53
|
-
ctx.image = await (0, commit_frame_1.commitFrame)(ctx.image);
|
|
54
46
|
}
|
|
55
|
-
return
|
|
47
|
+
return canvas;
|
|
56
48
|
}
|
|
57
49
|
}
|
|
58
50
|
exports.Template = Template;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type FontOptions = {
|
|
2
|
+
size: number;
|
|
3
|
+
color?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
filePath?: string;
|
|
6
|
+
};
|
|
7
|
+
export type FontConfigOptions = {
|
|
8
|
+
color?: string;
|
|
9
|
+
name: string;
|
|
10
|
+
filePath?: string;
|
|
11
|
+
};
|
|
12
|
+
export type PartialFontOptions = {
|
|
13
|
+
size: number;
|
|
14
|
+
color?: string;
|
|
15
|
+
};
|
|
16
|
+
export type Font = Omit<FontOptions, "filePath">;
|
|
17
|
+
export declare function font(options: FontConfigOptions): (partialOptions: PartialFontOptions) => Font;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.font = font;
|
|
7
|
+
const canvas_1 = require("@napi-rs/canvas");
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
function font(options) {
|
|
10
|
+
if (options.filePath) {
|
|
11
|
+
const buffer = node_fs_1.default.readFileSync(options.filePath);
|
|
12
|
+
canvas_1.GlobalFonts.register(buffer, options.name);
|
|
13
|
+
}
|
|
14
|
+
return (partialOptions) => {
|
|
15
|
+
const { filePath: _, ...rest } = {
|
|
16
|
+
...options,
|
|
17
|
+
...partialOptions,
|
|
18
|
+
};
|
|
19
|
+
return rest;
|
|
20
|
+
};
|
|
21
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from "./wrap-text";
|
|
2
|
-
export * from "./font
|
|
2
|
+
export * from "./font";
|
package/dist/utils/index.js
CHANGED
|
@@ -15,4 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./wrap-text"), exports);
|
|
18
|
-
__exportStar(require("./font
|
|
18
|
+
__exportStar(require("./font"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ValueOfEnum<Enum extends string | number> = Enum | `${Enum}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xpict",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Xpict é uma biblioteca para a geração de imagens padronizadas a partir de modelos declarativos.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/jest": "^30.0.0",
|
|
30
30
|
"@types/node": "^25.0.3",
|
|
31
|
+
"eslint": "^9.39.2",
|
|
31
32
|
"eslint-config-prettier": "^10.1.8",
|
|
32
|
-
"eslint-plugin-prettier": "^5.5.
|
|
33
|
-
"prettier": "^3.
|
|
33
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
34
|
+
"prettier": "^3.8.1",
|
|
34
35
|
"prettier-plugin-sort-imports": "^1.8.9",
|
|
35
36
|
"ts-jest": "^29.4.6",
|
|
36
37
|
"ts-node": "^10.9.2",
|
|
37
38
|
"typescript": "^5.9.3"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
|
-
"canvas": "^
|
|
41
|
-
"sharp": "^0.34.5"
|
|
41
|
+
"@napi-rs/canvas": "^0.1.88"
|
|
42
42
|
}
|
|
43
43
|
}
|