js-draw 0.1.11 → 0.2.0

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 (220) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.firebaserc +5 -0
  3. package/.github/workflows/firebase-hosting-merge.yml +25 -0
  4. package/.github/workflows/firebase-hosting-pull-request.yml +22 -0
  5. package/.github/workflows/github-pages.yml +52 -0
  6. package/CHANGELOG.md +13 -0
  7. package/README.md +11 -6
  8. package/dist/bundle.js +1 -1
  9. package/dist/src/Color4.d.ts +19 -0
  10. package/dist/src/Color4.js +24 -3
  11. package/dist/src/Editor.d.ts +133 -4
  12. package/dist/src/Editor.js +124 -27
  13. package/dist/src/EditorImage.d.ts +8 -3
  14. package/dist/src/EditorImage.js +42 -26
  15. package/dist/src/EventDispatcher.d.ts +18 -0
  16. package/dist/src/EventDispatcher.js +19 -4
  17. package/dist/src/Pointer.d.ts +1 -1
  18. package/dist/src/Pointer.js +4 -3
  19. package/dist/src/SVGLoader.d.ts +1 -1
  20. package/dist/src/SVGLoader.js +14 -6
  21. package/dist/src/UndoRedoHistory.js +15 -2
  22. package/dist/src/Viewport.d.ts +8 -25
  23. package/dist/src/Viewport.js +18 -10
  24. package/dist/src/bundle/bundled.d.ts +1 -2
  25. package/dist/src/bundle/bundled.js +1 -2
  26. package/dist/src/commands/Command.d.ts +2 -2
  27. package/dist/src/commands/Command.js +4 -4
  28. package/dist/src/commands/Duplicate.d.ts +2 -2
  29. package/dist/src/commands/Duplicate.js +4 -5
  30. package/dist/src/commands/Erase.d.ts +2 -2
  31. package/dist/src/commands/Erase.js +7 -6
  32. package/dist/src/commands/SerializableCommand.d.ts +4 -5
  33. package/dist/src/commands/SerializableCommand.js +12 -4
  34. package/dist/src/commands/invertCommand.d.ts +4 -0
  35. package/dist/src/commands/invertCommand.js +44 -0
  36. package/dist/src/commands/lib.d.ts +6 -0
  37. package/dist/src/commands/lib.js +6 -0
  38. package/dist/src/commands/localization.d.ts +2 -1
  39. package/dist/src/commands/localization.js +1 -0
  40. package/dist/src/components/AbstractComponent.d.ts +16 -11
  41. package/dist/src/components/AbstractComponent.js +28 -17
  42. package/dist/src/components/SVGGlobalAttributesObject.d.ts +4 -4
  43. package/dist/src/components/SVGGlobalAttributesObject.js +8 -2
  44. package/dist/src/components/Stroke.d.ts +16 -6
  45. package/dist/src/components/Stroke.js +12 -9
  46. package/dist/src/components/Text.d.ts +5 -5
  47. package/dist/src/components/Text.js +9 -9
  48. package/dist/src/components/UnknownSVGObject.d.ts +4 -4
  49. package/dist/src/components/UnknownSVGObject.js +7 -2
  50. package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
  51. package/dist/src/components/builders/ArrowBuilder.js +1 -1
  52. package/dist/src/components/builders/FreehandLineBuilder.d.ts +8 -3
  53. package/dist/src/components/builders/FreehandLineBuilder.js +142 -71
  54. package/dist/src/components/builders/LineBuilder.d.ts +1 -1
  55. package/dist/src/components/builders/LineBuilder.js +1 -1
  56. package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
  57. package/dist/src/components/builders/RectangleBuilder.js +3 -3
  58. package/dist/src/components/builders/types.d.ts +1 -1
  59. package/dist/src/components/lib.d.ts +4 -0
  60. package/dist/src/components/lib.js +4 -0
  61. package/dist/src/lib.d.ts +25 -0
  62. package/dist/src/lib.js +25 -0
  63. package/dist/src/localization.d.ts +1 -0
  64. package/dist/src/localization.js +5 -1
  65. package/dist/src/localizations/es.js +1 -1
  66. package/dist/src/{geometry → math}/LineSegment2.d.ts +0 -0
  67. package/dist/src/{geometry → math}/LineSegment2.js +0 -0
  68. package/dist/src/math/Mat33.d.ts +78 -0
  69. package/dist/src/{geometry → math}/Mat33.js +48 -20
  70. package/dist/src/{geometry → math}/Path.d.ts +2 -1
  71. package/dist/src/{geometry → math}/Path.js +59 -52
  72. package/dist/src/{geometry → math}/Rect2.d.ts +2 -2
  73. package/dist/src/{geometry → math}/Rect2.js +0 -0
  74. package/dist/src/{geometry → math}/Vec2.d.ts +0 -0
  75. package/dist/src/{geometry → math}/Vec2.js +0 -0
  76. package/dist/src/math/Vec3.d.ts +96 -0
  77. package/dist/src/{geometry → math}/Vec3.js +63 -15
  78. package/dist/src/math/lib.d.ts +7 -0
  79. package/dist/src/math/lib.js +7 -0
  80. package/dist/src/math/rounding.d.ts +3 -0
  81. package/dist/src/math/rounding.js +121 -0
  82. package/dist/src/rendering/Display.d.ts +47 -1
  83. package/dist/src/rendering/Display.js +60 -15
  84. package/dist/src/rendering/caching/CacheRecord.d.ts +3 -2
  85. package/dist/src/rendering/caching/CacheRecord.js +4 -1
  86. package/dist/src/rendering/caching/CacheRecordManager.d.ts +5 -4
  87. package/dist/src/rendering/caching/CacheRecordManager.js +16 -4
  88. package/dist/src/rendering/caching/RenderingCache.d.ts +2 -3
  89. package/dist/src/rendering/caching/RenderingCache.js +10 -11
  90. package/dist/src/rendering/caching/RenderingCacheNode.d.ts +2 -1
  91. package/dist/src/rendering/caching/RenderingCacheNode.js +18 -7
  92. package/dist/src/rendering/caching/testUtils.js +1 -1
  93. package/dist/src/rendering/caching/types.d.ts +2 -4
  94. package/dist/src/rendering/localization.d.ts +2 -0
  95. package/dist/src/rendering/localization.js +2 -0
  96. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +4 -4
  97. package/dist/src/rendering/renderers/AbstractRenderer.js +2 -2
  98. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +4 -4
  99. package/dist/src/rendering/renderers/CanvasRenderer.js +2 -2
  100. package/dist/src/rendering/renderers/DummyRenderer.d.ts +4 -4
  101. package/dist/src/rendering/renderers/DummyRenderer.js +1 -1
  102. package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -3
  103. package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
  104. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +5 -3
  105. package/dist/src/rendering/renderers/TextOnlyRenderer.js +13 -3
  106. package/dist/src/toolbar/HTMLToolbar.js +1 -0
  107. package/dist/src/toolbar/icons.d.ts +3 -0
  108. package/dist/src/toolbar/icons.js +142 -132
  109. package/dist/src/toolbar/localization.d.ts +2 -1
  110. package/dist/src/toolbar/localization.js +2 -1
  111. package/dist/src/toolbar/makeColorInput.js +3 -2
  112. package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +13 -0
  113. package/dist/src/toolbar/widgets/ActionButtonWidget.js +21 -0
  114. package/dist/src/toolbar/widgets/BaseWidget.js +2 -0
  115. package/dist/src/toolbar/widgets/HandToolWidget.js +3 -3
  116. package/dist/src/toolbar/widgets/PenWidget.js +1 -0
  117. package/dist/src/toolbar/widgets/SelectionWidget.d.ts +0 -1
  118. package/dist/src/toolbar/widgets/SelectionWidget.js +23 -30
  119. package/dist/src/tools/Eraser.js +1 -1
  120. package/dist/src/tools/PanZoom.d.ts +1 -1
  121. package/dist/src/tools/PanZoom.js +24 -14
  122. package/dist/src/tools/Pen.d.ts +1 -2
  123. package/dist/src/tools/Pen.js +8 -1
  124. package/dist/src/tools/PipetteTool.js +1 -0
  125. package/dist/src/tools/SelectionTool.d.ts +3 -3
  126. package/dist/src/tools/SelectionTool.js +51 -28
  127. package/dist/src/tools/TextTool.js +1 -1
  128. package/dist/src/types.d.ts +21 -10
  129. package/dist/src/types.js +7 -5
  130. package/firebase.json +16 -0
  131. package/package.json +118 -101
  132. package/src/Color4.ts +23 -2
  133. package/src/Editor.ts +181 -37
  134. package/src/EditorImage.test.ts +2 -4
  135. package/src/EditorImage.ts +46 -28
  136. package/src/EventDispatcher.ts +21 -6
  137. package/src/Pointer.ts +4 -3
  138. package/src/SVGLoader.ts +14 -6
  139. package/src/UndoRedoHistory.ts +18 -2
  140. package/src/Viewport.ts +23 -18
  141. package/src/bundle/bundled.ts +1 -2
  142. package/src/commands/Command.ts +5 -5
  143. package/src/commands/Duplicate.ts +4 -5
  144. package/src/commands/Erase.ts +7 -6
  145. package/src/commands/SerializableCommand.ts +17 -9
  146. package/src/commands/invertCommand.ts +51 -0
  147. package/src/commands/lib.ts +14 -0
  148. package/src/commands/localization.ts +3 -1
  149. package/src/components/AbstractComponent.ts +35 -24
  150. package/src/components/SVGGlobalAttributesObject.ts +11 -4
  151. package/src/components/Stroke.test.ts +4 -6
  152. package/src/components/Stroke.ts +15 -11
  153. package/src/components/Text.test.ts +2 -2
  154. package/src/components/Text.ts +9 -10
  155. package/src/components/UnknownSVGObject.ts +10 -4
  156. package/src/components/builders/ArrowBuilder.ts +2 -2
  157. package/src/components/builders/FreehandLineBuilder.ts +190 -80
  158. package/src/components/builders/LineBuilder.ts +2 -2
  159. package/src/components/builders/RectangleBuilder.ts +3 -3
  160. package/src/components/builders/types.ts +1 -1
  161. package/src/components/lib.ts +9 -0
  162. package/src/lib.ts +28 -0
  163. package/src/localization.ts +6 -0
  164. package/src/localizations/es.ts +2 -1
  165. package/src/{geometry → math}/LineSegment2.test.ts +0 -0
  166. package/src/{geometry → math}/LineSegment2.ts +0 -0
  167. package/src/{geometry → math}/Mat33.test.ts +0 -0
  168. package/src/{geometry → math}/Mat33.ts +48 -20
  169. package/src/{geometry → math}/Path.fromString.test.ts +0 -0
  170. package/src/{geometry → math}/Path.test.ts +0 -0
  171. package/src/{geometry → math}/Path.toString.test.ts +11 -2
  172. package/src/{geometry → math}/Path.ts +61 -58
  173. package/src/{geometry → math}/Rect2.test.ts +0 -0
  174. package/src/{geometry → math}/Rect2.ts +2 -2
  175. package/src/{geometry → math}/Vec2.test.ts +0 -0
  176. package/src/{geometry → math}/Vec2.ts +0 -0
  177. package/src/{geometry → math}/Vec3.test.ts +0 -0
  178. package/src/{geometry → math}/Vec3.ts +64 -16
  179. package/src/math/lib.ts +15 -0
  180. package/src/math/rounding.test.ts +40 -0
  181. package/src/math/rounding.ts +147 -0
  182. package/src/rendering/Display.ts +63 -15
  183. package/src/rendering/caching/CacheRecord.test.ts +3 -3
  184. package/src/rendering/caching/CacheRecord.ts +6 -2
  185. package/src/rendering/caching/CacheRecordManager.ts +34 -8
  186. package/src/rendering/caching/RenderingCache.test.ts +3 -3
  187. package/src/rendering/caching/RenderingCache.ts +11 -16
  188. package/src/rendering/caching/RenderingCacheNode.ts +23 -7
  189. package/src/rendering/caching/testUtils.ts +1 -1
  190. package/src/rendering/caching/types.ts +2 -7
  191. package/src/rendering/localization.ts +4 -0
  192. package/src/rendering/renderers/AbstractRenderer.ts +4 -4
  193. package/src/rendering/renderers/CanvasRenderer.ts +5 -5
  194. package/src/rendering/renderers/DummyRenderer.test.ts +2 -2
  195. package/src/rendering/renderers/DummyRenderer.ts +4 -4
  196. package/src/rendering/renderers/SVGRenderer.ts +10 -4
  197. package/src/rendering/renderers/TextOnlyRenderer.ts +17 -6
  198. package/src/toolbar/HTMLToolbar.ts +1 -0
  199. package/src/toolbar/icons.ts +157 -137
  200. package/src/toolbar/localization.ts +4 -2
  201. package/src/toolbar/makeColorInput.ts +3 -2
  202. package/src/toolbar/toolbar.css +1 -1
  203. package/src/toolbar/widgets/ActionButtonWidget.ts +31 -0
  204. package/src/toolbar/widgets/BaseWidget.ts +2 -0
  205. package/src/toolbar/widgets/HandToolWidget.ts +3 -3
  206. package/src/toolbar/widgets/PenWidget.ts +2 -0
  207. package/src/toolbar/widgets/SelectionWidget.ts +46 -41
  208. package/src/tools/Eraser.ts +2 -2
  209. package/src/tools/PanZoom.ts +28 -17
  210. package/src/tools/Pen.ts +11 -2
  211. package/src/tools/PipetteTool.ts +2 -0
  212. package/src/tools/SelectionTool.test.ts +2 -4
  213. package/src/tools/SelectionTool.ts +52 -24
  214. package/src/tools/TextTool.ts +2 -2
  215. package/src/tools/UndoRedoShortcut.test.ts +1 -1
  216. package/src/types.ts +23 -7
  217. package/tsconfig.json +4 -1
  218. package/typedoc.json +20 -0
  219. package/dist/src/geometry/Mat33.d.ts +0 -32
  220. package/dist/src/geometry/Vec3.d.ts +0 -34
@@ -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
+ // @param 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;
@@ -279,7 +286,7 @@ export default class Path {
279
286
  }
280
287
  // Create a Path from a SVG path specification.
281
288
  // TODO: Support a larger subset of SVG paths.
282
- // TODO: Support s,t shorthands.
289
+ // TODO: Support `s`,`t` commands shorthands.
283
290
  static fromString(pathString) {
284
291
  // See the MDN reference:
285
292
  // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
@@ -1,7 +1,8 @@
1
1
  import LineSegment2 from './LineSegment2';
2
2
  import Mat33 from './Mat33';
3
3
  import { Point2, Vec2 } from './Vec2';
4
- interface RectTemplate {
4
+ /** An object that can be converted to a Rect2. */
5
+ export interface RectTemplate {
5
6
  x: number;
6
7
  y: number;
7
8
  w?: number;
@@ -48,4 +49,3 @@ export default class Rect2 {
48
49
  static empty: Rect2;
49
50
  static unitSquare: Rect2;
50
51
  }
51
- export {};
File without changes
File without changes
File without changes
@@ -0,0 +1,96 @@
1
+ /**
2
+ * A vector with three components. Can also be used to represent a two-component vector.
3
+ *
4
+ * A `Vec3` is immutable.
5
+ */
6
+ export default class Vec3 {
7
+ readonly x: number;
8
+ readonly y: number;
9
+ readonly z: number;
10
+ private constructor();
11
+ /** Returns the x, y components of this. */
12
+ get xy(): {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ static of(x: number, y: number, z: number): Vec3;
17
+ /** Returns this' `idx`th component. For example, `Vec3.of(1, 2, 3).at(1) → 2`. */
18
+ at(idx: number): number;
19
+ /** Alias for this.magnitude. */
20
+ length(): number;
21
+ magnitude(): number;
22
+ magnitudeSquared(): number;
23
+ /**
24
+ * Return this' angle in the XY plane (treats this as a Vec2).
25
+ *
26
+ * This is equivalent to `Math.atan2(vec.y, vec.x)`.
27
+ */
28
+ angle(): number;
29
+ /**
30
+ * Returns a unit vector in the same direction as this.
31
+ *
32
+ * If `this` has zero length, the resultant vector has `NaN` components.
33
+ */
34
+ normalized(): Vec3;
35
+ /** @returns A copy of `this` multiplied by a scalar. */
36
+ times(c: number): Vec3;
37
+ plus(v: Vec3): Vec3;
38
+ minus(v: Vec3): Vec3;
39
+ dot(other: Vec3): number;
40
+ cross(other: Vec3): Vec3;
41
+ /**
42
+ * Returns a vector orthogonal to this. If this is a Vec2, returns `this` rotated
43
+ * 90 degrees counter-clockwise.
44
+ */
45
+ orthog(): Vec3;
46
+ /** Returns this plus a vector of length `distance` in `direction`. */
47
+ extend(distance: number, direction: Vec3): Vec3;
48
+ /** Returns a vector `fractionTo` of the way to target from this. */
49
+ lerp(target: Vec3, fractionTo: number): Vec3;
50
+ /**
51
+ * `zip` Maps a component of this and a corresponding component of
52
+ * `other` to a component of the output vector.
53
+ *
54
+ * @example
55
+ * ```
56
+ * const a = Vec3.of(1, 2, 3);
57
+ * const b = Vec3.of(0.5, 2.1, 2.9);
58
+ *
59
+ * const zipped = a.zip(b, (aComponent, bComponent) => {
60
+ * return Math.min(aComponent, bComponent);
61
+ * });
62
+ *
63
+ * console.log(zipped.toString()); // → Vec(0.5, 2, 2.9)
64
+ * ```
65
+ */
66
+ zip(other: Vec3, zip: (componentInThis: number, componentInOther: number) => number): Vec3;
67
+ /**
68
+ * Returns a vector with each component acted on by `fn`.
69
+ *
70
+ * @example
71
+ * ```
72
+ * console.log(Vec3.of(1, 2, 3).map(val => val + 1)); // → Vec(2, 3, 4)
73
+ * ```
74
+ */
75
+ map(fn: (component: number, index: number) => number): Vec3;
76
+ asArray(): number[];
77
+ /**
78
+ * [fuzz] The maximum difference between two components for this and [other]
79
+ * to be considered equal.
80
+ *
81
+ * @example
82
+ * ```
83
+ * Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 100); // → true
84
+ * Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 0.1); // → false
85
+ * Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 3); // → true
86
+ * Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 3.01); // → true
87
+ * Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 2.99); // → false
88
+ * ```
89
+ */
90
+ eq(other: Vec3, fuzz: number): boolean;
91
+ toString(): string;
92
+ static unitX: Vec3;
93
+ static unitY: Vec3;
94
+ static unitZ: Vec3;
95
+ static zero: Vec3;
96
+ }
@@ -1,11 +1,15 @@
1
- // A vector with three components. Can also be used to represent a two-component vector
1
+ /**
2
+ * A vector with three components. Can also be used to represent a two-component vector.
3
+ *
4
+ * A `Vec3` is immutable.
5
+ */
2
6
  export default class Vec3 {
3
7
  constructor(x, y, z) {
4
8
  this.x = x;
5
9
  this.y = y;
6
10
  this.z = z;
7
11
  }
8
- // Returns the x, y components of this
12
+ /** Returns the x, y components of this. */
9
13
  get xy() {
10
14
  // Useful for APIs that behave differently if .z is present.
11
15
  return {
@@ -16,7 +20,7 @@ export default class Vec3 {
16
20
  static of(x, y, z) {
17
21
  return new Vec3(x, y, z);
18
22
  }
19
- // Returns this' [idx]th component
23
+ /** Returns this' `idx`th component. For example, `Vec3.of(1, 2, 3).at(1) → 2`. */
20
24
  at(idx) {
21
25
  if (idx === 0)
22
26
  return this.x;
@@ -26,7 +30,7 @@ export default class Vec3 {
26
30
  return this.z;
27
31
  throw new Error(`${idx} out of bounds!`);
28
32
  }
29
- // Alias for this.magnitude
33
+ /** Alias for this.magnitude. */
30
34
  length() {
31
35
  return this.magnitude();
32
36
  }
@@ -36,14 +40,24 @@ export default class Vec3 {
36
40
  magnitudeSquared() {
37
41
  return this.dot(this);
38
42
  }
39
- // Return this' angle in the XY plane (treats this as a Vec2)
43
+ /**
44
+ * Return this' angle in the XY plane (treats this as a Vec2).
45
+ *
46
+ * This is equivalent to `Math.atan2(vec.y, vec.x)`.
47
+ */
40
48
  angle() {
41
49
  return Math.atan2(this.y, this.x);
42
50
  }
51
+ /**
52
+ * Returns a unit vector in the same direction as this.
53
+ *
54
+ * If `this` has zero length, the resultant vector has `NaN` components.
55
+ */
43
56
  normalized() {
44
57
  const norm = this.magnitude();
45
58
  return Vec3.of(this.x / norm, this.y / norm, this.z / norm);
46
59
  }
60
+ /** @returns A copy of `this` multiplied by a scalar. */
47
61
  times(c) {
48
62
  return Vec3.of(this.x * c, this.y * c, this.z * c);
49
63
  }
@@ -62,8 +76,10 @@ export default class Vec3 {
62
76
  // | x2 y2 z2|
63
77
  return Vec3.of(this.y * other.z - other.y * this.z, other.x * this.z - this.x * other.z, this.x * other.y - other.x * this.y);
64
78
  }
65
- // Returns a vector orthogonal to this. If this is a Vec2, returns [this] rotated
66
- // 90 degrees counter-clockwise.
79
+ /**
80
+ * Returns a vector orthogonal to this. If this is a Vec2, returns `this` rotated
81
+ * 90 degrees counter-clockwise.
82
+ */
67
83
  orthog() {
68
84
  // If parallel to the z-axis
69
85
  if (this.dot(Vec3.unitX) === 0 && this.dot(Vec3.unitY) === 0) {
@@ -71,28 +87,60 @@ export default class Vec3 {
71
87
  }
72
88
  return this.cross(Vec3.unitZ.times(-1)).normalized();
73
89
  }
74
- // Returns this plus a vector of length [distance] in [direction]
90
+ /** Returns this plus a vector of length `distance` in `direction`. */
75
91
  extend(distance, direction) {
76
92
  return this.plus(direction.normalized().times(distance));
77
93
  }
78
- // Returns a vector [fractionTo] of the way to target from this.
94
+ /** Returns a vector `fractionTo` of the way to target from this. */
79
95
  lerp(target, fractionTo) {
80
96
  return this.times(1 - fractionTo).plus(target.times(fractionTo));
81
97
  }
82
- // [zip] Maps a component of this and a corresponding component of
83
- // [other] to a component of the output vector.
98
+ /**
99
+ * `zip` Maps a component of this and a corresponding component of
100
+ * `other` to a component of the output vector.
101
+ *
102
+ * @example
103
+ * ```
104
+ * const a = Vec3.of(1, 2, 3);
105
+ * const b = Vec3.of(0.5, 2.1, 2.9);
106
+ *
107
+ * const zipped = a.zip(b, (aComponent, bComponent) => {
108
+ * return Math.min(aComponent, bComponent);
109
+ * });
110
+ *
111
+ * console.log(zipped.toString()); // → Vec(0.5, 2, 2.9)
112
+ * ```
113
+ */
84
114
  zip(other, zip) {
85
115
  return Vec3.of(zip(other.x, this.x), zip(other.y, this.y), zip(other.z, this.z));
86
116
  }
87
- // Returns a vector with each component acted on by [fn]
117
+ /**
118
+ * Returns a vector with each component acted on by `fn`.
119
+ *
120
+ * @example
121
+ * ```
122
+ * console.log(Vec3.of(1, 2, 3).map(val => val + 1)); // → Vec(2, 3, 4)
123
+ * ```
124
+ */
88
125
  map(fn) {
89
- return Vec3.of(fn(this.x), fn(this.y), fn(this.z));
126
+ return Vec3.of(fn(this.x, 0), fn(this.y, 1), fn(this.z, 2));
90
127
  }
91
128
  asArray() {
92
129
  return [this.x, this.y, this.z];
93
130
  }
94
- // [fuzz] The maximum difference between two components for this and [other]
95
- // to be considered equal.
131
+ /**
132
+ * [fuzz] The maximum difference between two components for this and [other]
133
+ * to be considered equal.
134
+ *
135
+ * @example
136
+ * ```
137
+ * Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 100); // → true
138
+ * Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 0.1); // → false
139
+ * Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 3); // → true
140
+ * Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 3.01); // → true
141
+ * Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 2.99); // → false
142
+ * ```
143
+ */
96
144
  eq(other, fuzz) {
97
145
  for (let i = 0; i < 3; i++) {
98
146
  if (Math.abs(other.at(i) - this.at(i)) > fuzz) {
@@ -0,0 +1,7 @@
1
+ import LineSegment2 from './LineSegment2';
2
+ import Mat33 from './Mat33';
3
+ import Path from './Path';
4
+ import Rect2 from './Rect2';
5
+ import { Vec2 } from './Vec2';
6
+ import Vec3 from './Vec3';
7
+ export { LineSegment2, Mat33, Path, Rect2, Vec3, Vec2, };
@@ -0,0 +1,7 @@
1
+ import LineSegment2 from './LineSegment2';
2
+ import Mat33 from './Mat33';
3
+ import Path from './Path';
4
+ import Rect2 from './Rect2';
5
+ import { Vec2 } from './Vec2';
6
+ import Vec3 from './Vec3';
7
+ export { LineSegment2, Mat33, Path, Rect2, Vec3, Vec2, };
@@ -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;
@@ -0,0 +1,121 @@
1
+ // @packageDocumentation @internal
2
+ // Clean up stringified numbers
3
+ const cleanUpNumber = (text) => {
4
+ // Regular expression substitions can be somewhat expensive. Only do them
5
+ // if necessary.
6
+ const lastChar = text.charAt(text.length - 1);
7
+ if (lastChar === '0' || lastChar === '.') {
8
+ // Remove trailing zeroes
9
+ text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
10
+ text = text.replace(/[.]0+$/, '.');
11
+ // Remove trailing period
12
+ text = text.replace(/[.]$/, '');
13
+ if (text === '-0') {
14
+ return '0';
15
+ }
16
+ }
17
+ const firstChar = text.charAt(0);
18
+ if (firstChar === '0' || firstChar === '-') {
19
+ // Remove unnecessary leading zeroes.
20
+ text = text.replace(/^(0+)[.]/, '.');
21
+ text = text.replace(/^-(0+)[.]/, '-.');
22
+ }
23
+ return text;
24
+ };
25
+ export const toRoundedString = (num) => {
26
+ // Try to remove rounding errors. If the number ends in at least three/four zeroes
27
+ // (or nines) just one or two digits, it's probably a rounding error.
28
+ const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d{1,2}$/;
29
+ const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,})\d{1,2}$/;
30
+ let text = num.toString(10);
31
+ if (text.indexOf('.') === -1) {
32
+ return text;
33
+ }
34
+ const roundingDownMatch = hasRoundingDownExp.exec(text);
35
+ if (roundingDownMatch) {
36
+ const negativeSign = roundingDownMatch[1];
37
+ const postDecimalString = roundingDownMatch[3];
38
+ const lastDigit = parseInt(postDecimalString.charAt(postDecimalString.length - 1), 10);
39
+ const postDecimal = parseInt(postDecimalString, 10);
40
+ const preDecimal = parseInt(roundingDownMatch[2], 10);
41
+ const origPostDecimalString = roundingDownMatch[3];
42
+ let newPostDecimal = (postDecimal + 10 - lastDigit).toString();
43
+ let carry = 0;
44
+ if (newPostDecimal.length > postDecimal.toString().length) {
45
+ // Left-shift
46
+ newPostDecimal = newPostDecimal.substring(1);
47
+ carry = 1;
48
+ }
49
+ // parseInt(...).toString() removes leading zeroes. Add them back.
50
+ while (newPostDecimal.length < origPostDecimalString.length) {
51
+ newPostDecimal = carry.toString(10) + newPostDecimal;
52
+ carry = 0;
53
+ }
54
+ text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
55
+ }
56
+ text = text.replace(fixRoundingUpExp, '$1');
57
+ return cleanUpNumber(text);
58
+ };
59
+ const numberExp = /^([-]?)(\d*)[.](\d+)$/;
60
+ export const getLenAfterDecimal = (numberAsString) => {
61
+ const numberMatch = numberExp.exec(numberAsString);
62
+ if (!numberMatch) {
63
+ // If not a match, either the number is exponential notation (or is something
64
+ // like NaN or Infinity)
65
+ if (numberAsString.search(/[eE]/) !== -1 || /^[a-zA-Z]+$/.exec(numberAsString)) {
66
+ return -1;
67
+ // Or it has no decimal point
68
+ }
69
+ else {
70
+ return 0;
71
+ }
72
+ }
73
+ const afterDecimalLen = numberMatch[3].length;
74
+ return afterDecimalLen;
75
+ };
76
+ // [reference] should be a string representation of a base-10 number (no exponential (e.g. 10e10))
77
+ export const toStringOfSamePrecision = (num, ...references) => {
78
+ const text = num.toString(10);
79
+ const textMatch = numberExp.exec(text);
80
+ if (!textMatch) {
81
+ return text;
82
+ }
83
+ let decimalPlaces = -1;
84
+ for (const reference of references) {
85
+ decimalPlaces = Math.max(getLenAfterDecimal(reference), decimalPlaces);
86
+ }
87
+ if (decimalPlaces === -1) {
88
+ return toRoundedString(num);
89
+ }
90
+ // Make text's after decimal length match [afterDecimalLen].
91
+ let postDecimal = textMatch[3].substring(0, decimalPlaces);
92
+ let preDecimal = textMatch[2];
93
+ const nextDigit = textMatch[3].charAt(decimalPlaces);
94
+ if (nextDigit !== '') {
95
+ const asNumber = parseInt(nextDigit, 10);
96
+ if (asNumber >= 5) {
97
+ // Don't attempt to parseInt() an empty string.
98
+ if (postDecimal.length > 0) {
99
+ const leadingZeroMatch = /^(0+)(\d*)$/.exec(postDecimal);
100
+ let leadingZeroes = '';
101
+ let postLeading = postDecimal;
102
+ if (leadingZeroMatch) {
103
+ leadingZeroes = leadingZeroMatch[1];
104
+ postLeading = leadingZeroMatch[2];
105
+ }
106
+ postDecimal = (parseInt(postDecimal) + 1).toString();
107
+ // If postDecimal got longer, remove leading zeroes if possible
108
+ if (postDecimal.length > postLeading.length && leadingZeroes.length > 0) {
109
+ leadingZeroes = leadingZeroes.substring(1);
110
+ }
111
+ postDecimal = leadingZeroes + postDecimal;
112
+ }
113
+ if (postDecimal.length === 0 || postDecimal.length > decimalPlaces) {
114
+ preDecimal = (parseInt(preDecimal) + 1).toString();
115
+ postDecimal = postDecimal.substring(1);
116
+ }
117
+ }
118
+ }
119
+ const negativeSign = textMatch[1];
120
+ return cleanUpNumber(`${negativeSign}${preDecimal}.${postDecimal}`);
121
+ };
@@ -1,6 +1,20 @@
1
+ /**
2
+ * Handles `HTMLCanvasElement`s (or other drawing surfaces if being used) used to display the editor's contents.
3
+ *
4
+ * @example
5
+ * ```
6
+ * const editor = new Editor(document.body);
7
+ * const w = editor.display.width;
8
+ * const h = editor.display.height;
9
+ * const center = Vec2.of(w / 2, h / 2);
10
+ * const colorAtCenter = editor.display.getColorAt(center);
11
+ * ```
12
+ *
13
+ * @packageDocumentation
14
+ */
1
15
  import AbstractRenderer from './renderers/AbstractRenderer';
2
16
  import { Editor } from '../Editor';
3
- import { Point2 } from '../geometry/Vec2';
17
+ import { Point2 } from '../math/Vec2';
4
18
  import RenderingCache from './caching/RenderingCache';
5
19
  import Color4 from '../Color4';
6
20
  export declare enum RenderingMode {
@@ -13,19 +27,51 @@ export default class Display {
13
27
  private dryInkRenderer;
14
28
  private wetInkRenderer;
15
29
  private textRenderer;
30
+ private textRerenderOutput;
16
31
  private cache;
17
32
  private resizeSurfacesCallback?;
18
33
  private flattenCallback?;
34
+ /** @internal */
19
35
  constructor(editor: Editor, mode: RenderingMode, parent: HTMLElement | null);
36
+ /**
37
+ * @returns the visible width of the display (e.g. how much
38
+ * space the display's element takes up in the x direction
39
+ * in the DOM).
40
+ */
20
41
  get width(): number;
21
42
  get height(): number;
43
+ /** @internal */
22
44
  getCache(): RenderingCache;
45
+ /**
46
+ * @returns the color at the given point on the dry ink renderer, or `null` if `screenPos`
47
+ * is not on the display.
48
+ */
23
49
  getColorAt: (_screenPos: Point2) => Color4 | null;
24
50
  private initializeCanvasRendering;
25
51
  private initializeTextRendering;
52
+ /**
53
+ * Rerenders the text-based display.
54
+ * The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
55
+ */
56
+ rerenderAsText(): void;
57
+ /**
58
+ * Clears the drawing surfaces and otherwise prepares for a rerender.
59
+ *
60
+ * @returns the dry ink renderer.
61
+ */
26
62
  startRerender(): AbstractRenderer;
63
+ /**
64
+ * If `draftMode`, the dry ink renderer is configured to render
65
+ * low-quality output.
66
+ */
27
67
  setDraftMode(draftMode: boolean): void;
68
+ /** @internal */
28
69
  getDryInkRenderer(): AbstractRenderer;
70
+ /**
71
+ * @returns The renderer used for showing action previews (e.g. an unfinished stroke).
72
+ * The `wetInkRenderer`'s surface is stacked above the `dryInkRenderer`'s.
73
+ */
29
74
  getWetInkRenderer(): AbstractRenderer;
75
+ /** Re-renders the contents of the wetInkRenderer onto the dryInkRenderer. */
30
76
  flatten(): void;
31
77
  }