@pooder/kit 6.1.1 → 6.2.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.
@@ -22,7 +22,7 @@ const DEFAULT_BACKGROUND_CONFIG = {
22
22
  order: 0,
23
23
  enabled: true,
24
24
  exportable: false,
25
- color: "#fff",
25
+ color: "#eee",
26
26
  },
27
27
  ],
28
28
  };
@@ -45,6 +45,22 @@ function normalizeFitMode(value, fallback) {
45
45
  }
46
46
  return fallback;
47
47
  }
48
+ function normalizeRegionUnit(value, fallback) {
49
+ if (value === "px" || value === "normalized") {
50
+ return value;
51
+ }
52
+ return fallback;
53
+ }
54
+ function normalizeRegistrationFrame(value, fallback) {
55
+ if (value === "trim" ||
56
+ value === "cut" ||
57
+ value === "bleed" ||
58
+ value === "focus" ||
59
+ value === "viewport") {
60
+ return value;
61
+ }
62
+ return fallback;
63
+ }
48
64
  function normalizeAnchor(value, fallback) {
49
65
  if (typeof value !== "string")
50
66
  return fallback;
@@ -57,6 +73,64 @@ function normalizeOrder(value, fallback) {
57
73
  return fallback;
58
74
  return numeric;
59
75
  }
76
+ function normalizeRegionValue(value, fallback) {
77
+ const numeric = Number(value);
78
+ return Number.isFinite(numeric) ? numeric : fallback;
79
+ }
80
+ function normalizeRegistrationRegion(raw, fallback) {
81
+ if (!raw || typeof raw !== "object") {
82
+ return fallback ? { ...fallback } : undefined;
83
+ }
84
+ const input = raw;
85
+ const base = fallback || {
86
+ left: 0,
87
+ top: 0,
88
+ width: 1,
89
+ height: 1,
90
+ unit: "normalized",
91
+ };
92
+ return {
93
+ left: normalizeRegionValue(input.left, base.left),
94
+ top: normalizeRegionValue(input.top, base.top),
95
+ width: normalizeRegionValue(input.width, base.width),
96
+ height: normalizeRegionValue(input.height, base.height),
97
+ unit: normalizeRegionUnit(input.unit, base.unit),
98
+ };
99
+ }
100
+ function normalizeRegistration(raw, fallback) {
101
+ if (!raw || typeof raw !== "object") {
102
+ return fallback
103
+ ? {
104
+ sourceRegion: fallback.sourceRegion
105
+ ? { ...fallback.sourceRegion }
106
+ : undefined,
107
+ targetFrame: fallback.targetFrame,
108
+ fit: fallback.fit,
109
+ }
110
+ : undefined;
111
+ }
112
+ const input = raw;
113
+ const normalized = {
114
+ sourceRegion: normalizeRegistrationRegion(input.sourceRegion, fallback?.sourceRegion),
115
+ targetFrame: normalizeRegistrationFrame(input.targetFrame, fallback?.targetFrame || "trim"),
116
+ fit: normalizeFitMode(input.fit, fallback?.fit || "stretch"),
117
+ };
118
+ if (!normalized.sourceRegion) {
119
+ return undefined;
120
+ }
121
+ return normalized;
122
+ }
123
+ function cloneRegistration(registration) {
124
+ if (!registration)
125
+ return undefined;
126
+ return {
127
+ sourceRegion: registration.sourceRegion
128
+ ? { ...registration.sourceRegion }
129
+ : undefined,
130
+ targetFrame: registration.targetFrame,
131
+ fit: registration.fit,
132
+ };
133
+ }
60
134
  function normalizeLayer(raw, index, fallback) {
61
135
  const fallbackLayer = fallback || {
62
136
  id: `layer-${index + 1}`,
@@ -70,7 +144,10 @@ function normalizeLayer(raw, index, fallback) {
70
144
  src: "",
71
145
  };
72
146
  if (!raw || typeof raw !== "object") {
73
- return { ...fallbackLayer };
147
+ return {
148
+ ...fallbackLayer,
149
+ registration: cloneRegistration(fallbackLayer.registration),
150
+ };
74
151
  }
75
152
  const input = raw;
76
153
  const kind = normalizeLayerKind(input.kind, fallbackLayer.kind);
@@ -103,6 +180,9 @@ function normalizeLayer(raw, index, fallback) {
103
180
  ? fallbackLayer.src
104
181
  : ""
105
182
  : undefined,
183
+ registration: kind === "image"
184
+ ? normalizeRegistration(input.registration, fallbackLayer.registration)
185
+ : undefined,
106
186
  };
107
187
  }
108
188
  function normalizeConfig(raw) {
@@ -136,7 +216,10 @@ function normalizeConfig(raw) {
136
216
  function cloneConfig(config) {
137
217
  return {
138
218
  version: config.version,
139
- layers: (config.layers || []).map((layer) => ({ ...layer })),
219
+ layers: (config.layers || []).map((layer) => ({
220
+ ...layer,
221
+ registration: cloneRegistration(layer.registration),
222
+ })),
140
223
  };
141
224
  }
142
225
  function mergeConfig(base, patch) {
@@ -353,6 +436,41 @@ class BackgroundTool {
353
436
  height: layout.trimRect.height,
354
437
  };
355
438
  }
439
+ resolveTargetFrameRect(frame) {
440
+ if (frame === "viewport") {
441
+ return this.getViewportRect();
442
+ }
443
+ const layout = this.resolveSceneLayout();
444
+ if (!layout) {
445
+ return frame === "focus" ? this.getViewportRect() : null;
446
+ }
447
+ switch (frame) {
448
+ case "trim":
449
+ case "focus":
450
+ return {
451
+ left: layout.trimRect.left,
452
+ top: layout.trimRect.top,
453
+ width: layout.trimRect.width,
454
+ height: layout.trimRect.height,
455
+ };
456
+ case "cut":
457
+ return {
458
+ left: layout.cutRect.left,
459
+ top: layout.cutRect.top,
460
+ width: layout.cutRect.width,
461
+ height: layout.cutRect.height,
462
+ };
463
+ case "bleed":
464
+ return {
465
+ left: layout.bleedRect.left,
466
+ top: layout.bleedRect.top,
467
+ width: layout.bleedRect.width,
468
+ height: layout.bleedRect.height,
469
+ };
470
+ default:
471
+ return null;
472
+ }
473
+ }
356
474
  resolveAnchorRect(anchor) {
357
475
  if (anchor === "focus") {
358
476
  return this.resolveFocusRect() || this.getViewportRect();
@@ -387,6 +505,60 @@ class BackgroundTool {
387
505
  scaleY: scale,
388
506
  };
389
507
  }
508
+ resolveRegistrationRegion(region, sourceSize) {
509
+ const sourceWidth = Math.max(1, Number(sourceSize.width || 0));
510
+ const sourceHeight = Math.max(1, Number(sourceSize.height || 0));
511
+ const width = region.unit === "normalized" ? region.width * sourceWidth : region.width;
512
+ const height = region.unit === "normalized"
513
+ ? region.height * sourceHeight
514
+ : region.height;
515
+ const left = region.unit === "normalized" ? region.left * sourceWidth : region.left;
516
+ const top = region.unit === "normalized" ? region.top * sourceHeight : region.top;
517
+ if (!Number.isFinite(left) ||
518
+ !Number.isFinite(top) ||
519
+ !Number.isFinite(width) ||
520
+ !Number.isFinite(height) ||
521
+ width <= 0 ||
522
+ height <= 0) {
523
+ return null;
524
+ }
525
+ return { left, top, width, height };
526
+ }
527
+ resolveRegistrationPlacement(layer, sourceSize) {
528
+ const registration = layer.registration;
529
+ if (!registration?.sourceRegion)
530
+ return null;
531
+ const targetRect = this.resolveTargetFrameRect(registration.targetFrame || "trim");
532
+ if (!targetRect)
533
+ return null;
534
+ const sourceRegion = this.resolveRegistrationRegion(registration.sourceRegion, sourceSize);
535
+ if (!sourceRegion)
536
+ return null;
537
+ const fit = registration.fit || "stretch";
538
+ const baseScaleX = targetRect.width / sourceRegion.width;
539
+ const baseScaleY = targetRect.height / sourceRegion.height;
540
+ if (fit === "stretch") {
541
+ return {
542
+ left: targetRect.left - sourceRegion.left * baseScaleX,
543
+ top: targetRect.top - sourceRegion.top * baseScaleY,
544
+ scaleX: baseScaleX,
545
+ scaleY: baseScaleY,
546
+ };
547
+ }
548
+ const uniformScale = fit === "contain"
549
+ ? Math.min(baseScaleX, baseScaleY)
550
+ : Math.max(baseScaleX, baseScaleY);
551
+ const alignedWidth = sourceRegion.width * uniformScale;
552
+ const alignedHeight = sourceRegion.height * uniformScale;
553
+ const offsetLeft = targetRect.left + (targetRect.width - alignedWidth) / 2;
554
+ const offsetTop = targetRect.top + (targetRect.height - alignedHeight) / 2;
555
+ return {
556
+ left: offsetLeft - sourceRegion.left * uniformScale,
557
+ top: offsetTop - sourceRegion.top * uniformScale,
558
+ scaleX: uniformScale,
559
+ scaleY: uniformScale,
560
+ };
561
+ }
390
562
  buildColorLayerSpec(layer) {
391
563
  const rect = this.resolveAnchorRect(layer.anchor);
392
564
  return {
@@ -422,8 +594,8 @@ class BackgroundTool {
422
594
  const sourceSize = this.sourceSizeCache.getSourceSize(src);
423
595
  if (!sourceSize)
424
596
  return [];
425
- const rect = this.resolveAnchorRect(layer.anchor);
426
- const placement = this.resolveImagePlacement(rect, sourceSize, layer.fit);
597
+ const placement = this.resolveRegistrationPlacement(layer, sourceSize) ||
598
+ this.resolveImagePlacement(this.resolveAnchorRect(layer.anchor), sourceSize, layer.fit);
427
599
  return [
428
600
  {
429
601
  id: `background.layer.${layer.id}.image`,
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPathConstraintProbePoint = getPathConstraintProbePoint;
4
+ function getPathConstraintProbePoint(args) {
5
+ const { x, y, geometry, feature } = args;
6
+ const minX = geometry.x - geometry.width / 2;
7
+ const minY = geometry.y - geometry.height / 2;
8
+ const maxX = minX + geometry.width;
9
+ const maxY = minY + geometry.height;
10
+ const absX = minX + x * geometry.width;
11
+ const absY = minY + y * geometry.height;
12
+ const minEdgeDistance = Math.min(x, 1 - x, y, 1 - y);
13
+ const isAnchoredToBoundary = x <= 0 || x >= 1 || y <= 0 || y >= 1 || minEdgeDistance <= 1e-6;
14
+ if (!isAnchoredToBoundary) {
15
+ return { x: absX, y: absY };
16
+ }
17
+ const clampedX = Math.max(0, Math.min(1, x));
18
+ const clampedY = Math.max(0, Math.min(1, y));
19
+ const halfFeatureExtent = feature?.shape === "circle"
20
+ ? Math.max(0, feature?.radius || 0)
21
+ : Math.max(0, (feature?.width || 0) / 2, (feature?.height || 0) / 2);
22
+ const outsidePad = Math.max(1e-3, halfFeatureExtent, Math.min(geometry.width, geometry.height) * 0.02);
23
+ let probeX = minX + clampedX * geometry.width;
24
+ let probeY = minY + clampedY * geometry.height;
25
+ const distances = [
26
+ { edge: "top", dist: Math.abs(y) },
27
+ { edge: "bottom", dist: Math.abs(1 - y) },
28
+ { edge: "left", dist: Math.abs(x) },
29
+ { edge: "right", dist: Math.abs(1 - x) },
30
+ ];
31
+ const nearestDist = Math.min(...distances.map((entry) => entry.dist));
32
+ const activeEdges = distances
33
+ .filter((entry) => Math.abs(entry.dist - nearestDist) <= 1e-6)
34
+ .map((entry) => entry.edge);
35
+ if (activeEdges.includes("top"))
36
+ probeY = minY - outsidePad;
37
+ if (activeEdges.includes("bottom"))
38
+ probeY = maxY + outsidePad;
39
+ if (activeEdges.includes("left"))
40
+ probeX = minX - outsidePad;
41
+ if (activeEdges.includes("right"))
42
+ probeX = maxX + outsidePad;
43
+ return { x: probeX, y: probeY };
44
+ }