@overtone-art/canvas-editor-core 0.7.0 → 0.8.0
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 +254 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +233 -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,109 @@ 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 FALLBACK_STOP_COLOR = DEFAULT_GRADIENT_CONFIG.stops[0].color;
|
|
4090
|
+
function linearCoords(angle, box) {
|
|
4091
|
+
const radians = angle * Math.PI / 180;
|
|
4092
|
+
const sin = Math.sin(radians);
|
|
4093
|
+
const cos = Math.cos(radians);
|
|
4094
|
+
const vx = box.width * sin;
|
|
4095
|
+
const vy = -box.height * cos;
|
|
4096
|
+
const length = Math.abs(box.width * sin) + Math.abs(box.height * cos);
|
|
4097
|
+
const scale = length / (vx * vx + vy * vy || 1);
|
|
4098
|
+
return {
|
|
4099
|
+
x1: 0.5 - scale * vx / 2,
|
|
4100
|
+
y1: 0.5 - scale * vy / 2,
|
|
4101
|
+
x2: 0.5 + scale * vx / 2,
|
|
4102
|
+
y2: 0.5 + scale * vy / 2
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
function angleFromCoords(coords, box) {
|
|
4106
|
+
const dx = (coords.x2 - coords.x1) / (box.width || 1);
|
|
4107
|
+
const dy = (coords.y2 - coords.y1) / (box.height || 1);
|
|
4108
|
+
const degrees = Math.atan2(dx, -dy) * 180 / Math.PI;
|
|
4109
|
+
return (degrees + 360) % 360;
|
|
4110
|
+
}
|
|
4111
|
+
function stopColor(stop) {
|
|
4112
|
+
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4113
|
+
const color = new Color(source);
|
|
4114
|
+
const opacity = Number.isFinite(stop.opacity) ? stop.opacity : 1;
|
|
4115
|
+
color.setAlpha(clamp(opacity, 0, 1));
|
|
4116
|
+
return color.toRgba();
|
|
4117
|
+
}
|
|
4118
|
+
function toFabricGradient(config, box) {
|
|
4119
|
+
const colorStops = [...config.stops].sort((a, b) => a.position - b.position).map((stop) => {
|
|
4120
|
+
const position = Number.isFinite(stop.position) ? stop.position : 0;
|
|
4121
|
+
return { offset: clamp(position, 0, 1), color: stopColor(stop) };
|
|
4122
|
+
});
|
|
4123
|
+
if (config.kind === "radial") {
|
|
4124
|
+
const radius = Math.max(MIN_RADIUS, config.radius);
|
|
4125
|
+
return new Gradient({
|
|
4126
|
+
type: "radial",
|
|
4127
|
+
gradientUnits: "percentage",
|
|
4128
|
+
coords: {
|
|
4129
|
+
x1: config.center.x,
|
|
4130
|
+
y1: config.center.y,
|
|
4131
|
+
r1: 0,
|
|
4132
|
+
x2: config.center.x,
|
|
4133
|
+
y2: config.center.y,
|
|
4134
|
+
r2: radius
|
|
4135
|
+
},
|
|
4136
|
+
colorStops
|
|
4137
|
+
});
|
|
4138
|
+
}
|
|
4139
|
+
return new Gradient({
|
|
4140
|
+
type: "linear",
|
|
4141
|
+
gradientUnits: "percentage",
|
|
4142
|
+
coords: linearCoords(config.angle, box),
|
|
4143
|
+
colorStops
|
|
4144
|
+
});
|
|
4145
|
+
}
|
|
4146
|
+
function readGradientConfig(object) {
|
|
4147
|
+
const fill = object.fill;
|
|
4148
|
+
if (!fill || typeof fill === "string" || !(fill instanceof Gradient)) return null;
|
|
4149
|
+
const stops = fill.colorStops.map((stop) => {
|
|
4150
|
+
const source = isCssColor(stop.color) ? stop.color : FALLBACK_STOP_COLOR;
|
|
4151
|
+
const color = new Color(source);
|
|
4152
|
+
const offset = Number.isFinite(stop.offset) ? stop.offset : 0;
|
|
4153
|
+
return {
|
|
4154
|
+
color: `#${color.toHex().toLowerCase()}`,
|
|
4155
|
+
opacity: color.getAlpha(),
|
|
4156
|
+
position: clamp(offset, 0, 1)
|
|
4157
|
+
};
|
|
4158
|
+
});
|
|
4159
|
+
if (fill.type === "radial") {
|
|
4160
|
+
const { x1, y1, r2 } = fill.coords;
|
|
4161
|
+
return {
|
|
4162
|
+
kind: "radial",
|
|
4163
|
+
angle: DEFAULT_GRADIENT_CONFIG.angle,
|
|
4164
|
+
center: { x: x1, y: y1 },
|
|
4165
|
+
radius: r2,
|
|
4166
|
+
stops
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
4169
|
+
const box = { width: object.width || 1, height: object.height || 1 };
|
|
4170
|
+
return {
|
|
4171
|
+
kind: "linear",
|
|
4172
|
+
angle: angleFromCoords(fill.coords, box),
|
|
4173
|
+
center: { ...DEFAULT_GRADIENT_CONFIG.center },
|
|
4174
|
+
radius: DEFAULT_GRADIENT_CONFIG.radius,
|
|
4175
|
+
stops
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
4178
|
+
|
|
4018
4179
|
// src/editor.ts
|
|
4019
4180
|
var MIN_ZOOM = 0.1;
|
|
4020
4181
|
var MAX_ZOOM = 8;
|
|
@@ -4171,6 +4332,65 @@ var CanvasEditor = class {
|
|
|
4171
4332
|
this.history.save();
|
|
4172
4333
|
return layer;
|
|
4173
4334
|
}
|
|
4335
|
+
/**
|
|
4336
|
+
* Insert a gradient layer. It is an ordinary `Rect` with a gradient fill —
|
|
4337
|
+
* which is exactly why the Node print compositor needs no code for it.
|
|
4338
|
+
*/
|
|
4339
|
+
addGradient(config = DEFAULT_GRADIENT_CONFIG, box) {
|
|
4340
|
+
const width = box?.width ?? this.canvas.getWidth();
|
|
4341
|
+
const height = box?.height ?? this.canvas.getHeight();
|
|
4342
|
+
const object = new Rect4({
|
|
4343
|
+
left: box?.left ?? 0,
|
|
4344
|
+
top: box?.top ?? 0,
|
|
4345
|
+
originX: "left",
|
|
4346
|
+
originY: "top",
|
|
4347
|
+
width,
|
|
4348
|
+
height,
|
|
4349
|
+
// A gradient fill IS the artwork; an outline is not part of this
|
|
4350
|
+
// layer's design, only its own gradient (unlike a plain shape, where a
|
|
4351
|
+
// stroke is a legitimate look).
|
|
4352
|
+
strokeWidth: 0,
|
|
4353
|
+
fill: toFabricGradient(config, { width, height })
|
|
4354
|
+
});
|
|
4355
|
+
const layer = this.layers.add("gradient", object, "Gradient");
|
|
4356
|
+
this.layers.select(layer.id);
|
|
4357
|
+
this.history.save();
|
|
4358
|
+
return layer;
|
|
4359
|
+
}
|
|
4360
|
+
/**
|
|
4361
|
+
* Repaint a layer's gradient. `save: false` while a slider is being dragged —
|
|
4362
|
+
* the settled value is the one worth an undo step.
|
|
4363
|
+
*/
|
|
4364
|
+
setGradient(layerId, config, save = true) {
|
|
4365
|
+
const layer = this.layers.get(layerId);
|
|
4366
|
+
if (!layer) return;
|
|
4367
|
+
const object = layer.fabricObject;
|
|
4368
|
+
object.set({
|
|
4369
|
+
fill: toFabricGradient(config, {
|
|
4370
|
+
width: object.width || 1,
|
|
4371
|
+
height: object.height || 1
|
|
4372
|
+
})
|
|
4373
|
+
});
|
|
4374
|
+
object.dirty = true;
|
|
4375
|
+
this.canvas.requestRenderAll();
|
|
4376
|
+
this.events.emit("layer:modified", { layerId });
|
|
4377
|
+
if (save) this.history.save();
|
|
4378
|
+
}
|
|
4379
|
+
/** Set a layer's blend mode. Applied to the render proxy too, like opacity. */
|
|
4380
|
+
setBlend(layerId, mode) {
|
|
4381
|
+
const layer = this.layers.get(layerId);
|
|
4382
|
+
if (!layer) return;
|
|
4383
|
+
const operation = blendOperation(mode);
|
|
4384
|
+
layer.fabricObject.set({ globalCompositeOperation: operation });
|
|
4385
|
+
layer.fabricObject.dirty = true;
|
|
4386
|
+
if (layer.renderProxy) {
|
|
4387
|
+
layer.renderProxy.set({ globalCompositeOperation: operation });
|
|
4388
|
+
layer.renderProxy.dirty = true;
|
|
4389
|
+
}
|
|
4390
|
+
this.canvas.requestRenderAll();
|
|
4391
|
+
this.events.emit("layer:modified", { layerId });
|
|
4392
|
+
this.history.save();
|
|
4393
|
+
}
|
|
4174
4394
|
async addTemplate(template, params) {
|
|
4175
4395
|
const values = {};
|
|
4176
4396
|
for (const parameter of template.parameters) {
|
|
@@ -5078,10 +5298,13 @@ var AnnotationOverlay = class {
|
|
|
5078
5298
|
};
|
|
5079
5299
|
export {
|
|
5080
5300
|
AnnotationOverlay,
|
|
5301
|
+
BLEND_GROUPS,
|
|
5302
|
+
BLEND_MODES,
|
|
5081
5303
|
CANVAS_MASK_TARGET,
|
|
5082
5304
|
CANVAS_SIZE_PRESETS,
|
|
5083
5305
|
CanvasEditor,
|
|
5084
5306
|
CropController,
|
|
5307
|
+
DEFAULT_GRADIENT_CONFIG,
|
|
5085
5308
|
DEFAULT_LAYER_SHADOW,
|
|
5086
5309
|
DEFAULT_PATTERN_CONFIG,
|
|
5087
5310
|
DEFAULT_SELECTION_STYLE,
|
|
@@ -5107,11 +5330,15 @@ export {
|
|
|
5107
5330
|
TextWrapManager,
|
|
5108
5331
|
TiledPatternObject,
|
|
5109
5332
|
UnitConverter,
|
|
5333
|
+
angleFromCoords,
|
|
5110
5334
|
applyAspectLock,
|
|
5111
5335
|
applyLayerShadow,
|
|
5112
5336
|
applyObjectSelectionStyle,
|
|
5113
5337
|
applySelectionStyle,
|
|
5114
5338
|
applyTextWrapToObject,
|
|
5339
|
+
blendModeLabel,
|
|
5340
|
+
blendModeOf,
|
|
5341
|
+
blendOperation,
|
|
5115
5342
|
buildCurveLinePaths,
|
|
5116
5343
|
buildCurvePathData,
|
|
5117
5344
|
clamp,
|
|
@@ -5137,9 +5364,11 @@ export {
|
|
|
5137
5364
|
isMaskPresetId,
|
|
5138
5365
|
isShapeMaskId,
|
|
5139
5366
|
isTextureMaskId,
|
|
5367
|
+
linearCoords,
|
|
5140
5368
|
needsAbsoluteSpace,
|
|
5141
5369
|
normalizeTextCurve,
|
|
5142
5370
|
preWrapWordSplit,
|
|
5371
|
+
readGradientConfig,
|
|
5143
5372
|
readLayerShadow,
|
|
5144
5373
|
readTextWrap,
|
|
5145
5374
|
renderTextureMask,
|
|
@@ -5152,6 +5381,7 @@ export {
|
|
|
5152
5381
|
textPathCurve,
|
|
5153
5382
|
textPathSpec,
|
|
5154
5383
|
toCanvasSpace,
|
|
5384
|
+
toFabricGradient,
|
|
5155
5385
|
toHostSpace,
|
|
5156
5386
|
unwrapGroup
|
|
5157
5387
|
};
|