@overtone-art/canvas-editor-core 0.2.8 → 0.3.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/{chunk-ORZZ6MGQ.mjs → chunk-MCBRZQ4M.mjs} +34 -1
- package/dist/chunk-MCBRZQ4M.mjs.map +1 -0
- package/dist/index.d.mts +184 -4
- package/dist/index.d.ts +184 -4
- package/dist/index.global.js +67 -67
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +742 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +676 -22
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +1 -1
- package/dist/{types-D60CfxL9.d.mts → types-Dh1unrzT.d.mts} +28 -1
- package/dist/{types-D60CfxL9.d.ts → types-Dh1unrzT.d.ts} +28 -1
- package/package.json +6 -4
- package/dist/chunk-ORZZ6MGQ.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -6,11 +6,12 @@ import {
|
|
|
6
6
|
exportIsolatedPNG,
|
|
7
7
|
exportMockup,
|
|
8
8
|
exportPNG,
|
|
9
|
+
exportPrintArea,
|
|
9
10
|
exportSVG
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MCBRZQ4M.mjs";
|
|
11
12
|
|
|
12
13
|
// src/editor.ts
|
|
13
|
-
import { Canvas, FabricImage as
|
|
14
|
+
import { Canvas, FabricImage as FabricImage4, Group, Textbox, filters, loadSVGFromString, util as util3 } from "fabric";
|
|
14
15
|
|
|
15
16
|
// src/events.ts
|
|
16
17
|
var EventEmitter = class {
|
|
@@ -230,6 +231,38 @@ var LayerManager = class {
|
|
|
230
231
|
this.emitChanged();
|
|
231
232
|
this.onPropertyChanged?.();
|
|
232
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Patch a layer's host metadata. A key whose value is `undefined` is removed.
|
|
236
|
+
*
|
|
237
|
+
* `meta` is part of `LayerData` and is serialized, so a host that assigns
|
|
238
|
+
* `layer.meta.x` directly gets neither of the two things every other setter
|
|
239
|
+
* here provides: `layers:changed` (so `useLayers()` keeps returning the old
|
|
240
|
+
* meta) and the history checkpoint (so an undo silently reverts the write —
|
|
241
|
+
* the state IS versioned, it was just never committed at the moment it
|
|
242
|
+
* changed). Going through this method is what makes metadata behave like
|
|
243
|
+
* every other layer property.
|
|
244
|
+
*/
|
|
245
|
+
setMeta(id, patch) {
|
|
246
|
+
const layer = this.get(id);
|
|
247
|
+
if (!layer) return;
|
|
248
|
+
const next = { ...layer.meta };
|
|
249
|
+
let changed = false;
|
|
250
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
251
|
+
if (value === void 0) {
|
|
252
|
+
if (key in next) {
|
|
253
|
+
delete next[key];
|
|
254
|
+
changed = true;
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (next[key] !== value) changed = true;
|
|
259
|
+
next[key] = value;
|
|
260
|
+
}
|
|
261
|
+
if (!changed) return;
|
|
262
|
+
layer.meta = next;
|
|
263
|
+
this.emitChanged();
|
|
264
|
+
this.onPropertyChanged?.();
|
|
265
|
+
}
|
|
233
266
|
clear() {
|
|
234
267
|
for (const layer of this.layers) {
|
|
235
268
|
this.canvas.remove(layer.fabricObject);
|
|
@@ -1088,6 +1121,521 @@ function mod2(n) {
|
|
|
1088
1121
|
return (n % 2 + 2) % 2;
|
|
1089
1122
|
}
|
|
1090
1123
|
|
|
1124
|
+
// src/text-curve.ts
|
|
1125
|
+
import { Path } from "fabric";
|
|
1126
|
+
var DEFAULT_TEXT_CURVE = { arc: 0, wave: 0 };
|
|
1127
|
+
var MIN_ARC = 0.5;
|
|
1128
|
+
var FULL_CIRCLE_ARC = 99.5;
|
|
1129
|
+
var MIN_SWEEP = 0.12;
|
|
1130
|
+
var WAVE_PERIOD_EM = 4.1;
|
|
1131
|
+
var WAVE_AMPLITUDE_EM = 0.9;
|
|
1132
|
+
var WAVE_STEP = 6;
|
|
1133
|
+
var MEASURE_WIDTH = 1e5;
|
|
1134
|
+
var PATH_SLACK = 0.06;
|
|
1135
|
+
function isCurvable(object) {
|
|
1136
|
+
return !!object && typeof object.text === "string";
|
|
1137
|
+
}
|
|
1138
|
+
function measureText(text) {
|
|
1139
|
+
const authored = text.width;
|
|
1140
|
+
try {
|
|
1141
|
+
text.set({ width: MEASURE_WIDTH });
|
|
1142
|
+
text.initDimensions?.();
|
|
1143
|
+
return Math.max(1, text.calcTextWidth?.() ?? text.width ?? 1);
|
|
1144
|
+
} finally {
|
|
1145
|
+
if (authored !== void 0) text.set({ width: authored });
|
|
1146
|
+
text.initDimensions?.();
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
function arcPathData(width, arc) {
|
|
1150
|
+
const magnitude = Math.min(100, Math.abs(arc));
|
|
1151
|
+
const direction = arc < 0 ? -1 : 1;
|
|
1152
|
+
const full = magnitude >= FULL_CIRCLE_ARC;
|
|
1153
|
+
const sweep = full ? Math.PI * 2 : Math.max(MIN_SWEEP, magnitude / 100 * Math.PI);
|
|
1154
|
+
const radius = width / sweep;
|
|
1155
|
+
const centerX = width / 2;
|
|
1156
|
+
const point = (angle) => [
|
|
1157
|
+
centerX + radius * Math.sin(angle),
|
|
1158
|
+
direction * radius * (1 - Math.cos(angle))
|
|
1159
|
+
];
|
|
1160
|
+
const sweepFlag = direction > 0 ? 1 : 0;
|
|
1161
|
+
const format = ([x, y]) => `${round(x)} ${round(y)}`;
|
|
1162
|
+
if (full) {
|
|
1163
|
+
const start2 = point(-Math.PI);
|
|
1164
|
+
const top = point(0);
|
|
1165
|
+
return {
|
|
1166
|
+
data: [
|
|
1167
|
+
`M ${format(start2)}`,
|
|
1168
|
+
`A ${round(radius)} ${round(radius)} 0 0 ${sweepFlag} ${format(top)}`,
|
|
1169
|
+
`A ${round(radius)} ${round(radius)} 0 0 ${sweepFlag} ${format(start2)}`
|
|
1170
|
+
].join(" "),
|
|
1171
|
+
length: width
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
const start = point(-sweep / 2);
|
|
1175
|
+
const end = point(sweep / 2);
|
|
1176
|
+
const largeArc = sweep > Math.PI ? 1 : 0;
|
|
1177
|
+
return {
|
|
1178
|
+
data: `M ${format(start)} A ${round(radius)} ${round(radius)} 0 ${largeArc} ${sweepFlag} ${format(end)}`,
|
|
1179
|
+
// radius = width / sweep, so the arc is exactly `width` long.
|
|
1180
|
+
length: width
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
function wavePathData(width, fontSize, wave) {
|
|
1184
|
+
const amplitude = clamp(wave, 0, 100) / 100 * fontSize * WAVE_AMPLITUDE_EM;
|
|
1185
|
+
const period = Math.max(1, fontSize * WAVE_PERIOD_EM);
|
|
1186
|
+
const steps = Math.max(2, Math.ceil(width / WAVE_STEP));
|
|
1187
|
+
const commands = [];
|
|
1188
|
+
let length = 0;
|
|
1189
|
+
let previous = null;
|
|
1190
|
+
for (let index = 0; index <= steps; index++) {
|
|
1191
|
+
const x = width * index / steps;
|
|
1192
|
+
const y = amplitude * Math.sin(x / period * Math.PI * 2);
|
|
1193
|
+
if (previous) length += Math.hypot(x - previous[0], y - previous[1]);
|
|
1194
|
+
previous = [x, y];
|
|
1195
|
+
commands.push(`${index === 0 ? "M" : "L"} ${round(x)} ${round(y)}`);
|
|
1196
|
+
}
|
|
1197
|
+
return { data: commands.join(" "), length };
|
|
1198
|
+
}
|
|
1199
|
+
function round(value) {
|
|
1200
|
+
return Math.round(value * 100) / 100;
|
|
1201
|
+
}
|
|
1202
|
+
function buildCurvePathData(config, width, fontSize) {
|
|
1203
|
+
if (Math.abs(config.arc) >= MIN_ARC) return arcPathData(width, config.arc);
|
|
1204
|
+
if (config.wave > 0) return wavePathData(width, fontSize, config.wave);
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
function normalize(config) {
|
|
1208
|
+
const arc = clamp(config.arc ?? 0, -100, 100);
|
|
1209
|
+
return { arc, wave: Math.abs(arc) >= MIN_ARC ? 0 : clamp(config.wave ?? 0, 0, 100) };
|
|
1210
|
+
}
|
|
1211
|
+
var TextCurveManager = class {
|
|
1212
|
+
constructor(canvas, layers, history, events) {
|
|
1213
|
+
this.canvas = canvas;
|
|
1214
|
+
this.layers = layers;
|
|
1215
|
+
this.history = history;
|
|
1216
|
+
this.events = events;
|
|
1217
|
+
}
|
|
1218
|
+
canvas;
|
|
1219
|
+
layers;
|
|
1220
|
+
history;
|
|
1221
|
+
events;
|
|
1222
|
+
/** Curve parameters for a layer, or null when it is not curved text. */
|
|
1223
|
+
get(layerId) {
|
|
1224
|
+
const layer = this.layers.get(layerId);
|
|
1225
|
+
if (!layer || !isCurvable(layer.fabricObject)) return null;
|
|
1226
|
+
return layer.meta.curve ?? { ...DEFAULT_TEXT_CURVE };
|
|
1227
|
+
}
|
|
1228
|
+
isCurved(layerId) {
|
|
1229
|
+
const curve = this.get(layerId);
|
|
1230
|
+
return !!curve && (Math.abs(curve.arc) >= MIN_ARC || curve.wave > 0);
|
|
1231
|
+
}
|
|
1232
|
+
/** Apply (or update) the curve on a text layer. Zeroed config clears it. */
|
|
1233
|
+
apply(layerId, config, save = true) {
|
|
1234
|
+
const layer = this.layers.get(layerId);
|
|
1235
|
+
if (!layer || !isCurvable(layer.fabricObject)) return false;
|
|
1236
|
+
const next = normalize(config);
|
|
1237
|
+
const text = layer.fabricObject;
|
|
1238
|
+
const run = measureText(text);
|
|
1239
|
+
const curve = buildCurvePathData(next, run * (1 + PATH_SLACK), text.fontSize);
|
|
1240
|
+
if (!curve) {
|
|
1241
|
+
this.detach(text, layer.meta.curveWidth);
|
|
1242
|
+
delete layer.meta.curve;
|
|
1243
|
+
delete layer.meta.curveWidth;
|
|
1244
|
+
} else {
|
|
1245
|
+
if (layer.meta.curveWidth === void 0) layer.meta.curveWidth = text.width ?? 0;
|
|
1246
|
+
text.set({ width: Math.max(layer.meta.curveWidth, run + 2) });
|
|
1247
|
+
text.set({
|
|
1248
|
+
path: new Path(curve.data, { visible: false, objectCaching: false }),
|
|
1249
|
+
pathAlign: "center",
|
|
1250
|
+
pathSide: "left",
|
|
1251
|
+
pathStartOffset: Math.max(0, (curve.length - run) / 2)
|
|
1252
|
+
});
|
|
1253
|
+
layer.meta.curve = next;
|
|
1254
|
+
}
|
|
1255
|
+
text.initDimensions?.();
|
|
1256
|
+
text.setCoords();
|
|
1257
|
+
text.dirty = true;
|
|
1258
|
+
this.canvas.requestRenderAll();
|
|
1259
|
+
this.events.emit("layer:modified", { layerId });
|
|
1260
|
+
if (save) this.history.save();
|
|
1261
|
+
return true;
|
|
1262
|
+
}
|
|
1263
|
+
/** Remove the curve, restoring the authored text box width. */
|
|
1264
|
+
clear(layerId, save = true) {
|
|
1265
|
+
return this.apply(layerId, DEFAULT_TEXT_CURVE, save);
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Rebuild the path from the stored parameters. Text content, font family and
|
|
1269
|
+
* font size all change the run's width, and the path is sized to that width —
|
|
1270
|
+
* without this the curve keeps the geometry of the text it was created from.
|
|
1271
|
+
*/
|
|
1272
|
+
refresh(layerId, save = false) {
|
|
1273
|
+
const curve = this.layers.get(layerId)?.meta.curve;
|
|
1274
|
+
if (!curve) return false;
|
|
1275
|
+
return this.apply(layerId, curve, save);
|
|
1276
|
+
}
|
|
1277
|
+
/** Rebuild every curved layer — used after a state restore. */
|
|
1278
|
+
refreshAll() {
|
|
1279
|
+
for (const layer of this.layers.getAll()) {
|
|
1280
|
+
if (layer.meta.curve) this.refresh(layer.id);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
detach(text, authoredWidth) {
|
|
1284
|
+
text.set({ path: null, pathStartOffset: 0 });
|
|
1285
|
+
if (authoredWidth !== void 0 && authoredWidth > 0) text.set({ width: authoredWidth });
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
// src/mask-presets/manager.ts
|
|
1290
|
+
import { FabricImage as FabricImage2, Path as Path2 } from "fabric";
|
|
1291
|
+
|
|
1292
|
+
// src/mask-presets/shapes.ts
|
|
1293
|
+
var SHAPE_MASK_IDS = [
|
|
1294
|
+
"circle",
|
|
1295
|
+
"square",
|
|
1296
|
+
"triangle",
|
|
1297
|
+
"star",
|
|
1298
|
+
"heart",
|
|
1299
|
+
"octagram",
|
|
1300
|
+
"arch",
|
|
1301
|
+
"zigzag"
|
|
1302
|
+
];
|
|
1303
|
+
function isShapeMaskId(value) {
|
|
1304
|
+
return typeof value === "string" && SHAPE_MASK_IDS.includes(value);
|
|
1305
|
+
}
|
|
1306
|
+
function starPoints(points, outer, inner, start = -Math.PI / 2) {
|
|
1307
|
+
const step = Math.PI / points;
|
|
1308
|
+
const coordinates = [];
|
|
1309
|
+
for (let index = 0; index < points * 2; index++) {
|
|
1310
|
+
const radius = index % 2 === 0 ? outer : inner;
|
|
1311
|
+
const angle = start + index * step;
|
|
1312
|
+
const x = 50 + Math.cos(angle) * radius;
|
|
1313
|
+
const y = 50 + Math.sin(angle) * radius;
|
|
1314
|
+
coordinates.push(`${index === 0 ? "M" : "L"} ${round3(x)} ${round3(y)}`);
|
|
1315
|
+
}
|
|
1316
|
+
return `${coordinates.join(" ")} Z`;
|
|
1317
|
+
}
|
|
1318
|
+
function round3(value) {
|
|
1319
|
+
return Math.round(value * 100) / 100;
|
|
1320
|
+
}
|
|
1321
|
+
var SHAPE_PATHS = {
|
|
1322
|
+
circle: "M 96 50 A 46 46 0 1 1 4 50 A 46 46 0 1 1 96 50 Z",
|
|
1323
|
+
square: "M 4 4 H 96 V 96 H 4 Z",
|
|
1324
|
+
triangle: "M 50 4 L 96 96 L 4 96 Z",
|
|
1325
|
+
star: starPoints(5, 46, 22),
|
|
1326
|
+
heart: "M 50 96 C -8 55 12 4 50 28 C 88 4 108 55 50 96 Z",
|
|
1327
|
+
octagram: starPoints(8, 46, 27),
|
|
1328
|
+
arch: "M 4 96 V 50 A 46 46 0 0 1 96 50 V 96 Z",
|
|
1329
|
+
zigzag: starPoints(16, 46, 39)
|
|
1330
|
+
};
|
|
1331
|
+
function shapeMaskPathData(id) {
|
|
1332
|
+
return SHAPE_PATHS[id];
|
|
1333
|
+
}
|
|
1334
|
+
var SHAPE_MASK_BOX = 100;
|
|
1335
|
+
|
|
1336
|
+
// src/mask-presets/textures.ts
|
|
1337
|
+
var TEXTURE_MASK_IDS = [
|
|
1338
|
+
"vignette",
|
|
1339
|
+
"halftone",
|
|
1340
|
+
"spray",
|
|
1341
|
+
"grunge",
|
|
1342
|
+
"torn",
|
|
1343
|
+
"band"
|
|
1344
|
+
];
|
|
1345
|
+
function isTextureMaskId(value) {
|
|
1346
|
+
return typeof value === "string" && TEXTURE_MASK_IDS.includes(value);
|
|
1347
|
+
}
|
|
1348
|
+
var TEXTURE_MASK_SIZE = 320;
|
|
1349
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1350
|
+
function seeded(seed) {
|
|
1351
|
+
let state = seed;
|
|
1352
|
+
return () => {
|
|
1353
|
+
state = state * 16807 % 2147483647;
|
|
1354
|
+
return (state - 1) / 2147483646;
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
function traceRoughEdge(context, random, jitter, inset) {
|
|
1358
|
+
const size = TEXTURE_MASK_SIZE;
|
|
1359
|
+
const corners = [
|
|
1360
|
+
[inset, inset],
|
|
1361
|
+
[size - inset, inset],
|
|
1362
|
+
[size - inset, size - inset],
|
|
1363
|
+
[inset, size - inset]
|
|
1364
|
+
];
|
|
1365
|
+
context.beginPath();
|
|
1366
|
+
for (let corner = 0; corner < corners.length; corner++) {
|
|
1367
|
+
const [fromX, fromY] = corners[corner];
|
|
1368
|
+
const [toX, toY] = corners[(corner + 1) % corners.length];
|
|
1369
|
+
for (let step = 0; step <= 26; step++) {
|
|
1370
|
+
const ratio = step / 26;
|
|
1371
|
+
const x = fromX + (toX - fromX) * ratio + (random() - 0.5) * jitter;
|
|
1372
|
+
const y = fromY + (toY - fromY) * ratio + (random() - 0.5) * jitter;
|
|
1373
|
+
if (corner === 0 && step === 0) context.moveTo(x, y);
|
|
1374
|
+
else context.lineTo(x, y);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
context.closePath();
|
|
1378
|
+
context.fill();
|
|
1379
|
+
}
|
|
1380
|
+
function paint(id, context) {
|
|
1381
|
+
const size = TEXTURE_MASK_SIZE;
|
|
1382
|
+
const center = size / 2;
|
|
1383
|
+
context.fillStyle = "#ffffff";
|
|
1384
|
+
switch (id) {
|
|
1385
|
+
case "vignette": {
|
|
1386
|
+
const gradient = context.createRadialGradient(
|
|
1387
|
+
center,
|
|
1388
|
+
center,
|
|
1389
|
+
size * 0.18,
|
|
1390
|
+
center,
|
|
1391
|
+
center,
|
|
1392
|
+
size * 0.52
|
|
1393
|
+
);
|
|
1394
|
+
gradient.addColorStop(0, "rgba(255,255,255,1)");
|
|
1395
|
+
gradient.addColorStop(1, "rgba(255,255,255,0)");
|
|
1396
|
+
context.fillStyle = gradient;
|
|
1397
|
+
context.fillRect(0, 0, size, size);
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
case "band": {
|
|
1401
|
+
const gradient = context.createLinearGradient(0, 0, 0, size);
|
|
1402
|
+
gradient.addColorStop(0, "rgba(255,255,255,0)");
|
|
1403
|
+
gradient.addColorStop(0.25, "rgba(255,255,255,1)");
|
|
1404
|
+
gradient.addColorStop(0.75, "rgba(255,255,255,1)");
|
|
1405
|
+
gradient.addColorStop(1, "rgba(255,255,255,0)");
|
|
1406
|
+
context.fillStyle = gradient;
|
|
1407
|
+
context.fillRect(0, 0, size, size);
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
case "halftone": {
|
|
1411
|
+
const pitch = 16;
|
|
1412
|
+
for (let y = pitch / 2; y < size; y += pitch) {
|
|
1413
|
+
for (let x = pitch / 2; x < size; x += pitch) {
|
|
1414
|
+
const distance = Math.hypot(x - center, y - center) / (size * 0.52);
|
|
1415
|
+
const radius = Math.max(0, pitch / 2 * (1 - distance) * 1.15);
|
|
1416
|
+
if (radius < 0.4) continue;
|
|
1417
|
+
context.beginPath();
|
|
1418
|
+
context.arc(x, y, radius, 0, Math.PI * 2);
|
|
1419
|
+
context.fill();
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
case "spray": {
|
|
1425
|
+
const random = seeded(42);
|
|
1426
|
+
const core = context.createRadialGradient(center, center, 10, center, center, size * 0.42);
|
|
1427
|
+
core.addColorStop(0, "rgba(255,255,255,1)");
|
|
1428
|
+
core.addColorStop(1, "rgba(255,255,255,0.85)");
|
|
1429
|
+
context.fillStyle = core;
|
|
1430
|
+
context.beginPath();
|
|
1431
|
+
context.arc(center, center, size * 0.42, 0, Math.PI * 2);
|
|
1432
|
+
context.fill();
|
|
1433
|
+
context.fillStyle = "rgba(255,255,255,0.9)";
|
|
1434
|
+
for (let dot = 0; dot < 900; dot++) {
|
|
1435
|
+
const angle = random() * Math.PI * 2;
|
|
1436
|
+
const distance = size * (0.3 + random() * 0.24);
|
|
1437
|
+
const x = center + Math.cos(angle) * distance;
|
|
1438
|
+
const y = center + Math.sin(angle) * distance;
|
|
1439
|
+
context.globalAlpha = 1 - (distance / size - 0.3) / 0.24;
|
|
1440
|
+
context.beginPath();
|
|
1441
|
+
context.arc(x, y, random() * 2.4, 0, Math.PI * 2);
|
|
1442
|
+
context.fill();
|
|
1443
|
+
}
|
|
1444
|
+
context.globalAlpha = 1;
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
case "grunge": {
|
|
1448
|
+
const random = seeded(7);
|
|
1449
|
+
traceRoughEdge(context, random, 14, size * 0.06);
|
|
1450
|
+
context.globalCompositeOperation = "destination-out";
|
|
1451
|
+
for (let speckle = 0; speckle < 240; speckle++) {
|
|
1452
|
+
const x = random() * size;
|
|
1453
|
+
const y = random() * size;
|
|
1454
|
+
const nearEdge = Math.min(x, y, size - x, size - y) < size * 0.12;
|
|
1455
|
+
if (!nearEdge && random() >= 0.12) continue;
|
|
1456
|
+
context.beginPath();
|
|
1457
|
+
context.arc(x, y, random() * 6 + 1, 0, Math.PI * 2);
|
|
1458
|
+
context.fill();
|
|
1459
|
+
}
|
|
1460
|
+
context.globalCompositeOperation = "source-over";
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
case "torn": {
|
|
1464
|
+
traceRoughEdge(context, seeded(99), 10, size * 0.08);
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
function renderTextureMask(id) {
|
|
1470
|
+
const cached = cache.get(id);
|
|
1471
|
+
if (cached) return cached;
|
|
1472
|
+
if (typeof document === "undefined") {
|
|
1473
|
+
throw new Error("Texture masks require a DOM canvas");
|
|
1474
|
+
}
|
|
1475
|
+
const canvas = document.createElement("canvas");
|
|
1476
|
+
canvas.width = TEXTURE_MASK_SIZE;
|
|
1477
|
+
canvas.height = TEXTURE_MASK_SIZE;
|
|
1478
|
+
const context = canvas.getContext("2d");
|
|
1479
|
+
if (!context) throw new Error("Texture masks require a 2D canvas context");
|
|
1480
|
+
paint(id, context);
|
|
1481
|
+
cache.set(id, canvas);
|
|
1482
|
+
return canvas;
|
|
1483
|
+
}
|
|
1484
|
+
function clearTextureMaskCache() {
|
|
1485
|
+
cache.clear();
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// src/mask-presets/manager.ts
|
|
1489
|
+
function isMaskPresetId(value) {
|
|
1490
|
+
return isShapeMaskId(value) || isTextureMaskId(value);
|
|
1491
|
+
}
|
|
1492
|
+
var MaskPresetManager = class {
|
|
1493
|
+
constructor(canvas, layers, history, events) {
|
|
1494
|
+
this.canvas = canvas;
|
|
1495
|
+
this.layers = layers;
|
|
1496
|
+
this.history = history;
|
|
1497
|
+
this.events = events;
|
|
1498
|
+
}
|
|
1499
|
+
canvas;
|
|
1500
|
+
layers;
|
|
1501
|
+
history;
|
|
1502
|
+
events;
|
|
1503
|
+
get(layerId) {
|
|
1504
|
+
return this.layers.get(layerId)?.meta.maskPreset ?? null;
|
|
1505
|
+
}
|
|
1506
|
+
/** Clip the layer to `id`. Passing null (or an unknown id) clears the clip. */
|
|
1507
|
+
apply(layerId, id, save = true) {
|
|
1508
|
+
const layer = this.layers.get(layerId);
|
|
1509
|
+
if (!layer) return false;
|
|
1510
|
+
const object = layer.fabricObject;
|
|
1511
|
+
if (id === null) {
|
|
1512
|
+
if (!layer.meta.maskPreset) return false;
|
|
1513
|
+
object.clipPath = void 0;
|
|
1514
|
+
delete layer.meta.maskPreset;
|
|
1515
|
+
} else {
|
|
1516
|
+
if (!isMaskPresetId(id)) return false;
|
|
1517
|
+
object.clipPath = this.buildClip(id, object);
|
|
1518
|
+
layer.meta.maskPreset = id;
|
|
1519
|
+
}
|
|
1520
|
+
object.dirty = true;
|
|
1521
|
+
object.setCoords();
|
|
1522
|
+
this.canvas.requestRenderAll();
|
|
1523
|
+
this.events.emit("layer:modified", { layerId });
|
|
1524
|
+
if (save) this.history.save();
|
|
1525
|
+
return true;
|
|
1526
|
+
}
|
|
1527
|
+
clear(layerId, save = true) {
|
|
1528
|
+
return this.apply(layerId, null, save);
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Re-fit the clip to the layer's current size. The clip is built for the
|
|
1532
|
+
* object's dimensions at the time it was applied; editing text or replacing an
|
|
1533
|
+
* image changes them, and a stale clip would crop the wrong region.
|
|
1534
|
+
*/
|
|
1535
|
+
refresh(layerId, save = false) {
|
|
1536
|
+
const id = this.get(layerId);
|
|
1537
|
+
if (!id) return false;
|
|
1538
|
+
return this.apply(layerId, id, save);
|
|
1539
|
+
}
|
|
1540
|
+
/** Re-fit every masked layer — used after a state restore. */
|
|
1541
|
+
refreshAll() {
|
|
1542
|
+
for (const layer of this.layers.getAll()) {
|
|
1543
|
+
if (layer.meta.maskPreset) this.refresh(layer.id);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
buildClip(id, object) {
|
|
1547
|
+
const width = Math.max(1, object.width ?? 1);
|
|
1548
|
+
const height = Math.max(1, object.height ?? 1);
|
|
1549
|
+
const shared = {
|
|
1550
|
+
originX: "center",
|
|
1551
|
+
originY: "center",
|
|
1552
|
+
left: 0,
|
|
1553
|
+
top: 0,
|
|
1554
|
+
objectCaching: false
|
|
1555
|
+
};
|
|
1556
|
+
if (isShapeMaskId(id)) {
|
|
1557
|
+
return new Path2(shapeMaskPathData(id), {
|
|
1558
|
+
...shared,
|
|
1559
|
+
scaleX: width / SHAPE_MASK_BOX,
|
|
1560
|
+
scaleY: height / SHAPE_MASK_BOX
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
return new FabricImage2(renderTextureMask(id), {
|
|
1564
|
+
...shared,
|
|
1565
|
+
scaleX: width / TEXTURE_MASK_SIZE,
|
|
1566
|
+
scaleY: height / TEXTURE_MASK_SIZE
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
// src/shadow.ts
|
|
1572
|
+
import { Shadow } from "fabric";
|
|
1573
|
+
|
|
1574
|
+
// src/utils/color.ts
|
|
1575
|
+
var CSS_COLOR = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\([\d.%+\-\s,/]+\)|[a-z]+)$/i;
|
|
1576
|
+
function isCssColor(value, allowEmpty = false) {
|
|
1577
|
+
if (typeof value !== "string") return false;
|
|
1578
|
+
const trimmed = value.trim();
|
|
1579
|
+
if (!trimmed) return allowEmpty;
|
|
1580
|
+
return CSS_COLOR.test(trimmed);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// src/shadow.ts
|
|
1584
|
+
var DEFAULT_LAYER_SHADOW = {
|
|
1585
|
+
enabled: false,
|
|
1586
|
+
color: "#000000",
|
|
1587
|
+
blur: 12,
|
|
1588
|
+
offsetX: 6,
|
|
1589
|
+
offsetY: 6
|
|
1590
|
+
};
|
|
1591
|
+
function readLayerShadow(object) {
|
|
1592
|
+
const shadow = object.shadow;
|
|
1593
|
+
if (!shadow || typeof shadow === "string") return { ...DEFAULT_LAYER_SHADOW };
|
|
1594
|
+
return {
|
|
1595
|
+
enabled: true,
|
|
1596
|
+
color: typeof shadow.color === "string" ? shadow.color : DEFAULT_LAYER_SHADOW.color,
|
|
1597
|
+
blur: shadow.blur ?? DEFAULT_LAYER_SHADOW.blur,
|
|
1598
|
+
offsetX: shadow.offsetX ?? DEFAULT_LAYER_SHADOW.offsetX,
|
|
1599
|
+
offsetY: shadow.offsetY ?? DEFAULT_LAYER_SHADOW.offsetY
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
function applyLayerShadow(object, config) {
|
|
1603
|
+
const next = { ...readLayerShadow(object), ...config };
|
|
1604
|
+
if (!next.enabled) {
|
|
1605
|
+
object.set({ shadow: null });
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
const color = isCssColor(next.color) ? next.color : DEFAULT_LAYER_SHADOW.color;
|
|
1609
|
+
object.set({
|
|
1610
|
+
shadow: new Shadow({
|
|
1611
|
+
color,
|
|
1612
|
+
blur: Math.max(0, next.blur),
|
|
1613
|
+
offsetX: next.offsetX,
|
|
1614
|
+
offsetY: next.offsetY
|
|
1615
|
+
})
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// src/transform.ts
|
|
1620
|
+
var SIDE_CONTROLS = ["ml", "mr", "mt", "mb"];
|
|
1621
|
+
function applyAspectLock(object, locked) {
|
|
1622
|
+
for (const control of SIDE_CONTROLS) {
|
|
1623
|
+
object.setControlVisible(control, !locked);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function resetTransform(object) {
|
|
1627
|
+
object.set({
|
|
1628
|
+
scaleX: 1,
|
|
1629
|
+
scaleY: 1,
|
|
1630
|
+
angle: 0,
|
|
1631
|
+
skewX: 0,
|
|
1632
|
+
skewY: 0,
|
|
1633
|
+
flipX: false,
|
|
1634
|
+
flipY: false
|
|
1635
|
+
});
|
|
1636
|
+
object.setCoords();
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1091
1639
|
// src/utils/units.ts
|
|
1092
1640
|
var MM_PER_INCH = 25.4;
|
|
1093
1641
|
var UnitConverter = class {
|
|
@@ -1137,17 +1685,6 @@ var UnitConverter = class {
|
|
|
1137
1685
|
|
|
1138
1686
|
// src/serialization.ts
|
|
1139
1687
|
import { util as util2 } from "fabric";
|
|
1140
|
-
|
|
1141
|
-
// src/utils/color.ts
|
|
1142
|
-
var CSS_COLOR = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\([\d.%+\-\s,/]+\)|[a-z]+)$/i;
|
|
1143
|
-
function isCssColor(value, allowEmpty = false) {
|
|
1144
|
-
if (typeof value !== "string") return false;
|
|
1145
|
-
const trimmed = value.trim();
|
|
1146
|
-
if (!trimmed) return allowEmpty;
|
|
1147
|
-
return CSS_COLOR.test(trimmed);
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
// src/serialization.ts
|
|
1151
1688
|
var VERSION = "2.0.0";
|
|
1152
1689
|
function serializeEditor(editor) {
|
|
1153
1690
|
return {
|
|
@@ -1226,6 +1763,7 @@ function restoreLayer(editor, serialized, fabricObject) {
|
|
|
1226
1763
|
const layer = editor.layers.add(serialized.type, fabricObject, serialized.name, serialized.id);
|
|
1227
1764
|
if (serialized.meta) {
|
|
1228
1765
|
layer.meta = serialized.meta;
|
|
1766
|
+
if (layer.meta.lockAspect) applyAspectLock(fabricObject, true);
|
|
1229
1767
|
}
|
|
1230
1768
|
if (!serialized.visible) {
|
|
1231
1769
|
editor.layers.setVisibility(layer.id, false);
|
|
@@ -1553,7 +2091,7 @@ var ProjectManager = class {
|
|
|
1553
2091
|
};
|
|
1554
2092
|
|
|
1555
2093
|
// src/mask.ts
|
|
1556
|
-
import { FabricImage as
|
|
2094
|
+
import { FabricImage as FabricImage3 } from "fabric";
|
|
1557
2095
|
var MaskRefinementError = class extends Error {
|
|
1558
2096
|
constructor(code, message, cause) {
|
|
1559
2097
|
super(message);
|
|
@@ -1585,7 +2123,7 @@ var MaskController = class {
|
|
|
1585
2123
|
throw new Error("Mask dimensions must be positive integers");
|
|
1586
2124
|
}
|
|
1587
2125
|
const backing = this.makeCanvas(width, height);
|
|
1588
|
-
const image = new
|
|
2126
|
+
const image = new FabricImage3(backing, {
|
|
1589
2127
|
left: 0,
|
|
1590
2128
|
top: 0,
|
|
1591
2129
|
originX: "left",
|
|
@@ -1825,6 +2363,8 @@ var CanvasEditor = class {
|
|
|
1825
2363
|
snapping;
|
|
1826
2364
|
crop;
|
|
1827
2365
|
patterns;
|
|
2366
|
+
curves;
|
|
2367
|
+
maskPresets;
|
|
1828
2368
|
fonts;
|
|
1829
2369
|
licensing;
|
|
1830
2370
|
pages;
|
|
@@ -1876,6 +2416,8 @@ var CanvasEditor = class {
|
|
|
1876
2416
|
this.events,
|
|
1877
2417
|
config.patternSourceResolver
|
|
1878
2418
|
);
|
|
2419
|
+
this.curves = new TextCurveManager(this.canvas, this.layers, this.history, this.events);
|
|
2420
|
+
this.maskPresets = new MaskPresetManager(this.canvas, this.layers, this.history, this.events);
|
|
1879
2421
|
this.setupCanvasEvents();
|
|
1880
2422
|
this.history.saveImmediate();
|
|
1881
2423
|
this.pages = new ProjectManager(this);
|
|
@@ -1884,7 +2426,7 @@ var CanvasEditor = class {
|
|
|
1884
2426
|
// ─── Layer Operations ────────────────────────────────
|
|
1885
2427
|
async addImage(url, options) {
|
|
1886
2428
|
try {
|
|
1887
|
-
const img = await
|
|
2429
|
+
const img = await FabricImage4.fromURL(
|
|
1888
2430
|
url,
|
|
1889
2431
|
{},
|
|
1890
2432
|
{ originX: "left", originY: "top", ...options }
|
|
@@ -1909,7 +2451,7 @@ var CanvasEditor = class {
|
|
|
1909
2451
|
if (this.crop.activeLayerId() === layerId) this.crop.cancel();
|
|
1910
2452
|
const previous = layer.fabricObject;
|
|
1911
2453
|
try {
|
|
1912
|
-
const replacement = await
|
|
2454
|
+
const replacement = await FabricImage4.fromURL(url, {}, { originX: "left", originY: "top" });
|
|
1913
2455
|
replacement.set({
|
|
1914
2456
|
left: previous.left,
|
|
1915
2457
|
top: previous.top,
|
|
@@ -2148,7 +2690,7 @@ var CanvasEditor = class {
|
|
|
2148
2690
|
const layer = this.layers.get(id);
|
|
2149
2691
|
if (!layer) throw new Error(`Layer not found: ${id}`);
|
|
2150
2692
|
try {
|
|
2151
|
-
if (options.resolution === "source" && layer.fabricObject instanceof
|
|
2693
|
+
if (options.resolution === "source" && layer.fabricObject instanceof FabricImage4) {
|
|
2152
2694
|
const image = await layer.fabricObject.clone();
|
|
2153
2695
|
image.set({
|
|
2154
2696
|
left: 0,
|
|
@@ -2232,6 +2774,23 @@ var CanvasEditor = class {
|
|
|
2232
2774
|
toDataURL(format = "png", multiplier = 1) {
|
|
2233
2775
|
return this.withDesignBackground(() => exportDataURL(this.canvas, format, multiplier));
|
|
2234
2776
|
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Export the print file: the design cropped to the mockup's print area, on
|
|
2779
|
+
* transparency. Without a print area this is the whole canvas, still
|
|
2780
|
+
* transparent — a print file never carries the design background.
|
|
2781
|
+
*/
|
|
2782
|
+
async toPrintFile(options = {}) {
|
|
2783
|
+
await this.fonts.ready();
|
|
2784
|
+
try {
|
|
2785
|
+
const area = this.mockup?.printArea;
|
|
2786
|
+
const blob = area ? await exportPrintArea(this.canvas, area, options) : await exportIsolatedPNG(this.canvas, this.canvas.getObjects(), options);
|
|
2787
|
+
this.licensing.track(`export:print:${options.format ?? "png"}`);
|
|
2788
|
+
return blob;
|
|
2789
|
+
} catch (error) {
|
|
2790
|
+
this.events.emit("error", { message: "Failed to export print file", error });
|
|
2791
|
+
throw error;
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2235
2794
|
/** Export the current product-preview composite. Advanced warping is host-defined. */
|
|
2236
2795
|
async toMockupImage(options = {}) {
|
|
2237
2796
|
if (!this.mockup) throw new Error("No mockup is configured");
|
|
@@ -2403,7 +2962,7 @@ var CanvasEditor = class {
|
|
|
2403
2962
|
return;
|
|
2404
2963
|
}
|
|
2405
2964
|
try {
|
|
2406
|
-
const image = await
|
|
2965
|
+
const image = await FabricImage4.fromURL(
|
|
2407
2966
|
url,
|
|
2408
2967
|
{ crossOrigin: options.crossOrigin ?? null, signal: options.signal },
|
|
2409
2968
|
{ originX: "left", originY: "top" }
|
|
@@ -2578,14 +3137,89 @@ var CanvasEditor = class {
|
|
|
2578
3137
|
clearPattern(layerId) {
|
|
2579
3138
|
return this.patterns.disable(layerId);
|
|
2580
3139
|
}
|
|
3140
|
+
// ─── Text curve ─────────────────────────────────────
|
|
3141
|
+
applyTextCurve(layerId, config) {
|
|
3142
|
+
return this.curves.apply(layerId, config);
|
|
3143
|
+
}
|
|
3144
|
+
clearTextCurve(layerId) {
|
|
3145
|
+
return this.curves.clear(layerId);
|
|
3146
|
+
}
|
|
3147
|
+
getTextCurve(layerId) {
|
|
3148
|
+
return this.curves.get(layerId);
|
|
3149
|
+
}
|
|
3150
|
+
// ─── Mask presets ───────────────────────────────────
|
|
3151
|
+
applyMaskPreset(layerId, id) {
|
|
3152
|
+
return this.maskPresets.apply(layerId, id);
|
|
3153
|
+
}
|
|
3154
|
+
clearMaskPreset(layerId) {
|
|
3155
|
+
return this.maskPresets.clear(layerId);
|
|
3156
|
+
}
|
|
3157
|
+
getMaskPreset(layerId) {
|
|
3158
|
+
return this.maskPresets.get(layerId);
|
|
3159
|
+
}
|
|
3160
|
+
// ─── Layer transform ────────────────────────────────
|
|
3161
|
+
/**
|
|
3162
|
+
* Constrain a layer to its current proportions. Persisted on the layer so a
|
|
3163
|
+
* reopened design still resizes the way it was set up to.
|
|
3164
|
+
*/
|
|
3165
|
+
setLayerAspectLock(layerId, locked) {
|
|
3166
|
+
const layer = this.layers.get(layerId);
|
|
3167
|
+
if (!layer) return false;
|
|
3168
|
+
applyAspectLock(layer.fabricObject, locked);
|
|
3169
|
+
if (locked) layer.meta.lockAspect = true;
|
|
3170
|
+
else delete layer.meta.lockAspect;
|
|
3171
|
+
this.canvas.requestRenderAll();
|
|
3172
|
+
this.events.emit("layer:modified", { layerId });
|
|
3173
|
+
this.history.save();
|
|
3174
|
+
return true;
|
|
3175
|
+
}
|
|
3176
|
+
getLayerAspectLock(layerId) {
|
|
3177
|
+
return this.layers.get(layerId)?.meta.lockAspect === true;
|
|
3178
|
+
}
|
|
3179
|
+
/** Re-apply every stored aspect lock — control visibility is not serialized. */
|
|
3180
|
+
restoreAspectLocks() {
|
|
3181
|
+
for (const layer of this.layers.getAll()) {
|
|
3182
|
+
if (layer.meta.lockAspect) applyAspectLock(layer.fabricObject, true);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
/** Drop scale, rotation, skew and flips; the layer stays where it is. */
|
|
3186
|
+
resetLayerTransform(layerId) {
|
|
3187
|
+
const layer = this.layers.get(layerId);
|
|
3188
|
+
if (!layer) return false;
|
|
3189
|
+
resetTransform(layer.fabricObject);
|
|
3190
|
+
this.canvas.requestRenderAll();
|
|
3191
|
+
this.events.emit("layer:modified", { layerId });
|
|
3192
|
+
this.history.save();
|
|
3193
|
+
return true;
|
|
3194
|
+
}
|
|
3195
|
+
// ─── Shadow ─────────────────────────────────────────
|
|
3196
|
+
setLayerShadow(layerId, config) {
|
|
3197
|
+
const layer = this.layers.get(layerId);
|
|
3198
|
+
if (!layer) return false;
|
|
3199
|
+
applyLayerShadow(layer.fabricObject, config);
|
|
3200
|
+
layer.fabricObject.dirty = true;
|
|
3201
|
+
this.canvas.requestRenderAll();
|
|
3202
|
+
this.events.emit("layer:modified", { layerId });
|
|
3203
|
+
this.history.save();
|
|
3204
|
+
return true;
|
|
3205
|
+
}
|
|
3206
|
+
getLayerShadow(layerId) {
|
|
3207
|
+
const layer = this.layers.get(layerId);
|
|
3208
|
+
return layer ? readLayerShadow(layer.fabricObject) : null;
|
|
3209
|
+
}
|
|
2581
3210
|
// ─── Mockup (preview-only) ──────────────────────────
|
|
2582
|
-
|
|
3211
|
+
/**
|
|
3212
|
+
* Show (or clear) the product preview. Pass `history: false` for preview-only
|
|
3213
|
+
* changes such as swapping a colourway — those are not design edits and
|
|
3214
|
+
* should not fill the undo stack.
|
|
3215
|
+
*/
|
|
3216
|
+
setMockup(mockup, options = {}) {
|
|
2583
3217
|
this.mockup = mockup;
|
|
2584
3218
|
this.canvas.backgroundColor = mockup ? "" : this.designBackground;
|
|
2585
3219
|
this.canvas.backgroundImage = mockup ? void 0 : this.designBackgroundImage ?? void 0;
|
|
2586
3220
|
this.canvas.requestRenderAll();
|
|
2587
3221
|
this.events.emit("mockup:changed", { mockup });
|
|
2588
|
-
this.history.save();
|
|
3222
|
+
if (options.history !== false) this.history.save();
|
|
2589
3223
|
}
|
|
2590
3224
|
clearMockup() {
|
|
2591
3225
|
this.setMockup(null);
|
|
@@ -2696,7 +3330,9 @@ export {
|
|
|
2696
3330
|
CANVAS_SIZE_PRESETS,
|
|
2697
3331
|
CanvasEditor,
|
|
2698
3332
|
CropController,
|
|
3333
|
+
DEFAULT_LAYER_SHADOW,
|
|
2699
3334
|
DEFAULT_PATTERN_CONFIG,
|
|
3335
|
+
DEFAULT_TEXT_CURVE,
|
|
2700
3336
|
EventEmitter,
|
|
2701
3337
|
FontRegistry,
|
|
2702
3338
|
HistoryManager,
|
|
@@ -2704,16 +3340,26 @@ export {
|
|
|
2704
3340
|
LayerManager,
|
|
2705
3341
|
LicenseManager,
|
|
2706
3342
|
MaskController,
|
|
3343
|
+
MaskPresetManager,
|
|
2707
3344
|
MaskRefinementError,
|
|
2708
3345
|
PatternManager,
|
|
2709
3346
|
ProjectManager,
|
|
3347
|
+
SHAPE_MASK_BOX,
|
|
3348
|
+
SHAPE_MASK_IDS,
|
|
2710
3349
|
SnapManager,
|
|
3350
|
+
TEXTURE_MASK_IDS,
|
|
3351
|
+
TEXTURE_MASK_SIZE,
|
|
3352
|
+
TextCurveManager,
|
|
2711
3353
|
UnitConverter,
|
|
3354
|
+
applyAspectLock,
|
|
3355
|
+
applyLayerShadow,
|
|
2712
3356
|
applyPatternLocks,
|
|
3357
|
+
buildCurvePathData,
|
|
2713
3358
|
buildPatternDataURL,
|
|
2714
3359
|
captureLocks,
|
|
2715
3360
|
clamp,
|
|
2716
3361
|
clearPatternImageCache,
|
|
3362
|
+
clearTextureMaskCache,
|
|
2717
3363
|
computeCoverPlacement,
|
|
2718
3364
|
computePrintAreaClip,
|
|
2719
3365
|
computeTilePositions,
|
|
@@ -2724,13 +3370,21 @@ export {
|
|
|
2724
3370
|
exportDataURL,
|
|
2725
3371
|
exportMockup,
|
|
2726
3372
|
exportPNG,
|
|
3373
|
+
exportPrintArea,
|
|
2727
3374
|
exportSVG,
|
|
2728
3375
|
generateId,
|
|
2729
3376
|
isCssColor,
|
|
3377
|
+
isMaskPresetId,
|
|
3378
|
+
isShapeMaskId,
|
|
3379
|
+
isTextureMaskId,
|
|
2730
3380
|
loadPatternImage,
|
|
3381
|
+
readLayerShadow,
|
|
3382
|
+
renderTextureMask,
|
|
3383
|
+
resetTransform,
|
|
2731
3384
|
restoreLocks,
|
|
2732
3385
|
round2,
|
|
2733
3386
|
sanitizeSvg,
|
|
2734
|
-
serializeEditor
|
|
3387
|
+
serializeEditor,
|
|
3388
|
+
shapeMaskPathData
|
|
2735
3389
|
};
|
|
2736
3390
|
//# sourceMappingURL=index.mjs.map
|