js-draw 0.1.9 → 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 (193) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +15 -3
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/Editor.d.ts +7 -2
  5. package/dist/src/Editor.js +74 -25
  6. package/dist/src/EditorImage.d.ts +1 -1
  7. package/dist/src/EditorImage.js +2 -2
  8. package/dist/src/Pointer.d.ts +1 -1
  9. package/dist/src/Pointer.js +1 -1
  10. package/dist/src/SVGLoader.d.ts +1 -1
  11. package/dist/src/SVGLoader.js +14 -6
  12. package/dist/src/UndoRedoHistory.js +3 -0
  13. package/dist/src/Viewport.d.ts +8 -25
  14. package/dist/src/Viewport.js +17 -10
  15. package/dist/src/bundle/bundled.d.ts +2 -1
  16. package/dist/src/bundle/bundled.js +2 -1
  17. package/dist/src/commands/Command.d.ts +2 -2
  18. package/dist/src/commands/Command.js +4 -4
  19. package/dist/src/commands/Duplicate.d.ts +1 -1
  20. package/dist/src/commands/Duplicate.js +1 -1
  21. package/dist/src/commands/Erase.d.ts +1 -1
  22. package/dist/src/commands/Erase.js +1 -1
  23. package/dist/src/commands/localization.d.ts +1 -1
  24. package/dist/src/components/AbstractComponent.d.ts +3 -3
  25. package/dist/src/components/AbstractComponent.js +2 -2
  26. package/dist/src/components/SVGGlobalAttributesObject.d.ts +3 -3
  27. package/dist/src/components/SVGGlobalAttributesObject.js +1 -1
  28. package/dist/src/components/Stroke.d.ts +4 -4
  29. package/dist/src/components/Stroke.js +2 -2
  30. package/dist/src/components/Text.d.ts +3 -3
  31. package/dist/src/components/Text.js +3 -3
  32. package/dist/src/components/UnknownSVGObject.d.ts +3 -3
  33. package/dist/src/components/UnknownSVGObject.js +1 -1
  34. package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
  35. package/dist/src/components/builders/ArrowBuilder.js +1 -1
  36. package/dist/src/components/builders/FreehandLineBuilder.d.ts +8 -3
  37. package/dist/src/components/builders/FreehandLineBuilder.js +142 -71
  38. package/dist/src/components/builders/LineBuilder.d.ts +1 -1
  39. package/dist/src/components/builders/LineBuilder.js +1 -1
  40. package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
  41. package/dist/src/components/builders/RectangleBuilder.js +3 -3
  42. package/dist/src/components/builders/types.d.ts +1 -1
  43. package/dist/src/localization.d.ts +1 -0
  44. package/dist/src/localization.js +5 -1
  45. package/dist/src/localizations/en.d.ts +3 -0
  46. package/dist/src/localizations/en.js +4 -0
  47. package/dist/src/localizations/es.d.ts +3 -0
  48. package/dist/src/localizations/es.js +18 -0
  49. package/dist/src/localizations/getLocalizationTable.d.ts +3 -0
  50. package/dist/src/localizations/getLocalizationTable.js +43 -0
  51. package/dist/src/{geometry → math}/LineSegment2.d.ts +1 -0
  52. package/dist/src/{geometry → math}/LineSegment2.js +16 -0
  53. package/dist/src/{geometry → math}/Mat33.d.ts +0 -0
  54. package/dist/src/{geometry → math}/Mat33.js +0 -0
  55. package/dist/src/{geometry → math}/Path.d.ts +2 -1
  56. package/dist/src/{geometry → math}/Path.js +58 -51
  57. package/dist/src/{geometry → math}/Rect2.d.ts +1 -0
  58. package/dist/src/{geometry → math}/Rect2.js +16 -0
  59. package/dist/src/{geometry → math}/Vec2.d.ts +0 -0
  60. package/dist/src/{geometry → math}/Vec2.js +0 -0
  61. package/dist/src/{geometry → math}/Vec3.d.ts +1 -1
  62. package/dist/src/{geometry → math}/Vec3.js +1 -1
  63. package/dist/src/math/rounding.d.ts +3 -0
  64. package/dist/src/math/rounding.js +120 -0
  65. package/dist/src/rendering/Display.d.ts +3 -1
  66. package/dist/src/rendering/Display.js +16 -10
  67. package/dist/src/rendering/caching/CacheRecord.d.ts +2 -2
  68. package/dist/src/rendering/caching/CacheRecord.js +1 -1
  69. package/dist/src/rendering/caching/CacheRecordManager.d.ts +1 -1
  70. package/dist/src/rendering/caching/RenderingCache.js +1 -1
  71. package/dist/src/rendering/caching/RenderingCacheNode.d.ts +2 -1
  72. package/dist/src/rendering/caching/RenderingCacheNode.js +18 -7
  73. package/dist/src/rendering/caching/testUtils.js +1 -1
  74. package/dist/src/rendering/caching/types.d.ts +1 -1
  75. package/dist/src/rendering/localization.d.ts +2 -0
  76. package/dist/src/rendering/localization.js +2 -0
  77. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +4 -4
  78. package/dist/src/rendering/renderers/AbstractRenderer.js +2 -2
  79. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +4 -4
  80. package/dist/src/rendering/renderers/CanvasRenderer.js +1 -1
  81. package/dist/src/rendering/renderers/DummyRenderer.d.ts +4 -4
  82. package/dist/src/rendering/renderers/DummyRenderer.js +1 -1
  83. package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -3
  84. package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
  85. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +5 -3
  86. package/dist/src/rendering/renderers/TextOnlyRenderer.js +13 -3
  87. package/dist/src/toolbar/HTMLToolbar.js +5 -7
  88. package/dist/src/toolbar/icons.d.ts +3 -0
  89. package/dist/src/toolbar/icons.js +152 -142
  90. package/dist/src/toolbar/localization.d.ts +4 -1
  91. package/dist/src/toolbar/localization.js +4 -1
  92. package/dist/src/toolbar/makeColorInput.js +2 -1
  93. package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +13 -0
  94. package/dist/src/toolbar/widgets/ActionButtonWidget.js +21 -0
  95. package/dist/src/toolbar/widgets/BaseWidget.js +31 -0
  96. package/dist/src/toolbar/widgets/HandToolWidget.js +10 -3
  97. package/dist/src/toolbar/widgets/SelectionWidget.d.ts +0 -1
  98. package/dist/src/toolbar/widgets/SelectionWidget.js +23 -30
  99. package/dist/src/tools/BaseTool.d.ts +2 -1
  100. package/dist/src/tools/BaseTool.js +3 -0
  101. package/dist/src/tools/Eraser.js +1 -1
  102. package/dist/src/tools/PanZoom.d.ts +3 -2
  103. package/dist/src/tools/PanZoom.js +32 -16
  104. package/dist/src/tools/SelectionTool.d.ts +11 -4
  105. package/dist/src/tools/SelectionTool.js +135 -23
  106. package/dist/src/tools/TextTool.js +1 -1
  107. package/dist/src/tools/ToolController.js +6 -2
  108. package/dist/src/tools/localization.d.ts +1 -0
  109. package/dist/src/tools/localization.js +1 -0
  110. package/dist/src/types.d.ts +13 -6
  111. package/dist/src/types.js +1 -0
  112. package/package.json +9 -1
  113. package/src/Editor.ts +86 -24
  114. package/src/EditorImage.test.ts +2 -4
  115. package/src/EditorImage.ts +2 -2
  116. package/src/Pointer.ts +1 -1
  117. package/src/SVGLoader.ts +14 -6
  118. package/src/UndoRedoHistory.ts +4 -0
  119. package/src/Viewport.ts +21 -17
  120. package/src/bundle/bundled.ts +2 -1
  121. package/src/commands/Command.ts +5 -5
  122. package/src/commands/Duplicate.ts +1 -1
  123. package/src/commands/Erase.ts +1 -1
  124. package/src/commands/localization.ts +1 -1
  125. package/src/components/AbstractComponent.ts +4 -4
  126. package/src/components/SVGGlobalAttributesObject.ts +3 -3
  127. package/src/components/Stroke.test.ts +3 -5
  128. package/src/components/Stroke.ts +4 -4
  129. package/src/components/Text.test.ts +2 -2
  130. package/src/components/Text.ts +3 -3
  131. package/src/components/UnknownSVGObject.ts +3 -3
  132. package/src/components/builders/ArrowBuilder.ts +2 -2
  133. package/src/components/builders/FreehandLineBuilder.ts +190 -80
  134. package/src/components/builders/LineBuilder.ts +2 -2
  135. package/src/components/builders/RectangleBuilder.ts +3 -3
  136. package/src/components/builders/types.ts +1 -1
  137. package/src/localization.ts +6 -0
  138. package/src/localizations/en.ts +8 -0
  139. package/src/localizations/es.ts +63 -0
  140. package/src/localizations/getLocalizationTable.test.ts +27 -0
  141. package/src/localizations/getLocalizationTable.ts +53 -0
  142. package/src/{geometry → math}/LineSegment2.test.ts +15 -0
  143. package/src/{geometry → math}/LineSegment2.ts +20 -0
  144. package/src/{geometry → math}/Mat33.test.ts +0 -0
  145. package/src/{geometry → math}/Mat33.ts +0 -0
  146. package/src/{geometry → math}/Path.fromString.test.ts +0 -0
  147. package/src/{geometry → math}/Path.test.ts +0 -0
  148. package/src/{geometry → math}/Path.toString.test.ts +11 -2
  149. package/src/{geometry → math}/Path.ts +60 -57
  150. package/src/{geometry → math}/Rect2.test.ts +20 -7
  151. package/src/{geometry → math}/Rect2.ts +19 -1
  152. package/src/{geometry → math}/Vec2.test.ts +0 -0
  153. package/src/{geometry → math}/Vec2.ts +0 -0
  154. package/src/{geometry → math}/Vec3.test.ts +0 -0
  155. package/src/{geometry → math}/Vec3.ts +2 -2
  156. package/src/math/rounding.test.ts +40 -0
  157. package/src/math/rounding.ts +145 -0
  158. package/src/rendering/Display.ts +18 -10
  159. package/src/rendering/caching/CacheRecord.test.ts +2 -2
  160. package/src/rendering/caching/CacheRecord.ts +2 -2
  161. package/src/rendering/caching/CacheRecordManager.ts +1 -1
  162. package/src/rendering/caching/RenderingCache.test.ts +3 -3
  163. package/src/rendering/caching/RenderingCache.ts +1 -1
  164. package/src/rendering/caching/RenderingCacheNode.ts +23 -7
  165. package/src/rendering/caching/testUtils.ts +1 -1
  166. package/src/rendering/caching/types.ts +1 -1
  167. package/src/rendering/localization.ts +4 -0
  168. package/src/rendering/renderers/AbstractRenderer.ts +4 -4
  169. package/src/rendering/renderers/CanvasRenderer.ts +4 -4
  170. package/src/rendering/renderers/DummyRenderer.test.ts +2 -2
  171. package/src/rendering/renderers/DummyRenderer.ts +4 -4
  172. package/src/rendering/renderers/SVGRenderer.ts +10 -4
  173. package/src/rendering/renderers/TextOnlyRenderer.ts +17 -6
  174. package/src/toolbar/HTMLToolbar.ts +5 -8
  175. package/src/toolbar/icons.ts +167 -147
  176. package/src/toolbar/localization.ts +8 -2
  177. package/src/toolbar/makeColorInput.ts +2 -1
  178. package/src/toolbar/toolbar.css +7 -3
  179. package/src/toolbar/widgets/ActionButtonWidget.ts +31 -0
  180. package/src/toolbar/widgets/BaseWidget.ts +36 -0
  181. package/src/toolbar/widgets/HandToolWidget.ts +14 -3
  182. package/src/toolbar/widgets/SelectionWidget.ts +46 -41
  183. package/src/tools/BaseTool.ts +5 -1
  184. package/src/tools/Eraser.ts +2 -2
  185. package/src/tools/PanZoom.ts +39 -18
  186. package/src/tools/SelectionTool.test.ts +26 -5
  187. package/src/tools/SelectionTool.ts +162 -27
  188. package/src/tools/TextTool.ts +2 -2
  189. package/src/tools/ToolController.ts +6 -2
  190. package/src/tools/UndoRedoShortcut.test.ts +1 -1
  191. package/src/tools/localization.ts +2 -0
  192. package/src/types.ts +14 -5
  193. package/dist-test/test-dist-bundle.html +0 -42
@@ -1,6 +1,6 @@
1
- import LineSegment2 from '../geometry/LineSegment2';
2
- import Mat33 from '../geometry/Mat33';
3
- import Rect2 from '../geometry/Rect2';
1
+ import LineSegment2 from '../math/LineSegment2';
2
+ import Mat33 from '../math/Mat33';
3
+ import Rect2 from '../math/Rect2';
4
4
  import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
5
5
  import SVGRenderer from '../rendering/renderers/SVGRenderer';
6
6
  import AbstractComponent from './AbstractComponent';
@@ -1,5 +1,5 @@
1
- import { PathCommandType } from '../../geometry/Path';
2
- import Rect2 from '../../geometry/Rect2';
1
+ import { PathCommandType } from '../../math/Path';
2
+ import Rect2 from '../../math/Rect2';
3
3
  import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
4
4
  import { StrokeDataPoint } from '../../types';
5
5
  import Viewport from '../../Viewport';
@@ -1,9 +1,9 @@
1
1
  import { Bezier } from 'bezier-js';
2
2
  import AbstractRenderer, { RenderablePathSpec } from '../../rendering/renderers/AbstractRenderer';
3
- import { Point2, Vec2 } from '../../geometry/Vec2';
4
- import Rect2 from '../../geometry/Rect2';
5
- import { PathCommand, PathCommandType } from '../../geometry/Path';
6
- import LineSegment2 from '../../geometry/LineSegment2';
3
+ import { Point2, Vec2 } from '../../math/Vec2';
4
+ import Rect2 from '../../math/Rect2';
5
+ import { LinePathCommand, PathCommandType, QuadraticBezierPathCommand } from '../../math/Path';
6
+ import LineSegment2 from '../../math/LineSegment2';
7
7
  import Stroke from '../Stroke';
8
8
  import Viewport from '../../Viewport';
9
9
  import { StrokeDataPoint } from '../../types';
@@ -22,9 +22,35 @@ export const makeFreehandLineBuilder: ComponentBuilderFactory = (initialPoint: S
22
22
  );
23
23
  };
24
24
 
25
+ type CurrentSegmentToPathResult = {
26
+ upperCurve: QuadraticBezierPathCommand,
27
+ lowerToUpperConnector: LinePathCommand,
28
+ upperToLowerConnector: LinePathCommand,
29
+ lowerCurve: QuadraticBezierPathCommand,
30
+ };
31
+
25
32
  // Handles stroke smoothing and creates Strokes from user/stylus input.
26
33
  export default class FreehandLineBuilder implements ComponentBuilder {
27
- private segments: RenderablePathSpec[];
34
+ private isFirstSegment: boolean = true;
35
+ private pathStartConnector: LinePathCommand|null = null;
36
+ private mostRecentConnector: LinePathCommand|null = null;
37
+
38
+ // Beginning of the list of lower parts
39
+ // ↓
40
+ // /---pathStartConnector---/ ← Beginning of the list of upper parts
41
+ // ___/ __/
42
+ // / /
43
+ // /--Most recent connector--/ ← most recent upper part goes here
44
+ // ↑
45
+ // most recent lower part goes here
46
+ //
47
+ // The upperSegments form a path that goes in reverse from the most recent edge to the
48
+ // least recent edge.
49
+ // The lowerSegments form a path that goes from the least recent edge to the most
50
+ // recent edge.
51
+ private upperSegments: QuadraticBezierPathCommand[];
52
+ private lowerSegments: QuadraticBezierPathCommand[];
53
+
28
54
  private buffer: Point2[];
29
55
  private lastPoint: StrokeDataPoint;
30
56
  private lastExitingVec: Vec2;
@@ -47,7 +73,9 @@ export default class FreehandLineBuilder implements ComponentBuilder {
47
73
  private maxFitAllowed: number
48
74
  ) {
49
75
  this.lastPoint = this.startPoint;
50
- this.segments = [];
76
+ this.upperSegments = [];
77
+ this.lowerSegments = [];
78
+
51
79
  this.buffer = [this.startPoint.pos];
52
80
  this.momentum = Vec2.zero;
53
81
  this.currentCurve = null;
@@ -65,19 +93,82 @@ export default class FreehandLineBuilder implements ComponentBuilder {
65
93
  };
66
94
  }
67
95
 
68
- // Get the segments that make up this' path. Can be called after calling build()
69
- private getPreview(): RenderablePathSpec[] {
70
- if (this.currentCurve && this.lastPoint) {
71
- const currentPath = this.currentSegmentToPath();
72
- return this.segments.concat(currentPath);
96
+ private previewPath(): RenderablePathSpec|null {
97
+ let upperPath: QuadraticBezierPathCommand[];
98
+ let lowerPath: QuadraticBezierPathCommand[];
99
+ let lowerToUpperCap: LinePathCommand;
100
+ let pathStartConnector: LinePathCommand;
101
+ if (this.currentCurve) {
102
+ const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
103
+ upperPath = this.upperSegments.concat(upperCurve);
104
+ lowerPath = this.lowerSegments.concat(lowerCurve);
105
+ lowerToUpperCap = lowerToUpperConnector;
106
+ pathStartConnector = this.pathStartConnector ?? upperToLowerConnector;
107
+ } else {
108
+ if (this.mostRecentConnector === null || this.pathStartConnector === null) {
109
+ return null;
110
+ }
111
+
112
+ upperPath = this.upperSegments.slice();
113
+ lowerPath = this.lowerSegments.slice();
114
+ lowerToUpperCap = this.mostRecentConnector;
115
+ pathStartConnector = this.pathStartConnector;
73
116
  }
117
+ const startPoint = lowerPath[lowerPath.length - 1].endPoint;
74
118
 
75
- return this.segments;
119
+
120
+ return {
121
+ // Start at the end of the lower curve:
122
+ // Start point
123
+ // ↓
124
+ // __/ __/ ← Most recent points on this end
125
+ // /___ /
126
+ // ↑
127
+ // Oldest points
128
+ startPoint,
129
+
130
+ commands: [
131
+ // Move to the most recent point on the upperPath:
132
+ // ----→•
133
+ // __/ __/
134
+ // /___ /
135
+ lowerToUpperCap,
136
+
137
+ // Move to the beginning of the upperPath:
138
+ // __/ __/
139
+ // /___ /
140
+ // • ←-
141
+ ...upperPath.reverse(),
142
+
143
+ // Move to the beginning of the lowerPath:
144
+ // __/ __/
145
+ // /___ /
146
+ // •
147
+ pathStartConnector,
148
+
149
+ // Move back to the start point:
150
+ // •
151
+ // __/ __/
152
+ // /___ /
153
+ ...lowerPath,
154
+ ],
155
+ style: this.getRenderingStyle(),
156
+ };
157
+ }
158
+
159
+ private previewStroke(): Stroke|null {
160
+ const pathPreview = this.previewPath();
161
+
162
+ if (pathPreview) {
163
+ return new Stroke([ pathPreview ]);
164
+ }
165
+ return null;
76
166
  }
77
167
 
78
168
  public preview(renderer: AbstractRenderer) {
79
- for (const part of this.getPreview()) {
80
- renderer.drawPath(part);
169
+ const path = this.previewPath();
170
+ if (path) {
171
+ renderer.drawPath(path);
81
172
  }
82
173
  }
83
174
 
@@ -85,9 +176,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
85
176
  if (this.lastPoint) {
86
177
  this.finalizeCurrentCurve();
87
178
  }
88
- return new Stroke(
89
- this.segments,
90
- );
179
+ return this.previewStroke()!;
91
180
  }
92
181
 
93
182
  private roundPoint(point: Point2): Point2 {
@@ -98,59 +187,77 @@ export default class FreehandLineBuilder implements ComponentBuilder {
98
187
  // Case where no points have been added
99
188
  if (!this.currentCurve) {
100
189
  // Don't create a circle around the initial point if the stroke has more than one point.
101
- if (this.segments.length > 0) {
190
+ if (!this.isFirstSegment) {
102
191
  return;
103
192
  }
104
193
 
105
- const width = Viewport.roundPoint(this.startPoint.width / 3, this.minFitAllowed);
194
+ const width = Viewport.roundPoint(this.startPoint.width / 3.5, this.minFitAllowed);
106
195
  const center = this.roundPoint(this.startPoint.pos);
107
196
 
197
+ // Start on the right, cycle clockwise:
198
+ // |
199
+ // ----- ←
200
+ // |
201
+ const startPoint = this.startPoint.pos.plus(Vec2.of(width, 0));
202
+
108
203
  // Draw a circle-ish shape around the start point
109
- this.segments.push({
110
- // Start on the right, cycle clockwise:
111
- // |
112
- // -----
113
- // |
114
- startPoint: this.startPoint.pos.plus(Vec2.of(width, 0)),
115
- commands: [
116
- {
117
- kind: PathCommandType.QuadraticBezierTo,
118
- controlPoint: center.plus(Vec2.of(width, width)),
119
-
120
- // Bottom of the circle
121
- // |
122
- // -----
123
- // |
124
- // ↑
125
- endPoint: center.plus(Vec2.of(0, width)),
126
- },
127
- {
128
- kind: PathCommandType.QuadraticBezierTo,
129
- controlPoint: center.plus(Vec2.of(-width, width)),
130
- endPoint: center.plus(Vec2.of(-width, 0)),
131
- },
132
- {
133
- kind: PathCommandType.QuadraticBezierTo,
134
- controlPoint: center.plus(Vec2.of(-width, -width)),
135
- endPoint: center.plus(Vec2.of(0, -width)),
136
- },
137
- {
138
- kind: PathCommandType.QuadraticBezierTo,
139
- controlPoint: center.plus(Vec2.of(width, -width)),
140
- endPoint: center.plus(Vec2.of(width, 0)),
141
- },
142
- ],
143
- style: this.getRenderingStyle(),
144
- });
204
+ this.lowerSegments.push(
205
+ {
206
+ kind: PathCommandType.QuadraticBezierTo,
207
+ controlPoint: center.plus(Vec2.of(width, width)),
208
+
209
+ // Bottom of the circle
210
+ // |
211
+ // -----
212
+ // |
213
+ // ↑
214
+ endPoint: center.plus(Vec2.of(0, width)),
215
+ },
216
+ {
217
+ kind: PathCommandType.QuadraticBezierTo,
218
+ controlPoint: center.plus(Vec2.of(-width, width)),
219
+ endPoint: center.plus(Vec2.of(-width, 0)),
220
+ },
221
+ {
222
+ kind: PathCommandType.QuadraticBezierTo,
223
+ controlPoint: center.plus(Vec2.of(-width, -width)),
224
+ endPoint: center.plus(Vec2.of(0, -width)),
225
+ },
226
+ {
227
+ kind: PathCommandType.QuadraticBezierTo,
228
+ controlPoint: center.plus(Vec2.of(width, -width)),
229
+ endPoint: center.plus(Vec2.of(width, 0)),
230
+ }
231
+ );
232
+ this.pathStartConnector = {
233
+ kind: PathCommandType.LineTo,
234
+ point: startPoint,
235
+ };
236
+ this.mostRecentConnector = this.pathStartConnector;
237
+
145
238
  return;
146
239
  }
147
240
 
148
- this.segments.push(this.currentSegmentToPath());
241
+ const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
242
+
243
+ if (this.isFirstSegment) {
244
+ // We draw the upper path (reversed), then the lower path, so we need the
245
+ // upperToLowerConnector to join the two paths.
246
+ this.pathStartConnector = upperToLowerConnector;
247
+ this.isFirstSegment = false;
248
+ }
249
+ // With the most recent connector, we're joining the end of the lowerPath to the most recent
250
+ // upperPath:
251
+ this.mostRecentConnector = lowerToUpperConnector;
252
+
253
+ this.upperSegments.push(upperCurve);
254
+ this.lowerSegments.push(lowerCurve);
255
+
149
256
  const lastPoint = this.buffer[this.buffer.length - 1];
150
257
  this.lastExitingVec = Vec2.ofXY(
151
258
  this.currentCurve.points[2]
152
259
  ).minus(Vec2.ofXY(this.currentCurve.points[1]));
153
- console.assert(this.lastExitingVec.magnitude() !== 0);
260
+ console.assert(this.lastExitingVec.magnitude() !== 0, 'lastExitingVec has zero length!');
154
261
 
155
262
  // Use the last two points to start a new curve (the last point isn't used
156
263
  // in the current curve and we want connected curves to share end points)
@@ -160,7 +267,8 @@ export default class FreehandLineBuilder implements ComponentBuilder {
160
267
  this.currentCurve = null;
161
268
  }
162
269
 
163
- private currentSegmentToPath(): RenderablePathSpec {
270
+ // Returns [upper curve, connector, lower curve]
271
+ private currentSegmentToPath(): CurrentSegmentToPathResult {
164
272
  if (this.currentCurve == null) {
165
273
  throw new Error('Invalid State: currentCurve is null!');
166
274
  }
@@ -217,31 +325,33 @@ export default class FreehandLineBuilder implements ComponentBuilder {
217
325
  halfVec = halfVec.times(2);
218
326
  }
219
327
 
328
+ // Each starts at startPt ± startVec
220
329
 
221
- const pathCommands: PathCommand[] = [
222
- {
223
- kind: PathCommandType.QuadraticBezierTo,
224
- controlPoint: this.roundPoint(controlPoint.plus(halfVec)),
225
- endPoint: this.roundPoint(endPt.plus(endVec)),
226
- },
330
+ const lowerCurve: QuadraticBezierPathCommand = {
331
+ kind: PathCommandType.QuadraticBezierTo,
332
+ controlPoint: this.roundPoint(controlPoint.plus(halfVec)),
333
+ endPoint: this.roundPoint(endPt.plus(endVec)),
334
+ };
227
335
 
228
- {
229
- kind: PathCommandType.LineTo,
230
- point: this.roundPoint(endPt.minus(endVec)),
231
- },
232
-
233
- {
234
- kind: PathCommandType.QuadraticBezierTo,
235
- controlPoint: this.roundPoint(controlPoint.minus(halfVec)),
236
- endPoint: this.roundPoint(startPt.minus(startVec)),
237
- },
238
- ];
336
+ // From the end of the upperCurve to the start of the lowerCurve:
337
+ const upperToLowerConnector: LinePathCommand = {
338
+ kind: PathCommandType.LineTo,
339
+ point: this.roundPoint(startPt.plus(startVec)),
340
+ };
239
341
 
240
- return {
241
- startPoint: this.roundPoint(startPt.plus(startVec)),
242
- commands: pathCommands,
243
- style: this.getRenderingStyle(),
342
+ // From the end of lowerCurve to the start of upperCurve:
343
+ const lowerToUpperConnector: LinePathCommand = {
344
+ kind: PathCommandType.LineTo,
345
+ point: this.roundPoint(endPt.minus(endVec))
346
+ };
347
+
348
+ const upperCurve: QuadraticBezierPathCommand = {
349
+ kind: PathCommandType.QuadraticBezierTo,
350
+ controlPoint: this.roundPoint(controlPoint.minus(halfVec)),
351
+ endPoint: this.roundPoint(startPt.minus(startVec)),
244
352
  };
353
+
354
+ return { upperCurve, upperToLowerConnector, lowerToUpperConnector, lowerCurve };
245
355
  }
246
356
 
247
357
  // Compute the direction of the velocity at the end of this.buffer
@@ -264,7 +374,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
264
374
 
265
375
  const threshold = Math.min(this.lastPoint.width, newPoint.width) / 4;
266
376
  const shouldSnapToInitial = this.startPoint.pos.minus(newPoint.pos).magnitude() < threshold
267
- && this.segments.length === 0;
377
+ && this.isFirstSegment;
268
378
 
269
379
  // Snap to the starting point if the stroke is contained within a small ball centered
270
380
  // at the starting point.
@@ -1,5 +1,5 @@
1
- import { PathCommandType } from '../../geometry/Path';
2
- import Rect2 from '../../geometry/Rect2';
1
+ import { PathCommandType } from '../../math/Path';
2
+ import Rect2 from '../../math/Rect2';
3
3
  import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
4
4
  import { StrokeDataPoint } from '../../types';
5
5
  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 AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
5
5
  import { StrokeDataPoint } from '../../types';
6
6
  import Viewport from '../../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';
@@ -6,6 +6,7 @@ import { defaultToolLocalization, ToolLocalization } from './tools/localization'
6
6
 
7
7
 
8
8
  export interface EditorLocalization extends ToolbarLocalization, ToolLocalization, CommandLocalization, ImageComponentLocalization, TextRendererLocalization {
9
+ accessibilityInputInstructions: string;
9
10
  undoAnnouncement: (actionDescription: string)=> string;
10
11
  redoAnnouncement: (actionDescription: string)=> string;
11
12
  doneLoading: string;
@@ -19,6 +20,11 @@ export const defaultEditorLocalization: EditorLocalization = {
19
20
  ...defaultCommandLocalization,
20
21
  ...defaultComponentLocalization,
21
22
  ...defaultTextRendererLocalization,
23
+ accessibilityInputInstructions: [
24
+ 'Press "t" to read the contents of the viewport as text.',
25
+ 'Use the arrow keys to move the viewport, click and drag to draw strokes.',
26
+ 'Press "w" to zoom in and "s" to zoom out.',
27
+ ].join(' '),
22
28
  loading: (percentage: number) => `Loading ${percentage}%...`,
23
29
  imageEditor: 'Image Editor',
24
30
  doneLoading: 'Done loading',
@@ -0,0 +1,8 @@
1
+ import { defaultEditorLocalization, EditorLocalization } from '../localization';
2
+
3
+ // Default localizations are already in English.
4
+ const localization: EditorLocalization = {
5
+ ...defaultEditorLocalization,
6
+ };
7
+
8
+ export default localization;
@@ -0,0 +1,63 @@
1
+ import { defaultEditorLocalization, EditorLocalization } from '../localization';
2
+
3
+ // A partial Spanish localization.
4
+ const localization: EditorLocalization = {
5
+ ...defaultEditorLocalization,
6
+
7
+ // Strings for the main editor interface
8
+ // (see src/localization.ts)
9
+ loading: (percentage: number) => `Cargando: ${percentage}%...`,
10
+ imageEditor: 'Editor de dibujos',
11
+
12
+ undoAnnouncement: (commandDescription: string) => `${commandDescription} fue deshecho`,
13
+ redoAnnouncement: (commandDescription: string) => `${commandDescription} fue rehecho`,
14
+ undo: 'Deshace',
15
+ redo: 'Rehace',
16
+
17
+ // Strings for the toolbar
18
+ // (see src/toolbar/localization.ts)
19
+ pen: 'Lapiz',
20
+ eraser: 'Borrador',
21
+ select: 'Selecciona',
22
+ thicknessLabel: 'Tamaño: ',
23
+ colorLabel: 'Color: ',
24
+ doneLoading: 'El cargado terminó',
25
+ fontLabel: 'Fuente: ',
26
+ anyDevicePanning: 'Mover la pantalla con todo dispotivo',
27
+ touchPanning: 'Mover la pantalla con un dedo',
28
+ touchPanTool: 'Instrumento de mover la pantalla con un dedo',
29
+ outlinedRectanglePen: 'Rectángulo con nada más que un borde',
30
+ filledRectanglePen: 'Rectángulo sin borde',
31
+ linePen: 'Línea',
32
+ arrowPen: 'Flecha',
33
+ freehandPen: 'Dibuja sin restricción de forma',
34
+ selectObjectType: 'Forma de dibuja:',
35
+ handTool: 'Mover',
36
+ zoom: 'Zoom',
37
+ resetView: 'Reiniciar vista',
38
+ resizeImageToSelection: 'Redimensionar la imagen a lo que está seleccionado',
39
+ deleteSelection: 'Borra la selección',
40
+ duplicateSelection: 'Duplica la selección',
41
+ pickColorFromScreen: 'Selecciona un color de la pantalla',
42
+ clickToPickColorAnnouncement: 'Haga un clic en la pantalla para seleccionar un color',
43
+ dropdownShown(toolName: string): string {
44
+ return `Menú por ${toolName} es visible`;
45
+ },
46
+ dropdownHidden: function (toolName: string): string {
47
+ return `Menú por ${toolName} fue ocultado`;
48
+ },
49
+ colorChangedAnnouncement: function (color: string): string {
50
+ return `Color fue cambiado a ${color}`;
51
+ },
52
+ keyboardPanZoom: 'Mover la pantalla con el teclado',
53
+ penTool: function (penId: number): string {
54
+ return `Lapiz ${penId}`;
55
+ },
56
+ selectionTool: 'Selecciona',
57
+ eraserTool: 'Borrador',
58
+ textTool: 'Texto',
59
+ enterTextToInsert: 'Entra texto',
60
+ rerenderAsText: 'Redibuja la pantalla al texto',
61
+ };
62
+
63
+ export default localization;
@@ -0,0 +1,27 @@
1
+
2
+ import { defaultEditorLocalization } from '../localization';
3
+ import en from './en';
4
+ import es from './es';
5
+ import getLocalizationTable from './getLocalizationTable';
6
+
7
+ describe('getLocalizationTable', () => {
8
+ it('should return the en localization for es_TEST', () => {
9
+ expect(getLocalizationTable([ 'es_TEST' ]) === es).toBe(true);
10
+ });
11
+
12
+ it('should return the default localization for unsupported language', () => {
13
+ expect(getLocalizationTable([ 'test' ]) === defaultEditorLocalization).toBe(true);
14
+ });
15
+
16
+ it('should return the first localization matching a language in the list of user locales', () => {
17
+ expect(getLocalizationTable([ 'test_TEST1', 'test_TEST2', 'test_TEST3', 'en_TEST', 'notalanguage']) === en).toBe(true);
18
+ });
19
+
20
+ it('should return the default localization for unsupported language', () => {
21
+ expect(getLocalizationTable([ 'test' ]) === defaultEditorLocalization).toBe(true);
22
+ });
23
+
24
+ it('should return first of user\'s supported languages', () => {
25
+ expect(getLocalizationTable([ 'es_MX', 'es_ES', 'en_US' ]) === es).toBe(true);
26
+ });
27
+ });
@@ -0,0 +1,53 @@
1
+
2
+ import { defaultEditorLocalization, EditorLocalization } from '../localization';
3
+ import en from './en';
4
+ import es from './es';
5
+
6
+ const allLocales: Record<string, EditorLocalization> = {
7
+ en,
8
+ es,
9
+ };
10
+
11
+ // [locale]: A string in the format languageCode_Region or just languageCode. For example, en_US.
12
+ const languageFromLocale = (locale: string) => {
13
+ const matches = /^(\w+)[_-](\w+)$/.exec(locale);
14
+ if (!matches) {
15
+ // If not in languageCode_region format, the locale should be the
16
+ // languageCode. Return that.
17
+ return locale;
18
+ }
19
+
20
+ return matches[1];
21
+ };
22
+
23
+ const getLocalizationTable = (userLocales?: readonly string[]): EditorLocalization => {
24
+ userLocales ??= navigator.languages;
25
+
26
+ let prevLanguage: string|undefined;
27
+ for (const locale of userLocales) {
28
+ const language = languageFromLocale(locale);
29
+
30
+ // If the specific localization of the language is not available, but
31
+ // a localization for the language is,
32
+ if (prevLanguage && language !== prevLanguage) {
33
+ if (prevLanguage in allLocales) {
34
+ return allLocales[prevLanguage];
35
+ }
36
+ }
37
+
38
+ // If the full locale (e.g. en_US) is available,
39
+ if (locale in allLocales) {
40
+ return allLocales[locale];
41
+ }
42
+
43
+ prevLanguage = language;
44
+ }
45
+
46
+ if (prevLanguage && prevLanguage in allLocales) {
47
+ return allLocales[prevLanguage];
48
+ } else {
49
+ return defaultEditorLocalization;
50
+ }
51
+ };
52
+
53
+ export default getLocalizationTable;
@@ -74,4 +74,19 @@ describe('Line2', () => {
74
74
  expect(line1.intersection(line2)).toBeNull();
75
75
  expect(line2.intersection(line1)).toBeNull();
76
76
  });
77
+
78
+ it('Closest point to (0,0) on the line x = 1 should be (1,0)', () => {
79
+ const line = new LineSegment2(Vec2.of(1, 100), Vec2.of(1, -100));
80
+ expect(line.closestPointTo(Vec2.zero)).objEq(Vec2.of(1, 0));
81
+ });
82
+
83
+ it('Closest point from (-1,2) to segment((1,1) -> (2,4)) should be (1,1)', () => {
84
+ const line = new LineSegment2(Vec2.of(1, 1), Vec2.of(2, 4));
85
+ expect(line.closestPointTo(Vec2.of(-1, 2))).objEq(Vec2.of(1, 1));
86
+ });
87
+
88
+ it('Closest point from (5,2) to segment((1,1) -> (2,4)) should be (2,4)', () => {
89
+ const line = new LineSegment2(Vec2.of(1, 1), Vec2.of(2, 4));
90
+ expect(line.closestPointTo(Vec2.of(5, 2))).objEq(Vec2.of(2, 4));
91
+ });
77
92
  });
@@ -7,6 +7,7 @@ interface IntersectionResult {
7
7
  }
8
8
 
9
9
  export default class LineSegment2 {
10
+ // invariant: ||direction|| = 1
10
11
  public readonly direction: Vec2;
11
12
  public readonly length: number;
12
13
  public readonly bbox;
@@ -124,4 +125,23 @@ export default class LineSegment2 {
124
125
  t: resultT,
125
126
  };
126
127
  }
128
+
129
+ // Returns the closest point on this to [target]
130
+ public closestPointTo(target: Point2) {
131
+ // Distance from P1 along this' direction.
132
+ const projectedDistFromP1 = target.minus(this.p1).dot(this.direction);
133
+ const projectedDistFromP2 = this.length - projectedDistFromP1;
134
+
135
+ const projection = this.p1.plus(this.direction.times(projectedDistFromP1));
136
+
137
+ if (projectedDistFromP1 > 0 && projectedDistFromP1 < this.length) {
138
+ return projection;
139
+ }
140
+
141
+ if (Math.abs(projectedDistFromP2) < Math.abs(projectedDistFromP1)) {
142
+ return this.p2;
143
+ } else {
144
+ return this.p1;
145
+ }
146
+ }
127
147
  }
File without changes
File without changes
File without changes
File without changes