@pooder/kit 4.1.0 → 4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/kit",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "Standard plugins for Pooder editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsup src/index.ts --format cjs,esm --dts",
26
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
26
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
27
+ "test": "tsc -p tsconfig.test.json && node .test-dist/tests/run.js"
27
28
  }
28
29
  }
@@ -1,8 +1,10 @@
1
1
  import { Canvas, Group, FabricObject } from "fabric";
2
2
  import { Service, EventBus } from "@pooder/core";
3
+ import { ViewportSystem } from "./ViewportSystem";
3
4
 
4
5
  export default class CanvasService implements Service {
5
6
  public canvas: Canvas;
7
+ public viewport: ViewportSystem;
6
8
  private eventBus?: EventBus;
7
9
 
8
10
  constructor(el: HTMLCanvasElement | string | Canvas, options?: any) {
@@ -14,6 +16,11 @@ export default class CanvasService implements Service {
14
16
  ...options,
15
17
  });
16
18
  }
19
+
20
+ this.viewport = new ViewportSystem();
21
+ if (this.canvas.width !== undefined && this.canvas.height !== undefined) {
22
+ this.viewport.updateContainer(this.canvas.width, this.canvas.height);
23
+ }
17
24
 
18
25
  if (options?.eventBus) {
19
26
  this.setEventBus(options.eventBus);
@@ -0,0 +1,92 @@
1
+ import { Coordinate, Layout, Point, Size } from "./coordinate";
2
+
3
+ export class ViewportSystem {
4
+ private _containerSize: Size = { width: 0, height: 0 };
5
+ private _physicalSize: Size = { width: 0, height: 0 };
6
+ private _padding: number = 0;
7
+ private _layout: Layout = {
8
+ scale: 1,
9
+ offsetX: 0,
10
+ offsetY: 0,
11
+ width: 0,
12
+ height: 0,
13
+ };
14
+
15
+ constructor(
16
+ containerSize: Size = { width: 0, height: 0 },
17
+ physicalSize: Size = { width: 0, height: 0 },
18
+ padding: number = 40,
19
+ ) {
20
+ this._containerSize = containerSize;
21
+ this._physicalSize = physicalSize;
22
+ this._padding = padding;
23
+ this.updateLayout();
24
+ }
25
+
26
+ get layout(): Layout {
27
+ return this._layout;
28
+ }
29
+
30
+ get scale(): number {
31
+ return this._layout.scale;
32
+ }
33
+
34
+ get offset(): Point {
35
+ return { x: this._layout.offsetX, y: this._layout.offsetY };
36
+ }
37
+
38
+ updateContainer(width: number, height: number) {
39
+ if (
40
+ this._containerSize.width === width &&
41
+ this._containerSize.height === height
42
+ )
43
+ return;
44
+ this._containerSize = { width, height };
45
+ this.updateLayout();
46
+ }
47
+
48
+ updatePhysical(width: number, height: number) {
49
+ if (this._physicalSize.width === width && this._physicalSize.height === height)
50
+ return;
51
+ this._physicalSize = { width, height };
52
+ this.updateLayout();
53
+ }
54
+
55
+ setPadding(padding: number) {
56
+ if (this._padding === padding) return;
57
+ this._padding = padding;
58
+ this.updateLayout();
59
+ }
60
+
61
+ private updateLayout() {
62
+ this._layout = Coordinate.calculateLayout(
63
+ this._containerSize,
64
+ this._physicalSize,
65
+ this._padding,
66
+ );
67
+ }
68
+
69
+ toPixel(value: number): number {
70
+ return value * this._layout.scale;
71
+ }
72
+
73
+ toPhysical(value: number): number {
74
+ return this._layout.scale === 0 ? 0 : value / this._layout.scale;
75
+ }
76
+
77
+ toPixelPoint(point: Point): Point {
78
+ return {
79
+ x: point.x * this._layout.scale + this._layout.offsetX,
80
+ y: point.y * this._layout.scale + this._layout.offsetY,
81
+ };
82
+ }
83
+
84
+ // Convert screen coordinate (e.g. mouse event) to physical coordinate (relative to content origin)
85
+ toPhysicalPoint(point: Point): Point {
86
+ if (this._layout.scale === 0) return { x: 0, y: 0 };
87
+ return {
88
+ x: (point.x - this._layout.offsetX) / this._layout.scale,
89
+ y: (point.y - this._layout.offsetY) / this._layout.scale,
90
+ };
91
+ }
92
+ }
@@ -1,14 +1,29 @@
1
- import { DielineFeature } from "./geometry";
2
-
3
1
  export interface ConstraintContext {
4
2
  dielineWidth: number;
5
3
  dielineHeight: number;
6
4
  }
7
5
 
6
+ export interface ConstraintFeature {
7
+ id: string;
8
+ groupId?: string;
9
+ operation: "add" | "subtract";
10
+ shape: "rect" | "circle";
11
+ x: number;
12
+ y: number;
13
+ width?: number;
14
+ height?: number;
15
+ radius?: number;
16
+ placement?: "edge" | "internal";
17
+ constraints?: {
18
+ type: string;
19
+ params?: any;
20
+ };
21
+ }
22
+
8
23
  export type ConstraintHandler = (
9
24
  x: number,
10
25
  y: number,
11
- feature: DielineFeature,
26
+ feature: ConstraintFeature,
12
27
  context: ConstraintContext
13
28
  ) => { x: number; y: number };
14
29
 
@@ -22,7 +37,7 @@ export class ConstraintRegistry {
22
37
  static apply(
23
38
  x: number,
24
39
  y: number,
25
- feature: DielineFeature,
40
+ feature: ConstraintFeature,
26
41
  context: ConstraintContext
27
42
  ): { x: number; y: number } {
28
43
  if (!feature.constraints || !feature.constraints.type) {
@@ -153,6 +168,40 @@ const internalConstraint: ConstraintHandler = (x, y, feature, context) => {
153
168
  return { x: clampedX, y: clampedY };
154
169
  };
155
170
 
171
+ /**
172
+ * Bottom Tangent Strategy (stand protrusion)
173
+ * Forces a feature to be tangent to the dieline bottom edge from outside (below).
174
+ * Params:
175
+ * - gap: number (mm, default 0) extra clearance between dieline and protrusion
176
+ * - confineX: boolean (default true) keep feature within left/right bounds
177
+ */
178
+ const tangentBottomConstraint: ConstraintHandler = (x, y, feature, context) => {
179
+ const { dielineWidth, dielineHeight } = context;
180
+ const params = feature.constraints?.params || {};
181
+ const gap = params.gap || 0;
182
+ const confineX = params.confineX !== false;
183
+
184
+ const extentY =
185
+ feature.shape === "circle"
186
+ ? feature.radius || 0
187
+ : (feature.height || 0) / 2;
188
+ const newY = 1 + (extentY + gap) / dielineHeight;
189
+
190
+ let newX = x;
191
+ if (confineX) {
192
+ const extentX =
193
+ feature.shape === "circle"
194
+ ? feature.radius || 0
195
+ : (feature.width || 0) / 2;
196
+ const minX = extentX / dielineWidth;
197
+ const maxX = 1 - extentX / dielineWidth;
198
+ newX = minX > maxX ? 0.5 : Math.max(minX, Math.min(newX, maxX));
199
+ }
200
+
201
+ return { x: newX, y: newY };
202
+ };
203
+
156
204
  // Register built-ins
157
205
  ConstraintRegistry.register("edge", edgeConstraint);
158
206
  ConstraintRegistry.register("internal", internalConstraint);
207
+ ConstraintRegistry.register("tangent-bottom", tangentBottomConstraint);
package/src/dieline.ts CHANGED
@@ -8,7 +8,8 @@ import {
8
8
  import { Path, Pattern } from "fabric";
9
9
  import CanvasService from "./CanvasService";
10
10
  import { ImageTracer } from "./tracer";
11
- import { Coordinate, Unit } from "./coordinate";
11
+ import { Unit } from "./coordinate";
12
+ import { parseLengthToMm } from "./units";
12
13
  import {
13
14
  generateDielinePath,
14
15
  generateMaskPath,
@@ -16,11 +17,11 @@ import {
16
17
  getPathBounds,
17
18
  DielineFeature,
18
19
  } from "./geometry";
19
- import { ConstraintRegistry } from "./constraints";
20
20
 
21
21
  export interface DielineGeometry {
22
22
  shape: "rect" | "circle" | "ellipse" | "custom";
23
- unit: Unit;
23
+ unit: "mm";
24
+ displayUnit: Unit;
24
25
  x: number;
25
26
  y: number;
26
27
  width: number;
@@ -41,7 +42,7 @@ export interface LineStyle {
41
42
  }
42
43
 
43
44
  export interface DielineState {
44
- unit: Unit;
45
+ displayUnit: Unit;
45
46
  shape: "rect" | "circle" | "ellipse" | "custom";
46
47
  width: number;
47
48
  height: number;
@@ -64,7 +65,7 @@ export class DielineTool implements Extension {
64
65
  };
65
66
 
66
67
  private state: DielineState = {
67
- unit: "mm",
68
+ displayUnit: "mm",
68
69
  shape: "rect",
69
70
  width: 500,
70
71
  height: 500,
@@ -119,61 +120,138 @@ export class DielineTool implements Extension {
119
120
  if (configService) {
120
121
  // Load initial config
121
122
  const s = this.state;
122
- s.unit = configService.get("dieline.unit", s.unit);
123
+ s.displayUnit = configService.get("dieline.displayUnit", s.displayUnit);
123
124
  s.shape = configService.get("dieline.shape", s.shape);
124
- s.width = configService.get("dieline.width", s.width);
125
- s.height = configService.get("dieline.height", s.height);
126
- s.radius = configService.get("dieline.radius", s.radius);
125
+ s.width = parseLengthToMm(
126
+ configService.get("dieline.width", s.width),
127
+ "mm",
128
+ );
129
+ s.height = parseLengthToMm(
130
+ configService.get("dieline.height", s.height),
131
+ "mm",
132
+ );
133
+ s.radius = parseLengthToMm(
134
+ configService.get("dieline.radius", s.radius),
135
+ "mm",
136
+ );
127
137
  s.padding = configService.get("dieline.padding", s.padding);
128
- s.offset = configService.get("dieline.offset", s.offset);
129
-
138
+ s.offset = parseLengthToMm(
139
+ configService.get("dieline.offset", s.offset),
140
+ "mm",
141
+ );
142
+
130
143
  // Main Line
131
- s.mainLine.width = configService.get("dieline.strokeWidth", s.mainLine.width);
132
- s.mainLine.color = configService.get("dieline.strokeColor", s.mainLine.color);
133
- s.mainLine.dashLength = configService.get("dieline.dashLength", s.mainLine.dashLength);
144
+ s.mainLine.width = configService.get(
145
+ "dieline.strokeWidth",
146
+ s.mainLine.width,
147
+ );
148
+ s.mainLine.color = configService.get(
149
+ "dieline.strokeColor",
150
+ s.mainLine.color,
151
+ );
152
+ s.mainLine.dashLength = configService.get(
153
+ "dieline.dashLength",
154
+ s.mainLine.dashLength,
155
+ );
134
156
  s.mainLine.style = configService.get("dieline.style", s.mainLine.style);
135
157
 
136
158
  // Offset Line
137
- s.offsetLine.width = configService.get("dieline.offsetStrokeWidth", s.offsetLine.width);
138
- s.offsetLine.color = configService.get("dieline.offsetStrokeColor", s.offsetLine.color);
139
- s.offsetLine.dashLength = configService.get("dieline.offsetDashLength", s.offsetLine.dashLength);
140
- s.offsetLine.style = configService.get("dieline.offsetStyle", s.offsetLine.style);
159
+ s.offsetLine.width = configService.get(
160
+ "dieline.offsetStrokeWidth",
161
+ s.offsetLine.width,
162
+ );
163
+ s.offsetLine.color = configService.get(
164
+ "dieline.offsetStrokeColor",
165
+ s.offsetLine.color,
166
+ );
167
+ s.offsetLine.dashLength = configService.get(
168
+ "dieline.offsetDashLength",
169
+ s.offsetLine.dashLength,
170
+ );
171
+ s.offsetLine.style = configService.get(
172
+ "dieline.offsetStyle",
173
+ s.offsetLine.style,
174
+ );
141
175
 
142
176
  s.insideColor = configService.get("dieline.insideColor", s.insideColor);
143
- s.outsideColor = configService.get("dieline.outsideColor", s.outsideColor);
144
- s.showBleedLines = configService.get("dieline.showBleedLines", s.showBleedLines);
177
+ s.outsideColor = configService.get(
178
+ "dieline.outsideColor",
179
+ s.outsideColor,
180
+ );
181
+ s.showBleedLines = configService.get(
182
+ "dieline.showBleedLines",
183
+ s.showBleedLines,
184
+ );
145
185
  s.features = configService.get("dieline.features", s.features);
146
186
  s.pathData = configService.get("dieline.pathData", s.pathData);
147
187
 
148
188
  // Listen for changes
149
189
  configService.onAnyChange((e: { key: string; value: any }) => {
150
190
  if (e.key.startsWith("dieline.")) {
151
- console.log(`[DielineTool] Config change detected: ${e.key} -> ${e.value}`);
152
-
153
191
  switch (e.key) {
154
- case "dieline.unit": s.unit = e.value; break;
155
- case "dieline.shape": s.shape = e.value; break;
156
- case "dieline.width": s.width = e.value; break;
157
- case "dieline.height": s.height = e.value; break;
158
- case "dieline.radius": s.radius = e.value; break;
159
- case "dieline.padding": s.padding = e.value; break;
160
- case "dieline.offset": s.offset = e.value; break;
161
-
162
- case "dieline.strokeWidth": s.mainLine.width = e.value; break;
163
- case "dieline.strokeColor": s.mainLine.color = e.value; break;
164
- case "dieline.dashLength": s.mainLine.dashLength = e.value; break;
165
- case "dieline.style": s.mainLine.style = e.value; break;
166
-
167
- case "dieline.offsetStrokeWidth": s.offsetLine.width = e.value; break;
168
- case "dieline.offsetStrokeColor": s.offsetLine.color = e.value; break;
169
- case "dieline.offsetDashLength": s.offsetLine.dashLength = e.value; break;
170
- case "dieline.offsetStyle": s.offsetLine.style = e.value; break;
171
-
172
- case "dieline.insideColor": s.insideColor = e.value; break;
173
- case "dieline.outsideColor": s.outsideColor = e.value; break;
174
- case "dieline.showBleedLines": s.showBleedLines = e.value; break;
175
- case "dieline.features": s.features = e.value; break;
176
- case "dieline.pathData": s.pathData = e.value; break;
192
+ case "dieline.displayUnit":
193
+ s.displayUnit = e.value;
194
+ break;
195
+ case "dieline.shape":
196
+ s.shape = e.value;
197
+ break;
198
+ case "dieline.width":
199
+ s.width = parseLengthToMm(e.value, "mm");
200
+ break;
201
+ case "dieline.height":
202
+ s.height = parseLengthToMm(e.value, "mm");
203
+ break;
204
+ case "dieline.radius":
205
+ s.radius = parseLengthToMm(e.value, "mm");
206
+ break;
207
+ case "dieline.padding":
208
+ s.padding = e.value;
209
+ break;
210
+ case "dieline.offset":
211
+ s.offset = parseLengthToMm(e.value, "mm");
212
+ break;
213
+
214
+ case "dieline.strokeWidth":
215
+ s.mainLine.width = e.value;
216
+ break;
217
+ case "dieline.strokeColor":
218
+ s.mainLine.color = e.value;
219
+ break;
220
+ case "dieline.dashLength":
221
+ s.mainLine.dashLength = e.value;
222
+ break;
223
+ case "dieline.style":
224
+ s.mainLine.style = e.value;
225
+ break;
226
+
227
+ case "dieline.offsetStrokeWidth":
228
+ s.offsetLine.width = e.value;
229
+ break;
230
+ case "dieline.offsetStrokeColor":
231
+ s.offsetLine.color = e.value;
232
+ break;
233
+ case "dieline.offsetDashLength":
234
+ s.offsetLine.dashLength = e.value;
235
+ break;
236
+ case "dieline.offsetStyle":
237
+ s.offsetLine.style = e.value;
238
+ break;
239
+
240
+ case "dieline.insideColor":
241
+ s.insideColor = e.value;
242
+ break;
243
+ case "dieline.outsideColor":
244
+ s.outsideColor = e.value;
245
+ break;
246
+ case "dieline.showBleedLines":
247
+ s.showBleedLines = e.value;
248
+ break;
249
+ case "dieline.features":
250
+ s.features = e.value;
251
+ break;
252
+ case "dieline.pathData":
253
+ s.pathData = e.value;
254
+ break;
177
255
  }
178
256
  this.updateDieline();
179
257
  }
@@ -195,11 +273,11 @@ export class DielineTool implements Extension {
195
273
  return {
196
274
  [ContributionPointIds.CONFIGURATIONS]: [
197
275
  {
198
- id: "dieline.unit",
276
+ id: "dieline.displayUnit",
199
277
  type: "select",
200
- label: "Unit",
201
- options: ["px", "mm", "cm", "in"],
202
- default: s.unit,
278
+ label: "Display Unit",
279
+ options: ["mm", "cm", "in"],
280
+ default: s.displayUnit,
203
281
  },
204
282
  {
205
283
  id: "dieline.shape",
@@ -211,7 +289,7 @@ export class DielineTool implements Extension {
211
289
  {
212
290
  id: "dieline.width",
213
291
  type: "number",
214
- label: "Width",
292
+ label: "Width (mm)",
215
293
  min: 10,
216
294
  max: 2000,
217
295
  default: s.width,
@@ -219,7 +297,7 @@ export class DielineTool implements Extension {
219
297
  {
220
298
  id: "dieline.height",
221
299
  type: "number",
222
- label: "Height",
300
+ label: "Height (mm)",
223
301
  min: 10,
224
302
  max: 2000,
225
303
  default: s.height,
@@ -227,7 +305,7 @@ export class DielineTool implements Extension {
227
305
  {
228
306
  id: "dieline.radius",
229
307
  type: "number",
230
- label: "Corner Radius",
308
+ label: "Corner Radius (mm)",
231
309
  min: 0,
232
310
  max: 500,
233
311
  default: s.radius,
@@ -242,7 +320,7 @@ export class DielineTool implements Extension {
242
320
  {
243
321
  id: "dieline.offset",
244
322
  type: "number",
245
- label: "Bleed Offset",
323
+ label: "Bleed Offset (mm)",
246
324
  min: -100,
247
325
  max: 100,
248
326
  default: s.offset,
@@ -343,20 +421,13 @@ export class DielineTool implements Extension {
343
421
  if (!configService) return;
344
422
 
345
423
  const features = configService.get("dieline.features") || [];
346
- const dielineWidth = configService.get("dieline.width") || 500;
347
- const dielineHeight = configService.get("dieline.height") || 500;
348
424
 
349
425
  let changed = false;
350
426
  const newFeatures = features.map((f: any) => {
351
427
  if (f.groupId === groupId) {
352
- const constrained = ConstraintRegistry.apply(x, y, f, {
353
- dielineWidth,
354
- dielineHeight,
355
- });
356
-
357
- if (f.x !== constrained.x || f.y !== constrained.y) {
428
+ if (f.x !== x || f.y !== y) {
358
429
  changed = true;
359
- return { ...f, x: constrained.x, y: constrained.y };
430
+ return { ...f, x, y };
360
431
  }
361
432
  }
362
433
  return f;
@@ -494,7 +565,7 @@ export class DielineTool implements Extension {
494
565
  if (!layer) return;
495
566
 
496
567
  const {
497
- unit,
568
+ displayUnit,
498
569
  shape,
499
570
  radius,
500
571
  offset,
@@ -505,7 +576,7 @@ export class DielineTool implements Extension {
505
576
  showBleedLines,
506
577
  features,
507
578
  } = this.state;
508
- let { width, height } = this.state;
579
+ const { width, height } = this.state;
509
580
 
510
581
  const canvasW = this.canvasService.canvas.width || 800;
511
582
  const canvasH = this.canvasService.canvas.height || 600;
@@ -513,11 +584,12 @@ export class DielineTool implements Extension {
513
584
  // Calculate Layout based on Physical Dimensions and Canvas Size
514
585
  // Add padding to avoid edge hugging
515
586
  const paddingPx = this.resolvePadding(canvasW, canvasH);
516
- const layout = Coordinate.calculateLayout(
517
- { width: canvasW, height: canvasH },
518
- { width, height },
519
- paddingPx,
520
- );
587
+
588
+ // Update Viewport System
589
+ this.canvasService.viewport.setPadding(paddingPx);
590
+ this.canvasService.viewport.updatePhysical(width, height);
591
+
592
+ const layout = this.canvasService.viewport.layout;
521
593
 
522
594
  const scale = layout.scale;
523
595
  const cx = layout.offsetX + layout.width / 2;
@@ -534,7 +606,6 @@ export class DielineTool implements Extension {
534
606
 
535
607
  // Scale Features for Geometry Generation
536
608
  const absoluteFeatures = (features || []).map((f) => {
537
- // Scale current unit -> pixels (features share the same unit as the dieline)
538
609
  const featureScale = scale;
539
610
 
540
611
  return {
@@ -709,7 +780,9 @@ export class DielineTool implements Extension {
709
780
  stroke: mainLine.style === "hidden" ? null : mainLine.color,
710
781
  strokeWidth: mainLine.width,
711
782
  strokeDashArray:
712
- mainLine.style === "dashed" ? [mainLine.dashLength, mainLine.dashLength] : undefined,
783
+ mainLine.style === "dashed"
784
+ ? [mainLine.dashLength, mainLine.dashLength]
785
+ : undefined,
713
786
  selectable: false,
714
787
  evented: false,
715
788
  originX: "left",
@@ -753,16 +826,26 @@ export class DielineTool implements Extension {
753
826
 
754
827
  public getGeometry(): DielineGeometry | null {
755
828
  if (!this.canvasService) return null;
756
- const { unit, shape, width, height, radius, offset, mainLine, pathData } = this.state;
829
+ const {
830
+ displayUnit,
831
+ shape,
832
+ width,
833
+ height,
834
+ radius,
835
+ offset,
836
+ mainLine,
837
+ pathData,
838
+ } = this.state;
757
839
  const canvasW = this.canvasService.canvas.width || 800;
758
840
  const canvasH = this.canvasService.canvas.height || 600;
759
841
 
760
842
  const paddingPx = this.resolvePadding(canvasW, canvasH);
761
- const layout = Coordinate.calculateLayout(
762
- { width: canvasW, height: canvasH },
763
- { width, height },
764
- paddingPx,
765
- );
843
+
844
+ // Update Viewport System (Ensure it's up to date)
845
+ this.canvasService.viewport.setPadding(paddingPx);
846
+ this.canvasService.viewport.updatePhysical(width, height);
847
+
848
+ const layout = this.canvasService.viewport.layout;
766
849
 
767
850
  const scale = layout.scale;
768
851
  const cx = layout.offsetX + layout.width / 2;
@@ -773,14 +856,14 @@ export class DielineTool implements Extension {
773
856
 
774
857
  return {
775
858
  shape,
776
- unit,
859
+ unit: "mm",
860
+ displayUnit,
777
861
  x: cx,
778
862
  y: cy,
779
863
  width: visualWidth,
780
864
  height: visualHeight,
781
865
  radius: radius * scale,
782
866
  offset: offset * scale,
783
- // Pass scale to help other tools (like FeatureTool) convert units
784
867
  scale,
785
868
  strokeWidth: mainLine.width,
786
869
  pathData,
@@ -794,16 +877,17 @@ export class DielineTool implements Extension {
794
877
  if (!userLayer) return null;
795
878
 
796
879
  // 1. Generate Path Data
797
- const { shape, width, height, radius, features, unit, pathData } = this.state;
880
+ const { shape, width, height, radius, features, pathData } = this.state;
798
881
  const canvasW = this.canvasService.canvas.width || 800;
799
882
  const canvasH = this.canvasService.canvas.height || 600;
800
883
 
801
884
  const paddingPx = this.resolvePadding(canvasW, canvasH);
802
- const layout = Coordinate.calculateLayout(
803
- { width: canvasW, height: canvasH },
804
- { width, height },
805
- paddingPx,
806
- );
885
+
886
+ // Update Viewport System
887
+ this.canvasService.viewport.setPadding(paddingPx);
888
+ this.canvasService.viewport.updatePhysical(width, height);
889
+
890
+ const layout = this.canvasService.viewport.layout;
807
891
  const scale = layout.scale;
808
892
  const cx = layout.offsetX + layout.width / 2;
809
893
  const cy = layout.offsetY + layout.height / 2;