@igxjs/text2png 3.0.2 → 3.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/LICENSE +2 -1
- package/README.md +183 -55
- package/bin/text2png.js +60 -36
- package/index.js +306 -82
- package/package.json +6 -5
- package/types.d.ts +94 -0
package/LICENSE
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2018
|
|
3
|
+
Copyright (c) 2018 Taka Kojima (original author)
|
|
4
|
+
Copyright (c) 2026 Michael (igxjs) (modifications and maintenance)
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
7
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,89 +1,217 @@
|
|
|
1
|
-
#
|
|
1
|
+
# text2png
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Convert text to PNG images with optional fixed dimensions and auto-scaling.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @igxjs/text2png
|
|
7
9
|
```
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
**Requirements:** [node-canvas](https://github.com/Automattic/node-canvas) (see [installation guide](https://github.com/Automattic/node-canvas/wiki))
|
|
10
12
|
|
|
11
|
-
## Quick
|
|
13
|
+
## Quick Start
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
```js
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const text2png = require('@igxjs/text2png');
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
// Basic usage
|
|
20
|
+
fs.writeFileSync('output.png', text2png('Hello World!'));
|
|
21
|
+
|
|
22
|
+
// With styling
|
|
23
|
+
fs.writeFileSync('styled.png', text2png('Hello!', {
|
|
24
|
+
font: '40px Arial',
|
|
25
|
+
color: 'blue',
|
|
26
|
+
backgroundColor: 'white',
|
|
27
|
+
padding: 20
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Fixed size (great for avatars!)
|
|
31
|
+
fs.writeFileSync('avatar.png', text2png('IGX', {
|
|
32
|
+
width: 200,
|
|
33
|
+
height: 200,
|
|
34
|
+
font: '80px Arial',
|
|
35
|
+
textColor: 'white',
|
|
36
|
+
backgroundColor: '#4F46E5',
|
|
37
|
+
textAlign: 'center',
|
|
38
|
+
verticalAlign: 'middle'
|
|
39
|
+
}));
|
|
18
40
|
```
|
|
19
41
|
|
|
42
|
+
## API
|
|
43
|
+
|
|
44
|
+
### `text2png(text, options)`
|
|
45
|
+
|
|
46
|
+
#### Options
|
|
47
|
+
|
|
48
|
+
| Option | Type | Default | Description |
|
|
49
|
+
|--------|------|---------|-------------|
|
|
50
|
+
| **Text Styling** |
|
|
51
|
+
| `font` | string | `'30px sans-serif'` | CSS font string |
|
|
52
|
+
| `color` / `textColor` | string | `'black'` | Text color (any CSS color) |
|
|
53
|
+
| `textAlign` | string | `'left'` | Horizontal alignment: `'left'`, `'center'`, `'right'` |
|
|
54
|
+
| `strokeWidth` | number | `0` | Text stroke/outline width |
|
|
55
|
+
| `strokeColor` | string | `'white'` | Text stroke color |
|
|
56
|
+
| **Background & Spacing** |
|
|
57
|
+
| `backgroundColor` / `bgColor` | string | `transparent` | Background color |
|
|
58
|
+
| `padding` | number | `0` | Padding on all sides |
|
|
59
|
+
| `paddingLeft/Right/Top/Bottom` | number | `0` | Individual side padding |
|
|
60
|
+
| `lineSpacing` | number | `0` | Extra spacing between lines |
|
|
61
|
+
| **Border** |
|
|
62
|
+
| `borderWidth` | number | `0` | Border width on all sides |
|
|
63
|
+
| `borderLeft/Right/Top/BottomWidth` | number | `0` | Individual border widths |
|
|
64
|
+
| `borderColor` | string | `'black'` | Border color |
|
|
65
|
+
| **Fixed Dimensions** ⭐ |
|
|
66
|
+
| `width` | number | `null` | Fixed width (auto-scales content to fit) |
|
|
67
|
+
| `height` | number | `null` | Fixed height (auto-scales content to fit) |
|
|
68
|
+
| `minFontSize` | number | `8` | Minimum font size when auto-scaling |
|
|
69
|
+
| `verticalAlign` | string | `'middle'` | Vertical alignment: `'top'`, `'middle'`, `'bottom'` |
|
|
70
|
+
| **Font Loading** |
|
|
71
|
+
| `localFontPath` | string | `null` | Path to custom font file |
|
|
72
|
+
| `localFontName` | string | `null` | Name to register custom font as |
|
|
73
|
+
| **Output** |
|
|
74
|
+
| `output` | string | `'buffer'` | Output format: `'buffer'`, `'stream'`, `'dataURL'`, `'canvas'` |
|
|
75
|
+
| `imageSmoothingEnabled` | boolean | `false` | Enable image smoothing |
|
|
76
|
+
|
|
77
|
+
## Examples
|
|
78
|
+
|
|
79
|
+
### Auto-sized Image (Default)
|
|
20
80
|
```js
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
81
|
+
const image = text2png('Hello\nWorld', {
|
|
82
|
+
font: '30px Arial',
|
|
83
|
+
color: 'teal',
|
|
84
|
+
backgroundColor: 'linen',
|
|
85
|
+
padding: 20
|
|
86
|
+
});
|
|
87
|
+
// Image size automatically fits the text
|
|
24
88
|
```
|
|
25
89
|
|
|
26
|
-
|
|
90
|
+

|
|
91
|
+
|
|
92
|
+
### Fixed Size with Auto-scaling
|
|
93
|
+
```js
|
|
94
|
+
// Long text automatically scales down to fit
|
|
95
|
+
const banner = text2png('This is a very long text that needs to fit!', {
|
|
96
|
+
width: 300,
|
|
97
|
+
height: 100,
|
|
98
|
+
font: '40px Arial',
|
|
99
|
+
textAlign: 'center',
|
|
100
|
+
verticalAlign: 'middle',
|
|
101
|
+
backgroundColor: '#f0f0f0'
|
|
102
|
+
});
|
|
103
|
+
// Text font size automatically scales to fit 300x100
|
|
104
|
+
```
|
|
27
105
|
|
|
28
|
-
|
|
106
|
+

|
|
29
107
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|option.borderWidth|0|
|
|
43
|
-
|option.border(Left\|Top\|Right\|Bottom)Width|0|
|
|
44
|
-
|option.borderColor|'black'|
|
|
45
|
-
|option.localFontPath||
|
|
46
|
-
|option.localFontName||
|
|
47
|
-
|option.output|'buffer'|
|
|
108
|
+
### User Avatar
|
|
109
|
+
```js
|
|
110
|
+
const avatar = text2png('IGX', {
|
|
111
|
+
width: 200,
|
|
112
|
+
height: 200,
|
|
113
|
+
font: '80px Arial',
|
|
114
|
+
textColor: 'white',
|
|
115
|
+
backgroundColor: '#4F46E5',
|
|
116
|
+
textAlign: 'center',
|
|
117
|
+
verticalAlign: 'middle'
|
|
118
|
+
});
|
|
119
|
+
```
|
|
48
120
|
|
|
49
|
-
|
|
121
|
+

|
|
50
122
|
|
|
51
|
-
|
|
123
|
+
### Multi-line with Fixed Dimensions
|
|
124
|
+
```js
|
|
125
|
+
const multiLine = text2png('Line 1\nLine 2\nLine 3', {
|
|
126
|
+
width: 250,
|
|
127
|
+
height: 150,
|
|
128
|
+
font: '24px Arial',
|
|
129
|
+
textColor: '#333',
|
|
130
|
+
backgroundColor: '#fff',
|
|
131
|
+
textAlign: 'center',
|
|
132
|
+
verticalAlign: 'middle',
|
|
133
|
+
padding: 10,
|
|
134
|
+
borderWidth: 1,
|
|
135
|
+
borderColor: '#ccc',
|
|
136
|
+
lineSpacing: 8 // Add spacing between lines
|
|
137
|
+
});
|
|
138
|
+
```
|
|
52
139
|
|
|
53
|
-
|
|
140
|
+

|
|
54
141
|
|
|
55
|
-
|
|
142
|
+
### Fixed Width, Auto Height
|
|
143
|
+
```js
|
|
144
|
+
const card = text2png('Hello World', {
|
|
145
|
+
width: 400, // Fixed width
|
|
146
|
+
// Height auto-calculates to 65px
|
|
147
|
+
font: '30px Arial',
|
|
148
|
+
textColor: 'blue',
|
|
149
|
+
backgroundColor: 'white',
|
|
150
|
+
textAlign: 'center',
|
|
151
|
+
padding: 20,
|
|
152
|
+
borderWidth: 2,
|
|
153
|
+
borderColor: 'blue'
|
|
154
|
+
});
|
|
155
|
+
```
|
|
56
156
|
|
|
57
|
-
|
|
157
|
+

|
|
58
158
|
|
|
159
|
+
### Custom Font
|
|
59
160
|
```js
|
|
60
|
-
text2png('
|
|
161
|
+
const styled = text2png('Fancy Text', {
|
|
61
162
|
font: '50px Lobster',
|
|
62
|
-
localFontPath: 'fonts/
|
|
163
|
+
localFontPath: './fonts/Lobster-Regular.ttf',
|
|
63
164
|
localFontName: 'Lobster'
|
|
64
165
|
});
|
|
65
166
|
```
|
|
66
167
|
|
|
67
|
-
##
|
|
168
|
+
## How Fixed Dimensions Work
|
|
68
169
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
170
|
+
When you specify `width` and/or `height`:
|
|
171
|
+
|
|
172
|
+
1. ✅ **Fits naturally** → Content is centered based on alignment options
|
|
173
|
+
2. 📏 **Too large** → Font automatically scales down to fit (respects `minFontSize`)
|
|
174
|
+
3. ✂️ **Exceeds minimum** → Content is clipped at boundaries
|
|
175
|
+
|
|
176
|
+
Without `width`/`height`, the image auto-sizes to fit content (original behavior).
|
|
177
|
+
|
|
178
|
+
## CLI Usage
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npm install -g @igxjs/text2png
|
|
182
|
+
text2png --help
|
|
183
|
+
text2png -t "Hello!" -o output.png
|
|
184
|
+
|
|
185
|
+
# Create an avatar
|
|
186
|
+
text2png -t "IGX" -o avatar.png --width 200 --height 200 \
|
|
187
|
+
--font "80px Arial" --color white --backgroundColor "#4F46E5" \
|
|
188
|
+
--textAlign center --verticalAlign middle
|
|
73
189
|
```
|
|
74
190
|
|
|
75
|
-
##
|
|
191
|
+
## Output Formats
|
|
76
192
|
|
|
77
193
|
```js
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
194
|
+
// Buffer (default) - for file writing
|
|
195
|
+
const buffer = text2png('text', { output: 'buffer' });
|
|
196
|
+
fs.writeFileSync('out.png', buffer);
|
|
197
|
+
|
|
198
|
+
// Stream - for piping
|
|
199
|
+
const stream = text2png('text', { output: 'stream' });
|
|
200
|
+
stream.pipe(fs.createWriteStream('out.png'));
|
|
201
|
+
|
|
202
|
+
// Data URL - for HTML/CSS
|
|
203
|
+
const dataURL = text2png('text', { output: 'dataURL' });
|
|
204
|
+
// image/png;base64,...
|
|
205
|
+
|
|
206
|
+
// Canvas - for further manipulation
|
|
207
|
+
const canvas = text2png('text', { output: 'canvas' });
|
|
208
|
+
const ctx = canvas.getContext('2d');
|
|
85
209
|
```
|
|
86
210
|
|
|
87
|
-
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT
|
|
214
|
+
|
|
215
|
+
## Credits
|
|
88
216
|
|
|
89
|
-
|
|
217
|
+
Built on [node-canvas](https://github.com/Automattic/node-canvas). Updated for Node.js v24+ compatibility.
|
package/bin/text2png.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
5
6
|
const commander = require("commander");
|
|
6
|
-
|
|
7
|
-
const
|
|
7
|
+
|
|
8
|
+
const { version } = require("../package.json");
|
|
9
|
+
const text2png = require('../index.js');
|
|
8
10
|
|
|
9
11
|
commander
|
|
10
12
|
.version(version)
|
|
@@ -18,7 +20,12 @@ commander
|
|
|
18
20
|
.option("-s, --lineSpacing <number>", "line spacing")
|
|
19
21
|
|
|
20
22
|
.option("--strokeWidth <number>", "stroke width")
|
|
21
|
-
.option("--strokeColor <
|
|
23
|
+
.option("--strokeColor <color>", "stroke color")
|
|
24
|
+
|
|
25
|
+
.option("--width <number>", "fixed width in pixels")
|
|
26
|
+
.option("--height <number>", "fixed height in pixels")
|
|
27
|
+
.option("--minFontSize <number>", "minimum font size when auto-scaling (default: 8)")
|
|
28
|
+
.option("--verticalAlign <alignment>", "vertical alignment: top, middle, bottom (default: middle)")
|
|
22
29
|
|
|
23
30
|
.option(
|
|
24
31
|
"-p, --padding <number>",
|
|
@@ -47,40 +54,57 @@ commander
|
|
|
47
54
|
|
|
48
55
|
.parse(process.argv);
|
|
49
56
|
|
|
57
|
+
// Helper function to convert string to number
|
|
58
|
+
const toNumber = value => value ? +value : undefined;
|
|
59
|
+
|
|
60
|
+
// Helper function to build options object from commander
|
|
61
|
+
const buildOptions = (commander) => ({
|
|
62
|
+
font: commander.font,
|
|
63
|
+
textAlign: commander.textAlign,
|
|
64
|
+
color: commander.color,
|
|
65
|
+
backgroundColor: commander.backgroundColor,
|
|
66
|
+
lineSpacing: toNumber(commander.lineSpacing),
|
|
67
|
+
|
|
68
|
+
strokeWidth: toNumber(commander.strokeWidth),
|
|
69
|
+
strokeColor: commander.strokeColor,
|
|
70
|
+
|
|
71
|
+
padding: toNumber(commander.padding),
|
|
72
|
+
paddingLeft: toNumber(commander.paddingLeft),
|
|
73
|
+
paddingTop: toNumber(commander.paddingTop),
|
|
74
|
+
paddingRight: toNumber(commander.paddingRight),
|
|
75
|
+
paddingBottom: toNumber(commander.paddingBottom),
|
|
76
|
+
|
|
77
|
+
borderWidth: toNumber(commander.borderWidth),
|
|
78
|
+
borderLeftWidth: toNumber(commander.borderLeftWidth),
|
|
79
|
+
borderTopWidth: toNumber(commander.borderTopWidth),
|
|
80
|
+
borderRightWidth: toNumber(commander.borderRightWidth),
|
|
81
|
+
borderBottomWidth: toNumber(commander.borderBottomWidth),
|
|
82
|
+
borderColor: commander.borderColor,
|
|
83
|
+
|
|
84
|
+
localFontPath: commander.localFontPath,
|
|
85
|
+
localFontName: commander.localFontName,
|
|
86
|
+
|
|
87
|
+
width: toNumber(commander.width),
|
|
88
|
+
height: toNumber(commander.height),
|
|
89
|
+
minFontSize: toNumber(commander.minFontSize),
|
|
90
|
+
verticalAlign: commander.verticalAlign,
|
|
91
|
+
|
|
92
|
+
output: "stream",
|
|
93
|
+
imageSmoothingEnabled: false
|
|
94
|
+
});
|
|
95
|
+
|
|
50
96
|
const exec = text => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
textAlign: commander.textAlign,
|
|
55
|
-
color: commander.color,
|
|
56
|
-
backgroundColor: commander.backgroundColor,
|
|
57
|
-
lineSpacing: commander.lineSpacing && +commander.lineSpacing,
|
|
58
|
-
|
|
59
|
-
padding: commander.padding && +commander.padding,
|
|
60
|
-
paddingLeft: commander.paddingLeft && +commander.paddingLeft,
|
|
61
|
-
paddingTop: commander.paddingTop && +commander.paddingTop,
|
|
62
|
-
paddingRight: commander.paddingRight && +commander.paddingRight,
|
|
63
|
-
paddingBottom: commander.paddingBottom && +commander.paddingBottom,
|
|
64
|
-
|
|
65
|
-
borderWidth: commander.borderWidth && +commander.borderWidth,
|
|
66
|
-
borderLeftWidth: commander.borderLeftWidth && +commander.borderLeftWidth,
|
|
67
|
-
borderTopWidth: commander.borderTopWidth && +commander.borderTopWidth,
|
|
68
|
-
borderRightWidth:
|
|
69
|
-
commander.borderRightWidth && +commander.borderRightWidth,
|
|
70
|
-
borderBottomWidth:
|
|
71
|
-
commander.borderBottomWidth && +commander.borderBottomWidth,
|
|
72
|
-
borderColor: commander.borderColor,
|
|
73
|
-
|
|
74
|
-
localFontPath: commander.localFontPath,
|
|
75
|
-
localFontName: commander.localFontName,
|
|
76
|
-
|
|
77
|
-
output: "stream"
|
|
78
|
-
});
|
|
79
|
-
const outputPath = path.resolve(process.cwd(), commander.output);
|
|
80
|
-
stream.pipe(fs.createWriteStream(outputPath));
|
|
81
|
-
} else {
|
|
97
|
+
const textInput = commander.text || text;
|
|
98
|
+
|
|
99
|
+
if (!textInput || !commander.output) {
|
|
82
100
|
commander.outputHelp();
|
|
101
|
+
return;
|
|
83
102
|
}
|
|
103
|
+
|
|
104
|
+
const options = buildOptions(commander);
|
|
105
|
+
const stream = text2png(textInput, options);
|
|
106
|
+
const outputPath = path.resolve(process.cwd(), commander.output);
|
|
107
|
+
stream.pipe(fs.createWriteStream(outputPath));
|
|
84
108
|
};
|
|
85
109
|
|
|
86
110
|
if (process.stdin.isTTY) {
|
package/index.js
CHANGED
|
@@ -7,30 +7,54 @@ const { registerFont, createCanvas } = require("canvas");
|
|
|
7
7
|
* @returns {string|Buffer|import('node:stream').Readable|import('canvas').Canvas} Returns PNG data as specified by options.output
|
|
8
8
|
*/
|
|
9
9
|
const text2png = (text, options = {}) => {
|
|
10
|
-
// Options
|
|
11
10
|
options = parseOptions(options);
|
|
11
|
+
registerCustomFont(options);
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
const canvas = createCanvas(0, 0);
|
|
14
|
+
const ctx = canvas.getContext("2d");
|
|
15
|
+
|
|
16
|
+
// Measure text and calculate natural dimensions
|
|
17
|
+
const textMetrics = measureTextMetrics(ctx, text, options);
|
|
18
|
+
const naturalDimensions = calculateNaturalDimensions(textMetrics, options);
|
|
19
|
+
|
|
20
|
+
// Apply dimensions and scaling
|
|
21
|
+
const dimensions = applyDimensions(canvas, textMetrics, naturalDimensions, options);
|
|
22
|
+
|
|
23
|
+
// Render background and border
|
|
24
|
+
renderBackground(ctx, canvas, options);
|
|
25
|
+
|
|
26
|
+
// Configure context for text rendering
|
|
27
|
+
configureContext(ctx, dimensions.finalFont, dimensions.scaleFactor, options);
|
|
28
|
+
|
|
29
|
+
// Calculate positioning offsets
|
|
30
|
+
const offsets = calculateOffsets(canvas, dimensions, options);
|
|
31
|
+
|
|
32
|
+
// Render text lines
|
|
33
|
+
renderTextLines(ctx, dimensions.scaledLineProps, dimensions.scaledMax, dimensions.scaledLineHeight, offsets, options, canvas);
|
|
34
|
+
|
|
35
|
+
return formatOutput(canvas, options);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Register custom font if provided
|
|
40
|
+
*/
|
|
41
|
+
function registerCustomFont(options) {
|
|
14
42
|
if (options.localFontPath && options.localFontName) {
|
|
15
43
|
try {
|
|
16
44
|
registerFont(options.localFontPath, { family: options.localFontName });
|
|
17
|
-
}
|
|
18
|
-
catch(error) {
|
|
45
|
+
} catch (error) {
|
|
19
46
|
throw new Error(`Failed to load local font from path: ${options.localFontPath}, error: ${error.message}`);
|
|
20
47
|
}
|
|
21
48
|
}
|
|
49
|
+
}
|
|
22
50
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
right: 0,
|
|
29
|
-
ascent: 0,
|
|
30
|
-
descent: 0
|
|
31
|
-
};
|
|
32
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Measure text and calculate metrics for all lines
|
|
53
|
+
*/
|
|
54
|
+
function measureTextMetrics(ctx, text, options) {
|
|
55
|
+
const max = { left: 0, right: 0, ascent: 0, descent: 0 };
|
|
33
56
|
let lastDescent;
|
|
57
|
+
|
|
34
58
|
const lineProps = text.split("\n").map(line => {
|
|
35
59
|
ctx.font = options.font;
|
|
36
60
|
const metrics = ctx.measureText(line);
|
|
@@ -50,89 +74,220 @@ const text2png = (text, options = {}) => {
|
|
|
50
74
|
});
|
|
51
75
|
|
|
52
76
|
const lineHeight = max.ascent + max.descent + options.lineSpacing;
|
|
53
|
-
|
|
54
77
|
const contentWidth = max.left + max.right;
|
|
55
|
-
const contentHeight =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
options.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
options.borderTopWidth +
|
|
70
|
-
options.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
const contentHeight = lineHeight * lineProps.length - options.lineSpacing - (max.descent - lastDescent);
|
|
79
|
+
|
|
80
|
+
return { max, lineProps, lastDescent, lineHeight, contentWidth, contentHeight };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Calculate natural canvas dimensions without fixed sizing
|
|
85
|
+
*/
|
|
86
|
+
function calculateNaturalDimensions(textMetrics, options) {
|
|
87
|
+
const width = textMetrics.contentWidth +
|
|
88
|
+
options.borderLeftWidth + options.borderRightWidth +
|
|
89
|
+
options.paddingLeft + options.paddingRight;
|
|
90
|
+
|
|
91
|
+
const height = textMetrics.contentHeight +
|
|
92
|
+
options.borderTopWidth + options.borderBottomWidth +
|
|
93
|
+
options.paddingTop + options.paddingBottom;
|
|
94
|
+
|
|
95
|
+
return { width, height };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Apply dimensions and calculate scaling if needed
|
|
100
|
+
*/
|
|
101
|
+
function applyDimensions(canvas, textMetrics, naturalDimensions, options) {
|
|
102
|
+
const hasFixedDimensions = options.width || options.height;
|
|
103
|
+
|
|
104
|
+
if (!hasFixedDimensions) {
|
|
105
|
+
// Auto-size mode
|
|
106
|
+
canvas.width = naturalDimensions.width;
|
|
107
|
+
canvas.height = naturalDimensions.height;
|
|
108
|
+
return {
|
|
109
|
+
scaleFactor: 1,
|
|
110
|
+
finalFont: options.font,
|
|
111
|
+
scaledMax: { ...textMetrics.max },
|
|
112
|
+
scaledLineProps: textMetrics.lineProps,
|
|
113
|
+
scaledLineHeight: textMetrics.lineHeight
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Fixed dimension mode
|
|
118
|
+
const targetWidth = options.width || naturalDimensions.width;
|
|
119
|
+
const targetHeight = options.height || naturalDimensions.height;
|
|
120
|
+
|
|
121
|
+
canvas.width = targetWidth;
|
|
122
|
+
canvas.height = targetHeight;
|
|
123
|
+
|
|
124
|
+
const availableWidth = targetWidth - options.borderLeftWidth - options.borderRightWidth - options.paddingLeft - options.paddingRight;
|
|
125
|
+
const availableHeight = targetHeight - options.borderTopWidth - options.borderBottomWidth - options.paddingTop - options.paddingBottom;
|
|
126
|
+
|
|
127
|
+
let scaleFactor = calculateScaleFactor(textMetrics.contentWidth, textMetrics.contentHeight, availableWidth, availableHeight);
|
|
128
|
+
|
|
129
|
+
if (scaleFactor < 1) {
|
|
130
|
+
const scalingResult = applyFontScaling(options.font, scaleFactor, options.minFontSize);
|
|
131
|
+
scaleFactor = scalingResult.actualScaleFactor;
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
scaleFactor,
|
|
135
|
+
finalFont: scalingResult.finalFont,
|
|
136
|
+
scaledMax: scaleMetrics(textMetrics.max, scaleFactor),
|
|
137
|
+
scaledLineProps: scaleLineProps(textMetrics.lineProps, scaleFactor),
|
|
138
|
+
scaledLineHeight: textMetrics.lineHeight * scaleFactor
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
scaleFactor: 1,
|
|
144
|
+
finalFont: options.font,
|
|
145
|
+
scaledMax: { ...textMetrics.max },
|
|
146
|
+
scaledLineProps: textMetrics.lineProps,
|
|
147
|
+
scaledLineHeight: textMetrics.lineHeight
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Apply font scaling based on scale factor and minimum font size
|
|
153
|
+
*/
|
|
154
|
+
function applyFontScaling(font, scaleFactor, minFontSize) {
|
|
155
|
+
const originalFontSize = extractFontSize(font);
|
|
156
|
+
const scaledFontSize = Math.max(originalFontSize * scaleFactor, minFontSize);
|
|
157
|
+
const actualScaleFactor = scaledFontSize === minFontSize ? scaledFontSize / originalFontSize : scaleFactor;
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
finalFont: setFontSize(font, scaledFontSize),
|
|
161
|
+
actualScaleFactor
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Scale metrics object
|
|
167
|
+
*/
|
|
168
|
+
function scaleMetrics(max, scaleFactor) {
|
|
169
|
+
return {
|
|
170
|
+
left: max.left * scaleFactor,
|
|
171
|
+
right: max.right * scaleFactor,
|
|
172
|
+
ascent: max.ascent * scaleFactor,
|
|
173
|
+
descent: max.descent * scaleFactor
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Scale line properties
|
|
179
|
+
*/
|
|
180
|
+
function scaleLineProps(lineProps, scaleFactor) {
|
|
181
|
+
return lineProps.map(prop => ({
|
|
182
|
+
line: prop.line,
|
|
183
|
+
left: prop.left * scaleFactor,
|
|
184
|
+
right: prop.right * scaleFactor,
|
|
185
|
+
ascent: prop.ascent * scaleFactor,
|
|
186
|
+
descent: prop.descent * scaleFactor
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Render background and border
|
|
192
|
+
*/
|
|
193
|
+
function renderBackground(ctx, canvas, options) {
|
|
194
|
+
const hasBorder = options.borderLeftWidth || options.borderTopWidth || options.borderRightWidth || options.borderBottomWidth;
|
|
79
195
|
|
|
80
196
|
if (hasBorder) {
|
|
81
197
|
ctx.fillStyle = options.borderColor;
|
|
82
198
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
83
199
|
}
|
|
84
200
|
|
|
201
|
+
const innerX = options.borderLeftWidth;
|
|
202
|
+
const innerY = options.borderTopWidth;
|
|
203
|
+
const innerWidth = canvas.width - (options.borderLeftWidth + options.borderRightWidth);
|
|
204
|
+
const innerHeight = canvas.height - (options.borderTopWidth + options.borderBottomWidth);
|
|
205
|
+
|
|
85
206
|
if (options.backgroundColor) {
|
|
86
207
|
ctx.fillStyle = options.backgroundColor;
|
|
87
|
-
ctx.fillRect(
|
|
88
|
-
options.borderLeftWidth,
|
|
89
|
-
options.borderTopWidth,
|
|
90
|
-
canvas.width - (options.borderLeftWidth + options.borderRightWidth),
|
|
91
|
-
canvas.height - (options.borderTopWidth + options.borderBottomWidth)
|
|
92
|
-
);
|
|
208
|
+
ctx.fillRect(innerX, innerY, innerWidth, innerHeight);
|
|
93
209
|
} else if (hasBorder) {
|
|
94
|
-
ctx.clearRect(
|
|
95
|
-
options.borderLeftWidth,
|
|
96
|
-
options.borderTopWidth,
|
|
97
|
-
canvas.width - (options.borderLeftWidth + options.borderRightWidth),
|
|
98
|
-
canvas.height - (options.borderTopWidth + options.borderBottomWidth)
|
|
99
|
-
);
|
|
210
|
+
ctx.clearRect(innerX, innerY, innerWidth, innerHeight);
|
|
100
211
|
}
|
|
212
|
+
}
|
|
101
213
|
|
|
102
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Configure canvas context for text rendering
|
|
216
|
+
*/
|
|
217
|
+
function configureContext(ctx, finalFont, scaleFactor, options) {
|
|
218
|
+
ctx.font = finalFont;
|
|
103
219
|
ctx.fillStyle = options.textColor;
|
|
104
220
|
ctx.antialias = 'gray';
|
|
105
221
|
ctx.imageSmoothingEnabled = options.imageSmoothingEnabled;
|
|
106
222
|
ctx.textAlign = options.textAlign;
|
|
107
|
-
ctx.lineWidth = options.strokeWidth;
|
|
223
|
+
ctx.lineWidth = options.strokeWidth * scaleFactor;
|
|
108
224
|
ctx.strokeStyle = options.strokeColor;
|
|
225
|
+
}
|
|
109
226
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Calculate vertical and horizontal offsets for text positioning
|
|
229
|
+
*/
|
|
230
|
+
function calculateOffsets(canvas, dimensions, options) {
|
|
231
|
+
const verticalOffset = calculateVerticalOffset(canvas, dimensions, options);
|
|
232
|
+
const horizontalOffset = calculateHorizontalOffset(canvas, dimensions, options);
|
|
233
|
+
|
|
234
|
+
return { verticalOffset, horizontalOffset };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Calculate vertical offset based on alignment
|
|
239
|
+
*/
|
|
240
|
+
function calculateVerticalOffset(canvas, dimensions, options) {
|
|
241
|
+
if (!options.height) {
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const availableHeight = canvas.height - options.borderTopWidth - options.borderBottomWidth - options.paddingTop - options.paddingBottom;
|
|
246
|
+
const scaledContentHeight = dimensions.scaledLineHeight * dimensions.scaledLineProps.length -
|
|
247
|
+
options.lineSpacing * dimensions.scaleFactor -
|
|
248
|
+
(dimensions.scaledMax.descent - dimensions.scaledLineProps[dimensions.scaledLineProps.length - 1].descent);
|
|
249
|
+
|
|
250
|
+
switch (options.verticalAlign) {
|
|
251
|
+
case "top":
|
|
252
|
+
return 0;
|
|
253
|
+
case "middle":
|
|
254
|
+
return (availableHeight - scaledContentHeight) / 2;
|
|
255
|
+
case "bottom":
|
|
256
|
+
return availableHeight - scaledContentHeight;
|
|
257
|
+
default:
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Calculate horizontal offset based on alignment
|
|
264
|
+
*/
|
|
265
|
+
function calculateHorizontalOffset(canvas, dimensions, options) {
|
|
266
|
+
if (!options.width) {
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const availableWidth = canvas.width - options.borderLeftWidth - options.borderRightWidth - options.paddingLeft - options.paddingRight;
|
|
271
|
+
const scaledContentWidth = dimensions.scaledMax.left + dimensions.scaledMax.right;
|
|
272
|
+
|
|
273
|
+
if (options.textAlign === 'center') {
|
|
274
|
+
return (availableWidth - scaledContentWidth) / 2;
|
|
275
|
+
} else if (options.textAlign === 'right' || options.textAlign === 'end') {
|
|
276
|
+
return availableWidth - scaledContentWidth;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Render text lines
|
|
284
|
+
*/
|
|
285
|
+
function renderTextLines(ctx, scaledLineProps, scaledMax, scaledLineHeight, offsets, options, canvas) {
|
|
286
|
+
let offsetY = options.borderTopWidth + options.paddingTop + offsets.verticalOffset;
|
|
287
|
+
|
|
288
|
+
scaledLineProps.forEach(lineProp => {
|
|
289
|
+
const x = calculateTextX(lineProp, scaledMax, offsets.horizontalOffset, options, canvas);
|
|
290
|
+
const y = scaledMax.ascent + offsetY;
|
|
136
291
|
|
|
137
292
|
ctx.fillText(lineProp.line, x, y);
|
|
138
293
|
|
|
@@ -140,9 +295,35 @@ const text2png = (text, options = {}) => {
|
|
|
140
295
|
ctx.strokeText(lineProp.line, x, y);
|
|
141
296
|
}
|
|
142
297
|
|
|
143
|
-
offsetY +=
|
|
298
|
+
offsetY += scaledLineHeight;
|
|
144
299
|
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Calculate X position for text based on alignment
|
|
304
|
+
*/
|
|
305
|
+
function calculateTextX(lineProp, scaledMax, horizontalOffset, options, canvas) {
|
|
306
|
+
switch (options.textAlign) {
|
|
307
|
+
case "start":
|
|
308
|
+
case "left":
|
|
309
|
+
return lineProp.left + options.borderLeftWidth + options.paddingLeft + horizontalOffset;
|
|
310
|
+
|
|
311
|
+
case "end":
|
|
312
|
+
case "right":
|
|
313
|
+
return canvas.width - options.borderRightWidth - options.paddingRight - horizontalOffset;
|
|
145
314
|
|
|
315
|
+
case "center":
|
|
316
|
+
return (scaledMax.left + scaledMax.right) / 2 + options.borderLeftWidth + options.paddingLeft + horizontalOffset;
|
|
317
|
+
|
|
318
|
+
default:
|
|
319
|
+
return lineProp.left + options.borderLeftWidth + options.paddingLeft + horizontalOffset;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Format output based on options
|
|
325
|
+
*/
|
|
326
|
+
function formatOutput(canvas, options) {
|
|
146
327
|
switch (options.output) {
|
|
147
328
|
case "buffer":
|
|
148
329
|
return canvas.toBuffer();
|
|
@@ -155,7 +336,7 @@ const text2png = (text, options = {}) => {
|
|
|
155
336
|
default:
|
|
156
337
|
throw new Error(`output type:${options.output} is not supported.`);
|
|
157
338
|
}
|
|
158
|
-
}
|
|
339
|
+
}
|
|
159
340
|
|
|
160
341
|
function parseOptions(options) {
|
|
161
342
|
return {
|
|
@@ -184,7 +365,12 @@ function parseOptions(options) {
|
|
|
184
365
|
|
|
185
366
|
output: orOr(options.output, "buffer"),
|
|
186
367
|
|
|
187
|
-
imageSmoothingEnabled: orOr(options.imageSmoothingEnabled, false)
|
|
368
|
+
imageSmoothingEnabled: orOr(options.imageSmoothingEnabled, false),
|
|
369
|
+
|
|
370
|
+
width: orOr(options.width, null),
|
|
371
|
+
height: orOr(options.height, null),
|
|
372
|
+
minFontSize: orOr(options.minFontSize, 8),
|
|
373
|
+
verticalAlign: orOr(options.verticalAlign, "middle")
|
|
188
374
|
};
|
|
189
375
|
}
|
|
190
376
|
|
|
@@ -197,4 +383,42 @@ function orOr() {
|
|
|
197
383
|
return arguments[arguments.length - 1];
|
|
198
384
|
}
|
|
199
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Extract font size from font string (e.g., "30px sans-serif" -> 30)
|
|
388
|
+
* @param {string} font Font string
|
|
389
|
+
* @returns {number} Font size in pixels
|
|
390
|
+
*/
|
|
391
|
+
function extractFontSize(font) {
|
|
392
|
+
const regex = /(\d+(?:\.\d+)?)\s*px/i;
|
|
393
|
+
const match = regex.exec(font);
|
|
394
|
+
return match ? Number.parseFloat(match[1]) : 30;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Create new font string with different size
|
|
399
|
+
* @param {string} font Original font string
|
|
400
|
+
* @param {number} newSize New size in pixels
|
|
401
|
+
* @returns {string} New font string
|
|
402
|
+
*/
|
|
403
|
+
function setFontSize(font, newSize) {
|
|
404
|
+
const regex = /(\d+(?:\.\d+)?)\s*px/i;
|
|
405
|
+
return font.replace(regex, `${newSize}px`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Calculate scale factor to fit content in fixed dimensions
|
|
410
|
+
* @param {number} contentWidth Natural content width
|
|
411
|
+
* @param {number} contentHeight Natural content height
|
|
412
|
+
* @param {number} availableWidth Available width
|
|
413
|
+
* @param {number} availableHeight Available height
|
|
414
|
+
* @returns {number} Scale factor (1 = no scaling needed)
|
|
415
|
+
*/
|
|
416
|
+
function calculateScaleFactor(contentWidth, contentHeight, availableWidth, availableHeight) {
|
|
417
|
+
const widthScale = availableWidth / contentWidth;
|
|
418
|
+
const heightScale = availableHeight / contentHeight;
|
|
419
|
+
return Math.min(widthScale, heightScale, 1);
|
|
420
|
+
}
|
|
421
|
+
|
|
200
422
|
module.exports = text2png;
|
|
423
|
+
|
|
424
|
+
module.exports.text2png = text2png;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@igxjs/text2png",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "Convert text to png for node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "git+https://github.com/
|
|
16
|
+
"url": "git+https://github.com/igxjs/text2png.git"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"text",
|
|
@@ -22,16 +22,17 @@
|
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
|
-
"author": "
|
|
25
|
+
"author": "Michael",
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"bugs": {
|
|
28
|
-
"url": "https://github.com/
|
|
28
|
+
"url": "https://github.com/igxjs/text2png/issues"
|
|
29
29
|
},
|
|
30
|
-
"homepage": "https://github.com/
|
|
30
|
+
"homepage": "https://github.com/igxjs/text2png#readme",
|
|
31
31
|
"files": [
|
|
32
32
|
"bin",
|
|
33
33
|
"README.md",
|
|
34
34
|
"index.js",
|
|
35
|
+
"types.d.ts",
|
|
35
36
|
"package.json",
|
|
36
37
|
"LICENSE"
|
|
37
38
|
],
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { Canvas } from 'canvas';
|
|
3
|
+
|
|
4
|
+
export interface Text2PngOptions {
|
|
5
|
+
/** CSS style font (default: "30px sans-serif") */
|
|
6
|
+
font?: string;
|
|
7
|
+
|
|
8
|
+
/** Text alignment (default: "left") */
|
|
9
|
+
textAlign?: "left" | "center" | "right" | "start" | "end";
|
|
10
|
+
|
|
11
|
+
/** Text color (default: "black") */
|
|
12
|
+
color?: string;
|
|
13
|
+
|
|
14
|
+
/** Text color (alias for color, default: "black") */
|
|
15
|
+
textColor?: string;
|
|
16
|
+
|
|
17
|
+
/** Background color */
|
|
18
|
+
backgroundColor?: string;
|
|
19
|
+
|
|
20
|
+
/** Background color (alias for backgroundColor) */
|
|
21
|
+
bgColor?: string;
|
|
22
|
+
|
|
23
|
+
/** Line spacing (default: 0) */
|
|
24
|
+
lineSpacing?: number;
|
|
25
|
+
|
|
26
|
+
/** Stroke width (default: 0) */
|
|
27
|
+
strokeWidth?: number;
|
|
28
|
+
|
|
29
|
+
/** Stroke color (default: "white") */
|
|
30
|
+
strokeColor?: string;
|
|
31
|
+
|
|
32
|
+
/** Padding for all sides (default: 0) */
|
|
33
|
+
padding?: number;
|
|
34
|
+
|
|
35
|
+
/** Left padding */
|
|
36
|
+
paddingLeft?: number;
|
|
37
|
+
|
|
38
|
+
/** Top padding */
|
|
39
|
+
paddingTop?: number;
|
|
40
|
+
|
|
41
|
+
/** Right padding */
|
|
42
|
+
paddingRight?: number;
|
|
43
|
+
|
|
44
|
+
/** Bottom padding */
|
|
45
|
+
paddingBottom?: number;
|
|
46
|
+
|
|
47
|
+
/** Border width for all sides (default: 0) */
|
|
48
|
+
borderWidth?: number;
|
|
49
|
+
|
|
50
|
+
/** Left border width (default: 0) */
|
|
51
|
+
borderLeftWidth?: number;
|
|
52
|
+
|
|
53
|
+
/** Top border width (default: 0) */
|
|
54
|
+
borderTopWidth?: number;
|
|
55
|
+
|
|
56
|
+
/** Right border width (default: 0) */
|
|
57
|
+
borderRightWidth?: number;
|
|
58
|
+
|
|
59
|
+
/** Bottom border width (default: 0) */
|
|
60
|
+
borderBottomWidth?: number;
|
|
61
|
+
|
|
62
|
+
/** Border color (default: "black") */
|
|
63
|
+
borderColor?: string;
|
|
64
|
+
|
|
65
|
+
/** Path to local font file (e.g., fonts/Lobster-Regular.ttf) */
|
|
66
|
+
localFontPath?: string;
|
|
67
|
+
|
|
68
|
+
/** Name of local font (e.g., Lobster) */
|
|
69
|
+
localFontName?: string;
|
|
70
|
+
|
|
71
|
+
/** Output type (default: "buffer") */
|
|
72
|
+
output?: "buffer" | "stream" | "dataURL" | "canvas";
|
|
73
|
+
|
|
74
|
+
/** Enable or disable image smoothing (default: false) */
|
|
75
|
+
imageSmoothingEnabled?: boolean;
|
|
76
|
+
|
|
77
|
+
/** Fixed width in pixels (optional, defaults to auto-calculated) */
|
|
78
|
+
width?: number;
|
|
79
|
+
|
|
80
|
+
/** Fixed height in pixels (optional, defaults to auto-calculated) */
|
|
81
|
+
height?: number;
|
|
82
|
+
|
|
83
|
+
/** Minimum font size when auto-scaling to fit fixed dimensions (default: 8) */
|
|
84
|
+
minFontSize?: number;
|
|
85
|
+
|
|
86
|
+
/** Vertical alignment when height is fixed (default: "middle") */
|
|
87
|
+
verticalAlign?: "top" | "middle" | "bottom";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
declare function text2png(text: string, options?: Text2PngOptions): string | Buffer | Readable | Canvas;
|
|
91
|
+
|
|
92
|
+
export default text2png;
|
|
93
|
+
|
|
94
|
+
export { text2png };
|