@overtone-art/canvas-editor-core 0.8.4 → 0.8.6
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.d.mts +9 -69
- package/dist/index.d.ts +9 -69
- package/dist/index.global.js +53 -53
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +294 -123
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +282 -111
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/{types-BeaEHTFc.d.mts → types-F2GH3dcU.d.mts} +73 -2
- package/dist/{types-BeaEHTFc.d.ts → types-F2GH3dcU.d.ts} +73 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4083,10 +4083,127 @@ var MaskController = class {
|
|
|
4083
4083
|
};
|
|
4084
4084
|
|
|
4085
4085
|
// src/masks/manager.ts
|
|
4086
|
-
var
|
|
4086
|
+
var import_fabric21 = require("fabric");
|
|
4087
4087
|
|
|
4088
4088
|
// src/masks/compose.ts
|
|
4089
|
+
var import_fabric16 = require("fabric");
|
|
4090
|
+
|
|
4091
|
+
// src/gradient.ts
|
|
4089
4092
|
var import_fabric15 = require("fabric");
|
|
4093
|
+
var DEFAULT_GRADIENT_CONFIG = {
|
|
4094
|
+
kind: "linear",
|
|
4095
|
+
angle: 90,
|
|
4096
|
+
center: { x: 0.5, y: 0.5 },
|
|
4097
|
+
radius: 0.5,
|
|
4098
|
+
stops: [
|
|
4099
|
+
{ color: "#ffffff", opacity: 1, position: 0 },
|
|
4100
|
+
{ color: "#000000", opacity: 1, position: 1 }
|
|
4101
|
+
]
|
|
4102
|
+
};
|
|
4103
|
+
var MIN_RADIUS = 1e-3;
|
|
4104
|
+
var MAX_GRADIENT_STOPS = 256;
|
|
4105
|
+
var FALLBACK_STOP_COLOR = DEFAULT_GRADIENT_CONFIG.stops[0].color;
|
|
4106
|
+
function linearCoords(angle, box) {
|
|
4107
|
+
const radians = angle * Math.PI / 180;
|
|
4108
|
+
const sin = Math.sin(radians);
|
|
4109
|
+
const cos = Math.cos(radians);
|
|
4110
|
+
const vx = box.width * sin;
|
|
4111
|
+
const vy = -box.height * cos;
|
|
4112
|
+
const length = Math.abs(box.width * sin) + Math.abs(box.height * cos);
|
|
4113
|
+
const scale = length / (vx * vx + vy * vy || 1);
|
|
4114
|
+
return {
|
|
4115
|
+
x1: 0.5 - scale * vx / 2,
|
|
4116
|
+
y1: 0.5 - scale * vy / 2,
|
|
4117
|
+
x2: 0.5 + scale * vx / 2,
|
|
4118
|
+
y2: 0.5 + scale * vy / 2
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
function angleFromCoords(coords, box) {
|
|
4122
|
+
const dx = (coords.x2 - coords.x1) / (box.width || 1);
|
|
4123
|
+
const dy = (coords.y2 - coords.y1) / (box.height || 1);
|
|
4124
|
+
const degrees = Math.atan2(dx, -dy) * 180 / Math.PI;
|
|
4125
|
+
return (degrees + 360) % 360;
|
|
4126
|
+
}
|
|
4127
|
+
function stopColor(stop) {
|
|
4128
|
+
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4129
|
+
const color = new import_fabric15.Color(source);
|
|
4130
|
+
const opacity = Number.isFinite(stop.opacity) ? stop.opacity : 1;
|
|
4131
|
+
color.setAlpha(clamp(opacity, 0, 1));
|
|
4132
|
+
return color.toRgba();
|
|
4133
|
+
}
|
|
4134
|
+
function toFabricGradient(config, box) {
|
|
4135
|
+
const colorStops = [...config.stops].sort((a, b) => a.position - b.position).slice(0, MAX_GRADIENT_STOPS).map((stop) => {
|
|
4136
|
+
const position = Number.isFinite(stop.position) ? stop.position : 0;
|
|
4137
|
+
return { offset: clamp(position, 0, 1), color: stopColor(stop) };
|
|
4138
|
+
});
|
|
4139
|
+
if (config.kind === "radial") {
|
|
4140
|
+
const radius = Math.max(MIN_RADIUS, config.radius);
|
|
4141
|
+
return new import_fabric15.Gradient({
|
|
4142
|
+
type: "radial",
|
|
4143
|
+
gradientUnits: "percentage",
|
|
4144
|
+
coords: {
|
|
4145
|
+
x1: config.center.x,
|
|
4146
|
+
y1: config.center.y,
|
|
4147
|
+
r1: 0,
|
|
4148
|
+
x2: config.center.x,
|
|
4149
|
+
y2: config.center.y,
|
|
4150
|
+
r2: radius
|
|
4151
|
+
},
|
|
4152
|
+
colorStops
|
|
4153
|
+
});
|
|
4154
|
+
}
|
|
4155
|
+
return new import_fabric15.Gradient({
|
|
4156
|
+
type: "linear",
|
|
4157
|
+
gradientUnits: "percentage",
|
|
4158
|
+
coords: linearCoords(config.angle, box),
|
|
4159
|
+
colorStops
|
|
4160
|
+
});
|
|
4161
|
+
}
|
|
4162
|
+
function toFabricMaskGradient(config, box) {
|
|
4163
|
+
const gradient = toFabricGradient(config, box);
|
|
4164
|
+
gradient.colorStops = gradient.colorStops.map((stop) => {
|
|
4165
|
+
const color = new import_fabric15.Color(stop.color);
|
|
4166
|
+
const [red, green, blue] = color.getSource();
|
|
4167
|
+
const luminance = (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 255;
|
|
4168
|
+
color.setSource([255, 255, 255, clamp(luminance * color.getAlpha(), 0, 1)]);
|
|
4169
|
+
return { offset: stop.offset, color: color.toRgba() };
|
|
4170
|
+
});
|
|
4171
|
+
return gradient;
|
|
4172
|
+
}
|
|
4173
|
+
function readGradientConfig(object) {
|
|
4174
|
+
const fill = object.fill;
|
|
4175
|
+
if (!fill || typeof fill === "string" || !(fill instanceof import_fabric15.Gradient)) return null;
|
|
4176
|
+
const stops = fill.colorStops.map((stop) => {
|
|
4177
|
+
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4178
|
+
const color = new import_fabric15.Color(source);
|
|
4179
|
+
const offset = Number.isFinite(stop.offset) ? stop.offset : 0;
|
|
4180
|
+
return {
|
|
4181
|
+
color: `#${color.toHex().toLowerCase()}`,
|
|
4182
|
+
opacity: color.getAlpha(),
|
|
4183
|
+
position: clamp(offset, 0, 1)
|
|
4184
|
+
};
|
|
4185
|
+
});
|
|
4186
|
+
if (fill.type === "radial") {
|
|
4187
|
+
const { x1, y1, r2 } = fill.coords;
|
|
4188
|
+
return {
|
|
4189
|
+
kind: "radial",
|
|
4190
|
+
angle: DEFAULT_GRADIENT_CONFIG.angle,
|
|
4191
|
+
center: { x: x1, y: y1 },
|
|
4192
|
+
radius: r2,
|
|
4193
|
+
stops
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
4196
|
+
const box = { width: object.width || 1, height: object.height || 1 };
|
|
4197
|
+
return {
|
|
4198
|
+
kind: "linear",
|
|
4199
|
+
angle: angleFromCoords(fill.coords, box),
|
|
4200
|
+
center: { ...DEFAULT_GRADIENT_CONFIG.center },
|
|
4201
|
+
radius: DEFAULT_GRADIENT_CONFIG.radius,
|
|
4202
|
+
stops
|
|
4203
|
+
};
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
// src/masks/compose.ts
|
|
4090
4207
|
var MODE_OPERATION = {
|
|
4091
4208
|
add: "source-over",
|
|
4092
4209
|
subtract: "destination-out",
|
|
@@ -4096,7 +4213,7 @@ function neutralize(child) {
|
|
|
4096
4213
|
child.set({ opacity: 0, globalCompositeOperation: "source-over" });
|
|
4097
4214
|
}
|
|
4098
4215
|
function baseRect(box) {
|
|
4099
|
-
return new
|
|
4216
|
+
return new import_fabric16.Rect({
|
|
4100
4217
|
left: box.left,
|
|
4101
4218
|
top: box.top,
|
|
4102
4219
|
width: Math.max(1, box.width),
|
|
@@ -4116,14 +4233,22 @@ function composeMaskGroup(children, entries, options) {
|
|
|
4116
4233
|
neutralize(child);
|
|
4117
4234
|
return;
|
|
4118
4235
|
}
|
|
4236
|
+
if (entry.gradient) {
|
|
4237
|
+
child.set({
|
|
4238
|
+
fill: toFabricMaskGradient(entry.gradient, {
|
|
4239
|
+
width: Math.max(1, child.width ?? 1),
|
|
4240
|
+
height: Math.max(1, child.height ?? 1)
|
|
4241
|
+
})
|
|
4242
|
+
});
|
|
4243
|
+
}
|
|
4119
4244
|
child.set({
|
|
4120
4245
|
opacity: entry.opacity,
|
|
4121
4246
|
globalCompositeOperation: MODE_OPERATION[entry.mode] ?? "source-over"
|
|
4122
4247
|
});
|
|
4123
4248
|
});
|
|
4124
4249
|
const first = entries.find((entry) => entry.visible);
|
|
4125
|
-
const withBase = first
|
|
4126
|
-
return new
|
|
4250
|
+
const withBase = !first || first.mode !== "add" ? [baseRect(options.box), ...children] : [...children];
|
|
4251
|
+
return new import_fabric16.Group(withBase, {
|
|
4127
4252
|
absolutePositioned: options.absolute,
|
|
4128
4253
|
// Cached, so the children's compositing operations resolve against each
|
|
4129
4254
|
// other instead of against the page underneath the mask.
|
|
@@ -4137,11 +4262,105 @@ function needsAbsoluteSpace(entries) {
|
|
|
4137
4262
|
}
|
|
4138
4263
|
|
|
4139
4264
|
// src/masks/edit.ts
|
|
4140
|
-
var
|
|
4265
|
+
var import_fabric17 = require("fabric");
|
|
4266
|
+
|
|
4267
|
+
// src/masks/edit-viewport.ts
|
|
4268
|
+
var MIN_PADDING = 48;
|
|
4269
|
+
var MAX_PADDING = 512;
|
|
4270
|
+
var PADDING_FRACTION = 0.35;
|
|
4271
|
+
var OUTSIDE_SHADE = "rgba(15, 15, 15, 0.55)";
|
|
4272
|
+
var MaskEditViewport = class {
|
|
4273
|
+
constructor(canvas) {
|
|
4274
|
+
this.canvas = canvas;
|
|
4275
|
+
}
|
|
4276
|
+
canvas;
|
|
4277
|
+
state = null;
|
|
4278
|
+
open(handle) {
|
|
4279
|
+
if (this.state) return;
|
|
4280
|
+
const width = this.canvas.getWidth();
|
|
4281
|
+
const height = this.canvas.getHeight();
|
|
4282
|
+
const base = Math.min(
|
|
4283
|
+
MAX_PADDING,
|
|
4284
|
+
Math.max(MIN_PADDING, Math.max(width, height) * PADDING_FRACTION)
|
|
4285
|
+
);
|
|
4286
|
+
handle.setCoords();
|
|
4287
|
+
const bounds = handle.getBoundingRect();
|
|
4288
|
+
const left = Math.min(MAX_PADDING, Math.max(base, -bounds.left + MIN_PADDING));
|
|
4289
|
+
const top = Math.min(MAX_PADDING, Math.max(base, -bounds.top + MIN_PADDING));
|
|
4290
|
+
const right = Math.min(
|
|
4291
|
+
MAX_PADDING,
|
|
4292
|
+
Math.max(base, bounds.left + bounds.width - width + MIN_PADDING)
|
|
4293
|
+
);
|
|
4294
|
+
const bottom = Math.min(
|
|
4295
|
+
MAX_PADDING,
|
|
4296
|
+
Math.max(base, bounds.top + bounds.height - height + MIN_PADDING)
|
|
4297
|
+
);
|
|
4298
|
+
const viewport = [...this.canvas.viewportTransform];
|
|
4299
|
+
const wrapper = this.canvas.wrapperEl;
|
|
4300
|
+
this.state = {
|
|
4301
|
+
width,
|
|
4302
|
+
height,
|
|
4303
|
+
left,
|
|
4304
|
+
top,
|
|
4305
|
+
viewport,
|
|
4306
|
+
wrapperCss: wrapper.style.cssText
|
|
4307
|
+
};
|
|
4308
|
+
this.canvas.setDimensions({ width: width + left + right, height: height + top + bottom });
|
|
4309
|
+
const paddedViewport = [...viewport];
|
|
4310
|
+
paddedViewport[4] += left;
|
|
4311
|
+
paddedViewport[5] += top;
|
|
4312
|
+
this.canvas.setViewportTransform(paddedViewport);
|
|
4313
|
+
wrapper.style.position = "absolute";
|
|
4314
|
+
wrapper.style.left = `${-left}px`;
|
|
4315
|
+
wrapper.style.top = `${-top}px`;
|
|
4316
|
+
wrapper.style.zIndex = "1";
|
|
4317
|
+
this.canvas.on("after:render", this.onAfterRender);
|
|
4318
|
+
this.canvas.calcOffset();
|
|
4319
|
+
this.canvas.requestRenderAll();
|
|
4320
|
+
}
|
|
4321
|
+
close() {
|
|
4322
|
+
const state = this.state;
|
|
4323
|
+
if (!state) return;
|
|
4324
|
+
this.canvas.off("after:render", this.onAfterRender);
|
|
4325
|
+
this.canvas.setViewportTransform(state.viewport);
|
|
4326
|
+
this.canvas.setDimensions({ width: state.width, height: state.height });
|
|
4327
|
+
this.canvas.wrapperEl.style.cssText = state.wrapperCss;
|
|
4328
|
+
this.canvas.calcOffset();
|
|
4329
|
+
this.state = null;
|
|
4330
|
+
}
|
|
4331
|
+
/** Save with real document dimensions, then reopen the same editing view. */
|
|
4332
|
+
checkpoint(handle, save) {
|
|
4333
|
+
this.close();
|
|
4334
|
+
try {
|
|
4335
|
+
save();
|
|
4336
|
+
} finally {
|
|
4337
|
+
this.open(handle);
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
onAfterRender = ({ ctx }) => {
|
|
4341
|
+
const state = this.state;
|
|
4342
|
+
if (!state || ctx !== this.canvas.getContext()) return;
|
|
4343
|
+
const fullWidth = this.canvas.getWidth();
|
|
4344
|
+
const fullHeight = this.canvas.getHeight();
|
|
4345
|
+
const right = state.left + state.width;
|
|
4346
|
+
const bottom = state.top + state.height;
|
|
4347
|
+
ctx.save();
|
|
4348
|
+
ctx.fillStyle = OUTSIDE_SHADE;
|
|
4349
|
+
ctx.fillRect(0, 0, fullWidth, state.top);
|
|
4350
|
+
ctx.fillRect(0, bottom, fullWidth, fullHeight - bottom);
|
|
4351
|
+
ctx.fillRect(0, state.top, state.left, state.height);
|
|
4352
|
+
ctx.fillRect(right, state.top, fullWidth - right, state.height);
|
|
4353
|
+
ctx.restore();
|
|
4354
|
+
this.canvas.drawControls(ctx);
|
|
4355
|
+
};
|
|
4356
|
+
};
|
|
4357
|
+
|
|
4358
|
+
// src/masks/edit.ts
|
|
4141
4359
|
var MaskEditController = class {
|
|
4142
4360
|
constructor(canvas, onCommit) {
|
|
4143
4361
|
this.canvas = canvas;
|
|
4144
4362
|
this.onCommit = onCommit;
|
|
4363
|
+
this.viewport = new MaskEditViewport(canvas);
|
|
4145
4364
|
}
|
|
4146
4365
|
canvas;
|
|
4147
4366
|
onCommit;
|
|
@@ -4149,6 +4368,7 @@ var MaskEditController = class {
|
|
|
4149
4368
|
editing = null;
|
|
4150
4369
|
child = null;
|
|
4151
4370
|
group = null;
|
|
4371
|
+
viewport;
|
|
4152
4372
|
active() {
|
|
4153
4373
|
return this.editing;
|
|
4154
4374
|
}
|
|
@@ -4179,6 +4399,7 @@ var MaskEditController = class {
|
|
|
4179
4399
|
this.group = group;
|
|
4180
4400
|
this.canvas.add(handle);
|
|
4181
4401
|
this.canvas.setActiveObject(handle);
|
|
4402
|
+
this.viewport.open(handle);
|
|
4182
4403
|
this.canvas.on("object:moving", this.onTransform);
|
|
4183
4404
|
this.canvas.on("object:scaling", this.onTransform);
|
|
4184
4405
|
this.canvas.on("object:rotating", this.onTransform);
|
|
@@ -4193,6 +4414,7 @@ var MaskEditController = class {
|
|
|
4193
4414
|
this.canvas.off("object:scaling", this.onTransform);
|
|
4194
4415
|
this.canvas.off("object:rotating", this.onTransform);
|
|
4195
4416
|
this.canvas.off("object:modified", this.onModified);
|
|
4417
|
+
this.viewport.close();
|
|
4196
4418
|
if (this.canvas.getActiveObject() === this.handle) this.canvas.discardActiveObject();
|
|
4197
4419
|
this.canvas.remove(this.handle);
|
|
4198
4420
|
this.handle = null;
|
|
@@ -4211,15 +4433,15 @@ var MaskEditController = class {
|
|
|
4211
4433
|
onModified = (event) => {
|
|
4212
4434
|
if (!this.handle || event.target !== this.handle) return;
|
|
4213
4435
|
this.write();
|
|
4214
|
-
this.onCommit
|
|
4436
|
+
this.viewport.checkpoint(this.handle, this.onCommit);
|
|
4215
4437
|
};
|
|
4216
4438
|
/** Handle transform (canvas space) → clip child transform (group-relative). */
|
|
4217
4439
|
write() {
|
|
4218
4440
|
if (!this.handle || !this.child || !this.group) return;
|
|
4219
4441
|
applyMatrix(
|
|
4220
4442
|
this.child,
|
|
4221
|
-
|
|
4222
|
-
|
|
4443
|
+
import_fabric17.util.multiplyTransformMatrices(
|
|
4444
|
+
import_fabric17.util.invertTransform(matrixOf(this.group)),
|
|
4223
4445
|
matrixOf(this.handle)
|
|
4224
4446
|
)
|
|
4225
4447
|
);
|
|
@@ -4230,15 +4452,15 @@ var MaskEditController = class {
|
|
|
4230
4452
|
};
|
|
4231
4453
|
|
|
4232
4454
|
// src/masks/store.ts
|
|
4233
|
-
var
|
|
4455
|
+
var import_fabric20 = require("fabric");
|
|
4234
4456
|
|
|
4235
4457
|
// src/masks/host.ts
|
|
4236
|
-
var
|
|
4458
|
+
var import_fabric18 = require("fabric");
|
|
4237
4459
|
function findCanvasHost(layers) {
|
|
4238
4460
|
return layers.getAll().find((layer) => layer.meta.canvasMask);
|
|
4239
4461
|
}
|
|
4240
4462
|
function createCanvasHost(canvas, layers) {
|
|
4241
|
-
const rect = new
|
|
4463
|
+
const rect = new import_fabric18.Rect({
|
|
4242
4464
|
left: 0,
|
|
4243
4465
|
top: 0,
|
|
4244
4466
|
width: canvas.getWidth(),
|
|
@@ -4287,9 +4509,9 @@ function hostBoxOf(canvas, host, absolute) {
|
|
|
4287
4509
|
}
|
|
4288
4510
|
|
|
4289
4511
|
// src/masks/install.ts
|
|
4290
|
-
var
|
|
4512
|
+
var import_fabric19 = require("fabric");
|
|
4291
4513
|
function convertSpace(host, sources, absolute) {
|
|
4292
|
-
const wasAbsolute = host.clipPath instanceof
|
|
4514
|
+
const wasAbsolute = host.clipPath instanceof import_fabric19.Group ? host.clipPath.absolutePositioned : absolute;
|
|
4293
4515
|
if (absolute === wasAbsolute) return;
|
|
4294
4516
|
for (const source of sources) {
|
|
4295
4517
|
if (absolute) toCanvasSpace(source, host);
|
|
@@ -4342,6 +4564,29 @@ var MaskStackStore = class {
|
|
|
4342
4564
|
get(target, maskId) {
|
|
4343
4565
|
return this.list(target).find((entry) => entry.id === maskId);
|
|
4344
4566
|
}
|
|
4567
|
+
/** Repaint a gradient mask without replacing or re-fitting its geometry. */
|
|
4568
|
+
setGradient(target, maskId, gradient, options = {}) {
|
|
4569
|
+
const host = this.host(target);
|
|
4570
|
+
if (!host) return false;
|
|
4571
|
+
const entries = [...this.list(target)];
|
|
4572
|
+
const index = entries.findIndex((entry) => entry.id === maskId);
|
|
4573
|
+
if (index === -1) return false;
|
|
4574
|
+
const sources = this.unwrap(target, host);
|
|
4575
|
+
const source = sources[index];
|
|
4576
|
+
if (!source) return false;
|
|
4577
|
+
source.set({
|
|
4578
|
+
fill: toFabricGradient(gradient, {
|
|
4579
|
+
width: Math.max(1, source.width ?? 1),
|
|
4580
|
+
height: Math.max(1, source.height ?? 1)
|
|
4581
|
+
})
|
|
4582
|
+
});
|
|
4583
|
+
entries[index] = {
|
|
4584
|
+
...entries[index],
|
|
4585
|
+
...options.meta ? { meta: options.meta } : {}
|
|
4586
|
+
};
|
|
4587
|
+
this.commit(target, host, entries, sources, options.save ?? true);
|
|
4588
|
+
return true;
|
|
4589
|
+
}
|
|
4345
4590
|
/** Every target that currently carries at least one mask. */
|
|
4346
4591
|
targets() {
|
|
4347
4592
|
return this.layers.getAll().filter((layer) => (layer.meta.maskStack ?? []).length > 0).map((layer) => layer.meta.canvasMask ? CANVAS_MASK_TARGET : layer.id);
|
|
@@ -4402,14 +4647,14 @@ var MaskStackStore = class {
|
|
|
4402
4647
|
const entries = this.list(target);
|
|
4403
4648
|
const meta = this.hostLayer(target)?.meta;
|
|
4404
4649
|
if (entries.length === 0 && this.holdsBoxClip(meta)) return [];
|
|
4405
|
-
if (entries.length === 0 || !(clip instanceof
|
|
4650
|
+
if (entries.length === 0 || !(clip instanceof import_fabric20.Group)) {
|
|
4406
4651
|
const source = asObject(clip);
|
|
4407
4652
|
if (meta?.overflow === "hidden") source.clipPath = void 0;
|
|
4408
|
-
return [source];
|
|
4653
|
+
return restoreGradientPaint(entries, [source]);
|
|
4409
4654
|
}
|
|
4410
4655
|
const children = unwrapGroup(clip);
|
|
4411
4656
|
const extra = children.length - entries.length;
|
|
4412
|
-
return extra > 0 ? children.slice(extra) : children;
|
|
4657
|
+
return restoreGradientPaint(entries, extra > 0 ? children.slice(extra) : children);
|
|
4413
4658
|
}
|
|
4414
4659
|
/**
|
|
4415
4660
|
* Entries for a stack, adopting a pre-stack clip as the first one. Without
|
|
@@ -4442,7 +4687,8 @@ var MaskStackStore = class {
|
|
|
4442
4687
|
if (!layer) return;
|
|
4443
4688
|
const absolute = forceAbsolute || target === CANVAS_MASK_TARGET || needsAbsoluteSpace(entries);
|
|
4444
4689
|
convertSpace(host, sources, absolute);
|
|
4445
|
-
const
|
|
4690
|
+
const painted = captureGradientPaint(entries, sources);
|
|
4691
|
+
const next = withRelativeTransforms(painted, sources, host, absolute);
|
|
4446
4692
|
installClip(host, next, sources, this.hostBox(target, absolute), absolute);
|
|
4447
4693
|
if (next.length === 0) {
|
|
4448
4694
|
delete layer.meta.maskStack;
|
|
@@ -4456,12 +4702,41 @@ var MaskStackStore = class {
|
|
|
4456
4702
|
if (save) this.history.save();
|
|
4457
4703
|
}
|
|
4458
4704
|
};
|
|
4705
|
+
function restoreGradientPaint(entries, sources) {
|
|
4706
|
+
entries.forEach((entry, index) => {
|
|
4707
|
+
const source = sources[index];
|
|
4708
|
+
if (!source || !entry.gradient) return;
|
|
4709
|
+
const box = {
|
|
4710
|
+
width: Math.max(1, source.width ?? 1),
|
|
4711
|
+
height: Math.max(1, source.height ?? 1)
|
|
4712
|
+
};
|
|
4713
|
+
const fill = source.fill;
|
|
4714
|
+
const rendered = toFabricMaskGradient(entry.gradient, box);
|
|
4715
|
+
if (fill instanceof import_fabric20.Gradient && JSON.stringify(fill.toObject()) !== JSON.stringify(rendered.toObject())) {
|
|
4716
|
+
return;
|
|
4717
|
+
}
|
|
4718
|
+
source.set({
|
|
4719
|
+
fill: toFabricGradient(entry.gradient, box)
|
|
4720
|
+
});
|
|
4721
|
+
});
|
|
4722
|
+
return sources;
|
|
4723
|
+
}
|
|
4724
|
+
function captureGradientPaint(entries, sources) {
|
|
4725
|
+
return entries.map((entry, index) => {
|
|
4726
|
+
const gradient = sources[index] ? readGradientConfig(sources[index]) : null;
|
|
4727
|
+
if (gradient) return { ...entry, gradient };
|
|
4728
|
+
if (!entry.gradient) return entry;
|
|
4729
|
+
const next = { ...entry };
|
|
4730
|
+
delete next.gradient;
|
|
4731
|
+
return next;
|
|
4732
|
+
});
|
|
4733
|
+
}
|
|
4459
4734
|
|
|
4460
4735
|
// src/masks/manager.ts
|
|
4461
4736
|
var LayerMaskManager = class extends MaskStackStore {
|
|
4462
4737
|
// Committing mid-drag would recompose the group the handle writes into and
|
|
4463
4738
|
// leave it pointing at a discarded object; the geometry is settled on endEdit.
|
|
4464
|
-
edits = new MaskEditController(this.canvas, () => this.history.
|
|
4739
|
+
edits = new MaskEditController(this.canvas, () => this.history.saveImmediate());
|
|
4465
4740
|
onLayersChanged = () => this.pin();
|
|
4466
4741
|
// Selecting a layer means the user has moved on from the mask they had open;
|
|
4467
4742
|
// leaving both selected would show mask controls for an unrelated layer.
|
|
@@ -4506,7 +4781,7 @@ var LayerMaskManager = class extends MaskStackStore {
|
|
|
4506
4781
|
if (index === -1) return false;
|
|
4507
4782
|
this.endEdit(false);
|
|
4508
4783
|
this.commit(target, host, [...entries], this.unwrap(target, host), false, true);
|
|
4509
|
-
const group = host.clipPath instanceof
|
|
4784
|
+
const group = host.clipPath instanceof import_fabric21.Group ? host.clipPath : null;
|
|
4510
4785
|
if (!group) return false;
|
|
4511
4786
|
const children = group.getObjects();
|
|
4512
4787
|
const child = children[children.length - entries.length + index];
|
|
@@ -4848,110 +5123,6 @@ function layerTypeOf(object) {
|
|
|
4848
5123
|
return "shape";
|
|
4849
5124
|
}
|
|
4850
5125
|
|
|
4851
|
-
// src/gradient.ts
|
|
4852
|
-
var import_fabric21 = require("fabric");
|
|
4853
|
-
var DEFAULT_GRADIENT_CONFIG = {
|
|
4854
|
-
kind: "linear",
|
|
4855
|
-
angle: 90,
|
|
4856
|
-
center: { x: 0.5, y: 0.5 },
|
|
4857
|
-
radius: 0.5,
|
|
4858
|
-
stops: [
|
|
4859
|
-
{ color: "#ffffff", opacity: 1, position: 0 },
|
|
4860
|
-
{ color: "#000000", opacity: 1, position: 1 }
|
|
4861
|
-
]
|
|
4862
|
-
};
|
|
4863
|
-
var MIN_RADIUS = 1e-3;
|
|
4864
|
-
var MAX_GRADIENT_STOPS = 256;
|
|
4865
|
-
var FALLBACK_STOP_COLOR = DEFAULT_GRADIENT_CONFIG.stops[0].color;
|
|
4866
|
-
function linearCoords(angle, box) {
|
|
4867
|
-
const radians = angle * Math.PI / 180;
|
|
4868
|
-
const sin = Math.sin(radians);
|
|
4869
|
-
const cos = Math.cos(radians);
|
|
4870
|
-
const vx = box.width * sin;
|
|
4871
|
-
const vy = -box.height * cos;
|
|
4872
|
-
const length = Math.abs(box.width * sin) + Math.abs(box.height * cos);
|
|
4873
|
-
const scale = length / (vx * vx + vy * vy || 1);
|
|
4874
|
-
return {
|
|
4875
|
-
x1: 0.5 - scale * vx / 2,
|
|
4876
|
-
y1: 0.5 - scale * vy / 2,
|
|
4877
|
-
x2: 0.5 + scale * vx / 2,
|
|
4878
|
-
y2: 0.5 + scale * vy / 2
|
|
4879
|
-
};
|
|
4880
|
-
}
|
|
4881
|
-
function angleFromCoords(coords, box) {
|
|
4882
|
-
const dx = (coords.x2 - coords.x1) / (box.width || 1);
|
|
4883
|
-
const dy = (coords.y2 - coords.y1) / (box.height || 1);
|
|
4884
|
-
const degrees = Math.atan2(dx, -dy) * 180 / Math.PI;
|
|
4885
|
-
return (degrees + 360) % 360;
|
|
4886
|
-
}
|
|
4887
|
-
function stopColor(stop) {
|
|
4888
|
-
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4889
|
-
const color = new import_fabric21.Color(source);
|
|
4890
|
-
const opacity = Number.isFinite(stop.opacity) ? stop.opacity : 1;
|
|
4891
|
-
color.setAlpha(clamp(opacity, 0, 1));
|
|
4892
|
-
return color.toRgba();
|
|
4893
|
-
}
|
|
4894
|
-
function toFabricGradient(config, box) {
|
|
4895
|
-
const colorStops = [...config.stops].sort((a, b) => a.position - b.position).slice(0, MAX_GRADIENT_STOPS).map((stop) => {
|
|
4896
|
-
const position = Number.isFinite(stop.position) ? stop.position : 0;
|
|
4897
|
-
return { offset: clamp(position, 0, 1), color: stopColor(stop) };
|
|
4898
|
-
});
|
|
4899
|
-
if (config.kind === "radial") {
|
|
4900
|
-
const radius = Math.max(MIN_RADIUS, config.radius);
|
|
4901
|
-
return new import_fabric21.Gradient({
|
|
4902
|
-
type: "radial",
|
|
4903
|
-
gradientUnits: "percentage",
|
|
4904
|
-
coords: {
|
|
4905
|
-
x1: config.center.x,
|
|
4906
|
-
y1: config.center.y,
|
|
4907
|
-
r1: 0,
|
|
4908
|
-
x2: config.center.x,
|
|
4909
|
-
y2: config.center.y,
|
|
4910
|
-
r2: radius
|
|
4911
|
-
},
|
|
4912
|
-
colorStops
|
|
4913
|
-
});
|
|
4914
|
-
}
|
|
4915
|
-
return new import_fabric21.Gradient({
|
|
4916
|
-
type: "linear",
|
|
4917
|
-
gradientUnits: "percentage",
|
|
4918
|
-
coords: linearCoords(config.angle, box),
|
|
4919
|
-
colorStops
|
|
4920
|
-
});
|
|
4921
|
-
}
|
|
4922
|
-
function readGradientConfig(object) {
|
|
4923
|
-
const fill = object.fill;
|
|
4924
|
-
if (!fill || typeof fill === "string" || !(fill instanceof import_fabric21.Gradient)) return null;
|
|
4925
|
-
const stops = fill.colorStops.map((stop) => {
|
|
4926
|
-
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4927
|
-
const color = new import_fabric21.Color(source);
|
|
4928
|
-
const offset = Number.isFinite(stop.offset) ? stop.offset : 0;
|
|
4929
|
-
return {
|
|
4930
|
-
color: `#${color.toHex().toLowerCase()}`,
|
|
4931
|
-
opacity: color.getAlpha(),
|
|
4932
|
-
position: clamp(offset, 0, 1)
|
|
4933
|
-
};
|
|
4934
|
-
});
|
|
4935
|
-
if (fill.type === "radial") {
|
|
4936
|
-
const { x1, y1, r2 } = fill.coords;
|
|
4937
|
-
return {
|
|
4938
|
-
kind: "radial",
|
|
4939
|
-
angle: DEFAULT_GRADIENT_CONFIG.angle,
|
|
4940
|
-
center: { x: x1, y: y1 },
|
|
4941
|
-
radius: r2,
|
|
4942
|
-
stops
|
|
4943
|
-
};
|
|
4944
|
-
}
|
|
4945
|
-
const box = { width: object.width || 1, height: object.height || 1 };
|
|
4946
|
-
return {
|
|
4947
|
-
kind: "linear",
|
|
4948
|
-
angle: angleFromCoords(fill.coords, box),
|
|
4949
|
-
center: { ...DEFAULT_GRADIENT_CONFIG.center },
|
|
4950
|
-
radius: DEFAULT_GRADIENT_CONFIG.radius,
|
|
4951
|
-
stops
|
|
4952
|
-
};
|
|
4953
|
-
}
|
|
4954
|
-
|
|
4955
5126
|
// src/editor.ts
|
|
4956
5127
|
var MIN_ZOOM = 0.01;
|
|
4957
5128
|
var MAX_ZOOM = 8;
|