/**
* Transactional `loadImage` pipeline. Validates the input,
* snapshots every field that the pipeline is about to mutate
* into a {@link RollbackBundle}, decodes the data URL into an
* `HTMLImageElement` under the configured timeout, optionally
* downsamples via {@link resampleImage}, awaits
* `FabricImage.fromURL` under the same timeout, applies the
* layout strategy chosen by `image/layout-manager.ts`, commits
* the new image to the canvas, and emits a fresh
* `lastSnapshot` via `core/state-serializer.ts`. Any failure
* between the snapshot and the commit replays the bundle and
* rejects with the original error.
*
* ## Owned contracts
*
* - When `loadImage` rejects, the original error is
* routed through the public `onError(error, message)` callback via
* `core/callback-reporter.ts → reportError`, AFTER `replayRollback` has
* restored editor state. Callback exceptions are caught and logged so a
* faulty integrator callback cannot mask the original error that the
* loader re-throws. The success path does NOT invoke `onError`.
* - Strings that are not supported image data URLs
* resolve without mutating placeholder visibility, scroll position,
* image state, or canvas state. The function returns before
* capturing the rollback bundle, so no observable side effect occurs.
* - On a supported image data URL, the loader captures
* the rollback bundle *before* mutating any of the fields it tracks
* (placeholder `hidden`, container `scrollTop`/`scrollLeft`, container
* `originalImage`, `lastSnapshot`, the canvas JSON
* snapshot, plus the editor transform fields the rollback needs to
* restore the live canvas to a consistent state).
* - Decode, Fabric, downsample, and timeout failures
* restore every field captured in the rollback bundle and reject with the
* original error.
* - On success, `isImageLoadedToCanvas` is set to
* `true`, `lastSnapshot` is replaced with a fresh snapshot derived from
* the new canvas, and both overlay counters are reset to `0`.
* - Either the new image is committed fully, or the
* prior committed state is restored fully. No partial state is observable
* after the returned promise settles.
* - Both the decode step and the
* `FabricImage.fromURL` step are bounded by `options.imageLoadTimeoutMs`
* via {@link withTimeout}.
* - Timeout failures reject with
* {@link ImageLoadTimeoutError} (built by `utils/timeout.ts`) and replay
* the rollback bundle.
* - The 2D-context failure inside
* {@link resampleImage} surfaces as {@link DownsampleError}; the loader
* catches it and routes through the rollback path.
* - On success, `maskCounter` and `annotationCounter` are reset to `0`.
*
* ## Implementation notes
*
* The loader is an exported **function** that takes its dependencies in a
* {@link LoadImageContext} parameter rather than a class. The `ImageEditor`
* facade owns all editor state (the canvas reference, the placeholder
* element, the editor scalar fields), so the loader must read and write
* that state through a small set of getter/setter callbacks. The class
* shape stays on the facade; the loader remains stateless so the rollback
* bundle is the single source of truth for what the operation has captured.
*
* The rollback bundle is built before the loader hides the placeholder or
* touches the canvas. It captures *every* field listed in the documented
* RollbackBundle definition plus the editor scalar fields
* (`isImageLoadedToCanvas`, `maskCounter`, `annotationCounter`,
* `currentScale`, `currentRotation`, `baseImageScale`) the success path mutates. Restoring
* those scalars is required for atomic rollback — without them, a failed
* load that ran past the scalar reset would leave the editor with
* `currentScale = 1` and `currentRotation = 0` even though
* `originalImage` (and therefore the live canvas) had been rewound to the
* previous image.
*
* `preserveScroll` is honored on both the success path
* and the rollback path. On success it is conditional on
* `loadOptions.preserveScroll === true`; on rollback the bundle is replayed
* unconditionally, which the rollback contract already requires for
* transactional rewind. When `preserveScroll` is omitted or `false`, the
* success path leaves the container scroll untouched, so the documented
* scroll/viewport behavior for the selected layout mode prevails.
*
* The loader does not invoke public success callbacks. It owns
* transactional mutation and rollback; the `ImageEditor` facade emits
* `onImageLoaded`, `onImageChanged`, `onMasksChanged`, and related lifecycle
* callbacks after this function returns from a committed load. The rollback
* path still reports load failures through `onError` after replaying the
* rollback bundle.
*
* Owner module references (per the documented "Mapping Contracts to
* modules" table): this module is the canonical owner of the transactional
* load helpers. It is NOT re-exported from `src/index.ts`.
*
* @module
*/
import type * as FabricNS from 'fabric';
import type { BaseImageObject, FabricModule, ImageMimeType, LoadImageOptions, ResolvedOptions } from '../core/public-types.js';
import { type ViewportCache } from './layout-manager.js';
/**
* Snapshot of every field the loader is about to mutate, captured before
* the first mutation so a failure mid-pipeline can rewind the editor to
* its pre-call state.
*
* Mirrors the documented `RollbackBundle` definition with the addition of
* the editor scalar fields the success path also rewrites
* (`isImageLoadedToCanvas`, `maskCounter`, `annotationCounter`,
* `currentScale`, `currentRotation`, `baseImageScale`). Those scalars must
* be restored together with the canvas JSON for atomic rewind.
*
*/
export interface RollbackBundle {
/** `placeholderElement.hidden` immediately before the loader hid it. */
placeholderHidden: boolean | null;
/** Container `scrollTop` immediately before the loader started. */
containerScrollTop: number | null;
/** Container `scrollLeft` immediately before the loader started. */
containerScrollLeft: number | null;
/** The previously-committed `originalImage` reference, if any. */
originalImage: BaseImageObject | null;
/** Whether an image was already committed before this call. */
isImageLoadedToCanvas: boolean;
/** Snapshot string used as the history baseline before the call. */
lastSnapshot: string | null;
/**
* Full canvas JSON serialization captured via `canvas.toJSON` with the
* editor's custom keys. Restored via `loadFromJSON` on rollback.
*/
canvasJson: string;
/** Mask counter value before the loader reset it to 0. */
maskCounter: number;
/** Annotation counter value before the loader reset it to 0. */
annotationCounter: number;
/** Image scale factor before the loader reset it to 1. */
currentScale: number;
/** Image rotation in degrees before the loader reset it to 0. */
currentRotation: number;
/** Base scale chosen by the previous load, restored on rollback. */
baseImageScale: number;
/** MIME type of the image committed before the load started. */
currentImageMimeType: ImageMimeType | null;
}
/**
* Dependency bundle passed by the `ImageEditor` facade into
* {@link loadImage}. The loader has no class state of its own — every
* editor field it reads or writes is exposed here as a getter/setter pair
* so the facade keeps ownership of the canonical state.
*
* The facade is responsible for:
* - constructing the {@link ViewportCache} once and reusing it across
* loads (so hidden-tab fallbacks work),
* - providing a `setPlaceholderVisible` callback that delegates to
* `ui/visibility-state.ts`.
*
* The facade is also responsible for public success lifecycle callbacks after
* this transactional helper returns. The loader only reports failed loads
* through `onError` after rollback.
*/
export interface LoadImageContext {
/** The Fabric module providing `FabricImage.fromURL`. */
fabric: FabricModule;
/** The live Fabric canvas. */
canvas: FabricNS.Canvas;
/** Resolved editor options (timeouts, downsample knobs, layout flags). */
options: ResolvedOptions;
/** Scrollable container wrapping the canvas, or `null`. */
containerElement: HTMLElement | null;
/** Empty-state placeholder element, or `null`. */
placeholderElement: HTMLElement | null;
/** Hidden-container viewport cache shared with the layout manager. */
viewportCache: ViewportCache;
/** Reads the previously-committed `originalImage`. */
getOriginalImage(): BaseImageObject | null;
/** Writes `originalImage` (used both on commit and on rollback). */
setOriginalImage(imageObject: BaseImageObject | null): void;
/** Reads `isImageLoadedToCanvas`. */
getIsImageLoadedToCanvas(): boolean;
/** Writes `isImageLoadedToCanvas`. */
setIsImageLoadedToCanvas(v: boolean): void;
/** Reads `lastSnapshot`. */
getLastSnapshot(): string | null;
/** Writes `lastSnapshot`. */
setLastSnapshot(s: string | null): void;
/** Reads `maskCounter`. */
getMaskCounter(): number;
/** Writes `maskCounter`. */
setMaskCounter(n: number): void;
/** Reads `annotationCounter`. */
getAnnotationCounter(): number;
/** Writes `annotationCounter`. */
setAnnotationCounter(n: number): void;
/** Reads `currentScale`. */
getCurrentScale(): number;
/** Writes `currentScale`. */
setCurrentScale(n: number): void;
/** Reads `currentRotation`. */
getCurrentRotation(): number;
/** Writes `currentRotation`. */
setCurrentRotation(n: number): void;
/** Reads `baseImageScale`. */
getBaseImageScale(): number;
/** Writes `baseImageScale`. */
setBaseImageScale(n: number): void;
/** Reads the MIME type of the currently committed image. */
getCurrentImageMimeType(): ImageMimeType | null;
/** Writes the MIME type of the currently committed image. */
setCurrentImageMimeType(mimeType: ImageMimeType | null): void;
/**
* Toggle placeholder/canvas-container visibility via
* `ui/visibility-state.ts`. `show === false` means "an image is now on
* the canvas — hide the placeholder".
*/
setPlaceholderVisible(show: boolean): void;
}
/**
* Transactional image loader. Loads a base64 data URL onto the Fabric
* canvas with full rollback on any failure.
*
* Steps, in order:
*
* 1. **Validate** — non-`data:image/` strings resolve
* immediately without capturing the bundle or touching state.
* 2. **Snapshot** — capture every field the pipeline
* will mutate into a {@link RollbackBundle}.
* 3. **Hide placeholder** — first observable mutation. Restored on
* rollback.
* 4. **Decode** — race the `
.onload` against
* `imageLoadTimeoutMs` via {@link withTimeout}.
* 5. **Downsample** — if the source exceeds the
* configured bounds, run {@link resampleImage}; a 2D-context failure
* surfaces as {@link DownsampleError} and triggers rollback.
* 6. **Fabric load** — `FabricImage.fromURL` under the
* same timeout.
* 7. **Layout** — pick a strategy via {@link selectLayoutStrategy} and
* apply via {@link applyCanvasDimensions}.
* 8. **Commit** — set `isImageLoadedToCanvas`,
* reset both overlay counters to 0, reset transforms, and emit a
* fresh `lastSnapshot` via {@link saveState}.
*
* Any rejection between step 3 and step 8 routes through {@link replayRollback}
* before re-throwing the original error. On the rollback
* path, the original error is also dispatched to the public `onError`
* callback via {@link reportError}; the helper catches
* and logs callback exceptions so a faulty integrator callback cannot
* replace the original error that this function re-throws.
*
* `preserveScroll` is honored on success when
* `loadOptions.preserveScroll === true`: after the canvas has been resized
* and the new image committed, the captured pre-load `scrollTop` and
* `scrollLeft` are written back to the container. The rollback path always
* restores scroll regardless of `preserveScroll` because the rollback
* requires the bundle to be replayed in full on failure. When
* `preserveScroll` is omitted or `false`, the success path leaves scroll
* untouched and legacy's documented scroll/viewport behavior for the selected
* layout mode applies.
*
* Public success lifecycle callbacks are emitted by the facade after this
* helper returns from a committed load. Keeping them outside the loader keeps
* transactional mutation/rollback separate from facade-level event ordering.
*
* @param context - Editor dependency bundle.
* @param imageBase64 - Supported image data URL to load.
* @param loadOptions - Public {@link LoadImageOptions}. Currently only
* `preserveScroll` is consulted; defaults to `false`.
* @returns Resolved promise on success, rejected with the original error
* (after rollback) on failure. Unsupported inputs resolve without
* observable mutation.
*
*/
export declare function loadImage(context: LoadImageContext, imageBase64: string, loadOptions?: LoadImageOptions): Promise;
//# sourceMappingURL=image-loader.d.ts.map