@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
|
@@ -3,16 +3,178 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.BackgroundTool = void 0;
|
|
4
4
|
const core_1 = require("@pooder/core");
|
|
5
5
|
const fabric_1 = require("fabric");
|
|
6
|
+
const sceneLayoutModel_1 = require("./sceneLayoutModel");
|
|
7
|
+
const BACKGROUND_LAYER_ID = "background";
|
|
8
|
+
const BACKGROUND_CONFIG_KEY = "background.config";
|
|
9
|
+
const DEFAULT_WIDTH = 800;
|
|
10
|
+
const DEFAULT_HEIGHT = 600;
|
|
11
|
+
const DEFAULT_BACKGROUND_CONFIG = {
|
|
12
|
+
version: 1,
|
|
13
|
+
layers: [
|
|
14
|
+
{
|
|
15
|
+
id: "base-color",
|
|
16
|
+
kind: "color",
|
|
17
|
+
anchor: "viewport",
|
|
18
|
+
fit: "cover",
|
|
19
|
+
opacity: 1,
|
|
20
|
+
order: 0,
|
|
21
|
+
enabled: true,
|
|
22
|
+
exportable: false,
|
|
23
|
+
color: "#aaa",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
function clampOpacity(value, fallback) {
|
|
28
|
+
const numeric = Number(value);
|
|
29
|
+
if (!Number.isFinite(numeric)) {
|
|
30
|
+
return Math.max(0, Math.min(1, fallback));
|
|
31
|
+
}
|
|
32
|
+
return Math.max(0, Math.min(1, numeric));
|
|
33
|
+
}
|
|
34
|
+
function normalizeLayerKind(value, fallback) {
|
|
35
|
+
if (value === "color" || value === "image") {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
function normalizeFitMode(value, fallback) {
|
|
41
|
+
if (value === "contain" || value === "cover" || value === "stretch") {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
function normalizeAnchor(value, fallback) {
|
|
47
|
+
if (typeof value !== "string")
|
|
48
|
+
return fallback;
|
|
49
|
+
const trimmed = value.trim();
|
|
50
|
+
return trimmed || fallback;
|
|
51
|
+
}
|
|
52
|
+
function normalizeOrder(value, fallback) {
|
|
53
|
+
const numeric = Number(value);
|
|
54
|
+
if (!Number.isFinite(numeric))
|
|
55
|
+
return fallback;
|
|
56
|
+
return numeric;
|
|
57
|
+
}
|
|
58
|
+
function normalizeLayer(raw, index, fallback) {
|
|
59
|
+
const fallbackLayer = fallback || {
|
|
60
|
+
id: `layer-${index + 1}`,
|
|
61
|
+
kind: "image",
|
|
62
|
+
anchor: "viewport",
|
|
63
|
+
fit: "contain",
|
|
64
|
+
opacity: 1,
|
|
65
|
+
order: index,
|
|
66
|
+
enabled: true,
|
|
67
|
+
exportable: false,
|
|
68
|
+
src: "",
|
|
69
|
+
};
|
|
70
|
+
if (!raw || typeof raw !== "object") {
|
|
71
|
+
return { ...fallbackLayer };
|
|
72
|
+
}
|
|
73
|
+
const input = raw;
|
|
74
|
+
const kind = normalizeLayerKind(input.kind, fallbackLayer.kind);
|
|
75
|
+
return {
|
|
76
|
+
id: typeof input.id === "string" && input.id.trim().length > 0
|
|
77
|
+
? input.id.trim()
|
|
78
|
+
: fallbackLayer.id,
|
|
79
|
+
kind,
|
|
80
|
+
anchor: normalizeAnchor(input.anchor, fallbackLayer.anchor),
|
|
81
|
+
fit: normalizeFitMode(input.fit, fallbackLayer.fit),
|
|
82
|
+
opacity: clampOpacity(input.opacity, fallbackLayer.opacity),
|
|
83
|
+
order: normalizeOrder(input.order, fallbackLayer.order),
|
|
84
|
+
enabled: typeof input.enabled === "boolean"
|
|
85
|
+
? input.enabled
|
|
86
|
+
: fallbackLayer.enabled,
|
|
87
|
+
exportable: typeof input.exportable === "boolean"
|
|
88
|
+
? input.exportable
|
|
89
|
+
: fallbackLayer.exportable,
|
|
90
|
+
color: kind === "color"
|
|
91
|
+
? typeof input.color === "string"
|
|
92
|
+
? input.color
|
|
93
|
+
: typeof fallbackLayer.color === "string"
|
|
94
|
+
? fallbackLayer.color
|
|
95
|
+
: "#ffffff"
|
|
96
|
+
: undefined,
|
|
97
|
+
src: kind === "image"
|
|
98
|
+
? typeof input.src === "string"
|
|
99
|
+
? input.src.trim()
|
|
100
|
+
: typeof fallbackLayer.src === "string"
|
|
101
|
+
? fallbackLayer.src
|
|
102
|
+
: ""
|
|
103
|
+
: undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function normalizeConfig(raw) {
|
|
107
|
+
if (!raw || typeof raw !== "object") {
|
|
108
|
+
return cloneConfig(DEFAULT_BACKGROUND_CONFIG);
|
|
109
|
+
}
|
|
110
|
+
const input = raw;
|
|
111
|
+
const version = Number.isFinite(Number(input.version))
|
|
112
|
+
? Number(input.version)
|
|
113
|
+
: DEFAULT_BACKGROUND_CONFIG.version;
|
|
114
|
+
const baseLayers = Array.isArray(input.layers)
|
|
115
|
+
? input.layers.map((layer, index) => normalizeLayer(layer, index))
|
|
116
|
+
: cloneConfig(DEFAULT_BACKGROUND_CONFIG).layers;
|
|
117
|
+
const uniqueLayers = [];
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
baseLayers.forEach((layer, index) => {
|
|
120
|
+
let nextId = layer.id || `layer-${index + 1}`;
|
|
121
|
+
let serial = 1;
|
|
122
|
+
while (seen.has(nextId)) {
|
|
123
|
+
serial += 1;
|
|
124
|
+
nextId = `${layer.id || `layer-${index + 1}`}-${serial}`;
|
|
125
|
+
}
|
|
126
|
+
seen.add(nextId);
|
|
127
|
+
uniqueLayers.push({ ...layer, id: nextId });
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
version,
|
|
131
|
+
layers: uniqueLayers,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function cloneConfig(config) {
|
|
135
|
+
return {
|
|
136
|
+
version: config.version,
|
|
137
|
+
layers: (config.layers || []).map((layer) => ({ ...layer })),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function mergeConfig(base, patch) {
|
|
141
|
+
const merged = {
|
|
142
|
+
version: patch.version === undefined
|
|
143
|
+
? base.version
|
|
144
|
+
: Number.isFinite(Number(patch.version))
|
|
145
|
+
? Number(patch.version)
|
|
146
|
+
: base.version,
|
|
147
|
+
layers: Array.isArray(patch.layers)
|
|
148
|
+
? patch.layers.map((layer, index) => normalizeLayer(layer, index))
|
|
149
|
+
: base.layers.map((layer) => ({ ...layer })),
|
|
150
|
+
};
|
|
151
|
+
return normalizeConfig(merged);
|
|
152
|
+
}
|
|
153
|
+
function configSignature(config) {
|
|
154
|
+
return JSON.stringify(config);
|
|
155
|
+
}
|
|
6
156
|
class BackgroundTool {
|
|
7
157
|
constructor(options) {
|
|
8
158
|
this.id = "pooder.kit.background";
|
|
9
159
|
this.metadata = {
|
|
10
160
|
name: "BackgroundTool",
|
|
11
161
|
};
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
-
|
|
162
|
+
this.config = cloneConfig(DEFAULT_BACKGROUND_CONFIG);
|
|
163
|
+
this.specs = [];
|
|
164
|
+
this.renderSeq = 0;
|
|
165
|
+
this.latestSceneLayout = null;
|
|
166
|
+
this.sourceSizeBySrc = new Map();
|
|
167
|
+
this.pendingSizeBySrc = new Map();
|
|
168
|
+
this.onCanvasResized = () => {
|
|
169
|
+
this.latestSceneLayout = null;
|
|
170
|
+
this.updateBackground();
|
|
171
|
+
};
|
|
172
|
+
this.onSceneLayoutChanged = (layout) => {
|
|
173
|
+
this.latestSceneLayout = layout;
|
|
174
|
+
this.updateBackground();
|
|
175
|
+
};
|
|
176
|
+
if (options && typeof options === "object") {
|
|
177
|
+
this.config = mergeConfig(this.config, options);
|
|
16
178
|
}
|
|
17
179
|
}
|
|
18
180
|
activate(context) {
|
|
@@ -21,182 +183,364 @@ class BackgroundTool {
|
|
|
21
183
|
console.warn("CanvasService not found for BackgroundTool");
|
|
22
184
|
return;
|
|
23
185
|
}
|
|
24
|
-
|
|
25
|
-
if (configService) {
|
|
26
|
-
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.updateBackground();
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
console.warn(`[BackgroundTool] Property ${prop} not found in options`);
|
|
41
|
-
}
|
|
186
|
+
this.configService = context.services.get("ConfigurationService");
|
|
187
|
+
if (this.configService) {
|
|
188
|
+
this.config = normalizeConfig(this.configService.get(BACKGROUND_CONFIG_KEY, DEFAULT_BACKGROUND_CONFIG));
|
|
189
|
+
this.configChangeDisposable?.dispose();
|
|
190
|
+
this.configChangeDisposable = this.configService.onAnyChange((e) => {
|
|
191
|
+
if (e.key === BACKGROUND_CONFIG_KEY) {
|
|
192
|
+
this.config = normalizeConfig(e.value);
|
|
193
|
+
this.updateBackground();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (e.key.startsWith("size.")) {
|
|
197
|
+
this.latestSceneLayout = null;
|
|
198
|
+
this.updateBackground();
|
|
42
199
|
}
|
|
43
200
|
});
|
|
44
201
|
}
|
|
45
|
-
this.
|
|
202
|
+
this.renderProducerDisposable?.dispose();
|
|
203
|
+
this.renderProducerDisposable = this.canvasService.registerRenderProducer(this.id, () => ({
|
|
204
|
+
passes: [
|
|
205
|
+
{
|
|
206
|
+
id: BACKGROUND_LAYER_ID,
|
|
207
|
+
stack: 0,
|
|
208
|
+
order: 0,
|
|
209
|
+
objects: this.specs,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
}), { priority: 0 });
|
|
213
|
+
context.eventBus.on("canvas:resized", this.onCanvasResized);
|
|
214
|
+
context.eventBus.on("scene:layout:change", this.onSceneLayoutChanged);
|
|
46
215
|
this.updateBackground();
|
|
47
216
|
}
|
|
48
217
|
deactivate(context) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
218
|
+
context.eventBus.off("canvas:resized", this.onCanvasResized);
|
|
219
|
+
context.eventBus.off("scene:layout:change", this.onSceneLayoutChanged);
|
|
220
|
+
this.renderSeq += 1;
|
|
221
|
+
this.specs = [];
|
|
222
|
+
this.latestSceneLayout = null;
|
|
223
|
+
this.configChangeDisposable?.dispose();
|
|
224
|
+
this.configChangeDisposable = undefined;
|
|
225
|
+
this.renderProducerDisposable?.dispose();
|
|
226
|
+
this.renderProducerDisposable = undefined;
|
|
227
|
+
if (!this.canvasService)
|
|
228
|
+
return;
|
|
229
|
+
void this.canvasService.flushRenderFromProducers();
|
|
230
|
+
this.canvasService.requestRenderAll();
|
|
231
|
+
this.canvasService = undefined;
|
|
232
|
+
this.configService = undefined;
|
|
56
233
|
}
|
|
57
234
|
contribute() {
|
|
58
235
|
return {
|
|
59
236
|
[core_1.ContributionPointIds.CONFIGURATIONS]: [
|
|
60
237
|
{
|
|
61
|
-
id:
|
|
62
|
-
type: "
|
|
63
|
-
label: "Background
|
|
64
|
-
default:
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: "background.url",
|
|
68
|
-
type: "string",
|
|
69
|
-
label: "Image URL",
|
|
70
|
-
default: "",
|
|
238
|
+
id: BACKGROUND_CONFIG_KEY,
|
|
239
|
+
type: "json",
|
|
240
|
+
label: "Background Config",
|
|
241
|
+
default: cloneConfig(DEFAULT_BACKGROUND_CONFIG),
|
|
71
242
|
},
|
|
72
243
|
],
|
|
73
244
|
[core_1.ContributionPointIds.COMMANDS]: [
|
|
74
245
|
{
|
|
75
|
-
command: "
|
|
76
|
-
title: "
|
|
246
|
+
command: "background.getConfig",
|
|
247
|
+
title: "Get Background Config",
|
|
248
|
+
handler: () => cloneConfig(this.config),
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
command: "background.resetConfig",
|
|
252
|
+
title: "Reset Background Config",
|
|
77
253
|
handler: () => {
|
|
78
|
-
this.
|
|
254
|
+
this.commitConfig(cloneConfig(DEFAULT_BACKGROUND_CONFIG));
|
|
79
255
|
return true;
|
|
80
256
|
},
|
|
81
257
|
},
|
|
82
258
|
{
|
|
83
|
-
command: "
|
|
84
|
-
title: "
|
|
85
|
-
handler: () => {
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
259
|
+
command: "background.replaceConfig",
|
|
260
|
+
title: "Replace Background Config",
|
|
261
|
+
handler: (config) => {
|
|
262
|
+
this.commitConfig(normalizeConfig(config));
|
|
263
|
+
return true;
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
command: "background.patchConfig",
|
|
268
|
+
title: "Patch Background Config",
|
|
269
|
+
handler: (patch) => {
|
|
270
|
+
this.commitConfig(mergeConfig(this.config, patch || {}));
|
|
89
271
|
return true;
|
|
90
272
|
},
|
|
91
273
|
},
|
|
92
274
|
{
|
|
93
|
-
command: "
|
|
94
|
-
title: "
|
|
95
|
-
handler: (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
275
|
+
command: "background.upsertLayer",
|
|
276
|
+
title: "Upsert Background Layer",
|
|
277
|
+
handler: (layer) => {
|
|
278
|
+
const normalized = normalizeLayer(layer, 0);
|
|
279
|
+
const existingIndex = this.config.layers.findIndex((item) => item.id === normalized.id);
|
|
280
|
+
const nextLayers = [...this.config.layers];
|
|
281
|
+
if (existingIndex >= 0) {
|
|
282
|
+
nextLayers[existingIndex] = normalizeLayer({ ...nextLayers[existingIndex], ...layer }, existingIndex, nextLayers[existingIndex]);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
nextLayers.push(normalizeLayer({
|
|
286
|
+
...normalized,
|
|
287
|
+
order: Number.isFinite(Number(layer.order))
|
|
288
|
+
? Number(layer.order)
|
|
289
|
+
: nextLayers.length,
|
|
290
|
+
}, nextLayers.length));
|
|
291
|
+
}
|
|
292
|
+
this.commitConfig(normalizeConfig({
|
|
293
|
+
...this.config,
|
|
294
|
+
layers: nextLayers,
|
|
295
|
+
}));
|
|
100
296
|
return true;
|
|
101
297
|
},
|
|
102
298
|
},
|
|
103
299
|
{
|
|
104
|
-
command: "
|
|
105
|
-
title: "
|
|
106
|
-
handler: (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
300
|
+
command: "background.removeLayer",
|
|
301
|
+
title: "Remove Background Layer",
|
|
302
|
+
handler: (id) => {
|
|
303
|
+
const nextLayers = this.config.layers.filter((layer) => layer.id !== id);
|
|
304
|
+
this.commitConfig(normalizeConfig({
|
|
305
|
+
...this.config,
|
|
306
|
+
layers: nextLayers,
|
|
307
|
+
}));
|
|
111
308
|
return true;
|
|
112
309
|
},
|
|
113
310
|
},
|
|
114
311
|
],
|
|
115
312
|
};
|
|
116
313
|
}
|
|
117
|
-
|
|
118
|
-
|
|
314
|
+
commitConfig(next) {
|
|
315
|
+
const normalized = normalizeConfig(next);
|
|
316
|
+
if (configSignature(normalized) === configSignature(this.config)) {
|
|
119
317
|
return;
|
|
120
|
-
let backgroundLayer = this.canvasService.getLayer("background");
|
|
121
|
-
if (!backgroundLayer) {
|
|
122
|
-
backgroundLayer = this.canvasService.createLayer("background", {
|
|
123
|
-
width: this.canvasService.canvas.width,
|
|
124
|
-
height: this.canvasService.canvas.height,
|
|
125
|
-
selectable: false,
|
|
126
|
-
evented: false,
|
|
127
|
-
});
|
|
128
|
-
this.canvasService.canvas.sendObjectToBack(backgroundLayer);
|
|
129
318
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (!this.canvasService)
|
|
133
|
-
return;
|
|
134
|
-
const layer = this.canvasService.getLayer("background");
|
|
135
|
-
if (!layer) {
|
|
136
|
-
console.warn("[BackgroundTool] Background layer not found");
|
|
319
|
+
if (this.configService) {
|
|
320
|
+
this.configService.update(BACKGROUND_CONFIG_KEY, cloneConfig(normalized));
|
|
137
321
|
return;
|
|
138
322
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
323
|
+
this.config = normalized;
|
|
324
|
+
this.updateBackground();
|
|
325
|
+
}
|
|
326
|
+
getViewportRect() {
|
|
327
|
+
const width = Number(this.canvasService?.canvas.width || 0);
|
|
328
|
+
const height = Number(this.canvasService?.canvas.height || 0);
|
|
329
|
+
return {
|
|
330
|
+
left: 0,
|
|
331
|
+
top: 0,
|
|
332
|
+
width: width > 0 ? width : DEFAULT_WIDTH,
|
|
333
|
+
height: height > 0 ? height : DEFAULT_HEIGHT,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
resolveSceneLayout() {
|
|
337
|
+
if (this.latestSceneLayout)
|
|
338
|
+
return this.latestSceneLayout;
|
|
339
|
+
if (!this.canvasService || !this.configService)
|
|
340
|
+
return null;
|
|
341
|
+
const layout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService, (0, sceneLayoutModel_1.readSizeState)(this.configService));
|
|
342
|
+
this.latestSceneLayout = layout;
|
|
343
|
+
return layout;
|
|
344
|
+
}
|
|
345
|
+
resolveFocusRect() {
|
|
346
|
+
const layout = this.resolveSceneLayout();
|
|
347
|
+
if (!layout)
|
|
348
|
+
return null;
|
|
349
|
+
return {
|
|
350
|
+
left: layout.trimRect.left,
|
|
351
|
+
top: layout.trimRect.top,
|
|
352
|
+
width: layout.trimRect.width,
|
|
353
|
+
height: layout.trimRect.height,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
resolveAnchorRect(anchor) {
|
|
357
|
+
if (anchor === "focus") {
|
|
358
|
+
return this.resolveFocusRect() || this.getViewportRect();
|
|
359
|
+
}
|
|
360
|
+
if (anchor !== "viewport") {
|
|
361
|
+
return this.getViewportRect();
|
|
147
362
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
363
|
+
return this.getViewportRect();
|
|
364
|
+
}
|
|
365
|
+
resolveImagePlacement(target, sourceSize, fit) {
|
|
366
|
+
const targetWidth = Math.max(1, Number(target.width || 0));
|
|
367
|
+
const targetHeight = Math.max(1, Number(target.height || 0));
|
|
368
|
+
const sourceWidth = Math.max(1, Number(sourceSize.width || 0));
|
|
369
|
+
const sourceHeight = Math.max(1, Number(sourceSize.height || 0));
|
|
370
|
+
if (fit === "stretch") {
|
|
371
|
+
return {
|
|
372
|
+
left: target.left,
|
|
373
|
+
top: target.top,
|
|
374
|
+
scaleX: targetWidth / sourceWidth,
|
|
375
|
+
scaleY: targetHeight / sourceHeight,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const scale = fit === "contain"
|
|
379
|
+
? Math.min(targetWidth / sourceWidth, targetHeight / sourceHeight)
|
|
380
|
+
: Math.max(targetWidth / sourceWidth, targetHeight / sourceHeight);
|
|
381
|
+
const renderWidth = sourceWidth * scale;
|
|
382
|
+
const renderHeight = sourceHeight * scale;
|
|
383
|
+
return {
|
|
384
|
+
left: target.left + (targetWidth - renderWidth) / 2,
|
|
385
|
+
top: target.top + (targetHeight - renderHeight) / 2,
|
|
386
|
+
scaleX: scale,
|
|
387
|
+
scaleY: scale,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
buildColorLayerSpec(layer) {
|
|
391
|
+
const rect = this.resolveAnchorRect(layer.anchor);
|
|
392
|
+
return {
|
|
393
|
+
id: `background.layer.${layer.id}.color`,
|
|
394
|
+
type: "rect",
|
|
395
|
+
space: "screen",
|
|
396
|
+
data: {
|
|
397
|
+
id: `background.layer.${layer.id}.color`,
|
|
398
|
+
layerId: BACKGROUND_LAYER_ID,
|
|
399
|
+
type: "background-layer",
|
|
400
|
+
layerRef: layer.id,
|
|
401
|
+
layerKind: layer.kind,
|
|
402
|
+
},
|
|
403
|
+
props: {
|
|
404
|
+
left: rect.left,
|
|
405
|
+
top: rect.top,
|
|
406
|
+
width: rect.width,
|
|
407
|
+
height: rect.height,
|
|
408
|
+
originX: "left",
|
|
409
|
+
originY: "top",
|
|
410
|
+
fill: layer.color || "transparent",
|
|
411
|
+
opacity: layer.opacity,
|
|
153
412
|
selectable: false,
|
|
154
413
|
evented: false,
|
|
414
|
+
excludeFromExport: !layer.exportable,
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
buildImageLayerSpec(layer) {
|
|
419
|
+
const src = String(layer.src || "").trim();
|
|
420
|
+
if (!src)
|
|
421
|
+
return [];
|
|
422
|
+
const sourceSize = this.sourceSizeBySrc.get(src);
|
|
423
|
+
if (!sourceSize)
|
|
424
|
+
return [];
|
|
425
|
+
const rect = this.resolveAnchorRect(layer.anchor);
|
|
426
|
+
const placement = this.resolveImagePlacement(rect, sourceSize, layer.fit);
|
|
427
|
+
return [
|
|
428
|
+
{
|
|
429
|
+
id: `background.layer.${layer.id}.image`,
|
|
430
|
+
type: "image",
|
|
431
|
+
src,
|
|
432
|
+
space: "screen",
|
|
155
433
|
data: {
|
|
156
|
-
id:
|
|
434
|
+
id: `background.layer.${layer.id}.image`,
|
|
435
|
+
layerId: BACKGROUND_LAYER_ID,
|
|
436
|
+
type: "background-layer",
|
|
437
|
+
layerRef: layer.id,
|
|
438
|
+
layerKind: layer.kind,
|
|
157
439
|
},
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
440
|
+
props: {
|
|
441
|
+
left: placement.left,
|
|
442
|
+
top: placement.top,
|
|
443
|
+
originX: "left",
|
|
444
|
+
originY: "top",
|
|
445
|
+
scaleX: placement.scaleX,
|
|
446
|
+
scaleY: placement.scaleY,
|
|
447
|
+
opacity: layer.opacity,
|
|
448
|
+
selectable: false,
|
|
449
|
+
evented: false,
|
|
450
|
+
excludeFromExport: !layer.exportable,
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
];
|
|
454
|
+
}
|
|
455
|
+
buildBackgroundSpecs(config) {
|
|
456
|
+
const activeLayers = (config.layers || [])
|
|
457
|
+
.filter((layer) => layer.enabled)
|
|
458
|
+
.map((layer, index) => ({ layer, index }))
|
|
459
|
+
.sort((a, b) => {
|
|
460
|
+
if (a.layer.order !== b.layer.order) {
|
|
461
|
+
return a.layer.order - b.layer.order;
|
|
462
|
+
}
|
|
463
|
+
return a.index - b.index;
|
|
464
|
+
});
|
|
465
|
+
const specs = [];
|
|
466
|
+
activeLayers.forEach(({ layer }) => {
|
|
467
|
+
if (layer.kind === "color") {
|
|
468
|
+
specs.push(this.buildColorLayerSpec(layer));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
specs.push(...this.buildImageLayerSpec(layer));
|
|
472
|
+
});
|
|
473
|
+
return specs;
|
|
474
|
+
}
|
|
475
|
+
collectActiveImageUrls(config) {
|
|
476
|
+
const urls = new Set();
|
|
477
|
+
(config.layers || []).forEach((layer) => {
|
|
478
|
+
if (!layer.enabled || layer.kind !== "image")
|
|
479
|
+
return;
|
|
480
|
+
const src = String(layer.src || "").trim();
|
|
481
|
+
if (!src)
|
|
482
|
+
return;
|
|
483
|
+
urls.add(src);
|
|
484
|
+
});
|
|
485
|
+
return Array.from(urls);
|
|
486
|
+
}
|
|
487
|
+
async ensureImageSize(src) {
|
|
488
|
+
if (!src)
|
|
489
|
+
return null;
|
|
490
|
+
const cached = this.sourceSizeBySrc.get(src);
|
|
491
|
+
if (cached)
|
|
492
|
+
return cached;
|
|
493
|
+
const pending = this.pendingSizeBySrc.get(src);
|
|
494
|
+
if (pending) {
|
|
495
|
+
return pending;
|
|
161
496
|
}
|
|
162
|
-
|
|
497
|
+
const task = this.loadImageSize(src);
|
|
498
|
+
this.pendingSizeBySrc.set(src, task);
|
|
163
499
|
try {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
else {
|
|
170
|
-
layer.remove(img);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
500
|
+
return await task;
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
if (this.pendingSizeBySrc.get(src) === task) {
|
|
504
|
+
this.pendingSizeBySrc.delete(src);
|
|
173
505
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
});
|
|
188
|
-
img.scaleToWidth(width);
|
|
189
|
-
if (img.getScaledHeight() < height)
|
|
190
|
-
img.scaleToHeight(height);
|
|
191
|
-
layer.add(img);
|
|
192
|
-
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async loadImageSize(src) {
|
|
509
|
+
try {
|
|
510
|
+
const image = await fabric_1.FabricImage.fromURL(src, {
|
|
511
|
+
crossOrigin: "anonymous",
|
|
512
|
+
});
|
|
513
|
+
const width = Number(image?.width || 0);
|
|
514
|
+
const height = Number(image?.height || 0);
|
|
515
|
+
if (width > 0 && height > 0) {
|
|
516
|
+
const size = { width, height };
|
|
517
|
+
this.sourceSizeBySrc.set(src, size);
|
|
518
|
+
return size;
|
|
193
519
|
}
|
|
194
|
-
this.canvasService.requestRenderAll();
|
|
195
520
|
}
|
|
196
|
-
catch (
|
|
197
|
-
console.error("[BackgroundTool] Failed to load image",
|
|
521
|
+
catch (error) {
|
|
522
|
+
console.error("[BackgroundTool] Failed to load image", src, error);
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
updateBackground() {
|
|
527
|
+
void this.updateBackgroundAsync();
|
|
528
|
+
}
|
|
529
|
+
async updateBackgroundAsync() {
|
|
530
|
+
if (!this.canvasService)
|
|
531
|
+
return;
|
|
532
|
+
const seq = ++this.renderSeq;
|
|
533
|
+
const currentConfig = cloneConfig(this.config);
|
|
534
|
+
const activeUrls = this.collectActiveImageUrls(currentConfig);
|
|
535
|
+
if (activeUrls.length > 0) {
|
|
536
|
+
await Promise.all(activeUrls.map((url) => this.ensureImageSize(url)));
|
|
537
|
+
if (seq !== this.renderSeq)
|
|
538
|
+
return;
|
|
198
539
|
}
|
|
199
|
-
|
|
540
|
+
this.specs = this.buildBackgroundSpecs(currentConfig);
|
|
541
|
+
await this.canvasService.flushRenderFromProducers();
|
|
542
|
+
if (seq !== this.renderSeq)
|
|
543
|
+
return;
|
|
200
544
|
this.canvasService.requestRenderAll();
|
|
201
545
|
}
|
|
202
546
|
}
|