@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.
- package/.test-dist/src/extensions/background.js +475 -131
- package/.test-dist/src/extensions/dieline.js +283 -180
- package/.test-dist/src/extensions/dielineShape.js +66 -0
- package/.test-dist/src/extensions/feature.js +388 -303
- package/.test-dist/src/extensions/film.js +133 -74
- package/.test-dist/src/extensions/geometry.js +120 -56
- package/.test-dist/src/extensions/image.js +296 -212
- package/.test-dist/src/extensions/index.js +1 -3
- package/.test-dist/src/extensions/maskOps.js +75 -20
- package/.test-dist/src/extensions/ruler.js +312 -215
- package/.test-dist/src/extensions/sceneLayoutModel.js +9 -3
- package/.test-dist/src/extensions/sceneVisibility.js +3 -10
- package/.test-dist/src/extensions/tracer.js +229 -58
- package/.test-dist/src/extensions/white-ink.js +139 -129
- package/.test-dist/src/services/CanvasService.js +888 -126
- package/.test-dist/src/services/index.js +1 -0
- package/.test-dist/src/services/visibility.js +54 -0
- package/.test-dist/tests/run.js +58 -4
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +377 -82
- package/dist/index.d.ts +377 -82
- package/dist/index.js +3920 -2178
- package/dist/index.mjs +3992 -2247
- package/package.json +1 -1
- package/src/extensions/background.ts +631 -145
- package/src/extensions/dieline.ts +280 -187
- package/src/extensions/dielineShape.ts +109 -0
- package/src/extensions/feature.ts +485 -366
- package/src/extensions/film.ts +152 -76
- package/src/extensions/geometry.ts +203 -104
- package/src/extensions/image.ts +319 -238
- package/src/extensions/index.ts +0 -1
- package/src/extensions/ruler.ts +481 -268
- package/src/extensions/sceneLayoutModel.ts +18 -6
- package/src/extensions/white-ink.ts +157 -171
- package/src/services/CanvasService.ts +1126 -140
- package/src/services/index.ts +1 -0
- package/src/services/renderSpec.ts +69 -4
- package/src/services/visibility.ts +78 -0
- package/tests/run.ts +139 -4
- package/.test-dist/src/CanvasService.js +0 -249
- package/.test-dist/src/ViewportSystem.js +0 -75
- package/.test-dist/src/background.js +0 -203
- package/.test-dist/src/bridgeSelection.js +0 -20
- package/.test-dist/src/constraints.js +0 -237
- package/.test-dist/src/dieline.js +0 -818
- package/.test-dist/src/edgeScale.js +0 -12
- package/.test-dist/src/feature.js +0 -826
- package/.test-dist/src/featureComplete.js +0 -32
- package/.test-dist/src/film.js +0 -167
- package/.test-dist/src/geometry.js +0 -506
- package/.test-dist/src/image.js +0 -1250
- package/.test-dist/src/maskOps.js +0 -270
- package/.test-dist/src/mirror.js +0 -104
- package/.test-dist/src/renderSpec.js +0 -2
- package/.test-dist/src/ruler.js +0 -343
- package/.test-dist/src/sceneLayout.js +0 -99
- package/.test-dist/src/sceneLayoutModel.js +0 -196
- package/.test-dist/src/sceneView.js +0 -40
- package/.test-dist/src/sceneVisibility.js +0 -42
- package/.test-dist/src/size.js +0 -332
- package/.test-dist/src/tracer.js +0 -544
- package/.test-dist/src/white-ink.js +0 -829
- package/.test-dist/src/wrappedOffsets.js +0 -33
- package/src/extensions/sceneVisibility.ts +0 -71
package/src/extensions/ruler.ts
CHANGED
|
@@ -6,10 +6,35 @@ import {
|
|
|
6
6
|
ConfigurationContribution,
|
|
7
7
|
ConfigurationService,
|
|
8
8
|
} from "@pooder/core";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
import { CanvasService, RenderObjectSpec } from "../services";
|
|
10
|
+
import {
|
|
11
|
+
buildSceneGeometry,
|
|
12
|
+
computeSceneLayout,
|
|
13
|
+
fromMm,
|
|
14
|
+
readSizeState,
|
|
15
|
+
} from "./sceneLayoutModel";
|
|
16
|
+
import type { Unit } from "../coordinate";
|
|
17
|
+
|
|
18
|
+
const RULER_LAYER_ID = "ruler-overlay";
|
|
19
|
+
const EXTENSION_LINE_LENGTH = 5;
|
|
20
|
+
const MIN_ARROW_SIZE = 4;
|
|
21
|
+
const THICKNESS_TO_STROKE_WIDTH_RATIO = 20;
|
|
22
|
+
|
|
23
|
+
const DEFAULT_THICKNESS = 20;
|
|
24
|
+
const DEFAULT_GAP = 45;
|
|
25
|
+
const DEFAULT_FONT_SIZE = 10;
|
|
26
|
+
const DEFAULT_BACKGROUND_COLOR = "#f0f0f0";
|
|
27
|
+
const DEFAULT_TEXT_COLOR = "#333333";
|
|
28
|
+
const DEFAULT_LINE_COLOR = "#999999";
|
|
29
|
+
|
|
30
|
+
const RULER_THICKNESS_MIN = 10;
|
|
31
|
+
const RULER_THICKNESS_MAX = 100;
|
|
32
|
+
const RULER_GAP_MIN = 0;
|
|
33
|
+
const RULER_GAP_MAX = 100;
|
|
34
|
+
const RULER_FONT_SIZE_MIN = 8;
|
|
35
|
+
const RULER_FONT_SIZE_MAX = 24;
|
|
36
|
+
|
|
37
|
+
type Point = { x: number; y: number };
|
|
13
38
|
|
|
14
39
|
export class RulerTool implements Extension {
|
|
15
40
|
id = "pooder.kit.ruler";
|
|
@@ -18,12 +43,16 @@ export class RulerTool implements Extension {
|
|
|
18
43
|
name: "RulerTool",
|
|
19
44
|
};
|
|
20
45
|
|
|
21
|
-
private thickness
|
|
22
|
-
private gap
|
|
23
|
-
private backgroundColor
|
|
24
|
-
private textColor
|
|
25
|
-
private lineColor
|
|
26
|
-
private fontSize
|
|
46
|
+
private thickness = DEFAULT_THICKNESS;
|
|
47
|
+
private gap = DEFAULT_GAP;
|
|
48
|
+
private backgroundColor = DEFAULT_BACKGROUND_COLOR;
|
|
49
|
+
private textColor = DEFAULT_TEXT_COLOR;
|
|
50
|
+
private lineColor = DEFAULT_LINE_COLOR;
|
|
51
|
+
private fontSize = DEFAULT_FONT_SIZE;
|
|
52
|
+
private renderSeq = 0;
|
|
53
|
+
private readonly numericProps = new Set(["thickness", "gap", "fontSize"]);
|
|
54
|
+
private specs: RenderObjectSpec[] = [];
|
|
55
|
+
private renderProducerDisposable?: { dispose: () => void };
|
|
27
56
|
|
|
28
57
|
private canvasService?: CanvasService;
|
|
29
58
|
private context?: ExtensionContext;
|
|
@@ -38,6 +67,7 @@ export class RulerTool implements Extension {
|
|
|
38
67
|
textColor: string;
|
|
39
68
|
lineColor: string;
|
|
40
69
|
fontSize: number;
|
|
70
|
+
gap: number;
|
|
41
71
|
}>,
|
|
42
72
|
) {
|
|
43
73
|
if (options) {
|
|
@@ -49,36 +79,61 @@ export class RulerTool implements Extension {
|
|
|
49
79
|
this.context = context;
|
|
50
80
|
this.canvasService = context.services.get<CanvasService>("CanvasService");
|
|
51
81
|
if (!this.canvasService) {
|
|
52
|
-
console.warn("CanvasService not found
|
|
82
|
+
console.warn("[RulerTool] CanvasService not found.");
|
|
53
83
|
return;
|
|
54
84
|
}
|
|
85
|
+
this.renderProducerDisposable?.dispose();
|
|
86
|
+
this.renderProducerDisposable = this.canvasService.registerRenderProducer(
|
|
87
|
+
this.id,
|
|
88
|
+
() => ({
|
|
89
|
+
passes: [
|
|
90
|
+
{
|
|
91
|
+
id: RULER_LAYER_ID,
|
|
92
|
+
stack: 950,
|
|
93
|
+
order: 0,
|
|
94
|
+
replace: true,
|
|
95
|
+
visibility: {
|
|
96
|
+
op: "not",
|
|
97
|
+
expr: {
|
|
98
|
+
op: "activeToolIn",
|
|
99
|
+
ids: ["pooder.kit.white-ink"],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
objects: this.specs,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
}),
|
|
106
|
+
{ priority: 400 },
|
|
107
|
+
);
|
|
55
108
|
|
|
56
109
|
const configService = context.services.get<ConfigurationService>(
|
|
57
110
|
"ConfigurationService",
|
|
58
111
|
);
|
|
59
112
|
if (configService) {
|
|
60
|
-
|
|
61
|
-
this.thickness = configService.get("ruler.thickness", this.thickness);
|
|
62
|
-
this.gap = configService.get("ruler.gap", this.gap);
|
|
63
|
-
this.backgroundColor = configService.get(
|
|
64
|
-
"ruler.backgroundColor",
|
|
65
|
-
this.backgroundColor,
|
|
66
|
-
);
|
|
67
|
-
this.textColor = configService.get("ruler.textColor", this.textColor);
|
|
68
|
-
this.lineColor = configService.get("ruler.lineColor", this.lineColor);
|
|
69
|
-
this.fontSize = configService.get("ruler.fontSize", this.fontSize);
|
|
70
|
-
|
|
71
|
-
// Listen for changes
|
|
113
|
+
this.syncConfig(configService);
|
|
72
114
|
configService.onAnyChange((e: { key: string; value: any }) => {
|
|
73
115
|
let shouldUpdate = false;
|
|
74
116
|
if (e.key.startsWith("ruler.")) {
|
|
75
117
|
const prop = e.key.split(".")[1];
|
|
76
118
|
if (prop && prop in this) {
|
|
77
|
-
(this
|
|
119
|
+
if (this.numericProps.has(prop)) {
|
|
120
|
+
(this as any)[prop] = this.toFiniteNumber(
|
|
121
|
+
e.value,
|
|
122
|
+
(this as any)[prop],
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
(this as any)[prop] = e.value;
|
|
126
|
+
}
|
|
78
127
|
shouldUpdate = true;
|
|
128
|
+
this.log("config:update", {
|
|
129
|
+
key: e.key,
|
|
130
|
+
raw: e.value,
|
|
131
|
+
normalized: (this as any)[prop],
|
|
132
|
+
});
|
|
79
133
|
}
|
|
80
134
|
} else if (e.key.startsWith("size.")) {
|
|
81
135
|
shouldUpdate = true;
|
|
136
|
+
this.log("size:update", { key: e.key, value: e.value });
|
|
82
137
|
}
|
|
83
138
|
|
|
84
139
|
if (shouldUpdate) {
|
|
@@ -87,16 +142,21 @@ export class RulerTool implements Extension {
|
|
|
87
142
|
});
|
|
88
143
|
}
|
|
89
144
|
|
|
90
|
-
this.createLayer();
|
|
91
145
|
context.eventBus.on("canvas:resized", this.onCanvasResized);
|
|
92
146
|
this.updateRuler();
|
|
93
147
|
}
|
|
94
148
|
|
|
95
149
|
deactivate(context: ExtensionContext) {
|
|
96
150
|
context.eventBus.off("canvas:resized", this.onCanvasResized);
|
|
97
|
-
this.
|
|
151
|
+
this.specs = [];
|
|
152
|
+
this.renderProducerDisposable?.dispose();
|
|
153
|
+
this.renderProducerDisposable = undefined;
|
|
154
|
+
if (this.canvasService) {
|
|
155
|
+
void this.canvasService.flushRenderFromProducers();
|
|
156
|
+
}
|
|
98
157
|
this.canvasService = undefined;
|
|
99
158
|
this.context = undefined;
|
|
159
|
+
this.renderSeq = 0;
|
|
100
160
|
}
|
|
101
161
|
|
|
102
162
|
contribute() {
|
|
@@ -106,43 +166,43 @@ export class RulerTool implements Extension {
|
|
|
106
166
|
id: "ruler.thickness",
|
|
107
167
|
type: "number",
|
|
108
168
|
label: "Thickness",
|
|
109
|
-
min:
|
|
110
|
-
max:
|
|
111
|
-
default:
|
|
169
|
+
min: RULER_THICKNESS_MIN,
|
|
170
|
+
max: RULER_THICKNESS_MAX,
|
|
171
|
+
default: DEFAULT_THICKNESS,
|
|
112
172
|
},
|
|
113
173
|
{
|
|
114
174
|
id: "ruler.gap",
|
|
115
175
|
type: "number",
|
|
116
176
|
label: "Gap",
|
|
117
|
-
min:
|
|
118
|
-
max:
|
|
119
|
-
default:
|
|
177
|
+
min: RULER_GAP_MIN,
|
|
178
|
+
max: RULER_GAP_MAX,
|
|
179
|
+
default: DEFAULT_GAP,
|
|
120
180
|
},
|
|
121
181
|
{
|
|
122
182
|
id: "ruler.backgroundColor",
|
|
123
183
|
type: "color",
|
|
124
184
|
label: "Background Color",
|
|
125
|
-
default:
|
|
185
|
+
default: DEFAULT_BACKGROUND_COLOR,
|
|
126
186
|
},
|
|
127
187
|
{
|
|
128
188
|
id: "ruler.textColor",
|
|
129
189
|
type: "color",
|
|
130
190
|
label: "Text Color",
|
|
131
|
-
default:
|
|
191
|
+
default: DEFAULT_TEXT_COLOR,
|
|
132
192
|
},
|
|
133
193
|
{
|
|
134
194
|
id: "ruler.lineColor",
|
|
135
195
|
type: "color",
|
|
136
196
|
label: "Line Color",
|
|
137
|
-
default:
|
|
197
|
+
default: DEFAULT_LINE_COLOR,
|
|
138
198
|
},
|
|
139
199
|
{
|
|
140
200
|
id: "ruler.fontSize",
|
|
141
201
|
type: "number",
|
|
142
202
|
label: "Font Size",
|
|
143
|
-
min:
|
|
144
|
-
max:
|
|
145
|
-
default:
|
|
203
|
+
min: RULER_FONT_SIZE_MIN,
|
|
204
|
+
max: RULER_FONT_SIZE_MAX,
|
|
205
|
+
default: DEFAULT_FONT_SIZE,
|
|
146
206
|
},
|
|
147
207
|
] as ConfigurationContribution[],
|
|
148
208
|
[ContributionPointIds.COMMANDS]: [
|
|
@@ -156,6 +216,7 @@ export class RulerTool implements Extension {
|
|
|
156
216
|
lineColor: string;
|
|
157
217
|
fontSize: number;
|
|
158
218
|
thickness: number;
|
|
219
|
+
gap: number;
|
|
159
220
|
}>,
|
|
160
221
|
) => {
|
|
161
222
|
const oldState = {
|
|
@@ -164,12 +225,23 @@ export class RulerTool implements Extension {
|
|
|
164
225
|
lineColor: this.lineColor,
|
|
165
226
|
fontSize: this.fontSize,
|
|
166
227
|
thickness: this.thickness,
|
|
228
|
+
gap: this.gap,
|
|
167
229
|
};
|
|
168
230
|
const newState = { ...oldState, ...theme };
|
|
169
|
-
if (JSON.stringify(newState) === JSON.stringify(oldState))
|
|
231
|
+
if (JSON.stringify(newState) === JSON.stringify(oldState)) {
|
|
170
232
|
return true;
|
|
233
|
+
}
|
|
171
234
|
|
|
172
235
|
Object.assign(this, newState);
|
|
236
|
+
this.thickness = this.toFiniteNumber(
|
|
237
|
+
this.thickness,
|
|
238
|
+
DEFAULT_THICKNESS,
|
|
239
|
+
);
|
|
240
|
+
this.gap = this.toFiniteNumber(this.gap, DEFAULT_GAP);
|
|
241
|
+
this.fontSize = this.toFiniteNumber(
|
|
242
|
+
this.fontSize,
|
|
243
|
+
DEFAULT_FONT_SIZE,
|
|
244
|
+
);
|
|
173
245
|
this.updateRuler();
|
|
174
246
|
return true;
|
|
175
247
|
},
|
|
@@ -178,274 +250,415 @@ export class RulerTool implements Extension {
|
|
|
178
250
|
};
|
|
179
251
|
}
|
|
180
252
|
|
|
181
|
-
private
|
|
182
|
-
|
|
253
|
+
private log(step: string, payload?: Record<string, unknown>) {
|
|
254
|
+
if (payload) {
|
|
255
|
+
console.debug(`[RulerTool] ${step}`, payload);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
console.debug(`[RulerTool] ${step}`);
|
|
183
259
|
}
|
|
184
260
|
|
|
185
|
-
private
|
|
186
|
-
|
|
261
|
+
private syncConfig(configService: ConfigurationService) {
|
|
262
|
+
this.thickness = this.toFiniteNumber(
|
|
263
|
+
configService.get("ruler.thickness", this.thickness),
|
|
264
|
+
DEFAULT_THICKNESS,
|
|
265
|
+
);
|
|
266
|
+
this.gap = Math.max(
|
|
267
|
+
0,
|
|
268
|
+
this.toFiniteNumber(
|
|
269
|
+
configService.get("ruler.gap", this.gap),
|
|
270
|
+
DEFAULT_GAP,
|
|
271
|
+
),
|
|
272
|
+
);
|
|
273
|
+
this.backgroundColor = configService.get(
|
|
274
|
+
"ruler.backgroundColor",
|
|
275
|
+
this.backgroundColor,
|
|
276
|
+
);
|
|
277
|
+
this.textColor = configService.get("ruler.textColor", this.textColor);
|
|
278
|
+
this.lineColor = configService.get("ruler.lineColor", this.lineColor);
|
|
279
|
+
this.fontSize = this.toFiniteNumber(
|
|
280
|
+
configService.get("ruler.fontSize", this.fontSize),
|
|
281
|
+
DEFAULT_FONT_SIZE,
|
|
282
|
+
);
|
|
187
283
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
selectable: false,
|
|
196
|
-
evented: false,
|
|
197
|
-
left: 0,
|
|
198
|
-
top: 0,
|
|
199
|
-
originX: "left",
|
|
200
|
-
originY: "top",
|
|
284
|
+
this.log("config:loaded", {
|
|
285
|
+
thickness: this.thickness,
|
|
286
|
+
gap: this.gap,
|
|
287
|
+
fontSize: this.fontSize,
|
|
288
|
+
backgroundColor: this.backgroundColor,
|
|
289
|
+
textColor: this.textColor,
|
|
290
|
+
lineColor: this.lineColor,
|
|
201
291
|
});
|
|
292
|
+
}
|
|
202
293
|
|
|
203
|
-
|
|
294
|
+
private toFiniteNumber(value: unknown, fallback: number): number {
|
|
295
|
+
const numeric = Number(value);
|
|
296
|
+
return Number.isFinite(numeric) ? numeric : fallback;
|
|
204
297
|
}
|
|
205
298
|
|
|
206
|
-
private
|
|
207
|
-
if (!this.canvasService) return;
|
|
208
|
-
|
|
209
|
-
if (layer) {
|
|
210
|
-
this.canvasService.canvas.remove(layer);
|
|
211
|
-
}
|
|
299
|
+
private toSceneDisplayLength(value: number): number {
|
|
300
|
+
if (!this.canvasService) return value;
|
|
301
|
+
return this.canvasService.toSceneLength(value);
|
|
212
302
|
}
|
|
213
303
|
|
|
214
|
-
private
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
color: string,
|
|
220
|
-
): Group {
|
|
221
|
-
const line = new Line([x1, y1, x2, y2], {
|
|
222
|
-
stroke: color,
|
|
223
|
-
strokeWidth: this.thickness / 20, // Scale stroke width relative to thickness (default 1)
|
|
224
|
-
selectable: false,
|
|
225
|
-
evented: false,
|
|
226
|
-
});
|
|
304
|
+
private formatLengthMm(valueMm: number, unit: Unit): string {
|
|
305
|
+
const converted = fromMm(valueMm, unit);
|
|
306
|
+
const fractionDigits = unit === "in" ? 3 : 2;
|
|
307
|
+
return Number(converted.toFixed(fractionDigits)).toString();
|
|
308
|
+
}
|
|
227
309
|
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
310
|
+
private buildLinePath(start: Point, end: Point): string {
|
|
311
|
+
const dx = end.x - start.x;
|
|
312
|
+
const dy = end.y - start.y;
|
|
313
|
+
return `M 0 0 L ${dx} ${dy}`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private buildStartArrowPath(size: number): string {
|
|
317
|
+
return `M 0 0 L ${size} ${-size / 2} L ${size} ${size / 2} Z`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private buildEndArrowPath(size: number): string {
|
|
321
|
+
return `M 0 0 L ${-size} ${-size / 2} L ${-size} ${size / 2} Z`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private createPathSpec(
|
|
325
|
+
id: string,
|
|
326
|
+
pathData: string,
|
|
327
|
+
position: Point,
|
|
328
|
+
options: {
|
|
329
|
+
stroke?: string | null;
|
|
330
|
+
fill?: string | null;
|
|
331
|
+
strokeWidth?: number;
|
|
332
|
+
originX?: "left" | "center" | "right";
|
|
333
|
+
originY?: "top" | "center" | "bottom";
|
|
334
|
+
angle?: number;
|
|
335
|
+
strokeLineCap?: "butt" | "round" | "square";
|
|
336
|
+
},
|
|
337
|
+
): RenderObjectSpec {
|
|
338
|
+
return {
|
|
339
|
+
id,
|
|
340
|
+
type: "path",
|
|
341
|
+
data: {
|
|
342
|
+
id,
|
|
343
|
+
type: "ruler",
|
|
344
|
+
},
|
|
345
|
+
props: {
|
|
346
|
+
pathData,
|
|
347
|
+
left: position.x,
|
|
348
|
+
top: position.y,
|
|
349
|
+
originX: options.originX ?? "left",
|
|
350
|
+
originY: options.originY ?? "top",
|
|
351
|
+
angle: options.angle ?? 0,
|
|
352
|
+
stroke: options.stroke ?? null,
|
|
353
|
+
fill: options.fill ?? null,
|
|
354
|
+
strokeWidth: options.strokeWidth ?? 1,
|
|
355
|
+
strokeLineCap: options.strokeLineCap ?? "butt",
|
|
246
356
|
selectable: false,
|
|
247
357
|
evented: false,
|
|
358
|
+
excludeFromExport: true,
|
|
248
359
|
},
|
|
249
|
-
|
|
360
|
+
};
|
|
361
|
+
}
|
|
250
362
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
363
|
+
private createTextSpec(
|
|
364
|
+
id: string,
|
|
365
|
+
text: string,
|
|
366
|
+
position: Point,
|
|
367
|
+
angle: number = 0,
|
|
368
|
+
): RenderObjectSpec {
|
|
369
|
+
return {
|
|
370
|
+
id,
|
|
371
|
+
type: "text",
|
|
372
|
+
data: {
|
|
373
|
+
id,
|
|
374
|
+
type: "ruler",
|
|
375
|
+
},
|
|
376
|
+
props: {
|
|
377
|
+
text,
|
|
378
|
+
left: position.x,
|
|
379
|
+
top: position.y,
|
|
380
|
+
angle,
|
|
381
|
+
fontSize: this.toSceneDisplayLength(this.fontSize),
|
|
382
|
+
fill: this.textColor,
|
|
383
|
+
fontFamily: "Arial",
|
|
384
|
+
originX: "center",
|
|
263
385
|
originY: "center",
|
|
264
|
-
|
|
386
|
+
backgroundColor: this.backgroundColor,
|
|
265
387
|
selectable: false,
|
|
266
388
|
evented: false,
|
|
389
|
+
excludeFromExport: true,
|
|
267
390
|
},
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return new Group([line, startArrow, endArrow], {
|
|
271
|
-
selectable: false,
|
|
272
|
-
evented: false,
|
|
273
|
-
});
|
|
391
|
+
};
|
|
274
392
|
}
|
|
275
393
|
|
|
276
|
-
private
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
394
|
+
private buildRulerSpecs(input: {
|
|
395
|
+
left: number;
|
|
396
|
+
top: number;
|
|
397
|
+
right: number;
|
|
398
|
+
bottom: number;
|
|
399
|
+
widthLabel: string;
|
|
400
|
+
heightLabel: string;
|
|
401
|
+
}): RenderObjectSpec[] {
|
|
402
|
+
const { left, top, right, bottom, widthLabel, heightLabel } = input;
|
|
403
|
+
const gap = Math.max(
|
|
404
|
+
0,
|
|
405
|
+
this.toSceneDisplayLength(this.toFiniteNumber(this.gap, DEFAULT_GAP)),
|
|
287
406
|
);
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const trimRect = layout.trimRect;
|
|
294
|
-
const cutRect = layout.cutRect;
|
|
295
|
-
const useCutAsRuler = layout.cutMode === "outset";
|
|
296
|
-
const rulerRect = useCutAsRuler ? cutRect : trimRect;
|
|
297
|
-
// Use gap configuration
|
|
298
|
-
const gap = this.gap || 15;
|
|
299
|
-
|
|
300
|
-
// New Bounding Box for Ruler
|
|
301
|
-
const rulerLeft = rulerRect.left;
|
|
302
|
-
const rulerTop = rulerRect.top;
|
|
303
|
-
const rulerRight = rulerRect.left + rulerRect.width;
|
|
304
|
-
const rulerBottom = rulerRect.top + rulerRect.height;
|
|
305
|
-
|
|
306
|
-
// Display Dimensions (Physical)
|
|
307
|
-
const displayWidthMm = useCutAsRuler
|
|
308
|
-
? layout.cutWidthMm
|
|
309
|
-
: layout.trimWidthMm;
|
|
310
|
-
const displayHeightMm = useCutAsRuler
|
|
311
|
-
? layout.cutHeightMm
|
|
312
|
-
: layout.trimHeightMm;
|
|
313
|
-
const displayUnit = sizeState.unit;
|
|
314
|
-
|
|
315
|
-
// Ruler Placement Coordinates
|
|
316
|
-
// Top Ruler: Above the top boundary
|
|
317
|
-
const topRulerY = rulerTop - gap;
|
|
318
|
-
const topRulerXStart = rulerLeft;
|
|
319
|
-
const topRulerXEnd = rulerRight;
|
|
320
|
-
|
|
321
|
-
// Left Ruler: Left of the left boundary
|
|
322
|
-
const leftRulerX = rulerLeft - gap;
|
|
323
|
-
const leftRulerYStart = rulerTop;
|
|
324
|
-
const leftRulerYEnd = rulerBottom;
|
|
325
|
-
|
|
326
|
-
// 1. Top Dimension Line (X-Axis)
|
|
327
|
-
const topDimLine = this.createArrowLine(
|
|
328
|
-
topRulerXStart,
|
|
329
|
-
topRulerY,
|
|
330
|
-
topRulerXEnd,
|
|
331
|
-
topRulerY,
|
|
332
|
-
lineColor,
|
|
407
|
+
const topY = top - gap;
|
|
408
|
+
const leftX = left - gap;
|
|
409
|
+
const arrowSize = Math.max(
|
|
410
|
+
this.toSceneDisplayLength(MIN_ARROW_SIZE),
|
|
411
|
+
this.toSceneDisplayLength(this.thickness * 0.3),
|
|
333
412
|
);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
413
|
+
const strokeWidth = Math.max(
|
|
414
|
+
this.toSceneDisplayLength(1),
|
|
415
|
+
this.toSceneDisplayLength(
|
|
416
|
+
this.thickness / THICKNESS_TO_STROKE_WIDTH_RATIO,
|
|
417
|
+
),
|
|
418
|
+
);
|
|
419
|
+
const extensionLength = this.toSceneDisplayLength(EXTENSION_LINE_LENGTH);
|
|
420
|
+
const topLineAngleDeg = 0;
|
|
421
|
+
const leftLineAngleDeg = 90;
|
|
422
|
+
|
|
423
|
+
// Keep dimension line inside the arrow heads so it doesn't visually overflow.
|
|
424
|
+
const topMidX = left + (right - left) / 2;
|
|
425
|
+
const leftMidY = top + (bottom - top) / 2;
|
|
426
|
+
const topLineStartX = Math.min(left + arrowSize, topMidX);
|
|
427
|
+
const topLineEndX = Math.max(right - arrowSize, topMidX);
|
|
428
|
+
const leftLineStartY = Math.min(top + arrowSize, leftMidY);
|
|
429
|
+
const leftLineEndY = Math.max(bottom - arrowSize, leftMidY);
|
|
430
|
+
|
|
431
|
+
const specs: RenderObjectSpec[] = [];
|
|
432
|
+
|
|
433
|
+
specs.push(
|
|
434
|
+
this.createPathSpec(
|
|
435
|
+
"ruler.top.line",
|
|
436
|
+
this.buildLinePath(
|
|
437
|
+
{ x: topLineStartX, y: topY },
|
|
438
|
+
{ x: topLineEndX, y: topY },
|
|
439
|
+
),
|
|
440
|
+
{ x: topLineStartX, y: topY },
|
|
346
441
|
{
|
|
347
|
-
stroke: lineColor,
|
|
348
|
-
strokeWidth
|
|
349
|
-
|
|
350
|
-
evented: false,
|
|
442
|
+
stroke: this.lineColor,
|
|
443
|
+
strokeWidth,
|
|
444
|
+
strokeLineCap: "butt",
|
|
351
445
|
},
|
|
352
446
|
),
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
447
|
+
this.createPathSpec(
|
|
448
|
+
"ruler.top.arrow.start",
|
|
449
|
+
this.buildStartArrowPath(arrowSize),
|
|
450
|
+
{ x: left, y: topY },
|
|
357
451
|
{
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
452
|
+
fill: this.lineColor,
|
|
453
|
+
stroke: this.lineColor,
|
|
454
|
+
strokeWidth: this.toSceneDisplayLength(1),
|
|
455
|
+
originX: "left",
|
|
456
|
+
originY: "center",
|
|
457
|
+
angle: topLineAngleDeg,
|
|
362
458
|
},
|
|
363
459
|
),
|
|
460
|
+
this.createPathSpec(
|
|
461
|
+
"ruler.top.arrow.end",
|
|
462
|
+
this.buildEndArrowPath(arrowSize),
|
|
463
|
+
{ x: right, y: topY },
|
|
464
|
+
{
|
|
465
|
+
fill: this.lineColor,
|
|
466
|
+
stroke: this.lineColor,
|
|
467
|
+
strokeWidth: this.toSceneDisplayLength(1),
|
|
468
|
+
originX: "right",
|
|
469
|
+
originY: "center",
|
|
470
|
+
angle: topLineAngleDeg,
|
|
471
|
+
},
|
|
472
|
+
),
|
|
473
|
+
this.createPathSpec(
|
|
474
|
+
"ruler.top.ext.start",
|
|
475
|
+
this.buildLinePath(
|
|
476
|
+
{ x: left, y: topY - extensionLength },
|
|
477
|
+
{ x: left, y: topY + extensionLength },
|
|
478
|
+
),
|
|
479
|
+
{ x: left, y: topY - extensionLength },
|
|
480
|
+
{
|
|
481
|
+
stroke: this.lineColor,
|
|
482
|
+
strokeWidth: this.toSceneDisplayLength(1),
|
|
483
|
+
},
|
|
484
|
+
),
|
|
485
|
+
this.createPathSpec(
|
|
486
|
+
"ruler.top.ext.end",
|
|
487
|
+
this.buildLinePath(
|
|
488
|
+
{ x: right, y: topY - extensionLength },
|
|
489
|
+
{ x: right, y: topY + extensionLength },
|
|
490
|
+
),
|
|
491
|
+
{ x: right, y: topY - extensionLength },
|
|
492
|
+
{
|
|
493
|
+
stroke: this.lineColor,
|
|
494
|
+
strokeWidth: this.toSceneDisplayLength(1),
|
|
495
|
+
},
|
|
496
|
+
),
|
|
497
|
+
this.createTextSpec("ruler.top.label", widthLabel, {
|
|
498
|
+
x: left + (right - left) / 2,
|
|
499
|
+
y: topY,
|
|
500
|
+
}),
|
|
364
501
|
);
|
|
365
502
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
fontFamily: "Arial",
|
|
375
|
-
originX: "center",
|
|
376
|
-
originY: "center",
|
|
377
|
-
backgroundColor: backgroundColor, // Background mask for readability
|
|
378
|
-
selectable: false,
|
|
379
|
-
evented: false,
|
|
380
|
-
});
|
|
381
|
-
// Add small padding to text background if Fabric supports it directly or via separate rect
|
|
382
|
-
// Fabric Text backgroundColor is tight.
|
|
383
|
-
layer.add(topText);
|
|
384
|
-
|
|
385
|
-
// 2. Left Dimension Line (Y-Axis)
|
|
386
|
-
const leftDimLine = this.createArrowLine(
|
|
387
|
-
leftRulerX,
|
|
388
|
-
leftRulerYStart,
|
|
389
|
-
leftRulerX,
|
|
390
|
-
leftRulerYEnd,
|
|
391
|
-
lineColor,
|
|
392
|
-
);
|
|
393
|
-
layer.add(leftDimLine);
|
|
394
|
-
|
|
395
|
-
// Left Extension Lines
|
|
396
|
-
layer.add(
|
|
397
|
-
new Line(
|
|
398
|
-
[
|
|
399
|
-
leftRulerX - extLen,
|
|
400
|
-
leftRulerYStart,
|
|
401
|
-
leftRulerX + extLen,
|
|
402
|
-
leftRulerYStart,
|
|
403
|
-
],
|
|
503
|
+
specs.push(
|
|
504
|
+
this.createPathSpec(
|
|
505
|
+
"ruler.left.line",
|
|
506
|
+
this.buildLinePath(
|
|
507
|
+
{ x: leftX, y: leftLineStartY },
|
|
508
|
+
{ x: leftX, y: leftLineEndY },
|
|
509
|
+
),
|
|
510
|
+
{ x: leftX, y: leftLineStartY },
|
|
404
511
|
{
|
|
405
|
-
stroke: lineColor,
|
|
406
|
-
strokeWidth
|
|
407
|
-
|
|
408
|
-
evented: false,
|
|
512
|
+
stroke: this.lineColor,
|
|
513
|
+
strokeWidth,
|
|
514
|
+
strokeLineCap: "butt",
|
|
409
515
|
},
|
|
410
516
|
),
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
517
|
+
this.createPathSpec(
|
|
518
|
+
"ruler.left.arrow.start",
|
|
519
|
+
this.buildStartArrowPath(arrowSize),
|
|
520
|
+
{ x: leftX, y: top },
|
|
521
|
+
{
|
|
522
|
+
fill: this.lineColor,
|
|
523
|
+
stroke: this.lineColor,
|
|
524
|
+
strokeWidth: this.toSceneDisplayLength(1),
|
|
525
|
+
originX: "left",
|
|
526
|
+
originY: "center",
|
|
527
|
+
angle: leftLineAngleDeg,
|
|
528
|
+
},
|
|
529
|
+
),
|
|
530
|
+
this.createPathSpec(
|
|
531
|
+
"ruler.left.arrow.end",
|
|
532
|
+
this.buildEndArrowPath(arrowSize),
|
|
533
|
+
{ x: leftX, y: bottom },
|
|
534
|
+
{
|
|
535
|
+
fill: this.lineColor,
|
|
536
|
+
stroke: this.lineColor,
|
|
537
|
+
strokeWidth: this.toSceneDisplayLength(1),
|
|
538
|
+
originX: "right",
|
|
539
|
+
originY: "center",
|
|
540
|
+
angle: leftLineAngleDeg,
|
|
541
|
+
},
|
|
542
|
+
),
|
|
543
|
+
this.createPathSpec(
|
|
544
|
+
"ruler.left.ext.start",
|
|
545
|
+
this.buildLinePath(
|
|
546
|
+
{ x: leftX - extensionLength, y: top },
|
|
547
|
+
{ x: leftX + extensionLength, y: top },
|
|
548
|
+
),
|
|
549
|
+
{ x: leftX - extensionLength, y: top },
|
|
550
|
+
{
|
|
551
|
+
stroke: this.lineColor,
|
|
552
|
+
strokeWidth: this.toSceneDisplayLength(1),
|
|
553
|
+
},
|
|
554
|
+
),
|
|
555
|
+
this.createPathSpec(
|
|
556
|
+
"ruler.left.ext.end",
|
|
557
|
+
this.buildLinePath(
|
|
558
|
+
{ x: leftX - extensionLength, y: bottom },
|
|
559
|
+
{ x: leftX + extensionLength, y: bottom },
|
|
560
|
+
),
|
|
561
|
+
{ x: leftX - extensionLength, y: bottom },
|
|
562
|
+
{
|
|
563
|
+
stroke: this.lineColor,
|
|
564
|
+
strokeWidth: this.toSceneDisplayLength(1),
|
|
565
|
+
},
|
|
566
|
+
),
|
|
567
|
+
this.createTextSpec(
|
|
568
|
+
"ruler.left.label",
|
|
569
|
+
heightLabel,
|
|
420
570
|
{
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
selectable: false,
|
|
424
|
-
evented: false,
|
|
571
|
+
x: leftX,
|
|
572
|
+
y: top + (bottom - top) / 2,
|
|
425
573
|
},
|
|
574
|
+
-90,
|
|
426
575
|
),
|
|
427
576
|
);
|
|
428
577
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
578
|
+
return specs;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private updateRuler() {
|
|
582
|
+
void this.updateRulerAsync();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private async updateRulerAsync() {
|
|
586
|
+
if (!this.canvasService) return;
|
|
587
|
+
const configService = this.context?.services.get<ConfigurationService>(
|
|
588
|
+
"ConfigurationService",
|
|
589
|
+
);
|
|
590
|
+
if (!configService) return;
|
|
591
|
+
|
|
592
|
+
const seq = ++this.renderSeq;
|
|
593
|
+
const sizeState = readSizeState(configService);
|
|
594
|
+
const layout = computeSceneLayout(this.canvasService, sizeState);
|
|
595
|
+
|
|
596
|
+
this.log("render:start", {
|
|
597
|
+
seq,
|
|
598
|
+
unit: sizeState.unit,
|
|
599
|
+
gap: this.gap,
|
|
600
|
+
thickness: this.thickness,
|
|
601
|
+
fontSize: this.fontSize,
|
|
602
|
+
hasLayout: !!layout,
|
|
603
|
+
scale: layout?.scale ?? null,
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
if (!layout || layout.scale <= 0) {
|
|
607
|
+
if (seq !== this.renderSeq) return;
|
|
608
|
+
this.log("render:skip", { seq, reason: "invalid-layout" });
|
|
609
|
+
this.specs = [];
|
|
610
|
+
await this.canvasService.flushRenderFromProducers();
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const geometry = buildSceneGeometry(configService, layout);
|
|
615
|
+
if (geometry.unit !== "px") {
|
|
616
|
+
console.warn("[RulerTool] Unexpected geometry unit.", geometry.unit);
|
|
617
|
+
}
|
|
618
|
+
const centerScene = this.canvasService.toScenePoint({
|
|
619
|
+
x: geometry.x,
|
|
620
|
+
y: geometry.y,
|
|
621
|
+
});
|
|
622
|
+
const widthScene = this.canvasService.toSceneLength(geometry.width);
|
|
623
|
+
const heightScene = this.canvasService.toSceneLength(geometry.height);
|
|
624
|
+
const rulerLeft = centerScene.x - widthScene / 2;
|
|
625
|
+
const rulerTop = centerScene.y - heightScene / 2;
|
|
626
|
+
const rulerRight = rulerLeft + widthScene;
|
|
627
|
+
const rulerBottom = rulerTop + heightScene;
|
|
628
|
+
|
|
629
|
+
const widthMm = widthScene;
|
|
630
|
+
const heightMm = heightScene;
|
|
631
|
+
const unit = sizeState.unit;
|
|
632
|
+
const widthLabel = `${this.formatLengthMm(widthMm, unit)} ${unit}`;
|
|
633
|
+
const heightLabel = `${this.formatLengthMm(heightMm, unit)} ${unit}`;
|
|
634
|
+
const specs = this.buildRulerSpecs({
|
|
635
|
+
left: rulerLeft,
|
|
636
|
+
top: rulerTop,
|
|
637
|
+
right: rulerRight,
|
|
638
|
+
bottom: rulerBottom,
|
|
639
|
+
widthLabel,
|
|
640
|
+
heightLabel,
|
|
444
641
|
});
|
|
445
|
-
layer.add(leftText);
|
|
446
642
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
643
|
+
this.log("render:geometry", {
|
|
644
|
+
seq,
|
|
645
|
+
left: rulerLeft,
|
|
646
|
+
top: rulerTop,
|
|
647
|
+
right: rulerRight,
|
|
648
|
+
bottom: rulerBottom,
|
|
649
|
+
widthScene,
|
|
650
|
+
heightScene,
|
|
651
|
+
widthMm,
|
|
652
|
+
heightMm,
|
|
653
|
+
specCount: specs.length,
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
if (seq !== this.renderSeq) return;
|
|
657
|
+
|
|
658
|
+
this.specs = specs;
|
|
659
|
+
await this.canvasService.flushRenderFromProducers();
|
|
660
|
+
if (seq !== this.renderSeq) return;
|
|
661
|
+
this.canvasService.requestRenderAll();
|
|
662
|
+
this.log("render:done", { seq });
|
|
450
663
|
}
|
|
451
664
|
}
|