@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
@@ -17,6 +17,7 @@ const IMAGE_DEFAULT_CONTROL_CAPABILITIES = [
17
17
  "rotate",
18
18
  "scale",
19
19
  ];
20
+ const IMAGE_MOVE_SNAP_THRESHOLD_PX = 6;
20
21
  const IMAGE_CONTROL_DESCRIPTORS = [
21
22
  {
22
23
  key: "tl",
@@ -59,12 +60,17 @@ class ImageTool {
59
60
  this.renderSeq = 0;
60
61
  this.imageSpecs = [];
61
62
  this.overlaySpecs = [];
63
+ this.activeSnapX = null;
64
+ this.activeSnapY = null;
65
+ this.movingImageId = null;
66
+ this.hasRenderedSnapGuides = false;
62
67
  this.subscriptions = new subscriptions_1.SubscriptionBag();
63
68
  this.imageControlsByCapabilityKey = new Map();
64
69
  this.onToolActivated = (event) => {
65
70
  const before = this.isToolActive;
66
71
  this.syncToolActiveFromWorkbench(event.id);
67
72
  if (!this.isToolActive) {
73
+ this.endMoveSnapInteraction();
68
74
  this.setImageFocus(null, {
69
75
  syncCanvasSelection: true,
70
76
  skipRender: true,
@@ -110,6 +116,7 @@ class ImageTool {
110
116
  this.updateImages();
111
117
  };
112
118
  this.onSelectionCleared = () => {
119
+ this.endMoveSnapInteraction();
113
120
  this.setImageFocus(null, {
114
121
  syncCanvasSelection: false,
115
122
  skipRender: true,
@@ -118,6 +125,7 @@ class ImageTool {
118
125
  this.updateImages();
119
126
  };
120
127
  this.onSceneLayoutChanged = () => {
128
+ this.canvasService?.requestRenderAll();
121
129
  this.updateImages();
122
130
  };
123
131
  this.onSceneGeometryChanged = () => {
@@ -131,7 +139,11 @@ class ImageTool {
131
139
  const layerId = target?.data?.layerId;
132
140
  if (typeof id !== "string" || layerId !== layers_1.IMAGE_OBJECT_LAYER_ID)
133
141
  return;
142
+ if (this.movingImageId === id) {
143
+ this.applyMoveSnapToTarget(target);
144
+ }
134
145
  const frame = this.getFrameRect();
146
+ this.endMoveSnapInteraction();
135
147
  if (!frame.width || !frame.height)
136
148
  return;
137
149
  const center = target.getCenterPoint
@@ -195,6 +207,7 @@ class ImageTool {
195
207
  },
196
208
  ],
197
209
  }), { priority: 300 });
210
+ this.bindCanvasInteractionHandlers();
198
211
  this.subscriptions.on(context.eventBus, "tool:activated", this.onToolActivated);
199
212
  this.subscriptions.on(context.eventBus, "object:modified", this.onObjectModified);
200
213
  this.subscriptions.on(context.eventBus, "selection:created", this.onSelectionChanged);
@@ -238,6 +251,8 @@ class ImageTool {
238
251
  this.imageSpecs = [];
239
252
  this.overlaySpecs = [];
240
253
  this.imageControlsByCapabilityKey.clear();
254
+ this.endMoveSnapInteraction();
255
+ this.unbindCanvasInteractionHandlers();
241
256
  this.clearRenderedImages();
242
257
  this.renderProducerDisposable?.dispose();
243
258
  this.renderProducerDisposable = undefined;
@@ -247,6 +262,271 @@ class ImageTool {
247
262
  }
248
263
  this.context = undefined;
249
264
  }
265
+ bindCanvasInteractionHandlers() {
266
+ if (!this.canvasService || this.canvasObjectMovingHandler)
267
+ return;
268
+ this.canvasMouseUpHandler = (e) => {
269
+ const target = this.getActiveImageTarget(e?.target);
270
+ if (target &&
271
+ typeof target?.data?.id === "string" &&
272
+ target.data.id === this.movingImageId) {
273
+ this.applyMoveSnapToTarget(target);
274
+ }
275
+ this.endMoveSnapInteraction();
276
+ };
277
+ this.canvasObjectMovingHandler = (e) => {
278
+ this.handleCanvasObjectMoving(e);
279
+ };
280
+ this.canvasBeforeRenderHandler = () => {
281
+ this.handleCanvasBeforeRender();
282
+ };
283
+ this.canvasAfterRenderHandler = () => {
284
+ this.handleCanvasAfterRender();
285
+ };
286
+ this.canvasService.canvas.on("mouse:up", this.canvasMouseUpHandler);
287
+ this.canvasService.canvas.on("object:moving", this.canvasObjectMovingHandler);
288
+ this.canvasService.canvas.on("before:render", this.canvasBeforeRenderHandler);
289
+ this.canvasService.canvas.on("after:render", this.canvasAfterRenderHandler);
290
+ }
291
+ unbindCanvasInteractionHandlers() {
292
+ if (!this.canvasService)
293
+ return;
294
+ if (this.canvasMouseUpHandler) {
295
+ this.canvasService.canvas.off("mouse:up", this.canvasMouseUpHandler);
296
+ }
297
+ if (this.canvasObjectMovingHandler) {
298
+ this.canvasService.canvas.off("object:moving", this.canvasObjectMovingHandler);
299
+ }
300
+ if (this.canvasBeforeRenderHandler) {
301
+ this.canvasService.canvas.off("before:render", this.canvasBeforeRenderHandler);
302
+ }
303
+ if (this.canvasAfterRenderHandler) {
304
+ this.canvasService.canvas.off("after:render", this.canvasAfterRenderHandler);
305
+ }
306
+ this.canvasMouseUpHandler = undefined;
307
+ this.canvasObjectMovingHandler = undefined;
308
+ this.canvasBeforeRenderHandler = undefined;
309
+ this.canvasAfterRenderHandler = undefined;
310
+ }
311
+ getActiveImageTarget(target) {
312
+ if (!this.isToolActive)
313
+ return null;
314
+ if (!target)
315
+ return null;
316
+ if (target?.data?.layerId !== layers_1.IMAGE_OBJECT_LAYER_ID)
317
+ return null;
318
+ if (typeof target?.data?.id !== "string")
319
+ return null;
320
+ return target;
321
+ }
322
+ getTargetBoundsScene(target) {
323
+ if (!this.canvasService || !target)
324
+ return null;
325
+ const rawBounds = typeof target.getBoundingRect === "function"
326
+ ? target.getBoundingRect()
327
+ : {
328
+ left: Number(target.left || 0),
329
+ top: Number(target.top || 0),
330
+ width: Number(target.width || 0),
331
+ height: Number(target.height || 0),
332
+ };
333
+ return this.canvasService.toSceneRect({
334
+ left: Number(rawBounds.left || 0),
335
+ top: Number(rawBounds.top || 0),
336
+ width: Number(rawBounds.width || 0),
337
+ height: Number(rawBounds.height || 0),
338
+ });
339
+ }
340
+ getSnapThresholdScene(px) {
341
+ if (!this.canvasService)
342
+ return px;
343
+ return this.canvasService.toSceneLength(px);
344
+ }
345
+ pickSnapMatch(candidates) {
346
+ if (!candidates.length)
347
+ return null;
348
+ const snapThreshold = this.getSnapThresholdScene(IMAGE_MOVE_SNAP_THRESHOLD_PX);
349
+ let best = null;
350
+ candidates.forEach((candidate) => {
351
+ if (Math.abs(candidate.deltaScene) > snapThreshold)
352
+ return;
353
+ if (!best || Math.abs(candidate.deltaScene) < Math.abs(best.deltaScene)) {
354
+ best = candidate;
355
+ }
356
+ });
357
+ return best;
358
+ }
359
+ computeMoveSnapMatches(bounds, frame) {
360
+ if (!bounds || frame.width <= 0 || frame.height <= 0) {
361
+ return { x: null, y: null };
362
+ }
363
+ const xCandidates = [
364
+ {
365
+ axis: "x",
366
+ lineId: "frame-left",
367
+ kind: "edge",
368
+ lineScene: frame.left,
369
+ deltaScene: frame.left - bounds.left,
370
+ },
371
+ {
372
+ axis: "x",
373
+ lineId: "frame-center-x",
374
+ kind: "center",
375
+ lineScene: frame.left + frame.width / 2,
376
+ deltaScene: frame.left + frame.width / 2 - (bounds.left + bounds.width / 2),
377
+ },
378
+ {
379
+ axis: "x",
380
+ lineId: "frame-right",
381
+ kind: "edge",
382
+ lineScene: frame.left + frame.width,
383
+ deltaScene: frame.left + frame.width - (bounds.left + bounds.width),
384
+ },
385
+ ];
386
+ const yCandidates = [
387
+ {
388
+ axis: "y",
389
+ lineId: "frame-top",
390
+ kind: "edge",
391
+ lineScene: frame.top,
392
+ deltaScene: frame.top - bounds.top,
393
+ },
394
+ {
395
+ axis: "y",
396
+ lineId: "frame-center-y",
397
+ kind: "center",
398
+ lineScene: frame.top + frame.height / 2,
399
+ deltaScene: frame.top + frame.height / 2 - (bounds.top + bounds.height / 2),
400
+ },
401
+ {
402
+ axis: "y",
403
+ lineId: "frame-bottom",
404
+ kind: "edge",
405
+ lineScene: frame.top + frame.height,
406
+ deltaScene: frame.top + frame.height - (bounds.top + bounds.height),
407
+ },
408
+ ];
409
+ return {
410
+ x: this.pickSnapMatch(xCandidates),
411
+ y: this.pickSnapMatch(yCandidates),
412
+ };
413
+ }
414
+ areSnapMatchesEqual(a, b) {
415
+ if (!a && !b)
416
+ return true;
417
+ if (!a || !b)
418
+ return false;
419
+ return a.lineId === b.lineId && a.axis === b.axis && a.kind === b.kind;
420
+ }
421
+ updateSnapMatchState(nextX, nextY) {
422
+ const changed = !this.areSnapMatchesEqual(this.activeSnapX, nextX) ||
423
+ !this.areSnapMatchesEqual(this.activeSnapY, nextY);
424
+ this.activeSnapX = nextX;
425
+ this.activeSnapY = nextY;
426
+ if (changed) {
427
+ this.canvasService?.requestRenderAll();
428
+ }
429
+ }
430
+ clearSnapPreview() {
431
+ this.activeSnapX = null;
432
+ this.activeSnapY = null;
433
+ this.hasRenderedSnapGuides = false;
434
+ this.canvasService?.requestRenderAll();
435
+ }
436
+ endMoveSnapInteraction() {
437
+ this.movingImageId = null;
438
+ this.clearSnapPreview();
439
+ }
440
+ applyMoveSnapToTarget(target) {
441
+ if (!this.canvasService) {
442
+ return { x: null, y: null };
443
+ }
444
+ const frame = this.getFrameRect();
445
+ if (frame.width <= 0 || frame.height <= 0) {
446
+ return { x: null, y: null };
447
+ }
448
+ const bounds = this.getTargetBoundsScene(target);
449
+ const matches = this.computeMoveSnapMatches(bounds, frame);
450
+ const deltaScreenX = this.canvasService.toScreenLength(matches.x?.deltaScene ?? 0);
451
+ const deltaScreenY = this.canvasService.toScreenLength(matches.y?.deltaScene ?? 0);
452
+ if (deltaScreenX || deltaScreenY) {
453
+ target.set({
454
+ left: Number(target.left || 0) + deltaScreenX,
455
+ top: Number(target.top || 0) + deltaScreenY,
456
+ });
457
+ target.setCoords();
458
+ }
459
+ return matches;
460
+ }
461
+ handleCanvasBeforeRender() {
462
+ if (!this.canvasService)
463
+ return;
464
+ if (!this.hasRenderedSnapGuides && !this.activeSnapX && !this.activeSnapY) {
465
+ return;
466
+ }
467
+ this.canvasService.canvas.clearContext(this.canvasService.canvas.contextTop);
468
+ this.hasRenderedSnapGuides = false;
469
+ }
470
+ drawSnapGuideLine(from, to) {
471
+ if (!this.canvasService)
472
+ return;
473
+ const ctx = this.canvasService.canvas.contextTop;
474
+ if (!ctx)
475
+ return;
476
+ const color = this.getConfig("image.control.borderColor", "#1677ff") ||
477
+ "#1677ff";
478
+ ctx.save();
479
+ ctx.strokeStyle = color;
480
+ ctx.lineWidth = 1;
481
+ ctx.beginPath();
482
+ ctx.moveTo(from.x, from.y);
483
+ ctx.lineTo(to.x, to.y);
484
+ ctx.stroke();
485
+ ctx.restore();
486
+ }
487
+ handleCanvasAfterRender() {
488
+ if (!this.canvasService || !this.isImageEditingVisible()) {
489
+ return;
490
+ }
491
+ const frame = this.getFrameRect();
492
+ if (frame.width <= 0 || frame.height <= 0) {
493
+ return;
494
+ }
495
+ const frameScreen = this.getFrameRectScreen(frame);
496
+ let drew = false;
497
+ if (this.activeSnapX) {
498
+ const x = this.canvasService.toScreenPoint({
499
+ x: this.activeSnapX.lineScene,
500
+ y: frame.top,
501
+ }).x;
502
+ this.drawSnapGuideLine({ x, y: frameScreen.top }, { x, y: frameScreen.top + frameScreen.height });
503
+ drew = true;
504
+ }
505
+ if (this.activeSnapY) {
506
+ const y = this.canvasService.toScreenPoint({
507
+ x: frame.left,
508
+ y: this.activeSnapY.lineScene,
509
+ }).y;
510
+ this.drawSnapGuideLine({ x: frameScreen.left, y }, { x: frameScreen.left + frameScreen.width, y });
511
+ drew = true;
512
+ }
513
+ this.hasRenderedSnapGuides = drew;
514
+ }
515
+ handleCanvasObjectMoving(e) {
516
+ const target = this.getActiveImageTarget(e?.target);
517
+ if (!target || !this.canvasService)
518
+ return;
519
+ this.movingImageId =
520
+ typeof target?.data?.id === "string" ? target.data.id : null;
521
+ const frame = this.getFrameRect();
522
+ if (frame.width <= 0 || frame.height <= 0) {
523
+ this.endMoveSnapInteraction();
524
+ return;
525
+ }
526
+ const rawBounds = this.getTargetBoundsScene(target);
527
+ const matches = this.computeMoveSnapMatches(rawBounds, frame);
528
+ this.updateSnapMatchState(matches.x, matches.y);
529
+ }
250
530
  syncToolActiveFromWorkbench(fallbackId) {
251
531
  const wb = this.context?.services.get("WorkbenchService");
252
532
  const activeId = wb?.activeToolId;
@@ -864,33 +1144,9 @@ class ImageTool {
864
1144
  originY: "top",
865
1145
  fill: hatchFill,
866
1146
  opacity: patternFill ? 1 : 0.8,
867
- stroke: null,
868
- fillRule: "evenodd",
869
- selectable: false,
870
- evented: false,
871
- excludeFromExport: true,
872
- objectCaching: false,
873
- },
874
- },
875
- {
876
- id: "image.cropShapePath",
877
- type: "path",
878
- data: { id: "image.cropShapePath", zIndex: 6 },
879
- layout: {
880
- reference: "custom",
881
- referenceRect: frameRect,
882
- alignX: "start",
883
- alignY: "start",
884
- offsetX: shapeBounds.x,
885
- offsetY: shapeBounds.y,
886
- },
887
- props: {
888
- pathData: shapePathData,
889
- originX: "left",
890
- originY: "top",
891
- fill: "rgba(0,0,0,0)",
892
1147
  stroke: "rgba(255, 0, 0, 0.9)",
893
1148
  strokeWidth: this.canvasService?.toSceneLength(1) ?? 1,
1149
+ fillRule: "evenodd",
894
1150
  selectable: false,
895
1151
  evented: false,
896
1152
  excludeFromExport: true,
@@ -13,6 +13,7 @@ const DEFAULT_FONT_SIZE = 10;
13
13
  const DEFAULT_BACKGROUND_COLOR = "#f0f0f0";
14
14
  const DEFAULT_TEXT_COLOR = "#333333";
15
15
  const DEFAULT_LINE_COLOR = "#999999";
16
+ const RULER_DEBUG_KEY = "ruler.debug";
16
17
  const RULER_THICKNESS_MIN = 10;
17
18
  const RULER_THICKNESS_MAX = 100;
18
19
  const RULER_GAP_MIN = 0;
@@ -31,6 +32,7 @@ class RulerTool {
31
32
  this.textColor = DEFAULT_TEXT_COLOR;
32
33
  this.lineColor = DEFAULT_LINE_COLOR;
33
34
  this.fontSize = DEFAULT_FONT_SIZE;
35
+ this.debugEnabled = false;
34
36
  this.renderSeq = 0;
35
37
  this.numericProps = new Set(["thickness", "gap", "fontSize"]);
36
38
  this.specs = [];
@@ -72,7 +74,15 @@ class RulerTool {
72
74
  this.syncConfig(configService);
73
75
  configService.onAnyChange((e) => {
74
76
  let shouldUpdate = false;
75
- if (e.key.startsWith("ruler.")) {
77
+ if (e.key === RULER_DEBUG_KEY) {
78
+ this.debugEnabled = e.value === true;
79
+ this.log("config:update", {
80
+ key: e.key,
81
+ raw: e.value,
82
+ normalized: this.debugEnabled,
83
+ });
84
+ }
85
+ else if (e.key.startsWith("ruler.")) {
76
86
  const prop = e.key.split(".")[1];
77
87
  if (prop && prop in this) {
78
88
  if (this.numericProps.has(prop)) {
@@ -158,6 +168,12 @@ class RulerTool {
158
168
  max: RULER_FONT_SIZE_MAX,
159
169
  default: DEFAULT_FONT_SIZE,
160
170
  },
171
+ {
172
+ id: RULER_DEBUG_KEY,
173
+ type: "boolean",
174
+ label: "Ruler Debug Log",
175
+ default: false,
176
+ },
161
177
  ],
162
178
  [core_1.ContributionPointIds.COMMANDS]: [
163
179
  {
@@ -187,7 +203,12 @@ class RulerTool {
187
203
  ],
188
204
  };
189
205
  }
206
+ isDebugEnabled() {
207
+ return this.debugEnabled;
208
+ }
190
209
  log(step, payload) {
210
+ if (!this.isDebugEnabled())
211
+ return;
191
212
  if (payload) {
192
213
  console.debug(`[RulerTool] ${step}`, payload);
193
214
  return;
@@ -201,6 +222,8 @@ class RulerTool {
201
222
  this.textColor = configService.get("ruler.textColor", this.textColor);
202
223
  this.lineColor = configService.get("ruler.lineColor", this.lineColor);
203
224
  this.fontSize = this.toFiniteNumber(configService.get("ruler.fontSize", this.fontSize), DEFAULT_FONT_SIZE);
225
+ this.debugEnabled =
226
+ configService.get(RULER_DEBUG_KEY, this.debugEnabled) === true;
204
227
  this.log("config:loaded", {
205
228
  thickness: this.thickness,
206
229
  gap: this.gap,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LAYER_IDS = exports.FILM_LAYER_ID = exports.RULER_LAYER_ID = exports.FEATURE_OVERLAY_LAYER_ID = exports.DIELINE_LAYER_ID = exports.WHITE_INK_OVERLAY_LAYER_ID = exports.WHITE_INK_COVER_LAYER_ID = exports.WHITE_INK_OBJECT_LAYER_ID = exports.IMAGE_OVERLAY_LAYER_ID = exports.IMAGE_OBJECT_LAYER_ID = exports.BACKGROUND_LAYER_ID = void 0;
3
+ exports.LAYER_IDS = exports.FILM_LAYER_ID = exports.RULER_LAYER_ID = exports.FEATURE_OVERLAY_LAYER_ID = exports.FEATURE_DIELINE_LAYER_ID = exports.DIELINE_LAYER_ID = exports.WHITE_INK_OVERLAY_LAYER_ID = exports.WHITE_INK_COVER_LAYER_ID = exports.WHITE_INK_OBJECT_LAYER_ID = exports.IMAGE_OVERLAY_LAYER_ID = exports.IMAGE_OBJECT_LAYER_ID = exports.BACKGROUND_LAYER_ID = void 0;
4
4
  exports.BACKGROUND_LAYER_ID = "background";
5
5
  exports.IMAGE_OBJECT_LAYER_ID = "image.user";
6
6
  exports.IMAGE_OVERLAY_LAYER_ID = "image-overlay";
@@ -8,6 +8,7 @@ exports.WHITE_INK_OBJECT_LAYER_ID = "white-ink.user";
8
8
  exports.WHITE_INK_COVER_LAYER_ID = "white-ink.cover";
9
9
  exports.WHITE_INK_OVERLAY_LAYER_ID = "white-ink.overlay";
10
10
  exports.DIELINE_LAYER_ID = "dieline-overlay";
11
+ exports.FEATURE_DIELINE_LAYER_ID = "feature-dieline-overlay";
11
12
  exports.FEATURE_OVERLAY_LAYER_ID = "feature-overlay";
12
13
  exports.RULER_LAYER_ID = "ruler-overlay";
13
14
  exports.FILM_LAYER_ID = "overlay";
@@ -19,6 +20,7 @@ exports.LAYER_IDS = {
19
20
  whiteInkCover: exports.WHITE_INK_COVER_LAYER_ID,
20
21
  whiteInkOverlay: exports.WHITE_INK_OVERLAY_LAYER_ID,
21
22
  dieline: exports.DIELINE_LAYER_ID,
23
+ featureDieline: exports.FEATURE_DIELINE_LAYER_ID,
22
24
  featureOverlay: exports.FEATURE_OVERLAY_LAYER_ID,
23
25
  rulerOverlay: exports.RULER_LAYER_ID,
24
26
  filmOverlay: exports.FILM_LAYER_ID,
@@ -11,6 +11,7 @@ const commands_2 = require("../src/extensions/white-ink/commands");
11
11
  const config_2 = require("../src/extensions/white-ink/config");
12
12
  const commands_3 = require("../src/extensions/dieline/commands");
13
13
  const config_3 = require("../src/extensions/dieline/config");
14
+ const featureCoordinates_1 = require("../src/extensions/featureCoordinates");
14
15
  function assert(condition, message) {
15
16
  if (!condition)
16
17
  throw new Error(message);
@@ -91,6 +92,29 @@ function testEdgeScale() {
91
92
  assert(width === 140, `expected width 140, got ${width}`);
92
93
  assert(height === 80, `expected height 80, got ${height}`);
93
94
  }
95
+ function testFeaturePlacementProjection() {
96
+ const trimGeometry = {
97
+ x: 100,
98
+ y: 120,
99
+ width: 120,
100
+ height: 180,
101
+ };
102
+ const cutGeometry = {
103
+ x: 100,
104
+ y: 120,
105
+ width: 150,
106
+ height: 210,
107
+ };
108
+ const trimFeature = {
109
+ x: 0.82,
110
+ y: 0.68,
111
+ };
112
+ const trimCenter = (0, featureCoordinates_1.resolveFeaturePosition)(trimFeature, trimGeometry);
113
+ const cutFeature = (0, featureCoordinates_1.normalizePointInGeometry)(trimCenter, cutGeometry);
114
+ const cutCenter = (0, featureCoordinates_1.resolveFeaturePosition)(cutFeature, cutGeometry);
115
+ assert(Math.abs(trimCenter.x - cutCenter.x) < 1e-6, `expected projected feature x to stay fixed, got ${trimCenter.x} vs ${cutCenter.x}`);
116
+ assert(Math.abs(trimCenter.y - cutCenter.y) < 1e-6, `expected projected feature y to stay fixed, got ${trimCenter.y} vs ${cutCenter.y}`);
117
+ }
94
118
  function testVisibilityDsl() {
95
119
  const layers = new Map([
96
120
  ["ruler-overlay", { exists: true, objectCount: 2 }],
@@ -259,6 +283,7 @@ function main() {
259
283
  testBridgeSelection();
260
284
  testMaskOps();
261
285
  testEdgeScale();
286
+ testFeaturePlacementProjection();
262
287
  testVisibilityDsl();
263
288
  testContributionCompatibility();
264
289
  console.log("ok");
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @pooder/kit
2
2
 
3
+ ## 6.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - bugfix
8
+
9
+ ## 6.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - refactor the dieline feature and modify the image snap
14
+
3
15
  ## 6.1.2
4
16
 
5
17
  ### Patch Changes