@overtone-art/canvas-editor-core 0.7.0 → 0.8.1
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 +86 -4
- package/dist/index.d.ts +86 -4
- package/dist/index.global.js +52 -52
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +255 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +234 -3
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/{types-DLrbzRj1.d.mts → types-DmintAiu.d.mts} +37 -2
- package/dist/{types-DLrbzRj1.d.ts → types-DmintAiu.d.ts} +37 -2
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from "./chunk-XNGPX7FG.mjs";
|
|
30
30
|
|
|
31
31
|
// src/editor.ts
|
|
32
|
-
import { Canvas, FabricImage as FabricImage3, Group as Group7, Textbox as Textbox2, filters } from "fabric";
|
|
32
|
+
import { Canvas, FabricImage as FabricImage3, Group as Group7, Rect as Rect4, Textbox as Textbox2, filters } from "fabric";
|
|
33
33
|
|
|
34
34
|
// src/events.ts
|
|
35
35
|
var EventEmitter = class {
|
|
@@ -73,6 +73,53 @@ import { ActiveSelection } from "fabric";
|
|
|
73
73
|
import { nanoid } from "nanoid";
|
|
74
74
|
var generateId = () => nanoid(12);
|
|
75
75
|
|
|
76
|
+
// src/blend.ts
|
|
77
|
+
var BLEND_MODES = [
|
|
78
|
+
"normal",
|
|
79
|
+
"darken",
|
|
80
|
+
"multiply",
|
|
81
|
+
"color-burn",
|
|
82
|
+
"lighten",
|
|
83
|
+
"screen",
|
|
84
|
+
"color-dodge",
|
|
85
|
+
"linear-dodge",
|
|
86
|
+
"overlay",
|
|
87
|
+
"soft-light",
|
|
88
|
+
"hard-light",
|
|
89
|
+
"difference",
|
|
90
|
+
"exclusion",
|
|
91
|
+
"hue",
|
|
92
|
+
"saturation",
|
|
93
|
+
"color",
|
|
94
|
+
"luminosity"
|
|
95
|
+
];
|
|
96
|
+
var OPERATION_EXCEPTIONS = {
|
|
97
|
+
normal: "source-over",
|
|
98
|
+
"linear-dodge": "lighter"
|
|
99
|
+
};
|
|
100
|
+
function blendOperation(mode) {
|
|
101
|
+
return OPERATION_EXCEPTIONS[mode] ?? mode;
|
|
102
|
+
}
|
|
103
|
+
function blendModeOf(operation) {
|
|
104
|
+
if (!operation) return "normal";
|
|
105
|
+
for (const mode of BLEND_MODES) {
|
|
106
|
+
if (blendOperation(mode) === operation) return mode;
|
|
107
|
+
}
|
|
108
|
+
return "normal";
|
|
109
|
+
}
|
|
110
|
+
var BLEND_GROUPS = [
|
|
111
|
+
{ label: "Normal", modes: ["normal"] },
|
|
112
|
+
{ label: "Darken", modes: ["darken", "multiply", "color-burn"] },
|
|
113
|
+
{ label: "Lighten", modes: ["lighten", "screen", "color-dodge", "linear-dodge"] },
|
|
114
|
+
{ label: "Contrast", modes: ["overlay", "soft-light", "hard-light"] },
|
|
115
|
+
{ label: "Comparative", modes: ["difference", "exclusion"] },
|
|
116
|
+
{ label: "Composite", modes: ["hue", "saturation", "color", "luminosity"] }
|
|
117
|
+
];
|
|
118
|
+
function blendModeLabel(mode) {
|
|
119
|
+
if (mode === "linear-dodge") return "Linear Dodge (Add)";
|
|
120
|
+
return mode.split("-").map((word) => (word[0] ?? "").toUpperCase() + word.slice(1)).join(" ");
|
|
121
|
+
}
|
|
122
|
+
|
|
76
123
|
// src/layer.ts
|
|
77
124
|
var Layer = class {
|
|
78
125
|
id;
|
|
@@ -116,6 +163,7 @@ var Layer = class {
|
|
|
116
163
|
return Object.keys(this.meta).length > 0;
|
|
117
164
|
}
|
|
118
165
|
toData() {
|
|
166
|
+
const blend = blendModeOf(this.fabricObject.globalCompositeOperation);
|
|
119
167
|
return {
|
|
120
168
|
id: this.id,
|
|
121
169
|
type: this.type,
|
|
@@ -123,6 +171,7 @@ var Layer = class {
|
|
|
123
171
|
visible: this.visible,
|
|
124
172
|
locked: this.locked,
|
|
125
173
|
opacity: this.opacity,
|
|
174
|
+
...blend !== "normal" ? { blend } : {},
|
|
126
175
|
...this.hasMeta() ? { meta: this.meta } : {},
|
|
127
176
|
...this.children.length > 0 ? { children: this.children.map((c) => c.toData()) } : {}
|
|
128
177
|
};
|
|
@@ -284,7 +333,11 @@ var LayerManager = class {
|
|
|
284
333
|
visible: layer.visible,
|
|
285
334
|
opacity: layer.opacity,
|
|
286
335
|
selectable: !layer.locked,
|
|
287
|
-
evented: !layer.locked
|
|
336
|
+
evented: !layer.locked,
|
|
337
|
+
// Compositing lives only on the fabric object (see `blendModeOf`); carry
|
|
338
|
+
// it from the object being replaced or a blend set before this call
|
|
339
|
+
// silently reverts to normal.
|
|
340
|
+
globalCompositeOperation: previous.globalCompositeOperation
|
|
288
341
|
});
|
|
289
342
|
this.canvas.insertAt(Math.max(0, stackIndex), fabricObject);
|
|
290
343
|
if (layer.renderProxy) {
|
|
@@ -314,7 +367,12 @@ var LayerManager = class {
|
|
|
314
367
|
visible: layer.visible,
|
|
315
368
|
opacity: layer.opacity,
|
|
316
369
|
selectable: !layer.locked,
|
|
317
|
-
evented: !layer.locked
|
|
370
|
+
evented: !layer.locked,
|
|
371
|
+
// The proxy is what actually draws once a layer has one (see
|
|
372
|
+
// `LayerManager`'s class doc); a blend set before the proxy existed
|
|
373
|
+
// must not silently stop applying. Read off the layer's own object —
|
|
374
|
+
// that stays the single source of truth (see `blendModeOf`).
|
|
375
|
+
globalCompositeOperation: layer.fabricObject.globalCompositeOperation
|
|
318
376
|
});
|
|
319
377
|
this.canvas.add(proxy);
|
|
320
378
|
this.syncZOrder();
|
|
@@ -4015,6 +4073,110 @@ var LayerMaskManager = class extends MaskStackStore {
|
|
|
4015
4073
|
}
|
|
4016
4074
|
};
|
|
4017
4075
|
|
|
4076
|
+
// src/gradient.ts
|
|
4077
|
+
import { Color, Gradient } from "fabric";
|
|
4078
|
+
var DEFAULT_GRADIENT_CONFIG = {
|
|
4079
|
+
kind: "linear",
|
|
4080
|
+
angle: 90,
|
|
4081
|
+
center: { x: 0.5, y: 0.5 },
|
|
4082
|
+
radius: 0.5,
|
|
4083
|
+
stops: [
|
|
4084
|
+
{ color: "#ffffff", opacity: 1, position: 0 },
|
|
4085
|
+
{ color: "#000000", opacity: 1, position: 1 }
|
|
4086
|
+
]
|
|
4087
|
+
};
|
|
4088
|
+
var MIN_RADIUS = 1e-3;
|
|
4089
|
+
var MAX_GRADIENT_STOPS = 256;
|
|
4090
|
+
var FALLBACK_STOP_COLOR = DEFAULT_GRADIENT_CONFIG.stops[0].color;
|
|
4091
|
+
function linearCoords(angle, box) {
|
|
4092
|
+
const radians = angle * Math.PI / 180;
|
|
4093
|
+
const sin = Math.sin(radians);
|
|
4094
|
+
const cos = Math.cos(radians);
|
|
4095
|
+
const vx = box.width * sin;
|
|
4096
|
+
const vy = -box.height * cos;
|
|
4097
|
+
const length = Math.abs(box.width * sin) + Math.abs(box.height * cos);
|
|
4098
|
+
const scale = length / (vx * vx + vy * vy || 1);
|
|
4099
|
+
return {
|
|
4100
|
+
x1: 0.5 - scale * vx / 2,
|
|
4101
|
+
y1: 0.5 - scale * vy / 2,
|
|
4102
|
+
x2: 0.5 + scale * vx / 2,
|
|
4103
|
+
y2: 0.5 + scale * vy / 2
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
function angleFromCoords(coords, box) {
|
|
4107
|
+
const dx = (coords.x2 - coords.x1) / (box.width || 1);
|
|
4108
|
+
const dy = (coords.y2 - coords.y1) / (box.height || 1);
|
|
4109
|
+
const degrees = Math.atan2(dx, -dy) * 180 / Math.PI;
|
|
4110
|
+
return (degrees + 360) % 360;
|
|
4111
|
+
}
|
|
4112
|
+
function stopColor(stop) {
|
|
4113
|
+
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4114
|
+
const color = new Color(source);
|
|
4115
|
+
const opacity = Number.isFinite(stop.opacity) ? stop.opacity : 1;
|
|
4116
|
+
color.setAlpha(clamp(opacity, 0, 1));
|
|
4117
|
+
return color.toRgba();
|
|
4118
|
+
}
|
|
4119
|
+
function toFabricGradient(config, box) {
|
|
4120
|
+
const colorStops = [...config.stops].sort((a, b) => a.position - b.position).slice(0, MAX_GRADIENT_STOPS).map((stop) => {
|
|
4121
|
+
const position = Number.isFinite(stop.position) ? stop.position : 0;
|
|
4122
|
+
return { offset: clamp(position, 0, 1), color: stopColor(stop) };
|
|
4123
|
+
});
|
|
4124
|
+
if (config.kind === "radial") {
|
|
4125
|
+
const radius = Math.max(MIN_RADIUS, config.radius);
|
|
4126
|
+
return new Gradient({
|
|
4127
|
+
type: "radial",
|
|
4128
|
+
gradientUnits: "percentage",
|
|
4129
|
+
coords: {
|
|
4130
|
+
x1: config.center.x,
|
|
4131
|
+
y1: config.center.y,
|
|
4132
|
+
r1: 0,
|
|
4133
|
+
x2: config.center.x,
|
|
4134
|
+
y2: config.center.y,
|
|
4135
|
+
r2: radius
|
|
4136
|
+
},
|
|
4137
|
+
colorStops
|
|
4138
|
+
});
|
|
4139
|
+
}
|
|
4140
|
+
return new Gradient({
|
|
4141
|
+
type: "linear",
|
|
4142
|
+
gradientUnits: "percentage",
|
|
4143
|
+
coords: linearCoords(config.angle, box),
|
|
4144
|
+
colorStops
|
|
4145
|
+
});
|
|
4146
|
+
}
|
|
4147
|
+
function readGradientConfig(object) {
|
|
4148
|
+
const fill = object.fill;
|
|
4149
|
+
if (!fill || typeof fill === "string" || !(fill instanceof Gradient)) return null;
|
|
4150
|
+
const stops = fill.colorStops.map((stop) => {
|
|
4151
|
+
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4152
|
+
const color = new Color(source);
|
|
4153
|
+
const offset = Number.isFinite(stop.offset) ? stop.offset : 0;
|
|
4154
|
+
return {
|
|
4155
|
+
color: `#${color.toHex().toLowerCase()}`,
|
|
4156
|
+
opacity: color.getAlpha(),
|
|
4157
|
+
position: clamp(offset, 0, 1)
|
|
4158
|
+
};
|
|
4159
|
+
});
|
|
4160
|
+
if (fill.type === "radial") {
|
|
4161
|
+
const { x1, y1, r2 } = fill.coords;
|
|
4162
|
+
return {
|
|
4163
|
+
kind: "radial",
|
|
4164
|
+
angle: DEFAULT_GRADIENT_CONFIG.angle,
|
|
4165
|
+
center: { x: x1, y: y1 },
|
|
4166
|
+
radius: r2,
|
|
4167
|
+
stops
|
|
4168
|
+
};
|
|
4169
|
+
}
|
|
4170
|
+
const box = { width: object.width || 1, height: object.height || 1 };
|
|
4171
|
+
return {
|
|
4172
|
+
kind: "linear",
|
|
4173
|
+
angle: angleFromCoords(fill.coords, box),
|
|
4174
|
+
center: { ...DEFAULT_GRADIENT_CONFIG.center },
|
|
4175
|
+
radius: DEFAULT_GRADIENT_CONFIG.radius,
|
|
4176
|
+
stops
|
|
4177
|
+
};
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4018
4180
|
// src/editor.ts
|
|
4019
4181
|
var MIN_ZOOM = 0.1;
|
|
4020
4182
|
var MAX_ZOOM = 8;
|
|
@@ -4171,6 +4333,65 @@ var CanvasEditor = class {
|
|
|
4171
4333
|
this.history.save();
|
|
4172
4334
|
return layer;
|
|
4173
4335
|
}
|
|
4336
|
+
/**
|
|
4337
|
+
* Insert a gradient layer. It is an ordinary `Rect` with a gradient fill —
|
|
4338
|
+
* which is exactly why the Node print compositor needs no code for it.
|
|
4339
|
+
*/
|
|
4340
|
+
addGradient(config = DEFAULT_GRADIENT_CONFIG, box) {
|
|
4341
|
+
const width = box?.width ?? this.canvas.getWidth();
|
|
4342
|
+
const height = box?.height ?? this.canvas.getHeight();
|
|
4343
|
+
const object = new Rect4({
|
|
4344
|
+
left: box?.left ?? 0,
|
|
4345
|
+
top: box?.top ?? 0,
|
|
4346
|
+
originX: "left",
|
|
4347
|
+
originY: "top",
|
|
4348
|
+
width,
|
|
4349
|
+
height,
|
|
4350
|
+
// A gradient fill IS the artwork; an outline is not part of this
|
|
4351
|
+
// layer's design, only its own gradient (unlike a plain shape, where a
|
|
4352
|
+
// stroke is a legitimate look).
|
|
4353
|
+
strokeWidth: 0,
|
|
4354
|
+
fill: toFabricGradient(config, { width, height })
|
|
4355
|
+
});
|
|
4356
|
+
const layer = this.layers.add("gradient", object, "Gradient");
|
|
4357
|
+
this.layers.select(layer.id);
|
|
4358
|
+
this.history.save();
|
|
4359
|
+
return layer;
|
|
4360
|
+
}
|
|
4361
|
+
/**
|
|
4362
|
+
* Repaint a layer's gradient. `save: false` while a slider is being dragged —
|
|
4363
|
+
* the settled value is the one worth an undo step.
|
|
4364
|
+
*/
|
|
4365
|
+
setGradient(layerId, config, save = true) {
|
|
4366
|
+
const layer = this.layers.get(layerId);
|
|
4367
|
+
if (!layer) return;
|
|
4368
|
+
const object = layer.fabricObject;
|
|
4369
|
+
object.set({
|
|
4370
|
+
fill: toFabricGradient(config, {
|
|
4371
|
+
width: object.width || 1,
|
|
4372
|
+
height: object.height || 1
|
|
4373
|
+
})
|
|
4374
|
+
});
|
|
4375
|
+
object.dirty = true;
|
|
4376
|
+
this.canvas.requestRenderAll();
|
|
4377
|
+
this.events.emit("layer:modified", { layerId });
|
|
4378
|
+
if (save) this.history.save();
|
|
4379
|
+
}
|
|
4380
|
+
/** Set a layer's blend mode. Applied to the render proxy too, like opacity. */
|
|
4381
|
+
setBlend(layerId, mode) {
|
|
4382
|
+
const layer = this.layers.get(layerId);
|
|
4383
|
+
if (!layer) return;
|
|
4384
|
+
const operation = blendOperation(mode);
|
|
4385
|
+
layer.fabricObject.set({ globalCompositeOperation: operation });
|
|
4386
|
+
layer.fabricObject.dirty = true;
|
|
4387
|
+
if (layer.renderProxy) {
|
|
4388
|
+
layer.renderProxy.set({ globalCompositeOperation: operation });
|
|
4389
|
+
layer.renderProxy.dirty = true;
|
|
4390
|
+
}
|
|
4391
|
+
this.canvas.requestRenderAll();
|
|
4392
|
+
this.events.emit("layer:modified", { layerId });
|
|
4393
|
+
this.history.save();
|
|
4394
|
+
}
|
|
4174
4395
|
async addTemplate(template, params) {
|
|
4175
4396
|
const values = {};
|
|
4176
4397
|
for (const parameter of template.parameters) {
|
|
@@ -5078,10 +5299,13 @@ var AnnotationOverlay = class {
|
|
|
5078
5299
|
};
|
|
5079
5300
|
export {
|
|
5080
5301
|
AnnotationOverlay,
|
|
5302
|
+
BLEND_GROUPS,
|
|
5303
|
+
BLEND_MODES,
|
|
5081
5304
|
CANVAS_MASK_TARGET,
|
|
5082
5305
|
CANVAS_SIZE_PRESETS,
|
|
5083
5306
|
CanvasEditor,
|
|
5084
5307
|
CropController,
|
|
5308
|
+
DEFAULT_GRADIENT_CONFIG,
|
|
5085
5309
|
DEFAULT_LAYER_SHADOW,
|
|
5086
5310
|
DEFAULT_PATTERN_CONFIG,
|
|
5087
5311
|
DEFAULT_SELECTION_STYLE,
|
|
@@ -5107,11 +5331,15 @@ export {
|
|
|
5107
5331
|
TextWrapManager,
|
|
5108
5332
|
TiledPatternObject,
|
|
5109
5333
|
UnitConverter,
|
|
5334
|
+
angleFromCoords,
|
|
5110
5335
|
applyAspectLock,
|
|
5111
5336
|
applyLayerShadow,
|
|
5112
5337
|
applyObjectSelectionStyle,
|
|
5113
5338
|
applySelectionStyle,
|
|
5114
5339
|
applyTextWrapToObject,
|
|
5340
|
+
blendModeLabel,
|
|
5341
|
+
blendModeOf,
|
|
5342
|
+
blendOperation,
|
|
5115
5343
|
buildCurveLinePaths,
|
|
5116
5344
|
buildCurvePathData,
|
|
5117
5345
|
clamp,
|
|
@@ -5137,9 +5365,11 @@ export {
|
|
|
5137
5365
|
isMaskPresetId,
|
|
5138
5366
|
isShapeMaskId,
|
|
5139
5367
|
isTextureMaskId,
|
|
5368
|
+
linearCoords,
|
|
5140
5369
|
needsAbsoluteSpace,
|
|
5141
5370
|
normalizeTextCurve,
|
|
5142
5371
|
preWrapWordSplit,
|
|
5372
|
+
readGradientConfig,
|
|
5143
5373
|
readLayerShadow,
|
|
5144
5374
|
readTextWrap,
|
|
5145
5375
|
renderTextureMask,
|
|
@@ -5152,6 +5382,7 @@ export {
|
|
|
5152
5382
|
textPathCurve,
|
|
5153
5383
|
textPathSpec,
|
|
5154
5384
|
toCanvasSpace,
|
|
5385
|
+
toFabricGradient,
|
|
5155
5386
|
toHostSpace,
|
|
5156
5387
|
unwrapGroup
|
|
5157
5388
|
};
|