@pixldocs/canvas-renderer 0.5.183 → 0.5.185

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/README.md CHANGED
@@ -367,6 +367,32 @@ Preview-blur is gated identically to the watermark: it only runs when
367
367
  `watermark === true` (or, by default, when `template.price > 0` and the user
368
368
  hasn't paid). Clean / paid downloads always render the original content.
369
369
 
370
+ ### Live `backdrop-filter` overlay in `<PixldocsPreview>`
371
+
372
+ The React `<PixldocsPreview>` component automatically renders a real CSS
373
+ `backdrop-filter: blur()` frosted-glass overlay on top of every element
374
+ marked `previewBlur: true`. Bounds are auto-derived from the config
375
+ (groups respected), so no extra wiring is required.
376
+
377
+ Defaults: `blur(5px) saturate(130%)` with a `rgba(255,255,255,0.12)` tint,
378
+ square corners, no stroke, no shadow — identical to the in-app preview on
379
+ pixldocs.com.
380
+
381
+ Opt-out or tune via props:
382
+
383
+ ```tsx
384
+ <PixldocsPreview
385
+ config={config}
386
+ // disable entirely
387
+ frostedBlur={false}
388
+ // or tune
389
+ frostedBlurOptions={{ blurPx: 6, saturatePct: 140, background: 'rgba(255,255,255,0.15)' }}
390
+ />
391
+ ```
392
+
393
+ The imperative PNG / PDF export paths still use the static translucent
394
+ rectangle (the closest pure-vector approximation), unchanged.
395
+
370
396
  ```ts
371
397
  // In your template config
372
398
  {
@@ -380,3 +406,44 @@ hasn't paid). Clean / paid downloads always render the original content.
380
406
 
381
407
  No API change is required on the consumer side — the same `renderFromForm`,
382
408
  `renderPdfFromForm`, etc. respect the flag automatically.
409
+
410
+ ### Example: per-field blur for a biodata form (use page)
411
+
412
+ On pixldocs.com's Use page for the Biodata preset, every form field renders
413
+ a small **🔒 Blur in preview** checkbox next to it. Toggling it redacts only
414
+ that field's text in the watermarked preview — perfect for hiding reference
415
+ rows, phone numbers, or addresses behind the paywall while leaving the rest
416
+ of the biodata visible. Paid / clean downloads always render the original
417
+ content.
418
+
419
+ Under the hood the flow is:
420
+
421
+ 1. Keep a `Set<string>` of "blurred field ids" in component state — each id
422
+ encodes the field's path (single field, repeatable entry, or nested
423
+ repeatable entry), e.g.
424
+ `__bioPreviewBlur__:nested:family:1:siblings:2:name`.
425
+ 2. Resolve each blurred field id to the **exact cloned element ids** in the
426
+ paginated config (a single repeatable field may map to N cloned elements,
427
+ one per entry).
428
+ 3. Pass that set to `injectPreviewBlur` as `extraElementExactIds` (or
429
+ `extraElementBaseIds` if you only know the source/base id), then render
430
+ the returned config like any other.
431
+
432
+ ```tsx
433
+ import { injectPreviewBlur } from '@pixldocs/canvas-renderer';
434
+
435
+ // `blurredElementIds` = the resolved Set<string> of exact cloned element ids
436
+ // you built from the user's checkbox state.
437
+ const previewConfig = injectPreviewBlur(paginatedConfig, {
438
+ extraElementExactIds: blurredElementIds,
439
+ });
440
+
441
+ // Then render `previewConfig` with <PixldocsPreview config={previewConfig} />
442
+ // or `renderer.renderAllPages(previewConfig)`. The live React preview will
443
+ // additionally upgrade the static rectangles to real CSS
444
+ // `backdrop-filter: blur()` overlays automatically.
445
+ ```
446
+
447
+ This is exactly how the Biodata preset on pixldocs.com wires its per-field
448
+ 🔒 checkboxes — no template authoring required, the user picks at runtime
449
+ which fields stay visible in the watermarked preview.
@@ -16144,7 +16144,39 @@ function countUnderlinedNodes(config) {
16144
16144
  for (const page of config.pages) walk(page.children || []);
16145
16145
  return count;
16146
16146
  }
16147
+ function getNum(v, fallback = 0) {
16148
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
16149
+ }
16150
+ function collectBlurBounds(node, parentLeft, parentTop, inheritedBlur, out) {
16151
+ if (!node || typeof node !== "object") return;
16152
+ const isBlurred = inheritedBlur || node.previewBlur === true;
16153
+ const absLeft = parentLeft + getNum(node.left, 0);
16154
+ const absTop = parentTop + getNum(node.top, 0);
16155
+ if (node.type === "group") {
16156
+ const children = node.children || node.elements;
16157
+ if (Array.isArray(children)) {
16158
+ for (const c of children) collectBlurBounds(c, absLeft, absTop, isBlurred, out);
16159
+ }
16160
+ return;
16161
+ }
16162
+ if (!isBlurred) return;
16163
+ const sx = getNum(node.scaleX, 1) || 1;
16164
+ const sy = getNum(node.scaleY, 1) || 1;
16165
+ const w = Math.max(4, getNum(node.width, 0) * sx);
16166
+ const h = Math.max(4, getNum(node.height, 0) * sy);
16167
+ out.push({ left: absLeft, top: absTop, width: w, height: h });
16168
+ }
16169
+ function computeFrostedBoundsForPage(config, pageIndex) {
16170
+ var _a;
16171
+ const page = (_a = config == null ? void 0 : config.pages) == null ? void 0 : _a[pageIndex];
16172
+ const children = (page == null ? void 0 : page.children) || (page == null ? void 0 : page.elements);
16173
+ if (!Array.isArray(children)) return [];
16174
+ const out = [];
16175
+ for (const c of children) collectBlurBounds(c, 0, 0, false, out);
16176
+ return out;
16177
+ }
16147
16178
  function PixldocsPreview(props) {
16179
+ var _a, _b;
16148
16180
  const {
16149
16181
  pageIndex = 0,
16150
16182
  zoom = 1,
@@ -16168,7 +16200,9 @@ function PixldocsPreview(props) {
16168
16200
  // `captureSvgViaPreviewCanvas`) already pass `skipFontReadyWait: false`
16169
16201
  // for this exact reason — that's why the downloaded PDF was correct
16170
16202
  // while the on-screen preview wasn't.
16171
- skipFontReadyWait = false
16203
+ skipFontReadyWait = false,
16204
+ frostedBlur = true,
16205
+ frostedBlurOptions
16172
16206
  } = props;
16173
16207
  useEffect(() => {
16174
16208
  setPackageApiUrl(imageProxyUrl);
@@ -16205,10 +16239,10 @@ function PixldocsPreview(props) {
16205
16239
  supabaseUrl: p.supabaseUrl,
16206
16240
  supabaseAnonKey: p.supabaseAnonKey
16207
16241
  }).then((resolved) => {
16208
- var _a, _b;
16242
+ var _a2, _b2;
16209
16243
  if (!cancelled) {
16210
16244
  console.log(PREVIEW_DEBUG_PREFIX, "resolve-done", {
16211
- pages: ((_b = (_a = resolved.config) == null ? void 0 : _a.pages) == null ? void 0 : _b.length) ?? 0,
16245
+ pages: ((_b2 = (_a2 = resolved.config) == null ? void 0 : _a2.pages) == null ? void 0 : _b2.length) ?? 0,
16212
16246
  underlinedNodes: countUnderlinedNodes(resolved.config)
16213
16247
  });
16214
16248
  setResolvedConfig(resolved.config);
@@ -16284,6 +16318,16 @@ function PixldocsPreview(props) {
16284
16318
  setCanvasSettled(true);
16285
16319
  onReady == null ? void 0 : onReady();
16286
16320
  }, [onReady, pageIndex]);
16321
+ const frostedBounds = useMemo(
16322
+ () => frostedBlur ? computeFrostedBoundsForPage(config, pageIndex) : [],
16323
+ [frostedBlur, config, pageIndex]
16324
+ );
16325
+ const blurPx = (frostedBlurOptions == null ? void 0 : frostedBlurOptions.blurPx) ?? 5;
16326
+ const satPct = (frostedBlurOptions == null ? void 0 : frostedBlurOptions.saturatePct) ?? 130;
16327
+ const bgTint = (frostedBlurOptions == null ? void 0 : frostedBlurOptions.background) ?? "rgba(255,255,255,0.12)";
16328
+ const canvasW = getNum((_a = config == null ? void 0 : config.canvas) == null ? void 0 : _a.width, 0);
16329
+ const canvasH = getNum((_b = config == null ? void 0 : config.canvas) == null ? void 0 : _b.height, 0);
16330
+ const hasOverlays = frostedBounds.length > 0 && canvasW > 0 && canvasH > 0;
16287
16331
  if (isLoading) {
16288
16332
  return /* @__PURE__ */ jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
16289
16333
  }
@@ -16292,19 +16336,55 @@ function PixldocsPreview(props) {
16292
16336
  return /* @__PURE__ */ jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
16293
16337
  }
16294
16338
  return /* @__PURE__ */ jsxs("div", { className, style: { ...style, position: "relative" }, children: [
16295
- /* @__PURE__ */ jsx("div", { style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsx(
16296
- PreviewCanvas,
16339
+ /* @__PURE__ */ jsxs(
16340
+ "div",
16297
16341
  {
16298
- config,
16299
- pageIndex,
16300
- zoom,
16301
- absoluteZoom,
16302
- skipFontReadyWait,
16303
- onDynamicFieldClick,
16304
- onReady: handleCanvasReady
16305
- },
16306
- previewKey
16307
- ) }),
16342
+ style: hasOverlays ? { visibility: canvasSettled ? "visible" : "hidden", position: "relative", width: canvasW * zoom, height: canvasH * zoom } : { visibility: canvasSettled ? "visible" : "hidden" },
16343
+ children: [
16344
+ /* @__PURE__ */ jsx(
16345
+ PreviewCanvas,
16346
+ {
16347
+ config,
16348
+ pageIndex,
16349
+ zoom,
16350
+ absoluteZoom,
16351
+ skipFontReadyWait,
16352
+ onDynamicFieldClick,
16353
+ onReady: handleCanvasReady
16354
+ },
16355
+ previewKey
16356
+ ),
16357
+ hasOverlays && /* @__PURE__ */ jsx(
16358
+ "div",
16359
+ {
16360
+ style: {
16361
+ position: "absolute",
16362
+ inset: 0,
16363
+ pointerEvents: "none",
16364
+ zIndex: 5
16365
+ },
16366
+ children: frostedBounds.map((b, i) => /* @__PURE__ */ jsx(
16367
+ "div",
16368
+ {
16369
+ style: {
16370
+ position: "absolute",
16371
+ left: b.left * zoom,
16372
+ top: b.top * zoom,
16373
+ width: b.width * zoom,
16374
+ height: b.height * zoom,
16375
+ borderRadius: 0,
16376
+ backdropFilter: `blur(${blurPx}px) saturate(${satPct}%)`,
16377
+ WebkitBackdropFilter: `blur(${blurPx}px) saturate(${satPct}%)`,
16378
+ background: bgTint
16379
+ }
16380
+ },
16381
+ `frost-${pageIndex}-${i}`
16382
+ ))
16383
+ }
16384
+ )
16385
+ ]
16386
+ }
16387
+ ),
16308
16388
  !canvasSettled && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
16309
16389
  ] });
16310
16390
  }
@@ -16481,9 +16561,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16481
16561
  }
16482
16562
  return svgString;
16483
16563
  }
16484
- const resolvedPackageVersion = "0.5.183";
16564
+ const resolvedPackageVersion = "0.5.185";
16485
16565
  const PACKAGE_VERSION = resolvedPackageVersion;
16486
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.183";
16566
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.185";
16487
16567
  const roundParityValue = (value) => {
16488
16568
  if (typeof value !== "number") return value;
16489
16569
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -16782,7 +16862,7 @@ class PixldocsRenderer {
16782
16862
  if (shouldWatermark) {
16783
16863
  const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
16784
16864
  configToRender = injectWatermark(configToRender, watermarkOptions);
16785
- const { injectPreviewBlur } = await import("./previewBlur-Dkg1mMKM.js");
16865
+ const { injectPreviewBlur } = await import("./previewBlur-B5SlBl4a.js");
16786
16866
  configToRender = injectPreviewBlur(configToRender);
16787
16867
  }
16788
16868
  return this.renderAllPages(configToRender, renderOpts);
@@ -16841,7 +16921,7 @@ class PixldocsRenderer {
16841
16921
  if (shouldWatermark) {
16842
16922
  const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
16843
16923
  configToRender = injectWatermark(configToRender, watermarkOptions);
16844
- const { injectPreviewBlur } = await import("./previewBlur-Dkg1mMKM.js");
16924
+ const { injectPreviewBlur } = await import("./previewBlur-B5SlBl4a.js");
16845
16925
  configToRender = injectPreviewBlur(configToRender);
16846
16926
  }
16847
16927
  return this.renderAllPageSvgs(configToRender);
@@ -16885,7 +16965,7 @@ class PixldocsRenderer {
16885
16965
  if (shouldWatermark) {
16886
16966
  const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
16887
16967
  configToRender = injectWatermark(configToRender, watermarkOptions);
16888
- const { injectPreviewBlur } = await import("./previewBlur-Dkg1mMKM.js");
16968
+ const { injectPreviewBlur } = await import("./previewBlur-B5SlBl4a.js");
16889
16969
  configToRender = injectPreviewBlur(configToRender);
16890
16970
  }
16891
16971
  return this.renderPdfViaClientExport(configToRender, {
@@ -16991,7 +17071,7 @@ class PixldocsRenderer {
16991
17071
  await this.waitForCanvasScene(container, cloned, i);
16992
17072
  }
16993
17073
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
16994
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-C09pvz-H.js");
17074
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-CfmlwA1-.js");
16995
17075
  const prepared = preparePagesForExport(
16996
17076
  cloned.pages,
16997
17077
  canvasWidth,
@@ -19136,7 +19216,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
19136
19216
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
19137
19217
  sanitizeSvgTreeForPdf(svgToDraw);
19138
19218
  try {
19139
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-C09pvz-H.js");
19219
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-CfmlwA1-.js");
19140
19220
  try {
19141
19221
  await logTextMeasurementDiagnostic(svgToDraw);
19142
19222
  } catch {
@@ -19533,4 +19613,4 @@ export {
19533
19613
  awaitFontsForConfig as y,
19534
19614
  collectFontDescriptorsFromConfig as z
19535
19615
  };
19536
- //# sourceMappingURL=index-IYEhmMeo.js.map
19616
+ //# sourceMappingURL=index-NWuzIM_r.js.map