@pooder/kit 6.1.2 → 6.2.1

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.
Files changed (30) hide show
  1. package/.test-dist/src/extensions/background/BackgroundTool.js +177 -5
  2. package/.test-dist/src/extensions/constraintUtils.js +44 -0
  3. package/.test-dist/src/extensions/dieline/DielineTool.js +52 -409
  4. package/.test-dist/src/extensions/dieline/featureResolution.js +29 -0
  5. package/.test-dist/src/extensions/dieline/model.js +83 -0
  6. package/.test-dist/src/extensions/dieline/renderBuilder.js +227 -0
  7. package/.test-dist/src/extensions/feature/FeatureTool.js +156 -45
  8. package/.test-dist/src/extensions/featureCoordinates.js +21 -0
  9. package/.test-dist/src/extensions/featurePlacement.js +46 -0
  10. package/.test-dist/src/extensions/image/ImageTool.js +281 -25
  11. package/.test-dist/src/extensions/ruler/RulerTool.js +24 -1
  12. package/.test-dist/src/shared/constants/layers.js +3 -1
  13. package/.test-dist/tests/run.js +25 -0
  14. package/CHANGELOG.md +12 -0
  15. package/dist/index.d.mts +47 -13
  16. package/dist/index.d.ts +47 -13
  17. package/dist/index.js +1325 -977
  18. package/dist/index.mjs +1311 -966
  19. package/package.json +1 -1
  20. package/src/extensions/background/BackgroundTool.ts +264 -4
  21. package/src/extensions/dieline/DielineTool.ts +67 -548
  22. package/src/extensions/dieline/model.ts +165 -1
  23. package/src/extensions/dieline/renderBuilder.ts +301 -0
  24. package/src/extensions/feature/FeatureTool.ts +190 -47
  25. package/src/extensions/featureCoordinates.ts +35 -0
  26. package/src/extensions/featurePlacement.ts +118 -0
  27. package/src/extensions/image/ImageTool.ts +139 -157
  28. package/src/extensions/ruler/RulerTool.ts +24 -2
  29. package/src/shared/constants/layers.ts +2 -0
  30. package/tests/run.ts +37 -0
@@ -1 +1,165 @@
1
- export type { DielineGeometry, DielineState, LineStyle } from "./DielineTool";
1
+ import type { ConfigurationService } from "@pooder/core";
2
+ import { parseLengthToMm } from "../../units";
3
+ import {
4
+ DEFAULT_DIELINE_SHAPE,
5
+ DEFAULT_DIELINE_SHAPE_STYLE,
6
+ normalizeShapeStyle,
7
+ normalizeDielineShape,
8
+ } from "../dielineShape";
9
+ import type { DielineShape, DielineShapeStyle } from "../dielineShape";
10
+ import { readSizeState } from "../../shared/scene/sceneLayoutModel";
11
+ import type { DielineFeature } from "../geometry";
12
+
13
+ export interface DielineGeometry {
14
+ shape: DielineShape;
15
+ shapeStyle: DielineShapeStyle;
16
+ unit: "px";
17
+ x: number;
18
+ y: number;
19
+ width: number;
20
+ height: number;
21
+ radius: number;
22
+ offset: number;
23
+ borderLength?: number;
24
+ scale?: number;
25
+ strokeWidth?: number;
26
+ pathData?: string;
27
+ customSourceWidthPx?: number;
28
+ customSourceHeightPx?: number;
29
+ }
30
+
31
+ export interface LineStyle {
32
+ width: number;
33
+ color: string;
34
+ dashLength: number;
35
+ style: "solid" | "dashed" | "hidden";
36
+ }
37
+
38
+ export interface DielineState {
39
+ shape: DielineShape;
40
+ shapeStyle: DielineShapeStyle;
41
+ width: number;
42
+ height: number;
43
+ radius: number;
44
+ offset: number;
45
+ padding: number | string;
46
+ mainLine: LineStyle;
47
+ offsetLine: LineStyle;
48
+ insideColor: string;
49
+ showBleedLines: boolean;
50
+ features: DielineFeature[];
51
+ pathData?: string;
52
+ customSourceWidthPx?: number;
53
+ customSourceHeightPx?: number;
54
+ }
55
+
56
+ export function createDefaultDielineState(): DielineState {
57
+ return {
58
+ shape: DEFAULT_DIELINE_SHAPE,
59
+ shapeStyle: { ...DEFAULT_DIELINE_SHAPE_STYLE },
60
+ width: 500,
61
+ height: 500,
62
+ radius: 0,
63
+ offset: 0,
64
+ padding: 140,
65
+ mainLine: {
66
+ width: 2.7,
67
+ color: "#FF0000",
68
+ dashLength: 5,
69
+ style: "solid",
70
+ },
71
+ offsetLine: {
72
+ width: 2.7,
73
+ color: "#FF0000",
74
+ dashLength: 5,
75
+ style: "solid",
76
+ },
77
+ insideColor: "rgba(0,0,0,0)",
78
+ showBleedLines: true,
79
+ features: [],
80
+ };
81
+ }
82
+
83
+ export function readDielineState(
84
+ configService: ConfigurationService,
85
+ fallback?: Partial<DielineState>,
86
+ ): DielineState {
87
+ const base = createDefaultDielineState();
88
+ if (fallback) {
89
+ Object.assign(base, fallback);
90
+ if (fallback.mainLine) {
91
+ base.mainLine = { ...base.mainLine, ...fallback.mainLine };
92
+ }
93
+ if (fallback.offsetLine) {
94
+ base.offsetLine = { ...base.offsetLine, ...fallback.offsetLine };
95
+ }
96
+ if (fallback.shapeStyle) {
97
+ base.shapeStyle = normalizeShapeStyle(fallback.shapeStyle, base.shapeStyle);
98
+ }
99
+ }
100
+
101
+ const sizeState = readSizeState(configService);
102
+ const sourceWidth = Number(configService.get("dieline.customSourceWidthPx", 0));
103
+ const sourceHeight = Number(
104
+ configService.get("dieline.customSourceHeightPx", 0),
105
+ );
106
+
107
+ return {
108
+ ...base,
109
+ shape: normalizeDielineShape(
110
+ configService.get("dieline.shape", base.shape),
111
+ base.shape,
112
+ ),
113
+ shapeStyle: normalizeShapeStyle(
114
+ configService.get("dieline.shapeStyle", base.shapeStyle),
115
+ base.shapeStyle,
116
+ ),
117
+ width: sizeState.actualWidthMm,
118
+ height: sizeState.actualHeightMm,
119
+ radius: parseLengthToMm(configService.get("dieline.radius", base.radius), "mm"),
120
+ padding: sizeState.viewPadding,
121
+ offset:
122
+ sizeState.cutMode === "outset"
123
+ ? sizeState.cutMarginMm
124
+ : sizeState.cutMode === "inset"
125
+ ? -sizeState.cutMarginMm
126
+ : 0,
127
+ mainLine: {
128
+ width: configService.get("dieline.strokeWidth", base.mainLine.width),
129
+ color: configService.get("dieline.strokeColor", base.mainLine.color),
130
+ dashLength: configService.get(
131
+ "dieline.dashLength",
132
+ base.mainLine.dashLength,
133
+ ),
134
+ style: configService.get("dieline.style", base.mainLine.style),
135
+ },
136
+ offsetLine: {
137
+ width: configService.get(
138
+ "dieline.offsetStrokeWidth",
139
+ base.offsetLine.width,
140
+ ),
141
+ color: configService.get(
142
+ "dieline.offsetStrokeColor",
143
+ base.offsetLine.color,
144
+ ),
145
+ dashLength: configService.get(
146
+ "dieline.offsetDashLength",
147
+ base.offsetLine.dashLength,
148
+ ),
149
+ style: configService.get("dieline.offsetStyle", base.offsetLine.style),
150
+ },
151
+ insideColor: configService.get("dieline.insideColor", base.insideColor),
152
+ showBleedLines: configService.get(
153
+ "dieline.showBleedLines",
154
+ base.showBleedLines,
155
+ ),
156
+ features: configService.get("dieline.features", base.features),
157
+ pathData: configService.get("dieline.pathData", base.pathData),
158
+ customSourceWidthPx:
159
+ Number.isFinite(sourceWidth) && sourceWidth > 0 ? sourceWidth : undefined,
160
+ customSourceHeightPx:
161
+ Number.isFinite(sourceHeight) && sourceHeight > 0
162
+ ? sourceHeight
163
+ : undefined,
164
+ };
165
+ }
@@ -0,0 +1,301 @@
1
+ import type { Pattern } from "fabric";
2
+ import type { RenderEffectSpec, RenderObjectSpec, VisibilityExpr } from "../../services";
3
+ import type { SceneLayoutSnapshot } from "../../shared/scene/sceneLayoutModel";
4
+ import { generateBleedZonePath, generateDielinePath } from "../geometry";
5
+ import {
6
+ projectPlacedFeatures,
7
+ resolveFeaturePlacements,
8
+ } from "../featurePlacement";
9
+ import { IMAGE_OBJECT_LAYER_ID } from "../../shared/constants/layers";
10
+ import type { DielineState } from "./model";
11
+
12
+ interface DielineRenderIds {
13
+ inside: string;
14
+ bleedZone: string;
15
+ offsetBorder: string;
16
+ border: string;
17
+ clip: string;
18
+ clipSource: string;
19
+ }
20
+
21
+ export interface DielineRenderBundle {
22
+ specs: RenderObjectSpec[];
23
+ effects: RenderEffectSpec[];
24
+ }
25
+
26
+ export interface DielineRenderOptions {
27
+ state: DielineState;
28
+ sceneLayout: SceneLayoutSnapshot;
29
+ canvasWidth: number;
30
+ canvasHeight: number;
31
+ hasImages: boolean;
32
+ ids?: Partial<DielineRenderIds>;
33
+ createHatchPattern?: (color: string) => Pattern | undefined;
34
+ includeImageClipEffect?: boolean;
35
+ clipTargetPassIds?: string[];
36
+ clipVisibility?: VisibilityExpr;
37
+ }
38
+
39
+ const DEFAULT_IDS: DielineRenderIds = {
40
+ inside: "dieline.inside",
41
+ bleedZone: "dieline.bleed-zone",
42
+ offsetBorder: "dieline.offset-border",
43
+ border: "dieline.border",
44
+ clip: "dieline.clip.image",
45
+ clipSource: "dieline.effect.clip-path",
46
+ };
47
+
48
+ export function buildDielineRenderBundle(
49
+ options: DielineRenderOptions,
50
+ ): DielineRenderBundle {
51
+ const ids = { ...DEFAULT_IDS, ...(options.ids || {}) };
52
+ const {
53
+ state,
54
+ sceneLayout,
55
+ canvasWidth,
56
+ canvasHeight,
57
+ hasImages,
58
+ createHatchPattern,
59
+ includeImageClipEffect = true,
60
+ clipTargetPassIds = [IMAGE_OBJECT_LAYER_ID],
61
+ clipVisibility,
62
+ } = options;
63
+ const { shape, shapeStyle, radius, mainLine, offsetLine, insideColor } = state;
64
+
65
+ const scale = sceneLayout.scale;
66
+ const cx = sceneLayout.trimRect.centerX;
67
+ const cy = sceneLayout.trimRect.centerY;
68
+ const visualWidth = sceneLayout.trimRect.width;
69
+ const visualHeight = sceneLayout.trimRect.height;
70
+ const visualRadius = radius * scale;
71
+ const cutW = sceneLayout.cutRect.width;
72
+ const cutH = sceneLayout.cutRect.height;
73
+ const visualOffset = (cutW - visualWidth) / 2;
74
+ const cutR =
75
+ visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
76
+ const placements = resolveFeaturePlacements(state.features || [], {
77
+ shape,
78
+ shapeStyle,
79
+ pathData: state.pathData,
80
+ customSourceWidthPx: state.customSourceWidthPx,
81
+ customSourceHeightPx: state.customSourceHeightPx,
82
+ canvasWidth,
83
+ canvasHeight,
84
+ x: cx,
85
+ y: cy,
86
+ width: visualWidth,
87
+ height: visualHeight,
88
+ radius: visualRadius,
89
+ scale,
90
+ });
91
+ const absoluteFeatures = projectPlacedFeatures(
92
+ placements,
93
+ {
94
+ x: cx,
95
+ y: cy,
96
+ width: visualWidth,
97
+ height: visualHeight,
98
+ },
99
+ scale,
100
+ );
101
+ const cutFeatures = projectPlacedFeatures(
102
+ placements.filter((placement) => !placement.feature.skipCut),
103
+ {
104
+ x: cx,
105
+ y: cy,
106
+ width: cutW,
107
+ height: cutH,
108
+ },
109
+ scale,
110
+ );
111
+
112
+ const common = {
113
+ shape,
114
+ shapeStyle,
115
+ pathData: state.pathData,
116
+ customSourceWidthPx: state.customSourceWidthPx,
117
+ customSourceHeightPx: state.customSourceHeightPx,
118
+ canvasWidth,
119
+ canvasHeight,
120
+ };
121
+
122
+ const specs: RenderObjectSpec[] = [];
123
+
124
+ if (
125
+ insideColor &&
126
+ insideColor !== "transparent" &&
127
+ insideColor !== "rgba(0,0,0,0)" &&
128
+ !hasImages
129
+ ) {
130
+ specs.push({
131
+ id: ids.inside,
132
+ type: "path",
133
+ space: "screen",
134
+ data: { id: ids.inside, type: "dieline" },
135
+ props: {
136
+ pathData: generateDielinePath({
137
+ ...common,
138
+ width: cutW,
139
+ height: cutH,
140
+ radius: cutR,
141
+ x: cx,
142
+ y: cy,
143
+ features: cutFeatures,
144
+ }),
145
+ fill: insideColor,
146
+ stroke: null,
147
+ selectable: false,
148
+ evented: false,
149
+ originX: "left",
150
+ originY: "top",
151
+ },
152
+ });
153
+ }
154
+
155
+ if (Math.abs(visualOffset) > 0.0001) {
156
+ const trimPathInput = {
157
+ ...common,
158
+ width: visualWidth,
159
+ height: visualHeight,
160
+ radius: visualRadius,
161
+ x: cx,
162
+ y: cy,
163
+ features: cutFeatures,
164
+ };
165
+ const cutPathInput = {
166
+ ...common,
167
+ width: cutW,
168
+ height: cutH,
169
+ radius: cutR,
170
+ x: cx,
171
+ y: cy,
172
+ features: cutFeatures,
173
+ };
174
+
175
+ if (state.showBleedLines !== false) {
176
+ const pattern = createHatchPattern?.(mainLine.color);
177
+ if (pattern) {
178
+ specs.push({
179
+ id: ids.bleedZone,
180
+ type: "path",
181
+ space: "screen",
182
+ data: { id: ids.bleedZone, type: "dieline" },
183
+ props: {
184
+ pathData: generateBleedZonePath(
185
+ trimPathInput,
186
+ cutPathInput,
187
+ visualOffset,
188
+ ),
189
+ fill: pattern,
190
+ stroke: null,
191
+ selectable: false,
192
+ evented: false,
193
+ objectCaching: false,
194
+ originX: "left",
195
+ originY: "top",
196
+ },
197
+ });
198
+ }
199
+ }
200
+
201
+ specs.push({
202
+ id: ids.offsetBorder,
203
+ type: "path",
204
+ space: "screen",
205
+ data: { id: ids.offsetBorder, type: "dieline" },
206
+ props: {
207
+ pathData: generateDielinePath(cutPathInput),
208
+ fill: null,
209
+ stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
210
+ strokeWidth: offsetLine.width,
211
+ strokeDashArray:
212
+ offsetLine.style === "dashed"
213
+ ? [offsetLine.dashLength, offsetLine.dashLength]
214
+ : undefined,
215
+ selectable: false,
216
+ evented: false,
217
+ originX: "left",
218
+ originY: "top",
219
+ },
220
+ });
221
+ }
222
+
223
+ specs.push({
224
+ id: ids.border,
225
+ type: "path",
226
+ space: "screen",
227
+ data: { id: ids.border, type: "dieline" },
228
+ props: {
229
+ pathData: generateDielinePath({
230
+ ...common,
231
+ width: visualWidth,
232
+ height: visualHeight,
233
+ radius: visualRadius,
234
+ x: cx,
235
+ y: cy,
236
+ features: absoluteFeatures,
237
+ }),
238
+ fill: "transparent",
239
+ stroke: mainLine.style === "hidden" ? null : mainLine.color,
240
+ strokeWidth: mainLine.width,
241
+ strokeDashArray:
242
+ mainLine.style === "dashed"
243
+ ? [mainLine.dashLength, mainLine.dashLength]
244
+ : undefined,
245
+ selectable: false,
246
+ evented: false,
247
+ originX: "left",
248
+ originY: "top",
249
+ },
250
+ });
251
+
252
+ if (!includeImageClipEffect) {
253
+ return { specs, effects: [] };
254
+ }
255
+
256
+ const clipPathData = generateDielinePath({
257
+ ...common,
258
+ width: cutW,
259
+ height: cutH,
260
+ radius: cutR,
261
+ x: cx,
262
+ y: cy,
263
+ features: cutFeatures,
264
+ });
265
+
266
+ if (!clipPathData) {
267
+ return { specs, effects: [] };
268
+ }
269
+
270
+ return {
271
+ specs,
272
+ effects: [
273
+ {
274
+ type: "clipPath",
275
+ id: ids.clip,
276
+ visibility: clipVisibility,
277
+ targetPassIds: clipTargetPassIds,
278
+ source: {
279
+ id: ids.clipSource,
280
+ type: "path",
281
+ space: "screen",
282
+ data: {
283
+ id: ids.clipSource,
284
+ type: "dieline-effect",
285
+ effect: "clipPath",
286
+ },
287
+ props: {
288
+ pathData: clipPathData,
289
+ fill: "#000000",
290
+ stroke: null,
291
+ originX: "left",
292
+ originY: "top",
293
+ selectable: false,
294
+ evented: false,
295
+ excludeFromExport: true,
296
+ },
297
+ },
298
+ },
299
+ ],
300
+ };
301
+ }