@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/.test-dist/src/extensions/dieline/renderBuilder.js +19 -2
- package/.test-dist/src/extensions/image/ImageTool.js +180 -467
- package/.test-dist/src/extensions/image/commands.js +60 -40
- package/.test-dist/src/extensions/image/imageOperations.js +75 -0
- package/.test-dist/src/extensions/image/index.js +1 -0
- package/.test-dist/src/extensions/image/model.js +4 -0
- package/.test-dist/src/extensions/image/sessionOverlay.js +148 -0
- package/.test-dist/src/extensions/ruler/RulerTool.js +1 -1
- package/.test-dist/tests/run.js +39 -5
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +252 -168
- package/dist/index.d.ts +252 -168
- package/dist/index.js +806 -846
- package/dist/index.mjs +802 -845
- package/package.json +1 -1
- package/src/extensions/dieline/renderBuilder.ts +26 -4
- package/src/extensions/image/ImageTool.ts +229 -557
- package/src/extensions/image/commands.ts +69 -48
- package/src/extensions/image/imageOperations.ts +135 -0
- package/src/extensions/image/index.ts +1 -0
- package/src/extensions/image/model.ts +13 -1
- package/src/extensions/image/sessionOverlay.ts +206 -0
- package/tests/run.ts +49 -8
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CommandContribution } from "@pooder/core";
|
|
2
|
+
import type { ImageOperation } from "./imageOperations";
|
|
2
3
|
|
|
3
4
|
export function createImageCommands(tool: any): CommandContribution[] {
|
|
4
5
|
return [
|
|
@@ -23,30 +24,43 @@ export function createImageCommands(tool: any): CommandContribution[] {
|
|
|
23
24
|
},
|
|
24
25
|
},
|
|
25
26
|
{
|
|
26
|
-
command: "
|
|
27
|
-
id: "
|
|
28
|
-
title: "
|
|
27
|
+
command: "applyImageOperation",
|
|
28
|
+
id: "applyImageOperation",
|
|
29
|
+
title: "Apply Image Operation",
|
|
30
|
+
handler: async (
|
|
31
|
+
id: string,
|
|
32
|
+
operation: ImageOperation,
|
|
33
|
+
options: Record<string, any> = {},
|
|
34
|
+
) => {
|
|
35
|
+
await tool.applyImageOperation(id, operation, options);
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
command: "getImageViewState",
|
|
40
|
+
id: "getImageViewState",
|
|
41
|
+
title: "Get Image View State",
|
|
29
42
|
handler: () => {
|
|
30
|
-
return tool.
|
|
43
|
+
return tool.getImageViewState();
|
|
31
44
|
},
|
|
32
45
|
},
|
|
33
46
|
{
|
|
34
|
-
command: "
|
|
35
|
-
id: "
|
|
36
|
-
title: "Set
|
|
37
|
-
handler: (
|
|
38
|
-
|
|
47
|
+
command: "setImageTransform",
|
|
48
|
+
id: "setImageTransform",
|
|
49
|
+
title: "Set Image Transform",
|
|
50
|
+
handler: async (
|
|
51
|
+
id: string,
|
|
52
|
+
updates: Record<string, any>,
|
|
53
|
+
options: Record<string, any> = {},
|
|
54
|
+
) => {
|
|
55
|
+
await tool.setImageTransform(id, updates, options);
|
|
39
56
|
},
|
|
40
57
|
},
|
|
41
58
|
{
|
|
42
|
-
command: "
|
|
43
|
-
id: "
|
|
44
|
-
title: "Reset
|
|
59
|
+
command: "imageSessionReset",
|
|
60
|
+
id: "imageSessionReset",
|
|
61
|
+
title: "Reset Image Session",
|
|
45
62
|
handler: () => {
|
|
46
|
-
tool.
|
|
47
|
-
tool.hasWorkingChanges = false;
|
|
48
|
-
tool.updateImages();
|
|
49
|
-
tool.emitWorkingChange();
|
|
63
|
+
tool.resetImageSession();
|
|
50
64
|
},
|
|
51
65
|
},
|
|
52
66
|
{
|
|
@@ -65,30 +79,6 @@ export function createImageCommands(tool: any): CommandContribution[] {
|
|
|
65
79
|
return await tool.exportUserCroppedImage(options);
|
|
66
80
|
},
|
|
67
81
|
},
|
|
68
|
-
{
|
|
69
|
-
command: "fitImageToArea",
|
|
70
|
-
id: "fitImageToArea",
|
|
71
|
-
title: "Fit Image to Area",
|
|
72
|
-
handler: async (
|
|
73
|
-
id: string,
|
|
74
|
-
area: {
|
|
75
|
-
width: number;
|
|
76
|
-
height: number;
|
|
77
|
-
left?: number;
|
|
78
|
-
top?: number;
|
|
79
|
-
},
|
|
80
|
-
) => {
|
|
81
|
-
await tool.fitImageToArea(id, area);
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
command: "fitImageToDefaultArea",
|
|
86
|
-
id: "fitImageToDefaultArea",
|
|
87
|
-
title: "Fit Image to Default Area",
|
|
88
|
-
handler: async (id: string) => {
|
|
89
|
-
await tool.fitImageToDefaultArea(id);
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
82
|
{
|
|
93
83
|
command: "focusImage",
|
|
94
84
|
id: "focusImage",
|
|
@@ -105,9 +95,10 @@ export function createImageCommands(tool: any): CommandContribution[] {
|
|
|
105
95
|
id: "removeImage",
|
|
106
96
|
title: "Remove Image",
|
|
107
97
|
handler: (id: string) => {
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
98
|
+
const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
|
|
99
|
+
const removed = sourceItems.find((item: any) => item.id === id);
|
|
100
|
+
const next = sourceItems.filter((item: any) => item.id !== id);
|
|
101
|
+
if (next.length !== sourceItems.length) {
|
|
111
102
|
tool.purgeSourceSizeCacheForItem(removed);
|
|
112
103
|
if (tool.focusedImageId === id) {
|
|
113
104
|
tool.setImageFocus(null, {
|
|
@@ -115,6 +106,13 @@ export function createImageCommands(tool: any): CommandContribution[] {
|
|
|
115
106
|
skipRender: true,
|
|
116
107
|
});
|
|
117
108
|
}
|
|
109
|
+
if (tool.isToolActive) {
|
|
110
|
+
tool.workingItems = tool.cloneItems(next);
|
|
111
|
+
tool.hasWorkingChanges = true;
|
|
112
|
+
tool.updateImages();
|
|
113
|
+
tool.emitWorkingChange(id);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
118
116
|
tool.updateConfig(next);
|
|
119
117
|
}
|
|
120
118
|
},
|
|
@@ -141,6 +139,13 @@ export function createImageCommands(tool: any): CommandContribution[] {
|
|
|
141
139
|
syncCanvasSelection: true,
|
|
142
140
|
skipRender: true,
|
|
143
141
|
});
|
|
142
|
+
if (tool.isToolActive) {
|
|
143
|
+
tool.workingItems = [];
|
|
144
|
+
tool.hasWorkingChanges = true;
|
|
145
|
+
tool.updateImages();
|
|
146
|
+
tool.emitWorkingChange();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
144
149
|
tool.updateConfig([]);
|
|
145
150
|
},
|
|
146
151
|
},
|
|
@@ -149,11 +154,19 @@ export function createImageCommands(tool: any): CommandContribution[] {
|
|
|
149
154
|
id: "bringToFront",
|
|
150
155
|
title: "Bring Image to Front",
|
|
151
156
|
handler: (id: string) => {
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
|
|
158
|
+
const index = sourceItems.findIndex((item: any) => item.id === id);
|
|
159
|
+
if (index !== -1 && index < sourceItems.length - 1) {
|
|
160
|
+
const next = [...sourceItems];
|
|
155
161
|
const [item] = next.splice(index, 1);
|
|
156
162
|
next.push(item);
|
|
163
|
+
if (tool.isToolActive) {
|
|
164
|
+
tool.workingItems = tool.cloneItems(next);
|
|
165
|
+
tool.hasWorkingChanges = true;
|
|
166
|
+
tool.updateImages();
|
|
167
|
+
tool.emitWorkingChange(id);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
157
170
|
tool.updateConfig(next);
|
|
158
171
|
}
|
|
159
172
|
},
|
|
@@ -163,11 +176,19 @@ export function createImageCommands(tool: any): CommandContribution[] {
|
|
|
163
176
|
id: "sendToBack",
|
|
164
177
|
title: "Send Image to Back",
|
|
165
178
|
handler: (id: string) => {
|
|
166
|
-
const
|
|
179
|
+
const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
|
|
180
|
+
const index = sourceItems.findIndex((item: any) => item.id === id);
|
|
167
181
|
if (index > 0) {
|
|
168
|
-
const next = [...
|
|
182
|
+
const next = [...sourceItems];
|
|
169
183
|
const [item] = next.splice(index, 1);
|
|
170
184
|
next.unshift(item);
|
|
185
|
+
if (tool.isToolActive) {
|
|
186
|
+
tool.workingItems = tool.cloneItems(next);
|
|
187
|
+
tool.hasWorkingChanges = true;
|
|
188
|
+
tool.updateImages();
|
|
189
|
+
tool.emitWorkingChange(id);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
171
192
|
tool.updateConfig(next);
|
|
172
193
|
}
|
|
173
194
|
},
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { FrameRect } from "../../shared/scene/frame";
|
|
2
|
+
import {
|
|
3
|
+
getCoverScale as getCoverScaleFromRect,
|
|
4
|
+
type SourceSize,
|
|
5
|
+
} from "../../shared/imaging/sourceSizeCache";
|
|
6
|
+
|
|
7
|
+
export interface ImageOperationArea {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
centerX: number;
|
|
11
|
+
centerY: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ImageOperationViewport {
|
|
15
|
+
left: number;
|
|
16
|
+
top: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ImageOperationAreaSpec =
|
|
22
|
+
| { type: "frame" }
|
|
23
|
+
| { type: "viewport" }
|
|
24
|
+
| ({
|
|
25
|
+
type: "custom";
|
|
26
|
+
} & ImageOperationArea);
|
|
27
|
+
|
|
28
|
+
export type ImageOperation =
|
|
29
|
+
| { type: "cover"; area?: ImageOperationAreaSpec }
|
|
30
|
+
| { type: "contain"; area?: ImageOperationAreaSpec }
|
|
31
|
+
| { type: "maximizeWidth"; area?: ImageOperationAreaSpec }
|
|
32
|
+
| { type: "maximizeHeight"; area?: ImageOperationAreaSpec }
|
|
33
|
+
| { type: "center"; area?: ImageOperationAreaSpec }
|
|
34
|
+
| { type: "resetTransform" };
|
|
35
|
+
|
|
36
|
+
export interface ComputeImageOperationArgs {
|
|
37
|
+
frame: FrameRect;
|
|
38
|
+
source: SourceSize;
|
|
39
|
+
operation: ImageOperation;
|
|
40
|
+
area: ImageOperationArea;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function clampNormalizedAnchor(value: number): number {
|
|
44
|
+
return Math.max(-1, Math.min(2, value));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function toNormalizedAnchor(center: number, start: number, size: number): number {
|
|
48
|
+
return clampNormalizedAnchor((center - start) / Math.max(1, size));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveAbsoluteScale(
|
|
52
|
+
operation: ImageOperation,
|
|
53
|
+
area: ImageOperationArea,
|
|
54
|
+
source: SourceSize,
|
|
55
|
+
): number | null {
|
|
56
|
+
const widthScale = Math.max(1, area.width) / Math.max(1, source.width);
|
|
57
|
+
const heightScale = Math.max(1, area.height) / Math.max(1, source.height);
|
|
58
|
+
|
|
59
|
+
switch (operation.type) {
|
|
60
|
+
case "cover":
|
|
61
|
+
return Math.max(widthScale, heightScale);
|
|
62
|
+
case "contain":
|
|
63
|
+
return Math.min(widthScale, heightScale);
|
|
64
|
+
case "maximizeWidth":
|
|
65
|
+
return widthScale;
|
|
66
|
+
case "maximizeHeight":
|
|
67
|
+
return heightScale;
|
|
68
|
+
default:
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function resolveImageOperationArea(args: {
|
|
74
|
+
frame: FrameRect;
|
|
75
|
+
viewport: ImageOperationViewport;
|
|
76
|
+
area?: ImageOperationAreaSpec;
|
|
77
|
+
}): ImageOperationArea {
|
|
78
|
+
const spec = args.area || { type: "frame" };
|
|
79
|
+
|
|
80
|
+
if (spec.type === "custom") {
|
|
81
|
+
return {
|
|
82
|
+
width: Math.max(1, spec.width),
|
|
83
|
+
height: Math.max(1, spec.height),
|
|
84
|
+
centerX: spec.centerX,
|
|
85
|
+
centerY: spec.centerY,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (spec.type === "viewport") {
|
|
90
|
+
return {
|
|
91
|
+
width: Math.max(1, args.viewport.width),
|
|
92
|
+
height: Math.max(1, args.viewport.height),
|
|
93
|
+
centerX: args.viewport.left + args.viewport.width / 2,
|
|
94
|
+
centerY: args.viewport.top + args.viewport.height / 2,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
width: Math.max(1, args.frame.width),
|
|
100
|
+
height: Math.max(1, args.frame.height),
|
|
101
|
+
centerX: args.frame.left + args.frame.width / 2,
|
|
102
|
+
centerY: args.frame.top + args.frame.height / 2,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function computeImageOperationUpdates(
|
|
107
|
+
args: ComputeImageOperationArgs,
|
|
108
|
+
): { scale?: number; left?: number; top?: number; angle?: number } {
|
|
109
|
+
const { frame, source, operation, area } = args;
|
|
110
|
+
|
|
111
|
+
if (operation.type === "resetTransform") {
|
|
112
|
+
return {
|
|
113
|
+
scale: 1,
|
|
114
|
+
left: 0.5,
|
|
115
|
+
top: 0.5,
|
|
116
|
+
angle: 0,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const left = toNormalizedAnchor(area.centerX, frame.left, frame.width);
|
|
121
|
+
const top = toNormalizedAnchor(area.centerY, frame.top, frame.height);
|
|
122
|
+
|
|
123
|
+
if (operation.type === "center") {
|
|
124
|
+
return { left, top };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const absoluteScale = resolveAbsoluteScale(operation, area, source);
|
|
128
|
+
const coverScale = getCoverScaleFromRect(frame, source);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
scale: Math.max(0.05, (absoluteScale || coverScale) / coverScale),
|
|
132
|
+
left,
|
|
133
|
+
top,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -1 +1,13 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type {
|
|
2
|
+
ImageItem,
|
|
3
|
+
ImageTransformUpdates,
|
|
4
|
+
ImageViewState,
|
|
5
|
+
} from "./ImageTool";
|
|
6
|
+
|
|
7
|
+
import type { ImageViewState } from "./ImageTool";
|
|
8
|
+
|
|
9
|
+
export function hasAnyImageInViewState(
|
|
10
|
+
state: ImageViewState | null | undefined,
|
|
11
|
+
): boolean {
|
|
12
|
+
return Boolean(state?.hasAnyImage);
|
|
13
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { Pattern } from "fabric";
|
|
2
|
+
import type { RenderObjectSpec } from "../../services";
|
|
3
|
+
import type {
|
|
4
|
+
SceneGeometrySnapshot,
|
|
5
|
+
SceneLayoutSnapshot,
|
|
6
|
+
SceneRect,
|
|
7
|
+
} from "../../shared/scene/sceneLayoutModel";
|
|
8
|
+
import { generateDielinePath } from "../geometry";
|
|
9
|
+
|
|
10
|
+
export interface ImageSessionOverlayVisualConfig {
|
|
11
|
+
strokeColor: string;
|
|
12
|
+
strokeWidth: number;
|
|
13
|
+
strokeStyle: "solid" | "dashed" | "hidden";
|
|
14
|
+
dashLength: number;
|
|
15
|
+
innerBackground: string;
|
|
16
|
+
outerBackground: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ImageSessionOverlayViewport {
|
|
20
|
+
left: number;
|
|
21
|
+
top: number;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface BuiltinShapeOverlayPaths {
|
|
27
|
+
hatchPathData: string;
|
|
28
|
+
shapePathData: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const EPSILON = 0.0001;
|
|
32
|
+
const SHAPE_OUTLINE_COLOR = "rgba(255, 0, 0, 0.9)";
|
|
33
|
+
const DEFAULT_HATCH_FILL = "rgba(255, 0, 0, 0.22)";
|
|
34
|
+
|
|
35
|
+
function buildRectPath(width: number, height: number): string {
|
|
36
|
+
return `M 0 0 L ${width} 0 L ${width} ${height} L 0 ${height} Z`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildViewportMaskPath(
|
|
40
|
+
viewport: ImageSessionOverlayViewport,
|
|
41
|
+
cutRect: SceneRect,
|
|
42
|
+
): string {
|
|
43
|
+
const cutLeft = cutRect.left - viewport.left;
|
|
44
|
+
const cutTop = cutRect.top - viewport.top;
|
|
45
|
+
return [
|
|
46
|
+
buildRectPath(viewport.width, viewport.height),
|
|
47
|
+
`M ${cutLeft} ${cutTop} L ${cutLeft + cutRect.width} ${cutTop} L ${
|
|
48
|
+
cutLeft + cutRect.width
|
|
49
|
+
} ${cutTop + cutRect.height} L ${cutLeft} ${cutTop + cutRect.height} Z`,
|
|
50
|
+
].join(" ");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveCutShapeRadiusPx(
|
|
54
|
+
geometry: SceneGeometrySnapshot,
|
|
55
|
+
cutRect: SceneRect,
|
|
56
|
+
): number {
|
|
57
|
+
const visualRadius = Number.isFinite(geometry.radius)
|
|
58
|
+
? Math.max(0, geometry.radius)
|
|
59
|
+
: 0;
|
|
60
|
+
const visualOffset = Number.isFinite(geometry.offset) ? geometry.offset : 0;
|
|
61
|
+
const rawCutRadius =
|
|
62
|
+
visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
63
|
+
const maxRadius = Math.max(0, Math.min(cutRect.width, cutRect.height) / 2);
|
|
64
|
+
return Math.max(0, Math.min(maxRadius, rawCutRadius));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildBuiltinShapeOverlayPaths(
|
|
68
|
+
cutRect: SceneRect,
|
|
69
|
+
geometry: SceneGeometrySnapshot | null,
|
|
70
|
+
): BuiltinShapeOverlayPaths | null {
|
|
71
|
+
if (!geometry || geometry.shape === "custom") {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const radius = resolveCutShapeRadiusPx(geometry, cutRect);
|
|
76
|
+
if (geometry.shape === "rect" && radius <= EPSILON) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const shapePathData = generateDielinePath({
|
|
81
|
+
shape: geometry.shape,
|
|
82
|
+
shapeStyle: geometry.shapeStyle,
|
|
83
|
+
width: Math.max(1, cutRect.width),
|
|
84
|
+
height: Math.max(1, cutRect.height),
|
|
85
|
+
radius,
|
|
86
|
+
x: cutRect.width / 2,
|
|
87
|
+
y: cutRect.height / 2,
|
|
88
|
+
features: [],
|
|
89
|
+
canvasWidth: Math.max(1, cutRect.width),
|
|
90
|
+
canvasHeight: Math.max(1, cutRect.height),
|
|
91
|
+
});
|
|
92
|
+
if (!shapePathData) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
shapePathData,
|
|
98
|
+
hatchPathData: `${buildRectPath(cutRect.width, cutRect.height)} ${shapePathData}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function buildImageSessionOverlaySpecs(args: {
|
|
103
|
+
viewport: ImageSessionOverlayViewport;
|
|
104
|
+
layout: SceneLayoutSnapshot;
|
|
105
|
+
geometry: SceneGeometrySnapshot | null;
|
|
106
|
+
visual: ImageSessionOverlayVisualConfig;
|
|
107
|
+
hatchPattern?: Pattern;
|
|
108
|
+
}): RenderObjectSpec[] {
|
|
109
|
+
const { viewport, layout, geometry, visual, hatchPattern } = args;
|
|
110
|
+
const cutRect = layout.cutRect;
|
|
111
|
+
const specs: RenderObjectSpec[] = [];
|
|
112
|
+
|
|
113
|
+
specs.push({
|
|
114
|
+
id: "image.cropMask.rect",
|
|
115
|
+
type: "path",
|
|
116
|
+
space: "screen",
|
|
117
|
+
data: { id: "image.cropMask.rect", zIndex: 1 },
|
|
118
|
+
props: {
|
|
119
|
+
pathData: buildViewportMaskPath(viewport, cutRect),
|
|
120
|
+
left: viewport.left,
|
|
121
|
+
top: viewport.top,
|
|
122
|
+
originX: "left",
|
|
123
|
+
originY: "top",
|
|
124
|
+
fill: visual.outerBackground,
|
|
125
|
+
stroke: null,
|
|
126
|
+
fillRule: "evenodd",
|
|
127
|
+
selectable: false,
|
|
128
|
+
evented: false,
|
|
129
|
+
excludeFromExport: true,
|
|
130
|
+
objectCaching: false,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const shapeOverlay = buildBuiltinShapeOverlayPaths(cutRect, geometry);
|
|
135
|
+
if (shapeOverlay) {
|
|
136
|
+
specs.push({
|
|
137
|
+
id: "image.cropShapeHatch",
|
|
138
|
+
type: "path",
|
|
139
|
+
space: "screen",
|
|
140
|
+
data: { id: "image.cropShapeHatch", zIndex: 5 },
|
|
141
|
+
props: {
|
|
142
|
+
pathData: shapeOverlay.hatchPathData,
|
|
143
|
+
left: cutRect.left,
|
|
144
|
+
top: cutRect.top,
|
|
145
|
+
originX: "left",
|
|
146
|
+
originY: "top",
|
|
147
|
+
fill: hatchPattern || DEFAULT_HATCH_FILL,
|
|
148
|
+
opacity: hatchPattern ? 1 : 0.8,
|
|
149
|
+
stroke: null,
|
|
150
|
+
fillRule: "evenodd",
|
|
151
|
+
selectable: false,
|
|
152
|
+
evented: false,
|
|
153
|
+
excludeFromExport: true,
|
|
154
|
+
objectCaching: false,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
specs.push({
|
|
158
|
+
id: "image.cropShapeOutline",
|
|
159
|
+
type: "path",
|
|
160
|
+
space: "screen",
|
|
161
|
+
data: { id: "image.cropShapeOutline", zIndex: 6 },
|
|
162
|
+
props: {
|
|
163
|
+
pathData: shapeOverlay.shapePathData,
|
|
164
|
+
left: cutRect.left,
|
|
165
|
+
top: cutRect.top,
|
|
166
|
+
originX: "left",
|
|
167
|
+
originY: "top",
|
|
168
|
+
fill: "transparent",
|
|
169
|
+
stroke: SHAPE_OUTLINE_COLOR,
|
|
170
|
+
strokeWidth: 1,
|
|
171
|
+
selectable: false,
|
|
172
|
+
evented: false,
|
|
173
|
+
excludeFromExport: true,
|
|
174
|
+
objectCaching: false,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
specs.push({
|
|
180
|
+
id: "image.cropFrame",
|
|
181
|
+
type: "rect",
|
|
182
|
+
space: "screen",
|
|
183
|
+
data: { id: "image.cropFrame", zIndex: 7 },
|
|
184
|
+
props: {
|
|
185
|
+
left: cutRect.left,
|
|
186
|
+
top: cutRect.top,
|
|
187
|
+
width: cutRect.width,
|
|
188
|
+
height: cutRect.height,
|
|
189
|
+
originX: "left",
|
|
190
|
+
originY: "top",
|
|
191
|
+
fill: visual.innerBackground,
|
|
192
|
+
stroke:
|
|
193
|
+
visual.strokeStyle === "hidden" ? "rgba(0,0,0,0)" : visual.strokeColor,
|
|
194
|
+
strokeWidth: visual.strokeStyle === "hidden" ? 0 : visual.strokeWidth,
|
|
195
|
+
strokeDashArray:
|
|
196
|
+
visual.strokeStyle === "dashed"
|
|
197
|
+
? [visual.dashLength, visual.dashLength]
|
|
198
|
+
: undefined,
|
|
199
|
+
selectable: false,
|
|
200
|
+
evented: false,
|
|
201
|
+
excludeFromExport: true,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return specs;
|
|
206
|
+
}
|
package/tests/run.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
normalizePointInGeometry,
|
|
26
26
|
resolveFeaturePosition,
|
|
27
27
|
} from "../src/extensions/featureCoordinates";
|
|
28
|
+
import { hasAnyImageInViewState } from "../src/extensions/image/model";
|
|
28
29
|
|
|
29
30
|
function assert(condition: unknown, message: string) {
|
|
30
31
|
if (!condition) throw new Error(message);
|
|
@@ -288,6 +289,46 @@ function testVisibilityDsl() {
|
|
|
288
289
|
);
|
|
289
290
|
}
|
|
290
291
|
|
|
292
|
+
function testImageViewStateHelper() {
|
|
293
|
+
assert(hasAnyImageInViewState(null) === false, "null image state should be empty");
|
|
294
|
+
assert(
|
|
295
|
+
hasAnyImageInViewState({
|
|
296
|
+
items: [],
|
|
297
|
+
hasAnyImage: false,
|
|
298
|
+
focusedId: null,
|
|
299
|
+
focusedItem: null,
|
|
300
|
+
isToolActive: false,
|
|
301
|
+
isImageSelectionActive: false,
|
|
302
|
+
hasWorkingChanges: false,
|
|
303
|
+
source: "committed",
|
|
304
|
+
}) === false,
|
|
305
|
+
"empty image state should report false",
|
|
306
|
+
);
|
|
307
|
+
assert(
|
|
308
|
+
hasAnyImageInViewState({
|
|
309
|
+
items: [
|
|
310
|
+
{
|
|
311
|
+
id: "img-1",
|
|
312
|
+
url: "blob:test",
|
|
313
|
+
opacity: 1,
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
hasAnyImage: true,
|
|
317
|
+
focusedId: "img-1",
|
|
318
|
+
focusedItem: {
|
|
319
|
+
id: "img-1",
|
|
320
|
+
url: "blob:test",
|
|
321
|
+
opacity: 1,
|
|
322
|
+
},
|
|
323
|
+
isToolActive: true,
|
|
324
|
+
isImageSelectionActive: true,
|
|
325
|
+
hasWorkingChanges: true,
|
|
326
|
+
source: "working",
|
|
327
|
+
}) === true,
|
|
328
|
+
"non-empty image state should report true",
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
291
332
|
function testContributionCompatibility() {
|
|
292
333
|
const imageCommandNames = createImageCommands({} as any).map(
|
|
293
334
|
(entry) => entry.command,
|
|
@@ -303,13 +344,12 @@ function testContributionCompatibility() {
|
|
|
303
344
|
const expectedImageCommands = [
|
|
304
345
|
"addImage",
|
|
305
346
|
"upsertImage",
|
|
306
|
-
"
|
|
307
|
-
"
|
|
308
|
-
"
|
|
347
|
+
"applyImageOperation",
|
|
348
|
+
"getImageViewState",
|
|
349
|
+
"setImageTransform",
|
|
350
|
+
"imageSessionReset",
|
|
309
351
|
"completeImages",
|
|
310
352
|
"exportUserCroppedImage",
|
|
311
|
-
"fitImageToArea",
|
|
312
|
-
"fitImageToDefaultArea",
|
|
313
353
|
"focusImage",
|
|
314
354
|
"removeImage",
|
|
315
355
|
"updateImage",
|
|
@@ -432,9 +472,10 @@ function main() {
|
|
|
432
472
|
testBridgeSelection();
|
|
433
473
|
testMaskOps();
|
|
434
474
|
testEdgeScale();
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
475
|
+
testFeaturePlacementProjection();
|
|
476
|
+
testVisibilityDsl();
|
|
477
|
+
testImageViewStateHelper();
|
|
478
|
+
testContributionCompatibility();
|
|
438
479
|
console.log("ok");
|
|
439
480
|
}
|
|
440
481
|
|