@pooder/kit 6.2.0 → 6.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/kit",
3
- "version": "6.2.0",
3
+ "version": "6.2.2",
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,
@@ -1,7 +1,15 @@
1
1
  import type { Pattern } from "fabric";
2
- import type { RenderEffectSpec, RenderObjectSpec, VisibilityExpr } from "../../services";
2
+ import type {
3
+ RenderEffectSpec,
4
+ RenderObjectSpec,
5
+ VisibilityExpr,
6
+ } from "../../services";
3
7
  import type { SceneLayoutSnapshot } from "../../shared/scene/sceneLayoutModel";
4
8
  import { generateBleedZonePath, generateDielinePath } from "../geometry";
9
+ import {
10
+ projectPlacedFeatures,
11
+ resolveFeaturePlacements,
12
+ } from "../featurePlacement";
5
13
  import { IMAGE_OBJECT_LAYER_ID } from "../../shared/constants/layers";
6
14
  import type { DielineState } from "./model";
7
15
 
@@ -41,17 +49,6 @@ const DEFAULT_IDS: DielineRenderIds = {
41
49
  clipSource: "dieline.effect.clip-path",
42
50
  };
43
51
 
44
- function scaleFeatures(state: DielineState, scale: number) {
45
- return (state.features || []).map((feature) => ({
46
- ...feature,
47
- x: feature.x,
48
- y: feature.y,
49
- width: (feature.width || 0) * scale,
50
- height: (feature.height || 0) * scale,
51
- radius: (feature.radius || 0) * scale,
52
- }));
53
- }
54
-
55
52
  export function buildDielineRenderBundle(
56
53
  options: DielineRenderOptions,
57
54
  ): DielineRenderBundle {
@@ -67,7 +64,8 @@ export function buildDielineRenderBundle(
67
64
  clipTargetPassIds = [IMAGE_OBJECT_LAYER_ID],
68
65
  clipVisibility,
69
66
  } = options;
70
- const { shape, shapeStyle, radius, mainLine, offsetLine, insideColor } = state;
67
+ const { shape, shapeStyle, radius, mainLine, offsetLine, insideColor } =
68
+ state;
71
69
 
72
70
  const scale = sceneLayout.scale;
73
71
  const cx = sceneLayout.trimRect.centerX;
@@ -80,8 +78,41 @@ export function buildDielineRenderBundle(
80
78
  const visualOffset = (cutW - visualWidth) / 2;
81
79
  const cutR =
82
80
  visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
83
- const absoluteFeatures = scaleFeatures(state, scale);
84
- const cutFeatures = absoluteFeatures.filter((feature) => !feature.skipCut);
81
+ const placements = resolveFeaturePlacements(state.features || [], {
82
+ shape,
83
+ shapeStyle,
84
+ pathData: state.pathData,
85
+ customSourceWidthPx: state.customSourceWidthPx,
86
+ customSourceHeightPx: state.customSourceHeightPx,
87
+ canvasWidth,
88
+ canvasHeight,
89
+ x: cx,
90
+ y: cy,
91
+ width: visualWidth,
92
+ height: visualHeight,
93
+ radius: visualRadius,
94
+ scale,
95
+ });
96
+ const absoluteFeatures = projectPlacedFeatures(
97
+ placements,
98
+ {
99
+ x: cx,
100
+ y: cy,
101
+ width: visualWidth,
102
+ height: visualHeight,
103
+ },
104
+ scale,
105
+ );
106
+ const cutFeatures = projectPlacedFeatures(
107
+ placements.filter((placement) => !placement.feature.skipCut),
108
+ {
109
+ x: cx,
110
+ y: cy,
111
+ width: cutW,
112
+ height: cutH,
113
+ },
114
+ scale,
115
+ );
85
116
 
86
117
  const common = {
87
118
  shape,
@@ -92,6 +123,13 @@ export function buildDielineRenderBundle(
92
123
  canvasWidth,
93
124
  canvasHeight,
94
125
  };
126
+ const cutFrameRect = {
127
+ left: cx - cutW / 2,
128
+ top: cy - cutH / 2,
129
+ width: cutW,
130
+ height: cutH,
131
+ space: "screen" as const,
132
+ };
95
133
 
96
134
  const specs: RenderObjectSpec[] = [];
97
135
 
@@ -232,9 +270,13 @@ export function buildDielineRenderBundle(
232
270
  width: cutW,
233
271
  height: cutH,
234
272
  radius: cutR,
235
- x: cx,
236
- y: cy,
273
+ // Build the clip path in the cut frame's local coordinates so Fabric
274
+ // does not have to infer placement from the standalone path bounds.
275
+ x: cutW / 2,
276
+ y: cutH / 2,
237
277
  features: cutFeatures,
278
+ canvasWidth: cutW,
279
+ canvasHeight: cutH,
238
280
  });
239
281
 
240
282
  if (!clipPathData) {
@@ -253,6 +295,12 @@ export function buildDielineRenderBundle(
253
295
  id: ids.clipSource,
254
296
  type: "path",
255
297
  space: "screen",
298
+ layout: {
299
+ reference: "custom",
300
+ referenceRect: cutFrameRect,
301
+ alignX: "start",
302
+ alignY: "start",
303
+ },
256
304
  data: {
257
305
  id: ids.clipSource,
258
306
  type: "dieline-effect",
@@ -13,9 +13,9 @@ import {
13
13
  RenderObjectSpec,
14
14
  RenderPassSpec,
15
15
  } from "../../services";
16
- import { resolveFeaturePosition } from "../geometry";
17
16
  import { ConstraintRegistry, ConstraintFeature } from "../constraints";
18
17
  import { completeFeaturesStrict } from "../featureComplete";
18
+ import { resolveFeaturePlacements } from "../featurePlacement";
19
19
  import {
20
20
  computeSceneLayout,
21
21
  readSizeState,
@@ -935,10 +935,30 @@ export class FeatureTool implements Extension {
935
935
 
936
936
  const groups = new Map<string, MarkerRenderState[]>();
937
937
  const singles: MarkerRenderState[] = [];
938
+ const placements = resolveFeaturePlacements(
939
+ this.workingFeatures,
940
+ {
941
+ shape: this.currentGeometry.shape,
942
+ shapeStyle: this.currentGeometry.shapeStyle,
943
+ pathData: this.currentGeometry.pathData,
944
+ customSourceWidthPx: this.currentGeometry.customSourceWidthPx,
945
+ customSourceHeightPx: this.currentGeometry.customSourceHeightPx,
946
+ x: this.currentGeometry.x,
947
+ y: this.currentGeometry.y,
948
+ width: this.currentGeometry.width,
949
+ height: this.currentGeometry.height,
950
+ radius: this.currentGeometry.radius,
951
+ scale: this.currentGeometry.scale || 1,
952
+ },
953
+ );
938
954
 
939
- this.workingFeatures.forEach((feature, index) => {
955
+ placements.forEach((placement, index) => {
956
+ const feature = placement.feature;
940
957
  const geometry = this.getGeometryForFeature(this.currentGeometry!, feature);
941
- const position = resolveFeaturePosition(feature, geometry);
958
+ const position = {
959
+ x: placement.centerX,
960
+ y: placement.centerY,
961
+ };
942
962
  const scale = geometry.scale || 1;
943
963
  const marker: MarkerRenderState = {
944
964
  feature,
@@ -0,0 +1,35 @@
1
+ import type { DielineFeature } from "./geometry";
2
+
3
+ export interface FeatureCoordinateGeometry {
4
+ x: number;
5
+ y: number;
6
+ width: number;
7
+ height: number;
8
+ }
9
+
10
+ export function resolveFeaturePosition(
11
+ feature: Pick<DielineFeature, "x" | "y">,
12
+ geometry: FeatureCoordinateGeometry,
13
+ ): { x: number; y: number } {
14
+ const { x, y, width, height } = geometry;
15
+ const left = x - width / 2;
16
+ const top = y - height / 2;
17
+
18
+ return {
19
+ x: left + feature.x * width,
20
+ y: top + feature.y * height,
21
+ };
22
+ }
23
+
24
+ export function normalizePointInGeometry(
25
+ point: { x: number; y: number },
26
+ geometry: FeatureCoordinateGeometry,
27
+ ): { x: number; y: number } {
28
+ const left = geometry.x - geometry.width / 2;
29
+ const top = geometry.y - geometry.height / 2;
30
+
31
+ return {
32
+ x: geometry.width > 0 ? (point.x - left) / geometry.width : 0.5,
33
+ y: geometry.height > 0 ? (point.y - top) / geometry.height : 0.5,
34
+ };
35
+ }
@@ -0,0 +1,118 @@
1
+ import { ConstraintFeature, ConstraintRegistry } from "./constraints";
2
+ import type { GeometryOptions } from "./geometry";
3
+ import {
4
+ normalizePointInGeometry,
5
+ resolveFeaturePosition,
6
+ } from "./featureCoordinates";
7
+
8
+ export interface FeaturePlacementGeometry
9
+ extends Pick<
10
+ GeometryOptions,
11
+ | "shape"
12
+ | "shapeStyle"
13
+ | "pathData"
14
+ | "customSourceWidthPx"
15
+ | "customSourceHeightPx"
16
+ | "canvasWidth"
17
+ | "canvasHeight"
18
+ > {
19
+ x: number;
20
+ y: number;
21
+ width: number;
22
+ height: number;
23
+ radius: number;
24
+ scale: number;
25
+ }
26
+
27
+ export interface FeatureProjectionGeometry {
28
+ x: number;
29
+ y: number;
30
+ width: number;
31
+ height: number;
32
+ }
33
+
34
+ export interface FeaturePlacement<TFeature extends ConstraintFeature = ConstraintFeature> {
35
+ feature: TFeature;
36
+ normalizedX: number;
37
+ normalizedY: number;
38
+ centerX: number;
39
+ centerY: number;
40
+ }
41
+
42
+ function scaleFeatureForRender<TFeature extends ConstraintFeature>(
43
+ feature: TFeature,
44
+ scale: number,
45
+ x: number,
46
+ y: number,
47
+ ): TFeature {
48
+ return {
49
+ ...feature,
50
+ x,
51
+ y,
52
+ width: feature.width !== undefined ? feature.width * scale : undefined,
53
+ height: feature.height !== undefined ? feature.height * scale : undefined,
54
+ radius: feature.radius !== undefined ? feature.radius * scale : undefined,
55
+ };
56
+ }
57
+
58
+ export function resolveFeaturePlacements<TFeature extends ConstraintFeature>(
59
+ features: TFeature[],
60
+ geometry: FeaturePlacementGeometry,
61
+ ): FeaturePlacement<TFeature>[] {
62
+ const dielineWidth =
63
+ geometry.scale > 0 ? geometry.width / geometry.scale : geometry.width;
64
+ const dielineHeight =
65
+ geometry.scale > 0 ? geometry.height / geometry.scale : geometry.height;
66
+
67
+ return (features || []).map((feature) => {
68
+ const activeConstraints = feature.constraints?.filter(
69
+ (constraint) => !constraint.validateOnly,
70
+ );
71
+ const constrained = ConstraintRegistry.apply(
72
+ feature.x,
73
+ feature.y,
74
+ feature,
75
+ {
76
+ dielineWidth,
77
+ dielineHeight,
78
+ geometry,
79
+ },
80
+ activeConstraints,
81
+ );
82
+ const center = resolveFeaturePosition(
83
+ {
84
+ ...feature,
85
+ x: constrained.x,
86
+ y: constrained.y,
87
+ },
88
+ geometry,
89
+ );
90
+
91
+ return {
92
+ feature,
93
+ normalizedX: constrained.x,
94
+ normalizedY: constrained.y,
95
+ centerX: center.x,
96
+ centerY: center.y,
97
+ };
98
+ });
99
+ }
100
+
101
+ export function projectPlacedFeatures<TFeature extends ConstraintFeature>(
102
+ placements: FeaturePlacement<TFeature>[],
103
+ geometry: FeatureProjectionGeometry,
104
+ scale: number,
105
+ ): TFeature[] {
106
+ return placements.map((placement) => {
107
+ const normalized = normalizePointInGeometry(
108
+ { x: placement.centerX, y: placement.centerY },
109
+ geometry,
110
+ );
111
+ return scaleFeatureForRender(
112
+ placement.feature,
113
+ scale,
114
+ normalized.x,
115
+ normalized.y,
116
+ );
117
+ });
118
+ }