satoru-render 1.0.10 → 1.0.11

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.
@@ -379,20 +379,20 @@ var SatoruBase = class {
379
379
  }
380
380
  async getModule() {
381
381
  if (!this.modPromise) this.modPromise = (async () => {
382
- let currentLogLevel = LogLevel.None;
382
+ let currentLogLevel = 0;
383
383
  let currentUserOnLog;
384
384
  const mod = await this.factory({
385
385
  onLog: (level, message) => {
386
- if (currentLogLevel !== LogLevel.None && level <= currentLogLevel && currentUserOnLog) currentUserOnLog(level, message);
386
+ if (currentLogLevel !== 0 && level <= currentLogLevel && currentUserOnLog) currentUserOnLog(level, message);
387
387
  },
388
388
  print: (text) => {
389
- if (currentLogLevel !== LogLevel.None && LogLevel.Info <= currentLogLevel && currentUserOnLog) currentUserOnLog(LogLevel.Info, text);
389
+ if (currentLogLevel !== 0 && 3 <= currentLogLevel && currentUserOnLog) currentUserOnLog(3, text);
390
390
  },
391
391
  printErr: (text) => {
392
- if (currentLogLevel !== LogLevel.None && LogLevel.Error <= currentLogLevel && currentUserOnLog) currentUserOnLog(LogLevel.Error, text);
392
+ if (currentLogLevel !== 0 && 1 <= currentLogLevel && currentUserOnLog) currentUserOnLog(1, text);
393
393
  }
394
394
  });
395
- mod.logLevel = LogLevel.None;
395
+ mod.logLevel = 0;
396
396
  const originalSetLogLevel = mod.set_log_level;
397
397
  mod.set_log_level = (level) => {
398
398
  currentLogLevel = level;
@@ -502,6 +502,13 @@ var SatoruBase = class {
502
502
  }
503
503
  async render(options) {
504
504
  let { format = "svg", value, url, baseUrl } = options;
505
+ const profileEnabled = options.profile === true;
506
+ const profile = {};
507
+ const now = () => typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
508
+ const addProfile = (name, elapsed) => {
509
+ if (!profileEnabled) return;
510
+ profile[name] = (profile[name] ?? 0) + elapsed;
511
+ };
505
512
  if (format === "pdf" && Array.isArray(value) && value.length > 1) {
506
513
  const pagePdfs = [];
507
514
  const SatoruClass = this.constructor;
@@ -534,7 +541,7 @@ var SatoruBase = class {
534
541
  const prevLogLevel = mod.logLevel;
535
542
  const prevOnLog = mod.onLog;
536
543
  const prevFontMap = this.currentFontMap;
537
- mod.logLevel = logLevel ?? LogLevel.None;
544
+ mod.logLevel = logLevel ?? 0;
538
545
  mod.set_log_level(mod.logLevel);
539
546
  mod.onLog = onLog;
540
547
  this.currentFontMap = options.fontMap ?? DEFAULT_FONT_MAP;
@@ -548,8 +555,13 @@ var SatoruBase = class {
548
555
  const cachedResolver = async (r) => {
549
556
  const cacheKey = `${r.type}:${r.url}:${r.characters ?? ""}`;
550
557
  const cached = this.resourceCache.get(cacheKey);
551
- if (cached) return cached;
558
+ if (cached) {
559
+ addProfile("resourceCacheHitsCount", 1);
560
+ return cached;
561
+ }
562
+ const resolveStart = now();
552
563
  const result = await resolver(r);
564
+ addProfile("resolveResources", now() - resolveStart);
553
565
  if (result) {
554
566
  if (result instanceof Uint8Array) this.resourceCache.set(cacheKey, result);
555
567
  else if ("css" in result && "fonts" in result) this.resourceCache.set(cacheKey, result);
@@ -571,23 +583,6 @@ var SatoruBase = class {
571
583
  if (images) for (const img of images) mod.load_image(instancePtr, img.name, img.url, img.width ?? 0, img.height ?? 0);
572
584
  if (css) mod.scan_css(instancePtr, css);
573
585
  const loadResourceData = (r, uint8) => {
574
- if (r.type === "image" && typeof createImageBitmap !== "undefined" && typeof OffscreenCanvas !== "undefined") return (async () => {
575
- try {
576
- const blob = new Blob([uint8.buffer]);
577
- const bitmap = await createImageBitmap(blob);
578
- const ctx = new OffscreenCanvas(bitmap.width, bitmap.height).getContext("2d");
579
- if (ctx) {
580
- ctx.drawImage(bitmap, 0, 0);
581
- const imageData = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
582
- mod.load_image_pixels(instancePtr, r.url, bitmap.width, bitmap.height, new Uint8Array(imageData.data.buffer), r.url);
583
- return;
584
- }
585
- } catch (e) {}
586
- let typeInt = 1;
587
- if (r.type === "image") typeInt = 2;
588
- if (r.type === "css") typeInt = 3;
589
- mod.add_resource(instancePtr, r.url, typeInt, uint8);
590
- })();
591
586
  let typeInt = 1;
592
587
  if (r.type === "image") typeInt = 2;
593
588
  if (r.type === "css") typeInt = 3;
@@ -599,14 +594,53 @@ var SatoruBase = class {
599
594
  for (const rawHtml of inputHtmls) {
600
595
  let processedHtml = rawHtml;
601
596
  for (let i = 0; i < 10; i++) {
602
- mod.collect_resources(instancePtr, processedHtml, width, height);
603
- const json = mod.get_pending_resources(instancePtr);
604
- if (!json) break;
605
- const pending = JSON.parse(json).filter((r) => {
597
+ addProfile("collectResourcesCount", 1);
598
+ const collectStart = now();
599
+ mod.collect_resources(instancePtr, processedHtml, width, height, options.mediaType === "print" ? 1 : 0);
600
+ addProfile("collectResources", now() - collectStart);
601
+ const pendingStart = now();
602
+ const binary = mod.get_pending_resources(instancePtr);
603
+ addProfile("getPendingResources", now() - pendingStart);
604
+ if (!binary) break;
605
+ const parseStart = now();
606
+ const view = new DataView(binary.buffer, binary.byteOffset, binary.byteLength);
607
+ let offset = 0;
608
+ const count = view.getUint32(offset, true);
609
+ offset += 4;
610
+ const resources = [];
611
+ for (let j = 0; j < count; j++) {
612
+ const typeInt = view.getUint8(offset++);
613
+ const redraw_on_ready = view.getUint8(offset++) !== 0;
614
+ const urlLen = view.getUint32(offset, true);
615
+ offset += 4;
616
+ const url = new TextDecoder().decode(new Uint8Array(binary.buffer, binary.byteOffset + offset, urlLen));
617
+ offset += urlLen;
618
+ const nameLen = view.getUint32(offset, true);
619
+ offset += 4;
620
+ const name = new TextDecoder().decode(new Uint8Array(binary.buffer, binary.byteOffset + offset, nameLen));
621
+ offset += nameLen;
622
+ const charsLen = view.getUint32(offset, true);
623
+ offset += 4;
624
+ const characters = new TextDecoder().decode(new Uint8Array(binary.buffer, binary.byteOffset + offset, charsLen));
625
+ offset += charsLen;
626
+ let type = "font";
627
+ if (typeInt === 2) type = "image";
628
+ else if (typeInt === 3) type = "css";
629
+ resources.push({
630
+ type,
631
+ url,
632
+ name,
633
+ characters,
634
+ redraw_on_ready
635
+ });
636
+ }
637
+ const pending = resources.filter((r) => {
606
638
  const key = `${r.type}:${r.url}:${r.characters ?? ""}`;
607
639
  return !resolvedResources.has(key);
608
640
  });
641
+ addProfile("parsePendingResources", now() - parseStart);
609
642
  if (pending.length === 0) break;
643
+ const loadStart = now();
610
644
  await Promise.all(pending.map(async (r) => {
611
645
  try {
612
646
  if (r.url.startsWith("data:")) return;
@@ -652,7 +686,9 @@ var SatoruBase = class {
652
686
  console.warn(`Failed to resolve resource: ${r.url}`, e);
653
687
  }
654
688
  }));
689
+ addProfile("loadPendingResources", now() - loadStart);
655
690
  }
691
+ const stripStart = now();
656
692
  const resolvedUrls = /* @__PURE__ */ new Set();
657
693
  resolvedResources.forEach((key) => {
658
694
  const parts = key.split(":");
@@ -664,13 +700,29 @@ var SatoruBase = class {
664
700
  processedHtml = processedHtml.replace(linkRegex, "");
665
701
  });
666
702
  processedHtmls.push(processedHtml);
703
+ addProfile("stripResolvedLinks", now() - stripStart);
667
704
  }
668
- const result = mod.render(instancePtr, processedHtmls, width, height, {
705
+ const formatMap = {
669
706
  svg: 0,
670
707
  png: 1,
671
708
  webp: 2,
672
709
  pdf: 3
673
- }[format] ?? 0, {
710
+ };
711
+ const renderStart = now();
712
+ const result = processedHtmls.length === 1 ? mod.render_from_state(instancePtr, width, height, formatMap[format] ?? 0, {
713
+ svgTextToPaths: options.textToPaths ?? true,
714
+ outputWidth: options.outputWidth ?? 0,
715
+ outputHeight: options.outputHeight ?? 0,
716
+ fitType: options.fit === "cover" ? 1 : options.fit === "fill" ? 2 : 0,
717
+ cropX: options.crop?.x ?? 0,
718
+ cropY: options.crop?.y ?? 0,
719
+ cropWidth: options.crop?.width ?? 0,
720
+ cropHeight: options.crop?.height ?? 0,
721
+ fitPositionX: options.fitPosition?.x ?? .5,
722
+ fitPositionY: options.fitPosition?.y ?? .5,
723
+ backgroundColor: this.parseColor(options.backgroundColor),
724
+ mediaType: options.mediaType === "print" ? 1 : 0
725
+ }) : mod.render(instancePtr, processedHtmls, width, height, formatMap[format] ?? 0, {
674
726
  svgTextToPaths: options.textToPaths ?? true,
675
727
  outputWidth: options.outputWidth ?? 0,
676
728
  outputHeight: options.outputHeight ?? 0,
@@ -684,12 +736,24 @@ var SatoruBase = class {
684
736
  backgroundColor: this.parseColor(options.backgroundColor),
685
737
  mediaType: options.mediaType === "print" ? 1 : 0
686
738
  });
739
+ addProfile("wasmRender", now() - renderStart);
687
740
  if (!result) {
741
+ options.onProfile?.(profile);
688
742
  if (format === "svg") return "";
689
743
  return new Uint8Array();
690
744
  }
691
- if (format === "svg") return new TextDecoder().decode(result);
692
- return new Uint8Array(result);
745
+ if (format === "svg") {
746
+ const decodeStart = now();
747
+ const svg = new TextDecoder().decode(result);
748
+ addProfile("decodeResult", now() - decodeStart);
749
+ options.onProfile?.(profile);
750
+ return svg;
751
+ }
752
+ const copyStart = now();
753
+ const bytes = new Uint8Array(result);
754
+ addProfile("copyResult", now() - copyStart);
755
+ options.onProfile?.(profile);
756
+ return bytes;
693
757
  } finally {
694
758
  mod.destroy_instance(instancePtr);
695
759
  mod.logLevel = prevLogLevel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "satoru-render",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "High-fidelity HTML/CSS to SVG/PNG/PDF converter running in WebAssembly",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -123,12 +123,12 @@
123
123
  "cloudflare-workers"
124
124
  ],
125
125
  "devDependencies": {
126
- "@types/jsdom": "28.0.1",
126
+ "@types/jsdom": "28.0.3",
127
127
  "@types/react": "^19.2.14",
128
128
  "@types/react-dom": "^19.2.3",
129
- "rolldown": "1.0.0-rc.11",
130
- "typescript": "^5.9.3",
131
- "vitest": "^4.1.0"
129
+ "rolldown": "1.0.1",
130
+ "typescript": "^6.0.3",
131
+ "vitest": "^4.1.6"
132
132
  },
133
133
  "dependencies": {
134
134
  "worker-lib": "2.2.0"