js-draw 0.11.3 → 0.13.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/CHANGELOG.md +15 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +13 -0
- package/dist/src/Color4.js +17 -0
- package/dist/src/Editor.d.ts +33 -18
- package/dist/src/Editor.js +26 -24
- package/dist/src/EditorImage.d.ts +12 -0
- package/dist/src/EditorImage.js +12 -0
- package/dist/src/Pointer.d.ts +1 -0
- package/dist/src/Pointer.js +8 -0
- package/dist/src/SVGLoader.d.ts +5 -0
- package/dist/src/SVGLoader.js +49 -36
- package/dist/src/Viewport.d.ts +30 -1
- package/dist/src/Viewport.js +39 -9
- package/dist/src/commands/invertCommand.js +1 -1
- package/dist/src/components/AbstractComponent.d.ts +20 -0
- package/dist/src/components/AbstractComponent.js +32 -2
- package/dist/src/lib.d.ts +6 -3
- package/dist/src/lib.js +4 -1
- package/dist/src/math/Mat33.d.ts +1 -1
- package/dist/src/math/Mat33.js +1 -1
- package/dist/src/rendering/Display.d.ts +9 -11
- package/dist/src/rendering/Display.js +12 -14
- package/dist/src/rendering/lib.d.ts +3 -0
- package/dist/src/rendering/lib.js +3 -0
- package/dist/src/rendering/renderers/DummyRenderer.js +2 -2
- package/dist/src/rendering/renderers/SVGRenderer.js +4 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +51 -0
- package/dist/src/toolbar/HTMLToolbar.js +63 -5
- package/dist/src/toolbar/IconProvider.d.ts +2 -2
- package/dist/src/toolbar/IconProvider.js +123 -35
- package/dist/src/toolbar/widgets/EraserToolWidget.d.ts +8 -1
- package/dist/src/toolbar/widgets/EraserToolWidget.js +45 -4
- package/dist/src/toolbar/widgets/PenToolWidget.js +2 -2
- package/dist/src/toolbar/widgets/SelectionToolWidget.js +12 -3
- package/dist/src/tools/Eraser.d.ts +10 -1
- package/dist/src/tools/Eraser.js +65 -13
- package/dist/src/tools/PanZoom.js +1 -1
- package/dist/src/tools/PasteHandler.d.ts +11 -4
- package/dist/src/tools/PasteHandler.js +12 -5
- package/dist/src/tools/Pen.d.ts +7 -2
- package/dist/src/tools/Pen.js +39 -6
- package/dist/src/tools/SelectionTool/Selection.d.ts +4 -1
- package/dist/src/tools/SelectionTool/Selection.js +64 -27
- package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +3 -0
- package/dist/src/tools/SelectionTool/SelectionHandle.js +6 -0
- package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -1
- package/dist/src/tools/SelectionTool/SelectionTool.js +56 -16
- package/dist/src/tools/TextTool.js +10 -6
- package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
- package/dist/src/tools/ToolSwitcherShortcut.js +9 -3
- package/dist/src/tools/UndoRedoShortcut.js +2 -4
- package/dist/src/types.d.ts +2 -2
- package/package.json +2 -2
- package/src/Color4.test.ts +11 -0
- package/src/Color4.ts +23 -0
- package/src/Editor.ts +39 -26
- package/src/EditorImage.ts +12 -0
- package/src/Pointer.ts +19 -0
- package/src/SVGLoader.ts +20 -15
- package/src/Viewport.ts +50 -11
- package/src/commands/invertCommand.ts +1 -1
- package/src/components/AbstractComponent.ts +52 -2
- package/src/lib.ts +6 -3
- package/src/math/Mat33.ts +1 -1
- package/src/rendering/Display.ts +12 -15
- package/src/rendering/RenderingStyle.ts +1 -1
- package/src/rendering/lib.ts +4 -0
- package/src/rendering/renderers/DummyRenderer.ts +2 -3
- package/src/rendering/renderers/SVGRenderer.ts +4 -0
- package/src/rendering/renderers/TextOnlyRenderer.ts +0 -1
- package/src/toolbar/HTMLToolbar.ts +81 -5
- package/src/toolbar/IconProvider.ts +132 -37
- package/src/toolbar/widgets/EraserToolWidget.ts +64 -5
- package/src/toolbar/widgets/PenToolWidget.ts +2 -2
- package/src/toolbar/widgets/SelectionToolWidget.ts +2 -2
- package/src/tools/Eraser.test.ts +79 -0
- package/src/tools/Eraser.ts +81 -17
- package/src/tools/PanZoom.ts +1 -1
- package/src/tools/PasteHandler.ts +12 -6
- package/src/tools/Pen.test.ts +44 -1
- package/src/tools/Pen.ts +53 -8
- package/src/tools/SelectionTool/Selection.ts +73 -23
- package/src/tools/SelectionTool/SelectionHandle.ts +9 -0
- package/src/tools/SelectionTool/SelectionTool.test.ts +138 -21
- package/src/tools/SelectionTool/SelectionTool.ts +70 -16
- package/src/tools/TextTool.ts +14 -8
- package/src/tools/ToolSwitcherShortcut.ts +10 -5
- package/src/tools/UndoRedoShortcut.ts +2 -5
- package/src/types.ts +2 -2
- package/typedoc.json +2 -2
package/dist/src/Color4.d.ts
CHANGED
@@ -20,6 +20,18 @@ export default class Color4 {
|
|
20
20
|
static fromString(text: string): Color4;
|
21
21
|
/** @returns true if `this` and `other` are approximately equal. */
|
22
22
|
eq(other: Color4 | null | undefined): boolean;
|
23
|
+
/**
|
24
|
+
* If `fractionTo` is not in the range [0, 1], it will be clamped to the nearest number
|
25
|
+
* in that range. For example, `a.mix(b, -1)` is equivalent to `a.mix(b, 0)`.
|
26
|
+
*
|
27
|
+
* @returns a color `fractionTo` of the way from this color to `other`.
|
28
|
+
*
|
29
|
+
* @example
|
30
|
+
* ```ts
|
31
|
+
* Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 1, 0), 0.1) // -> Color4(0.9, 0.1, 0)
|
32
|
+
* ```
|
33
|
+
*/
|
34
|
+
mix(other: Color4, fractionTo: number): Color4;
|
23
35
|
private hexString;
|
24
36
|
/**
|
25
37
|
* @returns a hexadecimal color string representation of `this`, in the form `#rrggbbaa`.
|
@@ -39,5 +51,6 @@ export default class Color4 {
|
|
39
51
|
static yellow: Color4;
|
40
52
|
static clay: Color4;
|
41
53
|
static black: Color4;
|
54
|
+
static gray: Color4;
|
42
55
|
static white: Color4;
|
43
56
|
}
|
package/dist/src/Color4.js
CHANGED
@@ -105,6 +105,22 @@ export default class Color4 {
|
|
105
105
|
}
|
106
106
|
return this.toHexString() === other.toHexString();
|
107
107
|
}
|
108
|
+
/**
|
109
|
+
* If `fractionTo` is not in the range [0, 1], it will be clamped to the nearest number
|
110
|
+
* in that range. For example, `a.mix(b, -1)` is equivalent to `a.mix(b, 0)`.
|
111
|
+
*
|
112
|
+
* @returns a color `fractionTo` of the way from this color to `other`.
|
113
|
+
*
|
114
|
+
* @example
|
115
|
+
* ```ts
|
116
|
+
* Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 1, 0), 0.1) // -> Color4(0.9, 0.1, 0)
|
117
|
+
* ```
|
118
|
+
*/
|
119
|
+
mix(other, fractionTo) {
|
120
|
+
fractionTo = Math.min(Math.max(fractionTo, 0), 1);
|
121
|
+
const fractionOfThis = 1 - fractionTo;
|
122
|
+
return new Color4(this.r * fractionOfThis + other.r * fractionTo, this.g * fractionOfThis + other.g * fractionTo, this.b * fractionOfThis + other.b * fractionTo, this.a * fractionOfThis + other.a * fractionTo);
|
123
|
+
}
|
108
124
|
/**
|
109
125
|
* @returns a hexadecimal color string representation of `this`, in the form `#rrggbbaa`.
|
110
126
|
*
|
@@ -146,4 +162,5 @@ Color4.purple = Color4.ofRGB(0.5, 0.2, 0.5);
|
|
146
162
|
Color4.yellow = Color4.ofRGB(1, 1, 0.1);
|
147
163
|
Color4.clay = Color4.ofRGB(0.8, 0.4, 0.2);
|
148
164
|
Color4.black = Color4.ofRGB(0, 0, 0);
|
165
|
+
Color4.gray = Color4.ofRGB(0.5, 0.5, 0.5);
|
149
166
|
Color4.white = Color4.ofRGB(1, 1, 1);
|
package/dist/src/Editor.d.ts
CHANGED
@@ -1,20 +1,3 @@
|
|
1
|
-
/**
|
2
|
-
* The main entrypoint for the full editor.
|
3
|
-
*
|
4
|
-
* @example
|
5
|
-
* To create an editor with a toolbar,
|
6
|
-
* ```
|
7
|
-
* const editor = new Editor(document.body);
|
8
|
-
*
|
9
|
-
* const toolbar = editor.addToolbar();
|
10
|
-
* toolbar.addActionButton('Save', () => {
|
11
|
-
* const saveData = editor.toSVG().outerHTML;
|
12
|
-
* // Do something with saveData...
|
13
|
-
* });
|
14
|
-
* ```
|
15
|
-
*
|
16
|
-
* @packageDocumentation
|
17
|
-
*/
|
18
1
|
import EditorImage from './EditorImage';
|
19
2
|
import ToolController from './tools/ToolController';
|
20
3
|
import { InputEvtType, EditorNotifier, ImageLoader } from './types';
|
@@ -48,9 +31,25 @@ export interface EditorSettings {
|
|
48
31
|
maxZoom: number;
|
49
32
|
iconProvider: IconProvider;
|
50
33
|
}
|
34
|
+
/**
|
35
|
+
* The main entrypoint for the full editor.
|
36
|
+
*
|
37
|
+
* @example
|
38
|
+
* To create an editor with a toolbar,
|
39
|
+
* ```
|
40
|
+
* const editor = new Editor(document.body);
|
41
|
+
*
|
42
|
+
* const toolbar = editor.addToolbar();
|
43
|
+
* toolbar.addActionButton('Save', () => {
|
44
|
+
* const saveData = editor.toSVG().outerHTML;
|
45
|
+
* // Do something with saveData...
|
46
|
+
* });
|
47
|
+
* ```
|
48
|
+
*/
|
51
49
|
export declare class Editor {
|
52
50
|
private container;
|
53
51
|
private renderingRegion;
|
52
|
+
/** Manages drawing surfaces/{@link lib!AbstractRenderer}s. */
|
54
53
|
display: Display;
|
55
54
|
/**
|
56
55
|
* Handles undo/redo.
|
@@ -87,10 +86,20 @@ export declare class Editor {
|
|
87
86
|
readonly image: EditorImage;
|
88
87
|
/** Viewport for the exported/imported image. */
|
89
88
|
private importExportViewport;
|
89
|
+
/**
|
90
|
+
* Allows transforming the view and querying information about
|
91
|
+
* what is currently visible.
|
92
|
+
*/
|
93
|
+
readonly viewport: Viewport;
|
90
94
|
/** @internal */
|
91
95
|
readonly localization: EditorLocalization;
|
96
|
+
/** {@link lib!EditorSettings.iconProvider} */
|
92
97
|
readonly icons: IconProvider;
|
93
|
-
|
98
|
+
/**
|
99
|
+
* Controls the list of tools. See
|
100
|
+
* [the custom tool example](https://github.com/personalizedrefrigerator/js-draw/tree/main/docs/example-custom-tools)
|
101
|
+
* for more.
|
102
|
+
*/
|
94
103
|
readonly toolController: ToolController;
|
95
104
|
/**
|
96
105
|
* Global event dispatcher/subscriber.
|
@@ -196,7 +205,13 @@ export declare class Editor {
|
|
196
205
|
*/
|
197
206
|
queueRerender(): Promise<void>;
|
198
207
|
rerender(showImageBounds?: boolean): void;
|
208
|
+
/**
|
209
|
+
* @see {@link Display.getWetInkRenderer} {@link Display.flatten}
|
210
|
+
*/
|
199
211
|
drawWetInk(...path: RenderablePathSpec[]): void;
|
212
|
+
/**
|
213
|
+
* @see {@link Display.getWetInkRenderer}
|
214
|
+
*/
|
200
215
|
clearWetInk(): void;
|
201
216
|
focus(): void;
|
202
217
|
createHTMLOverlay(overlay: HTMLElement): {
|
package/dist/src/Editor.js
CHANGED
@@ -1,20 +1,3 @@
|
|
1
|
-
/**
|
2
|
-
* The main entrypoint for the full editor.
|
3
|
-
*
|
4
|
-
* @example
|
5
|
-
* To create an editor with a toolbar,
|
6
|
-
* ```
|
7
|
-
* const editor = new Editor(document.body);
|
8
|
-
*
|
9
|
-
* const toolbar = editor.addToolbar();
|
10
|
-
* toolbar.addActionButton('Save', () => {
|
11
|
-
* const saveData = editor.toSVG().outerHTML;
|
12
|
-
* // Do something with saveData...
|
13
|
-
* });
|
14
|
-
* ```
|
15
|
-
*
|
16
|
-
* @packageDocumentation
|
17
|
-
*/
|
18
1
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
19
2
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
20
3
|
return new (P || (P = Promise))(function (resolve, reject) {
|
@@ -48,7 +31,21 @@ import untilNextAnimationFrame from './util/untilNextAnimationFrame';
|
|
48
31
|
import fileToBase64 from './util/fileToBase64';
|
49
32
|
import uniteCommands from './commands/uniteCommands';
|
50
33
|
import SelectionTool from './tools/SelectionTool/SelectionTool';
|
51
|
-
|
34
|
+
/**
|
35
|
+
* The main entrypoint for the full editor.
|
36
|
+
*
|
37
|
+
* @example
|
38
|
+
* To create an editor with a toolbar,
|
39
|
+
* ```
|
40
|
+
* const editor = new Editor(document.body);
|
41
|
+
*
|
42
|
+
* const toolbar = editor.addToolbar();
|
43
|
+
* toolbar.addActionButton('Save', () => {
|
44
|
+
* const saveData = editor.toSVG().outerHTML;
|
45
|
+
* // Do something with saveData...
|
46
|
+
* });
|
47
|
+
* ```
|
48
|
+
*/
|
52
49
|
export class Editor {
|
53
50
|
/**
|
54
51
|
* @example
|
@@ -188,8 +185,7 @@ export class Editor {
|
|
188
185
|
addToolbar(defaultLayout = true) {
|
189
186
|
const toolbar = new HTMLToolbar(this, this.container, this.localization);
|
190
187
|
if (defaultLayout) {
|
191
|
-
toolbar.
|
192
|
-
toolbar.addDefaultActionButtons();
|
188
|
+
toolbar.addDefaults();
|
193
189
|
}
|
194
190
|
return toolbar;
|
195
191
|
}
|
@@ -583,11 +579,17 @@ export class Editor {
|
|
583
579
|
this.nextRerenderListeners.forEach(listener => listener());
|
584
580
|
this.nextRerenderListeners = [];
|
585
581
|
}
|
582
|
+
/**
|
583
|
+
* @see {@link Display.getWetInkRenderer} {@link Display.flatten}
|
584
|
+
*/
|
586
585
|
drawWetInk(...path) {
|
587
586
|
for (const part of path) {
|
588
587
|
this.display.getWetInkRenderer().drawPath(part);
|
589
588
|
}
|
590
589
|
}
|
590
|
+
/**
|
591
|
+
* @see {@link Display.getWetInkRenderer}
|
592
|
+
*/
|
591
593
|
clearWetInk() {
|
592
594
|
this.display.getWetInkRenderer().clear();
|
593
595
|
}
|
@@ -682,7 +684,7 @@ export class Editor {
|
|
682
684
|
// The export resolution is the same as the size of the drawing canvas.
|
683
685
|
toDataURL(format = 'image/png') {
|
684
686
|
const canvas = document.createElement('canvas');
|
685
|
-
const resolution = this.importExportViewport.
|
687
|
+
const resolution = this.importExportViewport.getScreenRectSize();
|
686
688
|
canvas.width = resolution.x;
|
687
689
|
canvas.height = resolution.y;
|
688
690
|
const ctx = canvas.getContext('2d');
|
@@ -718,9 +720,9 @@ export class Editor {
|
|
718
720
|
return __awaiter(this, void 0, void 0, function* () {
|
719
721
|
this.showLoadingWarning(0);
|
720
722
|
this.display.setDraftMode(true);
|
721
|
-
yield loader.start((component) => {
|
722
|
-
this.dispatchNoAnnounce(EditorImage.addElement(component));
|
723
|
-
}, (countProcessed, totalToProcess) => {
|
723
|
+
yield loader.start((component) => __awaiter(this, void 0, void 0, function* () {
|
724
|
+
yield this.dispatchNoAnnounce(EditorImage.addElement(component));
|
725
|
+
}), (countProcessed, totalToProcess) => {
|
724
726
|
if (countProcessed % 500 === 0) {
|
725
727
|
this.showLoadingWarning(countProcessed / totalToProcess);
|
726
728
|
this.rerender();
|
@@ -22,8 +22,20 @@ export default class EditorImage {
|
|
22
22
|
getElementsIntersectingRegion(region: Rect2): AbstractComponent[];
|
23
23
|
/** @internal */
|
24
24
|
onDestroyElement(elem: AbstractComponent): void;
|
25
|
+
/**
|
26
|
+
* @returns the AbstractComponent with `id`, if it exists.
|
27
|
+
*
|
28
|
+
* @see {@link AbstractComponent.getId}
|
29
|
+
*/
|
25
30
|
lookupElement(id: string): AbstractComponent | null;
|
26
31
|
private addElementDirectly;
|
32
|
+
/**
|
33
|
+
* Returns a command that adds the given element to the `EditorImage`.
|
34
|
+
* If `applyByFlattening` is true, the content of the wet ink renderer is
|
35
|
+
* rendered onto the main rendering canvas instead of doing a full re-render.
|
36
|
+
*
|
37
|
+
* @see {@link Display.flatten}
|
38
|
+
*/
|
27
39
|
static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
|
28
40
|
private static AddElementCommand;
|
29
41
|
}
|
package/dist/src/EditorImage.js
CHANGED
@@ -55,6 +55,11 @@ export default class EditorImage {
|
|
55
55
|
onDestroyElement(elem) {
|
56
56
|
delete this.componentsById[elem.getId()];
|
57
57
|
}
|
58
|
+
/**
|
59
|
+
* @returns the AbstractComponent with `id`, if it exists.
|
60
|
+
*
|
61
|
+
* @see {@link AbstractComponent.getId}
|
62
|
+
*/
|
58
63
|
lookupElement(id) {
|
59
64
|
var _a;
|
60
65
|
return (_a = this.componentsById[id]) !== null && _a !== void 0 ? _a : null;
|
@@ -63,6 +68,13 @@ export default class EditorImage {
|
|
63
68
|
this.componentsById[elem.getId()] = elem;
|
64
69
|
return this.root.addLeaf(elem);
|
65
70
|
}
|
71
|
+
/**
|
72
|
+
* Returns a command that adds the given element to the `EditorImage`.
|
73
|
+
* If `applyByFlattening` is true, the content of the wet ink renderer is
|
74
|
+
* rendered onto the main rendering canvas instead of doing a full re-render.
|
75
|
+
*
|
76
|
+
* @see {@link Display.flatten}
|
77
|
+
*/
|
66
78
|
static addElement(elem, applyByFlattening = false) {
|
67
79
|
return new EditorImage.AddElementCommand(elem, applyByFlattening);
|
68
80
|
}
|
package/dist/src/Pointer.d.ts
CHANGED
@@ -18,6 +18,7 @@ export default class Pointer {
|
|
18
18
|
readonly id: number;
|
19
19
|
readonly timeStamp: number;
|
20
20
|
private constructor();
|
21
|
+
snappedToGrid(viewport: Viewport): Pointer;
|
21
22
|
static ofEvent(evt: PointerEvent, isDown: boolean, viewport: Viewport, relativeTo?: HTMLElement): Pointer;
|
22
23
|
static ofCanvasPoint(canvasPos: Point2, isDown: boolean, viewport: Viewport, id?: number, device?: PointerDevice, isPrimary?: boolean, pressure?: number | null): Pointer;
|
23
24
|
}
|
package/dist/src/Pointer.js
CHANGED
@@ -31,6 +31,14 @@ export default class Pointer {
|
|
31
31
|
this.id = id;
|
32
32
|
this.timeStamp = timeStamp;
|
33
33
|
}
|
34
|
+
// Snaps this pointer to the nearest grid point (rounds the coordinates of this
|
35
|
+
// pointer based on the current zoom). Returns a new Pointer and does not modify
|
36
|
+
// this.
|
37
|
+
snappedToGrid(viewport) {
|
38
|
+
const snappedCanvasPos = viewport.snapToGrid(this.canvasPos);
|
39
|
+
const snappedScreenPos = viewport.canvasToScreen(snappedCanvasPos);
|
40
|
+
return new Pointer(snappedScreenPos, snappedCanvasPos, this.pressure, this.isPrimary, this.down, this.device, this.id, this.timeStamp);
|
41
|
+
}
|
34
42
|
// Creates a Pointer from a DOM event. If `relativeTo` is given, (0, 0) in screen coordinates is
|
35
43
|
// considered the top left of `relativeTo`.
|
36
44
|
static ofEvent(evt, isDown, viewport, relativeTo) {
|
package/dist/src/SVGLoader.d.ts
CHANGED
@@ -34,5 +34,10 @@ export default class SVGLoader implements ImageLoader {
|
|
34
34
|
private visit;
|
35
35
|
private getSourceAttrs;
|
36
36
|
start(onAddComponent: ComponentAddedListener, onProgress: OnProgressListener, onDetermineExportRect?: OnDetermineExportRectListener | null): Promise<void>;
|
37
|
+
/**
|
38
|
+
* @see {@link Editor.loadFrom}
|
39
|
+
* @param text - Textual representation of the SVG (e.g. `<svg viewbox='...'>...</svg>`).
|
40
|
+
* @param sanitize - if `true`, don't store unknown attributes.
|
41
|
+
*/
|
37
42
|
static fromString(text: string, sanitize?: boolean): SVGLoader;
|
38
43
|
}
|
package/dist/src/SVGLoader.js
CHANGED
@@ -23,6 +23,7 @@ export const defaultSVGViewRect = new Rect2(0, 0, 500, 500);
|
|
23
23
|
export const svgAttributesDataKey = 'svgAttrs';
|
24
24
|
export const svgStyleAttributesDataKey = 'svgStyleAttrs';
|
25
25
|
const supportedStrokeFillStyleAttrs = ['stroke', 'fill', 'stroke-width'];
|
26
|
+
// Handles loading images from SVG.
|
26
27
|
export default class SVGLoader {
|
27
28
|
constructor(source, onFinish, storeUnknown = true) {
|
28
29
|
this.source = source;
|
@@ -125,22 +126,24 @@ export default class SVGLoader {
|
|
125
126
|
// Adds a stroke with a single path
|
126
127
|
addPath(node) {
|
127
128
|
var _a;
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
catch (e) {
|
135
|
-
console.error('Invalid path in node', node, '\nError:', e, '\nAdding as an unknown object.');
|
136
|
-
if (this.storeUnknown) {
|
137
|
-
elem = new UnknownSVGObject(node);
|
129
|
+
return __awaiter(this, void 0, void 0, function* () {
|
130
|
+
let elem;
|
131
|
+
try {
|
132
|
+
const strokeData = this.strokeDataFromElem(node);
|
133
|
+
elem = new Stroke(strokeData);
|
134
|
+
this.attachUnrecognisedAttrs(elem, node, new Set([...supportedStrokeFillStyleAttrs, 'd']), new Set(supportedStrokeFillStyleAttrs));
|
138
135
|
}
|
139
|
-
|
140
|
-
|
136
|
+
catch (e) {
|
137
|
+
console.error('Invalid path in node', node, '\nError:', e, '\nAdding as an unknown object.');
|
138
|
+
if (this.storeUnknown) {
|
139
|
+
elem = new UnknownSVGObject(node);
|
140
|
+
}
|
141
|
+
else {
|
142
|
+
return;
|
143
|
+
}
|
141
144
|
}
|
142
|
-
|
143
|
-
|
145
|
+
yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, elem));
|
146
|
+
});
|
144
147
|
}
|
145
148
|
// If given, 'supportedAttrs' will have x, y, etc. attributes that were used in computing the transform added to it,
|
146
149
|
// to prevent storing duplicate transform information when saving the component.
|
@@ -223,14 +226,16 @@ export default class SVGLoader {
|
|
223
226
|
}
|
224
227
|
addText(elem) {
|
225
228
|
var _a;
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
229
|
+
return __awaiter(this, void 0, void 0, function* () {
|
230
|
+
try {
|
231
|
+
const textElem = this.makeText(elem);
|
232
|
+
yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, textElem));
|
233
|
+
}
|
234
|
+
catch (e) {
|
235
|
+
console.error('Invalid text object in node', elem, '. Continuing.... Error:', e);
|
236
|
+
this.addUnknownNode(elem);
|
237
|
+
}
|
238
|
+
});
|
234
239
|
}
|
235
240
|
addImage(elem) {
|
236
241
|
var _a, _b, _c;
|
@@ -243,20 +248,22 @@ export default class SVGLoader {
|
|
243
248
|
const transform = this.getTransform(elem, supportedAttrs);
|
244
249
|
const imageElem = yield ImageComponent.fromImage(image, transform);
|
245
250
|
this.attachUnrecognisedAttrs(imageElem, elem, new Set(supportedAttrs), new Set(['transform']));
|
246
|
-
(_c = this.onAddComponent) === null || _c === void 0 ? void 0 : _c.call(this, imageElem);
|
251
|
+
yield ((_c = this.onAddComponent) === null || _c === void 0 ? void 0 : _c.call(this, imageElem));
|
247
252
|
}
|
248
253
|
catch (e) {
|
249
254
|
console.error('Error loading image:', e, '. Element: ', elem, '. Continuing...');
|
250
|
-
this.addUnknownNode(elem);
|
255
|
+
yield this.addUnknownNode(elem);
|
251
256
|
}
|
252
257
|
});
|
253
258
|
}
|
254
259
|
addUnknownNode(node) {
|
255
260
|
var _a;
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
261
|
+
return __awaiter(this, void 0, void 0, function* () {
|
262
|
+
if (this.storeUnknown) {
|
263
|
+
const component = new UnknownSVGObject(node);
|
264
|
+
yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, component));
|
265
|
+
}
|
266
|
+
});
|
260
267
|
}
|
261
268
|
updateViewBox(node) {
|
262
269
|
var _a;
|
@@ -278,9 +285,11 @@ export default class SVGLoader {
|
|
278
285
|
}
|
279
286
|
updateSVGAttrs(node) {
|
280
287
|
var _a;
|
281
|
-
|
282
|
-
(
|
283
|
-
|
288
|
+
return __awaiter(this, void 0, void 0, function* () {
|
289
|
+
if (this.storeUnknown) {
|
290
|
+
yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, new SVGGlobalAttributesObject(this.getSourceAttrs(node))));
|
291
|
+
}
|
292
|
+
});
|
284
293
|
}
|
285
294
|
visit(node) {
|
286
295
|
var _a;
|
@@ -292,10 +301,10 @@ export default class SVGLoader {
|
|
292
301
|
// Continue -- visit the node's children.
|
293
302
|
break;
|
294
303
|
case 'path':
|
295
|
-
this.addPath(node);
|
304
|
+
yield this.addPath(node);
|
296
305
|
break;
|
297
306
|
case 'text':
|
298
|
-
this.addText(node);
|
307
|
+
yield this.addText(node);
|
299
308
|
visitChildren = false;
|
300
309
|
break;
|
301
310
|
case 'image':
|
@@ -308,14 +317,14 @@ export default class SVGLoader {
|
|
308
317
|
this.updateSVGAttrs(node);
|
309
318
|
break;
|
310
319
|
case 'style':
|
311
|
-
this.addUnknownNode(node);
|
320
|
+
yield this.addUnknownNode(node);
|
312
321
|
break;
|
313
322
|
default:
|
314
323
|
console.warn('Unknown SVG element,', node);
|
315
324
|
if (!(node instanceof SVGElement)) {
|
316
325
|
console.warn('Element', node, 'is not an SVGElement!', this.storeUnknown ? 'Continuing anyway.' : 'Skipping.');
|
317
326
|
}
|
318
|
-
this.addUnknownNode(node);
|
327
|
+
yield this.addUnknownNode(node);
|
319
328
|
return;
|
320
329
|
}
|
321
330
|
if (visitChildren) {
|
@@ -351,7 +360,11 @@ export default class SVGLoader {
|
|
351
360
|
(_b = this.onFinish) === null || _b === void 0 ? void 0 : _b.call(this);
|
352
361
|
});
|
353
362
|
}
|
354
|
-
|
363
|
+
/**
|
364
|
+
* @see {@link Editor.loadFrom}
|
365
|
+
* @param text - Textual representation of the SVG (e.g. `<svg viewbox='...'>...</svg>`).
|
366
|
+
* @param sanitize - if `true`, don't store unknown attributes.
|
367
|
+
*/
|
355
368
|
static fromString(text, sanitize = false) {
|
356
369
|
var _a, _b;
|
357
370
|
const sandbox = document.createElement('iframe');
|
package/dist/src/Viewport.d.ts
CHANGED
@@ -2,6 +2,7 @@ import Command from './commands/Command';
|
|
2
2
|
import Mat33 from './math/Mat33';
|
3
3
|
import Rect2 from './math/Rect2';
|
4
4
|
import { Point2, Vec2 } from './math/Vec2';
|
5
|
+
import Vec3 from './math/Vec3';
|
5
6
|
import { StrokeDataPoint } from './types';
|
6
7
|
import { EditorNotifier } from './types';
|
7
8
|
type PointDataType<T extends Point2 | StrokeDataPoint | number> = T extends Point2 ? Point2 : number;
|
@@ -16,17 +17,45 @@ export declare class Viewport {
|
|
16
17
|
private screenRect;
|
17
18
|
constructor(notifier: EditorNotifier);
|
18
19
|
updateScreenSize(screenSize: Vec2): void;
|
20
|
+
/** Get the screen's visible region transformed into canvas space. */
|
19
21
|
get visibleRect(): Rect2;
|
22
|
+
/** @returns the given point, but in canvas coordinates */
|
20
23
|
screenToCanvas(screenPoint: Point2): Point2;
|
24
|
+
/** @returns the given point transformed into screen coordinates. */
|
21
25
|
canvasToScreen(canvasPoint: Point2): Point2;
|
26
|
+
/** @returns a command that transforms the canvas by `transform`. */
|
22
27
|
static transformBy(transform: Mat33): ViewportTransform;
|
28
|
+
/**
|
29
|
+
* Updates the transformation directly. Using `transformBy` is preferred.
|
30
|
+
* @param newTransform - should map from canvas coordinates to screen coordinates.
|
31
|
+
*/
|
23
32
|
resetTransform(newTransform?: Mat33): void;
|
24
33
|
get screenToCanvasTransform(): Mat33;
|
25
34
|
get canvasToScreenTransform(): Mat33;
|
26
|
-
|
35
|
+
/** @returns the size of the visible region in pixels. */
|
36
|
+
getScreenRectSize(): Vec2;
|
37
|
+
/** Alias for `getScreenRectSize`. @deprecated */
|
38
|
+
getResolution(): Vec3;
|
39
|
+
/** @returns the amount a vector on the canvas is scaled to become a vector on the screen. */
|
27
40
|
getScaleFactor(): number;
|
41
|
+
/**
|
42
|
+
* @returns `getScaleFactor()` rounded to the nearest power of 10.
|
43
|
+
* For example, if `getScaleFactor()` returns 101, `getScaleFactorToNearestPowerOfTen()`
|
44
|
+
* should return `100` because `100` is the nearest power of 10 to 101.
|
45
|
+
*/
|
46
|
+
getScaleFactorToNearestPowerOfTen(): number;
|
47
|
+
snapToGrid(canvasPos: Point2): Vec3;
|
48
|
+
/** Returns the size of one screen pixel in canvas units. */
|
28
49
|
getSizeOfPixelOnCanvas(): number;
|
50
|
+
/**
|
51
|
+
* @returns the angle of the canvas in radians.
|
52
|
+
* This is the angle by which the canvas is rotated relative to the screen.
|
53
|
+
*/
|
29
54
|
getRotationAngle(): number;
|
55
|
+
/**
|
56
|
+
* Rounds the given `point` to a multiple of 10 such that it is within `tolerance` of
|
57
|
+
* its original location. This is useful for preparing data for base-10 conversion.
|
58
|
+
*/
|
30
59
|
static roundPoint<T extends Point2 | number>(point: T, tolerance: number): PointDataType<T>;
|
31
60
|
roundPoint(point: Point2): Point2;
|
32
61
|
static roundScaleRatio(scaleRatio: number, roundAmount?: number): number;
|
package/dist/src/Viewport.js
CHANGED
@@ -29,22 +29,26 @@ export class Viewport {
|
|
29
29
|
updateScreenSize(screenSize) {
|
30
30
|
this.screenRect = this.screenRect.resizedTo(screenSize);
|
31
31
|
}
|
32
|
-
|
32
|
+
/** Get the screen's visible region transformed into canvas space. */
|
33
33
|
get visibleRect() {
|
34
34
|
return this.screenRect.transformedBoundingBox(this.inverseTransform);
|
35
35
|
}
|
36
|
-
|
36
|
+
/** @returns the given point, but in canvas coordinates */
|
37
37
|
screenToCanvas(screenPoint) {
|
38
38
|
return this.inverseTransform.transformVec2(screenPoint);
|
39
39
|
}
|
40
|
+
/** @returns the given point transformed into screen coordinates. */
|
40
41
|
canvasToScreen(canvasPoint) {
|
41
42
|
return this.transform.transformVec2(canvasPoint);
|
42
43
|
}
|
44
|
+
/** @returns a command that transforms the canvas by `transform`. */
|
43
45
|
static transformBy(transform) {
|
44
46
|
return new Viewport.ViewportTransform(transform);
|
45
47
|
}
|
46
|
-
|
47
|
-
|
48
|
+
/**
|
49
|
+
* Updates the transformation directly. Using `transformBy` is preferred.
|
50
|
+
* @param newTransform - should map from canvas coordinates to screen coordinates.
|
51
|
+
*/
|
48
52
|
resetTransform(newTransform = Mat33.identity) {
|
49
53
|
const oldTransform = this.transform;
|
50
54
|
this.transform = newTransform;
|
@@ -61,20 +65,46 @@ export class Viewport {
|
|
61
65
|
get canvasToScreenTransform() {
|
62
66
|
return this.transform;
|
63
67
|
}
|
64
|
-
|
68
|
+
/** @returns the size of the visible region in pixels. */
|
69
|
+
getScreenRectSize() {
|
65
70
|
return this.screenRect.size;
|
66
71
|
}
|
67
|
-
|
72
|
+
/** Alias for `getScreenRectSize`. @deprecated */
|
73
|
+
getResolution() {
|
74
|
+
return this.getScreenRectSize();
|
75
|
+
}
|
76
|
+
/** @returns the amount a vector on the canvas is scaled to become a vector on the screen. */
|
68
77
|
getScaleFactor() {
|
69
78
|
// Use transformVec3 to avoid translating the vector
|
70
79
|
return this.transform.transformVec3(Vec3.unitX).magnitude();
|
71
80
|
}
|
72
|
-
|
81
|
+
/**
|
82
|
+
* @returns `getScaleFactor()` rounded to the nearest power of 10.
|
83
|
+
* For example, if `getScaleFactor()` returns 101, `getScaleFactorToNearestPowerOfTen()`
|
84
|
+
* should return `100` because `100` is the nearest power of 10 to 101.
|
85
|
+
*/
|
86
|
+
getScaleFactorToNearestPowerOfTen() {
|
87
|
+
const scaleFactor = this.getScaleFactor();
|
88
|
+
return Math.pow(10, Math.round(Math.log10(scaleFactor)));
|
89
|
+
}
|
90
|
+
snapToGrid(canvasPos) {
|
91
|
+
const snapCoordinate = (coordinate) => {
|
92
|
+
const scaleFactor = this.getScaleFactorToNearestPowerOfTen();
|
93
|
+
const roundFactor = scaleFactor / 100;
|
94
|
+
const snapped = Math.round(coordinate * roundFactor) / roundFactor;
|
95
|
+
return snapped;
|
96
|
+
};
|
97
|
+
const snappedCanvasPos = Vec2.of(snapCoordinate(canvasPos.x), snapCoordinate(canvasPos.y));
|
98
|
+
return snappedCanvasPos;
|
99
|
+
}
|
100
|
+
/** Returns the size of one screen pixel in canvas units. */
|
73
101
|
getSizeOfPixelOnCanvas() {
|
74
102
|
return 1 / this.getScaleFactor();
|
75
103
|
}
|
76
|
-
|
77
|
-
|
104
|
+
/**
|
105
|
+
* @returns the angle of the canvas in radians.
|
106
|
+
* This is the angle by which the canvas is rotated relative to the screen.
|
107
|
+
*/
|
78
108
|
getRotationAngle() {
|
79
109
|
return this.transform.transformVec3(Vec3.unitX).angle();
|
80
110
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import Command from './Command';
|
2
2
|
import SerializableCommand from './SerializableCommand';
|
3
|
-
// Returns a command
|
3
|
+
// Returns a command that does the opposite of the given command --- `result.apply()` calls
|
4
4
|
// `command.unapply()` and `result.unapply()` calls `command.apply()`.
|
5
5
|
const invertCommand = (command) => {
|
6
6
|
if (command instanceof SerializableCommand) {
|
@@ -7,6 +7,9 @@ import { ImageComponentLocalization } from './localization';
|
|
7
7
|
export type LoadSaveData = (string[] | Record<symbol, string | number>);
|
8
8
|
export type LoadSaveDataTable = Record<string, Array<LoadSaveData>>;
|
9
9
|
export type DeserializeCallback = (data: string) => AbstractComponent;
|
10
|
+
/**
|
11
|
+
* A base class for everything that can be added to an {@link EditorImage}.
|
12
|
+
*/
|
10
13
|
export default abstract class AbstractComponent {
|
11
14
|
private readonly componentKind;
|
12
15
|
protected lastChangedTime: number;
|
@@ -19,12 +22,25 @@ export default abstract class AbstractComponent {
|
|
19
22
|
private static deserializationCallbacks;
|
20
23
|
static registerComponent(componentKind: string, deserialize: DeserializeCallback | null): void;
|
21
24
|
private loadSaveData;
|
25
|
+
/**
|
26
|
+
* Attach data that can be used while exporting the component (e.g. to SVG).
|
27
|
+
*
|
28
|
+
* This is intended for use by a {@link ImageLoader}.
|
29
|
+
*/
|
22
30
|
attachLoadSaveData(key: string, data: LoadSaveData): void;
|
31
|
+
/** See {@link attachLoadSaveData} */
|
23
32
|
getLoadSaveData(): LoadSaveDataTable;
|
24
33
|
getZIndex(): number;
|
34
|
+
/** @returns the bounding box of */
|
25
35
|
getBBox(): Rect2;
|
26
36
|
abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
37
|
+
/** @return true if `lineSegment` intersects this component. */
|
27
38
|
abstract intersects(lineSegment: LineSegment2): boolean;
|
39
|
+
/**
|
40
|
+
* @returns true if this component intersects `rect` -- it is entirely contained
|
41
|
+
* within the rectangle or one of the rectangle's edges intersects this component.
|
42
|
+
*/
|
43
|
+
intersectsRect(rect: Rect2): boolean;
|
28
44
|
protected abstract serializeToJSON(): any[] | Record<string, any> | number | string | null;
|
29
45
|
protected abstract applyTransformation(affineTransfm: Mat33): void;
|
30
46
|
transformBy(affineTransfm: Mat33): SerializableCommand;
|
@@ -34,6 +50,10 @@ export default abstract class AbstractComponent {
|
|
34
50
|
private static transformElementCommandId;
|
35
51
|
private static UnresolvedTransformElementCommand;
|
36
52
|
private static TransformElementCommand;
|
53
|
+
/**
|
54
|
+
* @return a description that could be read by a screen reader
|
55
|
+
* (e.g. when adding/erasing the component)
|
56
|
+
*/
|
37
57
|
abstract description(localizationTable: ImageComponentLocalization): string;
|
38
58
|
protected abstract createClone(): AbstractComponent;
|
39
59
|
clone(): AbstractComponent;
|