@pooder/kit 6.2.0 → 6.2.2
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/dieline/DielineTool.js +22 -9
- package/.test-dist/src/extensions/dieline/renderBuilder.js +47 -14
- package/.test-dist/src/extensions/feature/FeatureTool.js +20 -3
- package/.test-dist/src/extensions/featureCoordinates.js +21 -0
- package/.test-dist/src/extensions/featurePlacement.js +46 -0
- package/.test-dist/src/extensions/image/ImageTool.js +46 -348
- package/.test-dist/src/extensions/image/sessionOverlay.js +148 -0
- package/.test-dist/src/extensions/ruler/RulerTool.js +25 -2
- package/.test-dist/tests/run.js +25 -0
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +4 -5
- package/dist/index.d.ts +4 -5
- package/dist/index.js +1506 -1494
- package/dist/index.mjs +1506 -1494
- package/package.json +1 -1
- package/src/extensions/dieline/DielineTool.ts +29 -9
- package/src/extensions/dieline/renderBuilder.ts +65 -17
- package/src/extensions/feature/FeatureTool.ts +23 -3
- package/src/extensions/featureCoordinates.ts +35 -0
- package/src/extensions/featurePlacement.ts +118 -0
- package/src/extensions/image/ImageTool.ts +57 -412
- package/src/extensions/image/sessionOverlay.ts +206 -0
- package/src/extensions/ruler/RulerTool.ts +24 -2
- package/tests/run.ts +37 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { Pattern } from "fabric";
|
|
2
|
+
import type { RenderObjectSpec } from "../../services";
|
|
3
|
+
import type {
|
|
4
|
+
SceneGeometrySnapshot,
|
|
5
|
+
SceneLayoutSnapshot,
|
|
6
|
+
SceneRect,
|
|
7
|
+
} from "../../shared/scene/sceneLayoutModel";
|
|
8
|
+
import { generateDielinePath } from "../geometry";
|
|
9
|
+
|
|
10
|
+
export interface ImageSessionOverlayVisualConfig {
|
|
11
|
+
strokeColor: string;
|
|
12
|
+
strokeWidth: number;
|
|
13
|
+
strokeStyle: "solid" | "dashed" | "hidden";
|
|
14
|
+
dashLength: number;
|
|
15
|
+
innerBackground: string;
|
|
16
|
+
outerBackground: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ImageSessionOverlayViewport {
|
|
20
|
+
left: number;
|
|
21
|
+
top: number;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface BuiltinShapeOverlayPaths {
|
|
27
|
+
hatchPathData: string;
|
|
28
|
+
shapePathData: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const EPSILON = 0.0001;
|
|
32
|
+
const SHAPE_OUTLINE_COLOR = "rgba(255, 0, 0, 0.9)";
|
|
33
|
+
const DEFAULT_HATCH_FILL = "rgba(255, 0, 0, 0.22)";
|
|
34
|
+
|
|
35
|
+
function buildRectPath(width: number, height: number): string {
|
|
36
|
+
return `M 0 0 L ${width} 0 L ${width} ${height} L 0 ${height} Z`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildViewportMaskPath(
|
|
40
|
+
viewport: ImageSessionOverlayViewport,
|
|
41
|
+
cutRect: SceneRect,
|
|
42
|
+
): string {
|
|
43
|
+
const cutLeft = cutRect.left - viewport.left;
|
|
44
|
+
const cutTop = cutRect.top - viewport.top;
|
|
45
|
+
return [
|
|
46
|
+
buildRectPath(viewport.width, viewport.height),
|
|
47
|
+
`M ${cutLeft} ${cutTop} L ${cutLeft + cutRect.width} ${cutTop} L ${
|
|
48
|
+
cutLeft + cutRect.width
|
|
49
|
+
} ${cutTop + cutRect.height} L ${cutLeft} ${cutTop + cutRect.height} Z`,
|
|
50
|
+
].join(" ");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveCutShapeRadiusPx(
|
|
54
|
+
geometry: SceneGeometrySnapshot,
|
|
55
|
+
cutRect: SceneRect,
|
|
56
|
+
): number {
|
|
57
|
+
const visualRadius = Number.isFinite(geometry.radius)
|
|
58
|
+
? Math.max(0, geometry.radius)
|
|
59
|
+
: 0;
|
|
60
|
+
const visualOffset = Number.isFinite(geometry.offset) ? geometry.offset : 0;
|
|
61
|
+
const rawCutRadius =
|
|
62
|
+
visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
63
|
+
const maxRadius = Math.max(0, Math.min(cutRect.width, cutRect.height) / 2);
|
|
64
|
+
return Math.max(0, Math.min(maxRadius, rawCutRadius));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildBuiltinShapeOverlayPaths(
|
|
68
|
+
cutRect: SceneRect,
|
|
69
|
+
geometry: SceneGeometrySnapshot | null,
|
|
70
|
+
): BuiltinShapeOverlayPaths | null {
|
|
71
|
+
if (!geometry || geometry.shape === "custom") {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const radius = resolveCutShapeRadiusPx(geometry, cutRect);
|
|
76
|
+
if (geometry.shape === "rect" && radius <= EPSILON) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const shapePathData = generateDielinePath({
|
|
81
|
+
shape: geometry.shape,
|
|
82
|
+
shapeStyle: geometry.shapeStyle,
|
|
83
|
+
width: Math.max(1, cutRect.width),
|
|
84
|
+
height: Math.max(1, cutRect.height),
|
|
85
|
+
radius,
|
|
86
|
+
x: cutRect.width / 2,
|
|
87
|
+
y: cutRect.height / 2,
|
|
88
|
+
features: [],
|
|
89
|
+
canvasWidth: Math.max(1, cutRect.width),
|
|
90
|
+
canvasHeight: Math.max(1, cutRect.height),
|
|
91
|
+
});
|
|
92
|
+
if (!shapePathData) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
shapePathData,
|
|
98
|
+
hatchPathData: `${buildRectPath(cutRect.width, cutRect.height)} ${shapePathData}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function buildImageSessionOverlaySpecs(args: {
|
|
103
|
+
viewport: ImageSessionOverlayViewport;
|
|
104
|
+
layout: SceneLayoutSnapshot;
|
|
105
|
+
geometry: SceneGeometrySnapshot | null;
|
|
106
|
+
visual: ImageSessionOverlayVisualConfig;
|
|
107
|
+
hatchPattern?: Pattern;
|
|
108
|
+
}): RenderObjectSpec[] {
|
|
109
|
+
const { viewport, layout, geometry, visual, hatchPattern } = args;
|
|
110
|
+
const cutRect = layout.cutRect;
|
|
111
|
+
const specs: RenderObjectSpec[] = [];
|
|
112
|
+
|
|
113
|
+
specs.push({
|
|
114
|
+
id: "image.cropMask.rect",
|
|
115
|
+
type: "path",
|
|
116
|
+
space: "screen",
|
|
117
|
+
data: { id: "image.cropMask.rect", zIndex: 1 },
|
|
118
|
+
props: {
|
|
119
|
+
pathData: buildViewportMaskPath(viewport, cutRect),
|
|
120
|
+
left: viewport.left,
|
|
121
|
+
top: viewport.top,
|
|
122
|
+
originX: "left",
|
|
123
|
+
originY: "top",
|
|
124
|
+
fill: visual.outerBackground,
|
|
125
|
+
stroke: null,
|
|
126
|
+
fillRule: "evenodd",
|
|
127
|
+
selectable: false,
|
|
128
|
+
evented: false,
|
|
129
|
+
excludeFromExport: true,
|
|
130
|
+
objectCaching: false,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const shapeOverlay = buildBuiltinShapeOverlayPaths(cutRect, geometry);
|
|
135
|
+
if (shapeOverlay) {
|
|
136
|
+
specs.push({
|
|
137
|
+
id: "image.cropShapeHatch",
|
|
138
|
+
type: "path",
|
|
139
|
+
space: "screen",
|
|
140
|
+
data: { id: "image.cropShapeHatch", zIndex: 5 },
|
|
141
|
+
props: {
|
|
142
|
+
pathData: shapeOverlay.hatchPathData,
|
|
143
|
+
left: cutRect.left,
|
|
144
|
+
top: cutRect.top,
|
|
145
|
+
originX: "left",
|
|
146
|
+
originY: "top",
|
|
147
|
+
fill: hatchPattern || DEFAULT_HATCH_FILL,
|
|
148
|
+
opacity: hatchPattern ? 1 : 0.8,
|
|
149
|
+
stroke: null,
|
|
150
|
+
fillRule: "evenodd",
|
|
151
|
+
selectable: false,
|
|
152
|
+
evented: false,
|
|
153
|
+
excludeFromExport: true,
|
|
154
|
+
objectCaching: false,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
specs.push({
|
|
158
|
+
id: "image.cropShapeOutline",
|
|
159
|
+
type: "path",
|
|
160
|
+
space: "screen",
|
|
161
|
+
data: { id: "image.cropShapeOutline", zIndex: 6 },
|
|
162
|
+
props: {
|
|
163
|
+
pathData: shapeOverlay.shapePathData,
|
|
164
|
+
left: cutRect.left,
|
|
165
|
+
top: cutRect.top,
|
|
166
|
+
originX: "left",
|
|
167
|
+
originY: "top",
|
|
168
|
+
fill: "transparent",
|
|
169
|
+
stroke: SHAPE_OUTLINE_COLOR,
|
|
170
|
+
strokeWidth: 1,
|
|
171
|
+
selectable: false,
|
|
172
|
+
evented: false,
|
|
173
|
+
excludeFromExport: true,
|
|
174
|
+
objectCaching: false,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
specs.push({
|
|
180
|
+
id: "image.cropFrame",
|
|
181
|
+
type: "rect",
|
|
182
|
+
space: "screen",
|
|
183
|
+
data: { id: "image.cropFrame", zIndex: 7 },
|
|
184
|
+
props: {
|
|
185
|
+
left: cutRect.left,
|
|
186
|
+
top: cutRect.top,
|
|
187
|
+
width: cutRect.width,
|
|
188
|
+
height: cutRect.height,
|
|
189
|
+
originX: "left",
|
|
190
|
+
originY: "top",
|
|
191
|
+
fill: visual.innerBackground,
|
|
192
|
+
stroke:
|
|
193
|
+
visual.strokeStyle === "hidden" ? "rgba(0,0,0,0)" : visual.strokeColor,
|
|
194
|
+
strokeWidth: visual.strokeStyle === "hidden" ? 0 : visual.strokeWidth,
|
|
195
|
+
strokeDashArray:
|
|
196
|
+
visual.strokeStyle === "dashed"
|
|
197
|
+
? [visual.dashLength, visual.dashLength]
|
|
198
|
+
: undefined,
|
|
199
|
+
selectable: false,
|
|
200
|
+
evented: false,
|
|
201
|
+
excludeFromExport: true,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return specs;
|
|
206
|
+
}
|
|
@@ -20,11 +20,12 @@ const MIN_ARROW_SIZE = 4;
|
|
|
20
20
|
const THICKNESS_TO_STROKE_WIDTH_RATIO = 20;
|
|
21
21
|
|
|
22
22
|
const DEFAULT_THICKNESS = 20;
|
|
23
|
-
const DEFAULT_GAP =
|
|
23
|
+
const DEFAULT_GAP = 65;
|
|
24
24
|
const DEFAULT_FONT_SIZE = 10;
|
|
25
25
|
const DEFAULT_BACKGROUND_COLOR = "#f0f0f0";
|
|
26
26
|
const DEFAULT_TEXT_COLOR = "#333333";
|
|
27
27
|
const DEFAULT_LINE_COLOR = "#999999";
|
|
28
|
+
const RULER_DEBUG_KEY = "ruler.debug";
|
|
28
29
|
|
|
29
30
|
const RULER_THICKNESS_MIN = 10;
|
|
30
31
|
const RULER_THICKNESS_MAX = 100;
|
|
@@ -48,6 +49,7 @@ export class RulerTool implements Extension {
|
|
|
48
49
|
private textColor = DEFAULT_TEXT_COLOR;
|
|
49
50
|
private lineColor = DEFAULT_LINE_COLOR;
|
|
50
51
|
private fontSize = DEFAULT_FONT_SIZE;
|
|
52
|
+
private debugEnabled = false;
|
|
51
53
|
private renderSeq = 0;
|
|
52
54
|
private readonly numericProps = new Set(["thickness", "gap", "fontSize"]);
|
|
53
55
|
private specs: RenderObjectSpec[] = [];
|
|
@@ -112,7 +114,14 @@ export class RulerTool implements Extension {
|
|
|
112
114
|
this.syncConfig(configService);
|
|
113
115
|
configService.onAnyChange((e: { key: string; value: any }) => {
|
|
114
116
|
let shouldUpdate = false;
|
|
115
|
-
if (e.key
|
|
117
|
+
if (e.key === RULER_DEBUG_KEY) {
|
|
118
|
+
this.debugEnabled = e.value === true;
|
|
119
|
+
this.log("config:update", {
|
|
120
|
+
key: e.key,
|
|
121
|
+
raw: e.value,
|
|
122
|
+
normalized: this.debugEnabled,
|
|
123
|
+
});
|
|
124
|
+
} else if (e.key.startsWith("ruler.")) {
|
|
116
125
|
const prop = e.key.split(".")[1];
|
|
117
126
|
if (prop && prop in this) {
|
|
118
127
|
if (this.numericProps.has(prop)) {
|
|
@@ -203,6 +212,12 @@ export class RulerTool implements Extension {
|
|
|
203
212
|
max: RULER_FONT_SIZE_MAX,
|
|
204
213
|
default: DEFAULT_FONT_SIZE,
|
|
205
214
|
},
|
|
215
|
+
{
|
|
216
|
+
id: RULER_DEBUG_KEY,
|
|
217
|
+
type: "boolean",
|
|
218
|
+
label: "Ruler Debug Log",
|
|
219
|
+
default: false,
|
|
220
|
+
},
|
|
206
221
|
] as ConfigurationContribution[],
|
|
207
222
|
[ContributionPointIds.COMMANDS]: [
|
|
208
223
|
{
|
|
@@ -249,7 +264,12 @@ export class RulerTool implements Extension {
|
|
|
249
264
|
};
|
|
250
265
|
}
|
|
251
266
|
|
|
267
|
+
private isDebugEnabled(): boolean {
|
|
268
|
+
return this.debugEnabled;
|
|
269
|
+
}
|
|
270
|
+
|
|
252
271
|
private log(step: string, payload?: Record<string, unknown>) {
|
|
272
|
+
if (!this.isDebugEnabled()) return;
|
|
253
273
|
if (payload) {
|
|
254
274
|
console.debug(`[RulerTool] ${step}`, payload);
|
|
255
275
|
return;
|
|
@@ -279,6 +299,8 @@ export class RulerTool implements Extension {
|
|
|
279
299
|
configService.get("ruler.fontSize", this.fontSize),
|
|
280
300
|
DEFAULT_FONT_SIZE,
|
|
281
301
|
);
|
|
302
|
+
this.debugEnabled =
|
|
303
|
+
configService.get(RULER_DEBUG_KEY, this.debugEnabled) === true;
|
|
282
304
|
|
|
283
305
|
this.log("config:loaded", {
|
|
284
306
|
thickness: this.thickness,
|
package/tests/run.ts
CHANGED
|
@@ -21,6 +21,10 @@ import { createWhiteInkCommands } from "../src/extensions/white-ink/commands";
|
|
|
21
21
|
import { createWhiteInkConfigurations } from "../src/extensions/white-ink/config";
|
|
22
22
|
import { createDielineCommands } from "../src/extensions/dieline/commands";
|
|
23
23
|
import { createDielineConfigurations } from "../src/extensions/dieline/config";
|
|
24
|
+
import {
|
|
25
|
+
normalizePointInGeometry,
|
|
26
|
+
resolveFeaturePosition,
|
|
27
|
+
} from "../src/extensions/featureCoordinates";
|
|
24
28
|
|
|
25
29
|
function assert(condition: unknown, message: string) {
|
|
26
30
|
if (!condition) throw new Error(message);
|
|
@@ -120,6 +124,38 @@ function testEdgeScale() {
|
|
|
120
124
|
assert(height === 80, `expected height 80, got ${height}`);
|
|
121
125
|
}
|
|
122
126
|
|
|
127
|
+
function testFeaturePlacementProjection() {
|
|
128
|
+
const trimGeometry = {
|
|
129
|
+
x: 100,
|
|
130
|
+
y: 120,
|
|
131
|
+
width: 120,
|
|
132
|
+
height: 180,
|
|
133
|
+
};
|
|
134
|
+
const cutGeometry = {
|
|
135
|
+
x: 100,
|
|
136
|
+
y: 120,
|
|
137
|
+
width: 150,
|
|
138
|
+
height: 210,
|
|
139
|
+
};
|
|
140
|
+
const trimFeature = {
|
|
141
|
+
x: 0.82,
|
|
142
|
+
y: 0.68,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const trimCenter = resolveFeaturePosition(trimFeature, trimGeometry);
|
|
146
|
+
const cutFeature = normalizePointInGeometry(trimCenter, cutGeometry);
|
|
147
|
+
const cutCenter = resolveFeaturePosition(cutFeature, cutGeometry);
|
|
148
|
+
|
|
149
|
+
assert(
|
|
150
|
+
Math.abs(trimCenter.x - cutCenter.x) < 1e-6,
|
|
151
|
+
`expected projected feature x to stay fixed, got ${trimCenter.x} vs ${cutCenter.x}`,
|
|
152
|
+
);
|
|
153
|
+
assert(
|
|
154
|
+
Math.abs(trimCenter.y - cutCenter.y) < 1e-6,
|
|
155
|
+
`expected projected feature y to stay fixed, got ${trimCenter.y} vs ${cutCenter.y}`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
123
159
|
function testVisibilityDsl() {
|
|
124
160
|
const layers = new Map([
|
|
125
161
|
["ruler-overlay", { exists: true, objectCount: 2 }],
|
|
@@ -396,6 +432,7 @@ function main() {
|
|
|
396
432
|
testBridgeSelection();
|
|
397
433
|
testMaskOps();
|
|
398
434
|
testEdgeScale();
|
|
435
|
+
testFeaturePlacementProjection();
|
|
399
436
|
testVisibilityDsl();
|
|
400
437
|
testContributionCompatibility();
|
|
401
438
|
console.log("ok");
|