@pooder/kit 6.2.0 → 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/DielineTool.js +22 -9
- package/.test-dist/src/extensions/dieline/renderBuilder.js +47 -14
- package/.test-dist/src/extensions/feature/FeatureTool.js +20 -3
- package/.test-dist/src/extensions/featureCoordinates.js +21 -0
- package/.test-dist/src/extensions/featurePlacement.js +46 -0
- 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 +25 -2
- package/.test-dist/tests/run.js +25 -0
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +4 -5
- package/dist/index.d.ts +4 -5
- package/dist/index.js +1506 -1494
- package/dist/index.mjs +1506 -1494
- package/package.json +1 -1
- package/src/extensions/dieline/DielineTool.ts +29 -9
- package/src/extensions/dieline/renderBuilder.ts +65 -17
- package/src/extensions/feature/FeatureTool.ts +23 -3
- package/src/extensions/featureCoordinates.ts +35 -0
- package/src/extensions/featurePlacement.ts +118 -0
- package/src/extensions/image/ImageTool.ts +57 -412
- package/src/extensions/image/sessionOverlay.ts +206 -0
- package/src/extensions/ruler/RulerTool.ts +24 -2
- package/tests/run.ts +37 -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
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
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
|
-
}
|
|
1662
|
-
} else {
|
|
1663
|
-
if (f.operation === "add") {
|
|
1664
|
-
adds.push(item);
|
|
1665
|
-
} else {
|
|
1666
|
-
subtracts.push(item);
|
|
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
|
|
1353
|
+
});
|
|
1667
1354
|
}
|
|
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
|
-
|
|
1674
|
+
const ratioEps = 1e-6;
|
|
1675
|
+
if (Math.abs(ratioA - ratioB) > ratioEps) {
|
|
1676
|
+
return ratioA > ratioB ? pointsA : pointsB;
|
|
1677
|
+
}
|
|
1678
|
+
if (scoreA !== scoreB) return scoreA > scoreB ? pointsA : pointsB;
|
|
1679
|
+
return pointsA.length <= pointsB.length ? pointsA : pointsB;
|
|
1680
|
+
}
|
|
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;
|
|
1826
1693
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
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;
|
|
1833
1705
|
}
|
|
1834
|
-
function
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
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;
|
|
1842
1755
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
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);
|
|
1847
1766
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
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;
|
|
1855
1806
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
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;
|
|
1863
1822
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
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;
|
|
1867
1830
|
}
|
|
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
|
-
handler: () => {
|
|
1897
|
-
return tool.cloneItems(tool.workingItems);
|
|
1898
|
-
}
|
|
1899
|
-
},
|
|
1900
|
-
{
|
|
1901
|
-
command: "setWorkingImage",
|
|
1902
|
-
id: "setWorkingImage",
|
|
1903
|
-
title: "Set Working Image",
|
|
1904
|
-
handler: (id, updates) => {
|
|
1905
|
-
tool.updateImageInWorking(id, updates);
|
|
1906
|
-
}
|
|
1907
|
-
},
|
|
1908
|
-
{
|
|
1909
|
-
command: "resetWorkingImages",
|
|
1910
|
-
id: "resetWorkingImages",
|
|
1911
|
-
title: "Reset Working Images",
|
|
1912
|
-
handler: () => {
|
|
1913
|
-
tool.workingItems = tool.cloneItems(tool.items);
|
|
1914
|
-
tool.hasWorkingChanges = false;
|
|
1915
|
-
tool.updateImages();
|
|
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);
|
|
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
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
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
|
|
2218
|
+
}
|
|
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
|
|
2154
2283
|
}
|
|
2155
|
-
|
|
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;
|
|
@@ -3424,209 +3397,72 @@ var ImageTool = class {
|
|
|
3424
3397
|
lockScalingFlip: true,
|
|
3425
3398
|
selectable: this.isImageEditingVisible(),
|
|
3426
3399
|
evented: this.isImageEditingVisible(),
|
|
3427
|
-
hasControls: this.isImageEditingVisible(),
|
|
3428
|
-
hasBorders: this.isImageEditingVisible(),
|
|
3429
|
-
opacity: render.opacity
|
|
3430
|
-
};
|
|
3431
|
-
}
|
|
3432
|
-
toSceneObjectScale(value) {
|
|
3433
|
-
if (!this.canvasService) return value;
|
|
3434
|
-
return value / this.canvasService.getSceneScale();
|
|
3435
|
-
}
|
|
3436
|
-
getCurrentSrc(obj) {
|
|
3437
|
-
var _a;
|
|
3438
|
-
if (!obj) return void 0;
|
|
3439
|
-
if (typeof obj.getSrc === "function") return obj.getSrc();
|
|
3440
|
-
return (_a = obj == null ? void 0 : obj._originalElement) == null ? void 0 : _a.src;
|
|
3441
|
-
}
|
|
3442
|
-
async buildImageSpecs(items, frame) {
|
|
3443
|
-
const specs = [];
|
|
3444
|
-
for (const item of items) {
|
|
3445
|
-
const render = this.resolveRenderImageState(item);
|
|
3446
|
-
if (!render.src) continue;
|
|
3447
|
-
const ensured = await this.ensureSourceSize(render.src);
|
|
3448
|
-
const sourceSize = ensured || this.getSourceSize(render.src);
|
|
3449
|
-
const props = this.computeCanvasProps(render, sourceSize, frame);
|
|
3450
|
-
specs.push({
|
|
3451
|
-
id: item.id,
|
|
3452
|
-
type: "image",
|
|
3453
|
-
src: render.src,
|
|
3454
|
-
data: {
|
|
3455
|
-
id: item.id,
|
|
3456
|
-
layerId: IMAGE_OBJECT_LAYER_ID,
|
|
3457
|
-
type: "image-item"
|
|
3458
|
-
},
|
|
3459
|
-
props
|
|
3460
|
-
});
|
|
3461
|
-
}
|
|
3462
|
-
return specs;
|
|
3463
|
-
}
|
|
3464
|
-
buildOverlaySpecs(frame, sceneGeometry) {
|
|
3465
|
-
const visible = this.isImageEditingVisible();
|
|
3466
|
-
if (!visible || frame.width <= 0 || frame.height <= 0 || !this.canvasService) {
|
|
3467
|
-
this.debug("overlay:hidden", {
|
|
3468
|
-
visible,
|
|
3469
|
-
frame,
|
|
3470
|
-
isToolActive: this.isToolActive,
|
|
3471
|
-
isImageSelectionActive: this.isImageSelectionActive,
|
|
3472
|
-
focusedImageId: this.focusedImageId
|
|
3473
|
-
});
|
|
3474
|
-
return [];
|
|
3475
|
-
}
|
|
3476
|
-
const viewport = this.canvasService.getSceneViewportRect();
|
|
3477
|
-
const canvasW = viewport.width || 0;
|
|
3478
|
-
const canvasH = viewport.height || 0;
|
|
3479
|
-
const canvasLeft = viewport.left || 0;
|
|
3480
|
-
const canvasTop = viewport.top || 0;
|
|
3481
|
-
const visual = this.getFrameVisualConfig();
|
|
3482
|
-
const strokeWidthScene = this.canvasService.toSceneLength(
|
|
3483
|
-
visual.strokeWidth
|
|
3484
|
-
);
|
|
3485
|
-
const dashLengthScene = this.canvasService.toSceneLength(visual.dashLength);
|
|
3486
|
-
const frameLeft = Math.max(
|
|
3487
|
-
canvasLeft,
|
|
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%"
|
|
3400
|
+
hasControls: this.isImageEditingVisible(),
|
|
3401
|
+
hasBorders: this.isImageEditingVisible(),
|
|
3402
|
+
opacity: render.opacity
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
3405
|
+
toSceneObjectScale(value) {
|
|
3406
|
+
if (!this.canvasService) return value;
|
|
3407
|
+
return value / this.canvasService.getSceneScale();
|
|
3408
|
+
}
|
|
3409
|
+
getCurrentSrc(obj) {
|
|
3410
|
+
var _a;
|
|
3411
|
+
if (!obj) return void 0;
|
|
3412
|
+
if (typeof obj.getSrc === "function") return obj.getSrc();
|
|
3413
|
+
return (_a = obj == null ? void 0 : obj._originalElement) == null ? void 0 : _a.src;
|
|
3414
|
+
}
|
|
3415
|
+
async buildImageSpecs(items, frame) {
|
|
3416
|
+
const specs = [];
|
|
3417
|
+
for (const item of items) {
|
|
3418
|
+
const render = this.resolveRenderImageState(item);
|
|
3419
|
+
if (!render.src) continue;
|
|
3420
|
+
const ensured = await this.ensureSourceSize(render.src);
|
|
3421
|
+
const sourceSize = ensured || this.getSourceSize(render.src);
|
|
3422
|
+
const props = this.computeCanvasProps(render, sourceSize, frame);
|
|
3423
|
+
specs.push({
|
|
3424
|
+
id: item.id,
|
|
3425
|
+
type: "image",
|
|
3426
|
+
src: render.src,
|
|
3427
|
+
data: {
|
|
3428
|
+
id: item.id,
|
|
3429
|
+
layerId: IMAGE_OBJECT_LAYER_ID,
|
|
3430
|
+
type: "image-item"
|
|
3593
3431
|
},
|
|
3594
|
-
props
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3432
|
+
props
|
|
3433
|
+
});
|
|
3434
|
+
}
|
|
3435
|
+
return specs;
|
|
3436
|
+
}
|
|
3437
|
+
buildOverlaySpecs(overlayState) {
|
|
3438
|
+
const visible = this.isImageEditingVisible();
|
|
3439
|
+
if (!visible || !overlayState || !this.canvasService) {
|
|
3440
|
+
this.debug("overlay:hidden", {
|
|
3441
|
+
visible,
|
|
3442
|
+
cutRect: overlayState == null ? void 0 : overlayState.layout.cutRect,
|
|
3443
|
+
isToolActive: this.isToolActive,
|
|
3444
|
+
isImageSelectionActive: this.isImageSelectionActive,
|
|
3445
|
+
focusedImageId: this.focusedImageId
|
|
3446
|
+
});
|
|
3447
|
+
return [];
|
|
3448
|
+
}
|
|
3449
|
+
const viewport = this.canvasService.getScreenViewportRect();
|
|
3450
|
+
const visual = this.getFrameVisualConfig();
|
|
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();
|
|
@@ -4582,6 +4417,259 @@ function readDielineState(configService, fallback) {
|
|
|
4582
4417
|
};
|
|
4583
4418
|
}
|
|
4584
4419
|
|
|
4420
|
+
// src/extensions/constraints.ts
|
|
4421
|
+
var ConstraintRegistry = class {
|
|
4422
|
+
static register(type, handler) {
|
|
4423
|
+
this.handlers.set(type, handler);
|
|
4424
|
+
}
|
|
4425
|
+
static apply(x, y, feature, context, constraints) {
|
|
4426
|
+
const list = constraints || feature.constraints;
|
|
4427
|
+
if (!list || list.length === 0) {
|
|
4428
|
+
return { x, y };
|
|
4429
|
+
}
|
|
4430
|
+
let currentX = x;
|
|
4431
|
+
let currentY = y;
|
|
4432
|
+
for (const constraint of list) {
|
|
4433
|
+
const handler = this.handlers.get(constraint.type);
|
|
4434
|
+
if (handler) {
|
|
4435
|
+
const result = handler(currentX, currentY, feature, context, constraint.params || {});
|
|
4436
|
+
currentX = result.x;
|
|
4437
|
+
currentY = result.y;
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
return { x: currentX, y: currentY };
|
|
4441
|
+
}
|
|
4442
|
+
};
|
|
4443
|
+
ConstraintRegistry.handlers = /* @__PURE__ */ new Map();
|
|
4444
|
+
var pathConstraint = (x, y, feature, context, params) => {
|
|
4445
|
+
const { dielineWidth, dielineHeight, geometry } = context;
|
|
4446
|
+
if (!geometry) return { x, y };
|
|
4447
|
+
const minX = geometry.x - geometry.width / 2;
|
|
4448
|
+
const minY = geometry.y - geometry.height / 2;
|
|
4449
|
+
const absX = minX + x * geometry.width;
|
|
4450
|
+
const absY = minY + y * geometry.height;
|
|
4451
|
+
const nearest = getNearestPointOnDieline(
|
|
4452
|
+
{ x: absX, y: absY },
|
|
4453
|
+
geometry
|
|
4454
|
+
);
|
|
4455
|
+
let finalX = nearest.x;
|
|
4456
|
+
let finalY = nearest.y;
|
|
4457
|
+
const hasOffsetParams = params.minOffset !== void 0 || params.maxOffset !== void 0;
|
|
4458
|
+
if (hasOffsetParams && nearest.normal) {
|
|
4459
|
+
const dx = absX - nearest.x;
|
|
4460
|
+
const dy = absY - nearest.y;
|
|
4461
|
+
const nx2 = nearest.normal.x;
|
|
4462
|
+
const ny2 = nearest.normal.y;
|
|
4463
|
+
const dist = dx * nx2 + dy * ny2;
|
|
4464
|
+
const scale = dielineWidth > 0 ? geometry.width / dielineWidth : 1;
|
|
4465
|
+
const rawMin = params.minOffset !== void 0 ? params.minOffset : 0;
|
|
4466
|
+
const rawMax = params.maxOffset !== void 0 ? params.maxOffset : 0;
|
|
4467
|
+
const minOffset = rawMin * scale;
|
|
4468
|
+
const maxOffset = rawMax * scale;
|
|
4469
|
+
const clampedDist = Math.max(minOffset, Math.min(dist, maxOffset));
|
|
4470
|
+
finalX = nearest.x + nx2 * clampedDist;
|
|
4471
|
+
finalY = nearest.y + ny2 * clampedDist;
|
|
4472
|
+
}
|
|
4473
|
+
const nx = geometry.width > 0 ? (finalX - minX) / geometry.width : 0.5;
|
|
4474
|
+
const ny = geometry.height > 0 ? (finalY - minY) / geometry.height : 0.5;
|
|
4475
|
+
return { x: nx, y: ny };
|
|
4476
|
+
};
|
|
4477
|
+
var edgeConstraint = (x, y, feature, context, params) => {
|
|
4478
|
+
const { dielineWidth, dielineHeight } = context;
|
|
4479
|
+
const allowedEdges = params.allowedEdges || [
|
|
4480
|
+
"top",
|
|
4481
|
+
"bottom",
|
|
4482
|
+
"left",
|
|
4483
|
+
"right"
|
|
4484
|
+
];
|
|
4485
|
+
const confine = params.confine || false;
|
|
4486
|
+
const offset = params.offset || 0;
|
|
4487
|
+
const distances = [];
|
|
4488
|
+
if (allowedEdges.includes("top"))
|
|
4489
|
+
distances.push({ edge: "top", dist: y * dielineHeight });
|
|
4490
|
+
if (allowedEdges.includes("bottom"))
|
|
4491
|
+
distances.push({ edge: "bottom", dist: (1 - y) * dielineHeight });
|
|
4492
|
+
if (allowedEdges.includes("left"))
|
|
4493
|
+
distances.push({ edge: "left", dist: x * dielineWidth });
|
|
4494
|
+
if (allowedEdges.includes("right"))
|
|
4495
|
+
distances.push({ edge: "right", dist: (1 - x) * dielineWidth });
|
|
4496
|
+
if (distances.length === 0) return { x, y };
|
|
4497
|
+
distances.sort((a, b) => a.dist - b.dist);
|
|
4498
|
+
const nearest = distances[0].edge;
|
|
4499
|
+
let newX = x;
|
|
4500
|
+
let newY = y;
|
|
4501
|
+
const fw = feature.width || 0;
|
|
4502
|
+
const fh = feature.height || 0;
|
|
4503
|
+
switch (nearest) {
|
|
4504
|
+
case "top":
|
|
4505
|
+
newY = 0 + offset / dielineHeight;
|
|
4506
|
+
if (confine) {
|
|
4507
|
+
const minX = fw / 2 / dielineWidth;
|
|
4508
|
+
const maxX = 1 - minX;
|
|
4509
|
+
newX = Math.max(minX, Math.min(newX, maxX));
|
|
4510
|
+
}
|
|
4511
|
+
break;
|
|
4512
|
+
case "bottom":
|
|
4513
|
+
newY = 1 - offset / dielineHeight;
|
|
4514
|
+
if (confine) {
|
|
4515
|
+
const minX = fw / 2 / dielineWidth;
|
|
4516
|
+
const maxX = 1 - minX;
|
|
4517
|
+
newX = Math.max(minX, Math.min(newX, maxX));
|
|
4518
|
+
}
|
|
4519
|
+
break;
|
|
4520
|
+
case "left":
|
|
4521
|
+
newX = 0 + offset / dielineWidth;
|
|
4522
|
+
if (confine) {
|
|
4523
|
+
const minY = fh / 2 / dielineHeight;
|
|
4524
|
+
const maxY = 1 - minY;
|
|
4525
|
+
newY = Math.max(minY, Math.min(newY, maxY));
|
|
4526
|
+
}
|
|
4527
|
+
break;
|
|
4528
|
+
case "right":
|
|
4529
|
+
newX = 1 - offset / dielineWidth;
|
|
4530
|
+
if (confine) {
|
|
4531
|
+
const minY = fh / 2 / dielineHeight;
|
|
4532
|
+
const maxY = 1 - minY;
|
|
4533
|
+
newY = Math.max(minY, Math.min(newY, maxY));
|
|
4534
|
+
}
|
|
4535
|
+
break;
|
|
4536
|
+
}
|
|
4537
|
+
return { x: newX, y: newY };
|
|
4538
|
+
};
|
|
4539
|
+
var internalConstraint = (x, y, feature, context, params) => {
|
|
4540
|
+
const { dielineWidth, dielineHeight } = context;
|
|
4541
|
+
const margin = params.margin || 0;
|
|
4542
|
+
const fw = feature.width || 0;
|
|
4543
|
+
const fh = feature.height || 0;
|
|
4544
|
+
const minX = (margin + fw / 2) / dielineWidth;
|
|
4545
|
+
const maxX = 1 - (margin + fw / 2) / dielineWidth;
|
|
4546
|
+
const minY = (margin + fh / 2) / dielineHeight;
|
|
4547
|
+
const maxY = 1 - (margin + fh / 2) / dielineHeight;
|
|
4548
|
+
const clampedX = minX > maxX ? 0.5 : Math.max(minX, Math.min(x, maxX));
|
|
4549
|
+
const clampedY = minY > maxY ? 0.5 : Math.max(minY, Math.min(y, maxY));
|
|
4550
|
+
return { x: clampedX, y: clampedY };
|
|
4551
|
+
};
|
|
4552
|
+
var tangentBottomConstraint = (x, y, feature, context, params) => {
|
|
4553
|
+
const { dielineWidth, dielineHeight } = context;
|
|
4554
|
+
const gap = params.gap || 0;
|
|
4555
|
+
const confineX = params.confineX !== false;
|
|
4556
|
+
const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
|
|
4557
|
+
const newY = 1 + (extentY + gap) / dielineHeight;
|
|
4558
|
+
let newX = x;
|
|
4559
|
+
if (confineX) {
|
|
4560
|
+
const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
|
|
4561
|
+
const minX = extentX / dielineWidth;
|
|
4562
|
+
const maxX = 1 - extentX / dielineWidth;
|
|
4563
|
+
newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
|
|
4564
|
+
}
|
|
4565
|
+
return { x: newX, y: newY };
|
|
4566
|
+
};
|
|
4567
|
+
var lowestTangentConstraint = (x, y, feature, context, params) => {
|
|
4568
|
+
const { dielineWidth, dielineHeight, geometry } = context;
|
|
4569
|
+
if (!geometry) return { x, y };
|
|
4570
|
+
const lowest = getLowestPointOnDieline(geometry);
|
|
4571
|
+
const minY = geometry.y - geometry.height / 2;
|
|
4572
|
+
const normY = (lowest.y - minY) / geometry.height;
|
|
4573
|
+
const gap = params.gap || 0;
|
|
4574
|
+
const confineX = params.confineX !== false;
|
|
4575
|
+
const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
|
|
4576
|
+
const newY = normY + (extentY + gap) / dielineHeight;
|
|
4577
|
+
let newX = x;
|
|
4578
|
+
if (confineX) {
|
|
4579
|
+
const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
|
|
4580
|
+
const minX = extentX / dielineWidth;
|
|
4581
|
+
const maxX = 1 - extentX / dielineWidth;
|
|
4582
|
+
newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
|
|
4583
|
+
}
|
|
4584
|
+
return { x: newX, y: newY };
|
|
4585
|
+
};
|
|
4586
|
+
ConstraintRegistry.register("path", pathConstraint);
|
|
4587
|
+
ConstraintRegistry.register("edge", edgeConstraint);
|
|
4588
|
+
ConstraintRegistry.register("internal", internalConstraint);
|
|
4589
|
+
ConstraintRegistry.register("tangent-bottom", tangentBottomConstraint);
|
|
4590
|
+
ConstraintRegistry.register("lowest-tangent", lowestTangentConstraint);
|
|
4591
|
+
|
|
4592
|
+
// src/extensions/featureCoordinates.ts
|
|
4593
|
+
function resolveFeaturePosition2(feature, geometry) {
|
|
4594
|
+
const { x, y, width, height } = geometry;
|
|
4595
|
+
const left = x - width / 2;
|
|
4596
|
+
const top = y - height / 2;
|
|
4597
|
+
return {
|
|
4598
|
+
x: left + feature.x * width,
|
|
4599
|
+
y: top + feature.y * height
|
|
4600
|
+
};
|
|
4601
|
+
}
|
|
4602
|
+
function normalizePointInGeometry(point, geometry) {
|
|
4603
|
+
const left = geometry.x - geometry.width / 2;
|
|
4604
|
+
const top = geometry.y - geometry.height / 2;
|
|
4605
|
+
return {
|
|
4606
|
+
x: geometry.width > 0 ? (point.x - left) / geometry.width : 0.5,
|
|
4607
|
+
y: geometry.height > 0 ? (point.y - top) / geometry.height : 0.5
|
|
4608
|
+
};
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
// src/extensions/featurePlacement.ts
|
|
4612
|
+
function scaleFeatureForRender(feature, scale, x, y) {
|
|
4613
|
+
return {
|
|
4614
|
+
...feature,
|
|
4615
|
+
x,
|
|
4616
|
+
y,
|
|
4617
|
+
width: feature.width !== void 0 ? feature.width * scale : void 0,
|
|
4618
|
+
height: feature.height !== void 0 ? feature.height * scale : void 0,
|
|
4619
|
+
radius: feature.radius !== void 0 ? feature.radius * scale : void 0
|
|
4620
|
+
};
|
|
4621
|
+
}
|
|
4622
|
+
function resolveFeaturePlacements(features, geometry) {
|
|
4623
|
+
const dielineWidth = geometry.scale > 0 ? geometry.width / geometry.scale : geometry.width;
|
|
4624
|
+
const dielineHeight = geometry.scale > 0 ? geometry.height / geometry.scale : geometry.height;
|
|
4625
|
+
return (features || []).map((feature) => {
|
|
4626
|
+
var _a;
|
|
4627
|
+
const activeConstraints = (_a = feature.constraints) == null ? void 0 : _a.filter(
|
|
4628
|
+
(constraint) => !constraint.validateOnly
|
|
4629
|
+
);
|
|
4630
|
+
const constrained = ConstraintRegistry.apply(
|
|
4631
|
+
feature.x,
|
|
4632
|
+
feature.y,
|
|
4633
|
+
feature,
|
|
4634
|
+
{
|
|
4635
|
+
dielineWidth,
|
|
4636
|
+
dielineHeight,
|
|
4637
|
+
geometry
|
|
4638
|
+
},
|
|
4639
|
+
activeConstraints
|
|
4640
|
+
);
|
|
4641
|
+
const center = resolveFeaturePosition2(
|
|
4642
|
+
{
|
|
4643
|
+
...feature,
|
|
4644
|
+
x: constrained.x,
|
|
4645
|
+
y: constrained.y
|
|
4646
|
+
},
|
|
4647
|
+
geometry
|
|
4648
|
+
);
|
|
4649
|
+
return {
|
|
4650
|
+
feature,
|
|
4651
|
+
normalizedX: constrained.x,
|
|
4652
|
+
normalizedY: constrained.y,
|
|
4653
|
+
centerX: center.x,
|
|
4654
|
+
centerY: center.y
|
|
4655
|
+
};
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4658
|
+
function projectPlacedFeatures(placements, geometry, scale) {
|
|
4659
|
+
return placements.map((placement) => {
|
|
4660
|
+
const normalized = normalizePointInGeometry(
|
|
4661
|
+
{ x: placement.centerX, y: placement.centerY },
|
|
4662
|
+
geometry
|
|
4663
|
+
);
|
|
4664
|
+
return scaleFeatureForRender(
|
|
4665
|
+
placement.feature,
|
|
4666
|
+
scale,
|
|
4667
|
+
normalized.x,
|
|
4668
|
+
normalized.y
|
|
4669
|
+
);
|
|
4670
|
+
});
|
|
4671
|
+
}
|
|
4672
|
+
|
|
4585
4673
|
// src/extensions/dieline/renderBuilder.ts
|
|
4586
4674
|
var DEFAULT_IDS = {
|
|
4587
4675
|
inside: "dieline.inside",
|
|
@@ -4591,16 +4679,6 @@ var DEFAULT_IDS = {
|
|
|
4591
4679
|
clip: "dieline.clip.image",
|
|
4592
4680
|
clipSource: "dieline.effect.clip-path"
|
|
4593
4681
|
};
|
|
4594
|
-
function scaleFeatures(state, scale) {
|
|
4595
|
-
return (state.features || []).map((feature) => ({
|
|
4596
|
-
...feature,
|
|
4597
|
-
x: feature.x,
|
|
4598
|
-
y: feature.y,
|
|
4599
|
-
width: (feature.width || 0) * scale,
|
|
4600
|
-
height: (feature.height || 0) * scale,
|
|
4601
|
-
radius: (feature.radius || 0) * scale
|
|
4602
|
-
}));
|
|
4603
|
-
}
|
|
4604
4682
|
function buildDielineRenderBundle(options) {
|
|
4605
4683
|
const ids = { ...DEFAULT_IDS, ...options.ids || {} };
|
|
4606
4684
|
const {
|
|
@@ -4625,8 +4703,41 @@ function buildDielineRenderBundle(options) {
|
|
|
4625
4703
|
const cutH = sceneLayout.cutRect.height;
|
|
4626
4704
|
const visualOffset = (cutW - visualWidth) / 2;
|
|
4627
4705
|
const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
4628
|
-
const
|
|
4629
|
-
|
|
4706
|
+
const placements = resolveFeaturePlacements(state.features || [], {
|
|
4707
|
+
shape,
|
|
4708
|
+
shapeStyle,
|
|
4709
|
+
pathData: state.pathData,
|
|
4710
|
+
customSourceWidthPx: state.customSourceWidthPx,
|
|
4711
|
+
customSourceHeightPx: state.customSourceHeightPx,
|
|
4712
|
+
canvasWidth,
|
|
4713
|
+
canvasHeight,
|
|
4714
|
+
x: cx,
|
|
4715
|
+
y: cy,
|
|
4716
|
+
width: visualWidth,
|
|
4717
|
+
height: visualHeight,
|
|
4718
|
+
radius: visualRadius,
|
|
4719
|
+
scale
|
|
4720
|
+
});
|
|
4721
|
+
const absoluteFeatures = projectPlacedFeatures(
|
|
4722
|
+
placements,
|
|
4723
|
+
{
|
|
4724
|
+
x: cx,
|
|
4725
|
+
y: cy,
|
|
4726
|
+
width: visualWidth,
|
|
4727
|
+
height: visualHeight
|
|
4728
|
+
},
|
|
4729
|
+
scale
|
|
4730
|
+
);
|
|
4731
|
+
const cutFeatures = projectPlacedFeatures(
|
|
4732
|
+
placements.filter((placement) => !placement.feature.skipCut),
|
|
4733
|
+
{
|
|
4734
|
+
x: cx,
|
|
4735
|
+
y: cy,
|
|
4736
|
+
width: cutW,
|
|
4737
|
+
height: cutH
|
|
4738
|
+
},
|
|
4739
|
+
scale
|
|
4740
|
+
);
|
|
4630
4741
|
const common = {
|
|
4631
4742
|
shape,
|
|
4632
4743
|
shapeStyle,
|
|
@@ -4636,6 +4747,13 @@ function buildDielineRenderBundle(options) {
|
|
|
4636
4747
|
canvasWidth,
|
|
4637
4748
|
canvasHeight
|
|
4638
4749
|
};
|
|
4750
|
+
const cutFrameRect = {
|
|
4751
|
+
left: cx - cutW / 2,
|
|
4752
|
+
top: cy - cutH / 2,
|
|
4753
|
+
width: cutW,
|
|
4754
|
+
height: cutH,
|
|
4755
|
+
space: "screen"
|
|
4756
|
+
};
|
|
4639
4757
|
const specs = [];
|
|
4640
4758
|
if (insideColor && insideColor !== "transparent" && insideColor !== "rgba(0,0,0,0)" && !hasImages) {
|
|
4641
4759
|
specs.push({
|
|
@@ -4757,9 +4875,13 @@ function buildDielineRenderBundle(options) {
|
|
|
4757
4875
|
width: cutW,
|
|
4758
4876
|
height: cutH,
|
|
4759
4877
|
radius: cutR,
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
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
|
|
4763
4885
|
});
|
|
4764
4886
|
if (!clipPathData) {
|
|
4765
4887
|
return { specs, effects: [] };
|
|
@@ -4776,6 +4898,12 @@ function buildDielineRenderBundle(options) {
|
|
|
4776
4898
|
id: ids.clipSource,
|
|
4777
4899
|
type: "path",
|
|
4778
4900
|
space: "screen",
|
|
4901
|
+
layout: {
|
|
4902
|
+
reference: "custom",
|
|
4903
|
+
referenceRect: cutFrameRect,
|
|
4904
|
+
alignX: "start",
|
|
4905
|
+
alignY: "start"
|
|
4906
|
+
},
|
|
4779
4907
|
data: {
|
|
4780
4908
|
id: ids.clipSource,
|
|
4781
4909
|
type: "dieline-effect",
|
|
@@ -5055,15 +5183,31 @@ var DielineTool = class {
|
|
|
5055
5183
|
const visualRadius = radius * scale;
|
|
5056
5184
|
const visualOffset = (cutW - sceneLayout.trimRect.width) / 2;
|
|
5057
5185
|
const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
5058
|
-
const
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5186
|
+
const placements = resolveFeaturePlacements(features || [], {
|
|
5187
|
+
shape,
|
|
5188
|
+
shapeStyle,
|
|
5189
|
+
pathData,
|
|
5190
|
+
customSourceWidthPx: this.state.customSourceWidthPx,
|
|
5191
|
+
customSourceHeightPx: this.state.customSourceHeightPx,
|
|
5192
|
+
canvasWidth: canvasW,
|
|
5193
|
+
canvasHeight: canvasH,
|
|
5194
|
+
x: cx,
|
|
5195
|
+
y: cy,
|
|
5196
|
+
width: sceneLayout.trimRect.width,
|
|
5197
|
+
height: sceneLayout.trimRect.height,
|
|
5198
|
+
radius: visualRadius,
|
|
5199
|
+
scale
|
|
5200
|
+
});
|
|
5201
|
+
const cutFeatures = projectPlacedFeatures(
|
|
5202
|
+
placements.filter((placement) => !placement.feature.skipCut),
|
|
5203
|
+
{
|
|
5204
|
+
x: cx,
|
|
5205
|
+
y: cy,
|
|
5206
|
+
width: cutW,
|
|
5207
|
+
height: cutH
|
|
5208
|
+
},
|
|
5209
|
+
scale
|
|
5210
|
+
);
|
|
5067
5211
|
const generatedPathData = generateDielinePath({
|
|
5068
5212
|
shape,
|
|
5069
5213
|
width: cutW,
|
|
@@ -5189,178 +5333,6 @@ import {
|
|
|
5189
5333
|
} from "@pooder/core";
|
|
5190
5334
|
import { Pattern as Pattern3 } from "fabric";
|
|
5191
5335
|
|
|
5192
|
-
// src/extensions/constraints.ts
|
|
5193
|
-
var ConstraintRegistry = class {
|
|
5194
|
-
static register(type, handler) {
|
|
5195
|
-
this.handlers.set(type, handler);
|
|
5196
|
-
}
|
|
5197
|
-
static apply(x, y, feature, context, constraints) {
|
|
5198
|
-
const list = constraints || feature.constraints;
|
|
5199
|
-
if (!list || list.length === 0) {
|
|
5200
|
-
return { x, y };
|
|
5201
|
-
}
|
|
5202
|
-
let currentX = x;
|
|
5203
|
-
let currentY = y;
|
|
5204
|
-
for (const constraint of list) {
|
|
5205
|
-
const handler = this.handlers.get(constraint.type);
|
|
5206
|
-
if (handler) {
|
|
5207
|
-
const result = handler(currentX, currentY, feature, context, constraint.params || {});
|
|
5208
|
-
currentX = result.x;
|
|
5209
|
-
currentY = result.y;
|
|
5210
|
-
}
|
|
5211
|
-
}
|
|
5212
|
-
return { x: currentX, y: currentY };
|
|
5213
|
-
}
|
|
5214
|
-
};
|
|
5215
|
-
ConstraintRegistry.handlers = /* @__PURE__ */ new Map();
|
|
5216
|
-
var pathConstraint = (x, y, feature, context, params) => {
|
|
5217
|
-
const { dielineWidth, dielineHeight, geometry } = context;
|
|
5218
|
-
if (!geometry) return { x, y };
|
|
5219
|
-
const minX = geometry.x - geometry.width / 2;
|
|
5220
|
-
const minY = geometry.y - geometry.height / 2;
|
|
5221
|
-
const absX = minX + x * geometry.width;
|
|
5222
|
-
const absY = minY + y * geometry.height;
|
|
5223
|
-
const nearest = getNearestPointOnDieline(
|
|
5224
|
-
{ x: absX, y: absY },
|
|
5225
|
-
geometry
|
|
5226
|
-
);
|
|
5227
|
-
let finalX = nearest.x;
|
|
5228
|
-
let finalY = nearest.y;
|
|
5229
|
-
const hasOffsetParams = params.minOffset !== void 0 || params.maxOffset !== void 0;
|
|
5230
|
-
if (hasOffsetParams && nearest.normal) {
|
|
5231
|
-
const dx = absX - nearest.x;
|
|
5232
|
-
const dy = absY - nearest.y;
|
|
5233
|
-
const nx2 = nearest.normal.x;
|
|
5234
|
-
const ny2 = nearest.normal.y;
|
|
5235
|
-
const dist = dx * nx2 + dy * ny2;
|
|
5236
|
-
const scale = dielineWidth > 0 ? geometry.width / dielineWidth : 1;
|
|
5237
|
-
const rawMin = params.minOffset !== void 0 ? params.minOffset : 0;
|
|
5238
|
-
const rawMax = params.maxOffset !== void 0 ? params.maxOffset : 0;
|
|
5239
|
-
const minOffset = rawMin * scale;
|
|
5240
|
-
const maxOffset = rawMax * scale;
|
|
5241
|
-
const clampedDist = Math.max(minOffset, Math.min(dist, maxOffset));
|
|
5242
|
-
finalX = nearest.x + nx2 * clampedDist;
|
|
5243
|
-
finalY = nearest.y + ny2 * clampedDist;
|
|
5244
|
-
}
|
|
5245
|
-
const nx = geometry.width > 0 ? (finalX - minX) / geometry.width : 0.5;
|
|
5246
|
-
const ny = geometry.height > 0 ? (finalY - minY) / geometry.height : 0.5;
|
|
5247
|
-
return { x: nx, y: ny };
|
|
5248
|
-
};
|
|
5249
|
-
var edgeConstraint = (x, y, feature, context, params) => {
|
|
5250
|
-
const { dielineWidth, dielineHeight } = context;
|
|
5251
|
-
const allowedEdges = params.allowedEdges || [
|
|
5252
|
-
"top",
|
|
5253
|
-
"bottom",
|
|
5254
|
-
"left",
|
|
5255
|
-
"right"
|
|
5256
|
-
];
|
|
5257
|
-
const confine = params.confine || false;
|
|
5258
|
-
const offset = params.offset || 0;
|
|
5259
|
-
const distances = [];
|
|
5260
|
-
if (allowedEdges.includes("top"))
|
|
5261
|
-
distances.push({ edge: "top", dist: y * dielineHeight });
|
|
5262
|
-
if (allowedEdges.includes("bottom"))
|
|
5263
|
-
distances.push({ edge: "bottom", dist: (1 - y) * dielineHeight });
|
|
5264
|
-
if (allowedEdges.includes("left"))
|
|
5265
|
-
distances.push({ edge: "left", dist: x * dielineWidth });
|
|
5266
|
-
if (allowedEdges.includes("right"))
|
|
5267
|
-
distances.push({ edge: "right", dist: (1 - x) * dielineWidth });
|
|
5268
|
-
if (distances.length === 0) return { x, y };
|
|
5269
|
-
distances.sort((a, b) => a.dist - b.dist);
|
|
5270
|
-
const nearest = distances[0].edge;
|
|
5271
|
-
let newX = x;
|
|
5272
|
-
let newY = y;
|
|
5273
|
-
const fw = feature.width || 0;
|
|
5274
|
-
const fh = feature.height || 0;
|
|
5275
|
-
switch (nearest) {
|
|
5276
|
-
case "top":
|
|
5277
|
-
newY = 0 + offset / dielineHeight;
|
|
5278
|
-
if (confine) {
|
|
5279
|
-
const minX = fw / 2 / dielineWidth;
|
|
5280
|
-
const maxX = 1 - minX;
|
|
5281
|
-
newX = Math.max(minX, Math.min(newX, maxX));
|
|
5282
|
-
}
|
|
5283
|
-
break;
|
|
5284
|
-
case "bottom":
|
|
5285
|
-
newY = 1 - offset / dielineHeight;
|
|
5286
|
-
if (confine) {
|
|
5287
|
-
const minX = fw / 2 / dielineWidth;
|
|
5288
|
-
const maxX = 1 - minX;
|
|
5289
|
-
newX = Math.max(minX, Math.min(newX, maxX));
|
|
5290
|
-
}
|
|
5291
|
-
break;
|
|
5292
|
-
case "left":
|
|
5293
|
-
newX = 0 + offset / dielineWidth;
|
|
5294
|
-
if (confine) {
|
|
5295
|
-
const minY = fh / 2 / dielineHeight;
|
|
5296
|
-
const maxY = 1 - minY;
|
|
5297
|
-
newY = Math.max(minY, Math.min(newY, maxY));
|
|
5298
|
-
}
|
|
5299
|
-
break;
|
|
5300
|
-
case "right":
|
|
5301
|
-
newX = 1 - offset / dielineWidth;
|
|
5302
|
-
if (confine) {
|
|
5303
|
-
const minY = fh / 2 / dielineHeight;
|
|
5304
|
-
const maxY = 1 - minY;
|
|
5305
|
-
newY = Math.max(minY, Math.min(newY, maxY));
|
|
5306
|
-
}
|
|
5307
|
-
break;
|
|
5308
|
-
}
|
|
5309
|
-
return { x: newX, y: newY };
|
|
5310
|
-
};
|
|
5311
|
-
var internalConstraint = (x, y, feature, context, params) => {
|
|
5312
|
-
const { dielineWidth, dielineHeight } = context;
|
|
5313
|
-
const margin = params.margin || 0;
|
|
5314
|
-
const fw = feature.width || 0;
|
|
5315
|
-
const fh = feature.height || 0;
|
|
5316
|
-
const minX = (margin + fw / 2) / dielineWidth;
|
|
5317
|
-
const maxX = 1 - (margin + fw / 2) / dielineWidth;
|
|
5318
|
-
const minY = (margin + fh / 2) / dielineHeight;
|
|
5319
|
-
const maxY = 1 - (margin + fh / 2) / dielineHeight;
|
|
5320
|
-
const clampedX = minX > maxX ? 0.5 : Math.max(minX, Math.min(x, maxX));
|
|
5321
|
-
const clampedY = minY > maxY ? 0.5 : Math.max(minY, Math.min(y, maxY));
|
|
5322
|
-
return { x: clampedX, y: clampedY };
|
|
5323
|
-
};
|
|
5324
|
-
var tangentBottomConstraint = (x, y, feature, context, params) => {
|
|
5325
|
-
const { dielineWidth, dielineHeight } = context;
|
|
5326
|
-
const gap = params.gap || 0;
|
|
5327
|
-
const confineX = params.confineX !== false;
|
|
5328
|
-
const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
|
|
5329
|
-
const newY = 1 + (extentY + gap) / dielineHeight;
|
|
5330
|
-
let newX = x;
|
|
5331
|
-
if (confineX) {
|
|
5332
|
-
const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
|
|
5333
|
-
const minX = extentX / dielineWidth;
|
|
5334
|
-
const maxX = 1 - extentX / dielineWidth;
|
|
5335
|
-
newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
|
|
5336
|
-
}
|
|
5337
|
-
return { x: newX, y: newY };
|
|
5338
|
-
};
|
|
5339
|
-
var lowestTangentConstraint = (x, y, feature, context, params) => {
|
|
5340
|
-
const { dielineWidth, dielineHeight, geometry } = context;
|
|
5341
|
-
if (!geometry) return { x, y };
|
|
5342
|
-
const lowest = getLowestPointOnDieline(geometry);
|
|
5343
|
-
const minY = geometry.y - geometry.height / 2;
|
|
5344
|
-
const normY = (lowest.y - minY) / geometry.height;
|
|
5345
|
-
const gap = params.gap || 0;
|
|
5346
|
-
const confineX = params.confineX !== false;
|
|
5347
|
-
const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
|
|
5348
|
-
const newY = normY + (extentY + gap) / dielineHeight;
|
|
5349
|
-
let newX = x;
|
|
5350
|
-
if (confineX) {
|
|
5351
|
-
const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
|
|
5352
|
-
const minX = extentX / dielineWidth;
|
|
5353
|
-
const maxX = 1 - extentX / dielineWidth;
|
|
5354
|
-
newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
|
|
5355
|
-
}
|
|
5356
|
-
return { x: newX, y: newY };
|
|
5357
|
-
};
|
|
5358
|
-
ConstraintRegistry.register("path", pathConstraint);
|
|
5359
|
-
ConstraintRegistry.register("edge", edgeConstraint);
|
|
5360
|
-
ConstraintRegistry.register("internal", internalConstraint);
|
|
5361
|
-
ConstraintRegistry.register("tangent-bottom", tangentBottomConstraint);
|
|
5362
|
-
ConstraintRegistry.register("lowest-tangent", lowestTangentConstraint);
|
|
5363
|
-
|
|
5364
5336
|
// src/extensions/featureComplete.ts
|
|
5365
5337
|
function validateFeaturesStrict(features, context) {
|
|
5366
5338
|
const eps = 1e-6;
|
|
@@ -6136,9 +6108,29 @@ var FeatureTool = class {
|
|
|
6136
6108
|
}
|
|
6137
6109
|
const groups = /* @__PURE__ */ new Map();
|
|
6138
6110
|
const singles = [];
|
|
6139
|
-
|
|
6111
|
+
const placements = resolveFeaturePlacements(
|
|
6112
|
+
this.workingFeatures,
|
|
6113
|
+
{
|
|
6114
|
+
shape: this.currentGeometry.shape,
|
|
6115
|
+
shapeStyle: this.currentGeometry.shapeStyle,
|
|
6116
|
+
pathData: this.currentGeometry.pathData,
|
|
6117
|
+
customSourceWidthPx: this.currentGeometry.customSourceWidthPx,
|
|
6118
|
+
customSourceHeightPx: this.currentGeometry.customSourceHeightPx,
|
|
6119
|
+
x: this.currentGeometry.x,
|
|
6120
|
+
y: this.currentGeometry.y,
|
|
6121
|
+
width: this.currentGeometry.width,
|
|
6122
|
+
height: this.currentGeometry.height,
|
|
6123
|
+
radius: this.currentGeometry.radius,
|
|
6124
|
+
scale: this.currentGeometry.scale || 1
|
|
6125
|
+
}
|
|
6126
|
+
);
|
|
6127
|
+
placements.forEach((placement, index) => {
|
|
6128
|
+
const feature = placement.feature;
|
|
6140
6129
|
const geometry = this.getGeometryForFeature(this.currentGeometry, feature);
|
|
6141
|
-
const position =
|
|
6130
|
+
const position = {
|
|
6131
|
+
x: placement.centerX,
|
|
6132
|
+
y: placement.centerY
|
|
6133
|
+
};
|
|
6142
6134
|
const scale = geometry.scale || 1;
|
|
6143
6135
|
const marker = {
|
|
6144
6136
|
feature,
|
|
@@ -6715,11 +6707,12 @@ var EXTENSION_LINE_LENGTH = 5;
|
|
|
6715
6707
|
var MIN_ARROW_SIZE = 4;
|
|
6716
6708
|
var THICKNESS_TO_STROKE_WIDTH_RATIO = 20;
|
|
6717
6709
|
var DEFAULT_THICKNESS = 20;
|
|
6718
|
-
var DEFAULT_GAP =
|
|
6710
|
+
var DEFAULT_GAP = 65;
|
|
6719
6711
|
var DEFAULT_FONT_SIZE = 10;
|
|
6720
6712
|
var DEFAULT_BACKGROUND_COLOR = "#f0f0f0";
|
|
6721
6713
|
var DEFAULT_TEXT_COLOR = "#333333";
|
|
6722
6714
|
var DEFAULT_LINE_COLOR = "#999999";
|
|
6715
|
+
var RULER_DEBUG_KEY = "ruler.debug";
|
|
6723
6716
|
var RULER_THICKNESS_MIN = 10;
|
|
6724
6717
|
var RULER_THICKNESS_MAX = 100;
|
|
6725
6718
|
var RULER_GAP_MIN = 0;
|
|
@@ -6738,6 +6731,7 @@ var RulerTool = class {
|
|
|
6738
6731
|
this.textColor = DEFAULT_TEXT_COLOR;
|
|
6739
6732
|
this.lineColor = DEFAULT_LINE_COLOR;
|
|
6740
6733
|
this.fontSize = DEFAULT_FONT_SIZE;
|
|
6734
|
+
this.debugEnabled = false;
|
|
6741
6735
|
this.renderSeq = 0;
|
|
6742
6736
|
this.numericProps = /* @__PURE__ */ new Set(["thickness", "gap", "fontSize"]);
|
|
6743
6737
|
this.specs = [];
|
|
@@ -6786,7 +6780,14 @@ var RulerTool = class {
|
|
|
6786
6780
|
this.syncConfig(configService);
|
|
6787
6781
|
configService.onAnyChange((e) => {
|
|
6788
6782
|
let shouldUpdate = false;
|
|
6789
|
-
if (e.key
|
|
6783
|
+
if (e.key === RULER_DEBUG_KEY) {
|
|
6784
|
+
this.debugEnabled = e.value === true;
|
|
6785
|
+
this.log("config:update", {
|
|
6786
|
+
key: e.key,
|
|
6787
|
+
raw: e.value,
|
|
6788
|
+
normalized: this.debugEnabled
|
|
6789
|
+
});
|
|
6790
|
+
} else if (e.key.startsWith("ruler.")) {
|
|
6790
6791
|
const prop = e.key.split(".")[1];
|
|
6791
6792
|
if (prop && prop in this) {
|
|
6792
6793
|
if (this.numericProps.has(prop)) {
|
|
@@ -6873,6 +6874,12 @@ var RulerTool = class {
|
|
|
6873
6874
|
min: RULER_FONT_SIZE_MIN,
|
|
6874
6875
|
max: RULER_FONT_SIZE_MAX,
|
|
6875
6876
|
default: DEFAULT_FONT_SIZE
|
|
6877
|
+
},
|
|
6878
|
+
{
|
|
6879
|
+
id: RULER_DEBUG_KEY,
|
|
6880
|
+
type: "boolean",
|
|
6881
|
+
label: "Ruler Debug Log",
|
|
6882
|
+
default: false
|
|
6876
6883
|
}
|
|
6877
6884
|
],
|
|
6878
6885
|
[ContributionPointIds8.COMMANDS]: [
|
|
@@ -6909,7 +6916,11 @@ var RulerTool = class {
|
|
|
6909
6916
|
]
|
|
6910
6917
|
};
|
|
6911
6918
|
}
|
|
6919
|
+
isDebugEnabled() {
|
|
6920
|
+
return this.debugEnabled;
|
|
6921
|
+
}
|
|
6912
6922
|
log(step, payload) {
|
|
6923
|
+
if (!this.isDebugEnabled()) return;
|
|
6913
6924
|
if (payload) {
|
|
6914
6925
|
console.debug(`[RulerTool] ${step}`, payload);
|
|
6915
6926
|
return;
|
|
@@ -6938,6 +6949,7 @@ var RulerTool = class {
|
|
|
6938
6949
|
configService.get("ruler.fontSize", this.fontSize),
|
|
6939
6950
|
DEFAULT_FONT_SIZE
|
|
6940
6951
|
);
|
|
6952
|
+
this.debugEnabled = configService.get(RULER_DEBUG_KEY, this.debugEnabled) === true;
|
|
6941
6953
|
this.log("config:loaded", {
|
|
6942
6954
|
thickness: this.thickness,
|
|
6943
6955
|
gap: this.gap,
|