js-draw 0.1.11 → 0.1.12

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 (167) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/Editor.d.ts +4 -2
  4. package/dist/src/Editor.js +30 -10
  5. package/dist/src/EditorImage.d.ts +1 -1
  6. package/dist/src/EditorImage.js +2 -2
  7. package/dist/src/Pointer.d.ts +1 -1
  8. package/dist/src/Pointer.js +1 -1
  9. package/dist/src/SVGLoader.d.ts +1 -1
  10. package/dist/src/SVGLoader.js +14 -6
  11. package/dist/src/Viewport.d.ts +8 -25
  12. package/dist/src/Viewport.js +15 -10
  13. package/dist/src/commands/Command.d.ts +2 -2
  14. package/dist/src/commands/Command.js +4 -4
  15. package/dist/src/commands/Duplicate.d.ts +1 -1
  16. package/dist/src/commands/Duplicate.js +1 -1
  17. package/dist/src/commands/Erase.d.ts +1 -1
  18. package/dist/src/commands/Erase.js +1 -1
  19. package/dist/src/commands/localization.d.ts +1 -1
  20. package/dist/src/components/AbstractComponent.d.ts +3 -3
  21. package/dist/src/components/AbstractComponent.js +2 -2
  22. package/dist/src/components/SVGGlobalAttributesObject.d.ts +3 -3
  23. package/dist/src/components/SVGGlobalAttributesObject.js +1 -1
  24. package/dist/src/components/Stroke.d.ts +4 -4
  25. package/dist/src/components/Stroke.js +2 -2
  26. package/dist/src/components/Text.d.ts +3 -3
  27. package/dist/src/components/Text.js +3 -3
  28. package/dist/src/components/UnknownSVGObject.d.ts +3 -3
  29. package/dist/src/components/UnknownSVGObject.js +1 -1
  30. package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
  31. package/dist/src/components/builders/ArrowBuilder.js +1 -1
  32. package/dist/src/components/builders/FreehandLineBuilder.d.ts +8 -3
  33. package/dist/src/components/builders/FreehandLineBuilder.js +142 -71
  34. package/dist/src/components/builders/LineBuilder.d.ts +1 -1
  35. package/dist/src/components/builders/LineBuilder.js +1 -1
  36. package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
  37. package/dist/src/components/builders/RectangleBuilder.js +3 -3
  38. package/dist/src/components/builders/types.d.ts +1 -1
  39. package/dist/src/localization.d.ts +1 -0
  40. package/dist/src/localization.js +5 -1
  41. package/dist/src/localizations/es.js +1 -1
  42. package/dist/src/{geometry → math}/LineSegment2.d.ts +0 -0
  43. package/dist/src/{geometry → math}/LineSegment2.js +0 -0
  44. package/dist/src/{geometry → math}/Mat33.d.ts +0 -0
  45. package/dist/src/{geometry → math}/Mat33.js +0 -0
  46. package/dist/src/{geometry → math}/Path.d.ts +2 -1
  47. package/dist/src/{geometry → math}/Path.js +58 -51
  48. package/dist/src/{geometry → math}/Rect2.d.ts +0 -0
  49. package/dist/src/{geometry → math}/Rect2.js +0 -0
  50. package/dist/src/{geometry → math}/Vec2.d.ts +0 -0
  51. package/dist/src/{geometry → math}/Vec2.js +0 -0
  52. package/dist/src/{geometry → math}/Vec3.d.ts +1 -1
  53. package/dist/src/{geometry → math}/Vec3.js +1 -1
  54. package/dist/src/math/rounding.d.ts +3 -0
  55. package/dist/src/math/rounding.js +120 -0
  56. package/dist/src/rendering/Display.d.ts +3 -1
  57. package/dist/src/rendering/Display.js +16 -10
  58. package/dist/src/rendering/caching/CacheRecord.d.ts +2 -2
  59. package/dist/src/rendering/caching/CacheRecord.js +1 -1
  60. package/dist/src/rendering/caching/CacheRecordManager.d.ts +1 -1
  61. package/dist/src/rendering/caching/RenderingCache.js +1 -1
  62. package/dist/src/rendering/caching/RenderingCacheNode.d.ts +2 -1
  63. package/dist/src/rendering/caching/RenderingCacheNode.js +18 -7
  64. package/dist/src/rendering/caching/testUtils.js +1 -1
  65. package/dist/src/rendering/caching/types.d.ts +1 -1
  66. package/dist/src/rendering/localization.d.ts +2 -0
  67. package/dist/src/rendering/localization.js +2 -0
  68. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +4 -4
  69. package/dist/src/rendering/renderers/AbstractRenderer.js +2 -2
  70. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +4 -4
  71. package/dist/src/rendering/renderers/CanvasRenderer.js +1 -1
  72. package/dist/src/rendering/renderers/DummyRenderer.d.ts +4 -4
  73. package/dist/src/rendering/renderers/DummyRenderer.js +1 -1
  74. package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -3
  75. package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
  76. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +5 -3
  77. package/dist/src/rendering/renderers/TextOnlyRenderer.js +13 -3
  78. package/dist/src/toolbar/icons.d.ts +3 -0
  79. package/dist/src/toolbar/icons.js +142 -132
  80. package/dist/src/toolbar/localization.d.ts +2 -1
  81. package/dist/src/toolbar/localization.js +2 -1
  82. package/dist/src/toolbar/makeColorInput.js +2 -1
  83. package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +13 -0
  84. package/dist/src/toolbar/widgets/ActionButtonWidget.js +21 -0
  85. package/dist/src/toolbar/widgets/BaseWidget.js +2 -0
  86. package/dist/src/toolbar/widgets/HandToolWidget.js +3 -3
  87. package/dist/src/toolbar/widgets/SelectionWidget.d.ts +0 -1
  88. package/dist/src/toolbar/widgets/SelectionWidget.js +23 -30
  89. package/dist/src/tools/Eraser.js +1 -1
  90. package/dist/src/tools/PanZoom.d.ts +1 -1
  91. package/dist/src/tools/PanZoom.js +24 -14
  92. package/dist/src/tools/SelectionTool.d.ts +3 -3
  93. package/dist/src/tools/SelectionTool.js +6 -6
  94. package/dist/src/tools/TextTool.js +1 -1
  95. package/dist/src/types.d.ts +4 -4
  96. package/package.json +1 -1
  97. package/src/Editor.ts +34 -12
  98. package/src/EditorImage.test.ts +2 -4
  99. package/src/EditorImage.ts +2 -2
  100. package/src/Pointer.ts +1 -1
  101. package/src/SVGLoader.ts +14 -6
  102. package/src/Viewport.ts +19 -17
  103. package/src/commands/Command.ts +5 -5
  104. package/src/commands/Duplicate.ts +1 -1
  105. package/src/commands/Erase.ts +1 -1
  106. package/src/commands/localization.ts +1 -1
  107. package/src/components/AbstractComponent.ts +4 -4
  108. package/src/components/SVGGlobalAttributesObject.ts +3 -3
  109. package/src/components/Stroke.test.ts +3 -5
  110. package/src/components/Stroke.ts +4 -4
  111. package/src/components/Text.test.ts +2 -2
  112. package/src/components/Text.ts +3 -3
  113. package/src/components/UnknownSVGObject.ts +3 -3
  114. package/src/components/builders/ArrowBuilder.ts +2 -2
  115. package/src/components/builders/FreehandLineBuilder.ts +190 -80
  116. package/src/components/builders/LineBuilder.ts +2 -2
  117. package/src/components/builders/RectangleBuilder.ts +3 -3
  118. package/src/components/builders/types.ts +1 -1
  119. package/src/localization.ts +6 -0
  120. package/src/localizations/es.ts +2 -1
  121. package/src/{geometry → math}/LineSegment2.test.ts +0 -0
  122. package/src/{geometry → math}/LineSegment2.ts +0 -0
  123. package/src/{geometry → math}/Mat33.test.ts +0 -0
  124. package/src/{geometry → math}/Mat33.ts +0 -0
  125. package/src/{geometry → math}/Path.fromString.test.ts +0 -0
  126. package/src/{geometry → math}/Path.test.ts +0 -0
  127. package/src/{geometry → math}/Path.toString.test.ts +11 -2
  128. package/src/{geometry → math}/Path.ts +60 -57
  129. package/src/{geometry → math}/Rect2.test.ts +0 -0
  130. package/src/{geometry → math}/Rect2.ts +0 -0
  131. package/src/{geometry → math}/Vec2.test.ts +0 -0
  132. package/src/{geometry → math}/Vec2.ts +0 -0
  133. package/src/{geometry → math}/Vec3.test.ts +0 -0
  134. package/src/{geometry → math}/Vec3.ts +2 -2
  135. package/src/math/rounding.test.ts +40 -0
  136. package/src/math/rounding.ts +145 -0
  137. package/src/rendering/Display.ts +18 -10
  138. package/src/rendering/caching/CacheRecord.test.ts +2 -2
  139. package/src/rendering/caching/CacheRecord.ts +2 -2
  140. package/src/rendering/caching/CacheRecordManager.ts +1 -1
  141. package/src/rendering/caching/RenderingCache.test.ts +3 -3
  142. package/src/rendering/caching/RenderingCache.ts +1 -1
  143. package/src/rendering/caching/RenderingCacheNode.ts +23 -7
  144. package/src/rendering/caching/testUtils.ts +1 -1
  145. package/src/rendering/caching/types.ts +1 -1
  146. package/src/rendering/localization.ts +4 -0
  147. package/src/rendering/renderers/AbstractRenderer.ts +4 -4
  148. package/src/rendering/renderers/CanvasRenderer.ts +4 -4
  149. package/src/rendering/renderers/DummyRenderer.test.ts +2 -2
  150. package/src/rendering/renderers/DummyRenderer.ts +4 -4
  151. package/src/rendering/renderers/SVGRenderer.ts +10 -4
  152. package/src/rendering/renderers/TextOnlyRenderer.ts +17 -6
  153. package/src/toolbar/icons.ts +157 -137
  154. package/src/toolbar/localization.ts +4 -2
  155. package/src/toolbar/makeColorInput.ts +2 -1
  156. package/src/toolbar/toolbar.css +1 -1
  157. package/src/toolbar/widgets/ActionButtonWidget.ts +31 -0
  158. package/src/toolbar/widgets/BaseWidget.ts +2 -0
  159. package/src/toolbar/widgets/HandToolWidget.ts +3 -3
  160. package/src/toolbar/widgets/SelectionWidget.ts +46 -41
  161. package/src/tools/Eraser.ts +2 -2
  162. package/src/tools/PanZoom.ts +28 -16
  163. package/src/tools/SelectionTool.test.ts +2 -4
  164. package/src/tools/SelectionTool.ts +6 -6
  165. package/src/tools/TextTool.ts +2 -2
  166. package/src/tools/UndoRedoShortcut.test.ts +1 -1
  167. package/src/types.ts +4 -4
@@ -1,8 +1,8 @@
1
1
  import { Bezier } from 'bezier-js';
2
- import { Vec2 } from '../../geometry/Vec2';
3
- import Rect2 from '../../geometry/Rect2';
4
- import { PathCommandType } from '../../geometry/Path';
5
- import LineSegment2 from '../../geometry/LineSegment2';
2
+ import { Vec2 } from '../../math/Vec2';
3
+ import Rect2 from '../../math/Rect2';
4
+ import { PathCommandType } from '../../math/Path';
5
+ import LineSegment2 from '../../math/LineSegment2';
6
6
  import Stroke from '../Stroke';
7
7
  import Viewport from '../../Viewport';
8
8
  export const makeFreehandLineBuilder = (initialPoint, viewport) => {
@@ -24,9 +24,13 @@ export default class FreehandLineBuilder {
24
24
  this.startPoint = startPoint;
25
25
  this.minFitAllowed = minFitAllowed;
26
26
  this.maxFitAllowed = maxFitAllowed;
27
+ this.isFirstSegment = true;
28
+ this.pathStartConnector = null;
29
+ this.mostRecentConnector = null;
27
30
  this.currentCurve = null;
28
31
  this.lastPoint = this.startPoint;
29
- this.segments = [];
32
+ this.upperSegments = [];
33
+ this.lowerSegments = [];
30
34
  this.buffer = [this.startPoint.pos];
31
35
  this.momentum = Vec2.zero;
32
36
  this.currentCurve = null;
@@ -41,24 +45,81 @@ export default class FreehandLineBuilder {
41
45
  fill: (_a = this.lastPoint.color) !== null && _a !== void 0 ? _a : null,
42
46
  };
43
47
  }
44
- // Get the segments that make up this' path. Can be called after calling build()
45
- getPreview() {
46
- if (this.currentCurve && this.lastPoint) {
47
- const currentPath = this.currentSegmentToPath();
48
- return this.segments.concat(currentPath);
48
+ previewPath() {
49
+ var _a;
50
+ let upperPath;
51
+ let lowerPath;
52
+ let lowerToUpperCap;
53
+ let pathStartConnector;
54
+ if (this.currentCurve) {
55
+ const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
56
+ upperPath = this.upperSegments.concat(upperCurve);
57
+ lowerPath = this.lowerSegments.concat(lowerCurve);
58
+ lowerToUpperCap = lowerToUpperConnector;
59
+ pathStartConnector = (_a = this.pathStartConnector) !== null && _a !== void 0 ? _a : upperToLowerConnector;
60
+ }
61
+ else {
62
+ if (this.mostRecentConnector === null || this.pathStartConnector === null) {
63
+ return null;
64
+ }
65
+ upperPath = this.upperSegments.slice();
66
+ lowerPath = this.lowerSegments.slice();
67
+ lowerToUpperCap = this.mostRecentConnector;
68
+ pathStartConnector = this.pathStartConnector;
69
+ }
70
+ const startPoint = lowerPath[lowerPath.length - 1].endPoint;
71
+ return {
72
+ // Start at the end of the lower curve:
73
+ // Start point
74
+ // ↓
75
+ // __/ __/ ← Most recent points on this end
76
+ // /___ /
77
+ // ↑
78
+ // Oldest points
79
+ startPoint,
80
+ commands: [
81
+ // Move to the most recent point on the upperPath:
82
+ // ----→•
83
+ // __/ __/
84
+ // /___ /
85
+ lowerToUpperCap,
86
+ // Move to the beginning of the upperPath:
87
+ // __/ __/
88
+ // /___ /
89
+ // • ←-
90
+ ...upperPath.reverse(),
91
+ // Move to the beginning of the lowerPath:
92
+ // __/ __/
93
+ // /___ /
94
+ // •
95
+ pathStartConnector,
96
+ // Move back to the start point:
97
+ // •
98
+ // __/ __/
99
+ // /___ /
100
+ ...lowerPath,
101
+ ],
102
+ style: this.getRenderingStyle(),
103
+ };
104
+ }
105
+ previewStroke() {
106
+ const pathPreview = this.previewPath();
107
+ if (pathPreview) {
108
+ return new Stroke([pathPreview]);
49
109
  }
50
- return this.segments;
110
+ return null;
51
111
  }
52
112
  preview(renderer) {
53
- for (const part of this.getPreview()) {
54
- renderer.drawPath(part);
113
+ const path = this.previewPath();
114
+ if (path) {
115
+ renderer.drawPath(path);
55
116
  }
56
117
  }
57
118
  build() {
58
119
  if (this.lastPoint) {
59
120
  this.finalizeCurrentCurve();
60
121
  }
61
- return new Stroke(this.segments);
122
+ return this.previewStroke();
62
123
  }
63
124
  roundPoint(point) {
64
125
  return Viewport.roundPoint(point, this.minFitAllowed);
@@ -67,53 +128,61 @@ export default class FreehandLineBuilder {
67
128
  // Case where no points have been added
68
129
  if (!this.currentCurve) {
69
130
  // Don't create a circle around the initial point if the stroke has more than one point.
70
- if (this.segments.length > 0) {
131
+ if (!this.isFirstSegment) {
71
132
  return;
72
133
  }
73
- const width = Viewport.roundPoint(this.startPoint.width / 3, this.minFitAllowed);
134
+ const width = Viewport.roundPoint(this.startPoint.width / 3.5, this.minFitAllowed);
74
135
  const center = this.roundPoint(this.startPoint.pos);
136
+ // Start on the right, cycle clockwise:
137
+ // |
138
+ // ----- ←
139
+ // |
140
+ const startPoint = this.startPoint.pos.plus(Vec2.of(width, 0));
75
141
  // Draw a circle-ish shape around the start point
76
- this.segments.push({
77
- // Start on the right, cycle clockwise:
142
+ this.lowerSegments.push({
143
+ kind: PathCommandType.QuadraticBezierTo,
144
+ controlPoint: center.plus(Vec2.of(width, width)),
145
+ // Bottom of the circle
78
146
  // |
79
- // -----
147
+ // -----
80
148
  // |
81
- startPoint: this.startPoint.pos.plus(Vec2.of(width, 0)),
82
- commands: [
83
- {
84
- kind: PathCommandType.QuadraticBezierTo,
85
- controlPoint: center.plus(Vec2.of(width, width)),
86
- // Bottom of the circle
87
- // |
88
- // -----
89
- // |
90
- // ↑
91
- endPoint: center.plus(Vec2.of(0, width)),
92
- },
93
- {
94
- kind: PathCommandType.QuadraticBezierTo,
95
- controlPoint: center.plus(Vec2.of(-width, width)),
96
- endPoint: center.plus(Vec2.of(-width, 0)),
97
- },
98
- {
99
- kind: PathCommandType.QuadraticBezierTo,
100
- controlPoint: center.plus(Vec2.of(-width, -width)),
101
- endPoint: center.plus(Vec2.of(0, -width)),
102
- },
103
- {
104
- kind: PathCommandType.QuadraticBezierTo,
105
- controlPoint: center.plus(Vec2.of(width, -width)),
106
- endPoint: center.plus(Vec2.of(width, 0)),
107
- },
108
- ],
109
- style: this.getRenderingStyle(),
149
+ // ↑
150
+ endPoint: center.plus(Vec2.of(0, width)),
151
+ }, {
152
+ kind: PathCommandType.QuadraticBezierTo,
153
+ controlPoint: center.plus(Vec2.of(-width, width)),
154
+ endPoint: center.plus(Vec2.of(-width, 0)),
155
+ }, {
156
+ kind: PathCommandType.QuadraticBezierTo,
157
+ controlPoint: center.plus(Vec2.of(-width, -width)),
158
+ endPoint: center.plus(Vec2.of(0, -width)),
159
+ }, {
160
+ kind: PathCommandType.QuadraticBezierTo,
161
+ controlPoint: center.plus(Vec2.of(width, -width)),
162
+ endPoint: center.plus(Vec2.of(width, 0)),
110
163
  });
164
+ this.pathStartConnector = {
165
+ kind: PathCommandType.LineTo,
166
+ point: startPoint,
167
+ };
168
+ this.mostRecentConnector = this.pathStartConnector;
111
169
  return;
112
170
  }
113
- this.segments.push(this.currentSegmentToPath());
171
+ const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
172
+ if (this.isFirstSegment) {
173
+ // We draw the upper path (reversed), then the lower path, so we need the
174
+ // upperToLowerConnector to join the two paths.
175
+ this.pathStartConnector = upperToLowerConnector;
176
+ this.isFirstSegment = false;
177
+ }
178
+ // With the most recent connector, we're joining the end of the lowerPath to the most recent
179
+ // upperPath:
180
+ this.mostRecentConnector = lowerToUpperConnector;
181
+ this.upperSegments.push(upperCurve);
182
+ this.lowerSegments.push(lowerCurve);
114
183
  const lastPoint = this.buffer[this.buffer.length - 1];
115
184
  this.lastExitingVec = Vec2.ofXY(this.currentCurve.points[2]).minus(Vec2.ofXY(this.currentCurve.points[1]));
116
- console.assert(this.lastExitingVec.magnitude() !== 0);
185
+ console.assert(this.lastExitingVec.magnitude() !== 0, 'lastExitingVec has zero length!');
117
186
  // Use the last two points to start a new curve (the last point isn't used
118
187
  // in the current curve and we want connected curves to share end points)
119
188
  this.buffer = [
@@ -121,6 +190,7 @@ export default class FreehandLineBuilder {
121
190
  ];
122
191
  this.currentCurve = null;
123
192
  }
193
+ // Returns [upper curve, connector, lower curve]
124
194
  currentSegmentToPath() {
125
195
  if (this.currentCurve == null) {
126
196
  throw new Error('Invalid State: currentCurve is null!');
@@ -163,27 +233,28 @@ export default class FreehandLineBuilder {
163
233
  if (upperBoundary.intersects(lowerBoundary).length > 0) {
164
234
  halfVec = halfVec.times(2);
165
235
  }
166
- const pathCommands = [
167
- {
168
- kind: PathCommandType.QuadraticBezierTo,
169
- controlPoint: this.roundPoint(controlPoint.plus(halfVec)),
170
- endPoint: this.roundPoint(endPt.plus(endVec)),
171
- },
172
- {
173
- kind: PathCommandType.LineTo,
174
- point: this.roundPoint(endPt.minus(endVec)),
175
- },
176
- {
177
- kind: PathCommandType.QuadraticBezierTo,
178
- controlPoint: this.roundPoint(controlPoint.minus(halfVec)),
179
- endPoint: this.roundPoint(startPt.minus(startVec)),
180
- },
181
- ];
182
- return {
183
- startPoint: this.roundPoint(startPt.plus(startVec)),
184
- commands: pathCommands,
185
- style: this.getRenderingStyle(),
236
+ // Each starts at startPt ± startVec
237
+ const lowerCurve = {
238
+ kind: PathCommandType.QuadraticBezierTo,
239
+ controlPoint: this.roundPoint(controlPoint.plus(halfVec)),
240
+ endPoint: this.roundPoint(endPt.plus(endVec)),
241
+ };
242
+ // From the end of the upperCurve to the start of the lowerCurve:
243
+ const upperToLowerConnector = {
244
+ kind: PathCommandType.LineTo,
245
+ point: this.roundPoint(startPt.plus(startVec)),
246
+ };
247
+ // From the end of lowerCurve to the start of upperCurve:
248
+ const lowerToUpperConnector = {
249
+ kind: PathCommandType.LineTo,
250
+ point: this.roundPoint(endPt.minus(endVec))
251
+ };
252
+ const upperCurve = {
253
+ kind: PathCommandType.QuadraticBezierTo,
254
+ controlPoint: this.roundPoint(controlPoint.minus(halfVec)),
255
+ endPoint: this.roundPoint(startPt.minus(startVec)),
186
256
  };
257
+ return { upperCurve, upperToLowerConnector, lowerToUpperConnector, lowerCurve };
187
258
  }
188
259
  // Compute the direction of the velocity at the end of this.buffer
189
260
  computeExitingVec() {
@@ -205,7 +276,7 @@ export default class FreehandLineBuilder {
205
276
  }
206
277
  const threshold = Math.min(this.lastPoint.width, newPoint.width) / 4;
207
278
  const shouldSnapToInitial = this.startPoint.pos.minus(newPoint.pos).magnitude() < threshold
208
- && this.segments.length === 0;
279
+ && this.isFirstSegment;
209
280
  // Snap to the starting point if the stroke is contained within a small ball centered
210
281
  // at the starting point.
211
282
  // This allows us to create a circle/dot at the start of the stroke.
@@ -1,4 +1,4 @@
1
- import Rect2 from '../../geometry/Rect2';
1
+ import Rect2 from '../../math/Rect2';
2
2
  import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
3
3
  import { StrokeDataPoint } from '../../types';
4
4
  import AbstractComponent from '../AbstractComponent';
@@ -1,4 +1,4 @@
1
- import { PathCommandType } from '../../geometry/Path';
1
+ import { PathCommandType } from '../../math/Path';
2
2
  import Stroke from '../Stroke';
3
3
  export const makeLineBuilder = (initialPoint, _viewport) => {
4
4
  return new LineBuilder(initialPoint);
@@ -1,4 +1,4 @@
1
- import Rect2 from '../../geometry/Rect2';
1
+ import Rect2 from '../../math/Rect2';
2
2
  import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
3
3
  import { StrokeDataPoint } from '../../types';
4
4
  import Viewport from '../../Viewport';
@@ -1,6 +1,6 @@
1
- import Mat33 from '../../geometry/Mat33';
2
- import Path from '../../geometry/Path';
3
- import Rect2 from '../../geometry/Rect2';
1
+ import Mat33 from '../../math/Mat33';
2
+ import Path from '../../math/Path';
3
+ import Rect2 from '../../math/Rect2';
4
4
  import Stroke from '../Stroke';
5
5
  export const makeFilledRectangleBuilder = (initialPoint, viewport) => {
6
6
  return new RectangleBuilder(initialPoint, true, viewport);
@@ -1,4 +1,4 @@
1
- import Rect2 from '../../geometry/Rect2';
1
+ import Rect2 from '../../math/Rect2';
2
2
  import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
3
3
  import { StrokeDataPoint } from '../../types';
4
4
  import Viewport from '../../Viewport';
@@ -4,6 +4,7 @@ import { TextRendererLocalization } from './rendering/localization';
4
4
  import { ToolbarLocalization } from './toolbar/localization';
5
5
  import { ToolLocalization } from './tools/localization';
6
6
  export interface EditorLocalization extends ToolbarLocalization, ToolLocalization, CommandLocalization, ImageComponentLocalization, TextRendererLocalization {
7
+ accessibilityInputInstructions: string;
7
8
  undoAnnouncement: (actionDescription: string) => string;
8
9
  redoAnnouncement: (actionDescription: string) => string;
9
10
  doneLoading: string;
@@ -3,4 +3,8 @@ import { defaultComponentLocalization } from './components/localization';
3
3
  import { defaultTextRendererLocalization } from './rendering/localization';
4
4
  import { defaultToolbarLocalization } from './toolbar/localization';
5
5
  import { defaultToolLocalization } from './tools/localization';
6
- export const defaultEditorLocalization = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, defaultToolbarLocalization), defaultToolLocalization), defaultCommandLocalization), defaultComponentLocalization), defaultTextRendererLocalization), { loading: (percentage) => `Loading ${percentage}%...`, imageEditor: 'Image Editor', doneLoading: 'Done loading', undoAnnouncement: (commandDescription) => `Undid ${commandDescription}`, redoAnnouncement: (commandDescription) => `Redid ${commandDescription}` });
6
+ export const defaultEditorLocalization = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, defaultToolbarLocalization), defaultToolLocalization), defaultCommandLocalization), defaultComponentLocalization), defaultTextRendererLocalization), { accessibilityInputInstructions: [
7
+ 'Press "t" to read the contents of the viewport as text.',
8
+ 'Use the arrow keys to move the viewport, click and drag to draw strokes.',
9
+ 'Press "w" to zoom in and "s" to zoom out.',
10
+ ].join(' '), loading: (percentage) => `Loading ${percentage}%...`, imageEditor: 'Image Editor', doneLoading: 'Done loading', undoAnnouncement: (commandDescription) => `Undid ${commandDescription}`, redoAnnouncement: (commandDescription) => `Redid ${commandDescription}` });
@@ -6,7 +6,7 @@ const localization = Object.assign(Object.assign({}, defaultEditorLocalization),
6
6
  loading: (percentage) => `Cargando: ${percentage}%...`, imageEditor: 'Editor de dibujos', undoAnnouncement: (commandDescription) => `${commandDescription} fue deshecho`, redoAnnouncement: (commandDescription) => `${commandDescription} fue rehecho`, undo: 'Deshace', redo: 'Rehace',
7
7
  // Strings for the toolbar
8
8
  // (see src/toolbar/localization.ts)
9
- pen: 'Lapiz', eraser: 'Borrador', select: 'Selecciona', thicknessLabel: 'Tamaño: ', colorLabel: 'Color: ', doneLoading: 'El cargado terminó', fontLabel: 'Fuente: ', anyDevicePanning: 'Mover la pantalla con todo dispotivo', touchPanning: 'Mover la pantalla con un dedo', touchPanTool: 'Instrumento de mover la pantalla con un dedo', outlinedRectanglePen: 'Rectángulo con nada más que un borde', filledRectanglePen: 'Rectángulo sin borde', linePen: 'Línea', arrowPen: 'Flecha', freehandPen: 'Dibuja sin restricción de forma', selectObjectType: 'Forma de dibuja:', handTool: 'Mover', zoom: 'Zoom', resetView: 'Reiniciar vista', resizeImageToSelection: 'Redimensionar la imagen a lo que está seleccionado', deleteSelection: 'Borra la selección', duplicateSelection: 'Duplica la selección', pickColorFronScreen: 'Selecciona un color de la pantalla', dropdownShown(toolName) {
9
+ pen: 'Lapiz', eraser: 'Borrador', select: 'Selecciona', thicknessLabel: 'Tamaño: ', colorLabel: 'Color: ', doneLoading: 'El cargado terminó', fontLabel: 'Fuente: ', anyDevicePanning: 'Mover la pantalla con todo dispotivo', touchPanning: 'Mover la pantalla con un dedo', touchPanTool: 'Instrumento de mover la pantalla con un dedo', outlinedRectanglePen: 'Rectángulo con nada más que un borde', filledRectanglePen: 'Rectángulo sin borde', linePen: 'Línea', arrowPen: 'Flecha', freehandPen: 'Dibuja sin restricción de forma', selectObjectType: 'Forma de dibuja:', handTool: 'Mover', zoom: 'Zoom', resetView: 'Reiniciar vista', resizeImageToSelection: 'Redimensionar la imagen a lo que está seleccionado', deleteSelection: 'Borra la selección', duplicateSelection: 'Duplica la selección', pickColorFromScreen: 'Selecciona un color de la pantalla', clickToPickColorAnnouncement: 'Haga un clic en la pantalla para seleccionar un color', dropdownShown(toolName) {
10
10
  return `Menú por ${toolName} es visible`;
11
11
  }, dropdownHidden: function (toolName) {
12
12
  return `Menú por ${toolName} fue ocultado`;
File without changes
File without changes
File without changes
File without changes
@@ -45,6 +45,7 @@ export default class Path {
45
45
  get geometry(): Array<LineSegment2 | Bezier>;
46
46
  static computeBBoxForSegment(startPoint: Point2, part: PathCommand): Rect2;
47
47
  intersection(line: LineSegment2): IntersectionResult[];
48
+ mapPoints(mapping: (point: Point2) => Point2): Path;
48
49
  transformedBy(affineTransfm: Mat33): Path;
49
50
  union(other: Path | null): Path;
50
51
  static fromRect(rect: Rect2, lineWidth?: number | null): Path;
@@ -52,7 +53,7 @@ export default class Path {
52
53
  toRenderable(fill: RenderingStyle): RenderablePathSpec;
53
54
  toString(): string;
54
55
  serialize(): string;
55
- static toString(startPoint: Point2, parts: PathCommand[]): string;
56
+ static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands?: boolean): string;
56
57
  static fromString(pathString: string): Path;
57
58
  static empty: Path;
58
59
  }
@@ -1,4 +1,5 @@
1
1
  import { Bezier } from 'bezier-js';
2
+ import { toRoundedString, toStringOfSamePrecision } from './rounding';
2
3
  import LineSegment2 from './LineSegment2';
3
4
  import Rect2 from './Rect2';
4
5
  import { Vec2 } from './Vec2';
@@ -109,8 +110,8 @@ export default class Path {
109
110
  }
110
111
  return result;
111
112
  }
112
- transformedBy(affineTransfm) {
113
- const startPoint = affineTransfm.transformVec2(this.startPoint);
113
+ mapPoints(mapping) {
114
+ const startPoint = mapping(this.startPoint);
114
115
  const newParts = [];
115
116
  let exhaustivenessCheck;
116
117
  for (const part of this.parts) {
@@ -119,22 +120,22 @@ export default class Path {
119
120
  case PathCommandType.LineTo:
120
121
  newParts.push({
121
122
  kind: part.kind,
122
- point: affineTransfm.transformVec2(part.point),
123
+ point: mapping(part.point),
123
124
  });
124
125
  break;
125
126
  case PathCommandType.CubicBezierTo:
126
127
  newParts.push({
127
128
  kind: part.kind,
128
- controlPoint1: affineTransfm.transformVec2(part.controlPoint1),
129
- controlPoint2: affineTransfm.transformVec2(part.controlPoint2),
130
- endPoint: affineTransfm.transformVec2(part.endPoint),
129
+ controlPoint1: mapping(part.controlPoint1),
130
+ controlPoint2: mapping(part.controlPoint2),
131
+ endPoint: mapping(part.endPoint),
131
132
  });
132
133
  break;
133
134
  case PathCommandType.QuadraticBezierTo:
134
135
  newParts.push({
135
136
  kind: part.kind,
136
- controlPoint: affineTransfm.transformVec2(part.controlPoint),
137
- endPoint: affineTransfm.transformVec2(part.endPoint),
137
+ controlPoint: mapping(part.controlPoint),
138
+ endPoint: mapping(part.endPoint),
138
139
  });
139
140
  break;
140
141
  default:
@@ -144,6 +145,9 @@ export default class Path {
144
145
  }
145
146
  return new Path(startPoint, newParts);
146
147
  }
148
+ transformedBy(affineTransfm) {
149
+ return this.mapPoints(point => affineTransfm.transformVec2(point));
150
+ }
147
151
  // Creates a new path by joining [other] to the end of this path
148
152
  union(other) {
149
153
  if (!other) {
@@ -201,58 +205,61 @@ export default class Path {
201
205
  };
202
206
  }
203
207
  toString() {
204
- return Path.toString(this.startPoint, this.parts);
208
+ // Hueristic: Try to determine whether converting absolute to relative commands is worth it.
209
+ // If we're near (0, 0), it probably isn't worth it and if bounding boxes are large,
210
+ // it also probably isn't worth it.
211
+ const makeRelativeCommands = Math.abs(this.bbox.topLeft.x) > 10 && Math.abs(this.bbox.size.x) < 2
212
+ && Math.abs(this.bbox.topLeft.y) > 10 && Math.abs(this.bbox.size.y) < 2;
213
+ return Path.toString(this.startPoint, this.parts, !makeRelativeCommands);
205
214
  }
206
215
  serialize() {
207
216
  return this.toString();
208
217
  }
209
- static toString(startPoint, parts) {
218
+ // [onlyAbsCommands]: True if we should avoid converting absolute coordinates to relative offsets -- such
219
+ // conversions can lead to smaller output strings, but also take time.
220
+ static toString(startPoint, parts, onlyAbsCommands = true) {
210
221
  const result = [];
211
- const toRoundedString = (num) => {
212
- // Try to remove rounding errors. If the number ends in at least three/four zeroes
213
- // (or nines) just one or two digits, it's probably a rounding error.
214
- const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d$/;
215
- const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,}\d)$/;
216
- let text = num.toString();
217
- if (text.indexOf('.') === -1) {
218
- return text;
219
- }
220
- const roundingDownMatch = hasRoundingDownExp.exec(text);
221
- if (roundingDownMatch) {
222
- const negativeSign = roundingDownMatch[1];
223
- const lastDigit = parseInt(text.charAt(text.length - 1), 10);
224
- const postDecimal = parseInt(roundingDownMatch[3], 10);
225
- const preDecimal = parseInt(roundingDownMatch[2], 10);
226
- const origPostDecimalString = roundingDownMatch[3];
227
- let newPostDecimal = (postDecimal + 10 - lastDigit).toString();
228
- let carry = 0;
229
- if (newPostDecimal.length > postDecimal.toString().length) {
230
- // Left-shift
231
- newPostDecimal = newPostDecimal.substring(1);
232
- carry = 1;
222
+ let prevPoint;
223
+ const addCommand = (command, ...points) => {
224
+ const absoluteCommandParts = [];
225
+ const relativeCommandParts = [];
226
+ const makeAbsCommand = !prevPoint || onlyAbsCommands;
227
+ const roundedPrevX = prevPoint ? toRoundedString(prevPoint.x) : '';
228
+ const roundedPrevY = prevPoint ? toRoundedString(prevPoint.y) : '';
229
+ for (const point of points) {
230
+ // Relative commands are often shorter as strings than absolute commands.
231
+ if (!makeAbsCommand) {
232
+ const xComponentRelative = toStringOfSamePrecision(point.x - prevPoint.x, roundedPrevX, roundedPrevY);
233
+ const yComponentRelative = toStringOfSamePrecision(point.y - prevPoint.y, roundedPrevX, roundedPrevY);
234
+ // No need for an additional separator if it starts with a '-'
235
+ if (yComponentRelative.charAt(0) === '-') {
236
+ relativeCommandParts.push(`${xComponentRelative}${yComponentRelative}`);
237
+ }
238
+ else {
239
+ relativeCommandParts.push(`${xComponentRelative},${yComponentRelative}`);
240
+ }
233
241
  }
234
- // parseInt(...).toString() removes leading zeroes. Add them back.
235
- while (newPostDecimal.length < origPostDecimalString.length) {
236
- newPostDecimal = carry.toString(10) + newPostDecimal;
237
- carry = 0;
242
+ else {
243
+ const xComponent = toRoundedString(point.x);
244
+ const yComponent = toRoundedString(point.y);
245
+ absoluteCommandParts.push(`${xComponent},${yComponent}`);
238
246
  }
239
- text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
240
247
  }
241
- text = text.replace(fixRoundingUpExp, '$1');
242
- // Remove trailing zeroes
243
- text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
244
- text = text.replace(/[.]0+$/, '.');
245
- // Remove trailing period
246
- return text.replace(/[.]$/, '');
247
- };
248
- const addCommand = (command, ...points) => {
249
- const parts = [];
250
- for (const point of points) {
251
- const xComponent = toRoundedString(point.x);
252
- const yComponent = toRoundedString(point.y);
253
- parts.push(`${xComponent},${yComponent}`);
248
+ let commandString;
249
+ if (makeAbsCommand) {
250
+ commandString = `${command}${absoluteCommandParts.join(' ')}`;
251
+ }
252
+ else {
253
+ commandString = `${command.toLowerCase()}${relativeCommandParts.join(' ')}`;
254
+ }
255
+ // Don't add no-ops.
256
+ if (commandString === 'l0,0') {
257
+ return;
258
+ }
259
+ result.push(commandString);
260
+ if (points.length > 0) {
261
+ prevPoint = points[points.length - 1];
254
262
  }
255
- result.push(`${command}${parts.join(' ')}`);
256
263
  };
257
264
  addCommand('M', startPoint);
258
265
  let exhaustivenessCheck;
File without changes
File without changes
File without changes
File without changes
@@ -23,7 +23,7 @@ export default class Vec3 {
23
23
  extend(distance: number, direction: Vec3): Vec3;
24
24
  lerp(target: Vec3, fractionTo: number): Vec3;
25
25
  zip(other: Vec3, zip: (componentInThis: number, componentInOther: number) => number): Vec3;
26
- map(fn: (component: number) => number): Vec3;
26
+ map(fn: (component: number, index: number) => number): Vec3;
27
27
  asArray(): number[];
28
28
  eq(other: Vec3, fuzz: number): boolean;
29
29
  toString(): string;
@@ -86,7 +86,7 @@ export default class Vec3 {
86
86
  }
87
87
  // Returns a vector with each component acted on by [fn]
88
88
  map(fn) {
89
- return Vec3.of(fn(this.x), fn(this.y), fn(this.z));
89
+ return Vec3.of(fn(this.x, 0), fn(this.y, 1), fn(this.z, 2));
90
90
  }
91
91
  asArray() {
92
92
  return [this.x, this.y, this.z];
@@ -0,0 +1,3 @@
1
+ export declare const toRoundedString: (num: number) => string;
2
+ export declare const getLenAfterDecimal: (numberAsString: string) => number;
3
+ export declare const toStringOfSamePrecision: (num: number, ...references: string[]) => string;