@swiftlysingh/excalidraw-cli 1.1.0 → 1.2.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 +72 -3
- package/dist/cli.js +72 -2
- package/dist/cli.js.map +1 -1
- package/dist/exporter/dom-polyfill.d.ts +25 -0
- package/dist/exporter/dom-polyfill.d.ts.map +1 -0
- package/dist/exporter/dom-polyfill.js +220 -0
- package/dist/exporter/dom-polyfill.js.map +1 -0
- package/dist/exporter/image-exporter.d.ts +87 -0
- package/dist/exporter/image-exporter.d.ts.map +1 -0
- package/dist/exporter/image-exporter.js +188 -0
- package/dist/exporter/image-exporter.js.map +1 -0
- package/dist/exporter/index.d.ts +6 -0
- package/dist/exporter/index.d.ts.map +1 -0
- package/dist/exporter/index.js +5 -0
- package/dist/exporter/index.js.map +1 -0
- package/dist/factory/connection-factory.js +12 -8
- package/dist/factory/connection-factory.js.map +1 -1
- package/dist/factory/image-factory.d.ts.map +1 -1
- package/dist/factory/image-factory.js +5 -2
- package/dist/factory/image-factory.js.map +1 -1
- package/dist/factory/node-factory.js +16 -9
- package/dist/factory/node-factory.js.map +1 -1
- package/dist/factory/text-factory.d.ts +1 -0
- package/dist/factory/text-factory.d.ts.map +1 -1
- package/dist/factory/text-factory.js +1 -0
- package/dist/factory/text-factory.js.map +1 -1
- package/dist/generator/excalidraw-generator.d.ts.map +1 -1
- package/dist/generator/excalidraw-generator.js +3 -30
- package/dist/generator/excalidraw-generator.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -8
- package/dist/index.js.map +1 -1
- package/dist/layout/elk-layout.d.ts +8 -1
- package/dist/layout/elk-layout.d.ts.map +1 -1
- package/dist/layout/elk-layout.js +1 -1
- package/dist/layout/elk-layout.js.map +1 -1
- package/dist/layout/index.d.ts +1 -1
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/layout/index.js +1 -1
- package/dist/layout/index.js.map +1 -1
- package/dist/parser/json-parser.js +1 -1
- package/dist/parser/json-parser.js.map +1 -1
- package/man/excalidraw-cli.1 +399 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -20,11 +20,15 @@
|
|
|
20
20
|
- **JSON API** for programmatic use
|
|
21
21
|
- **Auto-layout** using ELK.js (Eclipse Layout Kernel)
|
|
22
22
|
- **Multiple flow directions**: TB (top-bottom), BT, LR, RL
|
|
23
|
+
- **Export to PNG & SVG** with dark mode, custom backgrounds, scale, and padding
|
|
23
24
|
- **Programmable API** for integration into other tools
|
|
24
25
|
|
|
26
|
+
|
|
25
27
|
## Installation
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
Requires `Node >=20.19.0`. Node 18 is no longer supported.
|
|
30
|
+
|
|
31
|
+
### Using npm
|
|
28
32
|
|
|
29
33
|
```bash
|
|
30
34
|
npm i @swiftlysingh/excalidraw-cli
|
|
@@ -62,6 +66,19 @@ excalidraw-cli create flowchart.dsl -o diagram.excalidraw
|
|
|
62
66
|
echo "[A] -> [B] -> [C]" | excalidraw-cli create --stdin -o diagram.excalidraw
|
|
63
67
|
```
|
|
64
68
|
|
|
69
|
+
### Export to Image
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Convert an existing .excalidraw file to PNG
|
|
73
|
+
excalidraw-cli convert diagram.excalidraw --format png
|
|
74
|
+
|
|
75
|
+
# Convert with options
|
|
76
|
+
excalidraw-cli convert diagram.excalidraw --format png --scale 2 --dark
|
|
77
|
+
|
|
78
|
+
# Convert to SVG without background
|
|
79
|
+
excalidraw-cli convert diagram.excalidraw --format svg --no-export-background
|
|
80
|
+
```
|
|
81
|
+
|
|
65
82
|
### DSL Syntax
|
|
66
83
|
|
|
67
84
|
| Syntax | Element | Description |
|
|
@@ -103,13 +120,32 @@ excalidraw-cli create [input] [options]
|
|
|
103
120
|
|
|
104
121
|
**Options:**
|
|
105
122
|
- `-o, --output <file>` - Output file path (default: flowchart.excalidraw)
|
|
106
|
-
- `-f, --format <type>` - Input format: dsl, json (default: dsl)
|
|
123
|
+
- `-f, --format <type>` - Input format: dsl, json, dot (default: dsl)
|
|
107
124
|
- `--inline <dsl>` - Inline DSL string
|
|
108
125
|
- `--stdin` - Read from stdin
|
|
109
126
|
- `-d, --direction <dir>` - Flow direction: TB, BT, LR, RL
|
|
110
127
|
- `-s, --spacing <n>` - Node spacing in pixels
|
|
111
128
|
- `--verbose` - Verbose output
|
|
112
129
|
|
|
130
|
+
#### `convert`
|
|
131
|
+
|
|
132
|
+
Convert an existing `.excalidraw` file to PNG or SVG.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
excalidraw-cli convert <input> [options]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Options:**
|
|
139
|
+
- `--format <format>` - **(required)** Export format: `png` or `svg`
|
|
140
|
+
- `-o, --output <file>` - Output file path (default: input file with swapped extension)
|
|
141
|
+
- `--export-background / --no-export-background` - Include or exclude background
|
|
142
|
+
- `--background-color <color>` - Background color (default: #ffffff)
|
|
143
|
+
- `--dark` - Export with dark mode theme
|
|
144
|
+
- `--embed-scene` - Embed scene data in exported image
|
|
145
|
+
- `--padding <n>` - Padding around content in pixels (default: 10)
|
|
146
|
+
- `--scale <n>` - Scale factor for PNG export (default: 1)
|
|
147
|
+
- `--verbose` - Verbose output
|
|
148
|
+
|
|
113
149
|
#### `parse`
|
|
114
150
|
|
|
115
151
|
Parse and validate input without generating output.
|
|
@@ -147,7 +183,12 @@ excalidraw-cli create flowchart.json -o diagram.excalidraw
|
|
|
147
183
|
## Programmatic Usage
|
|
148
184
|
|
|
149
185
|
```typescript
|
|
150
|
-
import {
|
|
186
|
+
import {
|
|
187
|
+
createFlowchartFromDSL,
|
|
188
|
+
createFlowchartFromJSON,
|
|
189
|
+
convertToSVG,
|
|
190
|
+
convertToPNG,
|
|
191
|
+
} from '@swiftlysingh/excalidraw-cli';
|
|
151
192
|
|
|
152
193
|
// From DSL
|
|
153
194
|
const dsl = '(Start) -> [Process] -> (End)';
|
|
@@ -164,6 +205,29 @@ const input = {
|
|
|
164
205
|
const json2 = await createFlowchartFromJSON(input);
|
|
165
206
|
```
|
|
166
207
|
|
|
208
|
+
### Export API
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { convertToSVG, convertToPNG } from '@swiftlysingh/excalidraw-cli';
|
|
212
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
213
|
+
|
|
214
|
+
// Load an existing .excalidraw file
|
|
215
|
+
const file = JSON.parse(readFileSync('diagram.excalidraw', 'utf-8'));
|
|
216
|
+
|
|
217
|
+
// Export to SVG
|
|
218
|
+
const svg = await convertToSVG(file, { padding: 20 });
|
|
219
|
+
writeFileSync('diagram.svg', svg);
|
|
220
|
+
|
|
221
|
+
// Export to PNG with 2x scale and dark mode
|
|
222
|
+
const png = await convertToPNG(file, {
|
|
223
|
+
scale: 2,
|
|
224
|
+
dark: true,
|
|
225
|
+
});
|
|
226
|
+
writeFileSync('diagram.png', png);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`@excalidraw/utils` remains a required runtime dependency for image export. The CLI uses its `exportToSvg()` implementation for SVG generation, and reuses the bundled Excalidraw font assets so server-side PNG rendering keeps text output close to the browser version.
|
|
230
|
+
|
|
167
231
|
## Examples
|
|
168
232
|
|
|
169
233
|
Here are some flowcharts created with excalidraw-cli:
|
|
@@ -185,6 +249,11 @@ The generated `.excalidraw` files can be:
|
|
|
185
249
|
2. Imported into Obsidian with the Excalidraw plugin
|
|
186
250
|
3. Used with any tool that supports the Excalidraw format
|
|
187
251
|
|
|
252
|
+
With the `convert` command, you can also generate:
|
|
253
|
+
|
|
254
|
+
- **SVG** — scalable vector graphics, ideal for embedding in docs or web pages
|
|
255
|
+
- **PNG** — raster images at any scale (1×, 2×, 3×, etc.) for presentations or sharing
|
|
256
|
+
|
|
188
257
|
## License
|
|
189
258
|
|
|
190
259
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -11,11 +11,12 @@ import { parseJSONString } from './parser/json-parser.js';
|
|
|
11
11
|
import { parseDOT } from './parser/dot-parser.js';
|
|
12
12
|
import { layoutGraph } from './layout/elk-layout.js';
|
|
13
13
|
import { generateExcalidraw, serializeExcalidraw } from './generator/excalidraw-generator.js';
|
|
14
|
+
import { convertImage, swapExtension } from './exporter/index.js';
|
|
14
15
|
const program = new Command();
|
|
15
16
|
program
|
|
16
17
|
.name('excalidraw-cli')
|
|
17
18
|
.description('Create Excalidraw flowcharts from DSL, JSON, or DOT')
|
|
18
|
-
.version('1.0
|
|
19
|
+
.version('1.2.0');
|
|
19
20
|
/**
|
|
20
21
|
* Create command - main flowchart creation
|
|
21
22
|
*/
|
|
@@ -25,7 +26,7 @@ program
|
|
|
25
26
|
.argument('[input]', 'Input file path (DSL, JSON, or DOT)')
|
|
26
27
|
.option('-o, --output <file>', 'Output file path', 'flowchart.excalidraw')
|
|
27
28
|
.option('-f, --format <type>', 'Input format: dsl, json, dot (default: dsl)', 'dsl')
|
|
28
|
-
.option('--inline <
|
|
29
|
+
.option('--inline <input>', 'Inline DSL, JSON, or DOT string')
|
|
29
30
|
.option('--stdin', 'Read input from stdin')
|
|
30
31
|
.option('-d, --direction <dir>', 'Flow direction: TB, BT, LR, RL (default: TB)')
|
|
31
32
|
.option('-s, --spacing <n>', 'Node spacing in pixels', '50')
|
|
@@ -166,6 +167,75 @@ program
|
|
|
166
167
|
process.exit(1);
|
|
167
168
|
}
|
|
168
169
|
});
|
|
170
|
+
/**
|
|
171
|
+
* Convert command - convert an existing .excalidraw file to PNG or SVG
|
|
172
|
+
*/
|
|
173
|
+
program
|
|
174
|
+
.command('convert')
|
|
175
|
+
.description('Convert an existing .excalidraw file to PNG or SVG')
|
|
176
|
+
.argument('<input>', 'Input .excalidraw file path')
|
|
177
|
+
.requiredOption('--format <format>', 'Export format: png or svg')
|
|
178
|
+
.option('-o, --output <file>', 'Output file path (default: input file with swapped extension)')
|
|
179
|
+
.option('--export-background', 'Include background in export (default: true)')
|
|
180
|
+
.option('--no-export-background', 'Exclude background from export')
|
|
181
|
+
.option('--background-color <color>', 'Background color (default: #ffffff)')
|
|
182
|
+
.option('--dark', 'Export with dark mode')
|
|
183
|
+
.option('--embed-scene', 'Embed scene data in exported image')
|
|
184
|
+
.option('--padding <n>', 'Padding around content in pixels', '10')
|
|
185
|
+
.option('--scale <n>', 'Scale factor for PNG export', '1')
|
|
186
|
+
.option('--verbose', 'Verbose output')
|
|
187
|
+
.action(async (inputFile, options) => {
|
|
188
|
+
try {
|
|
189
|
+
const format = options.format.toLowerCase();
|
|
190
|
+
if (format !== 'png' && format !== 'svg') {
|
|
191
|
+
console.error('Error: --format must be "png" or "svg"');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
// Read the .excalidraw file
|
|
195
|
+
const rawInput = readFileSync(inputFile, 'utf-8');
|
|
196
|
+
const excalidrawFile = JSON.parse(rawInput);
|
|
197
|
+
if (options.verbose) {
|
|
198
|
+
console.log(`Input: ${inputFile}`);
|
|
199
|
+
console.log(`Elements: ${excalidrawFile.elements?.length || 0}`);
|
|
200
|
+
console.log(`Files: ${Object.keys(excalidrawFile.files || {}).length}`);
|
|
201
|
+
}
|
|
202
|
+
const padding = parseInt(options.padding, 10);
|
|
203
|
+
if (Number.isNaN(padding) || padding < 0) {
|
|
204
|
+
console.error('Error: --padding must be a non-negative integer');
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
const scale = parseFloat(options.scale);
|
|
208
|
+
if (Number.isNaN(scale) || scale <= 0) {
|
|
209
|
+
console.error('Error: --scale must be a positive number');
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
const exportOpts = {
|
|
213
|
+
format: format,
|
|
214
|
+
exportBackground: options.exportBackground !== false,
|
|
215
|
+
viewBackgroundColor: options.backgroundColor,
|
|
216
|
+
dark: options.dark || false,
|
|
217
|
+
exportEmbedScene: options.embedScene || false,
|
|
218
|
+
padding,
|
|
219
|
+
scale,
|
|
220
|
+
};
|
|
221
|
+
const outputPath = options.output || swapExtension(inputFile, format);
|
|
222
|
+
if (options.verbose) {
|
|
223
|
+
console.log(`Exporting as ${format.toUpperCase()} to ${outputPath}...`);
|
|
224
|
+
}
|
|
225
|
+
const result = await convertImage(excalidrawFile, exportOpts);
|
|
226
|
+
if (typeof result === 'string') {
|
|
227
|
+
writeFileSync(outputPath, result, 'utf-8');
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
writeFileSync(outputPath, result);
|
|
231
|
+
}
|
|
232
|
+
console.log(`Exported: ${outputPath}`);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
169
239
|
// Parse arguments and run
|
|
170
240
|
program.parse();
|
|
171
241
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM polyfill utilities for image export.
|
|
3
|
+
*
|
|
4
|
+
* `@excalidraw/utils.exportToSvg()` expects a browser-ish environment even when
|
|
5
|
+
* running in Node. We provide that environment only for the duration of an
|
|
6
|
+
* export so host globals such as `window`, `document`, `navigator`, and `fetch`
|
|
7
|
+
* are restored afterwards.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the bundled `@excalidraw/utils` asset directory.
|
|
11
|
+
*
|
|
12
|
+
* The exporter still depends on this package for two things:
|
|
13
|
+
* - `exportToSvg()` for SVG generation
|
|
14
|
+
* - bundled font files used by both the DOM polyfill and PNG rasterization
|
|
15
|
+
*/
|
|
16
|
+
export declare function getExcalidrawAssetDir(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Run a callback with temporary browser globals installed.
|
|
19
|
+
*
|
|
20
|
+
* Exports are serialized because the polyfill touches process-wide globals.
|
|
21
|
+
* Serializing the critical section guarantees each export sees a consistent
|
|
22
|
+
* environment and that the host globals are restored cleanly afterwards.
|
|
23
|
+
*/
|
|
24
|
+
export declare function withDOMPolyfill<T>(callback: () => Promise<T>): Promise<T>;
|
|
25
|
+
//# sourceMappingURL=dom-polyfill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom-polyfill.d.ts","sourceRoot":"","sources":["../../src/exporter/dom-polyfill.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAcH;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAO9C;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAoB/E"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM polyfill utilities for image export.
|
|
3
|
+
*
|
|
4
|
+
* `@excalidraw/utils.exportToSvg()` expects a browser-ish environment even when
|
|
5
|
+
* running in Node. We provide that environment only for the duration of an
|
|
6
|
+
* export so host globals such as `window`, `document`, `navigator`, and `fetch`
|
|
7
|
+
* are restored afterwards.
|
|
8
|
+
*/
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
import { readFile } from 'node:fs/promises';
|
|
11
|
+
import { resolve, dirname } from 'node:path';
|
|
12
|
+
/** Fake base URL used to route bundled font requests through the file loader. */
|
|
13
|
+
const FONT_PROXY_BASE = 'https://excalidraw-fonts.local/';
|
|
14
|
+
let cachedAssetsDir = null;
|
|
15
|
+
let exportScopeQueue = Promise.resolve();
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the bundled `@excalidraw/utils` asset directory.
|
|
18
|
+
*
|
|
19
|
+
* The exporter still depends on this package for two things:
|
|
20
|
+
* - `exportToSvg()` for SVG generation
|
|
21
|
+
* - bundled font files used by both the DOM polyfill and PNG rasterization
|
|
22
|
+
*/
|
|
23
|
+
export function getExcalidrawAssetDir() {
|
|
24
|
+
if (cachedAssetsDir)
|
|
25
|
+
return cachedAssetsDir;
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
const utilsEntry = require.resolve('@excalidraw/utils');
|
|
28
|
+
cachedAssetsDir = resolve(dirname(utilsEntry), 'assets');
|
|
29
|
+
return cachedAssetsDir;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Run a callback with temporary browser globals installed.
|
|
33
|
+
*
|
|
34
|
+
* Exports are serialized because the polyfill touches process-wide globals.
|
|
35
|
+
* Serializing the critical section guarantees each export sees a consistent
|
|
36
|
+
* environment and that the host globals are restored cleanly afterwards.
|
|
37
|
+
*/
|
|
38
|
+
export async function withDOMPolyfill(callback) {
|
|
39
|
+
const previousRun = exportScopeQueue;
|
|
40
|
+
let releaseQueue;
|
|
41
|
+
exportScopeQueue = new Promise((resolveQueue) => {
|
|
42
|
+
releaseQueue = resolveQueue;
|
|
43
|
+
});
|
|
44
|
+
await previousRun;
|
|
45
|
+
try {
|
|
46
|
+
const restore = await installDOMPolyfill();
|
|
47
|
+
try {
|
|
48
|
+
return await callback();
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
restore();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
releaseQueue();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function installDOMPolyfill() {
|
|
59
|
+
const { JSDOM } = await import('jsdom');
|
|
60
|
+
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
61
|
+
url: 'https://localhost',
|
|
62
|
+
pretendToBeVisual: true,
|
|
63
|
+
});
|
|
64
|
+
const restoreGlobals = [];
|
|
65
|
+
const assetsDir = getExcalidrawAssetDir();
|
|
66
|
+
const win = dom.window;
|
|
67
|
+
const doc = dom.window.document;
|
|
68
|
+
const Path2DImpl = class Path2D {
|
|
69
|
+
d;
|
|
70
|
+
constructor(path) {
|
|
71
|
+
this.d = path || '';
|
|
72
|
+
}
|
|
73
|
+
addPath() { }
|
|
74
|
+
moveTo() { }
|
|
75
|
+
lineTo() { }
|
|
76
|
+
bezierCurveTo() { }
|
|
77
|
+
quadraticCurveTo() { }
|
|
78
|
+
arc() { }
|
|
79
|
+
arcTo() { }
|
|
80
|
+
ellipse() { }
|
|
81
|
+
rect() { }
|
|
82
|
+
closePath() { }
|
|
83
|
+
};
|
|
84
|
+
const FontFaceImpl = class FontFace {
|
|
85
|
+
family;
|
|
86
|
+
source;
|
|
87
|
+
descriptors;
|
|
88
|
+
status = 'loaded';
|
|
89
|
+
loaded;
|
|
90
|
+
display;
|
|
91
|
+
style;
|
|
92
|
+
weight;
|
|
93
|
+
unicodeRange;
|
|
94
|
+
featureSettings;
|
|
95
|
+
constructor(family, source, descriptors) {
|
|
96
|
+
this.family = family;
|
|
97
|
+
this.source = typeof source === 'string' ? source : 'arraybuffer';
|
|
98
|
+
this.descriptors = descriptors || {};
|
|
99
|
+
this.display = descriptors?.display || 'swap';
|
|
100
|
+
this.style = descriptors?.style || 'normal';
|
|
101
|
+
this.weight = descriptors?.weight || '400';
|
|
102
|
+
this.unicodeRange = descriptors?.unicodeRange || 'U+0000-FFFF';
|
|
103
|
+
this.featureSettings = descriptors?.featureSettings || '';
|
|
104
|
+
this.loaded = Promise.resolve(this);
|
|
105
|
+
}
|
|
106
|
+
load() {
|
|
107
|
+
return Promise.resolve(this);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const fontSet = new Set();
|
|
111
|
+
const fontFaceSet = {
|
|
112
|
+
add(face) {
|
|
113
|
+
fontSet.add(face);
|
|
114
|
+
},
|
|
115
|
+
has(face) {
|
|
116
|
+
return fontSet.has(face);
|
|
117
|
+
},
|
|
118
|
+
delete(face) {
|
|
119
|
+
return fontSet.delete(face);
|
|
120
|
+
},
|
|
121
|
+
check(_font, _text) {
|
|
122
|
+
return true;
|
|
123
|
+
},
|
|
124
|
+
load(_font, _text) {
|
|
125
|
+
return Promise.resolve([]);
|
|
126
|
+
},
|
|
127
|
+
forEach(cb) {
|
|
128
|
+
fontSet.forEach(cb);
|
|
129
|
+
},
|
|
130
|
+
get size() {
|
|
131
|
+
return fontSet.size;
|
|
132
|
+
},
|
|
133
|
+
get status() {
|
|
134
|
+
return 'loaded';
|
|
135
|
+
},
|
|
136
|
+
ready: Promise.resolve(),
|
|
137
|
+
[Symbol.iterator]() {
|
|
138
|
+
return fontSet[Symbol.iterator]();
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
Object.defineProperty(doc, 'fonts', {
|
|
142
|
+
value: fontFaceSet,
|
|
143
|
+
writable: true,
|
|
144
|
+
configurable: true,
|
|
145
|
+
});
|
|
146
|
+
win.EXCALIDRAW_ASSET_PATH = FONT_PROXY_BASE;
|
|
147
|
+
const originalFetch = globalThis.fetch?.bind(globalThis);
|
|
148
|
+
const fetchOverride = async (input, init) => {
|
|
149
|
+
const url = typeof input === 'string'
|
|
150
|
+
? input
|
|
151
|
+
: input instanceof URL
|
|
152
|
+
? input.href
|
|
153
|
+
: input.url;
|
|
154
|
+
if (url.startsWith(FONT_PROXY_BASE)) {
|
|
155
|
+
const fontFile = decodeURIComponent(url.slice(FONT_PROXY_BASE.length));
|
|
156
|
+
if (fontFile.includes('..') || fontFile.startsWith('/') || fontFile.includes('\\')) {
|
|
157
|
+
return new Response(null, { status: 400, statusText: 'Invalid font path' });
|
|
158
|
+
}
|
|
159
|
+
const fontPath = resolve(assetsDir, fontFile);
|
|
160
|
+
try {
|
|
161
|
+
const data = await readFile(fontPath);
|
|
162
|
+
return new Response(data, {
|
|
163
|
+
status: 200,
|
|
164
|
+
headers: {
|
|
165
|
+
'Content-Type': 'font/ttf',
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return new Response(null, { status: 404, statusText: 'Font not found' });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!originalFetch) {
|
|
174
|
+
throw new Error('fetch is not available in this Node runtime');
|
|
175
|
+
}
|
|
176
|
+
return originalFetch(input, init);
|
|
177
|
+
};
|
|
178
|
+
restoreGlobals.push(overrideGlobal('window', dom.window));
|
|
179
|
+
restoreGlobals.push(overrideGlobal('document', doc));
|
|
180
|
+
restoreGlobals.push(overrideGlobal('navigator', dom.window.navigator));
|
|
181
|
+
restoreGlobals.push(overrideGlobal('DOMParser', dom.window.DOMParser));
|
|
182
|
+
restoreGlobals.push(overrideGlobal('Node', dom.window.Node));
|
|
183
|
+
restoreGlobals.push(overrideGlobal('devicePixelRatio', 1));
|
|
184
|
+
restoreGlobals.push(overrideGlobal('Path2D', Path2DImpl));
|
|
185
|
+
restoreGlobals.push(overrideGlobal('FontFace', FontFaceImpl));
|
|
186
|
+
restoreGlobals.push(overrideGlobal('fetch', fetchOverride));
|
|
187
|
+
return () => {
|
|
188
|
+
for (let i = restoreGlobals.length - 1; i >= 0; i--) {
|
|
189
|
+
restoreGlobals[i]();
|
|
190
|
+
}
|
|
191
|
+
dom.window.close();
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function overrideGlobal(key, value) {
|
|
195
|
+
const globalObject = globalThis;
|
|
196
|
+
const existing = Object.getOwnPropertyDescriptor(globalThis, key);
|
|
197
|
+
if (existing && existing.configurable === false) {
|
|
198
|
+
if ('writable' in existing && existing.writable) {
|
|
199
|
+
const previousValue = globalObject[key];
|
|
200
|
+
globalObject[key] = value;
|
|
201
|
+
return () => {
|
|
202
|
+
globalObject[key] = previousValue;
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
throw new Error(`Cannot temporarily override globalThis.${String(key)}`);
|
|
206
|
+
}
|
|
207
|
+
Object.defineProperty(globalThis, key, {
|
|
208
|
+
configurable: true,
|
|
209
|
+
writable: true,
|
|
210
|
+
value,
|
|
211
|
+
});
|
|
212
|
+
return () => {
|
|
213
|
+
if (existing) {
|
|
214
|
+
Object.defineProperty(globalThis, key, existing);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
delete globalObject[key];
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=dom-polyfill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom-polyfill.js","sourceRoot":"","sources":["../../src/exporter/dom-polyfill.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Exporter
|
|
3
|
+
*
|
|
4
|
+
* Exports Excalidraw files to SVG and PNG image formats.
|
|
5
|
+
* Keeps `@excalidraw/utils` because it is still required for:
|
|
6
|
+
* - `exportToSvg()` during SVG generation
|
|
7
|
+
* - bundled font assets used by server-side SVG/PNG text rendering
|
|
8
|
+
*
|
|
9
|
+
* PNG export rasterizes the generated SVG with `@resvg/resvg-js`.
|
|
10
|
+
*/
|
|
11
|
+
import type { ExcalidrawFile } from '../types/excalidraw.js';
|
|
12
|
+
/**
|
|
13
|
+
* Export options matching Excalidraw website capabilities
|
|
14
|
+
*/
|
|
15
|
+
export interface ExportOptions {
|
|
16
|
+
/** Output format: 'svg' or 'png' */
|
|
17
|
+
format: 'svg' | 'png';
|
|
18
|
+
/** Whether to include the background in the export (default: true) */
|
|
19
|
+
exportBackground?: boolean;
|
|
20
|
+
/** Background color (default: from appState or '#ffffff') */
|
|
21
|
+
viewBackgroundColor?: string;
|
|
22
|
+
/** Export with dark mode (default: false) */
|
|
23
|
+
dark?: boolean;
|
|
24
|
+
/** Embed scene data into the exported image (default: false) */
|
|
25
|
+
exportEmbedScene?: boolean;
|
|
26
|
+
/** Padding around the exported content in pixels (default: 10) */
|
|
27
|
+
padding?: number;
|
|
28
|
+
/** Scale factor for PNG export (default: 1). Higher values = higher resolution */
|
|
29
|
+
scale?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Default export options
|
|
33
|
+
*/
|
|
34
|
+
export declare const DEFAULT_EXPORT_OPTIONS: Required<ExportOptions>;
|
|
35
|
+
/**
|
|
36
|
+
* Convert an Excalidraw file to an SVG string.
|
|
37
|
+
*
|
|
38
|
+
* Internally initialises the DOM polyfill (if not already done) and
|
|
39
|
+
* delegates to `@excalidraw/utils.exportToSvg` for the actual rendering.
|
|
40
|
+
* Console noise from font / Path2D warnings is suppressed during the call.
|
|
41
|
+
*
|
|
42
|
+
* @param file - The Excalidraw file to convert.
|
|
43
|
+
* @param options - Optional overrides for background, dark-mode, padding, etc.
|
|
44
|
+
* @returns The rendered SVG markup as a string.
|
|
45
|
+
*/
|
|
46
|
+
export declare function convertToSVG(file: ExcalidrawFile, options?: Partial<ExportOptions>): Promise<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Convert an Excalidraw file to a PNG image buffer.
|
|
49
|
+
*
|
|
50
|
+
* Pipeline: Excalidraw → SVG (via {@link convertToSVG}) → PNG (via resvg-js).
|
|
51
|
+
* The `scale` option controls the output resolution.
|
|
52
|
+
*
|
|
53
|
+
* Font rendering note: `@resvg/resvg-js` ignores `@font-face` CSS inside the
|
|
54
|
+
* SVG, so bundled TTF font files are provided through the `fontDirs` option.
|
|
55
|
+
*
|
|
56
|
+
* @param file - The Excalidraw file to convert.
|
|
57
|
+
* @param options - Optional overrides for scale, background, dark-mode, etc.
|
|
58
|
+
* @returns A Buffer containing the PNG image data.
|
|
59
|
+
*/
|
|
60
|
+
export declare function convertToPNG(file: ExcalidrawFile, options?: Partial<ExportOptions>): Promise<Buffer>;
|
|
61
|
+
/**
|
|
62
|
+
* Convert an Excalidraw file to the specified image format.
|
|
63
|
+
*
|
|
64
|
+
* This is the main entry-point that routes to {@link convertToSVG} or
|
|
65
|
+
* {@link convertToPNG} based on `options.format`.
|
|
66
|
+
*
|
|
67
|
+
* @param file - The Excalidraw file to convert.
|
|
68
|
+
* @param options - Export options; `format` is required (`'svg'` | `'png'`).
|
|
69
|
+
* @returns An SVG string when `format` is `'svg'`, or a PNG `Buffer` when `'png'`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function convertImage(file: ExcalidrawFile, options: ExportOptions): Promise<string | Buffer>;
|
|
72
|
+
/**
|
|
73
|
+
* Replace a file path's extension with a new one.
|
|
74
|
+
*
|
|
75
|
+
* If the path has no extension (no dot, or dot at position 0 like `.gitignore`),
|
|
76
|
+
* the new extension is appended instead.
|
|
77
|
+
*
|
|
78
|
+
* @param filePath - The original file path (may include directories).
|
|
79
|
+
* @param newExt - The new extension **without** the leading dot (e.g. `'svg'`).
|
|
80
|
+
* @returns The file path with its extension swapped.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* swapExtension('diagram.excalidraw', 'png') // → 'diagram.png'
|
|
84
|
+
* swapExtension('/tmp/out.json', 'svg') // → '/tmp/out.svg'
|
|
85
|
+
*/
|
|
86
|
+
export declare function swapExtension(filePath: string, newExt: string): string;
|
|
87
|
+
//# sourceMappingURL=image-exporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-exporter.d.ts","sourceRoot":"","sources":["../../src/exporter/image-exporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,MAAM,EAAE,KAAK,GAAG,KAAK,CAAC;IAEtB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,6DAA6D;IAC7D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B,6CAA6C;IAC7C,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,aAAa,CAQ1D,CAAC;AA6BF;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAC/B,OAAO,CAAC,MAAM,CAAC,CAGjB;AAeD;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAC/B,OAAO,CAAC,MAAM,CAAC,CAqCjB;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,CAK1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGtE"}
|