@pixldocs/canvas-renderer 0.5.496 → 0.5.497

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;
@@ -6368,8 +6343,13 @@ async function loadImageAsync(element, placeholder, fc, fabricRef, syncLockedRef
6368
6343
  });
6369
6344
  const imgLoadOptions = url.startsWith("data:") || url.startsWith("blob:") ? {} : { crossOrigin: "anonymous" };
6370
6345
  const img = await fabric.FabricImage.fromURL(url, imgLoadOptions);
6371
- if (!fabricRef.current) return;
6346
+ if (fabricRef && !fabricRef.current) return;
6372
6347
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat);
6348
+ const natW = img.width ?? 0;
6349
+ const natH = img.height ?? 0;
6350
+ if (natW <= 1 && natH <= 1 && Number(element.width) > 8 && Number(element.height) > 8) {
6351
+ throw new Error(`image decoded to ${natW}x${natH} — treating as failed load`);
6352
+ }
6373
6353
  const isHidden = !element.visible;
6374
6354
  img.set({
6375
6355
  originX: "left",
@@ -6567,9 +6547,111 @@ async function loadImageAsync(element, placeholder, fc, fabricRef, syncLockedRef
6567
6547
  proxiedUrl: getProxiedImageUrl(imageUrl).slice(0, 240),
6568
6548
  error: error instanceof Error ? error.message : String(error)
6569
6549
  });
6550
+ if (fc.__pixldocsEditMode === true) {
6551
+ try {
6552
+ markPlaceholderAsBrokenImage(placeholder);
6553
+ fc.requestRenderAll();
6554
+ } catch {
6555
+ }
6556
+ }
6570
6557
  return;
6571
6558
  }
6572
6559
  }
6560
+ function markPlaceholderAsBrokenImage(placeholder) {
6561
+ if (!(placeholder instanceof fabric.Group)) return;
6562
+ const group = placeholder;
6563
+ if (group.__pixldocsBrokenImage) return;
6564
+ group.__pixldocsBrokenImage = true;
6565
+ const frameChild = group.getObjects().find(
6566
+ (o) => o.__isPlaceholderFrame
6567
+ );
6568
+ const w = Math.max(1, (frameChild == null ? void 0 : frameChild.width) ?? group.width ?? 1);
6569
+ const h = Math.max(1, (frameChild == null ? void 0 : frameChild.height) ?? group.height ?? 1);
6570
+ const center = group.getCenterPoint();
6571
+ const cx = center.x;
6572
+ const cy = center.y;
6573
+ const prevPose = { left: group.left, top: group.top, width: group.width, height: group.height };
6574
+ const bg = new fabric.Rect({
6575
+ originX: "center",
6576
+ originY: "center",
6577
+ left: cx,
6578
+ top: cy,
6579
+ width: w,
6580
+ height: h,
6581
+ fill: "rgba(148, 163, 184, 0.12)",
6582
+ stroke: "#94A3B8",
6583
+ strokeWidth: 1.5,
6584
+ strokeDashArray: [6, 4]
6585
+ });
6586
+ const s = Math.max(16, Math.min(w, h) * 0.28);
6587
+ const glyphStroke = { stroke: "#64748B", strokeWidth: Math.max(1.5, s / 12), fill: "transparent" };
6588
+ const iconFrame = new fabric.Rect({
6589
+ originX: "center",
6590
+ originY: "center",
6591
+ left: cx,
6592
+ top: cy,
6593
+ width: s,
6594
+ height: s * 0.8,
6595
+ rx: s * 0.08,
6596
+ ry: s * 0.08,
6597
+ ...glyphStroke
6598
+ });
6599
+ const mountain = new fabric.Polyline(
6600
+ [
6601
+ { x: -s * 0.38, y: s * 0.24 },
6602
+ { x: -s * 0.1, y: -s * 0.08 },
6603
+ { x: s * 0.08, y: s * 0.1 },
6604
+ { x: s * 0.22, y: -s * 0.02 },
6605
+ { x: s * 0.38, y: s * 0.24 }
6606
+ ],
6607
+ { originX: "center", originY: "center", left: cx, top: cy + s * 0.08, ...glyphStroke }
6608
+ );
6609
+ const slash = new fabric.Line([cx - s * 0.55, cy - s * 0.5, cx + s * 0.55, cy + s * 0.5], {
6610
+ ...glyphStroke,
6611
+ originX: "center",
6612
+ originY: "center",
6613
+ left: cx,
6614
+ top: cy
6615
+ });
6616
+ for (const obj of [bg, iconFrame, mountain, slash]) {
6617
+ obj.set({ selectable: false, evented: false, excludeFromExport: true });
6618
+ obj.__pixldocsEditorChrome = true;
6619
+ group.add(obj);
6620
+ }
6621
+ group.set(prevPose);
6622
+ group.setCoords();
6623
+ group.dirty = true;
6624
+ }
6625
+ function hideEditorChromeForExport(fc) {
6626
+ const hidden = [];
6627
+ const walk = (objs) => {
6628
+ var _a2;
6629
+ for (const o of objs) {
6630
+ if (o.__pixldocsEditorChrome && o.visible !== false) {
6631
+ o.visible = false;
6632
+ hidden.push(o);
6633
+ }
6634
+ const kids = (_a2 = o.getObjects) == null ? void 0 : _a2.call(o);
6635
+ if (kids == null ? void 0 : kids.length) walk(kids);
6636
+ }
6637
+ };
6638
+ walk(fc.getObjects());
6639
+ if (hidden.length) {
6640
+ for (const o of hidden) {
6641
+ const g = o.group;
6642
+ if (g) g.dirty = true;
6643
+ }
6644
+ fc.renderAll();
6645
+ }
6646
+ return () => {
6647
+ for (const o of hidden) {
6648
+ o.visible = true;
6649
+ const g = o.group;
6650
+ if (g) g.dirty = true;
6651
+ }
6652
+ if (hidden.length) fc.requestRenderAll();
6653
+ };
6654
+ }
6573
6655
  const canvasImageLoader = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
6574
6656
  __proto__: null,
6575
6657
  SVG_DECODE_HARD_CAP,
@@ -6579,12 +6661,14 @@ const canvasImageLoader = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.d
6579
6661
  fetchSvgTextPublic,
6580
6662
  getNormalizedSvgUrl,
6581
6663
  getProxiedImageUrl,
6664
+ hideEditorChromeForExport,
6582
6665
  isBundledAssetUrl,
6583
6666
  isEmptyImagePlaceholderGroup,
6584
6667
  isPrivateUrl,
6585
6668
  isSvgImage,
6586
6669
  loadImageAsync,
6587
6670
  loadSvgDimensions,
6671
+ markPlaceholderAsBrokenImage,
6588
6672
  normalizeSvgImageDimensions,
6589
6673
  parseSvgDimensionsFromText,
6590
6674
  preloadImage,
@@ -18096,6 +18180,7 @@ const PageCanvas = forwardRef(
18096
18180
  // Transparent so underlay (page bg + group bgs) shows through
18097
18181
  backgroundColor: "transparent"
18098
18182
  });
18183
+ fabricCanvas.__pixldocsEditMode = isEditorMode && !isPreviewMode;
18099
18184
  fabricCanvas.hoverCursor = "default";
18100
18185
  fabricCanvas.moveCursor = "move";
18101
18186
  const suppressTextEditForClick = (textbox) => {
@@ -25421,6 +25506,7 @@ const PageCanvas = forwardRef(
25421
25506
  } else if (obj instanceof fabric.Textbox) {
25422
25507
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
25423
25508
  let text = element.text != null && element.text !== "" ? element.text : " ";
25509
+ text = applyElementTextCase(text, element.textCase);
25424
25510
  let parsedStyles = null;
25425
25511
  if (element.formattingEnabled === true) {
25426
25512
  const parsed = parseTextMarkdown(text);
@@ -26065,6 +26151,13 @@ const PageCanvas = forwardRef(
26065
26151
  const img = await fabric.FabricImage.fromURL(url, { crossOrigin: "anonymous" });
26066
26152
  if (!fabricRef.current || !isLatestRequest()) return;
26067
26153
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat, svgDecodeTarget);
26154
+ {
26155
+ const natW = img.width ?? 0;
26156
+ const natH = img.height ?? 0;
26157
+ if (natW <= 1 && natH <= 1 && Number(element.width) > 8 && Number(element.height) > 8) {
26158
+ throw new Error(`image decoded to ${natW}x${natH} — treating as failed load`);
26159
+ }
26160
+ }
26068
26161
  if (!isLatestRequest()) return;
26069
26162
  const imageFitForFade = element.imageFit || ((_a2 = element.style) == null ? void 0 : _a2.imageFit) || "cover";
26070
26163
  const clipShapeForFade = element.clipShape ?? ((_b2 = element.style) == null ? void 0 : _b2.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
@@ -26431,6 +26524,19 @@ const PageCanvas = forwardRef(
26431
26524
  }
26432
26525
  fc.requestRenderAll();
26433
26526
  } catch (error) {
26527
+ console.error("[PageCanvas] image failed to load", {
26528
+ id: element.id,
26529
+ url: (element.src || element.imageUrl || "").slice(0, 200),
26530
+ error: error instanceof Error ? error.message : String(error)
26531
+ });
26532
+ const fcNow = fabricRef.current;
26533
+ if (fcNow && fcNow.__pixldocsEditMode === true) {
26534
+ try {
26535
+ markPlaceholderAsBrokenImage(placeholder);
26536
+ fcNow.requestRenderAll();
26537
+ } catch {
26538
+ }
26539
+ }
26434
26540
  }
26435
26541
  };
26436
26542
  const handleCanvasClick = useCallback(
@@ -34101,6 +34207,14 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
34101
34207
  try {
34102
34208
  const visit = (obj) => {
34103
34209
  if (!obj) return;
34210
+ if (obj.__pixldocsEditorChrome) {
34211
+ const originalToSVG = typeof obj.toSVG === "function" ? obj.toSVG.bind(obj) : null;
34212
+ if (originalToSVG) {
34213
+ obj.toSVG = () => "";
34214
+ svgPatchRecords.push({ obj, originalToSVG });
34215
+ }
34216
+ return;
34217
+ }
34104
34218
  const imageId = typeof obj.__docuforgeId === "string" && hasRenderableRasterCandidate(obj) ? obj.__docuforgeId : "";
34105
34219
  const alphaGradient = objectNeedsAlphaGradientRaster(obj);
34106
34220
  if ((isTextboxLike(obj) || imageId || alphaGradient) && typeof obj.toSVG === "function") {
@@ -34208,9 +34322,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
34208
34322
  }
34209
34323
  return svgString;
34210
34324
  }
34211
- const resolvedPackageVersion = "0.5.496";
34325
+ const resolvedPackageVersion = "0.5.497";
34212
34326
  const PACKAGE_VERSION = resolvedPackageVersion;
34213
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.496";
34327
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.497";
34214
34328
  const roundParityValue = (value) => {
34215
34329
  if (typeof value !== "number") return value;
34216
34330
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -34649,6 +34763,11 @@ class PixldocsRenderer {
34649
34763
  * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
34650
34764
  */
34651
34765
  async render(templateConfig, options = {}) {
34766
+ if (options.watermark === true && !templateConfig.__pixldocsWatermarked) {
34767
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34768
+ templateConfig = injectWatermark2(templateConfig, options.watermarkOptions);
34769
+ templateConfig.__pixldocsWatermarked = true;
34770
+ }
34652
34771
  const pageIndex = options.pageIndex ?? 0;
34653
34772
  const format = options.format ?? "png";
34654
34773
  const quality = options.quality ?? 0.92;
@@ -34664,6 +34783,7 @@ class PixldocsRenderer {
34664
34783
  const hasAutoShrink = configHasAutoShrinkText(templateConfig);
34665
34784
  const defaultWait = hasAutoShrink ? 4e3 : 1800;
34666
34785
  await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
34786
+ this.flushTextMeasurementCaches();
34667
34787
  }
34668
34788
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
34669
34789
  setPackageApiUrl2(this.config.imageProxyUrl);
@@ -34691,6 +34811,7 @@ class PixldocsRenderer {
34691
34811
  const hasAutoShrink = configHasAutoShrinkText(templateConfig);
34692
34812
  const defaultWait = hasAutoShrink ? 4e3 : 1800;
34693
34813
  await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
34814
+ this.flushTextMeasurementCaches();
34694
34815
  }
34695
34816
  const results = [];
34696
34817
  for (let i = 0; i < templateConfig.pages.length; i++) {
@@ -34717,8 +34838,8 @@ class PixldocsRenderer {
34717
34838
  const shouldWatermark = watermark ?? resolved.price > 0;
34718
34839
  let configToRender = resolved.config;
34719
34840
  if (shouldWatermark) {
34720
- const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
34721
- configToRender = injectWatermark(configToRender, watermarkOptions);
34841
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34842
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34722
34843
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34723
34844
  configToRender = injectPreviewBlur2(configToRender);
34724
34845
  }
@@ -34777,8 +34898,8 @@ class PixldocsRenderer {
34777
34898
  const shouldWatermark = watermark ?? resolved.price > 0;
34778
34899
  let configToRender = resolved.config;
34779
34900
  if (shouldWatermark) {
34780
- const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
34781
- configToRender = injectWatermark(configToRender, watermarkOptions);
34901
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34902
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34782
34903
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34783
34904
  configToRender = injectPreviewBlur2(configToRender);
34784
34905
  }
@@ -34798,6 +34919,10 @@ class PixldocsRenderer {
34798
34919
  * exporter, which is what `renderPdfViaClientExport` does below.
34799
34920
  */
34800
34921
  async renderPdf(templateConfig, options) {
34922
+ if ((options == null ? void 0 : options.watermark) === true) {
34923
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34924
+ templateConfig = injectWatermark2(templateConfig, options.watermarkOptions);
34925
+ }
34801
34926
  return this.renderPdfViaClientExport(templateConfig, {
34802
34927
  title: options == null ? void 0 : options.title,
34803
34928
  textMode: options == null ? void 0 : options.textMode,
@@ -34826,8 +34951,8 @@ class PixldocsRenderer {
34826
34951
  const shouldWatermark = watermark ?? resolved.price > 0;
34827
34952
  let configToRender = resolved.config;
34828
34953
  if (shouldWatermark) {
34829
- const { injectWatermark } = await import("./canvasWatermark-pkhacGge.js");
34830
- configToRender = injectWatermark(configToRender, watermarkOptions);
34954
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34955
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34831
34956
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34832
34957
  configToRender = injectPreviewBlur2(configToRender);
34833
34958
  }
@@ -35024,7 +35149,7 @@ class PixldocsRenderer {
35024
35149
  await this.waitForCanvasScene(container, cloned, i);
35025
35150
  }
35026
35151
  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");
35152
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-DV-TJwjP.js");
35028
35153
  const prepared = preparePagesForExport(
35029
35154
  cloned.pages,
35030
35155
  canvasWidth,
@@ -35302,6 +35427,37 @@ class PixldocsRenderer {
35302
35427
  new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
35303
35428
  ]);
35304
35429
  }
35430
+ /**
35431
+ * Flush BOTH process-global text-measurement caches after fonts are ready
35432
+ * and BEFORE the capture canvas mounts.
35433
+ *
35434
+ * Why: anything that measured text before the webfonts finished loading —
35435
+ * most notably `applyContentBoundsPagination` running inside
35436
+ * `resolveFromForm` on a cold page (the EC2/MCP flow: resolve + render in
35437
+ * one browser context) — populates the app measurement cache and Fabric's
35438
+ * global char-width cache with FALLBACK metrics. `createText` then places
35439
+ * center/right-aligned lines from those stale widths, so a right-aligned
35440
+ * grid cell on a continuation page drew shifted left by
35441
+ * (fallbackWidth − realWidth) px vs the editor (caught by
35442
+ * tests/parity `auto-paginate`, a 9px shift). The later
35443
+ * `waitForStableTextMetrics` pass re-primes glyph bounds but never moves
35444
+ * the already-positioned box, so the flush must happen pre-mount.
35445
+ *
35446
+ * Only called from the headless PNG paths (`render`/`renderAllPages`),
35447
+ * which already default to clearing the global char cache in
35448
+ * `waitForStableTextMetrics` — the live-preview-adjacent PDF path keeps
35449
+ * its `clearGlobalCharCache: false` behaviour untouched.
35450
+ */
35451
+ flushTextMeasurementCaches() {
35452
+ try {
35453
+ clearMeasurementCache();
35454
+ } catch {
35455
+ }
35456
+ try {
35457
+ clearFabricCharCache();
35458
+ } catch {
35459
+ }
35460
+ }
35305
35461
  getNormalizedGradientStops(gradient) {
35306
35462
  const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
35307
35463
  offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
@@ -37344,7 +37500,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
37344
37500
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
37345
37501
  sanitizeSvgTreeForPdf(svgToDraw);
37346
37502
  try {
37347
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-E3x8GBto.js");
37503
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-DV-TJwjP.js");
37348
37504
  try {
37349
37505
  await logTextMeasurementDiagnostic(svgToDraw);
37350
37506
  } catch {
@@ -37592,13 +37748,136 @@ async function getPublishedTemplate(options) {
37592
37748
  const rows = await res.json();
37593
37749
  return rows[0] ?? null;
37594
37750
  }
37751
+ const WATERMARK_ID_PREFIX = "__wm_";
37752
+ const DEFAULT_WATERMARK_OPACITY = 0.14;
37753
+ const DEFAULT_WATERMARK_FONT_SIZE = 18;
37754
+ const DEFAULT_WATERMARK_ANGLE = -30;
37755
+ function getWatermarkText() {
37756
+ try {
37757
+ const host = window.location.hostname;
37758
+ if (host.includes("biomaker")) return "biomaker.app";
37759
+ } catch {
37760
+ }
37761
+ return "pixldocs.com";
37762
+ }
37763
+ function getLuminance(color) {
37764
+ if (!color || color === "transparent" || color === "none") return 1;
37765
+ let r = 255, g = 255, b = 255;
37766
+ if (color.startsWith("#")) {
37767
+ const hex = color.slice(1);
37768
+ const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
37769
+ const n = parseInt(full, 16);
37770
+ r = n >> 16 & 255;
37771
+ g = n >> 8 & 255;
37772
+ b = n & 255;
37773
+ } else {
37774
+ const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
37775
+ if (m) {
37776
+ r = +m[1];
37777
+ g = +m[2];
37778
+ b = +m[3];
37779
+ }
37780
+ }
37781
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
37782
+ }
37783
+ function getWatermarkColor(bgColor) {
37784
+ const lum = getLuminance(bgColor);
37785
+ return lum < 0.45 ? "#cccccc" : "#808080";
37786
+ }
37787
+ function getPageBgColor(page, canvasBg) {
37788
+ var _a2, _b2;
37789
+ const settingsBg = (_a2 = page.settings) == null ? void 0 : _a2.backgroundColor;
37790
+ if (settingsBg && settingsBg !== "transparent" && settingsBg !== "none") {
37791
+ return settingsBg;
37792
+ }
37793
+ const children = page.children || [];
37794
+ for (const child of children) {
37795
+ if (child.type === "shape" && child.fill && child.fill !== "transparent" && child.fill !== "none") {
37796
+ return child.fill;
37797
+ }
37798
+ if ((_b2 = child.children) == null ? void 0 : _b2.length) {
37799
+ for (const gc of child.children) {
37800
+ if (gc.type === "shape" && gc.fill && gc.fill !== "transparent" && gc.fill !== "none") {
37801
+ return gc.fill;
37802
+ }
37803
+ }
37804
+ }
37805
+ }
37806
+ return canvasBg;
37807
+ }
37808
+ function generateWatermarkElements(canvasWidth, canvasHeight, wmColor, options = {}) {
37809
+ const elements = [];
37810
+ const text = options.text ?? getWatermarkText();
37811
+ const fontSize = options.fontSize ?? DEFAULT_WATERMARK_FONT_SIZE;
37812
+ const angle = options.angle ?? DEFAULT_WATERMARK_ANGLE;
37813
+ const opacity = options.opacity ?? DEFAULT_WATERMARK_OPACITY;
37814
+ const estimatedTextWidth = text.length * fontSize * 0.55;
37815
+ const spacingX = Math.max(160, estimatedTextWidth + 30);
37816
+ const spacingY = 90;
37817
+ const startX = -canvasWidth * 0.3;
37818
+ const startY = -canvasHeight * 0.3;
37819
+ const endX = canvasWidth * 1.5;
37820
+ const endY = canvasHeight * 1.5;
37821
+ let idx = 0;
37822
+ for (let y = startY; y < endY; y += spacingY) {
37823
+ for (let x = startX; x < endX; x += spacingX) {
37824
+ elements.push({
37825
+ id: `${WATERMARK_ID_PREFIX}${idx++}`,
37826
+ type: "text",
37827
+ left: x,
37828
+ top: y,
37829
+ width: 200,
37830
+ height: 40,
37831
+ text,
37832
+ fontFamily: "Montserrat",
37833
+ fontSize,
37834
+ fontWeight: "600",
37835
+ fill: wmColor,
37836
+ angle,
37837
+ selectable: false,
37838
+ locked: true,
37839
+ visible: true,
37840
+ opacity,
37841
+ scaleX: 1,
37842
+ scaleY: 1,
37843
+ letterSpacing: 4
37844
+ });
37845
+ }
37846
+ }
37847
+ return elements;
37848
+ }
37849
+ function injectWatermark(config, options = {}) {
37850
+ var _a2, _b2, _c2;
37851
+ const canvasWidth = ((_a2 = config.canvas) == null ? void 0 : _a2.width) || 612;
37852
+ const canvasHeight = ((_b2 = config.canvas) == null ? void 0 : _b2.height) || 792;
37853
+ const canvasBg = (_c2 = config.canvas) == null ? void 0 : _c2.backgroundColor;
37854
+ return {
37855
+ ...config,
37856
+ pages: config.pages.map((page) => {
37857
+ const pageBg = getPageBgColor(page, canvasBg);
37858
+ const wmColor = getWatermarkColor(pageBg);
37859
+ const watermarkElements = generateWatermarkElements(canvasWidth, canvasHeight, wmColor, options);
37860
+ return {
37861
+ ...page,
37862
+ children: [
37863
+ ...page.children || [],
37864
+ ...watermarkElements
37865
+ ]
37866
+ };
37867
+ })
37868
+ };
37869
+ }
37870
+ const canvasWatermark = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
37871
+ __proto__: null,
37872
+ injectWatermark
37873
+ }, Symbol.toStringTag, { value: "Module" }));
37595
37874
  function setAutoShrinkDebug(enabled) {
37596
37875
  if (typeof window !== "undefined") {
37597
37876
  window.__pixldocsDebugAutoShrink = !!enabled;
37598
37877
  }
37599
37878
  }
37600
37879
  export {
37601
- resolveFontWeight as $,
37880
+ resolveBlurElementExactIdsFromFlatFormKeys as $,
37602
37881
  API_URL as A,
37603
37882
  collectFontDescriptorsFromConfig as B,
37604
37883
  collectFontsFromConfig as C,
@@ -37619,23 +37898,24 @@ export {
37619
37898
  hasAnyPreviewBlur as R,
37620
37899
  injectPreviewBlur as S,
37621
37900
  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 _,
37901
+ injectWatermark as U,
37902
+ isBundledAssetUrl as V,
37903
+ isFontAvailable as W,
37904
+ isPrivateUrl as X,
37905
+ listPublishedTemplates as Y,
37906
+ loadGoogleFontCSS as Z,
37907
+ normalizeFontFamily as _,
37629
37908
  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,
37909
+ resolveFontWeight as a0,
37910
+ resolveForRender as a1,
37911
+ resolveFromForm as a2,
37912
+ resolveTemplateData as a3,
37913
+ rewriteSvgFontsForJsPDF as a4,
37914
+ setAutoShrinkDebug as a5,
37915
+ setBundledAssetPrefixes as a6,
37916
+ warmResolvedTemplateForPreview as a7,
37917
+ warmTemplateFromForm as a8,
37918
+ canvasImageLoader as a9,
37639
37919
  getProxiedImageUrl as b,
37640
37920
  captureFabricCanvasSvgForPdf as c,
37641
37921
  getImageProxyFetchOptions as d,
@@ -37662,4 +37942,4 @@ export {
37662
37942
  awaitFontsForConfig as y,
37663
37943
  buildTeaserBlurFlatKeys as z
37664
37944
  };
37665
- //# sourceMappingURL=index-CiSoT2Lk.js.map
37945
+ //# sourceMappingURL=index-CKhnRi3S.js.map