@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/.test-dist/src/CanvasService.js +83 -0
- package/.test-dist/src/ViewportSystem.js +75 -0
- package/.test-dist/src/background.js +203 -0
- package/.test-dist/src/constraints.js +153 -0
- package/.test-dist/src/coordinate.js +74 -0
- package/.test-dist/src/dieline.js +758 -0
- package/.test-dist/src/feature.js +687 -0
- package/.test-dist/src/featureComplete.js +31 -0
- package/.test-dist/src/featureDraft.js +31 -0
- package/.test-dist/src/film.js +167 -0
- package/.test-dist/src/geometry.js +292 -0
- package/.test-dist/src/image.js +421 -0
- package/.test-dist/src/index.js +31 -0
- package/.test-dist/src/mirror.js +104 -0
- package/.test-dist/src/ruler.js +383 -0
- package/.test-dist/src/tracer.js +448 -0
- package/.test-dist/src/units.js +30 -0
- package/.test-dist/src/white-ink.js +310 -0
- package/.test-dist/tests/run.js +60 -0
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +50 -5
- package/dist/index.d.ts +50 -5
- package/dist/index.js +544 -297
- package/dist/index.mjs +541 -296
- package/package.json +3 -2
- package/src/CanvasService.ts +7 -0
- package/src/ViewportSystem.ts +92 -0
- package/src/constraints.ts +53 -4
- package/src/dieline.ts +169 -85
- package/src/feature.ts +217 -150
- package/src/featureComplete.ts +45 -0
- package/src/index.ts +1 -0
- package/src/ruler.ts +26 -18
- package/src/units.ts +27 -0
- package/tests/run.ts +81 -0
- package/tsconfig.test.json +15 -0
package/src/ruler.ts
CHANGED
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
} from "@pooder/core";
|
|
8
8
|
import { Rect, Line, Text, Group, Polygon } from "fabric";
|
|
9
9
|
import CanvasService from "./CanvasService";
|
|
10
|
-
import {
|
|
10
|
+
import { Unit } from "./coordinate";
|
|
11
|
+
import { formatMm } from "./units";
|
|
11
12
|
|
|
12
13
|
export class RulerTool implements Extension {
|
|
13
14
|
id = "pooder.kit.ruler";
|
|
@@ -26,7 +27,7 @@ export class RulerTool implements Extension {
|
|
|
26
27
|
// Dieline context for sync
|
|
27
28
|
private dielineWidth: number = 500;
|
|
28
29
|
private dielineHeight: number = 500;
|
|
29
|
-
private
|
|
30
|
+
private dielineDisplayUnit: Unit = "mm";
|
|
30
31
|
private dielinePadding: number | string = 40;
|
|
31
32
|
private dielineOffset: number = 0;
|
|
32
33
|
|
|
@@ -67,7 +68,10 @@ export class RulerTool implements Extension {
|
|
|
67
68
|
this.fontSize = configService.get("ruler.fontSize", this.fontSize);
|
|
68
69
|
|
|
69
70
|
// Load Dieline Config
|
|
70
|
-
this.
|
|
71
|
+
this.dielineDisplayUnit = configService.get(
|
|
72
|
+
"dieline.displayUnit",
|
|
73
|
+
this.dielineDisplayUnit,
|
|
74
|
+
);
|
|
71
75
|
this.dielineWidth = configService.get("dieline.width", this.dielineWidth);
|
|
72
76
|
this.dielineHeight = configService.get(
|
|
73
77
|
"dieline.height",
|
|
@@ -92,7 +96,8 @@ export class RulerTool implements Extension {
|
|
|
92
96
|
shouldUpdate = true;
|
|
93
97
|
}
|
|
94
98
|
} else if (e.key.startsWith("dieline.")) {
|
|
95
|
-
if (e.key === "dieline.
|
|
99
|
+
if (e.key === "dieline.displayUnit")
|
|
100
|
+
this.dielineDisplayUnit = e.value;
|
|
96
101
|
if (e.key === "dieline.width") this.dielineWidth = e.value;
|
|
97
102
|
if (e.key === "dieline.height") this.dielineHeight = e.value;
|
|
98
103
|
if (e.key === "dieline.padding") this.dielinePadding = e.value;
|
|
@@ -320,12 +325,16 @@ export class RulerTool implements Extension {
|
|
|
320
325
|
// Calculate Layout using Dieline properties
|
|
321
326
|
// Add padding to match DielineTool
|
|
322
327
|
const paddingPx = this.resolvePadding(width, height);
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
328
|
+
|
|
329
|
+
// Sync Viewport (in case DielineTool hasn't updated it yet, or purely for consistency)
|
|
330
|
+
this.canvasService.viewport.setPadding(paddingPx);
|
|
331
|
+
this.canvasService.viewport.updatePhysical(
|
|
332
|
+
this.dielineWidth,
|
|
333
|
+
this.dielineHeight,
|
|
327
334
|
);
|
|
328
335
|
|
|
336
|
+
const layout = this.canvasService.viewport.layout;
|
|
337
|
+
|
|
329
338
|
const scale = layout.scale;
|
|
330
339
|
const offsetX = layout.offsetX;
|
|
331
340
|
const offsetY = layout.offsetY;
|
|
@@ -341,12 +350,12 @@ export class RulerTool implements Extension {
|
|
|
341
350
|
// - Dimensions show original size.
|
|
342
351
|
// - Bleed area is internal, so we ignore it for ruler placement.
|
|
343
352
|
|
|
344
|
-
const
|
|
353
|
+
const rawOffsetMm = this.dielineOffset || 0;
|
|
345
354
|
// Effective offset for ruler calculations (only positive offset expands the ruler)
|
|
346
|
-
const
|
|
355
|
+
const effectiveOffsetMm = rawOffsetMm > 0 ? rawOffsetMm : 0;
|
|
347
356
|
|
|
348
357
|
// Pixel expansion based on effective offset
|
|
349
|
-
const expandPixels =
|
|
358
|
+
const expandPixels = effectiveOffsetMm * scale;
|
|
350
359
|
// Use gap configuration
|
|
351
360
|
const gap = this.gap || 15;
|
|
352
361
|
|
|
@@ -357,8 +366,8 @@ export class RulerTool implements Extension {
|
|
|
357
366
|
const rulerBottom = offsetY + visualHeight + expandPixels;
|
|
358
367
|
|
|
359
368
|
// Display Dimensions (Physical)
|
|
360
|
-
const
|
|
361
|
-
const
|
|
369
|
+
const displayWidthMm = this.dielineWidth + effectiveOffsetMm * 2;
|
|
370
|
+
const displayHeightMm = this.dielineHeight + effectiveOffsetMm * 2;
|
|
362
371
|
|
|
363
372
|
// Ruler Placement Coordinates
|
|
364
373
|
// Top Ruler: Above the top boundary
|
|
@@ -412,9 +421,8 @@ export class RulerTool implements Extension {
|
|
|
412
421
|
);
|
|
413
422
|
|
|
414
423
|
// Top Text (Centered)
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
const topTextContent = `${widthStr} ${this.dielineUnit}`;
|
|
424
|
+
const widthStr = formatMm(displayWidthMm, this.dielineDisplayUnit);
|
|
425
|
+
const topTextContent = `${widthStr} ${this.dielineDisplayUnit}`;
|
|
418
426
|
const topText = new Text(topTextContent, {
|
|
419
427
|
left: topRulerXStart + (rulerRight - rulerLeft) / 2,
|
|
420
428
|
top: topRulerY,
|
|
@@ -476,8 +484,8 @@ export class RulerTool implements Extension {
|
|
|
476
484
|
);
|
|
477
485
|
|
|
478
486
|
// Left Text (Centered, Rotated)
|
|
479
|
-
const heightStr =
|
|
480
|
-
const leftTextContent = `${heightStr} ${this.
|
|
487
|
+
const heightStr = formatMm(displayHeightMm, this.dielineDisplayUnit);
|
|
488
|
+
const leftTextContent = `${heightStr} ${this.dielineDisplayUnit}`;
|
|
481
489
|
const leftText = new Text(leftTextContent, {
|
|
482
490
|
left: leftRulerX,
|
|
483
491
|
top: leftRulerYStart + (rulerBottom - rulerTop) / 2,
|
package/src/units.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Coordinate, Unit } from "./coordinate";
|
|
2
|
+
|
|
3
|
+
export function parseLengthToMm(input: number | string, defaultUnit: Unit): number {
|
|
4
|
+
if (typeof input === "number") {
|
|
5
|
+
if (!Number.isFinite(input)) return 0;
|
|
6
|
+
return Coordinate.convertUnit(input, defaultUnit, "mm");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const raw = input.trim();
|
|
10
|
+
if (!raw) return 0;
|
|
11
|
+
|
|
12
|
+
const match = raw.match(/^([+-]?\d+(?:\.\d+)?)\s*(px|mm|cm|in)?$/i);
|
|
13
|
+
if (!match) return 0;
|
|
14
|
+
|
|
15
|
+
const value = Number(match[1]);
|
|
16
|
+
if (!Number.isFinite(value)) return 0;
|
|
17
|
+
|
|
18
|
+
const unit = (match[2]?.toLowerCase() as Unit | undefined) ?? defaultUnit;
|
|
19
|
+
return Coordinate.convertUnit(value, unit, "mm");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatMm(valueMm: number, displayUnit: Unit, fractionDigits: number = 2): string {
|
|
23
|
+
if (!Number.isFinite(valueMm)) return "0";
|
|
24
|
+
const value = Coordinate.convertUnit(valueMm, "mm", displayUnit);
|
|
25
|
+
const rounded = Number(value.toFixed(fractionDigits));
|
|
26
|
+
return rounded.toString();
|
|
27
|
+
}
|
package/tests/run.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ConstraintRegistry } from "../src/constraints";
|
|
2
|
+
import { completeFeaturesStrict } from "../src/featureComplete";
|
|
3
|
+
import { ConstraintFeature } from "../src/constraints";
|
|
4
|
+
|
|
5
|
+
function assert(condition: any, message: string) {
|
|
6
|
+
if (!condition) throw new Error(message);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function closeTo(a: number, b: number, eps: number = 1e-6) {
|
|
10
|
+
return Math.abs(a - b) <= eps;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function testTangentBottom() {
|
|
14
|
+
const feature: ConstraintFeature = {
|
|
15
|
+
id: "stand",
|
|
16
|
+
operation: "add",
|
|
17
|
+
shape: "rect",
|
|
18
|
+
x: 0.5,
|
|
19
|
+
y: 0.2,
|
|
20
|
+
width: 40,
|
|
21
|
+
height: 20,
|
|
22
|
+
constraints: { type: "tangent-bottom", params: { gap: 0 } },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const out = ConstraintRegistry.apply(feature.x, feature.y, feature, {
|
|
26
|
+
dielineWidth: 100,
|
|
27
|
+
dielineHeight: 100,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
assert(closeTo(out.y, 1.1), `tangent-bottom y expected 1.1, got ${out.y}`);
|
|
31
|
+
assert(closeTo(out.x, 0.5), `tangent-bottom x expected 0.5, got ${out.x}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function testCompleteFeaturesStrict() {
|
|
35
|
+
const updates: any[] = [];
|
|
36
|
+
|
|
37
|
+
const illegal: ConstraintFeature = {
|
|
38
|
+
id: "stand",
|
|
39
|
+
operation: "add",
|
|
40
|
+
shape: "rect",
|
|
41
|
+
x: 0.5,
|
|
42
|
+
y: 0.5,
|
|
43
|
+
width: 40,
|
|
44
|
+
height: 20,
|
|
45
|
+
constraints: { type: "tangent-bottom", params: { gap: 0 } },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const failed = completeFeaturesStrict(
|
|
49
|
+
[illegal],
|
|
50
|
+
{ dielineWidth: 100, dielineHeight: 100 },
|
|
51
|
+
(next) => updates.push({ key: "dieline.features", value: next }),
|
|
52
|
+
);
|
|
53
|
+
assert(failed.ok === false, "completeFeatures should fail for illegal features");
|
|
54
|
+
assert(updates.length === 0, "illegal draft should not update configuration");
|
|
55
|
+
|
|
56
|
+
const legal: ConstraintFeature = {
|
|
57
|
+
...illegal,
|
|
58
|
+
y: 1.1,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const ok = completeFeaturesStrict(
|
|
62
|
+
[legal],
|
|
63
|
+
{ dielineWidth: 100, dielineHeight: 100 },
|
|
64
|
+
(next) => updates.push({ key: "dieline.features", value: next }),
|
|
65
|
+
);
|
|
66
|
+
assert(ok.ok === true, "completeFeatures should succeed for legal features");
|
|
67
|
+
assert(updates.length === 1, "legal draft should update configuration once");
|
|
68
|
+
assert(updates[0].key === "dieline.features", "should update dieline.features");
|
|
69
|
+
assert(
|
|
70
|
+
closeTo(updates[0].value[0].y, 1.1),
|
|
71
|
+
"saved feature y should remain 1.1",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function main() {
|
|
76
|
+
testTangentBottom();
|
|
77
|
+
testCompleteFeaturesStrict();
|
|
78
|
+
console.log("ok");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"lib": ["ES2020", "DOM"],
|
|
6
|
+
"moduleResolution": "Node",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"outDir": ".test-dist",
|
|
11
|
+
"rootDir": "."
|
|
12
|
+
},
|
|
13
|
+
"include": ["tests/**/*.ts", "src/**/*.ts"]
|
|
14
|
+
}
|
|
15
|
+
|