@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.js CHANGED
@@ -5656,6 +5656,259 @@ function readDielineState(configService, fallback) {
5656
5656
  };
5657
5657
  }
5658
5658
 
5659
+ // src/extensions/constraints.ts
5660
+ var ConstraintRegistry = class {
5661
+ static register(type, handler) {
5662
+ this.handlers.set(type, handler);
5663
+ }
5664
+ static apply(x, y, feature, context, constraints) {
5665
+ const list = constraints || feature.constraints;
5666
+ if (!list || list.length === 0) {
5667
+ return { x, y };
5668
+ }
5669
+ let currentX = x;
5670
+ let currentY = y;
5671
+ for (const constraint of list) {
5672
+ const handler = this.handlers.get(constraint.type);
5673
+ if (handler) {
5674
+ const result = handler(currentX, currentY, feature, context, constraint.params || {});
5675
+ currentX = result.x;
5676
+ currentY = result.y;
5677
+ }
5678
+ }
5679
+ return { x: currentX, y: currentY };
5680
+ }
5681
+ };
5682
+ ConstraintRegistry.handlers = /* @__PURE__ */ new Map();
5683
+ var pathConstraint = (x, y, feature, context, params) => {
5684
+ const { dielineWidth, dielineHeight, geometry } = context;
5685
+ if (!geometry) return { x, y };
5686
+ const minX = geometry.x - geometry.width / 2;
5687
+ const minY = geometry.y - geometry.height / 2;
5688
+ const absX = minX + x * geometry.width;
5689
+ const absY = minY + y * geometry.height;
5690
+ const nearest = getNearestPointOnDieline(
5691
+ { x: absX, y: absY },
5692
+ geometry
5693
+ );
5694
+ let finalX = nearest.x;
5695
+ let finalY = nearest.y;
5696
+ const hasOffsetParams = params.minOffset !== void 0 || params.maxOffset !== void 0;
5697
+ if (hasOffsetParams && nearest.normal) {
5698
+ const dx = absX - nearest.x;
5699
+ const dy = absY - nearest.y;
5700
+ const nx2 = nearest.normal.x;
5701
+ const ny2 = nearest.normal.y;
5702
+ const dist = dx * nx2 + dy * ny2;
5703
+ const scale = dielineWidth > 0 ? geometry.width / dielineWidth : 1;
5704
+ const rawMin = params.minOffset !== void 0 ? params.minOffset : 0;
5705
+ const rawMax = params.maxOffset !== void 0 ? params.maxOffset : 0;
5706
+ const minOffset = rawMin * scale;
5707
+ const maxOffset = rawMax * scale;
5708
+ const clampedDist = Math.max(minOffset, Math.min(dist, maxOffset));
5709
+ finalX = nearest.x + nx2 * clampedDist;
5710
+ finalY = nearest.y + ny2 * clampedDist;
5711
+ }
5712
+ const nx = geometry.width > 0 ? (finalX - minX) / geometry.width : 0.5;
5713
+ const ny = geometry.height > 0 ? (finalY - minY) / geometry.height : 0.5;
5714
+ return { x: nx, y: ny };
5715
+ };
5716
+ var edgeConstraint = (x, y, feature, context, params) => {
5717
+ const { dielineWidth, dielineHeight } = context;
5718
+ const allowedEdges = params.allowedEdges || [
5719
+ "top",
5720
+ "bottom",
5721
+ "left",
5722
+ "right"
5723
+ ];
5724
+ const confine = params.confine || false;
5725
+ const offset = params.offset || 0;
5726
+ const distances = [];
5727
+ if (allowedEdges.includes("top"))
5728
+ distances.push({ edge: "top", dist: y * dielineHeight });
5729
+ if (allowedEdges.includes("bottom"))
5730
+ distances.push({ edge: "bottom", dist: (1 - y) * dielineHeight });
5731
+ if (allowedEdges.includes("left"))
5732
+ distances.push({ edge: "left", dist: x * dielineWidth });
5733
+ if (allowedEdges.includes("right"))
5734
+ distances.push({ edge: "right", dist: (1 - x) * dielineWidth });
5735
+ if (distances.length === 0) return { x, y };
5736
+ distances.sort((a, b) => a.dist - b.dist);
5737
+ const nearest = distances[0].edge;
5738
+ let newX = x;
5739
+ let newY = y;
5740
+ const fw = feature.width || 0;
5741
+ const fh = feature.height || 0;
5742
+ switch (nearest) {
5743
+ case "top":
5744
+ newY = 0 + offset / dielineHeight;
5745
+ if (confine) {
5746
+ const minX = fw / 2 / dielineWidth;
5747
+ const maxX = 1 - minX;
5748
+ newX = Math.max(minX, Math.min(newX, maxX));
5749
+ }
5750
+ break;
5751
+ case "bottom":
5752
+ newY = 1 - offset / dielineHeight;
5753
+ if (confine) {
5754
+ const minX = fw / 2 / dielineWidth;
5755
+ const maxX = 1 - minX;
5756
+ newX = Math.max(minX, Math.min(newX, maxX));
5757
+ }
5758
+ break;
5759
+ case "left":
5760
+ newX = 0 + offset / dielineWidth;
5761
+ if (confine) {
5762
+ const minY = fh / 2 / dielineHeight;
5763
+ const maxY = 1 - minY;
5764
+ newY = Math.max(minY, Math.min(newY, maxY));
5765
+ }
5766
+ break;
5767
+ case "right":
5768
+ newX = 1 - offset / dielineWidth;
5769
+ if (confine) {
5770
+ const minY = fh / 2 / dielineHeight;
5771
+ const maxY = 1 - minY;
5772
+ newY = Math.max(minY, Math.min(newY, maxY));
5773
+ }
5774
+ break;
5775
+ }
5776
+ return { x: newX, y: newY };
5777
+ };
5778
+ var internalConstraint = (x, y, feature, context, params) => {
5779
+ const { dielineWidth, dielineHeight } = context;
5780
+ const margin = params.margin || 0;
5781
+ const fw = feature.width || 0;
5782
+ const fh = feature.height || 0;
5783
+ const minX = (margin + fw / 2) / dielineWidth;
5784
+ const maxX = 1 - (margin + fw / 2) / dielineWidth;
5785
+ const minY = (margin + fh / 2) / dielineHeight;
5786
+ const maxY = 1 - (margin + fh / 2) / dielineHeight;
5787
+ const clampedX = minX > maxX ? 0.5 : Math.max(minX, Math.min(x, maxX));
5788
+ const clampedY = minY > maxY ? 0.5 : Math.max(minY, Math.min(y, maxY));
5789
+ return { x: clampedX, y: clampedY };
5790
+ };
5791
+ var tangentBottomConstraint = (x, y, feature, context, params) => {
5792
+ const { dielineWidth, dielineHeight } = context;
5793
+ const gap = params.gap || 0;
5794
+ const confineX = params.confineX !== false;
5795
+ const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
5796
+ const newY = 1 + (extentY + gap) / dielineHeight;
5797
+ let newX = x;
5798
+ if (confineX) {
5799
+ const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
5800
+ const minX = extentX / dielineWidth;
5801
+ const maxX = 1 - extentX / dielineWidth;
5802
+ newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
5803
+ }
5804
+ return { x: newX, y: newY };
5805
+ };
5806
+ var lowestTangentConstraint = (x, y, feature, context, params) => {
5807
+ const { dielineWidth, dielineHeight, geometry } = context;
5808
+ if (!geometry) return { x, y };
5809
+ const lowest = getLowestPointOnDieline(geometry);
5810
+ const minY = geometry.y - geometry.height / 2;
5811
+ const normY = (lowest.y - minY) / geometry.height;
5812
+ const gap = params.gap || 0;
5813
+ const confineX = params.confineX !== false;
5814
+ const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
5815
+ const newY = normY + (extentY + gap) / dielineHeight;
5816
+ let newX = x;
5817
+ if (confineX) {
5818
+ const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
5819
+ const minX = extentX / dielineWidth;
5820
+ const maxX = 1 - extentX / dielineWidth;
5821
+ newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
5822
+ }
5823
+ return { x: newX, y: newY };
5824
+ };
5825
+ ConstraintRegistry.register("path", pathConstraint);
5826
+ ConstraintRegistry.register("edge", edgeConstraint);
5827
+ ConstraintRegistry.register("internal", internalConstraint);
5828
+ ConstraintRegistry.register("tangent-bottom", tangentBottomConstraint);
5829
+ ConstraintRegistry.register("lowest-tangent", lowestTangentConstraint);
5830
+
5831
+ // src/extensions/featureCoordinates.ts
5832
+ function resolveFeaturePosition2(feature, geometry) {
5833
+ const { x, y, width, height } = geometry;
5834
+ const left = x - width / 2;
5835
+ const top = y - height / 2;
5836
+ return {
5837
+ x: left + feature.x * width,
5838
+ y: top + feature.y * height
5839
+ };
5840
+ }
5841
+ function normalizePointInGeometry(point, geometry) {
5842
+ const left = geometry.x - geometry.width / 2;
5843
+ const top = geometry.y - geometry.height / 2;
5844
+ return {
5845
+ x: geometry.width > 0 ? (point.x - left) / geometry.width : 0.5,
5846
+ y: geometry.height > 0 ? (point.y - top) / geometry.height : 0.5
5847
+ };
5848
+ }
5849
+
5850
+ // src/extensions/featurePlacement.ts
5851
+ function scaleFeatureForRender(feature, scale, x, y) {
5852
+ return {
5853
+ ...feature,
5854
+ x,
5855
+ y,
5856
+ width: feature.width !== void 0 ? feature.width * scale : void 0,
5857
+ height: feature.height !== void 0 ? feature.height * scale : void 0,
5858
+ radius: feature.radius !== void 0 ? feature.radius * scale : void 0
5859
+ };
5860
+ }
5861
+ function resolveFeaturePlacements(features, geometry) {
5862
+ const dielineWidth = geometry.scale > 0 ? geometry.width / geometry.scale : geometry.width;
5863
+ const dielineHeight = geometry.scale > 0 ? geometry.height / geometry.scale : geometry.height;
5864
+ return (features || []).map((feature) => {
5865
+ var _a;
5866
+ const activeConstraints = (_a = feature.constraints) == null ? void 0 : _a.filter(
5867
+ (constraint) => !constraint.validateOnly
5868
+ );
5869
+ const constrained = ConstraintRegistry.apply(
5870
+ feature.x,
5871
+ feature.y,
5872
+ feature,
5873
+ {
5874
+ dielineWidth,
5875
+ dielineHeight,
5876
+ geometry
5877
+ },
5878
+ activeConstraints
5879
+ );
5880
+ const center = resolveFeaturePosition2(
5881
+ {
5882
+ ...feature,
5883
+ x: constrained.x,
5884
+ y: constrained.y
5885
+ },
5886
+ geometry
5887
+ );
5888
+ return {
5889
+ feature,
5890
+ normalizedX: constrained.x,
5891
+ normalizedY: constrained.y,
5892
+ centerX: center.x,
5893
+ centerY: center.y
5894
+ };
5895
+ });
5896
+ }
5897
+ function projectPlacedFeatures(placements, geometry, scale) {
5898
+ return placements.map((placement) => {
5899
+ const normalized = normalizePointInGeometry(
5900
+ { x: placement.centerX, y: placement.centerY },
5901
+ geometry
5902
+ );
5903
+ return scaleFeatureForRender(
5904
+ placement.feature,
5905
+ scale,
5906
+ normalized.x,
5907
+ normalized.y
5908
+ );
5909
+ });
5910
+ }
5911
+
5659
5912
  // src/extensions/dieline/renderBuilder.ts
5660
5913
  var DEFAULT_IDS = {
5661
5914
  inside: "dieline.inside",
@@ -5665,16 +5918,6 @@ var DEFAULT_IDS = {
5665
5918
  clip: "dieline.clip.image",
5666
5919
  clipSource: "dieline.effect.clip-path"
5667
5920
  };
5668
- function scaleFeatures(state, scale) {
5669
- return (state.features || []).map((feature) => ({
5670
- ...feature,
5671
- x: feature.x,
5672
- y: feature.y,
5673
- width: (feature.width || 0) * scale,
5674
- height: (feature.height || 0) * scale,
5675
- radius: (feature.radius || 0) * scale
5676
- }));
5677
- }
5678
5921
  function buildDielineRenderBundle(options) {
5679
5922
  const ids = { ...DEFAULT_IDS, ...options.ids || {} };
5680
5923
  const {
@@ -5699,8 +5942,41 @@ function buildDielineRenderBundle(options) {
5699
5942
  const cutH = sceneLayout.cutRect.height;
5700
5943
  const visualOffset = (cutW - visualWidth) / 2;
5701
5944
  const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
5702
- const absoluteFeatures = scaleFeatures(state, scale);
5703
- const cutFeatures = absoluteFeatures.filter((feature) => !feature.skipCut);
5945
+ const placements = resolveFeaturePlacements(state.features || [], {
5946
+ shape,
5947
+ shapeStyle,
5948
+ pathData: state.pathData,
5949
+ customSourceWidthPx: state.customSourceWidthPx,
5950
+ customSourceHeightPx: state.customSourceHeightPx,
5951
+ canvasWidth,
5952
+ canvasHeight,
5953
+ x: cx,
5954
+ y: cy,
5955
+ width: visualWidth,
5956
+ height: visualHeight,
5957
+ radius: visualRadius,
5958
+ scale
5959
+ });
5960
+ const absoluteFeatures = projectPlacedFeatures(
5961
+ placements,
5962
+ {
5963
+ x: cx,
5964
+ y: cy,
5965
+ width: visualWidth,
5966
+ height: visualHeight
5967
+ },
5968
+ scale
5969
+ );
5970
+ const cutFeatures = projectPlacedFeatures(
5971
+ placements.filter((placement) => !placement.feature.skipCut),
5972
+ {
5973
+ x: cx,
5974
+ y: cy,
5975
+ width: cutW,
5976
+ height: cutH
5977
+ },
5978
+ scale
5979
+ );
5704
5980
  const common = {
5705
5981
  shape,
5706
5982
  shapeStyle,
@@ -6129,15 +6405,31 @@ var DielineTool = class {
6129
6405
  const visualRadius = radius * scale;
6130
6406
  const visualOffset = (cutW - sceneLayout.trimRect.width) / 2;
6131
6407
  const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
6132
- const absoluteFeatures = (features || []).map((f) => ({
6133
- ...f,
6134
- x: f.x,
6135
- y: f.y,
6136
- width: (f.width || 0) * scale,
6137
- height: (f.height || 0) * scale,
6138
- radius: (f.radius || 0) * scale
6139
- }));
6140
- const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
6408
+ const placements = resolveFeaturePlacements(features || [], {
6409
+ shape,
6410
+ shapeStyle,
6411
+ pathData,
6412
+ customSourceWidthPx: this.state.customSourceWidthPx,
6413
+ customSourceHeightPx: this.state.customSourceHeightPx,
6414
+ canvasWidth: canvasW,
6415
+ canvasHeight: canvasH,
6416
+ x: cx,
6417
+ y: cy,
6418
+ width: sceneLayout.trimRect.width,
6419
+ height: sceneLayout.trimRect.height,
6420
+ radius: visualRadius,
6421
+ scale
6422
+ });
6423
+ const cutFeatures = projectPlacedFeatures(
6424
+ placements.filter((placement) => !placement.feature.skipCut),
6425
+ {
6426
+ x: cx,
6427
+ y: cy,
6428
+ width: cutW,
6429
+ height: cutH
6430
+ },
6431
+ scale
6432
+ );
6141
6433
  const generatedPathData = generateDielinePath({
6142
6434
  shape,
6143
6435
  width: cutW,
@@ -6261,178 +6553,6 @@ var DielineTool = class {
6261
6553
  var import_core5 = require("@pooder/core");
6262
6554
  var import_fabric4 = require("fabric");
6263
6555
 
6264
- // src/extensions/constraints.ts
6265
- var ConstraintRegistry = class {
6266
- static register(type, handler) {
6267
- this.handlers.set(type, handler);
6268
- }
6269
- static apply(x, y, feature, context, constraints) {
6270
- const list = constraints || feature.constraints;
6271
- if (!list || list.length === 0) {
6272
- return { x, y };
6273
- }
6274
- let currentX = x;
6275
- let currentY = y;
6276
- for (const constraint of list) {
6277
- const handler = this.handlers.get(constraint.type);
6278
- if (handler) {
6279
- const result = handler(currentX, currentY, feature, context, constraint.params || {});
6280
- currentX = result.x;
6281
- currentY = result.y;
6282
- }
6283
- }
6284
- return { x: currentX, y: currentY };
6285
- }
6286
- };
6287
- ConstraintRegistry.handlers = /* @__PURE__ */ new Map();
6288
- var pathConstraint = (x, y, feature, context, params) => {
6289
- const { dielineWidth, dielineHeight, geometry } = context;
6290
- if (!geometry) return { x, y };
6291
- const minX = geometry.x - geometry.width / 2;
6292
- const minY = geometry.y - geometry.height / 2;
6293
- const absX = minX + x * geometry.width;
6294
- const absY = minY + y * geometry.height;
6295
- const nearest = getNearestPointOnDieline(
6296
- { x: absX, y: absY },
6297
- geometry
6298
- );
6299
- let finalX = nearest.x;
6300
- let finalY = nearest.y;
6301
- const hasOffsetParams = params.minOffset !== void 0 || params.maxOffset !== void 0;
6302
- if (hasOffsetParams && nearest.normal) {
6303
- const dx = absX - nearest.x;
6304
- const dy = absY - nearest.y;
6305
- const nx2 = nearest.normal.x;
6306
- const ny2 = nearest.normal.y;
6307
- const dist = dx * nx2 + dy * ny2;
6308
- const scale = dielineWidth > 0 ? geometry.width / dielineWidth : 1;
6309
- const rawMin = params.minOffset !== void 0 ? params.minOffset : 0;
6310
- const rawMax = params.maxOffset !== void 0 ? params.maxOffset : 0;
6311
- const minOffset = rawMin * scale;
6312
- const maxOffset = rawMax * scale;
6313
- const clampedDist = Math.max(minOffset, Math.min(dist, maxOffset));
6314
- finalX = nearest.x + nx2 * clampedDist;
6315
- finalY = nearest.y + ny2 * clampedDist;
6316
- }
6317
- const nx = geometry.width > 0 ? (finalX - minX) / geometry.width : 0.5;
6318
- const ny = geometry.height > 0 ? (finalY - minY) / geometry.height : 0.5;
6319
- return { x: nx, y: ny };
6320
- };
6321
- var edgeConstraint = (x, y, feature, context, params) => {
6322
- const { dielineWidth, dielineHeight } = context;
6323
- const allowedEdges = params.allowedEdges || [
6324
- "top",
6325
- "bottom",
6326
- "left",
6327
- "right"
6328
- ];
6329
- const confine = params.confine || false;
6330
- const offset = params.offset || 0;
6331
- const distances = [];
6332
- if (allowedEdges.includes("top"))
6333
- distances.push({ edge: "top", dist: y * dielineHeight });
6334
- if (allowedEdges.includes("bottom"))
6335
- distances.push({ edge: "bottom", dist: (1 - y) * dielineHeight });
6336
- if (allowedEdges.includes("left"))
6337
- distances.push({ edge: "left", dist: x * dielineWidth });
6338
- if (allowedEdges.includes("right"))
6339
- distances.push({ edge: "right", dist: (1 - x) * dielineWidth });
6340
- if (distances.length === 0) return { x, y };
6341
- distances.sort((a, b) => a.dist - b.dist);
6342
- const nearest = distances[0].edge;
6343
- let newX = x;
6344
- let newY = y;
6345
- const fw = feature.width || 0;
6346
- const fh = feature.height || 0;
6347
- switch (nearest) {
6348
- case "top":
6349
- newY = 0 + offset / dielineHeight;
6350
- if (confine) {
6351
- const minX = fw / 2 / dielineWidth;
6352
- const maxX = 1 - minX;
6353
- newX = Math.max(minX, Math.min(newX, maxX));
6354
- }
6355
- break;
6356
- case "bottom":
6357
- newY = 1 - offset / dielineHeight;
6358
- if (confine) {
6359
- const minX = fw / 2 / dielineWidth;
6360
- const maxX = 1 - minX;
6361
- newX = Math.max(minX, Math.min(newX, maxX));
6362
- }
6363
- break;
6364
- case "left":
6365
- newX = 0 + offset / dielineWidth;
6366
- if (confine) {
6367
- const minY = fh / 2 / dielineHeight;
6368
- const maxY = 1 - minY;
6369
- newY = Math.max(minY, Math.min(newY, maxY));
6370
- }
6371
- break;
6372
- case "right":
6373
- newX = 1 - offset / dielineWidth;
6374
- if (confine) {
6375
- const minY = fh / 2 / dielineHeight;
6376
- const maxY = 1 - minY;
6377
- newY = Math.max(minY, Math.min(newY, maxY));
6378
- }
6379
- break;
6380
- }
6381
- return { x: newX, y: newY };
6382
- };
6383
- var internalConstraint = (x, y, feature, context, params) => {
6384
- const { dielineWidth, dielineHeight } = context;
6385
- const margin = params.margin || 0;
6386
- const fw = feature.width || 0;
6387
- const fh = feature.height || 0;
6388
- const minX = (margin + fw / 2) / dielineWidth;
6389
- const maxX = 1 - (margin + fw / 2) / dielineWidth;
6390
- const minY = (margin + fh / 2) / dielineHeight;
6391
- const maxY = 1 - (margin + fh / 2) / dielineHeight;
6392
- const clampedX = minX > maxX ? 0.5 : Math.max(minX, Math.min(x, maxX));
6393
- const clampedY = minY > maxY ? 0.5 : Math.max(minY, Math.min(y, maxY));
6394
- return { x: clampedX, y: clampedY };
6395
- };
6396
- var tangentBottomConstraint = (x, y, feature, context, params) => {
6397
- const { dielineWidth, dielineHeight } = context;
6398
- const gap = params.gap || 0;
6399
- const confineX = params.confineX !== false;
6400
- const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
6401
- const newY = 1 + (extentY + gap) / dielineHeight;
6402
- let newX = x;
6403
- if (confineX) {
6404
- const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
6405
- const minX = extentX / dielineWidth;
6406
- const maxX = 1 - extentX / dielineWidth;
6407
- newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
6408
- }
6409
- return { x: newX, y: newY };
6410
- };
6411
- var lowestTangentConstraint = (x, y, feature, context, params) => {
6412
- const { dielineWidth, dielineHeight, geometry } = context;
6413
- if (!geometry) return { x, y };
6414
- const lowest = getLowestPointOnDieline(geometry);
6415
- const minY = geometry.y - geometry.height / 2;
6416
- const normY = (lowest.y - minY) / geometry.height;
6417
- const gap = params.gap || 0;
6418
- const confineX = params.confineX !== false;
6419
- const extentY = feature.shape === "circle" ? feature.radius || 0 : (feature.height || 0) / 2;
6420
- const newY = normY + (extentY + gap) / dielineHeight;
6421
- let newX = x;
6422
- if (confineX) {
6423
- const extentX = feature.shape === "circle" ? feature.radius || 0 : (feature.width || 0) / 2;
6424
- const minX = extentX / dielineWidth;
6425
- const maxX = 1 - extentX / dielineWidth;
6426
- newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
6427
- }
6428
- return { x: newX, y: newY };
6429
- };
6430
- ConstraintRegistry.register("path", pathConstraint);
6431
- ConstraintRegistry.register("edge", edgeConstraint);
6432
- ConstraintRegistry.register("internal", internalConstraint);
6433
- ConstraintRegistry.register("tangent-bottom", tangentBottomConstraint);
6434
- ConstraintRegistry.register("lowest-tangent", lowestTangentConstraint);
6435
-
6436
6556
  // src/extensions/featureComplete.ts
6437
6557
  function validateFeaturesStrict(features, context) {
6438
6558
  const eps = 1e-6;
@@ -7208,9 +7328,29 @@ var FeatureTool = class {
7208
7328
  }
7209
7329
  const groups = /* @__PURE__ */ new Map();
7210
7330
  const singles = [];
7211
- this.workingFeatures.forEach((feature, index) => {
7331
+ const placements = resolveFeaturePlacements(
7332
+ this.workingFeatures,
7333
+ {
7334
+ shape: this.currentGeometry.shape,
7335
+ shapeStyle: this.currentGeometry.shapeStyle,
7336
+ pathData: this.currentGeometry.pathData,
7337
+ customSourceWidthPx: this.currentGeometry.customSourceWidthPx,
7338
+ customSourceHeightPx: this.currentGeometry.customSourceHeightPx,
7339
+ x: this.currentGeometry.x,
7340
+ y: this.currentGeometry.y,
7341
+ width: this.currentGeometry.width,
7342
+ height: this.currentGeometry.height,
7343
+ radius: this.currentGeometry.radius,
7344
+ scale: this.currentGeometry.scale || 1
7345
+ }
7346
+ );
7347
+ placements.forEach((placement, index) => {
7348
+ const feature = placement.feature;
7212
7349
  const geometry = this.getGeometryForFeature(this.currentGeometry, feature);
7213
- const position = resolveFeaturePosition(feature, geometry);
7350
+ const position = {
7351
+ x: placement.centerX,
7352
+ y: placement.centerY
7353
+ };
7214
7354
  const scale = geometry.scale || 1;
7215
7355
  const marker = {
7216
7356
  feature,
@@ -7781,11 +7921,12 @@ var EXTENSION_LINE_LENGTH = 5;
7781
7921
  var MIN_ARROW_SIZE = 4;
7782
7922
  var THICKNESS_TO_STROKE_WIDTH_RATIO = 20;
7783
7923
  var DEFAULT_THICKNESS = 20;
7784
- var DEFAULT_GAP = 45;
7924
+ var DEFAULT_GAP = 65;
7785
7925
  var DEFAULT_FONT_SIZE = 10;
7786
7926
  var DEFAULT_BACKGROUND_COLOR = "#f0f0f0";
7787
7927
  var DEFAULT_TEXT_COLOR = "#333333";
7788
7928
  var DEFAULT_LINE_COLOR = "#999999";
7929
+ var RULER_DEBUG_KEY = "ruler.debug";
7789
7930
  var RULER_THICKNESS_MIN = 10;
7790
7931
  var RULER_THICKNESS_MAX = 100;
7791
7932
  var RULER_GAP_MIN = 0;
@@ -7804,6 +7945,7 @@ var RulerTool = class {
7804
7945
  this.textColor = DEFAULT_TEXT_COLOR;
7805
7946
  this.lineColor = DEFAULT_LINE_COLOR;
7806
7947
  this.fontSize = DEFAULT_FONT_SIZE;
7948
+ this.debugEnabled = false;
7807
7949
  this.renderSeq = 0;
7808
7950
  this.numericProps = /* @__PURE__ */ new Set(["thickness", "gap", "fontSize"]);
7809
7951
  this.specs = [];
@@ -7852,7 +7994,14 @@ var RulerTool = class {
7852
7994
  this.syncConfig(configService);
7853
7995
  configService.onAnyChange((e) => {
7854
7996
  let shouldUpdate = false;
7855
- if (e.key.startsWith("ruler.")) {
7997
+ if (e.key === RULER_DEBUG_KEY) {
7998
+ this.debugEnabled = e.value === true;
7999
+ this.log("config:update", {
8000
+ key: e.key,
8001
+ raw: e.value,
8002
+ normalized: this.debugEnabled
8003
+ });
8004
+ } else if (e.key.startsWith("ruler.")) {
7856
8005
  const prop = e.key.split(".")[1];
7857
8006
  if (prop && prop in this) {
7858
8007
  if (this.numericProps.has(prop)) {
@@ -7939,6 +8088,12 @@ var RulerTool = class {
7939
8088
  min: RULER_FONT_SIZE_MIN,
7940
8089
  max: RULER_FONT_SIZE_MAX,
7941
8090
  default: DEFAULT_FONT_SIZE
8091
+ },
8092
+ {
8093
+ id: RULER_DEBUG_KEY,
8094
+ type: "boolean",
8095
+ label: "Ruler Debug Log",
8096
+ default: false
7942
8097
  }
7943
8098
  ],
7944
8099
  [import_core8.ContributionPointIds.COMMANDS]: [
@@ -7975,7 +8130,11 @@ var RulerTool = class {
7975
8130
  ]
7976
8131
  };
7977
8132
  }
8133
+ isDebugEnabled() {
8134
+ return this.debugEnabled;
8135
+ }
7978
8136
  log(step, payload) {
8137
+ if (!this.isDebugEnabled()) return;
7979
8138
  if (payload) {
7980
8139
  console.debug(`[RulerTool] ${step}`, payload);
7981
8140
  return;
@@ -8004,6 +8163,7 @@ var RulerTool = class {
8004
8163
  configService.get("ruler.fontSize", this.fontSize),
8005
8164
  DEFAULT_FONT_SIZE
8006
8165
  );
8166
+ this.debugEnabled = configService.get(RULER_DEBUG_KEY, this.debugEnabled) === true;
8007
8167
  this.log("config:loaded", {
8008
8168
  thickness: this.thickness,
8009
8169
  gap: this.gap,