@pooder/kit 1.0.0 → 3.0.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/CHANGELOG.md +22 -0
- package/dist/index.d.mts +250 -115
- package/dist/index.d.ts +250 -115
- package/dist/index.js +2177 -831
- package/dist/index.mjs +2182 -826
- package/package.json +3 -2
- package/src/CanvasService.ts +65 -0
- package/src/background.ts +230 -172
- package/src/coordinate.ts +49 -0
- package/src/dieline.ts +780 -421
- package/src/film.ts +194 -156
- package/src/geometry.ts +464 -244
- package/src/hole.ts +629 -413
- package/src/image.ts +504 -147
- package/src/index.ts +9 -7
- package/src/mirror.ts +128 -0
- package/src/ruler.ts +325 -239
- package/src/tracer.ts +372 -0
- package/src/white-ink.ts +373 -301
- package/tsconfig.json +13 -13
package/src/geometry.ts
CHANGED
|
@@ -1,244 +1,464 @@
|
|
|
1
|
-
import paper from
|
|
2
|
-
|
|
3
|
-
export interface HoleData {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface GeometryOptions {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
1
|
+
import paper from "paper";
|
|
2
|
+
|
|
3
|
+
export interface HoleData {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
innerRadius: number;
|
|
7
|
+
outerRadius: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface GeometryOptions {
|
|
11
|
+
shape: "rect" | "circle" | "ellipse" | "custom";
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
radius: number;
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
holes: Array<HoleData>;
|
|
18
|
+
pathData?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MaskGeometryOptions extends GeometryOptions {
|
|
22
|
+
canvasWidth: number;
|
|
23
|
+
canvasHeight: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initializes paper.js project if not already initialized.
|
|
28
|
+
*/
|
|
29
|
+
function ensurePaper(width: number, height: number) {
|
|
30
|
+
if (!paper.project) {
|
|
31
|
+
paper.setup(new paper.Size(width, height));
|
|
32
|
+
} else {
|
|
33
|
+
paper.view.viewSize = new paper.Size(width, height);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates the base dieline shape (Rect/Circle/Ellipse/Custom)
|
|
39
|
+
*/
|
|
40
|
+
function createBaseShape(options: GeometryOptions): paper.PathItem {
|
|
41
|
+
const { shape, width, height, radius, x, y, pathData } = options;
|
|
42
|
+
const center = new paper.Point(x, y);
|
|
43
|
+
|
|
44
|
+
if (shape === "rect") {
|
|
45
|
+
return new paper.Path.Rectangle({
|
|
46
|
+
point: [x - width / 2, y - height / 2],
|
|
47
|
+
size: [Math.max(0, width), Math.max(0, height)],
|
|
48
|
+
radius: Math.max(0, radius),
|
|
49
|
+
});
|
|
50
|
+
} else if (shape === "circle") {
|
|
51
|
+
const r = Math.min(width, height) / 2;
|
|
52
|
+
return new paper.Path.Circle({
|
|
53
|
+
center: center,
|
|
54
|
+
radius: Math.max(0, r),
|
|
55
|
+
});
|
|
56
|
+
} else if (shape === "ellipse") {
|
|
57
|
+
return new paper.Path.Ellipse({
|
|
58
|
+
center: center,
|
|
59
|
+
radius: [Math.max(0, width / 2), Math.max(0, height / 2)],
|
|
60
|
+
});
|
|
61
|
+
} else if (shape === "custom" && pathData) {
|
|
62
|
+
const path = new paper.Path();
|
|
63
|
+
path.pathData = pathData;
|
|
64
|
+
// Align center
|
|
65
|
+
path.position = center;
|
|
66
|
+
// Scale to match width/height if needed?
|
|
67
|
+
// For now, assume pathData is correct size, but we might want to support resizing.
|
|
68
|
+
// If width/height are provided and different from bounds, we could scale.
|
|
69
|
+
if (
|
|
70
|
+
width > 0 &&
|
|
71
|
+
height > 0 &&
|
|
72
|
+
path.bounds.width > 0 &&
|
|
73
|
+
path.bounds.height > 0
|
|
74
|
+
) {
|
|
75
|
+
path.scale(width / path.bounds.width, height / path.bounds.height);
|
|
76
|
+
}
|
|
77
|
+
return path;
|
|
78
|
+
} else {
|
|
79
|
+
// Fallback
|
|
80
|
+
return new paper.Path.Rectangle({
|
|
81
|
+
point: [x - width / 2, y - height / 2],
|
|
82
|
+
size: [Math.max(0, width), Math.max(0, height)],
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates an offset version of the base shape.
|
|
89
|
+
* For Rect/Circle, we can just adjust params.
|
|
90
|
+
* For Custom shapes, we need a true offset algorithm (Paper.js doesn't have a robust one built-in for all cases,
|
|
91
|
+
* but we can simulate it or use a simple scaling if offset is small, OR rely on a library like Clipper.js.
|
|
92
|
+
* However, since we want to avoid heavy deps, let's try a simple approach:
|
|
93
|
+
* If it's a simple shape, we re-create it.
|
|
94
|
+
* If it's custom, we unfortunately have to scale it for now as a poor-man's offset,
|
|
95
|
+
* UNLESS we implement a stroke expansion.
|
|
96
|
+
*
|
|
97
|
+
* Stroke Expansion Trick:
|
|
98
|
+
* 1. Create path
|
|
99
|
+
* 2. Set strokeWidth = offset * 2
|
|
100
|
+
* 3. Convert stroke to path (paper.js has path.expand())
|
|
101
|
+
* 4. Union original + expanded (for positive offset) or Subtract (for negative).
|
|
102
|
+
*/
|
|
103
|
+
function createOffsetShape(
|
|
104
|
+
options: GeometryOptions,
|
|
105
|
+
offset: number,
|
|
106
|
+
): paper.PathItem {
|
|
107
|
+
const { shape, width, height, radius, x, y, pathData } = options;
|
|
108
|
+
const center = new paper.Point(x, y);
|
|
109
|
+
|
|
110
|
+
if (shape === "rect" || shape === "circle" || shape === "ellipse") {
|
|
111
|
+
// For standard shapes, we can just adjust the dimensions
|
|
112
|
+
const offsetOptions = {
|
|
113
|
+
...options,
|
|
114
|
+
width: Math.max(0, width + offset * 2),
|
|
115
|
+
height: Math.max(0, height + offset * 2),
|
|
116
|
+
radius: radius === 0 ? 0 : Math.max(0, radius + offset),
|
|
117
|
+
};
|
|
118
|
+
return createBaseShape(offsetOptions);
|
|
119
|
+
} else if (shape === "custom" && pathData) {
|
|
120
|
+
const original = createBaseShape(options);
|
|
121
|
+
if (offset === 0) return original;
|
|
122
|
+
|
|
123
|
+
// Use Stroke Expansion for Offset
|
|
124
|
+
// Create a copy for stroking
|
|
125
|
+
const stroker = original.clone() as paper.Path;
|
|
126
|
+
stroker.strokeColor = new paper.Color("black");
|
|
127
|
+
stroker.strokeWidth = Math.abs(offset) * 2;
|
|
128
|
+
// Round join usually looks better for offsets
|
|
129
|
+
stroker.strokeJoin = "round";
|
|
130
|
+
stroker.strokeCap = "round";
|
|
131
|
+
|
|
132
|
+
// Expand stroke to path
|
|
133
|
+
// @ts-ignore - paper.js types might be missing expand depending on version, but it exists in recent versions
|
|
134
|
+
// If expand is not available, we might fallback to scaling.
|
|
135
|
+
// Assuming modern paper.js
|
|
136
|
+
let expanded: paper.Item;
|
|
137
|
+
try {
|
|
138
|
+
// @ts-ignore
|
|
139
|
+
expanded = stroker.expand({ stroke: true, fill: false, insert: false });
|
|
140
|
+
} catch (e) {
|
|
141
|
+
// Fallback if expand fails or not present
|
|
142
|
+
stroker.remove();
|
|
143
|
+
// Fallback to scaling (imperfect)
|
|
144
|
+
const scaleX =
|
|
145
|
+
(original.bounds.width + offset * 2) / original.bounds.width;
|
|
146
|
+
const scaleY =
|
|
147
|
+
(original.bounds.height + offset * 2) / original.bounds.height;
|
|
148
|
+
original.scale(scaleX, scaleY);
|
|
149
|
+
return original;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
stroker.remove();
|
|
153
|
+
|
|
154
|
+
// The expanded stroke is a "ring".
|
|
155
|
+
// For positive offset: Union(Original, Ring)
|
|
156
|
+
// For negative offset: Subtract(Original, Ring) ? No, that makes a hole.
|
|
157
|
+
// For negative offset: We want the "inner" boundary of the ring.
|
|
158
|
+
|
|
159
|
+
// Actually, expand() returns a Group or Path.
|
|
160
|
+
// If it's a closed path, the ring has an outer and inner boundary.
|
|
161
|
+
|
|
162
|
+
let result: paper.PathItem;
|
|
163
|
+
|
|
164
|
+
if (offset > 0) {
|
|
165
|
+
// @ts-ignore
|
|
166
|
+
result = original.unite(expanded);
|
|
167
|
+
} else {
|
|
168
|
+
// For negative offset (shrink), we want the original MINUS the stroke?
|
|
169
|
+
// No, the stroke is centered on the line.
|
|
170
|
+
// So the inner edge of the stroke is at -offset.
|
|
171
|
+
// We want the area INSIDE the inner edge.
|
|
172
|
+
// That is Original SUBTRACT the Ring?
|
|
173
|
+
// Yes, if we subtract the ring, we lose the border area.
|
|
174
|
+
// @ts-ignore
|
|
175
|
+
result = original.subtract(expanded);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Cleanup
|
|
179
|
+
original.remove();
|
|
180
|
+
expanded.remove();
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return createBaseShape(options);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Internal helper to generate the Dieline Shape (Paper Item).
|
|
190
|
+
* Caller is responsible for cleanup.
|
|
191
|
+
*/
|
|
192
|
+
function getDielineShape(options: GeometryOptions): paper.PathItem {
|
|
193
|
+
// 1. Create Base Shape
|
|
194
|
+
let mainShape = createBaseShape(options);
|
|
195
|
+
|
|
196
|
+
const { holes } = options;
|
|
197
|
+
|
|
198
|
+
if (holes && holes.length > 0) {
|
|
199
|
+
let lugsPath: paper.PathItem | null = null;
|
|
200
|
+
let cutsPath: paper.PathItem | null = null;
|
|
201
|
+
|
|
202
|
+
holes.forEach((hole) => {
|
|
203
|
+
// Create Lug (Outer Radius)
|
|
204
|
+
const lug = new paper.Path.Circle({
|
|
205
|
+
center: [hole.x, hole.y],
|
|
206
|
+
radius: hole.outerRadius,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// REMOVED: Intersects check. We want to process all holes defined in config.
|
|
210
|
+
// If a hole is completely outside, it might form an island, but that's better than missing it.
|
|
211
|
+
// Users can remove the hole if they don't want it.
|
|
212
|
+
|
|
213
|
+
// Create Cut (Inner Radius)
|
|
214
|
+
const cut = new paper.Path.Circle({
|
|
215
|
+
center: [hole.x, hole.y],
|
|
216
|
+
radius: hole.innerRadius,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Union Lugs
|
|
220
|
+
if (!lugsPath) {
|
|
221
|
+
lugsPath = lug;
|
|
222
|
+
} else {
|
|
223
|
+
try {
|
|
224
|
+
const temp = lugsPath.unite(lug);
|
|
225
|
+
lugsPath.remove();
|
|
226
|
+
lug.remove();
|
|
227
|
+
lugsPath = temp;
|
|
228
|
+
} catch (e) {
|
|
229
|
+
console.error("Geometry: Failed to unite lug", e);
|
|
230
|
+
// Keep previous lugsPath, ignore this one to prevent crash
|
|
231
|
+
lug.remove();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Union Cuts
|
|
236
|
+
if (!cutsPath) {
|
|
237
|
+
cutsPath = cut;
|
|
238
|
+
} else {
|
|
239
|
+
try {
|
|
240
|
+
const temp = cutsPath.unite(cut);
|
|
241
|
+
cutsPath.remove();
|
|
242
|
+
cut.remove();
|
|
243
|
+
cutsPath = temp;
|
|
244
|
+
} catch (e) {
|
|
245
|
+
console.error("Geometry: Failed to unite cut", e);
|
|
246
|
+
cut.remove();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// 2. Add Lugs to Main Shape (Union) - Additive Fusion
|
|
252
|
+
if (lugsPath) {
|
|
253
|
+
try {
|
|
254
|
+
const temp = mainShape.unite(lugsPath);
|
|
255
|
+
mainShape.remove();
|
|
256
|
+
// @ts-ignore
|
|
257
|
+
lugsPath.remove();
|
|
258
|
+
mainShape = temp;
|
|
259
|
+
} catch (e) {
|
|
260
|
+
console.error("Geometry: Failed to unite lugsPath to mainShape", e);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 3. Subtract Cuts from Main Shape (Difference)
|
|
265
|
+
if (cutsPath) {
|
|
266
|
+
try {
|
|
267
|
+
const temp = mainShape.subtract(cutsPath);
|
|
268
|
+
mainShape.remove();
|
|
269
|
+
// @ts-ignore
|
|
270
|
+
cutsPath.remove();
|
|
271
|
+
mainShape = temp;
|
|
272
|
+
} catch (e) {
|
|
273
|
+
console.error("Geometry: Failed to subtract cutsPath from mainShape", e);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return mainShape;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Generates the path data for the Dieline (Product Shape).
|
|
283
|
+
* Logic: (BaseShape UNION IntersectingLugs) SUBTRACT Cuts
|
|
284
|
+
*/
|
|
285
|
+
export function generateDielinePath(options: GeometryOptions): string {
|
|
286
|
+
ensurePaper(options.width * 2, options.height * 2);
|
|
287
|
+
paper.project.activeLayer.removeChildren();
|
|
288
|
+
|
|
289
|
+
const mainShape = getDielineShape(options);
|
|
290
|
+
|
|
291
|
+
const pathData = mainShape.pathData;
|
|
292
|
+
mainShape.remove();
|
|
293
|
+
|
|
294
|
+
return pathData;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Generates the path data for the Mask (Background Overlay).
|
|
299
|
+
* Logic: Canvas SUBTRACT ProductShape
|
|
300
|
+
*/
|
|
301
|
+
export function generateMaskPath(options: MaskGeometryOptions): string {
|
|
302
|
+
ensurePaper(options.canvasWidth, options.canvasHeight);
|
|
303
|
+
paper.project.activeLayer.removeChildren();
|
|
304
|
+
|
|
305
|
+
const { canvasWidth, canvasHeight } = options;
|
|
306
|
+
|
|
307
|
+
// 1. Canvas Background
|
|
308
|
+
const maskRect = new paper.Path.Rectangle({
|
|
309
|
+
point: [0, 0],
|
|
310
|
+
size: [canvasWidth, canvasHeight],
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// 2. Re-create Product Shape
|
|
314
|
+
const mainShape = getDielineShape(options);
|
|
315
|
+
|
|
316
|
+
// 3. Subtract Product from Mask
|
|
317
|
+
const finalMask = maskRect.subtract(mainShape);
|
|
318
|
+
|
|
319
|
+
maskRect.remove();
|
|
320
|
+
mainShape.remove();
|
|
321
|
+
|
|
322
|
+
const pathData = finalMask.pathData;
|
|
323
|
+
finalMask.remove();
|
|
324
|
+
|
|
325
|
+
return pathData;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Generates the path data for the Bleed Zone (Area between Original and Offset).
|
|
330
|
+
*/
|
|
331
|
+
export function generateBleedZonePath(
|
|
332
|
+
options: GeometryOptions,
|
|
333
|
+
offset: number,
|
|
334
|
+
): string {
|
|
335
|
+
// Ensure canvas is large enough
|
|
336
|
+
const maxDim = Math.max(options.width, options.height) + Math.abs(offset) * 4;
|
|
337
|
+
ensurePaper(maxDim, maxDim);
|
|
338
|
+
paper.project.activeLayer.removeChildren();
|
|
339
|
+
|
|
340
|
+
// 1. Original Shape
|
|
341
|
+
const shapeOriginal = getDielineShape(options);
|
|
342
|
+
|
|
343
|
+
// 2. Offset Shape
|
|
344
|
+
// We use createOffsetShape for more accurate offset (especially for custom shapes)
|
|
345
|
+
// But we still need to respect holes if they exist.
|
|
346
|
+
// getDielineShape handles holes.
|
|
347
|
+
// The issue is: do holes shrink/expand with bleed?
|
|
348
|
+
// Usually, bleed is only for the outer cut. Holes are internal cuts.
|
|
349
|
+
// Internal cuts usually also have bleed if they are die-cut, but maybe different direction?
|
|
350
|
+
// For simplicity, let's assume we offset the FINAL shape (including holes).
|
|
351
|
+
|
|
352
|
+
// Actually, getDielineShape calls createBaseShape.
|
|
353
|
+
// Let's modify generateBleedZonePath to use createOffsetShape logic if possible,
|
|
354
|
+
// OR just perform offset on the final shape result.
|
|
355
|
+
|
|
356
|
+
// The previous logic was: create base shape with adjusted width/height/radius.
|
|
357
|
+
// This works for Rect/Circle.
|
|
358
|
+
// For Custom, we need createOffsetShape.
|
|
359
|
+
|
|
360
|
+
let shapeOffset: paper.PathItem;
|
|
361
|
+
|
|
362
|
+
if (options.shape === "custom") {
|
|
363
|
+
// For custom shape, we offset the base shape first, then apply holes?
|
|
364
|
+
// Or offset the final result?
|
|
365
|
+
// Bleed is usually "outside" the cut line.
|
|
366
|
+
// If we have a donut, bleed is outside the outer circle AND inside the inner circle?
|
|
367
|
+
// Or just outside the outer?
|
|
368
|
+
// Let's assume bleed expands the solid area.
|
|
369
|
+
|
|
370
|
+
// So we take the final shape (Original) and expand it.
|
|
371
|
+
// We can use the same Stroke Expansion trick on the final shape.
|
|
372
|
+
|
|
373
|
+
// Since shapeOriginal is already the final shape (Base - Holes),
|
|
374
|
+
// we can try to offset it directly.
|
|
375
|
+
|
|
376
|
+
const stroker = shapeOriginal.clone() as paper.Path;
|
|
377
|
+
stroker.strokeColor = new paper.Color("black");
|
|
378
|
+
stroker.strokeWidth = Math.abs(offset) * 2;
|
|
379
|
+
stroker.strokeJoin = "round";
|
|
380
|
+
stroker.strokeCap = "round";
|
|
381
|
+
|
|
382
|
+
let expanded: paper.Item;
|
|
383
|
+
try {
|
|
384
|
+
// @ts-ignore
|
|
385
|
+
expanded = stroker.expand({ stroke: true, fill: false, insert: false });
|
|
386
|
+
} catch (e) {
|
|
387
|
+
// Fallback
|
|
388
|
+
stroker.remove();
|
|
389
|
+
shapeOffset = shapeOriginal.clone();
|
|
390
|
+
// scaling fallback...
|
|
391
|
+
return shapeOffset.pathData; // Fail gracefully
|
|
392
|
+
}
|
|
393
|
+
stroker.remove();
|
|
394
|
+
|
|
395
|
+
if (offset > 0) {
|
|
396
|
+
// @ts-ignore
|
|
397
|
+
shapeOffset = shapeOriginal.unite(expanded);
|
|
398
|
+
} else {
|
|
399
|
+
// @ts-ignore
|
|
400
|
+
shapeOffset = shapeOriginal.subtract(expanded);
|
|
401
|
+
}
|
|
402
|
+
expanded.remove();
|
|
403
|
+
} else {
|
|
404
|
+
// Legacy logic for standard shapes (still valid and fast)
|
|
405
|
+
// Adjust dimensions for offset
|
|
406
|
+
const offsetOptions: GeometryOptions = {
|
|
407
|
+
...options,
|
|
408
|
+
width: Math.max(0, options.width + offset * 2),
|
|
409
|
+
height: Math.max(0, options.height + offset * 2),
|
|
410
|
+
radius: options.radius === 0 ? 0 : Math.max(0, options.radius + offset),
|
|
411
|
+
};
|
|
412
|
+
shapeOffset = getDielineShape(offsetOptions);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// 3. Calculate Difference
|
|
416
|
+
let bleedZone: paper.PathItem;
|
|
417
|
+
if (offset > 0) {
|
|
418
|
+
bleedZone = shapeOffset.subtract(shapeOriginal);
|
|
419
|
+
} else {
|
|
420
|
+
bleedZone = shapeOriginal.subtract(shapeOffset);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const pathData = bleedZone.pathData;
|
|
424
|
+
|
|
425
|
+
// Cleanup
|
|
426
|
+
shapeOriginal.remove();
|
|
427
|
+
shapeOffset.remove();
|
|
428
|
+
bleedZone.remove();
|
|
429
|
+
|
|
430
|
+
return pathData;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Finds the nearest point on the Dieline geometry for a given target point.
|
|
435
|
+
* Used for constraining hole movement.
|
|
436
|
+
*/
|
|
437
|
+
export function getNearestPointOnDieline(
|
|
438
|
+
point: { x: number; y: number },
|
|
439
|
+
options: GeometryOptions,
|
|
440
|
+
): { x: number; y: number } {
|
|
441
|
+
ensurePaper(options.width * 2, options.height * 2);
|
|
442
|
+
paper.project.activeLayer.removeChildren();
|
|
443
|
+
|
|
444
|
+
const shape = createBaseShape(options);
|
|
445
|
+
|
|
446
|
+
const p = new paper.Point(point.x, point.y);
|
|
447
|
+
const nearest = shape.getNearestPoint(p);
|
|
448
|
+
|
|
449
|
+
const result = { x: nearest.x, y: nearest.y };
|
|
450
|
+
shape.remove();
|
|
451
|
+
|
|
452
|
+
return result;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export function getPathBounds(pathData: string): {
|
|
456
|
+
width: number;
|
|
457
|
+
height: number;
|
|
458
|
+
} {
|
|
459
|
+
const path = new paper.Path();
|
|
460
|
+
path.pathData = pathData;
|
|
461
|
+
const bounds = path.bounds;
|
|
462
|
+
path.remove();
|
|
463
|
+
return { width: bounds.width, height: bounds.height };
|
|
464
|
+
}
|