js-draw 0.0.9 → 0.1.1
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 +12 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +2 -2
- package/dist/src/Editor.js +15 -7
- package/dist/src/EditorImage.d.ts +15 -7
- package/dist/src/EditorImage.js +43 -37
- package/dist/src/SVGLoader.d.ts +3 -2
- package/dist/src/SVGLoader.js +9 -7
- package/dist/src/Viewport.d.ts +4 -0
- package/dist/src/Viewport.js +41 -0
- package/dist/src/components/AbstractComponent.d.ts +3 -2
- package/dist/src/components/AbstractComponent.js +3 -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/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 +5 -3
- 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} +6 -2
- package/dist/src/{Display.js → rendering/Display.js} +37 -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} +19 -8
- package/dist/src/rendering/{AbstractRenderer.js → renderers/AbstractRenderer.js} +37 -2
- package/dist/src/rendering/{CanvasRenderer.d.ts → renderers/CanvasRenderer.d.ts} +14 -5
- package/dist/src/rendering/renderers/CanvasRenderer.js +164 -0
- 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} +4 -3
- package/dist/src/rendering/{SVGRenderer.js → renderers/SVGRenderer.js} +14 -11
- package/dist/src/testing/createEditor.js +1 -1
- package/dist/src/toolbar/HTMLToolbar.js +11 -2
- package/dist/src/toolbar/localization.d.ts +1 -0
- package/dist/src/toolbar/localization.js +1 -0
- package/dist/src/tools/PanZoom.js +3 -0
- package/dist/src/tools/SelectionTool.d.ts +3 -0
- package/dist/src/tools/SelectionTool.js +22 -24
- package/dist/src/types.d.ts +2 -1
- package/package.json +1 -1
- package/src/Editor.ts +17 -8
- package/src/EditorImage.test.ts +2 -2
- package/src/EditorImage.ts +54 -42
- package/src/SVGLoader.ts +11 -8
- package/src/Viewport.ts +56 -0
- package/src/components/AbstractComponent.ts +6 -2
- package/src/components/SVGGlobalAttributesObject.ts +2 -2
- package/src/components/Stroke.ts +1 -1
- 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.toString.test.ts +12 -2
- package/src/geometry/Path.ts +8 -4
- package/src/geometry/Rect2.test.ts +47 -8
- package/src/geometry/Rect2.ts +57 -9
- package/src/{Display.ts → rendering/Display.ts} +43 -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 -8
- package/src/rendering/renderers/CanvasRenderer.ts +219 -0
- 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} +17 -13
- package/src/testing/createEditor.ts +1 -1
- package/src/toolbar/HTMLToolbar.ts +13 -2
- package/src/toolbar/localization.ts +2 -0
- package/src/tools/PanZoom.ts +3 -0
- package/src/tools/SelectionTool.test.ts +1 -1
- package/src/tools/SelectionTool.ts +28 -33
- 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/rendering/CanvasRenderer.js +0 -108
- package/src/rendering/CanvasRenderer.ts +0 -141
@@ -1,3 +1,4 @@
|
|
1
|
+
import Command from '../commands/Command';
|
1
2
|
import Editor from '../Editor';
|
2
3
|
import Rect2 from '../geometry/Rect2';
|
3
4
|
import { Point2, Vec2 } from '../geometry/Vec2';
|
@@ -30,6 +31,7 @@ declare class Selection {
|
|
30
31
|
private recomputeBoxRotation;
|
31
32
|
getSelectedItemCount(): number;
|
32
33
|
updateUI(): void;
|
34
|
+
deleteSelectedObjects(): Command;
|
33
35
|
}
|
34
36
|
export default class SelectionTool extends BaseTool {
|
35
37
|
private editor;
|
@@ -45,5 +47,6 @@ export default class SelectionTool extends BaseTool {
|
|
45
47
|
onGestureCancel(): void;
|
46
48
|
setEnabled(enabled: boolean): void;
|
47
49
|
getSelection(): Selection | null;
|
50
|
+
clearSelection(): void;
|
48
51
|
}
|
49
52
|
export {};
|
@@ -7,12 +7,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8
8
|
});
|
9
9
|
};
|
10
|
+
import Erase from '../commands/Erase';
|
10
11
|
import Mat33 from '../geometry/Mat33';
|
11
12
|
// import Mat33 from "../geometry/Mat33";
|
12
13
|
import Rect2 from '../geometry/Rect2';
|
13
14
|
import { Vec2 } from '../geometry/Vec2';
|
14
15
|
import { EditorEventType } from '../types';
|
15
|
-
import Viewport from '../Viewport';
|
16
16
|
import BaseTool from './BaseTool';
|
17
17
|
import { ToolType } from './ToolController';
|
18
18
|
const handleScreenSize = 30;
|
@@ -114,7 +114,7 @@ const makeDraggable = (element, onDrag, onDragEnd) => {
|
|
114
114
|
element.addEventListener('pointercancel', onPointerEnd);
|
115
115
|
};
|
116
116
|
// Maximum number of strokes to transform without a re-render.
|
117
|
-
const updateChunkSize =
|
117
|
+
const updateChunkSize = 100;
|
118
118
|
class Selection {
|
119
119
|
constructor(startPoint, editor) {
|
120
120
|
this.startPoint = startPoint;
|
@@ -284,10 +284,13 @@ class Selection {
|
|
284
284
|
if (this.region.containsRect(elem.getBBox())) {
|
285
285
|
return true;
|
286
286
|
}
|
287
|
-
|
288
|
-
|
287
|
+
// Calculated bounding boxes can be slightly larger than their actual contents' bounding box.
|
288
|
+
// As such, test with more lines than just this' edges.
|
289
|
+
const testLines = [];
|
290
|
+
for (const subregion of this.region.divideIntoGrid(2, 2)) {
|
291
|
+
testLines.push(...subregion.getEdges());
|
289
292
|
}
|
290
|
-
return
|
293
|
+
return testLines.some(edge => elem.intersects(edge));
|
291
294
|
});
|
292
295
|
// Find the bounding box of all selected elements.
|
293
296
|
if (!this.recomputeRegion()) {
|
@@ -344,6 +347,9 @@ class Selection {
|
|
344
347
|
this.backgroundBox.style.transform = `rotate(${rotationDeg}deg)`;
|
345
348
|
this.rotateCircle.style.transform = `rotate(${-rotationDeg}deg)`;
|
346
349
|
}
|
350
|
+
deleteSelectedObjects() {
|
351
|
+
return new Erase(this.selectedElems);
|
352
|
+
}
|
347
353
|
}
|
348
354
|
export default class SelectionTool extends BaseTool {
|
349
355
|
constructor(editor, description) {
|
@@ -388,26 +394,9 @@ export default class SelectionTool extends BaseTool {
|
|
388
394
|
tool: this,
|
389
395
|
});
|
390
396
|
if (hasSelection) {
|
391
|
-
const visibleRect = this.editor.viewport.visibleRect;
|
392
|
-
const selectionRect = this.selectionBox.region;
|
393
397
|
this.editor.announceForAccessibility(this.editor.localization.selectedElements(this.selectionBox.getSelectedItemCount()));
|
394
|
-
|
395
|
-
|
396
|
-
// Ensure that the selection fits within the target
|
397
|
-
if (targetRect.w < selectionRect.w || targetRect.h < selectionRect.h) {
|
398
|
-
const multiplier = Math.max(selectionRect.w / targetRect.w, selectionRect.h / targetRect.h);
|
399
|
-
const visibleRectTransform = Mat33.scaling2D(multiplier, targetRect.topLeft);
|
400
|
-
const viewportContentTransform = visibleRectTransform.inverse();
|
401
|
-
(new Viewport.ViewportTransform(viewportContentTransform)).apply(this.editor);
|
402
|
-
}
|
403
|
-
// Ensure that the top left is visible
|
404
|
-
if (!targetRect.containsRect(selectionRect)) {
|
405
|
-
// target position - current position
|
406
|
-
const translation = selectionRect.center.minus(targetRect.center);
|
407
|
-
const visibleRectTransform = Mat33.translation(translation);
|
408
|
-
const viewportContentTransform = visibleRectTransform.inverse();
|
409
|
-
(new Viewport.ViewportTransform(viewportContentTransform)).apply(this.editor);
|
410
|
-
}
|
398
|
+
const selectionRect = this.selectionBox.region;
|
399
|
+
this.editor.viewport.zoomTo(selectionRect).apply(this.editor);
|
411
400
|
}
|
412
401
|
}
|
413
402
|
onPointerUp(event) {
|
@@ -434,4 +423,13 @@ export default class SelectionTool extends BaseTool {
|
|
434
423
|
getSelection() {
|
435
424
|
return this.selectionBox;
|
436
425
|
}
|
426
|
+
clearSelection() {
|
427
|
+
this.handleOverlay.replaceChildren();
|
428
|
+
this.prevSelectionBox = this.selectionBox;
|
429
|
+
this.selectionBox = null;
|
430
|
+
this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
|
431
|
+
kind: EditorEventType.ToolUpdated,
|
432
|
+
tool: this,
|
433
|
+
});
|
434
|
+
}
|
437
435
|
}
|
package/dist/src/types.d.ts
CHANGED
@@ -89,8 +89,9 @@ export interface ColorPickerToggled {
|
|
89
89
|
export declare type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | ColorPickerToggled;
|
90
90
|
export declare type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null;
|
91
91
|
export declare type ComponentAddedListener = (component: AbstractComponent) => void;
|
92
|
+
export declare type OnDetermineExportRectListener = (exportRect: Rect2) => void;
|
92
93
|
export interface ImageLoader {
|
93
|
-
start(onAddComponent: ComponentAddedListener, onProgressListener: OnProgressListener): Promise<
|
94
|
+
start(onAddComponent: ComponentAddedListener, onProgressListener: OnProgressListener, onDetermineExportRect?: OnDetermineExportRectListener): Promise<void>;
|
94
95
|
}
|
95
96
|
export interface StrokeDataPoint {
|
96
97
|
pos: Point2;
|
package/package.json
CHANGED
package/src/Editor.ts
CHANGED
@@ -9,9 +9,9 @@ import EventDispatcher from './EventDispatcher';
|
|
9
9
|
import { Point2, Vec2 } from './geometry/Vec2';
|
10
10
|
import Vec3 from './geometry/Vec3';
|
11
11
|
import HTMLToolbar from './toolbar/HTMLToolbar';
|
12
|
-
import { RenderablePathSpec } from './rendering/AbstractRenderer';
|
13
|
-
import Display, { RenderingMode } from './Display';
|
14
|
-
import SVGRenderer from './rendering/SVGRenderer';
|
12
|
+
import { RenderablePathSpec } from './rendering/renderers/AbstractRenderer';
|
13
|
+
import Display, { RenderingMode } from './rendering/Display';
|
14
|
+
import SVGRenderer from './rendering/renderers/SVGRenderer';
|
15
15
|
import Color4 from './Color4';
|
16
16
|
import SVGLoader from './SVGLoader';
|
17
17
|
import Pointer from './Pointer';
|
@@ -311,6 +311,7 @@ export class Editor {
|
|
311
311
|
private async asyncApplyOrUnapplyCommands(
|
312
312
|
commands: Command[], apply: boolean, updateChunkSize: number
|
313
313
|
) {
|
314
|
+
this.display.setDraftMode(true);
|
314
315
|
for (let i = 0; i < commands.length; i += updateChunkSize) {
|
315
316
|
this.showLoadingWarning(i / commands.length);
|
316
317
|
|
@@ -332,6 +333,7 @@ export class Editor {
|
|
332
333
|
});
|
333
334
|
}
|
334
335
|
}
|
336
|
+
this.display.setDraftMode(false);
|
335
337
|
this.hideLoadingWarning();
|
336
338
|
}
|
337
339
|
|
@@ -378,7 +380,8 @@ export class Editor {
|
|
378
380
|
);
|
379
381
|
}
|
380
382
|
|
381
|
-
this.image.render(renderer, this.viewport);
|
383
|
+
//this.image.render(renderer, this.viewport);
|
384
|
+
this.image.renderWithCache(renderer, this.display.getCache(), this.viewport);
|
382
385
|
this.rerenderQueued = false;
|
383
386
|
}
|
384
387
|
|
@@ -461,22 +464,28 @@ export class Editor {
|
|
461
464
|
|
462
465
|
public async loadFrom(loader: ImageLoader) {
|
463
466
|
this.showLoadingWarning(0);
|
464
|
-
|
467
|
+
this.display.setDraftMode(true);
|
468
|
+
|
469
|
+
await loader.start((component) => {
|
465
470
|
(new EditorImage.AddElementCommand(component)).apply(this);
|
466
471
|
}, (countProcessed: number, totalToProcess: number) => {
|
467
|
-
if (countProcessed %
|
472
|
+
if (countProcessed % 500 === 0) {
|
468
473
|
this.showLoadingWarning(countProcessed / totalToProcess);
|
469
|
-
this.rerender(
|
474
|
+
this.rerender();
|
470
475
|
return new Promise(resolve => {
|
471
476
|
requestAnimationFrame(() => resolve());
|
472
477
|
});
|
473
478
|
}
|
474
479
|
|
475
480
|
return null;
|
481
|
+
}, (importExportRect: Rect2) => {
|
482
|
+
this.setImportExportRect(importExportRect).apply(this);
|
483
|
+
this.viewport.zoomTo(importExportRect).apply(this);
|
476
484
|
});
|
477
485
|
this.hideLoadingWarning();
|
478
486
|
|
479
|
-
this.
|
487
|
+
this.display.setDraftMode(false);
|
488
|
+
this.queueRerender();
|
480
489
|
}
|
481
490
|
|
482
491
|
// Returns the size of the visible region of the output SVG
|
package/src/EditorImage.test.ts
CHANGED
@@ -5,8 +5,8 @@ import Stroke from './components/Stroke';
|
|
5
5
|
import { Vec2 } from './geometry/Vec2';
|
6
6
|
import Path, { PathCommandType } from './geometry/Path';
|
7
7
|
import Color4 from './Color4';
|
8
|
-
import DummyRenderer from './rendering/DummyRenderer';
|
9
|
-
import { RenderingStyle } from './rendering/AbstractRenderer';
|
8
|
+
import DummyRenderer from './rendering/renderers/DummyRenderer';
|
9
|
+
import { RenderingStyle } from './rendering/renderers/AbstractRenderer';
|
10
10
|
import createEditor from './testing/createEditor';
|
11
11
|
|
12
12
|
describe('EditorImage', () => {
|
package/src/EditorImage.ts
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
import Editor from './Editor';
|
2
|
-
import AbstractRenderer from './rendering/AbstractRenderer';
|
2
|
+
import AbstractRenderer from './rendering/renderers/AbstractRenderer';
|
3
3
|
import Command from './commands/Command';
|
4
4
|
import Viewport from './Viewport';
|
5
5
|
import AbstractComponent from './components/AbstractComponent';
|
6
6
|
import Rect2 from './geometry/Rect2';
|
7
7
|
import { EditorLocalization } from './localization';
|
8
|
+
import RenderingCache from './rendering/caching/RenderingCache';
|
9
|
+
|
10
|
+
export const sortLeavesByZIndex = (leaves: Array<ImageNode>) => {
|
11
|
+
leaves.sort((a, b) => a.getContent()!.getZIndex() - b.getContent()!.getZIndex());
|
12
|
+
};
|
8
13
|
|
9
14
|
// Handles lookup/storage of elements in the image
|
10
15
|
export default class EditorImage {
|
@@ -20,7 +25,7 @@ export default class EditorImage {
|
|
20
25
|
|
21
26
|
// Returns the parent of the given element, if it exists.
|
22
27
|
public findParent(elem: AbstractComponent): ImageNode|null {
|
23
|
-
const candidates = this.root.
|
28
|
+
const candidates = this.root.getLeavesIntersectingRegion(elem.getBBox());
|
24
29
|
for (const candidate of candidates) {
|
25
30
|
if (candidate.getContent() === elem) {
|
26
31
|
return candidate;
|
@@ -29,25 +34,18 @@ export default class EditorImage {
|
|
29
34
|
return null;
|
30
35
|
}
|
31
36
|
|
32
|
-
|
33
|
-
|
37
|
+
public renderWithCache(screenRenderer: AbstractRenderer, cache: RenderingCache, viewport: Viewport) {
|
38
|
+
cache.render(screenRenderer, this.root, viewport);
|
34
39
|
}
|
35
40
|
|
36
|
-
public render(renderer: AbstractRenderer, viewport: Viewport
|
37
|
-
|
38
|
-
const leaves = this.root.getLeavesInRegion(viewport.visibleRect, minFraction);
|
39
|
-
this.sortLeaves(leaves);
|
40
|
-
|
41
|
-
for (const leaf of leaves) {
|
42
|
-
// Leaves by definition have content
|
43
|
-
leaf.getContent()!.render(renderer, viewport.visibleRect);
|
44
|
-
}
|
41
|
+
public render(renderer: AbstractRenderer, viewport: Viewport) {
|
42
|
+
this.root.render(renderer, viewport.visibleRect);
|
45
43
|
}
|
46
44
|
|
47
45
|
// Renders all nodes, even ones not within the viewport
|
48
46
|
public renderAll(renderer: AbstractRenderer) {
|
49
47
|
const leaves = this.root.getLeaves();
|
50
|
-
|
48
|
+
sortLeavesByZIndex(leaves);
|
51
49
|
|
52
50
|
for (const leaf of leaves) {
|
53
51
|
leaf.getContent()!.render(renderer, leaf.getBBox());
|
@@ -55,8 +53,9 @@ export default class EditorImage {
|
|
55
53
|
}
|
56
54
|
|
57
55
|
public getElementsIntersectingRegion(region: Rect2): AbstractComponent[] {
|
58
|
-
const leaves = this.root.
|
59
|
-
|
56
|
+
const leaves = this.root.getLeavesIntersectingRegion(region);
|
57
|
+
sortLeavesByZIndex(leaves);
|
58
|
+
|
60
59
|
return leaves.map(leaf => leaf.getContent()!);
|
61
60
|
}
|
62
61
|
|
@@ -100,15 +99,17 @@ export default class EditorImage {
|
|
100
99
|
}
|
101
100
|
|
102
101
|
export type AddElementCommand = typeof EditorImage.AddElementCommand.prototype;
|
102
|
+
type TooSmallToRenderCheck = (rect: Rect2)=> boolean;
|
103
103
|
|
104
|
-
|
104
|
+
// TODO: Assign leaf nodes to CacheNodes. When leaf nodes are modified, the corresponding CacheNodes can be updated.
|
105
105
|
export class ImageNode {
|
106
106
|
private content: AbstractComponent|null;
|
107
107
|
private bbox: Rect2;
|
108
108
|
private children: ImageNode[];
|
109
109
|
private targetChildCount: number = 30;
|
110
|
-
|
111
|
-
private
|
110
|
+
|
111
|
+
private id: number;
|
112
|
+
private static idCounter: number = 0;
|
112
113
|
|
113
114
|
public constructor(
|
114
115
|
private parent: ImageNode|null = null
|
@@ -117,8 +118,15 @@ export class ImageNode {
|
|
117
118
|
this.bbox = Rect2.empty;
|
118
119
|
this.content = null;
|
119
120
|
|
120
|
-
this.
|
121
|
-
|
121
|
+
this.id = ImageNode.idCounter++;
|
122
|
+
}
|
123
|
+
|
124
|
+
public getId() {
|
125
|
+
return this.id;
|
126
|
+
}
|
127
|
+
|
128
|
+
public onContentChange() {
|
129
|
+
this.id = ImageNode.idCounter++;
|
122
130
|
}
|
123
131
|
|
124
132
|
public getContent(): AbstractComponent|null {
|
@@ -129,18 +137,25 @@ export class ImageNode {
|
|
129
137
|
return this.parent;
|
130
138
|
}
|
131
139
|
|
132
|
-
private
|
140
|
+
private getChildrenIntersectingRegion(region: Rect2): ImageNode[] {
|
133
141
|
return this.children.filter(child => {
|
134
142
|
return child.getBBox().intersects(region);
|
135
143
|
});
|
136
144
|
}
|
137
145
|
|
146
|
+
public getChildrenOrSelfIntersectingRegion(region: Rect2): ImageNode[] {
|
147
|
+
if (this.content) {
|
148
|
+
return [this];
|
149
|
+
}
|
150
|
+
return this.getChildrenIntersectingRegion(region);
|
151
|
+
}
|
152
|
+
|
138
153
|
// Returns a list of `ImageNode`s with content (and thus no children).
|
139
|
-
public
|
154
|
+
public getLeavesIntersectingRegion(region: Rect2, isTooSmall?: TooSmallToRenderCheck): ImageNode[] {
|
140
155
|
const result: ImageNode[] = [];
|
141
156
|
|
142
157
|
// Don't render if too small
|
143
|
-
if (this.bbox
|
158
|
+
if (isTooSmall?.(this.bbox)) {
|
144
159
|
return [];
|
145
160
|
}
|
146
161
|
|
@@ -148,9 +163,9 @@ export class ImageNode {
|
|
148
163
|
result.push(this);
|
149
164
|
}
|
150
165
|
|
151
|
-
const children = this.
|
166
|
+
const children = this.getChildrenIntersectingRegion(region);
|
152
167
|
for (const child of children) {
|
153
|
-
result.push(...child.
|
168
|
+
result.push(...child.getLeavesIntersectingRegion(region, isTooSmall));
|
154
169
|
}
|
155
170
|
|
156
171
|
return result;
|
@@ -172,6 +187,8 @@ export class ImageNode {
|
|
172
187
|
}
|
173
188
|
|
174
189
|
public addLeaf(leaf: AbstractComponent): ImageNode {
|
190
|
+
this.onContentChange();
|
191
|
+
|
175
192
|
if (this.content === null && this.children.length === 0) {
|
176
193
|
this.content = leaf;
|
177
194
|
this.recomputeBBox(true);
|
@@ -239,12 +256,8 @@ export class ImageNode {
|
|
239
256
|
const oldBBox = this.bbox;
|
240
257
|
if (this.content !== null) {
|
241
258
|
this.bbox = this.content.getBBox();
|
242
|
-
this.minZIndex = this.content.zIndex;
|
243
|
-
this.maxZIndex = this.content.zIndex;
|
244
259
|
} else {
|
245
260
|
this.bbox = Rect2.empty;
|
246
|
-
this.minZIndex = null;
|
247
|
-
this.maxZIndex = null;
|
248
261
|
let isFirst = true;
|
249
262
|
|
250
263
|
for (const child of this.children) {
|
@@ -254,15 +267,6 @@ export class ImageNode {
|
|
254
267
|
} else {
|
255
268
|
this.bbox = this.bbox.union(child.getBBox());
|
256
269
|
}
|
257
|
-
|
258
|
-
this.minZIndex ??= child.minZIndex;
|
259
|
-
this.maxZIndex ??= child.maxZIndex;
|
260
|
-
if (child.minZIndex !== null && this.minZIndex !== null) {
|
261
|
-
this.minZIndex = Math.min(child.minZIndex, this.minZIndex);
|
262
|
-
}
|
263
|
-
if (child.maxZIndex !== null && this.maxZIndex !== null) {
|
264
|
-
this.maxZIndex = Math.max(child.maxZIndex, this.maxZIndex);
|
265
|
-
}
|
266
270
|
}
|
267
271
|
}
|
268
272
|
|
@@ -295,9 +299,6 @@ export class ImageNode {
|
|
295
299
|
|
296
300
|
// Remove this node and all of its children
|
297
301
|
public remove() {
|
298
|
-
this.minZIndex = null;
|
299
|
-
this.maxZIndex = null;
|
300
|
-
|
301
302
|
if (!this.parent) {
|
302
303
|
this.content = null;
|
303
304
|
this.children = [];
|
@@ -322,4 +323,15 @@ export class ImageNode {
|
|
322
323
|
this.parent = null;
|
323
324
|
this.children = [];
|
324
325
|
}
|
326
|
+
|
327
|
+
public render(renderer: AbstractRenderer, visibleRect: Rect2) {
|
328
|
+
// Don't render components that are < 0.1% of the viewport.
|
329
|
+
const leaves = this.getLeavesIntersectingRegion(visibleRect, rect => renderer.isTooSmallToRender(rect));
|
330
|
+
sortLeavesByZIndex(leaves);
|
331
|
+
|
332
|
+
for (const leaf of leaves) {
|
333
|
+
// Leaves by definition have content
|
334
|
+
leaf.getContent()!.render(renderer, visibleRect);
|
335
|
+
}
|
336
|
+
}
|
325
337
|
}
|
package/src/SVGLoader.ts
CHANGED
@@ -5,8 +5,8 @@ import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
|
|
5
5
|
import UnknownSVGObject from './components/UnknownSVGObject';
|
6
6
|
import Path from './geometry/Path';
|
7
7
|
import Rect2 from './geometry/Rect2';
|
8
|
-
import { RenderablePathSpec, RenderingStyle } from './rendering/AbstractRenderer';
|
9
|
-
import { ComponentAddedListener, ImageLoader, OnProgressListener } from './types';
|
8
|
+
import { RenderablePathSpec, RenderingStyle } from './rendering/renderers/AbstractRenderer';
|
9
|
+
import { ComponentAddedListener, ImageLoader, OnDetermineExportRectListener, OnProgressListener } from './types';
|
10
10
|
|
11
11
|
type OnFinishListener = ()=> void;
|
12
12
|
|
@@ -16,6 +16,8 @@ export const defaultSVGViewRect = new Rect2(0, 0, 500, 500);
|
|
16
16
|
export default class SVGLoader implements ImageLoader {
|
17
17
|
private onAddComponent: ComponentAddedListener|null = null;
|
18
18
|
private onProgress: OnProgressListener|null = null;
|
19
|
+
private onDetermineExportRect: OnDetermineExportRectListener|null = null;
|
20
|
+
|
19
21
|
private processedCount: number = 0;
|
20
22
|
private totalToProcess: number = 0;
|
21
23
|
private rootViewBox: Rect2|null;
|
@@ -126,6 +128,7 @@ export default class SVGLoader implements ImageLoader {
|
|
126
128
|
}
|
127
129
|
|
128
130
|
this.rootViewBox = new Rect2(x, y, width, height);
|
131
|
+
this.onDetermineExportRect?.(this.rootViewBox);
|
129
132
|
}
|
130
133
|
|
131
134
|
private updateSVGAttrs(node: SVGSVGElement) {
|
@@ -172,10 +175,12 @@ export default class SVGLoader implements ImageLoader {
|
|
172
175
|
}
|
173
176
|
|
174
177
|
public async start(
|
175
|
-
onAddComponent: ComponentAddedListener, onProgress: OnProgressListener
|
176
|
-
|
178
|
+
onAddComponent: ComponentAddedListener, onProgress: OnProgressListener,
|
179
|
+
onDetermineExportRect: OnDetermineExportRectListener|null = null
|
180
|
+
): Promise<void> {
|
177
181
|
this.onAddComponent = onAddComponent;
|
178
182
|
this.onProgress = onProgress;
|
183
|
+
this.onDetermineExportRect = onDetermineExportRect;
|
179
184
|
|
180
185
|
// Estimate the number of tags to process.
|
181
186
|
this.totalToProcess = this.source.childElementCount;
|
@@ -185,14 +190,12 @@ export default class SVGLoader implements ImageLoader {
|
|
185
190
|
await this.visit(this.source);
|
186
191
|
|
187
192
|
const viewBox = this.rootViewBox;
|
188
|
-
let result = defaultSVGViewRect;
|
189
193
|
|
190
|
-
if (viewBox) {
|
191
|
-
|
194
|
+
if (!viewBox) {
|
195
|
+
this.onDetermineExportRect?.(defaultSVGViewRect);
|
192
196
|
}
|
193
197
|
|
194
198
|
this.onFinish?.();
|
195
|
-
return result;
|
196
199
|
}
|
197
200
|
|
198
201
|
// TODO: Handling unsafe data! Tripple-check that this is secure!
|
package/src/Viewport.ts
CHANGED
@@ -118,12 +118,20 @@ export class Viewport {
|
|
118
118
|
return this.transform;
|
119
119
|
}
|
120
120
|
|
121
|
+
public getResolution(): Vec2 {
|
122
|
+
return this.screenRect.size;
|
123
|
+
}
|
124
|
+
|
121
125
|
// Returns the amount a vector on the canvas is scaled to become a vector on the screen.
|
122
126
|
public getScaleFactor(): number {
|
123
127
|
// Use transformVec3 to avoid translating the vector
|
124
128
|
return this.transform.transformVec3(Vec3.unitX).magnitude();
|
125
129
|
}
|
126
130
|
|
131
|
+
public getSizeOfPixelOnCanvas(): number {
|
132
|
+
return 1/this.getScaleFactor();
|
133
|
+
}
|
134
|
+
|
127
135
|
// Returns the angle of the canvas in radians
|
128
136
|
public getRotationAngle(): number {
|
129
137
|
return this.transform.transformVec3(Vec3.unitX).angle();
|
@@ -158,6 +166,54 @@ export class Viewport {
|
|
158
166
|
public roundPoint(point: Point2): Point2 {
|
159
167
|
return Viewport.roundPoint(point, 1 / this.getScaleFactor());
|
160
168
|
}
|
169
|
+
|
170
|
+
// Returns a Command that transforms the view such that [rect] is visible, and perhaps
|
171
|
+
// centered in the viewport.
|
172
|
+
// Returns null if no transformation is necessary
|
173
|
+
public zoomTo(toMakeVisible: Rect2): Command {
|
174
|
+
let transform = Mat33.identity;
|
175
|
+
|
176
|
+
// Try to move the selection within the center 2/3rds of the viewport.
|
177
|
+
const recomputeTargetRect = () => {
|
178
|
+
// transform transforms objects on the canvas. As such, we need to invert it
|
179
|
+
// to transform the viewport.
|
180
|
+
const visibleRect = this.visibleRect.transformedBoundingBox(transform.inverse());
|
181
|
+
return visibleRect.transformedBoundingBox(Mat33.scaling2D(2 / 3, visibleRect.center));
|
182
|
+
};
|
183
|
+
|
184
|
+
let targetRect = recomputeTargetRect();
|
185
|
+
const largerThanTarget = targetRect.w < toMakeVisible.w || targetRect.h < toMakeVisible.h;
|
186
|
+
|
187
|
+
// Ensure that toMakeVisible is at least 1/8th of the visible region.
|
188
|
+
const muchSmallerThanTarget = toMakeVisible.maxDimension / targetRect.maxDimension < 0.125;
|
189
|
+
|
190
|
+
if (largerThanTarget || muchSmallerThanTarget) {
|
191
|
+
// If larger than the target, ensure that the longest axis is visible.
|
192
|
+
// If smaller, shrink the visible rectangle as much as possible
|
193
|
+
const multiplier = (largerThanTarget ? Math.max : Math.min)(
|
194
|
+
toMakeVisible.w / targetRect.w, toMakeVisible.h / targetRect.h
|
195
|
+
);
|
196
|
+
const visibleRectTransform = Mat33.scaling2D(multiplier, targetRect.topLeft);
|
197
|
+
const viewportContentTransform = visibleRectTransform.inverse();
|
198
|
+
|
199
|
+
transform = transform.rightMul(viewportContentTransform);
|
200
|
+
}
|
201
|
+
|
202
|
+
targetRect = recomputeTargetRect();
|
203
|
+
|
204
|
+
// Ensure that the center of the region is visible
|
205
|
+
if (!targetRect.containsRect(toMakeVisible)) {
|
206
|
+
// target position - current position
|
207
|
+
const translation = toMakeVisible.center.minus(targetRect.center);
|
208
|
+
const visibleRectTransform = Mat33.translation(translation);
|
209
|
+
const viewportContentTransform = visibleRectTransform.inverse();
|
210
|
+
|
211
|
+
transform = transform.rightMul(viewportContentTransform);
|
212
|
+
}
|
213
|
+
|
214
|
+
|
215
|
+
return new Viewport.ViewportTransform(transform);
|
216
|
+
}
|
161
217
|
}
|
162
218
|
|
163
219
|
export namespace Viewport { // eslint-disable-line
|
@@ -4,13 +4,13 @@ import EditorImage from '../EditorImage';
|
|
4
4
|
import LineSegment2 from '../geometry/LineSegment2';
|
5
5
|
import Mat33 from '../geometry/Mat33';
|
6
6
|
import Rect2 from '../geometry/Rect2';
|
7
|
-
import AbstractRenderer from '../rendering/AbstractRenderer';
|
7
|
+
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
8
8
|
import { ImageComponentLocalization } from './localization';
|
9
9
|
|
10
10
|
export default abstract class AbstractComponent {
|
11
11
|
protected lastChangedTime: number;
|
12
12
|
protected abstract contentBBox: Rect2;
|
13
|
-
|
13
|
+
private zIndex: number;
|
14
14
|
|
15
15
|
// Topmost z-index
|
16
16
|
private static zIndexCounter: number = 0;
|
@@ -20,6 +20,10 @@ export default abstract class AbstractComponent {
|
|
20
20
|
this.zIndex = AbstractComponent.zIndexCounter++;
|
21
21
|
}
|
22
22
|
|
23
|
+
public getZIndex(): number {
|
24
|
+
return this.zIndex;
|
25
|
+
}
|
26
|
+
|
23
27
|
public getBBox(): Rect2 {
|
24
28
|
return this.contentBBox;
|
25
29
|
}
|
@@ -1,8 +1,8 @@
|
|
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';
|
5
|
-
import SVGRenderer from '../rendering/SVGRenderer';
|
4
|
+
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
5
|
+
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
6
6
|
import AbstractComponent from './AbstractComponent';
|
7
7
|
import { ImageComponentLocalization } from './localization';
|
8
8
|
|
package/src/components/Stroke.ts
CHANGED
@@ -2,7 +2,7 @@ import LineSegment2 from '../geometry/LineSegment2';
|
|
2
2
|
import Mat33 from '../geometry/Mat33';
|
3
3
|
import Path from '../geometry/Path';
|
4
4
|
import Rect2 from '../geometry/Rect2';
|
5
|
-
import AbstractRenderer, { RenderablePathSpec, RenderingStyle } from '../rendering/AbstractRenderer';
|
5
|
+
import AbstractRenderer, { RenderablePathSpec, RenderingStyle } from '../rendering/renderers/AbstractRenderer';
|
6
6
|
import AbstractComponent from './AbstractComponent';
|
7
7
|
import { ImageComponentLocalization } from './localization';
|
8
8
|
|
@@ -1,8 +1,8 @@
|
|
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';
|
5
|
-
import SVGRenderer from '../rendering/SVGRenderer';
|
4
|
+
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
5
|
+
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
6
6
|
import AbstractComponent from './AbstractComponent';
|
7
7
|
import { ImageComponentLocalization } from './localization';
|
8
8
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { PathCommandType } from '../../geometry/Path';
|
2
2
|
import Rect2 from '../../geometry/Rect2';
|
3
|
-
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
3
|
+
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
|
4
4
|
import { StrokeDataPoint } from '../../types';
|
5
5
|
import Viewport from '../../Viewport';
|
6
6
|
import AbstractComponent from '../AbstractComponent';
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
|
-
import AbstractRenderer, { RenderingStyle, RenderablePathSpec } from '../../rendering/AbstractRenderer';
|
2
|
+
import AbstractRenderer, { RenderingStyle, RenderablePathSpec } from '../../rendering/renderers/AbstractRenderer';
|
3
3
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
4
4
|
import Rect2 from '../../geometry/Rect2';
|
5
5
|
import { PathCommand, PathCommandType } from '../../geometry/Path';
|
@@ -212,7 +212,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
212
212
|
const lowerBoundary = computeBoundaryCurve(-1, halfVec);
|
213
213
|
|
214
214
|
// If the boundaries have two intersections, increasing the half vector's length could fix this.
|
215
|
-
if (upperBoundary.intersects(lowerBoundary).length
|
215
|
+
if (upperBoundary.intersects(lowerBoundary).length > 0) {
|
216
216
|
halfVec = halfVec.times(2);
|
217
217
|
}
|
218
218
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { PathCommandType } from '../../geometry/Path';
|
2
2
|
import Rect2 from '../../geometry/Rect2';
|
3
|
-
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
3
|
+
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
|
4
4
|
import { StrokeDataPoint } from '../../types';
|
5
5
|
import Viewport from '../../Viewport';
|
6
6
|
import AbstractComponent from '../AbstractComponent';
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import Path from '../../geometry/Path';
|
2
2
|
import Rect2 from '../../geometry/Rect2';
|
3
|
-
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
3
|
+
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
|
4
4
|
import { StrokeDataPoint } from '../../types';
|
5
5
|
import Viewport from '../../Viewport';
|
6
6
|
import AbstractComponent from '../AbstractComponent';
|