@pixldocs/canvas-renderer 0.5.186 → 0.5.188

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
@@ -482,3 +482,87 @@ const blurFieldIds = ['text-reference-name', 'text-phone', 'text-address'];
482
482
  **downloads** (PNG/PDF) you still want to run `injectPreviewBlur(...)` on
483
483
  the resolved config before handing it to the imperative renderer, so the
484
484
  blur is baked into the exported pixels.
485
+
486
+ ### Biodata teaser previews — blur by flat form key (v0.5.187+)
487
+
488
+ Form-driven apps (BioMaker, the pixldocs.com Use page, etc.) already
489
+ speak the **flat form-key** language produced by `applyFormDataToConfig`
490
+ — e.g.
491
+
492
+ ```
493
+ field_<treeNodeId>_<entryIdx1>_<fieldKey>
494
+ field_<parentTree>_<pi>_field_<childTree>_<ci>_<fieldKey> // nested
495
+ ```
496
+
497
+ Instead of reverse-engineering `config.__cloneIdMap` keys or the
498
+ `__cN` / `_eN` clone suffixes on canvas element ids, you can pass those
499
+ flat keys straight to `<PixldocsPreview>` and the package resolves them:
500
+
501
+ ```tsx
502
+ import { PixldocsPreview } from '@pixldocs/canvas-renderer';
503
+
504
+ // e.g. for a biodata teaser: blur every detail-row VALUE after row 3.
505
+ const blurFlatFormKeys = buildTeaserBlurFlatKeys(sectionState, schema, {
506
+ afterRow: 3,
507
+ bindings: 'value',
508
+ });
509
+
510
+ <PixldocsPreview
511
+ config={displayConfig}
512
+ frostedBlur
513
+ blurFlatFormKeys={blurFlatFormKeys}
514
+ // Optional — defaults to { bindings: 'value' } so only `*_value` keys
515
+ // are honoured. Pass 'all' to blur labels / titles too.
516
+ blurFlatFormKeyOptions={{ bindings: 'value' }}
517
+ />
518
+ ```
519
+
520
+ Notes:
521
+
522
+ - Matching is done on `config.__cloneIdMap` and handles all alias
523
+ variants (`grp-…_4_value`, `…_4_field_detail_section_N_field_N_value`,
524
+ with or without the wrapper `field_` prefix). Unknown keys are
525
+ silently ignored — the package never blurs an element it can't
526
+ positively identify, so a stale key won't accidentally blur the full
527
+ name, photo, or section titles.
528
+ - Defaults to `bindings: 'value'`, i.e. only flat keys whose last
529
+ segment is `value` resolve. This is exactly the biodata teaser
530
+ pattern: blur row values, leave labels / section titles / full-name /
531
+ photo sharp. Switch to `'all'` if you want labels blurred too.
532
+ - This only drives the live preview overlay. For the watermarked
533
+ PNG/PDF download path, resolve the same keys to exact ids and bake
534
+ them in (see below).
535
+
536
+ #### Watermarked downloads — same resolver + `injectPreviewBlur`
537
+
538
+ ```ts
539
+ import {
540
+ injectPreviewBlur,
541
+ resolveBlurElementExactIdsFromFlatFormKeys,
542
+ } from '@pixldocs/canvas-renderer';
543
+
544
+ const exactIds = resolveBlurElementExactIdsFromFlatFormKeys(
545
+ resolvedConfig,
546
+ blurFlatFormKeys,
547
+ // { bindings: 'value' } by default
548
+ );
549
+
550
+ const exportConfig = injectPreviewBlur(resolvedConfig, {
551
+ extraElementExactIds: new Set(exactIds),
552
+ });
553
+
554
+ // Hand `exportConfig` to renderer.renderAllPages / renderPdf / …
555
+ ```
556
+
557
+ Same resolution path as the live preview — so the on-screen overlay and
558
+ the downloaded PNG/PDF blur exactly the same elements.
559
+
560
+ #### Parity note
561
+
562
+ The pixldocs.com **Use page** (the in-app paid-template preview, including
563
+ the Biodata preset's 🔒 per-field checkboxes) and external consumers
564
+ (BioMaker, etc.) both call **the same `resolveBlurElementExactIdsFromFlatFormKeys`**
565
+ function — there is now exactly one resolver shared across the entire
566
+ stack. Anything that blurs correctly in one place blurs identically in
567
+ the other; visual/UX bugs introduced in one pipeline cannot diverge
568
+ silently from the other.
@@ -16133,6 +16133,162 @@ async function getTemplateForm(options) {
16133
16133
  initialSectionState
16134
16134
  };
16135
16135
  }
16136
+ const OVERLAY_ID_PREFIX = "__pb_";
16137
+ function getNumber(v, fallback = 0) {
16138
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
16139
+ }
16140
+ function resolveSize(node, key) {
16141
+ const v = node == null ? void 0 : node[key];
16142
+ if (typeof v === "number" && Number.isFinite(v)) return v;
16143
+ return 0;
16144
+ }
16145
+ function collectOverlays(node, parentLeft, parentTop, inheritedBlur, extraBaseIds, extraExactIds, out) {
16146
+ if (!node || typeof node !== "object") return;
16147
+ const matchesBase = !!(extraBaseIds && typeof node.id === "string" && extraBaseIds.has(baseId(node.id)));
16148
+ const matchesExact = !!(extraExactIds && typeof node.id === "string" && extraExactIds.has(node.id));
16149
+ const isBlurred = inheritedBlur || node.previewBlur === true || matchesBase || matchesExact;
16150
+ const absLeft = parentLeft + getNumber(node.left, 0);
16151
+ const absTop = parentTop + getNumber(node.top, 0);
16152
+ if (node.type === "group") {
16153
+ const children = node.children || node.elements;
16154
+ if (Array.isArray(children)) {
16155
+ for (const child of children) {
16156
+ collectOverlays(child, absLeft, absTop, isBlurred, extraBaseIds, extraExactIds, out);
16157
+ }
16158
+ }
16159
+ return;
16160
+ }
16161
+ if (!isBlurred) return;
16162
+ const scaleX = getNumber(node.scaleX, 1) || 1;
16163
+ const scaleY = getNumber(node.scaleY, 1) || 1;
16164
+ const w = Math.max(4, resolveSize(node, "width") * scaleX);
16165
+ const h = Math.max(4, resolveSize(node, "height") * scaleY);
16166
+ out.push({
16167
+ left: absLeft,
16168
+ top: absTop,
16169
+ width: w,
16170
+ height: h,
16171
+ hintFill: typeof node.fill === "string" ? node.fill : void 0
16172
+ });
16173
+ }
16174
+ function parseColor$1(c) {
16175
+ if (!c) return null;
16176
+ const s = c.trim();
16177
+ const hex = s.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
16178
+ if (hex) {
16179
+ let h = hex[1];
16180
+ if (h.length === 3) h = h.split("").map((x) => x + x).join("");
16181
+ return {
16182
+ r: parseInt(h.slice(0, 2), 16),
16183
+ g: parseInt(h.slice(2, 4), 16),
16184
+ b: parseInt(h.slice(4, 6), 16)
16185
+ };
16186
+ }
16187
+ const rgb = s.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
16188
+ if (rgb) return { r: +rgb[1], g: +rgb[2], b: +rgb[3] };
16189
+ return null;
16190
+ }
16191
+ function buildOverlay(b, idx) {
16192
+ const c = parseColor$1(b.hintFill);
16193
+ const lum = c ? (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) / 255 : 0.2;
16194
+ const isLightGlass = lum < 0.5;
16195
+ const baseFill = isLightGlass ? "rgba(245,245,245,0.68)" : "rgba(30,30,30,0.62)";
16196
+ return {
16197
+ id: `${OVERLAY_ID_PREFIX}${idx}`,
16198
+ type: "shape",
16199
+ shapeType: "rounded-rect",
16200
+ left: b.left,
16201
+ top: b.top,
16202
+ width: b.width,
16203
+ height: b.height,
16204
+ cornerRadius: 0,
16205
+ fill: baseFill,
16206
+ stroke: "transparent",
16207
+ strokeWidth: 0,
16208
+ opacity: 1,
16209
+ selectable: false,
16210
+ locked: true,
16211
+ visible: true,
16212
+ scaleX: 1,
16213
+ scaleY: 1
16214
+ };
16215
+ }
16216
+ function injectPreviewBlur(config, options) {
16217
+ const cloned = typeof structuredClone === "function" ? structuredClone(config) : JSON.parse(JSON.stringify(config));
16218
+ const extraBase = (options == null ? void 0 : options.extraElementBaseIds) && options.extraElementBaseIds.size > 0 ? options.extraElementBaseIds : void 0;
16219
+ const extraExact = (options == null ? void 0 : options.extraElementExactIds) && options.extraElementExactIds.size > 0 ? options.extraElementExactIds : void 0;
16220
+ let idx = 0;
16221
+ for (const page of cloned.pages || []) {
16222
+ const children = page.children || page.elements;
16223
+ if (!Array.isArray(children)) continue;
16224
+ const overlays = [];
16225
+ for (const child of children) {
16226
+ collectOverlays(child, 0, 0, false, extraBase, extraExact, overlays);
16227
+ }
16228
+ if (overlays.length === 0) continue;
16229
+ for (const b of overlays) {
16230
+ children.push(buildOverlay(b, idx++));
16231
+ }
16232
+ }
16233
+ return cloned;
16234
+ }
16235
+ function hasAnyPreviewBlur(config) {
16236
+ function walk(node) {
16237
+ if (!node || typeof node !== "object") return false;
16238
+ if (node.previewBlur === true) return true;
16239
+ const children = node.children || node.elements;
16240
+ if (Array.isArray(children)) {
16241
+ for (const c of children) if (walk(c)) return true;
16242
+ }
16243
+ return false;
16244
+ }
16245
+ for (const page of config.pages || []) if (walk(page)) return true;
16246
+ return false;
16247
+ }
16248
+ function stripFieldPrefix(s) {
16249
+ return s.startsWith("field_") ? s.slice("field_".length) : s;
16250
+ }
16251
+ function addAllToSet(val, out) {
16252
+ if (!val) return false;
16253
+ if (Array.isArray(val)) {
16254
+ for (const v of val) out.add(v);
16255
+ return val.length > 0;
16256
+ }
16257
+ out.add(val);
16258
+ return true;
16259
+ }
16260
+ function resolveBlurElementExactIdsFromFlatFormKeys(config, flatFormKeys, options) {
16261
+ const cloneIdMap = (config == null ? void 0 : config.__cloneIdMap) || {};
16262
+ if (!cloneIdMap || typeof cloneIdMap !== "object") return [];
16263
+ const bindings = (options == null ? void 0 : options.bindings) ?? "value";
16264
+ const out = /* @__PURE__ */ new Set();
16265
+ for (const rawId of flatFormKeys) {
16266
+ if (typeof rawId !== "string" || !rawId) continue;
16267
+ if (bindings === "value" && !rawId.endsWith("_value")) continue;
16268
+ const candidates = [rawId];
16269
+ if (rawId.startsWith("field_")) {
16270
+ const topStripped = stripFieldPrefix(rawId);
16271
+ candidates.push(topStripped);
16272
+ const nestedIdx = topStripped.indexOf("_field_");
16273
+ if (nestedIdx >= 0) {
16274
+ const nestedStripped = topStripped.slice(0, nestedIdx + 1) + topStripped.slice(nestedIdx + 1 + "field_".length);
16275
+ candidates.push(nestedStripped);
16276
+ }
16277
+ }
16278
+ for (const k of candidates) {
16279
+ if (k in cloneIdMap) {
16280
+ if (addAllToSet(cloneIdMap[k], out)) break;
16281
+ }
16282
+ }
16283
+ }
16284
+ return Array.from(out);
16285
+ }
16286
+ const previewBlur = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
16287
+ __proto__: null,
16288
+ hasAnyPreviewBlur,
16289
+ injectPreviewBlur,
16290
+ resolveBlurElementExactIdsFromFlatFormKeys
16291
+ }, Symbol.toStringTag, { value: "Module" }));
16136
16292
  const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
16137
16293
  function computeFontSignature(config) {
16138
16294
  var _a;
@@ -16226,7 +16382,9 @@ function PixldocsPreview(props) {
16226
16382
  frostedBlur = true,
16227
16383
  frostedBlurOptions,
16228
16384
  blurFieldIds,
16229
- blurElementExactIds
16385
+ blurElementExactIds,
16386
+ blurFlatFormKeys,
16387
+ blurFlatFormKeyOptions
16230
16388
  } = props;
16231
16389
  react.useEffect(() => {
16232
16390
  setPackageApiUrl(imageProxyUrl);
@@ -16347,8 +16505,26 @@ function PixldocsPreview(props) {
16347
16505
  [blurFieldIds ? blurFieldIds.join("|") : ""]
16348
16506
  );
16349
16507
  const blurExactSet = react.useMemo(
16350
- () => blurElementExactIds && blurElementExactIds.length ? new Set(blurElementExactIds) : void 0,
16351
- [blurElementExactIds ? blurElementExactIds.join("|") : ""]
16508
+ () => {
16509
+ const merged = /* @__PURE__ */ new Set();
16510
+ if (blurElementExactIds) for (const id of blurElementExactIds) merged.add(id);
16511
+ if (config && blurFlatFormKeys && blurFlatFormKeys.length) {
16512
+ for (const id of resolveBlurElementExactIdsFromFlatFormKeys(
16513
+ config,
16514
+ blurFlatFormKeys,
16515
+ blurFlatFormKeyOptions
16516
+ )) {
16517
+ merged.add(id);
16518
+ }
16519
+ }
16520
+ return merged.size > 0 ? merged : void 0;
16521
+ },
16522
+ [
16523
+ blurElementExactIds ? blurElementExactIds.join("|") : "",
16524
+ blurFlatFormKeys ? blurFlatFormKeys.join("|") : "",
16525
+ blurFlatFormKeyOptions == null ? void 0 : blurFlatFormKeyOptions.bindings,
16526
+ config
16527
+ ]
16352
16528
  );
16353
16529
  const frostedBounds = react.useMemo(
16354
16530
  () => frostedBlur ? computeFrostedBoundsForPage(config, pageIndex, blurBaseSet, blurExactSet) : [],
@@ -16593,9 +16769,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16593
16769
  }
16594
16770
  return svgString;
16595
16771
  }
16596
- const resolvedPackageVersion = "0.5.186";
16772
+ const resolvedPackageVersion = "0.5.188";
16597
16773
  const PACKAGE_VERSION = resolvedPackageVersion;
16598
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.186";
16774
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.188";
16599
16775
  const roundParityValue = (value) => {
16600
16776
  if (typeof value !== "number") return value;
16601
16777
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -16894,8 +17070,8 @@ class PixldocsRenderer {
16894
17070
  if (shouldWatermark) {
16895
17071
  const { injectWatermark } = await Promise.resolve().then(() => require("./canvasWatermark-B0ab38Ok.cjs"));
16896
17072
  configToRender = injectWatermark(configToRender, watermarkOptions);
16897
- const { injectPreviewBlur } = await Promise.resolve().then(() => require("./previewBlur-D2aWUrre.cjs"));
16898
- configToRender = injectPreviewBlur(configToRender);
17073
+ const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
17074
+ configToRender = injectPreviewBlur2(configToRender);
16899
17075
  }
16900
17076
  return this.renderAllPages(configToRender, renderOpts);
16901
17077
  }
@@ -16953,8 +17129,8 @@ class PixldocsRenderer {
16953
17129
  if (shouldWatermark) {
16954
17130
  const { injectWatermark } = await Promise.resolve().then(() => require("./canvasWatermark-B0ab38Ok.cjs"));
16955
17131
  configToRender = injectWatermark(configToRender, watermarkOptions);
16956
- const { injectPreviewBlur } = await Promise.resolve().then(() => require("./previewBlur-D2aWUrre.cjs"));
16957
- configToRender = injectPreviewBlur(configToRender);
17132
+ const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
17133
+ configToRender = injectPreviewBlur2(configToRender);
16958
17134
  }
16959
17135
  return this.renderAllPageSvgs(configToRender);
16960
17136
  }
@@ -16997,8 +17173,8 @@ class PixldocsRenderer {
16997
17173
  if (shouldWatermark) {
16998
17174
  const { injectWatermark } = await Promise.resolve().then(() => require("./canvasWatermark-B0ab38Ok.cjs"));
16999
17175
  configToRender = injectWatermark(configToRender, watermarkOptions);
17000
- const { injectPreviewBlur } = await Promise.resolve().then(() => require("./previewBlur-D2aWUrre.cjs"));
17001
- configToRender = injectPreviewBlur(configToRender);
17176
+ const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
17177
+ configToRender = injectPreviewBlur2(configToRender);
17002
17178
  }
17003
17179
  return this.renderPdfViaClientExport(configToRender, {
17004
17180
  title: title ?? resolved.config.name,
@@ -17103,7 +17279,7 @@ class PixldocsRenderer {
17103
17279
  await this.waitForCanvasScene(container, cloned, i);
17104
17280
  }
17105
17281
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
17106
- const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-Dh3el4F0.cjs"));
17282
+ const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-B2Cf-9eT.cjs"));
17107
17283
  const prepared = preparePagesForExport(
17108
17284
  cloned.pages,
17109
17285
  canvasWidth,
@@ -19248,7 +19424,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
19248
19424
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
19249
19425
  sanitizeSvgTreeForPdf(svgToDraw);
19250
19426
  try {
19251
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-Dh3el4F0.cjs"));
19427
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-B2Cf-9eT.cjs"));
19252
19428
  try {
19253
19429
  await logTextMeasurementDiagnostic(svgToDraw);
19254
19430
  } catch {
@@ -19598,7 +19774,6 @@ exports.applyThemeToConfig = applyThemeToConfig;
19598
19774
  exports.assemblePdfFromSvgs = assemblePdfFromSvgs;
19599
19775
  exports.awaitFontsForConfig = awaitFontsForConfig;
19600
19776
  exports.bakeEdgeFade = bakeEdgeFade;
19601
- exports.baseId = baseId;
19602
19777
  exports.buildRoundedTrianglePath = buildRoundedTrianglePath;
19603
19778
  exports.canvasImageLoader = canvasImageLoader;
19604
19779
  exports.captureFabricCanvasSvgForPdf = captureFabricCanvasSvgForPdf;
@@ -19622,7 +19797,9 @@ exports.getPublishedTemplate = getPublishedTemplate;
19622
19797
  exports.getRoundedRectRadii = getRoundedRectRadii;
19623
19798
  exports.getTemplateForm = getTemplateForm;
19624
19799
  exports.getTrianglePoints = getTrianglePoints;
19800
+ exports.hasAnyPreviewBlur = hasAnyPreviewBlur;
19625
19801
  exports.hasEdgeFade = hasEdgeFade;
19802
+ exports.injectPreviewBlur = injectPreviewBlur;
19626
19803
  exports.isBundledAssetUrl = isBundledAssetUrl;
19627
19804
  exports.isElement = isElement;
19628
19805
  exports.isFontAvailable = isFontAvailable;
@@ -19634,6 +19811,7 @@ exports.normalizeFontFamily = normalizeFontFamily;
19634
19811
  exports.normalizeShapeType = normalizeShapeType;
19635
19812
  exports.parseTextMarkdown = parseTextMarkdown;
19636
19813
  exports.renderSmartElementToSvg = renderSmartElementToSvg;
19814
+ exports.resolveBlurElementExactIdsFromFlatFormKeys = resolveBlurElementExactIdsFromFlatFormKeys;
19637
19815
  exports.resolveFontWeight = resolveFontWeight;
19638
19816
  exports.resolveFromForm = resolveFromForm;
19639
19817
  exports.resolveTemplateData = resolveTemplateData;
@@ -19642,4 +19820,4 @@ exports.setAutoShrinkDebug = setAutoShrinkDebug;
19642
19820
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
19643
19821
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
19644
19822
  exports.warmTemplateFromForm = warmTemplateFromForm;
19645
- //# sourceMappingURL=index-_ogxqALr.cjs.map
19823
+ //# sourceMappingURL=index-0rk-MRGy.cjs.map