satoru-render 1.0.1 → 1.0.3
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 +5 -5
- package/dist/core.d.ts +22 -0
- package/dist/core.js +39 -3
- package/dist/node.js +12 -4
- package/dist/satoru-single.js +0 -0
- package/dist/satoru.js +2 -4727
- package/dist/satoru.wasm +0 -0
- package/dist/web-workers.js +442 -551
- package/dist/workers-parent.js +32 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -350,12 +350,12 @@ npx satoru-render input.html -f webp --verbose
|
|
|
350
350
|
| :---------------- | :----------------------------------------- | :------------------------------------------------------ |
|
|
351
351
|
| `value` | `string \| string[] \| HTMLElement \| ...` | HTML string, array of strings, or DOM element(s). |
|
|
352
352
|
| `url` | `string` | URL to fetch HTML from. |
|
|
353
|
-
| `width` | `number` | **Required.**
|
|
354
|
-
| `height` | `number` |
|
|
353
|
+
| `width` | `number` | **Required.** Canvas width in pixels (used for layout). |
|
|
354
|
+
| `height` | `number` | Canvas height. Default: `0` (auto-calculate). |
|
|
355
355
|
| `crop` | `{ x, y, width, height }` | Crop parameters to extract a specific region. |
|
|
356
|
-
| `outputWidth` | `number` |
|
|
357
|
-
| `outputHeight` | `number` |
|
|
358
|
-
| `fit` | `"contain" \| "cover" \| "fill"` |
|
|
356
|
+
| `outputWidth` | `number` | Output image width. Default: canvas/crop width. |
|
|
357
|
+
| `outputHeight` | `number` | Output image height. Default: canvas/crop height. |
|
|
358
|
+
| `fit` | `"contain" \| "cover" \| "fill"` | Fit strategy when canvas/crop size differs from output. |
|
|
359
359
|
| `format` | `"svg" \| "png" \| "webp" \| "pdf"` | Output format. Default: `"svg"`. |
|
|
360
360
|
| `resolveResource` | `ResourceResolver` | Async callback to fetch assets (fonts, images, CSS). |
|
|
361
361
|
| `fonts` | `Object[]` | Pre-load fonts: `[{ name, data: Uint8Array }]`. |
|
package/dist/core.d.ts
CHANGED
|
@@ -36,19 +36,35 @@ export interface ResolvedFontResult {
|
|
|
36
36
|
}
|
|
37
37
|
export type ResourceResolver = (resource: RequiredResource, defaultResolver: (resource: RequiredResource) => Promise<Uint8Array | ResolvedFontResult | null>) => Promise<Uint8Array | ArrayBufferView | ResolvedFontResult | null>;
|
|
38
38
|
export interface RenderOptions {
|
|
39
|
+
/** Input content (HTML string or state object) */
|
|
39
40
|
value?: string | string[] | any | any[];
|
|
41
|
+
/** Source URL */
|
|
40
42
|
url?: string;
|
|
43
|
+
/** Canvas width for layout (used for CSS calculations) */
|
|
41
44
|
width: number;
|
|
45
|
+
/** Canvas height for layout. If omitted, determined automatically by content height */
|
|
42
46
|
height?: number;
|
|
47
|
+
/** Cropping options for the source canvas */
|
|
43
48
|
crop?: {
|
|
44
49
|
x: number;
|
|
45
50
|
y: number;
|
|
46
51
|
width: number;
|
|
47
52
|
height: number;
|
|
48
53
|
};
|
|
54
|
+
/** Final output image width. Defaults to width (or crop.width) */
|
|
49
55
|
outputWidth?: number;
|
|
56
|
+
/** Final output image height. Defaults to height (or crop.height) */
|
|
50
57
|
outputHeight?: number;
|
|
58
|
+
/** Resizing strategy to fit the canvas into the output size (default: "contain") */
|
|
51
59
|
fit?: "contain" | "cover" | "fill";
|
|
60
|
+
/** Alignment origin when fitted (default: {x: 0.5, y: 0.5}) */
|
|
61
|
+
fitPosition?: {
|
|
62
|
+
x: number;
|
|
63
|
+
y: number;
|
|
64
|
+
};
|
|
65
|
+
/** Background color of the output canvas. (e.g., "#ffffff", "rgba(0,0,0,0.5)") */
|
|
66
|
+
backgroundColor?: string;
|
|
67
|
+
/** Output format */
|
|
52
68
|
format?: "svg" | "png" | "webp" | "pdf";
|
|
53
69
|
textToPaths?: boolean;
|
|
54
70
|
resolveResource?: ResourceResolver;
|
|
@@ -95,11 +111,17 @@ export declare abstract class SatoruBase {
|
|
|
95
111
|
outputWidth?: number;
|
|
96
112
|
outputHeight?: number;
|
|
97
113
|
fit?: "contain" | "cover" | "fill";
|
|
114
|
+
fitPosition?: {
|
|
115
|
+
x: number;
|
|
116
|
+
y: number;
|
|
117
|
+
};
|
|
118
|
+
backgroundColor?: string;
|
|
98
119
|
format?: "svg" | "png" | "webp" | "pdf";
|
|
99
120
|
textToPaths?: boolean;
|
|
100
121
|
}): Promise<string | Uint8Array>;
|
|
101
122
|
destroyInstance(inst: any): Promise<void>;
|
|
102
123
|
loadFallbackFont(data: Uint8Array): Promise<void>;
|
|
124
|
+
protected parseColor(color?: string): number;
|
|
103
125
|
protected abstract resolveDefaultResource(resource: RequiredResource, baseUrl?: string, userAgent?: string): Promise<Uint8Array | ResolvedFontResult | null>;
|
|
104
126
|
protected abstract fetchHtml(url: string, userAgent?: string): Promise<string>;
|
|
105
127
|
render(options: RenderOptions & {
|
package/dist/core.js
CHANGED
|
@@ -237,6 +237,9 @@ export class SatoruBase {
|
|
|
237
237
|
cropY: options.crop?.y ?? 0,
|
|
238
238
|
cropWidth: options.crop?.width ?? 0,
|
|
239
239
|
cropHeight: options.crop?.height ?? 0,
|
|
240
|
+
fitPositionX: options.fitPosition?.x ?? 0.5,
|
|
241
|
+
fitPositionY: options.fitPosition?.y ?? 0.5,
|
|
242
|
+
backgroundColor: this.parseColor(options.backgroundColor),
|
|
240
243
|
});
|
|
241
244
|
if (!result) {
|
|
242
245
|
if (options.format === "svg")
|
|
@@ -262,6 +265,36 @@ export class SatoruBase {
|
|
|
262
265
|
mod.destroy_instance(inst);
|
|
263
266
|
}
|
|
264
267
|
}
|
|
268
|
+
parseColor(color) {
|
|
269
|
+
if (!color)
|
|
270
|
+
return 0x00000000;
|
|
271
|
+
if (color.startsWith("#")) {
|
|
272
|
+
let hex = color.slice(1);
|
|
273
|
+
if (hex.length === 3) {
|
|
274
|
+
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
275
|
+
}
|
|
276
|
+
if (hex.length === 6) {
|
|
277
|
+
return (0xff000000 | parseInt(hex, 16)) >>> 0;
|
|
278
|
+
}
|
|
279
|
+
if (hex.length === 8) {
|
|
280
|
+
// RRGGBBAA -> AARRGGBB
|
|
281
|
+
const r = hex.slice(0, 2);
|
|
282
|
+
const g = hex.slice(2, 4);
|
|
283
|
+
const b = hex.slice(4, 6);
|
|
284
|
+
const a = hex.slice(6, 8);
|
|
285
|
+
return parseInt(a + r + g + b, 16) >>> 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
289
|
+
if (m) {
|
|
290
|
+
const r = parseInt(m[1]);
|
|
291
|
+
const g = parseInt(m[2]);
|
|
292
|
+
const b = parseInt(m[3]);
|
|
293
|
+
const a = m[4] ? Math.round(parseFloat(m[4]) * 255) : 255;
|
|
294
|
+
return ((a << 24) | (r << 16) | (g << 8) | b) >>> 0;
|
|
295
|
+
}
|
|
296
|
+
return 0x00000000;
|
|
297
|
+
}
|
|
265
298
|
async render(options) {
|
|
266
299
|
let { format = "svg", value, url, baseUrl } = options;
|
|
267
300
|
if (format === "pdf" && Array.isArray(value) && value.length > 1) {
|
|
@@ -374,7 +407,7 @@ export class SatoruBase {
|
|
|
374
407
|
catch (e) {
|
|
375
408
|
// fall through
|
|
376
409
|
}
|
|
377
|
-
let typeInt = 1;
|
|
410
|
+
let typeInt = 1; // Font
|
|
378
411
|
if (r.type === "image")
|
|
379
412
|
typeInt = 2;
|
|
380
413
|
if (r.type === "css")
|
|
@@ -382,7 +415,7 @@ export class SatoruBase {
|
|
|
382
415
|
mod.add_resource(instancePtr, r.url, typeInt, uint8);
|
|
383
416
|
})();
|
|
384
417
|
}
|
|
385
|
-
let typeInt = 1;
|
|
418
|
+
let typeInt = 1; // Font
|
|
386
419
|
if (r.type === "image")
|
|
387
420
|
typeInt = 2;
|
|
388
421
|
if (r.type === "css")
|
|
@@ -422,7 +455,7 @@ export class SatoruBase {
|
|
|
422
455
|
"fonts" in data) {
|
|
423
456
|
const fontResult = data;
|
|
424
457
|
// Load the CSS first so C++ can parse @font-face
|
|
425
|
-
mod.add_resource(instancePtr, r.url,
|
|
458
|
+
mod.add_resource(instancePtr, r.url, 3, // Css type
|
|
426
459
|
fontResult.css);
|
|
427
460
|
// Then load all prefetched font binaries directly
|
|
428
461
|
for (const font of fontResult.fonts) {
|
|
@@ -476,6 +509,9 @@ export class SatoruBase {
|
|
|
476
509
|
cropY: options.crop?.y ?? 0,
|
|
477
510
|
cropWidth: options.crop?.width ?? 0,
|
|
478
511
|
cropHeight: options.crop?.height ?? 0,
|
|
512
|
+
fitPositionX: options.fitPosition?.x ?? 0.5,
|
|
513
|
+
fitPositionY: options.fitPosition?.y ?? 0.5,
|
|
514
|
+
backgroundColor: this.parseColor(options.backgroundColor),
|
|
479
515
|
});
|
|
480
516
|
if (!result) {
|
|
481
517
|
if (format === "svg")
|
package/dist/node.js
CHANGED
|
@@ -12,7 +12,7 @@ export class Satoru extends SatoruBase {
|
|
|
12
12
|
if (resource.url.startsWith("provider:google-fonts")) {
|
|
13
13
|
return resolveGoogleFonts(resource, userAgent);
|
|
14
14
|
}
|
|
15
|
-
const isAbsolute = /^[a-z][a-z0-9+.-]
|
|
15
|
+
const isAbsolute = /^[a-z][a-z0-9+.-]*:\/\//i.test(resource.url) || resource.url.startsWith("data:");
|
|
16
16
|
let baseDir = baseUrl
|
|
17
17
|
? baseUrl.startsWith("file://")
|
|
18
18
|
? baseUrl.slice(7)
|
|
@@ -24,7 +24,7 @@ export class Satoru extends SatoruBase {
|
|
|
24
24
|
if (!isAbsolute &&
|
|
25
25
|
!/^[a-z][a-z0-9+.-]*:\/\//i.test(baseDir) &&
|
|
26
26
|
!baseDir.startsWith("data:")) {
|
|
27
|
-
const filePath = path.join(baseDir, resource.url);
|
|
27
|
+
const filePath = path.isAbsolute(resource.url) ? resource.url : path.join(baseDir, resource.url);
|
|
28
28
|
if (fs.existsSync(filePath)) {
|
|
29
29
|
return new Uint8Array(fs.readFileSync(filePath));
|
|
30
30
|
}
|
|
@@ -33,8 +33,16 @@ export class Satoru extends SatoruBase {
|
|
|
33
33
|
if (isAbsolute) {
|
|
34
34
|
finalUrl = resource.url;
|
|
35
35
|
}
|
|
36
|
-
else if (baseUrl
|
|
37
|
-
|
|
36
|
+
else if (baseUrl) {
|
|
37
|
+
try {
|
|
38
|
+
const base = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseUrl)
|
|
39
|
+
? baseUrl
|
|
40
|
+
: new URL(`file:///${baseUrl.replace(/\\/g, "/")}`).href;
|
|
41
|
+
finalUrl = new URL(resource.url, base).href;
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
// ignore
|
|
45
|
+
}
|
|
38
46
|
}
|
|
39
47
|
if (!finalUrl)
|
|
40
48
|
return null;
|
package/dist/satoru-single.js
CHANGED
|
Binary file
|