@coolms/image-editor-angular 2.0.0-alpha.2 → 2.0.0-alpha.4
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/ATTRIBUTION.md +43 -0
- package/CHANGELOG.md +120 -0
- package/README.md +36 -12
- package/fesm2022/coolms-image-editor-angular.mjs +4882 -4914
- package/fesm2022/coolms-image-editor-angular.mjs.map +1 -1
- package/package.json +7 -4
- package/types/coolms-image-editor-angular.d.ts +1338 -1191
|
@@ -1,38 +1,36 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { AfterViewInit, OnDestroy, OnInit } from
|
|
3
|
-
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import { AfterViewInit, InjectionToken, OnDestroy, OnInit } from "@angular/core";
|
|
4
3
|
/**
|
|
5
4
|
* Pure geometry primitives shared across the engine and any UI shell
|
|
6
5
|
* sitting on top. Deliberately framework-agnostic: no Angular, no
|
|
7
|
-
* fabric.js, no Toast UI imports
|
|
6
|
+
* fabric.js, no Toast UI imports -- these types are part of the public
|
|
8
7
|
* surface of `@coolms/image-editor` and need to compile without the
|
|
9
8
|
* peer dependency installed.
|
|
10
9
|
*/
|
|
11
10
|
interface Point {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
readonly x: number;
|
|
12
|
+
readonly y: number;
|
|
14
13
|
}
|
|
15
14
|
interface Size {
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
readonly width: number;
|
|
16
|
+
readonly height: number;
|
|
18
17
|
}
|
|
19
18
|
/**
|
|
20
19
|
* Axis-aligned rectangle. The engine's `crop(rect)` consumes this shape
|
|
21
20
|
* directly; UI shells produce one from their cropzone overlay.
|
|
22
21
|
*/
|
|
23
22
|
interface Rect {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
readonly x: number;
|
|
24
|
+
readonly y: number;
|
|
25
|
+
readonly width: number;
|
|
26
|
+
readonly height: number;
|
|
28
27
|
}
|
|
29
28
|
interface Bounds {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
readonly minWidth: number;
|
|
30
|
+
readonly minHeight: number;
|
|
31
|
+
readonly maxWidth: number;
|
|
32
|
+
readonly maxHeight: number;
|
|
34
33
|
}
|
|
35
|
-
|
|
36
34
|
/**
|
|
37
35
|
* Filter contract for the engine.
|
|
38
36
|
*
|
|
@@ -57,14 +55,13 @@ type FilterName = 'brightness' | 'contrast' | 'saturation' | 'sepia' | 'grayscal
|
|
|
57
55
|
* `applyFilter(name)` and `removeFilter(name)`).
|
|
58
56
|
*/
|
|
59
57
|
interface FilterParams {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
readonly brightness?: number;
|
|
59
|
+
readonly contrast?: number;
|
|
60
|
+
readonly saturation?: number;
|
|
61
|
+
readonly blur?: number;
|
|
62
|
+
readonly noise?: number;
|
|
63
|
+
readonly pixelate?: number;
|
|
66
64
|
}
|
|
67
|
-
|
|
68
65
|
/**
|
|
69
66
|
* Object / layer model for the image editor (Track C.1).
|
|
70
67
|
*
|
|
@@ -74,11 +71,11 @@ interface FilterParams {
|
|
|
74
71
|
* layer stacked on top. Fabric's canvas is already an ordered object
|
|
75
72
|
* stack, so a "layer" is just a canvas object carrying a stable id.
|
|
76
73
|
*
|
|
77
|
-
* Resize / filters still operate on the base image only
|
|
74
|
+
* Resize / filters still operate on the base image only -- they're
|
|
78
75
|
* pixel-baking ops, meant to run before annotations. C.1c makes
|
|
79
76
|
* **rotate / flip** target the selected non-image layer (falling back
|
|
80
77
|
* to the base image) and adds **masking** (a clip-path on the target;
|
|
81
|
-
* see {@link MaskSpec}); C.1e makes **crop** composition-aware
|
|
78
|
+
* see {@link MaskSpec}); C.1e makes **crop** composition-aware -- the
|
|
82
79
|
* annotation layers shift with the cropped content and clip at the new
|
|
83
80
|
* edges, so a crop after annotating stays aligned.
|
|
84
81
|
*/
|
|
@@ -87,51 +84,51 @@ type LayerKind = 'image' | 'shape' | 'text';
|
|
|
87
84
|
/** The shape primitives the Shapes tool can insert. */
|
|
88
85
|
type ShapeKind = 'rect' | 'ellipse' | 'line' | 'arrow' | 'triangle';
|
|
89
86
|
/**
|
|
90
|
-
* Canvas-space geometry of an annotation shape
|
|
87
|
+
* Canvas-space geometry of an annotation shape -- the round-trippable
|
|
91
88
|
* subset consumers that author geometry (e.g. the ImageMap region
|
|
92
89
|
* page) read and write via `addShapeAt()` / `getShapeGeometry()`.
|
|
93
90
|
* Coordinates are SCENE space (viewport zoom/pan factored out), the
|
|
94
91
|
* same space `getImageBounds()` reports in.
|
|
95
92
|
*/
|
|
96
93
|
type ShapeGeometry = {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
kind: 'rect';
|
|
95
|
+
rect: {
|
|
96
|
+
x: number;
|
|
97
|
+
y: number;
|
|
98
|
+
width: number;
|
|
99
|
+
height: number;
|
|
100
|
+
};
|
|
104
101
|
} | {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
kind: 'ellipse';
|
|
103
|
+
center: {
|
|
104
|
+
x: number;
|
|
105
|
+
y: number;
|
|
106
|
+
};
|
|
107
|
+
rx: number;
|
|
108
|
+
ry: number;
|
|
112
109
|
} | {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
kind: 'polygon';
|
|
111
|
+
points: {
|
|
112
|
+
x: number;
|
|
113
|
+
y: number;
|
|
114
|
+
}[];
|
|
118
115
|
};
|
|
119
116
|
/**
|
|
120
117
|
* Options for `addShapeAt()`. Style keys mirror {@link ShapeOptions};
|
|
121
118
|
* the extras tune the shape for geometry-authoring use:
|
|
122
119
|
* - `name`: layer display name (defaults to the shape kind).
|
|
123
|
-
* - `lockRotation`: hide the rotate handle
|
|
120
|
+
* - `lockRotation`: hide the rotate handle -- authored geometry is
|
|
124
121
|
* axis-aligned (rect regions have no rotation representation).
|
|
125
122
|
* - `lockNonUniformScaling`: hide the edge handles so corner drags
|
|
126
123
|
* scale uniformly (keeps a circle circular).
|
|
127
124
|
*/
|
|
128
125
|
interface AnnotationShapeOptions {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
fill?: string;
|
|
127
|
+
stroke?: string;
|
|
128
|
+
strokeWidth?: number;
|
|
129
|
+
name?: string;
|
|
130
|
+
lockRotation?: boolean;
|
|
131
|
+
lockNonUniformScaling?: boolean;
|
|
135
132
|
}
|
|
136
133
|
/**
|
|
137
134
|
* How a shape / text layer is filled (C.1b).
|
|
@@ -149,10 +146,10 @@ type FillType = 'solid' | 'linear' | 'radial';
|
|
|
149
146
|
* (0 = left->right, 90 = top->bottom) and only meaningful for `linear`.
|
|
150
147
|
*/
|
|
151
148
|
interface FillSpec {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
149
|
+
type: FillType;
|
|
150
|
+
color: string;
|
|
151
|
+
color2: string;
|
|
152
|
+
angle: number;
|
|
156
153
|
}
|
|
157
154
|
/**
|
|
158
155
|
* A read-only view of one layer, surfaced to the Layers panel. The
|
|
@@ -160,57 +157,63 @@ interface FillSpec {
|
|
|
160
157
|
* the UI binds to (never a live fabric handle).
|
|
161
158
|
*/
|
|
162
159
|
interface LayerInfo {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
160
|
+
readonly id: string;
|
|
161
|
+
readonly kind: LayerKind;
|
|
162
|
+
readonly name: string;
|
|
163
|
+
readonly visible: boolean;
|
|
164
|
+
/** 0..1. */
|
|
165
|
+
readonly opacity: number;
|
|
166
|
+
/**
|
|
167
|
+
* True for the base image: it can't be deleted or reordered below
|
|
168
|
+
* itself (it's the canvas floor). Shapes / text are never locked.
|
|
169
|
+
*/
|
|
170
|
+
readonly locked: boolean;
|
|
174
171
|
}
|
|
175
172
|
/** Style options for an inserted shape. */
|
|
176
173
|
interface ShapeOptions {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
174
|
+
fill: string;
|
|
175
|
+
stroke: string;
|
|
176
|
+
strokeWidth: number;
|
|
177
|
+
/**
|
|
178
|
+
* Richer fill description (solid colour or gradient). When present
|
|
179
|
+
* it takes precedence over the flat `fill` string. The Shapes tool
|
|
180
|
+
* always sends this; `fill` stays a plain-colour fallback for any
|
|
181
|
+
* caller that doesn't. (C.1b)
|
|
182
|
+
*/
|
|
183
|
+
fillSpec?: FillSpec;
|
|
187
184
|
}
|
|
188
185
|
/** Style options for inserted text. */
|
|
189
186
|
interface TextOptions {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
187
|
+
text: string;
|
|
188
|
+
fontFamily: string;
|
|
189
|
+
fontSize: number;
|
|
190
|
+
fill: string;
|
|
191
|
+
fontWeight: 'normal' | 'bold';
|
|
192
|
+
fontStyle: 'normal' | 'italic';
|
|
193
|
+
textAlign: 'left' | 'center' | 'right';
|
|
194
|
+
/** Gradient-capable fill; see {@link ShapeOptions.fillSpec}. (C.1b) */
|
|
195
|
+
fillSpec?: FillSpec;
|
|
199
196
|
}
|
|
200
197
|
/**
|
|
201
198
|
* Generic property bag the style panels read/write against the active
|
|
202
199
|
* object. Keys are fabric property names (`fill`, `stroke`,
|
|
203
|
-
* `strokeWidth`, `opacity`, `fontFamily`,
|
|
200
|
+
* `strokeWidth`, `opacity`, `fontFamily`, ...); the engine reads them off
|
|
204
201
|
* / sets them on the active object without the UI importing fabric.
|
|
205
202
|
*/
|
|
206
203
|
type ObjectProps = Record<string, string | number>;
|
|
204
|
+
declare const DEFAULT_SHAPE_OPTIONS: ShapeOptions;
|
|
205
|
+
/** Starting fill for the Shapes / Text tools (solid, blue accent). (C.1b) */
|
|
206
|
+
declare const DEFAULT_FILL_SPEC: FillSpec;
|
|
207
|
+
declare const DEFAULT_TEXT_OPTIONS: TextOptions;
|
|
208
|
+
/** Font families offered in the Text tool (web-safe set). */
|
|
209
|
+
declare const TEXT_FONT_FAMILIES: readonly ["Arial", "Helvetica", "Times New Roman", "Georgia", "Courier New", "Verdana", "Trebuchet MS", "Impact"];
|
|
207
210
|
/**
|
|
208
211
|
* Mask / clip shapes a layer can be cut to (C.1c / C.1d).
|
|
209
212
|
*
|
|
210
213
|
* `none` clears the mask. `rect` / `rounded` / `ellipse` / `triangle`
|
|
211
214
|
* are geometric primitives; `star` / `heart` / `hexagon` are SVG-path
|
|
212
215
|
* presets. `custom` (C.1d) clips to arbitrary path geometry the user
|
|
213
|
-
* pasted or uploaded as an SVG
|
|
216
|
+
* pasted or uploaded as an SVG -- see {@link MaskSpec.svgPath}. The
|
|
214
217
|
* engine builds the matching fabric `clipPath` against the target's own
|
|
215
218
|
* box, so the mask tracks the object through scale / move, and the
|
|
216
219
|
* export flatten bakes it in (the clip is part of the object's normal
|
|
@@ -227,101 +230,98 @@ type MaskShape = 'none' | 'rect' | 'rounded' | 'ellipse' | 'triangle' | 'star' |
|
|
|
227
230
|
* the clip's shorter half-dimension (50 = pill / circle).
|
|
228
231
|
* - `svgPath` : SVG path `d` data for `shape: 'custom'` (C.1d). Sanitised
|
|
229
232
|
* geometry extracted from a pasted path string or uploaded
|
|
230
|
-
* SVG file
|
|
233
|
+
* SVG file -- every drawable element is flattened to a single
|
|
231
234
|
* combined path (paths only, no script / external refs). The
|
|
232
235
|
* engine scales its bounding box to the clip box exactly
|
|
233
236
|
* like the built-in path presets, so it tracks scale / move
|
|
234
237
|
* and round-trips through the `coolmsMask` stamp.
|
|
235
238
|
*/
|
|
236
239
|
interface MaskSpec {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
shape: MaskShape;
|
|
241
|
+
inset: number;
|
|
242
|
+
radius: number;
|
|
243
|
+
svgPath?: string;
|
|
241
244
|
}
|
|
242
|
-
|
|
245
|
+
/** Starting mask for the Mask tool (no clip). (C.1c) */
|
|
246
|
+
declare const DEFAULT_MASK_SPEC: MaskSpec;
|
|
243
247
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* The adapter translates engine-internal events (Toast UI's
|
|
247
|
-
* `objectActivated`, `undoStackChanged`, etc.) into this stable map
|
|
248
|
-
* so the UI shell never imports vendor types.
|
|
248
|
+
* SVG path data for the path-preset masks, normalised to a 0..100 box.
|
|
249
|
+
* The engine scales each to the target's clip box. (C.1c)
|
|
249
250
|
*/
|
|
250
|
-
|
|
251
|
+
declare const MASK_PATHS: Readonly<Record<'star' | 'heart' | 'hexagon', string>>;
|
|
251
252
|
interface EngineEvents {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
253
|
+
/** Fires after `loadImage()` resolves. Carries the loaded dimensions. */
|
|
254
|
+
readonly imageLoaded: {
|
|
255
|
+
readonly width: number;
|
|
256
|
+
readonly height: number;
|
|
257
|
+
};
|
|
258
|
+
/** Any operation that mutates the canvas (filter, crop, rotate, etc.). */
|
|
259
|
+
readonly imageChanged: void;
|
|
260
|
+
/** History stacks updated. Use `canUndo`/`canRedo` for current depth. */
|
|
261
|
+
readonly historyStateChanged: {
|
|
262
|
+
readonly canUndo: boolean;
|
|
263
|
+
readonly canRedo: boolean;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Fires whenever the image's transform state changes, from either
|
|
267
|
+
* direct mouse manipulation (corner / rotation handles) or a
|
|
268
|
+
* sidebar tool action (slider drag, flip button). The `source`
|
|
269
|
+
* field tells consumers which path produced the change so that
|
|
270
|
+
* tool components can decide whether to update bound display
|
|
271
|
+
* values without echoing back into the engine.
|
|
272
|
+
*/
|
|
273
|
+
readonly imageTransformed: {
|
|
274
|
+
readonly angle: number;
|
|
275
|
+
readonly scaleX: number;
|
|
276
|
+
readonly scaleY: number;
|
|
277
|
+
readonly flipX: boolean;
|
|
278
|
+
readonly flipY: boolean;
|
|
279
|
+
readonly left: number;
|
|
280
|
+
readonly top: number;
|
|
281
|
+
readonly source: 'mouse' | 'sidebar';
|
|
282
|
+
};
|
|
283
|
+
/** A canvas object was activated (selected). */
|
|
284
|
+
readonly objectActivated: {
|
|
285
|
+
readonly objectType: string;
|
|
286
|
+
};
|
|
287
|
+
/** No object is currently selected. */
|
|
288
|
+
readonly objectDeactivated: void;
|
|
289
|
+
/**
|
|
290
|
+
* The layer stack changed -- a shape / text layer was added,
|
|
291
|
+
* removed, reordered, or had its visibility / opacity toggled.
|
|
292
|
+
* Carries no payload: consumers re-pull `getLayers()`. (C.1)
|
|
293
|
+
*/
|
|
294
|
+
readonly layersChanged: void;
|
|
295
|
+
/**
|
|
296
|
+
* The active (selected) layer changed -- to another layer, or to
|
|
297
|
+
* none. `id` is the engine's stable layer id; `kind` lets the
|
|
298
|
+
* sidebar decide which style panel is relevant. Distinct from
|
|
299
|
+
* `objectActivated`, which predates the layer model and carries
|
|
300
|
+
* the raw fabric type only. (C.1)
|
|
301
|
+
*/
|
|
302
|
+
readonly activeObjectChanged: {
|
|
303
|
+
readonly id: string | null;
|
|
304
|
+
readonly kind: LayerKind | null;
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* Raw pointer activity in SCENE coordinates (canvas space with the
|
|
308
|
+
* viewport zoom/pan factored out -- the same space `getImageBounds()`
|
|
309
|
+
* and `getShapeGeometry()` report in). The annotation seam for
|
|
310
|
+
* consumers that implement their own draw-by-drag interactions
|
|
311
|
+
* (e.g. the ImageMap region authoring page): `down`->`move`->`up`
|
|
312
|
+
* drives rubber-band shapes, `dblclick` closes polygon paths.
|
|
313
|
+
* `targetLayerId` is the stamped layer under the pointer, or null
|
|
314
|
+
* on empty canvas / when hit-testing is disabled.
|
|
315
|
+
*/
|
|
316
|
+
readonly scenePointer: {
|
|
317
|
+
readonly phase: 'down' | 'move' | 'up' | 'dblclick';
|
|
318
|
+
readonly x: number;
|
|
319
|
+
readonly y: number;
|
|
320
|
+
readonly targetLayerId: string | null;
|
|
321
|
+
};
|
|
321
322
|
}
|
|
322
323
|
type EngineEventType = keyof EngineEvents;
|
|
323
324
|
type EngineEventHandler<T extends EngineEventType> = (data: EngineEvents[T]) => void;
|
|
324
|
-
|
|
325
325
|
/**
|
|
326
326
|
* Engine contract for image manipulation.
|
|
327
327
|
*
|
|
@@ -338,337 +338,336 @@ type EngineEventHandler<T extends EngineEventType> = (data: EngineEvents[T]) =>
|
|
|
338
338
|
* without a tick.
|
|
339
339
|
*/
|
|
340
340
|
interface ImageEditorEngine {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Load an image into the canvas. Replaces any existing content.
|
|
343
|
+
* Returns the loaded image's intrinsic dimensions.
|
|
344
|
+
*/
|
|
345
|
+
loadImage(source: string | Blob, name?: string): Promise<Size>;
|
|
346
|
+
/**
|
|
347
|
+
* Roll back every change since the last `loadImage()`. Implemented
|
|
348
|
+
* via repeated `undo()` until the stack is empty rather than a
|
|
349
|
+
* separate "reset" semantic, so behaviour is consistent with the
|
|
350
|
+
* undo button in the UI.
|
|
351
|
+
*/
|
|
352
|
+
reset(): Promise<void>;
|
|
353
|
+
/** Crop to the given rectangle (in canvas coordinates). */
|
|
354
|
+
crop(rect: Rect): Promise<void>;
|
|
355
|
+
/**
|
|
356
|
+
* Enter the engine's interactive crop UI: a draggable cropzone
|
|
357
|
+
* appears on the canvas and the user resizes / repositions it
|
|
358
|
+
* with the mouse. The actual crop operation only happens when
|
|
359
|
+
* `crop(rect)` is called against the rect returned by
|
|
360
|
+
* `getCropRect()`. Idempotent: a second call while already in
|
|
361
|
+
* crop mode is a no-op.
|
|
362
|
+
*/
|
|
363
|
+
enterCropMode(): Promise<void>;
|
|
364
|
+
/**
|
|
365
|
+
* Exit the interactive crop UI without applying. The cropzone
|
|
366
|
+
* overlay is removed; the canvas state is unchanged.
|
|
367
|
+
*/
|
|
368
|
+
exitCropMode(): Promise<void>;
|
|
369
|
+
/**
|
|
370
|
+
* Lock the cropzone to a fixed `width / height` ratio. `null`
|
|
371
|
+
* frees the cropzone (user can drag any rectangle). Only valid
|
|
372
|
+
* while in crop mode; outside crop mode this is a no-op.
|
|
373
|
+
*/
|
|
374
|
+
setCropAspectRatio(ratio: number | null): void;
|
|
375
|
+
/**
|
|
376
|
+
* Read the current cropzone rectangle in canvas-space coordinates
|
|
377
|
+
* (the overlay's `getBoundingRect()` AABB, no viewport zoom or
|
|
378
|
+
* pan factored in). Returns `null` when not in crop mode or when
|
|
379
|
+
* the overlay has zero width or height.
|
|
380
|
+
*/
|
|
381
|
+
getCropRect(): Rect | null;
|
|
382
|
+
/**
|
|
383
|
+
* Rotate by `degrees` (positive = clockwise). Targets the active
|
|
384
|
+
* non-image layer (shape / text) when one is selected, otherwise
|
|
385
|
+
* the base image (C.1c per-layer transforms).
|
|
386
|
+
*/
|
|
387
|
+
rotate(degrees: number): Promise<void>;
|
|
388
|
+
/**
|
|
389
|
+
* Flip along the given axis. Targets the active non-image layer
|
|
390
|
+
* when one is selected, otherwise the base image (C.1c).
|
|
391
|
+
*/
|
|
392
|
+
flip(axis: 'horizontal' | 'vertical'): Promise<void>;
|
|
393
|
+
/** Resize the canvas (and the loaded image) to `size`. */
|
|
394
|
+
resize(size: Size): Promise<void>;
|
|
395
|
+
/**
|
|
396
|
+
* Apply a filter. Idempotent: re-calling with new params updates
|
|
397
|
+
* the existing instance of that filter rather than stacking a new
|
|
398
|
+
* one. Parameter ranges are normalised -- see FilterParams docblock.
|
|
399
|
+
*/
|
|
400
|
+
applyFilter(name: FilterName, params?: FilterParams): Promise<void>;
|
|
401
|
+
/** Remove a previously-applied filter (no-op if not active). */
|
|
402
|
+
removeFilter(name: FilterName): Promise<void>;
|
|
403
|
+
/** True when the named filter is currently active. */
|
|
404
|
+
hasFilter(name: FilterName): boolean;
|
|
405
|
+
/**
|
|
406
|
+
* Add a shape, centred on the image, styled per `opts` (defaults
|
|
407
|
+
* fill in). The new shape becomes the active object.
|
|
408
|
+
*/
|
|
409
|
+
addShape(kind: ShapeKind, opts?: Partial<ShapeOptions>): void;
|
|
410
|
+
/**
|
|
411
|
+
* Add an editable text box, centred on the image. The new text
|
|
412
|
+
* becomes the active object.
|
|
413
|
+
*/
|
|
414
|
+
addText(opts?: Partial<TextOptions>): void;
|
|
415
|
+
/**
|
|
416
|
+
* The base image's scene-space bounding rect (the frame authored
|
|
417
|
+
* geometry is normalized against), or `null` before `loadImage()`.
|
|
418
|
+
*/
|
|
419
|
+
getImageBounds(): {
|
|
420
|
+
x: number;
|
|
421
|
+
y: number;
|
|
422
|
+
width: number;
|
|
423
|
+
height: number;
|
|
424
|
+
} | null;
|
|
425
|
+
/**
|
|
426
|
+
* Add a shape at explicit scene-space geometry (no centering).
|
|
427
|
+
* Becomes the active object; undoable like `addShape`. Returns the
|
|
428
|
+
* new layer's id so the caller can key its own metadata to it.
|
|
429
|
+
*/
|
|
430
|
+
addShapeAt(geometry: ShapeGeometry, opts?: AnnotationShapeOptions): string;
|
|
431
|
+
/**
|
|
432
|
+
* Read a shape layer's current scene-space geometry, reflecting
|
|
433
|
+
* any interactive move / scale the user applied. Rects report the
|
|
434
|
+
* axis-aligned box (rotation is not representable -- pair with
|
|
435
|
+
* `lockRotation`); ellipses report center + scaled radii; polygons
|
|
436
|
+
* report fully-transformed vertices. `null` for unknown ids, the
|
|
437
|
+
* base image, or non-shape layers.
|
|
438
|
+
*/
|
|
439
|
+
getShapeGeometry(id: string): ShapeGeometry | null;
|
|
440
|
+
/**
|
|
441
|
+
* Toggle per-vertex editing on a POLYGON shape layer. While enabled
|
|
442
|
+
* the polygon's default scale/rotate handles are replaced by one
|
|
443
|
+
* draggable control per vertex (whole-shape dragging is locked so
|
|
444
|
+
* the vertices are the only interaction); a completed vertex drag
|
|
445
|
+
* is undoable like any other mutation. Disabling restores the
|
|
446
|
+
* default handles. Returns `false` (no-op) for unknown ids or
|
|
447
|
+
* non-polygon layers; enabling twice is idempotent.
|
|
448
|
+
*/
|
|
449
|
+
setVertexEditing(id: string, enabled: boolean): boolean;
|
|
450
|
+
/**
|
|
451
|
+
* Set a layer's fill for a TRANSIENT preview tint (e.g. a live
|
|
452
|
+
* busy/free status overlay while authoring). Not undoable and not part
|
|
453
|
+
* of the shape's geometry, so it never affects what a save persists.
|
|
454
|
+
* Passing `null` restores the default annotation fill. No-op for the
|
|
455
|
+
* base image or an unknown id.
|
|
456
|
+
*/
|
|
457
|
+
setLayerFill(id: string, fill: string | null): void;
|
|
458
|
+
/**
|
|
459
|
+
* Insert a new draggable vertex into the polygon `id` currently in
|
|
460
|
+
* vertex-edit mode, at the point on its outline NEAREST to the scene
|
|
461
|
+
* coordinate `(sceneX, sceneY)` -- a collinear insert, so the polygon's
|
|
462
|
+
* shape is unchanged, it just gains a handle the author can then drag.
|
|
463
|
+
* The controls are rebuilt (re-indexed) and the insert is undoable.
|
|
464
|
+
* Returns `false` (no-op) if `id` is not the polygon currently in
|
|
465
|
+
* vertex-edit mode. Intended to be wired to a double-click on the
|
|
466
|
+
* outline while editing vertices.
|
|
467
|
+
*/
|
|
468
|
+
addVertexAt(id: string, sceneX: number, sceneY: number): boolean;
|
|
469
|
+
/**
|
|
470
|
+
* Remove the vertex of the polygon `id` (currently in vertex-edit mode)
|
|
471
|
+
* NEAREST to the scene coordinate `(sceneX, sceneY)`, but only when the
|
|
472
|
+
* coordinate lands ON a vertex handle (within a fraction of the polygon's
|
|
473
|
+
* own size). The controls are rebuilt (re-indexed) and the removal is
|
|
474
|
+
* undoable. Returns `false` (no-op) if `id` is not the polygon in
|
|
475
|
+
* vertex-edit mode, if removing would drop below the 3-vertex minimum a
|
|
476
|
+
* polygon requires, OR if the coordinate is too far from any vertex -- so
|
|
477
|
+
* a caller can route a double-click to this FIRST and fall through to
|
|
478
|
+
* {@link addVertexAt} (which projects onto the outline) when it returns
|
|
479
|
+
* `false`. Intended to be wired to a double-click on a vertex handle.
|
|
480
|
+
*/
|
|
481
|
+
removeVertexAt(id: string, sceneX: number, sceneY: number): boolean;
|
|
482
|
+
/**
|
|
483
|
+
* The current layer stack, **top-most first** (so a Layers panel
|
|
484
|
+
* lists it the way every editor does). Always includes the base
|
|
485
|
+
* image as the last (bottom) entry.
|
|
486
|
+
*/
|
|
487
|
+
getLayers(): LayerInfo[];
|
|
488
|
+
/** The active layer's id, or `null` when nothing is selected. */
|
|
489
|
+
getActiveLayerId(): string | null;
|
|
490
|
+
/** Select a layer by id (`null` clears selection). */
|
|
491
|
+
selectLayer(id: string | null): void;
|
|
492
|
+
/** Delete a shape / text layer (no-op for the locked base image). */
|
|
493
|
+
removeLayer(id: string): void;
|
|
494
|
+
/** Show / hide a layer. */
|
|
495
|
+
setLayerVisibility(id: string, visible: boolean): void;
|
|
496
|
+
/** Set a layer's opacity (0..1). */
|
|
497
|
+
setLayerOpacity(id: string, opacity: number): void;
|
|
498
|
+
/**
|
|
499
|
+
* Restack a layer. `up`/`down` move one step; `front`/`back` jump
|
|
500
|
+
* to the extremes. The base image is pinned to the bottom -- other
|
|
501
|
+
* layers can't sink below it.
|
|
502
|
+
*/
|
|
503
|
+
reorderLayer(id: string, direction: 'up' | 'down' | 'front' | 'back'): void;
|
|
504
|
+
/**
|
|
505
|
+
* Read the active object's editable style props (`fill`, `stroke`,
|
|
506
|
+
* `strokeWidth`, `opacity`, plus font props for text). Returns
|
|
507
|
+
* `null` when no object -- or the base image -- is selected.
|
|
508
|
+
*/
|
|
509
|
+
getActiveObjectProps(): ObjectProps | null;
|
|
510
|
+
/**
|
|
511
|
+
* Apply style props to the active object (single undo command).
|
|
512
|
+
* No-op when nothing editable is selected.
|
|
513
|
+
*/
|
|
514
|
+
updateActiveObject(props: ObjectProps): void;
|
|
515
|
+
/**
|
|
516
|
+
* Read the active object's fill as a UI-friendly {@link FillSpec},
|
|
517
|
+
* resolving a fabric `Gradient` back to mode + colour stops + angle
|
|
518
|
+
* so the style panels can round-trip a gradient on re-select.
|
|
519
|
+
* Returns `null` when no shape / text layer is selected. (C.1b)
|
|
520
|
+
*/
|
|
521
|
+
getActiveObjectFill(): FillSpec | null;
|
|
522
|
+
/**
|
|
523
|
+
* Set the active object's fill from a {@link FillSpec}, building a
|
|
524
|
+
* fabric `Gradient` for the linear / radial modes (single undo
|
|
525
|
+
* command). No-op when nothing editable is selected. (C.1b)
|
|
526
|
+
*/
|
|
527
|
+
setActiveObjectFill(spec: FillSpec): void;
|
|
528
|
+
/**
|
|
529
|
+
* The active non-image layer's rotation + flip state, so the
|
|
530
|
+
* Rotate / Flip tools can bind to the selected layer rather than
|
|
531
|
+
* the base image. Returns `null` when the base image -- or nothing
|
|
532
|
+
* -- is the active object (the tools fall back to image state).
|
|
533
|
+
*/
|
|
534
|
+
getActiveLayerTransform(): {
|
|
535
|
+
angle: number;
|
|
536
|
+
flipX: boolean;
|
|
537
|
+
flipY: boolean;
|
|
538
|
+
} | null;
|
|
539
|
+
/**
|
|
540
|
+
* Clip the mask target to a shape (single undo command). The target
|
|
541
|
+
* is the active non-image layer when one is selected, otherwise the
|
|
542
|
+
* base image -- so selecting a layer masks it, selecting nothing (or
|
|
543
|
+
* the Background) masks the photo. `shape: 'none'` clears the mask.
|
|
544
|
+
*/
|
|
545
|
+
setMask(spec: MaskSpec): void;
|
|
546
|
+
/**
|
|
547
|
+
* Read the mask target's current {@link MaskSpec} so the Mask tool
|
|
548
|
+
* can seed its controls on (re)selection. Returns the default
|
|
549
|
+
* (`shape: 'none'`) when the target has no mask, or `null` when
|
|
550
|
+
* there is no target at all (no image loaded).
|
|
551
|
+
*/
|
|
552
|
+
getMask(): MaskSpec | null;
|
|
553
|
+
undo(): Promise<void>;
|
|
554
|
+
redo(): Promise<void>;
|
|
555
|
+
canUndo(): boolean;
|
|
556
|
+
canRedo(): boolean;
|
|
557
|
+
/**
|
|
558
|
+
* Forget every entry currently in the redo stack.
|
|
559
|
+
*
|
|
560
|
+
* Used by the live-preview filter tools: each slider drag does an
|
|
561
|
+
* `undo()` followed by an `applyFilter()`, so the redo stack
|
|
562
|
+
* accumulates one stale entry per drag. Calling this on commit
|
|
563
|
+
* leaves the stack in the same shape it would have had if the
|
|
564
|
+
* user had jumped straight to the final value, with no preview
|
|
565
|
+
* cycle at all.
|
|
566
|
+
*/
|
|
567
|
+
clearRedoStack(): void;
|
|
568
|
+
/**
|
|
569
|
+
* Whether the flattened render contains any transparency, so the
|
|
570
|
+
* save flow can force a PNG export instead of flattening a masked /
|
|
571
|
+
* transparent image onto an opaque (JPEG) background -- which loses
|
|
572
|
+
* the cut-away region to opaque black (C.1d). True when the base
|
|
573
|
+
* image or any visible layer carries a mask (`clipPath`), or when
|
|
574
|
+
* the flattened pixels contain a non-opaque alpha value. Cheap for
|
|
575
|
+
* the masking case; scans pixels only as a fallback.
|
|
576
|
+
*/
|
|
577
|
+
hasAlpha(): boolean;
|
|
578
|
+
/**
|
|
579
|
+
* Render the current canvas as a Blob. `format` defaults to 'png';
|
|
580
|
+
* `quality` (0..1) applies to lossy formats only.
|
|
581
|
+
*/
|
|
582
|
+
export(format?: 'png' | 'jpeg' | 'webp', quality?: number): Promise<Blob>;
|
|
583
|
+
/**
|
|
584
|
+
* Synchronous data-URL export. Useful when the consumer needs the
|
|
585
|
+
* dataURL form directly (e.g., immediate <img src> assignment).
|
|
586
|
+
*/
|
|
587
|
+
exportDataUrl(format?: 'png' | 'jpeg' | 'webp', quality?: number): string;
|
|
588
|
+
/** Current canvas dimensions. */
|
|
589
|
+
getCanvasSize(): Size;
|
|
590
|
+
/**
|
|
591
|
+
* Set the viewport zoom centred on the canvas. Pure rendering
|
|
592
|
+
* concern -- does not modify image data, does not push to the
|
|
593
|
+
* undo stack. The exported bytes are independent of zoom level.
|
|
594
|
+
*
|
|
595
|
+
* `scale = 1` is 100% (one canvas pixel per CSS pixel). Values
|
|
596
|
+
* are clamped at the consumer level (the state service uses
|
|
597
|
+
* 0.1..5).
|
|
598
|
+
*/
|
|
599
|
+
setZoom(scale: number): void;
|
|
600
|
+
/** Read the current viewport zoom. Returns 1 when at 100%. */
|
|
601
|
+
getZoom(): number;
|
|
602
|
+
/** Reset the viewport transform to the identity (zoom = 1, no pan). */
|
|
603
|
+
resetZoom(): void;
|
|
604
|
+
/**
|
|
605
|
+
* Pan the viewport by the given pixel deltas. Pure rendering;
|
|
606
|
+
* does not modify image data or push to undo. Used by the Hand
|
|
607
|
+
* tool's drag handler.
|
|
608
|
+
*/
|
|
609
|
+
pan(dx: number, dy: number): void;
|
|
610
|
+
/** Current pan offset in viewport coordinates. */
|
|
611
|
+
getPanOffset(): Point;
|
|
612
|
+
/**
|
|
613
|
+
* Set the pan offset to the given absolute viewport translation,
|
|
614
|
+
* leaving the current zoom in place. Used by the state service
|
|
615
|
+
* to centre the scaled image inside the visible viewport after
|
|
616
|
+
* applying a new zoom factor.
|
|
617
|
+
*/
|
|
618
|
+
setPanOffset(offset: Point): void;
|
|
619
|
+
/**
|
|
620
|
+
* Reset the pan offset to zero, leaving the current zoom in
|
|
621
|
+
* place. Distinct from `resetZoom()`, which clears both.
|
|
622
|
+
*/
|
|
623
|
+
resetPan(): void;
|
|
624
|
+
/**
|
|
625
|
+
* Compute the zoom factor that fits the image into the given
|
|
626
|
+
* viewport rectangle, leaving a small margin so the image edges
|
|
627
|
+
* stay visible. Not clamped: small images upscale to fit the
|
|
628
|
+
* viewport, large images shrink to fit. The adapter computes
|
|
629
|
+
* from image dimensions vs viewport dimensions directly.
|
|
630
|
+
*/
|
|
631
|
+
getFitZoom(viewportSize: Size): number;
|
|
632
|
+
/**
|
|
633
|
+
* Set the canvas cursor. fabric.js owns its own cursor state
|
|
634
|
+
* (defaultCursor / hoverCursor / moveCursor) independent of DOM
|
|
635
|
+
* CSS, so a `cursor: grab` rule on the host element is silently
|
|
636
|
+
* overridden by fabric's defaults. Used by the Hand tool to
|
|
637
|
+
* surface grab / grabbing.
|
|
638
|
+
*/
|
|
639
|
+
setCursor(cursor: 'default' | 'grab' | 'grabbing' | 'crosshair'): void;
|
|
640
|
+
/**
|
|
641
|
+
* Toggle fabric's group-selection rectangle and object hit
|
|
642
|
+
* detection. Disabled when the Hand tool is active so a drag on
|
|
643
|
+
* empty canvas pans instead of drawing a marquee, and so pointer
|
|
644
|
+
* events don't get sniped by hit-tests against canvas objects.
|
|
645
|
+
*/
|
|
646
|
+
setSelectionEnabled(enabled: boolean): void;
|
|
647
|
+
/**
|
|
648
|
+
* Set the canvas backstore + display dimensions to the given
|
|
649
|
+
* viewport. Owning canvas size in the adapter (rather than
|
|
650
|
+
* inheriting from a vendor's CSS auto-fit layer) is what makes
|
|
651
|
+
* viewport zoom the single, predictable scale knob: small
|
|
652
|
+
* images can upscale to fill the viewport without being clipped
|
|
653
|
+
* by the canvas backstore, and large images shrink through the
|
|
654
|
+
* same setZoom path. Recenters the loaded image on the resized
|
|
655
|
+
* canvas.
|
|
656
|
+
*/
|
|
657
|
+
setCanvasDimensions(viewport: Size): void;
|
|
658
|
+
on<T extends EngineEventType>(event: T, handler: EngineEventHandler<T>): void;
|
|
659
|
+
off<T extends EngineEventType>(event: T, handler?: EngineEventHandler<T>): void;
|
|
660
|
+
/** Tear down the engine and free its DOM/canvas resources. */
|
|
661
|
+
destroy(): void;
|
|
662
|
+
/**
|
|
663
|
+
* Direct access to the underlying canvas element. Escape hatch for
|
|
664
|
+
* shells that need to hit the DOM API directly (pixel sampling,
|
|
665
|
+
* pointer-event hookup, etc.). Most shells should not need this.
|
|
666
|
+
*/
|
|
667
|
+
readonly canvasElement: HTMLCanvasElement | null;
|
|
668
668
|
}
|
|
669
|
-
|
|
670
669
|
interface FabricEngineCreateOptions {
|
|
671
|
-
|
|
670
|
+
container: HTMLElement;
|
|
672
671
|
}
|
|
673
672
|
/**
|
|
674
673
|
* fabric.js v7 adapter for the CoolMS image editor.
|
|
@@ -694,323 +693,321 @@ interface FabricEngineCreateOptions {
|
|
|
694
693
|
* factory wires up the canvas element and seeds initial dimensions
|
|
695
694
|
* from the container's measured size.
|
|
696
695
|
*/
|
|
697
|
-
declare class FabricEngineAdapter implements ImageEditorEngine {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
696
|
+
export declare class FabricEngineAdapter implements ImageEditorEngine {
|
|
697
|
+
private static readonly TOGGLE_FILTERS;
|
|
698
|
+
private static readonly SLIDER_FILTERS;
|
|
699
|
+
private readonly canvas;
|
|
700
|
+
private readonly canvasEl;
|
|
701
|
+
private readonly container;
|
|
702
|
+
/** The default annotation-shape fill; `setLayerFill(id, null)` restores it. */
|
|
703
|
+
private static readonly DEFAULT_SHAPE_FILL;
|
|
704
|
+
private readonly listeners;
|
|
705
|
+
private readonly commandStack;
|
|
706
|
+
private image;
|
|
707
|
+
private vertexEdit;
|
|
708
|
+
private originalSource;
|
|
709
|
+
private cropOverlay;
|
|
710
|
+
private frameOverlay;
|
|
711
|
+
private mouseTransformSnapshot;
|
|
712
|
+
/** Monotonic id source for shape / text layers. */
|
|
713
|
+
private nextLayerId;
|
|
714
|
+
/**
|
|
715
|
+
* Transform snapshot for a non-image object captured on
|
|
716
|
+
* `mouse:down`, so `object:modified` can build the inverse for
|
|
717
|
+
* undo (mirrors `mouseTransformSnapshot` but for shapes / text).
|
|
718
|
+
*/
|
|
719
|
+
private objTransformSnapshot;
|
|
720
|
+
private constructor();
|
|
721
|
+
/**
|
|
722
|
+
* Build an adapter against the given container element. Creates a
|
|
723
|
+
* `<canvas>` child sized to the container and constructs a fabric
|
|
724
|
+
* Canvas around it.
|
|
725
|
+
*/
|
|
726
|
+
static create(options: FabricEngineCreateOptions): Promise<FabricEngineAdapter>;
|
|
727
|
+
loadImage(source: string | Blob, name?: string): Promise<Size>;
|
|
728
|
+
reset(): Promise<void>;
|
|
729
|
+
crop(canvasSpaceRect: Rect): Promise<void>;
|
|
730
|
+
enterCropMode(): Promise<void>;
|
|
731
|
+
exitCropMode(): Promise<void>;
|
|
732
|
+
/** Toggle selectability of the annotation layers (used by crop mode). */
|
|
733
|
+
private setLayersInteractive;
|
|
734
|
+
setCropAspectRatio(ratio: number | null): void;
|
|
735
|
+
getCropRect(): Rect | null;
|
|
736
|
+
rotate(degrees: number): Promise<void>;
|
|
737
|
+
flip(axis: 'horizontal' | 'vertical'): Promise<void>;
|
|
738
|
+
resize(size: Size): Promise<void>;
|
|
739
|
+
applyFilter(name: FilterName, params?: FilterParams): Promise<void>;
|
|
740
|
+
removeFilter(name: FilterName): Promise<void>;
|
|
741
|
+
hasFilter(name: FilterName): boolean;
|
|
742
|
+
addShape(kind: ShapeKind, opts?: Partial<ShapeOptions>): void;
|
|
743
|
+
addText(opts?: Partial<TextOptions>): void;
|
|
744
|
+
getImageBounds(): Rect | null;
|
|
745
|
+
addShapeAt(geometry: ShapeGeometry, opts?: AnnotationShapeOptions): string;
|
|
746
|
+
getShapeGeometry(id: string): ShapeGeometry | null;
|
|
747
|
+
setVertexEditing(id: string, enabled: boolean): boolean;
|
|
748
|
+
/**
|
|
749
|
+
* Swap the polygon's default handles for one draggable Control per
|
|
750
|
+
* vertex -- the official fabric "custom polygon controls" pattern:
|
|
751
|
+
* `positionHandler` projects `points[i]` through viewport x object
|
|
752
|
+
* transform; `actionHandler` writes the pointer back into
|
|
753
|
+
* `points[i]` (object-local, stroke-compensated) and re-anchors the
|
|
754
|
+
* polygon on a neighbouring vertex after `setDimensions()` so the
|
|
755
|
+
* shape doesn't jump while its bounding box is recomputed.
|
|
756
|
+
*/
|
|
757
|
+
private enterVertexEdit;
|
|
758
|
+
private exitVertexEdit;
|
|
759
|
+
private vertexControlPosition;
|
|
760
|
+
private moveVertex;
|
|
761
|
+
/**
|
|
762
|
+
* (Re)build one draggable Control per vertex -- used on entering vertex
|
|
763
|
+
* edit AND after a topology change (add/remove) so the `vertexN` controls
|
|
764
|
+
* stay index-aligned with the current `points` array.
|
|
765
|
+
*/
|
|
766
|
+
private rebuildVertexControls;
|
|
767
|
+
addVertexAt(id: string, sceneX: number, sceneY: number): boolean;
|
|
768
|
+
removeVertexAt(id: string, sceneX: number, sceneY: number): boolean;
|
|
769
|
+
/** Closest point on segment `a->b` to `p` (all object-local coords). */
|
|
770
|
+
private projectOnSegment;
|
|
771
|
+
/**
|
|
772
|
+
* Replace the polygon's `points` and re-index its vertex controls,
|
|
773
|
+
* keeping one SURVIVING vertex visually fixed across the
|
|
774
|
+
* `setDimensions()` bbox recompute so the shape never jumps.
|
|
775
|
+
*
|
|
776
|
+
* `fromIndex` is the anchor vertex's index in the CURRENT points,
|
|
777
|
+
* `toIndex` its index in the NEW `pts` -- the two must be the SAME
|
|
778
|
+
* vertex. For an insert (add) or the identity case both are 0 (the
|
|
779
|
+
* defaults). A removal that drops vertex 0 shifts every later vertex
|
|
780
|
+
* down one, so its surviving anchor lives at different indices before
|
|
781
|
+
* vs after -- hence the explicit pair.
|
|
782
|
+
*/
|
|
783
|
+
private applyVertexTopology;
|
|
784
|
+
private polygonSizeWithStroke;
|
|
785
|
+
private vertexDragStart;
|
|
786
|
+
private vertexDragEnd;
|
|
787
|
+
getLayers(): LayerInfo[];
|
|
788
|
+
getActiveLayerId(): string | null;
|
|
789
|
+
selectLayer(id: string | null): void;
|
|
790
|
+
setLayerFill(id: string, fill: string | null): void;
|
|
791
|
+
removeLayer(id: string): void;
|
|
792
|
+
setLayerVisibility(id: string, visible: boolean): void;
|
|
793
|
+
setLayerOpacity(id: string, opacity: number): void;
|
|
794
|
+
reorderLayer(id: string, direction: 'up' | 'down' | 'front' | 'back'): void;
|
|
795
|
+
getActiveObjectProps(): ObjectProps | null;
|
|
796
|
+
getActiveObjectFill(): FillSpec | null;
|
|
797
|
+
setActiveObjectFill(spec: FillSpec): void;
|
|
798
|
+
getActiveLayerTransform(): {
|
|
799
|
+
angle: number;
|
|
800
|
+
flipX: boolean;
|
|
801
|
+
flipY: boolean;
|
|
802
|
+
} | null;
|
|
803
|
+
setMask(spec: MaskSpec): void;
|
|
804
|
+
getMask(): MaskSpec | null;
|
|
805
|
+
updateActiveObject(props: ObjectProps): void;
|
|
806
|
+
undo(): Promise<void>;
|
|
807
|
+
redo(): Promise<void>;
|
|
808
|
+
canUndo(): boolean;
|
|
809
|
+
canRedo(): boolean;
|
|
810
|
+
clearRedoStack(): void;
|
|
811
|
+
export(format?: 'png' | 'jpeg' | 'webp', quality?: number): Promise<Blob>;
|
|
812
|
+
exportDataUrl(format?: 'png' | 'jpeg' | 'webp', quality?: number): string;
|
|
813
|
+
hasAlpha(): boolean;
|
|
814
|
+
/** True when `obj` carries a fabric `clipPath` (a mask). */
|
|
815
|
+
private objectHasClip;
|
|
816
|
+
/**
|
|
817
|
+
* Render the base image plus every visible annotation layer into a
|
|
818
|
+
* single bitmap (the export-time flatten). With no annotations the
|
|
819
|
+
* fast path returns the image baked at its own resolution; with
|
|
820
|
+
* annotations we composite each object onto a canvas sized to the
|
|
821
|
+
* image's bounding box, offsetting every object by its position
|
|
822
|
+
* relative to the image (both in canvas-space px, multiplier 1).
|
|
823
|
+
*
|
|
824
|
+
* C.1 keeps transforms whole-image, so the common authoring flow
|
|
825
|
+
* (edit pixels -> annotate -> save) lands annotations at full
|
|
826
|
+
* resolution; a base image the user manually scaled with handles
|
|
827
|
+
* exports at its displayed size (a documented limitation).
|
|
828
|
+
*/
|
|
829
|
+
private renderFlattened;
|
|
830
|
+
getCanvasSize(): Size;
|
|
831
|
+
setZoom(scale: number): void;
|
|
832
|
+
getZoom(): number;
|
|
833
|
+
resetZoom(): void;
|
|
834
|
+
pan(dx: number, dy: number): void;
|
|
835
|
+
getPanOffset(): Point;
|
|
836
|
+
setPanOffset(offset: Point): void;
|
|
837
|
+
resetPan(): void;
|
|
838
|
+
getFitZoom(viewportSize: Size): number;
|
|
839
|
+
setCursor(cursor: 'default' | 'grab' | 'grabbing' | 'crosshair'): void;
|
|
840
|
+
setSelectionEnabled(enabled: boolean): void;
|
|
841
|
+
setCanvasDimensions(viewport: Size): void;
|
|
842
|
+
on<T extends EngineEventType>(event: T, handler: EngineEventHandler<T>): void;
|
|
843
|
+
off<T extends EngineEventType>(event: T, handler?: EngineEventHandler<T>): void;
|
|
844
|
+
destroy(): void;
|
|
845
|
+
get canvasElement(): HTMLCanvasElement | null;
|
|
846
|
+
private requireImage;
|
|
847
|
+
/**
|
|
848
|
+
* Add a thin orange rectangle that mirrors the image's transform
|
|
849
|
+
* so the user can see exactly where the image edge is against the
|
|
850
|
+
* dark canvas background. Sent to the back so the image renders
|
|
851
|
+
* on top of the inner half of the stroke; the outer half stays
|
|
852
|
+
* visible just outside the image bounds. `strokeWidth = 2 / zoom`
|
|
853
|
+
* keeps that outer band a constant 1 visual pixel regardless of
|
|
854
|
+
* viewport zoom. Excluded from export by both convention and
|
|
855
|
+
* construction: our `export` / `exportDataUrl` paths render only
|
|
856
|
+
* the FabricImage object, not the canvas.
|
|
857
|
+
*/
|
|
858
|
+
private addFrameOverlay;
|
|
859
|
+
/**
|
|
860
|
+
* Refresh the frame to mirror the image's current transform and
|
|
861
|
+
* keep the stroke at a constant 1 visual pixel. Called whenever
|
|
862
|
+
* the image moves, scales, rotates, or flips, and after viewport
|
|
863
|
+
* zoom changes (since the latter only matters for the stroke
|
|
864
|
+
* width). Hidden frame stays hidden but its geometry still
|
|
865
|
+
* updates so the next show is in sync.
|
|
866
|
+
*/
|
|
867
|
+
private syncFrameOverlay;
|
|
868
|
+
private removeFrameOverlay;
|
|
869
|
+
/**
|
|
870
|
+
* Install a new FabricImage as the active image after a crop or
|
|
871
|
+
* resize replacement. Adds it to the canvas, centers it in canvas
|
|
872
|
+
* coordinates, drops any leftover viewport zoom/pan from the
|
|
873
|
+
* previous image (otherwise the new, smaller image lands
|
|
874
|
+
* off-screen because the user was zoomed into the corner of the
|
|
875
|
+
* old one), and re-emits `imageLoaded` so consumers refit the
|
|
876
|
+
* view to the new dimensions.
|
|
877
|
+
*/
|
|
878
|
+
private replaceImage;
|
|
879
|
+
/**
|
|
880
|
+
* Make sure fabric's WebGL filter backend has a GL canvas large
|
|
881
|
+
* enough to hold the image's full pixel area. Fabric defaults
|
|
882
|
+
* `config.textureSize` to 4096 and constructs a 4096x4096 GL
|
|
883
|
+
* canvas; rendering a 4608x3456 image into that backend leaves
|
|
884
|
+
* a ~512 px right-edge band un-rendered (the WebGL viewport is
|
|
885
|
+
* set to image dims but the GL canvas can't hold them), which
|
|
886
|
+
* `copyGLTo2D` then reads as transparent. That surfaced as the
|
|
887
|
+
* right-side crop the user reported on Sharpen.
|
|
888
|
+
*
|
|
889
|
+
* Fix: bump `config.textureSize` to the next power-of-two that
|
|
890
|
+
* fits the image, capped at 16384, then drop the cached backend
|
|
891
|
+
* so `getFilterBackend()` re-initialises with the new tile size.
|
|
892
|
+
* The probe inside `initFilterBackend` falls back to the Canvas2D
|
|
893
|
+
* backend automatically if the GPU can't support the new size,
|
|
894
|
+
* so this is safe on low-end hardware.
|
|
895
|
+
*/
|
|
896
|
+
private ensureFilterBackendFitsImage;
|
|
897
|
+
private pushCommand;
|
|
898
|
+
private resolveFilterCtor;
|
|
899
|
+
private instantiateFilter;
|
|
900
|
+
private attachVendorListeners;
|
|
901
|
+
private captureTransform;
|
|
902
|
+
private transformsEqual;
|
|
903
|
+
private emitImageTransformed;
|
|
904
|
+
/** A filled right-pointing arrow, drawn as a single polygon. */
|
|
905
|
+
private static readonly ARROW_POINTS;
|
|
906
|
+
private createShape;
|
|
907
|
+
/** A fabric fill value: a flat colour string or a `Gradient`. */
|
|
908
|
+
private resolveFill;
|
|
909
|
+
/**
|
|
910
|
+
* Build a fabric solid colour or `Gradient` from a {@link FillSpec}.
|
|
911
|
+
* Gradients use `gradientUnits: 'percentage'`, so coords are 0..1 of
|
|
912
|
+
* the object's bounding box (origin top-left) and the gradient
|
|
913
|
+
* tracks the object through resize / text reflow -- fabric applies
|
|
914
|
+
* `ctx.transform(width, 0, 0, height, ...)` at paint time, including
|
|
915
|
+
* the `toCanvasElement` flatten the export composites.
|
|
916
|
+
*/
|
|
917
|
+
private buildFill;
|
|
918
|
+
/** Resolve a fabric fill (string or `Gradient`) back to a {@link FillSpec}. */
|
|
919
|
+
private readFill;
|
|
920
|
+
/** Best-effort solid-colour hex for the generic prop bag (gradients collapse to their first stop). */
|
|
921
|
+
private fillToHex;
|
|
922
|
+
private isGradient;
|
|
923
|
+
/** Coerce a colour-stop value to an `<input type=color>`-safe 6-digit hex. */
|
|
924
|
+
private colorStopHex;
|
|
925
|
+
private shapeLabel;
|
|
926
|
+
/**
|
|
927
|
+
* Centre `obj` on the image, stamp it as a layer, add + select it,
|
|
928
|
+
* and push an add/remove undo command. Shared by addShape/addText.
|
|
929
|
+
*/
|
|
930
|
+
private placeAndAdd;
|
|
931
|
+
private stampLayer;
|
|
932
|
+
/** Every stamped layer object on the canvas (base image + extras). */
|
|
933
|
+
private layerObjects;
|
|
934
|
+
/** Drop every non-image layer (used by load / reset). */
|
|
935
|
+
private removeLayerObjects;
|
|
936
|
+
private findLayer;
|
|
937
|
+
private layerId;
|
|
938
|
+
private layerKind;
|
|
939
|
+
private layerName;
|
|
940
|
+
/** Keep the base image pinned to the canvas floor (z-index 0). */
|
|
941
|
+
private keepImageAtBack;
|
|
942
|
+
/** Notify consumers a layer changed + re-broadcast the active layer. */
|
|
943
|
+
private afterLayerMutation;
|
|
944
|
+
private emitActiveFromCanvas;
|
|
945
|
+
private captureObjectState;
|
|
946
|
+
/**
|
|
947
|
+
* The active object when it's a selectable shape / text layer (not
|
|
948
|
+
* the base image, not nothing). The rotate / flip / mask targeting
|
|
949
|
+
* keys off this: a non-null result means "operate on this layer".
|
|
950
|
+
*/
|
|
951
|
+
private activeNonImageLayer;
|
|
952
|
+
/**
|
|
953
|
+
* The mask target: the active non-image layer if one is selected,
|
|
954
|
+
* else the base image (which may be `null` before any load).
|
|
955
|
+
*/
|
|
956
|
+
private maskTarget;
|
|
957
|
+
private rotateLayer;
|
|
958
|
+
private flipLayer;
|
|
959
|
+
/**
|
|
960
|
+
* Build a fabric `clipPath` for `spec` against `target`'s own box.
|
|
961
|
+
* The clip is centred on the object (originX/Y = 'center', left/top
|
|
962
|
+
* = 0): fabric renders clipPaths in the object's centred local space
|
|
963
|
+
* before applying the object's own scale, so the mask tracks the
|
|
964
|
+
* object through scale / move and bakes into `toCanvasElement`.
|
|
965
|
+
* Returns `null` for `shape: 'none'` (clears the mask).
|
|
966
|
+
*/
|
|
967
|
+
private buildClipPath;
|
|
968
|
+
/**
|
|
969
|
+
* Build a `Path` clip from raw SVG path `d` data, scaling its
|
|
970
|
+
* bounding box to the `cw x ch` clip box (centred via `common`).
|
|
971
|
+
* Shared by the built-in path presets and arbitrary `custom` masks
|
|
972
|
+
* (C.1d) -- fabric computes the path's intrinsic bbox, and the
|
|
973
|
+
* `scaleX/Y` maps it onto the target's clip box, so any path tracks
|
|
974
|
+
* scale / move and bakes into `toCanvasElement` like the presets.
|
|
975
|
+
* Returns `null` for empty / unparseable data (clears the mask).
|
|
976
|
+
*/
|
|
977
|
+
private buildPathClip;
|
|
978
|
+
private emit;
|
|
980
979
|
}
|
|
981
|
-
|
|
982
980
|
/**
|
|
983
981
|
* Construction options for any `ImageEditorEngine`. The container is
|
|
984
982
|
* required; everything else is optional with sensible defaults.
|
|
985
983
|
*
|
|
986
|
-
* No engine-specific knobs leak into this shape
|
|
984
|
+
* No engine-specific knobs leak into this shape -- Toast-UI-only
|
|
987
985
|
* options (selectionStyle borderColor etc.) belong on a Toast-UI-only
|
|
988
986
|
* sub-options bag if a future shell needs to thread them through. For
|
|
989
987
|
* MVP, the defaults are enough.
|
|
990
988
|
*/
|
|
991
989
|
interface EngineOptions {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
990
|
+
/** DOM element the engine mounts its canvas into. */
|
|
991
|
+
readonly container: HTMLElement;
|
|
992
|
+
/** Initial canvas size. Defaults to the container's measured size. */
|
|
993
|
+
readonly initialSize?: Size;
|
|
994
|
+
/** Display-side cap for canvas width in CSS pixels. */
|
|
995
|
+
readonly cssMaxWidth?: number;
|
|
996
|
+
/** Display-side cap for canvas height in CSS pixels. */
|
|
997
|
+
readonly cssMaxHeight?: number;
|
|
998
|
+
/**
|
|
999
|
+
* Toast UI's anonymised usage telemetry. Off by default; flip true
|
|
1000
|
+
* only with explicit user consent.
|
|
1001
|
+
*/
|
|
1002
|
+
readonly usageStatistics?: boolean;
|
|
1005
1003
|
}
|
|
1006
|
-
|
|
1007
1004
|
/**
|
|
1008
1005
|
* Lifecycle states the shell renders against.
|
|
1009
1006
|
*
|
|
1010
|
-
* `uninitialized`
|
|
1011
|
-
* `loading`
|
|
1012
|
-
* `ready`
|
|
1013
|
-
* `error`
|
|
1007
|
+
* `uninitialized` -- fresh service instance, no engine yet.
|
|
1008
|
+
* `loading` -- engine creation or initial image load is in flight.
|
|
1009
|
+
* `ready` -- engine is mounted and the image is on the canvas.
|
|
1010
|
+
* `error` -- engine creation or initial load failed; `errorMessage`
|
|
1014
1011
|
* holds the user-facing reason.
|
|
1015
1012
|
*/
|
|
1016
1013
|
type EditorState = 'uninitialized' | 'loading' | 'ready' | 'error';
|
|
@@ -1025,170 +1022,172 @@ type EditorState = 'uninitialized' | 'loading' | 'ready' | 'error';
|
|
|
1025
1022
|
* implementations in beta.4 reach the engine through `engine()` rather
|
|
1026
1023
|
* than threading domain logic through this service.
|
|
1027
1024
|
*/
|
|
1028
|
-
declare class ImageEditorStateService {
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1025
|
+
export declare class ImageEditorStateService {
|
|
1026
|
+
private readonly engineInstance;
|
|
1027
|
+
readonly state: import("@angular/core").WritableSignal<EditorState>;
|
|
1028
|
+
readonly canvasSize: import("@angular/core").WritableSignal<Size | null>;
|
|
1029
|
+
readonly canUndo: import("@angular/core").WritableSignal<boolean>;
|
|
1030
|
+
readonly canRedo: import("@angular/core").WritableSignal<boolean>;
|
|
1031
|
+
/** The active tool's id, set from the tool rail or a tool's shortcut; null when none is. */
|
|
1032
|
+
readonly activeTool: import("@angular/core").WritableSignal<string | null>;
|
|
1033
|
+
readonly errorMessage: import("@angular/core").WritableSignal<string | null>;
|
|
1034
|
+
/**
|
|
1035
|
+
* Viewport zoom level, where 1.0 = 100%. Mirrors the engine's
|
|
1036
|
+
* canvas zoom; mutated through `zoomIn` / `zoomOut` / `resetZoom` /
|
|
1037
|
+
* `setZoom`, never directly by consumers (the value here must
|
|
1038
|
+
* stay in lockstep with what the engine actually rendered).
|
|
1039
|
+
*/
|
|
1040
|
+
readonly currentZoom: import("@angular/core").WritableSignal<number>;
|
|
1041
|
+
/**
|
|
1042
|
+
* Fit-to-viewport zoom -- the scale at which the loaded image
|
|
1043
|
+
* fills the canvas-mount (with a small margin). Calculated by
|
|
1044
|
+
* the canvas-mount component once it has measured its viewport,
|
|
1045
|
+
* and re-calculated on container resize. Used as the target for
|
|
1046
|
+
* `resetZoom()` and the full `reset()` so "back to 100%" lands
|
|
1047
|
+
* the user at the same view they saw on first mount.
|
|
1048
|
+
*/
|
|
1049
|
+
readonly fitZoom: import("@angular/core").WritableSignal<number>;
|
|
1050
|
+
/**
|
|
1051
|
+
* Hand-tool toggle. When true, mouse-down + drag on the canvas
|
|
1052
|
+
* pans the viewport instead of doing nothing. The cursor swaps
|
|
1053
|
+
* to grab / grabbing via canvas-mount's class bindings.
|
|
1054
|
+
*/
|
|
1055
|
+
readonly handToolActive: import("@angular/core").WritableSignal<boolean>;
|
|
1056
|
+
readonly imageRotation: import("@angular/core").WritableSignal<number>;
|
|
1057
|
+
readonly imageScaleX: import("@angular/core").WritableSignal<number>;
|
|
1058
|
+
readonly imageScaleY: import("@angular/core").WritableSignal<number>;
|
|
1059
|
+
readonly imageFlipX: import("@angular/core").WritableSignal<boolean>;
|
|
1060
|
+
readonly imageFlipY: import("@angular/core").WritableSignal<boolean>;
|
|
1061
|
+
readonly imagePosition: import("@angular/core").WritableSignal<{
|
|
1062
|
+
x: number;
|
|
1063
|
+
y: number;
|
|
1064
|
+
}>;
|
|
1065
|
+
/**
|
|
1066
|
+
* The layer stack (top-most first), mirrored from the engine's
|
|
1067
|
+
* `layersChanged` event. The Layers panel binds to this. (C.1)
|
|
1068
|
+
*/
|
|
1069
|
+
readonly layers: import("@angular/core").WritableSignal<LayerInfo[]>;
|
|
1070
|
+
/** Active layer id, mirrored from `activeObjectChanged`. (C.1) */
|
|
1071
|
+
readonly activeLayerId: import("@angular/core").WritableSignal<string | null>;
|
|
1072
|
+
/** Active layer kind, so style panels know which controls apply. (C.1) */
|
|
1073
|
+
readonly activeLayerKind: import("@angular/core").WritableSignal<LayerKind | null>;
|
|
1074
|
+
/**
|
|
1075
|
+
* The active non-image layer's transform, mirrored from the engine
|
|
1076
|
+
* (C.1c). The Rotate / Flip tools read these via the
|
|
1077
|
+
* `transformTarget*` computeds so a selected shape / text layer is
|
|
1078
|
+
* the transform target instead of the base image.
|
|
1079
|
+
*/
|
|
1080
|
+
readonly activeLayerAngle: import("@angular/core").WritableSignal<number>;
|
|
1081
|
+
readonly activeLayerFlipX: import("@angular/core").WritableSignal<boolean>;
|
|
1082
|
+
readonly activeLayerFlipY: import("@angular/core").WritableSignal<boolean>;
|
|
1083
|
+
/** True when a non-image layer (not the Background) is the active object. */
|
|
1084
|
+
private readonly targetingLayer;
|
|
1085
|
+
/**
|
|
1086
|
+
* The rotation / flip the Rotate + Flip tools bind to: the selected
|
|
1087
|
+
* non-image layer's when one is active, else the base image's. (C.1c)
|
|
1088
|
+
*/
|
|
1089
|
+
readonly transformTargetRotation: import("@angular/core").Signal<number>;
|
|
1090
|
+
readonly transformTargetFlipX: import("@angular/core").Signal<boolean>;
|
|
1091
|
+
readonly transformTargetFlipY: import("@angular/core").Signal<boolean>;
|
|
1092
|
+
/**
|
|
1093
|
+
* Image scale as a percentage rounded to a whole number. Uses the
|
|
1094
|
+
* absolute scale value so a horizontally-flipped image at scale
|
|
1095
|
+
* -1 reads as 100%, not -100%. Flip is reported separately.
|
|
1096
|
+
*/
|
|
1097
|
+
readonly imageScalePercent: import("@angular/core").Signal<number>;
|
|
1098
|
+
/** Read-only handle the toolbar and tools use to drive the engine. */
|
|
1099
|
+
readonly engine: import("@angular/core").Signal<ImageEditorEngine | null>;
|
|
1100
|
+
readonly isReady: import("@angular/core").Signal<boolean>;
|
|
1101
|
+
/**
|
|
1102
|
+
* Create the engine, load the image, and attach the listeners that
|
|
1103
|
+
* keep the history-state signals in sync. Throws (and flips state to
|
|
1104
|
+
* 'error') if either step fails -- the shell surfaces the message
|
|
1105
|
+
* through the canvas-mount overlay.
|
|
1106
|
+
*
|
|
1107
|
+
* The 'uninitialized' guard is what makes the service safe to call
|
|
1108
|
+
* from `ngAfterViewInit`: a re-entrant call (e.g. after a hot
|
|
1109
|
+
* module replace cycle in dev) is a no-op rather than a duplicate
|
|
1110
|
+
* mount.
|
|
1111
|
+
*/
|
|
1112
|
+
initializeEngine(container: HTMLElement, sourceUrl: string, filename: string): Promise<void>;
|
|
1113
|
+
/**
|
|
1114
|
+
* Tear down the engine and reset every signal to its starting
|
|
1115
|
+
* state. Idempotent so the host can call it from `ngOnDestroy`
|
|
1116
|
+
* without checking `state()` first.
|
|
1117
|
+
*/
|
|
1118
|
+
destroyEngine(): void;
|
|
1119
|
+
setActiveTool(toolId: string | null): void;
|
|
1120
|
+
/**
|
|
1121
|
+
* Refresh the cached canvas dimensions after an in-place
|
|
1122
|
+
* operation that the engine doesn't proactively re-measure for
|
|
1123
|
+
* us (Resize tool's primary use). Engine load + initial mount
|
|
1124
|
+
* still write through `initializeEngine`.
|
|
1125
|
+
*/
|
|
1126
|
+
updateCanvasSize(size: Size): void;
|
|
1127
|
+
/**
|
|
1128
|
+
* Viewport zoom controls. Clamped 0.05..10 (5%..1000%) -- wide
|
|
1129
|
+
* range to accommodate fit-zoom for very large images (a 8000px
|
|
1130
|
+
* image in a 600px viewport fits at ~7%) and the occasional
|
|
1131
|
+
* pixel-peeping zoom-in on small images. Step factor of 1.25
|
|
1132
|
+
* matches the convention used by Photoshop, Affinity, and most
|
|
1133
|
+
* desktop image editors.
|
|
1134
|
+
*/
|
|
1135
|
+
private static readonly ZOOM_MIN;
|
|
1136
|
+
private static readonly ZOOM_MAX;
|
|
1137
|
+
private static readonly ZOOM_STEP;
|
|
1138
|
+
setZoom(scale: number): void;
|
|
1139
|
+
zoomIn(): void;
|
|
1140
|
+
zoomOut(): void;
|
|
1141
|
+
resetZoom(): void;
|
|
1142
|
+
/**
|
|
1143
|
+
* Compute and apply the fit-to-viewport zoom for the currently
|
|
1144
|
+
* loaded image. Called by canvas-mount after the engine emits
|
|
1145
|
+
* `imageLoaded` and on container resize. The fabric adapter
|
|
1146
|
+
* owns canvas dimensions, so the returned fit factor drives the
|
|
1147
|
+
* viewport zoom directly (small images upscale beyond 1.0 to
|
|
1148
|
+
* fill the viewport, large images shrink below 1.0 to fit).
|
|
1149
|
+
*/
|
|
1150
|
+
applyFitZoom(viewportSize: Size): void;
|
|
1151
|
+
/**
|
|
1152
|
+
* Update the cached fit zoom without touching the live view.
|
|
1153
|
+
* Used by the resize observer so a window resize while the
|
|
1154
|
+
* user has manually zoomed in doesn't yank them back to fit
|
|
1155
|
+
* mid-edit; the next Reset / "%" click picks up the fresh
|
|
1156
|
+
* value instead.
|
|
1157
|
+
*/
|
|
1158
|
+
recalculateFitZoom(viewportSize: Size): void;
|
|
1159
|
+
toggleHandTool(): void;
|
|
1160
|
+
/**
|
|
1161
|
+
* Top-toolbar Reset button entry point. Restores the canvas to
|
|
1162
|
+
* the pristine loaded state in one call:
|
|
1163
|
+
* - any active tool is closed (its `ngOnDestroy` runs the
|
|
1164
|
+
* tool's deactivation, e.g. `exitCropMode` for the cropper);
|
|
1165
|
+
* - the Hand tool is deactivated;
|
|
1166
|
+
* - the engine reloads the original image and clears both
|
|
1167
|
+
* undo and redo stacks;
|
|
1168
|
+
* - the viewport pan offset is zeroed and the zoom is set to
|
|
1169
|
+
* the fit-to-viewport value (matches what the user saw on
|
|
1170
|
+
* first mount, not the raw 1.0x that would shrink large
|
|
1171
|
+
* images).
|
|
1172
|
+
*
|
|
1173
|
+
* The order matters: tool deactivation first so a mid-edit
|
|
1174
|
+
* cropper doesn't try to re-render against a stale canvas while
|
|
1175
|
+
* the engine is reloading the original image.
|
|
1176
|
+
*/
|
|
1177
|
+
reset(): Promise<void>;
|
|
1178
|
+
private attachEngineListeners;
|
|
1179
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ImageEditorStateService, never>;
|
|
1180
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1184
1181
|
}
|
|
1185
|
-
|
|
1186
1182
|
/**
|
|
1187
|
-
*
|
|
1183
|
+
* Shell for the CoolMS image editor.
|
|
1188
1184
|
*
|
|
1189
|
-
* Composition: top toolbar (action commands + size badge)
|
|
1190
|
-
*
|
|
1191
|
-
*
|
|
1185
|
+
* Composition: top toolbar (action commands + size badge); below it the
|
|
1186
|
+
* tool rail on the left (the tools in their groups, with names or as
|
|
1187
|
+
* icons), the canvas mount (fills the remaining space), and the
|
|
1188
|
+
* properties panel on the right (the active tool's, or the image's own
|
|
1189
|
+
* when no tool is active). A tool's shortcut selects it while the editor
|
|
1190
|
+
* has the keyboard. The footer (Save / Save as / Cancel)
|
|
1192
1191
|
* lives in the host component so the shell can later be embedded in
|
|
1193
1192
|
* non-dialog contexts (inline editing, side panels) where the save
|
|
1194
1193
|
* flow looks different.
|
|
@@ -1202,60 +1201,156 @@ declare class ImageEditorStateService {
|
|
|
1202
1201
|
* `static: true` for its container ViewChild, so by the time this
|
|
1203
1202
|
* component's `ngAfterViewInit` runs the inner `<div #container>`
|
|
1204
1203
|
* exists. The engine adapter is created against that element and the
|
|
1205
|
-
* image is loaded before `engineReady` emits
|
|
1204
|
+
* image is loaded before `engineReady` emits -- consumers never see
|
|
1206
1205
|
* an unloaded engine.
|
|
1207
1206
|
*/
|
|
1208
|
-
declare class CoolmsImageEditorComponent implements AfterViewInit, OnDestroy {
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1207
|
+
export declare class CoolmsImageEditorComponent implements AfterViewInit, OnDestroy {
|
|
1208
|
+
sourceUrl: import("@angular/core").InputSignal<string>;
|
|
1209
|
+
sourceFilename: import("@angular/core").InputSignal<string>;
|
|
1210
|
+
/** Fires after the engine is created and the initial image has loaded. */
|
|
1211
|
+
readonly engineReady: import("@angular/core").OutputEmitterRef<ImageEditorEngine>;
|
|
1212
|
+
/**
|
|
1213
|
+
* Fires if engine creation or initial load throws.
|
|
1214
|
+
*
|
|
1215
|
+
* `failed`, not `error`: an output named after a native DOM event is
|
|
1216
|
+
* ambiguous in a template -- `(error)` could bind either, and which one
|
|
1217
|
+
* wins is not visible to whoever reads the template.
|
|
1218
|
+
*/
|
|
1219
|
+
readonly failed: import("@angular/core").OutputEmitterRef<Error>;
|
|
1220
|
+
protected readonly state: ImageEditorStateService;
|
|
1221
|
+
private readonly registry;
|
|
1222
|
+
private readonly canvasMount;
|
|
1223
|
+
/**
|
|
1224
|
+
* Tracks teardown so an engine that finishes loading after the host
|
|
1225
|
+
* dialog has already been dismissed gets cleaned up promptly. Without
|
|
1226
|
+
* this guard a slow `loadImage` could leave an orphan canvas in the
|
|
1227
|
+
* DOM after `ngOnDestroy`.
|
|
1228
|
+
*/
|
|
1229
|
+
private destroyed;
|
|
1230
|
+
constructor();
|
|
1231
|
+
/**
|
|
1232
|
+
* Public escape hatch the host calls before `engine.export()` to
|
|
1233
|
+
* make sure no tool is mid-edit (e.g., user forgot to Apply a
|
|
1234
|
+
* crop). Setting `activeTool` to `null` destroys the properties
|
|
1235
|
+
* component, whose `ngOnDestroy` runs the tool's deactivation
|
|
1236
|
+
* (e.g., `exitCropMode()`) -- so the export captures clean canvas
|
|
1237
|
+
* pixels with no overlay.
|
|
1238
|
+
*
|
|
1239
|
+
* The adapter's `exportDataUrl()` also defensively calls
|
|
1240
|
+
* `stopDrawingMode()`, but doing it here too makes the
|
|
1241
|
+
* higher-level intent obvious in the host's save flow.
|
|
1242
|
+
*/
|
|
1243
|
+
deactivateActiveTool(): void;
|
|
1244
|
+
/**
|
|
1245
|
+
* Public hook the host calls after an intentional dialog size
|
|
1246
|
+
* change (fullscreen toggle). Delegates to the canvas mount which
|
|
1247
|
+
* re-syncs the fabric canvas dimensions, recalculates fit zoom,
|
|
1248
|
+
* and re-applies it so the image stays centred. The
|
|
1249
|
+
* ResizeObserver inside canvas-mount stays conservative for
|
|
1250
|
+
* casual window resizes (preserves manual zoom).
|
|
1251
|
+
*/
|
|
1252
|
+
refit(): void;
|
|
1253
|
+
ngAfterViewInit(): Promise<void>;
|
|
1254
|
+
/**
|
|
1255
|
+
* Delete on the selected shape or text layer removes it (one undoable step, as the Layers panel's delete). With
|
|
1256
|
+
* nothing selected, or the base image selected, it does nothing. True when it removed a layer.
|
|
1257
|
+
*/
|
|
1258
|
+
onKeydown(event: KeyboardEvent): boolean;
|
|
1259
|
+
ngOnDestroy(): void;
|
|
1260
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CoolmsImageEditorComponent, never>;
|
|
1261
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<CoolmsImageEditorComponent, "coolms-image-editor", never, {
|
|
1262
|
+
"sourceUrl": {
|
|
1263
|
+
"alias": "sourceUrl";
|
|
1264
|
+
"required": true;
|
|
1265
|
+
"isSignal": true;
|
|
1266
|
+
};
|
|
1267
|
+
"sourceFilename": {
|
|
1268
|
+
"alias": "sourceFilename";
|
|
1269
|
+
"required": true;
|
|
1270
|
+
"isSignal": true;
|
|
1271
|
+
};
|
|
1272
|
+
}, {
|
|
1273
|
+
"engineReady": "engineReady";
|
|
1274
|
+
"failed": "failed";
|
|
1275
|
+
}, never, never, true, never>;
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* The image editor's own text: keys, and the base catalogue that holds their English. A host translates by handing the
|
|
1279
|
+
* editor a `Translator`; one that does not hear a key answers what the base catalogue holds, never the key.
|
|
1280
|
+
*
|
|
1281
|
+
* Imports nothing outside `src/core/` (the lint rule for this directory refuses it).
|
|
1282
|
+
*/
|
|
1283
|
+
/** Answers a key's text, `%name%` placeholders filled from `params`. */
|
|
1284
|
+
type Translator = (key: string, params?: Readonly<Record<string, string | number>>) => string;
|
|
1285
|
+
export declare const IMAGE_EDITOR_MESSAGES: Readonly<Record<string, string>>;
|
|
1286
|
+
/** Fills `%name%` placeholders; a placeholder with no value is left as it is. */
|
|
1287
|
+
declare function fill(text: string, params?: Readonly<Record<string, string | number>>): string;
|
|
1288
|
+
/** The base catalogue's translator: a key it does not hold answers the key itself, so a gap shows in development. */
|
|
1289
|
+
export declare const baseTranslator: Translator;
|
|
1290
|
+
/**
|
|
1291
|
+
* A host's catalogue in front of the base one: a key the host does not hold, or holds blank, answers the base
|
|
1292
|
+
* catalogue's English. A partial translation degrades one message at a time, never to a key or an empty label.
|
|
1293
|
+
*/
|
|
1294
|
+
export declare function catalogueTranslator(messages: Readonly<Record<string, string>>): Translator;
|
|
1295
|
+
/**
|
|
1296
|
+
* The image editor's tools as data, with no framework: what a tool is called, where it sits on the rail, and which key
|
|
1297
|
+
* selects it. An Angular or React host maps a tool's id to its own properties component; nothing here names one.
|
|
1298
|
+
*
|
|
1299
|
+
* Imports nothing outside `src/core/` (the lint rule for this directory refuses it).
|
|
1300
|
+
*/
|
|
1301
|
+
/** The rail's groups, in the order they are drawn. */
|
|
1302
|
+
type ToolGroup = 'transform' | 'insert' | 'filter' | 'layers';
|
|
1303
|
+
declare const TOOL_GROUPS: readonly {
|
|
1304
|
+
readonly id: ToolGroup;
|
|
1305
|
+
readonly labelKey: string;
|
|
1306
|
+
}[];
|
|
1307
|
+
interface ToolDefinition {
|
|
1308
|
+
readonly id: string;
|
|
1309
|
+
/** The tool's name, as a message key (`imageEditor.tool.<id>`). */
|
|
1310
|
+
readonly labelKey: string;
|
|
1311
|
+
/** A Bootstrap Icons class. */
|
|
1312
|
+
readonly icon: string;
|
|
1313
|
+
readonly group: ToolGroup;
|
|
1314
|
+
/** Its place within the group, smallest first. */
|
|
1315
|
+
readonly order: number;
|
|
1316
|
+
/** One printable key that selects the tool while the editor has the keyboard, e.g. `c`. */
|
|
1317
|
+
readonly shortcut?: string;
|
|
1257
1318
|
}
|
|
1258
|
-
|
|
1319
|
+
/** How the rail draws its tools: with their names, or as icons whose names show as tooltips. */
|
|
1320
|
+
type RailMode = 'labels' | 'icons';
|
|
1321
|
+
export declare const DEFAULT_RAIL_MODE: RailMode;
|
|
1322
|
+
/** A stored preference read back: anything but a known mode is the default. */
|
|
1323
|
+
export declare function railModeFrom(stored: unknown): RailMode;
|
|
1324
|
+
/** The tools of each group, groups in rail order and tools by `order` then id; empty groups left out. */
|
|
1325
|
+
declare function groupTools<T extends ToolDefinition>(tools: readonly T[]): {
|
|
1326
|
+
group: ToolGroup;
|
|
1327
|
+
labelKey: string;
|
|
1328
|
+
tools: T[];
|
|
1329
|
+
}[];
|
|
1330
|
+
/**
|
|
1331
|
+
* The tool a key selects: an unmodified printable key equal to a tool's shortcut (case ignored), or null. A key with
|
|
1332
|
+
* Ctrl, Alt or Meta is never a tool's: those belong to the editor's commands and the browser.
|
|
1333
|
+
*/
|
|
1334
|
+
declare function toolForKey<T extends ToolDefinition>(tools: readonly T[], event: {
|
|
1335
|
+
readonly key: string;
|
|
1336
|
+
readonly ctrlKey: boolean;
|
|
1337
|
+
readonly altKey: boolean;
|
|
1338
|
+
readonly metaKey: boolean;
|
|
1339
|
+
}): T | null;
|
|
1340
|
+
/**
|
|
1341
|
+
* The editor's text. A host provides its own translator (the platform's catalogues, say); without one the editor speaks
|
|
1342
|
+
* the base catalogue's English (`core/messages.ts`).
|
|
1343
|
+
*/
|
|
1344
|
+
export declare const IMAGE_EDITOR_TRANSLATOR: InjectionToken<Translator>;
|
|
1345
|
+
/** Where the rail's mode is remembered. The admin keeps it in the person's preferences, so it follows them. */
|
|
1346
|
+
interface RailModeStore {
|
|
1347
|
+
/** The mode last chosen, or null when none was. */
|
|
1348
|
+
read(): RailMode | null;
|
|
1349
|
+
write(mode: RailMode): void;
|
|
1350
|
+
}
|
|
1351
|
+
/** Remembered for this page load only: what a host without preferences gets. */
|
|
1352
|
+
export declare function sessionRailModeStore(): RailModeStore;
|
|
1353
|
+
export declare const IMAGE_EDITOR_RAIL_MODE: InjectionToken<RailModeStore>;
|
|
1259
1354
|
/**
|
|
1260
1355
|
* Wire-shape types for the CDK Dialog host.
|
|
1261
1356
|
*
|
|
@@ -1264,13 +1359,13 @@ declare class CoolmsImageEditorComponent implements AfterViewInit, OnDestroy {
|
|
|
1264
1359
|
* caller having to know which one. Each branch carries exactly the
|
|
1265
1360
|
* fields that branch needs:
|
|
1266
1361
|
*
|
|
1267
|
-
* - **Media context**
|
|
1362
|
+
* - **Media context** -- the asset is a registered `MediaAsset`,
|
|
1268
1363
|
* so save flows through Media's APIs (`POST /media/{id}/replace`
|
|
1269
1364
|
* for overwrite, `POST /media/upload` for save-as-new). The
|
|
1270
1365
|
* asset's `path` carries the VFS location; we derive the parent
|
|
1271
1366
|
* directory from it for the save-as-new target.
|
|
1272
1367
|
*
|
|
1273
|
-
* - **VFS context**
|
|
1368
|
+
* - **VFS context** -- the file is a plain VFS file with no
|
|
1274
1369
|
* MediaAsset record (e.g., a theme asset, a template image).
|
|
1275
1370
|
* Save flows through VFS's binary endpoint
|
|
1276
1371
|
* (`POST /vfs/files/binary`). `canWrite` gates the Save button:
|
|
@@ -1279,67 +1374,88 @@ declare class CoolmsImageEditorComponent implements AfterViewInit, OnDestroy {
|
|
|
1279
1374
|
*/
|
|
1280
1375
|
type CoolmsImageEditorHostData = CoolmsImageEditorMediaContext | CoolmsImageEditorVfsContext;
|
|
1281
1376
|
interface CoolmsImageEditorMediaContext {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
/**
|
|
1308
|
-
readonly
|
|
1377
|
+
readonly context: 'media';
|
|
1378
|
+
readonly asset: {
|
|
1379
|
+
/** Maps to `MediaAsset.id`; backend resolves the VFS node from it. */
|
|
1380
|
+
readonly uuid: string;
|
|
1381
|
+
/** Source URL the engine loads via `loadImage(url)` when no `sourceUrl` is given. */
|
|
1382
|
+
readonly originalUrl: string;
|
|
1383
|
+
/**
|
|
1384
|
+
* Where the asset's own bytes are read with their version (the response's ETag), as for a file: the version a
|
|
1385
|
+
* replace names as If-Match is the version of the pixels opened, from that one read (2026-10-15). Without it
|
|
1386
|
+
* the image is shown from `originalUrl`, which names no version, and Save is off: the edits go to a new image.
|
|
1387
|
+
*/
|
|
1388
|
+
readonly sourceUrl?: string;
|
|
1389
|
+
/** Display label in the dialog header and default save-as filename. */
|
|
1390
|
+
readonly filename: string;
|
|
1391
|
+
/** Picks the export format (source MIME wins per the existing contract). */
|
|
1392
|
+
readonly mimeType: string;
|
|
1393
|
+
/**
|
|
1394
|
+
* Materialised VFS path of the asset (e.g., `/media/uploads/photo.jpg`).
|
|
1395
|
+
* The save-as-new flow derives the target directory from this -- the
|
|
1396
|
+
* new asset lands in the same collection as the source. Optional
|
|
1397
|
+
* because `MediaAssetDto.path` can be null for fresh upload responses;
|
|
1398
|
+
* callers set this when they have it (the editor falls back to
|
|
1399
|
+
* `/media/uploads` when absent).
|
|
1400
|
+
*/
|
|
1401
|
+
readonly path?: string | null;
|
|
1402
|
+
/** Pre-load dimensions surfaced in the header before the engine reports its own. */
|
|
1403
|
+
readonly dimensions: {
|
|
1404
|
+
readonly width: number;
|
|
1405
|
+
readonly height: number;
|
|
1406
|
+
} | null;
|
|
1407
|
+
};
|
|
1408
|
+
/** BCP-47 locale; held for future locale work, beta.X doesn't read it. */
|
|
1409
|
+
readonly locale?: string;
|
|
1309
1410
|
}
|
|
1310
1411
|
interface CoolmsImageEditorVfsContext {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1412
|
+
readonly context: 'vfs';
|
|
1413
|
+
readonly node: {
|
|
1414
|
+
/** Absolute VFS path of the file. */
|
|
1415
|
+
readonly path: string;
|
|
1416
|
+
/** The file's node id: the host's file watch follows it to say when it changes or goes elsewhere. */
|
|
1417
|
+
readonly id?: string;
|
|
1418
|
+
/** Caller's effective write permission on the file (mirrors `VfsNodeDto.permissions.write`). */
|
|
1419
|
+
readonly canWrite: boolean;
|
|
1420
|
+
/**
|
|
1421
|
+
* Offered when `canWrite` is false: what Save does INSTEAD of being
|
|
1422
|
+
* dead (the elevation client requirement, point 2). The caller asks
|
|
1423
|
+
* the server for elevation and resolves true when the session has
|
|
1424
|
+
* it; the host then treats the file as writable and saves. Absent,
|
|
1425
|
+
* Save is disabled as before. The host decides nothing itself.
|
|
1426
|
+
*/
|
|
1427
|
+
readonly requestWrite?: () => Promise<boolean>;
|
|
1428
|
+
/**
|
|
1429
|
+
* Where the file's own bytes are read, and with them their version (the response's ETag): the version an
|
|
1430
|
+
* overwrite names as If-Match is the version of the pixels the editor opened, from that one read -- a file
|
|
1431
|
+
* changed after it was listed is edited as it is now, and a copy served from a cache carries its own older
|
|
1432
|
+
* version, so its save is refused (412), never written over the newer file (2026-10-15). A read that names no
|
|
1433
|
+
* version leaves Save off: the edits can go to a new image only.
|
|
1434
|
+
*/
|
|
1435
|
+
readonly sourceUrl: string;
|
|
1436
|
+
/** Display label in the dialog header and default save-as filename. */
|
|
1437
|
+
readonly filename: string;
|
|
1438
|
+
/** Picks the export format. */
|
|
1439
|
+
readonly mimeType: string;
|
|
1440
|
+
/** Parent directory; the save-as-new flow joins `${parentPath}/${newName}`. */
|
|
1441
|
+
readonly parentPath: string;
|
|
1442
|
+
/** Optional dimensions; null when not available pre-load. */
|
|
1443
|
+
readonly dimensions?: {
|
|
1444
|
+
readonly width: number;
|
|
1445
|
+
readonly height: number;
|
|
1446
|
+
} | null;
|
|
1447
|
+
};
|
|
1331
1448
|
}
|
|
1332
1449
|
type CoolmsImageEditorHostResult = {
|
|
1333
|
-
|
|
1450
|
+
readonly kind: 'cancelled';
|
|
1334
1451
|
} | {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1452
|
+
readonly kind: 'saved';
|
|
1453
|
+
readonly mode: 'overwrite' | 'save_as_new';
|
|
1454
|
+
/** Set on the Media context. */
|
|
1455
|
+
readonly assetUuid?: string;
|
|
1456
|
+
/** Set on the VFS context. */
|
|
1457
|
+
readonly vfsPath?: string;
|
|
1341
1458
|
};
|
|
1342
|
-
|
|
1343
1459
|
type HostState = 'preparing' | 'editing' | 'saving' | 'error';
|
|
1344
1460
|
/**
|
|
1345
1461
|
* CDK Dialog host wrapping `CoolmsImageEditorComponent`.
|
|
@@ -1359,7 +1475,7 @@ type HostState = 'preparing' | 'editing' | 'saving' | 'error';
|
|
|
1359
1475
|
*
|
|
1360
1476
|
* Save button gating: in the VFS context, `canWrite=false` disables
|
|
1361
1477
|
* Save (overwriting a read-only file would 403 anyway) but Save as
|
|
1362
|
-
* stays enabled
|
|
1478
|
+
* stays enabled -- the user can always save a copy if they have
|
|
1363
1479
|
* write permission on the parent directory.
|
|
1364
1480
|
*
|
|
1365
1481
|
* Failures stay non-fatal: the dialog stays open with state set back
|
|
@@ -1367,122 +1483,153 @@ type HostState = 'preparing' | 'editing' | 'saving' | 'error';
|
|
|
1367
1483
|
* descriptive message (status code -> friendly label, or `.message`
|
|
1368
1484
|
* fallback). The engine remains live throughout.
|
|
1369
1485
|
*/
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1486
|
+
/**
|
|
1487
|
+
* The format Save writes a source back as, or null when Save may not overwrite it: only PNG and
|
|
1488
|
+
* JPEG, which the editor encodes as themselves. GIF (animated or not), SVG, TIFF, HEIC, BMP and WebP -- until its export
|
|
1489
|
+
* is verified in every supported browser -- have Save as only: overwriting them would put PNG or JPEG bytes under their
|
|
1490
|
+
* name, and an animation or vectors would be lost.
|
|
1491
|
+
*/
|
|
1492
|
+
declare function writtenBackAs(mimeType: string): 'png' | 'jpeg' | null;
|
|
1493
|
+
export declare class CoolmsImageEditorHostComponent implements OnInit, OnDestroy {
|
|
1494
|
+
private readonly cdkDialog;
|
|
1495
|
+
private readonly dialogRef;
|
|
1496
|
+
private readonly editService;
|
|
1497
|
+
private readonly toast;
|
|
1498
|
+
private readonly http;
|
|
1499
|
+
private readonly esc;
|
|
1500
|
+
private readonly confirm;
|
|
1501
|
+
private readonly unsaved;
|
|
1502
|
+
private readonly destroyRef;
|
|
1503
|
+
protected readonly data: CoolmsImageEditorHostData;
|
|
1504
|
+
/**
|
|
1505
|
+
* The file was changed or deleted elsewhere while the editor is open (2026-10-14: "the editor says so, from the VFS
|
|
1506
|
+
* change event"). Said above the canvas; Save then answers 412 / 404 and goes to "Save as".
|
|
1507
|
+
*/
|
|
1508
|
+
protected readonly changedElsewhere: import("@angular/core").WritableSignal<"changed" | "deleted" | null>;
|
|
1509
|
+
readonly state: import("@angular/core").WritableSignal<HostState>;
|
|
1510
|
+
readonly errorMessage: import("@angular/core").WritableSignal<string | null>;
|
|
1511
|
+
/**
|
|
1512
|
+
* Object URL fed into the embedded shell. Resolved asynchronously in
|
|
1513
|
+
* `ngOnInit`: VFS sources go through HttpClient (so the auth interceptor
|
|
1514
|
+
* attaches the Bearer token) and become a `blob:` URL the engine can
|
|
1515
|
+
* load via a regular `<img>` element. Media sources are public, so the
|
|
1516
|
+
* original URL passes through unchanged.
|
|
1517
|
+
*/
|
|
1518
|
+
readonly resolvedSourceUrl: import("@angular/core").WritableSignal<string | null>;
|
|
1519
|
+
/** Tracks the blob: URL we own so `ngOnDestroy` can revoke it. */
|
|
1520
|
+
private ownedObjectUrl;
|
|
1521
|
+
/**
|
|
1522
|
+
* Captured the moment the shell finishes loading the image. Save
|
|
1523
|
+
* actions check this through the `engineReadyOk` signal so the
|
|
1524
|
+
* Save buttons only enable once an engine handle is available.
|
|
1525
|
+
*/
|
|
1526
|
+
private engine;
|
|
1527
|
+
private readonly shell?;
|
|
1528
|
+
readonly engineReadyOk: import("@angular/core").WritableSignal<boolean>;
|
|
1529
|
+
readonly isSaving: import("@angular/core").Signal<boolean>;
|
|
1530
|
+
/**
|
|
1531
|
+
* Set once the caller's `requestWrite` resolved true: the server elevated
|
|
1532
|
+
* the session, so the flag it computed before no longer describes it.
|
|
1533
|
+
*/
|
|
1534
|
+
private readonly writeGranted;
|
|
1535
|
+
/** Whether the VFS file is writable: the flag, or an elevation granted since. */
|
|
1536
|
+
private readonly canWrite;
|
|
1537
|
+
/**
|
|
1538
|
+
* Save (overwrite) is gated on engine readiness AND, for VFS context,
|
|
1539
|
+
* write permission -- unless the caller offered `requestWrite`, in which
|
|
1540
|
+
* case the button stays live and asks for elevation instead of sitting
|
|
1541
|
+
* dead (the control stays live and the server decides).
|
|
1542
|
+
*/
|
|
1543
|
+
readonly canSave: import("@angular/core").Signal<boolean>;
|
|
1544
|
+
/** The version of the bytes the editor opened: the ETag of the read that delivered them. */
|
|
1545
|
+
private readonly readVersion;
|
|
1546
|
+
private readonly canRequestWrite;
|
|
1547
|
+
/** Save as is gated only on engine readiness -- even read-only sources can be saved as new. */
|
|
1548
|
+
readonly canSaveAs: import("@angular/core").Signal<boolean>;
|
|
1549
|
+
readonly canClose: import("@angular/core").Signal<boolean>;
|
|
1550
|
+
/** Filename header label and default save-as name. */
|
|
1551
|
+
readonly filename: import("@angular/core").Signal<string>;
|
|
1552
|
+
/** Source MIME, drives export format. */
|
|
1553
|
+
readonly mimeType: import("@angular/core").Signal<string>;
|
|
1554
|
+
/** Whether Save may overwrite the source: the editor writes it back in its own format. */
|
|
1555
|
+
readonly savesInItsOwnFormat: import("@angular/core").Signal<boolean>;
|
|
1556
|
+
/** Why Save is off for this format, said on the button; null when it is not the format. */
|
|
1557
|
+
readonly saveFormatNote: import("@angular/core").Signal<"Save writes an image back only as PNG or JPEG, in its own format. Use Save as for this one." | null>;
|
|
1558
|
+
readonly dimensionsLabel: import("@angular/core").Signal<string>;
|
|
1559
|
+
/**
|
|
1560
|
+
* CSS-only fullscreen mode, mirroring viewer-modal's pattern.
|
|
1561
|
+
* Toggle from the header button or by dblclicking a non-interactive
|
|
1562
|
+
* area of the host. ESC exits fullscreen via the EscCoordinator
|
|
1563
|
+
* stack so it composes cleanly with any future dialog-level ESC
|
|
1564
|
+
* handlers; the host opens with CDK `disableClose: true`, so the
|
|
1565
|
+
* dialog itself never closes on ESC -- only the X / Cancel buttons
|
|
1566
|
+
* exit the editor.
|
|
1567
|
+
*/
|
|
1568
|
+
readonly isFullscreen: import("@angular/core").WritableSignal<boolean>;
|
|
1569
|
+
get fullscreenClass(): boolean;
|
|
1570
|
+
toggleFullscreen(): void;
|
|
1571
|
+
/**
|
|
1572
|
+
* Double-click anywhere on the dialog toggles fullscreen, EXCEPT
|
|
1573
|
+
* on interactive controls and the editor canvas. The canvas is
|
|
1574
|
+
* the primary work surface and fabric.js may bind its own
|
|
1575
|
+
* dblclick semantics (object enter, text edit, etc.); excluding
|
|
1576
|
+
* it keeps that path clear.
|
|
1577
|
+
*/
|
|
1578
|
+
protected onDblClick(event: MouseEvent): void;
|
|
1579
|
+
constructor();
|
|
1580
|
+
ngOnInit(): Promise<void>;
|
|
1581
|
+
ngOnDestroy(): void;
|
|
1582
|
+
onEngineReady(engine: ImageEditorEngine): void;
|
|
1583
|
+
onShellError(err: Error): void;
|
|
1584
|
+
onSave(): Promise<void>;
|
|
1585
|
+
onSaveAs(): Promise<void>;
|
|
1586
|
+
/**
|
|
1587
|
+
* `canClose()` only ever meant "not mid-save" -- there was no dirty
|
|
1588
|
+
* concept here at all, so a crop, a rotate and three filters were
|
|
1589
|
+
* thrown away by Cancel without a word.
|
|
1590
|
+
*
|
|
1591
|
+
* `engine.canUndo()` is the dirty flag we would otherwise have had to
|
|
1592
|
+
* invent: it is true exactly when the user has done something undoable,
|
|
1593
|
+
* and it goes back to false if they undo their way to the start.
|
|
1594
|
+
*/
|
|
1595
|
+
onCancel(): void;
|
|
1596
|
+
/** The engine's undo depth IS "has anything been edited". */
|
|
1597
|
+
private hasEdits;
|
|
1598
|
+
private executeSave;
|
|
1599
|
+
private saveMedia;
|
|
1600
|
+
private saveVfs;
|
|
1601
|
+
/**
|
|
1602
|
+
* Pull a user-readable message out of whatever the HTTP layer
|
|
1603
|
+
* threw at us. Angular's `HttpErrorResponse` implements the
|
|
1604
|
+
* `Error` interface but doesn't extend the class, so a plain
|
|
1605
|
+
* `instanceof Error` check misses it and we'd fall through to
|
|
1606
|
+
* "unknown error".
|
|
1607
|
+
*/
|
|
1608
|
+
private describeError;
|
|
1609
|
+
/**
|
|
1610
|
+
* Export format. Content wins over source: when the flattened image
|
|
1611
|
+
* has any transparency (a mask / clipPath cut-out, or a transparent
|
|
1612
|
+
* source) we force PNG, because flattening it onto an opaque JPEG
|
|
1613
|
+
* loses the cut-away region to opaque black (the C.1d fix -- masked
|
|
1614
|
+
* Save-as previously baked black corners). Otherwise it follows the
|
|
1615
|
+
* source MIME (PNG stays PNG, everything else -> JPEG, the two formats
|
|
1616
|
+
* the underlying canvas reliably encodes).
|
|
1617
|
+
*/
|
|
1618
|
+
private deriveFormat;
|
|
1619
|
+
/** Whether the current edit has transparency the export must preserve. */
|
|
1620
|
+
private contentWantsPng;
|
|
1621
|
+
/**
|
|
1622
|
+
* Normalise a filename's extension to match the export format, so a
|
|
1623
|
+
* forced-PNG save (transparency preserved) doesn't keep a `.jpg`
|
|
1624
|
+
* name. `jpg`/`jpeg` both satisfy the JPEG format.
|
|
1625
|
+
*/
|
|
1626
|
+
private ensureExtension;
|
|
1627
|
+
private deriveSaveAsName;
|
|
1628
|
+
private parentDir;
|
|
1629
|
+
private joinPath;
|
|
1630
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CoolmsImageEditorHostComponent, never>;
|
|
1631
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<CoolmsImageEditorHostComponent, "coolms-image-editor-host", never, {}, {}, never, never, true, never>;
|
|
1485
1632
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
export type { AnnotationShapeOptions, Bounds, CoolmsImageEditorHostData, CoolmsImageEditorHostResult, EditorState, EngineEventHandler, EngineEventType, EngineEvents, EngineOptions, FilterName, FilterParams, ImageEditorEngine, Point, Rect, ShapeGeometry, Size };
|
|
1633
|
+
/** The version an ETag names: `"abc"` and `W/"abc"` are both `abc`; no ETag, no version. */
|
|
1634
|
+
declare function versionOf(etag: string | null): string | null;
|
|
1635
|
+
export type { AnnotationShapeOptions, Bounds, CoolmsImageEditorHostData, CoolmsImageEditorHostResult, EditorState, EngineEventHandler, EngineEventType, EngineEvents, EngineOptions, FilterName, FilterParams, ImageEditorEngine, Point, RailMode, RailModeStore, Rect, ShapeGeometry, Size, ToolDefinition, ToolGroup, Translator };
|