@pixldocs/canvas-renderer 0.5.179 → 0.5.181

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
@@ -206,6 +206,48 @@ const result = await renderer.renderPdf(templateConfig, { title: 'My Doc' });
206
206
 
207
207
  ## API Reference
208
208
 
209
+ ### Form schema shape (V2 `sectionState`)
210
+
211
+ `sectionState` accepts entries for both **repeatable sections** and
212
+ **repeatable pages**. Both look the same on the wire — an array of entry
213
+ objects keyed by the section/page `id`:
214
+
215
+ ```ts
216
+ sectionState = {
217
+ // single section
218
+ personal: { name: 'Jane' },
219
+
220
+ // repeatable section (e.g. Experience)
221
+ experience: [
222
+ { title: 'Designer', company: 'Acme' },
223
+ { title: 'Lead', company: 'Beta' },
224
+ ],
225
+
226
+ // repeatable PAGE (e.g. Photo Page) — same shape as above, but at render
227
+ // time the bound template page is cloned once per entry.
228
+ photo_page: [
229
+ { caption: 'Cover' },
230
+ { caption: 'Back' },
231
+ ],
232
+ }
233
+ ```
234
+
235
+ Fetch the schema from the Pixldocs form API to discover what keys/fields are
236
+ expected. The response exposes repeatable pages as a dedicated
237
+ `schema.repeatablePages[]` array (each entry mirrors a repeatable section:
238
+ `id`, `label`, `order`, `templateKeyPrefix`, `minEntries`, `maxEntries`,
239
+ `entryFields`).
240
+
241
+ ```ts
242
+ const res = await fetch(
243
+ `${SUPABASE_URL}/functions/v1/form-api?form_schema_id=${SCHEMA_ID}&action=schema`,
244
+ { headers: { apikey: SUPABASE_ANON_KEY } },
245
+ ).then((r) => r.json());
246
+
247
+ res.schema.sections // single + repeatable sections
248
+ res.schema.repeatablePages // repeatable pages (clone-per-entry)
249
+ ```
250
+
209
251
  ### `PixldocsPreview` (React Component)
210
252
 
211
253
  | Prop | Type | Default | Description |
@@ -302,3 +344,30 @@ const { config } = await resolveTemplateData({
302
344
  ## License
303
345
 
304
346
  UNLICENSED — Private package for Pixldocs ecosystem only.
347
+
348
+ ---
349
+
350
+ ## Preview Blur (anti-screenshot)
351
+
352
+ Set `previewBlur: true` on any text or image element in your config to redact
353
+ it in watermarked previews — useful for protecting paid content (e.g. biodata
354
+ reference rows) from screenshot theft. The block characters (`█`) replace
355
+ the text content so layout/wrap stays identical, but content is unreadable.
356
+
357
+ Preview-blur is gated identically to the watermark: it only runs when
358
+ `watermark === true` (or, by default, when `template.price > 0` and the user
359
+ hasn't paid). Clean / paid downloads always render the original content.
360
+
361
+ ```ts
362
+ // In your template config
363
+ {
364
+ id: 'reference-1-name',
365
+ type: 'text',
366
+ text: 'John Doe',
367
+ previewBlur: true, // ← redacted in preview, full in paid download
368
+ ...
369
+ }
370
+ ```
371
+
372
+ No API change is required on the consumer side — the same `renderFromForm`,
373
+ `renderPdfFromForm`, etc. respect the flag automatically.
@@ -466,9 +466,19 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
466
466
  const gap = group.stackSpacing ?? 8;
467
467
  const padTop = group.paddingTop ?? 0;
468
468
  const padLeft = group.paddingLeft ?? 0;
469
+ const padRight = group.paddingRight ?? 0;
470
+ const padBottom = group.paddingBottom ?? 0;
471
+ const justify = group.justifyContent ?? "start";
472
+ const align = group.alignItems ?? "start";
473
+ const isVertical = isVerticalStackLayoutMode(mode);
469
474
  const kids = group.children ?? [];
470
475
  const out = /* @__PURE__ */ new Map();
471
- if (isVerticalStackLayoutMode(mode)) {
476
+ const sizes = /* @__PURE__ */ new Map();
477
+ for (const c of kids) {
478
+ const b = getNodeBounds(c, pageChildren);
479
+ sizes.set(c.id, { width: b.width, height: b.height });
480
+ }
481
+ if (isVertical) {
472
482
  let prevBottom = padTop;
473
483
  let firstSeen = false;
474
484
  for (let i = 0; i < kids.length; i++) {
@@ -480,7 +490,7 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
480
490
  const effectiveTop = !firstSeen ? padTop + storedTop + mTop : prevBottom + gap + storedTop + mTop;
481
491
  firstSeen = true;
482
492
  out.set(child.id, { top: effectiveTop, left: padLeft + storedLeft + mLeft });
483
- const h = getNodeBounds(child, pageChildren).height;
493
+ const h = sizes.get(child.id).height;
484
494
  prevBottom = effectiveTop + h + (child.marginBottom ?? 0);
485
495
  }
486
496
  } else {
@@ -495,10 +505,99 @@ function resolveStackGroupEffectivePositions(group, pageChildren, options) {
495
505
  const effectiveLeft = !firstSeen ? padLeft + storedLeft + mLeft : prevRight + gap + storedLeft + mLeft;
496
506
  firstSeen = true;
497
507
  out.set(child.id, { top: padTop + storedTop + mTop, left: effectiveLeft });
498
- const w = getNodeBounds(child, pageChildren).width;
508
+ const w = sizes.get(child.id).width;
499
509
  prevRight = effectiveLeft + w + (child.marginRight ?? 0);
500
510
  }
501
511
  }
512
+ const containerW = typeof group.width === "number" ? group.width : void 0;
513
+ const containerH = typeof group.height === "number" ? group.height : void 0;
514
+ const mainContainer = isVertical ? containerH : containerW;
515
+ const crossContainer = isVertical ? containerW : containerH;
516
+ if (align !== "start" && crossContainer != null && kids.length > 0) {
517
+ const crossPad0 = isVertical ? padLeft : padTop;
518
+ const crossPadEnd = isVertical ? padRight : padBottom;
519
+ const innerCross = Math.max(0, crossContainer - crossPad0 - crossPadEnd);
520
+ for (const child of kids) {
521
+ const pos = out.get(child.id);
522
+ if (!pos) continue;
523
+ const sz = sizes.get(child.id);
524
+ const childCross = isVertical ? sz.width : sz.height;
525
+ let crossPos;
526
+ if (align === "stretch") {
527
+ crossPos = crossPad0;
528
+ } else if (align === "center") {
529
+ crossPos = crossPad0 + (innerCross - childCross) / 2;
530
+ } else {
531
+ crossPos = crossPad0 + (innerCross - childCross);
532
+ }
533
+ if (isVertical) {
534
+ out.set(child.id, { top: pos.top, left: crossPos });
535
+ } else {
536
+ out.set(child.id, { top: crossPos, left: pos.left });
537
+ }
538
+ }
539
+ }
540
+ if (justify !== "start" && mainContainer != null && kids.length > 0) {
541
+ const mainPad0 = isVertical ? padTop : padLeft;
542
+ const mainPadEnd = isVertical ? padBottom : padRight;
543
+ const innerMain = Math.max(0, mainContainer - mainPad0 - mainPadEnd);
544
+ let totalMain = 0;
545
+ for (let i = 0; i < kids.length; i++) {
546
+ const child = kids[i];
547
+ const sz = sizes.get(child.id);
548
+ totalMain += isVertical ? sz.height : sz.width;
549
+ const marginStart = isVertical ? child.marginTop ?? 0 : child.marginLeft ?? 0;
550
+ const marginEnd = isVertical ? child.marginBottom ?? 0 : child.marginRight ?? 0;
551
+ totalMain += marginStart + marginEnd;
552
+ }
553
+ const baseGapTotal = gap * Math.max(0, kids.length - 1);
554
+ const free = innerMain - totalMain - baseGapTotal;
555
+ let offset = 0;
556
+ let extraGap = 0;
557
+ if (free > 0) {
558
+ switch (justify) {
559
+ case "center":
560
+ offset = free / 2;
561
+ break;
562
+ case "end":
563
+ offset = free;
564
+ break;
565
+ case "space-between":
566
+ if (kids.length > 1) extraGap = free / (kids.length - 1);
567
+ else offset = free / 2;
568
+ break;
569
+ case "space-around":
570
+ if (kids.length > 0) {
571
+ extraGap = free / kids.length;
572
+ offset = extraGap / 2;
573
+ }
574
+ break;
575
+ case "space-evenly":
576
+ extraGap = free / (kids.length + 1);
577
+ offset = extraGap;
578
+ break;
579
+ }
580
+ }
581
+ if (offset !== 0 || extraGap !== 0) {
582
+ let cursor = mainPad0 + offset;
583
+ for (let i = 0; i < kids.length; i++) {
584
+ const child = kids[i];
585
+ const sz = sizes.get(child.id);
586
+ const marginStart = isVertical ? child.marginTop ?? 0 : child.marginLeft ?? 0;
587
+ const marginEnd = isVertical ? child.marginBottom ?? 0 : child.marginRight ?? 0;
588
+ const main = isVertical ? sz.height : sz.width;
589
+ const pos = out.get(child.id);
590
+ if (!pos) continue;
591
+ const startMain = cursor + marginStart;
592
+ if (isVertical) {
593
+ out.set(child.id, { top: startMain, left: pos.left });
594
+ } else {
595
+ out.set(child.id, { top: pos.top, left: startMain });
596
+ }
597
+ cursor = startMain + main + marginEnd + gap + extraGap;
598
+ }
599
+ }
600
+ }
502
601
  return out;
503
602
  }
504
603
  function groupBoundsFromChildren(group, pageChildren, options) {
@@ -7599,7 +7698,28 @@ const PageCanvas = forwardRef(
7599
7698
  const element = id ? elementById.get(id) : void 0;
7600
7699
  if (!element) return;
7601
7700
  if (element.overflowPolicy === "auto-shrink") {
7701
+ try {
7702
+ const measured = createText(element);
7703
+ const newFontSize = measured.fontSize;
7704
+ const newWidth = measured.width;
7705
+ const currentFontSize = obj.fontSize;
7706
+ if (typeof newFontSize === "number" && newFontSize > 0 && newFontSize !== currentFontSize) {
7707
+ obj.set({ fontSize: newFontSize });
7708
+ if (typeof newWidth === "number" && newWidth > 0) {
7709
+ obj.width = newWidth;
7710
+ }
7711
+ obj.initDimensions();
7712
+ obj.setCoords();
7713
+ didReflow = true;
7714
+ }
7715
+ } catch {
7716
+ }
7602
7717
  obj.dirty = true;
7718
+ try {
7719
+ obj._forceClearCache = true;
7720
+ if (typeof obj._clearCache === "function") obj._clearCache();
7721
+ } catch {
7722
+ }
7603
7723
  return;
7604
7724
  }
7605
7725
  const targetWidth = Math.max(1, Number(element.width) > 0 ? Number(element.width) : Number(obj.width ?? 200));
@@ -9212,9 +9332,26 @@ const PageCanvas = forwardRef(
9212
9332
  const needsCropGroupFadeUpdate = isCropGroup2 && fadeKeyChanged;
9213
9333
  const hadUrlBefore = storedImageUrl && String(storedImageUrl).trim() !== "";
9214
9334
  if (!hasUrl && hadUrlBefore) {
9215
- const placeholder = isCropGroup2 ? createImagePlaceholderForGroup(element) : createImagePlaceholder(element);
9335
+ const resolvedSizeImg = (pageChildren == null ? void 0 : pageChildren.length) ? getNodeBounds(element, pageChildren) : { width: typeof element.width === "number" ? element.width : 200, height: typeof element.height === "number" ? element.height : 50 };
9336
+ const hasExplicitSize = typeof element.width === "number" && Number.isFinite(element.width) && element.width > 0 && typeof element.height === "number" && Number.isFinite(element.height) && element.height > 0;
9337
+ const minVisiblePlaceholder = hasExplicitSize ? 1 : 20;
9338
+ const nextWidth = Math.max(minVisiblePlaceholder, Number(resolvedSizeImg.width) || 200);
9339
+ const nextHeight = Math.max(minVisiblePlaceholder, Number(resolvedSizeImg.height) || 50);
9340
+ const storePosImg = pageChildren ? (() => {
9341
+ const node = findNodeById(pageChildren, element.id);
9342
+ return node ? getAbsoluteBounds(node, pageChildren) : { left: element.left ?? 0, top: element.top ?? 0 };
9343
+ })() : { left: element.left ?? 0, top: element.top ?? 0 };
9344
+ const elementForPlaceholder = { ...element, width: nextWidth, height: nextHeight };
9345
+ const placeholder = isCropGroup2 ? createImagePlaceholderForGroup(elementForPlaceholder) : createImagePlaceholder(elementForPlaceholder);
9216
9346
  setObjectData(placeholder, element.id);
9217
9347
  placeholder.__imageSrc = "";
9348
+ placeholder.set({
9349
+ left: storePosImg.left + nextWidth / 2,
9350
+ top: storePosImg.top + nextHeight / 2,
9351
+ originX: "center",
9352
+ originY: "center"
9353
+ });
9354
+ placeholder.setCoords();
9218
9355
  const idx = fc.getObjects().indexOf(existingObj);
9219
9356
  fc.remove(existingObj);
9220
9357
  if (idx >= 0) {
@@ -11738,7 +11875,8 @@ function formDefSectionsToInferred(schemaSections, repeatableNodeMap) {
11738
11875
  // Honor minEntries: 0 — start with no entries, user adds via "+ Add" button.
11739
11876
  initialEntryCount: minEntries,
11740
11877
  parentId,
11741
- entryNameFieldKey: def.entryNameFieldKey
11878
+ entryNameFieldKey: def.entryNameFieldKey,
11879
+ isRepeatablePage: def.isRepeatablePage
11742
11880
  };
11743
11881
  if (treeNodeId) section.treeNodeId = treeNodeId;
11744
11882
  sections.push(section);
@@ -16342,9 +16480,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16342
16480
  }
16343
16481
  return svgString;
16344
16482
  }
16345
- const resolvedPackageVersion = "0.5.179";
16483
+ const resolvedPackageVersion = "0.5.181";
16346
16484
  const PACKAGE_VERSION = resolvedPackageVersion;
16347
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.179";
16485
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.181";
16348
16486
  const roundParityValue = (value) => {
16349
16487
  if (typeof value !== "number") return value;
16350
16488
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -16643,6 +16781,8 @@ class PixldocsRenderer {
16643
16781
  if (shouldWatermark) {
16644
16782
  const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
16645
16783
  configToRender = injectWatermark(configToRender, watermarkOptions);
16784
+ const { injectPreviewBlur } = await import("./previewBlur-Dj6dSSNO.js");
16785
+ configToRender = injectPreviewBlur(configToRender);
16646
16786
  }
16647
16787
  return this.renderAllPages(configToRender, renderOpts);
16648
16788
  }
@@ -16700,6 +16840,8 @@ class PixldocsRenderer {
16700
16840
  if (shouldWatermark) {
16701
16841
  const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
16702
16842
  configToRender = injectWatermark(configToRender, watermarkOptions);
16843
+ const { injectPreviewBlur } = await import("./previewBlur-Dj6dSSNO.js");
16844
+ configToRender = injectPreviewBlur(configToRender);
16703
16845
  }
16704
16846
  return this.renderAllPageSvgs(configToRender);
16705
16847
  }
@@ -16742,6 +16884,8 @@ class PixldocsRenderer {
16742
16884
  if (shouldWatermark) {
16743
16885
  const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
16744
16886
  configToRender = injectWatermark(configToRender, watermarkOptions);
16887
+ const { injectPreviewBlur } = await import("./previewBlur-Dj6dSSNO.js");
16888
+ configToRender = injectPreviewBlur(configToRender);
16745
16889
  }
16746
16890
  return this.renderPdfViaClientExport(configToRender, {
16747
16891
  title: title ?? resolved.config.name,
@@ -16846,7 +16990,7 @@ class PixldocsRenderer {
16846
16990
  await this.waitForCanvasScene(container, cloned, i);
16847
16991
  }
16848
16992
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
16849
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-FmQQMHmX.js");
16993
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-KXmgWTkZ.js");
16850
16994
  const prepared = preparePagesForExport(
16851
16995
  cloned.pages,
16852
16996
  canvasWidth,
@@ -18991,7 +19135,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
18991
19135
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
18992
19136
  sanitizeSvgTreeForPdf(svgToDraw);
18993
19137
  try {
18994
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-FmQQMHmX.js");
19138
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-KXmgWTkZ.js");
18995
19139
  try {
18996
19140
  await logTextMeasurementDiagnostic(svgToDraw);
18997
19141
  } catch {
@@ -19387,4 +19531,4 @@ export {
19387
19531
  collectFontDescriptorsFromConfig as y,
19388
19532
  collectFontsFromConfig as z
19389
19533
  };
19390
- //# sourceMappingURL=index-C3W71an-.js.map
19534
+ //# sourceMappingURL=index-Ck5VHk_Q.js.map