@pooder/kit 6.2.1 → 6.2.2
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/.test-dist/src/extensions/dieline/renderBuilder.js +19 -2
- package/.test-dist/src/extensions/image/ImageTool.js +46 -348
- package/.test-dist/src/extensions/image/sessionOverlay.js +148 -0
- package/.test-dist/src/extensions/ruler/RulerTool.js +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +2 -5
- package/dist/index.d.ts +2 -5
- package/dist/index.js +1104 -1252
- package/dist/index.mjs +1103 -1251
- package/package.json +1 -1
- package/src/extensions/dieline/renderBuilder.ts +26 -4
- package/src/extensions/image/ImageTool.ts +57 -412
- package/src/extensions/image/sessionOverlay.ts +206 -0
package/dist/index.mjs
CHANGED
|
@@ -1192,967 +1192,1097 @@ import {
|
|
|
1192
1192
|
controlsUtils
|
|
1193
1193
|
} from "fabric";
|
|
1194
1194
|
|
|
1195
|
-
// src/
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
// src/extensions/bridgeSelection.ts
|
|
1199
|
-
function pickExitIndex(hits) {
|
|
1200
|
-
for (let i = 0; i < hits.length; i++) {
|
|
1201
|
-
const h = hits[i];
|
|
1202
|
-
if (h.insideBelow && !h.insideAbove) return i;
|
|
1203
|
-
}
|
|
1204
|
-
return -1;
|
|
1195
|
+
// src/shared/scene/frame.ts
|
|
1196
|
+
function emptyFrameRect() {
|
|
1197
|
+
return { left: 0, top: 0, width: 0, height: 0 };
|
|
1205
1198
|
}
|
|
1206
|
-
function
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
if (s.outsideAbove) score++;
|
|
1199
|
+
function resolveCutFrameRect(canvasService, configService) {
|
|
1200
|
+
if (!canvasService || !configService) {
|
|
1201
|
+
return emptyFrameRect();
|
|
1210
1202
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
function wrappedDistance(total, start, end) {
|
|
1216
|
-
if (!Number.isFinite(total) || total <= 0) return 0;
|
|
1217
|
-
if (!Number.isFinite(start) || !Number.isFinite(end)) return 0;
|
|
1218
|
-
const s = (start % total + total) % total;
|
|
1219
|
-
const e = (end % total + total) % total;
|
|
1220
|
-
return e >= s ? e - s : total - s + e;
|
|
1221
|
-
}
|
|
1222
|
-
function sampleWrappedOffsets(total, start, end, count) {
|
|
1223
|
-
if (!Number.isFinite(total) || total <= 0) return [];
|
|
1224
|
-
if (!Number.isFinite(start) || !Number.isFinite(end)) return [];
|
|
1225
|
-
const n = Math.max(0, Math.floor(count));
|
|
1226
|
-
if (n <= 0) return [];
|
|
1227
|
-
const dist = wrappedDistance(total, start, end);
|
|
1228
|
-
if (n === 1) return [(start % total + total) % total];
|
|
1229
|
-
const step = dist / (n - 1);
|
|
1230
|
-
const offsets = [];
|
|
1231
|
-
for (let i = 0; i < n; i++) {
|
|
1232
|
-
const raw = start + step * i;
|
|
1233
|
-
const wrapped = (raw % total + total) % total;
|
|
1234
|
-
offsets.push(wrapped);
|
|
1203
|
+
const sizeState = readSizeState(configService);
|
|
1204
|
+
const layout = computeSceneLayout(canvasService, sizeState);
|
|
1205
|
+
if (!layout) {
|
|
1206
|
+
return emptyFrameRect();
|
|
1235
1207
|
}
|
|
1236
|
-
return
|
|
1208
|
+
return canvasService.toSceneRect({
|
|
1209
|
+
left: layout.cutRect.left,
|
|
1210
|
+
top: layout.cutRect.top,
|
|
1211
|
+
width: layout.cutRect.width,
|
|
1212
|
+
height: layout.cutRect.height
|
|
1213
|
+
});
|
|
1237
1214
|
}
|
|
1238
|
-
|
|
1239
|
-
// src/extensions/geometry.ts
|
|
1240
|
-
function resolveFeaturePosition(feature, geometry) {
|
|
1241
|
-
const { x, y, width, height } = geometry;
|
|
1242
|
-
const left = x - width / 2;
|
|
1243
|
-
const top = y - height / 2;
|
|
1215
|
+
function toLayoutSceneRect(rect) {
|
|
1244
1216
|
return {
|
|
1245
|
-
|
|
1246
|
-
|
|
1217
|
+
left: rect.left,
|
|
1218
|
+
top: rect.top,
|
|
1219
|
+
width: rect.width,
|
|
1220
|
+
height: rect.height,
|
|
1221
|
+
space: "scene"
|
|
1247
1222
|
};
|
|
1248
1223
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
paper.view.viewSize = new paper.Size(width, height);
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
var isBridgeDebugEnabled = () => Boolean(globalThis.__POODER_BRIDGE_DEBUG__);
|
|
1257
|
-
function normalizePathItem(shape) {
|
|
1258
|
-
let result = shape;
|
|
1259
|
-
if (typeof result.resolveCrossings === "function") result = result.resolveCrossings();
|
|
1260
|
-
if (typeof result.reduce === "function") result = result.reduce({});
|
|
1261
|
-
if (typeof result.reorient === "function") result = result.reorient(true, true);
|
|
1262
|
-
if (typeof result.reduce === "function") result = result.reduce({});
|
|
1263
|
-
return result;
|
|
1264
|
-
}
|
|
1265
|
-
function getBridgeDelta(itemBounds, overlap) {
|
|
1266
|
-
return Math.max(overlap, Math.min(5, Math.max(1, itemBounds.height * 0.02)));
|
|
1267
|
-
}
|
|
1268
|
-
function getExitHit(args) {
|
|
1269
|
-
const { mainShape, x, bridgeBottom, toY, eps, delta, overlap, op } = args;
|
|
1270
|
-
const ray = new paper.Path.Line({
|
|
1271
|
-
from: [x, bridgeBottom],
|
|
1272
|
-
to: [x, toY],
|
|
1273
|
-
insert: false
|
|
1274
|
-
});
|
|
1275
|
-
const intersections = mainShape.getIntersections(ray) || [];
|
|
1276
|
-
ray.remove();
|
|
1277
|
-
const validHits = intersections.filter((i) => i.point.y < bridgeBottom - eps);
|
|
1278
|
-
if (validHits.length === 0) return null;
|
|
1279
|
-
validHits.sort((a, b) => b.point.y - a.point.y);
|
|
1280
|
-
const flags = validHits.map((h) => {
|
|
1281
|
-
const above = h.point.add(new paper.Point(0, -delta));
|
|
1282
|
-
const below = h.point.add(new paper.Point(0, delta));
|
|
1283
|
-
return {
|
|
1284
|
-
insideAbove: mainShape.contains(above),
|
|
1285
|
-
insideBelow: mainShape.contains(below)
|
|
1286
|
-
};
|
|
1287
|
-
});
|
|
1288
|
-
const idx = pickExitIndex(flags);
|
|
1289
|
-
if (idx < 0) return null;
|
|
1290
|
-
if (isBridgeDebugEnabled()) {
|
|
1291
|
-
console.debug("Geometry: Bridge ray", {
|
|
1292
|
-
x,
|
|
1293
|
-
validHits: validHits.length,
|
|
1294
|
-
idx,
|
|
1295
|
-
delta,
|
|
1296
|
-
overlap,
|
|
1297
|
-
op
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
|
-
const hit = validHits[idx];
|
|
1301
|
-
return { point: hit.point, location: hit };
|
|
1224
|
+
|
|
1225
|
+
// src/shared/runtime/sessionState.ts
|
|
1226
|
+
function cloneWithJson(value) {
|
|
1227
|
+
return JSON.parse(JSON.stringify(value));
|
|
1302
1228
|
}
|
|
1303
|
-
function
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
const scoreB = scoreOutsideAbove(
|
|
1311
|
-
pointsB.map((p) => ({
|
|
1312
|
-
outsideAbove: !mainShape.contains(p.add(new paper.Point(0, -delta)))
|
|
1313
|
-
}))
|
|
1314
|
-
);
|
|
1315
|
-
const ratioA = scoreA / pointsA.length;
|
|
1316
|
-
const ratioB = scoreB / pointsB.length;
|
|
1317
|
-
if (isBridgeDebugEnabled()) {
|
|
1318
|
-
console.debug("Geometry: Bridge chain", {
|
|
1319
|
-
scoreA,
|
|
1320
|
-
scoreB,
|
|
1321
|
-
lenA: pointsA.length,
|
|
1322
|
-
lenB: pointsB.length,
|
|
1323
|
-
ratioA,
|
|
1324
|
-
ratioB,
|
|
1325
|
-
delta,
|
|
1326
|
-
overlap,
|
|
1327
|
-
op
|
|
1328
|
-
});
|
|
1329
|
-
}
|
|
1330
|
-
const ratioEps = 1e-6;
|
|
1331
|
-
if (Math.abs(ratioA - ratioB) > ratioEps) {
|
|
1332
|
-
return ratioA > ratioB ? pointsA : pointsB;
|
|
1229
|
+
function applyCommittedSnapshot(session, nextCommitted, options) {
|
|
1230
|
+
const clone = options.clone;
|
|
1231
|
+
session.committed = clone(nextCommitted);
|
|
1232
|
+
const shouldPreserveDirtyWorking = options.toolActive && options.preserveDirtyWorking !== false && session.hasWorkingChanges;
|
|
1233
|
+
if (!shouldPreserveDirtyWorking) {
|
|
1234
|
+
session.working = clone(session.committed);
|
|
1235
|
+
session.hasWorkingChanges = false;
|
|
1333
1236
|
}
|
|
1334
|
-
if (scoreA !== scoreB) return scoreA > scoreB ? pointsA : pointsB;
|
|
1335
|
-
return pointsA.length <= pointsB.length ? pointsA : pointsB;
|
|
1336
1237
|
}
|
|
1337
|
-
function
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
if (
|
|
1341
|
-
|
|
1342
|
-
return
|
|
1343
|
-
}
|
|
1344
|
-
item.translate(new paper.Point(-bounds.left, -bounds.top));
|
|
1345
|
-
if (fitMode === "stretch") {
|
|
1346
|
-
item.scale(width / bounds.width, height / bounds.height, new paper.Point(0, 0));
|
|
1347
|
-
item.translate(new paper.Point(left, top));
|
|
1348
|
-
return item;
|
|
1238
|
+
function runDeferredConfigUpdate(state, action, cooldownMs = 0) {
|
|
1239
|
+
state.isUpdatingConfig = true;
|
|
1240
|
+
action();
|
|
1241
|
+
if (cooldownMs <= 0) {
|
|
1242
|
+
state.isUpdatingConfig = false;
|
|
1243
|
+
return;
|
|
1349
1244
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
const scaledHeight = bounds.height * uniformScale;
|
|
1354
|
-
item.translate(
|
|
1355
|
-
new paper.Point(
|
|
1356
|
-
left + (width - scaledWidth) / 2,
|
|
1357
|
-
top + (height - scaledHeight) / 2
|
|
1358
|
-
)
|
|
1359
|
-
);
|
|
1360
|
-
return item;
|
|
1361
|
-
}
|
|
1362
|
-
function createNormalizedHeartPath(params) {
|
|
1363
|
-
const { lobeSpread, notchDepth, tipSharpness } = params;
|
|
1364
|
-
const halfSpread = 0.22 + lobeSpread * 0.18;
|
|
1365
|
-
const notchY = 0.06 + notchDepth * 0.2;
|
|
1366
|
-
const shoulderY = 0.24 + notchDepth * 0.2;
|
|
1367
|
-
const topLift = 0.12 + (1 - notchDepth) * 0.06;
|
|
1368
|
-
const topY = notchY - topLift;
|
|
1369
|
-
const sideCtrlY = shoulderY - (0.18 - notchDepth * 0.08);
|
|
1370
|
-
const lowerCtrlY = 0.58 + (1 - tipSharpness) * 0.16;
|
|
1371
|
-
const tipCtrlX = 0.34 - tipSharpness * 0.2;
|
|
1372
|
-
const notchCtrlX = 0.06 + lobeSpread * 0.06;
|
|
1373
|
-
const lobeCtrlX = 0.1 + lobeSpread * 0.08;
|
|
1374
|
-
const notchCtrlY = notchY - topLift * 0.45;
|
|
1375
|
-
const xPeakL = 0.5 - halfSpread;
|
|
1376
|
-
const xPeakR = 0.5 + halfSpread;
|
|
1377
|
-
const heartPath = new paper.Path({ insert: false });
|
|
1378
|
-
heartPath.moveTo(new paper.Point(0.5, notchY));
|
|
1379
|
-
heartPath.cubicCurveTo(
|
|
1380
|
-
new paper.Point(0.5 - notchCtrlX, notchCtrlY),
|
|
1381
|
-
new paper.Point(xPeakL + lobeCtrlX, topY),
|
|
1382
|
-
new paper.Point(xPeakL, topY)
|
|
1383
|
-
);
|
|
1384
|
-
heartPath.cubicCurveTo(
|
|
1385
|
-
new paper.Point(xPeakL - lobeCtrlX, topY),
|
|
1386
|
-
new paper.Point(0, sideCtrlY),
|
|
1387
|
-
new paper.Point(0, shoulderY)
|
|
1388
|
-
);
|
|
1389
|
-
heartPath.cubicCurveTo(
|
|
1390
|
-
new paper.Point(0, lowerCtrlY),
|
|
1391
|
-
new paper.Point(tipCtrlX, 1),
|
|
1392
|
-
new paper.Point(0.5, 1)
|
|
1393
|
-
);
|
|
1394
|
-
heartPath.cubicCurveTo(
|
|
1395
|
-
new paper.Point(1 - tipCtrlX, 1),
|
|
1396
|
-
new paper.Point(1, lowerCtrlY),
|
|
1397
|
-
new paper.Point(1, shoulderY)
|
|
1398
|
-
);
|
|
1399
|
-
heartPath.cubicCurveTo(
|
|
1400
|
-
new paper.Point(1, sideCtrlY),
|
|
1401
|
-
new paper.Point(xPeakR + lobeCtrlX, topY),
|
|
1402
|
-
new paper.Point(xPeakR, topY)
|
|
1403
|
-
);
|
|
1404
|
-
heartPath.cubicCurveTo(
|
|
1405
|
-
new paper.Point(xPeakR - lobeCtrlX, topY),
|
|
1406
|
-
new paper.Point(0.5 + notchCtrlX, notchCtrlY),
|
|
1407
|
-
new paper.Point(0.5, notchY)
|
|
1408
|
-
);
|
|
1409
|
-
heartPath.closed = true;
|
|
1410
|
-
return heartPath;
|
|
1245
|
+
setTimeout(() => {
|
|
1246
|
+
state.isUpdatingConfig = false;
|
|
1247
|
+
}, cooldownMs);
|
|
1411
1248
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
return new paper.Path.Rectangle({
|
|
1427
|
-
point: [x - width / 2, y - height / 2],
|
|
1428
|
-
size: [Math.max(0, width), Math.max(0, height)],
|
|
1429
|
-
radius: Math.max(0, radius)
|
|
1430
|
-
});
|
|
1431
|
-
},
|
|
1432
|
-
circle: (options) => {
|
|
1433
|
-
const { x, y, width, height } = options;
|
|
1434
|
-
const r = Math.min(width, height) / 2;
|
|
1435
|
-
return new paper.Path.Circle({
|
|
1436
|
-
center: new paper.Point(x, y),
|
|
1437
|
-
radius: Math.max(0, r)
|
|
1438
|
-
});
|
|
1439
|
-
},
|
|
1440
|
-
ellipse: (options) => {
|
|
1441
|
-
const { x, y, width, height } = options;
|
|
1442
|
-
return new paper.Path.Ellipse({
|
|
1443
|
-
center: new paper.Point(x, y),
|
|
1444
|
-
radius: [Math.max(0, width / 2), Math.max(0, height / 2)]
|
|
1445
|
-
});
|
|
1446
|
-
},
|
|
1447
|
-
heart: createHeartBaseShape
|
|
1448
|
-
};
|
|
1449
|
-
function createCustomBaseShape(options) {
|
|
1450
|
-
var _a;
|
|
1451
|
-
const {
|
|
1452
|
-
pathData,
|
|
1453
|
-
customSourceWidthPx,
|
|
1454
|
-
customSourceHeightPx,
|
|
1455
|
-
x,
|
|
1456
|
-
y,
|
|
1457
|
-
width,
|
|
1458
|
-
height
|
|
1459
|
-
} = options;
|
|
1460
|
-
if (typeof pathData !== "string" || pathData.trim().length === 0) {
|
|
1461
|
-
return null;
|
|
1462
|
-
}
|
|
1463
|
-
const center = new paper.Point(x, y);
|
|
1464
|
-
const hasMultipleSubPaths = ((_a = (pathData.match(/[Mm]/g) || []).length) != null ? _a : 0) > 1;
|
|
1465
|
-
const path = hasMultipleSubPaths ? new paper.CompoundPath(pathData) : (() => {
|
|
1466
|
-
const single = new paper.Path();
|
|
1467
|
-
single.pathData = pathData;
|
|
1468
|
-
return single;
|
|
1469
|
-
})();
|
|
1470
|
-
const sourceWidth = Number(customSourceWidthPx != null ? customSourceWidthPx : 0);
|
|
1471
|
-
const sourceHeight = Number(customSourceHeightPx != null ? customSourceHeightPx : 0);
|
|
1472
|
-
if (Number.isFinite(sourceWidth) && Number.isFinite(sourceHeight) && sourceWidth > 0 && sourceHeight > 0 && width > 0 && height > 0) {
|
|
1473
|
-
const targetLeft = x - width / 2;
|
|
1474
|
-
const targetTop = y - height / 2;
|
|
1475
|
-
path.scale(width / sourceWidth, height / sourceHeight, new paper.Point(0, 0));
|
|
1476
|
-
path.translate(new paper.Point(targetLeft, targetTop));
|
|
1477
|
-
return path;
|
|
1478
|
-
}
|
|
1479
|
-
if (width > 0 && height > 0 && path.bounds.width > 0 && path.bounds.height > 0) {
|
|
1480
|
-
path.position = center;
|
|
1481
|
-
path.scale(width / path.bounds.width, height / path.bounds.height);
|
|
1482
|
-
return path;
|
|
1483
|
-
}
|
|
1484
|
-
path.position = center;
|
|
1485
|
-
return path;
|
|
1486
|
-
}
|
|
1487
|
-
function createBaseShape(options) {
|
|
1488
|
-
const { shape } = options;
|
|
1489
|
-
if (shape === "custom") {
|
|
1490
|
-
const customShape = createCustomBaseShape(options);
|
|
1491
|
-
if (customShape) return customShape;
|
|
1492
|
-
return BUILTIN_SHAPE_BUILDERS[DEFAULT_DIELINE_SHAPE](options);
|
|
1493
|
-
}
|
|
1494
|
-
return BUILTIN_SHAPE_BUILDERS[shape](options);
|
|
1495
|
-
}
|
|
1496
|
-
function resolveBridgeBasePath(shape, anchor) {
|
|
1497
|
-
if (shape instanceof paper.Path) {
|
|
1498
|
-
return shape;
|
|
1499
|
-
}
|
|
1500
|
-
if (shape instanceof paper.CompoundPath) {
|
|
1501
|
-
const children = (shape.children || []).filter(
|
|
1502
|
-
(child) => child instanceof paper.Path
|
|
1503
|
-
);
|
|
1504
|
-
if (!children.length) return null;
|
|
1505
|
-
let best = children[0];
|
|
1506
|
-
let bestDistance = Infinity;
|
|
1507
|
-
for (const child of children) {
|
|
1508
|
-
const location = child.getNearestLocation(anchor);
|
|
1509
|
-
const point = location == null ? void 0 : location.point;
|
|
1510
|
-
if (!point) continue;
|
|
1511
|
-
const distance = point.getDistance(anchor);
|
|
1512
|
-
if (distance < bestDistance) {
|
|
1513
|
-
bestDistance = distance;
|
|
1514
|
-
best = child;
|
|
1249
|
+
|
|
1250
|
+
// src/extensions/image/commands.ts
|
|
1251
|
+
function createImageCommands(tool) {
|
|
1252
|
+
return [
|
|
1253
|
+
{
|
|
1254
|
+
command: "addImage",
|
|
1255
|
+
id: "addImage",
|
|
1256
|
+
title: "Add Image",
|
|
1257
|
+
handler: async (url, options) => {
|
|
1258
|
+
const result = await tool.upsertImageEntry(url, {
|
|
1259
|
+
mode: "add",
|
|
1260
|
+
addOptions: options
|
|
1261
|
+
});
|
|
1262
|
+
return result.id;
|
|
1515
1263
|
}
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
command: "upsertImage",
|
|
1267
|
+
id: "upsertImage",
|
|
1268
|
+
title: "Upsert Image",
|
|
1269
|
+
handler: async (url, options = {}) => {
|
|
1270
|
+
return await tool.upsertImageEntry(url, options);
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
command: "getWorkingImages",
|
|
1275
|
+
id: "getWorkingImages",
|
|
1276
|
+
title: "Get Working Images",
|
|
1277
|
+
handler: () => {
|
|
1278
|
+
return tool.cloneItems(tool.workingItems);
|
|
1279
|
+
}
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
command: "setWorkingImage",
|
|
1283
|
+
id: "setWorkingImage",
|
|
1284
|
+
title: "Set Working Image",
|
|
1285
|
+
handler: (id, updates) => {
|
|
1286
|
+
tool.updateImageInWorking(id, updates);
|
|
1287
|
+
}
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
command: "resetWorkingImages",
|
|
1291
|
+
id: "resetWorkingImages",
|
|
1292
|
+
title: "Reset Working Images",
|
|
1293
|
+
handler: () => {
|
|
1294
|
+
tool.workingItems = tool.cloneItems(tool.items);
|
|
1295
|
+
tool.hasWorkingChanges = false;
|
|
1296
|
+
tool.updateImages();
|
|
1297
|
+
tool.emitWorkingChange();
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
command: "completeImages",
|
|
1302
|
+
id: "completeImages",
|
|
1303
|
+
title: "Complete Images",
|
|
1304
|
+
handler: async () => {
|
|
1305
|
+
return await tool.commitWorkingImagesAsCropped();
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
command: "exportUserCroppedImage",
|
|
1310
|
+
id: "exportUserCroppedImage",
|
|
1311
|
+
title: "Export User Cropped Image",
|
|
1312
|
+
handler: async (options = {}) => {
|
|
1313
|
+
return await tool.exportUserCroppedImage(options);
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
command: "fitImageToArea",
|
|
1318
|
+
id: "fitImageToArea",
|
|
1319
|
+
title: "Fit Image to Area",
|
|
1320
|
+
handler: async (id, area) => {
|
|
1321
|
+
await tool.fitImageToArea(id, area);
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
command: "fitImageToDefaultArea",
|
|
1326
|
+
id: "fitImageToDefaultArea",
|
|
1327
|
+
title: "Fit Image to Default Area",
|
|
1328
|
+
handler: async (id) => {
|
|
1329
|
+
await tool.fitImageToDefaultArea(id);
|
|
1330
|
+
}
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
command: "focusImage",
|
|
1334
|
+
id: "focusImage",
|
|
1335
|
+
title: "Focus Image",
|
|
1336
|
+
handler: (id, options = {}) => {
|
|
1337
|
+
return tool.setImageFocus(id, options);
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
{
|
|
1341
|
+
command: "removeImage",
|
|
1342
|
+
id: "removeImage",
|
|
1343
|
+
title: "Remove Image",
|
|
1344
|
+
handler: (id) => {
|
|
1345
|
+
const removed = tool.items.find((item) => item.id === id);
|
|
1346
|
+
const next = tool.items.filter((item) => item.id !== id);
|
|
1347
|
+
if (next.length !== tool.items.length) {
|
|
1348
|
+
tool.purgeSourceSizeCacheForItem(removed);
|
|
1349
|
+
if (tool.focusedImageId === id) {
|
|
1350
|
+
tool.setImageFocus(null, {
|
|
1351
|
+
syncCanvasSelection: true,
|
|
1352
|
+
skipRender: true
|
|
1593
1353
|
});
|
|
1594
|
-
if (leftHit && rightHit) {
|
|
1595
|
-
const pathLength = bridgeBasePath.length;
|
|
1596
|
-
const leftOffset = leftHit.location.offset;
|
|
1597
|
-
const rightOffset = rightHit.location.offset;
|
|
1598
|
-
const distanceA = wrappedDistance(pathLength, leftOffset, rightOffset);
|
|
1599
|
-
const distanceB = wrappedDistance(pathLength, rightOffset, leftOffset);
|
|
1600
|
-
const countFor = (d) => Math.max(8, Math.min(80, Math.ceil(d / 6)));
|
|
1601
|
-
const offsetsA = sampleWrappedOffsets(
|
|
1602
|
-
pathLength,
|
|
1603
|
-
leftOffset,
|
|
1604
|
-
rightOffset,
|
|
1605
|
-
countFor(distanceA)
|
|
1606
|
-
);
|
|
1607
|
-
const offsetsB = sampleWrappedOffsets(
|
|
1608
|
-
pathLength,
|
|
1609
|
-
rightOffset,
|
|
1610
|
-
leftOffset,
|
|
1611
|
-
countFor(distanceB)
|
|
1612
|
-
);
|
|
1613
|
-
const pointsA = offsetsA.map((o) => bridgeBasePath.getPointAt(o)).filter((p) => Boolean(p));
|
|
1614
|
-
const pointsB = offsetsB.map((o) => bridgeBasePath.getPointAt(o)).filter((p) => Boolean(p));
|
|
1615
|
-
if (pointsA.length >= 2 && pointsB.length >= 2) {
|
|
1616
|
-
let topBase = selectOuterChain({
|
|
1617
|
-
mainShape: bridgeBasePath,
|
|
1618
|
-
pointsA,
|
|
1619
|
-
pointsB,
|
|
1620
|
-
delta,
|
|
1621
|
-
overlap,
|
|
1622
|
-
op: f.operation
|
|
1623
|
-
});
|
|
1624
|
-
const dist2 = (a, b) => {
|
|
1625
|
-
const dx = a.x - b.x;
|
|
1626
|
-
const dy = a.y - b.y;
|
|
1627
|
-
return dx * dx + dy * dy;
|
|
1628
|
-
};
|
|
1629
|
-
if (dist2(topBase[0], leftHit.point) > dist2(topBase[0], rightHit.point)) {
|
|
1630
|
-
topBase = topBase.slice().reverse();
|
|
1631
|
-
}
|
|
1632
|
-
topBase = topBase.slice();
|
|
1633
|
-
topBase[0] = leftHit.point;
|
|
1634
|
-
topBase[topBase.length - 1] = rightHit.point;
|
|
1635
|
-
const capShiftY = f.operation === "subtract" ? -Math.max(overlap * 2, delta) : overlap;
|
|
1636
|
-
const topPoints = topBase.map(
|
|
1637
|
-
(p) => p.add(new paper.Point(0, capShiftY))
|
|
1638
|
-
);
|
|
1639
|
-
const bridgeBottomY = bridgeBottom + overlap * 2;
|
|
1640
|
-
const bridgePoly = new paper.Path({ insert: false });
|
|
1641
|
-
for (const p of topPoints) bridgePoly.add(p);
|
|
1642
|
-
bridgePoly.add(new paper.Point(xRight, bridgeBottomY));
|
|
1643
|
-
bridgePoly.add(new paper.Point(xLeft, bridgeBottomY));
|
|
1644
|
-
bridgePoly.closed = true;
|
|
1645
|
-
const unitedItem = item.unite(bridgePoly);
|
|
1646
|
-
item.remove();
|
|
1647
|
-
bridgePoly.remove();
|
|
1648
|
-
if (f.operation === "add") {
|
|
1649
|
-
adds.push(unitedItem);
|
|
1650
|
-
} else {
|
|
1651
|
-
subtracts.push(unitedItem);
|
|
1652
|
-
}
|
|
1653
|
-
return;
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
if (f.operation === "add") {
|
|
1658
|
-
adds.push(item);
|
|
1659
|
-
} else {
|
|
1660
|
-
subtracts.push(item);
|
|
1661
1354
|
}
|
|
1662
|
-
|
|
1663
|
-
if (f.operation === "add") {
|
|
1664
|
-
adds.push(item);
|
|
1665
|
-
} else {
|
|
1666
|
-
subtracts.push(item);
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
} else {
|
|
1670
|
-
if (f.operation === "add") {
|
|
1671
|
-
adds.push(item);
|
|
1672
|
-
} else {
|
|
1673
|
-
subtracts.push(item);
|
|
1355
|
+
tool.updateConfig(next);
|
|
1674
1356
|
}
|
|
1675
1357
|
}
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
command: "updateImage",
|
|
1361
|
+
id: "updateImage",
|
|
1362
|
+
title: "Update Image",
|
|
1363
|
+
handler: async (id, updates, options = {}) => {
|
|
1364
|
+
await tool.updateImage(id, updates, options);
|
|
1365
|
+
}
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
command: "clearImages",
|
|
1369
|
+
id: "clearImages",
|
|
1370
|
+
title: "Clear Images",
|
|
1371
|
+
handler: () => {
|
|
1372
|
+
tool.sourceSizeCache.clear();
|
|
1373
|
+
tool.setImageFocus(null, {
|
|
1374
|
+
syncCanvasSelection: true,
|
|
1375
|
+
skipRender: true
|
|
1376
|
+
});
|
|
1377
|
+
tool.updateConfig([]);
|
|
1378
|
+
}
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
command: "bringToFront",
|
|
1382
|
+
id: "bringToFront",
|
|
1383
|
+
title: "Bring Image to Front",
|
|
1384
|
+
handler: (id) => {
|
|
1385
|
+
const index = tool.items.findIndex((item) => item.id === id);
|
|
1386
|
+
if (index !== -1 && index < tool.items.length - 1) {
|
|
1387
|
+
const next = [...tool.items];
|
|
1388
|
+
const [item] = next.splice(index, 1);
|
|
1389
|
+
next.push(item);
|
|
1390
|
+
tool.updateConfig(next);
|
|
1687
1391
|
}
|
|
1688
1392
|
}
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
command: "sendToBack",
|
|
1396
|
+
id: "sendToBack",
|
|
1397
|
+
title: "Send Image to Back",
|
|
1398
|
+
handler: (id) => {
|
|
1399
|
+
const index = tool.items.findIndex((item) => item.id === id);
|
|
1400
|
+
if (index > 0) {
|
|
1401
|
+
const next = [...tool.items];
|
|
1402
|
+
const [item] = next.splice(index, 1);
|
|
1403
|
+
next.unshift(item);
|
|
1404
|
+
tool.updateConfig(next);
|
|
1700
1405
|
}
|
|
1701
1406
|
}
|
|
1702
1407
|
}
|
|
1408
|
+
];
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// src/extensions/image/config.ts
|
|
1412
|
+
function createImageConfigurations() {
|
|
1413
|
+
return [
|
|
1414
|
+
{
|
|
1415
|
+
id: "image.items",
|
|
1416
|
+
type: "array",
|
|
1417
|
+
label: "Images",
|
|
1418
|
+
default: []
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
id: "image.debug",
|
|
1422
|
+
type: "boolean",
|
|
1423
|
+
label: "Image Debug Log",
|
|
1424
|
+
default: false
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
id: "image.control.cornerSize",
|
|
1428
|
+
type: "number",
|
|
1429
|
+
label: "Image Control Corner Size",
|
|
1430
|
+
min: 4,
|
|
1431
|
+
max: 64,
|
|
1432
|
+
step: 1,
|
|
1433
|
+
default: 14
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
id: "image.control.touchCornerSize",
|
|
1437
|
+
type: "number",
|
|
1438
|
+
label: "Image Control Touch Corner Size",
|
|
1439
|
+
min: 8,
|
|
1440
|
+
max: 96,
|
|
1441
|
+
step: 1,
|
|
1442
|
+
default: 24
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
id: "image.control.cornerStyle",
|
|
1446
|
+
type: "select",
|
|
1447
|
+
label: "Image Control Corner Style",
|
|
1448
|
+
options: ["circle", "rect"],
|
|
1449
|
+
default: "circle"
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
id: "image.control.cornerColor",
|
|
1453
|
+
type: "color",
|
|
1454
|
+
label: "Image Control Corner Color",
|
|
1455
|
+
default: "#ffffff"
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
id: "image.control.cornerStrokeColor",
|
|
1459
|
+
type: "color",
|
|
1460
|
+
label: "Image Control Corner Stroke Color",
|
|
1461
|
+
default: "#1677ff"
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
id: "image.control.transparentCorners",
|
|
1465
|
+
type: "boolean",
|
|
1466
|
+
label: "Image Control Transparent Corners",
|
|
1467
|
+
default: false
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
id: "image.control.borderColor",
|
|
1471
|
+
type: "color",
|
|
1472
|
+
label: "Image Control Border Color",
|
|
1473
|
+
default: "#1677ff"
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
id: "image.control.borderScaleFactor",
|
|
1477
|
+
type: "number",
|
|
1478
|
+
label: "Image Control Border Width",
|
|
1479
|
+
min: 0.5,
|
|
1480
|
+
max: 8,
|
|
1481
|
+
step: 0.1,
|
|
1482
|
+
default: 1.5
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
id: "image.control.padding",
|
|
1486
|
+
type: "number",
|
|
1487
|
+
label: "Image Control Padding",
|
|
1488
|
+
min: 0,
|
|
1489
|
+
max: 64,
|
|
1490
|
+
step: 1,
|
|
1491
|
+
default: 0
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
id: "image.frame.strokeColor",
|
|
1495
|
+
type: "color",
|
|
1496
|
+
label: "Image Frame Stroke Color",
|
|
1497
|
+
default: "#808080"
|
|
1498
|
+
},
|
|
1499
|
+
{
|
|
1500
|
+
id: "image.frame.strokeWidth",
|
|
1501
|
+
type: "number",
|
|
1502
|
+
label: "Image Frame Stroke Width",
|
|
1503
|
+
min: 0,
|
|
1504
|
+
max: 20,
|
|
1505
|
+
step: 0.5,
|
|
1506
|
+
default: 2
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
id: "image.frame.strokeStyle",
|
|
1510
|
+
type: "select",
|
|
1511
|
+
label: "Image Frame Stroke Style",
|
|
1512
|
+
options: ["solid", "dashed", "hidden"],
|
|
1513
|
+
default: "dashed"
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
id: "image.frame.dashLength",
|
|
1517
|
+
type: "number",
|
|
1518
|
+
label: "Image Frame Dash Length",
|
|
1519
|
+
min: 1,
|
|
1520
|
+
max: 40,
|
|
1521
|
+
step: 1,
|
|
1522
|
+
default: 8
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
id: "image.frame.innerBackground",
|
|
1526
|
+
type: "color",
|
|
1527
|
+
label: "Image Frame Inner Background",
|
|
1528
|
+
default: "rgba(0,0,0,0)"
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
id: "image.frame.outerBackground",
|
|
1532
|
+
type: "color",
|
|
1533
|
+
label: "Image Frame Outer Background",
|
|
1534
|
+
default: "#f5f5f5"
|
|
1535
|
+
}
|
|
1536
|
+
];
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/extensions/geometry.ts
|
|
1540
|
+
import paper from "paper";
|
|
1541
|
+
|
|
1542
|
+
// src/extensions/bridgeSelection.ts
|
|
1543
|
+
function pickExitIndex(hits) {
|
|
1544
|
+
for (let i = 0; i < hits.length; i++) {
|
|
1545
|
+
const h = hits[i];
|
|
1546
|
+
if (h.insideBelow && !h.insideAbove) return i;
|
|
1703
1547
|
}
|
|
1704
|
-
return
|
|
1548
|
+
return -1;
|
|
1705
1549
|
}
|
|
1706
|
-
function
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
if (surfaceFeatures.length === 0) return shape;
|
|
1711
|
-
let result = shape;
|
|
1712
|
-
for (const f of surfaceFeatures) {
|
|
1713
|
-
const pos = resolveFeaturePosition(f, options);
|
|
1714
|
-
const center = new paper.Point(pos.x, pos.y);
|
|
1715
|
-
const item = createFeatureItem(f, center);
|
|
1716
|
-
try {
|
|
1717
|
-
if (f.operation === "add") {
|
|
1718
|
-
const temp = result.unite(item);
|
|
1719
|
-
result.remove();
|
|
1720
|
-
item.remove();
|
|
1721
|
-
result = normalizePathItem(temp);
|
|
1722
|
-
} else {
|
|
1723
|
-
const temp = result.subtract(item);
|
|
1724
|
-
result.remove();
|
|
1725
|
-
item.remove();
|
|
1726
|
-
result = normalizePathItem(temp);
|
|
1727
|
-
}
|
|
1728
|
-
} catch (e) {
|
|
1729
|
-
console.error("Geometry: Failed to apply surface feature", e);
|
|
1730
|
-
item.remove();
|
|
1731
|
-
}
|
|
1550
|
+
function scoreOutsideAbove(samples) {
|
|
1551
|
+
let score = 0;
|
|
1552
|
+
for (const s of samples) {
|
|
1553
|
+
if (s.outsideAbove) score++;
|
|
1732
1554
|
}
|
|
1733
|
-
return
|
|
1555
|
+
return score;
|
|
1734
1556
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
const
|
|
1741
|
-
const
|
|
1742
|
-
|
|
1743
|
-
finalShape.remove();
|
|
1744
|
-
return pathData;
|
|
1557
|
+
|
|
1558
|
+
// src/extensions/wrappedOffsets.ts
|
|
1559
|
+
function wrappedDistance(total, start, end) {
|
|
1560
|
+
if (!Number.isFinite(total) || total <= 0) return 0;
|
|
1561
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return 0;
|
|
1562
|
+
const s = (start % total + total) % total;
|
|
1563
|
+
const e = (end % total + total) % total;
|
|
1564
|
+
return e >= s ? e - s : total - s + e;
|
|
1745
1565
|
}
|
|
1746
|
-
function
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
pOffset,
|
|
1760
|
-
offsetOptions.features,
|
|
1761
|
-
offsetOptions
|
|
1762
|
-
);
|
|
1763
|
-
let bleedZone;
|
|
1764
|
-
if (offset > 0) {
|
|
1765
|
-
bleedZone = shapeOffset.subtract(shapeOriginal);
|
|
1766
|
-
} else {
|
|
1767
|
-
bleedZone = shapeOriginal.subtract(shapeOffset);
|
|
1566
|
+
function sampleWrappedOffsets(total, start, end, count) {
|
|
1567
|
+
if (!Number.isFinite(total) || total <= 0) return [];
|
|
1568
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return [];
|
|
1569
|
+
const n = Math.max(0, Math.floor(count));
|
|
1570
|
+
if (n <= 0) return [];
|
|
1571
|
+
const dist = wrappedDistance(total, start, end);
|
|
1572
|
+
if (n === 1) return [(start % total + total) % total];
|
|
1573
|
+
const step = dist / (n - 1);
|
|
1574
|
+
const offsets = [];
|
|
1575
|
+
for (let i = 0; i < n; i++) {
|
|
1576
|
+
const raw = start + step * i;
|
|
1577
|
+
const wrapped = (raw % total + total) % total;
|
|
1578
|
+
offsets.push(wrapped);
|
|
1768
1579
|
}
|
|
1769
|
-
|
|
1770
|
-
shapeOriginal.remove();
|
|
1771
|
-
shapeOffset.remove();
|
|
1772
|
-
bleedZone.remove();
|
|
1773
|
-
return pathData;
|
|
1580
|
+
return offsets;
|
|
1774
1581
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
const
|
|
1779
|
-
const
|
|
1780
|
-
const
|
|
1781
|
-
|
|
1782
|
-
|
|
1582
|
+
|
|
1583
|
+
// src/extensions/geometry.ts
|
|
1584
|
+
function resolveFeaturePosition(feature, geometry) {
|
|
1585
|
+
const { x, y, width, height } = geometry;
|
|
1586
|
+
const left = x - width / 2;
|
|
1587
|
+
const top = y - height / 2;
|
|
1588
|
+
return {
|
|
1589
|
+
x: left + feature.x * width,
|
|
1590
|
+
y: top + feature.y * height
|
|
1783
1591
|
};
|
|
1784
|
-
shape.remove();
|
|
1785
|
-
return result;
|
|
1786
1592
|
}
|
|
1787
|
-
function
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1593
|
+
function ensurePaper(width, height) {
|
|
1594
|
+
if (!paper.project) {
|
|
1595
|
+
paper.setup(new paper.Size(width, height));
|
|
1596
|
+
} else {
|
|
1597
|
+
paper.view.viewSize = new paper.Size(width, height);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
var isBridgeDebugEnabled = () => Boolean(globalThis.__POODER_BRIDGE_DEBUG__);
|
|
1601
|
+
function normalizePathItem(shape) {
|
|
1602
|
+
let result = shape;
|
|
1603
|
+
if (typeof result.resolveCrossings === "function") result = result.resolveCrossings();
|
|
1604
|
+
if (typeof result.reduce === "function") result = result.reduce({});
|
|
1605
|
+
if (typeof result.reorient === "function") result = result.reorient(true, true);
|
|
1606
|
+
if (typeof result.reduce === "function") result = result.reduce({});
|
|
1799
1607
|
return result;
|
|
1800
1608
|
}
|
|
1801
|
-
function
|
|
1802
|
-
|
|
1803
|
-
path.pathData = pathData;
|
|
1804
|
-
const bounds = path.bounds;
|
|
1805
|
-
path.remove();
|
|
1806
|
-
return {
|
|
1807
|
-
x: bounds.x,
|
|
1808
|
-
y: bounds.y,
|
|
1809
|
-
width: bounds.width,
|
|
1810
|
-
height: bounds.height
|
|
1811
|
-
};
|
|
1609
|
+
function getBridgeDelta(itemBounds, overlap) {
|
|
1610
|
+
return Math.max(overlap, Math.min(5, Math.max(1, itemBounds.height * 0.02)));
|
|
1812
1611
|
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1612
|
+
function getExitHit(args) {
|
|
1613
|
+
const { mainShape, x, bridgeBottom, toY, eps, delta, overlap, op } = args;
|
|
1614
|
+
const ray = new paper.Path.Line({
|
|
1615
|
+
from: [x, bridgeBottom],
|
|
1616
|
+
to: [x, toY],
|
|
1617
|
+
insert: false
|
|
1618
|
+
});
|
|
1619
|
+
const intersections = mainShape.getIntersections(ray) || [];
|
|
1620
|
+
ray.remove();
|
|
1621
|
+
const validHits = intersections.filter((i) => i.point.y < bridgeBottom - eps);
|
|
1622
|
+
if (validHits.length === 0) return null;
|
|
1623
|
+
validHits.sort((a, b) => b.point.y - a.point.y);
|
|
1624
|
+
const flags = validHits.map((h) => {
|
|
1625
|
+
const above = h.point.add(new paper.Point(0, -delta));
|
|
1626
|
+
const below = h.point.add(new paper.Point(0, delta));
|
|
1627
|
+
return {
|
|
1628
|
+
insideAbove: mainShape.contains(above),
|
|
1629
|
+
insideBelow: mainShape.contains(below)
|
|
1630
|
+
};
|
|
1631
|
+
});
|
|
1632
|
+
const idx = pickExitIndex(flags);
|
|
1633
|
+
if (idx < 0) return null;
|
|
1634
|
+
if (isBridgeDebugEnabled()) {
|
|
1635
|
+
console.debug("Geometry: Bridge ray", {
|
|
1636
|
+
x,
|
|
1637
|
+
validHits: validHits.length,
|
|
1638
|
+
idx,
|
|
1639
|
+
delta,
|
|
1640
|
+
overlap,
|
|
1641
|
+
op
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
const hit = validHits[idx];
|
|
1645
|
+
return { point: hit.point, location: hit };
|
|
1817
1646
|
}
|
|
1818
|
-
function
|
|
1819
|
-
|
|
1820
|
-
|
|
1647
|
+
function selectOuterChain(args) {
|
|
1648
|
+
const { mainShape, pointsA, pointsB, delta, overlap, op } = args;
|
|
1649
|
+
const scoreA = scoreOutsideAbove(
|
|
1650
|
+
pointsA.map((p) => ({
|
|
1651
|
+
outsideAbove: !mainShape.contains(p.add(new paper.Point(0, -delta)))
|
|
1652
|
+
}))
|
|
1653
|
+
);
|
|
1654
|
+
const scoreB = scoreOutsideAbove(
|
|
1655
|
+
pointsB.map((p) => ({
|
|
1656
|
+
outsideAbove: !mainShape.contains(p.add(new paper.Point(0, -delta)))
|
|
1657
|
+
}))
|
|
1658
|
+
);
|
|
1659
|
+
const ratioA = scoreA / pointsA.length;
|
|
1660
|
+
const ratioB = scoreB / pointsB.length;
|
|
1661
|
+
if (isBridgeDebugEnabled()) {
|
|
1662
|
+
console.debug("Geometry: Bridge chain", {
|
|
1663
|
+
scoreA,
|
|
1664
|
+
scoreB,
|
|
1665
|
+
lenA: pointsA.length,
|
|
1666
|
+
lenB: pointsB.length,
|
|
1667
|
+
ratioA,
|
|
1668
|
+
ratioB,
|
|
1669
|
+
delta,
|
|
1670
|
+
overlap,
|
|
1671
|
+
op
|
|
1672
|
+
});
|
|
1821
1673
|
}
|
|
1822
|
-
const
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
return emptyFrameRect();
|
|
1674
|
+
const ratioEps = 1e-6;
|
|
1675
|
+
if (Math.abs(ratioA - ratioB) > ratioEps) {
|
|
1676
|
+
return ratioA > ratioB ? pointsA : pointsB;
|
|
1826
1677
|
}
|
|
1827
|
-
return
|
|
1828
|
-
|
|
1829
|
-
top: layout.cutRect.top,
|
|
1830
|
-
width: layout.cutRect.width,
|
|
1831
|
-
height: layout.cutRect.height
|
|
1832
|
-
});
|
|
1678
|
+
if (scoreA !== scoreB) return scoreA > scoreB ? pointsA : pointsB;
|
|
1679
|
+
return pointsA.length <= pointsB.length ? pointsA : pointsB;
|
|
1833
1680
|
}
|
|
1834
|
-
function
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1681
|
+
function fitPathItemToRect(item, rect, fitMode) {
|
|
1682
|
+
const { left, top, width, height } = rect;
|
|
1683
|
+
const bounds = item.bounds;
|
|
1684
|
+
if (width <= 0 || height <= 0 || !Number.isFinite(bounds.width) || !Number.isFinite(bounds.height) || bounds.width <= 0 || bounds.height <= 0) {
|
|
1685
|
+
item.position = new paper.Point(left + width / 2, top + height / 2);
|
|
1686
|
+
return item;
|
|
1687
|
+
}
|
|
1688
|
+
item.translate(new paper.Point(-bounds.left, -bounds.top));
|
|
1689
|
+
if (fitMode === "stretch") {
|
|
1690
|
+
item.scale(width / bounds.width, height / bounds.height, new paper.Point(0, 0));
|
|
1691
|
+
item.translate(new paper.Point(left, top));
|
|
1692
|
+
return item;
|
|
1693
|
+
}
|
|
1694
|
+
const uniformScale = Math.min(width / bounds.width, height / bounds.height);
|
|
1695
|
+
item.scale(uniformScale, uniformScale, new paper.Point(0, 0));
|
|
1696
|
+
const scaledWidth = bounds.width * uniformScale;
|
|
1697
|
+
const scaledHeight = bounds.height * uniformScale;
|
|
1698
|
+
item.translate(
|
|
1699
|
+
new paper.Point(
|
|
1700
|
+
left + (width - scaledWidth) / 2,
|
|
1701
|
+
top + (height - scaledHeight) / 2
|
|
1702
|
+
)
|
|
1703
|
+
);
|
|
1704
|
+
return item;
|
|
1842
1705
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1706
|
+
function createNormalizedHeartPath(params) {
|
|
1707
|
+
const { lobeSpread, notchDepth, tipSharpness } = params;
|
|
1708
|
+
const halfSpread = 0.22 + lobeSpread * 0.18;
|
|
1709
|
+
const notchY = 0.06 + notchDepth * 0.2;
|
|
1710
|
+
const shoulderY = 0.24 + notchDepth * 0.2;
|
|
1711
|
+
const topLift = 0.12 + (1 - notchDepth) * 0.06;
|
|
1712
|
+
const topY = notchY - topLift;
|
|
1713
|
+
const sideCtrlY = shoulderY - (0.18 - notchDepth * 0.08);
|
|
1714
|
+
const lowerCtrlY = 0.58 + (1 - tipSharpness) * 0.16;
|
|
1715
|
+
const tipCtrlX = 0.34 - tipSharpness * 0.2;
|
|
1716
|
+
const notchCtrlX = 0.06 + lobeSpread * 0.06;
|
|
1717
|
+
const lobeCtrlX = 0.1 + lobeSpread * 0.08;
|
|
1718
|
+
const notchCtrlY = notchY - topLift * 0.45;
|
|
1719
|
+
const xPeakL = 0.5 - halfSpread;
|
|
1720
|
+
const xPeakR = 0.5 + halfSpread;
|
|
1721
|
+
const heartPath = new paper.Path({ insert: false });
|
|
1722
|
+
heartPath.moveTo(new paper.Point(0.5, notchY));
|
|
1723
|
+
heartPath.cubicCurveTo(
|
|
1724
|
+
new paper.Point(0.5 - notchCtrlX, notchCtrlY),
|
|
1725
|
+
new paper.Point(xPeakL + lobeCtrlX, topY),
|
|
1726
|
+
new paper.Point(xPeakL, topY)
|
|
1727
|
+
);
|
|
1728
|
+
heartPath.cubicCurveTo(
|
|
1729
|
+
new paper.Point(xPeakL - lobeCtrlX, topY),
|
|
1730
|
+
new paper.Point(0, sideCtrlY),
|
|
1731
|
+
new paper.Point(0, shoulderY)
|
|
1732
|
+
);
|
|
1733
|
+
heartPath.cubicCurveTo(
|
|
1734
|
+
new paper.Point(0, lowerCtrlY),
|
|
1735
|
+
new paper.Point(tipCtrlX, 1),
|
|
1736
|
+
new paper.Point(0.5, 1)
|
|
1737
|
+
);
|
|
1738
|
+
heartPath.cubicCurveTo(
|
|
1739
|
+
new paper.Point(1 - tipCtrlX, 1),
|
|
1740
|
+
new paper.Point(1, lowerCtrlY),
|
|
1741
|
+
new paper.Point(1, shoulderY)
|
|
1742
|
+
);
|
|
1743
|
+
heartPath.cubicCurveTo(
|
|
1744
|
+
new paper.Point(1, sideCtrlY),
|
|
1745
|
+
new paper.Point(xPeakR + lobeCtrlX, topY),
|
|
1746
|
+
new paper.Point(xPeakR, topY)
|
|
1747
|
+
);
|
|
1748
|
+
heartPath.cubicCurveTo(
|
|
1749
|
+
new paper.Point(xPeakR - lobeCtrlX, topY),
|
|
1750
|
+
new paper.Point(0.5 + notchCtrlX, notchCtrlY),
|
|
1751
|
+
new paper.Point(0.5, notchY)
|
|
1752
|
+
);
|
|
1753
|
+
heartPath.closed = true;
|
|
1754
|
+
return heartPath;
|
|
1847
1755
|
}
|
|
1848
|
-
function
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1756
|
+
function createHeartBaseShape(options) {
|
|
1757
|
+
const { x, y, width, height } = options;
|
|
1758
|
+
const w = Math.max(0, width);
|
|
1759
|
+
const h = Math.max(0, height);
|
|
1760
|
+
const left = x - w / 2;
|
|
1761
|
+
const top = y - h / 2;
|
|
1762
|
+
const fitMode = getShapeFitMode(options.shapeStyle);
|
|
1763
|
+
const heartParams = getHeartShapeParams(options.shapeStyle);
|
|
1764
|
+
const rawHeart = createNormalizedHeartPath(heartParams);
|
|
1765
|
+
return fitPathItemToRect(rawHeart, { left, top, width: w, height: h }, fitMode);
|
|
1856
1766
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1767
|
+
var BUILTIN_SHAPE_BUILDERS = {
|
|
1768
|
+
rect: (options) => {
|
|
1769
|
+
const { x, y, width, height, radius } = options;
|
|
1770
|
+
return new paper.Path.Rectangle({
|
|
1771
|
+
point: [x - width / 2, y - height / 2],
|
|
1772
|
+
size: [Math.max(0, width), Math.max(0, height)],
|
|
1773
|
+
radius: Math.max(0, radius)
|
|
1774
|
+
});
|
|
1775
|
+
},
|
|
1776
|
+
circle: (options) => {
|
|
1777
|
+
const { x, y, width, height } = options;
|
|
1778
|
+
const r = Math.min(width, height) / 2;
|
|
1779
|
+
return new paper.Path.Circle({
|
|
1780
|
+
center: new paper.Point(x, y),
|
|
1781
|
+
radius: Math.max(0, r)
|
|
1782
|
+
});
|
|
1783
|
+
},
|
|
1784
|
+
ellipse: (options) => {
|
|
1785
|
+
const { x, y, width, height } = options;
|
|
1786
|
+
return new paper.Path.Ellipse({
|
|
1787
|
+
center: new paper.Point(x, y),
|
|
1788
|
+
radius: [Math.max(0, width / 2), Math.max(0, height / 2)]
|
|
1789
|
+
});
|
|
1790
|
+
},
|
|
1791
|
+
heart: createHeartBaseShape
|
|
1792
|
+
};
|
|
1793
|
+
function createCustomBaseShape(options) {
|
|
1794
|
+
var _a;
|
|
1795
|
+
const {
|
|
1796
|
+
pathData,
|
|
1797
|
+
customSourceWidthPx,
|
|
1798
|
+
customSourceHeightPx,
|
|
1799
|
+
x,
|
|
1800
|
+
y,
|
|
1801
|
+
width,
|
|
1802
|
+
height
|
|
1803
|
+
} = options;
|
|
1804
|
+
if (typeof pathData !== "string" || pathData.trim().length === 0) {
|
|
1805
|
+
return null;
|
|
1863
1806
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
{
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
tool.emitWorkingChange();
|
|
1917
|
-
}
|
|
1918
|
-
},
|
|
1919
|
-
{
|
|
1920
|
-
command: "completeImages",
|
|
1921
|
-
id: "completeImages",
|
|
1922
|
-
title: "Complete Images",
|
|
1923
|
-
handler: async () => {
|
|
1924
|
-
return await tool.commitWorkingImagesAsCropped();
|
|
1925
|
-
}
|
|
1926
|
-
},
|
|
1927
|
-
{
|
|
1928
|
-
command: "exportUserCroppedImage",
|
|
1929
|
-
id: "exportUserCroppedImage",
|
|
1930
|
-
title: "Export User Cropped Image",
|
|
1931
|
-
handler: async (options = {}) => {
|
|
1932
|
-
return await tool.exportUserCroppedImage(options);
|
|
1933
|
-
}
|
|
1934
|
-
},
|
|
1935
|
-
{
|
|
1936
|
-
command: "fitImageToArea",
|
|
1937
|
-
id: "fitImageToArea",
|
|
1938
|
-
title: "Fit Image to Area",
|
|
1939
|
-
handler: async (id, area) => {
|
|
1940
|
-
await tool.fitImageToArea(id, area);
|
|
1941
|
-
}
|
|
1942
|
-
},
|
|
1943
|
-
{
|
|
1944
|
-
command: "fitImageToDefaultArea",
|
|
1945
|
-
id: "fitImageToDefaultArea",
|
|
1946
|
-
title: "Fit Image to Default Area",
|
|
1947
|
-
handler: async (id) => {
|
|
1948
|
-
await tool.fitImageToDefaultArea(id);
|
|
1949
|
-
}
|
|
1950
|
-
},
|
|
1951
|
-
{
|
|
1952
|
-
command: "focusImage",
|
|
1953
|
-
id: "focusImage",
|
|
1954
|
-
title: "Focus Image",
|
|
1955
|
-
handler: (id, options = {}) => {
|
|
1956
|
-
return tool.setImageFocus(id, options);
|
|
1807
|
+
const center = new paper.Point(x, y);
|
|
1808
|
+
const hasMultipleSubPaths = ((_a = (pathData.match(/[Mm]/g) || []).length) != null ? _a : 0) > 1;
|
|
1809
|
+
const path = hasMultipleSubPaths ? new paper.CompoundPath(pathData) : (() => {
|
|
1810
|
+
const single = new paper.Path();
|
|
1811
|
+
single.pathData = pathData;
|
|
1812
|
+
return single;
|
|
1813
|
+
})();
|
|
1814
|
+
const sourceWidth = Number(customSourceWidthPx != null ? customSourceWidthPx : 0);
|
|
1815
|
+
const sourceHeight = Number(customSourceHeightPx != null ? customSourceHeightPx : 0);
|
|
1816
|
+
if (Number.isFinite(sourceWidth) && Number.isFinite(sourceHeight) && sourceWidth > 0 && sourceHeight > 0 && width > 0 && height > 0) {
|
|
1817
|
+
const targetLeft = x - width / 2;
|
|
1818
|
+
const targetTop = y - height / 2;
|
|
1819
|
+
path.scale(width / sourceWidth, height / sourceHeight, new paper.Point(0, 0));
|
|
1820
|
+
path.translate(new paper.Point(targetLeft, targetTop));
|
|
1821
|
+
return path;
|
|
1822
|
+
}
|
|
1823
|
+
if (width > 0 && height > 0 && path.bounds.width > 0 && path.bounds.height > 0) {
|
|
1824
|
+
path.position = center;
|
|
1825
|
+
path.scale(width / path.bounds.width, height / path.bounds.height);
|
|
1826
|
+
return path;
|
|
1827
|
+
}
|
|
1828
|
+
path.position = center;
|
|
1829
|
+
return path;
|
|
1830
|
+
}
|
|
1831
|
+
function createBaseShape(options) {
|
|
1832
|
+
const { shape } = options;
|
|
1833
|
+
if (shape === "custom") {
|
|
1834
|
+
const customShape = createCustomBaseShape(options);
|
|
1835
|
+
if (customShape) return customShape;
|
|
1836
|
+
return BUILTIN_SHAPE_BUILDERS[DEFAULT_DIELINE_SHAPE](options);
|
|
1837
|
+
}
|
|
1838
|
+
return BUILTIN_SHAPE_BUILDERS[shape](options);
|
|
1839
|
+
}
|
|
1840
|
+
function resolveBridgeBasePath(shape, anchor) {
|
|
1841
|
+
if (shape instanceof paper.Path) {
|
|
1842
|
+
return shape;
|
|
1843
|
+
}
|
|
1844
|
+
if (shape instanceof paper.CompoundPath) {
|
|
1845
|
+
const children = (shape.children || []).filter(
|
|
1846
|
+
(child) => child instanceof paper.Path
|
|
1847
|
+
);
|
|
1848
|
+
if (!children.length) return null;
|
|
1849
|
+
let best = children[0];
|
|
1850
|
+
let bestDistance = Infinity;
|
|
1851
|
+
for (const child of children) {
|
|
1852
|
+
const location = child.getNearestLocation(anchor);
|
|
1853
|
+
const point = location == null ? void 0 : location.point;
|
|
1854
|
+
if (!point) continue;
|
|
1855
|
+
const distance = point.getDistance(anchor);
|
|
1856
|
+
if (distance < bestDistance) {
|
|
1857
|
+
bestDistance = distance;
|
|
1858
|
+
best = child;
|
|
1957
1859
|
}
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1860
|
+
}
|
|
1861
|
+
return best;
|
|
1862
|
+
}
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
function createFeatureItem(feature, center) {
|
|
1866
|
+
let item;
|
|
1867
|
+
if (feature.shape === "rect") {
|
|
1868
|
+
const w = feature.width || 10;
|
|
1869
|
+
const h = feature.height || 10;
|
|
1870
|
+
const r = feature.radius || 0;
|
|
1871
|
+
item = new paper.Path.Rectangle({
|
|
1872
|
+
point: [center.x - w / 2, center.y - h / 2],
|
|
1873
|
+
size: [w, h],
|
|
1874
|
+
radius: r
|
|
1875
|
+
});
|
|
1876
|
+
} else {
|
|
1877
|
+
const r = feature.radius || 5;
|
|
1878
|
+
item = new paper.Path.Circle({
|
|
1879
|
+
center,
|
|
1880
|
+
radius: r
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
if (feature.rotation) {
|
|
1884
|
+
item.rotate(feature.rotation, center);
|
|
1885
|
+
}
|
|
1886
|
+
return item;
|
|
1887
|
+
}
|
|
1888
|
+
function getPerimeterShape(options) {
|
|
1889
|
+
let mainShape = createBaseShape(options);
|
|
1890
|
+
const { features } = options;
|
|
1891
|
+
if (features && features.length > 0) {
|
|
1892
|
+
const edgeFeatures = features.filter(
|
|
1893
|
+
(f) => !f.renderBehavior || f.renderBehavior === "edge"
|
|
1894
|
+
);
|
|
1895
|
+
const adds = [];
|
|
1896
|
+
const subtracts = [];
|
|
1897
|
+
edgeFeatures.forEach((f) => {
|
|
1898
|
+
const pos = resolveFeaturePosition(f, options);
|
|
1899
|
+
const center = new paper.Point(pos.x, pos.y);
|
|
1900
|
+
const item = createFeatureItem(f, center);
|
|
1901
|
+
if (f.bridge && f.bridge.type === "vertical") {
|
|
1902
|
+
const itemBounds = item.bounds;
|
|
1903
|
+
const mainBounds = mainShape.bounds;
|
|
1904
|
+
const bridgeTop = mainBounds.top;
|
|
1905
|
+
const bridgeBottom = itemBounds.top;
|
|
1906
|
+
if (bridgeBottom > bridgeTop) {
|
|
1907
|
+
const overlap = 2;
|
|
1908
|
+
const rayPadding = 10;
|
|
1909
|
+
const eps = 0.1;
|
|
1910
|
+
const delta = getBridgeDelta(itemBounds, overlap);
|
|
1911
|
+
const toY = bridgeTop - rayPadding;
|
|
1912
|
+
const inset = Math.min(1, Math.max(0, itemBounds.width * 0.01));
|
|
1913
|
+
const xLeft = itemBounds.left + inset;
|
|
1914
|
+
const xRight = itemBounds.right - inset;
|
|
1915
|
+
const bridgeBasePath = resolveBridgeBasePath(mainShape, center);
|
|
1916
|
+
const canBridge = !!bridgeBasePath && xRight - xLeft > eps;
|
|
1917
|
+
if (canBridge && bridgeBasePath) {
|
|
1918
|
+
const leftHit = getExitHit({
|
|
1919
|
+
mainShape: bridgeBasePath,
|
|
1920
|
+
x: xLeft,
|
|
1921
|
+
bridgeBottom,
|
|
1922
|
+
toY,
|
|
1923
|
+
eps,
|
|
1924
|
+
delta,
|
|
1925
|
+
overlap,
|
|
1926
|
+
op: f.operation
|
|
1927
|
+
});
|
|
1928
|
+
const rightHit = getExitHit({
|
|
1929
|
+
mainShape: bridgeBasePath,
|
|
1930
|
+
x: xRight,
|
|
1931
|
+
bridgeBottom,
|
|
1932
|
+
toY,
|
|
1933
|
+
eps,
|
|
1934
|
+
delta,
|
|
1935
|
+
overlap,
|
|
1936
|
+
op: f.operation
|
|
1972
1937
|
});
|
|
1938
|
+
if (leftHit && rightHit) {
|
|
1939
|
+
const pathLength = bridgeBasePath.length;
|
|
1940
|
+
const leftOffset = leftHit.location.offset;
|
|
1941
|
+
const rightOffset = rightHit.location.offset;
|
|
1942
|
+
const distanceA = wrappedDistance(pathLength, leftOffset, rightOffset);
|
|
1943
|
+
const distanceB = wrappedDistance(pathLength, rightOffset, leftOffset);
|
|
1944
|
+
const countFor = (d) => Math.max(8, Math.min(80, Math.ceil(d / 6)));
|
|
1945
|
+
const offsetsA = sampleWrappedOffsets(
|
|
1946
|
+
pathLength,
|
|
1947
|
+
leftOffset,
|
|
1948
|
+
rightOffset,
|
|
1949
|
+
countFor(distanceA)
|
|
1950
|
+
);
|
|
1951
|
+
const offsetsB = sampleWrappedOffsets(
|
|
1952
|
+
pathLength,
|
|
1953
|
+
rightOffset,
|
|
1954
|
+
leftOffset,
|
|
1955
|
+
countFor(distanceB)
|
|
1956
|
+
);
|
|
1957
|
+
const pointsA = offsetsA.map((o) => bridgeBasePath.getPointAt(o)).filter((p) => Boolean(p));
|
|
1958
|
+
const pointsB = offsetsB.map((o) => bridgeBasePath.getPointAt(o)).filter((p) => Boolean(p));
|
|
1959
|
+
if (pointsA.length >= 2 && pointsB.length >= 2) {
|
|
1960
|
+
let topBase = selectOuterChain({
|
|
1961
|
+
mainShape: bridgeBasePath,
|
|
1962
|
+
pointsA,
|
|
1963
|
+
pointsB,
|
|
1964
|
+
delta,
|
|
1965
|
+
overlap,
|
|
1966
|
+
op: f.operation
|
|
1967
|
+
});
|
|
1968
|
+
const dist2 = (a, b) => {
|
|
1969
|
+
const dx = a.x - b.x;
|
|
1970
|
+
const dy = a.y - b.y;
|
|
1971
|
+
return dx * dx + dy * dy;
|
|
1972
|
+
};
|
|
1973
|
+
if (dist2(topBase[0], leftHit.point) > dist2(topBase[0], rightHit.point)) {
|
|
1974
|
+
topBase = topBase.slice().reverse();
|
|
1975
|
+
}
|
|
1976
|
+
topBase = topBase.slice();
|
|
1977
|
+
topBase[0] = leftHit.point;
|
|
1978
|
+
topBase[topBase.length - 1] = rightHit.point;
|
|
1979
|
+
const capShiftY = f.operation === "subtract" ? -Math.max(overlap * 2, delta) : overlap;
|
|
1980
|
+
const topPoints = topBase.map(
|
|
1981
|
+
(p) => p.add(new paper.Point(0, capShiftY))
|
|
1982
|
+
);
|
|
1983
|
+
const bridgeBottomY = bridgeBottom + overlap * 2;
|
|
1984
|
+
const bridgePoly = new paper.Path({ insert: false });
|
|
1985
|
+
for (const p of topPoints) bridgePoly.add(p);
|
|
1986
|
+
bridgePoly.add(new paper.Point(xRight, bridgeBottomY));
|
|
1987
|
+
bridgePoly.add(new paper.Point(xLeft, bridgeBottomY));
|
|
1988
|
+
bridgePoly.closed = true;
|
|
1989
|
+
const unitedItem = item.unite(bridgePoly);
|
|
1990
|
+
item.remove();
|
|
1991
|
+
bridgePoly.remove();
|
|
1992
|
+
if (f.operation === "add") {
|
|
1993
|
+
adds.push(unitedItem);
|
|
1994
|
+
} else {
|
|
1995
|
+
subtracts.push(unitedItem);
|
|
1996
|
+
}
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
if (f.operation === "add") {
|
|
2002
|
+
adds.push(item);
|
|
2003
|
+
} else {
|
|
2004
|
+
subtracts.push(item);
|
|
2005
|
+
}
|
|
2006
|
+
} else {
|
|
2007
|
+
if (f.operation === "add") {
|
|
2008
|
+
adds.push(item);
|
|
2009
|
+
} else {
|
|
2010
|
+
subtracts.push(item);
|
|
1973
2011
|
}
|
|
1974
|
-
|
|
2012
|
+
}
|
|
2013
|
+
} else {
|
|
2014
|
+
if (f.operation === "add") {
|
|
2015
|
+
adds.push(item);
|
|
2016
|
+
} else {
|
|
2017
|
+
subtracts.push(item);
|
|
1975
2018
|
}
|
|
1976
2019
|
}
|
|
1977
|
-
}
|
|
1978
|
-
{
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
id: "clearImages",
|
|
1989
|
-
title: "Clear Images",
|
|
1990
|
-
handler: () => {
|
|
1991
|
-
tool.sourceSizeCache.clear();
|
|
1992
|
-
tool.setImageFocus(null, {
|
|
1993
|
-
syncCanvasSelection: true,
|
|
1994
|
-
skipRender: true
|
|
1995
|
-
});
|
|
1996
|
-
tool.updateConfig([]);
|
|
1997
|
-
}
|
|
1998
|
-
},
|
|
1999
|
-
{
|
|
2000
|
-
command: "bringToFront",
|
|
2001
|
-
id: "bringToFront",
|
|
2002
|
-
title: "Bring Image to Front",
|
|
2003
|
-
handler: (id) => {
|
|
2004
|
-
const index = tool.items.findIndex((item) => item.id === id);
|
|
2005
|
-
if (index !== -1 && index < tool.items.length - 1) {
|
|
2006
|
-
const next = [...tool.items];
|
|
2007
|
-
const [item] = next.splice(index, 1);
|
|
2008
|
-
next.push(item);
|
|
2009
|
-
tool.updateConfig(next);
|
|
2020
|
+
});
|
|
2021
|
+
if (adds.length > 0) {
|
|
2022
|
+
for (const item of adds) {
|
|
2023
|
+
try {
|
|
2024
|
+
const temp = mainShape.unite(item);
|
|
2025
|
+
mainShape.remove();
|
|
2026
|
+
item.remove();
|
|
2027
|
+
mainShape = normalizePathItem(temp);
|
|
2028
|
+
} catch (e) {
|
|
2029
|
+
console.error("Geometry: Failed to unite feature", e);
|
|
2030
|
+
item.remove();
|
|
2010
2031
|
}
|
|
2011
2032
|
}
|
|
2012
|
-
}
|
|
2013
|
-
{
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
tool.updateConfig(next);
|
|
2033
|
+
}
|
|
2034
|
+
if (subtracts.length > 0) {
|
|
2035
|
+
for (const item of subtracts) {
|
|
2036
|
+
try {
|
|
2037
|
+
const temp = mainShape.subtract(item);
|
|
2038
|
+
mainShape.remove();
|
|
2039
|
+
item.remove();
|
|
2040
|
+
mainShape = normalizePathItem(temp);
|
|
2041
|
+
} catch (e) {
|
|
2042
|
+
console.error("Geometry: Failed to subtract feature", e);
|
|
2043
|
+
item.remove();
|
|
2024
2044
|
}
|
|
2025
2045
|
}
|
|
2026
2046
|
}
|
|
2027
|
-
|
|
2047
|
+
}
|
|
2048
|
+
return mainShape;
|
|
2049
|
+
}
|
|
2050
|
+
function applySurfaceFeatures(shape, features, options) {
|
|
2051
|
+
const surfaceFeatures = features.filter(
|
|
2052
|
+
(f) => f.renderBehavior === "surface"
|
|
2053
|
+
);
|
|
2054
|
+
if (surfaceFeatures.length === 0) return shape;
|
|
2055
|
+
let result = shape;
|
|
2056
|
+
for (const f of surfaceFeatures) {
|
|
2057
|
+
const pos = resolveFeaturePosition(f, options);
|
|
2058
|
+
const center = new paper.Point(pos.x, pos.y);
|
|
2059
|
+
const item = createFeatureItem(f, center);
|
|
2060
|
+
try {
|
|
2061
|
+
if (f.operation === "add") {
|
|
2062
|
+
const temp = result.unite(item);
|
|
2063
|
+
result.remove();
|
|
2064
|
+
item.remove();
|
|
2065
|
+
result = normalizePathItem(temp);
|
|
2066
|
+
} else {
|
|
2067
|
+
const temp = result.subtract(item);
|
|
2068
|
+
result.remove();
|
|
2069
|
+
item.remove();
|
|
2070
|
+
result = normalizePathItem(temp);
|
|
2071
|
+
}
|
|
2072
|
+
} catch (e) {
|
|
2073
|
+
console.error("Geometry: Failed to apply surface feature", e);
|
|
2074
|
+
item.remove();
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
return result;
|
|
2078
|
+
}
|
|
2079
|
+
function generateDielinePath(options) {
|
|
2080
|
+
const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
|
|
2081
|
+
const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
|
|
2082
|
+
ensurePaper(paperWidth, paperHeight);
|
|
2083
|
+
paper.project.activeLayer.removeChildren();
|
|
2084
|
+
const perimeter = getPerimeterShape(options);
|
|
2085
|
+
const finalShape = applySurfaceFeatures(perimeter, options.features, options);
|
|
2086
|
+
const pathData = finalShape.pathData;
|
|
2087
|
+
finalShape.remove();
|
|
2088
|
+
return pathData;
|
|
2089
|
+
}
|
|
2090
|
+
function generateBleedZonePath(originalOptions, offsetOptions, offset) {
|
|
2091
|
+
const paperWidth = originalOptions.canvasWidth || originalOptions.width * 2 || 2e3;
|
|
2092
|
+
const paperHeight = originalOptions.canvasHeight || originalOptions.height * 2 || 2e3;
|
|
2093
|
+
ensurePaper(paperWidth, paperHeight);
|
|
2094
|
+
paper.project.activeLayer.removeChildren();
|
|
2095
|
+
const pOriginal = getPerimeterShape(originalOptions);
|
|
2096
|
+
const shapeOriginal = applySurfaceFeatures(
|
|
2097
|
+
pOriginal,
|
|
2098
|
+
originalOptions.features,
|
|
2099
|
+
originalOptions
|
|
2100
|
+
);
|
|
2101
|
+
const pOffset = getPerimeterShape(offsetOptions);
|
|
2102
|
+
const shapeOffset = applySurfaceFeatures(
|
|
2103
|
+
pOffset,
|
|
2104
|
+
offsetOptions.features,
|
|
2105
|
+
offsetOptions
|
|
2106
|
+
);
|
|
2107
|
+
let bleedZone;
|
|
2108
|
+
if (offset > 0) {
|
|
2109
|
+
bleedZone = shapeOffset.subtract(shapeOriginal);
|
|
2110
|
+
} else {
|
|
2111
|
+
bleedZone = shapeOriginal.subtract(shapeOffset);
|
|
2112
|
+
}
|
|
2113
|
+
const pathData = bleedZone.pathData;
|
|
2114
|
+
shapeOriginal.remove();
|
|
2115
|
+
shapeOffset.remove();
|
|
2116
|
+
bleedZone.remove();
|
|
2117
|
+
return pathData;
|
|
2118
|
+
}
|
|
2119
|
+
function getLowestPointOnDieline(options) {
|
|
2120
|
+
ensurePaper(options.width * 2, options.height * 2);
|
|
2121
|
+
paper.project.activeLayer.removeChildren();
|
|
2122
|
+
const shape = createBaseShape(options);
|
|
2123
|
+
const bounds = shape.bounds;
|
|
2124
|
+
const result = {
|
|
2125
|
+
x: bounds.center.x,
|
|
2126
|
+
y: bounds.bottom
|
|
2127
|
+
};
|
|
2128
|
+
shape.remove();
|
|
2129
|
+
return result;
|
|
2130
|
+
}
|
|
2131
|
+
function getNearestPointOnDieline(point, options) {
|
|
2132
|
+
ensurePaper(options.width * 2, options.height * 2);
|
|
2133
|
+
paper.project.activeLayer.removeChildren();
|
|
2134
|
+
const shape = createBaseShape(options);
|
|
2135
|
+
const p = new paper.Point(point.x, point.y);
|
|
2136
|
+
const location = shape.getNearestLocation(p);
|
|
2137
|
+
const result = {
|
|
2138
|
+
x: location.point.x,
|
|
2139
|
+
y: location.point.y,
|
|
2140
|
+
normal: location.normal ? { x: location.normal.x, y: location.normal.y } : void 0
|
|
2141
|
+
};
|
|
2142
|
+
shape.remove();
|
|
2143
|
+
return result;
|
|
2028
2144
|
}
|
|
2029
2145
|
|
|
2030
|
-
// src/extensions/image/
|
|
2031
|
-
|
|
2146
|
+
// src/extensions/image/sessionOverlay.ts
|
|
2147
|
+
var EPSILON = 1e-4;
|
|
2148
|
+
var SHAPE_OUTLINE_COLOR = "rgba(255, 0, 0, 0.9)";
|
|
2149
|
+
var DEFAULT_HATCH_FILL = "rgba(255, 0, 0, 0.22)";
|
|
2150
|
+
function buildRectPath(width, height) {
|
|
2151
|
+
return `M 0 0 L ${width} 0 L ${width} ${height} L 0 ${height} Z`;
|
|
2152
|
+
}
|
|
2153
|
+
function buildViewportMaskPath(viewport, cutRect) {
|
|
2154
|
+
const cutLeft = cutRect.left - viewport.left;
|
|
2155
|
+
const cutTop = cutRect.top - viewport.top;
|
|
2032
2156
|
return [
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
{
|
|
2095
|
-
id: "image.control.borderScaleFactor",
|
|
2096
|
-
type: "number",
|
|
2097
|
-
label: "Image Control Border Width",
|
|
2098
|
-
min: 0.5,
|
|
2099
|
-
max: 8,
|
|
2100
|
-
step: 0.1,
|
|
2101
|
-
default: 1.5
|
|
2102
|
-
},
|
|
2103
|
-
{
|
|
2104
|
-
id: "image.control.padding",
|
|
2105
|
-
type: "number",
|
|
2106
|
-
label: "Image Control Padding",
|
|
2107
|
-
min: 0,
|
|
2108
|
-
max: 64,
|
|
2109
|
-
step: 1,
|
|
2110
|
-
default: 0
|
|
2111
|
-
},
|
|
2112
|
-
{
|
|
2113
|
-
id: "image.frame.strokeColor",
|
|
2114
|
-
type: "color",
|
|
2115
|
-
label: "Image Frame Stroke Color",
|
|
2116
|
-
default: "#808080"
|
|
2117
|
-
},
|
|
2118
|
-
{
|
|
2119
|
-
id: "image.frame.strokeWidth",
|
|
2120
|
-
type: "number",
|
|
2121
|
-
label: "Image Frame Stroke Width",
|
|
2122
|
-
min: 0,
|
|
2123
|
-
max: 20,
|
|
2124
|
-
step: 0.5,
|
|
2125
|
-
default: 2
|
|
2126
|
-
},
|
|
2127
|
-
{
|
|
2128
|
-
id: "image.frame.strokeStyle",
|
|
2129
|
-
type: "select",
|
|
2130
|
-
label: "Image Frame Stroke Style",
|
|
2131
|
-
options: ["solid", "dashed", "hidden"],
|
|
2132
|
-
default: "dashed"
|
|
2133
|
-
},
|
|
2134
|
-
{
|
|
2135
|
-
id: "image.frame.dashLength",
|
|
2136
|
-
type: "number",
|
|
2137
|
-
label: "Image Frame Dash Length",
|
|
2138
|
-
min: 1,
|
|
2139
|
-
max: 40,
|
|
2140
|
-
step: 1,
|
|
2141
|
-
default: 8
|
|
2142
|
-
},
|
|
2143
|
-
{
|
|
2144
|
-
id: "image.frame.innerBackground",
|
|
2145
|
-
type: "color",
|
|
2146
|
-
label: "Image Frame Inner Background",
|
|
2147
|
-
default: "rgba(0,0,0,0)"
|
|
2148
|
-
},
|
|
2149
|
-
{
|
|
2150
|
-
id: "image.frame.outerBackground",
|
|
2151
|
-
type: "color",
|
|
2152
|
-
label: "Image Frame Outer Background",
|
|
2153
|
-
default: "#f5f5f5"
|
|
2157
|
+
buildRectPath(viewport.width, viewport.height),
|
|
2158
|
+
`M ${cutLeft} ${cutTop} L ${cutLeft + cutRect.width} ${cutTop} L ${cutLeft + cutRect.width} ${cutTop + cutRect.height} L ${cutLeft} ${cutTop + cutRect.height} Z`
|
|
2159
|
+
].join(" ");
|
|
2160
|
+
}
|
|
2161
|
+
function resolveCutShapeRadiusPx(geometry, cutRect) {
|
|
2162
|
+
const visualRadius = Number.isFinite(geometry.radius) ? Math.max(0, geometry.radius) : 0;
|
|
2163
|
+
const visualOffset = Number.isFinite(geometry.offset) ? geometry.offset : 0;
|
|
2164
|
+
const rawCutRadius = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
2165
|
+
const maxRadius = Math.max(0, Math.min(cutRect.width, cutRect.height) / 2);
|
|
2166
|
+
return Math.max(0, Math.min(maxRadius, rawCutRadius));
|
|
2167
|
+
}
|
|
2168
|
+
function buildBuiltinShapeOverlayPaths(cutRect, geometry) {
|
|
2169
|
+
if (!geometry || geometry.shape === "custom") {
|
|
2170
|
+
return null;
|
|
2171
|
+
}
|
|
2172
|
+
const radius = resolveCutShapeRadiusPx(geometry, cutRect);
|
|
2173
|
+
if (geometry.shape === "rect" && radius <= EPSILON) {
|
|
2174
|
+
return null;
|
|
2175
|
+
}
|
|
2176
|
+
const shapePathData = generateDielinePath({
|
|
2177
|
+
shape: geometry.shape,
|
|
2178
|
+
shapeStyle: geometry.shapeStyle,
|
|
2179
|
+
width: Math.max(1, cutRect.width),
|
|
2180
|
+
height: Math.max(1, cutRect.height),
|
|
2181
|
+
radius,
|
|
2182
|
+
x: cutRect.width / 2,
|
|
2183
|
+
y: cutRect.height / 2,
|
|
2184
|
+
features: [],
|
|
2185
|
+
canvasWidth: Math.max(1, cutRect.width),
|
|
2186
|
+
canvasHeight: Math.max(1, cutRect.height)
|
|
2187
|
+
});
|
|
2188
|
+
if (!shapePathData) {
|
|
2189
|
+
return null;
|
|
2190
|
+
}
|
|
2191
|
+
return {
|
|
2192
|
+
shapePathData,
|
|
2193
|
+
hatchPathData: `${buildRectPath(cutRect.width, cutRect.height)} ${shapePathData}`
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
function buildImageSessionOverlaySpecs(args) {
|
|
2197
|
+
const { viewport, layout, geometry, visual, hatchPattern } = args;
|
|
2198
|
+
const cutRect = layout.cutRect;
|
|
2199
|
+
const specs = [];
|
|
2200
|
+
specs.push({
|
|
2201
|
+
id: "image.cropMask.rect",
|
|
2202
|
+
type: "path",
|
|
2203
|
+
space: "screen",
|
|
2204
|
+
data: { id: "image.cropMask.rect", zIndex: 1 },
|
|
2205
|
+
props: {
|
|
2206
|
+
pathData: buildViewportMaskPath(viewport, cutRect),
|
|
2207
|
+
left: viewport.left,
|
|
2208
|
+
top: viewport.top,
|
|
2209
|
+
originX: "left",
|
|
2210
|
+
originY: "top",
|
|
2211
|
+
fill: visual.outerBackground,
|
|
2212
|
+
stroke: null,
|
|
2213
|
+
fillRule: "evenodd",
|
|
2214
|
+
selectable: false,
|
|
2215
|
+
evented: false,
|
|
2216
|
+
excludeFromExport: true,
|
|
2217
|
+
objectCaching: false
|
|
2154
2218
|
}
|
|
2155
|
-
|
|
2219
|
+
});
|
|
2220
|
+
const shapeOverlay = buildBuiltinShapeOverlayPaths(cutRect, geometry);
|
|
2221
|
+
if (shapeOverlay) {
|
|
2222
|
+
specs.push({
|
|
2223
|
+
id: "image.cropShapeHatch",
|
|
2224
|
+
type: "path",
|
|
2225
|
+
space: "screen",
|
|
2226
|
+
data: { id: "image.cropShapeHatch", zIndex: 5 },
|
|
2227
|
+
props: {
|
|
2228
|
+
pathData: shapeOverlay.hatchPathData,
|
|
2229
|
+
left: cutRect.left,
|
|
2230
|
+
top: cutRect.top,
|
|
2231
|
+
originX: "left",
|
|
2232
|
+
originY: "top",
|
|
2233
|
+
fill: hatchPattern || DEFAULT_HATCH_FILL,
|
|
2234
|
+
opacity: hatchPattern ? 1 : 0.8,
|
|
2235
|
+
stroke: null,
|
|
2236
|
+
fillRule: "evenodd",
|
|
2237
|
+
selectable: false,
|
|
2238
|
+
evented: false,
|
|
2239
|
+
excludeFromExport: true,
|
|
2240
|
+
objectCaching: false
|
|
2241
|
+
}
|
|
2242
|
+
});
|
|
2243
|
+
specs.push({
|
|
2244
|
+
id: "image.cropShapeOutline",
|
|
2245
|
+
type: "path",
|
|
2246
|
+
space: "screen",
|
|
2247
|
+
data: { id: "image.cropShapeOutline", zIndex: 6 },
|
|
2248
|
+
props: {
|
|
2249
|
+
pathData: shapeOverlay.shapePathData,
|
|
2250
|
+
left: cutRect.left,
|
|
2251
|
+
top: cutRect.top,
|
|
2252
|
+
originX: "left",
|
|
2253
|
+
originY: "top",
|
|
2254
|
+
fill: "transparent",
|
|
2255
|
+
stroke: SHAPE_OUTLINE_COLOR,
|
|
2256
|
+
strokeWidth: 1,
|
|
2257
|
+
selectable: false,
|
|
2258
|
+
evented: false,
|
|
2259
|
+
excludeFromExport: true,
|
|
2260
|
+
objectCaching: false
|
|
2261
|
+
}
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
specs.push({
|
|
2265
|
+
id: "image.cropFrame",
|
|
2266
|
+
type: "rect",
|
|
2267
|
+
space: "screen",
|
|
2268
|
+
data: { id: "image.cropFrame", zIndex: 7 },
|
|
2269
|
+
props: {
|
|
2270
|
+
left: cutRect.left,
|
|
2271
|
+
top: cutRect.top,
|
|
2272
|
+
width: cutRect.width,
|
|
2273
|
+
height: cutRect.height,
|
|
2274
|
+
originX: "left",
|
|
2275
|
+
originY: "top",
|
|
2276
|
+
fill: visual.innerBackground,
|
|
2277
|
+
stroke: visual.strokeStyle === "hidden" ? "rgba(0,0,0,0)" : visual.strokeColor,
|
|
2278
|
+
strokeWidth: visual.strokeStyle === "hidden" ? 0 : visual.strokeWidth,
|
|
2279
|
+
strokeDashArray: visual.strokeStyle === "dashed" ? [visual.dashLength, visual.dashLength] : void 0,
|
|
2280
|
+
selectable: false,
|
|
2281
|
+
evented: false,
|
|
2282
|
+
excludeFromExport: true
|
|
2283
|
+
}
|
|
2284
|
+
});
|
|
2285
|
+
return specs;
|
|
2156
2286
|
}
|
|
2157
2287
|
|
|
2158
2288
|
// src/extensions/image/ImageTool.ts
|
|
@@ -2614,11 +2744,21 @@ var ImageTool = class {
|
|
|
2614
2744
|
(_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
|
|
2615
2745
|
}
|
|
2616
2746
|
}
|
|
2747
|
+
clearSnapGuideContext() {
|
|
2748
|
+
var _a;
|
|
2749
|
+
const topContext = (_a = this.canvasService) == null ? void 0 : _a.canvas.contextTop;
|
|
2750
|
+
if (!this.canvasService || !topContext) return;
|
|
2751
|
+
this.canvasService.canvas.clearContext(topContext);
|
|
2752
|
+
}
|
|
2617
2753
|
clearSnapPreview() {
|
|
2618
2754
|
var _a;
|
|
2755
|
+
const shouldClearCanvas = this.hasRenderedSnapGuides || !!this.activeSnapX || !!this.activeSnapY;
|
|
2619
2756
|
this.activeSnapX = null;
|
|
2620
2757
|
this.activeSnapY = null;
|
|
2621
2758
|
this.hasRenderedSnapGuides = false;
|
|
2759
|
+
if (shouldClearCanvas) {
|
|
2760
|
+
this.clearSnapGuideContext();
|
|
2761
|
+
}
|
|
2622
2762
|
(_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
|
|
2623
2763
|
}
|
|
2624
2764
|
endMoveSnapInteraction() {
|
|
@@ -3022,9 +3162,6 @@ var ImageTool = class {
|
|
|
3022
3162
|
}
|
|
3023
3163
|
return this.canvasService.toScreenRect(frame || this.getFrameRect());
|
|
3024
3164
|
}
|
|
3025
|
-
toLayoutSceneRect(rect) {
|
|
3026
|
-
return toLayoutSceneRect(rect);
|
|
3027
|
-
}
|
|
3028
3165
|
async resolveDefaultFitArea() {
|
|
3029
3166
|
if (!this.canvasService) return null;
|
|
3030
3167
|
const frame = this.getFrameRect();
|
|
@@ -3151,74 +3288,37 @@ var ImageTool = class {
|
|
|
3151
3288
|
outerBackground: this.getConfig("image.frame.outerBackground", "#f5f5f5") || "#f5f5f5"
|
|
3152
3289
|
};
|
|
3153
3290
|
}
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
if (!isDielineShape(shape)) {
|
|
3291
|
+
resolveSessionOverlayState() {
|
|
3292
|
+
if (!this.canvasService || !this.context) {
|
|
3157
3293
|
return null;
|
|
3158
3294
|
}
|
|
3159
|
-
const radiusRaw = Number(raw == null ? void 0 : raw.radius);
|
|
3160
|
-
const offsetRaw = Number(raw == null ? void 0 : raw.offset);
|
|
3161
|
-
const unit = typeof (raw == null ? void 0 : raw.unit) === "string" ? raw.unit : "px";
|
|
3162
|
-
const radius = unit === "scene" || !this.canvasService ? radiusRaw : this.canvasService.toSceneLength(radiusRaw);
|
|
3163
|
-
const offset = unit === "scene" || !this.canvasService ? offsetRaw : this.canvasService.toSceneLength(offsetRaw);
|
|
3164
|
-
return {
|
|
3165
|
-
shape,
|
|
3166
|
-
shapeStyle: normalizeShapeStyle(raw == null ? void 0 : raw.shapeStyle),
|
|
3167
|
-
radius: Number.isFinite(radius) ? radius : 0,
|
|
3168
|
-
offset: Number.isFinite(offset) ? offset : 0
|
|
3169
|
-
};
|
|
3170
|
-
}
|
|
3171
|
-
async resolveSceneGeometryForOverlay() {
|
|
3172
|
-
if (!this.context) return null;
|
|
3173
|
-
const commandService = this.context.services.get("CommandService");
|
|
3174
|
-
if (commandService) {
|
|
3175
|
-
try {
|
|
3176
|
-
const raw = await Promise.resolve(
|
|
3177
|
-
commandService.executeCommand("getSceneGeometry")
|
|
3178
|
-
);
|
|
3179
|
-
const geometry2 = this.toSceneGeometryLike(raw);
|
|
3180
|
-
if (geometry2) {
|
|
3181
|
-
this.debug("overlay:sceneGeometry:command", geometry2);
|
|
3182
|
-
return geometry2;
|
|
3183
|
-
}
|
|
3184
|
-
this.debug("overlay:sceneGeometry:command:invalid", { raw });
|
|
3185
|
-
} catch (error) {
|
|
3186
|
-
this.debug("overlay:sceneGeometry:command:error", {
|
|
3187
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3188
|
-
});
|
|
3189
|
-
}
|
|
3190
|
-
}
|
|
3191
|
-
if (!this.canvasService) return null;
|
|
3192
3295
|
const configService = this.context.services.get(
|
|
3193
3296
|
"ConfigurationService"
|
|
3194
3297
|
);
|
|
3195
|
-
if (!configService)
|
|
3196
|
-
const sizeState = readSizeState(configService);
|
|
3197
|
-
const layout = computeSceneLayout(this.canvasService, sizeState);
|
|
3198
|
-
if (!layout) {
|
|
3199
|
-
this.debug("overlay:sceneGeometry:fallback:missing-layout");
|
|
3298
|
+
if (!configService) {
|
|
3200
3299
|
return null;
|
|
3201
3300
|
}
|
|
3202
|
-
const
|
|
3203
|
-
|
|
3301
|
+
const layout = computeSceneLayout(
|
|
3302
|
+
this.canvasService,
|
|
3303
|
+
readSizeState(configService)
|
|
3204
3304
|
);
|
|
3205
|
-
if (
|
|
3206
|
-
this.debug("overlay:
|
|
3305
|
+
if (!layout) {
|
|
3306
|
+
this.debug("overlay:layout:missing");
|
|
3307
|
+
return null;
|
|
3207
3308
|
}
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3309
|
+
const geometry = buildSceneGeometry(configService, layout);
|
|
3310
|
+
this.debug("overlay:state:resolved", {
|
|
3311
|
+
cutRect: layout.cutRect,
|
|
3312
|
+
shape: geometry.shape,
|
|
3313
|
+
shapeStyle: geometry.shapeStyle,
|
|
3314
|
+
radius: geometry.radius,
|
|
3315
|
+
offset: geometry.offset
|
|
3316
|
+
});
|
|
3317
|
+
return { layout, geometry };
|
|
3216
3318
|
}
|
|
3217
3319
|
getCropShapeHatchPattern(color = "rgba(255, 0, 0, 0.6)") {
|
|
3218
|
-
var _a;
|
|
3219
3320
|
if (typeof document === "undefined") return void 0;
|
|
3220
|
-
const
|
|
3221
|
-
const cacheKey = `${color}::${sceneScale.toFixed(6)}`;
|
|
3321
|
+
const cacheKey = color;
|
|
3222
3322
|
if (this.cropShapeHatchPattern && this.cropShapeHatchPatternColor === color && this.cropShapeHatchPatternKey === cacheKey) {
|
|
3223
3323
|
return this.cropShapeHatchPattern;
|
|
3224
3324
|
}
|
|
@@ -3248,138 +3348,11 @@ var ImageTool = class {
|
|
|
3248
3348
|
// @ts-ignore: Fabric Pattern accepts canvas source here.
|
|
3249
3349
|
repetition: "repeat"
|
|
3250
3350
|
});
|
|
3251
|
-
pattern.patternTransform = [
|
|
3252
|
-
1 / sceneScale,
|
|
3253
|
-
0,
|
|
3254
|
-
0,
|
|
3255
|
-
1 / sceneScale,
|
|
3256
|
-
0,
|
|
3257
|
-
0
|
|
3258
|
-
];
|
|
3259
3351
|
this.cropShapeHatchPattern = pattern;
|
|
3260
3352
|
this.cropShapeHatchPatternColor = color;
|
|
3261
3353
|
this.cropShapeHatchPatternKey = cacheKey;
|
|
3262
3354
|
return pattern;
|
|
3263
3355
|
}
|
|
3264
|
-
buildCropShapeOverlaySpecs(frame, sceneGeometry) {
|
|
3265
|
-
var _a, _b;
|
|
3266
|
-
if (!sceneGeometry) {
|
|
3267
|
-
this.debug("overlay:shape:skip", { reason: "scene-geometry-missing" });
|
|
3268
|
-
return [];
|
|
3269
|
-
}
|
|
3270
|
-
if (sceneGeometry.shape === "custom") {
|
|
3271
|
-
this.debug("overlay:shape:skip", { reason: "shape-custom" });
|
|
3272
|
-
return [];
|
|
3273
|
-
}
|
|
3274
|
-
const shape = sceneGeometry.shape;
|
|
3275
|
-
const shapeStyle = sceneGeometry.shapeStyle;
|
|
3276
|
-
const inset = 0;
|
|
3277
|
-
const shapeWidth = Math.max(1, frame.width);
|
|
3278
|
-
const shapeHeight = Math.max(1, frame.height);
|
|
3279
|
-
const radius = this.resolveCutShapeRadius(sceneGeometry, frame);
|
|
3280
|
-
this.debug("overlay:shape:geometry", {
|
|
3281
|
-
shape,
|
|
3282
|
-
frameWidth: frame.width,
|
|
3283
|
-
frameHeight: frame.height,
|
|
3284
|
-
offset: sceneGeometry.offset,
|
|
3285
|
-
shapeStyle,
|
|
3286
|
-
inset,
|
|
3287
|
-
shapeWidth,
|
|
3288
|
-
shapeHeight,
|
|
3289
|
-
baseRadius: sceneGeometry.radius,
|
|
3290
|
-
radius
|
|
3291
|
-
});
|
|
3292
|
-
const isSameAsFrame = Math.abs(shapeWidth - frame.width) <= 1e-4 && Math.abs(shapeHeight - frame.height) <= 1e-4;
|
|
3293
|
-
if (shape === "rect" && radius <= 1e-4 && isSameAsFrame) {
|
|
3294
|
-
this.debug("overlay:shape:skip", {
|
|
3295
|
-
reason: "shape-rect-no-radius"
|
|
3296
|
-
});
|
|
3297
|
-
return [];
|
|
3298
|
-
}
|
|
3299
|
-
const baseOptions = {
|
|
3300
|
-
shape,
|
|
3301
|
-
width: shapeWidth,
|
|
3302
|
-
height: shapeHeight,
|
|
3303
|
-
radius,
|
|
3304
|
-
x: frame.width / 2,
|
|
3305
|
-
y: frame.height / 2,
|
|
3306
|
-
features: [],
|
|
3307
|
-
shapeStyle,
|
|
3308
|
-
canvasWidth: frame.width,
|
|
3309
|
-
canvasHeight: frame.height
|
|
3310
|
-
};
|
|
3311
|
-
try {
|
|
3312
|
-
const shapePathData = generateDielinePath(baseOptions);
|
|
3313
|
-
const outerRectPathData = `M 0 0 L ${frame.width} 0 L ${frame.width} ${frame.height} L 0 ${frame.height} Z`;
|
|
3314
|
-
const hatchPathData = `${outerRectPathData} ${shapePathData}`;
|
|
3315
|
-
if (!shapePathData || !hatchPathData) {
|
|
3316
|
-
this.debug("overlay:shape:skip", {
|
|
3317
|
-
reason: "path-generation-empty",
|
|
3318
|
-
shape,
|
|
3319
|
-
radius
|
|
3320
|
-
});
|
|
3321
|
-
return [];
|
|
3322
|
-
}
|
|
3323
|
-
const patternFill = this.getCropShapeHatchPattern();
|
|
3324
|
-
const hatchFill = patternFill || "rgba(255, 0, 0, 0.22)";
|
|
3325
|
-
const shapeBounds = getPathBounds(shapePathData);
|
|
3326
|
-
const hatchBounds = getPathBounds(hatchPathData);
|
|
3327
|
-
const frameRect = this.toLayoutSceneRect(frame);
|
|
3328
|
-
const hatchPathLength = hatchPathData.length;
|
|
3329
|
-
const shapePathLength = shapePathData.length;
|
|
3330
|
-
const specs = [
|
|
3331
|
-
{
|
|
3332
|
-
id: "image.cropShapeHatch",
|
|
3333
|
-
type: "path",
|
|
3334
|
-
data: { id: "image.cropShapeHatch", zIndex: 5 },
|
|
3335
|
-
layout: {
|
|
3336
|
-
reference: "custom",
|
|
3337
|
-
referenceRect: frameRect,
|
|
3338
|
-
alignX: "start",
|
|
3339
|
-
alignY: "start",
|
|
3340
|
-
offsetX: hatchBounds.x,
|
|
3341
|
-
offsetY: hatchBounds.y
|
|
3342
|
-
},
|
|
3343
|
-
props: {
|
|
3344
|
-
pathData: hatchPathData,
|
|
3345
|
-
originX: "left",
|
|
3346
|
-
originY: "top",
|
|
3347
|
-
fill: hatchFill,
|
|
3348
|
-
opacity: patternFill ? 1 : 0.8,
|
|
3349
|
-
stroke: "rgba(255, 0, 0, 0.9)",
|
|
3350
|
-
strokeWidth: (_b = (_a = this.canvasService) == null ? void 0 : _a.toSceneLength(1)) != null ? _b : 1,
|
|
3351
|
-
fillRule: "evenodd",
|
|
3352
|
-
selectable: false,
|
|
3353
|
-
evented: false,
|
|
3354
|
-
excludeFromExport: true,
|
|
3355
|
-
objectCaching: false
|
|
3356
|
-
}
|
|
3357
|
-
}
|
|
3358
|
-
];
|
|
3359
|
-
this.debug("overlay:shape:built", {
|
|
3360
|
-
shape,
|
|
3361
|
-
radius,
|
|
3362
|
-
inset,
|
|
3363
|
-
shapeWidth,
|
|
3364
|
-
shapeHeight,
|
|
3365
|
-
fillRule: "evenodd",
|
|
3366
|
-
shapePathLength,
|
|
3367
|
-
hatchPathLength,
|
|
3368
|
-
shapeBounds,
|
|
3369
|
-
hatchBounds,
|
|
3370
|
-
hatchFillType: hatchFill && typeof hatchFill === "object" ? "pattern" : "color",
|
|
3371
|
-
ids: specs.map((spec) => spec.id)
|
|
3372
|
-
});
|
|
3373
|
-
return specs;
|
|
3374
|
-
} catch (error) {
|
|
3375
|
-
this.debug("overlay:shape:error", {
|
|
3376
|
-
shape,
|
|
3377
|
-
radius,
|
|
3378
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3379
|
-
});
|
|
3380
|
-
return [];
|
|
3381
|
-
}
|
|
3382
|
-
}
|
|
3383
3356
|
resolveRenderImageState(item) {
|
|
3384
3357
|
var _a;
|
|
3385
3358
|
const active = this.isToolActive;
|
|
@@ -3461,172 +3434,35 @@ var ImageTool = class {
|
|
|
3461
3434
|
}
|
|
3462
3435
|
return specs;
|
|
3463
3436
|
}
|
|
3464
|
-
buildOverlaySpecs(
|
|
3437
|
+
buildOverlaySpecs(overlayState) {
|
|
3465
3438
|
const visible = this.isImageEditingVisible();
|
|
3466
|
-
if (!visible ||
|
|
3439
|
+
if (!visible || !overlayState || !this.canvasService) {
|
|
3467
3440
|
this.debug("overlay:hidden", {
|
|
3468
3441
|
visible,
|
|
3469
|
-
|
|
3442
|
+
cutRect: overlayState == null ? void 0 : overlayState.layout.cutRect,
|
|
3470
3443
|
isToolActive: this.isToolActive,
|
|
3471
3444
|
isImageSelectionActive: this.isImageSelectionActive,
|
|
3472
3445
|
focusedImageId: this.focusedImageId
|
|
3473
3446
|
});
|
|
3474
3447
|
return [];
|
|
3475
3448
|
}
|
|
3476
|
-
const viewport = this.canvasService.
|
|
3477
|
-
const canvasW = viewport.width || 0;
|
|
3478
|
-
const canvasH = viewport.height || 0;
|
|
3479
|
-
const canvasLeft = viewport.left || 0;
|
|
3480
|
-
const canvasTop = viewport.top || 0;
|
|
3449
|
+
const viewport = this.canvasService.getScreenViewportRect();
|
|
3481
3450
|
const visual = this.getFrameVisualConfig();
|
|
3482
|
-
const
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
Math.min(canvasLeft + canvasW, frame.left)
|
|
3489
|
-
);
|
|
3490
|
-
const frameTop = Math.max(
|
|
3491
|
-
canvasTop,
|
|
3492
|
-
Math.min(canvasTop + canvasH, frame.top)
|
|
3493
|
-
);
|
|
3494
|
-
const frameRight = Math.max(
|
|
3495
|
-
frameLeft,
|
|
3496
|
-
Math.min(canvasLeft + canvasW, frame.left + frame.width)
|
|
3497
|
-
);
|
|
3498
|
-
const frameBottom = Math.max(
|
|
3499
|
-
frameTop,
|
|
3500
|
-
Math.min(canvasTop + canvasH, frame.top + frame.height)
|
|
3501
|
-
);
|
|
3502
|
-
const visibleFrameH = Math.max(0, frameBottom - frameTop);
|
|
3503
|
-
const topH = Math.max(0, frameTop - canvasTop);
|
|
3504
|
-
const bottomH = Math.max(0, canvasTop + canvasH - frameBottom);
|
|
3505
|
-
const leftW = Math.max(0, frameLeft - canvasLeft);
|
|
3506
|
-
const rightW = Math.max(0, canvasLeft + canvasW - frameRight);
|
|
3507
|
-
const viewportRect = this.toLayoutSceneRect({
|
|
3508
|
-
left: canvasLeft,
|
|
3509
|
-
top: canvasTop,
|
|
3510
|
-
width: canvasW,
|
|
3511
|
-
height: canvasH
|
|
3512
|
-
});
|
|
3513
|
-
const visibleFrameBandRect = this.toLayoutSceneRect({
|
|
3514
|
-
left: canvasLeft,
|
|
3515
|
-
top: frameTop,
|
|
3516
|
-
width: canvasW,
|
|
3517
|
-
height: visibleFrameH
|
|
3518
|
-
});
|
|
3519
|
-
const frameRect = this.toLayoutSceneRect(frame);
|
|
3520
|
-
const shapeOverlay = this.buildCropShapeOverlaySpecs(frame, sceneGeometry);
|
|
3521
|
-
const mask = [
|
|
3522
|
-
{
|
|
3523
|
-
id: "image.cropMask.top",
|
|
3524
|
-
type: "rect",
|
|
3525
|
-
data: { id: "image.cropMask.top", zIndex: 1 },
|
|
3526
|
-
layout: {
|
|
3527
|
-
reference: "custom",
|
|
3528
|
-
referenceRect: viewportRect,
|
|
3529
|
-
alignX: "start",
|
|
3530
|
-
alignY: "start",
|
|
3531
|
-
width: "100%",
|
|
3532
|
-
height: topH
|
|
3533
|
-
},
|
|
3534
|
-
props: {
|
|
3535
|
-
originX: "left",
|
|
3536
|
-
originY: "top",
|
|
3537
|
-
fill: visual.outerBackground,
|
|
3538
|
-
selectable: false,
|
|
3539
|
-
evented: false
|
|
3540
|
-
}
|
|
3541
|
-
},
|
|
3542
|
-
{
|
|
3543
|
-
id: "image.cropMask.bottom",
|
|
3544
|
-
type: "rect",
|
|
3545
|
-
data: { id: "image.cropMask.bottom", zIndex: 2 },
|
|
3546
|
-
layout: {
|
|
3547
|
-
reference: "custom",
|
|
3548
|
-
referenceRect: viewportRect,
|
|
3549
|
-
alignX: "start",
|
|
3550
|
-
alignY: "end",
|
|
3551
|
-
width: "100%",
|
|
3552
|
-
height: bottomH
|
|
3553
|
-
},
|
|
3554
|
-
props: {
|
|
3555
|
-
originX: "left",
|
|
3556
|
-
originY: "top",
|
|
3557
|
-
fill: visual.outerBackground,
|
|
3558
|
-
selectable: false,
|
|
3559
|
-
evented: false
|
|
3560
|
-
}
|
|
3561
|
-
},
|
|
3562
|
-
{
|
|
3563
|
-
id: "image.cropMask.left",
|
|
3564
|
-
type: "rect",
|
|
3565
|
-
data: { id: "image.cropMask.left", zIndex: 3 },
|
|
3566
|
-
layout: {
|
|
3567
|
-
reference: "custom",
|
|
3568
|
-
referenceRect: visibleFrameBandRect,
|
|
3569
|
-
alignX: "start",
|
|
3570
|
-
alignY: "start",
|
|
3571
|
-
width: leftW,
|
|
3572
|
-
height: "100%"
|
|
3573
|
-
},
|
|
3574
|
-
props: {
|
|
3575
|
-
originX: "left",
|
|
3576
|
-
originY: "top",
|
|
3577
|
-
fill: visual.outerBackground,
|
|
3578
|
-
selectable: false,
|
|
3579
|
-
evented: false
|
|
3580
|
-
}
|
|
3581
|
-
},
|
|
3582
|
-
{
|
|
3583
|
-
id: "image.cropMask.right",
|
|
3584
|
-
type: "rect",
|
|
3585
|
-
data: { id: "image.cropMask.right", zIndex: 4 },
|
|
3586
|
-
layout: {
|
|
3587
|
-
reference: "custom",
|
|
3588
|
-
referenceRect: visibleFrameBandRect,
|
|
3589
|
-
alignX: "end",
|
|
3590
|
-
alignY: "start",
|
|
3591
|
-
width: rightW,
|
|
3592
|
-
height: "100%"
|
|
3593
|
-
},
|
|
3594
|
-
props: {
|
|
3595
|
-
originX: "left",
|
|
3596
|
-
originY: "top",
|
|
3597
|
-
fill: visual.outerBackground,
|
|
3598
|
-
selectable: false,
|
|
3599
|
-
evented: false
|
|
3600
|
-
}
|
|
3601
|
-
}
|
|
3602
|
-
];
|
|
3603
|
-
const frameSpec = {
|
|
3604
|
-
id: "image.cropFrame",
|
|
3605
|
-
type: "rect",
|
|
3606
|
-
data: { id: "image.cropFrame", zIndex: 7 },
|
|
3607
|
-
layout: {
|
|
3608
|
-
reference: "custom",
|
|
3609
|
-
referenceRect: frameRect,
|
|
3610
|
-
alignX: "start",
|
|
3611
|
-
alignY: "start",
|
|
3612
|
-
width: "100%",
|
|
3613
|
-
height: "100%"
|
|
3451
|
+
const specs = buildImageSessionOverlaySpecs({
|
|
3452
|
+
viewport: {
|
|
3453
|
+
left: viewport.left,
|
|
3454
|
+
top: viewport.top,
|
|
3455
|
+
width: viewport.width,
|
|
3456
|
+
height: viewport.height
|
|
3614
3457
|
},
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
strokeWidth: visual.strokeStyle === "hidden" ? 0 : strokeWidthScene,
|
|
3621
|
-
strokeDashArray: visual.strokeStyle === "dashed" ? [dashLengthScene, dashLengthScene] : void 0,
|
|
3622
|
-
selectable: false,
|
|
3623
|
-
evented: false
|
|
3624
|
-
}
|
|
3625
|
-
};
|
|
3626
|
-
const specs = shapeOverlay.length > 0 ? [...mask, ...shapeOverlay] : [...mask, ...shapeOverlay, frameSpec];
|
|
3458
|
+
layout: overlayState.layout,
|
|
3459
|
+
geometry: overlayState.geometry,
|
|
3460
|
+
visual,
|
|
3461
|
+
hatchPattern: this.getCropShapeHatchPattern()
|
|
3462
|
+
});
|
|
3627
3463
|
this.debug("overlay:built", {
|
|
3628
|
-
|
|
3629
|
-
shape:
|
|
3464
|
+
cutRect: overlayState.layout.cutRect,
|
|
3465
|
+
shape: overlayState.geometry.shape,
|
|
3630
3466
|
overlayIds: specs.map((spec) => {
|
|
3631
3467
|
var _a;
|
|
3632
3468
|
return {
|
|
@@ -3655,10 +3491,9 @@ var ImageTool = class {
|
|
|
3655
3491
|
}
|
|
3656
3492
|
const imageSpecs = await this.buildImageSpecs(renderItems, frame);
|
|
3657
3493
|
if (seq !== this.renderSeq) return;
|
|
3658
|
-
const
|
|
3659
|
-
if (seq !== this.renderSeq) return;
|
|
3494
|
+
const overlayState = this.resolveSessionOverlayState();
|
|
3660
3495
|
this.imageSpecs = imageSpecs;
|
|
3661
|
-
this.overlaySpecs = this.buildOverlaySpecs(
|
|
3496
|
+
this.overlaySpecs = this.buildOverlaySpecs(overlayState);
|
|
3662
3497
|
await this.canvasService.flushRenderFromProducers();
|
|
3663
3498
|
if (seq !== this.renderSeq) return;
|
|
3664
3499
|
this.refreshImageObjectInteractionState();
|
|
@@ -4912,6 +4747,13 @@ function buildDielineRenderBundle(options) {
|
|
|
4912
4747
|
canvasWidth,
|
|
4913
4748
|
canvasHeight
|
|
4914
4749
|
};
|
|
4750
|
+
const cutFrameRect = {
|
|
4751
|
+
left: cx - cutW / 2,
|
|
4752
|
+
top: cy - cutH / 2,
|
|
4753
|
+
width: cutW,
|
|
4754
|
+
height: cutH,
|
|
4755
|
+
space: "screen"
|
|
4756
|
+
};
|
|
4915
4757
|
const specs = [];
|
|
4916
4758
|
if (insideColor && insideColor !== "transparent" && insideColor !== "rgba(0,0,0,0)" && !hasImages) {
|
|
4917
4759
|
specs.push({
|
|
@@ -5033,9 +4875,13 @@ function buildDielineRenderBundle(options) {
|
|
|
5033
4875
|
width: cutW,
|
|
5034
4876
|
height: cutH,
|
|
5035
4877
|
radius: cutR,
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
4878
|
+
// Build the clip path in the cut frame's local coordinates so Fabric
|
|
4879
|
+
// does not have to infer placement from the standalone path bounds.
|
|
4880
|
+
x: cutW / 2,
|
|
4881
|
+
y: cutH / 2,
|
|
4882
|
+
features: cutFeatures,
|
|
4883
|
+
canvasWidth: cutW,
|
|
4884
|
+
canvasHeight: cutH
|
|
5039
4885
|
});
|
|
5040
4886
|
if (!clipPathData) {
|
|
5041
4887
|
return { specs, effects: [] };
|
|
@@ -5052,6 +4898,12 @@ function buildDielineRenderBundle(options) {
|
|
|
5052
4898
|
id: ids.clipSource,
|
|
5053
4899
|
type: "path",
|
|
5054
4900
|
space: "screen",
|
|
4901
|
+
layout: {
|
|
4902
|
+
reference: "custom",
|
|
4903
|
+
referenceRect: cutFrameRect,
|
|
4904
|
+
alignX: "start",
|
|
4905
|
+
alignY: "start"
|
|
4906
|
+
},
|
|
5055
4907
|
data: {
|
|
5056
4908
|
id: ids.clipSource,
|
|
5057
4909
|
type: "dieline-effect",
|