@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.
@@ -112,6 +112,15 @@ const generateId = (prefix = "el") => {
112
112
  return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 9)}-${Math.random().toString(36).substr(2, 9)}`;
113
113
  };
114
114
  const createDefaultElement = (partial) => {
115
+ const loose = partial;
116
+ if (loose.type === "frame" || loose.type === "group") {
117
+ return createDefaultGroup({
118
+ layoutMode: loose.layout,
119
+ ...partial,
120
+ type: "group",
121
+ children: Array.isArray(loose.children) ? loose.children : []
122
+ });
123
+ }
115
124
  const isText = partial.type === "text";
116
125
  const base = {
117
126
  left: 48,
@@ -129,7 +138,7 @@ const createDefaultElement = (partial) => {
129
138
  originY: "top",
130
139
  opacity: 1,
131
140
  fill: "#e2e8f0",
132
- ...isText ? {} : { stroke: "#64748b", strokeWidth: 1 },
141
+ ...isText ? {} : { stroke: "transparent", strokeWidth: 0 },
133
142
  visible: true,
134
143
  selectable: true,
135
144
  evented: true,
@@ -975,49 +984,15 @@ function worldPointToParentLocal(worldX, worldY, parent, pageChildren, resolveGr
975
984
  );
976
985
  return { left: local.x, top: local.y };
977
986
  }
978
- function unbakeChildrenToGroupLocal(group) {
979
- const angle = getGroupAngleDeg(group);
980
- if (Math.abs(angle) < 0.01) return group;
981
- const { width, height } = getGroupFrameSize(group);
982
- const cx = width / 2;
983
- const cy = height / 2;
984
- const inv = rotateAroundAffDeg(-angle, cx, cy);
985
- const kids = (group.children ?? []).map((child) => {
986
- const p = applyAff(inv, child.left ?? 0, child.top ?? 0);
987
- const childAngle = typeof child.angle === "number" ? child.angle : 0;
988
- const localAngle = normalizeAngleDeg(childAngle - angle);
989
- if (isGroup(child)) {
990
- const relocated = {
991
- ...child,
992
- left: p.x,
993
- top: p.y,
994
- angle: localAngle
995
- };
996
- return Math.abs(getGroupAngleDeg(relocated)) > 0.01 ? unbakeChildrenToGroupLocal(relocated) : relocated;
997
- }
998
- return {
999
- ...child,
1000
- left: p.x,
1001
- top: p.y,
1002
- angle: localAngle
1003
- };
1004
- });
1005
- return { ...group, children: kids };
1006
- }
1007
987
  function migratePageTreeToGroupOwnedTransforms(nodes) {
1008
988
  return nodes.map((node) => {
1009
989
  if (!isGroup(node)) return node;
1010
990
  const g = node;
1011
- let next = {
991
+ const next = {
1012
992
  ...g,
1013
993
  children: migratePageTreeToGroupOwnedTransforms(g.children ?? [])
1014
994
  };
1015
- if (isRotatableGroup(next) && Math.abs(getGroupAngleDeg(next)) > 0.01) {
1016
- if (g.groupTransformModel !== "owned-v1") {
1017
- next = unbakeChildrenToGroupLocal(next);
1018
- next.groupTransformModel = "owned-v1";
1019
- }
1020
- } else if (isRotatableGroup(next)) {
995
+ if (isRotatableGroup(next)) {
1021
996
  next.groupTransformModel = "owned-v1";
1022
997
  }
1023
998
  return next;
@@ -2690,7 +2665,7 @@ let sizeBaseSnapshot = null;
2690
2665
  const reflowPagesFromBase = (base, W, H) => {
2691
2666
  const sx = base.width ? W / base.width : 1;
2692
2667
  const sy = base.height ? H / base.height : 1;
2693
- const fscale = Math.sqrt(Math.max(0.01, sx * sy));
2668
+ const fscale = Math.max(0.01, Math.min(sx, sy));
2694
2669
  const adapt = layoutAdaptationFor(base.width, base.height, W, H);
2695
2670
  const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
2696
2671
  const scaleNode = (node) => scaleNodeForReflow(node, sx, sy, fscale);
@@ -3239,7 +3214,7 @@ const useEditorStore = zustand.create((set, get) => ({
3239
3214
  setCanvasSize: (width, height) => set((state) => {
3240
3215
  const oldW = state.canvas.width || width, oldH = state.canvas.height || height;
3241
3216
  const sx = oldW ? width / oldW : 1, sy = oldH ? height / oldH : 1;
3242
- const fscale = Math.sqrt(Math.max(0.01, sx * sy));
3217
+ const fscale = Math.max(0.01, Math.min(sx, sy));
3243
3218
  const n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
3244
3219
  const scaleNode = (node) => {
3245
3220
  const el = { ...node };
@@ -5895,6 +5870,26 @@ const setupFontLoadingListener = (canvas, afterRerender, options = {}) => {
5895
5870
  }
5896
5871
  };
5897
5872
  };
5873
+ const GLOBAL_KEY = "__PIXLDOCS_IMAGE_FAILURES__";
5874
+ const MAX_RECORDED = 50;
5875
+ function host() {
5876
+ return typeof globalThis === "undefined" ? null : globalThis;
5877
+ }
5878
+ function recordImageLoadFailure(failure) {
5879
+ try {
5880
+ const g = host();
5881
+ if (!g) return;
5882
+ const list = g[GLOBAL_KEY] ?? [];
5883
+ if (list.length >= MAX_RECORDED) return;
5884
+ list.push({
5885
+ id: String(failure.id ?? ""),
5886
+ url: String(failure.url ?? "").slice(0, 500),
5887
+ reason: String(failure.reason ?? "").slice(0, 300)
5888
+ });
5889
+ g[GLOBAL_KEY] = list;
5890
+ } catch {
5891
+ }
5892
+ }
5898
5893
  const EMPTY_IMAGE_PLACEHOLDER_DATA_URL = "data:image/png;base64,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";
5899
5894
  const svgTextCache = /* @__PURE__ */ new Map();
5900
5895
  const htmlImageCache = /* @__PURE__ */ new Map();
@@ -6386,8 +6381,13 @@ async function loadImageAsync(element, placeholder, fc, fabricRef, syncLockedRef
6386
6381
  });
6387
6382
  const imgLoadOptions = url.startsWith("data:") || url.startsWith("blob:") ? {} : { crossOrigin: "anonymous" };
6388
6383
  const img = await fabric__namespace.FabricImage.fromURL(url, imgLoadOptions);
6389
- if (!fabricRef.current) return;
6384
+ if (fabricRef && !fabricRef.current) return;
6390
6385
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat);
6386
+ const natW = img.width ?? 0;
6387
+ const natH = img.height ?? 0;
6388
+ if (natW <= 1 && natH <= 1 && Number(element.width) > 8 && Number(element.height) > 8) {
6389
+ throw new Error(`image decoded to ${natW}x${natH} — treating as failed load`);
6390
+ }
6391
6391
  const isHidden = !element.visible;
6392
6392
  img.set({
6393
6393
  originX: "left",
@@ -6585,9 +6585,116 @@ async function loadImageAsync(element, placeholder, fc, fabricRef, syncLockedRef
6585
6585
  proxiedUrl: getProxiedImageUrl(imageUrl).slice(0, 240),
6586
6586
  error: error instanceof Error ? error.message : String(error)
6587
6587
  });
6588
+ recordImageLoadFailure({
6589
+ id: String(element.id ?? ""),
6590
+ url: imageUrl,
6591
+ reason: error instanceof Error ? error.message : String(error)
6592
+ });
6593
+ if (fc.__pixldocsEditMode === true) {
6594
+ try {
6595
+ markPlaceholderAsBrokenImage(placeholder);
6596
+ fc.requestRenderAll();
6597
+ } catch {
6598
+ }
6599
+ }
6588
6600
  return;
6589
6601
  }
6590
6602
  }
6603
+ function markPlaceholderAsBrokenImage(placeholder) {
6604
+ if (!(placeholder instanceof fabric__namespace.Group)) return;
6605
+ const group = placeholder;
6606
+ if (group.__pixldocsBrokenImage) return;
6607
+ group.__pixldocsBrokenImage = true;
6608
+ const frameChild = group.getObjects().find(
6609
+ (o) => o.__isPlaceholderFrame
6610
+ );
6611
+ const w = Math.max(1, (frameChild == null ? void 0 : frameChild.width) ?? group.width ?? 1);
6612
+ const h = Math.max(1, (frameChild == null ? void 0 : frameChild.height) ?? group.height ?? 1);
6613
+ const center = group.getCenterPoint();
6614
+ const cx = center.x;
6615
+ const cy = center.y;
6616
+ const prevPose = { left: group.left, top: group.top, width: group.width, height: group.height };
6617
+ const bg = new fabric__namespace.Rect({
6618
+ originX: "center",
6619
+ originY: "center",
6620
+ left: cx,
6621
+ top: cy,
6622
+ width: w,
6623
+ height: h,
6624
+ fill: "rgba(148, 163, 184, 0.12)",
6625
+ stroke: "#94A3B8",
6626
+ strokeWidth: 1.5,
6627
+ strokeDashArray: [6, 4]
6628
+ });
6629
+ const s = Math.max(16, Math.min(w, h) * 0.28);
6630
+ const glyphStroke = { stroke: "#64748B", strokeWidth: Math.max(1.5, s / 12), fill: "transparent" };
6631
+ const iconFrame = new fabric__namespace.Rect({
6632
+ originX: "center",
6633
+ originY: "center",
6634
+ left: cx,
6635
+ top: cy,
6636
+ width: s,
6637
+ height: s * 0.8,
6638
+ rx: s * 0.08,
6639
+ ry: s * 0.08,
6640
+ ...glyphStroke
6641
+ });
6642
+ const mountain = new fabric__namespace.Polyline(
6643
+ [
6644
+ { x: -s * 0.38, y: s * 0.24 },
6645
+ { x: -s * 0.1, y: -s * 0.08 },
6646
+ { x: s * 0.08, y: s * 0.1 },
6647
+ { x: s * 0.22, y: -s * 0.02 },
6648
+ { x: s * 0.38, y: s * 0.24 }
6649
+ ],
6650
+ { originX: "center", originY: "center", left: cx, top: cy + s * 0.08, ...glyphStroke }
6651
+ );
6652
+ const slash = new fabric__namespace.Line([cx - s * 0.55, cy - s * 0.5, cx + s * 0.55, cy + s * 0.5], {
6653
+ ...glyphStroke,
6654
+ originX: "center",
6655
+ originY: "center",
6656
+ left: cx,
6657
+ top: cy
6658
+ });
6659
+ for (const obj of [bg, iconFrame, mountain, slash]) {
6660
+ obj.set({ selectable: false, evented: false, excludeFromExport: true });
6661
+ obj.__pixldocsEditorChrome = true;
6662
+ group.add(obj);
6663
+ }
6664
+ group.set(prevPose);
6665
+ group.setCoords();
6666
+ group.dirty = true;
6667
+ }
6668
+ function hideEditorChromeForExport(fc) {
6669
+ const hidden = [];
6670
+ const walk = (objs) => {
6671
+ var _a2;
6672
+ for (const o of objs) {
6673
+ if (o.__pixldocsEditorChrome && o.visible !== false) {
6674
+ o.visible = false;
6675
+ hidden.push(o);
6676
+ }
6677
+ const kids = (_a2 = o.getObjects) == null ? void 0 : _a2.call(o);
6678
+ if (kids == null ? void 0 : kids.length) walk(kids);
6679
+ }
6680
+ };
6681
+ walk(fc.getObjects());
6682
+ if (hidden.length) {
6683
+ for (const o of hidden) {
6684
+ const g = o.group;
6685
+ if (g) g.dirty = true;
6686
+ }
6687
+ fc.renderAll();
6688
+ }
6689
+ return () => {
6690
+ for (const o of hidden) {
6691
+ o.visible = true;
6692
+ const g = o.group;
6693
+ if (g) g.dirty = true;
6694
+ }
6695
+ if (hidden.length) fc.requestRenderAll();
6696
+ };
6697
+ }
6591
6698
  const canvasImageLoader = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
6592
6699
  __proto__: null,
6593
6700
  SVG_DECODE_HARD_CAP,
@@ -6597,12 +6704,14 @@ const canvasImageLoader = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.d
6597
6704
  fetchSvgTextPublic,
6598
6705
  getNormalizedSvgUrl,
6599
6706
  getProxiedImageUrl,
6707
+ hideEditorChromeForExport,
6600
6708
  isBundledAssetUrl,
6601
6709
  isEmptyImagePlaceholderGroup,
6602
6710
  isPrivateUrl,
6603
6711
  isSvgImage,
6604
6712
  loadImageAsync,
6605
6713
  loadSvgDimensions,
6714
+ markPlaceholderAsBrokenImage,
6606
6715
  normalizeSvgImageDimensions,
6607
6716
  parseSvgDimensionsFromText,
6608
6717
  preloadImage,
@@ -10328,10 +10437,10 @@ function textPathBoundsContainScenePoint(obj, point) {
10328
10437
  }
10329
10438
  function drawTextPathBounds(ctx, obj, bounds, hostCanvas) {
10330
10439
  var _a2, _b2, _c2;
10331
- const host = hostCanvas || obj.canvas || ((_a2 = obj.group) == null ? void 0 : _a2.canvas);
10332
- if (!host) return;
10333
- const retina = ((_b2 = host.getRetinaScaling) == null ? void 0 : _b2.call(host)) || ((_c2 = obj.getCanvasRetinaScaling) == null ? void 0 : _c2.call(obj)) || 1;
10334
- const matrix = fabric__namespace.util.multiplyTransformMatrices(host.viewportTransform || [1, 0, 0, 1, 0, 0], obj.calcTransformMatrix());
10440
+ const host2 = hostCanvas || obj.canvas || ((_a2 = obj.group) == null ? void 0 : _a2.canvas);
10441
+ if (!host2) return;
10442
+ const retina = ((_b2 = host2.getRetinaScaling) == null ? void 0 : _b2.call(host2)) || ((_c2 = obj.getCanvasRetinaScaling) == null ? void 0 : _c2.call(obj)) || 1;
10443
+ const matrix = fabric__namespace.util.multiplyTransformMatrices(host2.viewportTransform || [1, 0, 0, 1, 0, 0], obj.calcTransformMatrix());
10335
10444
  const corners = [
10336
10445
  new fabric__namespace.Point(bounds.minX, bounds.minY),
10337
10446
  new fabric__namespace.Point(bounds.maxX, bounds.minY),
@@ -10582,11 +10691,11 @@ if (GroupProto && typeof GroupProto._renderControls === "function" && !GroupProt
10582
10691
  GroupProto.__pixldocsOrigRenderControls = GroupProto._renderControls;
10583
10692
  GroupProto._renderControls = function(ctx, styleOverride, childrenOverride) {
10584
10693
  var _a2;
10585
- const host = this.canvas;
10586
- const active = (_a2 = host == null ? void 0 : host.getActiveObject) == null ? void 0 : _a2.call(host);
10587
- const isActiveOrInActive = !!host && (active === this || !!active && typeof active.contains === "function" && active.contains(this, true));
10694
+ const host2 = this.canvas;
10695
+ const active = (_a2 = host2 == null ? void 0 : host2.getActiveObject) == null ? void 0 : _a2.call(host2);
10696
+ const isActiveOrInActive = !!host2 && (active === this || !!active && typeof active.contains === "function" && active.contains(this, true));
10588
10697
  GroupProto.__pixldocsOrigRenderControls.call(this, ctx, styleOverride, childrenOverride);
10589
- if (!host || !isActiveOrInActive) return;
10698
+ if (!host2 || !isActiveOrInActive) return;
10590
10699
  const drawForDescendants = (parent) => {
10591
10700
  const kids = (parent == null ? void 0 : parent._objects) || [];
10592
10701
  for (const child of kids) {
@@ -10595,7 +10704,7 @@ if (GroupProto && typeof GroupProto._renderControls === "function" && !GroupProt
10595
10704
  }
10596
10705
  if (child && child.textPath && child.textPath.preset && child.textPath.preset !== "none") {
10597
10706
  const bounds = getTextPathHitBounds(child);
10598
- if (bounds) drawTextPathBounds(ctx, child, bounds, host);
10707
+ if (bounds) drawTextPathBounds(ctx, child, bounds, host2);
10599
10708
  }
10600
10709
  }
10601
10710
  };
@@ -18114,6 +18223,7 @@ const PageCanvas = react.forwardRef(
18114
18223
  // Transparent so underlay (page bg + group bgs) shows through
18115
18224
  backgroundColor: "transparent"
18116
18225
  });
18226
+ fabricCanvas.__pixldocsEditMode = isEditorMode && !isPreviewMode;
18117
18227
  fabricCanvas.hoverCursor = "default";
18118
18228
  fabricCanvas.moveCursor = "move";
18119
18229
  const suppressTextEditForClick = (textbox) => {
@@ -25439,6 +25549,7 @@ const PageCanvas = react.forwardRef(
25439
25549
  } else if (obj instanceof fabric__namespace.Textbox) {
25440
25550
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
25441
25551
  let text = element.text != null && element.text !== "" ? element.text : " ";
25552
+ text = applyElementTextCase(text, element.textCase);
25442
25553
  let parsedStyles = null;
25443
25554
  if (element.formattingEnabled === true) {
25444
25555
  const parsed = parseTextMarkdown(text);
@@ -26083,6 +26194,13 @@ const PageCanvas = react.forwardRef(
26083
26194
  const img = await fabric__namespace.FabricImage.fromURL(url, { crossOrigin: "anonymous" });
26084
26195
  if (!fabricRef.current || !isLatestRequest()) return;
26085
26196
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat, svgDecodeTarget);
26197
+ {
26198
+ const natW = img.width ?? 0;
26199
+ const natH = img.height ?? 0;
26200
+ if (natW <= 1 && natH <= 1 && Number(element.width) > 8 && Number(element.height) > 8) {
26201
+ throw new Error(`image decoded to ${natW}x${natH} — treating as failed load`);
26202
+ }
26203
+ }
26086
26204
  if (!isLatestRequest()) return;
26087
26205
  const imageFitForFade = element.imageFit || ((_a2 = element.style) == null ? void 0 : _a2.imageFit) || "cover";
26088
26206
  const clipShapeForFade = element.clipShape ?? ((_b2 = element.style) == null ? void 0 : _b2.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
@@ -26449,6 +26567,24 @@ const PageCanvas = react.forwardRef(
26449
26567
  }
26450
26568
  fc.requestRenderAll();
26451
26569
  } catch (error) {
26570
+ console.error("[PageCanvas] image failed to load", {
26571
+ id: element.id,
26572
+ url: (element.src || element.imageUrl || "").slice(0, 200),
26573
+ error: error instanceof Error ? error.message : String(error)
26574
+ });
26575
+ recordImageLoadFailure({
26576
+ id: String(element.id ?? ""),
26577
+ url: element.src || element.imageUrl || "",
26578
+ reason: error instanceof Error ? error.message : String(error)
26579
+ });
26580
+ const fcNow = fabricRef.current;
26581
+ if (fcNow && fcNow.__pixldocsEditMode === true) {
26582
+ try {
26583
+ markPlaceholderAsBrokenImage(placeholder);
26584
+ fcNow.requestRenderAll();
26585
+ } catch {
26586
+ }
26587
+ }
26452
26588
  }
26453
26589
  };
26454
26590
  const handleCanvasClick = react.useCallback(
@@ -27772,6 +27908,7 @@ function PreviewCanvas({
27772
27908
  const pageSettings = react.useMemo(() => {
27773
27909
  var _a3, _b3;
27774
27910
  return {
27911
+ // design-drift-ignore: artifact paper colour, not chrome
27775
27912
  backgroundColor: ((_a3 = page == null ? void 0 : page.settings) == null ? void 0 : _a3.backgroundColor) || "#ffffff",
27776
27913
  backgroundGradient: (_b3 = page == null ? void 0 : page.settings) == null ? void 0 : _b3.backgroundGradient
27777
27914
  };
@@ -32394,11 +32531,31 @@ async function resolveTemplateData(options) {
32394
32531
  ...formData ?? {}
32395
32532
  };
32396
32533
  const dynamicFields = config.dynamicFields;
32397
- if (!Array.isArray(dynamicFields) || dynamicFields.length === 0) {
32534
+ const hasBoundTable = (() => {
32535
+ let found = false;
32536
+ const walk = (nodes) => {
32537
+ var _a2, _b2;
32538
+ for (const n of nodes || []) {
32539
+ if (found) return;
32540
+ 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)) {
32541
+ found = true;
32542
+ return;
32543
+ }
32544
+ walk(n == null ? void 0 : n.children);
32545
+ }
32546
+ };
32547
+ for (const p of config.pages || []) walk(p.children);
32548
+ return found;
32549
+ })();
32550
+ const hasDynamicFields = Array.isArray(dynamicFields) && dynamicFields.length > 0;
32551
+ if (!hasDynamicFields && (hasBoundTable || repeatableSectionsInput.length > 0)) {
32552
+ console.log("[resolveTemplateData] no dynamicFields; binding via bound table / repeatable sections");
32553
+ }
32554
+ if (!hasDynamicFields && !hasBoundTable && repeatableSectionsInput.length === 0) {
32398
32555
  return { config: applyThemeIfNeeded(config), templateName: template.name || "Untitled", templateId, price: template.price ?? 0 };
32399
32556
  }
32400
32557
  const mappings = [];
32401
- for (const f of dynamicFields) {
32558
+ for (const f of dynamicFields || []) {
32402
32559
  for (const m of f.mappings || []) {
32403
32560
  mappings.push({
32404
32561
  field_key: f.id,
@@ -34119,6 +34276,14 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
34119
34276
  try {
34120
34277
  const visit = (obj) => {
34121
34278
  if (!obj) return;
34279
+ if (obj.__pixldocsEditorChrome) {
34280
+ const originalToSVG = typeof obj.toSVG === "function" ? obj.toSVG.bind(obj) : null;
34281
+ if (originalToSVG) {
34282
+ obj.toSVG = () => "";
34283
+ svgPatchRecords.push({ obj, originalToSVG });
34284
+ }
34285
+ return;
34286
+ }
34122
34287
  const imageId = typeof obj.__docuforgeId === "string" && hasRenderableRasterCandidate(obj) ? obj.__docuforgeId : "";
34123
34288
  const alphaGradient = objectNeedsAlphaGradientRaster(obj);
34124
34289
  if ((isTextboxLike(obj) || imageId || alphaGradient) && typeof obj.toSVG === "function") {
@@ -34226,9 +34391,22 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
34226
34391
  }
34227
34392
  return svgString;
34228
34393
  }
34229
- const resolvedPackageVersion = "0.5.496";
34394
+ const resolvedPackageVersion = "0.5.498";
34230
34395
  const PACKAGE_VERSION = resolvedPackageVersion;
34231
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.496";
34396
+ async function crPhase(name, fn) {
34397
+ const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
34398
+ const t0 = now();
34399
+ try {
34400
+ return await fn();
34401
+ } finally {
34402
+ const g = globalThis;
34403
+ const bag = g.__CR_PHASES__ || (g.__CR_PHASES__ = {});
34404
+ const cur = bag[name] || (bag[name] = { count: 0, ms: 0 });
34405
+ cur.count += 1;
34406
+ cur.ms += Math.round(now() - t0);
34407
+ }
34408
+ }
34409
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.498";
34232
34410
  const roundParityValue = (value) => {
34233
34411
  if (typeof value !== "number") return value;
34234
34412
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -34667,6 +34845,11 @@ class PixldocsRenderer {
34667
34845
  * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
34668
34846
  */
34669
34847
  async render(templateConfig, options = {}) {
34848
+ if (options.watermark === true && !templateConfig.__pixldocsWatermarked) {
34849
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34850
+ templateConfig = injectWatermark2(templateConfig, options.watermarkOptions);
34851
+ templateConfig.__pixldocsWatermarked = true;
34852
+ }
34670
34853
  const pageIndex = options.pageIndex ?? 0;
34671
34854
  const format = options.format ?? "png";
34672
34855
  const quality = options.quality ?? 0.92;
@@ -34682,6 +34865,7 @@ class PixldocsRenderer {
34682
34865
  const hasAutoShrink = configHasAutoShrinkText(templateConfig);
34683
34866
  const defaultWait = hasAutoShrink ? 4e3 : 1800;
34684
34867
  await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
34868
+ this.flushTextMeasurementCaches();
34685
34869
  }
34686
34870
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
34687
34871
  setPackageApiUrl2(this.config.imageProxyUrl);
@@ -34709,6 +34893,7 @@ class PixldocsRenderer {
34709
34893
  const hasAutoShrink = configHasAutoShrinkText(templateConfig);
34710
34894
  const defaultWait = hasAutoShrink ? 4e3 : 1800;
34711
34895
  await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
34896
+ this.flushTextMeasurementCaches();
34712
34897
  }
34713
34898
  const results = [];
34714
34899
  for (let i = 0; i < templateConfig.pages.length; i++) {
@@ -34735,8 +34920,8 @@ class PixldocsRenderer {
34735
34920
  const shouldWatermark = watermark ?? resolved.price > 0;
34736
34921
  let configToRender = resolved.config;
34737
34922
  if (shouldWatermark) {
34738
- const { injectWatermark } = await Promise.resolve().then(() => require("./canvasWatermark-B0ab38Ok.cjs"));
34739
- configToRender = injectWatermark(configToRender, watermarkOptions);
34923
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34924
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34740
34925
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34741
34926
  configToRender = injectPreviewBlur2(configToRender);
34742
34927
  }
@@ -34795,8 +34980,8 @@ class PixldocsRenderer {
34795
34980
  const shouldWatermark = watermark ?? resolved.price > 0;
34796
34981
  let configToRender = resolved.config;
34797
34982
  if (shouldWatermark) {
34798
- const { injectWatermark } = await Promise.resolve().then(() => require("./canvasWatermark-B0ab38Ok.cjs"));
34799
- configToRender = injectWatermark(configToRender, watermarkOptions);
34983
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34984
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34800
34985
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34801
34986
  configToRender = injectPreviewBlur2(configToRender);
34802
34987
  }
@@ -34816,6 +35001,10 @@ class PixldocsRenderer {
34816
35001
  * exporter, which is what `renderPdfViaClientExport` does below.
34817
35002
  */
34818
35003
  async renderPdf(templateConfig, options) {
35004
+ if ((options == null ? void 0 : options.watermark) === true) {
35005
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
35006
+ templateConfig = injectWatermark2(templateConfig, options.watermarkOptions);
35007
+ }
34819
35008
  return this.renderPdfViaClientExport(templateConfig, {
34820
35009
  title: options == null ? void 0 : options.title,
34821
35010
  textMode: options == null ? void 0 : options.textMode,
@@ -34844,8 +35033,8 @@ class PixldocsRenderer {
34844
35033
  const shouldWatermark = watermark ?? resolved.price > 0;
34845
35034
  let configToRender = resolved.config;
34846
35035
  if (shouldWatermark) {
34847
- const { injectWatermark } = await Promise.resolve().then(() => require("./canvasWatermark-B0ab38Ok.cjs"));
34848
- configToRender = injectWatermark(configToRender, watermarkOptions);
35036
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
35037
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34849
35038
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34850
35039
  configToRender = injectPreviewBlur2(configToRender);
34851
35040
  }
@@ -35016,7 +35205,7 @@ class PixldocsRenderer {
35016
35205
  containers.push(container);
35017
35206
  const root = client.createRoot(container);
35018
35207
  roots.push(root);
35019
- await new Promise((resolve, reject) => {
35208
+ await crPhase("pdf_mount", () => new Promise((resolve, reject) => {
35020
35209
  const timer = setTimeout(() => reject(new Error(`PreviewCanvas onReady timeout for page ${i}`)), 3e4);
35021
35210
  root.render(
35022
35211
  react.createElement(PreviewCanvas2, {
@@ -35034,15 +35223,15 @@ class PixldocsRenderer {
35034
35223
  }
35035
35224
  })
35036
35225
  );
35037
- });
35038
- await this.waitForCanvasScene(container, cloned, i);
35226
+ }));
35227
+ await crPhase("pdf_scene1", () => this.waitForCanvasScene(container, cloned, i));
35039
35228
  const expected = this.getExpectedImageIds(cloned, i);
35040
- await this.waitForCanvasImages(container, expected);
35041
- await this.waitForStableTextMetrics(container, cloned, { clearGlobalCharCache: false });
35042
- await this.waitForCanvasScene(container, cloned, i);
35229
+ await crPhase("pdf_images", () => this.waitForCanvasImages(container, expected));
35230
+ await crPhase("pdf_text_metrics", () => this.waitForStableTextMetrics(container, cloned, { clearGlobalCharCache: false }));
35231
+ await crPhase("pdf_scene2", () => this.waitForCanvasScene(container, cloned, i));
35043
35232
  }
35044
35233
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
35045
- const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-B2KtFs76.cjs"));
35234
+ const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-6Hi7Og4W.cjs"));
35046
35235
  const prepared = preparePagesForExport(
35047
35236
  cloned.pages,
35048
35237
  canvasWidth,
@@ -35220,8 +35409,14 @@ class PixldocsRenderer {
35220
35409
  }
35221
35410
  const sceneSettled = fabricReady && allDomLoaded && canvasReady && fabricObjects.length > 0;
35222
35411
  const idleFor = Date.now() - lastProgressAt;
35223
- if (!strictById && sceneSettled && actualImageCount < expectedImageCount && idleFor >= earlyExitMs) {
35224
- if (debug) {
35412
+ const strictIdleMs = Math.max(earlyExitMs, 4e3);
35413
+ const stalled = strictById ? missingImageIds.length > 0 : actualImageCount < expectedImageCount;
35414
+ if (sceneSettled && stalled && idleFor >= (strictById ? strictIdleMs : earlyExitMs)) {
35415
+ if (strictById) {
35416
+ console.warn(
35417
+ `[canvas-renderer][asset-wait] early-exit after ${elapsed}ms with unresolved image id(s): ${missingImageIds.join(", ")} (idle=${idleFor}ms, ${summary})`
35418
+ );
35419
+ } else if (debug) {
35225
35420
  console.log(
35226
35421
  `[canvas-renderer][asset-wait] early-exit after ${elapsed}ms (idle=${idleFor}ms, ${summary})`
35227
35422
  );
@@ -35320,6 +35515,37 @@ class PixldocsRenderer {
35320
35515
  new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
35321
35516
  ]);
35322
35517
  }
35518
+ /**
35519
+ * Flush BOTH process-global text-measurement caches after fonts are ready
35520
+ * and BEFORE the capture canvas mounts.
35521
+ *
35522
+ * Why: anything that measured text before the webfonts finished loading —
35523
+ * most notably `applyContentBoundsPagination` running inside
35524
+ * `resolveFromForm` on a cold page (the EC2/MCP flow: resolve + render in
35525
+ * one browser context) — populates the app measurement cache and Fabric's
35526
+ * global char-width cache with FALLBACK metrics. `createText` then places
35527
+ * center/right-aligned lines from those stale widths, so a right-aligned
35528
+ * grid cell on a continuation page drew shifted left by
35529
+ * (fallbackWidth − realWidth) px vs the editor (caught by
35530
+ * tests/parity `auto-paginate`, a 9px shift). The later
35531
+ * `waitForStableTextMetrics` pass re-primes glyph bounds but never moves
35532
+ * the already-positioned box, so the flush must happen pre-mount.
35533
+ *
35534
+ * Only called from the headless PNG paths (`render`/`renderAllPages`),
35535
+ * which already default to clearing the global char cache in
35536
+ * `waitForStableTextMetrics` — the live-preview-adjacent PDF path keeps
35537
+ * its `clearGlobalCharCache: false` behaviour untouched.
35538
+ */
35539
+ flushTextMeasurementCaches() {
35540
+ try {
35541
+ clearMeasurementCache();
35542
+ } catch {
35543
+ }
35544
+ try {
35545
+ clearFabricCharCache();
35546
+ } catch {
35547
+ }
35548
+ }
35323
35549
  getNormalizedGradientStops(gradient) {
35324
35550
  const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
35325
35551
  offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
@@ -35409,6 +35635,8 @@ class PixldocsRenderer {
35409
35635
  });
35410
35636
  }
35411
35637
  return new Promise((resolve, reject) => {
35638
+ const mountStartedAt = typeof performance !== "undefined" ? performance.now() : Date.now();
35639
+ let mountRecorded = false;
35412
35640
  const container = document.createElement("div");
35413
35641
  container.style.cssText = `
35414
35642
  position: fixed; left: -99999px; top: -99999px;
@@ -35473,13 +35701,22 @@ class PixldocsRenderer {
35473
35701
  });
35474
35702
  };
35475
35703
  const onReady = () => {
35476
- this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
35704
+ if (!mountRecorded) {
35705
+ mountRecorded = true;
35706
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
35707
+ const g = globalThis;
35708
+ const bag = g.__CR_PHASES__ || (g.__CR_PHASES__ = {});
35709
+ const cur = bag.raster_mount || (bag.raster_mount = { count: 0, ms: 0 });
35710
+ cur.count += 1;
35711
+ cur.ms += Math.round(now - mountStartedAt);
35712
+ }
35713
+ crPhase("raster_scene1", () => this.waitForCanvasScene(container, renderConfig, pageIndex)).then(async () => {
35477
35714
  try {
35478
35715
  const fabricInstance = this.getFabricCanvasFromContainer(container);
35479
35716
  const expectedImageIds = this.getExpectedImageIds(renderConfig, pageIndex);
35480
- await this.waitForCanvasImages(container, expectedImageIds);
35481
- await this.waitForStableTextMetrics(container, renderConfig);
35482
- await this.waitForCanvasScene(container, renderConfig, pageIndex);
35717
+ await crPhase("raster_images", () => this.waitForCanvasImages(container, expectedImageIds));
35718
+ await crPhase("raster_text_metrics", () => this.waitForStableTextMetrics(container, renderConfig));
35719
+ await crPhase("raster_scene2", () => this.waitForCanvasScene(container, renderConfig, pageIndex));
35483
35720
  firstMountSettled = true;
35484
35721
  if (hasAutoShrink && lateFontSettleDetected) {
35485
35722
  console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
@@ -37362,7 +37599,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
37362
37599
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
37363
37600
  sanitizeSvgTreeForPdf(svgToDraw);
37364
37601
  try {
37365
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-B2KtFs76.cjs"));
37602
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-6Hi7Og4W.cjs"));
37366
37603
  try {
37367
37604
  await logTextMeasurementDiagnostic(svgToDraw);
37368
37605
  } catch {
@@ -37610,6 +37847,129 @@ async function getPublishedTemplate(options) {
37610
37847
  const rows = await res.json();
37611
37848
  return rows[0] ?? null;
37612
37849
  }
37850
+ const WATERMARK_ID_PREFIX = "__wm_";
37851
+ const DEFAULT_WATERMARK_OPACITY = 0.14;
37852
+ const DEFAULT_WATERMARK_FONT_SIZE = 18;
37853
+ const DEFAULT_WATERMARK_ANGLE = -30;
37854
+ function getWatermarkText() {
37855
+ try {
37856
+ const host2 = window.location.hostname;
37857
+ if (host2.includes("biomaker")) return "biomaker.app";
37858
+ } catch {
37859
+ }
37860
+ return "pixldocs.com";
37861
+ }
37862
+ function getLuminance(color) {
37863
+ if (!color || color === "transparent" || color === "none") return 1;
37864
+ let r = 255, g = 255, b = 255;
37865
+ if (color.startsWith("#")) {
37866
+ const hex = color.slice(1);
37867
+ const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
37868
+ const n = parseInt(full, 16);
37869
+ r = n >> 16 & 255;
37870
+ g = n >> 8 & 255;
37871
+ b = n & 255;
37872
+ } else {
37873
+ const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
37874
+ if (m) {
37875
+ r = +m[1];
37876
+ g = +m[2];
37877
+ b = +m[3];
37878
+ }
37879
+ }
37880
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
37881
+ }
37882
+ function getWatermarkColor(bgColor) {
37883
+ const lum = getLuminance(bgColor);
37884
+ return lum < 0.45 ? "#cccccc" : "#808080";
37885
+ }
37886
+ function getPageBgColor(page, canvasBg) {
37887
+ var _a2, _b2;
37888
+ const settingsBg = (_a2 = page.settings) == null ? void 0 : _a2.backgroundColor;
37889
+ if (settingsBg && settingsBg !== "transparent" && settingsBg !== "none") {
37890
+ return settingsBg;
37891
+ }
37892
+ const children = page.children || [];
37893
+ for (const child of children) {
37894
+ if (child.type === "shape" && child.fill && child.fill !== "transparent" && child.fill !== "none") {
37895
+ return child.fill;
37896
+ }
37897
+ if ((_b2 = child.children) == null ? void 0 : _b2.length) {
37898
+ for (const gc of child.children) {
37899
+ if (gc.type === "shape" && gc.fill && gc.fill !== "transparent" && gc.fill !== "none") {
37900
+ return gc.fill;
37901
+ }
37902
+ }
37903
+ }
37904
+ }
37905
+ return canvasBg;
37906
+ }
37907
+ function generateWatermarkElements(canvasWidth, canvasHeight, wmColor, options = {}) {
37908
+ const elements = [];
37909
+ const text = options.text ?? getWatermarkText();
37910
+ const fontSize = options.fontSize ?? DEFAULT_WATERMARK_FONT_SIZE;
37911
+ const angle = options.angle ?? DEFAULT_WATERMARK_ANGLE;
37912
+ const opacity = options.opacity ?? DEFAULT_WATERMARK_OPACITY;
37913
+ const estimatedTextWidth = text.length * fontSize * 0.55;
37914
+ const spacingX = Math.max(160, estimatedTextWidth + 30);
37915
+ const spacingY = 90;
37916
+ const startX = -canvasWidth * 0.3;
37917
+ const startY = -canvasHeight * 0.3;
37918
+ const endX = canvasWidth * 1.5;
37919
+ const endY = canvasHeight * 1.5;
37920
+ let idx = 0;
37921
+ for (let y = startY; y < endY; y += spacingY) {
37922
+ for (let x = startX; x < endX; x += spacingX) {
37923
+ elements.push({
37924
+ id: `${WATERMARK_ID_PREFIX}${idx++}`,
37925
+ type: "text",
37926
+ left: x,
37927
+ top: y,
37928
+ width: 200,
37929
+ height: 40,
37930
+ text,
37931
+ fontFamily: "Montserrat",
37932
+ fontSize,
37933
+ fontWeight: "600",
37934
+ fill: wmColor,
37935
+ angle,
37936
+ selectable: false,
37937
+ locked: true,
37938
+ visible: true,
37939
+ opacity,
37940
+ scaleX: 1,
37941
+ scaleY: 1,
37942
+ letterSpacing: 4
37943
+ });
37944
+ }
37945
+ }
37946
+ return elements;
37947
+ }
37948
+ function injectWatermark(config, options = {}) {
37949
+ var _a2, _b2, _c2;
37950
+ const canvasWidth = ((_a2 = config.canvas) == null ? void 0 : _a2.width) || 612;
37951
+ const canvasHeight = ((_b2 = config.canvas) == null ? void 0 : _b2.height) || 792;
37952
+ const canvasBg = (_c2 = config.canvas) == null ? void 0 : _c2.backgroundColor;
37953
+ return {
37954
+ ...config,
37955
+ pages: config.pages.map((page) => {
37956
+ const pageBg = getPageBgColor(page, canvasBg);
37957
+ const wmColor = getWatermarkColor(pageBg);
37958
+ const watermarkElements = generateWatermarkElements(canvasWidth, canvasHeight, wmColor, options);
37959
+ return {
37960
+ ...page,
37961
+ children: [
37962
+ ...page.children || [],
37963
+ ...watermarkElements
37964
+ ]
37965
+ };
37966
+ })
37967
+ };
37968
+ }
37969
+ const canvasWatermark = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
37970
+ __proto__: null,
37971
+ injectWatermark
37972
+ }, Symbol.toStringTag, { value: "Module" }));
37613
37973
  function setAutoShrinkDebug(enabled) {
37614
37974
  if (typeof window !== "undefined") {
37615
37975
  window.__pixldocsDebugAutoShrink = !!enabled;
@@ -37656,6 +38016,7 @@ exports.getWorldAngleDeg = getWorldAngleDeg$1;
37656
38016
  exports.hasAnyPreviewBlur = hasAnyPreviewBlur;
37657
38017
  exports.hasEdgeFade = hasEdgeFade;
37658
38018
  exports.injectPreviewBlur = injectPreviewBlur;
38019
+ exports.injectWatermark = injectWatermark;
37659
38020
  exports.isBundledAssetUrl = isBundledAssetUrl;
37660
38021
  exports.isElement = isElement;
37661
38022
  exports.isFontAvailable = isFontAvailable;
@@ -37677,4 +38038,4 @@ exports.setAutoShrinkDebug = setAutoShrinkDebug;
37677
38038
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
37678
38039
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
37679
38040
  exports.warmTemplateFromForm = warmTemplateFromForm;
37680
- //# sourceMappingURL=index-CZtvER5z.cjs.map
38041
+ //# sourceMappingURL=index-CUZInOyk.cjs.map