@pixldocs/canvas-renderer 0.5.209 → 0.5.211

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.
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jspdf = require("jspdf");
4
4
  const svg2pdf_js = require("svg2pdf.js");
5
5
  const fabric = require("fabric");
6
- const index = require("./index-DQF_on2h.cjs");
6
+ const index = require("./index-d8U2oDzI.cjs");
7
7
  const pdfFonts = require("./pdfFonts-BTj2f465.cjs");
8
8
  function _interopNamespaceDefault(e) {
9
9
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -143,6 +143,32 @@ function elementHasFade(element) {
143
143
  const yieldToUI = () => new Promise((resolve) => {
144
144
  requestAnimationFrame(() => setTimeout(resolve, 0));
145
145
  });
146
+ const IS_SAFARI = typeof navigator !== "undefined" && /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent || "");
147
+ const IS_IOS = typeof navigator !== "undefined" && /iPad|iPhone|iPod/.test(navigator.userAgent || "");
148
+ const SAFARI_MAX_SIDE = IS_IOS ? 4096 : 16384;
149
+ const SAFARI_MAX_AREA = IS_IOS ? 16777216 : 268435456;
150
+ function getSafeCanvasDims(w, h) {
151
+ const width = Math.max(1, Math.round(w));
152
+ const height = Math.max(1, Math.round(h));
153
+ if (!IS_SAFARI) return { width, height, scale: 1 };
154
+ const sideScale = Math.min(1, SAFARI_MAX_SIDE / Math.max(width, height));
155
+ const areaScale = Math.min(1, Math.sqrt(SAFARI_MAX_AREA / (width * height)));
156
+ const scale = Math.min(sideScale, areaScale);
157
+ if (scale >= 1) return { width, height, scale: 1 };
158
+ return {
159
+ width: Math.max(1, Math.floor(width * scale)),
160
+ height: Math.max(1, Math.floor(height * scale)),
161
+ scale
162
+ };
163
+ }
164
+ async function ensureImageDecoded(img) {
165
+ try {
166
+ if (typeof img.decode === "function") {
167
+ await img.decode();
168
+ }
169
+ } catch {
170
+ }
171
+ }
146
172
  const SHADOW_RASTER_ALPHA_COMPENSATION = 0.84;
147
173
  function collectFontSpecsFromShadowMarkup(markup) {
148
174
  const specs = /* @__PURE__ */ new Set();
@@ -333,6 +359,7 @@ async function rasterizeStoredCropImageToPng(options) {
333
359
  }
334
360
  });
335
361
  if (!img) return null;
362
+ await ensureImageDecoded(img);
336
363
  const natW = Math.max(1, naturalWidth || img.naturalWidth || frameW);
337
364
  const natH = Math.max(1, naturalHeight || img.naturalHeight || frameH);
338
365
  const baseScale = Math.max(frameW / natW, frameH / natH);
@@ -347,8 +374,11 @@ async function rasterizeStoredCropImageToPng(options) {
347
374
  const offsetY = overflowY > 0 ? -overflowY * (clampedPanY - 0.5) : 0;
348
375
  const imgX = frameW / 2 + offsetX - drawW / 2;
349
376
  const imgY = frameH / 2 + offsetY - drawH / 2;
350
- const outW = maintainResolution && natW > frameW ? Math.max(1, Math.round(natW)) : Math.max(1, Math.round(frameW * 2));
351
- const outH = maintainResolution && natH > frameH ? Math.max(1, Math.round(natH)) : Math.max(1, Math.round(frameH * 2));
377
+ const requestedW = maintainResolution && natW > frameW ? Math.max(1, Math.round(natW)) : Math.max(1, Math.round(frameW * 2));
378
+ const requestedH = maintainResolution && natH > frameH ? Math.max(1, Math.round(natH)) : Math.max(1, Math.round(frameH * 2));
379
+ const safe = getSafeCanvasDims(requestedW, requestedH);
380
+ const outW = safe.width;
381
+ const outH = safe.height;
352
382
  const canvas = document.createElement("canvas");
353
383
  canvas.width = outW;
354
384
  canvas.height = outH;
@@ -510,6 +540,51 @@ function parseSvgNumber(value) {
510
540
  const n = Number(match[0]);
511
541
  return Number.isFinite(n) && n > 0 ? n : null;
512
542
  }
543
+ function getPdfJpegQuality() {
544
+ return Math.max(0.5, Math.min(0.95, Number(__pdfJpegQuality) || 0.82));
545
+ }
546
+ function getCompressedRasterSizing(quality) {
547
+ if (quality >= 0.9) return { density: 2.25, maxEdge: 2600 };
548
+ if (quality >= 0.8) return { density: 1.9, maxEdge: 2200 };
549
+ if (quality >= 0.65) return { density: 1.45, maxEdge: 1700 };
550
+ return { density: 1.15, maxEdge: 1300 };
551
+ }
552
+ async function decodeImageForPdf(src) {
553
+ return new Promise((resolve) => {
554
+ try {
555
+ const img = new Image();
556
+ if (!src.startsWith("data:") && !src.startsWith("blob:")) img.crossOrigin = "anonymous";
557
+ img.onload = async () => {
558
+ try {
559
+ if (typeof img.decode === "function") await img.decode();
560
+ } catch {
561
+ }
562
+ resolve((img.naturalWidth || img.width) && (img.naturalHeight || img.height) ? img : null);
563
+ };
564
+ img.onerror = () => resolve(null);
565
+ img.src = src;
566
+ } catch {
567
+ resolve(null);
568
+ }
569
+ });
570
+ }
571
+ function decodedImageHasAlpha(img, width, height) {
572
+ try {
573
+ const sample = document.createElement("canvas");
574
+ sample.width = Math.min(32, Math.max(1, width));
575
+ sample.height = Math.min(32, Math.max(1, height));
576
+ const ctx = sample.getContext("2d");
577
+ if (!ctx) return false;
578
+ ctx.clearRect(0, 0, sample.width, sample.height);
579
+ ctx.drawImage(img, 0, 0, sample.width, sample.height);
580
+ const pixels = ctx.getImageData(0, 0, sample.width, sample.height).data;
581
+ for (let i = 3; i < pixels.length; i += 4) {
582
+ if (pixels[i] < 255) return true;
583
+ }
584
+ } catch {
585
+ }
586
+ return false;
587
+ }
513
588
  function getApproxSvgTransformScale(image) {
514
589
  let sx = 1;
515
590
  let sy = 1;
@@ -536,6 +611,61 @@ function getApproxSvgTransformScale(image) {
536
611
  }
537
612
  return { x: Math.max(0.01, Math.abs(sx)), y: Math.max(0.01, Math.abs(sy)) };
538
613
  }
614
+ async function rewriteCompressedLiveSvgRasterImages(svg) {
615
+ if (!__pdfCompressImages || typeof document === "undefined") return;
616
+ const images = Array.from(svg.querySelectorAll("image")).filter((image) => {
617
+ const href = getSvgImageHref(image).trim();
618
+ return /^data:image\/(?:png|jpe?g|webp|avif)[;,]/i.test(href);
619
+ });
620
+ if (images.length === 0) return;
621
+ const jpegQuality = getPdfJpegQuality();
622
+ const sizing = getCompressedRasterSizing(jpegQuality);
623
+ let rewritten = 0;
624
+ for (const image of images) {
625
+ const href = getSvgImageHref(image).trim();
626
+ const loaded = await decodeImageForPdf(href);
627
+ if (!loaded) continue;
628
+ const naturalW = Math.max(1, Math.round(loaded.naturalWidth || loaded.width || 1));
629
+ const naturalH = Math.max(1, Math.round(loaded.naturalHeight || loaded.height || 1));
630
+ const attrW = parseSvgNumber(image.getAttribute("width")) || naturalW;
631
+ const attrH = parseSvgNumber(image.getAttribute("height")) || naturalH;
632
+ const transformScale = getApproxSvgTransformScale(image);
633
+ let targetW = Math.max(1, Math.round(Math.min(naturalW, Math.max(attrW, attrW * transformScale.x * sizing.density))));
634
+ let targetH = Math.max(1, Math.round(Math.min(naturalH, Math.max(attrH, attrH * transformScale.y * sizing.density))));
635
+ const longest = Math.max(targetW, targetH);
636
+ if (longest > sizing.maxEdge) {
637
+ const scale = sizing.maxEdge / longest;
638
+ targetW = Math.max(1, Math.round(targetW * scale));
639
+ targetH = Math.max(1, Math.round(targetH * scale));
640
+ }
641
+ const hasAlpha = !/^data:image\/jpe?g[;,]/i.test(href) && decodedImageHasAlpha(loaded, naturalW, naturalH);
642
+ const canvas = document.createElement("canvas");
643
+ canvas.width = targetW;
644
+ canvas.height = targetH;
645
+ const ctx = canvas.getContext("2d");
646
+ if (!ctx) continue;
647
+ if (hasAlpha) {
648
+ ctx.clearRect(0, 0, targetW, targetH);
649
+ } else {
650
+ ctx.fillStyle = "#ffffff";
651
+ ctx.fillRect(0, 0, targetW, targetH);
652
+ }
653
+ ctx.imageSmoothingEnabled = true;
654
+ try {
655
+ ctx.imageSmoothingQuality = "high";
656
+ } catch {
657
+ }
658
+ ctx.drawImage(loaded, 0, 0, targetW, targetH);
659
+ const next = hasAlpha ? canvas.toDataURL("image/png") : canvas.toDataURL("image/jpeg", jpegQuality);
660
+ image.setAttribute("href", next);
661
+ if (image.hasAttribute("xlink:href")) image.setAttribute("xlink:href", next);
662
+ image.setAttribute("data-pixldocs-compressed-pdf-asset", hasAlpha ? "PNG" : "JPEG");
663
+ rewritten++;
664
+ }
665
+ if (rewritten > 0) {
666
+ console.log(`[client-pdf-export] compressed ${rewritten} live SVG raster image(s) @ q=${jpegQuality}`);
667
+ }
668
+ }
539
669
  async function rewriteSafariInlineJpegImagesToPng(svg) {
540
670
  if (!isSafariLikeForInlineJpegSvgPdf() || typeof document === "undefined") return;
541
671
  const jpegImages = Array.from(svg.querySelectorAll("image")).filter((image) => /^data:image\/jpe?g[;,]/i.test(getSvgImageHref(image)));
@@ -581,8 +711,7 @@ async function rewriteSafariInlineJpegImagesToPng(svg) {
581
711
  const transformScale = getApproxSvgTransformScale(image);
582
712
  const renderedW = attrW * transformScale.x;
583
713
  const renderedH = attrH * transformScale.y;
584
- const density = 2;
585
- const maxEdge = 1800;
714
+ const { density, maxEdge } = getCompressedRasterSizing(getPdfJpegQuality());
586
715
  targetW = Math.max(1, Math.round(Math.min(attrW, renderedW * density || attrW)));
587
716
  targetH = Math.max(1, Math.round(Math.min(attrH, renderedH * density || attrH)));
588
717
  const longest = Math.max(targetW, targetH);
@@ -592,7 +721,6 @@ async function rewriteSafariInlineJpegImagesToPng(svg) {
592
721
  targetH = Math.max(1, Math.round(targetH * scale));
593
722
  }
594
723
  }
595
- const outputAsPng = __pdfCompressImages;
596
724
  let pngDataUrl = null;
597
725
  for (const shrink of __pdfCompressImages ? [1, 0.82, 0.68, 0.55] : [1]) {
598
726
  const canvas = document.createElement("canvas");
@@ -603,7 +731,7 @@ async function rewriteSafariInlineJpegImagesToPng(svg) {
603
731
  ctx.fillStyle = "#ffffff";
604
732
  ctx.fillRect(0, 0, canvas.width, canvas.height);
605
733
  ctx.drawImage(loaded, 0, 0, canvas.width, canvas.height);
606
- pngDataUrl = outputAsPng ? canvas.toDataURL("image/png") : canvas.toDataURL("image/jpeg", 1);
734
+ pngDataUrl = canvas.toDataURL("image/jpeg", __pdfCompressImages ? getPdfJpegQuality() : 1);
607
735
  if (!__pdfCompressImages || estimateDataUrlBytesForPdf(pngDataUrl) <= 4e6 || shrink === 0.55) break;
608
736
  }
609
737
  if (blobUrl) URL.revokeObjectURL(blobUrl);
@@ -648,8 +776,7 @@ async function rewriteUnsupportedRasterImagesForSvgPdf(svg) {
648
776
  const transformScale = getApproxSvgTransformScale(image);
649
777
  const renderedW = attrW * transformScale.x;
650
778
  const renderedH = attrH * transformScale.y;
651
- const density = 2;
652
- const maxEdge = 2200;
779
+ const { density, maxEdge } = getCompressedRasterSizing(getPdfJpegQuality());
653
780
  targetW = Math.max(1, Math.round(Math.min(bitmap.width || attrW, renderedW * density || attrW)));
654
781
  targetH = Math.max(1, Math.round(Math.min(bitmap.height || attrH, renderedH * density || attrH)));
655
782
  const longest = Math.max(targetW, targetH);
@@ -692,7 +819,7 @@ async function rewriteUnsupportedRasterImagesForSvgPdf(svg) {
692
819
  } catch {
693
820
  }
694
821
  ctx.drawImage(bitmap, 0, 0, targetW, targetH);
695
- const jpegQuality = __pdfCompressImages ? Math.max(__pdfJpegQuality, 0.6) : 1;
822
+ const jpegQuality = __pdfCompressImages ? getPdfJpegQuality() : 1;
696
823
  const safeDataUrl = hasAlpha ? canvas.toDataURL("image/png") : canvas.toDataURL("image/jpeg", jpegQuality);
697
824
  image.setAttribute("href", safeDataUrl);
698
825
  if (image.hasAttribute("xlink:href")) image.setAttribute("xlink:href", safeDataUrl);
@@ -844,6 +971,28 @@ function isInSvgDefinitionSubtree(el) {
844
971
  }
845
972
  return false;
846
973
  }
974
+ async function encodePdfRasterDataUrl(dataUrl, options) {
975
+ if (!__pdfCompressImages || !(options == null ? void 0 : options.allowJpeg)) return { data: dataUrl, format: "PNG" };
976
+ const loaded = await decodeImageForPdf(dataUrl);
977
+ if (!loaded) return { data: dataUrl, format: "PNG" };
978
+ const w = Math.max(1, Math.round(loaded.naturalWidth || loaded.width || 1));
979
+ const h = Math.max(1, Math.round(loaded.naturalHeight || loaded.height || 1));
980
+ if (decodedImageHasAlpha(loaded, w, h)) return { data: dataUrl, format: "PNG" };
981
+ const canvas = document.createElement("canvas");
982
+ canvas.width = w;
983
+ canvas.height = h;
984
+ const ctx = canvas.getContext("2d");
985
+ if (!ctx) return { data: dataUrl, format: "PNG" };
986
+ ctx.fillStyle = "#ffffff";
987
+ ctx.fillRect(0, 0, w, h);
988
+ ctx.imageSmoothingEnabled = true;
989
+ try {
990
+ ctx.imageSmoothingQuality = "high";
991
+ } catch {
992
+ }
993
+ ctx.drawImage(loaded, 0, 0, w, h);
994
+ return { data: canvas.toDataURL("image/jpeg", getPdfJpegQuality()), format: "JPEG" };
995
+ }
847
996
  function parseInlineSvgStyleDeclarations(styleText) {
848
997
  return styleText.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
849
998
  const idx = part.indexOf(":");
@@ -2195,6 +2344,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
2195
2344
  }
2196
2345
  sanitizeSvgTreeForPdf(svgToDraw);
2197
2346
  await replaceLiveSvgRasterImagesWithOriginalAssets(svgToDraw, options == null ? void 0 : options.originalRasterImagesById);
2347
+ await rewriteCompressedLiveSvgRasterImages(svgToDraw);
2198
2348
  await rewriteUnsupportedRasterImagesForSvgPdf(svgToDraw);
2199
2349
  await rewriteSafariInlineJpegImagesToPng(svgToDraw);
2200
2350
  try {
@@ -2820,7 +2970,7 @@ async function fetchSvgAsElement(imageUrl, colorMap) {
2820
2970
  async function getRecoloredSvgDataUrl(imageUrl, colorMap) {
2821
2971
  if (!colorMap || Object.keys(colorMap).length === 0) return null;
2822
2972
  try {
2823
- const { getNormalizedSvgUrl } = await Promise.resolve().then(() => require("./index-DQF_on2h.cjs")).then((n) => n.canvasImageLoader);
2973
+ const { getNormalizedSvgUrl } = await Promise.resolve().then(() => require("./index-d8U2oDzI.cjs")).then((n) => n.canvasImageLoader);
2824
2974
  return await getNormalizedSvgUrl(imageUrl, colorMap);
2825
2975
  } catch {
2826
2976
  return null;
@@ -3516,7 +3666,7 @@ function __setPdfJpegQuality(v) {
3516
3666
  if (typeof v === "number" && isFinite(v) && v > 0 && v <= 1) __pdfJpegQuality = v;
3517
3667
  }
3518
3668
  function pdfRasterCompression() {
3519
- return __pdfCompressImages ? "SLOW" : "NONE";
3669
+ return __pdfCompressImages ? "SLOW" : "FAST";
3520
3670
  }
3521
3671
  function normalizeBgColor(bg) {
3522
3672
  if (!bg || bg === "transparent" || bg === "none") return "#ffffff";
@@ -3566,9 +3716,11 @@ function getTargetPixelSize(bitmap, opts) {
3566
3716
  if (!__pdfCompressImages && !(opts.resolutionMultiplier && opts.resolutionMultiplier > 1)) {
3567
3717
  return { w: bitmap.width, h: bitmap.height };
3568
3718
  }
3569
- const dpiScale = 1.5 * (opts.resolutionMultiplier ?? 1);
3719
+ const quality = getPdfJpegQuality();
3720
+ const compressedSizing = getCompressedRasterSizing(quality);
3721
+ const dpiScale = (__pdfCompressImages ? compressedSizing.density : 1.5) * (opts.resolutionMultiplier ?? 1);
3570
3722
  const hasMultiplier = !!(opts.resolutionMultiplier && opts.resolutionMultiplier > 1);
3571
- const maxPx = !__pdfCompressImages ? 1e4 : hasMultiplier ? 4400 : 2200;
3723
+ const maxPx = !__pdfCompressImages ? 1e4 : hasMultiplier ? Math.max(3200, compressedSizing.maxEdge * 1.7) : compressedSizing.maxEdge;
3572
3724
  const capByBitmap = !(opts.resolutionMultiplier && opts.resolutionMultiplier > 1);
3573
3725
  const desiredW = Math.max(1, Math.round((opts.targetWidthPt ?? bitmap.width) * dpiScale));
3574
3726
  const desiredH = Math.max(1, Math.round((opts.targetHeightPt ?? bitmap.height) * dpiScale));
@@ -3598,7 +3750,7 @@ async function rasterizeToDataUrl(blob, opts, out, flattenBackground) {
3598
3750
  }
3599
3751
  ctx.drawImage(bitmap, 0, 0, w, h);
3600
3752
  if (out === "JPEG") {
3601
- const quality = __pdfCompressImages ? __pdfJpegQuality : 1;
3753
+ const quality = __pdfCompressImages ? getPdfJpegQuality() : 1;
3602
3754
  return canvas.toDataURL("image/jpeg", quality);
3603
3755
  }
3604
3756
  return canvas.toDataURL("image/png");
@@ -3627,7 +3779,7 @@ async function fetchImageAsBase64(imageUrl, opts = {}) {
3627
3779
  }
3628
3780
  let fetchUrl = imageUrl;
3629
3781
  if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
3630
- const { isPrivateUrl } = await Promise.resolve().then(() => require("./index-DQF_on2h.cjs")).then((n) => n.canvasImageLoader);
3782
+ const { isPrivateUrl } = await Promise.resolve().then(() => require("./index-d8U2oDzI.cjs")).then((n) => n.canvasImageLoader);
3631
3783
  if (isPrivateUrl(imageUrl)) return null;
3632
3784
  const proxyUrl = new URL(`${index.API_URL}/image-proxy`);
3633
3785
  proxyUrl.searchParams.set("url", imageUrl);
@@ -3856,6 +4008,7 @@ async function rasterizeCropGroupToPng(liveGroup, frameW, frameH, multiplier = 2
3856
4008
  }
3857
4009
  }
3858
4010
  if (htmlImg) {
4011
+ await ensureImageDecoded(htmlImg);
3859
4012
  const iw = htmlImg.naturalWidth || useNatW;
3860
4013
  const ih = htmlImg.naturalHeight || useNatH;
3861
4014
  const baseScale = Math.max(frameW / iw, frameH / ih);
@@ -3873,39 +4026,42 @@ async function rasterizeCropGroupToPng(liveGroup, frameW, frameH, multiplier = 2
3873
4026
  const srcW = Math.min(Math.ceil(frameW / finalScale), iw - srcX);
3874
4027
  const srcH = Math.min(Math.ceil(frameH / finalScale), ih - srcY);
3875
4028
  if (srcW >= 1 && srcH >= 1) {
4029
+ const safeCrop = getSafeCanvasDims(srcW, srcH);
3876
4030
  const cropCanvas = document.createElement("canvas");
3877
- cropCanvas.width = srcW;
3878
- cropCanvas.height = srcH;
4031
+ cropCanvas.width = safeCrop.width;
4032
+ cropCanvas.height = safeCrop.height;
3879
4033
  const ctx = cropCanvas.getContext("2d");
3880
4034
  if (ctx) {
4035
+ const dstW = safeCrop.width;
4036
+ const dstH = safeCrop.height;
3881
4037
  if (bgFill) {
3882
4038
  ctx.fillStyle = bgFill;
3883
- ctx.fillRect(0, 0, srcW, srcH);
4039
+ ctx.fillRect(0, 0, dstW, dstH);
3884
4040
  } else {
3885
- ctx.clearRect(0, 0, srcW, srcH);
4041
+ ctx.clearRect(0, 0, dstW, dstH);
3886
4042
  }
3887
- ctx.drawImage(htmlImg, srcX, srcY, srcW, srcH, 0, 0, srcW, srcH);
4043
+ ctx.drawImage(htmlImg, srcX, srcY, srcW, srcH, 0, 0, dstW, dstH);
3888
4044
  if (shape === "circle" || shape === "roundRect" || shape === "rect") {
3889
4045
  ctx.globalCompositeOperation = "destination-in";
3890
4046
  if (shape === "circle") {
3891
4047
  ctx.beginPath();
3892
- ctx.ellipse(srcW / 2, srcH / 2, srcW / 2, srcH / 2, 0, 0, 2 * Math.PI);
4048
+ ctx.ellipse(dstW / 2, dstH / 2, dstW / 2, dstH / 2, 0, 0, 2 * Math.PI);
3893
4049
  ctx.fill();
3894
4050
  } else {
3895
4051
  const rx2 = Math.min(
3896
- (rxRatio > 0.5 ? rxRatio : rxRatio * minFrameDim) * (srcW / frameW),
3897
- srcW / 2,
3898
- srcH / 2
4052
+ (rxRatio > 0.5 ? rxRatio : rxRatio * minFrameDim) * (dstW / frameW),
4053
+ dstW / 2,
4054
+ dstH / 2
3899
4055
  );
3900
4056
  const r = Math.max(0, rx2);
3901
4057
  ctx.beginPath();
3902
4058
  ctx.moveTo(r, 0);
3903
- ctx.lineTo(srcW - r, 0);
3904
- if (r > 0) ctx.quadraticCurveTo(srcW, 0, srcW, r);
3905
- ctx.lineTo(srcW, srcH - r);
3906
- if (r > 0) ctx.quadraticCurveTo(srcW, srcH, srcW - r, srcH);
3907
- ctx.lineTo(r, srcH);
3908
- if (r > 0) ctx.quadraticCurveTo(0, srcH, 0, srcH - r);
4059
+ ctx.lineTo(dstW - r, 0);
4060
+ if (r > 0) ctx.quadraticCurveTo(dstW, 0, dstW, r);
4061
+ ctx.lineTo(dstW, dstH - r);
4062
+ if (r > 0) ctx.quadraticCurveTo(dstW, dstH, dstW - r, dstH);
4063
+ ctx.lineTo(r, dstH);
4064
+ if (r > 0) ctx.quadraticCurveTo(0, dstH, 0, dstH - r);
3909
4065
  ctx.lineTo(0, r);
3910
4066
  if (r > 0) ctx.quadraticCurveTo(0, 0, r, 0);
3911
4067
  ctx.closePath();
@@ -3924,8 +4080,11 @@ async function rasterizeCropGroupToPng(liveGroup, frameW, frameH, multiplier = 2
3924
4080
  const natH = (dims == null ? void 0 : dims.height) ?? (stored == null ? void 0 : stored.naturalHeight) ?? frameH;
3925
4081
  const useNatW = natW > 0 && natH > 0 ? natW : frameW;
3926
4082
  const useNatH = natW > 0 && natH > 0 ? natH : frameH;
3927
- const outW2 = maintainResolution && useNatW > frameW ? Math.max(1, Math.round(useNatW)) : Math.max(1, Math.round(frameW * multiplier));
3928
- const outH2 = maintainResolution && useNatH > frameH ? Math.max(1, Math.round(useNatH)) : Math.max(1, Math.round(frameH * multiplier));
4083
+ const reqOutW = maintainResolution && useNatW > frameW ? Math.max(1, Math.round(useNatW)) : Math.max(1, Math.round(frameW * multiplier));
4084
+ const reqOutH = maintainResolution && useNatH > frameH ? Math.max(1, Math.round(useNatH)) : Math.max(1, Math.round(frameH * multiplier));
4085
+ const safeOut = getSafeCanvasDims(reqOutW, reqOutH);
4086
+ const outW2 = safeOut.width;
4087
+ const outH2 = safeOut.height;
3929
4088
  const proxied = index.getProxiedImageUrl(imageUrl);
3930
4089
  const res = await fetch(proxied);
3931
4090
  if (!res.ok) throw new Error(`Export blob fetch failed: ${res.status}`);
@@ -4517,6 +4676,7 @@ async function drawElement(pdf, element, embeddedFonts, canvasWidth, canvasHeigh
4517
4676
  if (fadedMasked) {
4518
4677
  png = fadedMasked;
4519
4678
  }
4679
+ const encodedMasked = await encodePdfRasterDataUrl(png, { allowJpeg: !fadedMasked });
4520
4680
  if (canUseAdvancedTransforms2 && fabricMatrix && fabricMatrix.length === 6) {
4521
4681
  const mPdf = fabricMatrixToJsPdfAdvancedMatrix(fabricMatrix);
4522
4682
  const localX = -frameW / 2;
@@ -4524,13 +4684,13 @@ async function drawElement(pdf, element, embeddedFonts, canvasWidth, canvasHeigh
4524
4684
  anyPdf2.advancedAPI(() => {
4525
4685
  pdf.saveGraphicsState();
4526
4686
  anyPdf2.setCurrentTransformationMatrix(anyPdf2.Matrix(...mPdf));
4527
- pdf.addImage(png, "PNG", localX, localY, frameW, frameH, void 0, pdfRasterCompression());
4687
+ pdf.addImage(encodedMasked.data, encodedMasked.format, localX, localY, frameW, frameH, void 0, pdfRasterCompression());
4528
4688
  pdf.restoreGraphicsState();
4529
4689
  });
4530
4690
  pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
4531
4691
  return;
4532
4692
  }
4533
- pdf.addImage(png, "PNG", norm.x, norm.y, frameW, frameH, void 0, pdfRasterCompression());
4693
+ pdf.addImage(encodedMasked.data, encodedMasked.format, norm.x, norm.y, frameW, frameH, void 0, pdfRasterCompression());
4534
4694
  pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
4535
4695
  return;
4536
4696
  }
@@ -5037,8 +5197,8 @@ async function drawElementContentCore(pdf, element, norm, x, y, w, h, embeddedFo
5037
5197
  if (croppedPng) {
5038
5198
  try {
5039
5199
  const fadedCrop = await bakeEdgeFadeIntoDataUrl(croppedPng, element);
5040
- const finalCropPng = fadedCrop || croppedPng;
5041
- pdf.addImage(finalCropPng, "PNG", x, y, w, h, void 0, pdfRasterCompression());
5200
+ const encodedCrop = await encodePdfRasterDataUrl(fadedCrop || croppedPng, { allowJpeg: !fadedCrop });
5201
+ pdf.addImage(encodedCrop.data, encodedCrop.format, x, y, w, h, void 0, pdfRasterCompression());
5042
5202
  break;
5043
5203
  } catch {
5044
5204
  }
@@ -5246,10 +5406,21 @@ function getExpectedRasterImageIdsForPdfPage(page) {
5246
5406
  function getOriginalRasterImageUrlMapForPdfPage(page) {
5247
5407
  const byId = /* @__PURE__ */ new Map();
5248
5408
  const isSvgUrl = (url) => /\.svg(?:\?|#|$)/i.test(url) || /^data:image\/svg/i.test(url);
5409
+ const hasVisualImageEffect = (item) => {
5410
+ const hasCrop = item.cropPanX != null || item.cropPanY != null || item.cropZoom != null;
5411
+ const hasMask = item.maskSvgUrl || item.clipShape && item.clipShape !== "none" && item.clipShape !== "rectangle";
5412
+ const hasFade = ["Top", "Right", "Bottom", "Left"].some(
5413
+ (side) => item[`fade${side}Amount`] != null || item[`fade${side}Size`] != null || item[`fade${side}Hardness`] != null
5414
+ );
5415
+ const hasFrame = (item.strokeWidth ?? 0) > 0 || !!item.stroke || (item.rx ?? 0) > 0 || (item.ry ?? 0) > 0;
5416
+ const hasShadowOrFilter = item.shadow || item.shadowColor || item.shadowBlur || item.shadowOffsetX || item.shadowOffsetY || item.filter || item.filters;
5417
+ return Boolean(hasCrop || hasMask || hasFade || hasFrame || hasShadowOrFilter);
5418
+ };
5249
5419
  for (const item of page.elements) {
5250
5420
  if (isGroupBackgroundDrawable(item) || item.type !== "image" || item.visible === false) continue;
5251
5421
  const src = String(item.src || item.imageUrl || "").trim();
5252
5422
  if (!src || item.sourceFormat === "svg" || isSvgUrl(src)) continue;
5423
+ if (hasVisualImageEffect(item)) continue;
5253
5424
  byId.set(item.id, src);
5254
5425
  }
5255
5426
  return byId;
@@ -5295,7 +5466,10 @@ async function __exportMultiPagePdfInner(pages, options) {
5295
5466
  unit: "px",
5296
5467
  format: [firstPage.width, firstPage.height],
5297
5468
  hotfixes: ["px_scaling"],
5298
- compress: __pdfCompressImages
5469
+ // Always keep PDF object/stream compression on. The UI's image-compression
5470
+ // switch controls raster resampling/JPEG quality, not lossless PDF packing;
5471
+ // disabling this made "uncompressed image" PDFs balloon with no quality gain.
5472
+ compress: true
5299
5473
  });
5300
5474
  if (title) {
5301
5475
  pdf.setProperties({ title, creator: "DocuForge" });
@@ -5704,4 +5878,4 @@ exports.exportMultiPagePdf = exportMultiPagePdf;
5704
5878
  exports.logTextMeasurementDiagnostic = logTextMeasurementDiagnostic;
5705
5879
  exports.preparePagesForExport = preparePagesForExport;
5706
5880
  exports.rewriteSvgFontsForJsPDFWithSourceMeta = rewriteSvgFontsForJsPDFWithSourceMeta;
5707
- //# sourceMappingURL=vectorPdfExport-B04JIcJr.cjs.map
5881
+ //# sourceMappingURL=vectorPdfExport-QzySzFJJ.cjs.map