@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.mjs
CHANGED
|
@@ -3387,6 +3387,123 @@ import { Group as Group6 } from "fabric";
|
|
|
3387
3387
|
|
|
3388
3388
|
// src/masks/compose.ts
|
|
3389
3389
|
import { Group as Group2, Rect as Rect2 } from "fabric";
|
|
3390
|
+
|
|
3391
|
+
// src/gradient.ts
|
|
3392
|
+
import { Color, Gradient } from "fabric";
|
|
3393
|
+
var DEFAULT_GRADIENT_CONFIG = {
|
|
3394
|
+
kind: "linear",
|
|
3395
|
+
angle: 90,
|
|
3396
|
+
center: { x: 0.5, y: 0.5 },
|
|
3397
|
+
radius: 0.5,
|
|
3398
|
+
stops: [
|
|
3399
|
+
{ color: "#ffffff", opacity: 1, position: 0 },
|
|
3400
|
+
{ color: "#000000", opacity: 1, position: 1 }
|
|
3401
|
+
]
|
|
3402
|
+
};
|
|
3403
|
+
var MIN_RADIUS = 1e-3;
|
|
3404
|
+
var MAX_GRADIENT_STOPS = 256;
|
|
3405
|
+
var FALLBACK_STOP_COLOR = DEFAULT_GRADIENT_CONFIG.stops[0].color;
|
|
3406
|
+
function linearCoords(angle, box) {
|
|
3407
|
+
const radians = angle * Math.PI / 180;
|
|
3408
|
+
const sin = Math.sin(radians);
|
|
3409
|
+
const cos = Math.cos(radians);
|
|
3410
|
+
const vx = box.width * sin;
|
|
3411
|
+
const vy = -box.height * cos;
|
|
3412
|
+
const length = Math.abs(box.width * sin) + Math.abs(box.height * cos);
|
|
3413
|
+
const scale = length / (vx * vx + vy * vy || 1);
|
|
3414
|
+
return {
|
|
3415
|
+
x1: 0.5 - scale * vx / 2,
|
|
3416
|
+
y1: 0.5 - scale * vy / 2,
|
|
3417
|
+
x2: 0.5 + scale * vx / 2,
|
|
3418
|
+
y2: 0.5 + scale * vy / 2
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
function angleFromCoords(coords, box) {
|
|
3422
|
+
const dx = (coords.x2 - coords.x1) / (box.width || 1);
|
|
3423
|
+
const dy = (coords.y2 - coords.y1) / (box.height || 1);
|
|
3424
|
+
const degrees = Math.atan2(dx, -dy) * 180 / Math.PI;
|
|
3425
|
+
return (degrees + 360) % 360;
|
|
3426
|
+
}
|
|
3427
|
+
function stopColor(stop) {
|
|
3428
|
+
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
3429
|
+
const color = new Color(source);
|
|
3430
|
+
const opacity = Number.isFinite(stop.opacity) ? stop.opacity : 1;
|
|
3431
|
+
color.setAlpha(clamp(opacity, 0, 1));
|
|
3432
|
+
return color.toRgba();
|
|
3433
|
+
}
|
|
3434
|
+
function toFabricGradient(config, box) {
|
|
3435
|
+
const colorStops = [...config.stops].sort((a, b) => a.position - b.position).slice(0, MAX_GRADIENT_STOPS).map((stop) => {
|
|
3436
|
+
const position = Number.isFinite(stop.position) ? stop.position : 0;
|
|
3437
|
+
return { offset: clamp(position, 0, 1), color: stopColor(stop) };
|
|
3438
|
+
});
|
|
3439
|
+
if (config.kind === "radial") {
|
|
3440
|
+
const radius = Math.max(MIN_RADIUS, config.radius);
|
|
3441
|
+
return new Gradient({
|
|
3442
|
+
type: "radial",
|
|
3443
|
+
gradientUnits: "percentage",
|
|
3444
|
+
coords: {
|
|
3445
|
+
x1: config.center.x,
|
|
3446
|
+
y1: config.center.y,
|
|
3447
|
+
r1: 0,
|
|
3448
|
+
x2: config.center.x,
|
|
3449
|
+
y2: config.center.y,
|
|
3450
|
+
r2: radius
|
|
3451
|
+
},
|
|
3452
|
+
colorStops
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
return new Gradient({
|
|
3456
|
+
type: "linear",
|
|
3457
|
+
gradientUnits: "percentage",
|
|
3458
|
+
coords: linearCoords(config.angle, box),
|
|
3459
|
+
colorStops
|
|
3460
|
+
});
|
|
3461
|
+
}
|
|
3462
|
+
function toFabricMaskGradient(config, box) {
|
|
3463
|
+
const gradient = toFabricGradient(config, box);
|
|
3464
|
+
gradient.colorStops = gradient.colorStops.map((stop) => {
|
|
3465
|
+
const color = new Color(stop.color);
|
|
3466
|
+
const [red, green, blue] = color.getSource();
|
|
3467
|
+
const luminance = (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 255;
|
|
3468
|
+
color.setSource([255, 255, 255, clamp(luminance * color.getAlpha(), 0, 1)]);
|
|
3469
|
+
return { offset: stop.offset, color: color.toRgba() };
|
|
3470
|
+
});
|
|
3471
|
+
return gradient;
|
|
3472
|
+
}
|
|
3473
|
+
function readGradientConfig(object) {
|
|
3474
|
+
const fill = object.fill;
|
|
3475
|
+
if (!fill || typeof fill === "string" || !(fill instanceof Gradient)) return null;
|
|
3476
|
+
const stops = fill.colorStops.map((stop) => {
|
|
3477
|
+
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
3478
|
+
const color = new Color(source);
|
|
3479
|
+
const offset = Number.isFinite(stop.offset) ? stop.offset : 0;
|
|
3480
|
+
return {
|
|
3481
|
+
color: `#${color.toHex().toLowerCase()}`,
|
|
3482
|
+
opacity: color.getAlpha(),
|
|
3483
|
+
position: clamp(offset, 0, 1)
|
|
3484
|
+
};
|
|
3485
|
+
});
|
|
3486
|
+
if (fill.type === "radial") {
|
|
3487
|
+
const { x1, y1, r2 } = fill.coords;
|
|
3488
|
+
return {
|
|
3489
|
+
kind: "radial",
|
|
3490
|
+
angle: DEFAULT_GRADIENT_CONFIG.angle,
|
|
3491
|
+
center: { x: x1, y: y1 },
|
|
3492
|
+
radius: r2,
|
|
3493
|
+
stops
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3496
|
+
const box = { width: object.width || 1, height: object.height || 1 };
|
|
3497
|
+
return {
|
|
3498
|
+
kind: "linear",
|
|
3499
|
+
angle: angleFromCoords(fill.coords, box),
|
|
3500
|
+
center: { ...DEFAULT_GRADIENT_CONFIG.center },
|
|
3501
|
+
radius: DEFAULT_GRADIENT_CONFIG.radius,
|
|
3502
|
+
stops
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// src/masks/compose.ts
|
|
3390
3507
|
var MODE_OPERATION = {
|
|
3391
3508
|
add: "source-over",
|
|
3392
3509
|
subtract: "destination-out",
|
|
@@ -3416,13 +3533,21 @@ function composeMaskGroup(children, entries, options) {
|
|
|
3416
3533
|
neutralize(child);
|
|
3417
3534
|
return;
|
|
3418
3535
|
}
|
|
3536
|
+
if (entry.gradient) {
|
|
3537
|
+
child.set({
|
|
3538
|
+
fill: toFabricMaskGradient(entry.gradient, {
|
|
3539
|
+
width: Math.max(1, child.width ?? 1),
|
|
3540
|
+
height: Math.max(1, child.height ?? 1)
|
|
3541
|
+
})
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3419
3544
|
child.set({
|
|
3420
3545
|
opacity: entry.opacity,
|
|
3421
3546
|
globalCompositeOperation: MODE_OPERATION[entry.mode] ?? "source-over"
|
|
3422
3547
|
});
|
|
3423
3548
|
});
|
|
3424
3549
|
const first = entries.find((entry) => entry.visible);
|
|
3425
|
-
const withBase = first
|
|
3550
|
+
const withBase = !first || first.mode !== "add" ? [baseRect(options.box), ...children] : [...children];
|
|
3426
3551
|
return new Group2(withBase, {
|
|
3427
3552
|
absolutePositioned: options.absolute,
|
|
3428
3553
|
// Cached, so the children's compositing operations resolve against each
|
|
@@ -3438,10 +3563,104 @@ function needsAbsoluteSpace(entries) {
|
|
|
3438
3563
|
|
|
3439
3564
|
// src/masks/edit.ts
|
|
3440
3565
|
import { util as util3 } from "fabric";
|
|
3566
|
+
|
|
3567
|
+
// src/masks/edit-viewport.ts
|
|
3568
|
+
var MIN_PADDING = 48;
|
|
3569
|
+
var MAX_PADDING = 512;
|
|
3570
|
+
var PADDING_FRACTION = 0.35;
|
|
3571
|
+
var OUTSIDE_SHADE = "rgba(15, 15, 15, 0.55)";
|
|
3572
|
+
var MaskEditViewport = class {
|
|
3573
|
+
constructor(canvas) {
|
|
3574
|
+
this.canvas = canvas;
|
|
3575
|
+
}
|
|
3576
|
+
canvas;
|
|
3577
|
+
state = null;
|
|
3578
|
+
open(handle) {
|
|
3579
|
+
if (this.state) return;
|
|
3580
|
+
const width = this.canvas.getWidth();
|
|
3581
|
+
const height = this.canvas.getHeight();
|
|
3582
|
+
const base = Math.min(
|
|
3583
|
+
MAX_PADDING,
|
|
3584
|
+
Math.max(MIN_PADDING, Math.max(width, height) * PADDING_FRACTION)
|
|
3585
|
+
);
|
|
3586
|
+
handle.setCoords();
|
|
3587
|
+
const bounds = handle.getBoundingRect();
|
|
3588
|
+
const left = Math.min(MAX_PADDING, Math.max(base, -bounds.left + MIN_PADDING));
|
|
3589
|
+
const top = Math.min(MAX_PADDING, Math.max(base, -bounds.top + MIN_PADDING));
|
|
3590
|
+
const right = Math.min(
|
|
3591
|
+
MAX_PADDING,
|
|
3592
|
+
Math.max(base, bounds.left + bounds.width - width + MIN_PADDING)
|
|
3593
|
+
);
|
|
3594
|
+
const bottom = Math.min(
|
|
3595
|
+
MAX_PADDING,
|
|
3596
|
+
Math.max(base, bounds.top + bounds.height - height + MIN_PADDING)
|
|
3597
|
+
);
|
|
3598
|
+
const viewport = [...this.canvas.viewportTransform];
|
|
3599
|
+
const wrapper = this.canvas.wrapperEl;
|
|
3600
|
+
this.state = {
|
|
3601
|
+
width,
|
|
3602
|
+
height,
|
|
3603
|
+
left,
|
|
3604
|
+
top,
|
|
3605
|
+
viewport,
|
|
3606
|
+
wrapperCss: wrapper.style.cssText
|
|
3607
|
+
};
|
|
3608
|
+
this.canvas.setDimensions({ width: width + left + right, height: height + top + bottom });
|
|
3609
|
+
const paddedViewport = [...viewport];
|
|
3610
|
+
paddedViewport[4] += left;
|
|
3611
|
+
paddedViewport[5] += top;
|
|
3612
|
+
this.canvas.setViewportTransform(paddedViewport);
|
|
3613
|
+
wrapper.style.position = "absolute";
|
|
3614
|
+
wrapper.style.left = `${-left}px`;
|
|
3615
|
+
wrapper.style.top = `${-top}px`;
|
|
3616
|
+
wrapper.style.zIndex = "1";
|
|
3617
|
+
this.canvas.on("after:render", this.onAfterRender);
|
|
3618
|
+
this.canvas.calcOffset();
|
|
3619
|
+
this.canvas.requestRenderAll();
|
|
3620
|
+
}
|
|
3621
|
+
close() {
|
|
3622
|
+
const state = this.state;
|
|
3623
|
+
if (!state) return;
|
|
3624
|
+
this.canvas.off("after:render", this.onAfterRender);
|
|
3625
|
+
this.canvas.setViewportTransform(state.viewport);
|
|
3626
|
+
this.canvas.setDimensions({ width: state.width, height: state.height });
|
|
3627
|
+
this.canvas.wrapperEl.style.cssText = state.wrapperCss;
|
|
3628
|
+
this.canvas.calcOffset();
|
|
3629
|
+
this.state = null;
|
|
3630
|
+
}
|
|
3631
|
+
/** Save with real document dimensions, then reopen the same editing view. */
|
|
3632
|
+
checkpoint(handle, save) {
|
|
3633
|
+
this.close();
|
|
3634
|
+
try {
|
|
3635
|
+
save();
|
|
3636
|
+
} finally {
|
|
3637
|
+
this.open(handle);
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
onAfterRender = ({ ctx }) => {
|
|
3641
|
+
const state = this.state;
|
|
3642
|
+
if (!state || ctx !== this.canvas.getContext()) return;
|
|
3643
|
+
const fullWidth = this.canvas.getWidth();
|
|
3644
|
+
const fullHeight = this.canvas.getHeight();
|
|
3645
|
+
const right = state.left + state.width;
|
|
3646
|
+
const bottom = state.top + state.height;
|
|
3647
|
+
ctx.save();
|
|
3648
|
+
ctx.fillStyle = OUTSIDE_SHADE;
|
|
3649
|
+
ctx.fillRect(0, 0, fullWidth, state.top);
|
|
3650
|
+
ctx.fillRect(0, bottom, fullWidth, fullHeight - bottom);
|
|
3651
|
+
ctx.fillRect(0, state.top, state.left, state.height);
|
|
3652
|
+
ctx.fillRect(right, state.top, fullWidth - right, state.height);
|
|
3653
|
+
ctx.restore();
|
|
3654
|
+
this.canvas.drawControls(ctx);
|
|
3655
|
+
};
|
|
3656
|
+
};
|
|
3657
|
+
|
|
3658
|
+
// src/masks/edit.ts
|
|
3441
3659
|
var MaskEditController = class {
|
|
3442
3660
|
constructor(canvas, onCommit) {
|
|
3443
3661
|
this.canvas = canvas;
|
|
3444
3662
|
this.onCommit = onCommit;
|
|
3663
|
+
this.viewport = new MaskEditViewport(canvas);
|
|
3445
3664
|
}
|
|
3446
3665
|
canvas;
|
|
3447
3666
|
onCommit;
|
|
@@ -3449,6 +3668,7 @@ var MaskEditController = class {
|
|
|
3449
3668
|
editing = null;
|
|
3450
3669
|
child = null;
|
|
3451
3670
|
group = null;
|
|
3671
|
+
viewport;
|
|
3452
3672
|
active() {
|
|
3453
3673
|
return this.editing;
|
|
3454
3674
|
}
|
|
@@ -3479,6 +3699,7 @@ var MaskEditController = class {
|
|
|
3479
3699
|
this.group = group;
|
|
3480
3700
|
this.canvas.add(handle);
|
|
3481
3701
|
this.canvas.setActiveObject(handle);
|
|
3702
|
+
this.viewport.open(handle);
|
|
3482
3703
|
this.canvas.on("object:moving", this.onTransform);
|
|
3483
3704
|
this.canvas.on("object:scaling", this.onTransform);
|
|
3484
3705
|
this.canvas.on("object:rotating", this.onTransform);
|
|
@@ -3493,6 +3714,7 @@ var MaskEditController = class {
|
|
|
3493
3714
|
this.canvas.off("object:scaling", this.onTransform);
|
|
3494
3715
|
this.canvas.off("object:rotating", this.onTransform);
|
|
3495
3716
|
this.canvas.off("object:modified", this.onModified);
|
|
3717
|
+
this.viewport.close();
|
|
3496
3718
|
if (this.canvas.getActiveObject() === this.handle) this.canvas.discardActiveObject();
|
|
3497
3719
|
this.canvas.remove(this.handle);
|
|
3498
3720
|
this.handle = null;
|
|
@@ -3511,7 +3733,7 @@ var MaskEditController = class {
|
|
|
3511
3733
|
onModified = (event) => {
|
|
3512
3734
|
if (!this.handle || event.target !== this.handle) return;
|
|
3513
3735
|
this.write();
|
|
3514
|
-
this.onCommit
|
|
3736
|
+
this.viewport.checkpoint(this.handle, this.onCommit);
|
|
3515
3737
|
};
|
|
3516
3738
|
/** Handle transform (canvas space) → clip child transform (group-relative). */
|
|
3517
3739
|
write() {
|
|
@@ -3530,7 +3752,7 @@ var MaskEditController = class {
|
|
|
3530
3752
|
};
|
|
3531
3753
|
|
|
3532
3754
|
// src/masks/store.ts
|
|
3533
|
-
import { Group as Group5 } from "fabric";
|
|
3755
|
+
import { Gradient as Gradient2, Group as Group5 } from "fabric";
|
|
3534
3756
|
|
|
3535
3757
|
// src/masks/host.ts
|
|
3536
3758
|
import { Rect as Rect3 } from "fabric";
|
|
@@ -3642,6 +3864,29 @@ var MaskStackStore = class {
|
|
|
3642
3864
|
get(target, maskId) {
|
|
3643
3865
|
return this.list(target).find((entry) => entry.id === maskId);
|
|
3644
3866
|
}
|
|
3867
|
+
/** Repaint a gradient mask without replacing or re-fitting its geometry. */
|
|
3868
|
+
setGradient(target, maskId, gradient, options = {}) {
|
|
3869
|
+
const host = this.host(target);
|
|
3870
|
+
if (!host) return false;
|
|
3871
|
+
const entries = [...this.list(target)];
|
|
3872
|
+
const index = entries.findIndex((entry) => entry.id === maskId);
|
|
3873
|
+
if (index === -1) return false;
|
|
3874
|
+
const sources = this.unwrap(target, host);
|
|
3875
|
+
const source = sources[index];
|
|
3876
|
+
if (!source) return false;
|
|
3877
|
+
source.set({
|
|
3878
|
+
fill: toFabricGradient(gradient, {
|
|
3879
|
+
width: Math.max(1, source.width ?? 1),
|
|
3880
|
+
height: Math.max(1, source.height ?? 1)
|
|
3881
|
+
})
|
|
3882
|
+
});
|
|
3883
|
+
entries[index] = {
|
|
3884
|
+
...entries[index],
|
|
3885
|
+
...options.meta ? { meta: options.meta } : {}
|
|
3886
|
+
};
|
|
3887
|
+
this.commit(target, host, entries, sources, options.save ?? true);
|
|
3888
|
+
return true;
|
|
3889
|
+
}
|
|
3645
3890
|
/** Every target that currently carries at least one mask. */
|
|
3646
3891
|
targets() {
|
|
3647
3892
|
return this.layers.getAll().filter((layer) => (layer.meta.maskStack ?? []).length > 0).map((layer) => layer.meta.canvasMask ? CANVAS_MASK_TARGET : layer.id);
|
|
@@ -3705,11 +3950,11 @@ var MaskStackStore = class {
|
|
|
3705
3950
|
if (entries.length === 0 || !(clip instanceof Group5)) {
|
|
3706
3951
|
const source = asObject(clip);
|
|
3707
3952
|
if (meta?.overflow === "hidden") source.clipPath = void 0;
|
|
3708
|
-
return [source];
|
|
3953
|
+
return restoreGradientPaint(entries, [source]);
|
|
3709
3954
|
}
|
|
3710
3955
|
const children = unwrapGroup(clip);
|
|
3711
3956
|
const extra = children.length - entries.length;
|
|
3712
|
-
return extra > 0 ? children.slice(extra) : children;
|
|
3957
|
+
return restoreGradientPaint(entries, extra > 0 ? children.slice(extra) : children);
|
|
3713
3958
|
}
|
|
3714
3959
|
/**
|
|
3715
3960
|
* Entries for a stack, adopting a pre-stack clip as the first one. Without
|
|
@@ -3742,7 +3987,8 @@ var MaskStackStore = class {
|
|
|
3742
3987
|
if (!layer) return;
|
|
3743
3988
|
const absolute = forceAbsolute || target === CANVAS_MASK_TARGET || needsAbsoluteSpace(entries);
|
|
3744
3989
|
convertSpace(host, sources, absolute);
|
|
3745
|
-
const
|
|
3990
|
+
const painted = captureGradientPaint(entries, sources);
|
|
3991
|
+
const next = withRelativeTransforms(painted, sources, host, absolute);
|
|
3746
3992
|
installClip(host, next, sources, this.hostBox(target, absolute), absolute);
|
|
3747
3993
|
if (next.length === 0) {
|
|
3748
3994
|
delete layer.meta.maskStack;
|
|
@@ -3756,12 +4002,41 @@ var MaskStackStore = class {
|
|
|
3756
4002
|
if (save) this.history.save();
|
|
3757
4003
|
}
|
|
3758
4004
|
};
|
|
4005
|
+
function restoreGradientPaint(entries, sources) {
|
|
4006
|
+
entries.forEach((entry, index) => {
|
|
4007
|
+
const source = sources[index];
|
|
4008
|
+
if (!source || !entry.gradient) return;
|
|
4009
|
+
const box = {
|
|
4010
|
+
width: Math.max(1, source.width ?? 1),
|
|
4011
|
+
height: Math.max(1, source.height ?? 1)
|
|
4012
|
+
};
|
|
4013
|
+
const fill = source.fill;
|
|
4014
|
+
const rendered = toFabricMaskGradient(entry.gradient, box);
|
|
4015
|
+
if (fill instanceof Gradient2 && JSON.stringify(fill.toObject()) !== JSON.stringify(rendered.toObject())) {
|
|
4016
|
+
return;
|
|
4017
|
+
}
|
|
4018
|
+
source.set({
|
|
4019
|
+
fill: toFabricGradient(entry.gradient, box)
|
|
4020
|
+
});
|
|
4021
|
+
});
|
|
4022
|
+
return sources;
|
|
4023
|
+
}
|
|
4024
|
+
function captureGradientPaint(entries, sources) {
|
|
4025
|
+
return entries.map((entry, index) => {
|
|
4026
|
+
const gradient = sources[index] ? readGradientConfig(sources[index]) : null;
|
|
4027
|
+
if (gradient) return { ...entry, gradient };
|
|
4028
|
+
if (!entry.gradient) return entry;
|
|
4029
|
+
const next = { ...entry };
|
|
4030
|
+
delete next.gradient;
|
|
4031
|
+
return next;
|
|
4032
|
+
});
|
|
4033
|
+
}
|
|
3759
4034
|
|
|
3760
4035
|
// src/masks/manager.ts
|
|
3761
4036
|
var LayerMaskManager = class extends MaskStackStore {
|
|
3762
4037
|
// Committing mid-drag would recompose the group the handle writes into and
|
|
3763
4038
|
// leave it pointing at a discarded object; the geometry is settled on endEdit.
|
|
3764
|
-
edits = new MaskEditController(this.canvas, () => this.history.
|
|
4039
|
+
edits = new MaskEditController(this.canvas, () => this.history.saveImmediate());
|
|
3765
4040
|
onLayersChanged = () => this.pin();
|
|
3766
4041
|
// Selecting a layer means the user has moved on from the mask they had open;
|
|
3767
4042
|
// leaving both selected would show mask controls for an unrelated layer.
|
|
@@ -4148,110 +4423,6 @@ function layerTypeOf(object) {
|
|
|
4148
4423
|
return "shape";
|
|
4149
4424
|
}
|
|
4150
4425
|
|
|
4151
|
-
// src/gradient.ts
|
|
4152
|
-
import { Color, Gradient } from "fabric";
|
|
4153
|
-
var DEFAULT_GRADIENT_CONFIG = {
|
|
4154
|
-
kind: "linear",
|
|
4155
|
-
angle: 90,
|
|
4156
|
-
center: { x: 0.5, y: 0.5 },
|
|
4157
|
-
radius: 0.5,
|
|
4158
|
-
stops: [
|
|
4159
|
-
{ color: "#ffffff", opacity: 1, position: 0 },
|
|
4160
|
-
{ color: "#000000", opacity: 1, position: 1 }
|
|
4161
|
-
]
|
|
4162
|
-
};
|
|
4163
|
-
var MIN_RADIUS = 1e-3;
|
|
4164
|
-
var MAX_GRADIENT_STOPS = 256;
|
|
4165
|
-
var FALLBACK_STOP_COLOR = DEFAULT_GRADIENT_CONFIG.stops[0].color;
|
|
4166
|
-
function linearCoords(angle, box) {
|
|
4167
|
-
const radians = angle * Math.PI / 180;
|
|
4168
|
-
const sin = Math.sin(radians);
|
|
4169
|
-
const cos = Math.cos(radians);
|
|
4170
|
-
const vx = box.width * sin;
|
|
4171
|
-
const vy = -box.height * cos;
|
|
4172
|
-
const length = Math.abs(box.width * sin) + Math.abs(box.height * cos);
|
|
4173
|
-
const scale = length / (vx * vx + vy * vy || 1);
|
|
4174
|
-
return {
|
|
4175
|
-
x1: 0.5 - scale * vx / 2,
|
|
4176
|
-
y1: 0.5 - scale * vy / 2,
|
|
4177
|
-
x2: 0.5 + scale * vx / 2,
|
|
4178
|
-
y2: 0.5 + scale * vy / 2
|
|
4179
|
-
};
|
|
4180
|
-
}
|
|
4181
|
-
function angleFromCoords(coords, box) {
|
|
4182
|
-
const dx = (coords.x2 - coords.x1) / (box.width || 1);
|
|
4183
|
-
const dy = (coords.y2 - coords.y1) / (box.height || 1);
|
|
4184
|
-
const degrees = Math.atan2(dx, -dy) * 180 / Math.PI;
|
|
4185
|
-
return (degrees + 360) % 360;
|
|
4186
|
-
}
|
|
4187
|
-
function stopColor(stop) {
|
|
4188
|
-
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4189
|
-
const color = new Color(source);
|
|
4190
|
-
const opacity = Number.isFinite(stop.opacity) ? stop.opacity : 1;
|
|
4191
|
-
color.setAlpha(clamp(opacity, 0, 1));
|
|
4192
|
-
return color.toRgba();
|
|
4193
|
-
}
|
|
4194
|
-
function toFabricGradient(config, box) {
|
|
4195
|
-
const colorStops = [...config.stops].sort((a, b) => a.position - b.position).slice(0, MAX_GRADIENT_STOPS).map((stop) => {
|
|
4196
|
-
const position = Number.isFinite(stop.position) ? stop.position : 0;
|
|
4197
|
-
return { offset: clamp(position, 0, 1), color: stopColor(stop) };
|
|
4198
|
-
});
|
|
4199
|
-
if (config.kind === "radial") {
|
|
4200
|
-
const radius = Math.max(MIN_RADIUS, config.radius);
|
|
4201
|
-
return new Gradient({
|
|
4202
|
-
type: "radial",
|
|
4203
|
-
gradientUnits: "percentage",
|
|
4204
|
-
coords: {
|
|
4205
|
-
x1: config.center.x,
|
|
4206
|
-
y1: config.center.y,
|
|
4207
|
-
r1: 0,
|
|
4208
|
-
x2: config.center.x,
|
|
4209
|
-
y2: config.center.y,
|
|
4210
|
-
r2: radius
|
|
4211
|
-
},
|
|
4212
|
-
colorStops
|
|
4213
|
-
});
|
|
4214
|
-
}
|
|
4215
|
-
return new Gradient({
|
|
4216
|
-
type: "linear",
|
|
4217
|
-
gradientUnits: "percentage",
|
|
4218
|
-
coords: linearCoords(config.angle, box),
|
|
4219
|
-
colorStops
|
|
4220
|
-
});
|
|
4221
|
-
}
|
|
4222
|
-
function readGradientConfig(object) {
|
|
4223
|
-
const fill = object.fill;
|
|
4224
|
-
if (!fill || typeof fill === "string" || !(fill instanceof Gradient)) return null;
|
|
4225
|
-
const stops = fill.colorStops.map((stop) => {
|
|
4226
|
-
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4227
|
-
const color = new Color(source);
|
|
4228
|
-
const offset = Number.isFinite(stop.offset) ? stop.offset : 0;
|
|
4229
|
-
return {
|
|
4230
|
-
color: `#${color.toHex().toLowerCase()}`,
|
|
4231
|
-
opacity: color.getAlpha(),
|
|
4232
|
-
position: clamp(offset, 0, 1)
|
|
4233
|
-
};
|
|
4234
|
-
});
|
|
4235
|
-
if (fill.type === "radial") {
|
|
4236
|
-
const { x1, y1, r2 } = fill.coords;
|
|
4237
|
-
return {
|
|
4238
|
-
kind: "radial",
|
|
4239
|
-
angle: DEFAULT_GRADIENT_CONFIG.angle,
|
|
4240
|
-
center: { x: x1, y: y1 },
|
|
4241
|
-
radius: r2,
|
|
4242
|
-
stops
|
|
4243
|
-
};
|
|
4244
|
-
}
|
|
4245
|
-
const box = { width: object.width || 1, height: object.height || 1 };
|
|
4246
|
-
return {
|
|
4247
|
-
kind: "linear",
|
|
4248
|
-
angle: angleFromCoords(fill.coords, box),
|
|
4249
|
-
center: { ...DEFAULT_GRADIENT_CONFIG.center },
|
|
4250
|
-
radius: DEFAULT_GRADIENT_CONFIG.radius,
|
|
4251
|
-
stops
|
|
4252
|
-
};
|
|
4253
|
-
}
|
|
4254
|
-
|
|
4255
4426
|
// src/editor.ts
|
|
4256
4427
|
var MIN_ZOOM = 0.01;
|
|
4257
4428
|
var MAX_ZOOM = 8;
|