@pooder/kit 6.2.1 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1074,6 +1074,7 @@ __export(index_exports, {
1074
1074
  ViewportSystem: () => ViewportSystem,
1075
1075
  WhiteInkTool: () => WhiteInkTool,
1076
1076
  computeImageCoverScale: () => getCoverScale,
1077
+ computeImageOperationUpdates: () => computeImageOperationUpdates,
1077
1078
  computeWhiteInkCoverScale: () => getCoverScale,
1078
1079
  createDefaultDielineState: () => createDefaultDielineState,
1079
1080
  createDielineCommands: () => createDielineCommands,
@@ -1083,7 +1084,9 @@ __export(index_exports, {
1083
1084
  createWhiteInkCommands: () => createWhiteInkCommands,
1084
1085
  createWhiteInkConfigurations: () => createWhiteInkConfigurations,
1085
1086
  evaluateVisibilityExpr: () => evaluateVisibilityExpr,
1086
- readDielineState: () => readDielineState
1087
+ hasAnyImageInViewState: () => hasAnyImageInViewState,
1088
+ readDielineState: () => readDielineState,
1089
+ resolveImageOperationArea: () => resolveImageOperationArea
1087
1090
  });
1088
1091
  module.exports = __toCommonJS(index_exports);
1089
1092
 
@@ -2270,6 +2273,442 @@ var BackgroundTool = class {
2270
2273
  var import_core2 = require("@pooder/core");
2271
2274
  var import_fabric2 = require("fabric");
2272
2275
 
2276
+ // src/shared/scene/frame.ts
2277
+ function emptyFrameRect() {
2278
+ return { left: 0, top: 0, width: 0, height: 0 };
2279
+ }
2280
+ function resolveCutFrameRect(canvasService, configService) {
2281
+ if (!canvasService || !configService) {
2282
+ return emptyFrameRect();
2283
+ }
2284
+ const sizeState = readSizeState(configService);
2285
+ const layout = computeSceneLayout(canvasService, sizeState);
2286
+ if (!layout) {
2287
+ return emptyFrameRect();
2288
+ }
2289
+ return canvasService.toSceneRect({
2290
+ left: layout.cutRect.left,
2291
+ top: layout.cutRect.top,
2292
+ width: layout.cutRect.width,
2293
+ height: layout.cutRect.height
2294
+ });
2295
+ }
2296
+ function toLayoutSceneRect(rect) {
2297
+ return {
2298
+ left: rect.left,
2299
+ top: rect.top,
2300
+ width: rect.width,
2301
+ height: rect.height,
2302
+ space: "scene"
2303
+ };
2304
+ }
2305
+
2306
+ // src/shared/runtime/sessionState.ts
2307
+ function cloneWithJson(value) {
2308
+ return JSON.parse(JSON.stringify(value));
2309
+ }
2310
+ function applyCommittedSnapshot(session, nextCommitted, options) {
2311
+ const clone = options.clone;
2312
+ session.committed = clone(nextCommitted);
2313
+ const shouldPreserveDirtyWorking = options.toolActive && options.preserveDirtyWorking !== false && session.hasWorkingChanges;
2314
+ if (!shouldPreserveDirtyWorking) {
2315
+ session.working = clone(session.committed);
2316
+ session.hasWorkingChanges = false;
2317
+ }
2318
+ }
2319
+ function runDeferredConfigUpdate(state, action, cooldownMs = 0) {
2320
+ state.isUpdatingConfig = true;
2321
+ action();
2322
+ if (cooldownMs <= 0) {
2323
+ state.isUpdatingConfig = false;
2324
+ return;
2325
+ }
2326
+ setTimeout(() => {
2327
+ state.isUpdatingConfig = false;
2328
+ }, cooldownMs);
2329
+ }
2330
+
2331
+ // src/extensions/image/commands.ts
2332
+ function createImageCommands(tool) {
2333
+ return [
2334
+ {
2335
+ command: "addImage",
2336
+ id: "addImage",
2337
+ title: "Add Image",
2338
+ handler: async (url, options) => {
2339
+ const result = await tool.upsertImageEntry(url, {
2340
+ mode: "add",
2341
+ addOptions: options
2342
+ });
2343
+ return result.id;
2344
+ }
2345
+ },
2346
+ {
2347
+ command: "upsertImage",
2348
+ id: "upsertImage",
2349
+ title: "Upsert Image",
2350
+ handler: async (url, options = {}) => {
2351
+ return await tool.upsertImageEntry(url, options);
2352
+ }
2353
+ },
2354
+ {
2355
+ command: "applyImageOperation",
2356
+ id: "applyImageOperation",
2357
+ title: "Apply Image Operation",
2358
+ handler: async (id, operation, options = {}) => {
2359
+ await tool.applyImageOperation(id, operation, options);
2360
+ }
2361
+ },
2362
+ {
2363
+ command: "getImageViewState",
2364
+ id: "getImageViewState",
2365
+ title: "Get Image View State",
2366
+ handler: () => {
2367
+ return tool.getImageViewState();
2368
+ }
2369
+ },
2370
+ {
2371
+ command: "setImageTransform",
2372
+ id: "setImageTransform",
2373
+ title: "Set Image Transform",
2374
+ handler: async (id, updates, options = {}) => {
2375
+ await tool.setImageTransform(id, updates, options);
2376
+ }
2377
+ },
2378
+ {
2379
+ command: "imageSessionReset",
2380
+ id: "imageSessionReset",
2381
+ title: "Reset Image Session",
2382
+ handler: () => {
2383
+ tool.resetImageSession();
2384
+ }
2385
+ },
2386
+ {
2387
+ command: "completeImages",
2388
+ id: "completeImages",
2389
+ title: "Complete Images",
2390
+ handler: async () => {
2391
+ return await tool.commitWorkingImagesAsCropped();
2392
+ }
2393
+ },
2394
+ {
2395
+ command: "exportUserCroppedImage",
2396
+ id: "exportUserCroppedImage",
2397
+ title: "Export User Cropped Image",
2398
+ handler: async (options = {}) => {
2399
+ return await tool.exportUserCroppedImage(options);
2400
+ }
2401
+ },
2402
+ {
2403
+ command: "focusImage",
2404
+ id: "focusImage",
2405
+ title: "Focus Image",
2406
+ handler: (id, options = {}) => {
2407
+ return tool.setImageFocus(id, options);
2408
+ }
2409
+ },
2410
+ {
2411
+ command: "removeImage",
2412
+ id: "removeImage",
2413
+ title: "Remove Image",
2414
+ handler: (id) => {
2415
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
2416
+ const removed = sourceItems.find((item) => item.id === id);
2417
+ const next = sourceItems.filter((item) => item.id !== id);
2418
+ if (next.length !== sourceItems.length) {
2419
+ tool.purgeSourceSizeCacheForItem(removed);
2420
+ if (tool.focusedImageId === id) {
2421
+ tool.setImageFocus(null, {
2422
+ syncCanvasSelection: true,
2423
+ skipRender: true
2424
+ });
2425
+ }
2426
+ if (tool.isToolActive) {
2427
+ tool.workingItems = tool.cloneItems(next);
2428
+ tool.hasWorkingChanges = true;
2429
+ tool.updateImages();
2430
+ tool.emitWorkingChange(id);
2431
+ return;
2432
+ }
2433
+ tool.updateConfig(next);
2434
+ }
2435
+ }
2436
+ },
2437
+ {
2438
+ command: "updateImage",
2439
+ id: "updateImage",
2440
+ title: "Update Image",
2441
+ handler: async (id, updates, options = {}) => {
2442
+ await tool.updateImage(id, updates, options);
2443
+ }
2444
+ },
2445
+ {
2446
+ command: "clearImages",
2447
+ id: "clearImages",
2448
+ title: "Clear Images",
2449
+ handler: () => {
2450
+ tool.sourceSizeCache.clear();
2451
+ tool.setImageFocus(null, {
2452
+ syncCanvasSelection: true,
2453
+ skipRender: true
2454
+ });
2455
+ if (tool.isToolActive) {
2456
+ tool.workingItems = [];
2457
+ tool.hasWorkingChanges = true;
2458
+ tool.updateImages();
2459
+ tool.emitWorkingChange();
2460
+ return;
2461
+ }
2462
+ tool.updateConfig([]);
2463
+ }
2464
+ },
2465
+ {
2466
+ command: "bringToFront",
2467
+ id: "bringToFront",
2468
+ title: "Bring Image to Front",
2469
+ handler: (id) => {
2470
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
2471
+ const index = sourceItems.findIndex((item) => item.id === id);
2472
+ if (index !== -1 && index < sourceItems.length - 1) {
2473
+ const next = [...sourceItems];
2474
+ const [item] = next.splice(index, 1);
2475
+ next.push(item);
2476
+ if (tool.isToolActive) {
2477
+ tool.workingItems = tool.cloneItems(next);
2478
+ tool.hasWorkingChanges = true;
2479
+ tool.updateImages();
2480
+ tool.emitWorkingChange(id);
2481
+ return;
2482
+ }
2483
+ tool.updateConfig(next);
2484
+ }
2485
+ }
2486
+ },
2487
+ {
2488
+ command: "sendToBack",
2489
+ id: "sendToBack",
2490
+ title: "Send Image to Back",
2491
+ handler: (id) => {
2492
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
2493
+ const index = sourceItems.findIndex((item) => item.id === id);
2494
+ if (index > 0) {
2495
+ const next = [...sourceItems];
2496
+ const [item] = next.splice(index, 1);
2497
+ next.unshift(item);
2498
+ if (tool.isToolActive) {
2499
+ tool.workingItems = tool.cloneItems(next);
2500
+ tool.hasWorkingChanges = true;
2501
+ tool.updateImages();
2502
+ tool.emitWorkingChange(id);
2503
+ return;
2504
+ }
2505
+ tool.updateConfig(next);
2506
+ }
2507
+ }
2508
+ }
2509
+ ];
2510
+ }
2511
+
2512
+ // src/extensions/image/config.ts
2513
+ function createImageConfigurations() {
2514
+ return [
2515
+ {
2516
+ id: "image.items",
2517
+ type: "array",
2518
+ label: "Images",
2519
+ default: []
2520
+ },
2521
+ {
2522
+ id: "image.debug",
2523
+ type: "boolean",
2524
+ label: "Image Debug Log",
2525
+ default: false
2526
+ },
2527
+ {
2528
+ id: "image.control.cornerSize",
2529
+ type: "number",
2530
+ label: "Image Control Corner Size",
2531
+ min: 4,
2532
+ max: 64,
2533
+ step: 1,
2534
+ default: 14
2535
+ },
2536
+ {
2537
+ id: "image.control.touchCornerSize",
2538
+ type: "number",
2539
+ label: "Image Control Touch Corner Size",
2540
+ min: 8,
2541
+ max: 96,
2542
+ step: 1,
2543
+ default: 24
2544
+ },
2545
+ {
2546
+ id: "image.control.cornerStyle",
2547
+ type: "select",
2548
+ label: "Image Control Corner Style",
2549
+ options: ["circle", "rect"],
2550
+ default: "circle"
2551
+ },
2552
+ {
2553
+ id: "image.control.cornerColor",
2554
+ type: "color",
2555
+ label: "Image Control Corner Color",
2556
+ default: "#ffffff"
2557
+ },
2558
+ {
2559
+ id: "image.control.cornerStrokeColor",
2560
+ type: "color",
2561
+ label: "Image Control Corner Stroke Color",
2562
+ default: "#1677ff"
2563
+ },
2564
+ {
2565
+ id: "image.control.transparentCorners",
2566
+ type: "boolean",
2567
+ label: "Image Control Transparent Corners",
2568
+ default: false
2569
+ },
2570
+ {
2571
+ id: "image.control.borderColor",
2572
+ type: "color",
2573
+ label: "Image Control Border Color",
2574
+ default: "#1677ff"
2575
+ },
2576
+ {
2577
+ id: "image.control.borderScaleFactor",
2578
+ type: "number",
2579
+ label: "Image Control Border Width",
2580
+ min: 0.5,
2581
+ max: 8,
2582
+ step: 0.1,
2583
+ default: 1.5
2584
+ },
2585
+ {
2586
+ id: "image.control.padding",
2587
+ type: "number",
2588
+ label: "Image Control Padding",
2589
+ min: 0,
2590
+ max: 64,
2591
+ step: 1,
2592
+ default: 0
2593
+ },
2594
+ {
2595
+ id: "image.frame.strokeColor",
2596
+ type: "color",
2597
+ label: "Image Frame Stroke Color",
2598
+ default: "#808080"
2599
+ },
2600
+ {
2601
+ id: "image.frame.strokeWidth",
2602
+ type: "number",
2603
+ label: "Image Frame Stroke Width",
2604
+ min: 0,
2605
+ max: 20,
2606
+ step: 0.5,
2607
+ default: 2
2608
+ },
2609
+ {
2610
+ id: "image.frame.strokeStyle",
2611
+ type: "select",
2612
+ label: "Image Frame Stroke Style",
2613
+ options: ["solid", "dashed", "hidden"],
2614
+ default: "dashed"
2615
+ },
2616
+ {
2617
+ id: "image.frame.dashLength",
2618
+ type: "number",
2619
+ label: "Image Frame Dash Length",
2620
+ min: 1,
2621
+ max: 40,
2622
+ step: 1,
2623
+ default: 8
2624
+ },
2625
+ {
2626
+ id: "image.frame.innerBackground",
2627
+ type: "color",
2628
+ label: "Image Frame Inner Background",
2629
+ default: "rgba(0,0,0,0)"
2630
+ },
2631
+ {
2632
+ id: "image.frame.outerBackground",
2633
+ type: "color",
2634
+ label: "Image Frame Outer Background",
2635
+ default: "#f5f5f5"
2636
+ }
2637
+ ];
2638
+ }
2639
+
2640
+ // src/extensions/image/imageOperations.ts
2641
+ function clampNormalizedAnchor(value) {
2642
+ return Math.max(-1, Math.min(2, value));
2643
+ }
2644
+ function toNormalizedAnchor(center, start, size) {
2645
+ return clampNormalizedAnchor((center - start) / Math.max(1, size));
2646
+ }
2647
+ function resolveAbsoluteScale(operation, area, source) {
2648
+ const widthScale = Math.max(1, area.width) / Math.max(1, source.width);
2649
+ const heightScale = Math.max(1, area.height) / Math.max(1, source.height);
2650
+ switch (operation.type) {
2651
+ case "cover":
2652
+ return Math.max(widthScale, heightScale);
2653
+ case "contain":
2654
+ return Math.min(widthScale, heightScale);
2655
+ case "maximizeWidth":
2656
+ return widthScale;
2657
+ case "maximizeHeight":
2658
+ return heightScale;
2659
+ default:
2660
+ return null;
2661
+ }
2662
+ }
2663
+ function resolveImageOperationArea(args) {
2664
+ const spec = args.area || { type: "frame" };
2665
+ if (spec.type === "custom") {
2666
+ return {
2667
+ width: Math.max(1, spec.width),
2668
+ height: Math.max(1, spec.height),
2669
+ centerX: spec.centerX,
2670
+ centerY: spec.centerY
2671
+ };
2672
+ }
2673
+ if (spec.type === "viewport") {
2674
+ return {
2675
+ width: Math.max(1, args.viewport.width),
2676
+ height: Math.max(1, args.viewport.height),
2677
+ centerX: args.viewport.left + args.viewport.width / 2,
2678
+ centerY: args.viewport.top + args.viewport.height / 2
2679
+ };
2680
+ }
2681
+ return {
2682
+ width: Math.max(1, args.frame.width),
2683
+ height: Math.max(1, args.frame.height),
2684
+ centerX: args.frame.left + args.frame.width / 2,
2685
+ centerY: args.frame.top + args.frame.height / 2
2686
+ };
2687
+ }
2688
+ function computeImageOperationUpdates(args) {
2689
+ const { frame, source, operation, area } = args;
2690
+ if (operation.type === "resetTransform") {
2691
+ return {
2692
+ scale: 1,
2693
+ left: 0.5,
2694
+ top: 0.5,
2695
+ angle: 0
2696
+ };
2697
+ }
2698
+ const left = toNormalizedAnchor(area.centerX, frame.left, frame.width);
2699
+ const top = toNormalizedAnchor(area.centerY, frame.top, frame.height);
2700
+ if (operation.type === "center") {
2701
+ return { left, top };
2702
+ }
2703
+ const absoluteScale = resolveAbsoluteScale(operation, area, source);
2704
+ const coverScale = getCoverScale(frame, source);
2705
+ return {
2706
+ scale: Math.max(0.05, (absoluteScale || coverScale) / coverScale),
2707
+ left,
2708
+ top
2709
+ };
2710
+ }
2711
+
2273
2712
  // src/extensions/geometry.ts
2274
2713
  var import_paper = __toESM(require("paper"));
2275
2714
 
@@ -2847,390 +3286,176 @@ function generateBleedZonePath(originalOptions, offsetOptions, offset) {
2847
3286
  const pathData = bleedZone.pathData;
2848
3287
  shapeOriginal.remove();
2849
3288
  shapeOffset.remove();
2850
- bleedZone.remove();
2851
- return pathData;
2852
- }
2853
- function getLowestPointOnDieline(options) {
2854
- ensurePaper(options.width * 2, options.height * 2);
2855
- import_paper.default.project.activeLayer.removeChildren();
2856
- const shape = createBaseShape(options);
2857
- const bounds = shape.bounds;
2858
- const result = {
2859
- x: bounds.center.x,
2860
- y: bounds.bottom
2861
- };
2862
- shape.remove();
2863
- return result;
2864
- }
2865
- function getNearestPointOnDieline(point, options) {
2866
- ensurePaper(options.width * 2, options.height * 2);
2867
- import_paper.default.project.activeLayer.removeChildren();
2868
- const shape = createBaseShape(options);
2869
- const p = new import_paper.default.Point(point.x, point.y);
2870
- const location = shape.getNearestLocation(p);
2871
- const result = {
2872
- x: location.point.x,
2873
- y: location.point.y,
2874
- normal: location.normal ? { x: location.normal.x, y: location.normal.y } : void 0
2875
- };
2876
- shape.remove();
2877
- return result;
2878
- }
2879
- function getPathBounds(pathData) {
2880
- const path = new import_paper.default.Path();
2881
- path.pathData = pathData;
2882
- const bounds = path.bounds;
2883
- path.remove();
2884
- return {
2885
- x: bounds.x,
2886
- y: bounds.y,
2887
- width: bounds.width,
2888
- height: bounds.height
2889
- };
2890
- }
2891
-
2892
- // src/shared/scene/frame.ts
2893
- function emptyFrameRect() {
2894
- return { left: 0, top: 0, width: 0, height: 0 };
2895
- }
2896
- function resolveCutFrameRect(canvasService, configService) {
2897
- if (!canvasService || !configService) {
2898
- return emptyFrameRect();
2899
- }
2900
- const sizeState = readSizeState(configService);
2901
- const layout = computeSceneLayout(canvasService, sizeState);
2902
- if (!layout) {
2903
- return emptyFrameRect();
2904
- }
2905
- return canvasService.toSceneRect({
2906
- left: layout.cutRect.left,
2907
- top: layout.cutRect.top,
2908
- width: layout.cutRect.width,
2909
- height: layout.cutRect.height
2910
- });
2911
- }
2912
- function toLayoutSceneRect(rect) {
2913
- return {
2914
- left: rect.left,
2915
- top: rect.top,
2916
- width: rect.width,
2917
- height: rect.height,
2918
- space: "scene"
2919
- };
2920
- }
2921
-
2922
- // src/shared/runtime/sessionState.ts
2923
- function cloneWithJson(value) {
2924
- return JSON.parse(JSON.stringify(value));
2925
- }
2926
- function applyCommittedSnapshot(session, nextCommitted, options) {
2927
- const clone = options.clone;
2928
- session.committed = clone(nextCommitted);
2929
- const shouldPreserveDirtyWorking = options.toolActive && options.preserveDirtyWorking !== false && session.hasWorkingChanges;
2930
- if (!shouldPreserveDirtyWorking) {
2931
- session.working = clone(session.committed);
2932
- session.hasWorkingChanges = false;
2933
- }
2934
- }
2935
- function runDeferredConfigUpdate(state, action, cooldownMs = 0) {
2936
- state.isUpdatingConfig = true;
2937
- action();
2938
- if (cooldownMs <= 0) {
2939
- state.isUpdatingConfig = false;
2940
- return;
2941
- }
2942
- setTimeout(() => {
2943
- state.isUpdatingConfig = false;
2944
- }, cooldownMs);
2945
- }
2946
-
2947
- // src/extensions/image/commands.ts
2948
- function createImageCommands(tool) {
2949
- return [
2950
- {
2951
- command: "addImage",
2952
- id: "addImage",
2953
- title: "Add Image",
2954
- handler: async (url, options) => {
2955
- const result = await tool.upsertImageEntry(url, {
2956
- mode: "add",
2957
- addOptions: options
2958
- });
2959
- return result.id;
2960
- }
2961
- },
2962
- {
2963
- command: "upsertImage",
2964
- id: "upsertImage",
2965
- title: "Upsert Image",
2966
- handler: async (url, options = {}) => {
2967
- return await tool.upsertImageEntry(url, options);
2968
- }
2969
- },
2970
- {
2971
- command: "getWorkingImages",
2972
- id: "getWorkingImages",
2973
- title: "Get Working Images",
2974
- handler: () => {
2975
- return tool.cloneItems(tool.workingItems);
2976
- }
2977
- },
2978
- {
2979
- command: "setWorkingImage",
2980
- id: "setWorkingImage",
2981
- title: "Set Working Image",
2982
- handler: (id, updates) => {
2983
- tool.updateImageInWorking(id, updates);
2984
- }
2985
- },
2986
- {
2987
- command: "resetWorkingImages",
2988
- id: "resetWorkingImages",
2989
- title: "Reset Working Images",
2990
- handler: () => {
2991
- tool.workingItems = tool.cloneItems(tool.items);
2992
- tool.hasWorkingChanges = false;
2993
- tool.updateImages();
2994
- tool.emitWorkingChange();
2995
- }
2996
- },
2997
- {
2998
- command: "completeImages",
2999
- id: "completeImages",
3000
- title: "Complete Images",
3001
- handler: async () => {
3002
- return await tool.commitWorkingImagesAsCropped();
3003
- }
3004
- },
3005
- {
3006
- command: "exportUserCroppedImage",
3007
- id: "exportUserCroppedImage",
3008
- title: "Export User Cropped Image",
3009
- handler: async (options = {}) => {
3010
- return await tool.exportUserCroppedImage(options);
3011
- }
3012
- },
3013
- {
3014
- command: "fitImageToArea",
3015
- id: "fitImageToArea",
3016
- title: "Fit Image to Area",
3017
- handler: async (id, area) => {
3018
- await tool.fitImageToArea(id, area);
3019
- }
3020
- },
3021
- {
3022
- command: "fitImageToDefaultArea",
3023
- id: "fitImageToDefaultArea",
3024
- title: "Fit Image to Default Area",
3025
- handler: async (id) => {
3026
- await tool.fitImageToDefaultArea(id);
3027
- }
3028
- },
3029
- {
3030
- command: "focusImage",
3031
- id: "focusImage",
3032
- title: "Focus Image",
3033
- handler: (id, options = {}) => {
3034
- return tool.setImageFocus(id, options);
3035
- }
3036
- },
3037
- {
3038
- command: "removeImage",
3039
- id: "removeImage",
3040
- title: "Remove Image",
3041
- handler: (id) => {
3042
- const removed = tool.items.find((item) => item.id === id);
3043
- const next = tool.items.filter((item) => item.id !== id);
3044
- if (next.length !== tool.items.length) {
3045
- tool.purgeSourceSizeCacheForItem(removed);
3046
- if (tool.focusedImageId === id) {
3047
- tool.setImageFocus(null, {
3048
- syncCanvasSelection: true,
3049
- skipRender: true
3050
- });
3051
- }
3052
- tool.updateConfig(next);
3053
- }
3054
- }
3055
- },
3056
- {
3057
- command: "updateImage",
3058
- id: "updateImage",
3059
- title: "Update Image",
3060
- handler: async (id, updates, options = {}) => {
3061
- await tool.updateImage(id, updates, options);
3062
- }
3063
- },
3064
- {
3065
- command: "clearImages",
3066
- id: "clearImages",
3067
- title: "Clear Images",
3068
- handler: () => {
3069
- tool.sourceSizeCache.clear();
3070
- tool.setImageFocus(null, {
3071
- syncCanvasSelection: true,
3072
- skipRender: true
3073
- });
3074
- tool.updateConfig([]);
3075
- }
3076
- },
3077
- {
3078
- command: "bringToFront",
3079
- id: "bringToFront",
3080
- title: "Bring Image to Front",
3081
- handler: (id) => {
3082
- const index = tool.items.findIndex((item) => item.id === id);
3083
- if (index !== -1 && index < tool.items.length - 1) {
3084
- const next = [...tool.items];
3085
- const [item] = next.splice(index, 1);
3086
- next.push(item);
3087
- tool.updateConfig(next);
3088
- }
3089
- }
3090
- },
3091
- {
3092
- command: "sendToBack",
3093
- id: "sendToBack",
3094
- title: "Send Image to Back",
3095
- handler: (id) => {
3096
- const index = tool.items.findIndex((item) => item.id === id);
3097
- if (index > 0) {
3098
- const next = [...tool.items];
3099
- const [item] = next.splice(index, 1);
3100
- next.unshift(item);
3101
- tool.updateConfig(next);
3102
- }
3103
- }
3104
- }
3105
- ];
3289
+ bleedZone.remove();
3290
+ return pathData;
3291
+ }
3292
+ function getLowestPointOnDieline(options) {
3293
+ ensurePaper(options.width * 2, options.height * 2);
3294
+ import_paper.default.project.activeLayer.removeChildren();
3295
+ const shape = createBaseShape(options);
3296
+ const bounds = shape.bounds;
3297
+ const result = {
3298
+ x: bounds.center.x,
3299
+ y: bounds.bottom
3300
+ };
3301
+ shape.remove();
3302
+ return result;
3303
+ }
3304
+ function getNearestPointOnDieline(point, options) {
3305
+ ensurePaper(options.width * 2, options.height * 2);
3306
+ import_paper.default.project.activeLayer.removeChildren();
3307
+ const shape = createBaseShape(options);
3308
+ const p = new import_paper.default.Point(point.x, point.y);
3309
+ const location = shape.getNearestLocation(p);
3310
+ const result = {
3311
+ x: location.point.x,
3312
+ y: location.point.y,
3313
+ normal: location.normal ? { x: location.normal.x, y: location.normal.y } : void 0
3314
+ };
3315
+ shape.remove();
3316
+ return result;
3106
3317
  }
3107
3318
 
3108
- // src/extensions/image/config.ts
3109
- function createImageConfigurations() {
3319
+ // src/extensions/image/sessionOverlay.ts
3320
+ var EPSILON = 1e-4;
3321
+ var SHAPE_OUTLINE_COLOR = "rgba(255, 0, 0, 0.9)";
3322
+ var DEFAULT_HATCH_FILL = "rgba(255, 0, 0, 0.22)";
3323
+ function buildRectPath(width, height) {
3324
+ return `M 0 0 L ${width} 0 L ${width} ${height} L 0 ${height} Z`;
3325
+ }
3326
+ function buildViewportMaskPath(viewport, cutRect) {
3327
+ const cutLeft = cutRect.left - viewport.left;
3328
+ const cutTop = cutRect.top - viewport.top;
3110
3329
  return [
3111
- {
3112
- id: "image.items",
3113
- type: "array",
3114
- label: "Images",
3115
- default: []
3116
- },
3117
- {
3118
- id: "image.debug",
3119
- type: "boolean",
3120
- label: "Image Debug Log",
3121
- default: false
3122
- },
3123
- {
3124
- id: "image.control.cornerSize",
3125
- type: "number",
3126
- label: "Image Control Corner Size",
3127
- min: 4,
3128
- max: 64,
3129
- step: 1,
3130
- default: 14
3131
- },
3132
- {
3133
- id: "image.control.touchCornerSize",
3134
- type: "number",
3135
- label: "Image Control Touch Corner Size",
3136
- min: 8,
3137
- max: 96,
3138
- step: 1,
3139
- default: 24
3140
- },
3141
- {
3142
- id: "image.control.cornerStyle",
3143
- type: "select",
3144
- label: "Image Control Corner Style",
3145
- options: ["circle", "rect"],
3146
- default: "circle"
3147
- },
3148
- {
3149
- id: "image.control.cornerColor",
3150
- type: "color",
3151
- label: "Image Control Corner Color",
3152
- default: "#ffffff"
3153
- },
3154
- {
3155
- id: "image.control.cornerStrokeColor",
3156
- type: "color",
3157
- label: "Image Control Corner Stroke Color",
3158
- default: "#1677ff"
3159
- },
3160
- {
3161
- id: "image.control.transparentCorners",
3162
- type: "boolean",
3163
- label: "Image Control Transparent Corners",
3164
- default: false
3165
- },
3166
- {
3167
- id: "image.control.borderColor",
3168
- type: "color",
3169
- label: "Image Control Border Color",
3170
- default: "#1677ff"
3171
- },
3172
- {
3173
- id: "image.control.borderScaleFactor",
3174
- type: "number",
3175
- label: "Image Control Border Width",
3176
- min: 0.5,
3177
- max: 8,
3178
- step: 0.1,
3179
- default: 1.5
3180
- },
3181
- {
3182
- id: "image.control.padding",
3183
- type: "number",
3184
- label: "Image Control Padding",
3185
- min: 0,
3186
- max: 64,
3187
- step: 1,
3188
- default: 0
3189
- },
3190
- {
3191
- id: "image.frame.strokeColor",
3192
- type: "color",
3193
- label: "Image Frame Stroke Color",
3194
- default: "#808080"
3195
- },
3196
- {
3197
- id: "image.frame.strokeWidth",
3198
- type: "number",
3199
- label: "Image Frame Stroke Width",
3200
- min: 0,
3201
- max: 20,
3202
- step: 0.5,
3203
- default: 2
3204
- },
3205
- {
3206
- id: "image.frame.strokeStyle",
3207
- type: "select",
3208
- label: "Image Frame Stroke Style",
3209
- options: ["solid", "dashed", "hidden"],
3210
- default: "dashed"
3211
- },
3212
- {
3213
- id: "image.frame.dashLength",
3214
- type: "number",
3215
- label: "Image Frame Dash Length",
3216
- min: 1,
3217
- max: 40,
3218
- step: 1,
3219
- default: 8
3220
- },
3221
- {
3222
- id: "image.frame.innerBackground",
3223
- type: "color",
3224
- label: "Image Frame Inner Background",
3225
- default: "rgba(0,0,0,0)"
3226
- },
3227
- {
3228
- id: "image.frame.outerBackground",
3229
- type: "color",
3230
- label: "Image Frame Outer Background",
3231
- default: "#f5f5f5"
3330
+ buildRectPath(viewport.width, viewport.height),
3331
+ `M ${cutLeft} ${cutTop} L ${cutLeft + cutRect.width} ${cutTop} L ${cutLeft + cutRect.width} ${cutTop + cutRect.height} L ${cutLeft} ${cutTop + cutRect.height} Z`
3332
+ ].join(" ");
3333
+ }
3334
+ function resolveCutShapeRadiusPx(geometry, cutRect) {
3335
+ const visualRadius = Number.isFinite(geometry.radius) ? Math.max(0, geometry.radius) : 0;
3336
+ const visualOffset = Number.isFinite(geometry.offset) ? geometry.offset : 0;
3337
+ const rawCutRadius = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
3338
+ const maxRadius = Math.max(0, Math.min(cutRect.width, cutRect.height) / 2);
3339
+ return Math.max(0, Math.min(maxRadius, rawCutRadius));
3340
+ }
3341
+ function buildBuiltinShapeOverlayPaths(cutRect, geometry) {
3342
+ if (!geometry || geometry.shape === "custom") {
3343
+ return null;
3344
+ }
3345
+ const radius = resolveCutShapeRadiusPx(geometry, cutRect);
3346
+ if (geometry.shape === "rect" && radius <= EPSILON) {
3347
+ return null;
3348
+ }
3349
+ const shapePathData = generateDielinePath({
3350
+ shape: geometry.shape,
3351
+ shapeStyle: geometry.shapeStyle,
3352
+ width: Math.max(1, cutRect.width),
3353
+ height: Math.max(1, cutRect.height),
3354
+ radius,
3355
+ x: cutRect.width / 2,
3356
+ y: cutRect.height / 2,
3357
+ features: [],
3358
+ canvasWidth: Math.max(1, cutRect.width),
3359
+ canvasHeight: Math.max(1, cutRect.height)
3360
+ });
3361
+ if (!shapePathData) {
3362
+ return null;
3363
+ }
3364
+ return {
3365
+ shapePathData,
3366
+ hatchPathData: `${buildRectPath(cutRect.width, cutRect.height)} ${shapePathData}`
3367
+ };
3368
+ }
3369
+ function buildImageSessionOverlaySpecs(args) {
3370
+ const { viewport, layout, geometry, visual, hatchPattern } = args;
3371
+ const cutRect = layout.cutRect;
3372
+ const specs = [];
3373
+ specs.push({
3374
+ id: "image.cropMask.rect",
3375
+ type: "path",
3376
+ space: "screen",
3377
+ data: { id: "image.cropMask.rect", zIndex: 1 },
3378
+ props: {
3379
+ pathData: buildViewportMaskPath(viewport, cutRect),
3380
+ left: viewport.left,
3381
+ top: viewport.top,
3382
+ originX: "left",
3383
+ originY: "top",
3384
+ fill: visual.outerBackground,
3385
+ stroke: null,
3386
+ fillRule: "evenodd",
3387
+ selectable: false,
3388
+ evented: false,
3389
+ excludeFromExport: true,
3390
+ objectCaching: false
3232
3391
  }
3233
- ];
3392
+ });
3393
+ const shapeOverlay = buildBuiltinShapeOverlayPaths(cutRect, geometry);
3394
+ if (shapeOverlay) {
3395
+ specs.push({
3396
+ id: "image.cropShapeHatch",
3397
+ type: "path",
3398
+ space: "screen",
3399
+ data: { id: "image.cropShapeHatch", zIndex: 5 },
3400
+ props: {
3401
+ pathData: shapeOverlay.hatchPathData,
3402
+ left: cutRect.left,
3403
+ top: cutRect.top,
3404
+ originX: "left",
3405
+ originY: "top",
3406
+ fill: hatchPattern || DEFAULT_HATCH_FILL,
3407
+ opacity: hatchPattern ? 1 : 0.8,
3408
+ stroke: null,
3409
+ fillRule: "evenodd",
3410
+ selectable: false,
3411
+ evented: false,
3412
+ excludeFromExport: true,
3413
+ objectCaching: false
3414
+ }
3415
+ });
3416
+ specs.push({
3417
+ id: "image.cropShapeOutline",
3418
+ type: "path",
3419
+ space: "screen",
3420
+ data: { id: "image.cropShapeOutline", zIndex: 6 },
3421
+ props: {
3422
+ pathData: shapeOverlay.shapePathData,
3423
+ left: cutRect.left,
3424
+ top: cutRect.top,
3425
+ originX: "left",
3426
+ originY: "top",
3427
+ fill: "transparent",
3428
+ stroke: SHAPE_OUTLINE_COLOR,
3429
+ strokeWidth: 1,
3430
+ selectable: false,
3431
+ evented: false,
3432
+ excludeFromExport: true,
3433
+ objectCaching: false
3434
+ }
3435
+ });
3436
+ }
3437
+ specs.push({
3438
+ id: "image.cropFrame",
3439
+ type: "rect",
3440
+ space: "screen",
3441
+ data: { id: "image.cropFrame", zIndex: 7 },
3442
+ props: {
3443
+ left: cutRect.left,
3444
+ top: cutRect.top,
3445
+ width: cutRect.width,
3446
+ height: cutRect.height,
3447
+ originX: "left",
3448
+ originY: "top",
3449
+ fill: visual.innerBackground,
3450
+ stroke: visual.strokeStyle === "hidden" ? "rgba(0,0,0,0)" : visual.strokeColor,
3451
+ strokeWidth: visual.strokeStyle === "hidden" ? 0 : visual.strokeWidth,
3452
+ strokeDashArray: visual.strokeStyle === "dashed" ? [visual.dashLength, visual.dashLength] : void 0,
3453
+ selectable: false,
3454
+ evented: false,
3455
+ excludeFromExport: true
3456
+ }
3457
+ });
3458
+ return specs;
3234
3459
  }
3235
3460
 
3236
3461
  // src/extensions/image/ImageTool.ts
@@ -3518,6 +3743,7 @@ var ImageTool = class {
3518
3743
  this.clearRenderedImages();
3519
3744
  (_b = this.renderProducerDisposable) == null ? void 0 : _b.dispose();
3520
3745
  this.renderProducerDisposable = void 0;
3746
+ this.emitImageStateChange();
3521
3747
  if (this.canvasService) {
3522
3748
  void this.canvasService.flushRenderFromProducers();
3523
3749
  this.canvasService = void 0;
@@ -3692,11 +3918,21 @@ var ImageTool = class {
3692
3918
  (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
3693
3919
  }
3694
3920
  }
3921
+ clearSnapGuideContext() {
3922
+ var _a;
3923
+ const topContext = (_a = this.canvasService) == null ? void 0 : _a.canvas.contextTop;
3924
+ if (!this.canvasService || !topContext) return;
3925
+ this.canvasService.canvas.clearContext(topContext);
3926
+ }
3695
3927
  clearSnapPreview() {
3696
3928
  var _a;
3929
+ const shouldClearCanvas = this.hasRenderedSnapGuides || !!this.activeSnapX || !!this.activeSnapY;
3697
3930
  this.activeSnapX = null;
3698
3931
  this.activeSnapY = null;
3699
3932
  this.hasRenderedSnapGuides = false;
3933
+ if (shouldClearCanvas) {
3934
+ this.clearSnapGuideContext();
3935
+ }
3700
3936
  (_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
3701
3937
  }
3702
3938
  endMoveSnapInteraction() {
@@ -3917,9 +4153,9 @@ var ImageTool = class {
3917
4153
  name: "Image",
3918
4154
  interaction: "session",
3919
4155
  commands: {
3920
- begin: "resetWorkingImages",
4156
+ begin: "imageSessionReset",
3921
4157
  commit: "completeImages",
3922
- rollback: "resetWorkingImages"
4158
+ rollback: "imageSessionReset"
3923
4159
  },
3924
4160
  session: {
3925
4161
  autoBegin: true,
@@ -3953,6 +4189,28 @@ var ImageTool = class {
3953
4189
  cloneItems(items) {
3954
4190
  return this.normalizeItems((items || []).map((i) => ({ ...i })));
3955
4191
  }
4192
+ getViewItems() {
4193
+ return this.isToolActive ? this.workingItems : this.items;
4194
+ }
4195
+ getImageViewState() {
4196
+ this.syncToolActiveFromWorkbench();
4197
+ const items = this.cloneItems(this.getViewItems());
4198
+ const focusedItem = this.focusedImageId == null ? null : items.find((item) => item.id === this.focusedImageId) || null;
4199
+ return {
4200
+ items,
4201
+ hasAnyImage: items.length > 0,
4202
+ focusedId: this.focusedImageId,
4203
+ focusedItem,
4204
+ isToolActive: this.isToolActive,
4205
+ isImageSelectionActive: this.isImageSelectionActive,
4206
+ hasWorkingChanges: this.hasWorkingChanges,
4207
+ source: this.isToolActive ? "working" : "committed"
4208
+ };
4209
+ }
4210
+ emitImageStateChange() {
4211
+ var _a;
4212
+ (_a = this.context) == null ? void 0 : _a.eventBus.emit("image:state:change", this.getImageViewState());
4213
+ }
3956
4214
  emitWorkingChange(changedId = null) {
3957
4215
  var _a;
3958
4216
  (_a = this.context) == null ? void 0 : _a.eventBus.emit("image:working:change", {
@@ -3988,10 +4246,13 @@ var ImageTool = class {
3988
4246
  }
3989
4247
  if (!options.skipRender) {
3990
4248
  this.updateImages();
4249
+ } else {
4250
+ this.emitImageStateChange();
3991
4251
  }
3992
4252
  return { ok: true, id };
3993
4253
  }
3994
- async addImageEntry(url, options, fitOnAdd = true) {
4254
+ async addImageEntry(url, options, operation) {
4255
+ this.syncToolActiveFromWorkbench();
3995
4256
  const id = this.generateId();
3996
4257
  const newItem = this.normalizeItem({
3997
4258
  id,
@@ -3999,13 +4260,20 @@ var ImageTool = class {
3999
4260
  opacity: 1,
4000
4261
  ...options
4001
4262
  });
4002
- const sessionDirtyBeforeAdd = this.isToolActive && this.hasWorkingChanges;
4003
4263
  const waitLoaded = this.waitImageLoaded(id, true);
4004
- this.updateConfig([...this.items, newItem]);
4005
- this.addItemToWorkingSessionIfNeeded(newItem, sessionDirtyBeforeAdd);
4264
+ if (this.isToolActive) {
4265
+ this.workingItems = this.cloneItems([...this.workingItems, newItem]);
4266
+ this.hasWorkingChanges = true;
4267
+ this.updateImages();
4268
+ this.emitWorkingChange(id);
4269
+ } else {
4270
+ this.updateConfig([...this.items, newItem]);
4271
+ }
4006
4272
  const loaded = await waitLoaded;
4007
- if (loaded && fitOnAdd) {
4008
- await this.fitImageToDefaultArea(id);
4273
+ if (loaded && operation) {
4274
+ await this.applyImageOperation(id, operation, {
4275
+ target: this.isToolActive ? "working" : "config"
4276
+ });
4009
4277
  }
4010
4278
  if (loaded) {
4011
4279
  this.setImageFocus(id);
@@ -4013,8 +4281,8 @@ var ImageTool = class {
4013
4281
  return id;
4014
4282
  }
4015
4283
  async upsertImageEntry(url, options = {}) {
4284
+ this.syncToolActiveFromWorkbench();
4016
4285
  const mode = options.mode || (options.id ? "replace" : "add");
4017
- const fitOnAdd = options.fitOnAdd !== false;
4018
4286
  if (mode === "replace") {
4019
4287
  if (!options.id) {
4020
4288
  throw new Error("replace-target-id-required");
@@ -4023,19 +4291,31 @@ var ImageTool = class {
4023
4291
  if (!this.hasImageItem(targetId)) {
4024
4292
  throw new Error("replace-target-not-found");
4025
4293
  }
4026
- await this.updateImageInConfig(targetId, { url });
4294
+ if (this.isToolActive) {
4295
+ const current = this.workingItems.find((item) => item.id === targetId) || this.items.find((item) => item.id === targetId);
4296
+ this.purgeSourceSizeCacheForItem(current);
4297
+ this.updateImageInWorking(targetId, {
4298
+ url,
4299
+ sourceUrl: url,
4300
+ committedUrl: void 0
4301
+ });
4302
+ } else {
4303
+ await this.updateImageInConfig(targetId, { url });
4304
+ }
4305
+ const loaded = await this.waitImageLoaded(targetId, true);
4306
+ if (loaded && options.operation) {
4307
+ await this.applyImageOperation(targetId, options.operation, {
4308
+ target: this.isToolActive ? "working" : "config"
4309
+ });
4310
+ }
4311
+ if (loaded) {
4312
+ this.setImageFocus(targetId);
4313
+ }
4027
4314
  return { id: targetId, mode: "replace" };
4028
4315
  }
4029
- const id = await this.addImageEntry(url, options.addOptions, fitOnAdd);
4316
+ const id = await this.addImageEntry(url, options.addOptions, options.operation);
4030
4317
  return { id, mode: "add" };
4031
4318
  }
4032
- addItemToWorkingSessionIfNeeded(item, sessionDirtyBeforeAdd) {
4033
- if (!sessionDirtyBeforeAdd || !this.isToolActive) return;
4034
- if (this.workingItems.some((existing) => existing.id === item.id)) return;
4035
- this.workingItems = this.cloneItems([...this.workingItems, item]);
4036
- this.updateImages();
4037
- this.emitWorkingChange(item.id);
4038
- }
4039
4319
  async updateImage(id, updates, options = {}) {
4040
4320
  this.syncToolActiveFromWorkbench();
4041
4321
  const target = options.target || "auto";
@@ -4095,41 +4375,10 @@ var ImageTool = class {
4095
4375
  return resolveCutFrameRect(this.canvasService, configService);
4096
4376
  }
4097
4377
  getFrameRectScreen(frame) {
4098
- if (!this.canvasService) {
4099
- return { left: 0, top: 0, width: 0, height: 0 };
4100
- }
4101
- return this.canvasService.toScreenRect(frame || this.getFrameRect());
4102
- }
4103
- toLayoutSceneRect(rect) {
4104
- return toLayoutSceneRect(rect);
4105
- }
4106
- async resolveDefaultFitArea() {
4107
- if (!this.canvasService) return null;
4108
- const frame = this.getFrameRect();
4109
- if (frame.width <= 0 || frame.height <= 0) return null;
4110
- return {
4111
- width: Math.max(1, frame.width),
4112
- height: Math.max(1, frame.height),
4113
- left: frame.left + frame.width / 2,
4114
- top: frame.top + frame.height / 2
4115
- };
4116
- }
4117
- async fitImageToDefaultArea(id) {
4118
- if (!this.canvasService) return;
4119
- const area = await this.resolveDefaultFitArea();
4120
- if (area) {
4121
- await this.fitImageToArea(id, area);
4122
- return;
4378
+ if (!this.canvasService) {
4379
+ return { left: 0, top: 0, width: 0, height: 0 };
4123
4380
  }
4124
- const viewport = this.canvasService.getSceneViewportRect();
4125
- const canvasW = Math.max(1, viewport.width || 0);
4126
- const canvasH = Math.max(1, viewport.height || 0);
4127
- await this.fitImageToArea(id, {
4128
- width: canvasW,
4129
- height: canvasH,
4130
- left: viewport.left + canvasW / 2,
4131
- top: viewport.top + canvasH / 2
4132
- });
4381
+ return this.canvasService.toScreenRect(frame || this.getFrameRect());
4133
4382
  }
4134
4383
  getImageObjects() {
4135
4384
  if (!this.canvasService) return [];
@@ -4229,74 +4478,37 @@ var ImageTool = class {
4229
4478
  outerBackground: this.getConfig("image.frame.outerBackground", "#f5f5f5") || "#f5f5f5"
4230
4479
  };
4231
4480
  }
4232
- toSceneGeometryLike(raw) {
4233
- const shape = raw == null ? void 0 : raw.shape;
4234
- if (!isDielineShape(shape)) {
4481
+ resolveSessionOverlayState() {
4482
+ if (!this.canvasService || !this.context) {
4235
4483
  return null;
4236
4484
  }
4237
- const radiusRaw = Number(raw == null ? void 0 : raw.radius);
4238
- const offsetRaw = Number(raw == null ? void 0 : raw.offset);
4239
- const unit = typeof (raw == null ? void 0 : raw.unit) === "string" ? raw.unit : "px";
4240
- const radius = unit === "scene" || !this.canvasService ? radiusRaw : this.canvasService.toSceneLength(radiusRaw);
4241
- const offset = unit === "scene" || !this.canvasService ? offsetRaw : this.canvasService.toSceneLength(offsetRaw);
4242
- return {
4243
- shape,
4244
- shapeStyle: normalizeShapeStyle(raw == null ? void 0 : raw.shapeStyle),
4245
- radius: Number.isFinite(radius) ? radius : 0,
4246
- offset: Number.isFinite(offset) ? offset : 0
4247
- };
4248
- }
4249
- async resolveSceneGeometryForOverlay() {
4250
- if (!this.context) return null;
4251
- const commandService = this.context.services.get("CommandService");
4252
- if (commandService) {
4253
- try {
4254
- const raw = await Promise.resolve(
4255
- commandService.executeCommand("getSceneGeometry")
4256
- );
4257
- const geometry2 = this.toSceneGeometryLike(raw);
4258
- if (geometry2) {
4259
- this.debug("overlay:sceneGeometry:command", geometry2);
4260
- return geometry2;
4261
- }
4262
- this.debug("overlay:sceneGeometry:command:invalid", { raw });
4263
- } catch (error) {
4264
- this.debug("overlay:sceneGeometry:command:error", {
4265
- error: error instanceof Error ? error.message : String(error)
4266
- });
4267
- }
4268
- }
4269
- if (!this.canvasService) return null;
4270
4485
  const configService = this.context.services.get(
4271
4486
  "ConfigurationService"
4272
4487
  );
4273
- if (!configService) return null;
4274
- const sizeState = readSizeState(configService);
4275
- const layout = computeSceneLayout(this.canvasService, sizeState);
4276
- if (!layout) {
4277
- this.debug("overlay:sceneGeometry:fallback:missing-layout");
4488
+ if (!configService) {
4278
4489
  return null;
4279
4490
  }
4280
- const geometry = this.toSceneGeometryLike(
4281
- buildSceneGeometry(configService, layout)
4491
+ const layout = computeSceneLayout(
4492
+ this.canvasService,
4493
+ readSizeState(configService)
4282
4494
  );
4283
- if (geometry) {
4284
- this.debug("overlay:sceneGeometry:fallback", geometry);
4495
+ if (!layout) {
4496
+ this.debug("overlay:layout:missing");
4497
+ return null;
4285
4498
  }
4286
- return geometry;
4287
- }
4288
- resolveCutShapeRadius(geometry, frame) {
4289
- const visualRadius = Number.isFinite(geometry.radius) ? Math.max(0, geometry.radius) : 0;
4290
- const visualOffset = Number.isFinite(geometry.offset) ? geometry.offset : 0;
4291
- const rawCutRadius = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
4292
- const maxRadius = Math.max(0, Math.min(frame.width, frame.height) / 2);
4293
- return Math.max(0, Math.min(maxRadius, rawCutRadius));
4499
+ const geometry = buildSceneGeometry(configService, layout);
4500
+ this.debug("overlay:state:resolved", {
4501
+ cutRect: layout.cutRect,
4502
+ shape: geometry.shape,
4503
+ shapeStyle: geometry.shapeStyle,
4504
+ radius: geometry.radius,
4505
+ offset: geometry.offset
4506
+ });
4507
+ return { layout, geometry };
4294
4508
  }
4295
4509
  getCropShapeHatchPattern(color = "rgba(255, 0, 0, 0.6)") {
4296
- var _a;
4297
4510
  if (typeof document === "undefined") return void 0;
4298
- const sceneScale = ((_a = this.canvasService) == null ? void 0 : _a.getSceneScale()) || 1;
4299
- const cacheKey = `${color}::${sceneScale.toFixed(6)}`;
4511
+ const cacheKey = color;
4300
4512
  if (this.cropShapeHatchPattern && this.cropShapeHatchPatternColor === color && this.cropShapeHatchPatternKey === cacheKey) {
4301
4513
  return this.cropShapeHatchPattern;
4302
4514
  }
@@ -4326,138 +4538,11 @@ var ImageTool = class {
4326
4538
  // @ts-ignore: Fabric Pattern accepts canvas source here.
4327
4539
  repetition: "repeat"
4328
4540
  });
4329
- pattern.patternTransform = [
4330
- 1 / sceneScale,
4331
- 0,
4332
- 0,
4333
- 1 / sceneScale,
4334
- 0,
4335
- 0
4336
- ];
4337
4541
  this.cropShapeHatchPattern = pattern;
4338
4542
  this.cropShapeHatchPatternColor = color;
4339
4543
  this.cropShapeHatchPatternKey = cacheKey;
4340
4544
  return pattern;
4341
4545
  }
4342
- buildCropShapeOverlaySpecs(frame, sceneGeometry) {
4343
- var _a, _b;
4344
- if (!sceneGeometry) {
4345
- this.debug("overlay:shape:skip", { reason: "scene-geometry-missing" });
4346
- return [];
4347
- }
4348
- if (sceneGeometry.shape === "custom") {
4349
- this.debug("overlay:shape:skip", { reason: "shape-custom" });
4350
- return [];
4351
- }
4352
- const shape = sceneGeometry.shape;
4353
- const shapeStyle = sceneGeometry.shapeStyle;
4354
- const inset = 0;
4355
- const shapeWidth = Math.max(1, frame.width);
4356
- const shapeHeight = Math.max(1, frame.height);
4357
- const radius = this.resolveCutShapeRadius(sceneGeometry, frame);
4358
- this.debug("overlay:shape:geometry", {
4359
- shape,
4360
- frameWidth: frame.width,
4361
- frameHeight: frame.height,
4362
- offset: sceneGeometry.offset,
4363
- shapeStyle,
4364
- inset,
4365
- shapeWidth,
4366
- shapeHeight,
4367
- baseRadius: sceneGeometry.radius,
4368
- radius
4369
- });
4370
- const isSameAsFrame = Math.abs(shapeWidth - frame.width) <= 1e-4 && Math.abs(shapeHeight - frame.height) <= 1e-4;
4371
- if (shape === "rect" && radius <= 1e-4 && isSameAsFrame) {
4372
- this.debug("overlay:shape:skip", {
4373
- reason: "shape-rect-no-radius"
4374
- });
4375
- return [];
4376
- }
4377
- const baseOptions = {
4378
- shape,
4379
- width: shapeWidth,
4380
- height: shapeHeight,
4381
- radius,
4382
- x: frame.width / 2,
4383
- y: frame.height / 2,
4384
- features: [],
4385
- shapeStyle,
4386
- canvasWidth: frame.width,
4387
- canvasHeight: frame.height
4388
- };
4389
- try {
4390
- const shapePathData = generateDielinePath(baseOptions);
4391
- const outerRectPathData = `M 0 0 L ${frame.width} 0 L ${frame.width} ${frame.height} L 0 ${frame.height} Z`;
4392
- const hatchPathData = `${outerRectPathData} ${shapePathData}`;
4393
- if (!shapePathData || !hatchPathData) {
4394
- this.debug("overlay:shape:skip", {
4395
- reason: "path-generation-empty",
4396
- shape,
4397
- radius
4398
- });
4399
- return [];
4400
- }
4401
- const patternFill = this.getCropShapeHatchPattern();
4402
- const hatchFill = patternFill || "rgba(255, 0, 0, 0.22)";
4403
- const shapeBounds = getPathBounds(shapePathData);
4404
- const hatchBounds = getPathBounds(hatchPathData);
4405
- const frameRect = this.toLayoutSceneRect(frame);
4406
- const hatchPathLength = hatchPathData.length;
4407
- const shapePathLength = shapePathData.length;
4408
- const specs = [
4409
- {
4410
- id: "image.cropShapeHatch",
4411
- type: "path",
4412
- data: { id: "image.cropShapeHatch", zIndex: 5 },
4413
- layout: {
4414
- reference: "custom",
4415
- referenceRect: frameRect,
4416
- alignX: "start",
4417
- alignY: "start",
4418
- offsetX: hatchBounds.x,
4419
- offsetY: hatchBounds.y
4420
- },
4421
- props: {
4422
- pathData: hatchPathData,
4423
- originX: "left",
4424
- originY: "top",
4425
- fill: hatchFill,
4426
- opacity: patternFill ? 1 : 0.8,
4427
- stroke: "rgba(255, 0, 0, 0.9)",
4428
- strokeWidth: (_b = (_a = this.canvasService) == null ? void 0 : _a.toSceneLength(1)) != null ? _b : 1,
4429
- fillRule: "evenodd",
4430
- selectable: false,
4431
- evented: false,
4432
- excludeFromExport: true,
4433
- objectCaching: false
4434
- }
4435
- }
4436
- ];
4437
- this.debug("overlay:shape:built", {
4438
- shape,
4439
- radius,
4440
- inset,
4441
- shapeWidth,
4442
- shapeHeight,
4443
- fillRule: "evenodd",
4444
- shapePathLength,
4445
- hatchPathLength,
4446
- shapeBounds,
4447
- hatchBounds,
4448
- hatchFillType: hatchFill && typeof hatchFill === "object" ? "pattern" : "color",
4449
- ids: specs.map((spec) => spec.id)
4450
- });
4451
- return specs;
4452
- } catch (error) {
4453
- this.debug("overlay:shape:error", {
4454
- shape,
4455
- radius,
4456
- error: error instanceof Error ? error.message : String(error)
4457
- });
4458
- return [];
4459
- }
4460
- }
4461
4546
  resolveRenderImageState(item) {
4462
4547
  var _a;
4463
4548
  const active = this.isToolActive;
@@ -4539,172 +4624,35 @@ var ImageTool = class {
4539
4624
  }
4540
4625
  return specs;
4541
4626
  }
4542
- buildOverlaySpecs(frame, sceneGeometry) {
4627
+ buildOverlaySpecs(overlayState) {
4543
4628
  const visible = this.isImageEditingVisible();
4544
- if (!visible || frame.width <= 0 || frame.height <= 0 || !this.canvasService) {
4629
+ if (!visible || !overlayState || !this.canvasService) {
4545
4630
  this.debug("overlay:hidden", {
4546
4631
  visible,
4547
- frame,
4632
+ cutRect: overlayState == null ? void 0 : overlayState.layout.cutRect,
4548
4633
  isToolActive: this.isToolActive,
4549
4634
  isImageSelectionActive: this.isImageSelectionActive,
4550
4635
  focusedImageId: this.focusedImageId
4551
4636
  });
4552
4637
  return [];
4553
4638
  }
4554
- const viewport = this.canvasService.getSceneViewportRect();
4555
- const canvasW = viewport.width || 0;
4556
- const canvasH = viewport.height || 0;
4557
- const canvasLeft = viewport.left || 0;
4558
- const canvasTop = viewport.top || 0;
4639
+ const viewport = this.canvasService.getScreenViewportRect();
4559
4640
  const visual = this.getFrameVisualConfig();
4560
- const strokeWidthScene = this.canvasService.toSceneLength(
4561
- visual.strokeWidth
4562
- );
4563
- const dashLengthScene = this.canvasService.toSceneLength(visual.dashLength);
4564
- const frameLeft = Math.max(
4565
- canvasLeft,
4566
- Math.min(canvasLeft + canvasW, frame.left)
4567
- );
4568
- const frameTop = Math.max(
4569
- canvasTop,
4570
- Math.min(canvasTop + canvasH, frame.top)
4571
- );
4572
- const frameRight = Math.max(
4573
- frameLeft,
4574
- Math.min(canvasLeft + canvasW, frame.left + frame.width)
4575
- );
4576
- const frameBottom = Math.max(
4577
- frameTop,
4578
- Math.min(canvasTop + canvasH, frame.top + frame.height)
4579
- );
4580
- const visibleFrameH = Math.max(0, frameBottom - frameTop);
4581
- const topH = Math.max(0, frameTop - canvasTop);
4582
- const bottomH = Math.max(0, canvasTop + canvasH - frameBottom);
4583
- const leftW = Math.max(0, frameLeft - canvasLeft);
4584
- const rightW = Math.max(0, canvasLeft + canvasW - frameRight);
4585
- const viewportRect = this.toLayoutSceneRect({
4586
- left: canvasLeft,
4587
- top: canvasTop,
4588
- width: canvasW,
4589
- height: canvasH
4590
- });
4591
- const visibleFrameBandRect = this.toLayoutSceneRect({
4592
- left: canvasLeft,
4593
- top: frameTop,
4594
- width: canvasW,
4595
- height: visibleFrameH
4596
- });
4597
- const frameRect = this.toLayoutSceneRect(frame);
4598
- const shapeOverlay = this.buildCropShapeOverlaySpecs(frame, sceneGeometry);
4599
- const mask = [
4600
- {
4601
- id: "image.cropMask.top",
4602
- type: "rect",
4603
- data: { id: "image.cropMask.top", zIndex: 1 },
4604
- layout: {
4605
- reference: "custom",
4606
- referenceRect: viewportRect,
4607
- alignX: "start",
4608
- alignY: "start",
4609
- width: "100%",
4610
- height: topH
4611
- },
4612
- props: {
4613
- originX: "left",
4614
- originY: "top",
4615
- fill: visual.outerBackground,
4616
- selectable: false,
4617
- evented: false
4618
- }
4619
- },
4620
- {
4621
- id: "image.cropMask.bottom",
4622
- type: "rect",
4623
- data: { id: "image.cropMask.bottom", zIndex: 2 },
4624
- layout: {
4625
- reference: "custom",
4626
- referenceRect: viewportRect,
4627
- alignX: "start",
4628
- alignY: "end",
4629
- width: "100%",
4630
- height: bottomH
4631
- },
4632
- props: {
4633
- originX: "left",
4634
- originY: "top",
4635
- fill: visual.outerBackground,
4636
- selectable: false,
4637
- evented: false
4638
- }
4639
- },
4640
- {
4641
- id: "image.cropMask.left",
4642
- type: "rect",
4643
- data: { id: "image.cropMask.left", zIndex: 3 },
4644
- layout: {
4645
- reference: "custom",
4646
- referenceRect: visibleFrameBandRect,
4647
- alignX: "start",
4648
- alignY: "start",
4649
- width: leftW,
4650
- height: "100%"
4651
- },
4652
- props: {
4653
- originX: "left",
4654
- originY: "top",
4655
- fill: visual.outerBackground,
4656
- selectable: false,
4657
- evented: false
4658
- }
4659
- },
4660
- {
4661
- id: "image.cropMask.right",
4662
- type: "rect",
4663
- data: { id: "image.cropMask.right", zIndex: 4 },
4664
- layout: {
4665
- reference: "custom",
4666
- referenceRect: visibleFrameBandRect,
4667
- alignX: "end",
4668
- alignY: "start",
4669
- width: rightW,
4670
- height: "100%"
4671
- },
4672
- props: {
4673
- originX: "left",
4674
- originY: "top",
4675
- fill: visual.outerBackground,
4676
- selectable: false,
4677
- evented: false
4678
- }
4679
- }
4680
- ];
4681
- const frameSpec = {
4682
- id: "image.cropFrame",
4683
- type: "rect",
4684
- data: { id: "image.cropFrame", zIndex: 7 },
4685
- layout: {
4686
- reference: "custom",
4687
- referenceRect: frameRect,
4688
- alignX: "start",
4689
- alignY: "start",
4690
- width: "100%",
4691
- height: "100%"
4641
+ const specs = buildImageSessionOverlaySpecs({
4642
+ viewport: {
4643
+ left: viewport.left,
4644
+ top: viewport.top,
4645
+ width: viewport.width,
4646
+ height: viewport.height
4692
4647
  },
4693
- props: {
4694
- originX: "left",
4695
- originY: "top",
4696
- fill: visual.innerBackground,
4697
- stroke: visual.strokeStyle === "hidden" ? "rgba(0,0,0,0)" : visual.strokeColor,
4698
- strokeWidth: visual.strokeStyle === "hidden" ? 0 : strokeWidthScene,
4699
- strokeDashArray: visual.strokeStyle === "dashed" ? [dashLengthScene, dashLengthScene] : void 0,
4700
- selectable: false,
4701
- evented: false
4702
- }
4703
- };
4704
- const specs = shapeOverlay.length > 0 ? [...mask, ...shapeOverlay] : [...mask, ...shapeOverlay, frameSpec];
4648
+ layout: overlayState.layout,
4649
+ geometry: overlayState.geometry,
4650
+ visual,
4651
+ hatchPattern: this.getCropShapeHatchPattern()
4652
+ });
4705
4653
  this.debug("overlay:built", {
4706
- frame,
4707
- shape: sceneGeometry == null ? void 0 : sceneGeometry.shape,
4654
+ cutRect: overlayState.layout.cutRect,
4655
+ shape: overlayState.geometry.shape,
4708
4656
  overlayIds: specs.map((spec) => {
4709
4657
  var _a;
4710
4658
  return {
@@ -4733,10 +4681,9 @@ var ImageTool = class {
4733
4681
  }
4734
4682
  const imageSpecs = await this.buildImageSpecs(renderItems, frame);
4735
4683
  if (seq !== this.renderSeq) return;
4736
- const sceneGeometry = await this.resolveSceneGeometryForOverlay();
4737
- if (seq !== this.renderSeq) return;
4684
+ const overlayState = this.resolveSessionOverlayState();
4738
4685
  this.imageSpecs = imageSpecs;
4739
- this.overlaySpecs = this.buildOverlaySpecs(frame, sceneGeometry);
4686
+ this.overlaySpecs = this.buildOverlaySpecs(overlayState);
4740
4687
  await this.canvasService.flushRenderFromProducers();
4741
4688
  if (seq !== this.renderSeq) return;
4742
4689
  this.refreshImageObjectInteractionState();
@@ -4763,11 +4710,38 @@ var ImageTool = class {
4763
4710
  isImageSelectionActive: this.isImageSelectionActive,
4764
4711
  focusedImageId: this.focusedImageId
4765
4712
  });
4713
+ this.emitImageStateChange();
4766
4714
  this.canvasService.requestRenderAll();
4767
4715
  }
4768
4716
  clampNormalized(value) {
4769
4717
  return Math.max(-1, Math.min(2, value));
4770
4718
  }
4719
+ async setImageTransform(id, updates, options = {}) {
4720
+ const next = {};
4721
+ if (Number.isFinite(updates.scale)) {
4722
+ next.scale = Math.max(0.05, Number(updates.scale));
4723
+ }
4724
+ if (Number.isFinite(updates.angle)) {
4725
+ next.angle = Number(updates.angle);
4726
+ }
4727
+ if (Number.isFinite(updates.left)) {
4728
+ next.left = this.clampNormalized(Number(updates.left));
4729
+ }
4730
+ if (Number.isFinite(updates.top)) {
4731
+ next.top = this.clampNormalized(Number(updates.top));
4732
+ }
4733
+ if (Number.isFinite(updates.opacity)) {
4734
+ next.opacity = Math.max(0, Math.min(1, Number(updates.opacity)));
4735
+ }
4736
+ if (!Object.keys(next).length) return;
4737
+ await this.updateImage(id, next, options);
4738
+ }
4739
+ resetImageSession() {
4740
+ this.workingItems = this.cloneItems(this.items);
4741
+ this.hasWorkingChanges = false;
4742
+ this.updateImages();
4743
+ this.emitWorkingChange();
4744
+ }
4771
4745
  updateImageInWorking(id, updates) {
4772
4746
  const index = this.workingItems.findIndex((item) => item.id === id);
4773
4747
  if (index < 0) return;
@@ -4785,7 +4759,6 @@ var ImageTool = class {
4785
4759
  this.emitWorkingChange(id);
4786
4760
  }
4787
4761
  async updateImageInConfig(id, updates) {
4788
- var _a, _b, _c, _d;
4789
4762
  const index = this.items.findIndex((item) => item.id === id);
4790
4763
  if (index < 0) return;
4791
4764
  const replacingSource = typeof updates.url === "string" && updates.url.length > 0;
@@ -4798,23 +4771,12 @@ var ImageTool = class {
4798
4771
  ...replacingSource ? {
4799
4772
  url: replacingUrl,
4800
4773
  sourceUrl: replacingUrl,
4801
- committedUrl: void 0,
4802
- scale: (_a = updates.scale) != null ? _a : 1,
4803
- angle: (_b = updates.angle) != null ? _b : 0,
4804
- left: (_c = updates.left) != null ? _c : 0.5,
4805
- top: (_d = updates.top) != null ? _d : 0.5
4774
+ committedUrl: void 0
4806
4775
  } : {}
4807
4776
  });
4808
4777
  this.updateConfig(next);
4809
4778
  if (replacingSource) {
4810
- this.debug("replace:image:begin", { id, replacingUrl });
4811
4779
  this.purgeSourceSizeCacheForItem(base);
4812
- const loaded = await this.waitImageLoaded(id, true);
4813
- this.debug("replace:image:loaded", { id, loaded });
4814
- if (loaded) {
4815
- await this.refitImageToFrame(id);
4816
- this.setImageFocus(id);
4817
- }
4818
4780
  }
4819
4781
  }
4820
4782
  waitImageLoaded(id, forceWait = false) {
@@ -4832,70 +4794,43 @@ var ImageTool = class {
4832
4794
  });
4833
4795
  });
4834
4796
  }
4835
- async refitImageToFrame(id) {
4797
+ async resolveImageSourceSize(id, src) {
4836
4798
  const obj = this.getImageObject(id);
4837
- if (!obj || !this.canvasService) return;
4838
- const current = this.items.find((item) => item.id === id);
4839
- if (!current) return;
4840
- const render = this.resolveRenderImageState(current);
4841
- this.rememberSourceSize(render.src, obj);
4842
- const source = this.getSourceSize(render.src, obj);
4843
- const frame = this.getFrameRect();
4844
- const coverScale = this.getCoverScale(frame, source);
4845
- const currentScale = this.toSceneObjectScale(obj.scaleX || 1);
4846
- const zoom = Math.max(0.05, currentScale / coverScale);
4847
- const updated = {
4848
- scale: Number.isFinite(zoom) ? zoom : 1,
4849
- angle: 0,
4850
- left: 0.5,
4851
- top: 0.5
4852
- };
4853
- const index = this.items.findIndex((item) => item.id === id);
4854
- if (index < 0) return;
4855
- const next = [...this.items];
4856
- next[index] = this.normalizeItem({ ...next[index], ...updated });
4857
- this.updateConfig(next);
4858
- this.workingItems = this.cloneItems(next);
4859
- this.hasWorkingChanges = false;
4860
- this.updateImages();
4861
- this.emitWorkingChange(id);
4799
+ if (obj) {
4800
+ this.rememberSourceSize(src, obj);
4801
+ }
4802
+ const ensured = await this.ensureSourceSize(src);
4803
+ if (ensured) return ensured;
4804
+ if (!obj) return null;
4805
+ const width = Number((obj == null ? void 0 : obj.width) || 0);
4806
+ const height = Number((obj == null ? void 0 : obj.height) || 0);
4807
+ if (width <= 0 || height <= 0) return null;
4808
+ return { width, height };
4862
4809
  }
4863
- async fitImageToArea(id, area) {
4864
- var _a, _b;
4810
+ async applyImageOperation(id, operation, options = {}) {
4865
4811
  if (!this.canvasService) return;
4866
- const loaded = await this.waitImageLoaded(id, false);
4867
- if (!loaded) return;
4868
- const obj = this.getImageObject(id);
4869
- if (!obj) return;
4870
- const renderItems = this.isToolActive ? this.workingItems : this.items;
4812
+ this.syncToolActiveFromWorkbench();
4813
+ const target = options.target || "auto";
4814
+ const renderItems = target === "working" || target === "auto" && this.isToolActive ? this.workingItems : this.items;
4871
4815
  const current = renderItems.find((item) => item.id === id);
4872
4816
  if (!current) return;
4873
4817
  const render = this.resolveRenderImageState(current);
4874
- this.rememberSourceSize(render.src, obj);
4875
- const source = this.getSourceSize(render.src, obj);
4818
+ const source = await this.resolveImageSourceSize(id, render.src);
4819
+ if (!source) return;
4876
4820
  const frame = this.getFrameRect();
4877
- const baseCover = this.getCoverScale(frame, source);
4878
- const desiredScale = Math.max(
4879
- Math.max(1, area.width) / Math.max(1, source.width),
4880
- Math.max(1, area.height) / Math.max(1, source.height)
4881
- );
4882
4821
  const viewport = this.canvasService.getSceneViewportRect();
4883
- const canvasW = viewport.width || 1;
4884
- const canvasH = viewport.height || 1;
4885
- const areaLeftInput = (_a = area.left) != null ? _a : 0.5;
4886
- const areaTopInput = (_b = area.top) != null ? _b : 0.5;
4887
- const areaLeftPx = areaLeftInput <= 1.5 ? viewport.left + areaLeftInput * canvasW : areaLeftInput;
4888
- const areaTopPx = areaTopInput <= 1.5 ? viewport.top + areaTopInput * canvasH : areaTopInput;
4889
- const updates = {
4890
- scale: Math.max(0.05, desiredScale / baseCover),
4891
- left: this.clampNormalized(
4892
- (areaLeftPx - frame.left) / Math.max(1, frame.width)
4893
- ),
4894
- top: this.clampNormalized(
4895
- (areaTopPx - frame.top) / Math.max(1, frame.height)
4896
- )
4897
- };
4898
- if (this.isToolActive) {
4822
+ const area = operation.type === "resetTransform" ? resolveImageOperationArea({ frame, viewport }) : resolveImageOperationArea({
4823
+ frame,
4824
+ viewport,
4825
+ area: operation.area
4826
+ });
4827
+ const updates = computeImageOperationUpdates({
4828
+ frame,
4829
+ source,
4830
+ operation,
4831
+ area
4832
+ });
4833
+ if (target === "working" || target === "auto" && this.isToolActive) {
4899
4834
  this.updateImageInWorking(id, updates);
4900
4835
  return;
4901
4836
  }
@@ -5026,6 +4961,11 @@ var ImageTool = class {
5026
4961
  }
5027
4962
  };
5028
4963
 
4964
+ // src/extensions/image/model.ts
4965
+ function hasAnyImageInViewState(state) {
4966
+ return Boolean(state == null ? void 0 : state.hasAnyImage);
4967
+ }
4968
+
5029
4969
  // src/extensions/size/SizeTool.ts
5030
4970
  var import_core3 = require("@pooder/core");
5031
4971
  var SizeTool = class {
@@ -5986,6 +5926,13 @@ function buildDielineRenderBundle(options) {
5986
5926
  canvasWidth,
5987
5927
  canvasHeight
5988
5928
  };
5929
+ const cutFrameRect = {
5930
+ left: cx - cutW / 2,
5931
+ top: cy - cutH / 2,
5932
+ width: cutW,
5933
+ height: cutH,
5934
+ space: "screen"
5935
+ };
5989
5936
  const specs = [];
5990
5937
  if (insideColor && insideColor !== "transparent" && insideColor !== "rgba(0,0,0,0)" && !hasImages) {
5991
5938
  specs.push({
@@ -6107,9 +6054,13 @@ function buildDielineRenderBundle(options) {
6107
6054
  width: cutW,
6108
6055
  height: cutH,
6109
6056
  radius: cutR,
6110
- x: cx,
6111
- y: cy,
6112
- features: cutFeatures
6057
+ // Build the clip path in the cut frame's local coordinates so Fabric
6058
+ // does not have to infer placement from the standalone path bounds.
6059
+ x: cutW / 2,
6060
+ y: cutH / 2,
6061
+ features: cutFeatures,
6062
+ canvasWidth: cutW,
6063
+ canvasHeight: cutH
6113
6064
  });
6114
6065
  if (!clipPathData) {
6115
6066
  return { specs, effects: [] };
@@ -6126,6 +6077,12 @@ function buildDielineRenderBundle(options) {
6126
6077
  id: ids.clipSource,
6127
6078
  type: "path",
6128
6079
  space: "screen",
6080
+ layout: {
6081
+ reference: "custom",
6082
+ referenceRect: cutFrameRect,
6083
+ alignX: "start",
6084
+ alignY: "start"
6085
+ },
6129
6086
  data: {
6130
6087
  id: ids.clipSource,
6131
6088
  type: "dieline-effect",
@@ -10935,6 +10892,7 @@ var CanvasService = class {
10935
10892
  ViewportSystem,
10936
10893
  WhiteInkTool,
10937
10894
  computeImageCoverScale,
10895
+ computeImageOperationUpdates,
10938
10896
  computeWhiteInkCoverScale,
10939
10897
  createDefaultDielineState,
10940
10898
  createDielineCommands,
@@ -10944,5 +10902,7 @@ var CanvasService = class {
10944
10902
  createWhiteInkCommands,
10945
10903
  createWhiteInkConfigurations,
10946
10904
  evaluateVisibilityExpr,
10947
- readDielineState
10905
+ hasAnyImageInViewState,
10906
+ readDielineState,
10907
+ resolveImageOperationArea
10948
10908
  });