@pooder/kit 6.1.2 → 6.2.1
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/BackgroundTool.js +177 -5
- package/.test-dist/src/extensions/constraintUtils.js +44 -0
- package/.test-dist/src/extensions/dieline/DielineTool.js +52 -409
- package/.test-dist/src/extensions/dieline/featureResolution.js +29 -0
- package/.test-dist/src/extensions/dieline/model.js +83 -0
- package/.test-dist/src/extensions/dieline/renderBuilder.js +227 -0
- package/.test-dist/src/extensions/feature/FeatureTool.js +156 -45
- 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 +281 -25
- package/.test-dist/src/extensions/ruler/RulerTool.js +24 -1
- package/.test-dist/src/shared/constants/layers.js +3 -1
- package/.test-dist/tests/run.js +25 -0
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +47 -13
- package/dist/index.d.ts +47 -13
- package/dist/index.js +1325 -977
- package/dist/index.mjs +1311 -966
- package/package.json +1 -1
- package/src/extensions/background/BackgroundTool.ts +264 -4
- package/src/extensions/dieline/DielineTool.ts +67 -548
- package/src/extensions/dieline/model.ts +165 -1
- package/src/extensions/dieline/renderBuilder.ts +301 -0
- package/src/extensions/feature/FeatureTool.ts +190 -47
- package/src/extensions/featureCoordinates.ts +35 -0
- package/src/extensions/featurePlacement.ts +118 -0
- package/src/extensions/image/ImageTool.ts +139 -157
- package/src/extensions/ruler/RulerTool.ts +24 -2
- package/src/shared/constants/layers.ts +2 -0
- package/tests/run.ts +37 -0
|
@@ -6,17 +6,31 @@ import {
|
|
|
6
6
|
ConfigurationService,
|
|
7
7
|
ToolSessionService,
|
|
8
8
|
} from "@pooder/core";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { Pattern } from "fabric";
|
|
10
|
+
import {
|
|
11
|
+
CanvasService,
|
|
12
|
+
RenderEffectSpec,
|
|
13
|
+
RenderObjectSpec,
|
|
14
|
+
RenderPassSpec,
|
|
15
|
+
} from "../../services";
|
|
11
16
|
import { ConstraintRegistry, ConstraintFeature } from "../constraints";
|
|
12
17
|
import { completeFeaturesStrict } from "../featureComplete";
|
|
18
|
+
import { resolveFeaturePlacements } from "../featurePlacement";
|
|
13
19
|
import {
|
|
20
|
+
computeSceneLayout,
|
|
14
21
|
readSizeState,
|
|
15
22
|
type SceneGeometrySnapshot as DielineGeometry,
|
|
16
23
|
} from "../../shared/scene/sceneLayoutModel";
|
|
17
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
DIELINE_LAYER_ID,
|
|
26
|
+
FEATURE_DIELINE_LAYER_ID,
|
|
27
|
+
FEATURE_OVERLAY_LAYER_ID,
|
|
28
|
+
IMAGE_OBJECT_LAYER_ID,
|
|
29
|
+
} from "../../shared/constants/layers";
|
|
18
30
|
import { SubscriptionBag } from "../../shared/runtime/subscriptions";
|
|
19
31
|
import { cloneWithJson } from "../../shared/runtime/sessionState";
|
|
32
|
+
import { buildDielineRenderBundle } from "../dieline/renderBuilder";
|
|
33
|
+
import { readDielineState } from "../dieline/model";
|
|
20
34
|
const FEATURE_STROKE_WIDTH = 2;
|
|
21
35
|
const DEFAULT_RECT_SIZE = 10;
|
|
22
36
|
const DEFAULT_CIRCLE_RADIUS = 5;
|
|
@@ -68,7 +82,9 @@ export class FeatureTool implements Extension {
|
|
|
68
82
|
private hasWorkingChanges = false;
|
|
69
83
|
private dirtyTrackerDisposable?: { dispose(): void };
|
|
70
84
|
private renderProducerDisposable?: { dispose: () => void };
|
|
71
|
-
private
|
|
85
|
+
private markerSpecs: RenderObjectSpec[] = [];
|
|
86
|
+
private sessionDielineSpecs: RenderObjectSpec[] = [];
|
|
87
|
+
private sessionDielineEffects: RenderEffectSpec[] = [];
|
|
72
88
|
private renderSeq = 0;
|
|
73
89
|
private readonly subscriptions = new SubscriptionBag();
|
|
74
90
|
|
|
@@ -103,16 +119,38 @@ export class FeatureTool implements Extension {
|
|
|
103
119
|
this.renderProducerDisposable?.dispose();
|
|
104
120
|
this.renderProducerDisposable = this.canvasService.registerRenderProducer(
|
|
105
121
|
this.id,
|
|
106
|
-
() =>
|
|
107
|
-
passes: [
|
|
122
|
+
() => {
|
|
123
|
+
const passes: RenderPassSpec[] = [
|
|
108
124
|
{
|
|
109
125
|
id: FEATURE_OVERLAY_LAYER_ID,
|
|
110
126
|
stack: 880,
|
|
111
127
|
order: 0,
|
|
112
|
-
|
|
128
|
+
replace: true,
|
|
129
|
+
objects: this.markerSpecs,
|
|
113
130
|
},
|
|
114
|
-
]
|
|
115
|
-
|
|
131
|
+
];
|
|
132
|
+
if (this.isSessionVisible()) {
|
|
133
|
+
passes.push(
|
|
134
|
+
{
|
|
135
|
+
id: DIELINE_LAYER_ID,
|
|
136
|
+
stack: 700,
|
|
137
|
+
order: 0,
|
|
138
|
+
replace: false,
|
|
139
|
+
visibility: { op: "const", value: false },
|
|
140
|
+
objects: [],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: FEATURE_DIELINE_LAYER_ID,
|
|
144
|
+
stack: 705,
|
|
145
|
+
order: 0,
|
|
146
|
+
replace: true,
|
|
147
|
+
effects: this.sessionDielineEffects,
|
|
148
|
+
objects: this.sessionDielineSpecs,
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return { passes };
|
|
153
|
+
},
|
|
116
154
|
{ priority: 350 },
|
|
117
155
|
);
|
|
118
156
|
|
|
@@ -131,12 +169,23 @@ export class FeatureTool implements Extension {
|
|
|
131
169
|
if (this.isUpdatingConfig) return;
|
|
132
170
|
|
|
133
171
|
if (e.key === "dieline.features") {
|
|
134
|
-
if (this.isFeatureSessionActive)
|
|
172
|
+
if (this.isFeatureSessionActive && this.hasFeatureSessionDraft()) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (this.hasFeatureSessionDraft()) {
|
|
176
|
+
this.clearFeatureSessionState();
|
|
177
|
+
}
|
|
135
178
|
const next = (e.value || []) as ConstraintFeature[];
|
|
136
179
|
this.workingFeatures = this.cloneFeatures(next);
|
|
137
180
|
this.hasWorkingChanges = false;
|
|
138
181
|
this.redraw();
|
|
139
182
|
this.emitWorkingChange();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (e.key.startsWith("size.") || e.key.startsWith("dieline.")) {
|
|
187
|
+
void this.refreshGeometry();
|
|
188
|
+
this.redraw({ enforceConstraints: true });
|
|
140
189
|
}
|
|
141
190
|
},
|
|
142
191
|
);
|
|
@@ -156,7 +205,8 @@ export class FeatureTool implements Extension {
|
|
|
156
205
|
|
|
157
206
|
deactivate(context: ExtensionContext) {
|
|
158
207
|
this.subscriptions.disposeAll();
|
|
159
|
-
this.
|
|
208
|
+
this.restoreCommittedFeaturesToConfig();
|
|
209
|
+
this.clearFeatureSessionState();
|
|
160
210
|
this.dirtyTrackerDisposable?.dispose();
|
|
161
211
|
this.dirtyTrackerDisposable = undefined;
|
|
162
212
|
this.teardown();
|
|
@@ -167,7 +217,7 @@ export class FeatureTool implements Extension {
|
|
|
167
217
|
private onToolActivated = (event: { id: string | null }) => {
|
|
168
218
|
this.isToolActive = event.id === this.id;
|
|
169
219
|
if (!this.isToolActive) {
|
|
170
|
-
this.
|
|
220
|
+
this.suspendFeatureSession();
|
|
171
221
|
}
|
|
172
222
|
this.updateVisibility();
|
|
173
223
|
};
|
|
@@ -176,6 +226,10 @@ export class FeatureTool implements Extension {
|
|
|
176
226
|
this.redraw();
|
|
177
227
|
}
|
|
178
228
|
|
|
229
|
+
private isSessionVisible(): boolean {
|
|
230
|
+
return this.isToolActive && this.isFeatureSessionActive;
|
|
231
|
+
}
|
|
232
|
+
|
|
179
233
|
contribute() {
|
|
180
234
|
return {
|
|
181
235
|
[ContributionPointIds.TOOLS]: [
|
|
@@ -202,15 +256,16 @@ export class FeatureTool implements Extension {
|
|
|
202
256
|
if (this.isFeatureSessionActive) {
|
|
203
257
|
return { ok: true };
|
|
204
258
|
}
|
|
205
|
-
|
|
206
|
-
|
|
259
|
+
if (!this.hasFeatureSessionDraft()) {
|
|
260
|
+
const original = this.getCommittedFeatures();
|
|
261
|
+
this.sessionOriginalFeatures = this.cloneFeatures(original);
|
|
262
|
+
this.setWorkingFeatures(this.cloneFeatures(original));
|
|
263
|
+
this.hasWorkingChanges = false;
|
|
264
|
+
}
|
|
207
265
|
this.isFeatureSessionActive = true;
|
|
208
266
|
await this.refreshGeometry();
|
|
209
|
-
this.setWorkingFeatures(this.cloneFeatures(original));
|
|
210
|
-
this.hasWorkingChanges = false;
|
|
211
267
|
this.redraw();
|
|
212
268
|
this.emitWorkingChange();
|
|
213
|
-
this.updateCommittedFeatures([]);
|
|
214
269
|
return { ok: true };
|
|
215
270
|
},
|
|
216
271
|
},
|
|
@@ -246,25 +301,6 @@ export class FeatureTool implements Extension {
|
|
|
246
301
|
return true;
|
|
247
302
|
},
|
|
248
303
|
},
|
|
249
|
-
{
|
|
250
|
-
command: "getWorkingFeatures",
|
|
251
|
-
title: "Get Working Features",
|
|
252
|
-
handler: () => {
|
|
253
|
-
return this.cloneFeatures(this.workingFeatures);
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
command: "setWorkingFeatures",
|
|
258
|
-
title: "Set Working Features",
|
|
259
|
-
handler: async (features: ConstraintFeature[]) => {
|
|
260
|
-
await this.refreshGeometry();
|
|
261
|
-
this.setWorkingFeatures(this.cloneFeatures(features || []));
|
|
262
|
-
this.hasWorkingChanges = true;
|
|
263
|
-
this.redraw();
|
|
264
|
-
this.emitWorkingChange();
|
|
265
|
-
return { ok: true };
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
304
|
{
|
|
269
305
|
command: "rollbackFeatureSession",
|
|
270
306
|
title: "Rollback Feature Session",
|
|
@@ -336,18 +372,27 @@ export class FeatureTool implements Extension {
|
|
|
336
372
|
}
|
|
337
373
|
}
|
|
338
374
|
|
|
375
|
+
private hasFeatureSessionDraft(): boolean {
|
|
376
|
+
return Array.isArray(this.sessionOriginalFeatures);
|
|
377
|
+
}
|
|
378
|
+
|
|
339
379
|
private clearFeatureSessionState() {
|
|
340
380
|
this.isFeatureSessionActive = false;
|
|
341
381
|
this.sessionOriginalFeatures = null;
|
|
342
382
|
}
|
|
343
383
|
|
|
344
|
-
private
|
|
345
|
-
if (!this.
|
|
384
|
+
private restoreCommittedFeaturesToConfig() {
|
|
385
|
+
if (!this.hasFeatureSessionDraft()) return;
|
|
346
386
|
const original = this.cloneFeatures(
|
|
347
387
|
this.sessionOriginalFeatures || this.getCommittedFeatures(),
|
|
348
388
|
);
|
|
349
389
|
this.updateCommittedFeatures(original);
|
|
350
|
-
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private suspendFeatureSession() {
|
|
393
|
+
if (!this.isFeatureSessionActive) return;
|
|
394
|
+
this.restoreCommittedFeaturesToConfig();
|
|
395
|
+
this.isFeatureSessionActive = false;
|
|
351
396
|
}
|
|
352
397
|
|
|
353
398
|
private emitWorkingChange() {
|
|
@@ -370,9 +415,7 @@ export class FeatureTool implements Extension {
|
|
|
370
415
|
|
|
371
416
|
private async resetWorkingFeaturesFromSource() {
|
|
372
417
|
const next = this.cloneFeatures(
|
|
373
|
-
this.
|
|
374
|
-
? this.sessionOriginalFeatures
|
|
375
|
-
: this.getCommittedFeatures(),
|
|
418
|
+
this.sessionOriginalFeatures || this.getCommittedFeatures(),
|
|
376
419
|
);
|
|
377
420
|
await this.refreshGeometry();
|
|
378
421
|
this.setWorkingFeatures(next);
|
|
@@ -636,12 +679,37 @@ export class FeatureTool implements Extension {
|
|
|
636
679
|
}
|
|
637
680
|
|
|
638
681
|
this.renderSeq += 1;
|
|
639
|
-
this.
|
|
682
|
+
this.markerSpecs = [];
|
|
683
|
+
this.sessionDielineSpecs = [];
|
|
684
|
+
this.sessionDielineEffects = [];
|
|
640
685
|
this.renderProducerDisposable?.dispose();
|
|
641
686
|
this.renderProducerDisposable = undefined;
|
|
642
687
|
void this.canvasService.flushRenderFromProducers();
|
|
643
688
|
}
|
|
644
689
|
|
|
690
|
+
private createHatchPattern(color: string = "rgba(0, 0, 0, 0.3)") {
|
|
691
|
+
if (typeof document === "undefined") {
|
|
692
|
+
return undefined;
|
|
693
|
+
}
|
|
694
|
+
const size = 20;
|
|
695
|
+
const canvas = document.createElement("canvas");
|
|
696
|
+
canvas.width = size;
|
|
697
|
+
canvas.height = size;
|
|
698
|
+
const ctx = canvas.getContext("2d");
|
|
699
|
+
if (ctx) {
|
|
700
|
+
ctx.clearRect(0, 0, size, size);
|
|
701
|
+
ctx.strokeStyle = color;
|
|
702
|
+
ctx.lineWidth = 1;
|
|
703
|
+
ctx.beginPath();
|
|
704
|
+
ctx.moveTo(0, size);
|
|
705
|
+
ctx.lineTo(size, 0);
|
|
706
|
+
ctx.stroke();
|
|
707
|
+
}
|
|
708
|
+
return new Pattern({
|
|
709
|
+
source: canvas,
|
|
710
|
+
} as any);
|
|
711
|
+
}
|
|
712
|
+
|
|
645
713
|
private getDraggableMarkerTarget(target: any): any | null {
|
|
646
714
|
if (!this.isFeatureSessionActive || !this.isToolActive) return null;
|
|
647
715
|
if (!target || target.data?.type !== "feature-marker") return null;
|
|
@@ -737,6 +805,7 @@ export class FeatureTool implements Extension {
|
|
|
737
805
|
next[index] = updatedFeature;
|
|
738
806
|
this.setWorkingFeatures(next);
|
|
739
807
|
this.hasWorkingChanges = true;
|
|
808
|
+
this.redraw();
|
|
740
809
|
this.emitWorkingChange();
|
|
741
810
|
}
|
|
742
811
|
|
|
@@ -780,6 +849,7 @@ export class FeatureTool implements Extension {
|
|
|
780
849
|
if (!changed) return;
|
|
781
850
|
this.setWorkingFeatures(next);
|
|
782
851
|
this.hasWorkingChanges = true;
|
|
852
|
+
this.redraw();
|
|
783
853
|
this.emitWorkingChange();
|
|
784
854
|
}
|
|
785
855
|
|
|
@@ -791,7 +861,10 @@ export class FeatureTool implements Extension {
|
|
|
791
861
|
if (!this.canvasService) return;
|
|
792
862
|
|
|
793
863
|
const seq = ++this.renderSeq;
|
|
794
|
-
this.
|
|
864
|
+
this.markerSpecs = this.buildMarkerSpecs();
|
|
865
|
+
const sessionRender = this.buildSessionDielineRender();
|
|
866
|
+
this.sessionDielineSpecs = sessionRender.specs;
|
|
867
|
+
this.sessionDielineEffects = sessionRender.effects;
|
|
795
868
|
if (seq !== this.renderSeq) return;
|
|
796
869
|
|
|
797
870
|
await this.canvasService.flushRenderFromProducers();
|
|
@@ -801,7 +874,57 @@ export class FeatureTool implements Extension {
|
|
|
801
874
|
}
|
|
802
875
|
}
|
|
803
876
|
|
|
804
|
-
private
|
|
877
|
+
private buildSessionDielineRender(): {
|
|
878
|
+
specs: RenderObjectSpec[];
|
|
879
|
+
effects: RenderEffectSpec[];
|
|
880
|
+
} {
|
|
881
|
+
if (!this.isSessionVisible() || !this.canvasService) {
|
|
882
|
+
return { specs: [], effects: [] };
|
|
883
|
+
}
|
|
884
|
+
const configService = this.getConfigService();
|
|
885
|
+
if (!configService) {
|
|
886
|
+
return { specs: [], effects: [] };
|
|
887
|
+
}
|
|
888
|
+
const sceneLayout = computeSceneLayout(
|
|
889
|
+
this.canvasService,
|
|
890
|
+
readSizeState(configService),
|
|
891
|
+
);
|
|
892
|
+
if (!sceneLayout) {
|
|
893
|
+
return { specs: [], effects: [] };
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const state = readDielineState(configService);
|
|
897
|
+
state.features = this.cloneFeatures(this.workingFeatures);
|
|
898
|
+
|
|
899
|
+
return buildDielineRenderBundle({
|
|
900
|
+
state,
|
|
901
|
+
sceneLayout,
|
|
902
|
+
canvasWidth: sceneLayout.canvasWidth || this.canvasService.canvas.width || 800,
|
|
903
|
+
canvasHeight:
|
|
904
|
+
sceneLayout.canvasHeight || this.canvasService.canvas.height || 600,
|
|
905
|
+
hasImages: this.hasImageItems(),
|
|
906
|
+
createHatchPattern: (color) => this.createHatchPattern(color),
|
|
907
|
+
clipTargetPassIds: [IMAGE_OBJECT_LAYER_ID],
|
|
908
|
+
clipVisibility: { op: "const", value: true },
|
|
909
|
+
ids: {
|
|
910
|
+
inside: "feature.session.dieline.inside",
|
|
911
|
+
bleedZone: "feature.session.dieline.bleed-zone",
|
|
912
|
+
offsetBorder: "feature.session.dieline.offset-border",
|
|
913
|
+
border: "feature.session.dieline.border",
|
|
914
|
+
clip: "feature.session.dieline.clip.image",
|
|
915
|
+
clipSource: "feature.session.dieline.effect.clip-path",
|
|
916
|
+
},
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
private hasImageItems(): boolean {
|
|
921
|
+
const configService = this.getConfigService();
|
|
922
|
+
if (!configService) return false;
|
|
923
|
+
const items = configService.get("image.items", []) as unknown;
|
|
924
|
+
return Array.isArray(items) && items.length > 0;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
private buildMarkerSpecs(): RenderObjectSpec[] {
|
|
805
928
|
if (
|
|
806
929
|
!this.isFeatureSessionActive ||
|
|
807
930
|
!this.currentGeometry ||
|
|
@@ -812,10 +935,30 @@ export class FeatureTool implements Extension {
|
|
|
812
935
|
|
|
813
936
|
const groups = new Map<string, MarkerRenderState[]>();
|
|
814
937
|
const singles: MarkerRenderState[] = [];
|
|
938
|
+
const placements = resolveFeaturePlacements(
|
|
939
|
+
this.workingFeatures,
|
|
940
|
+
{
|
|
941
|
+
shape: this.currentGeometry.shape,
|
|
942
|
+
shapeStyle: this.currentGeometry.shapeStyle,
|
|
943
|
+
pathData: this.currentGeometry.pathData,
|
|
944
|
+
customSourceWidthPx: this.currentGeometry.customSourceWidthPx,
|
|
945
|
+
customSourceHeightPx: this.currentGeometry.customSourceHeightPx,
|
|
946
|
+
x: this.currentGeometry.x,
|
|
947
|
+
y: this.currentGeometry.y,
|
|
948
|
+
width: this.currentGeometry.width,
|
|
949
|
+
height: this.currentGeometry.height,
|
|
950
|
+
radius: this.currentGeometry.radius,
|
|
951
|
+
scale: this.currentGeometry.scale || 1,
|
|
952
|
+
},
|
|
953
|
+
);
|
|
815
954
|
|
|
816
|
-
|
|
955
|
+
placements.forEach((placement, index) => {
|
|
956
|
+
const feature = placement.feature;
|
|
817
957
|
const geometry = this.getGeometryForFeature(this.currentGeometry!, feature);
|
|
818
|
-
const position =
|
|
958
|
+
const position = {
|
|
959
|
+
x: placement.centerX,
|
|
960
|
+
y: placement.centerY,
|
|
961
|
+
};
|
|
819
962
|
const scale = geometry.scale || 1;
|
|
820
963
|
const marker: MarkerRenderState = {
|
|
821
964
|
feature,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DielineFeature } from "./geometry";
|
|
2
|
+
|
|
3
|
+
export interface FeatureCoordinateGeometry {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function resolveFeaturePosition(
|
|
11
|
+
feature: Pick<DielineFeature, "x" | "y">,
|
|
12
|
+
geometry: FeatureCoordinateGeometry,
|
|
13
|
+
): { x: number; y: number } {
|
|
14
|
+
const { x, y, width, height } = geometry;
|
|
15
|
+
const left = x - width / 2;
|
|
16
|
+
const top = y - height / 2;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
x: left + feature.x * width,
|
|
20
|
+
y: top + feature.y * height,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function normalizePointInGeometry(
|
|
25
|
+
point: { x: number; y: number },
|
|
26
|
+
geometry: FeatureCoordinateGeometry,
|
|
27
|
+
): { x: number; y: number } {
|
|
28
|
+
const left = geometry.x - geometry.width / 2;
|
|
29
|
+
const top = geometry.y - geometry.height / 2;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
x: geometry.width > 0 ? (point.x - left) / geometry.width : 0.5,
|
|
33
|
+
y: geometry.height > 0 ? (point.y - top) / geometry.height : 0.5,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { ConstraintFeature, ConstraintRegistry } from "./constraints";
|
|
2
|
+
import type { GeometryOptions } from "./geometry";
|
|
3
|
+
import {
|
|
4
|
+
normalizePointInGeometry,
|
|
5
|
+
resolveFeaturePosition,
|
|
6
|
+
} from "./featureCoordinates";
|
|
7
|
+
|
|
8
|
+
export interface FeaturePlacementGeometry
|
|
9
|
+
extends Pick<
|
|
10
|
+
GeometryOptions,
|
|
11
|
+
| "shape"
|
|
12
|
+
| "shapeStyle"
|
|
13
|
+
| "pathData"
|
|
14
|
+
| "customSourceWidthPx"
|
|
15
|
+
| "customSourceHeightPx"
|
|
16
|
+
| "canvasWidth"
|
|
17
|
+
| "canvasHeight"
|
|
18
|
+
> {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
radius: number;
|
|
24
|
+
scale: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface FeatureProjectionGeometry {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FeaturePlacement<TFeature extends ConstraintFeature = ConstraintFeature> {
|
|
35
|
+
feature: TFeature;
|
|
36
|
+
normalizedX: number;
|
|
37
|
+
normalizedY: number;
|
|
38
|
+
centerX: number;
|
|
39
|
+
centerY: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function scaleFeatureForRender<TFeature extends ConstraintFeature>(
|
|
43
|
+
feature: TFeature,
|
|
44
|
+
scale: number,
|
|
45
|
+
x: number,
|
|
46
|
+
y: number,
|
|
47
|
+
): TFeature {
|
|
48
|
+
return {
|
|
49
|
+
...feature,
|
|
50
|
+
x,
|
|
51
|
+
y,
|
|
52
|
+
width: feature.width !== undefined ? feature.width * scale : undefined,
|
|
53
|
+
height: feature.height !== undefined ? feature.height * scale : undefined,
|
|
54
|
+
radius: feature.radius !== undefined ? feature.radius * scale : undefined,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function resolveFeaturePlacements<TFeature extends ConstraintFeature>(
|
|
59
|
+
features: TFeature[],
|
|
60
|
+
geometry: FeaturePlacementGeometry,
|
|
61
|
+
): FeaturePlacement<TFeature>[] {
|
|
62
|
+
const dielineWidth =
|
|
63
|
+
geometry.scale > 0 ? geometry.width / geometry.scale : geometry.width;
|
|
64
|
+
const dielineHeight =
|
|
65
|
+
geometry.scale > 0 ? geometry.height / geometry.scale : geometry.height;
|
|
66
|
+
|
|
67
|
+
return (features || []).map((feature) => {
|
|
68
|
+
const activeConstraints = feature.constraints?.filter(
|
|
69
|
+
(constraint) => !constraint.validateOnly,
|
|
70
|
+
);
|
|
71
|
+
const constrained = ConstraintRegistry.apply(
|
|
72
|
+
feature.x,
|
|
73
|
+
feature.y,
|
|
74
|
+
feature,
|
|
75
|
+
{
|
|
76
|
+
dielineWidth,
|
|
77
|
+
dielineHeight,
|
|
78
|
+
geometry,
|
|
79
|
+
},
|
|
80
|
+
activeConstraints,
|
|
81
|
+
);
|
|
82
|
+
const center = resolveFeaturePosition(
|
|
83
|
+
{
|
|
84
|
+
...feature,
|
|
85
|
+
x: constrained.x,
|
|
86
|
+
y: constrained.y,
|
|
87
|
+
},
|
|
88
|
+
geometry,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
feature,
|
|
93
|
+
normalizedX: constrained.x,
|
|
94
|
+
normalizedY: constrained.y,
|
|
95
|
+
centerX: center.x,
|
|
96
|
+
centerY: center.y,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function projectPlacedFeatures<TFeature extends ConstraintFeature>(
|
|
102
|
+
placements: FeaturePlacement<TFeature>[],
|
|
103
|
+
geometry: FeatureProjectionGeometry,
|
|
104
|
+
scale: number,
|
|
105
|
+
): TFeature[] {
|
|
106
|
+
return placements.map((placement) => {
|
|
107
|
+
const normalized = normalizePointInGeometry(
|
|
108
|
+
{ x: placement.centerX, y: placement.centerY },
|
|
109
|
+
geometry,
|
|
110
|
+
);
|
|
111
|
+
return scaleFeatureForRender(
|
|
112
|
+
placement.feature,
|
|
113
|
+
scale,
|
|
114
|
+
normalized.x,
|
|
115
|
+
normalized.y,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
}
|