@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.
@@ -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;
@@ -6386,8 +6361,13 @@ async function loadImageAsync(element, placeholder, fc, fabricRef, syncLockedRef
6386
6361
  });
6387
6362
  const imgLoadOptions = url.startsWith("data:") || url.startsWith("blob:") ? {} : { crossOrigin: "anonymous" };
6388
6363
  const img = await fabric__namespace.FabricImage.fromURL(url, imgLoadOptions);
6389
- if (!fabricRef.current) return;
6364
+ if (fabricRef && !fabricRef.current) return;
6390
6365
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat);
6366
+ const natW = img.width ?? 0;
6367
+ const natH = img.height ?? 0;
6368
+ if (natW <= 1 && natH <= 1 && Number(element.width) > 8 && Number(element.height) > 8) {
6369
+ throw new Error(`image decoded to ${natW}x${natH} — treating as failed load`);
6370
+ }
6391
6371
  const isHidden = !element.visible;
6392
6372
  img.set({
6393
6373
  originX: "left",
@@ -6585,9 +6565,111 @@ async function loadImageAsync(element, placeholder, fc, fabricRef, syncLockedRef
6585
6565
  proxiedUrl: getProxiedImageUrl(imageUrl).slice(0, 240),
6586
6566
  error: error instanceof Error ? error.message : String(error)
6587
6567
  });
6568
+ if (fc.__pixldocsEditMode === true) {
6569
+ try {
6570
+ markPlaceholderAsBrokenImage(placeholder);
6571
+ fc.requestRenderAll();
6572
+ } catch {
6573
+ }
6574
+ }
6588
6575
  return;
6589
6576
  }
6590
6577
  }
6578
+ function markPlaceholderAsBrokenImage(placeholder) {
6579
+ if (!(placeholder instanceof fabric__namespace.Group)) return;
6580
+ const group = placeholder;
6581
+ if (group.__pixldocsBrokenImage) return;
6582
+ group.__pixldocsBrokenImage = true;
6583
+ const frameChild = group.getObjects().find(
6584
+ (o) => o.__isPlaceholderFrame
6585
+ );
6586
+ const w = Math.max(1, (frameChild == null ? void 0 : frameChild.width) ?? group.width ?? 1);
6587
+ const h = Math.max(1, (frameChild == null ? void 0 : frameChild.height) ?? group.height ?? 1);
6588
+ const center = group.getCenterPoint();
6589
+ const cx = center.x;
6590
+ const cy = center.y;
6591
+ const prevPose = { left: group.left, top: group.top, width: group.width, height: group.height };
6592
+ const bg = new fabric__namespace.Rect({
6593
+ originX: "center",
6594
+ originY: "center",
6595
+ left: cx,
6596
+ top: cy,
6597
+ width: w,
6598
+ height: h,
6599
+ fill: "rgba(148, 163, 184, 0.12)",
6600
+ stroke: "#94A3B8",
6601
+ strokeWidth: 1.5,
6602
+ strokeDashArray: [6, 4]
6603
+ });
6604
+ const s = Math.max(16, Math.min(w, h) * 0.28);
6605
+ const glyphStroke = { stroke: "#64748B", strokeWidth: Math.max(1.5, s / 12), fill: "transparent" };
6606
+ const iconFrame = new fabric__namespace.Rect({
6607
+ originX: "center",
6608
+ originY: "center",
6609
+ left: cx,
6610
+ top: cy,
6611
+ width: s,
6612
+ height: s * 0.8,
6613
+ rx: s * 0.08,
6614
+ ry: s * 0.08,
6615
+ ...glyphStroke
6616
+ });
6617
+ const mountain = new fabric__namespace.Polyline(
6618
+ [
6619
+ { x: -s * 0.38, y: s * 0.24 },
6620
+ { x: -s * 0.1, y: -s * 0.08 },
6621
+ { x: s * 0.08, y: s * 0.1 },
6622
+ { x: s * 0.22, y: -s * 0.02 },
6623
+ { x: s * 0.38, y: s * 0.24 }
6624
+ ],
6625
+ { originX: "center", originY: "center", left: cx, top: cy + s * 0.08, ...glyphStroke }
6626
+ );
6627
+ const slash = new fabric__namespace.Line([cx - s * 0.55, cy - s * 0.5, cx + s * 0.55, cy + s * 0.5], {
6628
+ ...glyphStroke,
6629
+ originX: "center",
6630
+ originY: "center",
6631
+ left: cx,
6632
+ top: cy
6633
+ });
6634
+ for (const obj of [bg, iconFrame, mountain, slash]) {
6635
+ obj.set({ selectable: false, evented: false, excludeFromExport: true });
6636
+ obj.__pixldocsEditorChrome = true;
6637
+ group.add(obj);
6638
+ }
6639
+ group.set(prevPose);
6640
+ group.setCoords();
6641
+ group.dirty = true;
6642
+ }
6643
+ function hideEditorChromeForExport(fc) {
6644
+ const hidden = [];
6645
+ const walk = (objs) => {
6646
+ var _a2;
6647
+ for (const o of objs) {
6648
+ if (o.__pixldocsEditorChrome && o.visible !== false) {
6649
+ o.visible = false;
6650
+ hidden.push(o);
6651
+ }
6652
+ const kids = (_a2 = o.getObjects) == null ? void 0 : _a2.call(o);
6653
+ if (kids == null ? void 0 : kids.length) walk(kids);
6654
+ }
6655
+ };
6656
+ walk(fc.getObjects());
6657
+ if (hidden.length) {
6658
+ for (const o of hidden) {
6659
+ const g = o.group;
6660
+ if (g) g.dirty = true;
6661
+ }
6662
+ fc.renderAll();
6663
+ }
6664
+ return () => {
6665
+ for (const o of hidden) {
6666
+ o.visible = true;
6667
+ const g = o.group;
6668
+ if (g) g.dirty = true;
6669
+ }
6670
+ if (hidden.length) fc.requestRenderAll();
6671
+ };
6672
+ }
6591
6673
  const canvasImageLoader = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
6592
6674
  __proto__: null,
6593
6675
  SVG_DECODE_HARD_CAP,
@@ -6597,12 +6679,14 @@ const canvasImageLoader = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.d
6597
6679
  fetchSvgTextPublic,
6598
6680
  getNormalizedSvgUrl,
6599
6681
  getProxiedImageUrl,
6682
+ hideEditorChromeForExport,
6600
6683
  isBundledAssetUrl,
6601
6684
  isEmptyImagePlaceholderGroup,
6602
6685
  isPrivateUrl,
6603
6686
  isSvgImage,
6604
6687
  loadImageAsync,
6605
6688
  loadSvgDimensions,
6689
+ markPlaceholderAsBrokenImage,
6606
6690
  normalizeSvgImageDimensions,
6607
6691
  parseSvgDimensionsFromText,
6608
6692
  preloadImage,
@@ -18114,6 +18198,7 @@ const PageCanvas = react.forwardRef(
18114
18198
  // Transparent so underlay (page bg + group bgs) shows through
18115
18199
  backgroundColor: "transparent"
18116
18200
  });
18201
+ fabricCanvas.__pixldocsEditMode = isEditorMode && !isPreviewMode;
18117
18202
  fabricCanvas.hoverCursor = "default";
18118
18203
  fabricCanvas.moveCursor = "move";
18119
18204
  const suppressTextEditForClick = (textbox) => {
@@ -25439,6 +25524,7 @@ const PageCanvas = react.forwardRef(
25439
25524
  } else if (obj instanceof fabric__namespace.Textbox) {
25440
25525
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
25441
25526
  let text = element.text != null && element.text !== "" ? element.text : " ";
25527
+ text = applyElementTextCase(text, element.textCase);
25442
25528
  let parsedStyles = null;
25443
25529
  if (element.formattingEnabled === true) {
25444
25530
  const parsed = parseTextMarkdown(text);
@@ -26083,6 +26169,13 @@ const PageCanvas = react.forwardRef(
26083
26169
  const img = await fabric__namespace.FabricImage.fromURL(url, { crossOrigin: "anonymous" });
26084
26170
  if (!fabricRef.current || !isLatestRequest()) return;
26085
26171
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat, svgDecodeTarget);
26172
+ {
26173
+ const natW = img.width ?? 0;
26174
+ const natH = img.height ?? 0;
26175
+ if (natW <= 1 && natH <= 1 && Number(element.width) > 8 && Number(element.height) > 8) {
26176
+ throw new Error(`image decoded to ${natW}x${natH} — treating as failed load`);
26177
+ }
26178
+ }
26086
26179
  if (!isLatestRequest()) return;
26087
26180
  const imageFitForFade = element.imageFit || ((_a2 = element.style) == null ? void 0 : _a2.imageFit) || "cover";
26088
26181
  const clipShapeForFade = element.clipShape ?? ((_b2 = element.style) == null ? void 0 : _b2.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
@@ -26449,6 +26542,19 @@ const PageCanvas = react.forwardRef(
26449
26542
  }
26450
26543
  fc.requestRenderAll();
26451
26544
  } catch (error) {
26545
+ console.error("[PageCanvas] image failed to load", {
26546
+ id: element.id,
26547
+ url: (element.src || element.imageUrl || "").slice(0, 200),
26548
+ error: error instanceof Error ? error.message : String(error)
26549
+ });
26550
+ const fcNow = fabricRef.current;
26551
+ if (fcNow && fcNow.__pixldocsEditMode === true) {
26552
+ try {
26553
+ markPlaceholderAsBrokenImage(placeholder);
26554
+ fcNow.requestRenderAll();
26555
+ } catch {
26556
+ }
26557
+ }
26452
26558
  }
26453
26559
  };
26454
26560
  const handleCanvasClick = react.useCallback(
@@ -34119,6 +34225,14 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
34119
34225
  try {
34120
34226
  const visit = (obj) => {
34121
34227
  if (!obj) return;
34228
+ if (obj.__pixldocsEditorChrome) {
34229
+ const originalToSVG = typeof obj.toSVG === "function" ? obj.toSVG.bind(obj) : null;
34230
+ if (originalToSVG) {
34231
+ obj.toSVG = () => "";
34232
+ svgPatchRecords.push({ obj, originalToSVG });
34233
+ }
34234
+ return;
34235
+ }
34122
34236
  const imageId = typeof obj.__docuforgeId === "string" && hasRenderableRasterCandidate(obj) ? obj.__docuforgeId : "";
34123
34237
  const alphaGradient = objectNeedsAlphaGradientRaster(obj);
34124
34238
  if ((isTextboxLike(obj) || imageId || alphaGradient) && typeof obj.toSVG === "function") {
@@ -34226,9 +34340,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
34226
34340
  }
34227
34341
  return svgString;
34228
34342
  }
34229
- const resolvedPackageVersion = "0.5.496";
34343
+ const resolvedPackageVersion = "0.5.497";
34230
34344
  const PACKAGE_VERSION = resolvedPackageVersion;
34231
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.496";
34345
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.497";
34232
34346
  const roundParityValue = (value) => {
34233
34347
  if (typeof value !== "number") return value;
34234
34348
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -34667,6 +34781,11 @@ class PixldocsRenderer {
34667
34781
  * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
34668
34782
  */
34669
34783
  async render(templateConfig, options = {}) {
34784
+ if (options.watermark === true && !templateConfig.__pixldocsWatermarked) {
34785
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34786
+ templateConfig = injectWatermark2(templateConfig, options.watermarkOptions);
34787
+ templateConfig.__pixldocsWatermarked = true;
34788
+ }
34670
34789
  const pageIndex = options.pageIndex ?? 0;
34671
34790
  const format = options.format ?? "png";
34672
34791
  const quality = options.quality ?? 0.92;
@@ -34682,6 +34801,7 @@ class PixldocsRenderer {
34682
34801
  const hasAutoShrink = configHasAutoShrinkText(templateConfig);
34683
34802
  const defaultWait = hasAutoShrink ? 4e3 : 1800;
34684
34803
  await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
34804
+ this.flushTextMeasurementCaches();
34685
34805
  }
34686
34806
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
34687
34807
  setPackageApiUrl2(this.config.imageProxyUrl);
@@ -34709,6 +34829,7 @@ class PixldocsRenderer {
34709
34829
  const hasAutoShrink = configHasAutoShrinkText(templateConfig);
34710
34830
  const defaultWait = hasAutoShrink ? 4e3 : 1800;
34711
34831
  await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
34832
+ this.flushTextMeasurementCaches();
34712
34833
  }
34713
34834
  const results = [];
34714
34835
  for (let i = 0; i < templateConfig.pages.length; i++) {
@@ -34735,8 +34856,8 @@ class PixldocsRenderer {
34735
34856
  const shouldWatermark = watermark ?? resolved.price > 0;
34736
34857
  let configToRender = resolved.config;
34737
34858
  if (shouldWatermark) {
34738
- const { injectWatermark } = await Promise.resolve().then(() => require("./canvasWatermark-B0ab38Ok.cjs"));
34739
- configToRender = injectWatermark(configToRender, watermarkOptions);
34859
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34860
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34740
34861
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34741
34862
  configToRender = injectPreviewBlur2(configToRender);
34742
34863
  }
@@ -34795,8 +34916,8 @@ class PixldocsRenderer {
34795
34916
  const shouldWatermark = watermark ?? resolved.price > 0;
34796
34917
  let configToRender = resolved.config;
34797
34918
  if (shouldWatermark) {
34798
- const { injectWatermark } = await Promise.resolve().then(() => require("./canvasWatermark-B0ab38Ok.cjs"));
34799
- configToRender = injectWatermark(configToRender, watermarkOptions);
34919
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34920
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34800
34921
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34801
34922
  configToRender = injectPreviewBlur2(configToRender);
34802
34923
  }
@@ -34816,6 +34937,10 @@ class PixldocsRenderer {
34816
34937
  * exporter, which is what `renderPdfViaClientExport` does below.
34817
34938
  */
34818
34939
  async renderPdf(templateConfig, options) {
34940
+ if ((options == null ? void 0 : options.watermark) === true) {
34941
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34942
+ templateConfig = injectWatermark2(templateConfig, options.watermarkOptions);
34943
+ }
34819
34944
  return this.renderPdfViaClientExport(templateConfig, {
34820
34945
  title: options == null ? void 0 : options.title,
34821
34946
  textMode: options == null ? void 0 : options.textMode,
@@ -34844,8 +34969,8 @@ class PixldocsRenderer {
34844
34969
  const shouldWatermark = watermark ?? resolved.price > 0;
34845
34970
  let configToRender = resolved.config;
34846
34971
  if (shouldWatermark) {
34847
- const { injectWatermark } = await Promise.resolve().then(() => require("./canvasWatermark-B0ab38Ok.cjs"));
34848
- configToRender = injectWatermark(configToRender, watermarkOptions);
34972
+ const { injectWatermark: injectWatermark2 } = await Promise.resolve().then(() => canvasWatermark);
34973
+ configToRender = injectWatermark2(configToRender, watermarkOptions);
34849
34974
  const { injectPreviewBlur: injectPreviewBlur2 } = await Promise.resolve().then(() => previewBlur);
34850
34975
  configToRender = injectPreviewBlur2(configToRender);
34851
34976
  }
@@ -35042,7 +35167,7 @@ class PixldocsRenderer {
35042
35167
  await this.waitForCanvasScene(container, cloned, i);
35043
35168
  }
35044
35169
  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"));
35170
+ const { exportMultiPagePdf, preparePagesForExport } = await Promise.resolve().then(() => require("./vectorPdfExport-De8UFmDy.cjs"));
35046
35171
  const prepared = preparePagesForExport(
35047
35172
  cloned.pages,
35048
35173
  canvasWidth,
@@ -35320,6 +35445,37 @@ class PixldocsRenderer {
35320
35445
  new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
35321
35446
  ]);
35322
35447
  }
35448
+ /**
35449
+ * Flush BOTH process-global text-measurement caches after fonts are ready
35450
+ * and BEFORE the capture canvas mounts.
35451
+ *
35452
+ * Why: anything that measured text before the webfonts finished loading —
35453
+ * most notably `applyContentBoundsPagination` running inside
35454
+ * `resolveFromForm` on a cold page (the EC2/MCP flow: resolve + render in
35455
+ * one browser context) — populates the app measurement cache and Fabric's
35456
+ * global char-width cache with FALLBACK metrics. `createText` then places
35457
+ * center/right-aligned lines from those stale widths, so a right-aligned
35458
+ * grid cell on a continuation page drew shifted left by
35459
+ * (fallbackWidth − realWidth) px vs the editor (caught by
35460
+ * tests/parity `auto-paginate`, a 9px shift). The later
35461
+ * `waitForStableTextMetrics` pass re-primes glyph bounds but never moves
35462
+ * the already-positioned box, so the flush must happen pre-mount.
35463
+ *
35464
+ * Only called from the headless PNG paths (`render`/`renderAllPages`),
35465
+ * which already default to clearing the global char cache in
35466
+ * `waitForStableTextMetrics` — the live-preview-adjacent PDF path keeps
35467
+ * its `clearGlobalCharCache: false` behaviour untouched.
35468
+ */
35469
+ flushTextMeasurementCaches() {
35470
+ try {
35471
+ clearMeasurementCache();
35472
+ } catch {
35473
+ }
35474
+ try {
35475
+ clearFabricCharCache();
35476
+ } catch {
35477
+ }
35478
+ }
35323
35479
  getNormalizedGradientStops(gradient) {
35324
35480
  const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
35325
35481
  offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
@@ -37362,7 +37518,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
37362
37518
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
37363
37519
  sanitizeSvgTreeForPdf(svgToDraw);
37364
37520
  try {
37365
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-B2KtFs76.cjs"));
37521
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await Promise.resolve().then(() => require("./vectorPdfExport-De8UFmDy.cjs"));
37366
37522
  try {
37367
37523
  await logTextMeasurementDiagnostic(svgToDraw);
37368
37524
  } catch {
@@ -37610,6 +37766,129 @@ async function getPublishedTemplate(options) {
37610
37766
  const rows = await res.json();
37611
37767
  return rows[0] ?? null;
37612
37768
  }
37769
+ const WATERMARK_ID_PREFIX = "__wm_";
37770
+ const DEFAULT_WATERMARK_OPACITY = 0.14;
37771
+ const DEFAULT_WATERMARK_FONT_SIZE = 18;
37772
+ const DEFAULT_WATERMARK_ANGLE = -30;
37773
+ function getWatermarkText() {
37774
+ try {
37775
+ const host = window.location.hostname;
37776
+ if (host.includes("biomaker")) return "biomaker.app";
37777
+ } catch {
37778
+ }
37779
+ return "pixldocs.com";
37780
+ }
37781
+ function getLuminance(color) {
37782
+ if (!color || color === "transparent" || color === "none") return 1;
37783
+ let r = 255, g = 255, b = 255;
37784
+ if (color.startsWith("#")) {
37785
+ const hex = color.slice(1);
37786
+ const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
37787
+ const n = parseInt(full, 16);
37788
+ r = n >> 16 & 255;
37789
+ g = n >> 8 & 255;
37790
+ b = n & 255;
37791
+ } else {
37792
+ const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
37793
+ if (m) {
37794
+ r = +m[1];
37795
+ g = +m[2];
37796
+ b = +m[3];
37797
+ }
37798
+ }
37799
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
37800
+ }
37801
+ function getWatermarkColor(bgColor) {
37802
+ const lum = getLuminance(bgColor);
37803
+ return lum < 0.45 ? "#cccccc" : "#808080";
37804
+ }
37805
+ function getPageBgColor(page, canvasBg) {
37806
+ var _a2, _b2;
37807
+ const settingsBg = (_a2 = page.settings) == null ? void 0 : _a2.backgroundColor;
37808
+ if (settingsBg && settingsBg !== "transparent" && settingsBg !== "none") {
37809
+ return settingsBg;
37810
+ }
37811
+ const children = page.children || [];
37812
+ for (const child of children) {
37813
+ if (child.type === "shape" && child.fill && child.fill !== "transparent" && child.fill !== "none") {
37814
+ return child.fill;
37815
+ }
37816
+ if ((_b2 = child.children) == null ? void 0 : _b2.length) {
37817
+ for (const gc of child.children) {
37818
+ if (gc.type === "shape" && gc.fill && gc.fill !== "transparent" && gc.fill !== "none") {
37819
+ return gc.fill;
37820
+ }
37821
+ }
37822
+ }
37823
+ }
37824
+ return canvasBg;
37825
+ }
37826
+ function generateWatermarkElements(canvasWidth, canvasHeight, wmColor, options = {}) {
37827
+ const elements = [];
37828
+ const text = options.text ?? getWatermarkText();
37829
+ const fontSize = options.fontSize ?? DEFAULT_WATERMARK_FONT_SIZE;
37830
+ const angle = options.angle ?? DEFAULT_WATERMARK_ANGLE;
37831
+ const opacity = options.opacity ?? DEFAULT_WATERMARK_OPACITY;
37832
+ const estimatedTextWidth = text.length * fontSize * 0.55;
37833
+ const spacingX = Math.max(160, estimatedTextWidth + 30);
37834
+ const spacingY = 90;
37835
+ const startX = -canvasWidth * 0.3;
37836
+ const startY = -canvasHeight * 0.3;
37837
+ const endX = canvasWidth * 1.5;
37838
+ const endY = canvasHeight * 1.5;
37839
+ let idx = 0;
37840
+ for (let y = startY; y < endY; y += spacingY) {
37841
+ for (let x = startX; x < endX; x += spacingX) {
37842
+ elements.push({
37843
+ id: `${WATERMARK_ID_PREFIX}${idx++}`,
37844
+ type: "text",
37845
+ left: x,
37846
+ top: y,
37847
+ width: 200,
37848
+ height: 40,
37849
+ text,
37850
+ fontFamily: "Montserrat",
37851
+ fontSize,
37852
+ fontWeight: "600",
37853
+ fill: wmColor,
37854
+ angle,
37855
+ selectable: false,
37856
+ locked: true,
37857
+ visible: true,
37858
+ opacity,
37859
+ scaleX: 1,
37860
+ scaleY: 1,
37861
+ letterSpacing: 4
37862
+ });
37863
+ }
37864
+ }
37865
+ return elements;
37866
+ }
37867
+ function injectWatermark(config, options = {}) {
37868
+ var _a2, _b2, _c2;
37869
+ const canvasWidth = ((_a2 = config.canvas) == null ? void 0 : _a2.width) || 612;
37870
+ const canvasHeight = ((_b2 = config.canvas) == null ? void 0 : _b2.height) || 792;
37871
+ const canvasBg = (_c2 = config.canvas) == null ? void 0 : _c2.backgroundColor;
37872
+ return {
37873
+ ...config,
37874
+ pages: config.pages.map((page) => {
37875
+ const pageBg = getPageBgColor(page, canvasBg);
37876
+ const wmColor = getWatermarkColor(pageBg);
37877
+ const watermarkElements = generateWatermarkElements(canvasWidth, canvasHeight, wmColor, options);
37878
+ return {
37879
+ ...page,
37880
+ children: [
37881
+ ...page.children || [],
37882
+ ...watermarkElements
37883
+ ]
37884
+ };
37885
+ })
37886
+ };
37887
+ }
37888
+ const canvasWatermark = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
37889
+ __proto__: null,
37890
+ injectWatermark
37891
+ }, Symbol.toStringTag, { value: "Module" }));
37613
37892
  function setAutoShrinkDebug(enabled) {
37614
37893
  if (typeof window !== "undefined") {
37615
37894
  window.__pixldocsDebugAutoShrink = !!enabled;
@@ -37656,6 +37935,7 @@ exports.getWorldAngleDeg = getWorldAngleDeg$1;
37656
37935
  exports.hasAnyPreviewBlur = hasAnyPreviewBlur;
37657
37936
  exports.hasEdgeFade = hasEdgeFade;
37658
37937
  exports.injectPreviewBlur = injectPreviewBlur;
37938
+ exports.injectWatermark = injectWatermark;
37659
37939
  exports.isBundledAssetUrl = isBundledAssetUrl;
37660
37940
  exports.isElement = isElement;
37661
37941
  exports.isFontAvailable = isFontAvailable;
@@ -37677,4 +37957,4 @@ exports.setAutoShrinkDebug = setAutoShrinkDebug;
37677
37957
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
37678
37958
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
37679
37959
  exports.warmTemplateFromForm = warmTemplateFromForm;
37680
- //# sourceMappingURL=index-CZtvER5z.cjs.map
37960
+ //# sourceMappingURL=index-CZm1oVfa.cjs.map