@pooder/kit 6.0.1 → 6.1.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 (91) hide show
  1. package/.test-dist/src/extensions/background/BackgroundTool.js +524 -0
  2. package/.test-dist/src/extensions/background/index.js +17 -0
  3. package/.test-dist/src/extensions/dieline/DielineTool.js +748 -0
  4. package/.test-dist/src/extensions/dieline/commands.js +127 -0
  5. package/.test-dist/src/extensions/dieline/config.js +107 -0
  6. package/.test-dist/src/extensions/dieline/index.js +21 -0
  7. package/.test-dist/src/extensions/dieline/model.js +2 -0
  8. package/.test-dist/src/extensions/dieline/renderer.js +2 -0
  9. package/.test-dist/src/extensions/feature/FeatureTool.js +914 -0
  10. package/.test-dist/src/extensions/feature/index.js +17 -0
  11. package/.test-dist/src/extensions/film/FilmTool.js +207 -0
  12. package/.test-dist/src/extensions/film/index.js +17 -0
  13. package/.test-dist/src/extensions/image/ImageTool.js +1499 -0
  14. package/.test-dist/src/extensions/image/commands.js +162 -0
  15. package/.test-dist/src/extensions/image/config.js +129 -0
  16. package/.test-dist/src/extensions/image/index.js +21 -0
  17. package/.test-dist/src/extensions/image/model.js +2 -0
  18. package/.test-dist/src/extensions/image/renderer.js +5 -0
  19. package/.test-dist/src/extensions/mirror/MirrorTool.js +104 -0
  20. package/.test-dist/src/extensions/mirror/index.js +17 -0
  21. package/.test-dist/src/extensions/ruler/RulerTool.js +442 -0
  22. package/.test-dist/src/extensions/ruler/index.js +17 -0
  23. package/.test-dist/src/extensions/sceneLayout.js +2 -93
  24. package/.test-dist/src/extensions/sceneLayoutModel.js +15 -200
  25. package/.test-dist/src/extensions/size/SizeTool.js +332 -0
  26. package/.test-dist/src/extensions/size/index.js +17 -0
  27. package/.test-dist/src/extensions/white-ink/WhiteInkTool.js +1003 -0
  28. package/.test-dist/src/extensions/white-ink/commands.js +148 -0
  29. package/.test-dist/src/extensions/white-ink/config.js +31 -0
  30. package/.test-dist/src/extensions/white-ink/index.js +21 -0
  31. package/.test-dist/src/extensions/white-ink/model.js +2 -0
  32. package/.test-dist/src/extensions/white-ink/renderer.js +5 -0
  33. package/.test-dist/src/services/SceneLayoutService.js +96 -0
  34. package/.test-dist/src/services/index.js +1 -0
  35. package/.test-dist/src/shared/constants/layers.js +25 -0
  36. package/.test-dist/src/shared/imaging/sourceSizeCache.js +82 -0
  37. package/.test-dist/src/shared/index.js +22 -0
  38. package/.test-dist/src/shared/runtime/sessionState.js +74 -0
  39. package/.test-dist/src/shared/runtime/subscriptions.js +30 -0
  40. package/.test-dist/src/shared/scene/frame.js +34 -0
  41. package/.test-dist/src/shared/scene/sceneLayoutModel.js +202 -0
  42. package/.test-dist/tests/run.js +116 -0
  43. package/CHANGELOG.md +14 -0
  44. package/dist/index.d.mts +390 -367
  45. package/dist/index.d.ts +390 -367
  46. package/dist/index.js +5138 -4927
  47. package/dist/index.mjs +1149 -1977
  48. package/dist/tracer-PO7CRBYY.mjs +1016 -0
  49. package/package.json +2 -2
  50. package/src/extensions/{background.ts → background/BackgroundTool.ts} +33 -50
  51. package/src/extensions/background/index.ts +1 -0
  52. package/src/extensions/{dieline.ts → dieline/DielineTool.ts} +14 -218
  53. package/src/extensions/dieline/commands.ts +109 -0
  54. package/src/extensions/dieline/config.ts +106 -0
  55. package/src/extensions/dieline/index.ts +5 -0
  56. package/src/extensions/dieline/model.ts +1 -0
  57. package/src/extensions/dieline/renderer.ts +1 -0
  58. package/src/extensions/{feature.ts → feature/FeatureTool.ts} +27 -21
  59. package/src/extensions/feature/index.ts +1 -0
  60. package/src/extensions/{film.ts → film/FilmTool.ts} +36 -48
  61. package/src/extensions/film/index.ts +1 -0
  62. package/src/extensions/{image.ts → image/ImageTool.ts} +123 -402
  63. package/src/extensions/image/commands.ts +176 -0
  64. package/src/extensions/image/config.ts +128 -0
  65. package/src/extensions/image/index.ts +5 -0
  66. package/src/extensions/image/model.ts +1 -0
  67. package/src/extensions/image/renderer.ts +1 -0
  68. package/src/extensions/{mirror.ts → mirror/MirrorTool.ts} +1 -1
  69. package/src/extensions/mirror/index.ts +1 -0
  70. package/src/extensions/{ruler.ts → ruler/RulerTool.ts} +4 -5
  71. package/src/extensions/ruler/index.ts +1 -0
  72. package/src/extensions/sceneLayout.ts +1 -140
  73. package/src/extensions/sceneLayoutModel.ts +1 -364
  74. package/src/extensions/{size.ts → size/SizeTool.ts} +7 -6
  75. package/src/extensions/size/index.ts +1 -0
  76. package/src/extensions/{white-ink.ts → white-ink/WhiteInkTool.ts} +130 -317
  77. package/src/extensions/white-ink/commands.ts +157 -0
  78. package/src/extensions/white-ink/config.ts +30 -0
  79. package/src/extensions/white-ink/index.ts +5 -0
  80. package/src/extensions/white-ink/model.ts +1 -0
  81. package/src/extensions/white-ink/renderer.ts +1 -0
  82. package/src/services/SceneLayoutService.ts +139 -0
  83. package/src/services/index.ts +1 -0
  84. package/src/shared/constants/layers.ts +23 -0
  85. package/src/shared/imaging/sourceSizeCache.ts +103 -0
  86. package/src/shared/index.ts +6 -0
  87. package/src/shared/runtime/sessionState.ts +105 -0
  88. package/src/shared/runtime/subscriptions.ts +45 -0
  89. package/src/shared/scene/frame.ts +46 -0
  90. package/src/shared/scene/sceneLayoutModel.ts +367 -0
  91. package/tests/run.ts +146 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/kit",
3
- "version": "6.0.1",
3
+ "version": "6.1.1",
4
4
  "description": "Standard plugins for Pooder editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "paper": "^0.12.18",
21
21
  "fabric": "^7.0.0",
22
- "@pooder/core": "2.2.0"
22
+ "@pooder/core": "2.2.1"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsup src/index.ts --format cjs,esm --dts",
@@ -7,17 +7,18 @@ import {
7
7
  ConfigurationService,
8
8
  } from "@pooder/core";
9
9
  import { FabricImage } from "fabric";
10
- import { CanvasService, RenderObjectSpec } from "../services";
10
+ import { CanvasService, RenderObjectSpec } from "../../services";
11
11
  import {
12
12
  computeSceneLayout,
13
13
  readSizeState,
14
14
  type SceneLayoutSnapshot,
15
- } from "./sceneLayoutModel";
16
-
17
- interface SourceSize {
18
- width: number;
19
- height: number;
20
- }
15
+ } from "../../shared/scene/sceneLayoutModel";
16
+ import { BACKGROUND_LAYER_ID } from "../../shared/constants/layers";
17
+ import {
18
+ createSourceSizeCache,
19
+ type SourceSize,
20
+ } from "../../shared/imaging/sourceSizeCache";
21
+ import { SubscriptionBag } from "../../shared/runtime/subscriptions";
21
22
 
22
23
  interface Rect {
23
24
  left: number;
@@ -47,7 +48,6 @@ export interface BackgroundConfig {
47
48
  layers: BackgroundLayer[];
48
49
  }
49
50
 
50
- const BACKGROUND_LAYER_ID = "background";
51
51
  const BACKGROUND_CONFIG_KEY = "background.config";
52
52
 
53
53
  const DEFAULT_WIDTH = 800;
@@ -65,7 +65,7 @@ const DEFAULT_BACKGROUND_CONFIG: BackgroundConfig = {
65
65
  order: 0,
66
66
  enabled: true,
67
67
  exportable: false,
68
- color: "#fff",
68
+ color: "#eee",
69
69
  },
70
70
  ],
71
71
  };
@@ -245,13 +245,13 @@ export class BackgroundTool implements Extension {
245
245
 
246
246
  private specs: RenderObjectSpec[] = [];
247
247
  private renderProducerDisposable?: { dispose: () => void };
248
- private configChangeDisposable?: { dispose: () => void };
248
+ private readonly subscriptions = new SubscriptionBag();
249
249
 
250
250
  private renderSeq = 0;
251
251
  private latestSceneLayout: SceneLayoutSnapshot | null = null;
252
-
253
- private sourceSizeBySrc: Map<string, SourceSize> = new Map();
254
- private pendingSizeBySrc: Map<string, Promise<SourceSize | null>> = new Map();
252
+ private sourceSizeCache = createSourceSizeCache((src) =>
253
+ this.loadImageSize(src),
254
+ );
255
255
 
256
256
  private onCanvasResized = () => {
257
257
  this.latestSceneLayout = null;
@@ -270,6 +270,7 @@ export class BackgroundTool implements Extension {
270
270
  }
271
271
 
272
272
  activate(context: ExtensionContext) {
273
+ this.subscriptions.disposeAll();
273
274
  this.canvasService = context.services.get<CanvasService>("CanvasService");
274
275
  if (!this.canvasService) {
275
276
  console.warn("CanvasService not found for BackgroundTool");
@@ -286,8 +287,8 @@ export class BackgroundTool implements Extension {
286
287
  DEFAULT_BACKGROUND_CONFIG,
287
288
  ),
288
289
  );
289
- this.configChangeDisposable?.dispose();
290
- this.configChangeDisposable = this.configService.onAnyChange(
290
+ this.subscriptions.onConfigChange(
291
+ this.configService,
291
292
  (e: { key: string; value: any }) => {
292
293
  if (e.key === BACKGROUND_CONFIG_KEY) {
293
294
  this.config = normalizeConfig(e.value);
@@ -319,21 +320,26 @@ export class BackgroundTool implements Extension {
319
320
  { priority: 0 },
320
321
  );
321
322
 
322
- context.eventBus.on("canvas:resized", this.onCanvasResized);
323
- context.eventBus.on("scene:layout:change", this.onSceneLayoutChanged);
323
+ this.subscriptions.on(
324
+ context.eventBus,
325
+ "canvas:resized",
326
+ this.onCanvasResized,
327
+ );
328
+ this.subscriptions.on(
329
+ context.eventBus,
330
+ "scene:layout:change",
331
+ this.onSceneLayoutChanged,
332
+ );
324
333
  this.updateBackground();
325
334
  }
326
335
 
327
336
  deactivate(context: ExtensionContext) {
328
- context.eventBus.off("canvas:resized", this.onCanvasResized);
329
- context.eventBus.off("scene:layout:change", this.onSceneLayoutChanged);
337
+ this.subscriptions.disposeAll();
330
338
 
331
339
  this.renderSeq += 1;
332
340
  this.specs = [];
333
341
  this.latestSceneLayout = null;
334
-
335
- this.configChangeDisposable?.dispose();
336
- this.configChangeDisposable = undefined;
342
+ this.sourceSizeCache.clear();
337
343
 
338
344
  this.renderProducerDisposable?.dispose();
339
345
  this.renderProducerDisposable = undefined;
@@ -576,7 +582,7 @@ export class BackgroundTool implements Extension {
576
582
  const src = String(layer.src || "").trim();
577
583
  if (!src) return [];
578
584
 
579
- const sourceSize = this.sourceSizeBySrc.get(src);
585
+ const sourceSize = this.sourceSizeCache.getSourceSize(src);
580
586
  if (!sourceSize) return [];
581
587
 
582
588
  const rect = this.resolveAnchorRect(layer.anchor);
@@ -648,29 +654,6 @@ export class BackgroundTool implements Extension {
648
654
  return Array.from(urls);
649
655
  }
650
656
 
651
- private async ensureImageSize(src: string): Promise<SourceSize | null> {
652
- if (!src) return null;
653
-
654
- const cached = this.sourceSizeBySrc.get(src);
655
- if (cached) return cached;
656
-
657
- const pending = this.pendingSizeBySrc.get(src);
658
- if (pending) {
659
- return pending;
660
- }
661
-
662
- const task = this.loadImageSize(src);
663
- this.pendingSizeBySrc.set(src, task);
664
-
665
- try {
666
- return await task;
667
- } finally {
668
- if (this.pendingSizeBySrc.get(src) === task) {
669
- this.pendingSizeBySrc.delete(src);
670
- }
671
- }
672
- }
673
-
674
657
  private async loadImageSize(src: string): Promise<SourceSize | null> {
675
658
  try {
676
659
  const image = await FabricImage.fromURL(src, {
@@ -679,9 +662,7 @@ export class BackgroundTool implements Extension {
679
662
  const width = Number(image?.width || 0);
680
663
  const height = Number(image?.height || 0);
681
664
  if (width > 0 && height > 0) {
682
- const size = { width, height };
683
- this.sourceSizeBySrc.set(src, size);
684
- return size;
665
+ return { width, height };
685
666
  }
686
667
  } catch (error) {
687
668
  console.error("[BackgroundTool] Failed to load image", src, error);
@@ -702,7 +683,9 @@ export class BackgroundTool implements Extension {
702
683
  const activeUrls = this.collectActiveImageUrls(currentConfig);
703
684
 
704
685
  if (activeUrls.length > 0) {
705
- await Promise.all(activeUrls.map((url) => this.ensureImageSize(url)));
686
+ await Promise.all(
687
+ activeUrls.map((url) => this.sourceSizeCache.ensureImageSize(url)),
688
+ );
706
689
  if (seq !== this.renderSeq) return;
707
690
  }
708
691
 
@@ -0,0 +1 @@
1
+ export * from "./BackgroundTool";
@@ -2,32 +2,34 @@ import {
2
2
  Extension,
3
3
  ExtensionContext,
4
4
  ContributionPointIds,
5
- CommandContribution,
6
- ConfigurationContribution,
7
5
  ConfigurationService,
8
6
  } from "@pooder/core";
9
7
  import { Canvas as FabricCanvas, Path, Pattern } from "fabric";
10
- import { CanvasService, RenderEffectSpec, RenderObjectSpec } from "../services";
11
- import { ImageTracer } from "./tracer";
12
- import { parseLengthToMm } from "../units";
8
+ import { CanvasService, RenderEffectSpec, RenderObjectSpec } from "../../services";
9
+ import { parseLengthToMm } from "../../units";
13
10
  import {
14
11
  DEFAULT_DIELINE_SHAPE,
15
12
  DEFAULT_DIELINE_SHAPE_STYLE,
16
- DIELINE_SHAPES,
17
13
  normalizeShapeStyle,
18
14
  normalizeDielineShape,
19
- } from "./dielineShape";
20
- import type { DielineShape, DielineShapeStyle } from "./dielineShape";
15
+ } from "../dielineShape";
16
+ import type { DielineShape, DielineShapeStyle } from "../dielineShape";
21
17
  import {
22
18
  generateDielinePath,
23
19
  generateBleedZonePath,
24
20
  DielineFeature,
25
- } from "./geometry";
21
+ } from "../geometry";
26
22
  import {
27
23
  buildSceneGeometry,
28
24
  computeSceneLayout,
29
25
  readSizeState,
30
- } from "./sceneLayoutModel";
26
+ } from "../../shared/scene/sceneLayoutModel";
27
+ import {
28
+ DIELINE_LAYER_ID,
29
+ IMAGE_OBJECT_LAYER_ID,
30
+ } from "../../shared/constants/layers";
31
+ import { createDielineCommands } from "./commands";
32
+ import { createDielineConfigurations } from "./config";
31
33
 
32
34
  export interface DielineGeometry {
33
35
  shape: DielineShape;
@@ -72,9 +74,6 @@ export interface DielineState {
72
74
  customSourceHeightPx?: number;
73
75
  }
74
76
 
75
- const IMAGE_OBJECT_LAYER_ID = "image.user";
76
- const DIELINE_LAYER_ID = "dieline-overlay";
77
-
78
77
  export class DielineTool implements Extension {
79
78
  id = "pooder.kit.dieline";
80
79
  public metadata = {
@@ -359,7 +358,6 @@ export class DielineTool implements Extension {
359
358
  }
360
359
 
361
360
  contribute() {
362
- const s = this.state;
363
361
  return {
364
362
  [ContributionPointIds.TOOLS]: [
365
363
  {
@@ -372,210 +370,8 @@ export class DielineTool implements Extension {
372
370
  },
373
371
  },
374
372
  ],
375
- [ContributionPointIds.CONFIGURATIONS]: [
376
- {
377
- id: "dieline.shape",
378
- type: "select",
379
- label: "Shape",
380
- options: Array.from(DIELINE_SHAPES),
381
- default: s.shape,
382
- },
383
- {
384
- id: "dieline.radius",
385
- type: "number",
386
- label: "Corner Radius (mm)",
387
- min: 0,
388
- max: 500,
389
- default: s.radius,
390
- },
391
- {
392
- id: "dieline.shapeStyle",
393
- type: "json",
394
- label: "Shape Style",
395
- default: s.shapeStyle,
396
- },
397
- {
398
- id: "dieline.showBleedLines",
399
- type: "boolean",
400
- label: "Show Bleed Lines",
401
- default: s.showBleedLines,
402
- },
403
- {
404
- id: "dieline.strokeWidth",
405
- type: "number",
406
- label: "Line Width",
407
- min: 0.1,
408
- max: 10,
409
- step: 0.1,
410
- default: s.mainLine.width,
411
- },
412
- {
413
- id: "dieline.strokeColor",
414
- type: "color",
415
- label: "Line Color",
416
- default: s.mainLine.color,
417
- },
418
- {
419
- id: "dieline.dashLength",
420
- type: "number",
421
- label: "Dash Length",
422
- min: 1,
423
- max: 50,
424
- default: s.mainLine.dashLength,
425
- },
426
- {
427
- id: "dieline.style",
428
- type: "select",
429
- label: "Line Style",
430
- options: ["solid", "dashed", "hidden"],
431
- default: s.mainLine.style,
432
- },
433
- {
434
- id: "dieline.offsetStrokeWidth",
435
- type: "number",
436
- label: "Offset Line Width",
437
- min: 0.1,
438
- max: 10,
439
- step: 0.1,
440
- default: s.offsetLine.width,
441
- },
442
- {
443
- id: "dieline.offsetStrokeColor",
444
- type: "color",
445
- label: "Offset Line Color",
446
- default: s.offsetLine.color,
447
- },
448
- {
449
- id: "dieline.offsetDashLength",
450
- type: "number",
451
- label: "Offset Dash Length",
452
- min: 1,
453
- max: 50,
454
- default: s.offsetLine.dashLength,
455
- },
456
- {
457
- id: "dieline.offsetStyle",
458
- type: "select",
459
- label: "Offset Line Style",
460
- options: ["solid", "dashed", "hidden"],
461
- default: s.offsetLine.style,
462
- },
463
- {
464
- id: "dieline.insideColor",
465
- type: "color",
466
- label: "Inside Color",
467
- default: s.insideColor,
468
- },
469
- {
470
- id: "dieline.features",
471
- type: "json",
472
- label: "Edge Features",
473
- default: s.features,
474
- },
475
- ] as ConfigurationContribution[],
476
- [ContributionPointIds.COMMANDS]: [
477
- {
478
- command: "updateFeaturePosition",
479
- title: "Update Feature Position",
480
- handler: (groupId: string, x: number, y: number) => {
481
- const configService = this.context?.services.get<any>(
482
- "ConfigurationService",
483
- );
484
- if (!configService) return;
485
-
486
- const features = configService.get("dieline.features") || [];
487
-
488
- let changed = false;
489
- const newFeatures = features.map((f: any) => {
490
- if (f.groupId === groupId) {
491
- if (f.x !== x || f.y !== y) {
492
- changed = true;
493
- return { ...f, x, y };
494
- }
495
- }
496
- return f;
497
- });
498
-
499
- if (changed) {
500
- configService.update("dieline.features", newFeatures);
501
- }
502
- },
503
- },
504
- {
505
- command: "exportCutImage",
506
- title: "Export Cut Image",
507
- handler: (options?: { debug?: boolean }) => {
508
- return this.exportCutImage(options);
509
- },
510
- },
511
- {
512
- command: "detectEdge",
513
- title: "Detect Edge from Image",
514
- handler: async (
515
- imageUrl: string,
516
- options?: {
517
- expand?: number;
518
- smoothing?: boolean;
519
- simplifyTolerance?: number;
520
- threshold?: number;
521
- debug?: boolean;
522
- },
523
- ) => {
524
- try {
525
- const detectOptions = options || {};
526
- const debug = detectOptions.debug === true;
527
- const tracerOptions = {
528
- expand: detectOptions.expand ?? 0,
529
- smoothing: detectOptions.smoothing ?? true,
530
- simplifyTolerance: detectOptions.simplifyTolerance ?? 2,
531
- threshold: detectOptions.threshold,
532
- debug,
533
- };
534
-
535
- // Helper to get image dimensions
536
- const loadImage = (url: string): Promise<HTMLImageElement> => {
537
- return new Promise((resolve, reject) => {
538
- const img = new Image();
539
- img.crossOrigin = "Anonymous";
540
- img.onload = () => resolve(img);
541
- img.onerror = (e) => reject(e);
542
- img.src = url;
543
- });
544
- };
545
-
546
- const [img, traced] = await Promise.all([
547
- loadImage(imageUrl),
548
- ImageTracer.traceWithBounds(imageUrl, tracerOptions),
549
- ]);
550
- const { pathData, baseBounds, bounds } = traced;
551
-
552
- if (debug) {
553
- console.info("[DielineTool] detectEdge", {
554
- imageWidth: img.width,
555
- imageHeight: img.height,
556
- baseBounds,
557
- expandedBounds: bounds,
558
- currentDielineWidth: s.width,
559
- currentDielineHeight: s.height,
560
- options: tracerOptions,
561
- strategy: "single-connected-silhouette",
562
- });
563
- }
564
-
565
- return {
566
- pathData,
567
- rawBounds: bounds,
568
- baseBounds,
569
- imageWidth: img.width,
570
- imageHeight: img.height,
571
- };
572
- } catch (e) {
573
- console.error("Edge detection failed", e);
574
- throw e;
575
- }
576
- },
577
- },
578
- ] as CommandContribution[],
373
+ [ContributionPointIds.CONFIGURATIONS]: createDielineConfigurations(this.state),
374
+ [ContributionPointIds.COMMANDS]: createDielineCommands(this, this.state),
579
375
  };
580
376
  }
581
377
 
@@ -0,0 +1,109 @@
1
+ import type { CommandContribution } from "@pooder/core";
2
+
3
+ export function createDielineCommands(tool: any, state: any): CommandContribution[] {
4
+ return [
5
+ {
6
+ command: "updateFeaturePosition",
7
+ id: "updateFeaturePosition",
8
+ title: "Update Feature Position",
9
+ handler: (groupId: string, x: number, y: number) => {
10
+ const configService = tool.context?.services.get("ConfigurationService");
11
+ if (!configService) return;
12
+
13
+ const features = configService.get("dieline.features") || [];
14
+
15
+ let changed = false;
16
+ const newFeatures = features.map((f: any) => {
17
+ if (f.groupId === groupId) {
18
+ if (f.x !== x || f.y !== y) {
19
+ changed = true;
20
+ return { ...f, x, y };
21
+ }
22
+ }
23
+ return f;
24
+ });
25
+
26
+ if (changed) {
27
+ configService.update("dieline.features", newFeatures);
28
+ }
29
+ },
30
+ },
31
+ {
32
+ command: "exportCutImage",
33
+ id: "exportCutImage",
34
+ title: "Export Cut Image",
35
+ handler: (options?: { debug?: boolean }) => {
36
+ return tool.exportCutImage(options);
37
+ },
38
+ },
39
+ {
40
+ command: "detectEdge",
41
+ id: "detectEdge",
42
+ title: "Detect Edge from Image",
43
+ handler: async (
44
+ imageUrl: string,
45
+ options?: {
46
+ expand?: number;
47
+ smoothing?: boolean;
48
+ simplifyTolerance?: number;
49
+ threshold?: number;
50
+ debug?: boolean;
51
+ },
52
+ ) => {
53
+ try {
54
+ const detectOptions = options || {};
55
+ const debug = detectOptions.debug === true;
56
+ const tracerOptions = {
57
+ expand: detectOptions.expand ?? 0,
58
+ smoothing: detectOptions.smoothing ?? true,
59
+ simplifyTolerance: detectOptions.simplifyTolerance ?? 2,
60
+ threshold: detectOptions.threshold,
61
+ debug,
62
+ };
63
+
64
+ const loadImage = (url: string): Promise<HTMLImageElement> => {
65
+ return new Promise((resolve, reject) => {
66
+ const img = new Image();
67
+ img.crossOrigin = "Anonymous";
68
+ img.onload = () => resolve(img);
69
+ img.onerror = (e) => reject(e);
70
+ img.src = url;
71
+ });
72
+ };
73
+
74
+ const [img, traced] = await Promise.all([
75
+ loadImage(imageUrl),
76
+ import("../tracer").then(({ ImageTracer }) =>
77
+ ImageTracer.traceWithBounds(imageUrl, tracerOptions),
78
+ ),
79
+ ]);
80
+ const { pathData, baseBounds, bounds } = traced;
81
+
82
+ if (debug) {
83
+ console.info("[DielineTool] detectEdge", {
84
+ imageWidth: img.width,
85
+ imageHeight: img.height,
86
+ baseBounds,
87
+ expandedBounds: bounds,
88
+ currentDielineWidth: state.width,
89
+ currentDielineHeight: state.height,
90
+ options: tracerOptions,
91
+ strategy: "single-connected-silhouette",
92
+ });
93
+ }
94
+
95
+ return {
96
+ pathData,
97
+ rawBounds: bounds,
98
+ baseBounds,
99
+ imageWidth: img.width,
100
+ imageHeight: img.height,
101
+ };
102
+ } catch (e) {
103
+ console.error("Edge detection failed", e);
104
+ throw e;
105
+ }
106
+ },
107
+ },
108
+ ];
109
+ }
@@ -0,0 +1,106 @@
1
+ import type { ConfigurationContribution } from "@pooder/core";
2
+ import { DIELINE_SHAPES } from "../dielineShape";
3
+
4
+ export function createDielineConfigurations(state: any): ConfigurationContribution[] {
5
+ return [
6
+ {
7
+ id: "dieline.shape",
8
+ type: "select",
9
+ label: "Shape",
10
+ options: Array.from(DIELINE_SHAPES),
11
+ default: state.shape,
12
+ },
13
+ {
14
+ id: "dieline.radius",
15
+ type: "number",
16
+ label: "Corner Radius (mm)",
17
+ min: 0,
18
+ max: 500,
19
+ default: state.radius,
20
+ },
21
+ {
22
+ id: "dieline.shapeStyle",
23
+ type: "json",
24
+ label: "Shape Style",
25
+ default: state.shapeStyle,
26
+ },
27
+ {
28
+ id: "dieline.showBleedLines",
29
+ type: "boolean",
30
+ label: "Show Bleed Lines",
31
+ default: state.showBleedLines,
32
+ },
33
+ {
34
+ id: "dieline.strokeWidth",
35
+ type: "number",
36
+ label: "Line Width",
37
+ min: 0.1,
38
+ max: 10,
39
+ step: 0.1,
40
+ default: state.mainLine.width,
41
+ },
42
+ {
43
+ id: "dieline.strokeColor",
44
+ type: "color",
45
+ label: "Line Color",
46
+ default: state.mainLine.color,
47
+ },
48
+ {
49
+ id: "dieline.dashLength",
50
+ type: "number",
51
+ label: "Dash Length",
52
+ min: 1,
53
+ max: 50,
54
+ default: state.mainLine.dashLength,
55
+ },
56
+ {
57
+ id: "dieline.style",
58
+ type: "select",
59
+ label: "Line Style",
60
+ options: ["solid", "dashed", "hidden"],
61
+ default: state.mainLine.style,
62
+ },
63
+ {
64
+ id: "dieline.offsetStrokeWidth",
65
+ type: "number",
66
+ label: "Offset Line Width",
67
+ min: 0.1,
68
+ max: 10,
69
+ step: 0.1,
70
+ default: state.offsetLine.width,
71
+ },
72
+ {
73
+ id: "dieline.offsetStrokeColor",
74
+ type: "color",
75
+ label: "Offset Line Color",
76
+ default: state.offsetLine.color,
77
+ },
78
+ {
79
+ id: "dieline.offsetDashLength",
80
+ type: "number",
81
+ label: "Offset Dash Length",
82
+ min: 1,
83
+ max: 50,
84
+ default: state.offsetLine.dashLength,
85
+ },
86
+ {
87
+ id: "dieline.offsetStyle",
88
+ type: "select",
89
+ label: "Offset Line Style",
90
+ options: ["solid", "dashed", "hidden"],
91
+ default: state.offsetLine.style,
92
+ },
93
+ {
94
+ id: "dieline.insideColor",
95
+ type: "color",
96
+ label: "Inside Color",
97
+ default: state.insideColor,
98
+ },
99
+ {
100
+ id: "dieline.features",
101
+ type: "json",
102
+ label: "Edge Features",
103
+ default: state.features,
104
+ },
105
+ ];
106
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./DielineTool";
2
+ export * from "./commands";
3
+ export * from "./config";
4
+ export * from "./model";
5
+ export * from "./renderer";
@@ -0,0 +1 @@
1
+ export type { DielineGeometry, DielineState, LineStyle } from "./DielineTool";
@@ -0,0 +1 @@
1
+ export type { RenderEffectSpec, RenderObjectSpec } from "../../services";