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.
Files changed (104) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/Editor.d.ts +2 -2
  4. package/dist/src/Editor.js +15 -7
  5. package/dist/src/EditorImage.d.ts +15 -7
  6. package/dist/src/EditorImage.js +43 -37
  7. package/dist/src/SVGLoader.d.ts +3 -2
  8. package/dist/src/SVGLoader.js +9 -7
  9. package/dist/src/Viewport.d.ts +4 -0
  10. package/dist/src/Viewport.js +41 -0
  11. package/dist/src/components/AbstractComponent.d.ts +3 -2
  12. package/dist/src/components/AbstractComponent.js +3 -0
  13. package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -1
  14. package/dist/src/components/SVGGlobalAttributesObject.js +1 -1
  15. package/dist/src/components/Stroke.d.ts +1 -1
  16. package/dist/src/components/UnknownSVGObject.d.ts +1 -1
  17. package/dist/src/components/UnknownSVGObject.js +1 -1
  18. package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
  19. package/dist/src/components/builders/FreehandLineBuilder.d.ts +1 -1
  20. package/dist/src/components/builders/FreehandLineBuilder.js +1 -1
  21. package/dist/src/components/builders/LineBuilder.d.ts +1 -1
  22. package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
  23. package/dist/src/components/builders/types.d.ts +1 -1
  24. package/dist/src/geometry/Mat33.js +3 -0
  25. package/dist/src/geometry/Path.d.ts +1 -1
  26. package/dist/src/geometry/Path.js +5 -3
  27. package/dist/src/geometry/Rect2.d.ts +1 -0
  28. package/dist/src/geometry/Rect2.js +47 -9
  29. package/dist/src/{Display.d.ts → rendering/Display.d.ts} +6 -2
  30. package/dist/src/{Display.js → rendering/Display.js} +37 -4
  31. package/dist/src/rendering/caching/CacheRecord.d.ts +19 -0
  32. package/dist/src/rendering/caching/CacheRecord.js +52 -0
  33. package/dist/src/rendering/caching/CacheRecordManager.d.ts +11 -0
  34. package/dist/src/rendering/caching/CacheRecordManager.js +31 -0
  35. package/dist/src/rendering/caching/RenderingCache.d.ts +12 -0
  36. package/dist/src/rendering/caching/RenderingCache.js +42 -0
  37. package/dist/src/rendering/caching/RenderingCacheNode.d.ts +28 -0
  38. package/dist/src/rendering/caching/RenderingCacheNode.js +301 -0
  39. package/dist/src/rendering/caching/testUtils.d.ts +9 -0
  40. package/dist/src/rendering/caching/testUtils.js +20 -0
  41. package/dist/src/rendering/caching/types.d.ts +21 -0
  42. package/dist/src/rendering/caching/types.js +1 -0
  43. package/dist/src/rendering/{AbstractRenderer.d.ts → renderers/AbstractRenderer.d.ts} +19 -8
  44. package/dist/src/rendering/{AbstractRenderer.js → renderers/AbstractRenderer.js} +37 -2
  45. package/dist/src/rendering/{CanvasRenderer.d.ts → renderers/CanvasRenderer.d.ts} +14 -5
  46. package/dist/src/rendering/renderers/CanvasRenderer.js +164 -0
  47. package/dist/src/rendering/{DummyRenderer.d.ts → renderers/DummyRenderer.d.ts} +9 -5
  48. package/dist/src/rendering/{DummyRenderer.js → renderers/DummyRenderer.js} +35 -4
  49. package/dist/src/rendering/{SVGRenderer.d.ts → renderers/SVGRenderer.d.ts} +4 -3
  50. package/dist/src/rendering/{SVGRenderer.js → renderers/SVGRenderer.js} +14 -11
  51. package/dist/src/testing/createEditor.js +1 -1
  52. package/dist/src/toolbar/HTMLToolbar.js +11 -2
  53. package/dist/src/toolbar/localization.d.ts +1 -0
  54. package/dist/src/toolbar/localization.js +1 -0
  55. package/dist/src/tools/PanZoom.js +3 -0
  56. package/dist/src/tools/SelectionTool.d.ts +3 -0
  57. package/dist/src/tools/SelectionTool.js +22 -24
  58. package/dist/src/types.d.ts +2 -1
  59. package/package.json +1 -1
  60. package/src/Editor.ts +17 -8
  61. package/src/EditorImage.test.ts +2 -2
  62. package/src/EditorImage.ts +54 -42
  63. package/src/SVGLoader.ts +11 -8
  64. package/src/Viewport.ts +56 -0
  65. package/src/components/AbstractComponent.ts +6 -2
  66. package/src/components/SVGGlobalAttributesObject.ts +2 -2
  67. package/src/components/Stroke.ts +1 -1
  68. package/src/components/UnknownSVGObject.ts +2 -2
  69. package/src/components/builders/ArrowBuilder.ts +1 -1
  70. package/src/components/builders/FreehandLineBuilder.ts +2 -2
  71. package/src/components/builders/LineBuilder.ts +1 -1
  72. package/src/components/builders/RectangleBuilder.ts +1 -1
  73. package/src/components/builders/types.ts +1 -1
  74. package/src/geometry/Mat33.ts +3 -0
  75. package/src/geometry/Path.toString.test.ts +12 -2
  76. package/src/geometry/Path.ts +8 -4
  77. package/src/geometry/Rect2.test.ts +47 -8
  78. package/src/geometry/Rect2.ts +57 -9
  79. package/src/{Display.ts → rendering/Display.ts} +43 -6
  80. package/src/rendering/caching/CacheRecord.test.ts +49 -0
  81. package/src/rendering/caching/CacheRecord.ts +73 -0
  82. package/src/rendering/caching/CacheRecordManager.ts +45 -0
  83. package/src/rendering/caching/RenderingCache.test.ts +44 -0
  84. package/src/rendering/caching/RenderingCache.ts +63 -0
  85. package/src/rendering/caching/RenderingCacheNode.ts +378 -0
  86. package/src/rendering/caching/testUtils.ts +35 -0
  87. package/src/rendering/caching/types.ts +39 -0
  88. package/src/rendering/{AbstractRenderer.ts → renderers/AbstractRenderer.ts} +57 -8
  89. package/src/rendering/renderers/CanvasRenderer.ts +219 -0
  90. package/src/rendering/renderers/DummyRenderer.test.ts +43 -0
  91. package/src/rendering/{DummyRenderer.ts → renderers/DummyRenderer.ts} +50 -7
  92. package/src/rendering/{SVGRenderer.ts → renderers/SVGRenderer.ts} +17 -13
  93. package/src/testing/createEditor.ts +1 -1
  94. package/src/toolbar/HTMLToolbar.ts +13 -2
  95. package/src/toolbar/localization.ts +2 -0
  96. package/src/tools/PanZoom.ts +3 -0
  97. package/src/tools/SelectionTool.test.ts +1 -1
  98. package/src/tools/SelectionTool.ts +28 -33
  99. package/src/types.ts +10 -3
  100. package/tsconfig.json +1 -0
  101. package/dist/__mocks__/coloris.d.ts +0 -2
  102. package/dist/__mocks__/coloris.js +0 -5
  103. package/dist/src/rendering/CanvasRenderer.js +0 -108
  104. package/src/rendering/CanvasRenderer.ts +0 -141
@@ -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';
@@ -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';
@@ -234,6 +234,7 @@ export class Editor {
234
234
  // has been applied.
235
235
  asyncApplyOrUnapplyCommands(commands, apply, updateChunkSize) {
236
236
  return __awaiter(this, void 0, void 0, function* () {
237
+ this.display.setDraftMode(true);
237
238
  for (let i = 0; i < commands.length; i += updateChunkSize) {
238
239
  this.showLoadingWarning(i / commands.length);
239
240
  for (let j = i; j < commands.length && j < i + updateChunkSize; j++) {
@@ -253,6 +254,7 @@ export class Editor {
253
254
  });
254
255
  }
255
256
  }
257
+ this.display.setDraftMode(false);
256
258
  this.hideLoadingWarning();
257
259
  });
258
260
  }
@@ -280,7 +282,8 @@ export class Editor {
280
282
  const exportRectStrokeWidth = 12;
281
283
  renderer.drawRect(this.importExportViewport.visibleRect, exportRectStrokeWidth, exportRectFill);
282
284
  }
283
- this.image.render(renderer, this.viewport);
285
+ //this.image.render(renderer, this.viewport);
286
+ this.image.renderWithCache(renderer, this.display.getCache(), this.viewport);
284
287
  this.rerenderQueued = false;
285
288
  }
286
289
  drawWetInk(...path) {
@@ -342,20 +345,25 @@ export class Editor {
342
345
  loadFrom(loader) {
343
346
  return __awaiter(this, void 0, void 0, function* () {
344
347
  this.showLoadingWarning(0);
345
- const imageRect = yield loader.start((component) => {
348
+ this.display.setDraftMode(true);
349
+ yield loader.start((component) => {
346
350
  (new EditorImage.AddElementCommand(component)).apply(this);
347
351
  }, (countProcessed, totalToProcess) => {
348
- if (countProcessed % 100 === 0) {
352
+ if (countProcessed % 500 === 0) {
349
353
  this.showLoadingWarning(countProcessed / totalToProcess);
350
- this.rerender(false);
354
+ this.rerender();
351
355
  return new Promise(resolve => {
352
356
  requestAnimationFrame(() => resolve());
353
357
  });
354
358
  }
355
359
  return null;
360
+ }, (importExportRect) => {
361
+ this.setImportExportRect(importExportRect).apply(this);
362
+ this.viewport.zoomTo(importExportRect).apply(this);
356
363
  });
357
364
  this.hideLoadingWarning();
358
- this.setImportExportRect(imageRect).apply(this);
365
+ this.display.setDraftMode(false);
366
+ this.queueRerender();
359
367
  });
360
368
  }
361
369
  // 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
- private sortLeaves;
13
- render(renderer: AbstractRenderer, viewport: Viewport, minFraction?: number): void;
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 minZIndex;
34
- private maxZIndex;
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 getChildrenInRegion;
39
- getLeavesInRegion(region: Rect2, minFractionOfRegion?: number): ImageNode[];
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 {};
@@ -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.getLeavesInRegion(elem.getBBox());
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
- sortLeaves(leaves) {
33
- leaves.sort((a, b) => a.getContent().zIndex - b.getContent().zIndex);
35
+ renderWithCache(screenRenderer, cache, viewport) {
36
+ cache.render(screenRenderer, this.root, viewport);
34
37
  }
35
- render(renderer, viewport, minFraction = 0.001) {
36
- // Don't render components that are < 0.1% of the viewport.
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
- this.sortLeaves(leaves);
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.getLeavesInRegion(region);
54
- this.sortLeaves(leaves);
50
+ const leaves = this.root.getLeavesIntersectingRegion(region);
51
+ sortLeavesByZIndex(leaves);
55
52
  return leaves.map(leaf => leaf.getContent());
56
53
  }
57
54
  }
@@ -88,6 +85,7 @@ EditorImage.AddElementCommand = (_a = class {
88
85
  _element = new WeakMap(),
89
86
  _applyByFlattening = new WeakMap(),
90
87
  _a);
88
+ // TODO: Assign leaf nodes to CacheNodes. When leaf nodes are modified, the corresponding CacheNodes can be updated.
91
89
  export class ImageNode {
92
90
  constructor(parent = null) {
93
91
  this.parent = parent;
@@ -95,8 +93,13 @@ export class ImageNode {
95
93
  this.children = [];
96
94
  this.bbox = Rect2.empty;
97
95
  this.content = null;
98
- this.minZIndex = null;
99
- this.maxZIndex = null;
96
+ this.id = ImageNode.idCounter++;
97
+ }
98
+ getId() {
99
+ return this.id;
100
+ }
101
+ onContentChange() {
102
+ this.id = ImageNode.idCounter++;
100
103
  }
101
104
  getContent() {
102
105
  return this.content;
@@ -104,24 +107,30 @@ export class ImageNode {
104
107
  getParent() {
105
108
  return this.parent;
106
109
  }
107
- getChildrenInRegion(region) {
110
+ getChildrenIntersectingRegion(region) {
108
111
  return this.children.filter(child => {
109
112
  return child.getBBox().intersects(region);
110
113
  });
111
114
  }
115
+ getChildrenOrSelfIntersectingRegion(region) {
116
+ if (this.content) {
117
+ return [this];
118
+ }
119
+ return this.getChildrenIntersectingRegion(region);
120
+ }
112
121
  // Returns a list of `ImageNode`s with content (and thus no children).
113
- getLeavesInRegion(region, minFractionOfRegion = 0) {
122
+ getLeavesIntersectingRegion(region, isTooSmall) {
114
123
  const result = [];
115
124
  // Don't render if too small
116
- if (this.bbox.maxDimension / region.maxDimension <= minFractionOfRegion) {
125
+ if (isTooSmall === null || isTooSmall === void 0 ? void 0 : isTooSmall(this.bbox)) {
117
126
  return [];
118
127
  }
119
128
  if (this.content !== null && this.getBBox().intersects(region)) {
120
129
  result.push(this);
121
130
  }
122
- const children = this.getChildrenInRegion(region);
131
+ const children = this.getChildrenIntersectingRegion(region);
123
132
  for (const child of children) {
124
- result.push(...child.getLeavesInRegion(region, minFractionOfRegion));
133
+ result.push(...child.getLeavesIntersectingRegion(region, isTooSmall));
125
134
  }
126
135
  return result;
127
136
  }
@@ -138,6 +147,7 @@ export class ImageNode {
138
147
  return result;
139
148
  }
140
149
  addLeaf(leaf) {
150
+ this.onContentChange();
141
151
  if (this.content === null && this.children.length === 0) {
142
152
  this.content = leaf;
143
153
  this.recomputeBBox(true);
@@ -186,17 +196,13 @@ export class ImageNode {
186
196
  // this' ancestors bounding boxes. This also re-computes this' bounding box
187
197
  // in the z-direction (z-indicies).
188
198
  recomputeBBox(bubbleUp) {
189
- var _a, _b, _c;
199
+ var _a;
190
200
  const oldBBox = this.bbox;
191
201
  if (this.content !== null) {
192
202
  this.bbox = this.content.getBBox();
193
- this.minZIndex = this.content.zIndex;
194
- this.maxZIndex = this.content.zIndex;
195
203
  }
196
204
  else {
197
205
  this.bbox = Rect2.empty;
198
- this.minZIndex = null;
199
- this.maxZIndex = null;
200
206
  let isFirst = true;
201
207
  for (const child of this.children) {
202
208
  if (isFirst) {
@@ -206,18 +212,10 @@ export class ImageNode {
206
212
  else {
207
213
  this.bbox = this.bbox.union(child.getBBox());
208
214
  }
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
215
  }
218
216
  }
219
217
  if (bubbleUp && !oldBBox.eq(this.bbox)) {
220
- (_c = this.parent) === null || _c === void 0 ? void 0 : _c.recomputeBBox(true);
218
+ (_a = this.parent) === null || _a === void 0 ? void 0 : _a.recomputeBBox(true);
221
219
  }
222
220
  }
223
221
  rebalance() {
@@ -243,8 +241,6 @@ export class ImageNode {
243
241
  }
244
242
  // Remove this node and all of its children
245
243
  remove() {
246
- this.minZIndex = null;
247
- this.maxZIndex = null;
248
244
  if (!this.parent) {
249
245
  this.content = null;
250
246
  this.children = [];
@@ -264,4 +260,14 @@ export class ImageNode {
264
260
  this.parent = null;
265
261
  this.children = [];
266
262
  }
263
+ render(renderer, visibleRect) {
264
+ // Don't render components that are < 0.1% of the viewport.
265
+ const leaves = this.getLeavesIntersectingRegion(visibleRect, rect => renderer.isTooSmallToRender(rect));
266
+ sortLeavesByZIndex(leaves);
267
+ for (const leaf of leaves) {
268
+ // Leaves by definition have content
269
+ leaf.getContent().render(renderer, visibleRect);
270
+ }
271
+ }
267
272
  }
273
+ ImageNode.idCounter = 0;
@@ -1,11 +1,12 @@
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
4
  export default class SVGLoader implements ImageLoader {
5
5
  private source;
6
6
  private onFinish?;
7
7
  private onAddComponent;
8
8
  private onProgress;
9
+ private onDetermineExportRect;
9
10
  private processedCount;
10
11
  private totalToProcess;
11
12
  private rootViewBox;
@@ -18,6 +19,6 @@ export default class SVGLoader implements ImageLoader {
18
19
  private updateSVGAttrs;
19
20
  private visit;
20
21
  private getSourceAttrs;
21
- start(onAddComponent: ComponentAddedListener, onProgress: OnProgressListener): Promise<Rect2>;
22
+ start(onAddComponent: ComponentAddedListener, onProgress: OnProgressListener, onDetermineExportRect?: OnDetermineExportRectListener | null): Promise<void>;
22
23
  static fromString(text: string): SVGLoader;
23
24
  }
@@ -21,6 +21,7 @@ export default class SVGLoader {
21
21
  this.onFinish = onFinish;
22
22
  this.onAddComponent = null;
23
23
  this.onProgress = null;
24
+ this.onDetermineExportRect = null;
24
25
  this.processedCount = 0;
25
26
  this.totalToProcess = 0;
26
27
  }
@@ -100,6 +101,7 @@ export default class SVGLoader {
100
101
  (_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, component);
101
102
  }
102
103
  updateViewBox(node) {
104
+ var _a;
103
105
  const viewBoxAttr = node.getAttribute('viewBox');
104
106
  if (this.rootViewBox || !viewBoxAttr) {
105
107
  return;
@@ -113,6 +115,7 @@ export default class SVGLoader {
113
115
  return;
114
116
  }
115
117
  this.rootViewBox = new Rect2(x, y, width, height);
118
+ (_a = this.onDetermineExportRect) === null || _a === void 0 ? void 0 : _a.call(this, this.rootViewBox);
116
119
  }
117
120
  updateSVGAttrs(node) {
118
121
  var _a;
@@ -154,23 +157,22 @@ export default class SVGLoader {
154
157
  return [attr, node.getAttribute(attr)];
155
158
  });
156
159
  }
157
- start(onAddComponent, onProgress) {
158
- var _a;
160
+ start(onAddComponent, onProgress, onDetermineExportRect = null) {
161
+ var _a, _b;
159
162
  return __awaiter(this, void 0, void 0, function* () {
160
163
  this.onAddComponent = onAddComponent;
161
164
  this.onProgress = onProgress;
165
+ this.onDetermineExportRect = onDetermineExportRect;
162
166
  // Estimate the number of tags to process.
163
167
  this.totalToProcess = this.source.childElementCount;
164
168
  this.processedCount = 0;
165
169
  this.rootViewBox = null;
166
170
  yield this.visit(this.source);
167
171
  const viewBox = this.rootViewBox;
168
- let result = defaultSVGViewRect;
169
- if (viewBox) {
170
- result = Rect2.of(viewBox);
172
+ if (!viewBox) {
173
+ (_a = this.onDetermineExportRect) === null || _a === void 0 ? void 0 : _a.call(this, defaultSVGViewRect);
171
174
  }
172
- (_a = this.onFinish) === null || _a === void 0 ? void 0 : _a.call(this);
173
- return result;
175
+ (_b = this.onFinish) === null || _b === void 0 ? void 0 : _b.call(this);
174
176
  });
175
177
  }
176
178
  // TODO: Handling unsafe data! Tripple-check that this is secure!
@@ -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;
@@ -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,41 @@ 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
+ // Try to move the selection within the center 2/3rds of the viewport.
91
+ const recomputeTargetRect = () => {
92
+ // transform transforms objects on the canvas. As such, we need to invert it
93
+ // to transform the viewport.
94
+ const visibleRect = this.visibleRect.transformedBoundingBox(transform.inverse());
95
+ return visibleRect.transformedBoundingBox(Mat33.scaling2D(2 / 3, visibleRect.center));
96
+ };
97
+ let targetRect = recomputeTargetRect();
98
+ const largerThanTarget = targetRect.w < toMakeVisible.w || targetRect.h < toMakeVisible.h;
99
+ // Ensure that toMakeVisible is at least 1/8th of the visible region.
100
+ const muchSmallerThanTarget = toMakeVisible.maxDimension / targetRect.maxDimension < 0.125;
101
+ if (largerThanTarget || muchSmallerThanTarget) {
102
+ // If larger than the target, ensure that the longest axis is visible.
103
+ // If smaller, shrink the visible rectangle as much as possible
104
+ const multiplier = (largerThanTarget ? Math.max : Math.min)(toMakeVisible.w / targetRect.w, toMakeVisible.h / targetRect.h);
105
+ const visibleRectTransform = Mat33.scaling2D(multiplier, targetRect.topLeft);
106
+ const viewportContentTransform = visibleRectTransform.inverse();
107
+ transform = transform.rightMul(viewportContentTransform);
108
+ }
109
+ targetRect = recomputeTargetRect();
110
+ // Ensure that the center of the region is visible
111
+ if (!targetRect.containsRect(toMakeVisible)) {
112
+ // target position - current position
113
+ const translation = toMakeVisible.center.minus(targetRect.center);
114
+ const visibleRectTransform = Mat33.translation(translation);
115
+ const viewportContentTransform = visibleRectTransform.inverse();
116
+ transform = transform.rightMul(viewportContentTransform);
117
+ }
118
+ return new Viewport.ViewportTransform(transform);
119
+ }
79
120
  }
80
121
  // Command that translates/scales the viewport.
81
122
  Viewport.ViewportTransform = (_a = class {
@@ -2,14 +2,15 @@ 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
7
  export default abstract class AbstractComponent {
8
8
  protected lastChangedTime: number;
9
9
  protected abstract contentBBox: Rect2;
10
- zIndex: number;
10
+ private zIndex;
11
11
  private static zIndexCounter;
12
12
  protected constructor();
13
+ getZIndex(): number;
13
14
  getBBox(): Rect2;
14
15
  abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
15
16
  abstract intersects(lineSegment: LineSegment2): boolean;
@@ -4,6 +4,9 @@ export default class AbstractComponent {
4
4
  this.lastChangedTime = (new Date()).getTime();
5
5
  this.zIndex = AbstractComponent.zIndexCounter++;
6
6
  }
7
+ getZIndex() {
8
+ return this.zIndex;
9
+ }
7
10
  getBBox() {
8
11
  return this.contentBBox;
9
12
  }
@@ -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 {
@@ -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 UnknownSVGObject 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
  export default class UnknownSVGObject extends AbstractComponent {
5
5
  constructor(svgObject) {
@@ -1,5 +1,5 @@
1
1
  import Rect2 from '../../geometry/Rect2';
2
- import AbstractRenderer from '../../rendering/AbstractRenderer';
2
+ import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
3
3
  import { StrokeDataPoint } from '../../types';
4
4
  import AbstractComponent from '../AbstractComponent';
5
5
  import { ComponentBuilder, ComponentBuilderFactory } from './types';
@@ -1,4 +1,4 @@
1
- import AbstractRenderer from '../../rendering/AbstractRenderer';
1
+ import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
2
2
  import Rect2 from '../../geometry/Rect2';
3
3
  import Stroke from '../Stroke';
4
4
  import { StrokeDataPoint } from '../../types';
@@ -160,7 +160,7 @@ export default class FreehandLineBuilder {
160
160
  const upperBoundary = computeBoundaryCurve(1, halfVec);
161
161
  const lowerBoundary = computeBoundaryCurve(-1, halfVec);
162
162
  // If the boundaries have two intersections, increasing the half vector's length could fix this.
163
- if (upperBoundary.intersects(lowerBoundary).length === 2) {
163
+ if (upperBoundary.intersects(lowerBoundary).length > 0) {
164
164
  halfVec = halfVec.times(2);
165
165
  }
166
166
  const pathCommands = [
@@ -1,5 +1,5 @@
1
1
  import Rect2 from '../../geometry/Rect2';
2
- import AbstractRenderer from '../../rendering/AbstractRenderer';
2
+ import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
3
3
  import { StrokeDataPoint } from '../../types';
4
4
  import AbstractComponent from '../AbstractComponent';
5
5
  import { ComponentBuilder, ComponentBuilderFactory } from './types';
@@ -1,5 +1,5 @@
1
1
  import Rect2 from '../../geometry/Rect2';
2
- import AbstractRenderer from '../../rendering/AbstractRenderer';
2
+ import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
3
3
  import { StrokeDataPoint } from '../../types';
4
4
  import AbstractComponent from '../AbstractComponent';
5
5
  import { ComponentBuilder, ComponentBuilderFactory } from './types';
@@ -1,5 +1,5 @@
1
1
  import Rect2 from '../../geometry/Rect2';
2
- import AbstractRenderer from '../../rendering/AbstractRenderer';
2
+ import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
3
3
  import { StrokeDataPoint } from '../../types';
4
4
  import Viewport from '../../Viewport';
5
5
  import AbstractComponent from '../AbstractComponent';
@@ -4,6 +4,9 @@ import Vec3 from './Vec3';
4
4
  // a two-dimensional affine transformation. (An affine transformation scales/rotates/shears
5
5
  // **and** translates while a linear transformation just scales/rotates/shears).
6
6
  export default class Mat33 {
7
+ // ⎡ a1 a2 a3 ⎤
8
+ // ⎢ b1 b2 b3 ⎥
9
+ // ⎣ c1 c2 c3 ⎦
7
10
  constructor(a1, a2, a3, b1, b2, b3, c1, c2, c3) {
8
11
  this.a1 = a1;
9
12
  this.a2 = a2;
@@ -1,5 +1,5 @@
1
1
  import { Bezier } from 'bezier-js';
2
- import { RenderingStyle, RenderablePathSpec } from '../rendering/AbstractRenderer';
2
+ import { RenderingStyle, RenderablePathSpec } from '../rendering/renderers/AbstractRenderer';
3
3
  import LineSegment2 from './LineSegment2';
4
4
  import Mat33 from './Mat33';
5
5
  import Rect2 from './Rect2';
@@ -208,8 +208,8 @@ export default class Path {
208
208
  const toRoundedString = (num) => {
209
209
  // Try to remove rounding errors. If the number ends in at least three/four zeroes
210
210
  // (or nines) just one or two digits, it's probably a rounding error.
211
- const fixRoundingUpExp = /^([-]?\d*\.?\d*[1-9.])0{4,}\d$/;
212
- const hasRoundingDownExp = /^([-]?)(\d*)\.(\d*9{4,}\d)$/;
211
+ const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d$/;
212
+ const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,}\d)$/;
213
213
  let text = num.toString();
214
214
  if (text.indexOf('.') === -1) {
215
215
  return text;
@@ -230,7 +230,9 @@ export default class Path {
230
230
  text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
231
231
  }
232
232
  text = text.replace(fixRoundingUpExp, '$1');
233
- // Remove trailing period (if it exists)
233
+ // Remove trailing zeroes
234
+ text = text.replace(/([.][^0]*)0+$/, '$1');
235
+ // Remove trailing period
234
236
  return text.replace(/[.]$/, '');
235
237
  };
236
238
  const addCommand = (command, ...points) => {
@@ -27,6 +27,7 @@ export default class Rect2 {
27
27
  intersects(other: Rect2): boolean;
28
28
  intersection(other: Rect2): Rect2 | null;
29
29
  union(other: Rect2): Rect2;
30
+ divideIntoGrid(columns: number, rows: number): Rect2[];
30
31
  grownToPoint(point: Point2, margin?: number): Rect2;
31
32
  grownBy(margin: number): Rect2;
32
33
  get corners(): Point2[];