js-draw 0.0.10 → 0.1.2
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 +11 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +2 -2
- package/dist/src/Editor.js +17 -7
- package/dist/src/EditorImage.d.ts +15 -7
- package/dist/src/EditorImage.js +46 -37
- package/dist/src/Pointer.d.ts +3 -2
- package/dist/src/Pointer.js +12 -3
- package/dist/src/SVGLoader.d.ts +6 -2
- package/dist/src/SVGLoader.js +20 -8
- package/dist/src/Viewport.d.ts +4 -0
- package/dist/src/Viewport.js +51 -0
- package/dist/src/components/AbstractComponent.d.ts +9 -2
- package/dist/src/components/AbstractComponent.js +14 -0
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -1
- package/dist/src/components/SVGGlobalAttributesObject.js +1 -1
- package/dist/src/components/Stroke.d.ts +1 -1
- package/dist/src/components/Stroke.js +1 -1
- package/dist/src/components/UnknownSVGObject.d.ts +1 -1
- package/dist/src/components/UnknownSVGObject.js +1 -1
- package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.js +1 -1
- package/dist/src/components/builders/LineBuilder.d.ts +1 -1
- package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
- package/dist/src/components/builders/types.d.ts +1 -1
- package/dist/src/geometry/Mat33.js +3 -0
- package/dist/src/geometry/Path.d.ts +1 -1
- package/dist/src/geometry/Path.js +102 -69
- package/dist/src/geometry/Rect2.d.ts +1 -0
- package/dist/src/geometry/Rect2.js +47 -9
- package/dist/src/{Display.d.ts → rendering/Display.d.ts} +5 -2
- package/dist/src/{Display.js → rendering/Display.js} +34 -4
- package/dist/src/rendering/caching/CacheRecord.d.ts +19 -0
- package/dist/src/rendering/caching/CacheRecord.js +52 -0
- package/dist/src/rendering/caching/CacheRecordManager.d.ts +11 -0
- package/dist/src/rendering/caching/CacheRecordManager.js +31 -0
- package/dist/src/rendering/caching/RenderingCache.d.ts +12 -0
- package/dist/src/rendering/caching/RenderingCache.js +42 -0
- package/dist/src/rendering/caching/RenderingCacheNode.d.ts +28 -0
- package/dist/src/rendering/caching/RenderingCacheNode.js +301 -0
- package/dist/src/rendering/caching/testUtils.d.ts +9 -0
- package/dist/src/rendering/caching/testUtils.js +20 -0
- package/dist/src/rendering/caching/types.d.ts +21 -0
- package/dist/src/rendering/caching/types.js +1 -0
- package/dist/src/rendering/{AbstractRenderer.d.ts → renderers/AbstractRenderer.d.ts} +20 -9
- package/dist/src/rendering/{AbstractRenderer.js → renderers/AbstractRenderer.js} +37 -3
- package/dist/src/rendering/{CanvasRenderer.d.ts → renderers/CanvasRenderer.d.ts} +10 -5
- package/dist/src/rendering/{CanvasRenderer.js → renderers/CanvasRenderer.js} +60 -20
- package/dist/src/rendering/{DummyRenderer.d.ts → renderers/DummyRenderer.d.ts} +9 -5
- package/dist/src/rendering/{DummyRenderer.js → renderers/DummyRenderer.js} +35 -4
- package/dist/src/rendering/{SVGRenderer.d.ts → renderers/SVGRenderer.d.ts} +7 -5
- package/dist/src/rendering/{SVGRenderer.js → renderers/SVGRenderer.js} +35 -18
- package/dist/src/testing/createEditor.js +1 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +2 -1
- package/dist/src/toolbar/HTMLToolbar.js +165 -154
- package/dist/src/toolbar/icons.d.ts +10 -0
- package/dist/src/toolbar/icons.js +180 -0
- package/dist/src/toolbar/localization.d.ts +4 -1
- package/dist/src/toolbar/localization.js +4 -1
- package/dist/src/toolbar/types.d.ts +4 -0
- package/dist/src/tools/PanZoom.d.ts +9 -6
- package/dist/src/tools/PanZoom.js +30 -21
- package/dist/src/tools/Pen.js +8 -3
- package/dist/src/tools/SelectionTool.js +9 -24
- package/dist/src/tools/ToolController.d.ts +5 -6
- package/dist/src/tools/ToolController.js +8 -10
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/dist/src/types.d.ts +2 -1
- package/package.json +1 -1
- package/src/Editor.ts +19 -8
- package/src/EditorImage.test.ts +2 -2
- package/src/EditorImage.ts +58 -42
- package/src/Pointer.ts +13 -4
- package/src/SVGLoader.ts +36 -10
- package/src/Viewport.ts +68 -0
- package/src/components/AbstractComponent.ts +21 -2
- package/src/components/SVGGlobalAttributesObject.ts +2 -2
- package/src/components/Stroke.ts +2 -2
- package/src/components/UnknownSVGObject.ts +2 -2
- package/src/components/builders/ArrowBuilder.ts +1 -1
- package/src/components/builders/FreehandLineBuilder.ts +2 -2
- package/src/components/builders/LineBuilder.ts +1 -1
- package/src/components/builders/RectangleBuilder.ts +1 -1
- package/src/components/builders/types.ts +1 -1
- package/src/geometry/Mat33.ts +3 -0
- package/src/geometry/Path.fromString.test.ts +94 -4
- package/src/geometry/Path.toString.test.ts +12 -2
- package/src/geometry/Path.ts +107 -71
- package/src/geometry/Rect2.test.ts +47 -8
- package/src/geometry/Rect2.ts +57 -9
- package/src/{Display.ts → rendering/Display.ts} +39 -6
- package/src/rendering/caching/CacheRecord.test.ts +49 -0
- package/src/rendering/caching/CacheRecord.ts +73 -0
- package/src/rendering/caching/CacheRecordManager.ts +45 -0
- package/src/rendering/caching/RenderingCache.test.ts +44 -0
- package/src/rendering/caching/RenderingCache.ts +63 -0
- package/src/rendering/caching/RenderingCacheNode.ts +378 -0
- package/src/rendering/caching/testUtils.ts +35 -0
- package/src/rendering/caching/types.ts +39 -0
- package/src/rendering/{AbstractRenderer.ts → renderers/AbstractRenderer.ts} +57 -9
- package/src/rendering/{CanvasRenderer.ts → renderers/CanvasRenderer.ts} +74 -25
- package/src/rendering/renderers/DummyRenderer.test.ts +43 -0
- package/src/rendering/{DummyRenderer.ts → renderers/DummyRenderer.ts} +50 -7
- package/src/rendering/{SVGRenderer.ts → renderers/SVGRenderer.ts} +39 -23
- package/src/testing/createEditor.ts +1 -1
- package/src/toolbar/HTMLToolbar.ts +199 -170
- package/src/toolbar/icons.ts +203 -0
- package/src/toolbar/localization.ts +9 -2
- package/src/toolbar/toolbar.css +21 -8
- package/src/toolbar/types.ts +5 -0
- package/src/tools/PanZoom.ts +37 -27
- package/src/tools/Pen.ts +7 -3
- package/src/tools/SelectionTool.test.ts +1 -1
- package/src/tools/SelectionTool.ts +12 -33
- package/src/tools/ToolController.ts +3 -5
- package/src/tools/localization.ts +2 -0
- package/src/types.ts +10 -3
- package/tsconfig.json +1 -0
- package/dist/__mocks__/coloris.d.ts +0 -2
- package/dist/__mocks__/coloris.js +0 -5
package/dist/src/Editor.d.ts
CHANGED
@@ -6,8 +6,8 @@ import UndoRedoHistory from './UndoRedoHistory';
|
|
6
6
|
import Viewport from './Viewport';
|
7
7
|
import { Point2 } from './geometry/Vec2';
|
8
8
|
import HTMLToolbar from './toolbar/HTMLToolbar';
|
9
|
-
import { RenderablePathSpec } from './rendering/AbstractRenderer';
|
10
|
-
import Display, { RenderingMode } from './Display';
|
9
|
+
import { RenderablePathSpec } from './rendering/renderers/AbstractRenderer';
|
10
|
+
import Display, { RenderingMode } from './rendering/Display';
|
11
11
|
import Pointer from './Pointer';
|
12
12
|
import Rect2 from './geometry/Rect2';
|
13
13
|
import { EditorLocalization } from './localization';
|
package/dist/src/Editor.js
CHANGED
@@ -16,8 +16,8 @@ import EventDispatcher from './EventDispatcher';
|
|
16
16
|
import { Vec2 } from './geometry/Vec2';
|
17
17
|
import Vec3 from './geometry/Vec3';
|
18
18
|
import HTMLToolbar from './toolbar/HTMLToolbar';
|
19
|
-
import Display, { RenderingMode } from './Display';
|
20
|
-
import SVGRenderer from './rendering/SVGRenderer';
|
19
|
+
import Display, { RenderingMode } from './rendering/Display';
|
20
|
+
import SVGRenderer from './rendering/renderers/SVGRenderer';
|
21
21
|
import Color4 from './Color4';
|
22
22
|
import SVGLoader from './SVGLoader';
|
23
23
|
import Pointer from './Pointer';
|
@@ -115,6 +115,10 @@ export class Editor {
|
|
115
115
|
// May be required to prevent text selection on iOS/Safari:
|
116
116
|
// See https://stackoverflow.com/a/70992717/17055750
|
117
117
|
this.renderingRegion.addEventListener('touchstart', evt => evt.preventDefault());
|
118
|
+
this.renderingRegion.addEventListener('contextmenu', evt => {
|
119
|
+
// Don't show a context menu
|
120
|
+
evt.preventDefault();
|
121
|
+
});
|
118
122
|
this.renderingRegion.addEventListener('pointerdown', evt => {
|
119
123
|
const pointer = Pointer.ofEvent(evt, true, this.viewport);
|
120
124
|
pointers[pointer.id] = pointer;
|
@@ -282,7 +286,8 @@ export class Editor {
|
|
282
286
|
const exportRectStrokeWidth = 12;
|
283
287
|
renderer.drawRect(this.importExportViewport.visibleRect, exportRectStrokeWidth, exportRectFill);
|
284
288
|
}
|
285
|
-
this.image.render(renderer, this.viewport);
|
289
|
+
//this.image.render(renderer, this.viewport);
|
290
|
+
this.image.renderWithCache(renderer, this.display.getCache(), this.viewport);
|
286
291
|
this.rerenderQueued = false;
|
287
292
|
}
|
288
293
|
drawWetInk(...path) {
|
@@ -344,20 +349,25 @@ export class Editor {
|
|
344
349
|
loadFrom(loader) {
|
345
350
|
return __awaiter(this, void 0, void 0, function* () {
|
346
351
|
this.showLoadingWarning(0);
|
347
|
-
|
352
|
+
this.display.setDraftMode(true);
|
353
|
+
yield loader.start((component) => {
|
348
354
|
(new EditorImage.AddElementCommand(component)).apply(this);
|
349
355
|
}, (countProcessed, totalToProcess) => {
|
350
|
-
if (countProcessed %
|
356
|
+
if (countProcessed % 500 === 0) {
|
351
357
|
this.showLoadingWarning(countProcessed / totalToProcess);
|
352
|
-
this.rerender(
|
358
|
+
this.rerender();
|
353
359
|
return new Promise(resolve => {
|
354
360
|
requestAnimationFrame(() => resolve());
|
355
361
|
});
|
356
362
|
}
|
357
363
|
return null;
|
364
|
+
}, (importExportRect) => {
|
365
|
+
this.setImportExportRect(importExportRect).apply(this);
|
366
|
+
this.viewport.zoomTo(importExportRect).apply(this);
|
358
367
|
});
|
359
368
|
this.hideLoadingWarning();
|
360
|
-
this.
|
369
|
+
this.display.setDraftMode(false);
|
370
|
+
this.queueRerender();
|
361
371
|
});
|
362
372
|
}
|
363
373
|
// Returns the size of the visible region of the output SVG
|
@@ -1,16 +1,18 @@
|
|
1
1
|
import Editor from './Editor';
|
2
|
-
import AbstractRenderer from './rendering/AbstractRenderer';
|
2
|
+
import AbstractRenderer from './rendering/renderers/AbstractRenderer';
|
3
3
|
import Viewport from './Viewport';
|
4
4
|
import AbstractComponent from './components/AbstractComponent';
|
5
5
|
import Rect2 from './geometry/Rect2';
|
6
6
|
import { EditorLocalization } from './localization';
|
7
|
+
import RenderingCache from './rendering/caching/RenderingCache';
|
8
|
+
export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;
|
7
9
|
export default class EditorImage {
|
8
10
|
private root;
|
9
11
|
constructor();
|
10
12
|
private addElement;
|
11
13
|
findParent(elem: AbstractComponent): ImageNode | null;
|
12
|
-
|
13
|
-
render(renderer: AbstractRenderer, viewport: Viewport
|
14
|
+
renderWithCache(screenRenderer: AbstractRenderer, cache: RenderingCache, viewport: Viewport): void;
|
15
|
+
render(renderer: AbstractRenderer, viewport: Viewport): void;
|
14
16
|
renderAll(renderer: AbstractRenderer): void;
|
15
17
|
getElementsIntersectingRegion(region: Rect2): AbstractComponent[];
|
16
18
|
static AddElementCommand: {
|
@@ -24,23 +26,29 @@ export default class EditorImage {
|
|
24
26
|
};
|
25
27
|
}
|
26
28
|
export declare type AddElementCommand = typeof EditorImage.AddElementCommand.prototype;
|
29
|
+
declare type TooSmallToRenderCheck = (rect: Rect2) => boolean;
|
27
30
|
export declare class ImageNode {
|
28
31
|
private parent;
|
29
32
|
private content;
|
30
33
|
private bbox;
|
31
34
|
private children;
|
32
35
|
private targetChildCount;
|
33
|
-
private
|
34
|
-
private
|
36
|
+
private id;
|
37
|
+
private static idCounter;
|
35
38
|
constructor(parent?: ImageNode | null);
|
39
|
+
getId(): number;
|
40
|
+
onContentChange(): void;
|
36
41
|
getContent(): AbstractComponent | null;
|
37
42
|
getParent(): ImageNode | null;
|
38
|
-
private
|
39
|
-
|
43
|
+
private getChildrenIntersectingRegion;
|
44
|
+
getChildrenOrSelfIntersectingRegion(region: Rect2): ImageNode[];
|
45
|
+
getLeavesIntersectingRegion(region: Rect2, isTooSmall?: TooSmallToRenderCheck): ImageNode[];
|
40
46
|
getLeaves(): ImageNode[];
|
41
47
|
addLeaf(leaf: AbstractComponent): ImageNode;
|
42
48
|
getBBox(): Rect2;
|
43
49
|
recomputeBBox(bubbleUp: boolean): void;
|
44
50
|
private rebalance;
|
45
51
|
remove(): void;
|
52
|
+
render(renderer: AbstractRenderer, visibleRect: Rect2): void;
|
46
53
|
}
|
54
|
+
export {};
|
package/dist/src/EditorImage.js
CHANGED
@@ -11,6 +11,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
11
11
|
};
|
12
12
|
var _element, _applyByFlattening, _a;
|
13
13
|
import Rect2 from './geometry/Rect2';
|
14
|
+
export const sortLeavesByZIndex = (leaves) => {
|
15
|
+
leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
|
16
|
+
};
|
14
17
|
// Handles lookup/storage of elements in the image
|
15
18
|
export default class EditorImage {
|
16
19
|
constructor() {
|
@@ -21,7 +24,7 @@ export default class EditorImage {
|
|
21
24
|
}
|
22
25
|
// Returns the parent of the given element, if it exists.
|
23
26
|
findParent(elem) {
|
24
|
-
const candidates = this.root.
|
27
|
+
const candidates = this.root.getLeavesIntersectingRegion(elem.getBBox());
|
25
28
|
for (const candidate of candidates) {
|
26
29
|
if (candidate.getContent() === elem) {
|
27
30
|
return candidate;
|
@@ -29,29 +32,23 @@ export default class EditorImage {
|
|
29
32
|
}
|
30
33
|
return null;
|
31
34
|
}
|
32
|
-
|
33
|
-
|
35
|
+
renderWithCache(screenRenderer, cache, viewport) {
|
36
|
+
cache.render(screenRenderer, this.root, viewport);
|
34
37
|
}
|
35
|
-
render(renderer, viewport
|
36
|
-
|
37
|
-
const leaves = this.root.getLeavesInRegion(viewport.visibleRect, minFraction);
|
38
|
-
this.sortLeaves(leaves);
|
39
|
-
for (const leaf of leaves) {
|
40
|
-
// Leaves by definition have content
|
41
|
-
leaf.getContent().render(renderer, viewport.visibleRect);
|
42
|
-
}
|
38
|
+
render(renderer, viewport) {
|
39
|
+
this.root.render(renderer, viewport.visibleRect);
|
43
40
|
}
|
44
41
|
// Renders all nodes, even ones not within the viewport
|
45
42
|
renderAll(renderer) {
|
46
43
|
const leaves = this.root.getLeaves();
|
47
|
-
|
44
|
+
sortLeavesByZIndex(leaves);
|
48
45
|
for (const leaf of leaves) {
|
49
46
|
leaf.getContent().render(renderer, leaf.getBBox());
|
50
47
|
}
|
51
48
|
}
|
52
49
|
getElementsIntersectingRegion(region) {
|
53
|
-
const leaves = this.root.
|
54
|
-
|
50
|
+
const leaves = this.root.getLeavesIntersectingRegion(region);
|
51
|
+
sortLeavesByZIndex(leaves);
|
55
52
|
return leaves.map(leaf => leaf.getContent());
|
56
53
|
}
|
57
54
|
}
|
@@ -65,6 +62,9 @@ EditorImage.AddElementCommand = (_a = class {
|
|
65
62
|
_applyByFlattening.set(this, false);
|
66
63
|
__classPrivateFieldSet(this, _element, element, "f");
|
67
64
|
__classPrivateFieldSet(this, _applyByFlattening, applyByFlattening, "f");
|
65
|
+
if (isNaN(__classPrivateFieldGet(this, _element, "f").getBBox().area)) {
|
66
|
+
throw new Error('Elements in the image cannot have NaN bounding boxes');
|
67
|
+
}
|
68
68
|
}
|
69
69
|
apply(editor) {
|
70
70
|
editor.image.addElement(__classPrivateFieldGet(this, _element, "f"));
|
@@ -88,6 +88,7 @@ EditorImage.AddElementCommand = (_a = class {
|
|
88
88
|
_element = new WeakMap(),
|
89
89
|
_applyByFlattening = new WeakMap(),
|
90
90
|
_a);
|
91
|
+
// TODO: Assign leaf nodes to CacheNodes. When leaf nodes are modified, the corresponding CacheNodes can be updated.
|
91
92
|
export class ImageNode {
|
92
93
|
constructor(parent = null) {
|
93
94
|
this.parent = parent;
|
@@ -95,8 +96,13 @@ export class ImageNode {
|
|
95
96
|
this.children = [];
|
96
97
|
this.bbox = Rect2.empty;
|
97
98
|
this.content = null;
|
98
|
-
this.
|
99
|
-
|
99
|
+
this.id = ImageNode.idCounter++;
|
100
|
+
}
|
101
|
+
getId() {
|
102
|
+
return this.id;
|
103
|
+
}
|
104
|
+
onContentChange() {
|
105
|
+
this.id = ImageNode.idCounter++;
|
100
106
|
}
|
101
107
|
getContent() {
|
102
108
|
return this.content;
|
@@ -104,24 +110,30 @@ export class ImageNode {
|
|
104
110
|
getParent() {
|
105
111
|
return this.parent;
|
106
112
|
}
|
107
|
-
|
113
|
+
getChildrenIntersectingRegion(region) {
|
108
114
|
return this.children.filter(child => {
|
109
115
|
return child.getBBox().intersects(region);
|
110
116
|
});
|
111
117
|
}
|
118
|
+
getChildrenOrSelfIntersectingRegion(region) {
|
119
|
+
if (this.content) {
|
120
|
+
return [this];
|
121
|
+
}
|
122
|
+
return this.getChildrenIntersectingRegion(region);
|
123
|
+
}
|
112
124
|
// Returns a list of `ImageNode`s with content (and thus no children).
|
113
|
-
|
125
|
+
getLeavesIntersectingRegion(region, isTooSmall) {
|
114
126
|
const result = [];
|
115
127
|
// Don't render if too small
|
116
|
-
if (
|
128
|
+
if (isTooSmall === null || isTooSmall === void 0 ? void 0 : isTooSmall(this.bbox)) {
|
117
129
|
return [];
|
118
130
|
}
|
119
131
|
if (this.content !== null && this.getBBox().intersects(region)) {
|
120
132
|
result.push(this);
|
121
133
|
}
|
122
|
-
const children = this.
|
134
|
+
const children = this.getChildrenIntersectingRegion(region);
|
123
135
|
for (const child of children) {
|
124
|
-
result.push(...child.
|
136
|
+
result.push(...child.getLeavesIntersectingRegion(region, isTooSmall));
|
125
137
|
}
|
126
138
|
return result;
|
127
139
|
}
|
@@ -138,6 +150,7 @@ export class ImageNode {
|
|
138
150
|
return result;
|
139
151
|
}
|
140
152
|
addLeaf(leaf) {
|
153
|
+
this.onContentChange();
|
141
154
|
if (this.content === null && this.children.length === 0) {
|
142
155
|
this.content = leaf;
|
143
156
|
this.recomputeBBox(true);
|
@@ -186,17 +199,13 @@ export class ImageNode {
|
|
186
199
|
// this' ancestors bounding boxes. This also re-computes this' bounding box
|
187
200
|
// in the z-direction (z-indicies).
|
188
201
|
recomputeBBox(bubbleUp) {
|
189
|
-
var _a
|
202
|
+
var _a;
|
190
203
|
const oldBBox = this.bbox;
|
191
204
|
if (this.content !== null) {
|
192
205
|
this.bbox = this.content.getBBox();
|
193
|
-
this.minZIndex = this.content.zIndex;
|
194
|
-
this.maxZIndex = this.content.zIndex;
|
195
206
|
}
|
196
207
|
else {
|
197
208
|
this.bbox = Rect2.empty;
|
198
|
-
this.minZIndex = null;
|
199
|
-
this.maxZIndex = null;
|
200
209
|
let isFirst = true;
|
201
210
|
for (const child of this.children) {
|
202
211
|
if (isFirst) {
|
@@ -206,18 +215,10 @@ export class ImageNode {
|
|
206
215
|
else {
|
207
216
|
this.bbox = this.bbox.union(child.getBBox());
|
208
217
|
}
|
209
|
-
(_a = this.minZIndex) !== null && _a !== void 0 ? _a : (this.minZIndex = child.minZIndex);
|
210
|
-
(_b = this.maxZIndex) !== null && _b !== void 0 ? _b : (this.maxZIndex = child.maxZIndex);
|
211
|
-
if (child.minZIndex !== null && this.minZIndex !== null) {
|
212
|
-
this.minZIndex = Math.min(child.minZIndex, this.minZIndex);
|
213
|
-
}
|
214
|
-
if (child.maxZIndex !== null && this.maxZIndex !== null) {
|
215
|
-
this.maxZIndex = Math.max(child.maxZIndex, this.maxZIndex);
|
216
|
-
}
|
217
218
|
}
|
218
219
|
}
|
219
220
|
if (bubbleUp && !oldBBox.eq(this.bbox)) {
|
220
|
-
(
|
221
|
+
(_a = this.parent) === null || _a === void 0 ? void 0 : _a.recomputeBBox(true);
|
221
222
|
}
|
222
223
|
}
|
223
224
|
rebalance() {
|
@@ -243,8 +244,6 @@ export class ImageNode {
|
|
243
244
|
}
|
244
245
|
// Remove this node and all of its children
|
245
246
|
remove() {
|
246
|
-
this.minZIndex = null;
|
247
|
-
this.maxZIndex = null;
|
248
247
|
if (!this.parent) {
|
249
248
|
this.content = null;
|
250
249
|
this.children = [];
|
@@ -264,4 +263,14 @@ export class ImageNode {
|
|
264
263
|
this.parent = null;
|
265
264
|
this.children = [];
|
266
265
|
}
|
266
|
+
render(renderer, visibleRect) {
|
267
|
+
// Don't render components that are < 0.1% of the viewport.
|
268
|
+
const leaves = this.getLeavesIntersectingRegion(visibleRect, rect => renderer.isTooSmallToRender(rect));
|
269
|
+
sortLeavesByZIndex(leaves);
|
270
|
+
for (const leaf of leaves) {
|
271
|
+
// Leaves by definition have content
|
272
|
+
leaf.getContent().render(renderer, visibleRect);
|
273
|
+
}
|
274
|
+
}
|
267
275
|
}
|
276
|
+
ImageNode.idCounter = 0;
|
package/dist/src/Pointer.d.ts
CHANGED
package/dist/src/Pointer.js
CHANGED
@@ -4,8 +4,9 @@ export var PointerDevice;
|
|
4
4
|
PointerDevice[PointerDevice["Pen"] = 0] = "Pen";
|
5
5
|
PointerDevice[PointerDevice["Eraser"] = 1] = "Eraser";
|
6
6
|
PointerDevice[PointerDevice["Touch"] = 2] = "Touch";
|
7
|
-
PointerDevice[PointerDevice["
|
8
|
-
PointerDevice[PointerDevice["
|
7
|
+
PointerDevice[PointerDevice["PrimaryButtonMouse"] = 3] = "PrimaryButtonMouse";
|
8
|
+
PointerDevice[PointerDevice["RightButtonMouse"] = 4] = "RightButtonMouse";
|
9
|
+
PointerDevice[PointerDevice["Other"] = 5] = "Other";
|
9
10
|
})(PointerDevice || (PointerDevice = {}));
|
10
11
|
// Provides a snapshot containing information about a pointer. A Pointer
|
11
12
|
// object is immutable --- it will not be updated when the pointer's information changes.
|
@@ -34,7 +35,7 @@ export default class Pointer {
|
|
34
35
|
var _a, _b;
|
35
36
|
const screenPos = Vec2.of(evt.offsetX, evt.offsetY);
|
36
37
|
const pointerTypeToDevice = {
|
37
|
-
'mouse': PointerDevice.
|
38
|
+
'mouse': PointerDevice.PrimaryButtonMouse,
|
38
39
|
'pen': PointerDevice.Pen,
|
39
40
|
'touch': PointerDevice.Touch,
|
40
41
|
};
|
@@ -45,6 +46,14 @@ export default class Pointer {
|
|
45
46
|
}
|
46
47
|
const timeStamp = (new Date()).getTime();
|
47
48
|
const canvasPos = viewport.roundPoint(viewport.screenToCanvas(screenPos));
|
49
|
+
if (device === PointerDevice.PrimaryButtonMouse) {
|
50
|
+
if (evt.buttons & 0x2) {
|
51
|
+
device = PointerDevice.RightButtonMouse;
|
52
|
+
}
|
53
|
+
else if (!(evt.buttons & 0x1)) {
|
54
|
+
device = PointerDevice.Other;
|
55
|
+
}
|
56
|
+
}
|
48
57
|
return new Pointer(screenPos, canvasPos, (_b = evt.pressure) !== null && _b !== void 0 ? _b : null, evt.isPrimary, isDown, device, evt.pointerId, timeStamp);
|
49
58
|
}
|
50
59
|
// Create a new Pointer from a point on the canvas.
|
package/dist/src/SVGLoader.d.ts
CHANGED
@@ -1,23 +1,27 @@
|
|
1
1
|
import Rect2 from './geometry/Rect2';
|
2
|
-
import { ComponentAddedListener, ImageLoader, OnProgressListener } from './types';
|
2
|
+
import { ComponentAddedListener, ImageLoader, OnDetermineExportRectListener, OnProgressListener } from './types';
|
3
3
|
export declare const defaultSVGViewRect: Rect2;
|
4
|
+
export declare const svgAttributesDataKey = "svgAttrs";
|
5
|
+
export declare type SVGLoaderUnknownAttribute = [string, string];
|
4
6
|
export default class SVGLoader implements ImageLoader {
|
5
7
|
private source;
|
6
8
|
private onFinish?;
|
7
9
|
private onAddComponent;
|
8
10
|
private onProgress;
|
11
|
+
private onDetermineExportRect;
|
9
12
|
private processedCount;
|
10
13
|
private totalToProcess;
|
11
14
|
private rootViewBox;
|
12
15
|
private constructor();
|
13
16
|
private getStyle;
|
14
17
|
private strokeDataFromElem;
|
18
|
+
private attachUnrecognisedAttrs;
|
15
19
|
private addPath;
|
16
20
|
private addUnknownNode;
|
17
21
|
private updateViewBox;
|
18
22
|
private updateSVGAttrs;
|
19
23
|
private visit;
|
20
24
|
private getSourceAttrs;
|
21
|
-
start(onAddComponent: ComponentAddedListener, onProgress: OnProgressListener): Promise<
|
25
|
+
start(onAddComponent: ComponentAddedListener, onProgress: OnProgressListener, onDetermineExportRect?: OnDetermineExportRectListener | null): Promise<void>;
|
22
26
|
static fromString(text: string): SVGLoader;
|
23
27
|
}
|
package/dist/src/SVGLoader.js
CHANGED
@@ -15,12 +15,15 @@ import Path from './geometry/Path';
|
|
15
15
|
import Rect2 from './geometry/Rect2';
|
16
16
|
// Size of a loaded image if no size is specified.
|
17
17
|
export const defaultSVGViewRect = new Rect2(0, 0, 500, 500);
|
18
|
+
// Key to retrieve unrecognised attributes from an AbstractComponent
|
19
|
+
export const svgAttributesDataKey = 'svgAttrs';
|
18
20
|
export default class SVGLoader {
|
19
21
|
constructor(source, onFinish) {
|
20
22
|
this.source = source;
|
21
23
|
this.onFinish = onFinish;
|
22
24
|
this.onAddComponent = null;
|
23
25
|
this.onProgress = null;
|
26
|
+
this.onDetermineExportRect = null;
|
24
27
|
this.processedCount = 0;
|
25
28
|
this.totalToProcess = 0;
|
26
29
|
}
|
@@ -80,6 +83,14 @@ export default class SVGLoader {
|
|
80
83
|
}
|
81
84
|
return result;
|
82
85
|
}
|
86
|
+
attachUnrecognisedAttrs(elem, node, supportedAttrs) {
|
87
|
+
for (const attr of node.getAttributeNames()) {
|
88
|
+
if (supportedAttrs.has(attr)) {
|
89
|
+
continue;
|
90
|
+
}
|
91
|
+
elem.attachLoadSaveData(svgAttributesDataKey, [attr, node.getAttribute(attr)]);
|
92
|
+
}
|
93
|
+
}
|
83
94
|
// Adds a stroke with a single path
|
84
95
|
addPath(node) {
|
85
96
|
var _a;
|
@@ -87,6 +98,7 @@ export default class SVGLoader {
|
|
87
98
|
try {
|
88
99
|
const strokeData = this.strokeDataFromElem(node);
|
89
100
|
elem = new Stroke(strokeData);
|
101
|
+
this.attachUnrecognisedAttrs(elem, node, new Set(['stroke', 'fill', 'stroke-width', 'd']));
|
90
102
|
}
|
91
103
|
catch (e) {
|
92
104
|
console.error('Invalid path in node', node, '\nError:', e, '\nAdding as an unknown object.');
|
@@ -100,6 +112,7 @@ export default class SVGLoader {
|
|
100
112
|
(_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, component);
|
101
113
|
}
|
102
114
|
updateViewBox(node) {
|
115
|
+
var _a;
|
103
116
|
const viewBoxAttr = node.getAttribute('viewBox');
|
104
117
|
if (this.rootViewBox || !viewBoxAttr) {
|
105
118
|
return;
|
@@ -113,6 +126,7 @@ export default class SVGLoader {
|
|
113
126
|
return;
|
114
127
|
}
|
115
128
|
this.rootViewBox = new Rect2(x, y, width, height);
|
129
|
+
(_a = this.onDetermineExportRect) === null || _a === void 0 ? void 0 : _a.call(this, this.rootViewBox);
|
116
130
|
}
|
117
131
|
updateSVGAttrs(node) {
|
118
132
|
var _a;
|
@@ -154,23 +168,22 @@ export default class SVGLoader {
|
|
154
168
|
return [attr, node.getAttribute(attr)];
|
155
169
|
});
|
156
170
|
}
|
157
|
-
start(onAddComponent, onProgress) {
|
158
|
-
var _a;
|
171
|
+
start(onAddComponent, onProgress, onDetermineExportRect = null) {
|
172
|
+
var _a, _b;
|
159
173
|
return __awaiter(this, void 0, void 0, function* () {
|
160
174
|
this.onAddComponent = onAddComponent;
|
161
175
|
this.onProgress = onProgress;
|
176
|
+
this.onDetermineExportRect = onDetermineExportRect;
|
162
177
|
// Estimate the number of tags to process.
|
163
178
|
this.totalToProcess = this.source.childElementCount;
|
164
179
|
this.processedCount = 0;
|
165
180
|
this.rootViewBox = null;
|
166
181
|
yield this.visit(this.source);
|
167
182
|
const viewBox = this.rootViewBox;
|
168
|
-
|
169
|
-
|
170
|
-
result = Rect2.of(viewBox);
|
183
|
+
if (!viewBox) {
|
184
|
+
(_a = this.onDetermineExportRect) === null || _a === void 0 ? void 0 : _a.call(this, defaultSVGViewRect);
|
171
185
|
}
|
172
|
-
(
|
173
|
-
return result;
|
186
|
+
(_b = this.onFinish) === null || _b === void 0 ? void 0 : _b.call(this);
|
174
187
|
});
|
175
188
|
}
|
176
189
|
// TODO: Handling unsafe data! Tripple-check that this is secure!
|
@@ -187,7 +200,6 @@ export default class SVGLoader {
|
|
187
200
|
sandbox.remove();
|
188
201
|
throw new Error('SVG loading iframe is not sandboxed.');
|
189
202
|
}
|
190
|
-
// Try running JavaScript within the iframe
|
191
203
|
const sandboxDoc = (_b = (_a = sandbox.contentWindow) === null || _a === void 0 ? void 0 : _a.document) !== null && _b !== void 0 ? _b : sandbox.contentDocument;
|
192
204
|
if (sandboxDoc == null)
|
193
205
|
throw new Error('Unable to open a sandboxed iframe!');
|
package/dist/src/Viewport.d.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import Command from './commands/Command';
|
1
2
|
import { CommandLocalization } from './commands/localization';
|
2
3
|
import Editor from './Editor';
|
3
4
|
import Mat33 from './geometry/Mat33';
|
@@ -28,10 +29,13 @@ export declare class Viewport {
|
|
28
29
|
resetTransform(newTransform: Mat33): void;
|
29
30
|
get screenToCanvasTransform(): Mat33;
|
30
31
|
get canvasToScreenTransform(): Mat33;
|
32
|
+
getResolution(): Vec2;
|
31
33
|
getScaleFactor(): number;
|
34
|
+
getSizeOfPixelOnCanvas(): number;
|
32
35
|
getRotationAngle(): number;
|
33
36
|
static roundPoint<T extends Point2 | number>(point: T, tolerance: number): PointDataType<T>;
|
34
37
|
roundPoint(point: Point2): Point2;
|
38
|
+
zoomTo(toMakeVisible: Rect2): Command;
|
35
39
|
}
|
36
40
|
export declare namespace Viewport {
|
37
41
|
type ViewportTransform = typeof Viewport.ViewportTransform.prototype;
|
package/dist/src/Viewport.js
CHANGED
@@ -50,11 +50,17 @@ export class Viewport {
|
|
50
50
|
get canvasToScreenTransform() {
|
51
51
|
return this.transform;
|
52
52
|
}
|
53
|
+
getResolution() {
|
54
|
+
return this.screenRect.size;
|
55
|
+
}
|
53
56
|
// Returns the amount a vector on the canvas is scaled to become a vector on the screen.
|
54
57
|
getScaleFactor() {
|
55
58
|
// Use transformVec3 to avoid translating the vector
|
56
59
|
return this.transform.transformVec3(Vec3.unitX).magnitude();
|
57
60
|
}
|
61
|
+
getSizeOfPixelOnCanvas() {
|
62
|
+
return 1 / this.getScaleFactor();
|
63
|
+
}
|
58
64
|
// Returns the angle of the canvas in radians
|
59
65
|
getRotationAngle() {
|
60
66
|
return this.transform.transformVec3(Vec3.unitX).angle();
|
@@ -76,6 +82,51 @@ export class Viewport {
|
|
76
82
|
roundPoint(point) {
|
77
83
|
return Viewport.roundPoint(point, 1 / this.getScaleFactor());
|
78
84
|
}
|
85
|
+
// Returns a Command that transforms the view such that [rect] is visible, and perhaps
|
86
|
+
// centered in the viewport.
|
87
|
+
// Returns null if no transformation is necessary
|
88
|
+
zoomTo(toMakeVisible) {
|
89
|
+
let transform = Mat33.identity;
|
90
|
+
if (toMakeVisible.w === 0 || toMakeVisible.h === 0) {
|
91
|
+
throw new Error(`${toMakeVisible.toString()} rectangle is empty! Cannot zoom to!`);
|
92
|
+
}
|
93
|
+
if (isNaN(toMakeVisible.size.magnitude())) {
|
94
|
+
throw new Error(`${toMakeVisible.toString()} rectangle has NaN size! Cannot zoom to!`);
|
95
|
+
}
|
96
|
+
// Try to move the selection within the center 2/3rds of the viewport.
|
97
|
+
const recomputeTargetRect = () => {
|
98
|
+
// transform transforms objects on the canvas. As such, we need to invert it
|
99
|
+
// to transform the viewport.
|
100
|
+
const visibleRect = this.visibleRect.transformedBoundingBox(transform.inverse());
|
101
|
+
return visibleRect.transformedBoundingBox(Mat33.scaling2D(2 / 3, visibleRect.center));
|
102
|
+
};
|
103
|
+
let targetRect = recomputeTargetRect();
|
104
|
+
const largerThanTarget = targetRect.w < toMakeVisible.w || targetRect.h < toMakeVisible.h;
|
105
|
+
// Ensure that toMakeVisible is at least 1/8th of the visible region.
|
106
|
+
const muchSmallerThanTarget = toMakeVisible.maxDimension / targetRect.maxDimension < 0.125;
|
107
|
+
if (largerThanTarget || muchSmallerThanTarget) {
|
108
|
+
// If larger than the target, ensure that the longest axis is visible.
|
109
|
+
// If smaller, shrink the visible rectangle as much as possible
|
110
|
+
const multiplier = (largerThanTarget ? Math.max : Math.min)(toMakeVisible.w / targetRect.w, toMakeVisible.h / targetRect.h);
|
111
|
+
const visibleRectTransform = Mat33.scaling2D(multiplier, targetRect.topLeft);
|
112
|
+
const viewportContentTransform = visibleRectTransform.inverse();
|
113
|
+
transform = transform.rightMul(viewportContentTransform);
|
114
|
+
}
|
115
|
+
targetRect = recomputeTargetRect();
|
116
|
+
// Ensure that the center of the region is visible
|
117
|
+
if (!targetRect.containsRect(toMakeVisible)) {
|
118
|
+
// target position - current position
|
119
|
+
const translation = toMakeVisible.center.minus(targetRect.center);
|
120
|
+
const visibleRectTransform = Mat33.translation(translation);
|
121
|
+
const viewportContentTransform = visibleRectTransform.inverse();
|
122
|
+
transform = transform.rightMul(viewportContentTransform);
|
123
|
+
}
|
124
|
+
if (!transform.invertable()) {
|
125
|
+
console.warn('Unable to zoom to ', toMakeVisible, '! Computed transform', transform, 'is singular.');
|
126
|
+
transform = Mat33.identity;
|
127
|
+
}
|
128
|
+
return new Viewport.ViewportTransform(transform);
|
129
|
+
}
|
79
130
|
}
|
80
131
|
// Command that translates/scales the viewport.
|
81
132
|
Viewport.ViewportTransform = (_a = class {
|
@@ -2,14 +2,20 @@ import Command from '../commands/Command';
|
|
2
2
|
import LineSegment2 from '../geometry/LineSegment2';
|
3
3
|
import Mat33 from '../geometry/Mat33';
|
4
4
|
import Rect2 from '../geometry/Rect2';
|
5
|
-
import AbstractRenderer from '../rendering/AbstractRenderer';
|
5
|
+
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
6
6
|
import { ImageComponentLocalization } from './localization';
|
7
|
+
declare type LoadSaveData = unknown;
|
8
|
+
export declare type LoadSaveDataTable = Record<string, Array<LoadSaveData>>;
|
7
9
|
export default abstract class AbstractComponent {
|
8
10
|
protected lastChangedTime: number;
|
9
11
|
protected abstract contentBBox: Rect2;
|
10
|
-
zIndex
|
12
|
+
private zIndex;
|
11
13
|
private static zIndexCounter;
|
12
14
|
protected constructor();
|
15
|
+
private loadSaveData;
|
16
|
+
attachLoadSaveData(key: string, data: LoadSaveData): void;
|
17
|
+
getLoadSaveData(): LoadSaveDataTable;
|
18
|
+
getZIndex(): number;
|
13
19
|
getBBox(): Rect2;
|
14
20
|
abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
15
21
|
abstract intersects(lineSegment: LineSegment2): boolean;
|
@@ -17,3 +23,4 @@ export default abstract class AbstractComponent {
|
|
17
23
|
transformBy(affineTransfm: Mat33): Command;
|
18
24
|
abstract description(localizationTable: ImageComponentLocalization): string;
|
19
25
|
}
|
26
|
+
export {};
|
@@ -1,9 +1,23 @@
|
|
1
1
|
import EditorImage from '../EditorImage';
|
2
2
|
export default class AbstractComponent {
|
3
3
|
constructor() {
|
4
|
+
// Get and manage data attached by a loader.
|
5
|
+
this.loadSaveData = {};
|
4
6
|
this.lastChangedTime = (new Date()).getTime();
|
5
7
|
this.zIndex = AbstractComponent.zIndexCounter++;
|
6
8
|
}
|
9
|
+
attachLoadSaveData(key, data) {
|
10
|
+
if (!this.loadSaveData[key]) {
|
11
|
+
this.loadSaveData[key] = [];
|
12
|
+
}
|
13
|
+
this.loadSaveData[key].push(data);
|
14
|
+
}
|
15
|
+
getLoadSaveData() {
|
16
|
+
return this.loadSaveData;
|
17
|
+
}
|
18
|
+
getZIndex() {
|
19
|
+
return this.zIndex;
|
20
|
+
}
|
7
21
|
getBBox() {
|
8
22
|
return this.contentBBox;
|
9
23
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import LineSegment2 from '../geometry/LineSegment2';
|
2
2
|
import Mat33 from '../geometry/Mat33';
|
3
3
|
import Rect2 from '../geometry/Rect2';
|
4
|
-
import AbstractRenderer from '../rendering/AbstractRenderer';
|
4
|
+
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
5
5
|
import AbstractComponent from './AbstractComponent';
|
6
6
|
import { ImageComponentLocalization } from './localization';
|
7
7
|
export default class SVGGlobalAttributesObject extends AbstractComponent {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import Rect2 from '../geometry/Rect2';
|
2
|
-
import SVGRenderer from '../rendering/SVGRenderer';
|
2
|
+
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
3
3
|
import AbstractComponent from './AbstractComponent';
|
4
4
|
// Stores global SVG attributes (e.g. namespace identifiers.)
|
5
5
|
export default class SVGGlobalAttributesObject extends AbstractComponent {
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import LineSegment2 from '../geometry/LineSegment2';
|
2
2
|
import Mat33 from '../geometry/Mat33';
|
3
3
|
import Rect2 from '../geometry/Rect2';
|
4
|
-
import AbstractRenderer, { RenderablePathSpec } from '../rendering/AbstractRenderer';
|
4
|
+
import AbstractRenderer, { RenderablePathSpec } from '../rendering/renderers/AbstractRenderer';
|
5
5
|
import AbstractComponent from './AbstractComponent';
|
6
6
|
import { ImageComponentLocalization } from './localization';
|
7
7
|
export default class Stroke extends AbstractComponent {
|