@ascentsparksoftware/angular-image-editor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,1205 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { OnDestroy } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A bounded linear undo/redo history.
|
|
6
|
+
*
|
|
7
|
+
* Each entry pairs a human label (for the History panel) with an opaque state
|
|
8
|
+
* snapshot (the editor stores serialized Fabric scenes here). Pure and fully
|
|
9
|
+
* unit-testable — it holds no Fabric references.
|
|
10
|
+
*/
|
|
11
|
+
interface HistoryEntry<T> {
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly state: T;
|
|
14
|
+
}
|
|
15
|
+
declare class EditHistory<T> {
|
|
16
|
+
private readonly stack;
|
|
17
|
+
private cursor;
|
|
18
|
+
private readonly maxEntries;
|
|
19
|
+
constructor(initialLabel: string, initialState: T, maxEntries?: number);
|
|
20
|
+
/** All retained entries, oldest first. */
|
|
21
|
+
get entries(): readonly HistoryEntry<T>[];
|
|
22
|
+
/** Index of the current entry within {@link entries}. */
|
|
23
|
+
get index(): number;
|
|
24
|
+
get length(): number;
|
|
25
|
+
get current(): HistoryEntry<T>;
|
|
26
|
+
get canUndo(): boolean;
|
|
27
|
+
get canRedo(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Record a new state. Any redo branch ahead of the cursor is discarded, and
|
|
30
|
+
* the oldest entries are dropped once the cap is exceeded.
|
|
31
|
+
*/
|
|
32
|
+
push(label: string, state: T): void;
|
|
33
|
+
/** Step back one entry, or return `null` if already at the start. */
|
|
34
|
+
undo(): HistoryEntry<T> | null;
|
|
35
|
+
/** Step forward one entry, or return `null` if already at the end. */
|
|
36
|
+
redo(): HistoryEntry<T> | null;
|
|
37
|
+
/** Replace the entire history with a single fresh entry. */
|
|
38
|
+
reset(label: string, state: T): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A bounded linear undo/redo history that stores **diffs**, not full snapshots.
|
|
43
|
+
*
|
|
44
|
+
* The editor serializes its whole scene (often hundreds of KB once an image is
|
|
45
|
+
* loaded) on every edit. Keeping 50 full snapshots would cost tens of MB. This
|
|
46
|
+
* history keeps one full base plus a chain of jsondiffpatch deltas — each
|
|
47
|
+
* incremental edit (move a shape, tweak a color) is a sub-KB delta — and
|
|
48
|
+
* reconstructs any state on demand by replaying deltas from the base.
|
|
49
|
+
*
|
|
50
|
+
* Behavior is identical to a full-snapshot stack: `current`/`undo`/`redo`
|
|
51
|
+
* return the exact serialized state, branches truncate on push, and the oldest
|
|
52
|
+
* entries drop once the cap is exceeded (the new oldest is re-materialized into
|
|
53
|
+
* the base so the chain stays valid). Pure and unit-testable — no Fabric refs.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/** Minimal projection of an entry for the History panel (labels only). */
|
|
57
|
+
interface HistoryStep {
|
|
58
|
+
readonly label: string;
|
|
59
|
+
}
|
|
60
|
+
declare class DeltaHistory {
|
|
61
|
+
private readonly differ;
|
|
62
|
+
private readonly stack;
|
|
63
|
+
private cursor;
|
|
64
|
+
private readonly maxEntries;
|
|
65
|
+
constructor(initialLabel: string, initialState: string, maxEntries?: number);
|
|
66
|
+
/** Labels for every retained entry, oldest first (for the History panel). */
|
|
67
|
+
get entries(): readonly HistoryStep[];
|
|
68
|
+
get index(): number;
|
|
69
|
+
get length(): number;
|
|
70
|
+
get current(): HistoryEntry<string>;
|
|
71
|
+
/** The base entry (index 0) — used by the engine's full "reset edits" action. */
|
|
72
|
+
get first(): HistoryEntry<string>;
|
|
73
|
+
get canUndo(): boolean;
|
|
74
|
+
get canRedo(): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Record a new state. The redo branch ahead of the cursor is discarded, and
|
|
77
|
+
* the oldest entries drop once the cap is exceeded.
|
|
78
|
+
*/
|
|
79
|
+
push(label: string, state: string): void;
|
|
80
|
+
/** Step back one entry, or return `null` if already at the start. */
|
|
81
|
+
undo(): HistoryEntry<string> | null;
|
|
82
|
+
/** Step forward one entry, or return `null` if already at the end. */
|
|
83
|
+
redo(): HistoryEntry<string> | null;
|
|
84
|
+
/** Replace the entire history with a single fresh base entry. */
|
|
85
|
+
reset(label: string, state: string): void;
|
|
86
|
+
/** Serialized byte length of the delta retained for an entry (for tests/metrics). */
|
|
87
|
+
retainedDeltaBytes(index: number): number;
|
|
88
|
+
/** Reconstruct the serialized state at an index by replaying deltas from the base. */
|
|
89
|
+
private entryAt;
|
|
90
|
+
/**
|
|
91
|
+
* Replay the delta chain from the base (index 0) up to `targetIndex`. The
|
|
92
|
+
* base is deep-cloned first so patching never mutates retained state.
|
|
93
|
+
*/
|
|
94
|
+
private reconstruct;
|
|
95
|
+
/**
|
|
96
|
+
* Drop the base entry and promote the next one to a fresh base by
|
|
97
|
+
* materializing its full state (its delta no longer has a predecessor).
|
|
98
|
+
*/
|
|
99
|
+
private dropOldest;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Public type contract for the editor.
|
|
104
|
+
*
|
|
105
|
+
* The tool and filter unions are derived from `as const` source arrays so the
|
|
106
|
+
* registry (and any exhaustiveness check) is structurally guaranteed to cover
|
|
107
|
+
* every member — there is one source of truth, not a union plus a parallel list
|
|
108
|
+
* that can drift.
|
|
109
|
+
*/
|
|
110
|
+
/** Layout/baseline preset — sets the chrome and a default tool set. */
|
|
111
|
+
type AspMode = 'viewer' | 'basic' | 'advanced' | 'full';
|
|
112
|
+
/**
|
|
113
|
+
* A host-controllable size: a number (interpreted as `px`) or any CSS length —
|
|
114
|
+
* `'600px'`, `'70%'`, `'80vh'`, `'calc(100vh - 120px)'`. Used for the editor's
|
|
115
|
+
* `width`/`height` inputs. A per-mode minimum is always enforced on top so the
|
|
116
|
+
* toolbars and panels keep enough room to render.
|
|
117
|
+
*/
|
|
118
|
+
type AspSize = number | string;
|
|
119
|
+
/** Every tool the editor can expose, grouped by capability. Source of truth for {@link AspTool}. */
|
|
120
|
+
declare const ALL_TOOLS: readonly ["crop", "rotate", "straighten", "flip", "resize", "pen", "highlighter", "eraser", "shapes", "arrow", "line", "text", "sticker", "redact", "magicwand", "removebg", "selectsubject", "adjust", "filters", "select", "layers", "duplicate", "delete", "opacity", "align", "group", "background", "frame"];
|
|
121
|
+
type AspTool = (typeof ALL_TOOLS)[number];
|
|
122
|
+
/** Fabric.js built-in filters exposed by the editor. Source of truth for {@link AspFilter}. */
|
|
123
|
+
declare const ALL_FILTERS: readonly ["brightness", "contrast", "saturation", "vibrance", "hue", "blur", "sharpen", "grayscale", "sepia", "invert", "pixelate", "noise", "gamma", "blendColor"];
|
|
124
|
+
type AspFilter = (typeof ALL_FILTERS)[number];
|
|
125
|
+
/** Export formats. `json` serializes the re-editable Fabric scene; `pdf` embeds a raster. */
|
|
126
|
+
type AspExportFormat = 'png' | 'jpeg' | 'webp' | 'svg' | 'json' | 'pdf';
|
|
127
|
+
/** A structured error surfaced via the `errorOccurred` output. */
|
|
128
|
+
interface AspEditorError {
|
|
129
|
+
/** Stable machine code, e.g. `'load-failed'`, `'export-failed'`, `'engine-init-failed'`. */
|
|
130
|
+
readonly code: string;
|
|
131
|
+
readonly message: string;
|
|
132
|
+
}
|
|
133
|
+
/** Crop aspect-ratio presets. */
|
|
134
|
+
type AspAspectPreset = 'free' | '1:1' | '4:3' | '16:9' | '3:2' | 'original';
|
|
135
|
+
/**
|
|
136
|
+
* A host-defined crop aspect option — e.g. a CMS target like a 1200×630 social
|
|
137
|
+
* image. `ratio` is width / height; build it from explicit dimensions with
|
|
138
|
+
* {@link aspectOption}.
|
|
139
|
+
*/
|
|
140
|
+
interface AspAspectOption {
|
|
141
|
+
readonly label: string;
|
|
142
|
+
readonly ratio: number;
|
|
143
|
+
}
|
|
144
|
+
/** Build an {@link AspAspectOption} from explicit pixel dimensions. */
|
|
145
|
+
declare function aspectOption(width: number, height: number, label?: string): AspAspectOption;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* EditorEngine — the only component that talks to Fabric.js directly.
|
|
149
|
+
*
|
|
150
|
+
* It owns a Fabric `Canvas` holding one base `FabricImage` (the photo) plus
|
|
151
|
+
* annotation objects on top, and exposes high-level editor operations: load,
|
|
152
|
+
* zoom/pan, rotate/flip/straighten, crop, adjustments/filters, shapes/text/
|
|
153
|
+
* free-draw, undo/redo (serialized-scene snapshots), and export. UI components
|
|
154
|
+
* call these methods and never import Fabric themselves.
|
|
155
|
+
*
|
|
156
|
+
* Not unit-tested in jsdom (Fabric needs a real canvas/WebGL); verified through
|
|
157
|
+
* the demo with Playwright screenshots, per the project's visual-truth rule.
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
interface EngineOptions {
|
|
161
|
+
readonly width: number;
|
|
162
|
+
readonly height: number;
|
|
163
|
+
}
|
|
164
|
+
type ShapeKind = 'rect' | 'ellipse' | 'line' | 'arrow' | 'triangle' | 'diamond' | 'pentagon' | 'hexagon' | 'star';
|
|
165
|
+
type RedactMode = 'blur' | 'pixelate' | 'solid';
|
|
166
|
+
interface AnnotationStyle {
|
|
167
|
+
readonly color: string;
|
|
168
|
+
readonly strokeWidth: number;
|
|
169
|
+
/** Rectangle corner radius (px, in object coordinates). `0`/absent = sharp corners. */
|
|
170
|
+
readonly cornerRadius?: number;
|
|
171
|
+
}
|
|
172
|
+
interface TextStyle {
|
|
173
|
+
readonly color: string;
|
|
174
|
+
readonly fontSize: number;
|
|
175
|
+
readonly fontFamily?: string;
|
|
176
|
+
}
|
|
177
|
+
/** Rich-text attributes of the selected text, for the Text panel toggles. */
|
|
178
|
+
interface TextStyleInfo {
|
|
179
|
+
readonly bold: boolean;
|
|
180
|
+
readonly italic: boolean;
|
|
181
|
+
readonly underline: boolean;
|
|
182
|
+
readonly strike: boolean;
|
|
183
|
+
readonly align: string;
|
|
184
|
+
/** The text's current font family, so the panel can reflect it on selection. */
|
|
185
|
+
readonly fontFamily: string;
|
|
186
|
+
}
|
|
187
|
+
/** Editable style of the current selection, surfaced to the host UI. */
|
|
188
|
+
interface SelectionStyleInfo {
|
|
189
|
+
/** `'text'` → color is fill + size is fontSize; `'stroke'` → color is stroke + size is strokeWidth. */
|
|
190
|
+
readonly kind: 'text' | 'stroke';
|
|
191
|
+
readonly color: string;
|
|
192
|
+
readonly size: number;
|
|
193
|
+
/** Present when a single text object is selected. */
|
|
194
|
+
readonly textStyle?: TextStyleInfo;
|
|
195
|
+
/** Present when a single rectangle is selected: its current corner radius. */
|
|
196
|
+
readonly cornerRadius?: number;
|
|
197
|
+
/** Present when a single rectangle is selected: the pill-cap radius for its size. */
|
|
198
|
+
readonly cornerRadiusMax?: number;
|
|
199
|
+
}
|
|
200
|
+
/** One entry in the layers panel (top of the z-stack first). */
|
|
201
|
+
interface LayerInfo {
|
|
202
|
+
readonly id: string;
|
|
203
|
+
readonly label: string;
|
|
204
|
+
readonly locked: boolean;
|
|
205
|
+
readonly visible: boolean;
|
|
206
|
+
readonly selected: boolean;
|
|
207
|
+
/** Object opacity, 0–1. */
|
|
208
|
+
readonly opacity: number;
|
|
209
|
+
/** False for the base image, which should not be deletable. */
|
|
210
|
+
readonly removable: boolean;
|
|
211
|
+
}
|
|
212
|
+
/** A target output size in pixels for the artboard / export region. */
|
|
213
|
+
interface ArtboardSize {
|
|
214
|
+
readonly width: number;
|
|
215
|
+
readonly height: number;
|
|
216
|
+
}
|
|
217
|
+
/** Progress of an in-browser AI operation (model fetch + inference). */
|
|
218
|
+
interface AiProgress {
|
|
219
|
+
/** `'loading'` while fetching the model, `'processing'` during inference, `'done'`. */
|
|
220
|
+
readonly stage: 'loading' | 'processing' | 'done';
|
|
221
|
+
/** 0–1 within the current stage (best-effort; some stages report no total). */
|
|
222
|
+
readonly progress: number;
|
|
223
|
+
}
|
|
224
|
+
/** A user-placed guide line at a fixed scene coordinate. */
|
|
225
|
+
interface ManualGuide {
|
|
226
|
+
readonly id: string;
|
|
227
|
+
/** `h` = horizontal line at scene-y `pos`; `v` = vertical line at scene-x `pos`. */
|
|
228
|
+
readonly orientation: 'h' | 'v';
|
|
229
|
+
readonly pos: number;
|
|
230
|
+
}
|
|
231
|
+
/** The current view transform plus canvas size — everything a ruler needs. */
|
|
232
|
+
interface Viewport {
|
|
233
|
+
readonly zoom: number;
|
|
234
|
+
readonly panX: number;
|
|
235
|
+
readonly panY: number;
|
|
236
|
+
readonly width: number;
|
|
237
|
+
readonly height: number;
|
|
238
|
+
}
|
|
239
|
+
declare class EditorEngine {
|
|
240
|
+
private readonly canvas;
|
|
241
|
+
private readonly fabric;
|
|
242
|
+
private readonly history;
|
|
243
|
+
private baseImage;
|
|
244
|
+
private rotation;
|
|
245
|
+
private straighten;
|
|
246
|
+
private zoomPct;
|
|
247
|
+
private adjustments;
|
|
248
|
+
private looks;
|
|
249
|
+
private frame;
|
|
250
|
+
private idCounter;
|
|
251
|
+
private clipboard;
|
|
252
|
+
private panMode;
|
|
253
|
+
private panLast;
|
|
254
|
+
private snapEnabled;
|
|
255
|
+
private activeGuides;
|
|
256
|
+
private artboard;
|
|
257
|
+
/** Committed crop region (scene coords) — drives the dim mask and raster/PDF export. */
|
|
258
|
+
private cropRegion;
|
|
259
|
+
/** The interactive crop frame while the Crop tool is active, else null. */
|
|
260
|
+
private cropFrame;
|
|
261
|
+
/** Aspect ratio (w/h) constraining the crop frame, or null for free crop. */
|
|
262
|
+
private cropRatio;
|
|
263
|
+
/** Per-object interactivity saved while a crop session locks the rest of the scene. */
|
|
264
|
+
private cropPrevState;
|
|
265
|
+
private cropPrevUniformScaling;
|
|
266
|
+
private rulersEnabled;
|
|
267
|
+
private manualGuides;
|
|
268
|
+
/** Live preview of a guide being dragged from a ruler (not yet committed). */
|
|
269
|
+
private guideDraft;
|
|
270
|
+
/** Id of an existing manual guide being dragged on the canvas, if any. */
|
|
271
|
+
private draggingGuideId;
|
|
272
|
+
private guideIdCounter;
|
|
273
|
+
private selectionListener;
|
|
274
|
+
private layersListener;
|
|
275
|
+
private viewportListener;
|
|
276
|
+
private guidesListener;
|
|
277
|
+
private textMode;
|
|
278
|
+
private textPlacementListener;
|
|
279
|
+
private textFinishListener;
|
|
280
|
+
private pendingFinishText;
|
|
281
|
+
private redactPlacement;
|
|
282
|
+
private magicMode;
|
|
283
|
+
private magicListener;
|
|
284
|
+
private aiProgressListener;
|
|
285
|
+
private onFontsLoaded;
|
|
286
|
+
private lastViewportKey;
|
|
287
|
+
/** Snap distance in *screen* pixels; divided by zoom to get a scene threshold. */
|
|
288
|
+
private static readonly SNAP_PX;
|
|
289
|
+
/** Pointer proximity (screen px) for grabbing a manual guide on the canvas. */
|
|
290
|
+
private static readonly GUIDE_GRAB_PX;
|
|
291
|
+
private constructor();
|
|
292
|
+
private nextId;
|
|
293
|
+
/** Register a callback fired when the active selection (and its style) changes. */
|
|
294
|
+
setSelectionListener(cb: (info: SelectionStyleInfo | null) => void): void;
|
|
295
|
+
/** Register a callback fired when the layer set or its state changes. */
|
|
296
|
+
setLayersListener(cb: () => void): void;
|
|
297
|
+
private notifyLayers;
|
|
298
|
+
private notifySelection;
|
|
299
|
+
private describeSelection;
|
|
300
|
+
/**
|
|
301
|
+
* Set the corner radius of the currently selected rectangle. The value is in
|
|
302
|
+
* on-screen pixels and is clamped to the rectangle's pill cap. No-op (returns
|
|
303
|
+
* false) when the selection is not a single rectangle. Pass `commit: false`
|
|
304
|
+
* for live slider drags; commit once on release.
|
|
305
|
+
*/
|
|
306
|
+
setSelectedCornerRadius(radius: number, commit?: boolean): boolean;
|
|
307
|
+
/**
|
|
308
|
+
* Apply a color and/or size to the currently selected object(s), routing by
|
|
309
|
+
* object type (text → fill/fontSize, shapes & paths → stroke/strokeWidth, and
|
|
310
|
+
* recursing into groups such as arrows). Returns false if nothing is selected.
|
|
311
|
+
* Pass `commit: false` for live slider drags; commit once on release.
|
|
312
|
+
*/
|
|
313
|
+
styleActiveObject(style: {
|
|
314
|
+
color?: string;
|
|
315
|
+
size?: number;
|
|
316
|
+
fontFamily?: string;
|
|
317
|
+
}, commit?: boolean): boolean;
|
|
318
|
+
private styleOne;
|
|
319
|
+
/** Enable/disable edge & center snapping. Clears any visible guides when off. */
|
|
320
|
+
setSnapping(enabled: boolean): void;
|
|
321
|
+
/** Scene-space bounding box of an object from its absolute corner coords. */
|
|
322
|
+
private sceneBox;
|
|
323
|
+
/**
|
|
324
|
+
* Nudge a dragged object so a near edge/center aligns to the canvas or another
|
|
325
|
+
* object, and record the guide lines to draw. Snap threshold is constant in
|
|
326
|
+
* *screen* pixels (zoom-aware) so it feels the same at any zoom.
|
|
327
|
+
*/
|
|
328
|
+
private applySnap;
|
|
329
|
+
/** Pick the smallest within-threshold offset from any anchor to any line. */
|
|
330
|
+
private bestSnap;
|
|
331
|
+
/** Paint active guides onto the overlay context, mapped through the viewport. */
|
|
332
|
+
private drawGuides;
|
|
333
|
+
/** Drop the guides and repaint so the overlay is clean. */
|
|
334
|
+
private clearGuides;
|
|
335
|
+
/**
|
|
336
|
+
* Erase the overlay (top) context. `contextTop` can be momentarily undefined
|
|
337
|
+
* outside a render cycle, so this guards before touching it; clearing by the
|
|
338
|
+
* backing canvas's pixel size is correct regardless of retina scaling.
|
|
339
|
+
*/
|
|
340
|
+
private clearOverlay;
|
|
341
|
+
/** Register a callback fired when the view transform or canvas size changes. */
|
|
342
|
+
setViewportListener(cb: () => void): void;
|
|
343
|
+
/** Register a callback fired when manual guides (or the live draft) change. */
|
|
344
|
+
setGuidesListener(cb: () => void): void;
|
|
345
|
+
private notifyGuides;
|
|
346
|
+
/** Current view transform + canvas size, in CSS pixels / scene units. */
|
|
347
|
+
getViewport(): Viewport;
|
|
348
|
+
private notifyViewportIfChanged;
|
|
349
|
+
/** Show/hide rulers; when off, an in-progress guide draft is dropped. */
|
|
350
|
+
setRulersEnabled(enabled: boolean): void;
|
|
351
|
+
isRulersEnabled(): boolean;
|
|
352
|
+
getManualGuides(): readonly ManualGuide[];
|
|
353
|
+
/** The live guide preview during a ruler drag, or null. */
|
|
354
|
+
getGuideDraft(): ManualGuide | null;
|
|
355
|
+
/** Map a viewport (screen, CSS-px) point to scene coordinates. */
|
|
356
|
+
viewportToScene(vx: number, vy: number): {
|
|
357
|
+
x: number;
|
|
358
|
+
y: number;
|
|
359
|
+
};
|
|
360
|
+
/** Map a client (page) point to canvas viewport (CSS-px) coordinates. */
|
|
361
|
+
clientToViewport(clientX: number, clientY: number): {
|
|
362
|
+
x: number;
|
|
363
|
+
y: number;
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Show a live guide preview at a scene position (during a ruler drag).
|
|
367
|
+
* Pass `null` to clear the preview. Does not touch history.
|
|
368
|
+
*/
|
|
369
|
+
setGuideDraft(orientation: 'h' | 'v', scenePos: number | null): void;
|
|
370
|
+
/** Commit a new manual guide at a scene position and record it in history. */
|
|
371
|
+
addManualGuide(orientation: 'h' | 'v', scenePos: number): void;
|
|
372
|
+
/** Remove every manual guide and record it in history (no-op if already empty). */
|
|
373
|
+
clearManualGuides(): void;
|
|
374
|
+
/** The manual guide whose line is within grab range of a screen point, or null. */
|
|
375
|
+
private guideAtViewport;
|
|
376
|
+
/** Live-move the guide being dragged to the scene coordinate under the pointer. */
|
|
377
|
+
private dragGuideTo;
|
|
378
|
+
/**
|
|
379
|
+
* Finish a guide drag. Dropping the line outside the canvas removes it;
|
|
380
|
+
* otherwise the move is committed to history. Restores object selection.
|
|
381
|
+
*/
|
|
382
|
+
private endGuideDrag;
|
|
383
|
+
/**
|
|
384
|
+
* Set (or clear) the artboard — a fixed output region. Content outside the
|
|
385
|
+
* region is dimmed on screen and excluded from raster/PDF export, which is
|
|
386
|
+
* rendered at exactly the artboard's pixel dimensions. `null` exports the
|
|
387
|
+
* whole canvas (the default).
|
|
388
|
+
*/
|
|
389
|
+
setArtboard(size: ArtboardSize | null): void;
|
|
390
|
+
getArtboard(): ArtboardSize | null;
|
|
391
|
+
/** True while the interactive crop frame is on the canvas. */
|
|
392
|
+
isCropping(): boolean;
|
|
393
|
+
/** True once a crop region has been applied (drives the dim mask + export). */
|
|
394
|
+
hasCropRegion(): boolean;
|
|
395
|
+
/**
|
|
396
|
+
* Begin an interactive crop. Drops a draggable, resizable frame over the canvas
|
|
397
|
+
* (rule-of-thirds + dimmed surroundings), starting from any existing crop region
|
|
398
|
+
* or a centered default for the given aspect `ratio` (null = free). The rest of
|
|
399
|
+
* the scene is made non-interactive until {@link applyCropRegion} or
|
|
400
|
+
* {@link cancelCrop}.
|
|
401
|
+
*/
|
|
402
|
+
beginCrop(ratio: number | null): void;
|
|
403
|
+
/** Reshape the active crop frame to a new aspect `ratio` (null = free). */
|
|
404
|
+
setCropRatio(ratio: number | null): void;
|
|
405
|
+
/** Only corner handles when an aspect ratio is locked; all handles when free. */
|
|
406
|
+
private applyCropControls;
|
|
407
|
+
/** Keep the dragged/resized crop frame within the canvas (and on-ratio). */
|
|
408
|
+
private constrainCropFrame;
|
|
409
|
+
/** Commit the crop frame as the output region and end the session. */
|
|
410
|
+
applyCropRegion(): void;
|
|
411
|
+
/** End the crop session without applying (the previous region is kept). */
|
|
412
|
+
cancelCrop(): void;
|
|
413
|
+
/** Clear any committed crop region (back to the full canvas). */
|
|
414
|
+
clearCropRegion(): void;
|
|
415
|
+
/** Remove the crop frame and restore the rest of the scene's interactivity. */
|
|
416
|
+
private endCropSession;
|
|
417
|
+
/**
|
|
418
|
+
* The artboard's on-canvas rectangle in scene coordinates: the largest
|
|
419
|
+
* centered rectangle of the artboard's aspect ratio that fits the canvas.
|
|
420
|
+
* Returns null when no artboard is set.
|
|
421
|
+
*/
|
|
422
|
+
private artboardRect;
|
|
423
|
+
/** Render the output region to a data URL at its target pixel size. */
|
|
424
|
+
private artboardDataUrl;
|
|
425
|
+
/** Output pixel size of the current region (crop region in scene px, else artboard preset). */
|
|
426
|
+
private outputSize;
|
|
427
|
+
/**
|
|
428
|
+
* Dim the canvas outside the current output region and outline it, on the
|
|
429
|
+
* overlay context. During an interactive crop the region is the live crop
|
|
430
|
+
* frame (with a rule-of-thirds grid and no extra outline, since Fabric draws
|
|
431
|
+
* the frame and its handles); otherwise it is the committed crop/artboard.
|
|
432
|
+
*/
|
|
433
|
+
private drawArtboardMask;
|
|
434
|
+
/** The current output region (committed crop, else centered artboard), scene coords. */
|
|
435
|
+
private outputRect;
|
|
436
|
+
/** The live crop frame's rectangle in scene coordinates (bakes its scale). */
|
|
437
|
+
private cropFrameRect;
|
|
438
|
+
/** Create an engine bound to a `<canvas>` element. */
|
|
439
|
+
static create(canvasEl: HTMLCanvasElement, options: EngineOptions): Promise<EditorEngine>;
|
|
440
|
+
/**
|
|
441
|
+
* Resolve an import source into a canvas-ready URL.
|
|
442
|
+
*
|
|
443
|
+
* - SVG → rasterized at high resolution (Fabric's vector parser mangles complex
|
|
444
|
+
* SVGs and a raw `<img>` misreads their intrinsic size).
|
|
445
|
+
* - Blob / data URL → decoded with EXIF orientation and downscaled, converting
|
|
446
|
+
* formats the browser can't decode natively (HEIC/HEIF). Throws a descriptive
|
|
447
|
+
* error on undecodable input rather than silently yielding nothing.
|
|
448
|
+
* - Remote URL → returned as-is and left to Fabric, since canvas-resampling a
|
|
449
|
+
* cross-origin image would taint it.
|
|
450
|
+
*/
|
|
451
|
+
private resolveImportUrl;
|
|
452
|
+
/**
|
|
453
|
+
* Load an image from a URL or Blob, fit it to the canvas, and reset history.
|
|
454
|
+
*
|
|
455
|
+
* A Blob is first decoded into a data URL so the image's serialized `src`
|
|
456
|
+
* survives in history snapshots (a transient object URL would be revoked and
|
|
457
|
+
* break undo).
|
|
458
|
+
*/
|
|
459
|
+
loadImage(src: string | Blob): Promise<void>;
|
|
460
|
+
get hasImage(): boolean;
|
|
461
|
+
private fitBaseImage;
|
|
462
|
+
/** Rotate the image by a signed multiple of 90°. */
|
|
463
|
+
rotateBy(deg: number): void;
|
|
464
|
+
/** Fine straighten angle, −45..45°. */
|
|
465
|
+
setStraighten(deg: number, commit?: boolean): void;
|
|
466
|
+
private applyAngle;
|
|
467
|
+
/** Flip the image horizontally or vertically. */
|
|
468
|
+
flip(axis: 'h' | 'v'): void;
|
|
469
|
+
get zoom(): number;
|
|
470
|
+
setZoom(pct: number): void;
|
|
471
|
+
zoomBy(deltaPct: number): void;
|
|
472
|
+
resetView(): void;
|
|
473
|
+
/** Crop the base image to a centered rectangle of the given aspect preset. */
|
|
474
|
+
applyCrop(preset: AspAspectPreset): void;
|
|
475
|
+
/**
|
|
476
|
+
* Crop the base image to a centered rectangle of an arbitrary width/height
|
|
477
|
+
* ratio (`null` = full image). Use this for host-defined / CMS aspect targets.
|
|
478
|
+
*/
|
|
479
|
+
applyCropRatio(ratio: number | null): void;
|
|
480
|
+
/** Merge adjustment values and re-render. Pass `commit` on slider release. */
|
|
481
|
+
setAdjustments(values: Partial<Record<AspFilter, number>>, commit?: boolean): void;
|
|
482
|
+
/** Toggle a one-tap look filter (grayscale/sepia/invert/sharpen). */
|
|
483
|
+
toggleLook(look: AspFilter): void;
|
|
484
|
+
isLookActive(look: AspFilter): boolean;
|
|
485
|
+
getAdjustment(key: AspFilter): number;
|
|
486
|
+
private rebuildFilters;
|
|
487
|
+
/** Add a shape at the canvas center. */
|
|
488
|
+
addShape(kind: ShapeKind, style: AnnotationStyle): void;
|
|
489
|
+
private buildArrow;
|
|
490
|
+
/** Add an editable text box at the canvas center. */
|
|
491
|
+
addText(text: string, style: TextStyle): void;
|
|
492
|
+
/**
|
|
493
|
+
* Enable/disable the "click to place text" mode (the Text tool). While on, the
|
|
494
|
+
* cursor is a text caret and clicking empty canvas drops an editable box.
|
|
495
|
+
*/
|
|
496
|
+
setTextMode(enabled: boolean): void;
|
|
497
|
+
/** Register the callback fired with a scene point when text mode is clicked. */
|
|
498
|
+
setTextPlacementListener(cb: (point: {
|
|
499
|
+
x: number;
|
|
500
|
+
y: number;
|
|
501
|
+
}) => void): void;
|
|
502
|
+
/** Enable/disable magic-wand mode (a canvas click flood-fill erases a region). */
|
|
503
|
+
setMagicMode(enabled: boolean): void;
|
|
504
|
+
/** Register the callback fired with a scene point when magic mode is clicked. */
|
|
505
|
+
setMagicListener(cb: (point: {
|
|
506
|
+
x: number;
|
|
507
|
+
y: number;
|
|
508
|
+
}) => void): void;
|
|
509
|
+
/**
|
|
510
|
+
* Register the callback fired when an empty-canvas click in text mode finishes
|
|
511
|
+
* an already-placed text (so the host can switch back to the Select tool).
|
|
512
|
+
*/
|
|
513
|
+
setTextFinishListener(cb: () => void): void;
|
|
514
|
+
/**
|
|
515
|
+
* Add a text box at a scene point and immediately enter in-place editing with
|
|
516
|
+
* the placeholder pre-selected, so the user just starts typing (Photoshop-style).
|
|
517
|
+
*/
|
|
518
|
+
addTextAt(x: number, y: number, style: TextStyle): void;
|
|
519
|
+
private placeText;
|
|
520
|
+
/**
|
|
521
|
+
* Add a movable/resizable redaction marquee centered on the canvas. The user
|
|
522
|
+
* positions it over the area to conceal; transient until {@link applyRedaction}
|
|
523
|
+
* bakes it.
|
|
524
|
+
*/
|
|
525
|
+
addRedactionMarquee(): void;
|
|
526
|
+
/** Add a redaction marquee centered at a scene point (used by click-to-place). */
|
|
527
|
+
addRedactionMarqueeAt(cx: number, cy: number): void;
|
|
528
|
+
/**
|
|
529
|
+
* Enable/disable click-to-place for redaction: while on, clicking empty canvas
|
|
530
|
+
* drops a new marquee there (but only when one isn't already present, so you
|
|
531
|
+
* reposition the existing box rather than spawning duplicates).
|
|
532
|
+
*/
|
|
533
|
+
setRedactPlacement(enabled: boolean): void;
|
|
534
|
+
/** Remove the redaction marquee without applying it. */
|
|
535
|
+
cancelRedaction(): void;
|
|
536
|
+
/**
|
|
537
|
+
* Bake the redaction: conceal the COMPOSITED content under the marquee. `solid`
|
|
538
|
+
* lays an opaque box; `blur`/`pixelate` sample the rendered pixels in the region
|
|
539
|
+
* (so all underlying layers are hidden) and place a filtered, opaque patch.
|
|
540
|
+
*/
|
|
541
|
+
applyRedaction(mode: RedactMode): Promise<void>;
|
|
542
|
+
private findByRole;
|
|
543
|
+
/**
|
|
544
|
+
* Apply a decorative frame around the image. Each style maps to a distinct,
|
|
545
|
+
* real border rendering (`none` clears it). The frame is a non-interactive
|
|
546
|
+
* rectangle tagged with `aspRole: 'frame'` so it can be re-found and replaced
|
|
547
|
+
* even after a history restore rebuilds every canvas object.
|
|
548
|
+
*/
|
|
549
|
+
applyFrame(style: string, color: string): void;
|
|
550
|
+
/**
|
|
551
|
+
* Set the canvas background to a solid color (`'transparent'` clears it, showing
|
|
552
|
+
* the checkerboard). Fabric serializes `backgroundColor`, so it survives undo.
|
|
553
|
+
*/
|
|
554
|
+
setBackground(color: string): void;
|
|
555
|
+
/** Set the canvas background to a linear gradient of the given color stops. */
|
|
556
|
+
setBackgroundGradient(colors: readonly string[]): void;
|
|
557
|
+
/** Set an uploaded image as the canvas background, scaled to cover. */
|
|
558
|
+
setBackgroundImage(src: string | Blob): Promise<void>;
|
|
559
|
+
/** Remove every canvas object tagged with the given `aspRole`. */
|
|
560
|
+
private removeTagged;
|
|
561
|
+
/**
|
|
562
|
+
* Enable or disable freehand drawing. When `highlighter` is set, the brush is
|
|
563
|
+
* translucent and wider so strokes read as a highlight over the underlying
|
|
564
|
+
* content rather than an opaque line.
|
|
565
|
+
*/
|
|
566
|
+
setFreeDraw(enabled: boolean, style: AnnotationStyle, highlighter?: boolean): void;
|
|
567
|
+
/** Apply rich-text attributes (weight/style/underline/align/spacing/bg) to the active text. */
|
|
568
|
+
applyTextStyle(props: Record<string, string | number | boolean>, commit?: boolean): boolean;
|
|
569
|
+
/** Set the fill of the active non-text object(s) (`'transparent'` clears it). */
|
|
570
|
+
setActiveFill(color: string, commit?: boolean): boolean;
|
|
571
|
+
/** Set opacity (0–1) on the active object(s). Returns false if nothing selected. */
|
|
572
|
+
setOpacity(value: number, commit?: boolean): boolean;
|
|
573
|
+
/** Set opacity (0–1) on a specific layer. */
|
|
574
|
+
setLayerOpacity(id: string, value: number, commit?: boolean): void;
|
|
575
|
+
/** Delete the currently selected object(s). */
|
|
576
|
+
deleteActive(): void;
|
|
577
|
+
/** Clear the active selection. */
|
|
578
|
+
discardSelection(): void;
|
|
579
|
+
/** Select all editable (unlocked, non-base) objects. */
|
|
580
|
+
selectAll(): void;
|
|
581
|
+
/** Copy the current selection to the internal clipboard. */
|
|
582
|
+
copy(): Promise<void>;
|
|
583
|
+
/** Paste the clipboard contents, offset and selected. */
|
|
584
|
+
paste(): Promise<void>;
|
|
585
|
+
/** Add an image (e.g. pasted from the OS clipboard) as a movable object. */
|
|
586
|
+
addImageObject(src: string | Blob): Promise<void>;
|
|
587
|
+
/**
|
|
588
|
+
* Magic-wand erase: from a scene point, flood-fill the image under the pointer
|
|
589
|
+
* and clear (make transparent) every contiguous pixel within `tolerance`
|
|
590
|
+
* (0–100) of the clicked color. Great for knocking out a solid background.
|
|
591
|
+
* Operates on the base image, or a selected image layer if one is active.
|
|
592
|
+
*/
|
|
593
|
+
magicErase(scenePoint: {
|
|
594
|
+
x: number;
|
|
595
|
+
y: number;
|
|
596
|
+
}, tolerance: number): Promise<boolean>;
|
|
597
|
+
/** Register a callback for AI-operation progress (model fetch + inference). */
|
|
598
|
+
setAiProgressListener(cb: (info: AiProgress) => void): void;
|
|
599
|
+
/**
|
|
600
|
+
* In-browser AI background removal. Segments the target image (the base image,
|
|
601
|
+
* or a selected image layer) and either replaces it with the cut-out subject
|
|
602
|
+
* (`mode: 'replace'`) or adds the cut-out as a new layer (`mode: 'subject'`).
|
|
603
|
+
* The model is fetched and cached on first use; progress is reported via the AI
|
|
604
|
+
* progress listener. Returns false if there's no image to process.
|
|
605
|
+
*/
|
|
606
|
+
removeImageBackground(mode: 'replace' | 'subject'): Promise<boolean>;
|
|
607
|
+
/** Draw an image object's source bitmap to a fresh canvas and return a PNG blob. */
|
|
608
|
+
private imageToBlob;
|
|
609
|
+
/** Duplicate the current selection in place (offset). */
|
|
610
|
+
duplicateActive(): Promise<void>;
|
|
611
|
+
private addClones;
|
|
612
|
+
private setActive;
|
|
613
|
+
/** Group the current multi-selection into a single object. */
|
|
614
|
+
groupActive(): void;
|
|
615
|
+
/** Ungroup the selected group back into individual objects. */
|
|
616
|
+
ungroupActive(): void;
|
|
617
|
+
/** Align the active object/selection to an edge or center of the canvas. */
|
|
618
|
+
alignActive(mode: 'left' | 'center-h' | 'right' | 'top' | 'center-v' | 'bottom'): void;
|
|
619
|
+
/** Enable/disable space-drag panning (disables selection while active). */
|
|
620
|
+
setPanMode(enabled: boolean): void;
|
|
621
|
+
get canUndo(): boolean;
|
|
622
|
+
get canRedo(): boolean;
|
|
623
|
+
get historyEntries(): readonly HistoryStep[];
|
|
624
|
+
get historyIndex(): number;
|
|
625
|
+
undo(): Promise<void>;
|
|
626
|
+
redo(): Promise<void>;
|
|
627
|
+
/**
|
|
628
|
+
* Serialize the whole editor — scene objects, transform/adjustment/look/frame
|
|
629
|
+
* state, and the artboard — into a portable, versioned template string. The
|
|
630
|
+
* inverse of {@link loadScene}.
|
|
631
|
+
*/
|
|
632
|
+
exportScene(): string;
|
|
633
|
+
/**
|
|
634
|
+
* Restore a template produced by {@link exportScene}. Resets the history to
|
|
635
|
+
* the loaded state so it becomes the new undo baseline. Throws on malformed
|
|
636
|
+
* input rather than loading a partial scene.
|
|
637
|
+
*/
|
|
638
|
+
loadScene(json: string): Promise<void>;
|
|
639
|
+
/**
|
|
640
|
+
* Serialize the full editor state — the Fabric scene PLUS the engine's own
|
|
641
|
+
* transform/adjustment/look/frame state — so a restore puts both back in sync
|
|
642
|
+
* (the canvas alone does not capture rotation buckets, slider values, etc).
|
|
643
|
+
*/
|
|
644
|
+
private snapshot;
|
|
645
|
+
private commit;
|
|
646
|
+
private restore;
|
|
647
|
+
private findBaseImage;
|
|
648
|
+
/** Reset all edits back to the freshly-loaded image. */
|
|
649
|
+
reset(): Promise<void>;
|
|
650
|
+
get rotationAngle(): number;
|
|
651
|
+
get straightenAngle(): number;
|
|
652
|
+
/** A copy of the current adjustment values. */
|
|
653
|
+
getAdjustments(): Record<AspFilter, number>;
|
|
654
|
+
/** The single active look (UI is single-select), or null. */
|
|
655
|
+
get activeLook(): AspFilter | null;
|
|
656
|
+
get activeFrame(): string;
|
|
657
|
+
/** The layer stack, top of the z-order first (matching visual stacking). */
|
|
658
|
+
getLayers(): LayerInfo[];
|
|
659
|
+
private findById;
|
|
660
|
+
/**
|
|
661
|
+
* Select a layer by id (no-op if it is locked or missing). When `additive`
|
|
662
|
+
* (shift/cmd/ctrl-click in the panel), toggle the layer in/out of a
|
|
663
|
+
* multi-selection instead of replacing it.
|
|
664
|
+
*/
|
|
665
|
+
selectLayer(id: string, additive?: boolean): void;
|
|
666
|
+
/**
|
|
667
|
+
* Reorder the whole z-stack to match a display order (front-most first, as the
|
|
668
|
+
* Layers panel shows it). Used by drag-and-drop reordering.
|
|
669
|
+
*/
|
|
670
|
+
reorderLayers(displayOrderIds: readonly string[]): void;
|
|
671
|
+
/** Rename a layer; an empty/blank name clears back to the auto label. */
|
|
672
|
+
renameLayer(id: string, name: string): void;
|
|
673
|
+
/** Lock/unlock a layer. Locked layers are not selectable, so clicks pass through. */
|
|
674
|
+
toggleLayerLock(id: string): void;
|
|
675
|
+
private setLocked;
|
|
676
|
+
/** Show/hide a layer. */
|
|
677
|
+
toggleLayerVisible(id: string): void;
|
|
678
|
+
/** Move a layer up (forward) or down (backward) in the z-order. */
|
|
679
|
+
moveLayer(id: string, direction: 'up' | 'down'): void;
|
|
680
|
+
/** Delete a layer (the base image is protected). */
|
|
681
|
+
deleteLayer(id: string): void;
|
|
682
|
+
/** Export the current scene to a Blob in the requested format. */
|
|
683
|
+
exportImage(format: AspExportFormat, qualityPct: number, allowedFormats: readonly AspExportFormat[]): Promise<Blob>;
|
|
684
|
+
/**
|
|
685
|
+
* Inline the web fonts used by the scene's text into the SVG so it is
|
|
686
|
+
* self-contained. Network failures fall back to the original SVG (text keeps
|
|
687
|
+
* its font-family). No-op outside a browser (no `fetch`).
|
|
688
|
+
*/
|
|
689
|
+
private embedSvgFonts;
|
|
690
|
+
/** Distinct `fontFamily` values across all text objects (recursing into groups). */
|
|
691
|
+
private collectTextFontFamilies;
|
|
692
|
+
/**
|
|
693
|
+
* Render into a single-page PDF. The page is sized to the artboard (when set)
|
|
694
|
+
* or the full canvas, and the image is the matching region. jsPDF is lazy-loaded.
|
|
695
|
+
*/
|
|
696
|
+
private exportPdf;
|
|
697
|
+
/** Pixel data of the current canvas, or null if no rendering context. */
|
|
698
|
+
getImageData(): ImageData | null;
|
|
699
|
+
/** Resize the editing surface. */
|
|
700
|
+
setSize(width: number, height: number): void;
|
|
701
|
+
/** Tear down the Fabric canvas and release resources. */
|
|
702
|
+
destroy(): Promise<void>;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Data-driven tool and filter catalog.
|
|
707
|
+
*
|
|
708
|
+
* The rail, options panels, and resolution all read from these tables — there
|
|
709
|
+
* is no per-tool branching anywhere else. `mode`/`tools`/`disabledTools`/
|
|
710
|
+
* `filters` resolve against this metadata (see `resolve-tools.ts`).
|
|
711
|
+
*/
|
|
712
|
+
|
|
713
|
+
type AspToolGroup = 'transform' | 'annotate' | 'color' | 'object' | 'canvas';
|
|
714
|
+
interface ToolMeta {
|
|
715
|
+
readonly key: AspTool;
|
|
716
|
+
readonly label: string;
|
|
717
|
+
/** Iconify/Lucide icon id. */
|
|
718
|
+
readonly icon: string;
|
|
719
|
+
readonly group: AspToolGroup;
|
|
720
|
+
}
|
|
721
|
+
/** A filter is either a continuous "adjustment" (slider) or a one-tap "look". */
|
|
722
|
+
type FilterKind = 'adjustment' | 'look';
|
|
723
|
+
interface FilterMeta {
|
|
724
|
+
readonly key: AspFilter;
|
|
725
|
+
readonly label: string;
|
|
726
|
+
readonly kind: FilterKind;
|
|
727
|
+
/** Slider bounds + default for adjustments (omitted for looks). */
|
|
728
|
+
readonly min?: number;
|
|
729
|
+
readonly max?: number;
|
|
730
|
+
readonly defaultValue?: number;
|
|
731
|
+
readonly unit?: string;
|
|
732
|
+
}
|
|
733
|
+
declare const TOOL_REGISTRY: Record<AspTool, ToolMeta>;
|
|
734
|
+
declare const FILTER_REGISTRY: Record<AspFilter, FilterMeta>;
|
|
735
|
+
declare const DEFAULT_TOOLS: Record<AspMode, readonly AspTool[]>;
|
|
736
|
+
/** Default filter set per mode. `'all'` means every Fabric filter. */
|
|
737
|
+
declare const DEFAULT_FILTERS: Record<AspMode, readonly AspFilter[] | 'all'>;
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Lazy web-font loading for the text tool.
|
|
741
|
+
*
|
|
742
|
+
* A chosen family is loaded by injecting the Google Fonts stylesheet once and
|
|
743
|
+
* awaiting the CSS Font Loading API, so text renders in the correct font rather
|
|
744
|
+
* than a fallback. System/generic stacks need no loading. SSR-safe (no-op when
|
|
745
|
+
* `document` is absent), idempotent, and resolves even if loading fails (the
|
|
746
|
+
* fallback simply renders).
|
|
747
|
+
*/
|
|
748
|
+
interface FontOption {
|
|
749
|
+
readonly label: string;
|
|
750
|
+
/** CSS `font-family` value applied to the text object. */
|
|
751
|
+
readonly value: string;
|
|
752
|
+
}
|
|
753
|
+
/** Default font choices: a system stack plus popular Google families. */
|
|
754
|
+
declare const DEFAULT_FONTS: readonly FontOption[];
|
|
755
|
+
|
|
756
|
+
/** Frame styles offered in the frame panel. */
|
|
757
|
+
interface FrameOption {
|
|
758
|
+
readonly key: string;
|
|
759
|
+
readonly label: string;
|
|
760
|
+
}
|
|
761
|
+
interface AdjustChange {
|
|
762
|
+
readonly key: AspFilter;
|
|
763
|
+
readonly value: number;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Toolbar grouping — maps the resolved tool set into a small number of toolbar
|
|
768
|
+
* slots, Photoshop-style. Each group occupies one slot; a group with more than
|
|
769
|
+
* one resolved member and `flyout: true` shows a flyout to switch between them.
|
|
770
|
+
*
|
|
771
|
+
* Tools NOT represented here (layers, object-ops like group/align/duplicate/
|
|
772
|
+
* delete/opacity) are intentionally absent — they live in the persistent Layers
|
|
773
|
+
* panel, not the toolbar.
|
|
774
|
+
*/
|
|
775
|
+
|
|
776
|
+
interface ResolvedGroup {
|
|
777
|
+
readonly id: string;
|
|
778
|
+
readonly label: string;
|
|
779
|
+
readonly icon: string;
|
|
780
|
+
/** The subset of members present in the resolved tool set, in order. */
|
|
781
|
+
readonly members: AspTool[];
|
|
782
|
+
readonly flyout: boolean;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* The editor's scoped CSS custom-property contract.
|
|
787
|
+
*
|
|
788
|
+
* Every visual style in the library references one of these `--asp-*` variables,
|
|
789
|
+
* and {@link deriveTheme} produces a value for each from the three theming inputs.
|
|
790
|
+
* Hosts may override any individual variable in their own CSS for fine control.
|
|
791
|
+
*/
|
|
792
|
+
/** Color tokens derived at runtime from `baseColor` + `accentColor` + `themeMode`. */
|
|
793
|
+
declare const COLOR_TOKEN_NAMES: readonly ["--asp-bg", "--asp-surface", "--asp-surface-2", "--asp-surface-sunk", "--asp-ink", "--asp-ink-700", "--asp-ink-muted", "--asp-ink-faint", "--asp-line", "--asp-line-strong", "--asp-accent", "--asp-accent-ink", "--asp-accent-hover", "--asp-accent-soft", "--asp-accent-soft-ink", "--asp-ring", "--asp-scrim", "--asp-success", "--asp-warning", "--asp-error"];
|
|
794
|
+
/** Non-color tokens: fixed dimensional/typographic values, the same in every theme. */
|
|
795
|
+
declare const STATIC_TOKEN_NAMES: readonly ["--asp-radius-sm", "--asp-radius-md", "--asp-radius-lg", "--asp-radius-pill", "--asp-ctl-h", "--asp-ctl-h-sm", "--asp-font-mono"];
|
|
796
|
+
/** Every token name the editor sets on its root element. */
|
|
797
|
+
declare const THEME_TOKEN_NAMES: readonly ["--asp-bg", "--asp-surface", "--asp-surface-2", "--asp-surface-sunk", "--asp-ink", "--asp-ink-700", "--asp-ink-muted", "--asp-ink-faint", "--asp-line", "--asp-line-strong", "--asp-accent", "--asp-accent-ink", "--asp-accent-hover", "--asp-accent-soft", "--asp-accent-soft-ink", "--asp-ring", "--asp-scrim", "--asp-success", "--asp-warning", "--asp-error", "--asp-radius-sm", "--asp-radius-md", "--asp-radius-lg", "--asp-radius-pill", "--asp-ctl-h", "--asp-ctl-h-sm", "--asp-font-mono"];
|
|
798
|
+
type ThemeTokenName = (typeof THEME_TOKEN_NAMES)[number];
|
|
799
|
+
/** A fully-resolved theme: every token name mapped to a CSS value string. */
|
|
800
|
+
type AspThemeTokens = Record<ThemeTokenName, string>;
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Derive the full `--asp-*` token set from the three theming inputs.
|
|
804
|
+
*
|
|
805
|
+
* Strategy: surfaces, ink, and lines are generated as a perceptually-even
|
|
806
|
+
* lightness scale tinted toward the BASE hue (so the editor blends with the
|
|
807
|
+
* host's neutral palette); interactive tokens are generated from the ACCENT.
|
|
808
|
+
* Text tokens are then run through {@link ensureContrastAA} against their actual
|
|
809
|
+
* background so WCAG AA (AAA for primary ink) is GUARANTEED for any input pair,
|
|
810
|
+
* in both light and dark — the 3 inputs alone always yield a legible result.
|
|
811
|
+
*/
|
|
812
|
+
|
|
813
|
+
/** Light or dark derivation. */
|
|
814
|
+
type AspThemeMode = 'light' | 'dark';
|
|
815
|
+
/**
|
|
816
|
+
* Build the complete theme token map.
|
|
817
|
+
*
|
|
818
|
+
* @param baseColor neutral anchor (hex) — surfaces/ink/lines tint toward its hue.
|
|
819
|
+
* @param accentColor interactive accent (hex) — kept exactly as the brand color.
|
|
820
|
+
* @param mode `'light'` or `'dark'`.
|
|
821
|
+
* @throws {Error} if either color is not valid hex.
|
|
822
|
+
*/
|
|
823
|
+
declare function deriveTheme(baseColor: string, accentColor: string, mode: AspThemeMode): AspThemeTokens;
|
|
824
|
+
|
|
825
|
+
/** A layer click, carrying whether a modifier (shift/cmd/ctrl) was held. */
|
|
826
|
+
interface LayerSelectEvent {
|
|
827
|
+
readonly id: string;
|
|
828
|
+
readonly additive: boolean;
|
|
829
|
+
}
|
|
830
|
+
/** A layer rename request. */
|
|
831
|
+
interface LayerRenameEvent {
|
|
832
|
+
readonly id: string;
|
|
833
|
+
readonly name: string;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Built-in sample images for the image picker, generated at runtime as
|
|
838
|
+
* same-origin data URLs (so filters and export are never tainted by CORS).
|
|
839
|
+
*/
|
|
840
|
+
interface SampleImage {
|
|
841
|
+
readonly key: string;
|
|
842
|
+
readonly label: string;
|
|
843
|
+
readonly dataUrl: string;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
type AlignMode = 'left' | 'center-h' | 'right' | 'top' | 'center-v' | 'bottom';
|
|
847
|
+
/**
|
|
848
|
+
* `<asp-image-editor>` — the editor's root/container component.
|
|
849
|
+
*
|
|
850
|
+
* Owns the {@link EditorEngine}, the resolved tool/filter sets, theming, and all
|
|
851
|
+
* workspace state. Presentational children (rail, options panel, history) render
|
|
852
|
+
* data and emit intent; this container is the only place that drives the engine.
|
|
853
|
+
*
|
|
854
|
+
* Phase 4 implements the `advanced`/`full` workspace; `basic`/`viewer` layouts
|
|
855
|
+
* arrive in Phase 5.
|
|
856
|
+
*/
|
|
857
|
+
declare class AspImageEditor implements OnDestroy {
|
|
858
|
+
readonly src: _angular_core.InputSignal<string | Blob | null>;
|
|
859
|
+
readonly mode: _angular_core.InputSignal<AspMode>;
|
|
860
|
+
/**
|
|
861
|
+
* Editor width — a number (px) or any CSS length (`'70%'`, `'80vh'`,
|
|
862
|
+
* `'calc(100vw - 320px)'`). Defaults to filling the host's container. A
|
|
863
|
+
* per-mode minimum is always enforced so the chrome stays usable.
|
|
864
|
+
*/
|
|
865
|
+
readonly width: _angular_core.InputSignal<AspSize | null>;
|
|
866
|
+
/** Editor height — same shape as {@link width}. */
|
|
867
|
+
readonly height: _angular_core.InputSignal<AspSize | null>;
|
|
868
|
+
readonly tools: _angular_core.InputSignal<("crop" | "rotate" | "straighten" | "flip" | "resize" | "pen" | "highlighter" | "eraser" | "shapes" | "arrow" | "line" | "text" | "sticker" | "redact" | "magicwand" | "removebg" | "selectsubject" | "adjust" | "filters" | "select" | "layers" | "duplicate" | "delete" | "opacity" | "align" | "group" | "background" | "frame")[] | null>;
|
|
869
|
+
readonly disabledTools: _angular_core.InputSignal<("crop" | "rotate" | "straighten" | "flip" | "resize" | "pen" | "highlighter" | "eraser" | "shapes" | "arrow" | "line" | "text" | "sticker" | "redact" | "magicwand" | "removebg" | "selectsubject" | "adjust" | "filters" | "select" | "layers" | "duplicate" | "delete" | "opacity" | "align" | "group" | "background" | "frame")[]>;
|
|
870
|
+
readonly filters: _angular_core.InputSignal<"all" | ("brightness" | "contrast" | "saturation" | "vibrance" | "hue" | "blur" | "sharpen" | "grayscale" | "sepia" | "invert" | "pixelate" | "noise" | "gamma" | "blendColor")[] | null>;
|
|
871
|
+
readonly aspectPresets: _angular_core.InputSignal<AspAspectPreset[]>;
|
|
872
|
+
/** Host-defined crop aspect targets (e.g. CMS sizes); shown after the presets. */
|
|
873
|
+
readonly aspectRatios: _angular_core.InputSignal<AspAspectOption[]>;
|
|
874
|
+
readonly exportFormats: _angular_core.InputSignal<AspExportFormat[]>;
|
|
875
|
+
readonly exportQuality: _angular_core.InputSignal<number>;
|
|
876
|
+
readonly baseColor: _angular_core.InputSignal<string>;
|
|
877
|
+
readonly accentColor: _angular_core.InputSignal<string>;
|
|
878
|
+
readonly themeMode: _angular_core.InputSignal<AspThemeMode>;
|
|
879
|
+
/** Heading shown by the `basic` modal layout. */
|
|
880
|
+
readonly heading: _angular_core.InputSignal<string>;
|
|
881
|
+
/** Show the edit-history panel in the workspace (hosts that don't want it set false). */
|
|
882
|
+
readonly showHistory: _angular_core.InputSignal<boolean>;
|
|
883
|
+
/** Enable keyboard shortcuts while the pointer is over the editor. */
|
|
884
|
+
readonly keyboardEnabled: _angular_core.InputSignal<boolean>;
|
|
885
|
+
readonly saved: _angular_core.OutputEmitterRef<Blob>;
|
|
886
|
+
readonly canceled: _angular_core.OutputEmitterRef<void>;
|
|
887
|
+
/** Fired after an image successfully loads (initial, picker, or upload). */
|
|
888
|
+
readonly imageLoaded: _angular_core.OutputEmitterRef<void>;
|
|
889
|
+
/** Fired with the exported Blob when the user downloads from the Export menu. */
|
|
890
|
+
readonly exported: _angular_core.OutputEmitterRef<Blob>;
|
|
891
|
+
/** Fired on a recoverable error (load/export/engine init) instead of throwing. */
|
|
892
|
+
readonly errorOccurred: _angular_core.OutputEmitterRef<AspEditorError>;
|
|
893
|
+
private readonly host;
|
|
894
|
+
private readonly canvasRef;
|
|
895
|
+
private readonly stageRef;
|
|
896
|
+
private readonly rulerTopRef;
|
|
897
|
+
private readonly rulerLeftRef;
|
|
898
|
+
private readonly guidesOverlayRef;
|
|
899
|
+
private engine;
|
|
900
|
+
private resizeObserver;
|
|
901
|
+
private boundCanvas;
|
|
902
|
+
private lastSource;
|
|
903
|
+
protected readonly engineReady: _angular_core.WritableSignal<boolean>;
|
|
904
|
+
protected readonly resolvedToolKeys: _angular_core.Signal<("crop" | "rotate" | "straighten" | "flip" | "resize" | "pen" | "highlighter" | "eraser" | "shapes" | "arrow" | "line" | "text" | "sticker" | "redact" | "magicwand" | "removebg" | "selectsubject" | "adjust" | "filters" | "select" | "layers" | "duplicate" | "delete" | "opacity" | "align" | "group" | "background" | "frame")[]>;
|
|
905
|
+
protected readonly resolvedTools: _angular_core.Signal<ToolMeta[]>;
|
|
906
|
+
protected readonly resolvedGroups: _angular_core.Signal<ResolvedGroup[]>;
|
|
907
|
+
protected readonly activeMembers: _angular_core.WritableSignal<Record<string, "crop" | "rotate" | "straighten" | "flip" | "resize" | "pen" | "highlighter" | "eraser" | "shapes" | "arrow" | "line" | "text" | "sticker" | "redact" | "magicwand" | "removebg" | "selectsubject" | "adjust" | "filters" | "select" | "layers" | "duplicate" | "delete" | "opacity" | "align" | "group" | "background" | "frame">>;
|
|
908
|
+
protected readonly resolvedFilters: _angular_core.Signal<("brightness" | "contrast" | "saturation" | "vibrance" | "hue" | "blur" | "sharpen" | "grayscale" | "sepia" | "invert" | "pixelate" | "noise" | "gamma" | "blendColor")[]>;
|
|
909
|
+
protected readonly adjustmentDefs: _angular_core.Signal<FilterMeta[]>;
|
|
910
|
+
protected readonly lookDefs: _angular_core.Signal<FilterMeta[]>;
|
|
911
|
+
protected readonly frameOptions: readonly FrameOption[];
|
|
912
|
+
protected readonly activeTool: _angular_core.WritableSignal<"crop" | "rotate" | "straighten" | "flip" | "resize" | "pen" | "highlighter" | "eraser" | "shapes" | "arrow" | "line" | "text" | "sticker" | "redact" | "magicwand" | "removebg" | "selectsubject" | "adjust" | "filters" | "select" | "layers" | "duplicate" | "delete" | "opacity" | "align" | "group" | "background" | "frame" | null>;
|
|
913
|
+
protected readonly zoomPct: _angular_core.WritableSignal<number>;
|
|
914
|
+
protected readonly canUndo: _angular_core.WritableSignal<boolean>;
|
|
915
|
+
protected readonly canRedo: _angular_core.WritableSignal<boolean>;
|
|
916
|
+
protected readonly historyEntries: _angular_core.WritableSignal<readonly HistoryStep[]>;
|
|
917
|
+
protected readonly historyIndex: _angular_core.WritableSignal<number>;
|
|
918
|
+
protected readonly adjustments: _angular_core.WritableSignal<Record<string, number>>;
|
|
919
|
+
protected readonly activeLook: _angular_core.WritableSignal<"brightness" | "contrast" | "saturation" | "vibrance" | "hue" | "blur" | "sharpen" | "grayscale" | "sepia" | "invert" | "pixelate" | "noise" | "gamma" | "blendColor" | null>;
|
|
920
|
+
protected readonly activeCrop: _angular_core.WritableSignal<AspAspectPreset>;
|
|
921
|
+
protected readonly activeAspectLabel: _angular_core.WritableSignal<string>;
|
|
922
|
+
protected readonly straighten: _angular_core.WritableSignal<number>;
|
|
923
|
+
/** Available text fonts (host-overridable). */
|
|
924
|
+
readonly fonts: _angular_core.InputSignal<FontOption[]>;
|
|
925
|
+
protected readonly annotationColor: _angular_core.WritableSignal<string>;
|
|
926
|
+
protected readonly annotationWidth: _angular_core.WritableSignal<number>;
|
|
927
|
+
protected readonly fontSize: _angular_core.WritableSignal<number>;
|
|
928
|
+
protected readonly fontFamily: _angular_core.WritableSignal<string>;
|
|
929
|
+
protected readonly textBold: _angular_core.WritableSignal<boolean>;
|
|
930
|
+
protected readonly textItalic: _angular_core.WritableSignal<boolean>;
|
|
931
|
+
protected readonly textUnderline: _angular_core.WritableSignal<boolean>;
|
|
932
|
+
protected readonly textStrike: _angular_core.WritableSignal<boolean>;
|
|
933
|
+
protected readonly textAlign: _angular_core.WritableSignal<string>;
|
|
934
|
+
protected readonly lineHeight: _angular_core.WritableSignal<number>;
|
|
935
|
+
protected readonly letterSpacing: _angular_core.WritableSignal<number>;
|
|
936
|
+
/** Custom fonts added at runtime, merged after the host-provided list. */
|
|
937
|
+
protected readonly customFonts: _angular_core.WritableSignal<FontOption[]>;
|
|
938
|
+
protected readonly allFonts: _angular_core.Signal<FontOption[]>;
|
|
939
|
+
/** Google font names offered as autocomplete suggestions in the add-font box. */
|
|
940
|
+
protected readonly googleFonts: readonly string[];
|
|
941
|
+
protected readonly hasSelection: _angular_core.WritableSignal<boolean>;
|
|
942
|
+
/** Kind of the current selection, so the panel can reflect it under Select. */
|
|
943
|
+
protected readonly selectionKind: _angular_core.WritableSignal<"text" | "stroke" | null>;
|
|
944
|
+
/** True while a web font for the current choice is still loading. */
|
|
945
|
+
protected readonly fontLoading: _angular_core.WritableSignal<boolean>;
|
|
946
|
+
/** Transient error message shown as an in-editor toast (null = hidden). */
|
|
947
|
+
protected readonly errorToast: _angular_core.WritableSignal<string | null>;
|
|
948
|
+
private errorToastTimer;
|
|
949
|
+
protected readonly activeFrame: _angular_core.WritableSignal<string>;
|
|
950
|
+
/** Corner radius (px) for the next rectangle, and the selected rectangle. */
|
|
951
|
+
protected readonly shapeRadius: _angular_core.WritableSignal<number>;
|
|
952
|
+
/** Pill-cap radius driving the corner-radius slider's max. */
|
|
953
|
+
protected readonly shapeRadiusMax: _angular_core.WritableSignal<number>;
|
|
954
|
+
/** True when the current selection is a single rectangle. */
|
|
955
|
+
protected readonly selectedIsRect: _angular_core.WritableSignal<boolean>;
|
|
956
|
+
/**
|
|
957
|
+
* Show the corner-radius slider when defining the next rectangle (Shapes tool,
|
|
958
|
+
* nothing selected) or when a rectangle is selected.
|
|
959
|
+
*/
|
|
960
|
+
protected readonly showCornerRadius: _angular_core.Signal<boolean>;
|
|
961
|
+
protected readonly redactMode: _angular_core.WritableSignal<RedactMode>;
|
|
962
|
+
protected readonly magicTolerance: _angular_core.WritableSignal<number>;
|
|
963
|
+
protected readonly aiBusy: _angular_core.WritableSignal<boolean>;
|
|
964
|
+
protected readonly aiStage: _angular_core.WritableSignal<string>;
|
|
965
|
+
protected readonly aiProgress: _angular_core.WritableSignal<number>;
|
|
966
|
+
/** True once a crop region has been applied (enables the panel's Reset). */
|
|
967
|
+
protected readonly hasCropRegion: _angular_core.WritableSignal<boolean>;
|
|
968
|
+
private redactActive;
|
|
969
|
+
private cropActive;
|
|
970
|
+
protected onMagicTolerance(value: number): void;
|
|
971
|
+
/** Run the active AI tool (background removal / subject cut-out) on the image. */
|
|
972
|
+
protected runAi(): void;
|
|
973
|
+
protected readonly layers: _angular_core.WritableSignal<LayerInfo[]>;
|
|
974
|
+
protected readonly pickerOpen: _angular_core.WritableSignal<boolean>;
|
|
975
|
+
protected readonly exportOpen: _angular_core.WritableSignal<boolean>;
|
|
976
|
+
protected readonly historyOpen: _angular_core.WritableSignal<boolean>;
|
|
977
|
+
protected readonly snapEnabled: _angular_core.WritableSignal<boolean>;
|
|
978
|
+
protected readonly rulersEnabled: _angular_core.WritableSignal<boolean>;
|
|
979
|
+
/** Bumped by the engine's viewport listener to re-render rulers on zoom/pan/resize. */
|
|
980
|
+
private readonly rulerVersion;
|
|
981
|
+
/** Bumped by the engine's guides listener to repaint the guides overlay. */
|
|
982
|
+
private readonly guidesVersion;
|
|
983
|
+
/** Tears down an in-flight ruler→guide drag; also called on destroy. */
|
|
984
|
+
private guideDraftCleanup;
|
|
985
|
+
protected readonly artboard: _angular_core.WritableSignal<ArtboardSize | null>;
|
|
986
|
+
protected readonly exportFormat: _angular_core.WritableSignal<AspExportFormat>;
|
|
987
|
+
protected readonly exportQ: _angular_core.WritableSignal<number>;
|
|
988
|
+
protected readonly samples: _angular_core.WritableSignal<SampleImage[]>;
|
|
989
|
+
protected readonly activeToolMeta: _angular_core.Signal<ToolMeta | null>;
|
|
990
|
+
protected readonly toolTitle: _angular_core.Signal<string>;
|
|
991
|
+
protected readonly zoomLabel: _angular_core.Signal<string>;
|
|
992
|
+
protected readonly layout: _angular_core.Signal<"viewer" | "basic" | "workspace">;
|
|
993
|
+
/**
|
|
994
|
+
* The resolved theme. Invalid hex inputs are a developer error; rather than
|
|
995
|
+
* throwing and breaking the host app, we fall back to the default palette
|
|
996
|
+
* (keeping the requested mode) and warn once. Declared before the constructor
|
|
997
|
+
* so it is initialized before any effect that reads it.
|
|
998
|
+
*/
|
|
999
|
+
private readonly theme;
|
|
1000
|
+
/** Serializes engine (re)binding/loading so concurrent effect fires can't race. */
|
|
1001
|
+
private opChain;
|
|
1002
|
+
constructor();
|
|
1003
|
+
ngOnDestroy(): void;
|
|
1004
|
+
private pointerInside;
|
|
1005
|
+
protected onPointerEnter(): void;
|
|
1006
|
+
protected onPointerLeave(): void;
|
|
1007
|
+
protected onKeydown(event: KeyboardEvent): void;
|
|
1008
|
+
protected onKeyup(event: KeyboardEvent): void;
|
|
1009
|
+
protected onPaste(event: ClipboardEvent): void;
|
|
1010
|
+
/** Reset zoom + viewport so the image fits the stage. */
|
|
1011
|
+
protected fitToScreen(): void;
|
|
1012
|
+
protected toggleSnap(): void;
|
|
1013
|
+
protected onArtboardChange(size: ArtboardSize | null): void;
|
|
1014
|
+
protected toggleRulers(): void;
|
|
1015
|
+
/** Clear all user-placed guides (the ruler corner button). */
|
|
1016
|
+
protected clearGuides(): void;
|
|
1017
|
+
/**
|
|
1018
|
+
* Begin dragging a new guide out of a ruler. The top ruler pulls a horizontal
|
|
1019
|
+
* guide; the left ruler a vertical one. A live preview tracks the pointer;
|
|
1020
|
+
* releasing over the canvas commits it, releasing outside cancels.
|
|
1021
|
+
*/
|
|
1022
|
+
protected startGuideDraft(orientation: 'h' | 'v', event: PointerEvent): void;
|
|
1023
|
+
/** Paint both ruler strips from the engine's viewport, scaled for the display. */
|
|
1024
|
+
private renderRulers;
|
|
1025
|
+
/**
|
|
1026
|
+
* Paint the user's guides (and any live draft) onto a dedicated overlay canvas
|
|
1027
|
+
* that sits above the Fabric canvas. Drawing here — rather than on Fabric's own
|
|
1028
|
+
* overlay context — keeps guides stable, since Fabric clears its overlay on its
|
|
1029
|
+
* own schedule (e.g. on mouse-up) without redrawing ours.
|
|
1030
|
+
*/
|
|
1031
|
+
private renderGuidesOverlay;
|
|
1032
|
+
protected duplicate(): void;
|
|
1033
|
+
private ensureEngineAndLoad;
|
|
1034
|
+
/** Load a source into the engine, emitting imageLoaded / errorOccurred. */
|
|
1035
|
+
private loadSource;
|
|
1036
|
+
private emitError;
|
|
1037
|
+
/** Surface a dismissible error toast, auto-clearing after a few seconds. */
|
|
1038
|
+
private showErrorToast;
|
|
1039
|
+
protected dismissErrorToast(): void;
|
|
1040
|
+
private observeResize;
|
|
1041
|
+
private resetUiState;
|
|
1042
|
+
private sync;
|
|
1043
|
+
private refreshLayers;
|
|
1044
|
+
protected onSelectLayer(event: LayerSelectEvent): void;
|
|
1045
|
+
protected onReorderLayers(orderedIds: readonly string[]): void;
|
|
1046
|
+
protected onRenameLayer(event: LayerRenameEvent): void;
|
|
1047
|
+
protected onToggleLayerLock(id: string): void;
|
|
1048
|
+
protected onToggleLayerVisible(id: string): void;
|
|
1049
|
+
protected onMoveLayer(id: string, direction: 'up' | 'down'): void;
|
|
1050
|
+
protected onDeleteLayer(id: string): void;
|
|
1051
|
+
protected onLayerOpacityInput(change: {
|
|
1052
|
+
id: string;
|
|
1053
|
+
value: number;
|
|
1054
|
+
}): void;
|
|
1055
|
+
protected onLayerOpacityCommit(change: {
|
|
1056
|
+
id: string;
|
|
1057
|
+
value: number;
|
|
1058
|
+
}): void;
|
|
1059
|
+
protected selectTool(tool: AspTool): void;
|
|
1060
|
+
protected undo(): Promise<void>;
|
|
1061
|
+
protected redo(): Promise<void>;
|
|
1062
|
+
/** Pull tool/adjustment/look/frame state from the engine into the panel signals. */
|
|
1063
|
+
private syncUiFromEngine;
|
|
1064
|
+
protected zoomIn(): void;
|
|
1065
|
+
protected zoomOut(): void;
|
|
1066
|
+
protected togglePicker(): void;
|
|
1067
|
+
protected toggleExport(): void;
|
|
1068
|
+
protected toggleHistory(): void;
|
|
1069
|
+
protected pickSample(sample: SampleImage): Promise<void>;
|
|
1070
|
+
protected onUpload(event: Event): Promise<void>;
|
|
1071
|
+
/** Add an uploaded image as a new movable layer (composite, not replace). */
|
|
1072
|
+
protected onAddImageLayer(event: Event): Promise<void>;
|
|
1073
|
+
/** Download the current scene as a reusable template (JSON). */
|
|
1074
|
+
protected saveTemplate(): void;
|
|
1075
|
+
/** Load a template (JSON) saved by {@link saveTemplate} and sync the UI to it. */
|
|
1076
|
+
protected loadTemplate(event: Event): Promise<void>;
|
|
1077
|
+
protected setExportFormat(format: AspExportFormat): void;
|
|
1078
|
+
protected onExportQuality(event: Event): void;
|
|
1079
|
+
protected download(): Promise<void>;
|
|
1080
|
+
/** Save (basic modal): export to a Blob and emit `saved` without downloading. */
|
|
1081
|
+
protected save(): Promise<void>;
|
|
1082
|
+
protected cancel(): void;
|
|
1083
|
+
protected onZoomSlider(event: Event): void;
|
|
1084
|
+
protected reset(): void;
|
|
1085
|
+
protected onAdjustInput(change: AdjustChange): void;
|
|
1086
|
+
protected onAdjustCommit(change: AdjustChange): void;
|
|
1087
|
+
protected selectLook(look: AspFilter | null): void;
|
|
1088
|
+
/** Resolve a crop preset to an aspect ratio (w/h), or null for a free crop. */
|
|
1089
|
+
private ratioFromPreset;
|
|
1090
|
+
/** Start the crop frame if one isn't already active (e.g. from the basic-mode chips). */
|
|
1091
|
+
private ensureCropSession;
|
|
1092
|
+
/** Choose a crop aspect preset — reshapes (or starts) the live crop frame. */
|
|
1093
|
+
protected selectCrop(preset: AspAspectPreset): void;
|
|
1094
|
+
/** Choose a custom crop aspect (e.g. a CMS target) — reshapes (or starts) the live frame. */
|
|
1095
|
+
protected selectCustomCrop(option: AspAspectOption): void;
|
|
1096
|
+
/** Commit the crop frame as the output region, then return to Select. */
|
|
1097
|
+
protected applyCrop(): void;
|
|
1098
|
+
/** Discard the in-progress crop frame and return to Select. */
|
|
1099
|
+
protected cancelCrop(): void;
|
|
1100
|
+
/** Clear any applied crop region and restart the frame at the current ratio. */
|
|
1101
|
+
protected resetCrop(): void;
|
|
1102
|
+
protected rotate(deg: number): void;
|
|
1103
|
+
protected flip(axis: 'h' | 'v'): void;
|
|
1104
|
+
protected onStraightenInput(value: number): void;
|
|
1105
|
+
protected onStraightenCommit(value: number): void;
|
|
1106
|
+
protected addShape(kind: ShapeKind): void;
|
|
1107
|
+
/** Live corner-radius drag: update a selected rectangle without committing. */
|
|
1108
|
+
protected onCornerRadiusInput(radius: number): void;
|
|
1109
|
+
/** Corner-radius release: commit the selected rectangle's radius to history. */
|
|
1110
|
+
protected onCornerRadiusCommit(radius: number): void;
|
|
1111
|
+
protected addText(text: string): void;
|
|
1112
|
+
protected onFontChange(value: string): void;
|
|
1113
|
+
protected onAddCustomFont(name: string): void;
|
|
1114
|
+
protected groupSelection(): void;
|
|
1115
|
+
protected ungroupSelection(): void;
|
|
1116
|
+
protected alignSelection(mode: AlignMode): void;
|
|
1117
|
+
/** Reflect the selected object's editable style into the panel signals. */
|
|
1118
|
+
private onSelectionChange;
|
|
1119
|
+
protected toggleBold(): void;
|
|
1120
|
+
protected toggleItalic(): void;
|
|
1121
|
+
protected toggleUnderline(): void;
|
|
1122
|
+
protected toggleStrike(): void;
|
|
1123
|
+
protected setTextAlign(align: string): void;
|
|
1124
|
+
protected setLineHeight(value: number): void;
|
|
1125
|
+
protected setLetterSpacing(value: number): void;
|
|
1126
|
+
protected setTextBg(color: string): void;
|
|
1127
|
+
protected setRedactMode(mode: RedactMode): void;
|
|
1128
|
+
protected applyRedaction(): void;
|
|
1129
|
+
protected setFill(color: string): void;
|
|
1130
|
+
protected setAnnotationColor(color: string): void;
|
|
1131
|
+
/** Live size drag — apply to the selection without committing each frame. */
|
|
1132
|
+
protected onSizeInput(size: number): void;
|
|
1133
|
+
/** Size drag released — commit one history entry. */
|
|
1134
|
+
protected onSizeCommit(size: number): void;
|
|
1135
|
+
protected selectFrame(frame: string): void;
|
|
1136
|
+
protected setBackgroundColor(color: string): void;
|
|
1137
|
+
protected setBackgroundGradient(colors: string[]): void;
|
|
1138
|
+
protected setBackgroundImageFromFile(file: File): void;
|
|
1139
|
+
protected deleteSelection(): void;
|
|
1140
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AspImageEditor, never>;
|
|
1141
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<AspImageEditor, "asp-image-editor", never, { "src": { "alias": "src"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "height": { "alias": "height"; "required": false; "isSignal": true; }; "tools": { "alias": "tools"; "required": false; "isSignal": true; }; "disabledTools": { "alias": "disabledTools"; "required": false; "isSignal": true; }; "filters": { "alias": "filters"; "required": false; "isSignal": true; }; "aspectPresets": { "alias": "aspectPresets"; "required": false; "isSignal": true; }; "aspectRatios": { "alias": "aspectRatios"; "required": false; "isSignal": true; }; "exportFormats": { "alias": "exportFormats"; "required": false; "isSignal": true; }; "exportQuality": { "alias": "exportQuality"; "required": false; "isSignal": true; }; "baseColor": { "alias": "baseColor"; "required": false; "isSignal": true; }; "accentColor": { "alias": "accentColor"; "required": false; "isSignal": true; }; "themeMode": { "alias": "themeMode"; "required": false; "isSignal": true; }; "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "showHistory": { "alias": "showHistory"; "required": false; "isSignal": true; }; "keyboardEnabled": { "alias": "keyboardEnabled"; "required": false; "isSignal": true; }; "fonts": { "alias": "fonts"; "required": false; "isSignal": true; }; }, { "saved": "saved"; "canceled": "canceled"; "imageLoaded": "imageLoaded"; "exported": "exported"; "errorOccurred": "errorOccurred"; }, never, never, true, never>;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/** Options for {@link AspImageEditorDialog.open} / {@link openImageEditor}. */
|
|
1145
|
+
interface OpenImageEditorConfig {
|
|
1146
|
+
readonly src?: string | Blob | null;
|
|
1147
|
+
readonly heading?: string;
|
|
1148
|
+
readonly baseColor?: string;
|
|
1149
|
+
readonly accentColor?: string;
|
|
1150
|
+
readonly themeMode?: AspThemeMode;
|
|
1151
|
+
readonly aspectPresets?: AspAspectPreset[];
|
|
1152
|
+
readonly exportFormats?: AspExportFormat[];
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Opens the editor's `basic` layout in a modal overlay and resolves with the
|
|
1156
|
+
* saved image Blob, or `null` if the user cancels (close button, scrim click,
|
|
1157
|
+
* or Escape). Implemented without @angular/cdk so the package stays dependency-light.
|
|
1158
|
+
*/
|
|
1159
|
+
declare class AspImageEditorDialog {
|
|
1160
|
+
private readonly appRef;
|
|
1161
|
+
private readonly environmentInjector;
|
|
1162
|
+
open(config?: OpenImageEditorConfig): Promise<Blob | null>;
|
|
1163
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AspImageEditorDialog, never>;
|
|
1164
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<AspImageEditorDialog>;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
/**
|
|
1168
|
+
* Resolve the visible tool and filter sets from the public inputs.
|
|
1169
|
+
*
|
|
1170
|
+
* Tools: `tools` (explicit allowlist) ?? default-for-`mode`, then minus `disabledTools`.
|
|
1171
|
+
* Filters: `filters` (`'all'` | explicit list) ?? default-for-`mode`.
|
|
1172
|
+
*
|
|
1173
|
+
* In all cases the output is de-duplicated and restricted to known catalog
|
|
1174
|
+
* members, so a stray or misspelled entry can never crash the UI or render a
|
|
1175
|
+
* phantom control. Explicit lists preserve their given order; an explicit empty
|
|
1176
|
+
* list is honored as "show nothing" (it is an override, not a fall-through).
|
|
1177
|
+
*/
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Resolve the ordered set of enabled tools.
|
|
1181
|
+
*
|
|
1182
|
+
* @param mode baseline preset.
|
|
1183
|
+
* @param tools explicit allowlist, or `null` to use the mode default.
|
|
1184
|
+
* @param disabledTools tools to subtract from the resolved set.
|
|
1185
|
+
*/
|
|
1186
|
+
declare function resolveTools(mode: AspMode, tools: readonly AspTool[] | null, disabledTools: readonly AspTool[]): AspTool[];
|
|
1187
|
+
/**
|
|
1188
|
+
* Resolve the ordered set of enabled filters.
|
|
1189
|
+
*
|
|
1190
|
+
* @param mode baseline preset.
|
|
1191
|
+
* @param filters explicit list, the literal `'all'`, or `null` for the mode default.
|
|
1192
|
+
*/
|
|
1193
|
+
declare function resolveFilters(mode: AspMode, filters: readonly AspFilter[] | 'all' | null): AspFilter[];
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Apply a derived theme to an element as scoped CSS custom properties.
|
|
1197
|
+
*
|
|
1198
|
+
* Tokens are set on the element's inline `style`, so they cascade only to the
|
|
1199
|
+
* editor's own subtree and never leak into (or clash with) the host page. Hosts
|
|
1200
|
+
* can still override any individual `--asp-*` variable with higher specificity.
|
|
1201
|
+
*/
|
|
1202
|
+
declare function applyTheme(element: HTMLElement, tokens: AspThemeTokens): void;
|
|
1203
|
+
|
|
1204
|
+
export { ALL_FILTERS, ALL_TOOLS, AspImageEditor, AspImageEditorDialog, COLOR_TOKEN_NAMES, DEFAULT_FILTERS, DEFAULT_FONTS, DEFAULT_TOOLS, DeltaHistory, EditHistory, EditorEngine, FILTER_REGISTRY, STATIC_TOKEN_NAMES, THEME_TOKEN_NAMES, TOOL_REGISTRY, applyTheme, aspectOption, deriveTheme, resolveFilters, resolveTools };
|
|
1205
|
+
export type { AiProgress, AnnotationStyle, ArtboardSize, AspAspectOption, AspAspectPreset, AspEditorError, AspExportFormat, AspFilter, AspMode, AspSize, AspThemeMode, AspThemeTokens, AspTool, AspToolGroup, EngineOptions, FilterKind, FilterMeta, FontOption, HistoryEntry, HistoryStep, LayerInfo, ManualGuide, OpenImageEditorConfig, RedactMode, SelectionStyleInfo, ShapeKind, TextStyle, ThemeTokenName, ToolMeta, Viewport };
|