@pooder/kit 6.2.0 → 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.
package/dist/index.mjs CHANGED
@@ -4582,6 +4582,259 @@ function readDielineState(configService, fallback) {
4582
4582
  };
4583
4583
  }
4584
4584
 
4585
+ // src/extensions/constraints.ts
4586
+ var ConstraintRegistry = class {
4587
+ static register(type, handler) {
4588
+ this.handlers.set(type, handler);
4589
+ }
4590
+ static apply(x, y, feature, context, constraints) {
4591
+ const list = constraints || feature.constraints;
4592
+ if (!list || list.length === 0) {
4593
+ return { x, y };
4594
+ }
4595
+ let currentX = x;
4596
+ let currentY = y;
4597
+ for (const constraint of list) {
4598
+ const handler = this.handlers.get(constraint.type);
4599
+ if (handler) {
4600
+ const result = handler(currentX, currentY, feature, context, constraint.params || {});
4601
+ currentX = result.x;
4602
+ currentY = result.y;
4603
+ }
4604
+ }
4605
+ return { x: currentX, y: currentY };
4606
+ }
4607
+ };
4608
+ ConstraintRegistry.handlers = /* @__PURE__ */ new Map();
4609
+ var pathConstraint = (x, y, feature, context, params) => {
4610
+ const { dielineWidth, dielineHeight, geometry } = context;
4611
+ if (!geometry) return { x, y };
4612
+ const minX = geometry.x - geometry.width / 2;
4613
+ const minY = geometry.y - geometry.height / 2;
4614
+ const absX = minX + x * geometry.width;
4615
+ const absY = minY + y * geometry.height;
4616
+ const nearest = getNearestPointOnDieline(
4617
+ { x: absX, y: absY },
4618
+ geometry
4619
+ );
4620
+ let finalX = nearest.x;
4621
+ let finalY = nearest.y;
4622
+ const hasOffsetParams = params.minOffset !== void 0 || params.maxOffset !== void 0;
4623
+ if (hasOffsetParams && nearest.normal) {
4624
+ const dx = absX - nearest.x;
4625
+ const dy = absY - nearest.y;
4626
+ const nx2 = nearest.normal.x;
4627
+ const ny2 = nearest.normal.y;
4628
+ const dist = dx * nx2 + dy * ny2;
4629
+ const scale = dielineWidth > 0 ? geometry.width / dielineWidth : 1;
4630
+ const rawMin = params.minOffset !== void 0 ? params.minOffset : 0;
4631
+ const rawMax = params.maxOffset !== void 0 ? params.maxOffset : 0;
4632
+ const minOffset = rawMin * scale;
4633
+ const maxOffset = rawMax * scale;
4634
+ const clampedDist = Math.max(minOffset, Math.min(dist, maxOffset));
4635
+ finalX = nearest.x + nx2 * clampedDist;
4636
+ finalY = nearest.y + ny2 * clampedDist;
4637
+ }
4638
+ const nx = geometry.width > 0 ? (finalX - minX) / geometry.width : 0.5;
4639
+ const ny = geometry.height > 0 ? (finalY - minY) / geometry.height : 0.5;
4640
+ return { x: nx, y: ny };
4641
+ };
4642
+ var edgeConstraint = (x, y, feature, context, params) => {
4643
+ const { dielineWidth, dielineHeight } = context;
4644
+ const allowedEdges = params.allowedEdges || [
4645
+ "top",
4646
+ "bottom",
4647
+ "left",
4648
+ "right"
4649
+ ];
4650
+ const confine = params.confine || false;
4651
+ const offset = params.offset || 0;
4652
+ const distances = [];
4653
+ if (allowedEdges.includes("top"))
4654
+ distances.push({ edge: "top", dist: y * dielineHeight });
4655
+ if (allowedEdges.includes("bottom"))
4656
+ distances.push({ edge: "bottom", dist: (1 - y) * dielineHeight });
4657
+ if (allowedEdges.includes("left"))
4658
+ distances.push({ edge: "left", dist: x * dielineWidth });
4659
+ if (allowedEdges.includes("right"))
4660
+ distances.push({ edge: "right", dist: (1 - x) * dielineWidth });
4661
+ if (distances.length === 0) return { x, y };
4662
+ distances.sort((a, b) => a.dist - b.dist);
4663
+ const nearest = distances[0].edge;
4664
+ let newX = x;
4665
+ let newY = y;
4666
+ const fw = feature.width || 0;
4667
+ const fh = feature.height || 0;
4668
+ switch (nearest) {
4669
+ case "top":
4670
+ newY = 0 + offset / dielineHeight;
4671
+ if (confine) {
4672
+ const minX = fw / 2 / dielineWidth;
4673
+ const maxX = 1 - minX;
4674
+ newX = Math.max(minX, Math.min(newX, maxX));
4675
+ }
4676
+ break;
4677
+ case "bottom":
4678
+ newY = 1 - offset / dielineHeight;
4679
+ if (confine) {
4680
+ const minX = fw / 2 / dielineWidth;
4681
+ const maxX = 1 - minX;
4682
+ newX = Math.max(minX, Math.min(newX, maxX));
4683
+ }
4684
+ break;
4685
+ case "left":
4686
+ newX = 0 + offset / dielineWidth;
4687
+ if (confine) {
4688
+ const minY = fh / 2 / dielineHeight;
4689
+ const maxY = 1 - minY;
4690
+ newY = Math.max(minY, Math.min(newY, maxY));
4691
+ }
4692
+ break;
4693
+ case "right":
4694
+ newX = 1 - offset / dielineWidth;
4695
+ if (confine) {
4696
+ const minY = fh / 2 / dielineHeight;
4697
+ const maxY = 1 - minY;
4698
+ newY = Math.max(minY, Math.min(newY, maxY));
4699
+ }
4700
+ break;
4701
+ }
4702
+ return { x: newX, y: newY };
4703
+ };
4704
+ var internalConstraint = (x, y, feature, context, params) => {
4705
+ const { dielineWidth, dielineHeight } = context;
4706
+ const margin = params.margin || 0;
4707
+ const fw = feature.width || 0;
4708
+ const fh = feature.height || 0;
4709
+ const minX = (margin + fw / 2) / dielineWidth;
4710
+ const maxX = 1 - (margin + fw / 2) / dielineWidth;
4711
+ const minY = (margin + fh / 2) / dielineHeight;
4712
+ const maxY = 1 - (margin + fh / 2) / dielineHeight;
4713
+ const clampedX = minX > maxX ? 0.5 : Math.max(minX, Math.min(x, maxX));
4714
+ const clampedY = minY > maxY ? 0.5 : Math.max(minY, Math.min(y, maxY));
4715
+ return { x: clampedX, y: clampedY };
4716
+ };
4717
+ var tangentBottomConstraint = (x, y, feature, context, params) => {
4718
+ const { dielineWidth, dielineHeight } = context;
4719
+ const gap = params.gap || 0;
4720
+ const confineX = params.confineX !== false;
4721
+ const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
4722
+ const newY = 1 + (extentY + gap) / dielineHeight;
4723
+ let newX = x;
4724
+ if (confineX) {
4725
+ const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
4726
+ const minX = extentX / dielineWidth;
4727
+ const maxX = 1 - extentX / dielineWidth;
4728
+ newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
4729
+ }
4730
+ return { x: newX, y: newY };
4731
+ };
4732
+ var lowestTangentConstraint = (x, y, feature, context, params) => {
4733
+ const { dielineWidth, dielineHeight, geometry } = context;
4734
+ if (!geometry) return { x, y };
4735
+ const lowest = getLowestPointOnDieline(geometry);
4736
+ const minY = geometry.y - geometry.height / 2;
4737
+ const normY = (lowest.y - minY) / geometry.height;
4738
+ const gap = params.gap || 0;
4739
+ const confineX = params.confineX !== false;
4740
+ const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
4741
+ const newY = normY + (extentY + gap) / dielineHeight;
4742
+ let newX = x;
4743
+ if (confineX) {
4744
+ const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
4745
+ const minX = extentX / dielineWidth;
4746
+ const maxX = 1 - extentX / dielineWidth;
4747
+ newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
4748
+ }
4749
+ return { x: newX, y: newY };
4750
+ };
4751
+ ConstraintRegistry.register("path", pathConstraint);
4752
+ ConstraintRegistry.register("edge", edgeConstraint);
4753
+ ConstraintRegistry.register("internal", internalConstraint);
4754
+ ConstraintRegistry.register("tangent-bottom", tangentBottomConstraint);
4755
+ ConstraintRegistry.register("lowest-tangent", lowestTangentConstraint);
4756
+
4757
+ // src/extensions/featureCoordinates.ts
4758
+ function resolveFeaturePosition2(feature, geometry) {
4759
+ const { x, y, width, height } = geometry;
4760
+ const left = x - width / 2;
4761
+ const top = y - height / 2;
4762
+ return {
4763
+ x: left + feature.x * width,
4764
+ y: top + feature.y * height
4765
+ };
4766
+ }
4767
+ function normalizePointInGeometry(point, geometry) {
4768
+ const left = geometry.x - geometry.width / 2;
4769
+ const top = geometry.y - geometry.height / 2;
4770
+ return {
4771
+ x: geometry.width > 0 ? (point.x - left) / geometry.width : 0.5,
4772
+ y: geometry.height > 0 ? (point.y - top) / geometry.height : 0.5
4773
+ };
4774
+ }
4775
+
4776
+ // src/extensions/featurePlacement.ts
4777
+ function scaleFeatureForRender(feature, scale, x, y) {
4778
+ return {
4779
+ ...feature,
4780
+ x,
4781
+ y,
4782
+ width: feature.width !== void 0 ? feature.width * scale : void 0,
4783
+ height: feature.height !== void 0 ? feature.height * scale : void 0,
4784
+ radius: feature.radius !== void 0 ? feature.radius * scale : void 0
4785
+ };
4786
+ }
4787
+ function resolveFeaturePlacements(features, geometry) {
4788
+ const dielineWidth = geometry.scale > 0 ? geometry.width / geometry.scale : geometry.width;
4789
+ const dielineHeight = geometry.scale > 0 ? geometry.height / geometry.scale : geometry.height;
4790
+ return (features || []).map((feature) => {
4791
+ var _a;
4792
+ const activeConstraints = (_a = feature.constraints) == null ? void 0 : _a.filter(
4793
+ (constraint) => !constraint.validateOnly
4794
+ );
4795
+ const constrained = ConstraintRegistry.apply(
4796
+ feature.x,
4797
+ feature.y,
4798
+ feature,
4799
+ {
4800
+ dielineWidth,
4801
+ dielineHeight,
4802
+ geometry
4803
+ },
4804
+ activeConstraints
4805
+ );
4806
+ const center = resolveFeaturePosition2(
4807
+ {
4808
+ ...feature,
4809
+ x: constrained.x,
4810
+ y: constrained.y
4811
+ },
4812
+ geometry
4813
+ );
4814
+ return {
4815
+ feature,
4816
+ normalizedX: constrained.x,
4817
+ normalizedY: constrained.y,
4818
+ centerX: center.x,
4819
+ centerY: center.y
4820
+ };
4821
+ });
4822
+ }
4823
+ function projectPlacedFeatures(placements, geometry, scale) {
4824
+ return placements.map((placement) => {
4825
+ const normalized = normalizePointInGeometry(
4826
+ { x: placement.centerX, y: placement.centerY },
4827
+ geometry
4828
+ );
4829
+ return scaleFeatureForRender(
4830
+ placement.feature,
4831
+ scale,
4832
+ normalized.x,
4833
+ normalized.y
4834
+ );
4835
+ });
4836
+ }
4837
+
4585
4838
  // src/extensions/dieline/renderBuilder.ts
4586
4839
  var DEFAULT_IDS = {
4587
4840
  inside: "dieline.inside",
@@ -4591,16 +4844,6 @@ var DEFAULT_IDS = {
4591
4844
  clip: "dieline.clip.image",
4592
4845
  clipSource: "dieline.effect.clip-path"
4593
4846
  };
4594
- function scaleFeatures(state, scale) {
4595
- return (state.features || []).map((feature) => ({
4596
- ...feature,
4597
- x: feature.x,
4598
- y: feature.y,
4599
- width: (feature.width || 0) * scale,
4600
- height: (feature.height || 0) * scale,
4601
- radius: (feature.radius || 0) * scale
4602
- }));
4603
- }
4604
4847
  function buildDielineRenderBundle(options) {
4605
4848
  const ids = { ...DEFAULT_IDS, ...options.ids || {} };
4606
4849
  const {
@@ -4625,8 +4868,41 @@ function buildDielineRenderBundle(options) {
4625
4868
  const cutH = sceneLayout.cutRect.height;
4626
4869
  const visualOffset = (cutW - visualWidth) / 2;
4627
4870
  const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
4628
- const absoluteFeatures = scaleFeatures(state, scale);
4629
- const cutFeatures = absoluteFeatures.filter((feature) => !feature.skipCut);
4871
+ const placements = resolveFeaturePlacements(state.features || [], {
4872
+ shape,
4873
+ shapeStyle,
4874
+ pathData: state.pathData,
4875
+ customSourceWidthPx: state.customSourceWidthPx,
4876
+ customSourceHeightPx: state.customSourceHeightPx,
4877
+ canvasWidth,
4878
+ canvasHeight,
4879
+ x: cx,
4880
+ y: cy,
4881
+ width: visualWidth,
4882
+ height: visualHeight,
4883
+ radius: visualRadius,
4884
+ scale
4885
+ });
4886
+ const absoluteFeatures = projectPlacedFeatures(
4887
+ placements,
4888
+ {
4889
+ x: cx,
4890
+ y: cy,
4891
+ width: visualWidth,
4892
+ height: visualHeight
4893
+ },
4894
+ scale
4895
+ );
4896
+ const cutFeatures = projectPlacedFeatures(
4897
+ placements.filter((placement) => !placement.feature.skipCut),
4898
+ {
4899
+ x: cx,
4900
+ y: cy,
4901
+ width: cutW,
4902
+ height: cutH
4903
+ },
4904
+ scale
4905
+ );
4630
4906
  const common = {
4631
4907
  shape,
4632
4908
  shapeStyle,
@@ -5055,15 +5331,31 @@ var DielineTool = class {
5055
5331
  const visualRadius = radius * scale;
5056
5332
  const visualOffset = (cutW - sceneLayout.trimRect.width) / 2;
5057
5333
  const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
5058
- const absoluteFeatures = (features || []).map((f) => ({
5059
- ...f,
5060
- x: f.x,
5061
- y: f.y,
5062
- width: (f.width || 0) * scale,
5063
- height: (f.height || 0) * scale,
5064
- radius: (f.radius || 0) * scale
5065
- }));
5066
- const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
5334
+ const placements = resolveFeaturePlacements(features || [], {
5335
+ shape,
5336
+ shapeStyle,
5337
+ pathData,
5338
+ customSourceWidthPx: this.state.customSourceWidthPx,
5339
+ customSourceHeightPx: this.state.customSourceHeightPx,
5340
+ canvasWidth: canvasW,
5341
+ canvasHeight: canvasH,
5342
+ x: cx,
5343
+ y: cy,
5344
+ width: sceneLayout.trimRect.width,
5345
+ height: sceneLayout.trimRect.height,
5346
+ radius: visualRadius,
5347
+ scale
5348
+ });
5349
+ const cutFeatures = projectPlacedFeatures(
5350
+ placements.filter((placement) => !placement.feature.skipCut),
5351
+ {
5352
+ x: cx,
5353
+ y: cy,
5354
+ width: cutW,
5355
+ height: cutH
5356
+ },
5357
+ scale
5358
+ );
5067
5359
  const generatedPathData = generateDielinePath({
5068
5360
  shape,
5069
5361
  width: cutW,
@@ -5189,178 +5481,6 @@ import {
5189
5481
  } from "@pooder/core";
5190
5482
  import { Pattern as Pattern3 } from "fabric";
5191
5483
 
5192
- // src/extensions/constraints.ts
5193
- var ConstraintRegistry = class {
5194
- static register(type, handler) {
5195
- this.handlers.set(type, handler);
5196
- }
5197
- static apply(x, y, feature, context, constraints) {
5198
- const list = constraints || feature.constraints;
5199
- if (!list || list.length === 0) {
5200
- return { x, y };
5201
- }
5202
- let currentX = x;
5203
- let currentY = y;
5204
- for (const constraint of list) {
5205
- const handler = this.handlers.get(constraint.type);
5206
- if (handler) {
5207
- const result = handler(currentX, currentY, feature, context, constraint.params || {});
5208
- currentX = result.x;
5209
- currentY = result.y;
5210
- }
5211
- }
5212
- return { x: currentX, y: currentY };
5213
- }
5214
- };
5215
- ConstraintRegistry.handlers = /* @__PURE__ */ new Map();
5216
- var pathConstraint = (x, y, feature, context, params) => {
5217
- const { dielineWidth, dielineHeight, geometry } = context;
5218
- if (!geometry) return { x, y };
5219
- const minX = geometry.x - geometry.width / 2;
5220
- const minY = geometry.y - geometry.height / 2;
5221
- const absX = minX + x * geometry.width;
5222
- const absY = minY + y * geometry.height;
5223
- const nearest = getNearestPointOnDieline(
5224
- { x: absX, y: absY },
5225
- geometry
5226
- );
5227
- let finalX = nearest.x;
5228
- let finalY = nearest.y;
5229
- const hasOffsetParams = params.minOffset !== void 0 || params.maxOffset !== void 0;
5230
- if (hasOffsetParams && nearest.normal) {
5231
- const dx = absX - nearest.x;
5232
- const dy = absY - nearest.y;
5233
- const nx2 = nearest.normal.x;
5234
- const ny2 = nearest.normal.y;
5235
- const dist = dx * nx2 + dy * ny2;
5236
- const scale = dielineWidth > 0 ? geometry.width / dielineWidth : 1;
5237
- const rawMin = params.minOffset !== void 0 ? params.minOffset : 0;
5238
- const rawMax = params.maxOffset !== void 0 ? params.maxOffset : 0;
5239
- const minOffset = rawMin * scale;
5240
- const maxOffset = rawMax * scale;
5241
- const clampedDist = Math.max(minOffset, Math.min(dist, maxOffset));
5242
- finalX = nearest.x + nx2 * clampedDist;
5243
- finalY = nearest.y + ny2 * clampedDist;
5244
- }
5245
- const nx = geometry.width > 0 ? (finalX - minX) / geometry.width : 0.5;
5246
- const ny = geometry.height > 0 ? (finalY - minY) / geometry.height : 0.5;
5247
- return { x: nx, y: ny };
5248
- };
5249
- var edgeConstraint = (x, y, feature, context, params) => {
5250
- const { dielineWidth, dielineHeight } = context;
5251
- const allowedEdges = params.allowedEdges || [
5252
- "top",
5253
- "bottom",
5254
- "left",
5255
- "right"
5256
- ];
5257
- const confine = params.confine || false;
5258
- const offset = params.offset || 0;
5259
- const distances = [];
5260
- if (allowedEdges.includes("top"))
5261
- distances.push({ edge: "top", dist: y * dielineHeight });
5262
- if (allowedEdges.includes("bottom"))
5263
- distances.push({ edge: "bottom", dist: (1 - y) * dielineHeight });
5264
- if (allowedEdges.includes("left"))
5265
- distances.push({ edge: "left", dist: x * dielineWidth });
5266
- if (allowedEdges.includes("right"))
5267
- distances.push({ edge: "right", dist: (1 - x) * dielineWidth });
5268
- if (distances.length === 0) return { x, y };
5269
- distances.sort((a, b) => a.dist - b.dist);
5270
- const nearest = distances[0].edge;
5271
- let newX = x;
5272
- let newY = y;
5273
- const fw = feature.width || 0;
5274
- const fh = feature.height || 0;
5275
- switch (nearest) {
5276
- case "top":
5277
- newY = 0 + offset / dielineHeight;
5278
- if (confine) {
5279
- const minX = fw / 2 / dielineWidth;
5280
- const maxX = 1 - minX;
5281
- newX = Math.max(minX, Math.min(newX, maxX));
5282
- }
5283
- break;
5284
- case "bottom":
5285
- newY = 1 - offset / dielineHeight;
5286
- if (confine) {
5287
- const minX = fw / 2 / dielineWidth;
5288
- const maxX = 1 - minX;
5289
- newX = Math.max(minX, Math.min(newX, maxX));
5290
- }
5291
- break;
5292
- case "left":
5293
- newX = 0 + offset / dielineWidth;
5294
- if (confine) {
5295
- const minY = fh / 2 / dielineHeight;
5296
- const maxY = 1 - minY;
5297
- newY = Math.max(minY, Math.min(newY, maxY));
5298
- }
5299
- break;
5300
- case "right":
5301
- newX = 1 - offset / dielineWidth;
5302
- if (confine) {
5303
- const minY = fh / 2 / dielineHeight;
5304
- const maxY = 1 - minY;
5305
- newY = Math.max(minY, Math.min(newY, maxY));
5306
- }
5307
- break;
5308
- }
5309
- return { x: newX, y: newY };
5310
- };
5311
- var internalConstraint = (x, y, feature, context, params) => {
5312
- const { dielineWidth, dielineHeight } = context;
5313
- const margin = params.margin || 0;
5314
- const fw = feature.width || 0;
5315
- const fh = feature.height || 0;
5316
- const minX = (margin + fw / 2) / dielineWidth;
5317
- const maxX = 1 - (margin + fw / 2) / dielineWidth;
5318
- const minY = (margin + fh / 2) / dielineHeight;
5319
- const maxY = 1 - (margin + fh / 2) / dielineHeight;
5320
- const clampedX = minX > maxX ? 0.5 : Math.max(minX, Math.min(x, maxX));
5321
- const clampedY = minY > maxY ? 0.5 : Math.max(minY, Math.min(y, maxY));
5322
- return { x: clampedX, y: clampedY };
5323
- };
5324
- var tangentBottomConstraint = (x, y, feature, context, params) => {
5325
- const { dielineWidth, dielineHeight } = context;
5326
- const gap = params.gap || 0;
5327
- const confineX = params.confineX !== false;
5328
- const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
5329
- const newY = 1 + (extentY + gap) / dielineHeight;
5330
- let newX = x;
5331
- if (confineX) {
5332
- const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
5333
- const minX = extentX / dielineWidth;
5334
- const maxX = 1 - extentX / dielineWidth;
5335
- newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
5336
- }
5337
- return { x: newX, y: newY };
5338
- };
5339
- var lowestTangentConstraint = (x, y, feature, context, params) => {
5340
- const { dielineWidth, dielineHeight, geometry } = context;
5341
- if (!geometry) return { x, y };
5342
- const lowest = getLowestPointOnDieline(geometry);
5343
- const minY = geometry.y - geometry.height / 2;
5344
- const normY = (lowest.y - minY) / geometry.height;
5345
- const gap = params.gap || 0;
5346
- const confineX = params.confineX !== false;
5347
- const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
5348
- const newY = normY + (extentY + gap) / dielineHeight;
5349
- let newX = x;
5350
- if (confineX) {
5351
- const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
5352
- const minX = extentX / dielineWidth;
5353
- const maxX = 1 - extentX / dielineWidth;
5354
- newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
5355
- }
5356
- return { x: newX, y: newY };
5357
- };
5358
- ConstraintRegistry.register("path", pathConstraint);
5359
- ConstraintRegistry.register("edge", edgeConstraint);
5360
- ConstraintRegistry.register("internal", internalConstraint);
5361
- ConstraintRegistry.register("tangent-bottom", tangentBottomConstraint);
5362
- ConstraintRegistry.register("lowest-tangent", lowestTangentConstraint);
5363
-
5364
5484
  // src/extensions/featureComplete.ts
5365
5485
  function validateFeaturesStrict(features, context) {
5366
5486
  const eps = 1e-6;
@@ -6136,9 +6256,29 @@ var FeatureTool = class {
6136
6256
  }
6137
6257
  const groups = /* @__PURE__ */ new Map();
6138
6258
  const singles = [];
6139
- this.workingFeatures.forEach((feature, index) => {
6259
+ const placements = resolveFeaturePlacements(
6260
+ this.workingFeatures,
6261
+ {
6262
+ shape: this.currentGeometry.shape,
6263
+ shapeStyle: this.currentGeometry.shapeStyle,
6264
+ pathData: this.currentGeometry.pathData,
6265
+ customSourceWidthPx: this.currentGeometry.customSourceWidthPx,
6266
+ customSourceHeightPx: this.currentGeometry.customSourceHeightPx,
6267
+ x: this.currentGeometry.x,
6268
+ y: this.currentGeometry.y,
6269
+ width: this.currentGeometry.width,
6270
+ height: this.currentGeometry.height,
6271
+ radius: this.currentGeometry.radius,
6272
+ scale: this.currentGeometry.scale || 1
6273
+ }
6274
+ );
6275
+ placements.forEach((placement, index) => {
6276
+ const feature = placement.feature;
6140
6277
  const geometry = this.getGeometryForFeature(this.currentGeometry, feature);
6141
- const position = resolveFeaturePosition(feature, geometry);
6278
+ const position = {
6279
+ x: placement.centerX,
6280
+ y: placement.centerY
6281
+ };
6142
6282
  const scale = geometry.scale || 1;
6143
6283
  const marker = {
6144
6284
  feature,
@@ -6715,11 +6855,12 @@ var EXTENSION_LINE_LENGTH = 5;
6715
6855
  var MIN_ARROW_SIZE = 4;
6716
6856
  var THICKNESS_TO_STROKE_WIDTH_RATIO = 20;
6717
6857
  var DEFAULT_THICKNESS = 20;
6718
- var DEFAULT_GAP = 45;
6858
+ var DEFAULT_GAP = 65;
6719
6859
  var DEFAULT_FONT_SIZE = 10;
6720
6860
  var DEFAULT_BACKGROUND_COLOR = "#f0f0f0";
6721
6861
  var DEFAULT_TEXT_COLOR = "#333333";
6722
6862
  var DEFAULT_LINE_COLOR = "#999999";
6863
+ var RULER_DEBUG_KEY = "ruler.debug";
6723
6864
  var RULER_THICKNESS_MIN = 10;
6724
6865
  var RULER_THICKNESS_MAX = 100;
6725
6866
  var RULER_GAP_MIN = 0;
@@ -6738,6 +6879,7 @@ var RulerTool = class {
6738
6879
  this.textColor = DEFAULT_TEXT_COLOR;
6739
6880
  this.lineColor = DEFAULT_LINE_COLOR;
6740
6881
  this.fontSize = DEFAULT_FONT_SIZE;
6882
+ this.debugEnabled = false;
6741
6883
  this.renderSeq = 0;
6742
6884
  this.numericProps = /* @__PURE__ */ new Set(["thickness", "gap", "fontSize"]);
6743
6885
  this.specs = [];
@@ -6786,7 +6928,14 @@ var RulerTool = class {
6786
6928
  this.syncConfig(configService);
6787
6929
  configService.onAnyChange((e) => {
6788
6930
  let shouldUpdate = false;
6789
- if (e.key.startsWith("ruler.")) {
6931
+ if (e.key === RULER_DEBUG_KEY) {
6932
+ this.debugEnabled = e.value === true;
6933
+ this.log("config:update", {
6934
+ key: e.key,
6935
+ raw: e.value,
6936
+ normalized: this.debugEnabled
6937
+ });
6938
+ } else if (e.key.startsWith("ruler.")) {
6790
6939
  const prop = e.key.split(".")[1];
6791
6940
  if (prop && prop in this) {
6792
6941
  if (this.numericProps.has(prop)) {
@@ -6873,6 +7022,12 @@ var RulerTool = class {
6873
7022
  min: RULER_FONT_SIZE_MIN,
6874
7023
  max: RULER_FONT_SIZE_MAX,
6875
7024
  default: DEFAULT_FONT_SIZE
7025
+ },
7026
+ {
7027
+ id: RULER_DEBUG_KEY,
7028
+ type: "boolean",
7029
+ label: "Ruler Debug Log",
7030
+ default: false
6876
7031
  }
6877
7032
  ],
6878
7033
  [ContributionPointIds8.COMMANDS]: [
@@ -6909,7 +7064,11 @@ var RulerTool = class {
6909
7064
  ]
6910
7065
  };
6911
7066
  }
7067
+ isDebugEnabled() {
7068
+ return this.debugEnabled;
7069
+ }
6912
7070
  log(step, payload) {
7071
+ if (!this.isDebugEnabled()) return;
6913
7072
  if (payload) {
6914
7073
  console.debug(`[RulerTool] ${step}`, payload);
6915
7074
  return;
@@ -6938,6 +7097,7 @@ var RulerTool = class {
6938
7097
  configService.get("ruler.fontSize", this.fontSize),
6939
7098
  DEFAULT_FONT_SIZE
6940
7099
  );
7100
+ this.debugEnabled = configService.get(RULER_DEBUG_KEY, this.debugEnabled) === true;
6941
7101
  this.log("config:loaded", {
6942
7102
  thickness: this.thickness,
6943
7103
  gap: this.gap,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/kit",
3
- "version": "6.2.0",
3
+ "version": "6.2.1",
4
4
  "description": "Standard plugins for Pooder editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -26,6 +26,10 @@ import {
26
26
  readDielineState,
27
27
  } from "./model";
28
28
  import { buildDielineRenderBundle } from "./renderBuilder";
29
+ import {
30
+ projectPlacedFeatures,
31
+ resolveFeaturePlacements,
32
+ } from "../featurePlacement";
29
33
 
30
34
  export class DielineTool implements Extension {
31
35
  id = "pooder.kit.dieline";
@@ -320,15 +324,31 @@ export class DielineTool implements Extension {
320
324
  const cutR =
321
325
  visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
322
326
 
323
- const absoluteFeatures = (features || []).map((f) => ({
324
- ...f,
325
- x: f.x,
326
- y: f.y,
327
- width: (f.width || 0) * scale,
328
- height: (f.height || 0) * scale,
329
- radius: (f.radius || 0) * scale,
330
- }));
331
- const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
327
+ const placements = resolveFeaturePlacements(features || [], {
328
+ shape,
329
+ shapeStyle,
330
+ pathData,
331
+ customSourceWidthPx: this.state.customSourceWidthPx,
332
+ customSourceHeightPx: this.state.customSourceHeightPx,
333
+ canvasWidth: canvasW,
334
+ canvasHeight: canvasH,
335
+ x: cx,
336
+ y: cy,
337
+ width: sceneLayout.trimRect.width,
338
+ height: sceneLayout.trimRect.height,
339
+ radius: visualRadius,
340
+ scale,
341
+ });
342
+ const cutFeatures = projectPlacedFeatures(
343
+ placements.filter((placement) => !placement.feature.skipCut),
344
+ {
345
+ x: cx,
346
+ y: cy,
347
+ width: cutW,
348
+ height: cutH,
349
+ },
350
+ scale,
351
+ );
332
352
 
333
353
  const generatedPathData = generateDielinePath({
334
354
  shape,