@pixldocs/canvas-renderer 0.5.496 → 0.5.498

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.
@@ -94,6 +94,15 @@ const generateId = (prefix = "el") => {
94
94
  return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 9)}-${Math.random().toString(36).substr(2, 9)}`;
95
95
  };
96
96
  const createDefaultElement = (partial) => {
97
+ const loose = partial;
98
+ if (loose.type === "frame" || loose.type === "group") {
99
+ return createDefaultGroup({
100
+ layoutMode: loose.layout,
101
+ ...partial,
102
+ type: "group",
103
+ children: Array.isArray(loose.children) ? loose.children : []
104
+ });
105
+ }
97
106
  const isText = partial.type === "text";
98
107
  const base = {
99
108
  left: 48,
@@ -111,7 +120,7 @@ const createDefaultElement = (partial) => {
111
120
  originY: "top",
112
121
  opacity: 1,
113
122
  fill: "#e2e8f0",
114
- ...isText ? {} : { stroke: "#64748b", strokeWidth: 1 },
123
+ ...isText ? {} : { stroke: "transparent", strokeWidth: 0 },
115
124
  visible: true,
116
125
  selectable: true,
117
126
  evented: true,
@@ -957,49 +966,15 @@ function worldPointToParentLocal(worldX, worldY, parent, pageChildren, resolveGr
957
966
  );
958
967
  return { left: local.x, top: local.y };
959
968
  }
960
- function unbakeChildrenToGroupLocal(group) {
961
- const angle = getGroupAngleDeg(group);
962
- if (Math.abs(angle) < 0.01) return group;
963
- const { width, height } = getGroupFrameSize(group);
964
- const cx = width / 2;
965
- const cy = height / 2;
966
- const inv = rotateAroundAffDeg(-angle, cx, cy);
967
- const kids = (group.children ?? []).map((child) => {
968
- const p = applyAff(inv, child.left ?? 0, child.top ?? 0);
969
- const childAngle = typeof child.angle === "number" ? child.angle : 0;
970
- const localAngle = normalizeAngleDeg(childAngle - angle);
971
- if (isGroup(child)) {
972
- const relocated = {
973
- ...child,
974
- left: p.x,
975
- top: p.y,
976
- angle: localAngle
977
- };
978
- return Math.abs(getGroupAngleDeg(relocated)) > 0.01 ? unbakeChildrenToGroupLocal(relocated) : relocated;
979
- }
980
- return {
981
- ...child,
982
- left: p.x,
983
- top: p.y,
984
- angle: localAngle
985
- };
986
- });
987
- return { ...group, children: kids };
988
- }
989
969
  function migratePageTreeToGroupOwnedTransforms(nodes) {
990
970
  return nodes.map((node) => {
991
971
  if (!isGroup(node)) return node;
992
972
  const g = node;
993
- let next = {
973
+ const next = {
994
974
  ...g,
995
975
  children: migratePageTreeToGroupOwnedTransforms(g.children ?? [])
996
976
  };
997
- if (isRotatableGroup(next) && Math.abs(getGroupAngleDeg(next)) > 0.01) {
998
- if (g.groupTransformModel !== "owned-v1") {
999
- next = unbakeChildrenToGroupLocal(next);
1000
- next.groupTransformModel = "owned-v1";
1001
- }
1002
- } else if (isRotatableGroup(next)) {
977
+ if (isRotatableGroup(next)) {
1003
978
  next.groupTransformModel = "owned-v1";
1004
979
  }
1005
980
  return next;
@@ -2672,7 +2647,7 @@ let sizeBaseSnapshot = null;
2672
2647
  const reflowPagesFromBase = (base, W, H) => {
2673
2648
  const sx = base.width ? W / base.width : 1;
2674
2649
  const sy = base.height ? H / base.height : 1;
2675
- const fscale = Math.sqrt(Math.max(0.01, sx * sy));
2650
+ const fscale = Math.max(0.01, Math.min(sx, sy));
2676
2651
  const adapt = layoutAdaptationFor(base.width, base.height, W, H);
2677
2652
  const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
2678
2653
  const scaleNode = (node) => scaleNodeForReflow(node, sx, sy, fscale);
@@ -3221,7 +3196,7 @@ const useEditorStore = create((set, get) => ({
3221
3196
  setCanvasSize: (width, height) => set((state) => {
3222
3197
  const oldW = state.canvas.width || width, oldH = state.canvas.height || height;
3223
3198
  const sx = oldW ? width / oldW : 1, sy = oldH ? height / oldH : 1;
3224
- const fscale = Math.sqrt(Math.max(0.01, sx * sy));
3199
+ const fscale = Math.max(0.01, Math.min(sx, sy));
3225
3200
  const n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
3226
3201
  const scaleNode = (node) => {
3227
3202
  const el = { ...node };
@@ -5877,6 +5852,26 @@ const setupFontLoadingListener = (canvas, afterRerender, options = {}) => {
5877
5852
  }
5878
5853
  };
5879
5854
  };
5855
+ const GLOBAL_KEY = "__PIXLDOCS_IMAGE_FAILURES__";
5856
+ const MAX_RECORDED = 50;
5857
+ function host() {
5858
+ return typeof globalThis === "undefined" ? null : globalThis;
5859
+ }
5860
+ function recordImageLoadFailure(failure) {
5861
+ try {
5862
+ const g = host();
5863
+ if (!g) return;
5864
+ const list = g[GLOBAL_KEY] ?? [];
5865
+ if (list.length >= MAX_RECORDED) return;
5866
+ list.push({
5867
+ id: String(failure.id ?? ""),
5868
+ url: String(failure.url ?? "").slice(0, 500),
5869
+ reason: String(failure.reason ?? "").slice(0, 300)
5870
+ });
5871
+ g[GLOBAL_KEY] = list;
5872
+ } catch {
5873
+ }
5874
+ }
5880
5875
  const EMPTY_IMAGE_PLACEHOLDER_DATA_URL = "data:image/png;base64,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";
5881
5876
  const svgTextCache = /* @__PURE__ */ new Map();
5882
5877
  const htmlImageCache = /* @__PURE__ */ new Map();
@@ -6368,8 +6363,13 @@ async function loadImageAsync(element, placeholder, fc, fabricRef, syncLockedRef
6368
6363
  });
6369
6364
  const imgLoadOptions = url.startsWith("data:") || url.startsWith("blob:") ? {} : { crossOrigin: "anonymous" };
6370
6365
  const img = await fabric.FabricImage.fromURL(url, imgLoadOptions);
6371
- if (!fabricRef.current) return;
6366
+ if (fabricRef && !fabricRef.current) return;
6372
6367
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat);
6368
+ const natW = img.width ?? 0;
6369
+ const natH = img.height ?? 0;
6370
+ if (natW <= 1 && natH <= 1 && Number(element.width) > 8 && Number(element.height) > 8) {
6371
+ throw new Error(`image decoded to ${natW}x${natH} — treating as failed load`);
6372
+ }
6373
6373
  const isHidden = !element.visible;
6374
6374
  img.set({
6375
6375
  originX: "left",
@@ -6567,9 +6567,116 @@ async function loadImageAsync(element, placeholder, fc, fabricRef, syncLockedRef
6567
6567
  proxiedUrl: getProxiedImageUrl(imageUrl).slice(0, 240),
6568
6568
  error: error instanceof Error ? error.message : String(error)
6569
6569
  });
6570
+ recordImageLoadFailure({
6571
+ id: String(element.id ?? ""),
6572
+ url: imageUrl,
6573
+ reason: error instanceof Error ? error.message : String(error)
6574
+ });
6575
+ if (fc.__pixldocsEditMode === true) {
6576
+ try {
6577
+ markPlaceholderAsBrokenImage(placeholder);
6578
+ fc.requestRenderAll();
6579
+ } catch {
6580
+ }
6581
+ }
6570
6582
  return;
6571
6583
  }
6572
6584
  }
6585
+ function markPlaceholderAsBrokenImage(placeholder) {
6586
+ if (!(placeholder instanceof fabric.Group)) return;
6587
+ const group = placeholder;
6588
+ if (group.__pixldocsBrokenImage) return;
6589
+ group.__pixldocsBrokenImage = true;
6590
+ const frameChild = group.getObjects().find(
6591
+ (o) => o.__isPlaceholderFrame
6592
+ );
6593
+ const w = Math.max(1, (frameChild == null ? void 0 : frameChild.width) ?? group.width ?? 1);
6594
+ const h = Math.max(1, (frameChild == null ? void 0 : frameChild.height) ?? group.height ?? 1);
6595
+ const center = group.getCenterPoint();
6596
+ const cx = center.x;
6597
+ const cy = center.y;
6598
+ const prevPose = { left: group.left, top: group.top, width: group.width, height: group.height };
6599
+ const bg = new fabric.Rect({
6600
+ originX: "center",
6601
+ originY: "center",
6602
+ left: cx,
6603
+ top: cy,
6604
+ width: w,
6605
+ height: h,
6606
+ fill: "rgba(148, 163, 184, 0.12)",
6607
+ stroke: "#94A3B8",
6608
+ strokeWidth: 1.5,
6609
+ strokeDashArray: [6, 4]
6610
+ });
6611
+ const s = Math.max(16, Math.min(w, h) * 0.28);
6612
+ const glyphStroke = { stroke: "#64748B", strokeWidth: Math.max(1.5, s / 12), fill: "transparent" };
6613
+ const iconFrame = new fabric.Rect({
6614
+ originX: "center",
6615
+ originY: "center",
6616
+ left: cx,
6617
+ top: cy,
6618
+ width: s,
6619
+ height: s * 0.8,
6620
+ rx: s * 0.08,
6621
+ ry: s * 0.08,
6622
+ ...glyphStroke
6623
+ });
6624
+ const mountain = new fabric.Polyline(
6625
+ [
6626
+ { x: -s * 0.38, y: s * 0.24 },
6627
+ { x: -s * 0.1, y: -s * 0.08 },
6628
+ { x: s * 0.08, y: s * 0.1 },
6629
+ { x: s * 0.22, y: -s * 0.02 },
6630
+ { x: s * 0.38, y: s * 0.24 }
6631
+ ],
6632
+ { originX: "center", originY: "center", left: cx, top: cy + s * 0.08, ...glyphStroke }
6633
+ );
6634
+ const slash = new fabric.Line([cx - s * 0.55, cy - s * 0.5, cx + s * 0.55, cy + s * 0.5], {
6635
+ ...glyphStroke,
6636
+ originX: "center",
6637
+ originY: "center",
6638
+ left: cx,
6639
+ top: cy
6640
+ });
6641
+ for (const obj of [bg, iconFrame, mountain, slash]) {
6642
+ obj.set({ selectable: false, evented: false, excludeFromExport: true });
6643
+ obj.__pixldocsEditorChrome = true;
6644
+ group.add(obj);
6645
+ }
6646
+ group.set(prevPose);
6647
+ group.setCoords();
6648
+ group.dirty = true;
6649
+ }
6650
+ function hideEditorChromeForExport(fc) {
6651
+ const hidden = [];
6652
+ const walk = (objs) => {
6653
+ var _a2;
6654
+ for (const o of objs) {
6655
+ if (o.__pixldocsEditorChrome && o.visible !== false) {
6656
+ o.visible = false;
6657
+ hidden.push(o);
6658
+ }
6659
+ const kids = (_a2 = o.getObjects) == null ? void 0 : _a2.call(o);
6660
+ if (kids == null ? void 0 : kids.length) walk(kids);
6661
+ }
6662
+ };
6663
+ walk(fc.getObjects());
6664
+ if (hidden.length) {
6665
+ for (const o of hidden) {
6666
+ const g = o.group;
6667
+ if (g) g.dirty = true;
6668
+ }
6669
+ fc.renderAll();
6670
+ }
6671
+ return () => {
6672
+ for (const o of hidden) {
6673
+ o.visible = true;
6674
+ const g = o.group;
6675
+ if (g) g.dirty = true;
6676
+ }
6677
+ if (hidden.length) fc.requestRenderAll();
6678
+ };
6679
+ }
6573
6680
  const canvasImageLoader = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
6574
6681
  __proto__: null,
6575
6682
  SVG_DECODE_HARD_CAP,
@@ -6579,12 +6686,14 @@ const canvasImageLoader = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.d
6579
6686
  fetchSvgTextPublic,
6580
6687
  getNormalizedSvgUrl,
6581
6688
  getProxiedImageUrl,
6689
+ hideEditorChromeForExport,
6582
6690
  isBundledAssetUrl,
6583
6691
  isEmptyImagePlaceholderGroup,
6584
6692
  isPrivateUrl,
6585
6693
  isSvgImage,
6586
6694
  loadImageAsync,
6587
6695
  loadSvgDimensions,
6696
+ markPlaceholderAsBrokenImage,
6588
6697
  normalizeSvgImageDimensions,
6589
6698
  parseSvgDimensionsFromText,
6590
6699
  preloadImage,
@@ -10310,10 +10419,10 @@ function textPathBoundsContainScenePoint(obj, point) {
10310
10419
  }
10311
10420
  function drawTextPathBounds(ctx, obj, bounds, hostCanvas) {
10312
10421
  var _a2, _b2, _c2;
10313
- const host = hostCanvas || obj.canvas || ((_a2 = obj.group) == null ? void 0 : _a2.canvas);
10314
- if (!host) return;
10315
- const retina = ((_b2 = host.getRetinaScaling) == null ? void 0 : _b2.call(host)) || ((_c2 = obj.getCanvasRetinaScaling) == null ? void 0 : _c2.call(obj)) || 1;
10316
- const matrix = fabric.util.multiplyTransformMatrices(host.viewportTransform || [1, 0, 0, 1, 0, 0], obj.calcTransformMatrix());
10422
+ const host2 = hostCanvas || obj.canvas || ((_a2 = obj.group) == null ? void 0 : _a2.canvas);
10423
+ if (!host2) return;
10424
+ const retina = ((_b2 = host2.getRetinaScaling) == null ? void 0 : _b2.call(host2)) || ((_c2 = obj.getCanvasRetinaScaling) == null ? void 0 : _c2.call(obj)) || 1;
10425
+ const matrix = fabric.util.multiplyTransformMatrices(host2.viewportTransform || [1, 0, 0, 1, 0, 0], obj.calcTransformMatrix());
10317
10426
  const corners = [
10318
10427
  new fabric.Point(bounds.minX, bounds.minY),
10319
10428
  new fabric.Point(bounds.maxX, bounds.minY),
@@ -10564,11 +10673,11 @@ if (GroupProto && typeof GroupProto._renderControls === "function" && !GroupProt
10564
10673
  GroupProto.__pixldocsOrigRenderControls = GroupProto._renderControls;
10565
10674
  GroupProto._renderControls = function(ctx, styleOverride, childrenOverride) {
10566
10675
  var _a2;
10567
- const host = this.canvas;
10568
- const active = (_a2 = host == null ? void 0 : host.getActiveObject) == null ? void 0 : _a2.call(host);
10569
- const isActiveOrInActive = !!host && (active === this || !!active && typeof active.contains === "function" && active.contains(this, true));
10676
+ const host2 = this.canvas;
10677
+ const active = (_a2 = host2 == null ? void 0 : host2.getActiveObject) == null ? void 0 : _a2.call(host2);
10678
+ const isActiveOrInActive = !!host2 && (active === this || !!active && typeof active.contains === "function" && active.contains(this, true));
10570
10679
  GroupProto.__pixldocsOrigRenderControls.call(this, ctx, styleOverride, childrenOverride);
10571
- if (!host || !isActiveOrInActive) return;
10680
+ if (!host2 || !isActiveOrInActive) return;
10572
10681
  const drawForDescendants = (parent) => {
10573
10682
  const kids = (parent == null ? void 0 : parent._objects) || [];
10574
10683
  for (const child of kids) {
@@ -10577,7 +10686,7 @@ if (GroupProto && typeof GroupProto._renderControls === "function" && !GroupProt
10577
10686
  }
10578
10687
  if (child && child.textPath && child.textPath.preset && child.textPath.preset !== "none") {
10579
10688
  const bounds = getTextPathHitBounds(child);
10580
- if (bounds) drawTextPathBounds(ctx, child, bounds, host);
10689
+ if (bounds) drawTextPathBounds(ctx, child, bounds, host2);
10581
10690
  }
10582
10691
  }
10583
10692
  };
@@ -18096,6 +18205,7 @@ const PageCanvas = forwardRef(
18096
18205
  // Transparent so underlay (page bg + group bgs) shows through
18097
18206
  backgroundColor: "transparent"
18098
18207
  });
18208
+ fabricCanvas.__pixldocsEditMode = isEditorMode && !isPreviewMode;
18099
18209
  fabricCanvas.hoverCursor = "default";
18100
18210
  fabricCanvas.moveCursor = "move";
18101
18211
  const suppressTextEditForClick = (textbox) => {
@@ -25421,6 +25531,7 @@ const PageCanvas = forwardRef(
25421
25531
  } else if (obj instanceof fabric.Textbox) {
25422
25532
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
25423
25533
  let text = element.text != null && element.text !== "" ? element.text : " ";
25534
+ text = applyElementTextCase(text, element.textCase);
25424
25535
  let parsedStyles = null;
25425
25536
  if (element.formattingEnabled === true) {
25426
25537
  const parsed = parseTextMarkdown(text);
@@ -26065,6 +26176,13 @@ const PageCanvas = forwardRef(
26065
26176
  const img = await fabric.FabricImage.fromURL(url, { crossOrigin: "anonymous" });
26066
26177
  if (!fabricRef.current || !isLatestRequest()) return;
26067
26178
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat, svgDecodeTarget);
26179
+ {
26180
+ const natW = img.width ?? 0;
26181
+ const natH = img.height ?? 0;
26182
+ if (natW <= 1 && natH <= 1 && Number(element.width) > 8 && Number(element.height) > 8) {
26183
+ throw new Error(`image decoded to ${natW}x${natH} — treating as failed load`);
26184
+ }
26185
+ }
26068
26186
  if (!isLatestRequest()) return;
26069
26187
  const imageFitForFade = element.imageFit || ((_a2 = element.style) == null ? void 0 : _a2.imageFit) || "cover";
26070
26188
  const clipShapeForFade = element.clipShape ?? ((_b2 = element.style) == null ? void 0 : _b2.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
@@ -26431,6 +26549,24 @@ const PageCanvas = forwardRef(
26431
26549
  }
26432
26550
  fc.requestRenderAll();
26433
26551
  } catch (error) {
26552
+ console.error("[PageCanvas] image failed to load", {
26553
+ id: element.id,
26554
+ url: (element.src || element.imageUrl || "").slice(0, 200),
26555
+ error: error instanceof Error ? error.message : String(error)
26556
+ });
26557
+ recordImageLoadFailure({
26558
+ id: String(element.id ?? ""),
26559
+ url: element.src || element.imageUrl || "",
26560
+ reason: error instanceof Error ? error.message : String(error)
26561
+ });
26562
+ const fcNow = fabricRef.current;
26563
+ if (fcNow && fcNow.__pixldocsEditMode === true) {
26564
+ try {
26565
+ markPlaceholderAsBrokenImage(placeholder);
26566
+ fcNow.requestRenderAll();
26567
+ } catch {
26568
+ }
26569
+ }
26434
26570
  }
26435
26571
  };
26436
26572
  const handleCanvasClick = useCallback(
@@ -27754,6 +27890,7 @@ function PreviewCanvas({
27754
27890
  const pageSettings = useMemo(() => {
27755
27891
  var _a3, _b3;
27756
27892
  return {
27893
+ // design-drift-ignore: artifact paper colour, not chrome
27757
27894
  backgroundColor: ((_a3 = page == null ? void 0 : page.settings) == null ? void 0 : _a3.backgroundColor) || "#ffffff",
27758
27895
  backgroundGradient: (_b3 = page == null ? void 0 : page.settings) == null ? void 0 : _b3.backgroundGradient
27759
27896
  };
@@ -32376,11 +32513,31 @@ async function resolveTemplateData(options) {
32376
32513
  ...formData ?? {}
32377
32514
  };
32378
32515
  const dynamicFields = config.dynamicFields;
32379
- if (!Array.isArray(dynamicFields) || dynamicFields.length === 0) {
32516
+ const hasBoundTable = (() => {
32517
+ let found = false;
32518
+ const walk = (nodes) => {
32519
+ var _a2, _b2;
32520
+ for (const n of nodes || []) {
32521
+ if (found) return;
32522
+ if ((n == null ? void 0 : n.type) === "table" && ((_b2 = (_a2 = n == null ? void 0 : n.tableData) == null ? void 0 : _a2.repeat) == null ? void 0 : _b2.from)) {
32523
+ found = true;
32524
+ return;
32525
+ }
32526
+ walk(n == null ? void 0 : n.children);
32527
+ }
32528
+ };
32529
+ for (const p of config.pages || []) walk(p.children);
32530
+ return found;
32531
+ })();
32532
+ const hasDynamicFields = Array.isArray(dynamicFields) && dynamicFields.length > 0;
32533
+ if (!hasDynamicFields && (hasBoundTable || repeatableSectionsInput.length > 0)) {
32534
+ console.log("[resolveTemplateData] no dynamicFields; binding via bound table / repeatable sections");
32535
+ }
32536
+ if (!hasDynamicFields && !hasBoundTable && repeatableSectionsInput.length === 0) {
32380
32537
  return { config: applyThemeIfNeeded(config), templateName: template.name || "Untitled", templateId, price: template.price ?? 0 };
32381
32538
  }
32382
32539
  const mappings = [];
32383
- for (const f of dynamicFields) {
32540
+ for (const f of dynamicFields || []) {
32384
32541
  for (const m of f.mappings || []) {
32385
32542
  mappings.push({
32386
32543
  field_key: f.id,
@@ -34101,6 +34258,14 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
34101
34258
  try {
34102
34259
  const visit = (obj) => {
34103
34260
  if (!obj) return;
34261
+ if (obj.__pixldocsEditorChrome) {
34262
+ const originalToSVG = typeof obj.toSVG === "function" ? obj.toSVG.bind(obj) : null;
34263
+ if (originalToSVG) {
34264
+ obj.toSVG = () => "";
34265
+ svgPatchRecords.push({ obj, originalToSVG });
34266
+ }
34267
+ return;
34268
+ }
34104
34269
  const imageId = typeof obj.__docuforgeId === "string" && hasRenderableRasterCandidate(obj) ? obj.__docuforgeId : "";
34105
34270
  const alphaGradient = objectNeedsAlphaGradientRaster(obj);
34106
34271
  if ((isTextboxLike(obj) || imageId || alphaGradient) && typeof obj.toSVG === "function") {
@@ -34208,9 +34373,22 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
34208
34373
  }
34209
34374
  return svgString;
34210
34375
  }
34211
- const resolvedPackageVersion = "0.5.496";
34376
+ const resolvedPackageVersion = "0.5.498";
34212
34377
  const PACKAGE_VERSION = resolvedPackageVersion;
34213
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.496";
34378
+ async function crPhase(name, fn) {
34379
+ const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
34380
+ const t0 = now();
34381
+ try {
34382
+ return await fn();
34383
+ } finally {
34384
+ const g = globalThis;
34385
+ const bag = g.__CR_PHASES__ || (g.__CR_PHASES__ = {});
34386
+ const cur = bag[name] || (bag[name] = { count: 0, ms: 0 });
34387
+ cur.count += 1;
34388
+ cur.ms += Math.round(now() - t0);
34389
+ }
34390
+ }
34391
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.498";
34214
34392
  const roundParityValue = (value) => {
34215
34393
  if (typeof value !== "number") return value;
34216
34394
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -34649,6 +34827,11 @@ class PixldocsRenderer {
34649
34827
  * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
34650
34828
  */
34651
34829
  async render(templateConfig, options = {}) {
34830
+ if (options.watermark === true && !templateConfig.__pixldocsWatermarked) {
34831
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34832
+ templateConfig = injectWatermark2(templateConfig, options.watermarkOptions);
34833
+ templateConfig.__pixldocsWatermarked = true;
34834
+ }
34652
34835
  const pageIndex = options.pageIndex ?? 0;
34653
34836
  const format = options.format ?? "png";
34654
34837
  const quality = options.quality ?? 0.92;
@@ -34664,6 +34847,7 @@ class PixldocsRenderer {
34664
34847
  const hasAutoShrink = configHasAutoShrinkText(templateConfig);
34665
34848
  const defaultWait = hasAutoShrink ? 4e3 : 1800;
34666
34849
  await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
34850
+ this.flushTextMeasurementCaches();
34667
34851
  }
34668
34852
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
34669
34853
  setPackageApiUrl2(this.config.imageProxyUrl);
@@ -34691,6 +34875,7 @@ class PixldocsRenderer {
34691
34875
  const hasAutoShrink = configHasAutoShrinkText(templateConfig);
34692
34876
  const defaultWait = hasAutoShrink ? 4e3 : 1800;
34693
34877
  await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
34878
+ this.flushTextMeasurementCaches();
34694
34879
  }
34695
34880
  const results = [];
34696
34881
  for (let i = 0; i < templateConfig.pages.length; i++) {
@@ -34717,8 +34902,8 @@ class PixldocsRenderer {
34717
34902
  const shouldWatermark = watermark ?? resolved.price > 0;
34718
34903
  let configToRender = resolved.config;
34719
34904
  if (shouldWatermark) {
34720
- const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
34721
- configToRender = injectWatermark(configToRender, watermarkOptions);
34905
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34906
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34722
34907
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34723
34908
  configToRender = injectPreviewBlur2(configToRender);
34724
34909
  }
@@ -34777,8 +34962,8 @@ class PixldocsRenderer {
34777
34962
  const shouldWatermark = watermark ?? resolved.price > 0;
34778
34963
  let configToRender = resolved.config;
34779
34964
  if (shouldWatermark) {
34780
- const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
34781
- configToRender = injectWatermark(configToRender, watermarkOptions);
34965
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34966
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34782
34967
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34783
34968
  configToRender = injectPreviewBlur2(configToRender);
34784
34969
  }
@@ -34798,6 +34983,10 @@ class PixldocsRenderer {
34798
34983
  * exporter, which is what `renderPdfViaClientExport` does below.
34799
34984
  */
34800
34985
  async renderPdf(templateConfig, options) {
34986
+ if ((options == null ? void 0 : options.watermark) === true) {
34987
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34988
+ templateConfig = injectWatermark2(templateConfig, options.watermarkOptions);
34989
+ }
34801
34990
  return this.renderPdfViaClientExport(templateConfig, {
34802
34991
  title: options == null ? void 0 : options.title,
34803
34992
  textMode: options == null ? void 0 : options.textMode,
@@ -34826,8 +35015,8 @@ class PixldocsRenderer {
34826
35015
  const shouldWatermark = watermark ?? resolved.price > 0;
34827
35016
  let configToRender = resolved.config;
34828
35017
  if (shouldWatermark) {
34829
- const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
34830
- configToRender = injectWatermark(configToRender, watermarkOptions);
35018
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
35019
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34831
35020
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34832
35021
  configToRender = injectPreviewBlur2(configToRender);
34833
35022
  }
@@ -34998,7 +35187,7 @@ class PixldocsRenderer {
34998
35187
  containers.push(container);
34999
35188
  const root = createRoot(container);
35000
35189
  roots.push(root);
35001
- await new Promise((resolve, reject) => {
35190
+ await crPhase("pdf_mount", () => new Promise((resolve, reject) => {
35002
35191
  const timer = setTimeout(() => reject(new Error(`PreviewCanvas onReady timeout for page ${i}`)), 3e4);
35003
35192
  root.render(
35004
35193
  createElement(PreviewCanvas2, {
@@ -35016,15 +35205,15 @@ class PixldocsRenderer {
35016
35205
  }
35017
35206
  })
35018
35207
  );
35019
- });
35020
- await this.waitForCanvasScene(container, cloned, i);
35208
+ }));
35209
+ await crPhase("pdf_scene1", () => this.waitForCanvasScene(container, cloned, i));
35021
35210
  const expected = this.getExpectedImageIds(cloned, i);
35022
- await this.waitForCanvasImages(container, expected);
35023
- await this.waitForStableTextMetrics(container, cloned, { clearGlobalCharCache: false });
35024
- await this.waitForCanvasScene(container, cloned, i);
35211
+ await crPhase("pdf_images", () => this.waitForCanvasImages(container, expected));
35212
+ await crPhase("pdf_text_metrics", () => this.waitForStableTextMetrics(container, cloned, { clearGlobalCharCache: false }));
35213
+ await crPhase("pdf_scene2", () => this.waitForCanvasScene(container, cloned, i));
35025
35214
  }
35026
35215
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
35027
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-E3x8GBto.js");
35216
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-D8iQi9WT.js");
35028
35217
  const prepared = preparePagesForExport(
35029
35218
  cloned.pages,
35030
35219
  canvasWidth,
@@ -35202,8 +35391,14 @@ class PixldocsRenderer {
35202
35391
  }
35203
35392
  const sceneSettled = fabricReady && allDomLoaded && canvasReady && fabricObjects.length > 0;
35204
35393
  const idleFor = Date.now() - lastProgressAt;
35205
- if (!strictById && sceneSettled && actualImageCount < expectedImageCount && idleFor >= earlyExitMs) {
35206
- if (debug) {
35394
+ const strictIdleMs = Math.max(earlyExitMs, 4e3);
35395
+ const stalled = strictById ? missingImageIds.length > 0 : actualImageCount < expectedImageCount;
35396
+ if (sceneSettled && stalled && idleFor >= (strictById ? strictIdleMs : earlyExitMs)) {
35397
+ if (strictById) {
35398
+ console.warn(
35399
+ `[canvas-renderer][asset-wait] early-exit after ${elapsed}ms with unresolved image id(s): ${missingImageIds.join(", ")} (idle=${idleFor}ms, ${summary})`
35400
+ );
35401
+ } else if (debug) {
35207
35402
  console.log(
35208
35403
  `[canvas-renderer][asset-wait] early-exit after ${elapsed}ms (idle=${idleFor}ms, ${summary})`
35209
35404
  );
@@ -35302,6 +35497,37 @@ class PixldocsRenderer {
35302
35497
  new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
35303
35498
  ]);
35304
35499
  }
35500
+ /**
35501
+ * Flush BOTH process-global text-measurement caches after fonts are ready
35502
+ * and BEFORE the capture canvas mounts.
35503
+ *
35504
+ * Why: anything that measured text before the webfonts finished loading —
35505
+ * most notably `applyContentBoundsPagination` running inside
35506
+ * `resolveFromForm` on a cold page (the EC2/MCP flow: resolve + render in
35507
+ * one browser context) — populates the app measurement cache and Fabric's
35508
+ * global char-width cache with FALLBACK metrics. `createText` then places
35509
+ * center/right-aligned lines from those stale widths, so a right-aligned
35510
+ * grid cell on a continuation page drew shifted left by
35511
+ * (fallbackWidth − realWidth) px vs the editor (caught by
35512
+ * tests/parity `auto-paginate`, a 9px shift). The later
35513
+ * `waitForStableTextMetrics` pass re-primes glyph bounds but never moves
35514
+ * the already-positioned box, so the flush must happen pre-mount.
35515
+ *
35516
+ * Only called from the headless PNG paths (`render`/`renderAllPages`),
35517
+ * which already default to clearing the global char cache in
35518
+ * `waitForStableTextMetrics` — the live-preview-adjacent PDF path keeps
35519
+ * its `clearGlobalCharCache: false` behaviour untouched.
35520
+ */
35521
+ flushTextMeasurementCaches() {
35522
+ try {
35523
+ clearMeasurementCache();
35524
+ } catch {
35525
+ }
35526
+ try {
35527
+ clearFabricCharCache();
35528
+ } catch {
35529
+ }
35530
+ }
35305
35531
  getNormalizedGradientStops(gradient) {
35306
35532
  const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
35307
35533
  offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
@@ -35391,6 +35617,8 @@ class PixldocsRenderer {
35391
35617
  });
35392
35618
  }
35393
35619
  return new Promise((resolve, reject) => {
35620
+ const mountStartedAt = typeof performance !== "undefined" ? performance.now() : Date.now();
35621
+ let mountRecorded = false;
35394
35622
  const container = document.createElement("div");
35395
35623
  container.style.cssText = `
35396
35624
  position: fixed; left: -99999px; top: -99999px;
@@ -35455,13 +35683,22 @@ class PixldocsRenderer {
35455
35683
  });
35456
35684
  };
35457
35685
  const onReady = () => {
35458
- this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
35686
+ if (!mountRecorded) {
35687
+ mountRecorded = true;
35688
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
35689
+ const g = globalThis;
35690
+ const bag = g.__CR_PHASES__ || (g.__CR_PHASES__ = {});
35691
+ const cur = bag.raster_mount || (bag.raster_mount = { count: 0, ms: 0 });
35692
+ cur.count += 1;
35693
+ cur.ms += Math.round(now - mountStartedAt);
35694
+ }
35695
+ crPhase("raster_scene1", () => this.waitForCanvasScene(container, renderConfig, pageIndex)).then(async () => {
35459
35696
  try {
35460
35697
  const fabricInstance = this.getFabricCanvasFromContainer(container);
35461
35698
  const expectedImageIds = this.getExpectedImageIds(renderConfig, pageIndex);
35462
- await this.waitForCanvasImages(container, expectedImageIds);
35463
- await this.waitForStableTextMetrics(container, renderConfig);
35464
- await this.waitForCanvasScene(container, renderConfig, pageIndex);
35699
+ await crPhase("raster_images", () => this.waitForCanvasImages(container, expectedImageIds));
35700
+ await crPhase("raster_text_metrics", () => this.waitForStableTextMetrics(container, renderConfig));
35701
+ await crPhase("raster_scene2", () => this.waitForCanvasScene(container, renderConfig, pageIndex));
35465
35702
  firstMountSettled = true;
35466
35703
  if (hasAutoShrink && lateFontSettleDetected) {
35467
35704
  console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
@@ -37344,7 +37581,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
37344
37581
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
37345
37582
  sanitizeSvgTreeForPdf(svgToDraw);
37346
37583
  try {
37347
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-E3x8GBto.js");
37584
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-D8iQi9WT.js");
37348
37585
  try {
37349
37586
  await logTextMeasurementDiagnostic(svgToDraw);
37350
37587
  } catch {
@@ -37592,13 +37829,136 @@ async function getPublishedTemplate(options) {
37592
37829
  const rows = await res.json();
37593
37830
  return rows[0] ?? null;
37594
37831
  }
37832
+ const WATERMARK_ID_PREFIX = "__wm_";
37833
+ const DEFAULT_WATERMARK_OPACITY = 0.14;
37834
+ const DEFAULT_WATERMARK_FONT_SIZE = 18;
37835
+ const DEFAULT_WATERMARK_ANGLE = -30;
37836
+ function getWatermarkText() {
37837
+ try {
37838
+ const host2 = window.location.hostname;
37839
+ if (host2.includes("biomaker")) return "biomaker.app";
37840
+ } catch {
37841
+ }
37842
+ return "pixldocs.com";
37843
+ }
37844
+ function getLuminance(color) {
37845
+ if (!color || color === "transparent" || color === "none") return 1;
37846
+ let r = 255, g = 255, b = 255;
37847
+ if (color.startsWith("#")) {
37848
+ const hex = color.slice(1);
37849
+ const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
37850
+ const n = parseInt(full, 16);
37851
+ r = n >> 16 & 255;
37852
+ g = n >> 8 & 255;
37853
+ b = n & 255;
37854
+ } else {
37855
+ const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
37856
+ if (m) {
37857
+ r = +m[1];
37858
+ g = +m[2];
37859
+ b = +m[3];
37860
+ }
37861
+ }
37862
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
37863
+ }
37864
+ function getWatermarkColor(bgColor) {
37865
+ const lum = getLuminance(bgColor);
37866
+ return lum < 0.45 ? "#cccccc" : "#808080";
37867
+ }
37868
+ function getPageBgColor(page, canvasBg) {
37869
+ var _a2, _b2;
37870
+ const settingsBg = (_a2 = page.settings) == null ? void 0 : _a2.backgroundColor;
37871
+ if (settingsBg && settingsBg !== "transparent" && settingsBg !== "none") {
37872
+ return settingsBg;
37873
+ }
37874
+ const children = page.children || [];
37875
+ for (const child of children) {
37876
+ if (child.type === "shape" && child.fill && child.fill !== "transparent" && child.fill !== "none") {
37877
+ return child.fill;
37878
+ }
37879
+ if ((_b2 = child.children) == null ? void 0 : _b2.length) {
37880
+ for (const gc of child.children) {
37881
+ if (gc.type === "shape" && gc.fill && gc.fill !== "transparent" && gc.fill !== "none") {
37882
+ return gc.fill;
37883
+ }
37884
+ }
37885
+ }
37886
+ }
37887
+ return canvasBg;
37888
+ }
37889
+ function generateWatermarkElements(canvasWidth, canvasHeight, wmColor, options = {}) {
37890
+ const elements = [];
37891
+ const text = options.text ?? getWatermarkText();
37892
+ const fontSize = options.fontSize ?? DEFAULT_WATERMARK_FONT_SIZE;
37893
+ const angle = options.angle ?? DEFAULT_WATERMARK_ANGLE;
37894
+ const opacity = options.opacity ?? DEFAULT_WATERMARK_OPACITY;
37895
+ const estimatedTextWidth = text.length * fontSize * 0.55;
37896
+ const spacingX = Math.max(160, estimatedTextWidth + 30);
37897
+ const spacingY = 90;
37898
+ const startX = -canvasWidth * 0.3;
37899
+ const startY = -canvasHeight * 0.3;
37900
+ const endX = canvasWidth * 1.5;
37901
+ const endY = canvasHeight * 1.5;
37902
+ let idx = 0;
37903
+ for (let y = startY; y < endY; y += spacingY) {
37904
+ for (let x = startX; x < endX; x += spacingX) {
37905
+ elements.push({
37906
+ id: `${WATERMARK_ID_PREFIX}${idx++}`,
37907
+ type: "text",
37908
+ left: x,
37909
+ top: y,
37910
+ width: 200,
37911
+ height: 40,
37912
+ text,
37913
+ fontFamily: "Montserrat",
37914
+ fontSize,
37915
+ fontWeight: "600",
37916
+ fill: wmColor,
37917
+ angle,
37918
+ selectable: false,
37919
+ locked: true,
37920
+ visible: true,
37921
+ opacity,
37922
+ scaleX: 1,
37923
+ scaleY: 1,
37924
+ letterSpacing: 4
37925
+ });
37926
+ }
37927
+ }
37928
+ return elements;
37929
+ }
37930
+ function injectWatermark(config, options = {}) {
37931
+ var _a2, _b2, _c2;
37932
+ const canvasWidth = ((_a2 = config.canvas) == null ? void 0 : _a2.width) || 612;
37933
+ const canvasHeight = ((_b2 = config.canvas) == null ? void 0 : _b2.height) || 792;
37934
+ const canvasBg = (_c2 = config.canvas) == null ? void 0 : _c2.backgroundColor;
37935
+ return {
37936
+ ...config,
37937
+ pages: config.pages.map((page) => {
37938
+ const pageBg = getPageBgColor(page, canvasBg);
37939
+ const wmColor = getWatermarkColor(pageBg);
37940
+ const watermarkElements = generateWatermarkElements(canvasWidth, canvasHeight, wmColor, options);
37941
+ return {
37942
+ ...page,
37943
+ children: [
37944
+ ...page.children || [],
37945
+ ...watermarkElements
37946
+ ]
37947
+ };
37948
+ })
37949
+ };
37950
+ }
37951
+ const canvasWatermark = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
37952
+ __proto__: null,
37953
+ injectWatermark
37954
+ }, Symbol.toStringTag, { value: "Module" }));
37595
37955
  function setAutoShrinkDebug(enabled) {
37596
37956
  if (typeof window !== "undefined") {
37597
37957
  window.__pixldocsDebugAutoShrink = !!enabled;
37598
37958
  }
37599
37959
  }
37600
37960
  export {
37601
- resolveFontWeight as $,
37961
+ resolveBlurElementExactIdsFromFlatFormKeys as $,
37602
37962
  API_URL as A,
37603
37963
  collectFontDescriptorsFromConfig as B,
37604
37964
  collectFontsFromConfig as C,
@@ -37619,23 +37979,24 @@ export {
37619
37979
  hasAnyPreviewBlur as R,
37620
37980
  injectPreviewBlur as S,
37621
37981
  TRIANGLE_STROKE_MITER_LIMIT as T,
37622
- isBundledAssetUrl as U,
37623
- isFontAvailable as V,
37624
- isPrivateUrl as W,
37625
- listPublishedTemplates as X,
37626
- loadGoogleFontCSS as Y,
37627
- normalizeFontFamily as Z,
37628
- resolveBlurElementExactIdsFromFlatFormKeys as _,
37982
+ injectWatermark as U,
37983
+ isBundledAssetUrl as V,
37984
+ isFontAvailable as W,
37985
+ isPrivateUrl as X,
37986
+ listPublishedTemplates as Y,
37987
+ loadGoogleFontCSS as Z,
37988
+ normalizeFontFamily as _,
37629
37989
  getAbsoluteBounds as a,
37630
- resolveForRender as a0,
37631
- resolveFromForm as a1,
37632
- resolveTemplateData as a2,
37633
- rewriteSvgFontsForJsPDF as a3,
37634
- setAutoShrinkDebug as a4,
37635
- setBundledAssetPrefixes as a5,
37636
- warmResolvedTemplateForPreview as a6,
37637
- warmTemplateFromForm as a7,
37638
- canvasImageLoader as a8,
37990
+ resolveFontWeight as a0,
37991
+ resolveForRender as a1,
37992
+ resolveFromForm as a2,
37993
+ resolveTemplateData as a3,
37994
+ rewriteSvgFontsForJsPDF as a4,
37995
+ setAutoShrinkDebug as a5,
37996
+ setBundledAssetPrefixes as a6,
37997
+ warmResolvedTemplateForPreview as a7,
37998
+ warmTemplateFromForm as a8,
37999
+ canvasImageLoader as a9,
37639
38000
  getProxiedImageUrl as b,
37640
38001
  captureFabricCanvasSvgForPdf as c,
37641
38002
  getImageProxyFetchOptions as d,
@@ -37662,4 +38023,4 @@ export {
37662
38023
  awaitFontsForConfig as y,
37663
38024
  buildTeaserBlurFlatKeys as z
37664
38025
  };
37665
- //# sourceMappingURL=index-CiSoT2Lk.js.map
38026
+ //# sourceMappingURL=index-lbU-Xs_t.js.map