@pooder/kit 5.3.1 → 6.0.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 (65) hide show
  1. package/.test-dist/src/extensions/background.js +475 -131
  2. package/.test-dist/src/extensions/dieline.js +283 -180
  3. package/.test-dist/src/extensions/dielineShape.js +66 -0
  4. package/.test-dist/src/extensions/feature.js +388 -303
  5. package/.test-dist/src/extensions/film.js +133 -74
  6. package/.test-dist/src/extensions/geometry.js +120 -56
  7. package/.test-dist/src/extensions/image.js +296 -212
  8. package/.test-dist/src/extensions/index.js +1 -3
  9. package/.test-dist/src/extensions/maskOps.js +75 -20
  10. package/.test-dist/src/extensions/ruler.js +312 -215
  11. package/.test-dist/src/extensions/sceneLayoutModel.js +9 -3
  12. package/.test-dist/src/extensions/sceneVisibility.js +3 -10
  13. package/.test-dist/src/extensions/tracer.js +229 -58
  14. package/.test-dist/src/extensions/white-ink.js +139 -129
  15. package/.test-dist/src/services/CanvasService.js +888 -126
  16. package/.test-dist/src/services/index.js +1 -0
  17. package/.test-dist/src/services/visibility.js +54 -0
  18. package/.test-dist/tests/run.js +58 -4
  19. package/CHANGELOG.md +12 -0
  20. package/dist/index.d.mts +377 -82
  21. package/dist/index.d.ts +377 -82
  22. package/dist/index.js +3920 -2178
  23. package/dist/index.mjs +3992 -2247
  24. package/package.json +1 -1
  25. package/src/extensions/background.ts +631 -145
  26. package/src/extensions/dieline.ts +280 -187
  27. package/src/extensions/dielineShape.ts +109 -0
  28. package/src/extensions/feature.ts +485 -366
  29. package/src/extensions/film.ts +152 -76
  30. package/src/extensions/geometry.ts +203 -104
  31. package/src/extensions/image.ts +319 -238
  32. package/src/extensions/index.ts +0 -1
  33. package/src/extensions/ruler.ts +481 -268
  34. package/src/extensions/sceneLayoutModel.ts +18 -6
  35. package/src/extensions/white-ink.ts +157 -171
  36. package/src/services/CanvasService.ts +1126 -140
  37. package/src/services/index.ts +1 -0
  38. package/src/services/renderSpec.ts +69 -4
  39. package/src/services/visibility.ts +78 -0
  40. package/tests/run.ts +139 -4
  41. package/.test-dist/src/CanvasService.js +0 -249
  42. package/.test-dist/src/ViewportSystem.js +0 -75
  43. package/.test-dist/src/background.js +0 -203
  44. package/.test-dist/src/bridgeSelection.js +0 -20
  45. package/.test-dist/src/constraints.js +0 -237
  46. package/.test-dist/src/dieline.js +0 -818
  47. package/.test-dist/src/edgeScale.js +0 -12
  48. package/.test-dist/src/feature.js +0 -826
  49. package/.test-dist/src/featureComplete.js +0 -32
  50. package/.test-dist/src/film.js +0 -167
  51. package/.test-dist/src/geometry.js +0 -506
  52. package/.test-dist/src/image.js +0 -1250
  53. package/.test-dist/src/maskOps.js +0 -270
  54. package/.test-dist/src/mirror.js +0 -104
  55. package/.test-dist/src/renderSpec.js +0 -2
  56. package/.test-dist/src/ruler.js +0 -343
  57. package/.test-dist/src/sceneLayout.js +0 -99
  58. package/.test-dist/src/sceneLayoutModel.js +0 -196
  59. package/.test-dist/src/sceneView.js +0 -40
  60. package/.test-dist/src/sceneVisibility.js +0 -42
  61. package/.test-dist/src/size.js +0 -332
  62. package/.test-dist/src/tracer.js +0 -544
  63. package/.test-dist/src/white-ink.js +0 -829
  64. package/.test-dist/src/wrappedOffsets.js +0 -33
  65. package/src/extensions/sceneVisibility.ts +0 -71
@@ -2,21 +2,38 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RulerTool = void 0;
4
4
  const core_1 = require("@pooder/core");
5
- const fabric_1 = require("fabric");
6
- const units_1 = require("../units");
7
5
  const sceneLayoutModel_1 = require("./sceneLayoutModel");
6
+ const RULER_LAYER_ID = "ruler-overlay";
7
+ const EXTENSION_LINE_LENGTH = 5;
8
+ const MIN_ARROW_SIZE = 4;
9
+ const THICKNESS_TO_STROKE_WIDTH_RATIO = 20;
10
+ const DEFAULT_THICKNESS = 20;
11
+ const DEFAULT_GAP = 45;
12
+ const DEFAULT_FONT_SIZE = 10;
13
+ const DEFAULT_BACKGROUND_COLOR = "#f0f0f0";
14
+ const DEFAULT_TEXT_COLOR = "#333333";
15
+ const DEFAULT_LINE_COLOR = "#999999";
16
+ const RULER_THICKNESS_MIN = 10;
17
+ const RULER_THICKNESS_MAX = 100;
18
+ const RULER_GAP_MIN = 0;
19
+ const RULER_GAP_MAX = 100;
20
+ const RULER_FONT_SIZE_MIN = 8;
21
+ const RULER_FONT_SIZE_MAX = 24;
8
22
  class RulerTool {
9
23
  constructor(options) {
10
24
  this.id = "pooder.kit.ruler";
11
25
  this.metadata = {
12
26
  name: "RulerTool",
13
27
  };
14
- this.thickness = 20;
15
- this.gap = 15;
16
- this.backgroundColor = "#f0f0f0";
17
- this.textColor = "#333333";
18
- this.lineColor = "#999999";
19
- this.fontSize = 10;
28
+ this.thickness = DEFAULT_THICKNESS;
29
+ this.gap = DEFAULT_GAP;
30
+ this.backgroundColor = DEFAULT_BACKGROUND_COLOR;
31
+ this.textColor = DEFAULT_TEXT_COLOR;
32
+ this.lineColor = DEFAULT_LINE_COLOR;
33
+ this.fontSize = DEFAULT_FONT_SIZE;
34
+ this.renderSeq = 0;
35
+ this.numericProps = new Set(["thickness", "gap", "fontSize"]);
36
+ this.specs = [];
20
37
  this.onCanvasResized = () => {
21
38
  this.updateRuler();
22
39
  };
@@ -28,45 +45,73 @@ class RulerTool {
28
45
  this.context = context;
29
46
  this.canvasService = context.services.get("CanvasService");
30
47
  if (!this.canvasService) {
31
- console.warn("CanvasService not found for RulerTool");
48
+ console.warn("[RulerTool] CanvasService not found.");
32
49
  return;
33
50
  }
51
+ this.renderProducerDisposable?.dispose();
52
+ this.renderProducerDisposable = this.canvasService.registerRenderProducer(this.id, () => ({
53
+ passes: [
54
+ {
55
+ id: RULER_LAYER_ID,
56
+ stack: 950,
57
+ order: 0,
58
+ replace: true,
59
+ visibility: {
60
+ op: "not",
61
+ expr: {
62
+ op: "activeToolIn",
63
+ ids: ["pooder.kit.white-ink"],
64
+ },
65
+ },
66
+ objects: this.specs,
67
+ },
68
+ ],
69
+ }), { priority: 400 });
34
70
  const configService = context.services.get("ConfigurationService");
35
71
  if (configService) {
36
- // Load initial config
37
- this.thickness = configService.get("ruler.thickness", this.thickness);
38
- this.gap = configService.get("ruler.gap", this.gap);
39
- this.backgroundColor = configService.get("ruler.backgroundColor", this.backgroundColor);
40
- this.textColor = configService.get("ruler.textColor", this.textColor);
41
- this.lineColor = configService.get("ruler.lineColor", this.lineColor);
42
- this.fontSize = configService.get("ruler.fontSize", this.fontSize);
43
- // Listen for changes
72
+ this.syncConfig(configService);
44
73
  configService.onAnyChange((e) => {
45
74
  let shouldUpdate = false;
46
75
  if (e.key.startsWith("ruler.")) {
47
76
  const prop = e.key.split(".")[1];
48
77
  if (prop && prop in this) {
49
- this[prop] = e.value;
78
+ if (this.numericProps.has(prop)) {
79
+ this[prop] = this.toFiniteNumber(e.value, this[prop]);
80
+ }
81
+ else {
82
+ this[prop] = e.value;
83
+ }
50
84
  shouldUpdate = true;
85
+ this.log("config:update", {
86
+ key: e.key,
87
+ raw: e.value,
88
+ normalized: this[prop],
89
+ });
51
90
  }
52
91
  }
53
92
  else if (e.key.startsWith("size.")) {
54
93
  shouldUpdate = true;
94
+ this.log("size:update", { key: e.key, value: e.value });
55
95
  }
56
96
  if (shouldUpdate) {
57
97
  this.updateRuler();
58
98
  }
59
99
  });
60
100
  }
61
- this.createLayer();
62
101
  context.eventBus.on("canvas:resized", this.onCanvasResized);
63
102
  this.updateRuler();
64
103
  }
65
104
  deactivate(context) {
66
105
  context.eventBus.off("canvas:resized", this.onCanvasResized);
67
- this.destroyLayer();
106
+ this.specs = [];
107
+ this.renderProducerDisposable?.dispose();
108
+ this.renderProducerDisposable = undefined;
109
+ if (this.canvasService) {
110
+ void this.canvasService.flushRenderFromProducers();
111
+ }
68
112
  this.canvasService = undefined;
69
113
  this.context = undefined;
114
+ this.renderSeq = 0;
70
115
  }
71
116
  contribute() {
72
117
  return {
@@ -75,43 +120,43 @@ class RulerTool {
75
120
  id: "ruler.thickness",
76
121
  type: "number",
77
122
  label: "Thickness",
78
- min: 10,
79
- max: 100,
80
- default: 20,
123
+ min: RULER_THICKNESS_MIN,
124
+ max: RULER_THICKNESS_MAX,
125
+ default: DEFAULT_THICKNESS,
81
126
  },
82
127
  {
83
128
  id: "ruler.gap",
84
129
  type: "number",
85
130
  label: "Gap",
86
- min: 0,
87
- max: 100,
88
- default: 15,
131
+ min: RULER_GAP_MIN,
132
+ max: RULER_GAP_MAX,
133
+ default: DEFAULT_GAP,
89
134
  },
90
135
  {
91
136
  id: "ruler.backgroundColor",
92
137
  type: "color",
93
138
  label: "Background Color",
94
- default: "#f0f0f0",
139
+ default: DEFAULT_BACKGROUND_COLOR,
95
140
  },
96
141
  {
97
142
  id: "ruler.textColor",
98
143
  type: "color",
99
144
  label: "Text Color",
100
- default: "#333333",
145
+ default: DEFAULT_TEXT_COLOR,
101
146
  },
102
147
  {
103
148
  id: "ruler.lineColor",
104
149
  type: "color",
105
150
  label: "Line Color",
106
- default: "#999999",
151
+ default: DEFAULT_LINE_COLOR,
107
152
  },
108
153
  {
109
154
  id: "ruler.fontSize",
110
155
  type: "number",
111
156
  label: "Font Size",
112
- min: 8,
113
- max: 24,
114
- default: 10,
157
+ min: RULER_FONT_SIZE_MIN,
158
+ max: RULER_FONT_SIZE_MAX,
159
+ default: DEFAULT_FONT_SIZE,
115
160
  },
116
161
  ],
117
162
  [core_1.ContributionPointIds.COMMANDS]: [
@@ -125,11 +170,16 @@ class RulerTool {
125
170
  lineColor: this.lineColor,
126
171
  fontSize: this.fontSize,
127
172
  thickness: this.thickness,
173
+ gap: this.gap,
128
174
  };
129
175
  const newState = { ...oldState, ...theme };
130
- if (JSON.stringify(newState) === JSON.stringify(oldState))
176
+ if (JSON.stringify(newState) === JSON.stringify(oldState)) {
131
177
  return true;
178
+ }
132
179
  Object.assign(this, newState);
180
+ this.thickness = this.toFiniteNumber(this.thickness, DEFAULT_THICKNESS);
181
+ this.gap = this.toFiniteNumber(this.gap, DEFAULT_GAP);
182
+ this.fontSize = this.toFiniteNumber(this.fontSize, DEFAULT_FONT_SIZE);
133
183
  this.updateRuler();
134
184
  return true;
135
185
  },
@@ -137,209 +187,256 @@ class RulerTool {
137
187
  ],
138
188
  };
139
189
  }
140
- getLayer() {
141
- return this.canvasService?.getLayer("ruler-overlay");
142
- }
143
- createLayer() {
144
- if (!this.canvasService)
190
+ log(step, payload) {
191
+ if (payload) {
192
+ console.debug(`[RulerTool] ${step}`, payload);
145
193
  return;
146
- const canvas = this.canvasService.canvas;
147
- const width = canvas.width || 800;
148
- const height = canvas.height || 600;
149
- const layer = this.canvasService.createLayer("ruler-overlay", {
150
- width,
151
- height,
152
- selectable: false,
153
- evented: false,
154
- left: 0,
155
- top: 0,
156
- originX: "left",
157
- originY: "top",
194
+ }
195
+ console.debug(`[RulerTool] ${step}`);
196
+ }
197
+ syncConfig(configService) {
198
+ this.thickness = this.toFiniteNumber(configService.get("ruler.thickness", this.thickness), DEFAULT_THICKNESS);
199
+ this.gap = Math.max(0, this.toFiniteNumber(configService.get("ruler.gap", this.gap), DEFAULT_GAP));
200
+ this.backgroundColor = configService.get("ruler.backgroundColor", this.backgroundColor);
201
+ this.textColor = configService.get("ruler.textColor", this.textColor);
202
+ this.lineColor = configService.get("ruler.lineColor", this.lineColor);
203
+ this.fontSize = this.toFiniteNumber(configService.get("ruler.fontSize", this.fontSize), DEFAULT_FONT_SIZE);
204
+ this.log("config:loaded", {
205
+ thickness: this.thickness,
206
+ gap: this.gap,
207
+ fontSize: this.fontSize,
208
+ backgroundColor: this.backgroundColor,
209
+ textColor: this.textColor,
210
+ lineColor: this.lineColor,
158
211
  });
159
- canvas.bringObjectToFront(layer);
160
212
  }
161
- destroyLayer() {
213
+ toFiniteNumber(value, fallback) {
214
+ const numeric = Number(value);
215
+ return Number.isFinite(numeric) ? numeric : fallback;
216
+ }
217
+ toSceneDisplayLength(value) {
162
218
  if (!this.canvasService)
163
- return;
164
- const layer = this.getLayer();
165
- if (layer) {
166
- this.canvasService.canvas.remove(layer);
167
- }
219
+ return value;
220
+ return this.canvasService.toSceneLength(value);
168
221
  }
169
- createArrowLine(x1, y1, x2, y2, color) {
170
- const line = new fabric_1.Line([x1, y1, x2, y2], {
171
- stroke: color,
172
- strokeWidth: this.thickness / 20, // Scale stroke width relative to thickness (default 1)
173
- selectable: false,
174
- evented: false,
175
- });
176
- // Arrow size proportional to thickness
177
- const arrowSize = Math.max(4, this.thickness * 0.3);
178
- const angle = Math.atan2(y2 - y1, x2 - x1);
179
- // End Arrow (at x2, y2)
180
- const endArrow = new fabric_1.Polygon([
181
- { x: 0, y: 0 },
182
- { x: -arrowSize, y: -arrowSize / 2 },
183
- { x: -arrowSize, y: arrowSize / 2 },
184
- ], {
185
- fill: color,
186
- left: x2,
187
- top: y2,
222
+ formatLengthMm(valueMm, unit) {
223
+ const converted = (0, sceneLayoutModel_1.fromMm)(valueMm, unit);
224
+ const fractionDigits = unit === "in" ? 3 : 2;
225
+ return Number(converted.toFixed(fractionDigits)).toString();
226
+ }
227
+ buildLinePath(start, end) {
228
+ const dx = end.x - start.x;
229
+ const dy = end.y - start.y;
230
+ return `M 0 0 L ${dx} ${dy}`;
231
+ }
232
+ buildStartArrowPath(size) {
233
+ return `M 0 0 L ${size} ${-size / 2} L ${size} ${size / 2} Z`;
234
+ }
235
+ buildEndArrowPath(size) {
236
+ return `M 0 0 L ${-size} ${-size / 2} L ${-size} ${size / 2} Z`;
237
+ }
238
+ createPathSpec(id, pathData, position, options) {
239
+ return {
240
+ id,
241
+ type: "path",
242
+ data: {
243
+ id,
244
+ type: "ruler",
245
+ },
246
+ props: {
247
+ pathData,
248
+ left: position.x,
249
+ top: position.y,
250
+ originX: options.originX ?? "left",
251
+ originY: options.originY ?? "top",
252
+ angle: options.angle ?? 0,
253
+ stroke: options.stroke ?? null,
254
+ fill: options.fill ?? null,
255
+ strokeWidth: options.strokeWidth ?? 1,
256
+ strokeLineCap: options.strokeLineCap ?? "butt",
257
+ selectable: false,
258
+ evented: false,
259
+ excludeFromExport: true,
260
+ },
261
+ };
262
+ }
263
+ createTextSpec(id, text, position, angle = 0) {
264
+ return {
265
+ id,
266
+ type: "text",
267
+ data: {
268
+ id,
269
+ type: "ruler",
270
+ },
271
+ props: {
272
+ text,
273
+ left: position.x,
274
+ top: position.y,
275
+ angle,
276
+ fontSize: this.toSceneDisplayLength(this.fontSize),
277
+ fill: this.textColor,
278
+ fontFamily: "Arial",
279
+ originX: "center",
280
+ originY: "center",
281
+ backgroundColor: this.backgroundColor,
282
+ selectable: false,
283
+ evented: false,
284
+ excludeFromExport: true,
285
+ },
286
+ };
287
+ }
288
+ buildRulerSpecs(input) {
289
+ const { left, top, right, bottom, widthLabel, heightLabel } = input;
290
+ const gap = Math.max(0, this.toSceneDisplayLength(this.toFiniteNumber(this.gap, DEFAULT_GAP)));
291
+ const topY = top - gap;
292
+ const leftX = left - gap;
293
+ const arrowSize = Math.max(this.toSceneDisplayLength(MIN_ARROW_SIZE), this.toSceneDisplayLength(this.thickness * 0.3));
294
+ const strokeWidth = Math.max(this.toSceneDisplayLength(1), this.toSceneDisplayLength(this.thickness / THICKNESS_TO_STROKE_WIDTH_RATIO));
295
+ const extensionLength = this.toSceneDisplayLength(EXTENSION_LINE_LENGTH);
296
+ const topLineAngleDeg = 0;
297
+ const leftLineAngleDeg = 90;
298
+ // Keep dimension line inside the arrow heads so it doesn't visually overflow.
299
+ const topMidX = left + (right - left) / 2;
300
+ const leftMidY = top + (bottom - top) / 2;
301
+ const topLineStartX = Math.min(left + arrowSize, topMidX);
302
+ const topLineEndX = Math.max(right - arrowSize, topMidX);
303
+ const leftLineStartY = Math.min(top + arrowSize, leftMidY);
304
+ const leftLineEndY = Math.max(bottom - arrowSize, leftMidY);
305
+ const specs = [];
306
+ specs.push(this.createPathSpec("ruler.top.line", this.buildLinePath({ x: topLineStartX, y: topY }, { x: topLineEndX, y: topY }), { x: topLineStartX, y: topY }, {
307
+ stroke: this.lineColor,
308
+ strokeWidth,
309
+ strokeLineCap: "butt",
310
+ }), this.createPathSpec("ruler.top.arrow.start", this.buildStartArrowPath(arrowSize), { x: left, y: topY }, {
311
+ fill: this.lineColor,
312
+ stroke: this.lineColor,
313
+ strokeWidth: this.toSceneDisplayLength(1),
314
+ originX: "left",
315
+ originY: "center",
316
+ angle: topLineAngleDeg,
317
+ }), this.createPathSpec("ruler.top.arrow.end", this.buildEndArrowPath(arrowSize), { x: right, y: topY }, {
318
+ fill: this.lineColor,
319
+ stroke: this.lineColor,
320
+ strokeWidth: this.toSceneDisplayLength(1),
188
321
  originX: "right",
189
322
  originY: "center",
190
- angle: (angle * 180) / Math.PI,
191
- selectable: false,
192
- evented: false,
193
- });
194
- // Start Arrow (at x1, y1)
195
- const startArrow = new fabric_1.Polygon([
196
- { x: 0, y: 0 },
197
- { x: arrowSize, y: -arrowSize / 2 },
198
- { x: arrowSize, y: arrowSize / 2 },
199
- ], {
200
- fill: color,
201
- left: x1,
202
- top: y1,
323
+ angle: topLineAngleDeg,
324
+ }), this.createPathSpec("ruler.top.ext.start", this.buildLinePath({ x: left, y: topY - extensionLength }, { x: left, y: topY + extensionLength }), { x: left, y: topY - extensionLength }, {
325
+ stroke: this.lineColor,
326
+ strokeWidth: this.toSceneDisplayLength(1),
327
+ }), this.createPathSpec("ruler.top.ext.end", this.buildLinePath({ x: right, y: topY - extensionLength }, { x: right, y: topY + extensionLength }), { x: right, y: topY - extensionLength }, {
328
+ stroke: this.lineColor,
329
+ strokeWidth: this.toSceneDisplayLength(1),
330
+ }), this.createTextSpec("ruler.top.label", widthLabel, {
331
+ x: left + (right - left) / 2,
332
+ y: topY,
333
+ }));
334
+ specs.push(this.createPathSpec("ruler.left.line", this.buildLinePath({ x: leftX, y: leftLineStartY }, { x: leftX, y: leftLineEndY }), { x: leftX, y: leftLineStartY }, {
335
+ stroke: this.lineColor,
336
+ strokeWidth,
337
+ strokeLineCap: "butt",
338
+ }), this.createPathSpec("ruler.left.arrow.start", this.buildStartArrowPath(arrowSize), { x: leftX, y: top }, {
339
+ fill: this.lineColor,
340
+ stroke: this.lineColor,
341
+ strokeWidth: this.toSceneDisplayLength(1),
203
342
  originX: "left",
204
343
  originY: "center",
205
- angle: (angle * 180) / Math.PI,
206
- selectable: false,
207
- evented: false,
208
- });
209
- return new fabric_1.Group([line, startArrow, endArrow], {
210
- selectable: false,
211
- evented: false,
212
- });
344
+ angle: leftLineAngleDeg,
345
+ }), this.createPathSpec("ruler.left.arrow.end", this.buildEndArrowPath(arrowSize), { x: leftX, y: bottom }, {
346
+ fill: this.lineColor,
347
+ stroke: this.lineColor,
348
+ strokeWidth: this.toSceneDisplayLength(1),
349
+ originX: "right",
350
+ originY: "center",
351
+ angle: leftLineAngleDeg,
352
+ }), this.createPathSpec("ruler.left.ext.start", this.buildLinePath({ x: leftX - extensionLength, y: top }, { x: leftX + extensionLength, y: top }), { x: leftX - extensionLength, y: top }, {
353
+ stroke: this.lineColor,
354
+ strokeWidth: this.toSceneDisplayLength(1),
355
+ }), this.createPathSpec("ruler.left.ext.end", this.buildLinePath({ x: leftX - extensionLength, y: bottom }, { x: leftX + extensionLength, y: bottom }), { x: leftX - extensionLength, y: bottom }, {
356
+ stroke: this.lineColor,
357
+ strokeWidth: this.toSceneDisplayLength(1),
358
+ }), this.createTextSpec("ruler.left.label", heightLabel, {
359
+ x: leftX,
360
+ y: top + (bottom - top) / 2,
361
+ }, -90));
362
+ return specs;
213
363
  }
214
364
  updateRuler() {
365
+ void this.updateRulerAsync();
366
+ }
367
+ async updateRulerAsync() {
215
368
  if (!this.canvasService)
216
369
  return;
217
- const layer = this.getLayer();
218
- if (!layer)
219
- return;
220
- layer.remove(...layer.getObjects());
221
- const { backgroundColor, lineColor, textColor, fontSize } = this;
222
370
  const configService = this.context?.services.get("ConfigurationService");
223
371
  if (!configService)
224
372
  return;
373
+ const seq = ++this.renderSeq;
225
374
  const sizeState = (0, sceneLayoutModel_1.readSizeState)(configService);
226
375
  const layout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService, sizeState);
227
- if (!layout)
376
+ this.log("render:start", {
377
+ seq,
378
+ unit: sizeState.unit,
379
+ gap: this.gap,
380
+ thickness: this.thickness,
381
+ fontSize: this.fontSize,
382
+ hasLayout: !!layout,
383
+ scale: layout?.scale ?? null,
384
+ });
385
+ if (!layout || layout.scale <= 0) {
386
+ if (seq !== this.renderSeq)
387
+ return;
388
+ this.log("render:skip", { seq, reason: "invalid-layout" });
389
+ this.specs = [];
390
+ await this.canvasService.flushRenderFromProducers();
228
391
  return;
229
- const trimRect = layout.trimRect;
230
- const cutRect = layout.cutRect;
231
- const useCutAsRuler = layout.cutMode === "outset";
232
- const rulerRect = useCutAsRuler ? cutRect : trimRect;
233
- // Use gap configuration
234
- const gap = this.gap || 15;
235
- // New Bounding Box for Ruler
236
- const rulerLeft = rulerRect.left;
237
- const rulerTop = rulerRect.top;
238
- const rulerRight = rulerRect.left + rulerRect.width;
239
- const rulerBottom = rulerRect.top + rulerRect.height;
240
- // Display Dimensions (Physical)
241
- const displayWidthMm = useCutAsRuler
242
- ? layout.cutWidthMm
243
- : layout.trimWidthMm;
244
- const displayHeightMm = useCutAsRuler
245
- ? layout.cutHeightMm
246
- : layout.trimHeightMm;
247
- const displayUnit = sizeState.unit;
248
- // Ruler Placement Coordinates
249
- // Top Ruler: Above the top boundary
250
- const topRulerY = rulerTop - gap;
251
- const topRulerXStart = rulerLeft;
252
- const topRulerXEnd = rulerRight;
253
- // Left Ruler: Left of the left boundary
254
- const leftRulerX = rulerLeft - gap;
255
- const leftRulerYStart = rulerTop;
256
- const leftRulerYEnd = rulerBottom;
257
- // 1. Top Dimension Line (X-Axis)
258
- const topDimLine = this.createArrowLine(topRulerXStart, topRulerY, topRulerXEnd, topRulerY, lineColor);
259
- layer.add(topDimLine);
260
- // Top Extension Lines
261
- const extLen = 5;
262
- layer.add(new fabric_1.Line([
263
- topRulerXStart,
264
- topRulerY - extLen,
265
- topRulerXStart,
266
- topRulerY + extLen,
267
- ], {
268
- stroke: lineColor,
269
- strokeWidth: 1,
270
- selectable: false,
271
- evented: false,
272
- }));
273
- layer.add(new fabric_1.Line([topRulerXEnd, topRulerY - extLen, topRulerXEnd, topRulerY + extLen], {
274
- stroke: lineColor,
275
- strokeWidth: 1,
276
- selectable: false,
277
- evented: false,
278
- }));
279
- // Top Text (Centered)
280
- const widthStr = (0, units_1.formatMm)(displayWidthMm, displayUnit);
281
- const topTextContent = `${widthStr} ${displayUnit}`;
282
- const topText = new fabric_1.Text(topTextContent, {
283
- left: topRulerXStart + (rulerRight - rulerLeft) / 2,
284
- top: topRulerY,
285
- fontSize: fontSize,
286
- fill: textColor,
287
- fontFamily: "Arial",
288
- originX: "center",
289
- originY: "center",
290
- backgroundColor: backgroundColor, // Background mask for readability
291
- selectable: false,
292
- evented: false,
392
+ }
393
+ const geometry = (0, sceneLayoutModel_1.buildSceneGeometry)(configService, layout);
394
+ if (geometry.unit !== "px") {
395
+ console.warn("[RulerTool] Unexpected geometry unit.", geometry.unit);
396
+ }
397
+ const centerScene = this.canvasService.toScenePoint({
398
+ x: geometry.x,
399
+ y: geometry.y,
293
400
  });
294
- // Add small padding to text background if Fabric supports it directly or via separate rect
295
- // Fabric Text backgroundColor is tight.
296
- layer.add(topText);
297
- // 2. Left Dimension Line (Y-Axis)
298
- const leftDimLine = this.createArrowLine(leftRulerX, leftRulerYStart, leftRulerX, leftRulerYEnd, lineColor);
299
- layer.add(leftDimLine);
300
- // Left Extension Lines
301
- layer.add(new fabric_1.Line([
302
- leftRulerX - extLen,
303
- leftRulerYStart,
304
- leftRulerX + extLen,
305
- leftRulerYStart,
306
- ], {
307
- stroke: lineColor,
308
- strokeWidth: 1,
309
- selectable: false,
310
- evented: false,
311
- }));
312
- layer.add(new fabric_1.Line([
313
- leftRulerX - extLen,
314
- leftRulerYEnd,
315
- leftRulerX + extLen,
316
- leftRulerYEnd,
317
- ], {
318
- stroke: lineColor,
319
- strokeWidth: 1,
320
- selectable: false,
321
- evented: false,
322
- }));
323
- // Left Text (Centered, Rotated)
324
- const heightStr = (0, units_1.formatMm)(displayHeightMm, displayUnit);
325
- const leftTextContent = `${heightStr} ${displayUnit}`;
326
- const leftText = new fabric_1.Text(leftTextContent, {
327
- left: leftRulerX,
328
- top: leftRulerYStart + (rulerBottom - rulerTop) / 2,
329
- angle: -90,
330
- fontSize: fontSize,
331
- fill: textColor,
332
- fontFamily: "Arial",
333
- originX: "center",
334
- originY: "center",
335
- backgroundColor: backgroundColor,
336
- selectable: false,
337
- evented: false,
401
+ const widthScene = this.canvasService.toSceneLength(geometry.width);
402
+ const heightScene = this.canvasService.toSceneLength(geometry.height);
403
+ const rulerLeft = centerScene.x - widthScene / 2;
404
+ const rulerTop = centerScene.y - heightScene / 2;
405
+ const rulerRight = rulerLeft + widthScene;
406
+ const rulerBottom = rulerTop + heightScene;
407
+ const widthMm = widthScene;
408
+ const heightMm = heightScene;
409
+ const unit = sizeState.unit;
410
+ const widthLabel = `${this.formatLengthMm(widthMm, unit)} ${unit}`;
411
+ const heightLabel = `${this.formatLengthMm(heightMm, unit)} ${unit}`;
412
+ const specs = this.buildRulerSpecs({
413
+ left: rulerLeft,
414
+ top: rulerTop,
415
+ right: rulerRight,
416
+ bottom: rulerBottom,
417
+ widthLabel,
418
+ heightLabel,
419
+ });
420
+ this.log("render:geometry", {
421
+ seq,
422
+ left: rulerLeft,
423
+ top: rulerTop,
424
+ right: rulerRight,
425
+ bottom: rulerBottom,
426
+ widthScene,
427
+ heightScene,
428
+ widthMm,
429
+ heightMm,
430
+ specCount: specs.length,
338
431
  });
339
- layer.add(leftText);
340
- // Always bring ruler to front
341
- this.canvasService.canvas.bringObjectToFront(layer);
342
- this.canvasService.canvas.requestRenderAll();
432
+ if (seq !== this.renderSeq)
433
+ return;
434
+ this.specs = specs;
435
+ await this.canvasService.flushRenderFromProducers();
436
+ if (seq !== this.renderSeq)
437
+ return;
438
+ this.canvasService.requestRenderAll();
439
+ this.log("render:done", { seq });
343
440
  }
344
441
  }
345
442
  exports.RulerTool = RulerTool;