@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.cjs
CHANGED
|
@@ -2951,12 +2951,117 @@ async function normalizeSvgImageDimensions(fabricImage, imageUrl, sourceFormat)
|
|
|
2951
2951
|
fabricImage.setCoords();
|
|
2952
2952
|
}
|
|
2953
2953
|
}
|
|
2954
|
+
const EMPTY_IMAGE_PLACEHOLDER_URL = "/image-placeholder.png";
|
|
2955
|
+
let placeholderTileImage = null;
|
|
2956
|
+
let placeholderTilePromise = null;
|
|
2957
|
+
function loadPlaceholderTile() {
|
|
2958
|
+
if (placeholderTileImage) return Promise.resolve(placeholderTileImage);
|
|
2959
|
+
if (placeholderTilePromise) return placeholderTilePromise;
|
|
2960
|
+
placeholderTilePromise = new Promise((resolve, reject) => {
|
|
2961
|
+
const img = new Image();
|
|
2962
|
+
img.crossOrigin = "anonymous";
|
|
2963
|
+
img.onload = () => {
|
|
2964
|
+
placeholderTileImage = img;
|
|
2965
|
+
resolve(img);
|
|
2966
|
+
};
|
|
2967
|
+
img.onerror = (e) => {
|
|
2968
|
+
placeholderTilePromise = null;
|
|
2969
|
+
reject(e);
|
|
2970
|
+
};
|
|
2971
|
+
img.src = EMPTY_IMAGE_PLACEHOLDER_URL;
|
|
2972
|
+
});
|
|
2973
|
+
return placeholderTilePromise;
|
|
2974
|
+
}
|
|
2975
|
+
function isEmptyImagePlaceholderGroup(obj) {
|
|
2976
|
+
return obj instanceof fabric__namespace.Group && Boolean(obj.__emptyImagePlaceholder);
|
|
2977
|
+
}
|
|
2978
|
+
function stabilizePlaceholderGroup(group, width, height) {
|
|
2979
|
+
group.set({ width, height, scaleX: 1, scaleY: 1 });
|
|
2980
|
+
if (group.layoutManager) {
|
|
2981
|
+
if (typeof fabric__namespace.FixedLayout === "function") {
|
|
2982
|
+
group.layoutManager.strategy = new fabric__namespace.FixedLayout();
|
|
2983
|
+
} else {
|
|
2984
|
+
group.layoutManager.performLayout = () => {
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
group.setCoords();
|
|
2989
|
+
}
|
|
2990
|
+
function attachEmptyPlaceholderImage(group, width, height) {
|
|
2991
|
+
loadPlaceholderTile().then((htmlImg) => {
|
|
2992
|
+
var _a;
|
|
2993
|
+
const objects = group._objects;
|
|
2994
|
+
const bgRect = objects == null ? void 0 : objects.find((obj) => obj.__isPlaceholderFrame);
|
|
2995
|
+
if (!bgRect) return;
|
|
2996
|
+
const pattern = new fabric__namespace.Pattern({
|
|
2997
|
+
source: htmlImg,
|
|
2998
|
+
repeat: "repeat"
|
|
2999
|
+
});
|
|
3000
|
+
pattern.__isPlaceholderPattern = true;
|
|
3001
|
+
bgRect.set({ fill: pattern });
|
|
3002
|
+
bgRect.dirty = true;
|
|
3003
|
+
if (!group.clipPath) {
|
|
3004
|
+
const clip = new fabric__namespace.Rect({
|
|
3005
|
+
width,
|
|
3006
|
+
height,
|
|
3007
|
+
left: 0,
|
|
3008
|
+
top: 0,
|
|
3009
|
+
originX: "center",
|
|
3010
|
+
originY: "center",
|
|
3011
|
+
selectable: false,
|
|
3012
|
+
evented: false
|
|
3013
|
+
});
|
|
3014
|
+
clip.absolutePositioned = false;
|
|
3015
|
+
clip.excludeFromExport = true;
|
|
3016
|
+
group.clipPath = clip;
|
|
3017
|
+
}
|
|
3018
|
+
stabilizePlaceholderGroup(group, width, height);
|
|
3019
|
+
group.dirty = true;
|
|
3020
|
+
(_a = group.canvas) == null ? void 0 : _a.requestRenderAll();
|
|
3021
|
+
}).catch(() => {
|
|
3022
|
+
});
|
|
3023
|
+
}
|
|
3024
|
+
function updateEmptyPlaceholderLayout(group, width, height) {
|
|
3025
|
+
const objects = group._objects;
|
|
3026
|
+
const backgroundRect = objects == null ? void 0 : objects.find((obj) => obj.__isPlaceholderFrame);
|
|
3027
|
+
const cropData = group.__cropData;
|
|
3028
|
+
if (cropData) {
|
|
3029
|
+
cropData.frameW = width;
|
|
3030
|
+
cropData.frameH = height;
|
|
3031
|
+
}
|
|
3032
|
+
backgroundRect == null ? void 0 : backgroundRect.set({
|
|
3033
|
+
width,
|
|
3034
|
+
height,
|
|
3035
|
+
left: 0,
|
|
3036
|
+
top: 0,
|
|
3037
|
+
originX: "center",
|
|
3038
|
+
originY: "center"
|
|
3039
|
+
});
|
|
3040
|
+
if (backgroundRect) backgroundRect.dirty = true;
|
|
3041
|
+
if (backgroundRect && !(backgroundRect.fill instanceof fabric__namespace.Pattern)) {
|
|
3042
|
+
attachEmptyPlaceholderImage(group, width, height);
|
|
3043
|
+
}
|
|
3044
|
+
if (group.clipPath && (group.clipPath instanceof fabric__namespace.Rect || group.clipPath instanceof fabric__namespace.Ellipse)) {
|
|
3045
|
+
group.clipPath.set({
|
|
3046
|
+
left: 0,
|
|
3047
|
+
top: 0,
|
|
3048
|
+
originX: "center",
|
|
3049
|
+
originY: "center",
|
|
3050
|
+
...group.clipPath instanceof fabric__namespace.Ellipse ? { rx: width / 2, ry: height / 2 } : { width, height }
|
|
3051
|
+
});
|
|
3052
|
+
group.clipPath.setCoords();
|
|
3053
|
+
group.clipPath.dirty = true;
|
|
3054
|
+
}
|
|
3055
|
+
stabilizePlaceholderGroup(group, width, height);
|
|
3056
|
+
group.dirty = true;
|
|
3057
|
+
}
|
|
2954
3058
|
function createImagePlaceholder(element) {
|
|
2955
3059
|
const visualWidth = Number(element.width) * (element.scaleX ?? 1);
|
|
2956
3060
|
const visualHeight = Number(element.height) * (element.scaleY ?? 1);
|
|
3061
|
+
const hasImage = !!(element.src || element.imageUrl);
|
|
2957
3062
|
const bgRect = new fabric__namespace.Rect({
|
|
2958
|
-
originX: "
|
|
2959
|
-
originY: "
|
|
3063
|
+
originX: "center",
|
|
3064
|
+
originY: "center",
|
|
2960
3065
|
left: 0,
|
|
2961
3066
|
top: 0,
|
|
2962
3067
|
width: visualWidth,
|
|
@@ -2967,56 +3072,65 @@ function createImagePlaceholder(element) {
|
|
|
2967
3072
|
rx: element.clipShape === "rounded" ? element.rx || 8 : 0,
|
|
2968
3073
|
ry: element.clipShape === "rounded" ? element.ry || 8 : 0
|
|
2969
3074
|
});
|
|
3075
|
+
bgRect.__isPlaceholderFrame = true;
|
|
2970
3076
|
const group = new fabric__namespace.Group([bgRect], {
|
|
2971
|
-
left: element.left,
|
|
2972
|
-
top: element.top,
|
|
2973
|
-
originX: "left",
|
|
2974
|
-
originY: "top",
|
|
2975
|
-
selectable: element.selectable,
|
|
2976
|
-
evented: element.evented
|
|
2977
|
-
});
|
|
2978
|
-
return group;
|
|
2979
|
-
}
|
|
2980
|
-
function createImagePlaceholderForGroup(element) {
|
|
2981
|
-
const frameWidth = Number(element.width) * (element.scaleX ?? 1);
|
|
2982
|
-
const frameHeight = Number(element.height) * (element.scaleY ?? 1);
|
|
2983
|
-
const bgRect = new fabric__namespace.Rect({
|
|
2984
|
-
originX: "left",
|
|
2985
|
-
originY: "top",
|
|
2986
|
-
left: 0,
|
|
2987
|
-
top: 0,
|
|
2988
|
-
width: frameWidth,
|
|
2989
|
-
height: frameHeight,
|
|
2990
|
-
fill: "transparent",
|
|
2991
|
-
stroke: "transparent",
|
|
2992
|
-
strokeWidth: 0,
|
|
2993
|
-
rx: element.clipShape === "rounded" ? element.rx || 8 : 0,
|
|
2994
|
-
ry: element.clipShape === "rounded" ? element.ry || 8 : 0
|
|
2995
|
-
});
|
|
2996
|
-
const group = new fabric__namespace.Group([bgRect], {
|
|
2997
|
-
left: element.left,
|
|
2998
|
-
top: element.top,
|
|
3077
|
+
left: (element.left ?? 0) + visualWidth / 2,
|
|
3078
|
+
top: (element.top ?? 0) + visualHeight / 2,
|
|
2999
3079
|
originX: "center",
|
|
3000
3080
|
originY: "center",
|
|
3001
|
-
width:
|
|
3002
|
-
height:
|
|
3081
|
+
width: visualWidth,
|
|
3082
|
+
height: visualHeight,
|
|
3003
3083
|
scaleX: 1,
|
|
3004
3084
|
scaleY: 1,
|
|
3005
3085
|
angle: element.angle ?? 0,
|
|
3006
3086
|
opacity: element.opacity ?? 1,
|
|
3007
3087
|
flipX: element.flipX ?? false,
|
|
3008
|
-
flipY: element.flipY ?? false
|
|
3088
|
+
flipY: element.flipY ?? false,
|
|
3089
|
+
selectable: element.selectable,
|
|
3090
|
+
evented: element.evented,
|
|
3091
|
+
hasControls: true,
|
|
3092
|
+
hasBorders: true,
|
|
3093
|
+
interactive: true,
|
|
3094
|
+
subTargetCheck: false,
|
|
3095
|
+
objectCaching: false
|
|
3009
3096
|
});
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3097
|
+
group.__emptyImagePlaceholder = true;
|
|
3098
|
+
group.__imageSrc = "";
|
|
3099
|
+
group.__cropData = {
|
|
3100
|
+
shape: element.clipShape === "circle" ? "circle" : element.clipShape === "rounded" ? "roundRect" : "rect",
|
|
3101
|
+
rx: element.clipShape === "rounded" ? element.rx || 0.1 : 0,
|
|
3102
|
+
frameW: visualWidth,
|
|
3103
|
+
frameH: visualHeight,
|
|
3104
|
+
fit: "cover",
|
|
3105
|
+
_img: null,
|
|
3106
|
+
_border: null,
|
|
3107
|
+
_placeholderFrame: bgRect
|
|
3108
|
+
};
|
|
3109
|
+
group._ct = group._ct || {};
|
|
3110
|
+
group._ct.isCropGroup = true;
|
|
3111
|
+
group.__cropGroup = true;
|
|
3112
|
+
const clipPath = createImageClipPath(
|
|
3113
|
+
{
|
|
3114
|
+
...element,
|
|
3115
|
+
clipShape: element.clipShape && element.clipShape !== "none" ? element.clipShape : "rectangle"
|
|
3116
|
+
},
|
|
3117
|
+
visualWidth,
|
|
3118
|
+
visualHeight
|
|
3119
|
+
);
|
|
3120
|
+
if (clipPath) {
|
|
3121
|
+
clipPath.absolutePositioned = false;
|
|
3122
|
+
clipPath.excludeFromExport = true;
|
|
3123
|
+
group.clipPath = clipPath;
|
|
3124
|
+
}
|
|
3125
|
+
stabilizePlaceholderGroup(group, visualWidth, visualHeight);
|
|
3126
|
+
if (!hasImage) {
|
|
3127
|
+
attachEmptyPlaceholderImage(group, visualWidth, visualHeight);
|
|
3017
3128
|
}
|
|
3018
3129
|
return group;
|
|
3019
3130
|
}
|
|
3131
|
+
function createImagePlaceholderForGroup(element) {
|
|
3132
|
+
return createImagePlaceholder(element);
|
|
3133
|
+
}
|
|
3020
3134
|
function createImageClipPath(element, imgWidth, imgHeight) {
|
|
3021
3135
|
const clipShape = element.clipShape || "none";
|
|
3022
3136
|
if (clipShape === "none") return void 0;
|
|
@@ -3054,7 +3168,328 @@ function createImageClipPath(element, imgWidth, imgHeight) {
|
|
|
3054
3168
|
return void 0;
|
|
3055
3169
|
}
|
|
3056
3170
|
}
|
|
3057
|
-
|
|
3171
|
+
const isCropGroup = (obj) => {
|
|
3172
|
+
var _a;
|
|
3173
|
+
const g = obj;
|
|
3174
|
+
return Boolean(g && (g.__cropGroup || ((_a = g._ct) == null ? void 0 : _a.isCropGroup)));
|
|
3175
|
+
};
|
|
3176
|
+
function parseLuminance(fill) {
|
|
3177
|
+
if (typeof fill !== "string") return null;
|
|
3178
|
+
const f = fill.trim().toLowerCase();
|
|
3179
|
+
if (!f || f === "none" || f === "transparent") return null;
|
|
3180
|
+
if (f === "white" || f === "#fff" || f === "#ffffff") return 1;
|
|
3181
|
+
if (f === "black" || f === "#000" || f === "#000000") return 0;
|
|
3182
|
+
const hex = f.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);
|
|
3183
|
+
if (hex) {
|
|
3184
|
+
const h = hex[1];
|
|
3185
|
+
const full = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
|
|
3186
|
+
const r = parseInt(full.slice(0, 2), 16) / 255;
|
|
3187
|
+
const g = parseInt(full.slice(2, 4), 16) / 255;
|
|
3188
|
+
const b = parseInt(full.slice(4, 6), 16) / 255;
|
|
3189
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
3190
|
+
}
|
|
3191
|
+
const rgb = f.match(/^rgba?\(([^)]+)\)$/);
|
|
3192
|
+
if (rgb) {
|
|
3193
|
+
const parts = rgb[1].split(",").map((s) => parseFloat(s.trim()));
|
|
3194
|
+
if (parts.length >= 3) {
|
|
3195
|
+
const r = parts[0] / 255;
|
|
3196
|
+
const g = parts[1] / 255;
|
|
3197
|
+
const b = parts[2] / 255;
|
|
3198
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
return null;
|
|
3202
|
+
}
|
|
3203
|
+
function getObjectRenderedSize(obj) {
|
|
3204
|
+
const bbox = typeof obj.getBoundingRect === "function" ? obj.getBoundingRect() : null;
|
|
3205
|
+
const width = Math.abs(Number(bbox == null ? void 0 : bbox.width) || (obj.width ?? 0) * Math.abs(obj.scaleX ?? 1));
|
|
3206
|
+
const height = Math.abs(Number(bbox == null ? void 0 : bbox.height) || (obj.height ?? 0) * Math.abs(obj.scaleY ?? 1));
|
|
3207
|
+
return { width, height };
|
|
3208
|
+
}
|
|
3209
|
+
function isLikelyFullFrameBackground(obj, luminance, viewBoxW, viewBoxH, totalObjects) {
|
|
3210
|
+
if (totalObjects <= 1 || viewBoxW <= 0 || viewBoxH <= 0 || luminance === null) return false;
|
|
3211
|
+
const { width, height } = getObjectRenderedSize(obj);
|
|
3212
|
+
const widthRatio = width / viewBoxW;
|
|
3213
|
+
const heightRatio = height / viewBoxH;
|
|
3214
|
+
const areaRatio = width * height / (viewBoxW * viewBoxH);
|
|
3215
|
+
return widthRatio >= 0.96 && heightRatio >= 0.96 && areaRatio >= 0.9;
|
|
3216
|
+
}
|
|
3217
|
+
async function loadSvgAsGroup(url) {
|
|
3218
|
+
const result = await fabric__namespace.loadSVGFromURL(url);
|
|
3219
|
+
const allObjects = (result.objects || []).filter(Boolean);
|
|
3220
|
+
const options = result.options || {};
|
|
3221
|
+
if (allObjects.length === 0) {
|
|
3222
|
+
throw new Error("SVG contained no parseable shapes");
|
|
3223
|
+
}
|
|
3224
|
+
const hintedViewBoxW = Number(options.width) || 0;
|
|
3225
|
+
const hintedViewBoxH = Number(options.height) || 0;
|
|
3226
|
+
const sourceLuminances = allObjects.map((o) => parseLuminance(o.fill));
|
|
3227
|
+
let objects = allObjects.filter((obj, i) => !isLikelyFullFrameBackground(
|
|
3228
|
+
obj,
|
|
3229
|
+
sourceLuminances[i],
|
|
3230
|
+
hintedViewBoxW,
|
|
3231
|
+
hintedViewBoxH,
|
|
3232
|
+
allObjects.length
|
|
3233
|
+
));
|
|
3234
|
+
if (objects.length === 0) objects = allObjects;
|
|
3235
|
+
if (objects.length === 0) objects = allObjects;
|
|
3236
|
+
for (const obj of objects) {
|
|
3237
|
+
obj.fillRule = "evenodd";
|
|
3238
|
+
obj.fill = "#000";
|
|
3239
|
+
obj.stroke = null;
|
|
3240
|
+
obj.strokeWidth = 0;
|
|
3241
|
+
}
|
|
3242
|
+
const group = new fabric__namespace.Group(objects, {
|
|
3243
|
+
originX: "center",
|
|
3244
|
+
originY: "center",
|
|
3245
|
+
selectable: false,
|
|
3246
|
+
evented: false,
|
|
3247
|
+
hasControls: false,
|
|
3248
|
+
hasBorders: false
|
|
3249
|
+
});
|
|
3250
|
+
const viewBoxW = Number(options.width) || group.width || 1;
|
|
3251
|
+
const viewBoxH = Number(options.height) || group.height || 1;
|
|
3252
|
+
return { group, viewBoxW, viewBoxH };
|
|
3253
|
+
}
|
|
3254
|
+
function fitMaskGroupToFrame(maskGroup, frameW, frameH) {
|
|
3255
|
+
const viewBoxW = Number(maskGroup.__svgMaskViewBoxW) || maskGroup.width || 1;
|
|
3256
|
+
const viewBoxH = Number(maskGroup.__svgMaskViewBoxH) || maskGroup.height || 1;
|
|
3257
|
+
maskGroup.set({
|
|
3258
|
+
left: 0,
|
|
3259
|
+
top: 0,
|
|
3260
|
+
originX: "center",
|
|
3261
|
+
originY: "center",
|
|
3262
|
+
scaleX: frameW / viewBoxW,
|
|
3263
|
+
scaleY: frameH / viewBoxH,
|
|
3264
|
+
selectable: false,
|
|
3265
|
+
evented: false,
|
|
3266
|
+
hasControls: false,
|
|
3267
|
+
hasBorders: false
|
|
3268
|
+
});
|
|
3269
|
+
maskGroup.absolutePositioned = false;
|
|
3270
|
+
maskGroup.excludeFromExport = true;
|
|
3271
|
+
maskGroup.inverted = false;
|
|
3272
|
+
maskGroup.dirty = true;
|
|
3273
|
+
maskGroup.setCoords();
|
|
3274
|
+
}
|
|
3275
|
+
function isSvgMaskClipPath(clipPath) {
|
|
3276
|
+
return Boolean(clipPath && clipPath.__svgMask && clipPath instanceof fabric__namespace.Group);
|
|
3277
|
+
}
|
|
3278
|
+
function isLuminanceMaskClipPath(clipPath) {
|
|
3279
|
+
return Boolean(clipPath && clipPath.__svgMaskType === "luminance");
|
|
3280
|
+
}
|
|
3281
|
+
function syncSvgMaskClipPath(cropGroup) {
|
|
3282
|
+
var _a, _b;
|
|
3283
|
+
const clipPath = cropGroup.clipPath;
|
|
3284
|
+
if (!isCropGroup(cropGroup)) return;
|
|
3285
|
+
const frameW = ((_a = cropGroup._ct) == null ? void 0 : _a.frameW) ?? cropGroup.width ?? 0;
|
|
3286
|
+
const frameH = ((_b = cropGroup._ct) == null ? void 0 : _b.frameH) ?? cropGroup.height ?? 0;
|
|
3287
|
+
if (frameW <= 0 || frameH <= 0) return;
|
|
3288
|
+
if (isSvgMaskClipPath(clipPath)) {
|
|
3289
|
+
fitMaskGroupToFrame(clipPath, frameW, frameH);
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
async function detectMaskType(svgUrl) {
|
|
3293
|
+
try {
|
|
3294
|
+
const res = await fetch(svgUrl);
|
|
3295
|
+
if (!res.ok) return "shape";
|
|
3296
|
+
const text = await res.text();
|
|
3297
|
+
const t = text.toLowerCase();
|
|
3298
|
+
if (t.includes("<lineargradient") || t.includes("<radialgradient")) return "luminance";
|
|
3299
|
+
if (t.includes("<image ") || t.includes("<image>")) return "luminance";
|
|
3300
|
+
if (t.includes('mask-type="luminance"') || t.includes("mask-type='luminance'")) return "luminance";
|
|
3301
|
+
if (t.includes("<filter")) return "luminance";
|
|
3302
|
+
if (/fill-opacity\s*=\s*["'](0?\.\d+)/.test(t)) return "luminance";
|
|
3303
|
+
if (/opacity\s*=\s*["'](0?\.\d+)/.test(t)) return "luminance";
|
|
3304
|
+
return "shape";
|
|
3305
|
+
} catch {
|
|
3306
|
+
return "shape";
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
async function applySvgMaskToCropGroup(cropGroup, svgUrl) {
|
|
3310
|
+
var _a, _b, _c;
|
|
3311
|
+
if (!isCropGroup(cropGroup)) {
|
|
3312
|
+
throw new Error("Selected object is not a crop group / image");
|
|
3313
|
+
}
|
|
3314
|
+
const frameW = ((_a = cropGroup._ct) == null ? void 0 : _a.frameW) ?? cropGroup.width ?? 0;
|
|
3315
|
+
const frameH = ((_b = cropGroup._ct) == null ? void 0 : _b.frameH) ?? cropGroup.height ?? 0;
|
|
3316
|
+
if (frameW <= 0 || frameH <= 0) {
|
|
3317
|
+
throw new Error("Crop group has no frame dimensions");
|
|
3318
|
+
}
|
|
3319
|
+
const { group: maskGroup, viewBoxW, viewBoxH } = await loadSvgAsGroup(svgUrl);
|
|
3320
|
+
const mw = maskGroup.width ?? 0;
|
|
3321
|
+
const mh = maskGroup.height ?? 0;
|
|
3322
|
+
if (mw <= 0 || mh <= 0 || viewBoxW <= 0 || viewBoxH <= 0) {
|
|
3323
|
+
throw new Error("This mask has no usable shapes (try the 'Soft' mode instead)");
|
|
3324
|
+
}
|
|
3325
|
+
maskGroup.__svgMask = true;
|
|
3326
|
+
maskGroup.__svgMaskUrl = svgUrl;
|
|
3327
|
+
maskGroup.__svgMaskType = "shape";
|
|
3328
|
+
maskGroup.__svgMaskViewBoxW = viewBoxW;
|
|
3329
|
+
maskGroup.__svgMaskViewBoxH = viewBoxH;
|
|
3330
|
+
fitMaskGroupToFrame(maskGroup, frameW, frameH);
|
|
3331
|
+
cropGroup.clipPath = maskGroup;
|
|
3332
|
+
cropGroup.__svgMaskUrl = svgUrl;
|
|
3333
|
+
cropGroup.__svgMaskType = "shape";
|
|
3334
|
+
cropGroup.dirty = true;
|
|
3335
|
+
if (cropGroup._objects) {
|
|
3336
|
+
for (const child of cropGroup._objects) {
|
|
3337
|
+
child.dirty = true;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
(_c = cropGroup.canvas) == null ? void 0 : _c.requestRenderAll();
|
|
3341
|
+
}
|
|
3342
|
+
function loadSvgAsImage(svgUrl) {
|
|
3343
|
+
return new Promise((resolve, reject) => {
|
|
3344
|
+
const tryFetch = async () => {
|
|
3345
|
+
try {
|
|
3346
|
+
const res = await fetch(svgUrl, { mode: "cors" });
|
|
3347
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
3348
|
+
const blob = await res.blob();
|
|
3349
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
3350
|
+
const img2 = new Image();
|
|
3351
|
+
img2.onload = () => resolve(img2);
|
|
3352
|
+
img2.onerror = () => reject(new Error("Failed to load SVG (after fetch fallback)"));
|
|
3353
|
+
img2.src = objectUrl;
|
|
3354
|
+
} catch (e) {
|
|
3355
|
+
reject(new Error(`Failed to load SVG: ${(e == null ? void 0 : e.message) || "network error"}`));
|
|
3356
|
+
}
|
|
3357
|
+
};
|
|
3358
|
+
const img = new Image();
|
|
3359
|
+
img.crossOrigin = "anonymous";
|
|
3360
|
+
img.onload = () => resolve(img);
|
|
3361
|
+
img.onerror = () => tryFetch();
|
|
3362
|
+
img.src = svgUrl;
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
async function buildLuminanceAlphaCanvas(svgUrl, frameW, frameH) {
|
|
3366
|
+
const w = Math.max(2, Math.round(frameW));
|
|
3367
|
+
const h = Math.max(2, Math.round(frameH));
|
|
3368
|
+
const img = await loadSvgAsImage(svgUrl);
|
|
3369
|
+
const canvas = document.createElement("canvas");
|
|
3370
|
+
canvas.width = w;
|
|
3371
|
+
canvas.height = h;
|
|
3372
|
+
const ctx = canvas.getContext("2d");
|
|
3373
|
+
if (!ctx) throw new Error("Failed to get 2D context for mask canvas");
|
|
3374
|
+
ctx.clearRect(0, 0, w, h);
|
|
3375
|
+
ctx.drawImage(img, 0, 0, w, h);
|
|
3376
|
+
let data;
|
|
3377
|
+
try {
|
|
3378
|
+
data = ctx.getImageData(0, 0, w, h);
|
|
3379
|
+
} catch (e) {
|
|
3380
|
+
throw new Error(
|
|
3381
|
+
"Could not read SVG pixels (CORS / tainted canvas). Try a different mask source."
|
|
3382
|
+
);
|
|
3383
|
+
}
|
|
3384
|
+
const px = data.data;
|
|
3385
|
+
for (let i = 0; i < px.length; i += 4) {
|
|
3386
|
+
const r = px[i];
|
|
3387
|
+
const g = px[i + 1];
|
|
3388
|
+
const b = px[i + 2];
|
|
3389
|
+
const a = px[i + 3];
|
|
3390
|
+
const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
3391
|
+
const alpha = lum / 255 * a;
|
|
3392
|
+
px[i] = 255;
|
|
3393
|
+
px[i + 1] = 255;
|
|
3394
|
+
px[i + 2] = 255;
|
|
3395
|
+
px[i + 3] = Math.round(alpha);
|
|
3396
|
+
}
|
|
3397
|
+
ctx.putImageData(data, 0, 0);
|
|
3398
|
+
return canvas;
|
|
3399
|
+
}
|
|
3400
|
+
async function applyLuminanceMaskToCropGroup(cropGroup, svgUrl) {
|
|
3401
|
+
var _a, _b, _c;
|
|
3402
|
+
if (!isCropGroup(cropGroup)) {
|
|
3403
|
+
throw new Error("Selected object is not a crop group / image");
|
|
3404
|
+
}
|
|
3405
|
+
const frameW = ((_a = cropGroup._ct) == null ? void 0 : _a.frameW) ?? cropGroup.width ?? 0;
|
|
3406
|
+
const frameH = ((_b = cropGroup._ct) == null ? void 0 : _b.frameH) ?? cropGroup.height ?? 0;
|
|
3407
|
+
if (frameW <= 0 || frameH <= 0) {
|
|
3408
|
+
throw new Error("Crop group has no frame dimensions");
|
|
3409
|
+
}
|
|
3410
|
+
const alphaCanvas = await buildLuminanceAlphaCanvas(svgUrl, frameW, frameH);
|
|
3411
|
+
const maskImg = new fabric__namespace.FabricImage(alphaCanvas, {
|
|
3412
|
+
originX: "center",
|
|
3413
|
+
originY: "center",
|
|
3414
|
+
left: 0,
|
|
3415
|
+
top: 0,
|
|
3416
|
+
selectable: false,
|
|
3417
|
+
evented: false,
|
|
3418
|
+
hasControls: false,
|
|
3419
|
+
hasBorders: false
|
|
3420
|
+
});
|
|
3421
|
+
maskImg.absolutePositioned = false;
|
|
3422
|
+
maskImg.excludeFromExport = true;
|
|
3423
|
+
maskImg.inverted = false;
|
|
3424
|
+
maskImg.__svgMask = true;
|
|
3425
|
+
maskImg.__svgMaskUrl = svgUrl;
|
|
3426
|
+
maskImg.__svgMaskType = "luminance";
|
|
3427
|
+
cropGroup.clipPath = maskImg;
|
|
3428
|
+
cropGroup.__svgMaskUrl = svgUrl;
|
|
3429
|
+
cropGroup.__svgMaskType = "luminance";
|
|
3430
|
+
cropGroup.dirty = true;
|
|
3431
|
+
if (cropGroup._objects) {
|
|
3432
|
+
for (const child of cropGroup._objects) {
|
|
3433
|
+
child.dirty = true;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
(_c = cropGroup.canvas) == null ? void 0 : _c.requestRenderAll();
|
|
3437
|
+
}
|
|
3438
|
+
async function applyMaskToCropGroup(cropGroup, svgUrl, maskType) {
|
|
3439
|
+
if (maskType === "luminance") {
|
|
3440
|
+
return applyLuminanceMaskToCropGroup(cropGroup, svgUrl);
|
|
3441
|
+
}
|
|
3442
|
+
return applySvgMaskToCropGroup(cropGroup, svgUrl);
|
|
3443
|
+
}
|
|
3444
|
+
function getAppliedSvgMaskUrl(obj) {
|
|
3445
|
+
if (!obj) return null;
|
|
3446
|
+
const url = obj.__svgMaskUrl;
|
|
3447
|
+
return typeof url === "string" ? url : null;
|
|
3448
|
+
}
|
|
3449
|
+
function getAppliedSvgMaskType(obj) {
|
|
3450
|
+
if (!obj) return null;
|
|
3451
|
+
const t = obj.__svgMaskType;
|
|
3452
|
+
return t === "luminance" || t === "shape" ? t : null;
|
|
3453
|
+
}
|
|
3454
|
+
function clearSvgMaskFromCropGroup(cropGroup) {
|
|
3455
|
+
var _a, _b, _c;
|
|
3456
|
+
if (!isCropGroup(cropGroup)) return;
|
|
3457
|
+
const frameW = ((_a = cropGroup._ct) == null ? void 0 : _a.frameW) ?? cropGroup.width ?? 0;
|
|
3458
|
+
const frameH = ((_b = cropGroup._ct) == null ? void 0 : _b.frameH) ?? cropGroup.height ?? 0;
|
|
3459
|
+
const rect = new fabric__namespace.Rect({
|
|
3460
|
+
width: frameW,
|
|
3461
|
+
height: frameH,
|
|
3462
|
+
left: 0,
|
|
3463
|
+
top: 0,
|
|
3464
|
+
originX: "center",
|
|
3465
|
+
originY: "center",
|
|
3466
|
+
selectable: false,
|
|
3467
|
+
evented: false,
|
|
3468
|
+
hasControls: false,
|
|
3469
|
+
hasBorders: false
|
|
3470
|
+
});
|
|
3471
|
+
rect.absolutePositioned = false;
|
|
3472
|
+
rect.excludeFromExport = true;
|
|
3473
|
+
cropGroup.clipPath = rect;
|
|
3474
|
+
delete cropGroup.__svgMaskUrl;
|
|
3475
|
+
delete cropGroup.__svgMaskType;
|
|
3476
|
+
cropGroup.dirty = true;
|
|
3477
|
+
(_c = cropGroup.canvas) == null ? void 0 : _c.requestRenderAll();
|
|
3478
|
+
}
|
|
3479
|
+
const svgMaskApply = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
3480
|
+
__proto__: null,
|
|
3481
|
+
applyLuminanceMaskToCropGroup,
|
|
3482
|
+
applyMaskToCropGroup,
|
|
3483
|
+
applySvgMaskToCropGroup,
|
|
3484
|
+
clearSvgMaskFromCropGroup,
|
|
3485
|
+
detectMaskType,
|
|
3486
|
+
getAppliedSvgMaskType,
|
|
3487
|
+
getAppliedSvgMaskUrl,
|
|
3488
|
+
isLuminanceMaskClipPath,
|
|
3489
|
+
isSvgMaskClipPath,
|
|
3490
|
+
syncSvgMaskClipPath
|
|
3491
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
3492
|
+
function clamp$1(v, min, max) {
|
|
3058
3493
|
return Math.max(min, Math.min(max, v));
|
|
3059
3494
|
}
|
|
3060
3495
|
function applyControlSizeForZoom(canvas, obj) {
|
|
@@ -3078,76 +3513,78 @@ function finalizeCropGroupCoords(g) {
|
|
|
3078
3513
|
g.setCoords();
|
|
3079
3514
|
}
|
|
3080
3515
|
function updateCoverLayout(g) {
|
|
3516
|
+
var _a;
|
|
3081
3517
|
const ct = g.__cropData;
|
|
3082
3518
|
if (!ct) return;
|
|
3083
3519
|
const { frameW, frameH, shape, rx: rxRatio, _img: img, _border: border } = ct;
|
|
3084
|
-
if (!img) return;
|
|
3085
3520
|
const minDim = Math.min(frameW, frameH);
|
|
3086
3521
|
let rx = rxRatio > 0.5 ? rxRatio : rxRatio * minDim;
|
|
3087
3522
|
rx = Math.max(0, Math.min(rx, frameW / 2, frameH / 2));
|
|
3088
|
-
const
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
clip.setCoords();
|
|
3120
|
-
clip.dirty = true;
|
|
3121
|
-
g.clipPath = clip;
|
|
3122
|
-
} else if (g.clipPath && typeof g.clipPath.set === "function") {
|
|
3123
|
-
if (shape === "circle") {
|
|
3124
|
-
g.clipPath.set({
|
|
3523
|
+
const currentClipPath = g.clipPath;
|
|
3524
|
+
const hasCustomSvgMask = Boolean(
|
|
3525
|
+
currentClipPath && (isSvgMaskClipPath(currentClipPath) || isLuminanceMaskClipPath(currentClipPath) || currentClipPath.__svgMask)
|
|
3526
|
+
);
|
|
3527
|
+
if (hasCustomSvgMask) {
|
|
3528
|
+
if (isSvgMaskClipPath(currentClipPath)) {
|
|
3529
|
+
syncSvgMaskClipPath(g);
|
|
3530
|
+
} else if (currentClipPath && typeof currentClipPath.set === "function") {
|
|
3531
|
+
const baseW = Math.max(1, Number(currentClipPath.width) || frameW);
|
|
3532
|
+
const baseH = Math.max(1, Number(currentClipPath.height) || frameH);
|
|
3533
|
+
currentClipPath.set({
|
|
3534
|
+
left: 0,
|
|
3535
|
+
top: 0,
|
|
3536
|
+
originX: "center",
|
|
3537
|
+
originY: "center",
|
|
3538
|
+
scaleX: frameW / baseW,
|
|
3539
|
+
scaleY: frameH / baseH,
|
|
3540
|
+
selectable: false,
|
|
3541
|
+
evented: false,
|
|
3542
|
+
hasControls: false,
|
|
3543
|
+
hasBorders: false
|
|
3544
|
+
});
|
|
3545
|
+
currentClipPath.absolutePositioned = false;
|
|
3546
|
+
currentClipPath.excludeFromExport = true;
|
|
3547
|
+
currentClipPath.dirty = true;
|
|
3548
|
+
currentClipPath.setCoords();
|
|
3549
|
+
}
|
|
3550
|
+
} else {
|
|
3551
|
+
const needsNewClipPath = !g.clipPath || shape === "circle" && !(g.clipPath instanceof fabric__namespace.Ellipse) || shape !== "circle" && !(g.clipPath instanceof fabric__namespace.Rect);
|
|
3552
|
+
if (needsNewClipPath) {
|
|
3553
|
+
const clip = shape === "circle" ? new fabric__namespace.Ellipse({
|
|
3125
3554
|
rx: frameW / 2,
|
|
3126
3555
|
ry: frameH / 2,
|
|
3127
3556
|
left: 0,
|
|
3128
3557
|
top: 0,
|
|
3129
3558
|
originX: "center",
|
|
3130
3559
|
originY: "center",
|
|
3131
|
-
|
|
3560
|
+
selectable: false,
|
|
3561
|
+
evented: false,
|
|
3562
|
+
hasControls: false,
|
|
3563
|
+
hasBorders: false
|
|
3564
|
+
}) : new fabric__namespace.Rect({
|
|
3565
|
+
width: frameW,
|
|
3566
|
+
height: frameH,
|
|
3567
|
+
rx,
|
|
3568
|
+
ry: rx,
|
|
3569
|
+
left: 0,
|
|
3570
|
+
top: 0,
|
|
3571
|
+
originX: "center",
|
|
3572
|
+
originY: "center",
|
|
3132
3573
|
selectable: false,
|
|
3133
3574
|
evented: false,
|
|
3134
3575
|
hasControls: false,
|
|
3135
3576
|
hasBorders: false
|
|
3136
3577
|
});
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
height: frameH,
|
|
3148
|
-
rx,
|
|
3149
|
-
ry: rx,
|
|
3150
|
-
// Ensure both are identical for uniform corners
|
|
3578
|
+
clip.absolutePositioned = false;
|
|
3579
|
+
clip.excludeFromExport = true;
|
|
3580
|
+
clip.setCoords();
|
|
3581
|
+
clip.dirty = true;
|
|
3582
|
+
g.clipPath = clip;
|
|
3583
|
+
} else if (g.clipPath && typeof g.clipPath.set === "function") {
|
|
3584
|
+
if (shape === "circle") {
|
|
3585
|
+
g.clipPath.set({
|
|
3586
|
+
rx: frameW / 2,
|
|
3587
|
+
ry: frameH / 2,
|
|
3151
3588
|
left: 0,
|
|
3152
3589
|
top: 0,
|
|
3153
3590
|
originX: "center",
|
|
@@ -3157,32 +3594,55 @@ function updateCoverLayout(g) {
|
|
|
3157
3594
|
hasControls: false,
|
|
3158
3595
|
hasBorders: false
|
|
3159
3596
|
});
|
|
3160
|
-
newClip.absolutePositioned = false;
|
|
3161
|
-
newClip.excludeFromExport = true;
|
|
3162
|
-
newClip.setCoords();
|
|
3163
|
-
newClip.dirty = true;
|
|
3164
|
-
g.clipPath = newClip;
|
|
3165
3597
|
} else {
|
|
3166
|
-
clipPathObj.
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3598
|
+
const clipPathObj = g.clipPath;
|
|
3599
|
+
const currentRx = clipPathObj.rx || 0;
|
|
3600
|
+
const currentWidth = clipPathObj.width || 0;
|
|
3601
|
+
const currentHeight = clipPathObj.height || 0;
|
|
3602
|
+
const rxChanged = Math.abs(currentRx - rx) > 0.01;
|
|
3603
|
+
const dimsChanged = Math.abs(currentWidth - frameW) > 0.1 || Math.abs(currentHeight - frameH) > 0.1;
|
|
3604
|
+
if (rxChanged || dimsChanged) {
|
|
3605
|
+
const newClip = new fabric__namespace.Rect({
|
|
3606
|
+
width: frameW,
|
|
3607
|
+
height: frameH,
|
|
3608
|
+
rx,
|
|
3609
|
+
ry: rx,
|
|
3610
|
+
left: 0,
|
|
3611
|
+
top: 0,
|
|
3612
|
+
originX: "center",
|
|
3613
|
+
originY: "center",
|
|
3614
|
+
selectable: false,
|
|
3615
|
+
evented: false,
|
|
3616
|
+
hasControls: false,
|
|
3617
|
+
hasBorders: false
|
|
3618
|
+
});
|
|
3619
|
+
newClip.absolutePositioned = false;
|
|
3620
|
+
newClip.excludeFromExport = true;
|
|
3621
|
+
newClip.setCoords();
|
|
3622
|
+
newClip.dirty = true;
|
|
3623
|
+
g.clipPath = newClip;
|
|
3624
|
+
} else {
|
|
3625
|
+
clipPathObj.set({
|
|
3626
|
+
width: frameW,
|
|
3627
|
+
height: frameH,
|
|
3628
|
+
rx,
|
|
3629
|
+
ry: rx,
|
|
3630
|
+
left: 0,
|
|
3631
|
+
top: 0,
|
|
3632
|
+
originX: "center",
|
|
3633
|
+
originY: "center",
|
|
3634
|
+
selectable: false,
|
|
3635
|
+
evented: false,
|
|
3636
|
+
hasControls: false,
|
|
3637
|
+
hasBorders: false
|
|
3638
|
+
});
|
|
3639
|
+
clipPathObj.setCoords();
|
|
3640
|
+
clipPathObj.dirty = true;
|
|
3641
|
+
}
|
|
3182
3642
|
}
|
|
3643
|
+
g.clipPath.absolutePositioned = false;
|
|
3644
|
+
g.clipPath.excludeFromExport = true;
|
|
3183
3645
|
}
|
|
3184
|
-
g.clipPath.absolutePositioned = false;
|
|
3185
|
-
g.clipPath.excludeFromExport = true;
|
|
3186
3646
|
}
|
|
3187
3647
|
if (border) {
|
|
3188
3648
|
if (shape === "circle") {
|
|
@@ -3194,6 +3654,28 @@ function updateCoverLayout(g) {
|
|
|
3194
3654
|
border.set({ width: frameW, height: frameH, rx: actualRx, ry: actualRx });
|
|
3195
3655
|
}
|
|
3196
3656
|
}
|
|
3657
|
+
if (!img) {
|
|
3658
|
+
const placeholderFrame = ct._placeholderFrame ?? ((_a = g._objects) == null ? void 0 : _a.find((obj) => obj.__isPlaceholderFrame));
|
|
3659
|
+
if (placeholderFrame && typeof placeholderFrame.set === "function") {
|
|
3660
|
+
placeholderFrame.set({
|
|
3661
|
+
width: frameW,
|
|
3662
|
+
height: frameH,
|
|
3663
|
+
left: 0,
|
|
3664
|
+
top: 0,
|
|
3665
|
+
originX: "center",
|
|
3666
|
+
originY: "center",
|
|
3667
|
+
...placeholderFrame instanceof fabric__namespace.Rect ? { rx, ry: rx } : {}
|
|
3668
|
+
});
|
|
3669
|
+
placeholderFrame.dirty = true;
|
|
3670
|
+
ct._placeholderFrame = placeholderFrame;
|
|
3671
|
+
}
|
|
3672
|
+
finalizeCropGroupCoords(g);
|
|
3673
|
+
if (g.canvas) {
|
|
3674
|
+
g.setCoords();
|
|
3675
|
+
g.canvas.requestRenderAll();
|
|
3676
|
+
}
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3197
3679
|
img._ct = img._ct || { panX: 0.5, panY: 0.5, zoom: 1 };
|
|
3198
3680
|
if (img.__panX !== void 0 || img.__panY !== void 0) {
|
|
3199
3681
|
img._ct.panX = img.__panX ?? 0.5;
|
|
@@ -3215,8 +3697,8 @@ function updateCoverLayout(g) {
|
|
|
3215
3697
|
const dispH = ih * finalScale;
|
|
3216
3698
|
const overflowX = Math.max(0, dispW - frameW);
|
|
3217
3699
|
const overflowY = Math.max(0, dispH - frameH);
|
|
3218
|
-
const panX = clamp(img._ct.panX ?? 0.5, 0, 1);
|
|
3219
|
-
const panY = clamp(img._ct.panY ?? 0.5, 0, 1);
|
|
3700
|
+
const panX = clamp$1(img._ct.panX ?? 0.5, 0, 1);
|
|
3701
|
+
const panY = clamp$1(img._ct.panY ?? 0.5, 0, 1);
|
|
3220
3702
|
const offsetX = fitContain ? 0 : overflowX > 0 ? -overflowX * (panX - 0.5) : 0;
|
|
3221
3703
|
const offsetY = fitContain ? 0 : overflowY > 0 ? -overflowY * (panY - 0.5) : 0;
|
|
3222
3704
|
img.set({ left: offsetX, top: offsetY });
|
|
@@ -3308,51 +3790,6 @@ function getRotatedControlCursor(controlKey, target) {
|
|
|
3308
3790
|
const makeRotatedCursorStyleHandler = (controlKey) => {
|
|
3309
3791
|
return (_eventData, _control, target) => getRotatedControlCursor(controlKey, target);
|
|
3310
3792
|
};
|
|
3311
|
-
function resizeFrameFromCorner(eventData, transform, _x, _y) {
|
|
3312
|
-
var _a;
|
|
3313
|
-
const g = transform.target;
|
|
3314
|
-
const ct = g.__cropData;
|
|
3315
|
-
if (!ct || !((_a = g._ct) == null ? void 0 : _a.isCropGroup)) return false;
|
|
3316
|
-
const canvas = g.canvas;
|
|
3317
|
-
if (!canvas) return false;
|
|
3318
|
-
const e = getDomEvent(eventData);
|
|
3319
|
-
if (!e) return false;
|
|
3320
|
-
const pointer = canvas.getPointer(e);
|
|
3321
|
-
g.setCoords();
|
|
3322
|
-
const a = g.aCoords;
|
|
3323
|
-
if (!a) return false;
|
|
3324
|
-
const corner = transform.corner;
|
|
3325
|
-
const anchor = getOppositeAnchor(a, corner);
|
|
3326
|
-
const MIN_W = 20;
|
|
3327
|
-
const MIN_H = 20;
|
|
3328
|
-
const angle = g.angle || 0;
|
|
3329
|
-
const deltaWorldX = pointer.x - anchor.x;
|
|
3330
|
-
const deltaWorldY = pointer.y - anchor.y;
|
|
3331
|
-
const localDelta = worldDeltaToLocal(deltaWorldX, deltaWorldY, angle);
|
|
3332
|
-
const defaultSigns = getCornerDefaultSigns(corner);
|
|
3333
|
-
const signX = Math.abs(localDelta.x) < 1e-3 ? defaultSigns.x : localDelta.x >= 0 ? 1 : -1;
|
|
3334
|
-
const signY = Math.abs(localDelta.y) < 1e-3 ? defaultSigns.y : localDelta.y >= 0 ? 1 : -1;
|
|
3335
|
-
const newW = Math.max(MIN_W, Math.abs(localDelta.x));
|
|
3336
|
-
const newH = Math.max(MIN_H, Math.abs(localDelta.y));
|
|
3337
|
-
ct.frameW = newW;
|
|
3338
|
-
ct.frameH = newH;
|
|
3339
|
-
const centerLocal = {
|
|
3340
|
-
x: signX * (newW / 2),
|
|
3341
|
-
y: signY * (newH / 2)
|
|
3342
|
-
};
|
|
3343
|
-
const centerWorld = localDeltaToWorld(centerLocal.x, centerLocal.y, angle);
|
|
3344
|
-
g.set({
|
|
3345
|
-
left: anchor.x + centerWorld.x,
|
|
3346
|
-
top: anchor.y + centerWorld.y,
|
|
3347
|
-
originX: "center",
|
|
3348
|
-
originY: "center",
|
|
3349
|
-
width: newW,
|
|
3350
|
-
height: newH
|
|
3351
|
-
});
|
|
3352
|
-
updateCoverLayout(g);
|
|
3353
|
-
canvas.requestRenderAll();
|
|
3354
|
-
return true;
|
|
3355
|
-
}
|
|
3356
3793
|
function resizeFrameFromCornerUniform(eventData, transform, _x, _y) {
|
|
3357
3794
|
var _a;
|
|
3358
3795
|
const g = transform.target;
|
|
@@ -3432,7 +3869,7 @@ function resizeFrameFromSide(g, side, localDx, localDy) {
|
|
|
3432
3869
|
}
|
|
3433
3870
|
function installCanvaMaskControls(g) {
|
|
3434
3871
|
const ct = g.__cropData;
|
|
3435
|
-
if (ct) ct.fit =
|
|
3872
|
+
if (ct) ct.fit = ct.fit ?? "cover";
|
|
3436
3873
|
g.setControlsVisibility({
|
|
3437
3874
|
mt: true,
|
|
3438
3875
|
mb: true,
|
|
@@ -3486,7 +3923,7 @@ function installCanvaMaskControls(g) {
|
|
|
3486
3923
|
if (canvas && canvas.__editLockRef) {
|
|
3487
3924
|
canvas.__editLockRef.current = true;
|
|
3488
3925
|
}
|
|
3489
|
-
return
|
|
3926
|
+
return resizeFrameFromCornerUniform(eventData, transform);
|
|
3490
3927
|
}
|
|
3491
3928
|
});
|
|
3492
3929
|
};
|
|
@@ -3496,47 +3933,6 @@ function installCanvaMaskControls(g) {
|
|
|
3496
3933
|
g.controls.br = makeCornerControl("br", 0.5, 0.5, "nwse-resize");
|
|
3497
3934
|
g.__hasCustomControls = true;
|
|
3498
3935
|
}
|
|
3499
|
-
function setSimpleScaleControls(g) {
|
|
3500
|
-
if (!g.__cropGroup) return;
|
|
3501
|
-
const ct = g.__cropData;
|
|
3502
|
-
if (ct) {
|
|
3503
|
-
updateCoverLayout(g);
|
|
3504
|
-
}
|
|
3505
|
-
g.lockScalingX = true;
|
|
3506
|
-
g.lockScalingY = true;
|
|
3507
|
-
g.setControlsVisibility({
|
|
3508
|
-
mt: false,
|
|
3509
|
-
mb: false,
|
|
3510
|
-
ml: false,
|
|
3511
|
-
mr: false,
|
|
3512
|
-
tl: true,
|
|
3513
|
-
tr: true,
|
|
3514
|
-
bl: true,
|
|
3515
|
-
br: true,
|
|
3516
|
-
mtr: true
|
|
3517
|
-
});
|
|
3518
|
-
g.padding = 0;
|
|
3519
|
-
const makeCornerControl = (key, x, y, cursor) => new fabric__namespace.Control({
|
|
3520
|
-
x,
|
|
3521
|
-
y,
|
|
3522
|
-
cursorStyle: cursor,
|
|
3523
|
-
cursorStyleHandler: makeRotatedCursorStyleHandler(key),
|
|
3524
|
-
actionName: "resize",
|
|
3525
|
-
actionHandler: (eventData, transform) => {
|
|
3526
|
-
const t = transform.target;
|
|
3527
|
-
if (t.canvas && t.canvas.__editLockRef) {
|
|
3528
|
-
t.canvas.__editLockRef.current = true;
|
|
3529
|
-
}
|
|
3530
|
-
return resizeFrameFromCornerUniform(eventData, transform);
|
|
3531
|
-
}
|
|
3532
|
-
});
|
|
3533
|
-
g.controls.tl = makeCornerControl("tl", -0.5, -0.5, "nwse-resize");
|
|
3534
|
-
g.controls.tr = makeCornerControl("tr", 0.5, -0.5, "nesw-resize");
|
|
3535
|
-
g.controls.bl = makeCornerControl("bl", -0.5, 0.5, "nesw-resize");
|
|
3536
|
-
g.controls.br = makeCornerControl("br", 0.5, 0.5, "nwse-resize");
|
|
3537
|
-
g.__hasCustomControls = true;
|
|
3538
|
-
g.setCoords();
|
|
3539
|
-
}
|
|
3540
3936
|
async function createMaskedImageElement({
|
|
3541
3937
|
url,
|
|
3542
3938
|
image,
|
|
@@ -3719,9 +4115,405 @@ async function createMaskedImageElement({
|
|
|
3719
4115
|
});
|
|
3720
4116
|
g.setCoords();
|
|
3721
4117
|
updateCoverLayout(g);
|
|
3722
|
-
|
|
4118
|
+
installCanvaMaskControls(g);
|
|
3723
4119
|
return g;
|
|
3724
4120
|
}
|
|
4121
|
+
const CROP_MODE_FLAG = "__inCropMode";
|
|
4122
|
+
const CROP_GHOST_FLAG = "__cropGhost";
|
|
4123
|
+
const CROP_OUTLINE_FLAG = "__cropOutline";
|
|
4124
|
+
const CROP_HANDLERS_FLAG = "__cropHandlers";
|
|
4125
|
+
function isCropGroupInCropMode(g) {
|
|
4126
|
+
return Boolean(g[CROP_MODE_FLAG]);
|
|
4127
|
+
}
|
|
4128
|
+
function clamp(v, min, max) {
|
|
4129
|
+
return Math.max(min, Math.min(max, v));
|
|
4130
|
+
}
|
|
4131
|
+
function syncGhostTransform(g) {
|
|
4132
|
+
const ghost = g[CROP_GHOST_FLAG];
|
|
4133
|
+
const ct = g.__cropData;
|
|
4134
|
+
if (!ghost || !(ct == null ? void 0 : ct._img)) return;
|
|
4135
|
+
const img = ct._img;
|
|
4136
|
+
const finalScale = img.scaleX || 1;
|
|
4137
|
+
const gLeft = g.left ?? 0;
|
|
4138
|
+
const gTop = g.top ?? 0;
|
|
4139
|
+
const angle = g.angle ?? 0;
|
|
4140
|
+
const imgLocalLeft = img.left ?? 0;
|
|
4141
|
+
const imgLocalTop = img.top ?? 0;
|
|
4142
|
+
const rad = fabric__namespace.util.degreesToRadians(angle);
|
|
4143
|
+
const cos = Math.cos(rad);
|
|
4144
|
+
const sin = Math.sin(rad);
|
|
4145
|
+
const worldDx = imgLocalLeft * cos - imgLocalTop * sin;
|
|
4146
|
+
const worldDy = imgLocalLeft * sin + imgLocalTop * cos;
|
|
4147
|
+
ghost.set({
|
|
4148
|
+
left: gLeft + worldDx,
|
|
4149
|
+
top: gTop + worldDy,
|
|
4150
|
+
scaleX: finalScale,
|
|
4151
|
+
scaleY: finalScale,
|
|
4152
|
+
angle,
|
|
4153
|
+
originX: "center",
|
|
4154
|
+
originY: "center"
|
|
4155
|
+
});
|
|
4156
|
+
ghost.setCoords();
|
|
4157
|
+
}
|
|
4158
|
+
function syncOutlineTransform(g) {
|
|
4159
|
+
const outline = g[CROP_OUTLINE_FLAG];
|
|
4160
|
+
const ct = g.__cropData;
|
|
4161
|
+
if (!outline || !ct) return;
|
|
4162
|
+
outline.set({
|
|
4163
|
+
left: g.left ?? 0,
|
|
4164
|
+
top: g.top ?? 0,
|
|
4165
|
+
width: ct.frameW,
|
|
4166
|
+
height: ct.frameH,
|
|
4167
|
+
angle: g.angle ?? 0,
|
|
4168
|
+
originX: "center",
|
|
4169
|
+
originY: "center"
|
|
4170
|
+
});
|
|
4171
|
+
if (outline instanceof fabric__namespace.Rect) {
|
|
4172
|
+
const minDim = Math.min(ct.frameW, ct.frameH);
|
|
4173
|
+
const rxRatio = ct.rx ?? 0;
|
|
4174
|
+
let rx = rxRatio > 0.5 ? rxRatio : rxRatio * minDim;
|
|
4175
|
+
rx = Math.max(0, Math.min(rx, ct.frameW / 2, ct.frameH / 2));
|
|
4176
|
+
outline.set({ rx, ry: rx });
|
|
4177
|
+
}
|
|
4178
|
+
outline.setCoords();
|
|
4179
|
+
}
|
|
4180
|
+
function installCropModeVisuals(g) {
|
|
4181
|
+
g.setControlsVisibility({
|
|
4182
|
+
mt: false,
|
|
4183
|
+
mb: false,
|
|
4184
|
+
ml: false,
|
|
4185
|
+
mr: false,
|
|
4186
|
+
tl: false,
|
|
4187
|
+
tr: false,
|
|
4188
|
+
bl: false,
|
|
4189
|
+
br: false,
|
|
4190
|
+
mtr: false
|
|
4191
|
+
});
|
|
4192
|
+
g.hasControls = false;
|
|
4193
|
+
g.lockScalingX = true;
|
|
4194
|
+
g.lockScalingY = true;
|
|
4195
|
+
g.lockRotation = true;
|
|
4196
|
+
g.lockMovementX = false;
|
|
4197
|
+
g.lockMovementY = false;
|
|
4198
|
+
g.borderColor = "hsl(256, 80%, 58%)";
|
|
4199
|
+
g.borderDashArray = [4, 4];
|
|
4200
|
+
g.setCoords();
|
|
4201
|
+
}
|
|
4202
|
+
function enterCropMode(g) {
|
|
4203
|
+
const ct = g.__cropData;
|
|
4204
|
+
if (!(ct == null ? void 0 : ct._img)) return false;
|
|
4205
|
+
if (g[CROP_MODE_FLAG]) return false;
|
|
4206
|
+
const canvas = g.canvas;
|
|
4207
|
+
if (!canvas) return false;
|
|
4208
|
+
g[CROP_MODE_FLAG] = true;
|
|
4209
|
+
const innerImg = ct._img;
|
|
4210
|
+
const imgEl = innerImg._element;
|
|
4211
|
+
if (!imgEl) {
|
|
4212
|
+
g[CROP_MODE_FLAG] = false;
|
|
4213
|
+
return false;
|
|
4214
|
+
}
|
|
4215
|
+
const ghost = new fabric__namespace.FabricImage(imgEl, {
|
|
4216
|
+
selectable: false,
|
|
4217
|
+
evented: false,
|
|
4218
|
+
hasControls: false,
|
|
4219
|
+
hasBorders: false,
|
|
4220
|
+
opacity: 0.35,
|
|
4221
|
+
originX: "center",
|
|
4222
|
+
originY: "center",
|
|
4223
|
+
objectCaching: false
|
|
4224
|
+
});
|
|
4225
|
+
ghost.excludeFromExport = true;
|
|
4226
|
+
ghost[CROP_GHOST_FLAG] = true;
|
|
4227
|
+
const outlineShape = ct.shape;
|
|
4228
|
+
const outline = outlineShape === "circle" ? new fabric__namespace.Ellipse({
|
|
4229
|
+
rx: ct.frameW / 2,
|
|
4230
|
+
ry: ct.frameH / 2,
|
|
4231
|
+
fill: "transparent",
|
|
4232
|
+
stroke: "hsl(256, 80%, 58%)",
|
|
4233
|
+
strokeWidth: 1.5,
|
|
4234
|
+
strokeDashArray: [6, 4],
|
|
4235
|
+
strokeUniform: true,
|
|
4236
|
+
selectable: false,
|
|
4237
|
+
evented: false,
|
|
4238
|
+
hasControls: false,
|
|
4239
|
+
hasBorders: false,
|
|
4240
|
+
originX: "center",
|
|
4241
|
+
originY: "center",
|
|
4242
|
+
objectCaching: false
|
|
4243
|
+
}) : new fabric__namespace.Rect({
|
|
4244
|
+
width: ct.frameW,
|
|
4245
|
+
height: ct.frameH,
|
|
4246
|
+
fill: "transparent",
|
|
4247
|
+
stroke: "hsl(256, 80%, 58%)",
|
|
4248
|
+
strokeWidth: 1.5,
|
|
4249
|
+
strokeDashArray: [6, 4],
|
|
4250
|
+
strokeUniform: true,
|
|
4251
|
+
selectable: false,
|
|
4252
|
+
evented: false,
|
|
4253
|
+
hasControls: false,
|
|
4254
|
+
hasBorders: false,
|
|
4255
|
+
originX: "center",
|
|
4256
|
+
originY: "center",
|
|
4257
|
+
objectCaching: false
|
|
4258
|
+
});
|
|
4259
|
+
outline.excludeFromExport = true;
|
|
4260
|
+
outline[CROP_OUTLINE_FLAG] = true;
|
|
4261
|
+
g[CROP_GHOST_FLAG] = ghost;
|
|
4262
|
+
g[CROP_OUTLINE_FLAG] = outline;
|
|
4263
|
+
const groupIndex = canvas._objects.indexOf(g);
|
|
4264
|
+
if (groupIndex >= 0) {
|
|
4265
|
+
canvas.insertAt(groupIndex, ghost);
|
|
4266
|
+
canvas.add(outline);
|
|
4267
|
+
canvas.bringObjectToFront(g);
|
|
4268
|
+
canvas.bringObjectToFront(outline);
|
|
4269
|
+
} else {
|
|
4270
|
+
canvas.add(ghost);
|
|
4271
|
+
canvas.add(g);
|
|
4272
|
+
canvas.add(outline);
|
|
4273
|
+
}
|
|
4274
|
+
syncGhostTransform(g);
|
|
4275
|
+
syncOutlineTransform(g);
|
|
4276
|
+
const groupAnchor = { left: g.left ?? 0, top: g.top ?? 0 };
|
|
4277
|
+
let panRafId = null;
|
|
4278
|
+
let pendingDx = 0;
|
|
4279
|
+
let pendingDy = 0;
|
|
4280
|
+
let lastDragDx = 0;
|
|
4281
|
+
let lastDragDy = 0;
|
|
4282
|
+
const flushPan = () => {
|
|
4283
|
+
panRafId = null;
|
|
4284
|
+
if (pendingDx === 0 && pendingDy === 0) return;
|
|
4285
|
+
const innerCt = g.__cropData;
|
|
4286
|
+
const img = innerCt == null ? void 0 : innerCt._img;
|
|
4287
|
+
if (!img) {
|
|
4288
|
+
pendingDx = 0;
|
|
4289
|
+
pendingDy = 0;
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
4292
|
+
const angle = g.angle ?? 0;
|
|
4293
|
+
const rad = fabric__namespace.util.degreesToRadians(-angle);
|
|
4294
|
+
const cos = Math.cos(rad);
|
|
4295
|
+
const sin = Math.sin(rad);
|
|
4296
|
+
const localDx = pendingDx * cos - pendingDy * sin;
|
|
4297
|
+
const localDy = pendingDx * sin + pendingDy * cos;
|
|
4298
|
+
pendingDx = 0;
|
|
4299
|
+
pendingDy = 0;
|
|
4300
|
+
const iw = img.width || 1;
|
|
4301
|
+
const ih = img.height || 1;
|
|
4302
|
+
const finalScale = img.scaleX || 1;
|
|
4303
|
+
const dispW = iw * finalScale;
|
|
4304
|
+
const dispH = ih * finalScale;
|
|
4305
|
+
const overflowX = Math.max(0, dispW - innerCt.frameW);
|
|
4306
|
+
const overflowY = Math.max(0, dispH - innerCt.frameH);
|
|
4307
|
+
img._ct = img._ct || { panX: 0.5, panY: 0.5, zoom: 1 };
|
|
4308
|
+
const ct2 = img._ct;
|
|
4309
|
+
if (overflowX > 0) ct2.panX = clamp((ct2.panX ?? 0.5) - localDx / overflowX, 0, 1);
|
|
4310
|
+
if (overflowY > 0) ct2.panY = clamp((ct2.panY ?? 0.5) - localDy / overflowY, 0, 1);
|
|
4311
|
+
updateCoverLayout(g);
|
|
4312
|
+
syncGhostTransform(g);
|
|
4313
|
+
g.setCoords();
|
|
4314
|
+
canvas.requestRenderAll();
|
|
4315
|
+
};
|
|
4316
|
+
const onMoving = (opt) => {
|
|
4317
|
+
const target = opt == null ? void 0 : opt.target;
|
|
4318
|
+
if (target !== g) return;
|
|
4319
|
+
const rawDx = (g.left ?? 0) - groupAnchor.left;
|
|
4320
|
+
const rawDy = (g.top ?? 0) - groupAnchor.top;
|
|
4321
|
+
const dx = rawDx - lastDragDx;
|
|
4322
|
+
const dy = rawDy - lastDragDy;
|
|
4323
|
+
lastDragDx = rawDx;
|
|
4324
|
+
lastDragDy = rawDy;
|
|
4325
|
+
g.left = groupAnchor.left;
|
|
4326
|
+
g.top = groupAnchor.top;
|
|
4327
|
+
if (dx === 0 && dy === 0) return;
|
|
4328
|
+
pendingDx += dx;
|
|
4329
|
+
pendingDy += dy;
|
|
4330
|
+
if (panRafId == null) panRafId = requestAnimationFrame(flushPan);
|
|
4331
|
+
};
|
|
4332
|
+
const onModified = (opt) => {
|
|
4333
|
+
const target = opt == null ? void 0 : opt.target;
|
|
4334
|
+
if (target !== g) return;
|
|
4335
|
+
if (panRafId != null) {
|
|
4336
|
+
cancelAnimationFrame(panRafId);
|
|
4337
|
+
panRafId = null;
|
|
4338
|
+
flushPan();
|
|
4339
|
+
}
|
|
4340
|
+
lastDragDx = 0;
|
|
4341
|
+
lastDragDy = 0;
|
|
4342
|
+
g.left = groupAnchor.left;
|
|
4343
|
+
g.top = groupAnchor.top;
|
|
4344
|
+
g.setCoords();
|
|
4345
|
+
};
|
|
4346
|
+
const onCanvasMouseDown = (opt) => {
|
|
4347
|
+
const target = opt.target;
|
|
4348
|
+
if (target === g || target === ghost || target === outline) return;
|
|
4349
|
+
setTimeout(() => exitCropMode(g, true), 0);
|
|
4350
|
+
};
|
|
4351
|
+
const onDblClick = (opt) => {
|
|
4352
|
+
const target = opt == null ? void 0 : opt.target;
|
|
4353
|
+
if (target !== g && target !== ghost && target !== outline) return;
|
|
4354
|
+
exitCropMode(g, true);
|
|
4355
|
+
};
|
|
4356
|
+
const onKeyDown = (e) => {
|
|
4357
|
+
if (e.key === "Escape" || e.key === "Enter") {
|
|
4358
|
+
e.preventDefault();
|
|
4359
|
+
e.stopPropagation();
|
|
4360
|
+
exitCropMode(g, true);
|
|
4361
|
+
}
|
|
4362
|
+
};
|
|
4363
|
+
let wheelCommitTimer = null;
|
|
4364
|
+
let zoomRafId = null;
|
|
4365
|
+
let pendingZoomDelta = 0;
|
|
4366
|
+
let pendingZoomCenter = null;
|
|
4367
|
+
let lastWheelWasPinch = false;
|
|
4368
|
+
const flushZoom = () => {
|
|
4369
|
+
zoomRafId = null;
|
|
4370
|
+
if (pendingZoomDelta === 0 || !pendingZoomCenter) return;
|
|
4371
|
+
const innerCt = g.__cropData;
|
|
4372
|
+
const img = innerCt == null ? void 0 : innerCt._img;
|
|
4373
|
+
if (!img) {
|
|
4374
|
+
pendingZoomDelta = 0;
|
|
4375
|
+
pendingZoomCenter = null;
|
|
4376
|
+
return;
|
|
4377
|
+
}
|
|
4378
|
+
const factor = lastWheelWasPinch ? 0.012 : 22e-4;
|
|
4379
|
+
const ratio = Math.exp(-pendingZoomDelta * factor);
|
|
4380
|
+
pendingZoomDelta = 0;
|
|
4381
|
+
img._ct = img._ct || { panX: 0.5, panY: 0.5, zoom: 1 };
|
|
4382
|
+
const ct2 = img._ct;
|
|
4383
|
+
const prevZoom = ct2.zoom ?? 1;
|
|
4384
|
+
const nextZoom = clamp(prevZoom * ratio, 1, 8);
|
|
4385
|
+
if (nextZoom === prevZoom) {
|
|
4386
|
+
pendingZoomCenter = null;
|
|
4387
|
+
return;
|
|
4388
|
+
}
|
|
4389
|
+
const angle = g.angle ?? 0;
|
|
4390
|
+
const rad = fabric__namespace.util.degreesToRadians(-angle);
|
|
4391
|
+
const cos = Math.cos(rad);
|
|
4392
|
+
const sin = Math.sin(rad);
|
|
4393
|
+
const cx = g.left ?? 0;
|
|
4394
|
+
const cy = g.top ?? 0;
|
|
4395
|
+
const wx = pendingZoomCenter.x - cx;
|
|
4396
|
+
const wy = pendingZoomCenter.y - cy;
|
|
4397
|
+
const localCx = wx * cos - wy * sin;
|
|
4398
|
+
const localCy = wx * sin + wy * cos;
|
|
4399
|
+
const iw = img.width || 1;
|
|
4400
|
+
const ih = img.height || 1;
|
|
4401
|
+
const prevScale = img.scaleX || 1;
|
|
4402
|
+
const prevOverflowX = Math.max(0, iw * prevScale - innerCt.frameW);
|
|
4403
|
+
const prevOverflowY = Math.max(0, ih * prevScale - innerCt.frameH);
|
|
4404
|
+
const prevPanX = ct2.panX ?? 0.5;
|
|
4405
|
+
const prevPanY = ct2.panY ?? 0.5;
|
|
4406
|
+
const prevOffsetX = prevOverflowX > 0 ? -prevOverflowX * (prevPanX - 0.5) : 0;
|
|
4407
|
+
const prevOffsetY = prevOverflowY > 0 ? -prevOverflowY * (prevPanY - 0.5) : 0;
|
|
4408
|
+
const imgPxX = (localCx - prevOffsetX) / prevScale + iw / 2;
|
|
4409
|
+
const imgPxY = (localCy - prevOffsetY) / prevScale + ih / 2;
|
|
4410
|
+
ct2.zoom = nextZoom;
|
|
4411
|
+
updateCoverLayout(g);
|
|
4412
|
+
const newScale = img.scaleX || 1;
|
|
4413
|
+
const newOverflowX = Math.max(0, iw * newScale - innerCt.frameW);
|
|
4414
|
+
const newOverflowY = Math.max(0, ih * newScale - innerCt.frameH);
|
|
4415
|
+
if (newOverflowX > 0) {
|
|
4416
|
+
const desiredOffsetX = localCx - (imgPxX - iw / 2) * newScale;
|
|
4417
|
+
ct2.panX = clamp(0.5 - desiredOffsetX / newOverflowX, 0, 1);
|
|
4418
|
+
} else {
|
|
4419
|
+
ct2.panX = 0.5;
|
|
4420
|
+
}
|
|
4421
|
+
if (newOverflowY > 0) {
|
|
4422
|
+
const desiredOffsetY = localCy - (imgPxY - ih / 2) * newScale;
|
|
4423
|
+
ct2.panY = clamp(0.5 - desiredOffsetY / newOverflowY, 0, 1);
|
|
4424
|
+
} else {
|
|
4425
|
+
ct2.panY = 0.5;
|
|
4426
|
+
}
|
|
4427
|
+
updateCoverLayout(g);
|
|
4428
|
+
syncGhostTransform(g);
|
|
4429
|
+
g.setCoords();
|
|
4430
|
+
canvas.requestRenderAll();
|
|
4431
|
+
if (wheelCommitTimer) clearTimeout(wheelCommitTimer);
|
|
4432
|
+
wheelCommitTimer = setTimeout(() => {
|
|
4433
|
+
canvas.fire("object:modified", { target: g });
|
|
4434
|
+
g.left = groupAnchor.left;
|
|
4435
|
+
g.top = groupAnchor.top;
|
|
4436
|
+
g.setCoords();
|
|
4437
|
+
}, 200);
|
|
4438
|
+
pendingZoomCenter = null;
|
|
4439
|
+
};
|
|
4440
|
+
const onWheel = (e) => {
|
|
4441
|
+
if (!canvas) return;
|
|
4442
|
+
const innerCt = g.__cropData;
|
|
4443
|
+
if (!innerCt) return;
|
|
4444
|
+
const pointer = canvas.getPointer(e, false);
|
|
4445
|
+
const br = g.getBoundingRect();
|
|
4446
|
+
if (pointer.x < br.left || pointer.x > br.left + br.width || pointer.y < br.top || pointer.y > br.top + br.height) return;
|
|
4447
|
+
const img = innerCt._img;
|
|
4448
|
+
if (!img) return;
|
|
4449
|
+
e.preventDefault();
|
|
4450
|
+
e.stopPropagation();
|
|
4451
|
+
pendingZoomDelta += e.deltaY;
|
|
4452
|
+
pendingZoomCenter = pointer;
|
|
4453
|
+
lastWheelWasPinch = !!e.ctrlKey;
|
|
4454
|
+
if (zoomRafId == null) zoomRafId = requestAnimationFrame(flushZoom);
|
|
4455
|
+
};
|
|
4456
|
+
canvas.on("object:moving", onMoving);
|
|
4457
|
+
canvas.on("object:modified", onModified);
|
|
4458
|
+
canvas.on("mouse:down:before", onCanvasMouseDown);
|
|
4459
|
+
canvas.on("mouse:dblclick", onDblClick);
|
|
4460
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
4461
|
+
const upperCanvasEl = canvas.upperCanvasEl;
|
|
4462
|
+
upperCanvasEl == null ? void 0 : upperCanvasEl.addEventListener("wheel", onWheel, { passive: false });
|
|
4463
|
+
g[CROP_HANDLERS_FLAG] = {
|
|
4464
|
+
onMoving,
|
|
4465
|
+
onModified,
|
|
4466
|
+
onCanvasMouseDown,
|
|
4467
|
+
onDblClick,
|
|
4468
|
+
onKeyDown,
|
|
4469
|
+
onWheel,
|
|
4470
|
+
upperCanvasEl
|
|
4471
|
+
};
|
|
4472
|
+
installCropModeVisuals(g);
|
|
4473
|
+
canvas.setActiveObject(g);
|
|
4474
|
+
canvas.requestRenderAll();
|
|
4475
|
+
return true;
|
|
4476
|
+
}
|
|
4477
|
+
function exitCropMode(g, commit = true) {
|
|
4478
|
+
var _a;
|
|
4479
|
+
if (!g[CROP_MODE_FLAG]) return;
|
|
4480
|
+
const canvas = g.canvas;
|
|
4481
|
+
const handlers = g[CROP_HANDLERS_FLAG];
|
|
4482
|
+
if (handlers && canvas) {
|
|
4483
|
+
canvas.off("object:moving", handlers.onMoving);
|
|
4484
|
+
canvas.off("object:modified", handlers.onModified);
|
|
4485
|
+
canvas.off("mouse:down:before", handlers.onCanvasMouseDown);
|
|
4486
|
+
if (handlers.onDblClick) canvas.off("mouse:dblclick", handlers.onDblClick);
|
|
4487
|
+
window.removeEventListener("keydown", handlers.onKeyDown, true);
|
|
4488
|
+
(_a = handlers.upperCanvasEl) == null ? void 0 : _a.removeEventListener("wheel", handlers.onWheel);
|
|
4489
|
+
}
|
|
4490
|
+
g[CROP_HANDLERS_FLAG] = void 0;
|
|
4491
|
+
const ghost = g[CROP_GHOST_FLAG];
|
|
4492
|
+
const outline = g[CROP_OUTLINE_FLAG];
|
|
4493
|
+
if (canvas && ghost) canvas.remove(ghost);
|
|
4494
|
+
if (canvas && outline) canvas.remove(outline);
|
|
4495
|
+
g[CROP_GHOST_FLAG] = void 0;
|
|
4496
|
+
g[CROP_OUTLINE_FLAG] = void 0;
|
|
4497
|
+
g.lockMovementX = false;
|
|
4498
|
+
g.lockMovementY = false;
|
|
4499
|
+
g.lockRotation = false;
|
|
4500
|
+
g.lockScalingX = false;
|
|
4501
|
+
g.lockScalingY = false;
|
|
4502
|
+
g.hasControls = true;
|
|
4503
|
+
g.borderDashArray = void 0;
|
|
4504
|
+
installCanvaMaskControls(g);
|
|
4505
|
+
g.borderColor = void 0;
|
|
4506
|
+
g.cornerColor = void 0;
|
|
4507
|
+
g.cornerStrokeColor = void 0;
|
|
4508
|
+
g.cornerStyle = "rect";
|
|
4509
|
+
g[CROP_MODE_FLAG] = false;
|
|
4510
|
+
g.__cropZoomLastPointer = void 0;
|
|
4511
|
+
g.__lastPointerForCrop = void 0;
|
|
4512
|
+
if (commit && canvas) {
|
|
4513
|
+
canvas.fire("object:modified", { target: g });
|
|
4514
|
+
}
|
|
4515
|
+
canvas == null ? void 0 : canvas.requestRenderAll();
|
|
4516
|
+
}
|
|
3725
4517
|
function getObjectSnapPoints(obj) {
|
|
3726
4518
|
try {
|
|
3727
4519
|
obj.setCoords();
|
|
@@ -5290,13 +6082,7 @@ const PageCanvas = react.forwardRef(
|
|
|
5290
6082
|
const activeObj = fabricCanvas.getActiveObject();
|
|
5291
6083
|
if (activeObj) applyControlSizeForZoom(fabricCanvas, activeObj);
|
|
5292
6084
|
if (activeObj && !(activeObj instanceof fabric__namespace.ActiveSelection) && (((_a = activeObj._ct) == null ? void 0 : _a.isCropGroup) || activeObj.__cropGroup)) {
|
|
5293
|
-
|
|
5294
|
-
const element = elementsRef.current.find((el) => el.id === objId);
|
|
5295
|
-
if (element == null ? void 0 : element.useCropHandles) {
|
|
5296
|
-
installCanvaMaskControls(activeObj);
|
|
5297
|
-
} else {
|
|
5298
|
-
setSimpleScaleControls(activeObj);
|
|
5299
|
-
}
|
|
6085
|
+
installCanvaMaskControls(activeObj);
|
|
5300
6086
|
}
|
|
5301
6087
|
});
|
|
5302
6088
|
fabricCanvas.on("selection:updated", () => {
|
|
@@ -5305,13 +6091,7 @@ const PageCanvas = react.forwardRef(
|
|
|
5305
6091
|
const activeObj = fabricCanvas.getActiveObject();
|
|
5306
6092
|
if (activeObj) applyControlSizeForZoom(fabricCanvas, activeObj);
|
|
5307
6093
|
if (activeObj && !(activeObj instanceof fabric__namespace.ActiveSelection) && (((_a = activeObj._ct) == null ? void 0 : _a.isCropGroup) || activeObj.__cropGroup)) {
|
|
5308
|
-
|
|
5309
|
-
const element = elementsRef.current.find((el) => el.id === objId);
|
|
5310
|
-
if (element == null ? void 0 : element.useCropHandles) {
|
|
5311
|
-
installCanvaMaskControls(activeObj);
|
|
5312
|
-
} else {
|
|
5313
|
-
setSimpleScaleControls(activeObj);
|
|
5314
|
-
}
|
|
6094
|
+
installCanvaMaskControls(activeObj);
|
|
5315
6095
|
}
|
|
5316
6096
|
});
|
|
5317
6097
|
fabricCanvas.on("selection:cleared", () => {
|
|
@@ -5329,6 +6109,8 @@ const PageCanvas = react.forwardRef(
|
|
|
5329
6109
|
let dragStarted = false;
|
|
5330
6110
|
const markTransforming = (target) => {
|
|
5331
6111
|
if (!target) return;
|
|
6112
|
+
const targetId = getObjectId(target);
|
|
6113
|
+
if (targetId) transformingIdsRef.current.add(targetId);
|
|
5332
6114
|
if (target instanceof fabric__namespace.Group) {
|
|
5333
6115
|
target.getObjects().forEach((obj) => {
|
|
5334
6116
|
const id2 = getObjectId(obj);
|
|
@@ -5349,7 +6131,7 @@ const PageCanvas = react.forwardRef(
|
|
|
5349
6131
|
const clearTransforming = () => {
|
|
5350
6132
|
transformingIdsRef.current.clear();
|
|
5351
6133
|
};
|
|
5352
|
-
const
|
|
6134
|
+
const isCropGroup2 = (o) => {
|
|
5353
6135
|
var _a;
|
|
5354
6136
|
return !!(((_a = o == null ? void 0 : o._ct) == null ? void 0 : _a.isCropGroup) || (o == null ? void 0 : o.__cropGroup));
|
|
5355
6137
|
};
|
|
@@ -5357,9 +6139,9 @@ const PageCanvas = react.forwardRef(
|
|
|
5357
6139
|
var _a, _b, _c;
|
|
5358
6140
|
const t = opt.target;
|
|
5359
6141
|
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") {
|
|
5360
|
-
if (t &&
|
|
6142
|
+
if (t && isCropGroup2(t)) {
|
|
5361
6143
|
fabricCanvas._hoveredTarget = t;
|
|
5362
|
-
} else if ((t == null ? void 0 : t.group) &&
|
|
6144
|
+
} else if ((t == null ? void 0 : t.group) && isCropGroup2(t.group)) {
|
|
5363
6145
|
fabricCanvas._hoveredTarget = t.group;
|
|
5364
6146
|
}
|
|
5365
6147
|
return;
|
|
@@ -5368,7 +6150,7 @@ const PageCanvas = react.forwardRef(
|
|
|
5368
6150
|
const pointer = fabricCanvas.getPointer(opt.e);
|
|
5369
6151
|
const objects = fabricCanvas.getObjects();
|
|
5370
6152
|
for (const obj of objects) {
|
|
5371
|
-
if (
|
|
6153
|
+
if (isCropGroup2(obj) && obj.containsPoint(pointer)) {
|
|
5372
6154
|
fabricCanvas.setActiveObject(obj);
|
|
5373
6155
|
opt.target = obj;
|
|
5374
6156
|
fabricCanvas._hoveredTarget = obj;
|
|
@@ -5378,13 +6160,13 @@ const PageCanvas = react.forwardRef(
|
|
|
5378
6160
|
return;
|
|
5379
6161
|
}
|
|
5380
6162
|
const g = t.group;
|
|
5381
|
-
if (g &&
|
|
6163
|
+
if (g && isCropGroup2(g)) {
|
|
5382
6164
|
fabricCanvas.setActiveObject(g);
|
|
5383
6165
|
opt.target = g;
|
|
5384
6166
|
fabricCanvas._hoveredTarget = g;
|
|
5385
6167
|
return;
|
|
5386
6168
|
}
|
|
5387
|
-
if (
|
|
6169
|
+
if (isCropGroup2(t)) {
|
|
5388
6170
|
fabricCanvas.setActiveObject(t);
|
|
5389
6171
|
t.set({
|
|
5390
6172
|
selectable: true,
|
|
@@ -5927,8 +6709,8 @@ const PageCanvas = react.forwardRef(
|
|
|
5927
6709
|
const h = (active.height ?? 0) * (active.scaleY ?? 1);
|
|
5928
6710
|
const centerX = active.left ?? 0;
|
|
5929
6711
|
const centerY = active.top ?? 0;
|
|
5930
|
-
const groupLeft = centerX - w / 2;
|
|
5931
|
-
const groupTop = centerY - h / 2;
|
|
6712
|
+
const groupLeft = active.originX === "center" ? centerX - w / 2 : centerX;
|
|
6713
|
+
const groupTop = active.originY === "center" ? centerY - h / 2 : centerY;
|
|
5932
6714
|
const storePos = absoluteToStorePosition(groupLeft, groupTop, groupId, pageChildren2);
|
|
5933
6715
|
useEditorStore.getState().updateNode(groupId, { left: storePos.left, top: storePos.top }, { recordHistory: false, skipLayoutRecalc: true });
|
|
5934
6716
|
commitHistory();
|
|
@@ -5961,76 +6743,79 @@ const PageCanvas = react.forwardRef(
|
|
|
5961
6743
|
const commonAncestor = findCommonAncestorGroup(selectedElementIds, pageChildren2);
|
|
5962
6744
|
const candidateGroup = sameDirectParent ? parentGroups[0] : commonAncestor;
|
|
5963
6745
|
const candidateIsStack = candidateGroup && isStackLayoutMode(candidateGroup.layoutMode);
|
|
5964
|
-
const
|
|
5965
|
-
|
|
5966
|
-
const
|
|
5967
|
-
if (
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
6746
|
+
const allMembersSelected = (() => {
|
|
6747
|
+
if (!candidateGroup) return false;
|
|
6748
|
+
const memberIds = getAllElementIds(candidateGroup.children ?? []);
|
|
6749
|
+
if (memberIds.length === 0) return false;
|
|
6750
|
+
const selectedSet = new Set(selectedElementIds);
|
|
6751
|
+
return memberIds.every((mid) => selectedSet.has(mid));
|
|
6752
|
+
})();
|
|
6753
|
+
const groupToMove = candidateIsStack || allMembersSelected ? candidateGroup : null;
|
|
6754
|
+
if (groupToMove) {
|
|
6755
|
+
const groupAbs = getAbsoluteBounds(groupToMove, pageChildren2);
|
|
6756
|
+
let movedGroupLeft = groupAbs.left;
|
|
6757
|
+
let movedGroupTop = groupAbs.top;
|
|
6758
|
+
if (activeObj) {
|
|
6759
|
+
const selectionRect = activeObj.getBoundingRect();
|
|
6760
|
+
movedGroupLeft = selectionRect.left;
|
|
6761
|
+
movedGroupTop = selectionRect.top;
|
|
6762
|
+
} else if (firstId) {
|
|
6763
|
+
const firstNode = findNodeById(pageChildren2, firstId);
|
|
6764
|
+
if (firstNode) {
|
|
6765
|
+
movedGroupLeft = getAbsoluteBounds(firstNode, pageChildren2).left;
|
|
6766
|
+
movedGroupTop = getAbsoluteBounds(firstNode, pageChildren2).top;
|
|
5980
6767
|
}
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
obj.setCoords();
|
|
6014
|
-
}
|
|
6015
|
-
}
|
|
6016
|
-
if (isActiveSelection && activeObjects.length > 0) {
|
|
6017
|
-
const selection = new fabric__namespace.ActiveSelection([...activeObjects], { canvas: fabricCanvas });
|
|
6018
|
-
fabricCanvas.setActiveObject(selection);
|
|
6019
|
-
skipSelectionClearOnDiscardRef.current = false;
|
|
6768
|
+
}
|
|
6769
|
+
const deltaX = movedGroupLeft - groupAbs.left;
|
|
6770
|
+
const deltaY = movedGroupTop - groupAbs.top;
|
|
6771
|
+
const hadScale = isActiveSelection && activeObj && (Math.abs((activeObj.scaleX ?? 1) - 1) > 0.01 || Math.abs((activeObj.scaleY ?? 1) - 1) > 0.01);
|
|
6772
|
+
if (!hadScale && (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1)) {
|
|
6773
|
+
if (typeof console !== "undefined" && console.log) {
|
|
6774
|
+
console.log("[object:modified] plain-groups: moving group id=", groupToMove.id, "newLeft=", (groupToMove.left ?? 0) + deltaX, "newTop=", (groupToMove.top ?? 0) + deltaY);
|
|
6775
|
+
}
|
|
6776
|
+
const { updateNode: updateNodeStore, commitHistory: commitHistoryStore, getCurrentElements } = useEditorStore.getState();
|
|
6777
|
+
const newLeft = (groupToMove.left ?? 0) + deltaX;
|
|
6778
|
+
const newTop = (groupToMove.top ?? 0) + deltaY;
|
|
6779
|
+
updateNodeStore(groupToMove.id, { left: newLeft, top: newTop }, { recordHistory: false, skipLayoutRecalc: true });
|
|
6780
|
+
commitHistoryStore();
|
|
6781
|
+
if (isActiveSelection && activeObj instanceof fabric__namespace.ActiveSelection) {
|
|
6782
|
+
skipSelectionClearOnDiscardRef.current = true;
|
|
6783
|
+
fabricCanvas.discardActiveObject();
|
|
6784
|
+
}
|
|
6785
|
+
const stateAfter = useEditorStore.getState();
|
|
6786
|
+
const pageAfter = stateAfter.canvas.pages.find((p) => p.id === pageId);
|
|
6787
|
+
const pageChildrenAfter = (pageAfter == null ? void 0 : pageAfter.children) ?? [];
|
|
6788
|
+
for (const obj of activeObjects) {
|
|
6789
|
+
const objId = getObjectId(obj);
|
|
6790
|
+
if (!objId || objId === "__background__") continue;
|
|
6791
|
+
const node = findNodeById(pageChildrenAfter, objId);
|
|
6792
|
+
if (node) {
|
|
6793
|
+
const abs = getAbsoluteBounds(node, pageChildrenAfter);
|
|
6794
|
+
const w = (obj.width ?? 0) * (obj.scaleX ?? 1);
|
|
6795
|
+
const h = (obj.height ?? 0) * (obj.scaleY ?? 1);
|
|
6796
|
+
const nextLeft = obj.originX === "center" ? abs.left + w / 2 : abs.left;
|
|
6797
|
+
const nextTop = obj.originY === "center" ? abs.top + h / 2 : abs.top;
|
|
6798
|
+
obj.set({ left: nextLeft, top: nextTop });
|
|
6799
|
+
obj.setCoords();
|
|
6020
6800
|
}
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6801
|
+
}
|
|
6802
|
+
if (isActiveSelection && activeObjects.length > 0) {
|
|
6803
|
+
const selection = new fabric__namespace.ActiveSelection([...activeObjects], { canvas: fabricCanvas });
|
|
6804
|
+
fabricCanvas.setActiveObject(selection);
|
|
6805
|
+
skipSelectionClearOnDiscardRef.current = false;
|
|
6806
|
+
}
|
|
6807
|
+
fabricCanvas.requestRenderAll();
|
|
6808
|
+
elementsRef.current = getCurrentElements();
|
|
6809
|
+
for (const obj of activeObjects) {
|
|
6810
|
+
const objId = getObjectId(obj);
|
|
6811
|
+
if (objId && objId !== "__background__") {
|
|
6812
|
+
justModifiedIdsRef.current.add(objId);
|
|
6813
|
+
modifiedIdsThisRound.add(objId);
|
|
6029
6814
|
}
|
|
6030
|
-
setTimeout(() => modifiedIdsThisRound.forEach((id) => justModifiedIdsRef.current.delete(id)), 150);
|
|
6031
|
-
unlockEditsSoon();
|
|
6032
|
-
return;
|
|
6033
6815
|
}
|
|
6816
|
+
setTimeout(() => modifiedIdsThisRound.forEach((id) => justModifiedIdsRef.current.delete(id)), 150);
|
|
6817
|
+
unlockEditsSoon();
|
|
6818
|
+
return;
|
|
6034
6819
|
}
|
|
6035
6820
|
}
|
|
6036
6821
|
}
|
|
@@ -6109,9 +6894,13 @@ const PageCanvas = react.forwardRef(
|
|
|
6109
6894
|
finalScaleX = 1;
|
|
6110
6895
|
finalScaleY = 1;
|
|
6111
6896
|
obj.set({ scaleX: 1, scaleY: 1 });
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6897
|
+
if (!isActiveSelection) {
|
|
6898
|
+
updateCoverLayout(obj);
|
|
6899
|
+
obj.__lastResizeHandle = null;
|
|
6900
|
+
fabricCanvas.setActiveObject(obj);
|
|
6901
|
+
} else {
|
|
6902
|
+
obj.__lastResizeHandle = null;
|
|
6903
|
+
}
|
|
6115
6904
|
}
|
|
6116
6905
|
} else if (obj instanceof fabric__namespace.FabricImage) {
|
|
6117
6906
|
if ((sourceElement == null ? void 0 : sourceElement.smartElementType) && sourceElement.smartProps) {
|
|
@@ -6254,6 +7043,13 @@ const PageCanvas = react.forwardRef(
|
|
|
6254
7043
|
fabricCanvas.on("mouse:dblclick", (e) => {
|
|
6255
7044
|
if (!isActiveRef.current || !allowEditing) return;
|
|
6256
7045
|
let target = e.target;
|
|
7046
|
+
if (target && target instanceof fabric__namespace.Group && target.__cropGroup) {
|
|
7047
|
+
const ct = target.__cropData;
|
|
7048
|
+
if ((ct == null ? void 0 : ct._img) && !isCropGroupInCropMode(target)) {
|
|
7049
|
+
enterCropMode(target);
|
|
7050
|
+
return;
|
|
7051
|
+
}
|
|
7052
|
+
}
|
|
6257
7053
|
if (!target || !(target instanceof fabric__namespace.Textbox)) {
|
|
6258
7054
|
const active = fabricCanvas.getActiveObject();
|
|
6259
7055
|
if (active instanceof fabric__namespace.Textbox) target = active;
|
|
@@ -6446,9 +7242,9 @@ const PageCanvas = react.forwardRef(
|
|
|
6446
7242
|
const activeId = activeBeforeSync ? getObjectId(activeBeforeSync) : null;
|
|
6447
7243
|
const isActiveTextBeingEdited = activeId && editingTextIdRef.current === activeId && activeBeforeSync instanceof fabric__namespace.Textbox;
|
|
6448
7244
|
if (!skipRestoreSelection && activeBeforeSync && activeStillOnCanvas && !isActiveTextBeingEdited) {
|
|
6449
|
-
const
|
|
7245
|
+
const isCropGroup2 = ((_d = activeBeforeSync._ct) == null ? void 0 : _d.isCropGroup) || activeBeforeSync.__cropGroup;
|
|
6450
7246
|
const isSectionGroup = activeId && sectionGroupIds.has(activeId);
|
|
6451
|
-
if ((
|
|
7247
|
+
if ((isCropGroup2 || !shouldSkipUpdates2) && !isSectionGroup) {
|
|
6452
7248
|
fc.setActiveObject(activeBeforeSync);
|
|
6453
7249
|
}
|
|
6454
7250
|
}
|
|
@@ -6569,11 +7365,12 @@ const PageCanvas = react.forwardRef(
|
|
|
6569
7365
|
const sourceUrlChanged = currentUrlNormalized !== storedUrlNormalized;
|
|
6570
7366
|
const needsReload = sourceUrlChanged || colorMapChanged;
|
|
6571
7367
|
const hasUrl = currentUrlNormalized !== "";
|
|
6572
|
-
const
|
|
6573
|
-
const
|
|
7368
|
+
const isCropGroup2 = existingObj instanceof fabric__namespace.Group && existingObj.__cropGroup;
|
|
7369
|
+
const isPlaceholderGroup = isEmptyImagePlaceholderGroup(existingObj);
|
|
7370
|
+
const isPlaceholder = isPlaceholderGroup || !(existingObj instanceof fabric__namespace.FabricImage) || existingObj instanceof fabric__namespace.Group && !isCropGroup2;
|
|
6574
7371
|
const hadUrlBefore = storedImageUrl && String(storedImageUrl).trim() !== "";
|
|
6575
7372
|
if (!hasUrl && hadUrlBefore) {
|
|
6576
|
-
const placeholder =
|
|
7373
|
+
const placeholder = isCropGroup2 ? createImagePlaceholderForGroup(element) : createImagePlaceholder(element);
|
|
6577
7374
|
setObjectData(placeholder, element.id);
|
|
6578
7375
|
placeholder.__imageSrc = "";
|
|
6579
7376
|
const idx = fc.getObjects().indexOf(existingObj);
|
|
@@ -6589,13 +7386,13 @@ const PageCanvas = react.forwardRef(
|
|
|
6589
7386
|
const imageFitForReplace = element.imageFit || ((_e = element.style) == null ? void 0 : _e.imageFit) || "cover";
|
|
6590
7387
|
const clipShapeForReplace = element.clipShape ?? ((_f = element.style) == null ? void 0 : _f.imageFrameShape) ?? (isPreviewMode ? "rectangle" : "none");
|
|
6591
7388
|
const needCropGroupForElement = imageFitForReplace !== "fill" || clipShapeForReplace && clipShapeForReplace !== "none";
|
|
6592
|
-
const plainImageNeedsCropGroup = hasUrl && !
|
|
7389
|
+
const plainImageNeedsCropGroup = hasUrl && !isCropGroup2 && existingObj instanceof fabric__namespace.FabricImage && needCropGroupForElement;
|
|
6593
7390
|
if (hasUrl && (needsReload || isPlaceholder || plainImageNeedsCropGroup)) {
|
|
6594
7391
|
if (needsReload && !isBeingTransformed && (!wasJustModified || sourceUrlChanged)) {
|
|
6595
7392
|
loadImageAsync(element, existingObj, fc);
|
|
6596
7393
|
} else if (plainImageNeedsCropGroup) {
|
|
6597
7394
|
loadImageAsync(element, existingObj, fc);
|
|
6598
|
-
} else if (!needsReload &&
|
|
7395
|
+
} else if (!needsReload && isCropGroup2) {
|
|
6599
7396
|
const ct = existingObj.__cropData;
|
|
6600
7397
|
if (ct) {
|
|
6601
7398
|
const resolvedCrop = pageTree.length > 0 ? getNodeBounds(element, pageTree) : { width: typeof element.width === "number" ? element.width : 200, height: typeof element.height === "number" ? element.height : 50 };
|
|
@@ -6641,6 +7438,22 @@ const PageCanvas = react.forwardRef(
|
|
|
6641
7438
|
existingObj.set({ opacity: clampedOpacity });
|
|
6642
7439
|
existingObj.set({ width: ct.frameW, height: ct.frameH });
|
|
6643
7440
|
existingObj.set({ backgroundColor: "transparent" });
|
|
7441
|
+
const liveMask = existingObj.clipPath;
|
|
7442
|
+
const liveMaskUrl = existingObj.__svgMaskUrl;
|
|
7443
|
+
const liveMaskType = existingObj.__svgMaskType;
|
|
7444
|
+
const schemaMaskUrl = element.maskSvgUrl;
|
|
7445
|
+
const schemaMaskType = element.maskType || "shape";
|
|
7446
|
+
if (liveMask && liveMask.__svgMask && !schemaMaskUrl) {
|
|
7447
|
+
existingObj.clipPath = void 0;
|
|
7448
|
+
delete existingObj.__svgMaskUrl;
|
|
7449
|
+
delete existingObj.__svgMaskType;
|
|
7450
|
+
existingObj.dirty = true;
|
|
7451
|
+
} else if (schemaMaskUrl && (schemaMaskUrl !== liveMaskUrl || schemaMaskType !== liveMaskType)) {
|
|
7452
|
+
Promise.resolve().then(() => svgMaskApply).then(({ applyMaskToCropGroup: applyMaskToCropGroup2 }) => {
|
|
7453
|
+
applyMaskToCropGroup2(existingObj, schemaMaskUrl, schemaMaskType).catch(() => {
|
|
7454
|
+
});
|
|
7455
|
+
});
|
|
7456
|
+
}
|
|
6644
7457
|
if (shapeChanged) {
|
|
6645
7458
|
const needsNewClipPath = !existingObj.clipPath || newShape === "circle" && !(existingObj.clipPath instanceof fabric__namespace.Ellipse) || newShape !== "circle" && !(existingObj.clipPath instanceof fabric__namespace.Rect);
|
|
6646
7459
|
if (needsNewClipPath) {
|
|
@@ -6677,11 +7490,7 @@ const PageCanvas = react.forwardRef(
|
|
|
6677
7490
|
}
|
|
6678
7491
|
updateCoverLayout(existingObj);
|
|
6679
7492
|
if (allowEditing) {
|
|
6680
|
-
|
|
6681
|
-
installCanvaMaskControls(existingObj);
|
|
6682
|
-
} else {
|
|
6683
|
-
setSimpleScaleControls(existingObj);
|
|
6684
|
-
}
|
|
7493
|
+
installCanvaMaskControls(existingObj);
|
|
6685
7494
|
} else {
|
|
6686
7495
|
existingObj.set({
|
|
6687
7496
|
hasControls: false,
|
|
@@ -6730,6 +7539,45 @@ const PageCanvas = react.forwardRef(
|
|
|
6730
7539
|
}
|
|
6731
7540
|
placeholder.__imageSrc = void 0;
|
|
6732
7541
|
} else if (!isBeingTransformed && !isBeingTextEdited && !shouldSkipUpdates2) {
|
|
7542
|
+
if (isPlaceholderGroup) {
|
|
7543
|
+
const storePosImg = pageChildren ? (() => {
|
|
7544
|
+
const node = findNodeById(pageChildren, element.id);
|
|
7545
|
+
return node ? getAbsoluteBounds(node, pageChildren) : { left: element.left ?? 0, top: element.top ?? 0 };
|
|
7546
|
+
})() : { left: element.left ?? 0, top: element.top ?? 0 };
|
|
7547
|
+
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 };
|
|
7548
|
+
const hasExplicitSize = typeof element.width === "number" && Number.isFinite(element.width) && element.width > 0 && typeof element.height === "number" && Number.isFinite(element.height) && element.height > 0;
|
|
7549
|
+
const minVisiblePlaceholder = hasExplicitSize ? 1 : 20;
|
|
7550
|
+
const nextWidth = Math.max(minVisiblePlaceholder, Number(resolvedSizeImg.width) || 200);
|
|
7551
|
+
const nextHeight = Math.max(minVisiblePlaceholder, Number(resolvedSizeImg.height) || 50);
|
|
7552
|
+
const isDynamicField = dynamicFieldIds.includes(element.id);
|
|
7553
|
+
const canBeEvented = isEditorMode || isPreviewMode && isDynamicField;
|
|
7554
|
+
updateEmptyPlaceholderLayout(existingObj, nextWidth, nextHeight);
|
|
7555
|
+
existingObj.set({
|
|
7556
|
+
left: storePosImg.left + nextWidth / 2,
|
|
7557
|
+
top: storePosImg.top + nextHeight / 2,
|
|
7558
|
+
originX: "center",
|
|
7559
|
+
originY: "center",
|
|
7560
|
+
angle: element.angle ?? 0,
|
|
7561
|
+
opacity: isHidden ? 0 : element.opacity ?? 1,
|
|
7562
|
+
flipX: element.flipX ?? false,
|
|
7563
|
+
flipY: element.flipY ?? false,
|
|
7564
|
+
selectable: allowSelection && !isHidden,
|
|
7565
|
+
evented: canBeEvented && !isHidden,
|
|
7566
|
+
hasControls: allowEditing && !isHidden,
|
|
7567
|
+
hasBorders: allowEditing && !isHidden,
|
|
7568
|
+
interactive: allowEditing && !isHidden,
|
|
7569
|
+
subTargetCheck: false,
|
|
7570
|
+
lockMovementX: !allowSelection,
|
|
7571
|
+
lockMovementY: !allowSelection,
|
|
7572
|
+
hoverCursor: isDynamicField && isPreviewMode ? "pointer" : void 0
|
|
7573
|
+
});
|
|
7574
|
+
if (allowEditing) {
|
|
7575
|
+
installCanvaMaskControls(existingObj);
|
|
7576
|
+
}
|
|
7577
|
+
existingObj.setCoords();
|
|
7578
|
+
fc.requestRenderAll();
|
|
7579
|
+
continue;
|
|
7580
|
+
}
|
|
6733
7581
|
if (existingObj instanceof fabric__namespace.Group && existingObj.__cropGroup) {
|
|
6734
7582
|
existingObj.set({
|
|
6735
7583
|
flipX: element.flipX ?? false,
|
|
@@ -6904,7 +7752,14 @@ const PageCanvas = react.forwardRef(
|
|
|
6904
7752
|
const node = findNodeById(pageTree, element.id);
|
|
6905
7753
|
return node ? getAbsoluteBounds(node, pageTree) : { left: element.left ?? 0, top: element.top ?? 0 };
|
|
6906
7754
|
})() : { left: element.left ?? 0, top: element.top ?? 0 };
|
|
6907
|
-
placeholder.
|
|
7755
|
+
const placeholderWidth = Number((placeholder.width ?? 0) * (placeholder.scaleX ?? 1));
|
|
7756
|
+
const placeholderHeight = Number((placeholder.height ?? 0) * (placeholder.scaleY ?? 1));
|
|
7757
|
+
placeholder.set({
|
|
7758
|
+
left: absPosImg.left + placeholderWidth / 2,
|
|
7759
|
+
top: absPosImg.top + placeholderHeight / 2,
|
|
7760
|
+
originX: "center",
|
|
7761
|
+
originY: "center"
|
|
7762
|
+
});
|
|
6908
7763
|
placeholder.setCoords();
|
|
6909
7764
|
const imageUrl = element.src || element.imageUrl;
|
|
6910
7765
|
placeholder.__imageSrc = imageUrl;
|
|
@@ -6997,8 +7852,8 @@ const PageCanvas = react.forwardRef(
|
|
|
6997
7852
|
isRebuildingRef.current = false;
|
|
6998
7853
|
fc.requestRenderAll();
|
|
6999
7854
|
if (activeBeforeSync && fc.getObjects().includes(activeBeforeSync)) {
|
|
7000
|
-
const
|
|
7001
|
-
if (
|
|
7855
|
+
const isCropGroup2 = ((_i = activeBeforeSync._ct) == null ? void 0 : _i.isCropGroup) || activeBeforeSync.__cropGroup;
|
|
7856
|
+
if (isCropGroup2) {
|
|
7002
7857
|
fc.setActiveObject(activeBeforeSync);
|
|
7003
7858
|
fc.requestRenderAll();
|
|
7004
7859
|
}
|
|
@@ -7425,11 +8280,7 @@ const PageCanvas = react.forwardRef(
|
|
|
7425
8280
|
obj.clipPath.dirty = true;
|
|
7426
8281
|
obj.clipPath.setCoords();
|
|
7427
8282
|
}
|
|
7428
|
-
|
|
7429
|
-
installCanvaMaskControls(obj);
|
|
7430
|
-
} else {
|
|
7431
|
-
setSimpleScaleControls(obj);
|
|
7432
|
-
}
|
|
8283
|
+
installCanvaMaskControls(obj);
|
|
7433
8284
|
obj.set({
|
|
7434
8285
|
selectable: true,
|
|
7435
8286
|
evented: true,
|
|
@@ -8204,11 +9055,7 @@ const PageCanvas = react.forwardRef(
|
|
|
8204
9055
|
evented: canBeEvented && !isHidden
|
|
8205
9056
|
});
|
|
8206
9057
|
} else {
|
|
8207
|
-
|
|
8208
|
-
installCanvaMaskControls(cropGroup);
|
|
8209
|
-
} else {
|
|
8210
|
-
setSimpleScaleControls(cropGroup);
|
|
8211
|
-
}
|
|
9058
|
+
installCanvaMaskControls(cropGroup);
|
|
8212
9059
|
}
|
|
8213
9060
|
const cropImg = (_l = cropGroup.__cropData) == null ? void 0 : _l._img;
|
|
8214
9061
|
if (cropImg) {
|
|
@@ -8228,6 +9075,16 @@ const PageCanvas = react.forwardRef(
|
|
|
8228
9075
|
useEditorStore.getState().updateElement(element.id, { imageNaturalWidth: nw, imageNaturalHeight: nh }, { recordHistory: false });
|
|
8229
9076
|
}
|
|
8230
9077
|
}
|
|
9078
|
+
const persistedMaskUrl = element.maskSvgUrl;
|
|
9079
|
+
if (persistedMaskUrl && typeof persistedMaskUrl === "string") {
|
|
9080
|
+
try {
|
|
9081
|
+
const { applyMaskToCropGroup: applyMaskToCropGroup2 } = await Promise.resolve().then(() => svgMaskApply);
|
|
9082
|
+
const persistedMaskType = element.maskType || "shape";
|
|
9083
|
+
await applyMaskToCropGroup2(cropGroup, persistedMaskUrl, persistedMaskType);
|
|
9084
|
+
} catch (err) {
|
|
9085
|
+
console.warn("[mask] failed to re-apply persisted mask", err);
|
|
9086
|
+
}
|
|
9087
|
+
}
|
|
8231
9088
|
finalObject = cropGroup;
|
|
8232
9089
|
} else {
|
|
8233
9090
|
const isHiddenFill = !element.visible;
|
|
@@ -10516,11 +11373,11 @@ async function resolveTemplateData(options) {
|
|
|
10516
11373
|
}
|
|
10517
11374
|
async function resolveFromForm(options) {
|
|
10518
11375
|
var _a, _b, _c;
|
|
10519
|
-
const { templateId, formSchemaId, sectionState, themeId, supabaseUrl, supabaseAnonKey } = options;
|
|
11376
|
+
const { templateId, formSchemaId, sectionState, themeId, supabaseUrl, supabaseAnonKey, prefetched } = options;
|
|
10520
11377
|
const [templateRow, formSchemaRow, defaultForm] = await Promise.all([
|
|
10521
|
-
fetchRow(supabaseUrl, supabaseAnonKey, "templates", templateId),
|
|
10522
|
-
fetchRow(supabaseUrl, supabaseAnonKey, "form_schemas", formSchemaId),
|
|
10523
|
-
fetchDefaultForm(supabaseUrl, supabaseAnonKey, formSchemaId)
|
|
11378
|
+
(prefetched == null ? void 0 : prefetched.templateRow) ? Promise.resolve(prefetched.templateRow) : fetchRow(supabaseUrl, supabaseAnonKey, "templates", templateId),
|
|
11379
|
+
(prefetched == null ? void 0 : prefetched.formSchemaRow) !== void 0 ? Promise.resolve(prefetched.formSchemaRow) : fetchRow(supabaseUrl, supabaseAnonKey, "form_schemas", formSchemaId),
|
|
11380
|
+
(prefetched == null ? void 0 : prefetched.defaultForm) !== void 0 ? Promise.resolve(prefetched.defaultForm) : fetchDefaultForm(supabaseUrl, supabaseAnonKey, formSchemaId)
|
|
10524
11381
|
]);
|
|
10525
11382
|
const templateConfig = templateRow.config;
|
|
10526
11383
|
const templateFormSchema = templateRow.form_schema;
|
|
@@ -11133,14 +11990,15 @@ class PixldocsRenderer {
|
|
|
11133
11990
|
* This is the primary external API for the package.
|
|
11134
11991
|
*/
|
|
11135
11992
|
async renderFromForm(options) {
|
|
11136
|
-
const { templateId, formSchemaId, sectionState, themeId, watermark, ...renderOpts } = options;
|
|
11993
|
+
const { templateId, formSchemaId, sectionState, themeId, watermark, prefetched, ...renderOpts } = options;
|
|
11137
11994
|
const resolved = await resolveFromForm({
|
|
11138
11995
|
templateId,
|
|
11139
11996
|
formSchemaId,
|
|
11140
11997
|
sectionState,
|
|
11141
11998
|
themeId,
|
|
11142
11999
|
supabaseUrl: this.config.supabaseUrl,
|
|
11143
|
-
supabaseAnonKey: this.config.supabaseAnonKey
|
|
12000
|
+
supabaseAnonKey: this.config.supabaseAnonKey,
|
|
12001
|
+
prefetched
|
|
11144
12002
|
});
|
|
11145
12003
|
const shouldWatermark = watermark ?? resolved.price > 0;
|
|
11146
12004
|
let configToRender = resolved.config;
|
|
@@ -11185,14 +12043,15 @@ class PixldocsRenderer {
|
|
|
11185
12043
|
* Resolve from V2 sectionState and return SVGs for all pages (for server vector PDF).
|
|
11186
12044
|
*/
|
|
11187
12045
|
async renderSvgsFromForm(options) {
|
|
11188
|
-
const { templateId, formSchemaId, sectionState, themeId, watermark } = options;
|
|
12046
|
+
const { templateId, formSchemaId, sectionState, themeId, watermark, prefetched } = options;
|
|
11189
12047
|
const resolved = await resolveFromForm({
|
|
11190
12048
|
templateId,
|
|
11191
12049
|
formSchemaId,
|
|
11192
12050
|
sectionState,
|
|
11193
12051
|
themeId,
|
|
11194
12052
|
supabaseUrl: this.config.supabaseUrl,
|
|
11195
|
-
supabaseAnonKey: this.config.supabaseAnonKey
|
|
12053
|
+
supabaseAnonKey: this.config.supabaseAnonKey,
|
|
12054
|
+
prefetched
|
|
11196
12055
|
});
|
|
11197
12056
|
const shouldWatermark = watermark ?? resolved.price > 0;
|
|
11198
12057
|
let configToRender = resolved.config;
|
|
@@ -11216,14 +12075,15 @@ class PixldocsRenderer {
|
|
|
11216
12075
|
* This is the primary PDF export API — mirrors renderFromForm() but returns a PDF.
|
|
11217
12076
|
*/
|
|
11218
12077
|
async renderPdfFromForm(options) {
|
|
11219
|
-
const { templateId, formSchemaId, sectionState, themeId, watermark, title, fontBaseUrl } = options;
|
|
12078
|
+
const { templateId, formSchemaId, sectionState, themeId, watermark, prefetched, title, fontBaseUrl } = options;
|
|
11220
12079
|
const resolved = await resolveFromForm({
|
|
11221
12080
|
templateId,
|
|
11222
12081
|
formSchemaId,
|
|
11223
12082
|
sectionState,
|
|
11224
12083
|
themeId,
|
|
11225
12084
|
supabaseUrl: this.config.supabaseUrl,
|
|
11226
|
-
supabaseAnonKey: this.config.supabaseAnonKey
|
|
12085
|
+
supabaseAnonKey: this.config.supabaseAnonKey,
|
|
12086
|
+
prefetched
|
|
11227
12087
|
});
|
|
11228
12088
|
const shouldWatermark = watermark ?? resolved.price > 0;
|
|
11229
12089
|
let configToRender = resolved.config;
|