js-draw
Version:
Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.
286 lines (285 loc) • 12 kB
TypeScript
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
import Viewport from '../Viewport';
import AbstractComponent from '../components/AbstractComponent';
import { Rect2 } from '@js-draw/math';
import RenderingCache from '../rendering/caching/RenderingCache';
import SerializableCommand from '../commands/SerializableCommand';
import EventDispatcher from '../EventDispatcher';
import Command from '../commands/Command';
export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;
export declare enum EditorImageEventType {
ExportViewportChanged = 0,
AutoresizeModeChanged = 1,
ComponentAdded = 2,
ComponentRemoved = 3
}
interface StateChangeEvent {
kind: EditorImageEventType.ExportViewportChanged | EditorImageEventType.AutoresizeModeChanged;
image: EditorImage;
}
interface ComponentAddRemoveEvent {
kind: EditorImageEventType.ComponentAdded | EditorImageEventType.ComponentRemoved;
image: EditorImage;
componentId: string;
}
export type EditorImageEvent = StateChangeEvent | ComponentAddRemoveEvent;
export type EditorImageNotifier = EventDispatcher<EditorImageEventType, EditorImageEvent>;
/**
* A callback used to
* 1. pause the render process
* 2. observe progress through `componentsProcessed` and `totalComponents`
* 3. stop the render process early by returning `false`.
*/
export type PreRenderComponentCallback = (component: AbstractComponent, componentsProcessed: number, totalComponents: number) => Promise<boolean>;
/**
* Handles lookup/storage of elements in the image.
*
* `js-draw` images are made up of a collection of {@link AbstractComponent}s (which
* includes {@link Stroke}s, {@link TextComponent}s, etc.). An `EditorImage`
* is the data structure that stores these components.
*
* Here's how to do a few common operations:
* - **Get all components in a {@link @js-draw/math!Rect2 | Rect2}**:
* {@link EditorImage.getComponentsIntersecting}.
* - **Draw an `EditorImage` onto a canvas/SVG**: {@link EditorImage.render}.
* - **Adding a new component**: {@link EditorImage.addComponent}.
*
* **Example**:
* [[include:doc-pages/inline-examples/image-add-and-lookup.md]]
*/
export default class EditorImage {
private root;
private background;
private componentsById;
private componentCount;
/** Viewport for the exported/imported image. */
private importExportViewport;
private shouldAutoresizeExportViewport;
readonly notifier: EditorImageNotifier;
constructor();
getBackgroundComponents(): AbstractComponent[];
findParent(elem: AbstractComponent): ImageNode | null;
queueRerenderOf(elem: AbstractComponent): void;
/** @internal */
renderWithCache(screenRenderer: AbstractRenderer, cache: RenderingCache, viewport: Viewport): void;
/**
* Renders this image to the given `renderer`.
*
* If `viewport` is non-null, only components that can be seen from that viewport
* will be rendered. If `viewport` is `null`, **all** components are rendered.
*
* **Example**:
* [[include:doc-pages/inline-examples/canvas-renderer.md]]
*/
render(renderer: AbstractRenderer, viewport: Viewport | null): void;
/**
* Like {@link renderAll}, but can be stopped early and paused.
*
* **Note**: If the image is being edited during an async rendering, there is no
* guarantee that all nodes will be rendered correctly (some may be missing).
*
* @internal
*/
renderAllAsync(renderer: AbstractRenderer, preRenderComponent: PreRenderComponentCallback): Promise<boolean>;
/**
* Renders all nodes, even ones not within the viewport.
*
* This can be slow for large images
* @internal
*/
renderAll(renderer: AbstractRenderer): void;
/**
* @returns all elements in the image, sorted by z-index (low to high).
*
* This can be slow for large images. If you only need all elemenst in part of the image,
* consider using {@link getComponentsIntersecting} instead.
*
* **Note**: The result does not include background elements. See {@link getBackgroundComponents}.
*/
getAllComponents(): AbstractComponent[];
/** @deprecated in favor of {@link getAllComponents} */
getAllElements(): AbstractComponent[];
/** Returns the number of elements added to this image. @internal */
estimateNumElements(): number;
/** @deprecated @see getComponentsIntersecting */
getElementsIntersectingRegion(region: Rect2, includeBackground?: boolean): AbstractComponent[];
/**
* @returns a list of `AbstractComponent`s intersecting `region`, sorted by increasing z-index.
*
* Components in the background layer are only included if `includeBackground` is `true`.
*/
getComponentsIntersecting(region: Rect2, includeBackground?: boolean): AbstractComponent[];
/** Called whenever (just after) an element is completely removed. @internal */
onDestroyElement(elem: AbstractComponent): void;
/** Called just after an element is added. @internal */
private onElementAdded;
/**
* @returns the AbstractComponent with `id`, if it exists.
*
* @see {@link AbstractComponent.getId}
*/
lookupElement(id: string): AbstractComponent | null;
private addComponentDirectly;
private removeElementDirectly;
/**
* Returns a command that adds the given element to the `EditorImage`.
* If `applyByFlattening` is true, the content of the wet ink renderer is
* rendered onto the main rendering canvas instead of doing a full re-render.
*
* @see {@link Display.flatten}
*
* **Example**:
*
* [[include:doc-pages/inline-examples/adding-a-stroke.md]]
*/
static addComponent(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
/** @see EditorImage.addComponent */
addComponent(component: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
/** Alias for {@link addComponent}. @deprecated Prefer `.addComponent` */
addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
/** Alias for {@link addComponent}. @deprecated Prefer `.addComponent`. */
static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
private static AddComponentCommand;
/**
* @returns a `Viewport` for rendering the image when importing/exporting.
*/
getImportExportViewport(): Viewport;
/**
* @see {@link setImportExportRect}
*/
getImportExportRect(): Rect2;
/**
* Sets the import/export rectangle to the given `imageRect`. Disables
* autoresize if it was previously enabled.
*
* **Note**: The import/export rectangle is the same as the size of any
* {@link BackgroundComponent}s (and other components that auto-resize).
*/
setImportExportRect(imageRect: Rect2): SerializableCommand;
/** @see {@link setAutoresizeEnabled} */
getAutoresizeEnabled(): boolean;
/**
* Returns a `Command` that sets whether the image should autoresize when
* {@link AbstractComponent}s are added/removed.
*
* @example
*
* ```ts,runnable
* import { Editor } from 'js-draw';
*
* const editor = new Editor(document.body);
* const toolbar = editor.addToolbar();
*
* // Add a save button to demonstrate what the output looks like
* // (it should change size to fit whatever was drawn)
* toolbar.addSaveButton(() => {
* document.body.replaceChildren(editor.toSVG({ sanitize: true }));
* });
*
* // Actually using setAutoresizeEnabled:
* //
* // To set autoresize without announcing for accessibility/making undoable
* const addToHistory = false;
* editor.dispatchNoAnnounce(editor.image.setAutoresizeEnabled(true), addToHistory);
*
* // Add to undo history **and** announce for accessibility
* //editor.dispatch(editor.image.setAutoresizeEnabled(true), true);
* ```
*/
setAutoresizeEnabled(autoresize: boolean): Command;
private setAutoresizeEnabledDirectly;
/** Updates the size/position of the viewport */
private autoresizeExportViewport;
private settingExportRect;
/**
* Sets the import/export viewport directly, without returning a `Command`.
* As such, this is not undoable.
*
* See setImportExportRect
*
* Returns true if changes to the viewport were made (and thus
* ExportViewportChanged was fired.)
*/
private setExportRectDirectly;
private onExportViewportChanged;
/**
* @internal
*
* Enables debug mode for **all** `EditorImage`s.
*
* **Only use for debugging**.
*
* @internal
*/
static setDebugMode(newDebugMode: boolean): void;
private static SetImportExportRectCommand;
}
/**
* Determines the first index in `sortedLeaves` that needs to be rendered
* (based on occlusion -- everything before that index can be skipped and
* produce a visually-equivalent image).
*
* Does nothing if visibleRect is not provided
*
* @internal
*/
export declare const computeFirstIndexToRender: (sortedLeaves: Array<ImageNode>, visibleRect?: Rect2) => number;
type TooSmallToRenderCheck = (rect: Rect2) => boolean;
/**
* Part of the Editor's image. Does not handle fullscreen/invisible components.
* @internal
*/
export declare class ImageNode {
private parent;
private content;
private bbox;
private children;
private targetChildCount;
private id;
private static idCounter;
constructor(parent?: ImageNode | null);
getId(): number;
onContentChange(): void;
getContent(): AbstractComponent | null;
getParent(): ImageNode | null;
protected getChildrenIntersectingRegion(region: Rect2, isTooSmallFilter?: TooSmallToRenderCheck): ImageNode[];
getChildrenOrSelfIntersectingRegion(region: Rect2, isTooSmall?: TooSmallToRenderCheck): ImageNode[];
/**
* Returns a list of `ImageNode`s with content (and thus no children).
* Override getChildrenIntersectingRegion to customize how this method
* determines whether/which children are in `region`.
*
* @paran region - All resultant `ImageNode`s must intersect `region`.
* @param isTooSmall - If `isTooSmall` returns true for an image node, that node
* is excluded from the output.
*
*/
getLeavesIntersectingRegion(region: Rect2, isTooSmall?: TooSmallToRenderCheck): ImageNode[];
getChildWithContent(target: AbstractComponent): ImageNode | null;
getLeaves(): ImageNode[];
addLeaf(leaf: AbstractComponent): ImageNode;
protected static createLeafNode(parent: ImageNode, content: AbstractComponent): ImageNode;
getBBox(): Rect2;
recomputeBBox(bubbleUp: boolean): void;
private unionBBoxWith;
private updateParents;
private rebalance;
protected removeChild(child: ImageNode): void;
remove(): void;
renderAllAsync(renderer: AbstractRenderer, preRenderComponent: PreRenderComponentCallback): Promise<boolean>;
render(renderer: AbstractRenderer, visibleRect?: Rect2): void;
renderDebugBoundingBoxes(renderer: AbstractRenderer, visibleRect: Rect2, depth?: number): void;
private checkRep;
}
/** An `ImageNode` that can properly handle fullscreen/data components. @internal */
export declare class RootImageNode extends ImageNode {
private fullscreenChildren;
private dataComponents;
protected getChildrenIntersectingRegion(region: Rect2, _isTooSmall?: TooSmallToRenderCheck): ImageNode[];
getChildrenOrSelfIntersectingRegion(region: Rect2, _isTooSmall?: TooSmallToRenderCheck): ImageNode[];
getLeaves(): ImageNode[];
removeChild(child: ImageNode): void;
getChildWithContent(target: AbstractComponent): ImageNode | null;
addLeaf(leafContent: AbstractComponent): ImageNode;
}
export {};