js-draw 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/translation.yml +56 -0
- package/CHANGELOG.md +13 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +1 -1
- package/dist/src/Color4.js +5 -1
- package/dist/src/Editor.d.ts +11 -2
- package/dist/src/Editor.js +66 -33
- package/dist/src/EditorImage.d.ts +28 -3
- package/dist/src/EditorImage.js +109 -18
- package/dist/src/EventDispatcher.d.ts +4 -3
- package/dist/src/SVGLoader.d.ts +1 -0
- package/dist/src/SVGLoader.js +15 -1
- package/dist/src/Viewport.d.ts +8 -3
- package/dist/src/Viewport.js +15 -8
- package/dist/src/components/AbstractComponent.d.ts +6 -1
- package/dist/src/components/AbstractComponent.js +15 -2
- package/dist/src/components/ImageBackground.d.ts +42 -0
- package/dist/src/components/ImageBackground.js +139 -0
- package/dist/src/components/ImageComponent.js +2 -0
- package/dist/src/components/builders/ArrowBuilder.d.ts +3 -1
- package/dist/src/components/builders/ArrowBuilder.js +43 -40
- package/dist/src/components/builders/LineBuilder.d.ts +3 -1
- package/dist/src/components/builders/LineBuilder.js +25 -28
- package/dist/src/components/builders/RectangleBuilder.js +1 -1
- package/dist/src/components/lib.d.ts +2 -1
- package/dist/src/components/lib.js +2 -1
- package/dist/src/components/localization.d.ts +2 -0
- package/dist/src/components/localization.js +2 -0
- package/dist/src/localizations/es.js +1 -1
- package/dist/src/math/Mat33.js +43 -5
- package/dist/src/math/Path.d.ts +5 -0
- package/dist/src/math/Path.js +80 -28
- package/dist/src/math/Vec3.js +1 -1
- package/dist/src/rendering/Display.js +1 -1
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +13 -1
- package/dist/src/rendering/renderers/AbstractRenderer.js +18 -3
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +12 -2
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
- package/dist/src/testing/sendTouchEvent.d.ts +6 -0
- package/dist/src/testing/sendTouchEvent.js +26 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +25 -2
- package/dist/src/toolbar/HTMLToolbar.js +127 -15
- package/dist/src/toolbar/IconProvider.d.ts +2 -0
- package/dist/src/toolbar/IconProvider.js +45 -2
- package/dist/src/toolbar/localization.d.ts +5 -0
- package/dist/src/toolbar/localization.js +5 -0
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +5 -1
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/BaseToolWidget.js +2 -1
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +7 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +23 -1
- package/dist/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +19 -0
- package/dist/src/toolbar/widgets/DocumentPropertiesWidget.js +135 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +1 -1
- package/dist/src/toolbar/widgets/OverflowWidget.d.ts +25 -0
- package/dist/src/toolbar/widgets/OverflowWidget.js +65 -0
- package/dist/src/toolbar/widgets/lib.d.ts +1 -0
- package/dist/src/toolbar/widgets/lib.js +1 -0
- package/dist/src/tools/Eraser.js +5 -2
- package/dist/src/tools/PanZoom.js +12 -0
- package/dist/src/tools/PasteHandler.js +2 -2
- package/dist/src/tools/SelectionTool/Selection.d.ts +2 -1
- package/dist/src/tools/SelectionTool/Selection.js +3 -2
- package/dist/src/tools/SelectionTool/SelectionTool.js +5 -1
- package/package.json +1 -1
- package/src/Color4.test.ts +6 -0
- package/src/Color4.ts +6 -1
- package/src/Editor.loadFrom.test.ts +24 -0
- package/src/Editor.ts +73 -39
- package/src/EditorImage.ts +136 -21
- package/src/EventDispatcher.ts +4 -1
- package/src/SVGLoader.ts +12 -1
- package/src/Viewport.ts +17 -7
- package/src/components/AbstractComponent.ts +17 -1
- package/src/components/ImageBackground.test.ts +35 -0
- package/src/components/ImageBackground.ts +176 -0
- package/src/components/ImageComponent.ts +2 -0
- package/src/components/builders/ArrowBuilder.ts +44 -41
- package/src/components/builders/LineBuilder.ts +26 -28
- package/src/components/builders/RectangleBuilder.ts +1 -1
- package/src/components/lib.ts +2 -0
- package/src/components/localization.ts +4 -0
- package/src/localizations/es.ts +8 -0
- package/src/math/Mat33.test.ts +47 -3
- package/src/math/Mat33.ts +47 -5
- package/src/math/Path.ts +87 -28
- package/src/math/Vec3.test.ts +4 -0
- package/src/math/Vec3.ts +1 -1
- package/src/rendering/Display.ts +1 -1
- package/src/rendering/renderers/AbstractRenderer.ts +20 -3
- package/src/rendering/renderers/CanvasRenderer.ts +17 -4
- package/src/rendering/renderers/DummyRenderer.test.ts +1 -2
- package/src/rendering/renderers/SVGRenderer.ts +8 -1
- package/src/testing/sendTouchEvent.ts +43 -0
- package/src/toolbar/HTMLToolbar.ts +164 -16
- package/src/toolbar/IconProvider.ts +47 -2
- package/src/toolbar/localization.ts +10 -0
- package/src/toolbar/toolbar.css +2 -0
- package/src/toolbar/widgets/ActionButtonWidget.ts +5 -0
- package/src/toolbar/widgets/BaseToolWidget.ts +3 -1
- package/src/toolbar/widgets/BaseWidget.ts +34 -2
- package/src/toolbar/widgets/DocumentPropertiesWidget.ts +185 -0
- package/src/toolbar/widgets/HandToolWidget.ts +1 -1
- package/src/toolbar/widgets/OverflowWidget.css +9 -0
- package/src/toolbar/widgets/OverflowWidget.ts +83 -0
- package/src/toolbar/widgets/lib.ts +2 -1
- package/src/tools/Eraser.test.ts +24 -1
- package/src/tools/Eraser.ts +6 -2
- package/src/tools/PanZoom.test.ts +267 -23
- package/src/tools/PanZoom.ts +15 -1
- package/src/tools/PasteHandler.ts +3 -2
- package/src/tools/SelectionTool/Selection.ts +3 -2
- package/src/tools/SelectionTool/SelectionTool.ts +6 -1
- package/src/types.ts +1 -0
package/dist/src/Color4.d.ts
CHANGED
@@ -5,7 +5,7 @@ export default class Color4 {
|
|
5
5
|
readonly g: number;
|
6
6
|
/** Blue component. `b` ∈ [0, 1] */
|
7
7
|
readonly b: number;
|
8
|
-
/** Alpha/transparent component. `a` ∈ [0, 1] */
|
8
|
+
/** Alpha/transparent component. `a` ∈ [0, 1]. 0 = transparent */
|
9
9
|
readonly a: number;
|
10
10
|
private constructor();
|
11
11
|
/**
|
package/dist/src/Color4.js
CHANGED
@@ -6,7 +6,7 @@ export default class Color4 {
|
|
6
6
|
g,
|
7
7
|
/** Blue component. `b` ∈ [0, 1] */
|
8
8
|
b,
|
9
|
-
/** Alpha/transparent component. `a` ∈ [0, 1] */
|
9
|
+
/** Alpha/transparent component. `a` ∈ [0, 1]. 0 = transparent */
|
10
10
|
a) {
|
11
11
|
this.r = r;
|
12
12
|
this.g = g;
|
@@ -103,6 +103,10 @@ export default class Color4 {
|
|
103
103
|
if (other == null) {
|
104
104
|
return false;
|
105
105
|
}
|
106
|
+
// If both completely transparent,
|
107
|
+
if (this.a === 0 && other.a === 0) {
|
108
|
+
return true;
|
109
|
+
}
|
106
110
|
return this.toHexString() === other.toHexString();
|
107
111
|
}
|
108
112
|
/**
|
package/dist/src/Editor.d.ts
CHANGED
@@ -8,6 +8,7 @@ import { Point2 } from './math/Vec2';
|
|
8
8
|
import HTMLToolbar from './toolbar/HTMLToolbar';
|
9
9
|
import { RenderablePathSpec } from './rendering/renderers/AbstractRenderer';
|
10
10
|
import Display, { RenderingMode } from './rendering/Display';
|
11
|
+
import Color4 from './Color4';
|
11
12
|
import Pointer from './Pointer';
|
12
13
|
import Rect2 from './math/Rect2';
|
13
14
|
import { EditorLocalization } from './localization';
|
@@ -84,8 +85,6 @@ export declare class Editor {
|
|
84
85
|
* ```
|
85
86
|
*/
|
86
87
|
readonly image: EditorImage;
|
87
|
-
/** Viewport for the exported/imported image. */
|
88
|
-
private importExportViewport;
|
89
88
|
/**
|
90
89
|
* Allows transforming the view and querying information about
|
91
90
|
* what is currently visible.
|
@@ -224,7 +223,17 @@ export declare class Editor {
|
|
224
223
|
addAndCenterComponents(components: AbstractComponent[], selectComponents?: boolean): Promise<void>;
|
225
224
|
toDataURL(format?: 'image/png' | 'image/jpeg' | 'image/webp'): string;
|
226
225
|
toSVG(): SVGElement;
|
226
|
+
/**
|
227
|
+
* Load editor data from an `ImageLoader` (e.g. an {@link SVGLoader}).
|
228
|
+
*
|
229
|
+
* @see loadFromSVG
|
230
|
+
*/
|
227
231
|
loadFrom(loader: ImageLoader): Promise<void>;
|
232
|
+
private getTopmostBackgroundComponent;
|
233
|
+
/**
|
234
|
+
* Set the background color of the image.
|
235
|
+
*/
|
236
|
+
setBackgroundColor(color: Color4): Command;
|
228
237
|
getImportExportRect(): Rect2;
|
229
238
|
setImportExportRect(imageRect: Rect2): Command;
|
230
239
|
/**
|
package/dist/src/Editor.js
CHANGED
@@ -10,7 +10,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
10
10
|
import EditorImage from './EditorImage';
|
11
11
|
import ToolController from './tools/ToolController';
|
12
12
|
import { InputEvtType, EditorEventType } from './types';
|
13
|
-
import Command from './commands/Command';
|
14
13
|
import UndoRedoHistory from './UndoRedoHistory';
|
15
14
|
import Viewport from './Viewport';
|
16
15
|
import EventDispatcher from './EventDispatcher';
|
@@ -31,6 +30,8 @@ import untilNextAnimationFrame from './util/untilNextAnimationFrame';
|
|
31
30
|
import fileToBase64 from './util/fileToBase64';
|
32
31
|
import uniteCommands from './commands/uniteCommands';
|
33
32
|
import SelectionTool from './tools/SelectionTool/SelectionTool';
|
33
|
+
import Erase from './commands/Erase';
|
34
|
+
import ImageBackground, { BackgroundType } from './components/ImageBackground';
|
34
35
|
/**
|
35
36
|
* The main entrypoint for the full editor.
|
36
37
|
*
|
@@ -119,15 +120,18 @@ export class Editor {
|
|
119
120
|
this.renderingRegion.setAttribute('tabIndex', '0');
|
120
121
|
this.renderingRegion.setAttribute('alt', '');
|
121
122
|
this.notifier = new EventDispatcher();
|
122
|
-
this.
|
123
|
-
|
123
|
+
this.viewport = new Viewport((oldTransform, newTransform) => {
|
124
|
+
this.notifier.dispatch(EditorEventType.ViewportChanged, {
|
125
|
+
kind: EditorEventType.ViewportChanged,
|
126
|
+
newTransform,
|
127
|
+
oldTransform,
|
128
|
+
});
|
129
|
+
});
|
124
130
|
this.display = new Display(this, this.settings.renderingMode, this.renderingRegion);
|
125
131
|
this.image = new EditorImage();
|
126
132
|
this.history = new UndoRedoHistory(this, this.announceRedoCallback, this.announceUndoCallback);
|
127
133
|
this.toolController = new ToolController(this, this.localization);
|
128
134
|
parent.appendChild(this.container);
|
129
|
-
// Default to a 500x500 image
|
130
|
-
this.importExportViewport.updateScreenSize(Vec2.of(500, 500));
|
131
135
|
this.viewport.updateScreenSize(Vec2.of(this.display.width, this.display.height));
|
132
136
|
this.registerListeners();
|
133
137
|
this.queueRerender();
|
@@ -231,14 +235,21 @@ export class Editor {
|
|
231
235
|
});
|
232
236
|
this.notifier.on(EditorEventType.DisplayResized, _event => {
|
233
237
|
this.viewport.updateScreenSize(Vec2.of(this.display.width, this.display.height));
|
238
|
+
this.queueRerender();
|
234
239
|
});
|
235
|
-
|
240
|
+
const handleResize = () => {
|
236
241
|
this.notifier.dispatch(EditorEventType.DisplayResized, {
|
237
242
|
kind: EditorEventType.DisplayResized,
|
238
243
|
newSize: Vec2.of(this.display.width, this.display.height),
|
239
244
|
});
|
240
|
-
|
241
|
-
|
245
|
+
};
|
246
|
+
if ('ResizeObserver' in window) {
|
247
|
+
const resizeObserver = new ResizeObserver(handleResize);
|
248
|
+
resizeObserver.observe(this.container);
|
249
|
+
}
|
250
|
+
else {
|
251
|
+
addEventListener('resize', handleResize);
|
252
|
+
}
|
242
253
|
this.accessibilityControlArea.addEventListener('input', () => {
|
243
254
|
this.accessibilityControlArea.value = '';
|
244
255
|
});
|
@@ -582,7 +593,7 @@ export class Editor {
|
|
582
593
|
if (showImageBounds) {
|
583
594
|
const exportRectFill = { fill: Color4.fromHex('#44444455') };
|
584
595
|
const exportRectStrokeWidth = 5 * this.viewport.getSizeOfPixelOnCanvas();
|
585
|
-
renderer.drawRect(this.
|
596
|
+
renderer.drawRect(this.getImportExportRect(), exportRectStrokeWidth, exportRectFill);
|
586
597
|
}
|
587
598
|
this.rerenderQueued = false;
|
588
599
|
this.nextRerenderListeners.forEach(listener => listener());
|
@@ -693,21 +704,22 @@ export class Editor {
|
|
693
704
|
// The export resolution is the same as the size of the drawing canvas.
|
694
705
|
toDataURL(format = 'image/png') {
|
695
706
|
const canvas = document.createElement('canvas');
|
696
|
-
const
|
707
|
+
const importExportViewport = this.image.getImportExportViewport();
|
708
|
+
const resolution = importExportViewport.getScreenRectSize();
|
697
709
|
canvas.width = resolution.x;
|
698
710
|
canvas.height = resolution.y;
|
699
711
|
const ctx = canvas.getContext('2d');
|
700
|
-
const renderer = new CanvasRenderer(ctx,
|
712
|
+
const renderer = new CanvasRenderer(ctx, importExportViewport);
|
701
713
|
this.image.renderAll(renderer);
|
702
714
|
const dataURL = canvas.toDataURL(format);
|
703
715
|
return dataURL;
|
704
716
|
}
|
705
717
|
toSVG() {
|
706
|
-
const importExportViewport = this.
|
718
|
+
const importExportViewport = this.image.getImportExportViewport().getTemporaryClone();
|
707
719
|
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
708
720
|
const result = document.createElementNS(svgNameSpace, 'svg');
|
709
721
|
const renderer = new SVGRenderer(result, importExportViewport);
|
710
|
-
const origTransform =
|
722
|
+
const origTransform = importExportViewport.canvasToScreenTransform;
|
711
723
|
// Render with (0,0) at (0,0) — we'll handle translation with
|
712
724
|
// the viewBox property.
|
713
725
|
importExportViewport.resetTransform(Mat33.identity);
|
@@ -725,10 +737,17 @@ export class Editor {
|
|
725
737
|
result.setAttribute('xmlns', svgNameSpace);
|
726
738
|
return result;
|
727
739
|
}
|
740
|
+
/**
|
741
|
+
* Load editor data from an `ImageLoader` (e.g. an {@link SVGLoader}).
|
742
|
+
*
|
743
|
+
* @see loadFromSVG
|
744
|
+
*/
|
728
745
|
loadFrom(loader) {
|
729
746
|
return __awaiter(this, void 0, void 0, function* () {
|
730
747
|
this.showLoadingWarning(0);
|
731
748
|
this.display.setDraftMode(true);
|
749
|
+
const originalBackgrounds = this.image.getBackgroundComponents();
|
750
|
+
const eraseBackgroundCommand = new Erase(originalBackgrounds);
|
732
751
|
yield loader.start((component) => __awaiter(this, void 0, void 0, function* () {
|
733
752
|
yield this.dispatchNoAnnounce(EditorImage.addElement(component));
|
734
753
|
}), (countProcessed, totalToProcess) => {
|
@@ -742,36 +761,50 @@ export class Editor {
|
|
742
761
|
this.dispatchNoAnnounce(this.setImportExportRect(importExportRect), false);
|
743
762
|
this.dispatchNoAnnounce(this.viewport.zoomTo(importExportRect), false);
|
744
763
|
});
|
764
|
+
// Ensure that we don't have multiple overlapping BackgroundComponents. Remove
|
765
|
+
// old BackgroundComponents.
|
766
|
+
// Overlapping BackgroundComponents may cause changing the background color to
|
767
|
+
// not work properly.
|
768
|
+
if (this.image.getBackgroundComponents().length !== originalBackgrounds.length) {
|
769
|
+
yield this.dispatchNoAnnounce(eraseBackgroundCommand);
|
770
|
+
}
|
745
771
|
this.hideLoadingWarning();
|
746
772
|
this.display.setDraftMode(false);
|
747
773
|
this.queueRerender();
|
748
774
|
});
|
749
775
|
}
|
776
|
+
getTopmostBackgroundComponent() {
|
777
|
+
let background = null;
|
778
|
+
// Find a background component, if one exists.
|
779
|
+
// Use the last (topmost) background component if there are multiple.
|
780
|
+
for (const component of this.image.getBackgroundComponents()) {
|
781
|
+
if (component instanceof ImageBackground) {
|
782
|
+
background = component;
|
783
|
+
}
|
784
|
+
}
|
785
|
+
return background;
|
786
|
+
}
|
787
|
+
/**
|
788
|
+
* Set the background color of the image.
|
789
|
+
*/
|
790
|
+
setBackgroundColor(color) {
|
791
|
+
let background = this.getTopmostBackgroundComponent();
|
792
|
+
if (!background) {
|
793
|
+
const backgroundType = color.eq(Color4.transparent) ? BackgroundType.None : BackgroundType.SolidColor;
|
794
|
+
background = new ImageBackground(backgroundType, color);
|
795
|
+
return this.image.addElement(background);
|
796
|
+
}
|
797
|
+
else {
|
798
|
+
return background.updateStyle({ color });
|
799
|
+
}
|
800
|
+
}
|
750
801
|
// Returns the size of the visible region of the output SVG
|
751
802
|
getImportExportRect() {
|
752
|
-
return this.
|
803
|
+
return this.image.getImportExportViewport().visibleRect;
|
753
804
|
}
|
754
805
|
// Resize the output SVG to match `imageRect`.
|
755
806
|
setImportExportRect(imageRect) {
|
756
|
-
|
757
|
-
const origTransform = this.importExportViewport.canvasToScreenTransform;
|
758
|
-
return new class extends Command {
|
759
|
-
apply(editor) {
|
760
|
-
const viewport = editor.importExportViewport;
|
761
|
-
viewport.updateScreenSize(imageRect.size);
|
762
|
-
viewport.resetTransform(Mat33.translation(imageRect.topLeft.times(-1)));
|
763
|
-
editor.queueRerender();
|
764
|
-
}
|
765
|
-
unapply(editor) {
|
766
|
-
const viewport = editor.importExportViewport;
|
767
|
-
viewport.updateScreenSize(origSize);
|
768
|
-
viewport.resetTransform(origTransform);
|
769
|
-
editor.queueRerender();
|
770
|
-
}
|
771
|
-
description(_editor, localizationTable) {
|
772
|
-
return localizationTable.resizeOutputCommand(imageRect);
|
773
|
-
}
|
774
|
-
};
|
807
|
+
return this.image.setImportExportRect(imageRect);
|
775
808
|
}
|
776
809
|
/**
|
777
810
|
* Alias for loadFrom(SVGLoader.fromString).
|
@@ -4,17 +4,38 @@ import AbstractComponent from './components/AbstractComponent';
|
|
4
4
|
import Rect2 from './math/Rect2';
|
5
5
|
import RenderingCache from './rendering/caching/RenderingCache';
|
6
6
|
import SerializableCommand from './commands/SerializableCommand';
|
7
|
+
import EventDispatcher from './EventDispatcher';
|
8
|
+
import Command from './commands/Command';
|
7
9
|
export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;
|
10
|
+
export declare enum EditorImageEventType {
|
11
|
+
ExportViewportChanged = 0
|
12
|
+
}
|
13
|
+
export type EditorImageNotifier = EventDispatcher<EditorImageEventType, {
|
14
|
+
image: EditorImage;
|
15
|
+
}>;
|
8
16
|
export default class EditorImage {
|
9
17
|
private root;
|
18
|
+
private background;
|
10
19
|
private componentsById;
|
20
|
+
/** Viewport for the exported/imported image. */
|
21
|
+
private importExportViewport;
|
22
|
+
readonly notifier: EditorImageNotifier;
|
11
23
|
constructor();
|
24
|
+
/**
|
25
|
+
* @returns a `Viewport` for rendering the image when importing/exporting.
|
26
|
+
*/
|
27
|
+
getImportExportViewport(): Viewport;
|
28
|
+
setImportExportRect(imageRect: Rect2): Command;
|
29
|
+
getBackgroundComponents(): AbstractComponent[];
|
12
30
|
findParent(elem: AbstractComponent): ImageNode | null;
|
13
31
|
queueRerenderOf(elem: AbstractComponent): void;
|
14
32
|
/** @internal */
|
15
33
|
renderWithCache(screenRenderer: AbstractRenderer, cache: RenderingCache, viewport: Viewport): void;
|
16
|
-
/**
|
17
|
-
|
34
|
+
/**
|
35
|
+
* Renders all nodes visible from `viewport` (or all nodes if `viewport = null`)
|
36
|
+
* @internal
|
37
|
+
*/
|
38
|
+
render(renderer: AbstractRenderer, viewport: Viewport | null): void;
|
18
39
|
/** Renders all nodes, even ones not within the viewport. @internal */
|
19
40
|
renderAll(renderer: AbstractRenderer): void;
|
20
41
|
/** @returns all elements in the image, sorted by z-index. This can be slow for large images. */
|
@@ -30,6 +51,7 @@ export default class EditorImage {
|
|
30
51
|
*/
|
31
52
|
lookupElement(id: string): AbstractComponent | null;
|
32
53
|
private addElementDirectly;
|
54
|
+
private removeElementDirectly;
|
33
55
|
/**
|
34
56
|
* Returns a command that adds the given element to the `EditorImage`.
|
35
57
|
* If `applyByFlattening` is true, the content of the wet ink renderer is
|
@@ -38,6 +60,8 @@ export default class EditorImage {
|
|
38
60
|
* @see {@link Display.flatten}
|
39
61
|
*/
|
40
62
|
static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
|
63
|
+
/** @see EditorImage.addElement */
|
64
|
+
addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
|
41
65
|
private static AddElementCommand;
|
42
66
|
}
|
43
67
|
type TooSmallToRenderCheck = (rect: Rect2) => boolean;
|
@@ -58,6 +82,7 @@ export declare class ImageNode {
|
|
58
82
|
private getChildrenIntersectingRegion;
|
59
83
|
getChildrenOrSelfIntersectingRegion(region: Rect2): ImageNode[];
|
60
84
|
getLeavesIntersectingRegion(region: Rect2, isTooSmall?: TooSmallToRenderCheck): ImageNode[];
|
85
|
+
getChildWithContent(target: AbstractComponent): ImageNode | null;
|
61
86
|
getLeaves(): ImageNode[];
|
62
87
|
addLeaf(leaf: AbstractComponent): ImageNode;
|
63
88
|
getBBox(): Rect2;
|
@@ -65,6 +90,6 @@ export declare class ImageNode {
|
|
65
90
|
private updateParents;
|
66
91
|
private rebalance;
|
67
92
|
remove(): void;
|
68
|
-
render(renderer: AbstractRenderer, visibleRect
|
93
|
+
render(renderer: AbstractRenderer, visibleRect?: Rect2): void;
|
69
94
|
}
|
70
95
|
export {};
|
package/dist/src/EditorImage.js
CHANGED
@@ -1,27 +1,82 @@
|
|
1
1
|
var _a;
|
2
|
+
import Viewport from './Viewport';
|
2
3
|
import AbstractComponent from './components/AbstractComponent';
|
3
4
|
import Rect2 from './math/Rect2';
|
4
5
|
import SerializableCommand from './commands/SerializableCommand';
|
6
|
+
import EventDispatcher from './EventDispatcher';
|
7
|
+
import { Vec2 } from './math/Vec2';
|
8
|
+
import Command from './commands/Command';
|
9
|
+
import Mat33 from './math/Mat33';
|
5
10
|
// @internal Sort by z-index, low to high
|
6
11
|
export const sortLeavesByZIndex = (leaves) => {
|
7
12
|
leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
|
8
13
|
};
|
14
|
+
export var EditorImageEventType;
|
15
|
+
(function (EditorImageEventType) {
|
16
|
+
EditorImageEventType[EditorImageEventType["ExportViewportChanged"] = 0] = "ExportViewportChanged";
|
17
|
+
})(EditorImageEventType || (EditorImageEventType = {}));
|
9
18
|
// Handles lookup/storage of elements in the image
|
10
19
|
export default class EditorImage {
|
11
20
|
// @internal
|
12
21
|
constructor() {
|
13
22
|
this.root = new ImageNode();
|
23
|
+
this.background = new ImageNode();
|
14
24
|
this.componentsById = {};
|
25
|
+
this.notifier = new EventDispatcher();
|
26
|
+
this.importExportViewport = new Viewport(() => {
|
27
|
+
this.notifier.dispatch(EditorImageEventType.ExportViewportChanged, {
|
28
|
+
image: this,
|
29
|
+
});
|
30
|
+
});
|
31
|
+
// Default to a 500x500 image
|
32
|
+
this.importExportViewport.updateScreenSize(Vec2.of(500, 500));
|
15
33
|
}
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
34
|
+
/**
|
35
|
+
* @returns a `Viewport` for rendering the image when importing/exporting.
|
36
|
+
*/
|
37
|
+
getImportExportViewport() {
|
38
|
+
return this.importExportViewport;
|
39
|
+
}
|
40
|
+
setImportExportRect(imageRect) {
|
41
|
+
const importExportViewport = this.getImportExportViewport();
|
42
|
+
const origSize = importExportViewport.visibleRect.size;
|
43
|
+
const origTransform = importExportViewport.canvasToScreenTransform;
|
44
|
+
return new class extends Command {
|
45
|
+
apply(editor) {
|
46
|
+
const viewport = editor.image.getImportExportViewport();
|
47
|
+
viewport.updateScreenSize(imageRect.size);
|
48
|
+
viewport.resetTransform(Mat33.translation(imageRect.topLeft.times(-1)));
|
49
|
+
editor.queueRerender();
|
50
|
+
}
|
51
|
+
unapply(editor) {
|
52
|
+
const viewport = editor.image.getImportExportViewport();
|
53
|
+
viewport.updateScreenSize(origSize);
|
54
|
+
viewport.resetTransform(origTransform);
|
55
|
+
editor.queueRerender();
|
56
|
+
}
|
57
|
+
description(_editor, localizationTable) {
|
58
|
+
return localizationTable.resizeOutputCommand(imageRect);
|
59
|
+
}
|
60
|
+
};
|
61
|
+
}
|
62
|
+
// Returns all components that make up the background of this image. These
|
63
|
+
// components are rendered below all other components.
|
64
|
+
getBackgroundComponents() {
|
65
|
+
const result = [];
|
66
|
+
const leaves = this.background.getLeaves();
|
67
|
+
sortLeavesByZIndex(leaves);
|
68
|
+
for (const leaf of leaves) {
|
69
|
+
const content = leaf.getContent();
|
70
|
+
if (content) {
|
71
|
+
result.push(content);
|
22
72
|
}
|
23
73
|
}
|
24
|
-
return
|
74
|
+
return result;
|
75
|
+
}
|
76
|
+
// Returns the parent of the given element, if it exists.
|
77
|
+
findParent(elem) {
|
78
|
+
var _a;
|
79
|
+
return (_a = this.background.getChildWithContent(elem)) !== null && _a !== void 0 ? _a : this.root.getChildWithContent(elem);
|
25
80
|
}
|
26
81
|
// Forces a re-render of `elem` when the image is next re-rendered as a whole.
|
27
82
|
// Does nothing if `elem` is not in this.
|
@@ -36,19 +91,20 @@ export default class EditorImage {
|
|
36
91
|
}
|
37
92
|
/** @internal */
|
38
93
|
renderWithCache(screenRenderer, cache, viewport) {
|
94
|
+
this.background.render(screenRenderer, viewport.visibleRect);
|
39
95
|
cache.render(screenRenderer, this.root, viewport);
|
40
96
|
}
|
41
|
-
/**
|
97
|
+
/**
|
98
|
+
* Renders all nodes visible from `viewport` (or all nodes if `viewport = null`)
|
99
|
+
* @internal
|
100
|
+
*/
|
42
101
|
render(renderer, viewport) {
|
43
|
-
this.
|
102
|
+
this.background.render(renderer, viewport === null || viewport === void 0 ? void 0 : viewport.visibleRect);
|
103
|
+
this.root.render(renderer, viewport === null || viewport === void 0 ? void 0 : viewport.visibleRect);
|
44
104
|
}
|
45
105
|
/** Renders all nodes, even ones not within the viewport. @internal */
|
46
106
|
renderAll(renderer) {
|
47
|
-
|
48
|
-
sortLeavesByZIndex(leaves);
|
49
|
-
for (const leaf of leaves) {
|
50
|
-
leaf.getContent().render(renderer, leaf.getBBox());
|
51
|
-
}
|
107
|
+
this.render(renderer, null);
|
52
108
|
}
|
53
109
|
/** @returns all elements in the image, sorted by z-index. This can be slow for large images. */
|
54
110
|
getAllElements() {
|
@@ -76,8 +132,21 @@ export default class EditorImage {
|
|
76
132
|
return (_a = this.componentsById[id]) !== null && _a !== void 0 ? _a : null;
|
77
133
|
}
|
78
134
|
addElementDirectly(elem) {
|
135
|
+
elem.onAddToImage(this);
|
79
136
|
this.componentsById[elem.getId()] = elem;
|
80
|
-
|
137
|
+
// If a background component, add to the background. Else,
|
138
|
+
// add to the normal component tree.
|
139
|
+
const parentTree = elem.isBackground() ? this.background : this.root;
|
140
|
+
return parentTree.addLeaf(elem);
|
141
|
+
}
|
142
|
+
removeElementDirectly(element) {
|
143
|
+
const container = this.findParent(element);
|
144
|
+
container === null || container === void 0 ? void 0 : container.remove();
|
145
|
+
if (container) {
|
146
|
+
this.onDestroyElement(element);
|
147
|
+
return true;
|
148
|
+
}
|
149
|
+
return false;
|
81
150
|
}
|
82
151
|
/**
|
83
152
|
* Returns a command that adds the given element to the `EditorImage`.
|
@@ -89,6 +158,10 @@ export default class EditorImage {
|
|
89
158
|
static addElement(elem, applyByFlattening = false) {
|
90
159
|
return new EditorImage.AddElementCommand(elem, applyByFlattening);
|
91
160
|
}
|
161
|
+
/** @see EditorImage.addElement */
|
162
|
+
addElement(elem, applyByFlattening) {
|
163
|
+
return EditorImage.addElement(elem, applyByFlattening);
|
164
|
+
}
|
92
165
|
}
|
93
166
|
// A Command that can access private [EditorImage] functionality
|
94
167
|
EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
|
@@ -117,8 +190,7 @@ EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
|
|
117
190
|
}
|
118
191
|
}
|
119
192
|
unapply(editor) {
|
120
|
-
|
121
|
-
container === null || container === void 0 ? void 0 : container.remove();
|
193
|
+
editor.image.removeElementDirectly(this.element);
|
122
194
|
editor.queueRerender();
|
123
195
|
}
|
124
196
|
description(_editor, localization) {
|
@@ -194,6 +266,17 @@ export class ImageNode {
|
|
194
266
|
}
|
195
267
|
return result;
|
196
268
|
}
|
269
|
+
// Returns the child of this with the target content or `null` if no
|
270
|
+
// such child exists.
|
271
|
+
getChildWithContent(target) {
|
272
|
+
const candidates = this.getLeavesIntersectingRegion(target.getBBox());
|
273
|
+
for (const candidate of candidates) {
|
274
|
+
if (candidate.getContent() === target) {
|
275
|
+
return candidate;
|
276
|
+
}
|
277
|
+
}
|
278
|
+
return null;
|
279
|
+
}
|
197
280
|
// Returns a list of leaves with this as an ancestor.
|
198
281
|
// Like getLeavesInRegion, but does not check whether ancestors are in a given rectangle
|
199
282
|
getLeaves() {
|
@@ -305,6 +388,8 @@ export class ImageNode {
|
|
305
388
|
}
|
306
389
|
// Remove this node and all of its children
|
307
390
|
remove() {
|
391
|
+
var _a;
|
392
|
+
(_a = this.content) === null || _a === void 0 ? void 0 : _a.onRemoveFromImage();
|
308
393
|
if (!this.parent) {
|
309
394
|
this.content = null;
|
310
395
|
this.children = [];
|
@@ -325,7 +410,13 @@ export class ImageNode {
|
|
325
410
|
this.children = [];
|
326
411
|
}
|
327
412
|
render(renderer, visibleRect) {
|
328
|
-
|
413
|
+
let leaves;
|
414
|
+
if (visibleRect) {
|
415
|
+
leaves = this.getLeavesIntersectingRegion(visibleRect, rect => renderer.isTooSmallToRender(rect));
|
416
|
+
}
|
417
|
+
else {
|
418
|
+
leaves = this.getLeaves();
|
419
|
+
}
|
329
420
|
sortLeavesByZIndex(leaves);
|
330
421
|
for (const leaf of leaves) {
|
331
422
|
// Leaves by definition have content
|
@@ -16,13 +16,14 @@
|
|
16
16
|
* @packageDocumentation
|
17
17
|
*/
|
18
18
|
type CallbackHandler<EventType> = (data: EventType) => void;
|
19
|
+
export interface DispatcherEventListener {
|
20
|
+
remove: () => void;
|
21
|
+
}
|
19
22
|
export default class EventDispatcher<EventKeyType extends string | symbol | number, EventMessageType> {
|
20
23
|
private listeners;
|
21
24
|
constructor();
|
22
25
|
dispatch(eventName: EventKeyType, event: EventMessageType): void;
|
23
|
-
on(eventName: EventKeyType, callback: CallbackHandler<EventMessageType>):
|
24
|
-
remove: () => boolean;
|
25
|
-
};
|
26
|
+
on(eventName: EventKeyType, callback: CallbackHandler<EventMessageType>): DispatcherEventListener;
|
26
27
|
/** Removes an event listener. This is equivalent to calling `.remove()` on the object returned by `.on`. */
|
27
28
|
off(eventName: EventKeyType, callback: CallbackHandler<EventMessageType>): void;
|
28
29
|
}
|
package/dist/src/SVGLoader.d.ts
CHANGED
package/dist/src/SVGLoader.js
CHANGED
@@ -8,6 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
8
8
|
});
|
9
9
|
};
|
10
10
|
import Color4 from './Color4';
|
11
|
+
import ImageBackground, { BackgroundType, imageBackgroundCSSClassName } from './components/ImageBackground';
|
11
12
|
import ImageComponent from './components/ImageComponent';
|
12
13
|
import Stroke from './components/Stroke';
|
13
14
|
import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
|
@@ -145,6 +146,14 @@ export default class SVGLoader {
|
|
145
146
|
yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, elem));
|
146
147
|
});
|
147
148
|
}
|
149
|
+
addBackground(node) {
|
150
|
+
var _a, _b, _c;
|
151
|
+
return __awaiter(this, void 0, void 0, function* () {
|
152
|
+
const fill = Color4.fromString((_b = (_a = node.getAttribute('fill')) !== null && _a !== void 0 ? _a : node.style.fill) !== null && _b !== void 0 ? _b : 'black');
|
153
|
+
const elem = new ImageBackground(BackgroundType.SolidColor, fill);
|
154
|
+
yield ((_c = this.onAddComponent) === null || _c === void 0 ? void 0 : _c.call(this, elem));
|
155
|
+
});
|
156
|
+
}
|
148
157
|
// If given, 'supportedAttrs' will have x, y, etc. attributes that were used in computing the transform added to it,
|
149
158
|
// to prevent storing duplicate transform information when saving the component.
|
150
159
|
getTransform(elem, supportedAttrs, computedStyles) {
|
@@ -307,7 +316,12 @@ export default class SVGLoader {
|
|
307
316
|
// Continue -- visit the node's children.
|
308
317
|
break;
|
309
318
|
case 'path':
|
310
|
-
|
319
|
+
if (node.classList.contains(imageBackgroundCSSClassName)) {
|
320
|
+
yield this.addBackground(node);
|
321
|
+
}
|
322
|
+
else {
|
323
|
+
yield this.addPath(node);
|
324
|
+
}
|
311
325
|
break;
|
312
326
|
case 'text':
|
313
327
|
yield this.addText(node);
|
package/dist/src/Viewport.d.ts
CHANGED
@@ -4,18 +4,23 @@ import Rect2 from './math/Rect2';
|
|
4
4
|
import { Point2, Vec2 } from './math/Vec2';
|
5
5
|
import Vec3 from './math/Vec3';
|
6
6
|
import { StrokeDataPoint } from './types';
|
7
|
-
import { EditorNotifier } from './types';
|
8
7
|
type PointDataType<T extends Point2 | StrokeDataPoint | number> = T extends Point2 ? Point2 : number;
|
9
8
|
export declare abstract class ViewportTransform extends Command {
|
10
9
|
abstract readonly transform: Mat33;
|
11
10
|
}
|
11
|
+
type TransformChangeCallback = (oldTransform: Mat33, newTransform: Mat33) => void;
|
12
12
|
export declare class Viewport {
|
13
|
-
private
|
13
|
+
private onTransformChangeCallback;
|
14
14
|
private static ViewportTransform;
|
15
15
|
private transform;
|
16
16
|
private inverseTransform;
|
17
17
|
private screenRect;
|
18
|
-
constructor(
|
18
|
+
constructor(onTransformChangeCallback: TransformChangeCallback);
|
19
|
+
/**
|
20
|
+
* @returns a temporary copy of `this` that does not notify when modified. This is
|
21
|
+
* useful when rendering with a temporarily different viewport.
|
22
|
+
*/
|
23
|
+
getTemporaryClone(): Viewport;
|
19
24
|
updateScreenSize(screenSize: Vec2): void;
|
20
25
|
/** Get the screen's visible region transformed into canvas space. */
|
21
26
|
get visibleRect(): Rect2;
|
package/dist/src/Viewport.js
CHANGED
@@ -15,16 +15,26 @@ import Mat33 from './math/Mat33';
|
|
15
15
|
import Rect2 from './math/Rect2';
|
16
16
|
import { Vec2 } from './math/Vec2';
|
17
17
|
import Vec3 from './math/Vec3';
|
18
|
-
import { EditorEventType } from './types';
|
19
18
|
export class ViewportTransform extends Command {
|
20
19
|
}
|
21
20
|
export class Viewport {
|
22
21
|
// @internal
|
23
|
-
constructor(
|
24
|
-
this.
|
22
|
+
constructor(onTransformChangeCallback) {
|
23
|
+
this.onTransformChangeCallback = onTransformChangeCallback;
|
25
24
|
this.resetTransform(Mat33.identity);
|
26
25
|
this.screenRect = Rect2.empty;
|
27
26
|
}
|
27
|
+
/**
|
28
|
+
* @returns a temporary copy of `this` that does not notify when modified. This is
|
29
|
+
* useful when rendering with a temporarily different viewport.
|
30
|
+
*/
|
31
|
+
getTemporaryClone() {
|
32
|
+
const result = new Viewport(() => { });
|
33
|
+
result.transform = this.transform;
|
34
|
+
result.inverseTransform = this.inverseTransform;
|
35
|
+
result.screenRect = this.screenRect;
|
36
|
+
return result;
|
37
|
+
}
|
28
38
|
// @internal
|
29
39
|
updateScreenSize(screenSize) {
|
30
40
|
this.screenRect = this.screenRect.resizedTo(screenSize);
|
@@ -50,14 +60,11 @@ export class Viewport {
|
|
50
60
|
* @param newTransform - should map from canvas coordinates to screen coordinates.
|
51
61
|
*/
|
52
62
|
resetTransform(newTransform = Mat33.identity) {
|
63
|
+
var _a;
|
53
64
|
const oldTransform = this.transform;
|
54
65
|
this.transform = newTransform;
|
55
66
|
this.inverseTransform = newTransform.inverse();
|
56
|
-
this.
|
57
|
-
kind: EditorEventType.ViewportChanged,
|
58
|
-
newTransform,
|
59
|
-
oldTransform,
|
60
|
-
});
|
67
|
+
(_a = this.onTransformChangeCallback) === null || _a === void 0 ? void 0 : _a.call(this, oldTransform, newTransform);
|
61
68
|
}
|
62
69
|
get screenToCanvasTransform() {
|
63
70
|
return this.inverseTransform;
|