@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.
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const index = require("./index-DQF_on2h.cjs");
3
+ const index = require("./index-d8U2oDzI.cjs");
4
4
  exports.DEPLOYMENT_VERSION_MARKER = index.DEPLOYMENT_VERSION_MARKER;
5
5
  exports.FONT_FALLBACK_DEVANAGARI = index.FONT_FALLBACK_DEVANAGARI;
6
6
  exports.FONT_FALLBACK_MATH = index.FONT_FALLBACK_MATH;
package/dist/index.d.ts CHANGED
@@ -373,6 +373,10 @@ export declare interface PdfFromFormOptions {
373
373
  * Per-call override of `RendererConfig.maxImageEdgePx`. See that field.
374
374
  */
375
375
  maxImageEdgePx?: number;
376
+ /** Preserve original raster bytes when false; use bounded JPEG/PNG optimization when true. */
377
+ compressImages?: boolean;
378
+ /** JPEG quality (0..1) used only when compressImages is true. */
379
+ compressionQuality?: number;
376
380
  }
377
381
 
378
382
  export declare interface PdfRenderResult {
@@ -540,6 +544,8 @@ export declare class PixldocsRenderer {
540
544
  textMode?: 'auto' | 'selectable' | 'pixel-perfect';
541
545
  forcePerElementPdf?: boolean | 'auto';
542
546
  maxImageEdgePx?: number;
547
+ compressImages?: boolean;
548
+ compressionQuality?: number;
543
549
  }): Promise<PdfRenderResult>;
544
550
  /**
545
551
  * Resolve from V2 sectionState and render a vector PDF.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D, F, o, q, s, P, t, u, v, w, x, y, z, B, C, E, G, H, I, J, K, L, M, b, N, O, Q, R, S, U, V, W, X, Y, Z, _, $, a0, a1, a2, a3, a4, a5 } from "./index-DOIdbh-Q.js";
1
+ import { D, F, o, q, s, P, t, u, v, w, x, y, z, B, C, E, G, H, I, J, K, L, M, b, N, O, Q, R, S, U, V, W, X, Y, Z, _, $, a0, a1, a2, a3, a4, a5 } from "./index-C0tTvSFQ.js";
2
2
  export {
3
3
  D as DEPLOYMENT_VERSION_MARKER,
4
4
  F as FONT_FALLBACK_DEVANAGARI,
@@ -1,7 +1,7 @@
1
1
  import { jsPDF, ShadingPattern } from "jspdf";
2
2
  import { svg2pdf } from "svg2pdf.js";
3
3
  import * as fabric from "fabric";
4
- import { p as parseTextMarkdown, r as renderSmartElementToSvg, g as getCanvasForPage, c as captureFabricCanvasSvgForPdf, f as findNodeById, a as getAbsoluteBounds, b as getProxiedImageUrl, d as getImageProxyFetchOptions, A as API_URL, n as normalizeShapeType, i as isElement, e as isGroup, h as buildRoundedTrianglePath, j as hasEdgeFade, k as bakeEdgeFade, l as getRoundedRectRadii, T as TRIANGLE_STROKE_MITER_LIMIT, m as getTrianglePoints } from "./index-DOIdbh-Q.js";
4
+ import { p as parseTextMarkdown, r as renderSmartElementToSvg, g as getCanvasForPage, c as captureFabricCanvasSvgForPdf, f as findNodeById, a as getAbsoluteBounds, b as getProxiedImageUrl, d as getImageProxyFetchOptions, A as API_URL, n as normalizeShapeType, i as isElement, e as isGroup, h as buildRoundedTrianglePath, j as hasEdgeFade, k as bakeEdgeFade, l as getRoundedRectRadii, T as TRIANGLE_STROKE_MITER_LIMIT, m as getTrianglePoints } from "./index-C0tTvSFQ.js";
5
5
  import { resetPdfFontRegistry, FONT_FALLBACK_SYMBOLS, FONT_FALLBACK_MATH, FONT_FALLBACK_DEVANAGARI, embedFontWithGoogleFallback, getEmbeddedVariantsList, isFontAvailable, isFamilyEmbedded, resolveBestRegisteredVariant, getEmbeddedJsPDFFontName, resolveFontWeight, doesVariantSupportChar } from "./pdfFonts-DhEaMTZl.js";
6
6
  async function embedFontsForSvg(pdf, svgStr) {
7
7
  var _a;
@@ -124,6 +124,32 @@ function elementHasFade(element) {
124
124
  const yieldToUI = () => new Promise((resolve) => {
125
125
  requestAnimationFrame(() => setTimeout(resolve, 0));
126
126
  });
127
+ const IS_SAFARI = typeof navigator !== "undefined" && /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent || "");
128
+ const IS_IOS = typeof navigator !== "undefined" && /iPad|iPhone|iPod/.test(navigator.userAgent || "");
129
+ const SAFARI_MAX_SIDE = IS_IOS ? 4096 : 16384;
130
+ const SAFARI_MAX_AREA = IS_IOS ? 16777216 : 268435456;
131
+ function getSafeCanvasDims(w, h) {
132
+ const width = Math.max(1, Math.round(w));
133
+ const height = Math.max(1, Math.round(h));
134
+ if (!IS_SAFARI) return { width, height, scale: 1 };
135
+ const sideScale = Math.min(1, SAFARI_MAX_SIDE / Math.max(width, height));
136
+ const areaScale = Math.min(1, Math.sqrt(SAFARI_MAX_AREA / (width * height)));
137
+ const scale = Math.min(sideScale, areaScale);
138
+ if (scale >= 1) return { width, height, scale: 1 };
139
+ return {
140
+ width: Math.max(1, Math.floor(width * scale)),
141
+ height: Math.max(1, Math.floor(height * scale)),
142
+ scale
143
+ };
144
+ }
145
+ async function ensureImageDecoded(img) {
146
+ try {
147
+ if (typeof img.decode === "function") {
148
+ await img.decode();
149
+ }
150
+ } catch {
151
+ }
152
+ }
127
153
  const SHADOW_RASTER_ALPHA_COMPENSATION = 0.84;
128
154
  function collectFontSpecsFromShadowMarkup(markup) {
129
155
  const specs = /* @__PURE__ */ new Set();
@@ -314,6 +340,7 @@ async function rasterizeStoredCropImageToPng(options) {
314
340
  }
315
341
  });
316
342
  if (!img) return null;
343
+ await ensureImageDecoded(img);
317
344
  const natW = Math.max(1, naturalWidth || img.naturalWidth || frameW);
318
345
  const natH = Math.max(1, naturalHeight || img.naturalHeight || frameH);
319
346
  const baseScale = Math.max(frameW / natW, frameH / natH);
@@ -328,8 +355,11 @@ async function rasterizeStoredCropImageToPng(options) {
328
355
  const offsetY = overflowY > 0 ? -overflowY * (clampedPanY - 0.5) : 0;
329
356
  const imgX = frameW / 2 + offsetX - drawW / 2;
330
357
  const imgY = frameH / 2 + offsetY - drawH / 2;
331
- const outW = maintainResolution && natW > frameW ? Math.max(1, Math.round(natW)) : Math.max(1, Math.round(frameW * 2));
332
- const outH = maintainResolution && natH > frameH ? Math.max(1, Math.round(natH)) : Math.max(1, Math.round(frameH * 2));
358
+ const requestedW = maintainResolution && natW > frameW ? Math.max(1, Math.round(natW)) : Math.max(1, Math.round(frameW * 2));
359
+ const requestedH = maintainResolution && natH > frameH ? Math.max(1, Math.round(natH)) : Math.max(1, Math.round(frameH * 2));
360
+ const safe = getSafeCanvasDims(requestedW, requestedH);
361
+ const outW = safe.width;
362
+ const outH = safe.height;
333
363
  const canvas = document.createElement("canvas");
334
364
  canvas.width = outW;
335
365
  canvas.height = outH;
@@ -491,6 +521,51 @@ function parseSvgNumber(value) {
491
521
  const n = Number(match[0]);
492
522
  return Number.isFinite(n) && n > 0 ? n : null;
493
523
  }
524
+ function getPdfJpegQuality() {
525
+ return Math.max(0.5, Math.min(0.95, Number(__pdfJpegQuality) || 0.82));
526
+ }
527
+ function getCompressedRasterSizing(quality) {
528
+ if (quality >= 0.9) return { density: 2.25, maxEdge: 2600 };
529
+ if (quality >= 0.8) return { density: 1.9, maxEdge: 2200 };
530
+ if (quality >= 0.65) return { density: 1.45, maxEdge: 1700 };
531
+ return { density: 1.15, maxEdge: 1300 };
532
+ }
533
+ async function decodeImageForPdf(src) {
534
+ return new Promise((resolve) => {
535
+ try {
536
+ const img = new Image();
537
+ if (!src.startsWith("data:") && !src.startsWith("blob:")) img.crossOrigin = "anonymous";
538
+ img.onload = async () => {
539
+ try {
540
+ if (typeof img.decode === "function") await img.decode();
541
+ } catch {
542
+ }
543
+ resolve((img.naturalWidth || img.width) && (img.naturalHeight || img.height) ? img : null);
544
+ };
545
+ img.onerror = () => resolve(null);
546
+ img.src = src;
547
+ } catch {
548
+ resolve(null);
549
+ }
550
+ });
551
+ }
552
+ function decodedImageHasAlpha(img, width, height) {
553
+ try {
554
+ const sample = document.createElement("canvas");
555
+ sample.width = Math.min(32, Math.max(1, width));
556
+ sample.height = Math.min(32, Math.max(1, height));
557
+ const ctx = sample.getContext("2d");
558
+ if (!ctx) return false;
559
+ ctx.clearRect(0, 0, sample.width, sample.height);
560
+ ctx.drawImage(img, 0, 0, sample.width, sample.height);
561
+ const pixels = ctx.getImageData(0, 0, sample.width, sample.height).data;
562
+ for (let i = 3; i < pixels.length; i += 4) {
563
+ if (pixels[i] < 255) return true;
564
+ }
565
+ } catch {
566
+ }
567
+ return false;
568
+ }
494
569
  function getApproxSvgTransformScale(image) {
495
570
  let sx = 1;
496
571
  let sy = 1;
@@ -517,6 +592,61 @@ function getApproxSvgTransformScale(image) {
517
592
  }
518
593
  return { x: Math.max(0.01, Math.abs(sx)), y: Math.max(0.01, Math.abs(sy)) };
519
594
  }
595
+ async function rewriteCompressedLiveSvgRasterImages(svg) {
596
+ if (!__pdfCompressImages || typeof document === "undefined") return;
597
+ const images = Array.from(svg.querySelectorAll("image")).filter((image) => {
598
+ const href = getSvgImageHref(image).trim();
599
+ return /^data:image\/(?:png|jpe?g|webp|avif)[;,]/i.test(href);
600
+ });
601
+ if (images.length === 0) return;
602
+ const jpegQuality = getPdfJpegQuality();
603
+ const sizing = getCompressedRasterSizing(jpegQuality);
604
+ let rewritten = 0;
605
+ for (const image of images) {
606
+ const href = getSvgImageHref(image).trim();
607
+ const loaded = await decodeImageForPdf(href);
608
+ if (!loaded) continue;
609
+ const naturalW = Math.max(1, Math.round(loaded.naturalWidth || loaded.width || 1));
610
+ const naturalH = Math.max(1, Math.round(loaded.naturalHeight || loaded.height || 1));
611
+ const attrW = parseSvgNumber(image.getAttribute("width")) || naturalW;
612
+ const attrH = parseSvgNumber(image.getAttribute("height")) || naturalH;
613
+ const transformScale = getApproxSvgTransformScale(image);
614
+ let targetW = Math.max(1, Math.round(Math.min(naturalW, Math.max(attrW, attrW * transformScale.x * sizing.density))));
615
+ let targetH = Math.max(1, Math.round(Math.min(naturalH, Math.max(attrH, attrH * transformScale.y * sizing.density))));
616
+ const longest = Math.max(targetW, targetH);
617
+ if (longest > sizing.maxEdge) {
618
+ const scale = sizing.maxEdge / longest;
619
+ targetW = Math.max(1, Math.round(targetW * scale));
620
+ targetH = Math.max(1, Math.round(targetH * scale));
621
+ }
622
+ const hasAlpha = !/^data:image\/jpe?g[;,]/i.test(href) && decodedImageHasAlpha(loaded, naturalW, naturalH);
623
+ const canvas = document.createElement("canvas");
624
+ canvas.width = targetW;
625
+ canvas.height = targetH;
626
+ const ctx = canvas.getContext("2d");
627
+ if (!ctx) continue;
628
+ if (hasAlpha) {
629
+ ctx.clearRect(0, 0, targetW, targetH);
630
+ } else {
631
+ ctx.fillStyle = "#ffffff";
632
+ ctx.fillRect(0, 0, targetW, targetH);
633
+ }
634
+ ctx.imageSmoothingEnabled = true;
635
+ try {
636
+ ctx.imageSmoothingQuality = "high";
637
+ } catch {
638
+ }
639
+ ctx.drawImage(loaded, 0, 0, targetW, targetH);
640
+ const next = hasAlpha ? canvas.toDataURL("image/png") : canvas.toDataURL("image/jpeg", jpegQuality);
641
+ image.setAttribute("href", next);
642
+ if (image.hasAttribute("xlink:href")) image.setAttribute("xlink:href", next);
643
+ image.setAttribute("data-pixldocs-compressed-pdf-asset", hasAlpha ? "PNG" : "JPEG");
644
+ rewritten++;
645
+ }
646
+ if (rewritten > 0) {
647
+ console.log(`[client-pdf-export] compressed ${rewritten} live SVG raster image(s) @ q=${jpegQuality}`);
648
+ }
649
+ }
520
650
  async function rewriteSafariInlineJpegImagesToPng(svg) {
521
651
  if (!isSafariLikeForInlineJpegSvgPdf() || typeof document === "undefined") return;
522
652
  const jpegImages = Array.from(svg.querySelectorAll("image")).filter((image) => /^data:image\/jpe?g[;,]/i.test(getSvgImageHref(image)));
@@ -562,8 +692,7 @@ async function rewriteSafariInlineJpegImagesToPng(svg) {
562
692
  const transformScale = getApproxSvgTransformScale(image);
563
693
  const renderedW = attrW * transformScale.x;
564
694
  const renderedH = attrH * transformScale.y;
565
- const density = 2;
566
- const maxEdge = 1800;
695
+ const { density, maxEdge } = getCompressedRasterSizing(getPdfJpegQuality());
567
696
  targetW = Math.max(1, Math.round(Math.min(attrW, renderedW * density || attrW)));
568
697
  targetH = Math.max(1, Math.round(Math.min(attrH, renderedH * density || attrH)));
569
698
  const longest = Math.max(targetW, targetH);
@@ -573,7 +702,6 @@ async function rewriteSafariInlineJpegImagesToPng(svg) {
573
702
  targetH = Math.max(1, Math.round(targetH * scale));
574
703
  }
575
704
  }
576
- const outputAsPng = __pdfCompressImages;
577
705
  let pngDataUrl = null;
578
706
  for (const shrink of __pdfCompressImages ? [1, 0.82, 0.68, 0.55] : [1]) {
579
707
  const canvas = document.createElement("canvas");
@@ -584,7 +712,7 @@ async function rewriteSafariInlineJpegImagesToPng(svg) {
584
712
  ctx.fillStyle = "#ffffff";
585
713
  ctx.fillRect(0, 0, canvas.width, canvas.height);
586
714
  ctx.drawImage(loaded, 0, 0, canvas.width, canvas.height);
587
- pngDataUrl = outputAsPng ? canvas.toDataURL("image/png") : canvas.toDataURL("image/jpeg", 1);
715
+ pngDataUrl = canvas.toDataURL("image/jpeg", __pdfCompressImages ? getPdfJpegQuality() : 1);
588
716
  if (!__pdfCompressImages || estimateDataUrlBytesForPdf(pngDataUrl) <= 4e6 || shrink === 0.55) break;
589
717
  }
590
718
  if (blobUrl) URL.revokeObjectURL(blobUrl);
@@ -629,8 +757,7 @@ async function rewriteUnsupportedRasterImagesForSvgPdf(svg) {
629
757
  const transformScale = getApproxSvgTransformScale(image);
630
758
  const renderedW = attrW * transformScale.x;
631
759
  const renderedH = attrH * transformScale.y;
632
- const density = 2;
633
- const maxEdge = 2200;
760
+ const { density, maxEdge } = getCompressedRasterSizing(getPdfJpegQuality());
634
761
  targetW = Math.max(1, Math.round(Math.min(bitmap.width || attrW, renderedW * density || attrW)));
635
762
  targetH = Math.max(1, Math.round(Math.min(bitmap.height || attrH, renderedH * density || attrH)));
636
763
  const longest = Math.max(targetW, targetH);
@@ -673,7 +800,7 @@ async function rewriteUnsupportedRasterImagesForSvgPdf(svg) {
673
800
  } catch {
674
801
  }
675
802
  ctx.drawImage(bitmap, 0, 0, targetW, targetH);
676
- const jpegQuality = __pdfCompressImages ? Math.max(__pdfJpegQuality, 0.6) : 1;
803
+ const jpegQuality = __pdfCompressImages ? getPdfJpegQuality() : 1;
677
804
  const safeDataUrl = hasAlpha ? canvas.toDataURL("image/png") : canvas.toDataURL("image/jpeg", jpegQuality);
678
805
  image.setAttribute("href", safeDataUrl);
679
806
  if (image.hasAttribute("xlink:href")) image.setAttribute("xlink:href", safeDataUrl);
@@ -825,6 +952,28 @@ function isInSvgDefinitionSubtree(el) {
825
952
  }
826
953
  return false;
827
954
  }
955
+ async function encodePdfRasterDataUrl(dataUrl, options) {
956
+ if (!__pdfCompressImages || !(options == null ? void 0 : options.allowJpeg)) return { data: dataUrl, format: "PNG" };
957
+ const loaded = await decodeImageForPdf(dataUrl);
958
+ if (!loaded) return { data: dataUrl, format: "PNG" };
959
+ const w = Math.max(1, Math.round(loaded.naturalWidth || loaded.width || 1));
960
+ const h = Math.max(1, Math.round(loaded.naturalHeight || loaded.height || 1));
961
+ if (decodedImageHasAlpha(loaded, w, h)) return { data: dataUrl, format: "PNG" };
962
+ const canvas = document.createElement("canvas");
963
+ canvas.width = w;
964
+ canvas.height = h;
965
+ const ctx = canvas.getContext("2d");
966
+ if (!ctx) return { data: dataUrl, format: "PNG" };
967
+ ctx.fillStyle = "#ffffff";
968
+ ctx.fillRect(0, 0, w, h);
969
+ ctx.imageSmoothingEnabled = true;
970
+ try {
971
+ ctx.imageSmoothingQuality = "high";
972
+ } catch {
973
+ }
974
+ ctx.drawImage(loaded, 0, 0, w, h);
975
+ return { data: canvas.toDataURL("image/jpeg", getPdfJpegQuality()), format: "JPEG" };
976
+ }
828
977
  function parseInlineSvgStyleDeclarations(styleText) {
829
978
  return styleText.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
830
979
  const idx = part.indexOf(":");
@@ -2176,6 +2325,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
2176
2325
  }
2177
2326
  sanitizeSvgTreeForPdf(svgToDraw);
2178
2327
  await replaceLiveSvgRasterImagesWithOriginalAssets(svgToDraw, options == null ? void 0 : options.originalRasterImagesById);
2328
+ await rewriteCompressedLiveSvgRasterImages(svgToDraw);
2179
2329
  await rewriteUnsupportedRasterImagesForSvgPdf(svgToDraw);
2180
2330
  await rewriteSafariInlineJpegImagesToPng(svgToDraw);
2181
2331
  try {
@@ -2801,7 +2951,7 @@ async function fetchSvgAsElement(imageUrl, colorMap) {
2801
2951
  async function getRecoloredSvgDataUrl(imageUrl, colorMap) {
2802
2952
  if (!colorMap || Object.keys(colorMap).length === 0) return null;
2803
2953
  try {
2804
- const { getNormalizedSvgUrl } = await import("./index-DOIdbh-Q.js").then((n) => n.a6);
2954
+ const { getNormalizedSvgUrl } = await import("./index-C0tTvSFQ.js").then((n) => n.a6);
2805
2955
  return await getNormalizedSvgUrl(imageUrl, colorMap);
2806
2956
  } catch {
2807
2957
  return null;
@@ -3497,7 +3647,7 @@ function __setPdfJpegQuality(v) {
3497
3647
  if (typeof v === "number" && isFinite(v) && v > 0 && v <= 1) __pdfJpegQuality = v;
3498
3648
  }
3499
3649
  function pdfRasterCompression() {
3500
- return __pdfCompressImages ? "SLOW" : "NONE";
3650
+ return __pdfCompressImages ? "SLOW" : "FAST";
3501
3651
  }
3502
3652
  function normalizeBgColor(bg) {
3503
3653
  if (!bg || bg === "transparent" || bg === "none") return "#ffffff";
@@ -3547,9 +3697,11 @@ function getTargetPixelSize(bitmap, opts) {
3547
3697
  if (!__pdfCompressImages && !(opts.resolutionMultiplier && opts.resolutionMultiplier > 1)) {
3548
3698
  return { w: bitmap.width, h: bitmap.height };
3549
3699
  }
3550
- const dpiScale = 1.5 * (opts.resolutionMultiplier ?? 1);
3700
+ const quality = getPdfJpegQuality();
3701
+ const compressedSizing = getCompressedRasterSizing(quality);
3702
+ const dpiScale = (__pdfCompressImages ? compressedSizing.density : 1.5) * (opts.resolutionMultiplier ?? 1);
3551
3703
  const hasMultiplier = !!(opts.resolutionMultiplier && opts.resolutionMultiplier > 1);
3552
- const maxPx = !__pdfCompressImages ? 1e4 : hasMultiplier ? 4400 : 2200;
3704
+ const maxPx = !__pdfCompressImages ? 1e4 : hasMultiplier ? Math.max(3200, compressedSizing.maxEdge * 1.7) : compressedSizing.maxEdge;
3553
3705
  const capByBitmap = !(opts.resolutionMultiplier && opts.resolutionMultiplier > 1);
3554
3706
  const desiredW = Math.max(1, Math.round((opts.targetWidthPt ?? bitmap.width) * dpiScale));
3555
3707
  const desiredH = Math.max(1, Math.round((opts.targetHeightPt ?? bitmap.height) * dpiScale));
@@ -3579,7 +3731,7 @@ async function rasterizeToDataUrl(blob, opts, out, flattenBackground) {
3579
3731
  }
3580
3732
  ctx.drawImage(bitmap, 0, 0, w, h);
3581
3733
  if (out === "JPEG") {
3582
- const quality = __pdfCompressImages ? __pdfJpegQuality : 1;
3734
+ const quality = __pdfCompressImages ? getPdfJpegQuality() : 1;
3583
3735
  return canvas.toDataURL("image/jpeg", quality);
3584
3736
  }
3585
3737
  return canvas.toDataURL("image/png");
@@ -3608,7 +3760,7 @@ async function fetchImageAsBase64(imageUrl, opts = {}) {
3608
3760
  }
3609
3761
  let fetchUrl = imageUrl;
3610
3762
  if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
3611
- const { isPrivateUrl } = await import("./index-DOIdbh-Q.js").then((n) => n.a6);
3763
+ const { isPrivateUrl } = await import("./index-C0tTvSFQ.js").then((n) => n.a6);
3612
3764
  if (isPrivateUrl(imageUrl)) return null;
3613
3765
  const proxyUrl = new URL(`${API_URL}/image-proxy`);
3614
3766
  proxyUrl.searchParams.set("url", imageUrl);
@@ -3837,6 +3989,7 @@ async function rasterizeCropGroupToPng(liveGroup, frameW, frameH, multiplier = 2
3837
3989
  }
3838
3990
  }
3839
3991
  if (htmlImg) {
3992
+ await ensureImageDecoded(htmlImg);
3840
3993
  const iw = htmlImg.naturalWidth || useNatW;
3841
3994
  const ih = htmlImg.naturalHeight || useNatH;
3842
3995
  const baseScale = Math.max(frameW / iw, frameH / ih);
@@ -3854,39 +4007,42 @@ async function rasterizeCropGroupToPng(liveGroup, frameW, frameH, multiplier = 2
3854
4007
  const srcW = Math.min(Math.ceil(frameW / finalScale), iw - srcX);
3855
4008
  const srcH = Math.min(Math.ceil(frameH / finalScale), ih - srcY);
3856
4009
  if (srcW >= 1 && srcH >= 1) {
4010
+ const safeCrop = getSafeCanvasDims(srcW, srcH);
3857
4011
  const cropCanvas = document.createElement("canvas");
3858
- cropCanvas.width = srcW;
3859
- cropCanvas.height = srcH;
4012
+ cropCanvas.width = safeCrop.width;
4013
+ cropCanvas.height = safeCrop.height;
3860
4014
  const ctx = cropCanvas.getContext("2d");
3861
4015
  if (ctx) {
4016
+ const dstW = safeCrop.width;
4017
+ const dstH = safeCrop.height;
3862
4018
  if (bgFill) {
3863
4019
  ctx.fillStyle = bgFill;
3864
- ctx.fillRect(0, 0, srcW, srcH);
4020
+ ctx.fillRect(0, 0, dstW, dstH);
3865
4021
  } else {
3866
- ctx.clearRect(0, 0, srcW, srcH);
4022
+ ctx.clearRect(0, 0, dstW, dstH);
3867
4023
  }
3868
- ctx.drawImage(htmlImg, srcX, srcY, srcW, srcH, 0, 0, srcW, srcH);
4024
+ ctx.drawImage(htmlImg, srcX, srcY, srcW, srcH, 0, 0, dstW, dstH);
3869
4025
  if (shape === "circle" || shape === "roundRect" || shape === "rect") {
3870
4026
  ctx.globalCompositeOperation = "destination-in";
3871
4027
  if (shape === "circle") {
3872
4028
  ctx.beginPath();
3873
- ctx.ellipse(srcW / 2, srcH / 2, srcW / 2, srcH / 2, 0, 0, 2 * Math.PI);
4029
+ ctx.ellipse(dstW / 2, dstH / 2, dstW / 2, dstH / 2, 0, 0, 2 * Math.PI);
3874
4030
  ctx.fill();
3875
4031
  } else {
3876
4032
  const rx2 = Math.min(
3877
- (rxRatio > 0.5 ? rxRatio : rxRatio * minFrameDim) * (srcW / frameW),
3878
- srcW / 2,
3879
- srcH / 2
4033
+ (rxRatio > 0.5 ? rxRatio : rxRatio * minFrameDim) * (dstW / frameW),
4034
+ dstW / 2,
4035
+ dstH / 2
3880
4036
  );
3881
4037
  const r = Math.max(0, rx2);
3882
4038
  ctx.beginPath();
3883
4039
  ctx.moveTo(r, 0);
3884
- ctx.lineTo(srcW - r, 0);
3885
- if (r > 0) ctx.quadraticCurveTo(srcW, 0, srcW, r);
3886
- ctx.lineTo(srcW, srcH - r);
3887
- if (r > 0) ctx.quadraticCurveTo(srcW, srcH, srcW - r, srcH);
3888
- ctx.lineTo(r, srcH);
3889
- if (r > 0) ctx.quadraticCurveTo(0, srcH, 0, srcH - r);
4040
+ ctx.lineTo(dstW - r, 0);
4041
+ if (r > 0) ctx.quadraticCurveTo(dstW, 0, dstW, r);
4042
+ ctx.lineTo(dstW, dstH - r);
4043
+ if (r > 0) ctx.quadraticCurveTo(dstW, dstH, dstW - r, dstH);
4044
+ ctx.lineTo(r, dstH);
4045
+ if (r > 0) ctx.quadraticCurveTo(0, dstH, 0, dstH - r);
3890
4046
  ctx.lineTo(0, r);
3891
4047
  if (r > 0) ctx.quadraticCurveTo(0, 0, r, 0);
3892
4048
  ctx.closePath();
@@ -3905,8 +4061,11 @@ async function rasterizeCropGroupToPng(liveGroup, frameW, frameH, multiplier = 2
3905
4061
  const natH = (dims == null ? void 0 : dims.height) ?? (stored == null ? void 0 : stored.naturalHeight) ?? frameH;
3906
4062
  const useNatW = natW > 0 && natH > 0 ? natW : frameW;
3907
4063
  const useNatH = natW > 0 && natH > 0 ? natH : frameH;
3908
- const outW2 = maintainResolution && useNatW > frameW ? Math.max(1, Math.round(useNatW)) : Math.max(1, Math.round(frameW * multiplier));
3909
- const outH2 = maintainResolution && useNatH > frameH ? Math.max(1, Math.round(useNatH)) : Math.max(1, Math.round(frameH * multiplier));
4064
+ const reqOutW = maintainResolution && useNatW > frameW ? Math.max(1, Math.round(useNatW)) : Math.max(1, Math.round(frameW * multiplier));
4065
+ const reqOutH = maintainResolution && useNatH > frameH ? Math.max(1, Math.round(useNatH)) : Math.max(1, Math.round(frameH * multiplier));
4066
+ const safeOut = getSafeCanvasDims(reqOutW, reqOutH);
4067
+ const outW2 = safeOut.width;
4068
+ const outH2 = safeOut.height;
3910
4069
  const proxied = getProxiedImageUrl(imageUrl);
3911
4070
  const res = await fetch(proxied);
3912
4071
  if (!res.ok) throw new Error(`Export blob fetch failed: ${res.status}`);
@@ -4498,6 +4657,7 @@ async function drawElement(pdf, element, embeddedFonts, canvasWidth, canvasHeigh
4498
4657
  if (fadedMasked) {
4499
4658
  png = fadedMasked;
4500
4659
  }
4660
+ const encodedMasked = await encodePdfRasterDataUrl(png, { allowJpeg: !fadedMasked });
4501
4661
  if (canUseAdvancedTransforms2 && fabricMatrix && fabricMatrix.length === 6) {
4502
4662
  const mPdf = fabricMatrixToJsPdfAdvancedMatrix(fabricMatrix);
4503
4663
  const localX = -frameW / 2;
@@ -4505,13 +4665,13 @@ async function drawElement(pdf, element, embeddedFonts, canvasWidth, canvasHeigh
4505
4665
  anyPdf2.advancedAPI(() => {
4506
4666
  pdf.saveGraphicsState();
4507
4667
  anyPdf2.setCurrentTransformationMatrix(anyPdf2.Matrix(...mPdf));
4508
- pdf.addImage(png, "PNG", localX, localY, frameW, frameH, void 0, pdfRasterCompression());
4668
+ pdf.addImage(encodedMasked.data, encodedMasked.format, localX, localY, frameW, frameH, void 0, pdfRasterCompression());
4509
4669
  pdf.restoreGraphicsState();
4510
4670
  });
4511
4671
  pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
4512
4672
  return;
4513
4673
  }
4514
- pdf.addImage(png, "PNG", norm.x, norm.y, frameW, frameH, void 0, pdfRasterCompression());
4674
+ pdf.addImage(encodedMasked.data, encodedMasked.format, norm.x, norm.y, frameW, frameH, void 0, pdfRasterCompression());
4515
4675
  pdf.setGState(pdf.GState({ opacity: 1, "stroke-opacity": 1 }));
4516
4676
  return;
4517
4677
  }
@@ -5018,8 +5178,8 @@ async function drawElementContentCore(pdf, element, norm, x, y, w, h, embeddedFo
5018
5178
  if (croppedPng) {
5019
5179
  try {
5020
5180
  const fadedCrop = await bakeEdgeFadeIntoDataUrl(croppedPng, element);
5021
- const finalCropPng = fadedCrop || croppedPng;
5022
- pdf.addImage(finalCropPng, "PNG", x, y, w, h, void 0, pdfRasterCompression());
5181
+ const encodedCrop = await encodePdfRasterDataUrl(fadedCrop || croppedPng, { allowJpeg: !fadedCrop });
5182
+ pdf.addImage(encodedCrop.data, encodedCrop.format, x, y, w, h, void 0, pdfRasterCompression());
5023
5183
  break;
5024
5184
  } catch {
5025
5185
  }
@@ -5227,10 +5387,21 @@ function getExpectedRasterImageIdsForPdfPage(page) {
5227
5387
  function getOriginalRasterImageUrlMapForPdfPage(page) {
5228
5388
  const byId = /* @__PURE__ */ new Map();
5229
5389
  const isSvgUrl = (url) => /\.svg(?:\?|#|$)/i.test(url) || /^data:image\/svg/i.test(url);
5390
+ const hasVisualImageEffect = (item) => {
5391
+ const hasCrop = item.cropPanX != null || item.cropPanY != null || item.cropZoom != null;
5392
+ const hasMask = item.maskSvgUrl || item.clipShape && item.clipShape !== "none" && item.clipShape !== "rectangle";
5393
+ const hasFade = ["Top", "Right", "Bottom", "Left"].some(
5394
+ (side) => item[`fade${side}Amount`] != null || item[`fade${side}Size`] != null || item[`fade${side}Hardness`] != null
5395
+ );
5396
+ const hasFrame = (item.strokeWidth ?? 0) > 0 || !!item.stroke || (item.rx ?? 0) > 0 || (item.ry ?? 0) > 0;
5397
+ const hasShadowOrFilter = item.shadow || item.shadowColor || item.shadowBlur || item.shadowOffsetX || item.shadowOffsetY || item.filter || item.filters;
5398
+ return Boolean(hasCrop || hasMask || hasFade || hasFrame || hasShadowOrFilter);
5399
+ };
5230
5400
  for (const item of page.elements) {
5231
5401
  if (isGroupBackgroundDrawable(item) || item.type !== "image" || item.visible === false) continue;
5232
5402
  const src = String(item.src || item.imageUrl || "").trim();
5233
5403
  if (!src || item.sourceFormat === "svg" || isSvgUrl(src)) continue;
5404
+ if (hasVisualImageEffect(item)) continue;
5234
5405
  byId.set(item.id, src);
5235
5406
  }
5236
5407
  return byId;
@@ -5276,7 +5447,10 @@ async function __exportMultiPagePdfInner(pages, options) {
5276
5447
  unit: "px",
5277
5448
  format: [firstPage.width, firstPage.height],
5278
5449
  hotfixes: ["px_scaling"],
5279
- compress: __pdfCompressImages
5450
+ // Always keep PDF object/stream compression on. The UI's image-compression
5451
+ // switch controls raster resampling/JPEG quality, not lossless PDF packing;
5452
+ // disabling this made "uncompressed image" PDFs balloon with no quality gain.
5453
+ compress: true
5280
5454
  });
5281
5455
  if (title) {
5282
5456
  pdf.setProperties({ title, creator: "DocuForge" });
@@ -5687,4 +5861,4 @@ export {
5687
5861
  preparePagesForExport,
5688
5862
  rewriteSvgFontsForJsPDFWithSourceMeta
5689
5863
  };
5690
- //# sourceMappingURL=vectorPdfExport-C0GsER0C.js.map
5864
+ //# sourceMappingURL=vectorPdfExport-BEGl71uB.js.map