@pixldocs/canvas-renderer 0.5.101 → 0.5.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3682,28 +3682,33 @@ function updateCoverLayout(g) {
3682
3682
  const hasCustomSvgMask = Boolean(
3683
3683
  currentClipPath && (isSvgMaskClipPath(currentClipPath) || isLuminanceMaskClipPath(currentClipPath) || currentClipPath.__svgMask)
3684
3684
  );
3685
- if (hasCustomSvgMask) {
3685
+ const hasEdgeFadeMask = Boolean(currentClipPath && currentClipPath.__edgeFadeMask);
3686
+ if (hasCustomSvgMask || hasEdgeFadeMask) {
3686
3687
  if (isSvgMaskClipPath(currentClipPath)) {
3687
3688
  syncSvgMaskClipPath(g);
3688
3689
  } else if (currentClipPath && typeof currentClipPath.set === "function") {
3689
- const baseW = Math.max(1, Number(currentClipPath.width) || frameW);
3690
- const baseH = Math.max(1, Number(currentClipPath.height) || frameH);
3691
- currentClipPath.set({
3692
- left: 0,
3693
- top: 0,
3694
- originX: "center",
3695
- originY: "center",
3696
- scaleX: frameW / baseW,
3697
- scaleY: frameH / baseH,
3698
- selectable: false,
3699
- evented: false,
3700
- hasControls: false,
3701
- hasBorders: false
3702
- });
3703
- currentClipPath.absolutePositioned = false;
3704
- currentClipPath.excludeFromExport = true;
3705
- currentClipPath.dirty = true;
3706
- currentClipPath.setCoords();
3690
+ if (hasEdgeFadeMask) {
3691
+ currentClipPath.dirty = true;
3692
+ } else {
3693
+ const baseW = Math.max(1, Number(currentClipPath.width) || frameW);
3694
+ const baseH = Math.max(1, Number(currentClipPath.height) || frameH);
3695
+ currentClipPath.set({
3696
+ left: 0,
3697
+ top: 0,
3698
+ originX: "center",
3699
+ originY: "center",
3700
+ scaleX: frameW / baseW,
3701
+ scaleY: frameH / baseH,
3702
+ selectable: false,
3703
+ evented: false,
3704
+ hasControls: false,
3705
+ hasBorders: false
3706
+ });
3707
+ currentClipPath.absolutePositioned = false;
3708
+ currentClipPath.excludeFromExport = true;
3709
+ currentClipPath.dirty = true;
3710
+ currentClipPath.setCoords();
3711
+ }
3707
3712
  }
3708
3713
  } else {
3709
3714
  const needsNewClipPath = !g.clipPath || shape === "circle" && !(g.clipPath instanceof fabric__namespace.Ellipse) || shape !== "circle" && !(g.clipPath instanceof fabric__namespace.Rect);
@@ -4660,10 +4665,12 @@ function exitCropMode(g, commit = true) {
4660
4665
  g.hasControls = true;
4661
4666
  g.borderDashArray = void 0;
4662
4667
  installCanvaMaskControls(g);
4663
- g.borderColor = void 0;
4664
- g.cornerColor = void 0;
4665
- g.cornerStrokeColor = void 0;
4668
+ g.borderColor = "#4f46e5";
4669
+ g.borderDashArray = void 0;
4670
+ g.cornerColor = "#ffffff";
4671
+ g.cornerStrokeColor = "#4f46e5";
4666
4672
  g.cornerStyle = "rect";
4673
+ g.transparentCorners = false;
4667
4674
  g[CROP_MODE_FLAG] = false;
4668
4675
  g.__cropZoomLastPointer = void 0;
4669
4676
  g.__lastPointerForCrop = void 0;
@@ -6193,6 +6200,127 @@ function renderSmartElementToDataUri(type, props, width, height) {
6193
6200
  if (!svg) return null;
6194
6201
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
6195
6202
  }
6203
+ function hasEdgeFade(p) {
6204
+ if (!p) return false;
6205
+ const sides = [
6206
+ [p.fadeTopSize, p.fadeTopAmount],
6207
+ [p.fadeRightSize, p.fadeRightAmount],
6208
+ [p.fadeBottomSize, p.fadeBottomAmount],
6209
+ [p.fadeLeftSize, p.fadeLeftAmount]
6210
+ ];
6211
+ for (const [size, amount] of sides) {
6212
+ const s = Number(size) || 0;
6213
+ const a = amount == null ? 1 : Number(amount);
6214
+ if (s > 0 && a < 1) return true;
6215
+ }
6216
+ return false;
6217
+ }
6218
+ function edgeFadeKey(p) {
6219
+ if (!hasEdgeFade(p)) return "";
6220
+ return [
6221
+ (p == null ? void 0 : p.fadeTopSize) ?? 0,
6222
+ (p == null ? void 0 : p.fadeTopAmount) ?? 1,
6223
+ (p == null ? void 0 : p.fadeRightSize) ?? 0,
6224
+ (p == null ? void 0 : p.fadeRightAmount) ?? 1,
6225
+ (p == null ? void 0 : p.fadeBottomSize) ?? 0,
6226
+ (p == null ? void 0 : p.fadeBottomAmount) ?? 1,
6227
+ (p == null ? void 0 : p.fadeLeftSize) ?? 0,
6228
+ (p == null ? void 0 : p.fadeLeftAmount) ?? 1,
6229
+ (p == null ? void 0 : p.fadeTopHardness) ?? 1,
6230
+ (p == null ? void 0 : p.fadeRightHardness) ?? 1,
6231
+ (p == null ? void 0 : p.fadeBottomHardness) ?? 1,
6232
+ (p == null ? void 0 : p.fadeLeftHardness) ?? 1
6233
+ ].join("|");
6234
+ }
6235
+ function clamp01(n) {
6236
+ if (!Number.isFinite(n)) return 0;
6237
+ return Math.max(0, Math.min(1, n));
6238
+ }
6239
+ function bakeEdgeFade(source, fade) {
6240
+ const w = source.naturalWidth || source.width || 0;
6241
+ const h = source.naturalHeight || source.height || 0;
6242
+ const canvas = document.createElement("canvas");
6243
+ canvas.width = Math.max(1, w);
6244
+ canvas.height = Math.max(1, h);
6245
+ const ctx = canvas.getContext("2d");
6246
+ if (!ctx) return canvas;
6247
+ ctx.drawImage(source, 0, 0, canvas.width, canvas.height);
6248
+ const clampHardness = (n) => {
6249
+ const v = Number(n);
6250
+ if (!Number.isFinite(v) || v <= 0) return 1;
6251
+ return Math.max(0.1, Math.min(5, v));
6252
+ };
6253
+ const sides = [
6254
+ { side: "top", size: clamp01(fade.fadeTopSize ?? 0), amount: clamp01(fade.fadeTopAmount ?? 1), hardness: clampHardness(fade.fadeTopHardness) },
6255
+ { side: "right", size: clamp01(fade.fadeRightSize ?? 0), amount: clamp01(fade.fadeRightAmount ?? 1), hardness: clampHardness(fade.fadeRightHardness) },
6256
+ { side: "bottom", size: clamp01(fade.fadeBottomSize ?? 0), amount: clamp01(fade.fadeBottomAmount ?? 1), hardness: clampHardness(fade.fadeBottomHardness) },
6257
+ { side: "left", size: clamp01(fade.fadeLeftSize ?? 0), amount: clamp01(fade.fadeLeftAmount ?? 1), hardness: clampHardness(fade.fadeLeftHardness) }
6258
+ ];
6259
+ for (const { side, size, amount, hardness } of sides) {
6260
+ if (size <= 0 || amount >= 1) continue;
6261
+ const mask = document.createElement("canvas");
6262
+ mask.width = canvas.width;
6263
+ mask.height = canvas.height;
6264
+ const mctx = mask.getContext("2d");
6265
+ if (!mctx) continue;
6266
+ mctx.fillStyle = "#000";
6267
+ mctx.fillRect(0, 0, mask.width, mask.height);
6268
+ let g;
6269
+ let x = 0, y = 0, rectW = mask.width, rectH = mask.height;
6270
+ const STOPS = 24;
6271
+ const innerAlpha = (t) => {
6272
+ const keepProgress = Math.pow(t, hardness);
6273
+ const erase = (1 - amount) * (1 - keepProgress);
6274
+ return 1 - erase;
6275
+ };
6276
+ const fillStops = (grad, reverse) => {
6277
+ for (let i = 0; i <= STOPS; i++) {
6278
+ const t = i / STOPS;
6279
+ const a = innerAlpha(t);
6280
+ grad.addColorStop(t, `rgba(0,0,0,${a})`);
6281
+ }
6282
+ };
6283
+ if (side === "top") {
6284
+ const band = Math.max(1, Math.round(mask.height * size));
6285
+ g = mctx.createLinearGradient(0, 0, 0, band);
6286
+ fillStops(g);
6287
+ rectH = band;
6288
+ } else if (side === "bottom") {
6289
+ const band = Math.max(1, Math.round(mask.height * size));
6290
+ y = mask.height - band;
6291
+ g = mctx.createLinearGradient(0, mask.height, 0, y);
6292
+ fillStops(g);
6293
+ rectH = band;
6294
+ } else if (side === "left") {
6295
+ const band = Math.max(1, Math.round(mask.width * size));
6296
+ g = mctx.createLinearGradient(0, 0, band, 0);
6297
+ fillStops(g);
6298
+ rectW = band;
6299
+ } else {
6300
+ const band = Math.max(1, Math.round(mask.width * size));
6301
+ x = mask.width - band;
6302
+ g = mctx.createLinearGradient(mask.width, 0, x, 0);
6303
+ fillStops(g);
6304
+ rectW = band;
6305
+ }
6306
+ mctx.fillStyle = g;
6307
+ mctx.fillRect(x, y, rectW, rectH);
6308
+ if (amount <= 1e-3) {
6309
+ const edgePx = Math.min(2, side === "top" || side === "bottom" ? rectH : rectW);
6310
+ mctx.fillStyle = "rgba(0,0,0,0)";
6311
+ mctx.globalCompositeOperation = "copy";
6312
+ if (side === "top") mctx.fillRect(0, 0, mask.width, edgePx);
6313
+ if (side === "bottom") mctx.fillRect(0, mask.height - edgePx, mask.width, edgePx);
6314
+ if (side === "left") mctx.fillRect(0, 0, edgePx, mask.height);
6315
+ if (side === "right") mctx.fillRect(mask.width - edgePx, 0, edgePx, mask.height);
6316
+ mctx.globalCompositeOperation = "source-over";
6317
+ }
6318
+ ctx.globalCompositeOperation = "destination-in";
6319
+ ctx.drawImage(mask, 0, 0);
6320
+ ctx.globalCompositeOperation = "source-over";
6321
+ }
6322
+ return canvas;
6323
+ }
6196
6324
  function angleToCoords(angleDeg) {
6197
6325
  const rad = angleDeg * Math.PI / 180;
6198
6326
  const x1 = 0.5 - Math.sin(rad) * 0.5;
@@ -7702,6 +7830,10 @@ const PageCanvas = react.forwardRef(
7702
7830
  fabricCanvas.on("mouse:dblclick", (e) => {
7703
7831
  if (!isActiveRef.current || !allowEditing) return;
7704
7832
  let target = e.target;
7833
+ if (!target) {
7834
+ const active = fabricCanvas.getActiveObject();
7835
+ if (active) target = active;
7836
+ }
7705
7837
  if (target && target instanceof fabric__namespace.Group && target.__cropGroup) {
7706
7838
  const ct = target.__cropData;
7707
7839
  if ((ct == null ? void 0 : ct._img) && !isCropGroupInCropMode(target)) {
@@ -7857,7 +7989,7 @@ const PageCanvas = react.forwardRef(
7857
7989
  visibilityUpdateInProgressRef.current = false;
7858
7990
  }
7859
7991
  doSyncRef.current = () => {
7860
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
7992
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
7861
7993
  const shouldSkipUpdates2 = syncLockedRef.current || editLockRef.current;
7862
7994
  const state = useEditorStore.getState();
7863
7995
  const elementsToSync = elements;
@@ -8018,15 +8150,22 @@ const PageCanvas = react.forwardRef(
8018
8150
  const storedImageUrl = existingObj.__imageSrc;
8019
8151
  const currentUrlNormalized = currentImageUrl.trim();
8020
8152
  const storedUrlNormalized = storedImageUrl ? String(storedImageUrl).trim() : "";
8021
- const svgColorMapStr = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
8022
- const storedColorMapStr = existingObj.__svgColorMap || "";
8023
- const colorMapChanged = svgColorMapStr !== storedColorMapStr;
8024
- const sourceUrlChanged = currentUrlNormalized !== storedUrlNormalized;
8025
- const needsReload = sourceUrlChanged || colorMapChanged;
8026
8153
  const hasUrl = currentUrlNormalized !== "";
8027
8154
  const isCropGroup2 = existingObj instanceof fabric__namespace.Group && existingObj.__cropGroup;
8028
8155
  const isPlaceholderGroup = isEmptyImagePlaceholderGroup(existingObj);
8029
8156
  const isPlaceholder = isPlaceholderGroup || !(existingObj instanceof fabric__namespace.FabricImage) || existingObj instanceof fabric__namespace.Group && !isCropGroup2;
8157
+ const svgColorMapStr = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
8158
+ const storedColorMapStr = existingObj.__svgColorMap || "";
8159
+ const colorMapChanged = svgColorMapStr !== storedColorMapStr;
8160
+ const sourceUrlChanged = currentUrlNormalized !== storedUrlNormalized;
8161
+ const newFadeKey = edgeFadeKey(element);
8162
+ const oldFadeKey = isCropGroup2 ? existingObj.__edgeFadeInputKey || "" : existingObj.__edgeFadeKey || "";
8163
+ const innerImg = (_e = existingObj == null ? void 0 : existingObj.__cropData) == null ? void 0 : _e._img;
8164
+ const innerOldKey = isCropGroup2 ? oldFadeKey : innerImg ? innerImg.__edgeFadeKey || "" : oldFadeKey;
8165
+ const cropFadeRendererMissing = isCropGroup2 && Boolean(newFadeKey) && !existingObj.__edgeFadeRenderConfig;
8166
+ const fadeKeyChanged = newFadeKey !== oldFadeKey || newFadeKey !== innerOldKey || cropFadeRendererMissing;
8167
+ const needsReload = sourceUrlChanged || colorMapChanged || fadeKeyChanged && !isCropGroup2;
8168
+ const needsCropGroupFadeUpdate = isCropGroup2 && fadeKeyChanged;
8030
8169
  const hadUrlBefore = storedImageUrl && String(storedImageUrl).trim() !== "";
8031
8170
  if (!hasUrl && hadUrlBefore) {
8032
8171
  const placeholder = isCropGroup2 ? createImagePlaceholderForGroup(element) : createImagePlaceholder(element);
@@ -8042,16 +8181,16 @@ const PageCanvas = react.forwardRef(
8042
8181
  fc.requestRenderAll();
8043
8182
  continue;
8044
8183
  }
8045
- const imageFitForReplace = element.imageFit || ((_e = element.style) == null ? void 0 : _e.imageFit) || "cover";
8046
- const clipShapeForReplace = element.clipShape ?? ((_f = element.style) == null ? void 0 : _f.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
8184
+ const imageFitForReplace = element.imageFit || ((_f = element.style) == null ? void 0 : _f.imageFit) || "cover";
8185
+ const clipShapeForReplace = element.clipShape ?? ((_g = element.style) == null ? void 0 : _g.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
8047
8186
  const needCropGroupForElement = imageFitForReplace !== "fill" || clipShapeForReplace && clipShapeForReplace !== "none";
8048
8187
  const plainImageNeedsCropGroup = hasUrl && !isCropGroup2 && existingObj instanceof fabric__namespace.FabricImage && needCropGroupForElement;
8049
- if (hasUrl && (needsReload || isPlaceholder || plainImageNeedsCropGroup)) {
8188
+ if (hasUrl && (needsReload || isPlaceholder || plainImageNeedsCropGroup || needsCropGroupFadeUpdate)) {
8050
8189
  if (needsReload && !isBeingTransformed && (!wasJustModified || sourceUrlChanged)) {
8051
8190
  loadImageAsync(element, existingObj, fc);
8052
8191
  } else if (plainImageNeedsCropGroup) {
8053
8192
  loadImageAsync(element, existingObj, fc);
8054
- } else if (!needsReload && isCropGroup2) {
8193
+ } else if ((!needsReload || needsCropGroupFadeUpdate) && isCropGroup2) {
8055
8194
  const ct = existingObj.__cropData;
8056
8195
  if (ct) {
8057
8196
  const resolvedCrop = pageTree.length > 0 ? getNodeBounds(element, pageTree) : { width: typeof element.width === "number" ? element.width : 200, height: typeof element.height === "number" ? element.height : 50 };
@@ -8148,6 +8287,7 @@ const PageCanvas = react.forwardRef(
8148
8287
  }
8149
8288
  }
8150
8289
  updateCoverLayout(existingObj);
8290
+ applyEdgeFadeFrameClipPath(existingObj, element, ct.frameW, ct.frameH, ct.shape || "rect", ct.rx || 0);
8151
8291
  if (allowEditing) {
8152
8292
  installCanvaMaskControls(existingObj);
8153
8293
  } else {
@@ -8457,7 +8597,7 @@ const PageCanvas = react.forwardRef(
8457
8597
  fc.add(placeholder);
8458
8598
  fc.bringObjectToFront(placeholder);
8459
8599
  const activeObj = fc.getActiveObject();
8460
- if (activeObj && (((_g = activeObj._ct) == null ? void 0 : _g.isCropGroup) || activeObj.__cropGroup)) {
8600
+ if (activeObj && (((_h = activeObj._ct) == null ? void 0 : _h.isCropGroup) || activeObj.__cropGroup)) {
8461
8601
  fc.setActiveObject(activeObj);
8462
8602
  }
8463
8603
  placeholder.dirty = true;
@@ -8497,7 +8637,7 @@ const PageCanvas = react.forwardRef(
8497
8637
  fc.add(obj);
8498
8638
  fc.bringObjectToFront(obj);
8499
8639
  const activeObj = fc.getActiveObject();
8500
- if (activeObj && (((_h = activeObj._ct) == null ? void 0 : _h.isCropGroup) || activeObj.__cropGroup)) {
8640
+ if (activeObj && (((_i = activeObj._ct) == null ? void 0 : _i.isCropGroup) || activeObj.__cropGroup)) {
8501
8641
  fc.setActiveObject(activeObj);
8502
8642
  }
8503
8643
  obj.dirty = true;
@@ -8531,7 +8671,7 @@ const PageCanvas = react.forwardRef(
8531
8671
  isRebuildingRef.current = false;
8532
8672
  fc.requestRenderAll();
8533
8673
  if (activeBeforeSync && fc.getObjects().includes(activeBeforeSync)) {
8534
- const isCropGroup2 = ((_i = activeBeforeSync._ct) == null ? void 0 : _i.isCropGroup) || activeBeforeSync.__cropGroup;
8674
+ const isCropGroup2 = ((_j = activeBeforeSync._ct) == null ? void 0 : _j.isCropGroup) || activeBeforeSync.__cropGroup;
8535
8675
  if (isCropGroup2) {
8536
8676
  fc.setActiveObject(activeBeforeSync);
8537
8677
  fc.requestRenderAll();
@@ -8953,6 +9093,7 @@ const PageCanvas = react.forwardRef(
8953
9093
  }
8954
9094
  }
8955
9095
  updateCoverLayout(obj);
9096
+ applyEdgeFadeFrameClipPath(obj, element, elementWidth, elementHeight, ct.shape || "rect", ct.rx || 0);
8956
9097
  obj.setCoords();
8957
9098
  obj.dirty = true;
8958
9099
  if (obj.clipPath) {
@@ -9536,8 +9677,131 @@ const PageCanvas = react.forwardRef(
9536
9677
  obj.__lastStrokeGradientJson = "null";
9537
9678
  }
9538
9679
  };
9680
+ const clamp2 = (v, min, max) => {
9681
+ return Math.max(min, Math.min(max, v));
9682
+ };
9683
+ const applyEdgeFadeFrameClipPath = (group, element, frameW, frameH, shape, rxRatio) => {
9684
+ var _a, _b, _c;
9685
+ const fadeElement = element;
9686
+ const fadeGroup = group;
9687
+ const inputKey = edgeFadeKey(fadeElement);
9688
+ const hasFade = hasEdgeFade(fadeElement);
9689
+ if (!fadeGroup.__edgeFadeOriginalDrawObject) {
9690
+ fadeGroup.__edgeFadeOriginalDrawObject = group.drawObject;
9691
+ }
9692
+ if (hasFade) {
9693
+ if ((_a = group.clipPath) == null ? void 0 : _a.__edgeFadeMask) {
9694
+ group.clipPath = void 0;
9695
+ updateCoverLayout(group);
9696
+ }
9697
+ fadeGroup.__edgeFadeRenderConfig = {
9698
+ key: `${inputKey}|${Math.round(frameW)}|${Math.round(frameH)}|${shape}|${rxRatio}`,
9699
+ frameW: Math.max(1, Number(frameW) || 1),
9700
+ frameH: Math.max(1, Number(frameH) || 1),
9701
+ topSize: clamp2(Number(fadeElement.fadeTopSize) || 0, 0, 1),
9702
+ topAmount: clamp2(fadeElement.fadeTopAmount == null ? 1 : Number(fadeElement.fadeTopAmount), 0, 1),
9703
+ rightSize: clamp2(Number(fadeElement.fadeRightSize) || 0, 0, 1),
9704
+ rightAmount: clamp2(fadeElement.fadeRightAmount == null ? 1 : Number(fadeElement.fadeRightAmount), 0, 1),
9705
+ bottomSize: clamp2(Number(fadeElement.fadeBottomSize) || 0, 0, 1),
9706
+ bottomAmount: clamp2(fadeElement.fadeBottomAmount == null ? 1 : Number(fadeElement.fadeBottomAmount), 0, 1),
9707
+ leftSize: clamp2(Number(fadeElement.fadeLeftSize) || 0, 0, 1),
9708
+ leftAmount: clamp2(fadeElement.fadeLeftAmount == null ? 1 : Number(fadeElement.fadeLeftAmount), 0, 1),
9709
+ topHardness: clamp2(Number(fadeElement.fadeTopHardness) || 1, 0.1, 5),
9710
+ rightHardness: clamp2(Number(fadeElement.fadeRightHardness) || 1, 0.1, 5),
9711
+ bottomHardness: clamp2(Number(fadeElement.fadeBottomHardness) || 1, 0.1, 5),
9712
+ leftHardness: clamp2(Number(fadeElement.fadeLeftHardness) || 1, 0.1, 5)
9713
+ };
9714
+ const originalDrawObject = fadeGroup.__edgeFadeOriginalDrawObject ?? group.drawObject;
9715
+ group.drawObject = function edgeFadeDrawObject(ctx, forClipping, context) {
9716
+ originalDrawObject.call(this, ctx, forClipping, context);
9717
+ if (forClipping) return;
9718
+ const cfg = this.__edgeFadeRenderConfig;
9719
+ if (!cfg) return;
9720
+ const liveW = Number(this.width) || 0;
9721
+ const liveH = Number(this.height) || 0;
9722
+ const w = liveW > 0 ? liveW : cfg.frameW;
9723
+ const h = liveH > 0 ? liveH : cfg.frameH;
9724
+ const x = -w / 2;
9725
+ const y = -h / 2;
9726
+ const paintBand = (side, size, amount, hardness) => {
9727
+ if (size <= 0 || amount >= 1) return;
9728
+ ctx.save();
9729
+ ctx.globalCompositeOperation = "destination-out";
9730
+ let gradient;
9731
+ const eraseAtEdge = 1 - amount;
9732
+ const STOPS = 24;
9733
+ const addStops = (g) => {
9734
+ for (let i = 0; i <= STOPS; i++) {
9735
+ const t = i / STOPS;
9736
+ const keepProgress = Math.pow(t, hardness);
9737
+ const a = eraseAtEdge * (1 - keepProgress);
9738
+ g.addColorStop(t, `rgba(0,0,0,${a})`);
9739
+ }
9740
+ };
9741
+ if (side === "top") {
9742
+ const band = Math.max(1, h * size);
9743
+ gradient = ctx.createLinearGradient(0, y, 0, y + band);
9744
+ addStops(gradient);
9745
+ ctx.fillStyle = gradient;
9746
+ ctx.fillRect(x, y, w, band);
9747
+ if (amount <= 1e-3) ctx.fillRect(x, y, w, Math.min(2, band));
9748
+ } else if (side === "bottom") {
9749
+ const band = Math.max(1, h * size);
9750
+ gradient = ctx.createLinearGradient(0, y + h, 0, y + h - band);
9751
+ addStops(gradient);
9752
+ ctx.fillStyle = gradient;
9753
+ ctx.fillRect(x, y + h - band, w, band);
9754
+ if (amount <= 1e-3) ctx.fillRect(x, y + h - Math.min(2, band), w, Math.min(2, band));
9755
+ } else if (side === "left") {
9756
+ const band = Math.max(1, w * size);
9757
+ gradient = ctx.createLinearGradient(x, 0, x + band, 0);
9758
+ addStops(gradient);
9759
+ ctx.fillStyle = gradient;
9760
+ ctx.fillRect(x, y, band, h);
9761
+ if (amount <= 1e-3) ctx.fillRect(x, y, Math.min(2, band), h);
9762
+ } else {
9763
+ const band = Math.max(1, w * size);
9764
+ gradient = ctx.createLinearGradient(x + w, 0, x + w - band, 0);
9765
+ addStops(gradient);
9766
+ ctx.fillStyle = gradient;
9767
+ ctx.fillRect(x + w - band, y, band, h);
9768
+ if (amount <= 1e-3) ctx.fillRect(x + w - Math.min(2, band), y, Math.min(2, band), h);
9769
+ }
9770
+ ctx.restore();
9771
+ };
9772
+ paintBand("top", cfg.topSize, cfg.topAmount, cfg.topHardness);
9773
+ paintBand("right", cfg.rightSize, cfg.rightAmount, cfg.rightHardness);
9774
+ paintBand("bottom", cfg.bottomSize, cfg.bottomAmount, cfg.bottomHardness);
9775
+ paintBand("left", cfg.leftSize, cfg.leftAmount, cfg.leftHardness);
9776
+ };
9777
+ group.set({ objectCaching: true, noScaleCache: false });
9778
+ fadeGroup.__edgeFadeKey = fadeGroup.__edgeFadeRenderConfig.key;
9779
+ fadeGroup.__edgeFadeInputKey = inputKey;
9780
+ } else {
9781
+ if (fadeGroup.__edgeFadeOriginalDrawObject) {
9782
+ group.drawObject = fadeGroup.__edgeFadeOriginalDrawObject;
9783
+ }
9784
+ delete fadeGroup.__edgeFadeRenderConfig;
9785
+ delete fadeGroup.__edgeFadeKey;
9786
+ delete fadeGroup.__edgeFadeInputKey;
9787
+ if ((_b = group.clipPath) == null ? void 0 : _b.__edgeFadeMask) {
9788
+ group.clipPath = void 0;
9789
+ updateCoverLayout(group);
9790
+ }
9791
+ }
9792
+ if (group.clipPath) {
9793
+ group.clipPath.dirty = true;
9794
+ group.clipPath.setCoords();
9795
+ }
9796
+ fadeGroup.dirty = true;
9797
+ const children = group.getObjects();
9798
+ children.forEach((child) => {
9799
+ child.dirty = true;
9800
+ });
9801
+ (_c = group.canvas) == null ? void 0 : _c.requestRenderAll();
9802
+ };
9539
9803
  const loadImageAsync = async (element, placeholder, fc) => {
9540
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
9804
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
9541
9805
  const imageUrl = element.src || element.imageUrl;
9542
9806
  if (!imageUrl) return;
9543
9807
  const elementId = element.id;
@@ -9550,7 +9814,9 @@ const PageCanvas = react.forwardRef(
9550
9814
  const existingImg = ct == null ? void 0 : ct._img;
9551
9815
  const existingSrc = placeholder.__imageSrc;
9552
9816
  const existingSvgColorMap = placeholder.__svgColorMap || "";
9553
- if (existingImg && existingSrc === imageUrl && existingSvgColorMap === nextSvgColorMap) {
9817
+ const existingFadeKey = existingImg && existingImg.__edgeFadeKey || placeholder.__edgeFadeKey || "";
9818
+ const nextFadeKey = edgeFadeKey(element);
9819
+ if (existingImg && existingSrc === imageUrl && existingSvgColorMap === nextSvgColorMap && existingFadeKey === nextFadeKey) {
9554
9820
  return placeholder;
9555
9821
  }
9556
9822
  }
@@ -9567,6 +9833,22 @@ const PageCanvas = react.forwardRef(
9567
9833
  if (!fabricRef.current || !isLatestRequest()) return;
9568
9834
  await normalizeSvgImageDimensions(img, imageUrl, element.sourceFormat);
9569
9835
  if (!isLatestRequest()) return;
9836
+ const imageFitForFade = element.imageFit || ((_a = element.style) == null ? void 0 : _a.imageFit) || "cover";
9837
+ const clipShapeForFade = element.clipShape ?? ((_b = element.style) == null ? void 0 : _b.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9838
+ const willUseCropGroupForFade = imageFitForFade !== "fill" || clipShapeForFade && clipShapeForFade !== "none";
9839
+ try {
9840
+ if (hasEdgeFade(element) && !willUseCropGroupForFade) {
9841
+ const srcEl = (_c = img.getElement) == null ? void 0 : _c.call(img);
9842
+ if (srcEl) {
9843
+ const baked = bakeEdgeFade(srcEl, element);
9844
+ img.setElement(baked);
9845
+ img.__edgeFadeKey = edgeFadeKey(element);
9846
+ img.dirty = true;
9847
+ }
9848
+ }
9849
+ } catch (e) {
9850
+ console.warn("[edgeFade] bake failed:", e);
9851
+ }
9570
9852
  const isHidden = !element.visible;
9571
9853
  img.set({
9572
9854
  originX: "left",
@@ -9599,8 +9881,8 @@ const PageCanvas = react.forwardRef(
9599
9881
  });
9600
9882
  img.setCoords();
9601
9883
  } else {
9602
- const imageFit = element.imageFit || ((_a = element.style) == null ? void 0 : _a.imageFit) || "cover";
9603
- const clipShape = element.clipShape ?? ((_b = element.style) == null ? void 0 : _b.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9884
+ const imageFit = element.imageFit || ((_d = element.style) == null ? void 0 : _d.imageFit) || "cover";
9885
+ const clipShape = element.clipShape ?? ((_e = element.style) == null ? void 0 : _e.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9604
9886
  const needCropGroup2 = imageFit !== "fill" || clipShape && clipShape !== "none";
9605
9887
  const imgNaturalWidth = img.width || 1;
9606
9888
  const imgNaturalHeight = img.height || 1;
@@ -9627,7 +9909,7 @@ const PageCanvas = react.forwardRef(
9627
9909
  if (imageFit === "fill" && !needCropGroup2) {
9628
9910
  const finalScaleX = baseScaleX * (element.scaleX ?? 1);
9629
9911
  const finalScaleY = baseScaleY * (element.scaleY ?? 1);
9630
- const pageTreeForCreate = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_c = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _c.children) ?? [];
9912
+ const pageTreeForCreate = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_f = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _f.children) ?? [];
9631
9913
  const createPos = pageTreeForCreate.length > 0 ? (() => {
9632
9914
  const node = findNodeById(pageTreeForCreate, element.id);
9633
9915
  return node ? getAbsoluteBounds(node, pageTreeForCreate) : { left: element.left ?? 0, top: element.top ?? 0 };
@@ -9672,12 +9954,12 @@ const PageCanvas = react.forwardRef(
9672
9954
  }
9673
9955
  img.__imageSrc = imageUrl;
9674
9956
  img.__svgColorMap = element.svgColorMap ? JSON.stringify(element.svgColorMap) : "";
9675
- const imageFitFinal = element.imageFit || ((_d = element.style) == null ? void 0 : _d.imageFit) || "cover";
9676
- const clipShapeFinal = element.clipShape ?? ((_e = element.style) == null ? void 0 : _e.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9957
+ const imageFitFinal = element.imageFit || ((_g = element.style) == null ? void 0 : _g.imageFit) || "cover";
9958
+ const clipShapeFinal = element.clipShape ?? ((_h = element.style) == null ? void 0 : _h.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
9677
9959
  const needCropGroup = imageFitFinal !== "fill" || clipShapeFinal && clipShapeFinal !== "none";
9678
9960
  let finalObject = img;
9679
9961
  if (needCropGroup) {
9680
- const pageTreeForCropResolve = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_f = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _f.children) ?? [];
9962
+ const pageTreeForCropResolve = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_i = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _i.children) ?? [];
9681
9963
  const nodeForSize = pageTreeForCropResolve.length ? findNodeById(pageTreeForCropResolve, element.id) : null;
9682
9964
  const w = nodeForSize && isElement(nodeForSize) ? nodeForSize.width : element.width;
9683
9965
  const h = nodeForSize && isElement(nodeForSize) ? nodeForSize.height : element.height;
@@ -9709,16 +9991,16 @@ const PageCanvas = react.forwardRef(
9709
9991
  let panY = element.cropPanY ?? 0.5;
9710
9992
  let zoom2 = element.cropZoom ?? 1;
9711
9993
  if (existingCropGroup) {
9712
- const existingImg = (_g = existingCropGroup.__cropData) == null ? void 0 : _g._img;
9994
+ const existingImg = (_j = existingCropGroup.__cropData) == null ? void 0 : _j._img;
9713
9995
  if (existingImg) {
9714
- panX = ((_h = existingImg._ct) == null ? void 0 : _h.panX) ?? existingImg.__panX ?? panX;
9715
- panY = ((_i = existingImg._ct) == null ? void 0 : _i.panY) ?? existingImg.__panY ?? panY;
9716
- zoom2 = ((_j = existingImg._ct) == null ? void 0 : _j.zoom) ?? zoom2;
9996
+ panX = ((_k = existingImg._ct) == null ? void 0 : _k.panX) ?? existingImg.__panX ?? panX;
9997
+ panY = ((_l = existingImg._ct) == null ? void 0 : _l.panY) ?? existingImg.__panY ?? panY;
9998
+ zoom2 = ((_m = existingImg._ct) == null ? void 0 : _m.zoom) ?? zoom2;
9717
9999
  }
9718
10000
  }
9719
10001
  const isDynamicField = dynamicFieldIds.includes(element.id);
9720
10002
  const canBeEvented = isEditorMode || isPreviewMode && isDynamicField;
9721
- const pageTreeForCrop = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_k = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _k.children) ?? [];
10003
+ const pageTreeForCrop = ((pageChildren == null ? void 0 : pageChildren.length) ? pageChildren : (_n = useEditorStore.getState().canvas.pages.find((p) => p.id === pageId)) == null ? void 0 : _n.children) ?? [];
9722
10004
  const createPosForCrop = pageTreeForCrop.length > 0 ? (() => {
9723
10005
  const node = findNodeById(pageTreeForCrop, element.id);
9724
10006
  return node ? getAbsoluteBounds(node, pageTreeForCrop) : { left: element.left ?? 0, top: element.top ?? 0 };
@@ -9769,17 +10051,18 @@ const PageCanvas = react.forwardRef(
9769
10051
  } else {
9770
10052
  installCanvaMaskControls(cropGroup);
9771
10053
  }
9772
- const cropImg = (_l = cropGroup.__cropData) == null ? void 0 : _l._img;
10054
+ const cropImg = (_o = cropGroup.__cropData) == null ? void 0 : _o._img;
9773
10055
  if (cropImg) {
9774
10056
  cropImg._ct = { panX, panY, zoom: zoom2 };
9775
10057
  updateCoverLayout(cropGroup);
9776
10058
  }
10059
+ applyEdgeFadeFrameClipPath(cropGroup, element, frameW, frameH, shape, rxRatio);
9777
10060
  setObjectData(cropGroup, element.id);
9778
10061
  cropGroup.__imageElement = cropImg;
9779
10062
  cropGroup.__imageSrc = imageUrl;
9780
10063
  cropGroup.__svgColorMap = nextSvgColorMap;
9781
10064
  if (cropImg && element.imageNaturalWidth == null) {
9782
- const el = ((_m = cropImg.getElement) == null ? void 0 : _m.call(cropImg)) ?? cropImg._element;
10065
+ const el = ((_p = cropImg.getElement) == null ? void 0 : _p.call(cropImg)) ?? cropImg._element;
9783
10066
  const orig = typeof cropImg.getOriginalSize === "function" ? cropImg.getOriginalSize() : null;
9784
10067
  const nw = (orig == null ? void 0 : orig.width) ?? (el == null ? void 0 : el.naturalWidth) ?? cropImg.width;
9785
10068
  const nh = (orig == null ? void 0 : orig.height) ?? (el == null ? void 0 : el.naturalHeight) ?? cropImg.height;
@@ -13940,12 +14223,69 @@ class PixldocsRenderer {
13940
14223
  { width: canvasWidth, height: canvasHeight },
13941
14224
  { cssOnly: false, backstoreOnly: false }
13942
14225
  );
14226
+ const fadeBakeRecords = [];
14227
+ try {
14228
+ const objs = fabricInstance.getObjects().slice();
14229
+ for (const obj of objs) {
14230
+ const isFadedCropGroup = obj instanceof fabric__namespace.Group && (Boolean(obj.__edgeFadeRenderConfig) || Boolean(obj.__edgeFadeKey) || Boolean(obj.__edgeFadeInputKey));
14231
+ if (!isFadedCropGroup) continue;
14232
+ try {
14233
+ const baked = obj.toCanvasElement({
14234
+ multiplier: 2,
14235
+ enableRetinaScaling: false
14236
+ });
14237
+ const rect = obj.getBoundingRect();
14238
+ const replacement = new fabric__namespace.FabricImage(baked, {
14239
+ left: rect.left,
14240
+ top: rect.top,
14241
+ originX: "left",
14242
+ originY: "top",
14243
+ scaleX: rect.width / baked.width,
14244
+ scaleY: rect.height / baked.height,
14245
+ selectable: false,
14246
+ evented: false,
14247
+ objectCaching: false
14248
+ });
14249
+ const insertIndex = fabricInstance._objects.indexOf(obj);
14250
+ const prevExclude = obj.excludeFromExport;
14251
+ obj.excludeFromExport = true;
14252
+ if (insertIndex >= 0) {
14253
+ fabricInstance.insertAt(insertIndex + 1, replacement);
14254
+ } else {
14255
+ fabricInstance.add(replacement);
14256
+ }
14257
+ fadeBakeRecords.push({
14258
+ original: obj,
14259
+ replacement,
14260
+ prevExclude,
14261
+ insertIndex
14262
+ });
14263
+ } catch (bakeErr) {
14264
+ console.warn("[canvas-renderer][edgeFade] bake-for-svg failed:", bakeErr);
14265
+ }
14266
+ }
14267
+ if (fadeBakeRecords.length) {
14268
+ fabricInstance.renderAll();
14269
+ console.log(
14270
+ `[canvas-renderer][edgeFade] baked ${fadeBakeRecords.length} faded object(s) for SVG capture`
14271
+ );
14272
+ }
14273
+ } catch (e) {
14274
+ console.warn("[canvas-renderer][edgeFade] bake pass failed:", e);
14275
+ }
13943
14276
  const rawSvgString = fabricInstance.toSVG();
13944
14277
  const svgString = this.normalizeSvgDimensions(
13945
14278
  rawSvgString,
13946
14279
  canvasWidth,
13947
14280
  canvasHeight
13948
14281
  );
14282
+ for (const rec of fadeBakeRecords) {
14283
+ try {
14284
+ fabricInstance.remove(rec.replacement);
14285
+ rec.original.excludeFromExport = rec.prevExclude;
14286
+ } catch {
14287
+ }
14288
+ }
13949
14289
  fabricInstance.enableRetinaScaling = prevRetina;
13950
14290
  fabricInstance.setDimensions(
13951
14291
  { width: prevWidth, height: prevHeight },