@pixldocs/canvas-renderer 0.5.189 → 0.5.191

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.
@@ -13309,6 +13309,7 @@ function isStaticOnNewPage(node) {
13309
13309
  function cloneNodeWithNewIds(node) {
13310
13310
  const base = node.__baseNodeId;
13311
13311
  const source = node.__sourceId;
13312
+ const effectiveSource = source ?? node.id;
13312
13313
  if (isGroup(node)) {
13313
13314
  const g = node;
13314
13315
  const cloned2 = {
@@ -13317,14 +13318,14 @@ function cloneNodeWithNewIds(node) {
13317
13318
  children: (g.children ?? []).map(cloneNodeWithNewIds)
13318
13319
  };
13319
13320
  if (base != null) cloned2.__baseNodeId = base;
13320
- if (source != null) cloned2.__sourceId = source;
13321
+ cloned2.__sourceId = effectiveSource;
13321
13322
  return cloned2;
13322
13323
  }
13323
13324
  const el = node;
13324
13325
  const prefix = el.type === "text" ? "text" : el.type === "image" ? "img" : el.type === "shape" ? "shape" : "line";
13325
13326
  const cloned = { ...el, id: generateId(prefix) };
13326
13327
  if (base != null) cloned.__baseNodeId = base;
13327
- if (source != null) cloned.__sourceId = source;
13328
+ cloned.__sourceId = effectiveSource;
13328
13329
  return cloned;
13329
13330
  }
13330
13331
  function cloneNodeWithStableIds(node, baseId2, path) {
@@ -13630,7 +13631,51 @@ function applyContentBoundsPagination(config) {
13630
13631
  resultPages.push(...paginated);
13631
13632
  }
13632
13633
  if (!mutated) return config;
13633
- return { ...config, pages: resultPages };
13634
+ const next = { ...config, pages: resultPages };
13635
+ remapCloneIdMapAfterPagination(next);
13636
+ return next;
13637
+ }
13638
+ function remapCloneIdMapAfterPagination(config) {
13639
+ const cloneIdMap = config.__cloneIdMap;
13640
+ if (!cloneIdMap || typeof cloneIdMap !== "object") return;
13641
+ const allIds = /* @__PURE__ */ new Set();
13642
+ const sourceToNew = /* @__PURE__ */ new Map();
13643
+ const walk = (node) => {
13644
+ if (!node || typeof node !== "object") return;
13645
+ const n = node;
13646
+ if (typeof n.id === "string") {
13647
+ allIds.add(n.id);
13648
+ const src = n.__sourceId;
13649
+ if (typeof src === "string" && src !== n.id) {
13650
+ const arr = sourceToNew.get(src);
13651
+ if (arr) arr.push(n.id);
13652
+ else sourceToNew.set(src, [n.id]);
13653
+ }
13654
+ }
13655
+ const children = n.children;
13656
+ if (Array.isArray(children)) for (const c of children) walk(c);
13657
+ };
13658
+ for (const page of config.pages ?? []) {
13659
+ const ch = page.children;
13660
+ if (Array.isArray(ch)) for (const c of ch) walk(c);
13661
+ }
13662
+ const updated = {};
13663
+ for (const [key, val] of Object.entries(cloneIdMap)) {
13664
+ const oldIds = Array.isArray(val) ? val : [val];
13665
+ const newIds = [];
13666
+ for (const oid of oldIds) {
13667
+ if (typeof oid !== "string") continue;
13668
+ if (allIds.has(oid)) newIds.push(oid);
13669
+ const clones = sourceToNew.get(oid);
13670
+ if (clones) {
13671
+ for (const c of clones) if (allIds.has(c)) newIds.push(c);
13672
+ }
13673
+ }
13674
+ if (newIds.length === 0) continue;
13675
+ const uniq = Array.from(new Set(newIds));
13676
+ updated[key] = uniq.length === 1 ? uniq[0] : uniq;
13677
+ }
13678
+ config.__cloneIdMap = updated;
13634
13679
  }
13635
13680
  const __vite_import_meta_env__ = {};
13636
13681
  const FONT_WEIGHT_LABELS = {
@@ -16256,6 +16301,32 @@ function collectPageTreeElementIds(config, pageIndex = "all") {
16256
16301
  }
16257
16302
  return out;
16258
16303
  }
16304
+ function collectIdsBySourceMatch(config, targetIds, pageIndex = "all") {
16305
+ const targets = /* @__PURE__ */ new Set();
16306
+ for (const t of targetIds) if (typeof t === "string" && t) targets.add(t);
16307
+ if (targets.size === 0) return [];
16308
+ const pages = config == null ? void 0 : config.pages;
16309
+ if (!Array.isArray(pages)) return [];
16310
+ const visitPages = pageIndex === "all" ? pages : pages[pageIndex] != null ? [pages[pageIndex]] : [];
16311
+ const out = /* @__PURE__ */ new Set();
16312
+ const walk = (node) => {
16313
+ if (!node || typeof node !== "object") return;
16314
+ const src = typeof node.__sourceId === "string" ? node.__sourceId : void 0;
16315
+ const base = typeof node.__baseNodeId === "string" ? node.__baseNodeId : void 0;
16316
+ if (typeof node.id === "string") {
16317
+ if (targets.has(node.id)) out.add(node.id);
16318
+ else if (src && targets.has(src)) out.add(node.id);
16319
+ else if (base && targets.has(base)) out.add(node.id);
16320
+ }
16321
+ const children = node.children || node.elements;
16322
+ if (Array.isArray(children)) for (const c of children) walk(c);
16323
+ };
16324
+ for (const page of visitPages) {
16325
+ const children = (page == null ? void 0 : page.children) || (page == null ? void 0 : page.elements);
16326
+ if (Array.isArray(children)) for (const c of children) walk(c);
16327
+ }
16328
+ return Array.from(out);
16329
+ }
16259
16330
  function resolveBlurElementExactIdsFromFlatFormKeys(config, flatFormKeys, options) {
16260
16331
  const cloneIdMap = (config == null ? void 0 : config.__cloneIdMap) || {};
16261
16332
  if (!cloneIdMap || typeof cloneIdMap !== "object") return [];
@@ -16289,7 +16360,8 @@ function resolveBlurElementExactIdsFromFlatFormKeys(config, flatFormKeys, option
16289
16360
  if (treeIds.size === 0) return Array.from(out);
16290
16361
  const verified = [];
16291
16362
  for (const id of out) if (treeIds.has(id)) verified.push(id);
16292
- return verified;
16363
+ if (verified.length > 0) return verified;
16364
+ return collectIdsBySourceMatch(config, out, (options == null ? void 0 : options.pageIndex) ?? "all");
16293
16365
  }
16294
16366
  function buildTeaserBlurFlatKeys(sectionState, sections, options) {
16295
16367
  const afterRow = Math.max(0, options.afterRow | 0);
@@ -16817,9 +16889,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16817
16889
  }
16818
16890
  return svgString;
16819
16891
  }
16820
- const resolvedPackageVersion = "0.5.189";
16892
+ const resolvedPackageVersion = "0.5.191";
16821
16893
  const PACKAGE_VERSION = resolvedPackageVersion;
16822
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.189";
16894
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.191";
16823
16895
  const roundParityValue = (value) => {
16824
16896
  if (typeof value !== "number") return value;
16825
16897
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -16846,6 +16918,96 @@ function isFabricTextboxLike(obj) {
16846
16918
  function isFabricGroupLike(obj) {
16847
16919
  return !!obj && (obj instanceof fabric.Group || obj.type === "group" || Array.isArray(obj._objects) && typeof obj.getObjects === "function");
16848
16920
  }
16921
+ function configHasUserDataImage(config) {
16922
+ let found = false;
16923
+ const isRasterDataUrl = (u) => typeof u === "string" && /^data:image\/(jpeg|jpg|png|webp)[;,]/i.test(u);
16924
+ const walk = (nodes) => {
16925
+ if (found || !Array.isArray(nodes)) return;
16926
+ for (const node of nodes) {
16927
+ if (!node || typeof node !== "object") continue;
16928
+ if (node.type === "image" && (isRasterDataUrl(node.src) || isRasterDataUrl(node.imageUrl))) {
16929
+ found = true;
16930
+ return;
16931
+ }
16932
+ if (Array.isArray(node.children)) walk(node.children);
16933
+ }
16934
+ };
16935
+ for (const page of config.pages ?? []) {
16936
+ walk((page == null ? void 0 : page.children) ?? []);
16937
+ if (found) break;
16938
+ }
16939
+ return found;
16940
+ }
16941
+ function detectSafariOrIos() {
16942
+ try {
16943
+ if (typeof navigator === "undefined") return false;
16944
+ const ua = navigator.userAgent || "";
16945
+ const vendor = navigator.vendor || "";
16946
+ const isIOS = /iPad|iPhone|iPod/.test(ua) || // iPadOS 13+ reports as Mac with touch
16947
+ /Macintosh/.test(ua) && typeof navigator.maxTouchPoints === "number" && navigator.maxTouchPoints > 1;
16948
+ const isSafariDesktop = /Safari/.test(ua) && /Apple/.test(vendor) && !/Chrome|CriOS|Chromium|Edg|FxiOS/.test(ua);
16949
+ return isIOS || isSafariDesktop;
16950
+ } catch {
16951
+ return false;
16952
+ }
16953
+ }
16954
+ async function downscaleConfigRasterImages(config, maxEdgePx) {
16955
+ if (!maxEdgePx || maxEdgePx <= 0) return 0;
16956
+ if (typeof document === "undefined") return 0;
16957
+ const targets = [];
16958
+ const isRasterDataUrl = (u) => typeof u === "string" && /^data:image\/(jpeg|jpg|png|webp)[;,]/i.test(u);
16959
+ const walk = (nodes) => {
16960
+ if (!Array.isArray(nodes)) return;
16961
+ for (const node of nodes) {
16962
+ if (!node || typeof node !== "object") continue;
16963
+ if (node.type === "image") {
16964
+ if (isRasterDataUrl(node.src)) targets.push({ node, field: "src" });
16965
+ else if (isRasterDataUrl(node.imageUrl)) targets.push({ node, field: "imageUrl" });
16966
+ }
16967
+ if (Array.isArray(node.children)) walk(node.children);
16968
+ }
16969
+ };
16970
+ for (const page of config.pages ?? []) walk((page == null ? void 0 : page.children) ?? []);
16971
+ if (targets.length === 0) return 0;
16972
+ const shrinkOne = async (dataUrl) => {
16973
+ try {
16974
+ const img = await new Promise((resolve, reject) => {
16975
+ const el = new Image();
16976
+ el.onload = () => resolve(el);
16977
+ el.onerror = (e) => reject(e);
16978
+ el.decoding = "sync";
16979
+ el.src = dataUrl;
16980
+ });
16981
+ const w = img.naturalWidth, h = img.naturalHeight;
16982
+ if (!w || !h) return null;
16983
+ const longest = Math.max(w, h);
16984
+ if (longest <= maxEdgePx) return null;
16985
+ const scale = maxEdgePx / longest;
16986
+ const tw = Math.max(1, Math.round(w * scale));
16987
+ const th = Math.max(1, Math.round(h * scale));
16988
+ const canvas = document.createElement("canvas");
16989
+ canvas.width = tw;
16990
+ canvas.height = th;
16991
+ const ctx = canvas.getContext("2d");
16992
+ if (!ctx) return null;
16993
+ ctx.fillStyle = "#ffffff";
16994
+ ctx.fillRect(0, 0, tw, th);
16995
+ ctx.drawImage(img, 0, 0, tw, th);
16996
+ return canvas.toDataURL("image/jpeg", 0.85);
16997
+ } catch {
16998
+ return null;
16999
+ }
17000
+ };
17001
+ let shrunk = 0;
17002
+ for (const { node, field } of targets) {
17003
+ const next = await shrinkOne(String(node[field]));
17004
+ if (next) {
17005
+ node[field] = next;
17006
+ shrunk++;
17007
+ }
17008
+ }
17009
+ return shrunk;
17010
+ }
16849
17011
  let __underlineFixInstalled = false;
16850
17012
  function installUnderlineFix(fab) {
16851
17013
  var _a;
@@ -17198,7 +17360,9 @@ class PixldocsRenderer {
17198
17360
  async renderPdf(templateConfig, options) {
17199
17361
  return this.renderPdfViaClientExport(templateConfig, {
17200
17362
  title: options == null ? void 0 : options.title,
17201
- textMode: options == null ? void 0 : options.textMode
17363
+ textMode: options == null ? void 0 : options.textMode,
17364
+ forcePerElementPdf: options == null ? void 0 : options.forcePerElementPdf,
17365
+ maxImageEdgePx: options == null ? void 0 : options.maxImageEdgePx
17202
17366
  });
17203
17367
  }
17204
17368
  /**
@@ -17206,7 +17370,7 @@ class PixldocsRenderer {
17206
17370
  * This is the primary PDF export API — mirrors renderFromForm() but returns a PDF.
17207
17371
  */
17208
17372
  async renderPdfFromForm(options) {
17209
- const { templateId, formSchemaId, sectionState, themeId, watermark, watermarkOptions, prefetched, title, fontBaseUrl, textMode } = options;
17373
+ const { templateId, formSchemaId, sectionState, themeId, watermark, watermarkOptions, prefetched, title, fontBaseUrl, textMode, forcePerElementPdf, maxImageEdgePx } = options;
17210
17374
  const resolved = await resolveFromForm({
17211
17375
  templateId,
17212
17376
  formSchemaId,
@@ -17227,7 +17391,9 @@ class PixldocsRenderer {
17227
17391
  return this.renderPdfViaClientExport(configToRender, {
17228
17392
  title: title ?? resolved.config.name,
17229
17393
  watermark: shouldWatermark,
17230
- textMode
17394
+ textMode,
17395
+ forcePerElementPdf,
17396
+ maxImageEdgePx
17231
17397
  });
17232
17398
  }
17233
17399
  async renderById(templateId, formData, options) {
@@ -17268,6 +17434,31 @@ class PixldocsRenderer {
17268
17434
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
17269
17435
  setPackageApiUrl2(this.config.imageProxyUrl);
17270
17436
  const cloned = JSON.parse(JSON.stringify(templateConfig));
17437
+ const callForce = options.forcePerElementPdf;
17438
+ const cfgForce = this.config.forcePerElementPdf;
17439
+ const forceMode = callForce !== void 0 ? callForce : cfgForce !== void 0 ? cfgForce : "auto";
17440
+ const hasUserDataImage = configHasUserDataImage(cloned);
17441
+ const isSafariLike = detectSafariOrIos();
17442
+ const shouldForcePerElement = forceMode === true ? true : forceMode === false ? false : hasUserDataImage && isSafariLike;
17443
+ const maxEdgeOpt = options.maxImageEdgePx ?? this.config.maxImageEdgePx;
17444
+ const effectiveMaxEdge = typeof maxEdgeOpt === "number" ? Math.max(0, maxEdgeOpt | 0) : shouldForcePerElement ? 2048 : 0;
17445
+ if (effectiveMaxEdge > 0) {
17446
+ try {
17447
+ const downscaled = await downscaleConfigRasterImages(cloned, effectiveMaxEdge);
17448
+ if (downscaled > 0) {
17449
+ console.log(`[canvas-renderer][pdf-unified] downscaled ${downscaled} raster image(s) to <=${effectiveMaxEdge}px edge`);
17450
+ }
17451
+ } catch (e) {
17452
+ console.warn("[canvas-renderer][pdf-unified] image downscale pass failed (continuing with originals):", e);
17453
+ }
17454
+ }
17455
+ console.log("[canvas-renderer][pdf-unified] export switches", {
17456
+ forceMode,
17457
+ hasUserDataImage,
17458
+ isSafariLike,
17459
+ shouldForcePerElement,
17460
+ effectiveMaxEdge
17461
+ });
17271
17462
  const stampPrefix = `__pixldocs_pdf_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
17272
17463
  const pageIds = cloned.pages.map((p, i) => {
17273
17464
  const id = `${stampPrefix}_p${i}`;
@@ -17327,7 +17518,7 @@ class PixldocsRenderer {
17327
17518
  await this.waitForCanvasScene(container, cloned, i);
17328
17519
  }
17329
17520
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
17330
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-Ch-qwcNJ.js");
17521
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-CrTGqxx6.js");
17331
17522
  const prepared = preparePagesForExport(
17332
17523
  cloned.pages,
17333
17524
  canvasWidth,
@@ -17337,7 +17528,8 @@ class PixldocsRenderer {
17337
17528
  title: options.title,
17338
17529
  watermark: !!options.watermark,
17339
17530
  returnBlob: true,
17340
- pdfTextMode: options.textMode ?? (cloned == null ? void 0 : cloned.pdfTextMode) ?? ((_a = cloned.canvas) == null ? void 0 : _a.n) ?? "auto"
17531
+ pdfTextMode: options.textMode ?? (cloned == null ? void 0 : cloned.pdfTextMode) ?? ((_a = cloned.canvas) == null ? void 0 : _a.n) ?? "auto",
17532
+ skipLiveCanvasSvgFastPath: shouldForcePerElement
17341
17533
  });
17342
17534
  if (!result || typeof result === "undefined") {
17343
17535
  throw new Error("exportMultiPagePdf returned no blob (returnBlob path failed)");
@@ -19472,7 +19664,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
19472
19664
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
19473
19665
  sanitizeSvgTreeForPdf(svgToDraw);
19474
19666
  try {
19475
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-Ch-qwcNJ.js");
19667
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-CrTGqxx6.js");
19476
19668
  try {
19477
19669
  await logTextMeasurementDiagnostic(svgToDraw);
19478
19670
  } catch {
@@ -19872,4 +20064,4 @@ export {
19872
20064
  buildTeaserBlurFlatKeys as y,
19873
20065
  collectFontDescriptorsFromConfig as z
19874
20066
  };
19875
- //# sourceMappingURL=index-lQXXPpb8.js.map
20067
+ //# sourceMappingURL=index-C3kuDISv.js.map