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