satoru-render 1.0.2 → 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/dist/core.d.ts CHANGED
@@ -57,6 +57,13 @@ export interface RenderOptions {
57
57
  outputHeight?: number;
58
58
  /** Resizing strategy to fit the canvas into the output size (default: "contain") */
59
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;
60
67
  /** Output format */
61
68
  format?: "svg" | "png" | "webp" | "pdf";
62
69
  textToPaths?: boolean;
@@ -104,11 +111,17 @@ export declare abstract class SatoruBase {
104
111
  outputWidth?: number;
105
112
  outputHeight?: number;
106
113
  fit?: "contain" | "cover" | "fill";
114
+ fitPosition?: {
115
+ x: number;
116
+ y: number;
117
+ };
118
+ backgroundColor?: string;
107
119
  format?: "svg" | "png" | "webp" | "pdf";
108
120
  textToPaths?: boolean;
109
121
  }): Promise<string | Uint8Array>;
110
122
  destroyInstance(inst: any): Promise<void>;
111
123
  loadFallbackFont(data: Uint8Array): Promise<void>;
124
+ protected parseColor(color?: string): number;
112
125
  protected abstract resolveDefaultResource(resource: RequiredResource, baseUrl?: string, userAgent?: string): Promise<Uint8Array | ResolvedFontResult | null>;
113
126
  protected abstract fetchHtml(url: string, userAgent?: string): Promise<string>;
114
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, 1, // Font type
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+.-]*:/i.test(resource.url);
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 && /^[a-z][a-z0-9+.-]*:/i.test(baseUrl)) {
37
- finalUrl = new URL(resource.url, baseUrl).href;
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;
Binary file