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.
Files changed (122) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/Editor.d.ts +2 -2
  4. package/dist/src/Editor.js +17 -7
  5. package/dist/src/EditorImage.d.ts +15 -7
  6. package/dist/src/EditorImage.js +46 -37
  7. package/dist/src/Pointer.d.ts +3 -2
  8. package/dist/src/Pointer.js +12 -3
  9. package/dist/src/SVGLoader.d.ts +6 -2
  10. package/dist/src/SVGLoader.js +20 -8
  11. package/dist/src/Viewport.d.ts +4 -0
  12. package/dist/src/Viewport.js +51 -0
  13. package/dist/src/components/AbstractComponent.d.ts +9 -2
  14. package/dist/src/components/AbstractComponent.js +14 -0
  15. package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -1
  16. package/dist/src/components/SVGGlobalAttributesObject.js +1 -1
  17. package/dist/src/components/Stroke.d.ts +1 -1
  18. package/dist/src/components/Stroke.js +1 -1
  19. package/dist/src/components/UnknownSVGObject.d.ts +1 -1
  20. package/dist/src/components/UnknownSVGObject.js +1 -1
  21. package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
  22. package/dist/src/components/builders/FreehandLineBuilder.d.ts +1 -1
  23. package/dist/src/components/builders/FreehandLineBuilder.js +1 -1
  24. package/dist/src/components/builders/LineBuilder.d.ts +1 -1
  25. package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
  26. package/dist/src/components/builders/types.d.ts +1 -1
  27. package/dist/src/geometry/Mat33.js +3 -0
  28. package/dist/src/geometry/Path.d.ts +1 -1
  29. package/dist/src/geometry/Path.js +102 -69
  30. package/dist/src/geometry/Rect2.d.ts +1 -0
  31. package/dist/src/geometry/Rect2.js +47 -9
  32. package/dist/src/{Display.d.ts → rendering/Display.d.ts} +5 -2
  33. package/dist/src/{Display.js → rendering/Display.js} +34 -4
  34. package/dist/src/rendering/caching/CacheRecord.d.ts +19 -0
  35. package/dist/src/rendering/caching/CacheRecord.js +52 -0
  36. package/dist/src/rendering/caching/CacheRecordManager.d.ts +11 -0
  37. package/dist/src/rendering/caching/CacheRecordManager.js +31 -0
  38. package/dist/src/rendering/caching/RenderingCache.d.ts +12 -0
  39. package/dist/src/rendering/caching/RenderingCache.js +42 -0
  40. package/dist/src/rendering/caching/RenderingCacheNode.d.ts +28 -0
  41. package/dist/src/rendering/caching/RenderingCacheNode.js +301 -0
  42. package/dist/src/rendering/caching/testUtils.d.ts +9 -0
  43. package/dist/src/rendering/caching/testUtils.js +20 -0
  44. package/dist/src/rendering/caching/types.d.ts +21 -0
  45. package/dist/src/rendering/caching/types.js +1 -0
  46. package/dist/src/rendering/{AbstractRenderer.d.ts → renderers/AbstractRenderer.d.ts} +20 -9
  47. package/dist/src/rendering/{AbstractRenderer.js → renderers/AbstractRenderer.js} +37 -3
  48. package/dist/src/rendering/{CanvasRenderer.d.ts → renderers/CanvasRenderer.d.ts} +10 -5
  49. package/dist/src/rendering/{CanvasRenderer.js → renderers/CanvasRenderer.js} +60 -20
  50. package/dist/src/rendering/{DummyRenderer.d.ts → renderers/DummyRenderer.d.ts} +9 -5
  51. package/dist/src/rendering/{DummyRenderer.js → renderers/DummyRenderer.js} +35 -4
  52. package/dist/src/rendering/{SVGRenderer.d.ts → renderers/SVGRenderer.d.ts} +7 -5
  53. package/dist/src/rendering/{SVGRenderer.js → renderers/SVGRenderer.js} +35 -18
  54. package/dist/src/testing/createEditor.js +1 -1
  55. package/dist/src/toolbar/HTMLToolbar.d.ts +2 -1
  56. package/dist/src/toolbar/HTMLToolbar.js +165 -154
  57. package/dist/src/toolbar/icons.d.ts +10 -0
  58. package/dist/src/toolbar/icons.js +180 -0
  59. package/dist/src/toolbar/localization.d.ts +4 -1
  60. package/dist/src/toolbar/localization.js +4 -1
  61. package/dist/src/toolbar/types.d.ts +4 -0
  62. package/dist/src/tools/PanZoom.d.ts +9 -6
  63. package/dist/src/tools/PanZoom.js +30 -21
  64. package/dist/src/tools/Pen.js +8 -3
  65. package/dist/src/tools/SelectionTool.js +9 -24
  66. package/dist/src/tools/ToolController.d.ts +5 -6
  67. package/dist/src/tools/ToolController.js +8 -10
  68. package/dist/src/tools/localization.d.ts +1 -0
  69. package/dist/src/tools/localization.js +1 -0
  70. package/dist/src/types.d.ts +2 -1
  71. package/package.json +1 -1
  72. package/src/Editor.ts +19 -8
  73. package/src/EditorImage.test.ts +2 -2
  74. package/src/EditorImage.ts +58 -42
  75. package/src/Pointer.ts +13 -4
  76. package/src/SVGLoader.ts +36 -10
  77. package/src/Viewport.ts +68 -0
  78. package/src/components/AbstractComponent.ts +21 -2
  79. package/src/components/SVGGlobalAttributesObject.ts +2 -2
  80. package/src/components/Stroke.ts +2 -2
  81. package/src/components/UnknownSVGObject.ts +2 -2
  82. package/src/components/builders/ArrowBuilder.ts +1 -1
  83. package/src/components/builders/FreehandLineBuilder.ts +2 -2
  84. package/src/components/builders/LineBuilder.ts +1 -1
  85. package/src/components/builders/RectangleBuilder.ts +1 -1
  86. package/src/components/builders/types.ts +1 -1
  87. package/src/geometry/Mat33.ts +3 -0
  88. package/src/geometry/Path.fromString.test.ts +94 -4
  89. package/src/geometry/Path.toString.test.ts +12 -2
  90. package/src/geometry/Path.ts +107 -71
  91. package/src/geometry/Rect2.test.ts +47 -8
  92. package/src/geometry/Rect2.ts +57 -9
  93. package/src/{Display.ts → rendering/Display.ts} +39 -6
  94. package/src/rendering/caching/CacheRecord.test.ts +49 -0
  95. package/src/rendering/caching/CacheRecord.ts +73 -0
  96. package/src/rendering/caching/CacheRecordManager.ts +45 -0
  97. package/src/rendering/caching/RenderingCache.test.ts +44 -0
  98. package/src/rendering/caching/RenderingCache.ts +63 -0
  99. package/src/rendering/caching/RenderingCacheNode.ts +378 -0
  100. package/src/rendering/caching/testUtils.ts +35 -0
  101. package/src/rendering/caching/types.ts +39 -0
  102. package/src/rendering/{AbstractRenderer.ts → renderers/AbstractRenderer.ts} +57 -9
  103. package/src/rendering/{CanvasRenderer.ts → renderers/CanvasRenderer.ts} +74 -25
  104. package/src/rendering/renderers/DummyRenderer.test.ts +43 -0
  105. package/src/rendering/{DummyRenderer.ts → renderers/DummyRenderer.ts} +50 -7
  106. package/src/rendering/{SVGRenderer.ts → renderers/SVGRenderer.ts} +39 -23
  107. package/src/testing/createEditor.ts +1 -1
  108. package/src/toolbar/HTMLToolbar.ts +199 -170
  109. package/src/toolbar/icons.ts +203 -0
  110. package/src/toolbar/localization.ts +9 -2
  111. package/src/toolbar/toolbar.css +21 -8
  112. package/src/toolbar/types.ts +5 -0
  113. package/src/tools/PanZoom.ts +37 -27
  114. package/src/tools/Pen.ts +7 -3
  115. package/src/tools/SelectionTool.test.ts +1 -1
  116. package/src/tools/SelectionTool.ts +12 -33
  117. package/src/tools/ToolController.ts +3 -5
  118. package/src/tools/localization.ts +2 -0
  119. package/src/types.ts +10 -3
  120. package/tsconfig.json +1 -0
  121. package/dist/__mocks__/coloris.d.ts +0 -2
  122. package/dist/__mocks__/coloris.js +0 -5
@@ -1,8 +1,9 @@
1
- import Color4 from '../Color4';
2
- import Rect2 from '../geometry/Rect2';
3
- import { Point2, Vec2 } from '../geometry/Vec2';
4
- import Vec3 from '../geometry/Vec3';
5
- import Viewport from '../Viewport';
1
+ import Color4 from '../../Color4';
2
+ import Mat33 from '../../geometry/Mat33';
3
+ import Rect2 from '../../geometry/Rect2';
4
+ import { Point2, Vec2 } from '../../geometry/Vec2';
5
+ import Vec3 from '../../geometry/Vec3';
6
+ import Viewport from '../../Viewport';
6
7
  import AbstractRenderer, { RenderablePathSpec, RenderingStyle } from './AbstractRenderer';
7
8
 
8
9
  export default class CanvasRenderer extends AbstractRenderer {
@@ -25,6 +26,30 @@ export default class CanvasRenderer extends AbstractRenderer {
25
26
  this.setDraftMode(false);
26
27
  }
27
28
 
29
+ public canRenderFromWithoutDataLoss(other: AbstractRenderer) {
30
+ return other instanceof CanvasRenderer;
31
+ }
32
+
33
+ public renderFromOtherOfSameType(transformBy: Mat33, other: AbstractRenderer): void {
34
+ if (!(other instanceof CanvasRenderer)) {
35
+ throw new Error(`${other} cannot be rendered onto ${this}`);
36
+ }
37
+ transformBy = this.getCanvasToScreenTransform().rightMul(transformBy);
38
+ this.ctx.save();
39
+ // From MDN, transform(a,b,c,d,e,f)
40
+ // takes input such that
41
+ // ⎡ a c e ⎤
42
+ // ⎢ b d f ⎥ transforms content drawn to [ctx].
43
+ // ⎣ 0 0 1 ⎦
44
+ this.ctx.transform(
45
+ transformBy.a1, transformBy.b1, // a, b
46
+ transformBy.a2, transformBy.b2, // c, d
47
+ transformBy.a3, transformBy.b3, // e, f
48
+ );
49
+ this.ctx.drawImage(other.ctx.canvas, 0, 0);
50
+ this.ctx.restore();
51
+ }
52
+
28
53
  // Set parameters for lower/higher quality rendering
29
54
  public setDraftMode(draftMode: boolean) {
30
55
  if (draftMode) {
@@ -50,7 +75,7 @@ export default class CanvasRenderer extends AbstractRenderer {
50
75
  }
51
76
 
52
77
  protected beginPath(startPoint: Point2) {
53
- startPoint = this.viewport.canvasToScreen(startPoint);
78
+ startPoint = this.canvasToScreen(startPoint);
54
79
 
55
80
  this.ctx.beginPath();
56
81
  this.ctx.moveTo(startPoint.x, startPoint.y);
@@ -62,7 +87,7 @@ export default class CanvasRenderer extends AbstractRenderer {
62
87
 
63
88
  if (style.stroke) {
64
89
  this.ctx.strokeStyle = style.stroke.color.toHexString();
65
- this.ctx.lineWidth = this.viewport.getScaleFactor() * style.stroke.width;
90
+ this.ctx.lineWidth = this.getSizeOfCanvasPixelOnScreen() * style.stroke.width;
66
91
  this.ctx.stroke();
67
92
  }
68
93
 
@@ -70,19 +95,19 @@ export default class CanvasRenderer extends AbstractRenderer {
70
95
  }
71
96
 
72
97
  protected lineTo(point: Point2) {
73
- point = this.viewport.canvasToScreen(point);
98
+ point = this.canvasToScreen(point);
74
99
  this.ctx.lineTo(point.x, point.y);
75
100
  }
76
101
 
77
102
  protected moveTo(point: Point2) {
78
- point = this.viewport.canvasToScreen(point);
103
+ point = this.canvasToScreen(point);
79
104
  this.ctx.moveTo(point.x, point.y);
80
105
  }
81
106
 
82
107
  protected traceCubicBezierCurve(p1: Point2, p2: Point2, p3: Point2) {
83
- p1 = this.viewport.canvasToScreen(p1);
84
- p2 = this.viewport.canvasToScreen(p2);
85
- p3 = this.viewport.canvasToScreen(p3);
108
+ p1 = this.canvasToScreen(p1);
109
+ p2 = this.canvasToScreen(p2);
110
+ p3 = this.canvasToScreen(p3);
86
111
 
87
112
  // Approximate the curve if small enough.
88
113
  const delta1 = p2.minus(p1);
@@ -96,8 +121,8 @@ export default class CanvasRenderer extends AbstractRenderer {
96
121
  }
97
122
 
98
123
  protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3) {
99
- controlPoint = this.viewport.canvasToScreen(controlPoint);
100
- endPoint = this.viewport.canvasToScreen(endPoint);
124
+ controlPoint = this.canvasToScreen(controlPoint);
125
+ endPoint = this.canvasToScreen(endPoint);
101
126
 
102
127
  // Approximate the curve with a line if small enough
103
128
  const delta = controlPoint.minus(endPoint);
@@ -118,23 +143,35 @@ export default class CanvasRenderer extends AbstractRenderer {
118
143
  super.drawPath(path);
119
144
  }
120
145
 
121
- public startObject(boundingBox: Rect2) {
122
- // Should we ignore all objects within this object's bbox?
123
- const diagonal = this.viewport.canvasToScreenTransform.transformVec3(boundingBox.size);
124
-
125
- const bothDimenMinSize = this.minRenderSizeBothDimens;
126
- const bothTooSmall = Math.abs(diagonal.x) < bothDimenMinSize && Math.abs(diagonal.y) < bothDimenMinSize;
127
- const anyDimenMinSize = this.minRenderSizeAnyDimen;
128
- const anyTooSmall = Math.abs(diagonal.x) < anyDimenMinSize || Math.abs(diagonal.y) < anyDimenMinSize;
129
-
130
- if (bothTooSmall || anyTooSmall) {
146
+ private clipLevels: number[] = [];
147
+ public startObject(boundingBox: Rect2, clip: boolean) {
148
+ if (this.isTooSmallToRender(boundingBox)) {
131
149
  this.ignoreObjectsAboveLevel = this.getNestingLevel();
132
150
  this.ignoringObject = true;
133
151
  }
134
152
 
135
153
  super.startObject(boundingBox);
154
+
155
+ if (!this.ignoringObject && clip) {
156
+ this.clipLevels.push(this.objectLevel);
157
+ this.ctx.save();
158
+ this.ctx.beginPath();
159
+ for (const corner of boundingBox.corners) {
160
+ const screenCorner = this.canvasToScreen(corner);
161
+ this.ctx.lineTo(screenCorner.x, screenCorner.y);
162
+ }
163
+ this.ctx.clip();
164
+ }
136
165
  }
166
+
137
167
  public endObject() {
168
+ if (!this.ignoringObject && this.clipLevels.length > 0) {
169
+ if (this.clipLevels[this.clipLevels.length - 1] === this.objectLevel) {
170
+ this.ctx.restore();
171
+ this.clipLevels.pop();
172
+ }
173
+ }
174
+
138
175
  super.endObject();
139
176
 
140
177
  // If exiting an object with a too-small-to-draw bounding box,
@@ -148,7 +185,7 @@ export default class CanvasRenderer extends AbstractRenderer {
148
185
  const pointRadius = 10;
149
186
 
150
187
  for (let i = 0; i < points.length; i++) {
151
- const point = this.viewport.canvasToScreen(points[i]);
188
+ const point = this.canvasToScreen(points[i]);
152
189
 
153
190
  this.ctx.beginPath();
154
191
  this.ctx.arc(point.x, point.y, pointRadius, 0, Math.PI * 2);
@@ -167,4 +204,16 @@ export default class CanvasRenderer extends AbstractRenderer {
167
204
  this.ctx.fillText(`${i}`, point.x, point.y, pointRadius * 2);
168
205
  }
169
206
  }
207
+
208
+ public isTooSmallToRender(rect: Rect2): boolean {
209
+ // Should we ignore all objects within this object's bbox?
210
+ const diagonal = this.getCanvasToScreenTransform().transformVec3(rect.size);
211
+
212
+ const bothDimenMinSize = this.minRenderSizeBothDimens;
213
+ const bothTooSmall = Math.abs(diagonal.x) < bothDimenMinSize && Math.abs(diagonal.y) < bothDimenMinSize;
214
+ const anyDimenMinSize = this.minRenderSizeAnyDimen;
215
+ const anyTooSmall = Math.abs(diagonal.x) < anyDimenMinSize || Math.abs(diagonal.y) < anyDimenMinSize;
216
+
217
+ return bothTooSmall || anyTooSmall;
218
+ }
170
219
  }
@@ -0,0 +1,43 @@
1
+
2
+ import EventDispatcher from '../../EventDispatcher';
3
+ import Mat33 from '../../geometry/Mat33';
4
+ import { Vec2 } from '../../geometry/Vec2';
5
+ import Viewport from '../../Viewport';
6
+ import DummyRenderer from './DummyRenderer';
7
+
8
+ const makeRenderer = (): [DummyRenderer, Viewport] => {
9
+ const viewport = new Viewport(new EventDispatcher());
10
+ return [ new DummyRenderer(viewport), viewport ];
11
+ };
12
+
13
+ describe('DummyRenderer', () => {
14
+ it('should correctly calculate the size of a pixel on the screen', () => {
15
+ const [ renderer, viewport ] = makeRenderer();
16
+ viewport.updateScreenSize(Vec2.of(100, 100));
17
+ viewport.resetTransform(Mat33.identity);
18
+
19
+ expect(1/viewport.getScaleFactor()).toBe(1);
20
+ expect(renderer.getSizeOfCanvasPixelOnScreen()).toBe(1);
21
+
22
+ // Updating the translation matrix shouldn't affect the size of a pixel on the
23
+ // screen.
24
+ renderer.setTransform(Mat33.translation(Vec2.of(-1, -2)));
25
+ expect(renderer.getSizeOfCanvasPixelOnScreen()).toBe(1);
26
+ viewport.resetTransform(Mat33.translation(Vec2.of(3, 4)));
27
+ expect(renderer.getSizeOfCanvasPixelOnScreen()).toBe(1);
28
+
29
+ // Scale objects by a factor of 2 when drawing
30
+ renderer.setTransform(Mat33.scaling2D(2));
31
+ expect(renderer.getSizeOfCanvasPixelOnScreen()).toBe(2);
32
+ viewport.resetTransform(Mat33.scaling2D(0.5));
33
+
34
+ // When a renderer transform is set, **only** the renderer transform should be used.
35
+ expect(renderer.getSizeOfCanvasPixelOnScreen()).toBe(2);
36
+ renderer.setTransform(null);
37
+ expect(renderer.getSizeOfCanvasPixelOnScreen()).toBe(0.5);
38
+
39
+ // Rotating should not affect the size of a pixel
40
+ renderer.setTransform(Mat33.zRotation(Math.PI / 4).rightMul(Mat33.scaling2D(4)));
41
+ expect(renderer.getSizeOfCanvasPixelOnScreen()).toBe(4);
42
+ });
43
+ });
@@ -1,9 +1,10 @@
1
1
  // Renderer that outputs nothing. Useful for automated tests.
2
2
 
3
- import Rect2 from '../geometry/Rect2';
4
- import { Point2, Vec2 } from '../geometry/Vec2';
5
- import Vec3 from '../geometry/Vec3';
6
- import Viewport from '../Viewport';
3
+ import Mat33 from '../../geometry/Mat33';
4
+ import Rect2 from '../../geometry/Rect2';
5
+ import { Point2, Vec2 } from '../../geometry/Vec2';
6
+ import Vec3 from '../../geometry/Vec3';
7
+ import Viewport from '../../Viewport';
7
8
  import AbstractRenderer, { RenderingStyle } from './AbstractRenderer';
8
9
 
9
10
  export default class DummyRenderer extends AbstractRenderer {
@@ -22,8 +23,17 @@ export default class DummyRenderer extends AbstractRenderer {
22
23
  }
23
24
 
24
25
  public displaySize(): Vec2 {
25
- // Return a dummy
26
- return Vec2.of(640, 480);
26
+ // Do we have a stored viewport size?
27
+ const viewportSize = this.getViewport().getResolution();
28
+
29
+ // Don't use a 0x0 viewport — DummyRenderer is often used
30
+ // for tests that run without a display, so pretend we have a
31
+ // reasonable-sized display.
32
+ if (viewportSize.x === 0 || viewportSize.y === 0) {
33
+ return Vec2.of(640, 480);
34
+ }
35
+
36
+ return viewportSize;
27
37
  }
28
38
 
29
39
  public clear() {
@@ -47,18 +57,29 @@ export default class DummyRenderer extends AbstractRenderer {
47
57
  this.lastFillStyle = style;
48
58
  }
49
59
  protected lineTo(point: Vec3) {
60
+ point = this.canvasToScreen(point);
61
+
50
62
  this.lastPoint = point;
51
63
  this.pointBuffer.push(point);
52
64
  }
53
65
  protected moveTo(point: Point2) {
66
+ point = this.canvasToScreen(point);
67
+
54
68
  this.lastPoint = point;
55
69
  this.pointBuffer.push(point);
56
70
  }
57
71
  protected traceCubicBezierCurve(p1: Vec3, p2: Vec3, p3: Vec3) {
72
+ p1 = this.canvasToScreen(p1);
73
+ p2 = this.canvasToScreen(p2);
74
+ p3 = this.canvasToScreen(p3);
75
+
58
76
  this.lastPoint = p3;
59
77
  this.pointBuffer.push(p1, p2, p3);
60
78
  }
61
79
  protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3) {
80
+ controlPoint = this.canvasToScreen(controlPoint);
81
+ endPoint = this.canvasToScreen(endPoint);
82
+
62
83
  this.lastPoint = endPoint;
63
84
  this.pointBuffer.push(controlPoint, endPoint);
64
85
  }
@@ -67,7 +88,7 @@ export default class DummyRenderer extends AbstractRenderer {
67
88
  // As such, it is unlikely to be the target of automated tests.
68
89
  }
69
90
 
70
- public startObject(boundingBox: Rect2) {
91
+ public startObject(boundingBox: Rect2, _clip: boolean) {
71
92
  super.startObject(boundingBox);
72
93
 
73
94
  this.objectNestingLevel += 1;
@@ -77,4 +98,26 @@ export default class DummyRenderer extends AbstractRenderer {
77
98
 
78
99
  this.objectNestingLevel -= 1;
79
100
  }
101
+
102
+ public isTooSmallToRender(_rect: Rect2): boolean {
103
+ return false;
104
+ }
105
+
106
+
107
+ public canRenderFromWithoutDataLoss(other: AbstractRenderer) {
108
+ return other instanceof DummyRenderer;
109
+ }
110
+
111
+ public renderFromOtherOfSameType(transform: Mat33, other: AbstractRenderer): void {
112
+ if (!(other instanceof DummyRenderer)) {
113
+ throw new Error(`${other} cannot be rendered onto ${this}`);
114
+ }
115
+
116
+ this.renderedPathCount += other.renderedPathCount;
117
+ this.lastFillStyle = other.lastFillStyle;
118
+ this.lastPoint = other.lastPoint;
119
+ this.pointBuffer.push(...other.pointBuffer.map(point => {
120
+ return transform.transformVec2(point);
121
+ }));
122
+ }
80
123
  }
@@ -1,8 +1,10 @@
1
1
 
2
- import Path, { PathCommand, PathCommandType } from '../geometry/Path';
3
- import Rect2 from '../geometry/Rect2';
4
- import { Point2, Vec2 } from '../geometry/Vec2';
5
- import Viewport from '../Viewport';
2
+ import { LoadSaveDataTable } from '../../components/AbstractComponent';
3
+ import Path, { PathCommand, PathCommandType } from '../../geometry/Path';
4
+ import Rect2 from '../../geometry/Rect2';
5
+ import { Point2, Vec2 } from '../../geometry/Vec2';
6
+ import { svgAttributesDataKey, SVGLoaderUnknownAttribute } from '../../SVGLoader';
7
+ import Viewport from '../../Viewport';
6
8
  import AbstractRenderer, { RenderingStyle } from './AbstractRenderer';
7
9
 
8
10
  const svgNameSpace = 'http://www.w3.org/2000/svg';
@@ -13,8 +15,8 @@ export default class SVGRenderer extends AbstractRenderer {
13
15
  private lastPathStyle: RenderingStyle|null;
14
16
  private lastPath: PathCommand[]|null;
15
17
  private lastPathStart: Point2|null;
18
+ private objectElems: SVGElement[]|null = null;
16
19
 
17
- private mainGroup: SVGGElement;
18
20
  private overwrittenAttrs: Record<string, string|null> = {};
19
21
 
20
22
  public constructor(private elem: SVGSVGElement, viewport: Viewport) {
@@ -41,8 +43,6 @@ export default class SVGRenderer extends AbstractRenderer {
41
43
  }
42
44
 
43
45
  public clear() {
44
- this.mainGroup = document.createElementNS(svgNameSpace, 'g');
45
-
46
46
  // Restore all alltributes
47
47
  for (const attrName in this.overwrittenAttrs) {
48
48
  const value = this.overwrittenAttrs[attrName];
@@ -54,14 +54,11 @@ export default class SVGRenderer extends AbstractRenderer {
54
54
  }
55
55
  }
56
56
  this.overwrittenAttrs = {};
57
-
58
- // Remove all children
59
- this.elem.replaceChildren(this.mainGroup);
60
57
  }
61
58
 
62
59
  protected beginPath(startPoint: Point2) {
63
60
  this.currentPath = [];
64
- this.pathStart = this.viewport.canvasToScreen(startPoint);
61
+ this.pathStart = this.canvasToScreen(startPoint);
65
62
  this.lastPathStart ??= this.pathStart;
66
63
  }
67
64
 
@@ -106,7 +103,8 @@ export default class SVGRenderer extends AbstractRenderer {
106
103
  pathElem.setAttribute('stroke-width', style.stroke.width.toString());
107
104
  }
108
105
 
109
- this.mainGroup.appendChild(pathElem);
106
+ this.elem.appendChild(pathElem);
107
+ this.objectElems?.push(pathElem);
110
108
  }
111
109
 
112
110
  public startObject(boundingBox: Rect2) {
@@ -116,17 +114,31 @@ export default class SVGRenderer extends AbstractRenderer {
116
114
  this.lastPath = null;
117
115
  this.lastPathStart = null;
118
116
  this.lastPathStyle = null;
117
+ this.objectElems = [];
119
118
  }
120
119
 
121
- public endObject() {
122
- super.endObject();
120
+ public endObject(loaderData?: LoadSaveDataTable) {
121
+ super.endObject(loaderData);
123
122
 
124
123
  // Don't extend paths across objects
125
124
  this.addPathToSVG();
125
+
126
+ if (loaderData) {
127
+ // Restore any attributes unsupported by the app.
128
+ for (const elem of this.objectElems ?? []) {
129
+ const attrs = loaderData[svgAttributesDataKey] as SVGLoaderUnknownAttribute[]|undefined;
130
+
131
+ if (attrs) {
132
+ for (const [ attr, value ] of attrs) {
133
+ elem.setAttribute(attr, value);
134
+ }
135
+ }
136
+ }
137
+ }
126
138
  }
127
139
 
128
140
  protected lineTo(point: Point2) {
129
- point = this.viewport.canvasToScreen(point);
141
+ point = this.canvasToScreen(point);
130
142
 
131
143
  this.currentPath!.push({
132
144
  kind: PathCommandType.LineTo,
@@ -135,7 +147,7 @@ export default class SVGRenderer extends AbstractRenderer {
135
147
  }
136
148
 
137
149
  protected moveTo(point: Point2) {
138
- point = this.viewport.canvasToScreen(point);
150
+ point = this.canvasToScreen(point);
139
151
 
140
152
  this.currentPath!.push({
141
153
  kind: PathCommandType.MoveTo,
@@ -146,9 +158,9 @@ export default class SVGRenderer extends AbstractRenderer {
146
158
  protected traceCubicBezierCurve(
147
159
  controlPoint1: Point2, controlPoint2: Point2, endPoint: Point2
148
160
  ) {
149
- controlPoint1 = this.viewport.canvasToScreen(controlPoint1);
150
- controlPoint2 = this.viewport.canvasToScreen(controlPoint2);
151
- endPoint = this.viewport.canvasToScreen(endPoint);
161
+ controlPoint1 = this.canvasToScreen(controlPoint1);
162
+ controlPoint2 = this.canvasToScreen(controlPoint2);
163
+ endPoint = this.canvasToScreen(endPoint);
152
164
 
153
165
  this.currentPath!.push({
154
166
  kind: PathCommandType.CubicBezierTo,
@@ -159,8 +171,8 @@ export default class SVGRenderer extends AbstractRenderer {
159
171
  }
160
172
 
161
173
  protected traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2) {
162
- controlPoint = this.viewport.canvasToScreen(controlPoint);
163
- endPoint = this.viewport.canvasToScreen(endPoint);
174
+ controlPoint = this.canvasToScreen(controlPoint);
175
+ endPoint = this.canvasToScreen(endPoint);
164
176
 
165
177
  this.currentPath!.push({
166
178
  kind: PathCommandType.QuadraticBezierTo,
@@ -175,12 +187,16 @@ export default class SVGRenderer extends AbstractRenderer {
175
187
  elem.setAttribute('cx', `${point.x}`);
176
188
  elem.setAttribute('cy', `${point.y}`);
177
189
  elem.setAttribute('r', '15');
178
- this.mainGroup.appendChild(elem);
190
+ this.elem.appendChild(elem);
179
191
  });
180
192
  }
181
193
 
182
- // Renders a copy of the given element.
194
+ // Renders a **copy** of the given element.
183
195
  public drawSVGElem(elem: SVGElement) {
184
196
  this.elem.appendChild(elem.cloneNode(true));
185
197
  }
198
+
199
+ public isTooSmallToRender(_rect: Rect2): boolean {
200
+ return false;
201
+ }
186
202
  }
@@ -1,4 +1,4 @@
1
- import { RenderingMode } from '../Display';
1
+ import { RenderingMode } from '../rendering/Display';
2
2
  import Editor from '../Editor';
3
3
 
4
4
  export default () => new Editor(document.body, { renderingMode: RenderingMode.DummyRenderer });