@pooder/kit 5.0.3 → 5.1.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/index.d.mts +239 -269
  3. package/dist/index.d.ts +239 -269
  4. package/dist/index.js +6485 -5833
  5. package/dist/index.mjs +6587 -5923
  6. package/package.json +2 -2
  7. package/src/{background.ts → extensions/background.ts} +1 -1
  8. package/src/{dieline.ts → extensions/dieline.ts} +39 -17
  9. package/src/{feature.ts → extensions/feature.ts} +80 -67
  10. package/src/{film.ts → extensions/film.ts} +1 -1
  11. package/src/{geometry.ts → extensions/geometry.ts} +151 -105
  12. package/src/{image.ts → extensions/image.ts} +190 -192
  13. package/src/extensions/index.ts +11 -0
  14. package/src/{maskOps.ts → extensions/maskOps.ts} +28 -10
  15. package/src/{mirror.ts → extensions/mirror.ts} +1 -1
  16. package/src/{ruler.ts → extensions/ruler.ts} +5 -3
  17. package/src/extensions/sceneLayout.ts +140 -0
  18. package/src/{sceneLayoutModel.ts → extensions/sceneLayoutModel.ts} +17 -10
  19. package/src/extensions/sceneVisibility.ts +71 -0
  20. package/src/{size.ts → extensions/size.ts} +23 -13
  21. package/src/{tracer.ts → extensions/tracer.ts} +374 -45
  22. package/src/{white-ink.ts → extensions/white-ink.ts} +620 -236
  23. package/src/index.ts +2 -14
  24. package/src/{ViewportSystem.ts → services/ViewportSystem.ts} +5 -2
  25. package/src/services/index.ts +3 -0
  26. package/src/sceneLayout.ts +0 -121
  27. package/src/sceneVisibility.ts +0 -49
  28. /package/src/{bridgeSelection.ts → extensions/bridgeSelection.ts} +0 -0
  29. /package/src/{constraints.ts → extensions/constraints.ts} +0 -0
  30. /package/src/{edgeScale.ts → extensions/edgeScale.ts} +0 -0
  31. /package/src/{featureComplete.ts → extensions/featureComplete.ts} +0 -0
  32. /package/src/{wrappedOffsets.ts → extensions/wrappedOffsets.ts} +0 -0
  33. /package/src/{CanvasService.ts → services/CanvasService.ts} +0 -0
  34. /package/src/{renderSpec.ts → services/renderSpec.ts} +0 -0
@@ -212,8 +212,14 @@ function createBaseShape(options: GeometryOptions): paper.PathItem {
212
212
  radius: [Math.max(0, width / 2), Math.max(0, height / 2)],
213
213
  });
214
214
  } else if (shape === "custom" && pathData) {
215
- const path = new paper.Path();
216
- path.pathData = pathData;
215
+ const hasMultipleSubPaths = ((pathData.match(/[Mm]/g) || []).length ?? 0) > 1;
216
+ const path: paper.PathItem = hasMultipleSubPaths
217
+ ? new paper.CompoundPath(pathData)
218
+ : (() => {
219
+ const single = new paper.Path();
220
+ single.pathData = pathData;
221
+ return single;
222
+ })();
217
223
  // Align center
218
224
  path.position = center;
219
225
  if (
@@ -233,6 +239,37 @@ function createBaseShape(options: GeometryOptions): paper.PathItem {
233
239
  }
234
240
  }
235
241
 
242
+ function resolveBridgeBasePath(
243
+ shape: paper.PathItem,
244
+ anchor: paper.Point,
245
+ ): paper.Path | null {
246
+ if (shape instanceof paper.Path) {
247
+ return shape;
248
+ }
249
+
250
+ if (shape instanceof paper.CompoundPath) {
251
+ const children = (shape.children || []).filter(
252
+ (child): child is paper.Path => child instanceof paper.Path,
253
+ );
254
+ if (!children.length) return null;
255
+ let best = children[0];
256
+ let bestDistance = Infinity;
257
+ for (const child of children) {
258
+ const location = child.getNearestLocation(anchor);
259
+ const point = location?.point;
260
+ if (!point) continue;
261
+ const distance = point.getDistance(anchor);
262
+ if (distance < bestDistance) {
263
+ bestDistance = distance;
264
+ best = child;
265
+ }
266
+ }
267
+ return best;
268
+ }
269
+
270
+ return null;
271
+ }
272
+
236
273
  /**
237
274
  * Creates a Paper.js Item for a single feature.
238
275
  */
@@ -307,113 +344,122 @@ function getPerimeterShape(options: GeometryOptions): paper.PathItem {
307
344
  const inset = Math.min(1, Math.max(0, itemBounds.width * 0.01));
308
345
  const xLeft = itemBounds.left + inset;
309
346
  const xRight = itemBounds.right - inset;
310
-
311
- if (!(mainShape instanceof paper.Path)) {
312
- throw new Error("Geometry: Bridge requires base shape to be a Path");
313
- }
314
-
315
- const leftHit = getExitHit({
316
- mainShape,
317
- x: xLeft,
318
- bridgeBottom,
319
- toY,
320
- eps,
321
- delta,
322
- overlap,
323
- op: f.operation,
324
- });
325
- const rightHit = getExitHit({
326
- mainShape,
327
- x: xRight,
328
- bridgeBottom,
329
- toY,
330
- eps,
331
- delta,
332
- overlap,
333
- op: f.operation,
334
- });
335
-
336
- if (!leftHit || !rightHit || xRight - xLeft <= eps) {
337
- throw new Error("Geometry: Bridge ray intersection not found");
338
- }
339
-
340
- const path = mainShape as paper.Path;
341
- const pathLength = path.length;
342
- const leftOffset = leftHit.location.offset;
343
- const rightOffset = rightHit.location.offset;
344
-
345
- const distanceA = wrappedDistance(pathLength, leftOffset, rightOffset);
346
- const distanceB = wrappedDistance(pathLength, rightOffset, leftOffset);
347
- const countFor = (d: number) => Math.max(8, Math.min(80, Math.ceil(d / 6)));
348
-
349
- const offsetsA = sampleWrappedOffsets(
350
- pathLength,
351
- leftOffset,
352
- rightOffset,
353
- countFor(distanceA),
354
- );
355
-
356
- const offsetsB = sampleWrappedOffsets(
357
- pathLength,
358
- rightOffset,
359
- leftOffset,
360
- countFor(distanceB),
361
- );
362
-
363
- const pointsA = offsetsA
364
- .map((o) => path.getPointAt(o))
365
- .filter((p): p is paper.Point => Boolean(p));
366
- const pointsB = offsetsB
367
- .map((o) => path.getPointAt(o))
368
- .filter((p): p is paper.Point => Boolean(p));
369
-
370
- if (pointsA.length < 2 || pointsB.length < 2) {
371
- throw new Error("Geometry: Bridge contour sampling failed");
347
+ const bridgeBasePath = resolveBridgeBasePath(mainShape, center);
348
+ const canBridge = !!bridgeBasePath && xRight - xLeft > eps;
349
+
350
+ if (canBridge && bridgeBasePath) {
351
+ const leftHit = getExitHit({
352
+ mainShape: bridgeBasePath,
353
+ x: xLeft,
354
+ bridgeBottom,
355
+ toY,
356
+ eps,
357
+ delta,
358
+ overlap,
359
+ op: f.operation,
360
+ });
361
+ const rightHit = getExitHit({
362
+ mainShape: bridgeBasePath,
363
+ x: xRight,
364
+ bridgeBottom,
365
+ toY,
366
+ eps,
367
+ delta,
368
+ overlap,
369
+ op: f.operation,
370
+ });
371
+
372
+ if (leftHit && rightHit) {
373
+ const pathLength = bridgeBasePath.length;
374
+ const leftOffset = leftHit.location.offset;
375
+ const rightOffset = rightHit.location.offset;
376
+
377
+ const distanceA = wrappedDistance(pathLength, leftOffset, rightOffset);
378
+ const distanceB = wrappedDistance(pathLength, rightOffset, leftOffset);
379
+ const countFor = (d: number) =>
380
+ Math.max(8, Math.min(80, Math.ceil(d / 6)));
381
+
382
+ const offsetsA = sampleWrappedOffsets(
383
+ pathLength,
384
+ leftOffset,
385
+ rightOffset,
386
+ countFor(distanceA),
387
+ );
388
+
389
+ const offsetsB = sampleWrappedOffsets(
390
+ pathLength,
391
+ rightOffset,
392
+ leftOffset,
393
+ countFor(distanceB),
394
+ );
395
+
396
+ const pointsA = offsetsA
397
+ .map((o) => bridgeBasePath.getPointAt(o))
398
+ .filter((p): p is paper.Point => Boolean(p));
399
+ const pointsB = offsetsB
400
+ .map((o) => bridgeBasePath.getPointAt(o))
401
+ .filter((p): p is paper.Point => Boolean(p));
402
+
403
+ if (pointsA.length >= 2 && pointsB.length >= 2) {
404
+ let topBase = selectOuterChain({
405
+ mainShape: bridgeBasePath,
406
+ pointsA,
407
+ pointsB,
408
+ delta,
409
+ overlap,
410
+ op: f.operation,
411
+ });
412
+
413
+ const dist2 = (a: paper.Point, b: paper.Point) => {
414
+ const dx = a.x - b.x;
415
+ const dy = a.y - b.y;
416
+ return dx * dx + dy * dy;
417
+ };
418
+
419
+ if (
420
+ dist2(topBase[0], leftHit.point) >
421
+ dist2(topBase[0], rightHit.point)
422
+ ) {
423
+ topBase = topBase.slice().reverse();
424
+ }
425
+
426
+ topBase = topBase.slice();
427
+ topBase[0] = leftHit.point;
428
+ topBase[topBase.length - 1] = rightHit.point;
429
+
430
+ const capShiftY =
431
+ f.operation === "subtract"
432
+ ? -Math.max(overlap * 2, delta)
433
+ : overlap;
434
+ const topPoints = topBase.map((p) =>
435
+ p.add(new paper.Point(0, capShiftY)),
436
+ );
437
+
438
+ const bridgeBottomY = bridgeBottom + overlap * 2;
439
+ const bridgePoly = new paper.Path({ insert: false });
440
+ for (const p of topPoints) bridgePoly.add(p);
441
+ bridgePoly.add(new paper.Point(xRight, bridgeBottomY));
442
+ bridgePoly.add(new paper.Point(xLeft, bridgeBottomY));
443
+ bridgePoly.closed = true;
444
+
445
+ const unitedItem = item.unite(bridgePoly);
446
+ item.remove();
447
+ bridgePoly.remove();
448
+
449
+ if (f.operation === "add") {
450
+ adds.push(unitedItem);
451
+ } else {
452
+ subtracts.push(unitedItem);
453
+ }
454
+ return;
455
+ }
456
+ }
372
457
  }
373
458
 
374
- let topBase = selectOuterChain({
375
- mainShape,
376
- pointsA,
377
- pointsB,
378
- delta,
379
- overlap,
380
- op: f.operation,
381
- });
382
-
383
- const dist2 = (a: paper.Point, b: paper.Point) => {
384
- const dx = a.x - b.x;
385
- const dy = a.y - b.y;
386
- return dx * dx + dy * dy;
387
- };
388
-
389
- if (dist2(topBase[0], leftHit.point) > dist2(topBase[0], rightHit.point)) {
390
- topBase = topBase.slice().reverse();
391
- }
392
-
393
- topBase = topBase.slice();
394
- topBase[0] = leftHit.point;
395
- topBase[topBase.length - 1] = rightHit.point;
396
-
397
- const capShiftY = f.operation === "subtract" ? -Math.max(overlap * 2, delta) : overlap;
398
- const topPoints = topBase.map(
399
- (p) => p.add(new paper.Point(0, capShiftY)),
400
- );
401
-
402
- const bridgeBottomY = bridgeBottom + overlap * 2;
403
- const bridgePoly = new paper.Path({ insert: false });
404
- for (const p of topPoints) bridgePoly.add(p);
405
- bridgePoly.add(new paper.Point(xRight, bridgeBottomY));
406
- bridgePoly.add(new paper.Point(xLeft, bridgeBottomY));
407
- bridgePoly.closed = true;
408
-
409
- const unitedItem = item.unite(bridgePoly);
410
- item.remove();
411
- bridgePoly.remove();
412
-
413
459
  if (f.operation === "add") {
414
- adds.push(unitedItem);
460
+ adds.push(item);
415
461
  } else {
416
- subtracts.push(unitedItem);
462
+ subtracts.push(item);
417
463
  }
418
464
  } else {
419
465
  if (f.operation === "add") {