@pixldocs/canvas-renderer 0.5.6 → 0.5.8
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 +1194 -334
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +81 -0
- package/dist/index.js +1194 -334
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2932,12 +2932,117 @@ async function normalizeSvgImageDimensions(fabricImage, imageUrl, sourceFormat)
|
|
|
2932
2932
|
fabricImage.setCoords();
|
|
2933
2933
|
}
|
|
2934
2934
|
}
|
|
2935
|
+
const EMPTY_IMAGE_PLACEHOLDER_URL = "/image-placeholder.png";
|
|
2936
|
+
let placeholderTileImage = null;
|
|
2937
|
+
let placeholderTilePromise = null;
|
|
2938
|
+
function loadPlaceholderTile() {
|
|
2939
|
+
if (placeholderTileImage) return Promise.resolve(placeholderTileImage);
|
|
2940
|
+
if (placeholderTilePromise) return placeholderTilePromise;
|
|
2941
|
+
placeholderTilePromise = new Promise((resolve, reject) => {
|
|
2942
|
+
const img = new Image();
|
|
2943
|
+
img.crossOrigin = "anonymous";
|
|
2944
|
+
img.onload = () => {
|
|
2945
|
+
placeholderTileImage = img;
|
|
2946
|
+
resolve(img);
|
|
2947
|
+
};
|
|
2948
|
+
img.onerror = (e) => {
|
|
2949
|
+
placeholderTilePromise = null;
|
|
2950
|
+
reject(e);
|
|
2951
|
+
};
|
|
2952
|
+
img.src = EMPTY_IMAGE_PLACEHOLDER_URL;
|
|
2953
|
+
});
|
|
2954
|
+
return placeholderTilePromise;
|
|
2955
|
+
}
|
|
2956
|
+
function isEmptyImagePlaceholderGroup(obj) {
|
|
2957
|
+
return obj instanceof fabric.Group && Boolean(obj.__emptyImagePlaceholder);
|
|
2958
|
+
}
|
|
2959
|
+
function stabilizePlaceholderGroup(group, width, height) {
|
|
2960
|
+
group.set({ width, height, scaleX: 1, scaleY: 1 });
|
|
2961
|
+
if (group.layoutManager) {
|
|
2962
|
+
if (typeof fabric.FixedLayout === "function") {
|
|
2963
|
+
group.layoutManager.strategy = new fabric.FixedLayout();
|
|
2964
|
+
} else {
|
|
2965
|
+
group.layoutManager.performLayout = () => {
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
group.setCoords();
|
|
2970
|
+
}
|
|
2971
|
+
function attachEmptyPlaceholderImage(group, width, height) {
|
|
2972
|
+
loadPlaceholderTile().then((htmlImg) => {
|
|
2973
|
+
var _a;
|
|
2974
|
+
const objects = group._objects;
|
|
2975
|
+
const bgRect = objects == null ? void 0 : objects.find((obj) => obj.__isPlaceholderFrame);
|
|
2976
|
+
if (!bgRect) return;
|
|
2977
|
+
const pattern = new fabric.Pattern({
|
|
2978
|
+
source: htmlImg,
|
|
2979
|
+
repeat: "repeat"
|
|
2980
|
+
});
|
|
2981
|
+
pattern.__isPlaceholderPattern = true;
|
|
2982
|
+
bgRect.set({ fill: pattern });
|
|
2983
|
+
bgRect.dirty = true;
|
|
2984
|
+
if (!group.clipPath) {
|
|
2985
|
+
const clip = new fabric.Rect({
|
|
2986
|
+
width,
|
|
2987
|
+
height,
|
|
2988
|
+
left: 0,
|
|
2989
|
+
top: 0,
|
|
2990
|
+
originX: "center",
|
|
2991
|
+
originY: "center",
|
|
2992
|
+
selectable: false,
|
|
2993
|
+
evented: false
|
|
2994
|
+
});
|
|
2995
|
+
clip.absolutePositioned = false;
|
|
2996
|
+
clip.excludeFromExport = true;
|
|
2997
|
+
group.clipPath = clip;
|
|
2998
|
+
}
|
|
2999
|
+
stabilizePlaceholderGroup(group, width, height);
|
|
3000
|
+
group.dirty = true;
|
|
3001
|
+
(_a = group.canvas) == null ? void 0 : _a.requestRenderAll();
|
|
3002
|
+
}).catch(() => {
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
function updateEmptyPlaceholderLayout(group, width, height) {
|
|
3006
|
+
const objects = group._objects;
|
|
3007
|
+
const backgroundRect = objects == null ? void 0 : objects.find((obj) => obj.__isPlaceholderFrame);
|
|
3008
|
+
const cropData = group.__cropData;
|
|
3009
|
+
if (cropData) {
|
|
3010
|
+
cropData.frameW = width;
|
|
3011
|
+
cropData.frameH = height;
|
|
3012
|
+
}
|
|
3013
|
+
backgroundRect == null ? void 0 : backgroundRect.set({
|
|
3014
|
+
width,
|
|
3015
|
+
height,
|
|
3016
|
+
left: 0,
|
|
3017
|
+
top: 0,
|
|
3018
|
+
originX: "center",
|
|
3019
|
+
originY: "center"
|
|
3020
|
+
});
|
|
3021
|
+
if (backgroundRect) backgroundRect.dirty = true;
|
|
3022
|
+
if (backgroundRect && !(backgroundRect.fill instanceof fabric.Pattern)) {
|
|
3023
|
+
attachEmptyPlaceholderImage(group, width, height);
|
|
3024
|
+
}
|
|
3025
|
+
if (group.clipPath && (group.clipPath instanceof fabric.Rect || group.clipPath instanceof fabric.Ellipse)) {
|
|
3026
|
+
group.clipPath.set({
|
|
3027
|
+
left: 0,
|
|
3028
|
+
top: 0,
|
|
3029
|
+
originX: "center",
|
|
3030
|
+
originY: "center",
|
|
3031
|
+
...group.clipPath instanceof fabric.Ellipse ? { rx: width / 2, ry: height / 2 } : { width, height }
|
|
3032
|
+
});
|
|
3033
|
+
group.clipPath.setCoords();
|
|
3034
|
+
group.clipPath.dirty = true;
|
|
3035
|
+
}
|
|
3036
|
+
stabilizePlaceholderGroup(group, width, height);
|
|
3037
|
+
group.dirty = true;
|
|
3038
|
+
}
|
|
2935
3039
|
function createImagePlaceholder(element) {
|
|
2936
3040
|
const visualWidth = Number(element.width) * (element.scaleX ?? 1);
|
|
2937
3041
|
const visualHeight = Number(element.height) * (element.scaleY ?? 1);
|
|
3042
|
+
const hasImage = !!(element.src || element.imageUrl);
|
|
2938
3043
|
const bgRect = new fabric.Rect({
|
|
2939
|
-
originX: "
|
|
2940
|
-
originY: "
|
|
3044
|
+
originX: "center",
|
|
3045
|
+
originY: "center",
|
|
2941
3046
|
left: 0,
|
|
2942
3047
|
top: 0,
|
|
2943
3048
|
width: visualWidth,
|
|
@@ -2948,56 +3053,65 @@ function createImagePlaceholder(element) {
|
|
|
2948
3053
|
rx: element.clipShape === "rounded" ? element.rx || 8 : 0,
|
|
2949
3054
|
ry: element.clipShape === "rounded" ? element.ry || 8 : 0
|
|
2950
3055
|
});
|
|
3056
|
+
bgRect.__isPlaceholderFrame = true;
|
|
2951
3057
|
const group = new fabric.Group([bgRect], {
|
|
2952
|
-
left: element.left,
|
|
2953
|
-
top: element.top,
|
|
2954
|
-
originX: "left",
|
|
2955
|
-
originY: "top",
|
|
2956
|
-
selectable: element.selectable,
|
|
2957
|
-
evented: element.evented
|
|
2958
|
-
});
|
|
2959
|
-
return group;
|
|
2960
|
-
}
|
|
2961
|
-
function createImagePlaceholderForGroup(element) {
|
|
2962
|
-
const frameWidth = Number(element.width) * (element.scaleX ?? 1);
|
|
2963
|
-
const frameHeight = Number(element.height) * (element.scaleY ?? 1);
|
|
2964
|
-
const bgRect = new fabric.Rect({
|
|
2965
|
-
originX: "left",
|
|
2966
|
-
originY: "top",
|
|
2967
|
-
left: 0,
|
|
2968
|
-
top: 0,
|
|
2969
|
-
width: frameWidth,
|
|
2970
|
-
height: frameHeight,
|
|
2971
|
-
fill: "transparent",
|
|
2972
|
-
stroke: "transparent",
|
|
2973
|
-
strokeWidth: 0,
|
|
2974
|
-
rx: element.clipShape === "rounded" ? element.rx || 8 : 0,
|
|
2975
|
-
ry: element.clipShape === "rounded" ? element.ry || 8 : 0
|
|
2976
|
-
});
|
|
2977
|
-
const group = new fabric.Group([bgRect], {
|
|
2978
|
-
left: element.left,
|
|
2979
|
-
top: element.top,
|
|
3058
|
+
left: (element.left ?? 0) + visualWidth / 2,
|
|
3059
|
+
top: (element.top ?? 0) + visualHeight / 2,
|
|
2980
3060
|
originX: "center",
|
|
2981
3061
|
originY: "center",
|
|
2982
|
-
width:
|
|
2983
|
-
height:
|
|
3062
|
+
width: visualWidth,
|
|
3063
|
+
height: visualHeight,
|
|
2984
3064
|
scaleX: 1,
|
|
2985
3065
|
scaleY: 1,
|
|
2986
3066
|
angle: element.angle ?? 0,
|
|
2987
3067
|
opacity: element.opacity ?? 1,
|
|
2988
3068
|
flipX: element.flipX ?? false,
|
|
2989
|
-
flipY: element.flipY ?? false
|
|
3069
|
+
flipY: element.flipY ?? false,
|
|
3070
|
+
selectable: element.selectable,
|
|
3071
|
+
evented: element.evented,
|
|
3072
|
+
hasControls: true,
|
|
3073
|
+
hasBorders: true,
|
|
3074
|
+
interactive: true,
|
|
3075
|
+
subTargetCheck: false,
|
|
3076
|
+
objectCaching: false
|
|
2990
3077
|
});
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
3078
|
+
group.__emptyImagePlaceholder = true;
|
|
3079
|
+
group.__imageSrc = "";
|
|
3080
|
+
group.__cropData = {
|
|
3081
|
+
shape: element.clipShape === "circle" ? "circle" : element.clipShape === "rounded" ? "roundRect" : "rect",
|
|
3082
|
+
rx: element.clipShape === "rounded" ? element.rx || 0.1 : 0,
|
|
3083
|
+
frameW: visualWidth,
|
|
3084
|
+
frameH: visualHeight,
|
|
3085
|
+
fit: "cover",
|
|
3086
|
+
_img: null,
|
|
3087
|
+
_border: null,
|
|
3088
|
+
_placeholderFrame: bgRect
|
|
3089
|
+
};
|
|
3090
|
+
group._ct = group._ct || {};
|
|
3091
|
+
group._ct.isCropGroup = true;
|
|
3092
|
+
group.__cropGroup = true;
|
|
3093
|
+
const clipPath = createImageClipPath(
|
|
3094
|
+
{
|
|
3095
|
+
...element,
|
|
3096
|
+
clipShape: element.clipShape && element.clipShape !== "none" ? element.clipShape : "rectangle"
|
|
3097
|
+
},
|
|
3098
|
+
visualWidth,
|
|
3099
|
+
visualHeight
|
|
3100
|
+
);
|
|
3101
|
+
if (clipPath) {
|
|
3102
|
+
clipPath.absolutePositioned = false;
|
|
3103
|
+
clipPath.excludeFromExport = true;
|
|
3104
|
+
group.clipPath = clipPath;
|
|
3105
|
+
}
|
|
3106
|
+
stabilizePlaceholderGroup(group, visualWidth, visualHeight);
|
|
3107
|
+
if (!hasImage) {
|
|
3108
|
+
attachEmptyPlaceholderImage(group, visualWidth, visualHeight);
|
|
2998
3109
|
}
|
|
2999
3110
|
return group;
|
|
3000
3111
|
}
|
|
3112
|
+
function createImagePlaceholderForGroup(element) {
|
|
3113
|
+
return createImagePlaceholder(element);
|
|
3114
|
+
}
|
|
3001
3115
|
function createImageClipPath(element, imgWidth, imgHeight) {
|
|
3002
3116
|
const clipShape = element.clipShape || "none";
|
|
3003
3117
|
if (clipShape === "none") return void 0;
|
|
@@ -3035,7 +3149,328 @@ function createImageClipPath(element, imgWidth, imgHeight) {
|
|
|
3035
3149
|
return void 0;
|
|
3036
3150
|
}
|
|
3037
3151
|
}
|
|
3038
|
-
|
|
3152
|
+
const isCropGroup = (obj) => {
|
|
3153
|
+
var _a;
|
|
3154
|
+
const g = obj;
|
|
3155
|
+
return Boolean(g && (g.__cropGroup || ((_a = g._ct) == null ? void 0 : _a.isCropGroup)));
|
|
3156
|
+
};
|
|
3157
|
+
function parseLuminance(fill) {
|
|
3158
|
+
if (typeof fill !== "string") return null;
|
|
3159
|
+
const f = fill.trim().toLowerCase();
|
|
3160
|
+
if (!f || f === "none" || f === "transparent") return null;
|
|
3161
|
+
if (f === "white" || f === "#fff" || f === "#ffffff") return 1;
|
|
3162
|
+
if (f === "black" || f === "#000" || f === "#000000") return 0;
|
|
3163
|
+
const hex = f.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);
|
|
3164
|
+
if (hex) {
|
|
3165
|
+
const h = hex[1];
|
|
3166
|
+
const full = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
|
|
3167
|
+
const r = parseInt(full.slice(0, 2), 16) / 255;
|
|
3168
|
+
const g = parseInt(full.slice(2, 4), 16) / 255;
|
|
3169
|
+
const b = parseInt(full.slice(4, 6), 16) / 255;
|
|
3170
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
3171
|
+
}
|
|
3172
|
+
const rgb = f.match(/^rgba?\(([^)]+)\)$/);
|
|
3173
|
+
if (rgb) {
|
|
3174
|
+
const parts = rgb[1].split(",").map((s) => parseFloat(s.trim()));
|
|
3175
|
+
if (parts.length >= 3) {
|
|
3176
|
+
const r = parts[0] / 255;
|
|
3177
|
+
const g = parts[1] / 255;
|
|
3178
|
+
const b = parts[2] / 255;
|
|
3179
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
return null;
|
|
3183
|
+
}
|
|
3184
|
+
function getObjectRenderedSize(obj) {
|
|
3185
|
+
const bbox = typeof obj.getBoundingRect === "function" ? obj.getBoundingRect() : null;
|
|
3186
|
+
const width = Math.abs(Number(bbox == null ? void 0 : bbox.width) || (obj.width ?? 0) * Math.abs(obj.scaleX ?? 1));
|
|
3187
|
+
const height = Math.abs(Number(bbox == null ? void 0 : bbox.height) || (obj.height ?? 0) * Math.abs(obj.scaleY ?? 1));
|
|
3188
|
+
return { width, height };
|
|
3189
|
+
}
|
|
3190
|
+
function isLikelyFullFrameBackground(obj, luminance, viewBoxW, viewBoxH, totalObjects) {
|
|
3191
|
+
if (totalObjects <= 1 || viewBoxW <= 0 || viewBoxH <= 0 || luminance === null) return false;
|
|
3192
|
+
const { width, height } = getObjectRenderedSize(obj);
|
|
3193
|
+
const widthRatio = width / viewBoxW;
|
|
3194
|
+
const heightRatio = height / viewBoxH;
|
|
3195
|
+
const areaRatio = width * height / (viewBoxW * viewBoxH);
|
|
3196
|
+
return widthRatio >= 0.96 && heightRatio >= 0.96 && areaRatio >= 0.9;
|
|
3197
|
+
}
|
|
3198
|
+
async function loadSvgAsGroup(url) {
|
|
3199
|
+
const result = await fabric.loadSVGFromURL(url);
|
|
3200
|
+
const allObjects = (result.objects || []).filter(Boolean);
|
|
3201
|
+
const options = result.options || {};
|
|
3202
|
+
if (allObjects.length === 0) {
|
|
3203
|
+
throw new Error("SVG contained no parseable shapes");
|
|
3204
|
+
}
|
|
3205
|
+
const hintedViewBoxW = Number(options.width) || 0;
|
|
3206
|
+
const hintedViewBoxH = Number(options.height) || 0;
|
|
3207
|
+
const sourceLuminances = allObjects.map((o) => parseLuminance(o.fill));
|
|
3208
|
+
let objects = allObjects.filter((obj, i) => !isLikelyFullFrameBackground(
|
|
3209
|
+
obj,
|
|
3210
|
+
sourceLuminances[i],
|
|
3211
|
+
hintedViewBoxW,
|
|
3212
|
+
hintedViewBoxH,
|
|
3213
|
+
allObjects.length
|
|
3214
|
+
));
|
|
3215
|
+
if (objects.length === 0) objects = allObjects;
|
|
3216
|
+
if (objects.length === 0) objects = allObjects;
|
|
3217
|
+
for (const obj of objects) {
|
|
3218
|
+
obj.fillRule = "evenodd";
|
|
3219
|
+
obj.fill = "#000";
|
|
3220
|
+
obj.stroke = null;
|
|
3221
|
+
obj.strokeWidth = 0;
|
|
3222
|
+
}
|
|
3223
|
+
const group = new fabric.Group(objects, {
|
|
3224
|
+
originX: "center",
|
|
3225
|
+
originY: "center",
|
|
3226
|
+
selectable: false,
|
|
3227
|
+
evented: false,
|
|
3228
|
+
hasControls: false,
|
|
3229
|
+
hasBorders: false
|
|
3230
|
+
});
|
|
3231
|
+
const viewBoxW = Number(options.width) || group.width || 1;
|
|
3232
|
+
const viewBoxH = Number(options.height) || group.height || 1;
|
|
3233
|
+
return { group, viewBoxW, viewBoxH };
|
|
3234
|
+
}
|
|
3235
|
+
function fitMaskGroupToFrame(maskGroup, frameW, frameH) {
|
|
3236
|
+
const viewBoxW = Number(maskGroup.__svgMaskViewBoxW) || maskGroup.width || 1;
|
|
3237
|
+
const viewBoxH = Number(maskGroup.__svgMaskViewBoxH) || maskGroup.height || 1;
|
|
3238
|
+
maskGroup.set({
|
|
3239
|
+
left: 0,
|
|
3240
|
+
top: 0,
|
|
3241
|
+
originX: "center",
|
|
3242
|
+
originY: "center",
|
|
3243
|
+
scaleX: frameW / viewBoxW,
|
|
3244
|
+
scaleY: frameH / viewBoxH,
|
|
3245
|
+
selectable: false,
|
|
3246
|
+
evented: false,
|
|
3247
|
+
hasControls: false,
|
|
3248
|
+
hasBorders: false
|
|
3249
|
+
});
|
|
3250
|
+
maskGroup.absolutePositioned = false;
|
|
3251
|
+
maskGroup.excludeFromExport = true;
|
|
3252
|
+
maskGroup.inverted = false;
|
|
3253
|
+
maskGroup.dirty = true;
|
|
3254
|
+
maskGroup.setCoords();
|
|
3255
|
+
}
|
|
3256
|
+
function isSvgMaskClipPath(clipPath) {
|
|
3257
|
+
return Boolean(clipPath && clipPath.__svgMask && clipPath instanceof fabric.Group);
|
|
3258
|
+
}
|
|
3259
|
+
function isLuminanceMaskClipPath(clipPath) {
|
|
3260
|
+
return Boolean(clipPath && clipPath.__svgMaskType === "luminance");
|
|
3261
|
+
}
|
|
3262
|
+
function syncSvgMaskClipPath(cropGroup) {
|
|
3263
|
+
var _a, _b;
|
|
3264
|
+
const clipPath = cropGroup.clipPath;
|
|
3265
|
+
if (!isCropGroup(cropGroup)) return;
|
|
3266
|
+
const frameW = ((_a = cropGroup._ct) == null ? void 0 : _a.frameW) ?? cropGroup.width ?? 0;
|
|
3267
|
+
const frameH = ((_b = cropGroup._ct) == null ? void 0 : _b.frameH) ?? cropGroup.height ?? 0;
|
|
3268
|
+
if (frameW <= 0 || frameH <= 0) return;
|
|
3269
|
+
if (isSvgMaskClipPath(clipPath)) {
|
|
3270
|
+
fitMaskGroupToFrame(clipPath, frameW, frameH);
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
async function detectMaskType(svgUrl) {
|
|
3274
|
+
try {
|
|
3275
|
+
const res = await fetch(svgUrl);
|
|
3276
|
+
if (!res.ok) return "shape";
|
|
3277
|
+
const text = await res.text();
|
|
3278
|
+
const t = text.toLowerCase();
|
|
3279
|
+
if (t.includes("<lineargradient") || t.includes("<radialgradient")) return "luminance";
|
|
3280
|
+
if (t.includes("<image ") || t.includes("<image>")) return "luminance";
|
|
3281
|
+
if (t.includes('mask-type="luminance"') || t.includes("mask-type='luminance'")) return "luminance";
|
|
3282
|
+
if (t.includes("<filter")) return "luminance";
|
|
3283
|
+
if (/fill-opacity\s*=\s*["'](0?\.\d+)/.test(t)) return "luminance";
|
|
3284
|
+
if (/opacity\s*=\s*["'](0?\.\d+)/.test(t)) return "luminance";
|
|
3285
|
+
return "shape";
|
|
3286
|
+
} catch {
|
|
3287
|
+
return "shape";
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
async function applySvgMaskToCropGroup(cropGroup, svgUrl) {
|
|
3291
|
+
var _a, _b, _c;
|
|
3292
|
+
if (!isCropGroup(cropGroup)) {
|
|
3293
|
+
throw new Error("Selected object is not a crop group / image");
|
|
3294
|
+
}
|
|
3295
|
+
const frameW = ((_a = cropGroup._ct) == null ? void 0 : _a.frameW) ?? cropGroup.width ?? 0;
|
|
3296
|
+
const frameH = ((_b = cropGroup._ct) == null ? void 0 : _b.frameH) ?? cropGroup.height ?? 0;
|
|
3297
|
+
if (frameW <= 0 || frameH <= 0) {
|
|
3298
|
+
throw new Error("Crop group has no frame dimensions");
|
|
3299
|
+
}
|
|
3300
|
+
const { group: maskGroup, viewBoxW, viewBoxH } = await loadSvgAsGroup(svgUrl);
|
|
3301
|
+
const mw = maskGroup.width ?? 0;
|
|
3302
|
+
const mh = maskGroup.height ?? 0;
|
|
3303
|
+
if (mw <= 0 || mh <= 0 || viewBoxW <= 0 || viewBoxH <= 0) {
|
|
3304
|
+
throw new Error("This mask has no usable shapes (try the 'Soft' mode instead)");
|
|
3305
|
+
}
|
|
3306
|
+
maskGroup.__svgMask = true;
|
|
3307
|
+
maskGroup.__svgMaskUrl = svgUrl;
|
|
3308
|
+
maskGroup.__svgMaskType = "shape";
|
|
3309
|
+
maskGroup.__svgMaskViewBoxW = viewBoxW;
|
|
3310
|
+
maskGroup.__svgMaskViewBoxH = viewBoxH;
|
|
3311
|
+
fitMaskGroupToFrame(maskGroup, frameW, frameH);
|
|
3312
|
+
cropGroup.clipPath = maskGroup;
|
|
3313
|
+
cropGroup.__svgMaskUrl = svgUrl;
|
|
3314
|
+
cropGroup.__svgMaskType = "shape";
|
|
3315
|
+
cropGroup.dirty = true;
|
|
3316
|
+
if (cropGroup._objects) {
|
|
3317
|
+
for (const child of cropGroup._objects) {
|
|
3318
|
+
child.dirty = true;
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
(_c = cropGroup.canvas) == null ? void 0 : _c.requestRenderAll();
|
|
3322
|
+
}
|
|
3323
|
+
function loadSvgAsImage(svgUrl) {
|
|
3324
|
+
return new Promise((resolve, reject) => {
|
|
3325
|
+
const tryFetch = async () => {
|
|
3326
|
+
try {
|
|
3327
|
+
const res = await fetch(svgUrl, { mode: "cors" });
|
|
3328
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
3329
|
+
const blob = await res.blob();
|
|
3330
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
3331
|
+
const img2 = new Image();
|
|
3332
|
+
img2.onload = () => resolve(img2);
|
|
3333
|
+
img2.onerror = () => reject(new Error("Failed to load SVG (after fetch fallback)"));
|
|
3334
|
+
img2.src = objectUrl;
|
|
3335
|
+
} catch (e) {
|
|
3336
|
+
reject(new Error(`Failed to load SVG: ${(e == null ? void 0 : e.message) || "network error"}`));
|
|
3337
|
+
}
|
|
3338
|
+
};
|
|
3339
|
+
const img = new Image();
|
|
3340
|
+
img.crossOrigin = "anonymous";
|
|
3341
|
+
img.onload = () => resolve(img);
|
|
3342
|
+
img.onerror = () => tryFetch();
|
|
3343
|
+
img.src = svgUrl;
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
async function buildLuminanceAlphaCanvas(svgUrl, frameW, frameH) {
|
|
3347
|
+
const w = Math.max(2, Math.round(frameW));
|
|
3348
|
+
const h = Math.max(2, Math.round(frameH));
|
|
3349
|
+
const img = await loadSvgAsImage(svgUrl);
|
|
3350
|
+
const canvas = document.createElement("canvas");
|
|
3351
|
+
canvas.width = w;
|
|
3352
|
+
canvas.height = h;
|
|
3353
|
+
const ctx = canvas.getContext("2d");
|
|
3354
|
+
if (!ctx) throw new Error("Failed to get 2D context for mask canvas");
|
|
3355
|
+
ctx.clearRect(0, 0, w, h);
|
|
3356
|
+
ctx.drawImage(img, 0, 0, w, h);
|
|
3357
|
+
let data;
|
|
3358
|
+
try {
|
|
3359
|
+
data = ctx.getImageData(0, 0, w, h);
|
|
3360
|
+
} catch (e) {
|
|
3361
|
+
throw new Error(
|
|
3362
|
+
"Could not read SVG pixels (CORS / tainted canvas). Try a different mask source."
|
|
3363
|
+
);
|
|
3364
|
+
}
|
|
3365
|
+
const px = data.data;
|
|
3366
|
+
for (let i = 0; i < px.length; i += 4) {
|
|
3367
|
+
const r = px[i];
|
|
3368
|
+
const g = px[i + 1];
|
|
3369
|
+
const b = px[i + 2];
|
|
3370
|
+
const a = px[i + 3];
|
|
3371
|
+
const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
3372
|
+
const alpha = lum / 255 * a;
|
|
3373
|
+
px[i] = 255;
|
|
3374
|
+
px[i + 1] = 255;
|
|
3375
|
+
px[i + 2] = 255;
|
|
3376
|
+
px[i + 3] = Math.round(alpha);
|
|
3377
|
+
}
|
|
3378
|
+
ctx.putImageData(data, 0, 0);
|
|
3379
|
+
return canvas;
|
|
3380
|
+
}
|
|
3381
|
+
async function applyLuminanceMaskToCropGroup(cropGroup, svgUrl) {
|
|
3382
|
+
var _a, _b, _c;
|
|
3383
|
+
if (!isCropGroup(cropGroup)) {
|
|
3384
|
+
throw new Error("Selected object is not a crop group / image");
|
|
3385
|
+
}
|
|
3386
|
+
const frameW = ((_a = cropGroup._ct) == null ? void 0 : _a.frameW) ?? cropGroup.width ?? 0;
|
|
3387
|
+
const frameH = ((_b = cropGroup._ct) == null ? void 0 : _b.frameH) ?? cropGroup.height ?? 0;
|
|
3388
|
+
if (frameW <= 0 || frameH <= 0) {
|
|
3389
|
+
throw new Error("Crop group has no frame dimensions");
|
|
3390
|
+
}
|
|
3391
|
+
const alphaCanvas = await buildLuminanceAlphaCanvas(svgUrl, frameW, frameH);
|
|
3392
|
+
const maskImg = new fabric.FabricImage(alphaCanvas, {
|
|
3393
|
+
originX: "center",
|
|
3394
|
+
originY: "center",
|
|
3395
|
+
left: 0,
|
|
3396
|
+
top: 0,
|
|
3397
|
+
selectable: false,
|
|
3398
|
+
evented: false,
|
|
3399
|
+
hasControls: false,
|
|
3400
|
+
hasBorders: false
|
|
3401
|
+
});
|
|
3402
|
+
maskImg.absolutePositioned = false;
|
|
3403
|
+
maskImg.excludeFromExport = true;
|
|
3404
|
+
maskImg.inverted = false;
|
|
3405
|
+
maskImg.__svgMask = true;
|
|
3406
|
+
maskImg.__svgMaskUrl = svgUrl;
|
|
3407
|
+
maskImg.__svgMaskType = "luminance";
|
|
3408
|
+
cropGroup.clipPath = maskImg;
|
|
3409
|
+
cropGroup.__svgMaskUrl = svgUrl;
|
|
3410
|
+
cropGroup.__svgMaskType = "luminance";
|
|
3411
|
+
cropGroup.dirty = true;
|
|
3412
|
+
if (cropGroup._objects) {
|
|
3413
|
+
for (const child of cropGroup._objects) {
|
|
3414
|
+
child.dirty = true;
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
(_c = cropGroup.canvas) == null ? void 0 : _c.requestRenderAll();
|
|
3418
|
+
}
|
|
3419
|
+
async function applyMaskToCropGroup(cropGroup, svgUrl, maskType) {
|
|
3420
|
+
if (maskType === "luminance") {
|
|
3421
|
+
return applyLuminanceMaskToCropGroup(cropGroup, svgUrl);
|
|
3422
|
+
}
|
|
3423
|
+
return applySvgMaskToCropGroup(cropGroup, svgUrl);
|
|
3424
|
+
}
|
|
3425
|
+
function getAppliedSvgMaskUrl(obj) {
|
|
3426
|
+
if (!obj) return null;
|
|
3427
|
+
const url = obj.__svgMaskUrl;
|
|
3428
|
+
return typeof url === "string" ? url : null;
|
|
3429
|
+
}
|
|
3430
|
+
function getAppliedSvgMaskType(obj) {
|
|
3431
|
+
if (!obj) return null;
|
|
3432
|
+
const t = obj.__svgMaskType;
|
|
3433
|
+
return t === "luminance" || t === "shape" ? t : null;
|
|
3434
|
+
}
|
|
3435
|
+
function clearSvgMaskFromCropGroup(cropGroup) {
|
|
3436
|
+
var _a, _b, _c;
|
|
3437
|
+
if (!isCropGroup(cropGroup)) return;
|
|
3438
|
+
const frameW = ((_a = cropGroup._ct) == null ? void 0 : _a.frameW) ?? cropGroup.width ?? 0;
|
|
3439
|
+
const frameH = ((_b = cropGroup._ct) == null ? void 0 : _b.frameH) ?? cropGroup.height ?? 0;
|
|
3440
|
+
const rect = new fabric.Rect({
|
|
3441
|
+
width: frameW,
|
|
3442
|
+
height: frameH,
|
|
3443
|
+
left: 0,
|
|
3444
|
+
top: 0,
|
|
3445
|
+
originX: "center",
|
|
3446
|
+
originY: "center",
|
|
3447
|
+
selectable: false,
|
|
3448
|
+
evented: false,
|
|
3449
|
+
hasControls: false,
|
|
3450
|
+
hasBorders: false
|
|
3451
|
+
});
|
|
3452
|
+
rect.absolutePositioned = false;
|
|
3453
|
+
rect.excludeFromExport = true;
|
|
3454
|
+
cropGroup.clipPath = rect;
|
|
3455
|
+
delete cropGroup.__svgMaskUrl;
|
|
3456
|
+
delete cropGroup.__svgMaskType;
|
|
3457
|
+
cropGroup.dirty = true;
|
|
3458
|
+
(_c = cropGroup.canvas) == null ? void 0 : _c.requestRenderAll();
|
|
3459
|
+
}
|
|
3460
|
+
const svgMaskApply = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
3461
|
+
__proto__: null,
|
|
3462
|
+
applyLuminanceMaskToCropGroup,
|
|
3463
|
+
applyMaskToCropGroup,
|
|
3464
|
+
applySvgMaskToCropGroup,
|
|
3465
|
+
clearSvgMaskFromCropGroup,
|
|
3466
|
+
detectMaskType,
|
|
3467
|
+
getAppliedSvgMaskType,
|
|
3468
|
+
getAppliedSvgMaskUrl,
|
|
3469
|
+
isLuminanceMaskClipPath,
|
|
3470
|
+
isSvgMaskClipPath,
|
|
3471
|
+
syncSvgMaskClipPath
|
|
3472
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
3473
|
+
function clamp$1(v, min, max) {
|
|
3039
3474
|
return Math.max(min, Math.min(max, v));
|
|
3040
3475
|
}
|
|
3041
3476
|
function applyControlSizeForZoom(canvas, obj) {
|
|
@@ -3059,76 +3494,78 @@ function finalizeCropGroupCoords(g) {
|
|
|
3059
3494
|
g.setCoords();
|
|
3060
3495
|
}
|
|
3061
3496
|
function updateCoverLayout(g) {
|
|
3497
|
+
var _a;
|
|
3062
3498
|
const ct = g.__cropData;
|
|
3063
3499
|
if (!ct) return;
|
|
3064
3500
|
const { frameW, frameH, shape, rx: rxRatio, _img: img, _border: border } = ct;
|
|
3065
|
-
if (!img) return;
|
|
3066
3501
|
const minDim = Math.min(frameW, frameH);
|
|
3067
3502
|
let rx = rxRatio > 0.5 ? rxRatio : rxRatio * minDim;
|
|
3068
3503
|
rx = Math.max(0, Math.min(rx, frameW / 2, frameH / 2));
|
|
3069
|
-
const
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
clip.setCoords();
|
|
3101
|
-
clip.dirty = true;
|
|
3102
|
-
g.clipPath = clip;
|
|
3103
|
-
} else if (g.clipPath && typeof g.clipPath.set === "function") {
|
|
3104
|
-
if (shape === "circle") {
|
|
3105
|
-
g.clipPath.set({
|
|
3504
|
+
const currentClipPath = g.clipPath;
|
|
3505
|
+
const hasCustomSvgMask = Boolean(
|
|
3506
|
+
currentClipPath && (isSvgMaskClipPath(currentClipPath) || isLuminanceMaskClipPath(currentClipPath) || currentClipPath.__svgMask)
|
|
3507
|
+
);
|
|
3508
|
+
if (hasCustomSvgMask) {
|
|
3509
|
+
if (isSvgMaskClipPath(currentClipPath)) {
|
|
3510
|
+
syncSvgMaskClipPath(g);
|
|
3511
|
+
} else if (currentClipPath && typeof currentClipPath.set === "function") {
|
|
3512
|
+
const baseW = Math.max(1, Number(currentClipPath.width) || frameW);
|
|
3513
|
+
const baseH = Math.max(1, Number(currentClipPath.height) || frameH);
|
|
3514
|
+
currentClipPath.set({
|
|
3515
|
+
left: 0,
|
|
3516
|
+
top: 0,
|
|
3517
|
+
originX: "center",
|
|
3518
|
+
originY: "center",
|
|
3519
|
+
scaleX: frameW / baseW,
|
|
3520
|
+
scaleY: frameH / baseH,
|
|
3521
|
+
selectable: false,
|
|
3522
|
+
evented: false,
|
|
3523
|
+
hasControls: false,
|
|
3524
|
+
hasBorders: false
|
|
3525
|
+
});
|
|
3526
|
+
currentClipPath.absolutePositioned = false;
|
|
3527
|
+
currentClipPath.excludeFromExport = true;
|
|
3528
|
+
currentClipPath.dirty = true;
|
|
3529
|
+
currentClipPath.setCoords();
|
|
3530
|
+
}
|
|
3531
|
+
} else {
|
|
3532
|
+
const needsNewClipPath = !g.clipPath || shape === "circle" && !(g.clipPath instanceof fabric.Ellipse) || shape !== "circle" && !(g.clipPath instanceof fabric.Rect);
|
|
3533
|
+
if (needsNewClipPath) {
|
|
3534
|
+
const clip = shape === "circle" ? new fabric.Ellipse({
|
|
3106
3535
|
rx: frameW / 2,
|
|
3107
3536
|
ry: frameH / 2,
|
|
3108
3537
|
left: 0,
|
|
3109
3538
|
top: 0,
|
|
3110
3539
|
originX: "center",
|
|
3111
3540
|
originY: "center",
|
|
3112
|
-
|
|
3541
|
+
selectable: false,
|
|
3542
|
+
evented: false,
|
|
3543
|
+
hasControls: false,
|
|
3544
|
+
hasBorders: false
|
|
3545
|
+
}) : new fabric.Rect({
|
|
3546
|
+
width: frameW,
|
|
3547
|
+
height: frameH,
|
|
3548
|
+
rx,
|
|
3549
|
+
ry: rx,
|
|
3550
|
+
left: 0,
|
|
3551
|
+
top: 0,
|
|
3552
|
+
originX: "center",
|
|
3553
|
+
originY: "center",
|
|
3113
3554
|
selectable: false,
|
|
3114
3555
|
evented: false,
|
|
3115
3556
|
hasControls: false,
|
|
3116
3557
|
hasBorders: false
|
|
3117
3558
|
});
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
height: frameH,
|
|
3129
|
-
rx,
|
|
3130
|
-
ry: rx,
|
|
3131
|
-
// Ensure both are identical for uniform corners
|
|
3559
|
+
clip.absolutePositioned = false;
|
|
3560
|
+
clip.excludeFromExport = true;
|
|
3561
|
+
clip.setCoords();
|
|
3562
|
+
clip.dirty = true;
|
|
3563
|
+
g.clipPath = clip;
|
|
3564
|
+
} else if (g.clipPath && typeof g.clipPath.set === "function") {
|
|
3565
|
+
if (shape === "circle") {
|
|
3566
|
+
g.clipPath.set({
|
|
3567
|
+
rx: frameW / 2,
|
|
3568
|
+
ry: frameH / 2,
|
|
3132
3569
|
left: 0,
|
|
3133
3570
|
top: 0,
|
|
3134
3571
|
originX: "center",
|
|
@@ -3138,32 +3575,55 @@ function updateCoverLayout(g) {
|
|
|
3138
3575
|
hasControls: false,
|
|
3139
3576
|
hasBorders: false
|
|
3140
3577
|
});
|
|
3141
|
-
newClip.absolutePositioned = false;
|
|
3142
|
-
newClip.excludeFromExport = true;
|
|
3143
|
-
newClip.setCoords();
|
|
3144
|
-
newClip.dirty = true;
|
|
3145
|
-
g.clipPath = newClip;
|
|
3146
3578
|
} else {
|
|
3147
|
-
clipPathObj.
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3579
|
+
const clipPathObj = g.clipPath;
|
|
3580
|
+
const currentRx = clipPathObj.rx || 0;
|
|
3581
|
+
const currentWidth = clipPathObj.width || 0;
|
|
3582
|
+
const currentHeight = clipPathObj.height || 0;
|
|
3583
|
+
const rxChanged = Math.abs(currentRx - rx) > 0.01;
|
|
3584
|
+
const dimsChanged = Math.abs(currentWidth - frameW) > 0.1 || Math.abs(currentHeight - frameH) > 0.1;
|
|
3585
|
+
if (rxChanged || dimsChanged) {
|
|
3586
|
+
const newClip = new fabric.Rect({
|
|
3587
|
+
width: frameW,
|
|
3588
|
+
height: frameH,
|
|
3589
|
+
rx,
|
|
3590
|
+
ry: rx,
|
|
3591
|
+
left: 0,
|
|
3592
|
+
top: 0,
|
|
3593
|
+
originX: "center",
|
|
3594
|
+
originY: "center",
|
|
3595
|
+
selectable: false,
|
|
3596
|
+
evented: false,
|
|
3597
|
+
hasControls: false,
|
|
3598
|
+
hasBorders: false
|
|
3599
|
+
});
|
|
3600
|
+
newClip.absolutePositioned = false;
|
|
3601
|
+
newClip.excludeFromExport = true;
|
|
3602
|
+
newClip.setCoords();
|
|
3603
|
+
newClip.dirty = true;
|
|
3604
|
+
g.clipPath = newClip;
|
|
3605
|
+
} else {
|
|
3606
|
+
clipPathObj.set({
|
|
3607
|
+
width: frameW,
|
|
3608
|
+
height: frameH,
|
|
3609
|
+
rx,
|
|
3610
|
+
ry: rx,
|
|
3611
|
+
left: 0,
|
|
3612
|
+
top: 0,
|
|
3613
|
+
originX: "center",
|
|
3614
|
+
originY: "center",
|
|
3615
|
+
selectable: false,
|
|
3616
|
+
evented: false,
|
|
3617
|
+
hasControls: false,
|
|
3618
|
+
hasBorders: false
|
|
3619
|
+
});
|
|
3620
|
+
clipPathObj.setCoords();
|
|
3621
|
+
clipPathObj.dirty = true;
|
|
3622
|
+
}
|
|
3163
3623
|
}
|
|
3624
|
+
g.clipPath.absolutePositioned = false;
|
|
3625
|
+
g.clipPath.excludeFromExport = true;
|
|
3164
3626
|
}
|
|
3165
|
-
g.clipPath.absolutePositioned = false;
|
|
3166
|
-
g.clipPath.excludeFromExport = true;
|
|
3167
3627
|
}
|
|
3168
3628
|
if (border) {
|
|
3169
3629
|
if (shape === "circle") {
|
|
@@ -3175,6 +3635,28 @@ function updateCoverLayout(g) {
|
|
|
3175
3635
|
border.set({ width: frameW, height: frameH, rx: actualRx, ry: actualRx });
|
|
3176
3636
|
}
|
|
3177
3637
|
}
|
|
3638
|
+
if (!img) {
|
|
3639
|
+
const placeholderFrame = ct._placeholderFrame ?? ((_a = g._objects) == null ? void 0 : _a.find((obj) => obj.__isPlaceholderFrame));
|
|
3640
|
+
if (placeholderFrame && typeof placeholderFrame.set === "function") {
|
|
3641
|
+
placeholderFrame.set({
|
|
3642
|
+
width: frameW,
|
|
3643
|
+
height: frameH,
|
|
3644
|
+
left: 0,
|
|
3645
|
+
top: 0,
|
|
3646
|
+
originX: "center",
|
|
3647
|
+
originY: "center",
|
|
3648
|
+
...placeholderFrame instanceof fabric.Rect ? { rx, ry: rx } : {}
|
|
3649
|
+
});
|
|
3650
|
+
placeholderFrame.dirty = true;
|
|
3651
|
+
ct._placeholderFrame = placeholderFrame;
|
|
3652
|
+
}
|
|
3653
|
+
finalizeCropGroupCoords(g);
|
|
3654
|
+
if (g.canvas) {
|
|
3655
|
+
g.setCoords();
|
|
3656
|
+
g.canvas.requestRenderAll();
|
|
3657
|
+
}
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3178
3660
|
img._ct = img._ct || { panX: 0.5, panY: 0.5, zoom: 1 };
|
|
3179
3661
|
if (img.__panX !== void 0 || img.__panY !== void 0) {
|
|
3180
3662
|
img._ct.panX = img.__panX ?? 0.5;
|
|
@@ -3196,8 +3678,8 @@ function updateCoverLayout(g) {
|
|
|
3196
3678
|
const dispH = ih * finalScale;
|
|
3197
3679
|
const overflowX = Math.max(0, dispW - frameW);
|
|
3198
3680
|
const overflowY = Math.max(0, dispH - frameH);
|
|
3199
|
-
const panX = clamp(img._ct.panX ?? 0.5, 0, 1);
|
|
3200
|
-
const panY = clamp(img._ct.panY ?? 0.5, 0, 1);
|
|
3681
|
+
const panX = clamp$1(img._ct.panX ?? 0.5, 0, 1);
|
|
3682
|
+
const panY = clamp$1(img._ct.panY ?? 0.5, 0, 1);
|
|
3201
3683
|
const offsetX = fitContain ? 0 : overflowX > 0 ? -overflowX * (panX - 0.5) : 0;
|
|
3202
3684
|
const offsetY = fitContain ? 0 : overflowY > 0 ? -overflowY * (panY - 0.5) : 0;
|
|
3203
3685
|
img.set({ left: offsetX, top: offsetY });
|
|
@@ -3289,51 +3771,6 @@ function getRotatedControlCursor(controlKey, target) {
|
|
|
3289
3771
|
const makeRotatedCursorStyleHandler = (controlKey) => {
|
|
3290
3772
|
return (_eventData, _control, target) => getRotatedControlCursor(controlKey, target);
|
|
3291
3773
|
};
|
|
3292
|
-
function resizeFrameFromCorner(eventData, transform, _x, _y) {
|
|
3293
|
-
var _a;
|
|
3294
|
-
const g = transform.target;
|
|
3295
|
-
const ct = g.__cropData;
|
|
3296
|
-
if (!ct || !((_a = g._ct) == null ? void 0 : _a.isCropGroup)) return false;
|
|
3297
|
-
const canvas = g.canvas;
|
|
3298
|
-
if (!canvas) return false;
|
|
3299
|
-
const e = getDomEvent(eventData);
|
|
3300
|
-
if (!e) return false;
|
|
3301
|
-
const pointer = canvas.getPointer(e);
|
|
3302
|
-
g.setCoords();
|
|
3303
|
-
const a = g.aCoords;
|
|
3304
|
-
if (!a) return false;
|
|
3305
|
-
const corner = transform.corner;
|
|
3306
|
-
const anchor = getOppositeAnchor(a, corner);
|
|
3307
|
-
const MIN_W = 20;
|
|
3308
|
-
const MIN_H = 20;
|
|
3309
|
-
const angle = g.angle || 0;
|
|
3310
|
-
const deltaWorldX = pointer.x - anchor.x;
|
|
3311
|
-
const deltaWorldY = pointer.y - anchor.y;
|
|
3312
|
-
const localDelta = worldDeltaToLocal(deltaWorldX, deltaWorldY, angle);
|
|
3313
|
-
const defaultSigns = getCornerDefaultSigns(corner);
|
|
3314
|
-
const signX = Math.abs(localDelta.x) < 1e-3 ? defaultSigns.x : localDelta.x >= 0 ? 1 : -1;
|
|
3315
|
-
const signY = Math.abs(localDelta.y) < 1e-3 ? defaultSigns.y : localDelta.y >= 0 ? 1 : -1;
|
|
3316
|
-
const newW = Math.max(MIN_W, Math.abs(localDelta.x));
|
|
3317
|
-
const newH = Math.max(MIN_H, Math.abs(localDelta.y));
|
|
3318
|
-
ct.frameW = newW;
|
|
3319
|
-
ct.frameH = newH;
|
|
3320
|
-
const centerLocal = {
|
|
3321
|
-
x: signX * (newW / 2),
|
|
3322
|
-
y: signY * (newH / 2)
|
|
3323
|
-
};
|
|
3324
|
-
const centerWorld = localDeltaToWorld(centerLocal.x, centerLocal.y, angle);
|
|
3325
|
-
g.set({
|
|
3326
|
-
left: anchor.x + centerWorld.x,
|
|
3327
|
-
top: anchor.y + centerWorld.y,
|
|
3328
|
-
originX: "center",
|
|
3329
|
-
originY: "center",
|
|
3330
|
-
width: newW,
|
|
3331
|
-
height: newH
|
|
3332
|
-
});
|
|
3333
|
-
updateCoverLayout(g);
|
|
3334
|
-
canvas.requestRenderAll();
|
|
3335
|
-
return true;
|
|
3336
|
-
}
|
|
3337
3774
|
function resizeFrameFromCornerUniform(eventData, transform, _x, _y) {
|
|
3338
3775
|
var _a;
|
|
3339
3776
|
const g = transform.target;
|
|
@@ -3413,7 +3850,7 @@ function resizeFrameFromSide(g, side, localDx, localDy) {
|
|
|
3413
3850
|
}
|
|
3414
3851
|
function installCanvaMaskControls(g) {
|
|
3415
3852
|
const ct = g.__cropData;
|
|
3416
|
-
if (ct) ct.fit =
|
|
3853
|
+
if (ct) ct.fit = ct.fit ?? "cover";
|
|
3417
3854
|
g.setControlsVisibility({
|
|
3418
3855
|
mt: true,
|
|
3419
3856
|
mb: true,
|
|
@@ -3467,7 +3904,7 @@ function installCanvaMaskControls(g) {
|
|
|
3467
3904
|
if (canvas && canvas.__editLockRef) {
|
|
3468
3905
|
canvas.__editLockRef.current = true;
|
|
3469
3906
|
}
|
|
3470
|
-
return
|
|
3907
|
+
return resizeFrameFromCornerUniform(eventData, transform);
|
|
3471
3908
|
}
|
|
3472
3909
|
});
|
|
3473
3910
|
};
|
|
@@ -3477,47 +3914,6 @@ function installCanvaMaskControls(g) {
|
|
|
3477
3914
|
g.controls.br = makeCornerControl("br", 0.5, 0.5, "nwse-resize");
|
|
3478
3915
|
g.__hasCustomControls = true;
|
|
3479
3916
|
}
|
|
3480
|
-
function setSimpleScaleControls(g) {
|
|
3481
|
-
if (!g.__cropGroup) return;
|
|
3482
|
-
const ct = g.__cropData;
|
|
3483
|
-
if (ct) {
|
|
3484
|
-
updateCoverLayout(g);
|
|
3485
|
-
}
|
|
3486
|
-
g.lockScalingX = true;
|
|
3487
|
-
g.lockScalingY = true;
|
|
3488
|
-
g.setControlsVisibility({
|
|
3489
|
-
mt: false,
|
|
3490
|
-
mb: false,
|
|
3491
|
-
ml: false,
|
|
3492
|
-
mr: false,
|
|
3493
|
-
tl: true,
|
|
3494
|
-
tr: true,
|
|
3495
|
-
bl: true,
|
|
3496
|
-
br: true,
|
|
3497
|
-
mtr: true
|
|
3498
|
-
});
|
|
3499
|
-
g.padding = 0;
|
|
3500
|
-
const makeCornerControl = (key, x, y, cursor) => new fabric.Control({
|
|
3501
|
-
x,
|
|
3502
|
-
y,
|
|
3503
|
-
cursorStyle: cursor,
|
|
3504
|
-
cursorStyleHandler: makeRotatedCursorStyleHandler(key),
|
|
3505
|
-
actionName: "resize",
|
|
3506
|
-
actionHandler: (eventData, transform) => {
|
|
3507
|
-
const t = transform.target;
|
|
3508
|
-
if (t.canvas && t.canvas.__editLockRef) {
|
|
3509
|
-
t.canvas.__editLockRef.current = true;
|
|
3510
|
-
}
|
|
3511
|
-
return resizeFrameFromCornerUniform(eventData, transform);
|
|
3512
|
-
}
|
|
3513
|
-
});
|
|
3514
|
-
g.controls.tl = makeCornerControl("tl", -0.5, -0.5, "nwse-resize");
|
|
3515
|
-
g.controls.tr = makeCornerControl("tr", 0.5, -0.5, "nesw-resize");
|
|
3516
|
-
g.controls.bl = makeCornerControl("bl", -0.5, 0.5, "nesw-resize");
|
|
3517
|
-
g.controls.br = makeCornerControl("br", 0.5, 0.5, "nwse-resize");
|
|
3518
|
-
g.__hasCustomControls = true;
|
|
3519
|
-
g.setCoords();
|
|
3520
|
-
}
|
|
3521
3917
|
async function createMaskedImageElement({
|
|
3522
3918
|
url,
|
|
3523
3919
|
image,
|
|
@@ -3700,9 +4096,405 @@ async function createMaskedImageElement({
|
|
|
3700
4096
|
});
|
|
3701
4097
|
g.setCoords();
|
|
3702
4098
|
updateCoverLayout(g);
|
|
3703
|
-
|
|
4099
|
+
installCanvaMaskControls(g);
|
|
3704
4100
|
return g;
|
|
3705
4101
|
}
|
|
4102
|
+
const CROP_MODE_FLAG = "__inCropMode";
|
|
4103
|
+
const CROP_GHOST_FLAG = "__cropGhost";
|
|
4104
|
+
const CROP_OUTLINE_FLAG = "__cropOutline";
|
|
4105
|
+
const CROP_HANDLERS_FLAG = "__cropHandlers";
|
|
4106
|
+
function isCropGroupInCropMode(g) {
|
|
4107
|
+
return Boolean(g[CROP_MODE_FLAG]);
|
|
4108
|
+
}
|
|
4109
|
+
function clamp(v, min, max) {
|
|
4110
|
+
return Math.max(min, Math.min(max, v));
|
|
4111
|
+
}
|
|
4112
|
+
function syncGhostTransform(g) {
|
|
4113
|
+
const ghost = g[CROP_GHOST_FLAG];
|
|
4114
|
+
const ct = g.__cropData;
|
|
4115
|
+
if (!ghost || !(ct == null ? void 0 : ct._img)) return;
|
|
4116
|
+
const img = ct._img;
|
|
4117
|
+
const finalScale = img.scaleX || 1;
|
|
4118
|
+
const gLeft = g.left ?? 0;
|
|
4119
|
+
const gTop = g.top ?? 0;
|
|
4120
|
+
const angle = g.angle ?? 0;
|
|
4121
|
+
const imgLocalLeft = img.left ?? 0;
|
|
4122
|
+
const imgLocalTop = img.top ?? 0;
|
|
4123
|
+
const rad = fabric.util.degreesToRadians(angle);
|
|
4124
|
+
const cos = Math.cos(rad);
|
|
4125
|
+
const sin = Math.sin(rad);
|
|
4126
|
+
const worldDx = imgLocalLeft * cos - imgLocalTop * sin;
|
|
4127
|
+
const worldDy = imgLocalLeft * sin + imgLocalTop * cos;
|
|
4128
|
+
ghost.set({
|
|
4129
|
+
left: gLeft + worldDx,
|
|
4130
|
+
top: gTop + worldDy,
|
|
4131
|
+
scaleX: finalScale,
|
|
4132
|
+
scaleY: finalScale,
|
|
4133
|
+
angle,
|
|
4134
|
+
originX: "center",
|
|
4135
|
+
originY: "center"
|
|
4136
|
+
});
|
|
4137
|
+
ghost.setCoords();
|
|
4138
|
+
}
|
|
4139
|
+
function syncOutlineTransform(g) {
|
|
4140
|
+
const outline = g[CROP_OUTLINE_FLAG];
|
|
4141
|
+
const ct = g.__cropData;
|
|
4142
|
+
if (!outline || !ct) return;
|
|
4143
|
+
outline.set({
|
|
4144
|
+
left: g.left ?? 0,
|
|
4145
|
+
top: g.top ?? 0,
|
|
4146
|
+
width: ct.frameW,
|
|
4147
|
+
height: ct.frameH,
|
|
4148
|
+
angle: g.angle ?? 0,
|
|
4149
|
+
originX: "center",
|
|
4150
|
+
originY: "center"
|
|
4151
|
+
});
|
|
4152
|
+
if (outline instanceof fabric.Rect) {
|
|
4153
|
+
const minDim = Math.min(ct.frameW, ct.frameH);
|
|
4154
|
+
const rxRatio = ct.rx ?? 0;
|
|
4155
|
+
let rx = rxRatio > 0.5 ? rxRatio : rxRatio * minDim;
|
|
4156
|
+
rx = Math.max(0, Math.min(rx, ct.frameW / 2, ct.frameH / 2));
|
|
4157
|
+
outline.set({ rx, ry: rx });
|
|
4158
|
+
}
|
|
4159
|
+
outline.setCoords();
|
|
4160
|
+
}
|
|
4161
|
+
function installCropModeVisuals(g) {
|
|
4162
|
+
g.setControlsVisibility({
|
|
4163
|
+
mt: false,
|
|
4164
|
+
mb: false,
|
|
4165
|
+
ml: false,
|
|
4166
|
+
mr: false,
|
|
4167
|
+
tl: false,
|
|
4168
|
+
tr: false,
|
|
4169
|
+
bl: false,
|
|
4170
|
+
br: false,
|
|
4171
|
+
mtr: false
|
|
4172
|
+
});
|
|
4173
|
+
g.hasControls = false;
|
|
4174
|
+
g.lockScalingX = true;
|
|
4175
|
+
g.lockScalingY = true;
|
|
4176
|
+
g.lockRotation = true;
|
|
4177
|
+
g.lockMovementX = false;
|
|
4178
|
+
g.lockMovementY = false;
|
|
4179
|
+
g.borderColor = "hsl(256, 80%, 58%)";
|
|
4180
|
+
g.borderDashArray = [4, 4];
|
|
4181
|
+
g.setCoords();
|
|
4182
|
+
}
|
|
4183
|
+
function enterCropMode(g) {
|
|
4184
|
+
const ct = g.__cropData;
|
|
4185
|
+
if (!(ct == null ? void 0 : ct._img)) return false;
|
|
4186
|
+
if (g[CROP_MODE_FLAG]) return false;
|
|
4187
|
+
const canvas = g.canvas;
|
|
4188
|
+
if (!canvas) return false;
|
|
4189
|
+
g[CROP_MODE_FLAG] = true;
|
|
4190
|
+
const innerImg = ct._img;
|
|
4191
|
+
const imgEl = innerImg._element;
|
|
4192
|
+
if (!imgEl) {
|
|
4193
|
+
g[CROP_MODE_FLAG] = false;
|
|
4194
|
+
return false;
|
|
4195
|
+
}
|
|
4196
|
+
const ghost = new fabric.FabricImage(imgEl, {
|
|
4197
|
+
selectable: false,
|
|
4198
|
+
evented: false,
|
|
4199
|
+
hasControls: false,
|
|
4200
|
+
hasBorders: false,
|
|
4201
|
+
opacity: 0.35,
|
|
4202
|
+
originX: "center",
|
|
4203
|
+
originY: "center",
|
|
4204
|
+
objectCaching: false
|
|
4205
|
+
});
|
|
4206
|
+
ghost.excludeFromExport = true;
|
|
4207
|
+
ghost[CROP_GHOST_FLAG] = true;
|
|
4208
|
+
const outlineShape = ct.shape;
|
|
4209
|
+
const outline = outlineShape === "circle" ? new fabric.Ellipse({
|
|
4210
|
+
rx: ct.frameW / 2,
|
|
4211
|
+
ry: ct.frameH / 2,
|
|
4212
|
+
fill: "transparent",
|
|
4213
|
+
stroke: "hsl(256, 80%, 58%)",
|
|
4214
|
+
strokeWidth: 1.5,
|
|
4215
|
+
strokeDashArray: [6, 4],
|
|
4216
|
+
strokeUniform: true,
|
|
4217
|
+
selectable: false,
|
|
4218
|
+
evented: false,
|
|
4219
|
+
hasControls: false,
|
|
4220
|
+
hasBorders: false,
|
|
4221
|
+
originX: "center",
|
|
4222
|
+
originY: "center",
|
|
4223
|
+
objectCaching: false
|
|
4224
|
+
}) : new fabric.Rect({
|
|
4225
|
+
width: ct.frameW,
|
|
4226
|
+
height: ct.frameH,
|
|
4227
|
+
fill: "transparent",
|
|
4228
|
+
stroke: "hsl(256, 80%, 58%)",
|
|
4229
|
+
strokeWidth: 1.5,
|
|
4230
|
+
strokeDashArray: [6, 4],
|
|
4231
|
+
strokeUniform: true,
|
|
4232
|
+
selectable: false,
|
|
4233
|
+
evented: false,
|
|
4234
|
+
hasControls: false,
|
|
4235
|
+
hasBorders: false,
|
|
4236
|
+
originX: "center",
|
|
4237
|
+
originY: "center",
|
|
4238
|
+
objectCaching: false
|
|
4239
|
+
});
|
|
4240
|
+
outline.excludeFromExport = true;
|
|
4241
|
+
outline[CROP_OUTLINE_FLAG] = true;
|
|
4242
|
+
g[CROP_GHOST_FLAG] = ghost;
|
|
4243
|
+
g[CROP_OUTLINE_FLAG] = outline;
|
|
4244
|
+
const groupIndex = canvas._objects.indexOf(g);
|
|
4245
|
+
if (groupIndex >= 0) {
|
|
4246
|
+
canvas.insertAt(groupIndex, ghost);
|
|
4247
|
+
canvas.add(outline);
|
|
4248
|
+
canvas.bringObjectToFront(g);
|
|
4249
|
+
canvas.bringObjectToFront(outline);
|
|
4250
|
+
} else {
|
|
4251
|
+
canvas.add(ghost);
|
|
4252
|
+
canvas.add(g);
|
|
4253
|
+
canvas.add(outline);
|
|
4254
|
+
}
|
|
4255
|
+
syncGhostTransform(g);
|
|
4256
|
+
syncOutlineTransform(g);
|
|
4257
|
+
const groupAnchor = { left: g.left ?? 0, top: g.top ?? 0 };
|
|
4258
|
+
let panRafId = null;
|
|
4259
|
+
let pendingDx = 0;
|
|
4260
|
+
let pendingDy = 0;
|
|
4261
|
+
let lastDragDx = 0;
|
|
4262
|
+
let lastDragDy = 0;
|
|
4263
|
+
const flushPan = () => {
|
|
4264
|
+
panRafId = null;
|
|
4265
|
+
if (pendingDx === 0 && pendingDy === 0) return;
|
|
4266
|
+
const innerCt = g.__cropData;
|
|
4267
|
+
const img = innerCt == null ? void 0 : innerCt._img;
|
|
4268
|
+
if (!img) {
|
|
4269
|
+
pendingDx = 0;
|
|
4270
|
+
pendingDy = 0;
|
|
4271
|
+
return;
|
|
4272
|
+
}
|
|
4273
|
+
const angle = g.angle ?? 0;
|
|
4274
|
+
const rad = fabric.util.degreesToRadians(-angle);
|
|
4275
|
+
const cos = Math.cos(rad);
|
|
4276
|
+
const sin = Math.sin(rad);
|
|
4277
|
+
const localDx = pendingDx * cos - pendingDy * sin;
|
|
4278
|
+
const localDy = pendingDx * sin + pendingDy * cos;
|
|
4279
|
+
pendingDx = 0;
|
|
4280
|
+
pendingDy = 0;
|
|
4281
|
+
const iw = img.width || 1;
|
|
4282
|
+
const ih = img.height || 1;
|
|
4283
|
+
const finalScale = img.scaleX || 1;
|
|
4284
|
+
const dispW = iw * finalScale;
|
|
4285
|
+
const dispH = ih * finalScale;
|
|
4286
|
+
const overflowX = Math.max(0, dispW - innerCt.frameW);
|
|
4287
|
+
const overflowY = Math.max(0, dispH - innerCt.frameH);
|
|
4288
|
+
img._ct = img._ct || { panX: 0.5, panY: 0.5, zoom: 1 };
|
|
4289
|
+
const ct2 = img._ct;
|
|
4290
|
+
if (overflowX > 0) ct2.panX = clamp((ct2.panX ?? 0.5) - localDx / overflowX, 0, 1);
|
|
4291
|
+
if (overflowY > 0) ct2.panY = clamp((ct2.panY ?? 0.5) - localDy / overflowY, 0, 1);
|
|
4292
|
+
updateCoverLayout(g);
|
|
4293
|
+
syncGhostTransform(g);
|
|
4294
|
+
g.setCoords();
|
|
4295
|
+
canvas.requestRenderAll();
|
|
4296
|
+
};
|
|
4297
|
+
const onMoving = (opt) => {
|
|
4298
|
+
const target = opt == null ? void 0 : opt.target;
|
|
4299
|
+
if (target !== g) return;
|
|
4300
|
+
const rawDx = (g.left ?? 0) - groupAnchor.left;
|
|
4301
|
+
const rawDy = (g.top ?? 0) - groupAnchor.top;
|
|
4302
|
+
const dx = rawDx - lastDragDx;
|
|
4303
|
+
const dy = rawDy - lastDragDy;
|
|
4304
|
+
lastDragDx = rawDx;
|
|
4305
|
+
lastDragDy = rawDy;
|
|
4306
|
+
g.left = groupAnchor.left;
|
|
4307
|
+
g.top = groupAnchor.top;
|
|
4308
|
+
if (dx === 0 && dy === 0) return;
|
|
4309
|
+
pendingDx += dx;
|
|
4310
|
+
pendingDy += dy;
|
|
4311
|
+
if (panRafId == null) panRafId = requestAnimationFrame(flushPan);
|
|
4312
|
+
};
|
|
4313
|
+
const onModified = (opt) => {
|
|
4314
|
+
const target = opt == null ? void 0 : opt.target;
|
|
4315
|
+
if (target !== g) return;
|
|
4316
|
+
if (panRafId != null) {
|
|
4317
|
+
cancelAnimationFrame(panRafId);
|
|
4318
|
+
panRafId = null;
|
|
4319
|
+
flushPan();
|
|
4320
|
+
}
|
|
4321
|
+
lastDragDx = 0;
|
|
4322
|
+
lastDragDy = 0;
|
|
4323
|
+
g.left = groupAnchor.left;
|
|
4324
|
+
g.top = groupAnchor.top;
|
|
4325
|
+
g.setCoords();
|
|
4326
|
+
};
|
|
4327
|
+
const onCanvasMouseDown = (opt) => {
|
|
4328
|
+
const target = opt.target;
|
|
4329
|
+
if (target === g || target === ghost || target === outline) return;
|
|
4330
|
+
setTimeout(() => exitCropMode(g, true), 0);
|
|
4331
|
+
};
|
|
4332
|
+
const onDblClick = (opt) => {
|
|
4333
|
+
const target = opt == null ? void 0 : opt.target;
|
|
4334
|
+
if (target !== g && target !== ghost && target !== outline) return;
|
|
4335
|
+
exitCropMode(g, true);
|
|
4336
|
+
};
|
|
4337
|
+
const onKeyDown = (e) => {
|
|
4338
|
+
if (e.key === "Escape" || e.key === "Enter") {
|
|
4339
|
+
e.preventDefault();
|
|
4340
|
+
e.stopPropagation();
|
|
4341
|
+
exitCropMode(g, true);
|
|
4342
|
+
}
|
|
4343
|
+
};
|
|
4344
|
+
let wheelCommitTimer = null;
|
|
4345
|
+
let zoomRafId = null;
|
|
4346
|
+
let pendingZoomDelta = 0;
|
|
4347
|
+
let pendingZoomCenter = null;
|
|
4348
|
+
let lastWheelWasPinch = false;
|
|
4349
|
+
const flushZoom = () => {
|
|
4350
|
+
zoomRafId = null;
|
|
4351
|
+
if (pendingZoomDelta === 0 || !pendingZoomCenter) return;
|
|
4352
|
+
const innerCt = g.__cropData;
|
|
4353
|
+
const img = innerCt == null ? void 0 : innerCt._img;
|
|
4354
|
+
if (!img) {
|
|
4355
|
+
pendingZoomDelta = 0;
|
|
4356
|
+
pendingZoomCenter = null;
|
|
4357
|
+
return;
|
|
4358
|
+
}
|
|
4359
|
+
const factor = lastWheelWasPinch ? 0.012 : 22e-4;
|
|
4360
|
+
const ratio = Math.exp(-pendingZoomDelta * factor);
|
|
4361
|
+
pendingZoomDelta = 0;
|
|
4362
|
+
img._ct = img._ct || { panX: 0.5, panY: 0.5, zoom: 1 };
|
|
4363
|
+
const ct2 = img._ct;
|
|
4364
|
+
const prevZoom = ct2.zoom ?? 1;
|
|
4365
|
+
const nextZoom = clamp(prevZoom * ratio, 1, 8);
|
|
4366
|
+
if (nextZoom === prevZoom) {
|
|
4367
|
+
pendingZoomCenter = null;
|
|
4368
|
+
return;
|
|
4369
|
+
}
|
|
4370
|
+
const angle = g.angle ?? 0;
|
|
4371
|
+
const rad = fabric.util.degreesToRadians(-angle);
|
|
4372
|
+
const cos = Math.cos(rad);
|
|
4373
|
+
const sin = Math.sin(rad);
|
|
4374
|
+
const cx = g.left ?? 0;
|
|
4375
|
+
const cy = g.top ?? 0;
|
|
4376
|
+
const wx = pendingZoomCenter.x - cx;
|
|
4377
|
+
const wy = pendingZoomCenter.y - cy;
|
|
4378
|
+
const localCx = wx * cos - wy * sin;
|
|
4379
|
+
const localCy = wx * sin + wy * cos;
|
|
4380
|
+
const iw = img.width || 1;
|
|
4381
|
+
const ih = img.height || 1;
|
|
4382
|
+
const prevScale = img.scaleX || 1;
|
|
4383
|
+
const prevOverflowX = Math.max(0, iw * prevScale - innerCt.frameW);
|
|
4384
|
+
const prevOverflowY = Math.max(0, ih * prevScale - innerCt.frameH);
|
|
4385
|
+
const prevPanX = ct2.panX ?? 0.5;
|
|
4386
|
+
const prevPanY = ct2.panY ?? 0.5;
|
|
4387
|
+
const prevOffsetX = prevOverflowX > 0 ? -prevOverflowX * (prevPanX - 0.5) : 0;
|
|
4388
|
+
const prevOffsetY = prevOverflowY > 0 ? -prevOverflowY * (prevPanY - 0.5) : 0;
|
|
4389
|
+
const imgPxX = (localCx - prevOffsetX) / prevScale + iw / 2;
|
|
4390
|
+
const imgPxY = (localCy - prevOffsetY) / prevScale + ih / 2;
|
|
4391
|
+
ct2.zoom = nextZoom;
|
|
4392
|
+
updateCoverLayout(g);
|
|
4393
|
+
const newScale = img.scaleX || 1;
|
|
4394
|
+
const newOverflowX = Math.max(0, iw * newScale - innerCt.frameW);
|
|
4395
|
+
const newOverflowY = Math.max(0, ih * newScale - innerCt.frameH);
|
|
4396
|
+
if (newOverflowX > 0) {
|
|
4397
|
+
const desiredOffsetX = localCx - (imgPxX - iw / 2) * newScale;
|
|
4398
|
+
ct2.panX = clamp(0.5 - desiredOffsetX / newOverflowX, 0, 1);
|
|
4399
|
+
} else {
|
|
4400
|
+
ct2.panX = 0.5;
|
|
4401
|
+
}
|
|
4402
|
+
if (newOverflowY > 0) {
|
|
4403
|
+
const desiredOffsetY = localCy - (imgPxY - ih / 2) * newScale;
|
|
4404
|
+
ct2.panY = clamp(0.5 - desiredOffsetY / newOverflowY, 0, 1);
|
|
4405
|
+
} else {
|
|
4406
|
+
ct2.panY = 0.5;
|
|
4407
|
+
}
|
|
4408
|
+
updateCoverLayout(g);
|
|
4409
|
+
syncGhostTransform(g);
|
|
4410
|
+
g.setCoords();
|
|
4411
|
+
canvas.requestRenderAll();
|
|
4412
|
+
if (wheelCommitTimer) clearTimeout(wheelCommitTimer);
|
|
4413
|
+
wheelCommitTimer = setTimeout(() => {
|
|
4414
|
+
canvas.fire("object:modified", { target: g });
|
|
4415
|
+
g.left = groupAnchor.left;
|
|
4416
|
+
g.top = groupAnchor.top;
|
|
4417
|
+
g.setCoords();
|
|
4418
|
+
}, 200);
|
|
4419
|
+
pendingZoomCenter = null;
|
|
4420
|
+
};
|
|
4421
|
+
const onWheel = (e) => {
|
|
4422
|
+
if (!canvas) return;
|
|
4423
|
+
const innerCt = g.__cropData;
|
|
4424
|
+
if (!innerCt) return;
|
|
4425
|
+
const pointer = canvas.getPointer(e, false);
|
|
4426
|
+
const br = g.getBoundingRect();
|
|
4427
|
+
if (pointer.x < br.left || pointer.x > br.left + br.width || pointer.y < br.top || pointer.y > br.top + br.height) return;
|
|
4428
|
+
const img = innerCt._img;
|
|
4429
|
+
if (!img) return;
|
|
4430
|
+
e.preventDefault();
|
|
4431
|
+
e.stopPropagation();
|
|
4432
|
+
pendingZoomDelta += e.deltaY;
|
|
4433
|
+
pendingZoomCenter = pointer;
|
|
4434
|
+
lastWheelWasPinch = !!e.ctrlKey;
|
|
4435
|
+
if (zoomRafId == null) zoomRafId = requestAnimationFrame(flushZoom);
|
|
4436
|
+
};
|
|
4437
|
+
canvas.on("object:moving", onMoving);
|
|
4438
|
+
canvas.on("object:modified", onModified);
|
|
4439
|
+
canvas.on("mouse:down:before", onCanvasMouseDown);
|
|
4440
|
+
canvas.on("mouse:dblclick", onDblClick);
|
|
4441
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
4442
|
+
const upperCanvasEl = canvas.upperCanvasEl;
|
|
4443
|
+
upperCanvasEl == null ? void 0 : upperCanvasEl.addEventListener("wheel", onWheel, { passive: false });
|
|
4444
|
+
g[CROP_HANDLERS_FLAG] = {
|
|
4445
|
+
onMoving,
|
|
4446
|
+
onModified,
|
|
4447
|
+
onCanvasMouseDown,
|
|
4448
|
+
onDblClick,
|
|
4449
|
+
onKeyDown,
|
|
4450
|
+
onWheel,
|
|
4451
|
+
upperCanvasEl
|
|
4452
|
+
};
|
|
4453
|
+
installCropModeVisuals(g);
|
|
4454
|
+
canvas.setActiveObject(g);
|
|
4455
|
+
canvas.requestRenderAll();
|
|
4456
|
+
return true;
|
|
4457
|
+
}
|
|
4458
|
+
function exitCropMode(g, commit = true) {
|
|
4459
|
+
var _a;
|
|
4460
|
+
if (!g[CROP_MODE_FLAG]) return;
|
|
4461
|
+
const canvas = g.canvas;
|
|
4462
|
+
const handlers = g[CROP_HANDLERS_FLAG];
|
|
4463
|
+
if (handlers && canvas) {
|
|
4464
|
+
canvas.off("object:moving", handlers.onMoving);
|
|
4465
|
+
canvas.off("object:modified", handlers.onModified);
|
|
4466
|
+
canvas.off("mouse:down:before", handlers.onCanvasMouseDown);
|
|
4467
|
+
if (handlers.onDblClick) canvas.off("mouse:dblclick", handlers.onDblClick);
|
|
4468
|
+
window.removeEventListener("keydown", handlers.onKeyDown, true);
|
|
4469
|
+
(_a = handlers.upperCanvasEl) == null ? void 0 : _a.removeEventListener("wheel", handlers.onWheel);
|
|
4470
|
+
}
|
|
4471
|
+
g[CROP_HANDLERS_FLAG] = void 0;
|
|
4472
|
+
const ghost = g[CROP_GHOST_FLAG];
|
|
4473
|
+
const outline = g[CROP_OUTLINE_FLAG];
|
|
4474
|
+
if (canvas && ghost) canvas.remove(ghost);
|
|
4475
|
+
if (canvas && outline) canvas.remove(outline);
|
|
4476
|
+
g[CROP_GHOST_FLAG] = void 0;
|
|
4477
|
+
g[CROP_OUTLINE_FLAG] = void 0;
|
|
4478
|
+
g.lockMovementX = false;
|
|
4479
|
+
g.lockMovementY = false;
|
|
4480
|
+
g.lockRotation = false;
|
|
4481
|
+
g.lockScalingX = false;
|
|
4482
|
+
g.lockScalingY = false;
|
|
4483
|
+
g.hasControls = true;
|
|
4484
|
+
g.borderDashArray = void 0;
|
|
4485
|
+
installCanvaMaskControls(g);
|
|
4486
|
+
g.borderColor = void 0;
|
|
4487
|
+
g.cornerColor = void 0;
|
|
4488
|
+
g.cornerStrokeColor = void 0;
|
|
4489
|
+
g.cornerStyle = "rect";
|
|
4490
|
+
g[CROP_MODE_FLAG] = false;
|
|
4491
|
+
g.__cropZoomLastPointer = void 0;
|
|
4492
|
+
g.__lastPointerForCrop = void 0;
|
|
4493
|
+
if (commit && canvas) {
|
|
4494
|
+
canvas.fire("object:modified", { target: g });
|
|
4495
|
+
}
|
|
4496
|
+
canvas == null ? void 0 : canvas.requestRenderAll();
|
|
4497
|
+
}
|
|
3706
4498
|
function getObjectSnapPoints(obj) {
|
|
3707
4499
|
try {
|
|
3708
4500
|
obj.setCoords();
|
|
@@ -5271,13 +6063,7 @@ const PageCanvas = forwardRef(
|
|
|
5271
6063
|
const activeObj = fabricCanvas.getActiveObject();
|
|
5272
6064
|
if (activeObj) applyControlSizeForZoom(fabricCanvas, activeObj);
|
|
5273
6065
|
if (activeObj && !(activeObj instanceof fabric.ActiveSelection) && (((_a = activeObj._ct) == null ? void 0 : _a.isCropGroup) || activeObj.__cropGroup)) {
|
|
5274
|
-
|
|
5275
|
-
const element = elementsRef.current.find((el) => el.id === objId);
|
|
5276
|
-
if (element == null ? void 0 : element.useCropHandles) {
|
|
5277
|
-
installCanvaMaskControls(activeObj);
|
|
5278
|
-
} else {
|
|
5279
|
-
setSimpleScaleControls(activeObj);
|
|
5280
|
-
}
|
|
6066
|
+
installCanvaMaskControls(activeObj);
|
|
5281
6067
|
}
|
|
5282
6068
|
});
|
|
5283
6069
|
fabricCanvas.on("selection:updated", () => {
|
|
@@ -5286,13 +6072,7 @@ const PageCanvas = forwardRef(
|
|
|
5286
6072
|
const activeObj = fabricCanvas.getActiveObject();
|
|
5287
6073
|
if (activeObj) applyControlSizeForZoom(fabricCanvas, activeObj);
|
|
5288
6074
|
if (activeObj && !(activeObj instanceof fabric.ActiveSelection) && (((_a = activeObj._ct) == null ? void 0 : _a.isCropGroup) || activeObj.__cropGroup)) {
|
|
5289
|
-
|
|
5290
|
-
const element = elementsRef.current.find((el) => el.id === objId);
|
|
5291
|
-
if (element == null ? void 0 : element.useCropHandles) {
|
|
5292
|
-
installCanvaMaskControls(activeObj);
|
|
5293
|
-
} else {
|
|
5294
|
-
setSimpleScaleControls(activeObj);
|
|
5295
|
-
}
|
|
6075
|
+
installCanvaMaskControls(activeObj);
|
|
5296
6076
|
}
|
|
5297
6077
|
});
|
|
5298
6078
|
fabricCanvas.on("selection:cleared", () => {
|
|
@@ -5310,6 +6090,8 @@ const PageCanvas = forwardRef(
|
|
|
5310
6090
|
let dragStarted = false;
|
|
5311
6091
|
const markTransforming = (target) => {
|
|
5312
6092
|
if (!target) return;
|
|
6093
|
+
const targetId = getObjectId(target);
|
|
6094
|
+
if (targetId) transformingIdsRef.current.add(targetId);
|
|
5313
6095
|
if (target instanceof fabric.Group) {
|
|
5314
6096
|
target.getObjects().forEach((obj) => {
|
|
5315
6097
|
const id2 = getObjectId(obj);
|
|
@@ -5330,7 +6112,7 @@ const PageCanvas = forwardRef(
|
|
|
5330
6112
|
const clearTransforming = () => {
|
|
5331
6113
|
transformingIdsRef.current.clear();
|
|
5332
6114
|
};
|
|
5333
|
-
const
|
|
6115
|
+
const isCropGroup2 = (o) => {
|
|
5334
6116
|
var _a;
|
|
5335
6117
|
return !!(((_a = o == null ? void 0 : o._ct) == null ? void 0 : _a.isCropGroup) || (o == null ? void 0 : o.__cropGroup));
|
|
5336
6118
|
};
|
|
@@ -5338,9 +6120,9 @@ const PageCanvas = forwardRef(
|
|
|
5338
6120
|
var _a, _b, _c;
|
|
5339
6121
|
const t = opt.target;
|
|
5340
6122
|
if (((_a = opt.e) == null ? void 0 : _a.type) !== "mousedown" && ((_b = opt.e) == null ? void 0 : _b.type) !== "pointerdown" && ((_c = opt.e) == null ? void 0 : _c.type) !== "touchstart") {
|
|
5341
|
-
if (t &&
|
|
6123
|
+
if (t && isCropGroup2(t)) {
|
|
5342
6124
|
fabricCanvas._hoveredTarget = t;
|
|
5343
|
-
} else if ((t == null ? void 0 : t.group) &&
|
|
6125
|
+
} else if ((t == null ? void 0 : t.group) && isCropGroup2(t.group)) {
|
|
5344
6126
|
fabricCanvas._hoveredTarget = t.group;
|
|
5345
6127
|
}
|
|
5346
6128
|
return;
|
|
@@ -5349,7 +6131,7 @@ const PageCanvas = forwardRef(
|
|
|
5349
6131
|
const pointer = fabricCanvas.getPointer(opt.e);
|
|
5350
6132
|
const objects = fabricCanvas.getObjects();
|
|
5351
6133
|
for (const obj of objects) {
|
|
5352
|
-
if (
|
|
6134
|
+
if (isCropGroup2(obj) && obj.containsPoint(pointer)) {
|
|
5353
6135
|
fabricCanvas.setActiveObject(obj);
|
|
5354
6136
|
opt.target = obj;
|
|
5355
6137
|
fabricCanvas._hoveredTarget = obj;
|
|
@@ -5359,13 +6141,13 @@ const PageCanvas = forwardRef(
|
|
|
5359
6141
|
return;
|
|
5360
6142
|
}
|
|
5361
6143
|
const g = t.group;
|
|
5362
|
-
if (g &&
|
|
6144
|
+
if (g && isCropGroup2(g)) {
|
|
5363
6145
|
fabricCanvas.setActiveObject(g);
|
|
5364
6146
|
opt.target = g;
|
|
5365
6147
|
fabricCanvas._hoveredTarget = g;
|
|
5366
6148
|
return;
|
|
5367
6149
|
}
|
|
5368
|
-
if (
|
|
6150
|
+
if (isCropGroup2(t)) {
|
|
5369
6151
|
fabricCanvas.setActiveObject(t);
|
|
5370
6152
|
t.set({
|
|
5371
6153
|
selectable: true,
|
|
@@ -5908,8 +6690,8 @@ const PageCanvas = forwardRef(
|
|
|
5908
6690
|
const h = (active.height ?? 0) * (active.scaleY ?? 1);
|
|
5909
6691
|
const centerX = active.left ?? 0;
|
|
5910
6692
|
const centerY = active.top ?? 0;
|
|
5911
|
-
const groupLeft = centerX - w / 2;
|
|
5912
|
-
const groupTop = centerY - h / 2;
|
|
6693
|
+
const groupLeft = active.originX === "center" ? centerX - w / 2 : centerX;
|
|
6694
|
+
const groupTop = active.originY === "center" ? centerY - h / 2 : centerY;
|
|
5913
6695
|
const storePos = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildren2);
|
|
5914
6696
|
useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top }, { recordHistory: false, skipLayoutRecalc: true });
|
|
5915
6697
|
commitHistory();
|
|
@@ -5942,76 +6724,79 @@ const PageCanvas = forwardRef(
|
|
|
5942
6724
|
const commonAncestor = findCommonAncestorGroup(selectedElementIds, pageChildren2);
|
|
5943
6725
|
const candidateGroup = sameDirectParent ? parentGroups[0] : commonAncestor;
|
|
5944
6726
|
const candidateIsStack = candidateGroup && isStackLayoutMode(candidateGroup.layoutMode);
|
|
5945
|
-
const
|
|
5946
|
-
|
|
5947
|
-
const
|
|
5948
|
-
if (
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
6727
|
+
const allMembersSelected = (() => {
|
|
6728
|
+
if (!candidateGroup) return false;
|
|
6729
|
+
const memberIds = getAllElementIds(candidateGroup.children ?? []);
|
|
6730
|
+
if (memberIds.length === 0) return false;
|
|
6731
|
+
const selectedSet = new Set(selectedElementIds);
|
|
6732
|
+
return memberIds.every((mid) => selectedSet.has(mid));
|
|
6733
|
+
})();
|
|
6734
|
+
const groupToMove = candidateIsStack || allMembersSelected ? candidateGroup : null;
|
|
6735
|
+
if (groupToMove) {
|
|
6736
|
+
const groupAbs = getAbsoluteBounds(groupToMove, pageChildren2);
|
|
6737
|
+
let movedGroupLeft = groupAbs.left;
|
|
6738
|
+
let movedGroupTop = groupAbs.top;
|
|
6739
|
+
if (activeObj) {
|
|
6740
|
+
const selectionRect = activeObj.getBoundingRect();
|
|
6741
|
+
movedGroupLeft = selectionRect.left;
|
|
6742
|
+
movedGroupTop = selectionRect.top;
|
|
6743
|
+
} else if (firstId) {
|
|
6744
|
+
const firstNode = findNodeById(pageChildren2, firstId);
|
|
6745
|
+
if (firstNode) {
|
|
6746
|
+
movedGroupLeft = getAbsoluteBounds(firstNode, pageChildren2).left;
|
|
6747
|
+
movedGroupTop = getAbsoluteBounds(firstNode, pageChildren2).top;
|
|
5961
6748
|
}
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
obj.setCoords();
|
|
5995
|
-
}
|
|
5996
|
-
}
|
|
5997
|
-
if (isActiveSelection && activeObjects.length > 0) {
|
|
5998
|
-
const selection = new fabric.ActiveSelection([...activeObjects], { canvas: fabricCanvas });
|
|
5999
|
-
fabricCanvas.setActiveObject(selection);
|
|
6000
|
-
skipSelectionClearOnDiscardRef.current = false;
|
|
6749
|
+
}
|
|
6750
|
+
const deltaX = movedGroupLeft - groupAbs.left;
|
|
6751
|
+
const deltaY = movedGroupTop - groupAbs.top;
|
|
6752
|
+
const hadScale = isActiveSelection && activeObj && (Math.abs((activeObj.scaleX ?? 1) - 1) > 0.01 || Math.abs((activeObj.scaleY ?? 1) - 1) > 0.01);
|
|
6753
|
+
if (!hadScale && (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1)) {
|
|
6754
|
+
if (typeof console !== "undefined" && console.log) {
|
|
6755
|
+
console.log("[object:modified] plain-groups: moving group id=", groupToMove.id, "newLeft=", (groupToMove.left ?? 0) + deltaX, "newTop=", (groupToMove.top ?? 0) + deltaY);
|
|
6756
|
+
}
|
|
6757
|
+
const { updateNode: updateNodeStore, commitHistory: commitHistoryStore, getCurrentElements } = useEditorStore.getState();
|
|
6758
|
+
const newLeft = (groupToMove.left ?? 0) + deltaX;
|
|
6759
|
+
const newTop = (groupToMove.top ?? 0) + deltaY;
|
|
6760
|
+
updateNodeStore(groupToMove.id, { left: newLeft, top: newTop }, { recordHistory: false, skipLayoutRecalc: true });
|
|
6761
|
+
commitHistoryStore();
|
|
6762
|
+
if (isActiveSelection && activeObj instanceof fabric.ActiveSelection) {
|
|
6763
|
+
skipSelectionClearOnDiscardRef.current = true;
|
|
6764
|
+
fabricCanvas.discardActiveObject();
|
|
6765
|
+
}
|
|
6766
|
+
const stateAfter = useEditorStore.getState();
|
|
6767
|
+
const pageAfter = stateAfter.canvas.pages.find((p) => p.id === pageId);
|
|
6768
|
+
const pageChildrenAfter = (pageAfter == null ? void 0 : pageAfter.children) ?? [];
|
|
6769
|
+
for (const obj of activeObjects) {
|
|
6770
|
+
const objId = getObjectId(obj);
|
|
6771
|
+
if (!objId || objId === "__background__") continue;
|
|
6772
|
+
const node = findNodeById(pageChildrenAfter, objId);
|
|
6773
|
+
if (node) {
|
|
6774
|
+
const abs = getAbsoluteBounds(node, pageChildrenAfter);
|
|
6775
|
+
const w = (obj.width ?? 0) * (obj.scaleX ?? 1);
|
|
6776
|
+
const h = (obj.height ?? 0) * (obj.scaleY ?? 1);
|
|
6777
|
+
const nextLeft = obj.originX === "center" ? abs.left + w / 2 : abs.left;
|
|
6778
|
+
const nextTop = obj.originY === "center" ? abs.top + h / 2 : abs.top;
|
|
6779
|
+
obj.set({ left: nextLeft, top: nextTop });
|
|
6780
|
+
obj.setCoords();
|
|
6001
6781
|
}
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6782
|
+
}
|
|
6783
|
+
if (isActiveSelection && activeObjects.length > 0) {
|
|
6784
|
+
const selection = new fabric.ActiveSelection([...activeObjects], { canvas: fabricCanvas });
|
|
6785
|
+
fabricCanvas.setActiveObject(selection);
|
|
6786
|
+
skipSelectionClearOnDiscardRef.current = false;
|
|
6787
|
+
}
|
|
6788
|
+
fabricCanvas.requestRenderAll();
|
|
6789
|
+
elementsRef.current = getCurrentElements();
|
|
6790
|
+
for (const obj of activeObjects) {
|
|
6791
|
+
const objId = getObjectId(obj);
|
|
6792
|
+
if (objId && objId !== "__background__") {
|
|
6793
|
+
justModifiedIdsRef.current.add(objId);
|
|
6794
|
+
modifiedIdsThisRound.add(objId);
|
|
6010
6795
|
}
|
|
6011
|
-
setTimeout(() => modifiedIdsThisRound.forEach((id) => justModifiedIdsRef.current.delete(id)), 150);
|
|
6012
|
-
unlockEditsSoon();
|
|
6013
|
-
return;
|
|
6014
6796
|
}
|
|
6797
|
+
setTimeout(() => modifiedIdsThisRound.forEach((id) => justModifiedIdsRef.current.delete(id)), 150);
|
|
6798
|
+
unlockEditsSoon();
|
|
6799
|
+
return;
|
|
6015
6800
|
}
|
|
6016
6801
|
}
|
|
6017
6802
|
}
|
|
@@ -6090,9 +6875,13 @@ const PageCanvas = forwardRef(
|
|
|
6090
6875
|
finalScaleX = 1;
|
|
6091
6876
|
finalScaleY = 1;
|
|
6092
6877
|
obj.set({ scaleX: 1, scaleY: 1 });
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6878
|
+
if (!isActiveSelection) {
|
|
6879
|
+
updateCoverLayout(obj);
|
|
6880
|
+
obj.__lastResizeHandle = null;
|
|
6881
|
+
fabricCanvas.setActiveObject(obj);
|
|
6882
|
+
} else {
|
|
6883
|
+
obj.__lastResizeHandle = null;
|
|
6884
|
+
}
|
|
6096
6885
|
}
|
|
6097
6886
|
} else if (obj instanceof fabric.FabricImage) {
|
|
6098
6887
|
if ((sourceElement == null ? void 0 : sourceElement.smartElementType) && sourceElement.smartProps) {
|
|
@@ -6235,6 +7024,13 @@ const PageCanvas = forwardRef(
|
|
|
6235
7024
|
fabricCanvas.on("mouse:dblclick", (e) => {
|
|
6236
7025
|
if (!isActiveRef.current || !allowEditing) return;
|
|
6237
7026
|
let target = e.target;
|
|
7027
|
+
if (target && target instanceof fabric.Group && target.__cropGroup) {
|
|
7028
|
+
const ct = target.__cropData;
|
|
7029
|
+
if ((ct == null ? void 0 : ct._img) && !isCropGroupInCropMode(target)) {
|
|
7030
|
+
enterCropMode(target);
|
|
7031
|
+
return;
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
6238
7034
|
if (!target || !(target instanceof fabric.Textbox)) {
|
|
6239
7035
|
const active = fabricCanvas.getActiveObject();
|
|
6240
7036
|
if (active instanceof fabric.Textbox) target = active;
|
|
@@ -6427,9 +7223,9 @@ const PageCanvas = forwardRef(
|
|
|
6427
7223
|
const activeId = activeBeforeSync ? getObjectId(activeBeforeSync) : null;
|
|
6428
7224
|
const isActiveTextBeingEdited = activeId && editingTextIdRef.current === activeId && activeBeforeSync instanceof fabric.Textbox;
|
|
6429
7225
|
if (!skipRestoreSelection && activeBeforeSync && activeStillOnCanvas && !isActiveTextBeingEdited) {
|
|
6430
|
-
const
|
|
7226
|
+
const isCropGroup2 = ((_d = activeBeforeSync._ct) == null ? void 0 : _d.isCropGroup) || activeBeforeSync.__cropGroup;
|
|
6431
7227
|
const isSectionGroup = activeId && sectionGroupIds.has(activeId);
|
|
6432
|
-
if ((
|
|
7228
|
+
if ((isCropGroup2 || !shouldSkipUpdates2) && !isSectionGroup) {
|
|
6433
7229
|
fc.setActiveObject(activeBeforeSync);
|
|
6434
7230
|
}
|
|
6435
7231
|
}
|
|
@@ -6550,11 +7346,12 @@ const PageCanvas = forwardRef(
|
|
|
6550
7346
|
const sourceUrlChanged = currentUrlNormalized !== storedUrlNormalized;
|
|
6551
7347
|
const needsReload = sourceUrlChanged || colorMapChanged;
|
|
6552
7348
|
const hasUrl = currentUrlNormalized !== "";
|
|
6553
|
-
const
|
|
6554
|
-
const
|
|
7349
|
+
const isCropGroup2 = existingObj instanceof fabric.Group && existingObj.__cropGroup;
|
|
7350
|
+
const isPlaceholderGroup = isEmptyImagePlaceholderGroup(existingObj);
|
|
7351
|
+
const isPlaceholder = isPlaceholderGroup || !(existingObj instanceof fabric.FabricImage) || existingObj instanceof fabric.Group && !isCropGroup2;
|
|
6555
7352
|
const hadUrlBefore = storedImageUrl && String(storedImageUrl).trim() !== "";
|
|
6556
7353
|
if (!hasUrl && hadUrlBefore) {
|
|
6557
|
-
const placeholder =
|
|
7354
|
+
const placeholder = isCropGroup2 ? createImagePlaceholderForGroup(element) : createImagePlaceholder(element);
|
|
6558
7355
|
setObjectData(placeholder, element.id);
|
|
6559
7356
|
placeholder.__imageSrc = "";
|
|
6560
7357
|
const idx = fc.getObjects().indexOf(existingObj);
|
|
@@ -6570,13 +7367,13 @@ const PageCanvas = forwardRef(
|
|
|
6570
7367
|
const imageFitForReplace = element.imageFit || ((_e = element.style) == null ? void 0 : _e.imageFit) || "cover";
|
|
6571
7368
|
const clipShapeForReplace = element.clipShape ?? ((_f = element.style) == null ? void 0 : _f.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
|
|
6572
7369
|
const needCropGroupForElement = imageFitForReplace !== "fill" || clipShapeForReplace && clipShapeForReplace !== "none";
|
|
6573
|
-
const plainImageNeedsCropGroup = hasUrl && !
|
|
7370
|
+
const plainImageNeedsCropGroup = hasUrl && !isCropGroup2 && existingObj instanceof fabric.FabricImage && needCropGroupForElement;
|
|
6574
7371
|
if (hasUrl && (needsReload || isPlaceholder || plainImageNeedsCropGroup)) {
|
|
6575
7372
|
if (needsReload && !isBeingTransformed && (!wasJustModified || sourceUrlChanged)) {
|
|
6576
7373
|
loadImageAsync(element, existingObj, fc);
|
|
6577
7374
|
} else if (plainImageNeedsCropGroup) {
|
|
6578
7375
|
loadImageAsync(element, existingObj, fc);
|
|
6579
|
-
} else if (!needsReload &&
|
|
7376
|
+
} else if (!needsReload && isCropGroup2) {
|
|
6580
7377
|
const ct = existingObj.__cropData;
|
|
6581
7378
|
if (ct) {
|
|
6582
7379
|
const resolvedCrop = pageTree.length > 0 ? getNodeBounds(element, pageTree) : { width: typeof element.width === "number" ? element.width : 200, height: typeof element.height === "number" ? element.height : 50 };
|
|
@@ -6622,6 +7419,22 @@ const PageCanvas = forwardRef(
|
|
|
6622
7419
|
existingObj.set({ opacity: clampedOpacity });
|
|
6623
7420
|
existingObj.set({ width: ct.frameW, height: ct.frameH });
|
|
6624
7421
|
existingObj.set({ backgroundColor: "transparent" });
|
|
7422
|
+
const liveMask = existingObj.clipPath;
|
|
7423
|
+
const liveMaskUrl = existingObj.__svgMaskUrl;
|
|
7424
|
+
const liveMaskType = existingObj.__svgMaskType;
|
|
7425
|
+
const schemaMaskUrl = element.maskSvgUrl;
|
|
7426
|
+
const schemaMaskType = element.maskType || "shape";
|
|
7427
|
+
if (liveMask && liveMask.__svgMask && !schemaMaskUrl) {
|
|
7428
|
+
existingObj.clipPath = void 0;
|
|
7429
|
+
delete existingObj.__svgMaskUrl;
|
|
7430
|
+
delete existingObj.__svgMaskType;
|
|
7431
|
+
existingObj.dirty = true;
|
|
7432
|
+
} else if (schemaMaskUrl && (schemaMaskUrl !== liveMaskUrl || schemaMaskType !== liveMaskType)) {
|
|
7433
|
+
Promise.resolve().then(() => svgMaskApply).then(({ applyMaskToCropGroup: applyMaskToCropGroup2 }) => {
|
|
7434
|
+
applyMaskToCropGroup2(existingObj, schemaMaskUrl, schemaMaskType).catch(() => {
|
|
7435
|
+
});
|
|
7436
|
+
});
|
|
7437
|
+
}
|
|
6625
7438
|
if (shapeChanged) {
|
|
6626
7439
|
const needsNewClipPath = !existingObj.clipPath || newShape === "circle" && !(existingObj.clipPath instanceof fabric.Ellipse) || newShape !== "circle" && !(existingObj.clipPath instanceof fabric.Rect);
|
|
6627
7440
|
if (needsNewClipPath) {
|
|
@@ -6658,11 +7471,7 @@ const PageCanvas = forwardRef(
|
|
|
6658
7471
|
}
|
|
6659
7472
|
updateCoverLayout(existingObj);
|
|
6660
7473
|
if (allowEditing) {
|
|
6661
|
-
|
|
6662
|
-
installCanvaMaskControls(existingObj);
|
|
6663
|
-
} else {
|
|
6664
|
-
setSimpleScaleControls(existingObj);
|
|
6665
|
-
}
|
|
7474
|
+
installCanvaMaskControls(existingObj);
|
|
6666
7475
|
} else {
|
|
6667
7476
|
existingObj.set({
|
|
6668
7477
|
hasControls: false,
|
|
@@ -6711,6 +7520,45 @@ const PageCanvas = forwardRef(
|
|
|
6711
7520
|
}
|
|
6712
7521
|
placeholder.__imageSrc = void 0;
|
|
6713
7522
|
} else if (!isBeingTransformed && !isBeingTextEdited && !shouldSkipUpdates2) {
|
|
7523
|
+
if (isPlaceholderGroup) {
|
|
7524
|
+
const storePosImg = pageChildren ? (() => {
|
|
7525
|
+
const node = findNodeById(pageChildren, element.id);
|
|
7526
|
+
return node ? getAbsoluteBounds(node, pageChildren) : { left: element.left ?? 0, top: element.top ?? 0 };
|
|
7527
|
+
})() : { left: element.left ?? 0, top: element.top ?? 0 };
|
|
7528
|
+
const resolvedSizeImg = (pageChildren == null ? void 0 : pageChildren.length) ? getNodeBounds(element, pageChildren) : { width: typeof element.width === "number" ? element.width : 200, height: typeof element.height === "number" ? element.height : 50 };
|
|
7529
|
+
const hasExplicitSize = typeof element.width === "number" && Number.isFinite(element.width) && element.width > 0 && typeof element.height === "number" && Number.isFinite(element.height) && element.height > 0;
|
|
7530
|
+
const minVisiblePlaceholder = hasExplicitSize ? 1 : 20;
|
|
7531
|
+
const nextWidth = Math.max(minVisiblePlaceholder, Number(resolvedSizeImg.width) || 200);
|
|
7532
|
+
const nextHeight = Math.max(minVisiblePlaceholder, Number(resolvedSizeImg.height) || 50);
|
|
7533
|
+
const isDynamicField = dynamicFieldIds.includes(element.id);
|
|
7534
|
+
const canBeEvented = isEditorMode || isPreviewMode && isDynamicField;
|
|
7535
|
+
updateEmptyPlaceholderLayout(existingObj, nextWidth, nextHeight);
|
|
7536
|
+
existingObj.set({
|
|
7537
|
+
left: storePosImg.left + nextWidth / 2,
|
|
7538
|
+
top: storePosImg.top + nextHeight / 2,
|
|
7539
|
+
originX: "center",
|
|
7540
|
+
originY: "center",
|
|
7541
|
+
angle: element.angle ?? 0,
|
|
7542
|
+
opacity: isHidden ? 0 : element.opacity ?? 1,
|
|
7543
|
+
flipX: element.flipX ?? false,
|
|
7544
|
+
flipY: element.flipY ?? false,
|
|
7545
|
+
selectable: allowSelection && !isHidden,
|
|
7546
|
+
evented: canBeEvented && !isHidden,
|
|
7547
|
+
hasControls: allowEditing && !isHidden,
|
|
7548
|
+
hasBorders: allowEditing && !isHidden,
|
|
7549
|
+
interactive: allowEditing && !isHidden,
|
|
7550
|
+
subTargetCheck: false,
|
|
7551
|
+
lockMovementX: !allowSelection,
|
|
7552
|
+
lockMovementY: !allowSelection,
|
|
7553
|
+
hoverCursor: isDynamicField && isPreviewMode ? "pointer" : void 0
|
|
7554
|
+
});
|
|
7555
|
+
if (allowEditing) {
|
|
7556
|
+
installCanvaMaskControls(existingObj);
|
|
7557
|
+
}
|
|
7558
|
+
existingObj.setCoords();
|
|
7559
|
+
fc.requestRenderAll();
|
|
7560
|
+
continue;
|
|
7561
|
+
}
|
|
6714
7562
|
if (existingObj instanceof fabric.Group && existingObj.__cropGroup) {
|
|
6715
7563
|
existingObj.set({
|
|
6716
7564
|
flipX: element.flipX ?? false,
|
|
@@ -6885,7 +7733,14 @@ const PageCanvas = forwardRef(
|
|
|
6885
7733
|
const node = findNodeById(pageTree, element.id);
|
|
6886
7734
|
return node ? getAbsoluteBounds(node, pageTree) : { left: element.left ?? 0, top: element.top ?? 0 };
|
|
6887
7735
|
})() : { left: element.left ?? 0, top: element.top ?? 0 };
|
|
6888
|
-
placeholder.
|
|
7736
|
+
const placeholderWidth = Number((placeholder.width ?? 0) * (placeholder.scaleX ?? 1));
|
|
7737
|
+
const placeholderHeight = Number((placeholder.height ?? 0) * (placeholder.scaleY ?? 1));
|
|
7738
|
+
placeholder.set({
|
|
7739
|
+
left: absPosImg.left + placeholderWidth / 2,
|
|
7740
|
+
top: absPosImg.top + placeholderHeight / 2,
|
|
7741
|
+
originX: "center",
|
|
7742
|
+
originY: "center"
|
|
7743
|
+
});
|
|
6889
7744
|
placeholder.setCoords();
|
|
6890
7745
|
const imageUrl = element.src || element.imageUrl;
|
|
6891
7746
|
placeholder.__imageSrc = imageUrl;
|
|
@@ -6978,8 +7833,8 @@ const PageCanvas = forwardRef(
|
|
|
6978
7833
|
isRebuildingRef.current = false;
|
|
6979
7834
|
fc.requestRenderAll();
|
|
6980
7835
|
if (activeBeforeSync && fc.getObjects().includes(activeBeforeSync)) {
|
|
6981
|
-
const
|
|
6982
|
-
if (
|
|
7836
|
+
const isCropGroup2 = ((_i = activeBeforeSync._ct) == null ? void 0 : _i.isCropGroup) || activeBeforeSync.__cropGroup;
|
|
7837
|
+
if (isCropGroup2) {
|
|
6983
7838
|
fc.setActiveObject(activeBeforeSync);
|
|
6984
7839
|
fc.requestRenderAll();
|
|
6985
7840
|
}
|
|
@@ -7406,11 +8261,7 @@ const PageCanvas = forwardRef(
|
|
|
7406
8261
|
obj.clipPath.dirty = true;
|
|
7407
8262
|
obj.clipPath.setCoords();
|
|
7408
8263
|
}
|
|
7409
|
-
|
|
7410
|
-
installCanvaMaskControls(obj);
|
|
7411
|
-
} else {
|
|
7412
|
-
setSimpleScaleControls(obj);
|
|
7413
|
-
}
|
|
8264
|
+
installCanvaMaskControls(obj);
|
|
7414
8265
|
obj.set({
|
|
7415
8266
|
selectable: true,
|
|
7416
8267
|
evented: true,
|
|
@@ -8185,11 +9036,7 @@ const PageCanvas = forwardRef(
|
|
|
8185
9036
|
evented: canBeEvented && !isHidden
|
|
8186
9037
|
});
|
|
8187
9038
|
} else {
|
|
8188
|
-
|
|
8189
|
-
installCanvaMaskControls(cropGroup);
|
|
8190
|
-
} else {
|
|
8191
|
-
setSimpleScaleControls(cropGroup);
|
|
8192
|
-
}
|
|
9039
|
+
installCanvaMaskControls(cropGroup);
|
|
8193
9040
|
}
|
|
8194
9041
|
const cropImg = (_l = cropGroup.__cropData) == null ? void 0 : _l._img;
|
|
8195
9042
|
if (cropImg) {
|
|
@@ -8209,6 +9056,16 @@ const PageCanvas = forwardRef(
|
|
|
8209
9056
|
useEditorStore.getState().updateElement(element.id, { imageNaturalWidth: nw, imageNaturalHeight: nh }, { recordHistory: false });
|
|
8210
9057
|
}
|
|
8211
9058
|
}
|
|
9059
|
+
const persistedMaskUrl = element.maskSvgUrl;
|
|
9060
|
+
if (persistedMaskUrl && typeof persistedMaskUrl === "string") {
|
|
9061
|
+
try {
|
|
9062
|
+
const { applyMaskToCropGroup: applyMaskToCropGroup2 } = await Promise.resolve().then(() => svgMaskApply);
|
|
9063
|
+
const persistedMaskType = element.maskType || "shape";
|
|
9064
|
+
await applyMaskToCropGroup2(cropGroup, persistedMaskUrl, persistedMaskType);
|
|
9065
|
+
} catch (err) {
|
|
9066
|
+
console.warn("[mask] failed to re-apply persisted mask", err);
|
|
9067
|
+
}
|
|
9068
|
+
}
|
|
8212
9069
|
finalObject = cropGroup;
|
|
8213
9070
|
} else {
|
|
8214
9071
|
const isHiddenFill = !element.visible;
|
|
@@ -10497,11 +11354,11 @@ async function resolveTemplateData(options) {
|
|
|
10497
11354
|
}
|
|
10498
11355
|
async function resolveFromForm(options) {
|
|
10499
11356
|
var _a, _b, _c;
|
|
10500
|
-
const { templateId, formSchemaId, sectionState, themeId, supabaseUrl, supabaseAnonKey } = options;
|
|
11357
|
+
const { templateId, formSchemaId, sectionState, themeId, supabaseUrl, supabaseAnonKey, prefetched } = options;
|
|
10501
11358
|
const [templateRow, formSchemaRow, defaultForm] = await Promise.all([
|
|
10502
|
-
fetchRow(supabaseUrl, supabaseAnonKey, "templates", templateId),
|
|
10503
|
-
fetchRow(supabaseUrl, supabaseAnonKey, "form_schemas", formSchemaId),
|
|
10504
|
-
fetchDefaultForm(supabaseUrl, supabaseAnonKey, formSchemaId)
|
|
11359
|
+
(prefetched == null ? void 0 : prefetched.templateRow) ? Promise.resolve(prefetched.templateRow) : fetchRow(supabaseUrl, supabaseAnonKey, "templates", templateId),
|
|
11360
|
+
(prefetched == null ? void 0 : prefetched.formSchemaRow) !== void 0 ? Promise.resolve(prefetched.formSchemaRow) : fetchRow(supabaseUrl, supabaseAnonKey, "form_schemas", formSchemaId),
|
|
11361
|
+
(prefetched == null ? void 0 : prefetched.defaultForm) !== void 0 ? Promise.resolve(prefetched.defaultForm) : fetchDefaultForm(supabaseUrl, supabaseAnonKey, formSchemaId)
|
|
10505
11362
|
]);
|
|
10506
11363
|
const templateConfig = templateRow.config;
|
|
10507
11364
|
const templateFormSchema = templateRow.form_schema;
|
|
@@ -11114,14 +11971,15 @@ class PixldocsRenderer {
|
|
|
11114
11971
|
* This is the primary external API for the package.
|
|
11115
11972
|
*/
|
|
11116
11973
|
async renderFromForm(options) {
|
|
11117
|
-
const { templateId, formSchemaId, sectionState, themeId, watermark, ...renderOpts } = options;
|
|
11974
|
+
const { templateId, formSchemaId, sectionState, themeId, watermark, prefetched, ...renderOpts } = options;
|
|
11118
11975
|
const resolved = await resolveFromForm({
|
|
11119
11976
|
templateId,
|
|
11120
11977
|
formSchemaId,
|
|
11121
11978
|
sectionState,
|
|
11122
11979
|
themeId,
|
|
11123
11980
|
supabaseUrl: this.config.supabaseUrl,
|
|
11124
|
-
supabaseAnonKey: this.config.supabaseAnonKey
|
|
11981
|
+
supabaseAnonKey: this.config.supabaseAnonKey,
|
|
11982
|
+
prefetched
|
|
11125
11983
|
});
|
|
11126
11984
|
const shouldWatermark = watermark ?? resolved.price > 0;
|
|
11127
11985
|
let configToRender = resolved.config;
|
|
@@ -11166,14 +12024,15 @@ class PixldocsRenderer {
|
|
|
11166
12024
|
* Resolve from V2 sectionState and return SVGs for all pages (for server vector PDF).
|
|
11167
12025
|
*/
|
|
11168
12026
|
async renderSvgsFromForm(options) {
|
|
11169
|
-
const { templateId, formSchemaId, sectionState, themeId, watermark } = options;
|
|
12027
|
+
const { templateId, formSchemaId, sectionState, themeId, watermark, prefetched } = options;
|
|
11170
12028
|
const resolved = await resolveFromForm({
|
|
11171
12029
|
templateId,
|
|
11172
12030
|
formSchemaId,
|
|
11173
12031
|
sectionState,
|
|
11174
12032
|
themeId,
|
|
11175
12033
|
supabaseUrl: this.config.supabaseUrl,
|
|
11176
|
-
supabaseAnonKey: this.config.supabaseAnonKey
|
|
12034
|
+
supabaseAnonKey: this.config.supabaseAnonKey,
|
|
12035
|
+
prefetched
|
|
11177
12036
|
});
|
|
11178
12037
|
const shouldWatermark = watermark ?? resolved.price > 0;
|
|
11179
12038
|
let configToRender = resolved.config;
|
|
@@ -11197,14 +12056,15 @@ class PixldocsRenderer {
|
|
|
11197
12056
|
* This is the primary PDF export API — mirrors renderFromForm() but returns a PDF.
|
|
11198
12057
|
*/
|
|
11199
12058
|
async renderPdfFromForm(options) {
|
|
11200
|
-
const { templateId, formSchemaId, sectionState, themeId, watermark, title, fontBaseUrl } = options;
|
|
12059
|
+
const { templateId, formSchemaId, sectionState, themeId, watermark, prefetched, title, fontBaseUrl } = options;
|
|
11201
12060
|
const resolved = await resolveFromForm({
|
|
11202
12061
|
templateId,
|
|
11203
12062
|
formSchemaId,
|
|
11204
12063
|
sectionState,
|
|
11205
12064
|
themeId,
|
|
11206
12065
|
supabaseUrl: this.config.supabaseUrl,
|
|
11207
|
-
supabaseAnonKey: this.config.supabaseAnonKey
|
|
12066
|
+
supabaseAnonKey: this.config.supabaseAnonKey,
|
|
12067
|
+
prefetched
|
|
11208
12068
|
});
|
|
11209
12069
|
const shouldWatermark = watermark ?? resolved.price > 0;
|
|
11210
12070
|
let configToRender = resolved.config;
|