@pixldocs/canvas-renderer 0.5.190 → 0.5.192

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.
@@ -16907,9 +16907,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16907
16907
  }
16908
16908
  return svgString;
16909
16909
  }
16910
- const resolvedPackageVersion = "0.5.190";
16910
+ const resolvedPackageVersion = "0.5.192";
16911
16911
  const PACKAGE_VERSION = resolvedPackageVersion;
16912
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.190";
16912
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.192";
16913
16913
  const roundParityValue = (value) => {
16914
16914
  if (typeof value !== "number") return value;
16915
16915
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -16936,6 +16936,96 @@ function isFabricTextboxLike(obj) {
16936
16936
  function isFabricGroupLike(obj) {
16937
16937
  return !!obj && (obj instanceof fabric__namespace.Group || obj.type === "group" || Array.isArray(obj._objects) && typeof obj.getObjects === "function");
16938
16938
  }
16939
+ function configHasUserDataImage(config) {
16940
+ let found = false;
16941
+ const isRasterDataUrl = (u) => typeof u === "string" && /^data:image\/(jpeg|jpg|png|webp)[;,]/i.test(u);
16942
+ const walk = (nodes) => {
16943
+ if (found || !Array.isArray(nodes)) return;
16944
+ for (const node of nodes) {
16945
+ if (!node || typeof node !== "object") continue;
16946
+ if (node.type === "image" && (isRasterDataUrl(node.src) || isRasterDataUrl(node.imageUrl))) {
16947
+ found = true;
16948
+ return;
16949
+ }
16950
+ if (Array.isArray(node.children)) walk(node.children);
16951
+ }
16952
+ };
16953
+ for (const page of config.pages ?? []) {
16954
+ walk((page == null ? void 0 : page.children) ?? []);
16955
+ if (found) break;
16956
+ }
16957
+ return found;
16958
+ }
16959
+ function detectSafariOrIos() {
16960
+ try {
16961
+ if (typeof navigator === "undefined") return false;
16962
+ const ua = navigator.userAgent || "";
16963
+ const vendor = navigator.vendor || "";
16964
+ const isIOS = /iPad|iPhone|iPod/.test(ua) || // iPadOS 13+ reports as Mac with touch
16965
+ /Macintosh/.test(ua) && typeof navigator.maxTouchPoints === "number" && navigator.maxTouchPoints > 1;
16966
+ const isSafariDesktop = /Safari/.test(ua) && /Apple/.test(vendor) && !/Chrome|CriOS|Chromium|Edg|FxiOS/.test(ua);
16967
+ return isIOS || isSafariDesktop;
16968
+ } catch {
16969
+ return false;
16970
+ }
16971
+ }
16972
+ async function downscaleConfigRasterImages(config, maxEdgePx) {
16973
+ if (!maxEdgePx || maxEdgePx <= 0) return 0;
16974
+ if (typeof document === "undefined") return 0;
16975
+ const targets = [];
16976
+ const isRasterDataUrl = (u) => typeof u === "string" && /^data:image\/(jpeg|jpg|png|webp)[;,]/i.test(u);
16977
+ const walk = (nodes) => {
16978
+ if (!Array.isArray(nodes)) return;
16979
+ for (const node of nodes) {
16980
+ if (!node || typeof node !== "object") continue;
16981
+ if (node.type === "image") {
16982
+ if (isRasterDataUrl(node.src)) targets.push({ node, field: "src" });
16983
+ else if (isRasterDataUrl(node.imageUrl)) targets.push({ node, field: "imageUrl" });
16984
+ }
16985
+ if (Array.isArray(node.children)) walk(node.children);
16986
+ }
16987
+ };
16988
+ for (const page of config.pages ?? []) walk((page == null ? void 0 : page.children) ?? []);
16989
+ if (targets.length === 0) return 0;
16990
+ const shrinkOne = async (dataUrl) => {
16991
+ try {
16992
+ const img = await new Promise((resolve, reject) => {
16993
+ const el = new Image();
16994
+ el.onload = () => resolve(el);
16995
+ el.onerror = (e) => reject(e);
16996
+ el.decoding = "sync";
16997
+ el.src = dataUrl;
16998
+ });
16999
+ const w = img.naturalWidth, h = img.naturalHeight;
17000
+ if (!w || !h) return null;
17001
+ const longest = Math.max(w, h);
17002
+ if (longest <= maxEdgePx) return null;
17003
+ const scale = maxEdgePx / longest;
17004
+ const tw = Math.max(1, Math.round(w * scale));
17005
+ const th = Math.max(1, Math.round(h * scale));
17006
+ const canvas = document.createElement("canvas");
17007
+ canvas.width = tw;
17008
+ canvas.height = th;
17009
+ const ctx = canvas.getContext("2d");
17010
+ if (!ctx) return null;
17011
+ ctx.fillStyle = "#ffffff";
17012
+ ctx.fillRect(0, 0, tw, th);
17013
+ ctx.drawImage(img, 0, 0, tw, th);
17014
+ return canvas.toDataURL("image/jpeg", 0.85);
17015
+ } catch {
17016
+ return null;
17017
+ }
17018
+ };
17019
+ let shrunk = 0;
17020
+ for (const { node, field } of targets) {
17021
+ const next = await shrinkOne(String(node[field]));
17022
+ if (next) {
17023
+ node[field] = next;
17024
+ shrunk++;
17025
+ }
17026
+ }
17027
+ return shrunk;
17028
+ }
16939
17029
  let __underlineFixInstalled = false;
16940
17030
  function installUnderlineFix(fab) {
16941
17031
  var _a;
@@ -17288,7 +17378,9 @@ class PixldocsRenderer {
17288
17378
  async renderPdf(templateConfig, options) {
17289
17379
  return this.renderPdfViaClientExport(templateConfig, {
17290
17380
  title: options == null ? void 0 : options.title,
17291
- textMode: options == null ? void 0 : options.textMode
17381
+ textMode: options == null ? void 0 : options.textMode,
17382
+ forcePerElementPdf: options == null ? void 0 : options.forcePerElementPdf,
17383
+ maxImageEdgePx: options == null ? void 0 : options.maxImageEdgePx
17292
17384
  });
17293
17385
  }
17294
17386
  /**
@@ -17296,7 +17388,7 @@ class PixldocsRenderer {
17296
17388
  * This is the primary PDF export API — mirrors renderFromForm() but returns a PDF.
17297
17389
  */
17298
17390
  async renderPdfFromForm(options) {
17299
- const { templateId, formSchemaId, sectionState, themeId, watermark, watermarkOptions, prefetched, title, fontBaseUrl, textMode } = options;
17391
+ const { templateId, formSchemaId, sectionState, themeId, watermark, watermarkOptions, prefetched, title, fontBaseUrl, textMode, forcePerElementPdf, maxImageEdgePx } = options;
17300
17392
  const resolved = await resolveFromForm({
17301
17393
  templateId,
17302
17394
  formSchemaId,
@@ -17317,7 +17409,9 @@ class PixldocsRenderer {
17317
17409
  return this.renderPdfViaClientExport(configToRender, {
17318
17410
  title: title ?? resolved.config.name,
17319
17411
  watermark: shouldWatermark,
17320
- textMode
17412
+ textMode,
17413
+ forcePerElementPdf,
17414
+ maxImageEdgePx
17321
17415
  });
17322
17416
  }
17323
17417
  async renderById(templateId, formData, options) {
@@ -17358,6 +17452,31 @@ class PixldocsRenderer {
17358
17452
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
17359
17453
  setPackageApiUrl2(this.config.imageProxyUrl);
17360
17454
  const cloned = JSON.parse(JSON.stringify(templateConfig));
17455
+ const callForce = options.forcePerElementPdf;
17456
+ const cfgForce = this.config.forcePerElementPdf;
17457
+ const forceMode = callForce !== void 0 ? callForce : cfgForce !== void 0 ? cfgForce : "auto";
17458
+ const hasUserDataImage = configHasUserDataImage(cloned);
17459
+ const isSafariLike = detectSafariOrIos();
17460
+ const shouldForcePerElement = forceMode === true ? true : forceMode === false ? false : hasUserDataImage && isSafariLike;
17461
+ const maxEdgeOpt = options.maxImageEdgePx ?? this.config.maxImageEdgePx;
17462
+ const effectiveMaxEdge = typeof maxEdgeOpt === "number" ? Math.max(0, maxEdgeOpt | 0) : shouldForcePerElement ? 2048 : 0;
17463
+ if (effectiveMaxEdge > 0) {
17464
+ try {
17465
+ const downscaled = await downscaleConfigRasterImages(cloned, effectiveMaxEdge);
17466
+ if (downscaled > 0) {
17467
+ console.log(`[canvas-renderer][pdf-unified] downscaled ${downscaled} raster image(s) to <=${effectiveMaxEdge}px edge`);
17468
+ }
17469
+ } catch (e) {
17470
+ console.warn("[canvas-renderer][pdf-unified] image downscale pass failed (continuing with originals):", e);
17471
+ }
17472
+ }
17473
+ console.log("[canvas-renderer][pdf-unified] export switches", {
17474
+ forceMode,
17475
+ hasUserDataImage,
17476
+ isSafariLike,
17477
+ shouldForcePerElement,
17478
+ effectiveMaxEdge
17479
+ });
17361
17480
  const stampPrefix = `__pixldocs_pdf_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
17362
17481
  const pageIds = cloned.pages.map((p, i) => {
17363
17482
  const id = `${stampPrefix}_p${i}`;
@@ -17417,7 +17536,7 @@ class PixldocsRenderer {
17417
17536
  await this.waitForCanvasScene(container, cloned, i);
17418
17537
  }
17419
17538
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
17420
- const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-Cgw8dB-L.cjs"));
17539
+ const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-O8vRHBlU.cjs"));
17421
17540
  const prepared = preparePagesForExport(
17422
17541
  cloned.pages,
17423
17542
  canvasWidth,
@@ -17427,7 +17546,28 @@ class PixldocsRenderer {
17427
17546
  title: options.title,
17428
17547
  watermark: !!options.watermark,
17429
17548
  returnBlob: true,
17430
- pdfTextMode: options.textMode ?? (cloned == null ? void 0 : cloned.pdfTextMode) ?? ((_a = cloned.canvas) == null ? void 0 : _a.n) ?? "auto"
17549
+ pdfTextMode: options.textMode ?? (cloned == null ? void 0 : cloned.pdfTextMode) ?? ((_a = cloned.canvas) == null ? void 0 : _a.n) ?? "auto",
17550
+ // IMPORTANT: We intentionally do NOT skip the live-canvas SVG fast path
17551
+ // here, even when `shouldForcePerElement` is true via auto-detection.
17552
+ //
17553
+ // Reason: skipping the fast path forces the config-space export, which
17554
+ // computes positions from stored values rather than the live Fabric
17555
+ // layout (post auto-shrink, post text-init, post crop-group bake).
17556
+ // That drift causes severe text overlap / mis-positioning in the PDF
17557
+ // while the on-screen preview (which uses the live canvas) still looks
17558
+ // correct. v0.5.191 shipped with this skip enabled and produced exactly
17559
+ // that regression.
17560
+ //
17561
+ // The Safari/iOS "missing photo" issue is solved end-to-end by the
17562
+ // `downscaleConfigRasterImages()` pre-pass above, which shrinks large
17563
+ // `data:image/*` JPEGs to a safe edge length before the canvas mounts.
17564
+ // That alone keeps Safari's SVG capture reliable without altering the
17565
+ // layout pipeline.
17566
+ //
17567
+ // We still honor an explicit `forcePerElementPdf: true` from the host
17568
+ // app (advanced opt-in for niche cases), but the auto path no longer
17569
+ // toggles this flag.
17570
+ skipLiveCanvasSvgFastPath: forceMode === true
17431
17571
  });
17432
17572
  if (!result || typeof result === "undefined") {
17433
17573
  throw new Error("exportMultiPagePdf returned no blob (returnBlob path failed)");
@@ -19562,7 +19702,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
19562
19702
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
19563
19703
  sanitizeSvgTreeForPdf(svgToDraw);
19564
19704
  try {
19565
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-Cgw8dB-L.cjs"));
19705
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-O8vRHBlU.cjs"));
19566
19706
  try {
19567
19707
  await logTextMeasurementDiagnostic(svgToDraw);
19568
19708
  } catch {
@@ -19959,4 +20099,4 @@ exports.setAutoShrinkDebug = setAutoShrinkDebug;
19959
20099
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
19960
20100
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
19961
20101
  exports.warmTemplateFromForm = warmTemplateFromForm;
19962
- //# sourceMappingURL=index-BUm3wqlB.cjs.map
20102
+ //# sourceMappingURL=index-UIDDdWwb.cjs.map