js-draw 0.18.2 → 0.20.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 (269) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +10 -0
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -0
  5. package/dist/cjs/src/Color4.d.ts +8 -0
  6. package/dist/cjs/src/Color4.js +67 -0
  7. package/dist/cjs/src/Editor.d.ts +2 -2
  8. package/dist/cjs/src/Editor.js +7 -7
  9. package/dist/cjs/src/SVGLoader.js +77 -12
  10. package/dist/cjs/src/Viewport.d.ts +2 -0
  11. package/dist/cjs/src/Viewport.js +6 -2
  12. package/dist/cjs/src/components/AbstractComponent.d.ts +2 -2
  13. package/dist/cjs/src/components/AbstractComponent.js +3 -3
  14. package/dist/cjs/src/components/{ImageBackground.d.ts → BackgroundComponent.d.ts} +23 -3
  15. package/dist/cjs/src/components/BackgroundComponent.js +309 -0
  16. package/dist/cjs/src/components/RestylableComponent.d.ts +21 -2
  17. package/dist/cjs/src/components/Stroke.d.ts +35 -0
  18. package/dist/cjs/src/components/Stroke.js +37 -3
  19. package/dist/cjs/src/components/TextComponent.d.ts +27 -17
  20. package/dist/cjs/src/components/TextComponent.js +23 -1
  21. package/dist/cjs/src/components/lib.d.ts +4 -3
  22. package/dist/cjs/src/components/lib.js +2 -2
  23. package/dist/cjs/src/components/util/StrokeSmoother.js +25 -15
  24. package/dist/cjs/src/lib.d.ts +30 -0
  25. package/dist/cjs/src/lib.js +30 -0
  26. package/dist/cjs/src/localizations/de.js +1 -1
  27. package/dist/cjs/src/localizations/es.js +1 -1
  28. package/dist/cjs/src/math/Path.js +1 -1
  29. package/dist/cjs/src/math/polynomial/QuadraticBezier.d.ts +28 -0
  30. package/dist/cjs/src/math/polynomial/QuadraticBezier.js +115 -0
  31. package/dist/cjs/src/math/polynomial/solveQuadratic.d.ts +6 -0
  32. package/dist/cjs/src/math/polynomial/solveQuadratic.js +36 -0
  33. package/dist/cjs/src/rendering/RenderingStyle.d.ts +4 -4
  34. package/dist/cjs/src/rendering/TextRenderingStyle.d.ts +10 -10
  35. package/dist/cjs/src/rendering/lib.d.ts +2 -0
  36. package/dist/cjs/src/rendering/renderers/AbstractRenderer.d.ts +2 -2
  37. package/dist/cjs/src/rendering/renderers/CanvasRenderer.d.ts +2 -2
  38. package/dist/cjs/src/rendering/renderers/CanvasRenderer.js +5 -3
  39. package/dist/cjs/src/rendering/renderers/DummyRenderer.d.ts +2 -2
  40. package/dist/cjs/src/rendering/renderers/SVGRenderer.d.ts +2 -2
  41. package/dist/cjs/src/rendering/renderers/SVGRenderer.js +15 -6
  42. package/dist/cjs/src/rendering/renderers/TextOnlyRenderer.d.ts +2 -2
  43. package/dist/cjs/src/toolbar/IconProvider.d.ts +2 -2
  44. package/dist/cjs/src/toolbar/localization.d.ts +2 -1
  45. package/dist/cjs/src/toolbar/localization.js +3 -2
  46. package/dist/cjs/src/toolbar/widgets/BaseWidget.js +1 -1
  47. package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +5 -0
  48. package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.js +77 -2
  49. package/dist/cjs/src/toolbar/widgets/PenToolWidget.js +1 -1
  50. package/dist/cjs/src/tools/FindTool.js +1 -1
  51. package/dist/cjs/src/tools/SoundUITool.d.ts +24 -0
  52. package/dist/cjs/src/tools/SoundUITool.js +164 -0
  53. package/dist/cjs/src/tools/TextTool.d.ts +2 -2
  54. package/dist/cjs/src/tools/ToolController.js +6 -1
  55. package/dist/cjs/src/tools/lib.d.ts +1 -0
  56. package/dist/cjs/src/tools/lib.js +3 -1
  57. package/dist/cjs/src/tools/localization.d.ts +3 -0
  58. package/dist/cjs/src/tools/localization.js +3 -0
  59. package/dist/mjs/src/Color4.d.ts +8 -0
  60. package/dist/mjs/src/Color4.mjs +64 -0
  61. package/dist/mjs/src/Editor.d.ts +2 -2
  62. package/dist/mjs/src/Editor.mjs +6 -6
  63. package/dist/mjs/src/SVGLoader.mjs +76 -11
  64. package/dist/mjs/src/Viewport.d.ts +2 -0
  65. package/dist/mjs/src/Viewport.mjs +6 -2
  66. package/dist/mjs/src/components/AbstractComponent.d.ts +2 -2
  67. package/dist/mjs/src/components/AbstractComponent.mjs +3 -3
  68. package/dist/mjs/src/components/{ImageBackground.d.ts → BackgroundComponent.d.ts} +23 -3
  69. package/dist/mjs/src/components/BackgroundComponent.mjs +279 -0
  70. package/dist/mjs/src/components/RestylableComponent.d.ts +21 -2
  71. package/dist/mjs/src/components/Stroke.d.ts +35 -0
  72. package/dist/mjs/src/components/Stroke.mjs +37 -3
  73. package/dist/mjs/src/components/TextComponent.d.ts +27 -17
  74. package/dist/mjs/src/components/TextComponent.mjs +23 -1
  75. package/dist/mjs/src/components/lib.d.ts +4 -3
  76. package/dist/mjs/src/components/lib.mjs +2 -2
  77. package/dist/mjs/src/components/util/StrokeSmoother.mjs +25 -15
  78. package/dist/mjs/src/lib.d.ts +30 -0
  79. package/dist/mjs/src/lib.mjs +30 -0
  80. package/dist/mjs/src/localizations/de.mjs +1 -1
  81. package/dist/mjs/src/localizations/es.mjs +1 -1
  82. package/dist/mjs/src/math/Path.mjs +1 -1
  83. package/dist/mjs/src/math/polynomial/QuadraticBezier.d.ts +28 -0
  84. package/dist/mjs/src/math/polynomial/QuadraticBezier.mjs +109 -0
  85. package/dist/mjs/src/math/polynomial/solveQuadratic.d.ts +6 -0
  86. package/dist/mjs/src/math/polynomial/solveQuadratic.mjs +34 -0
  87. package/dist/mjs/src/rendering/RenderingStyle.d.ts +4 -4
  88. package/dist/mjs/src/rendering/TextRenderingStyle.d.ts +10 -10
  89. package/dist/mjs/src/rendering/lib.d.ts +2 -0
  90. package/dist/mjs/src/rendering/renderers/AbstractRenderer.d.ts +2 -2
  91. package/dist/mjs/src/rendering/renderers/CanvasRenderer.d.ts +2 -2
  92. package/dist/mjs/src/rendering/renderers/CanvasRenderer.mjs +5 -3
  93. package/dist/mjs/src/rendering/renderers/DummyRenderer.d.ts +2 -2
  94. package/dist/mjs/src/rendering/renderers/SVGRenderer.d.ts +2 -2
  95. package/dist/mjs/src/rendering/renderers/SVGRenderer.mjs +15 -6
  96. package/dist/mjs/src/rendering/renderers/TextOnlyRenderer.d.ts +2 -2
  97. package/dist/mjs/src/toolbar/IconProvider.d.ts +2 -2
  98. package/dist/mjs/src/toolbar/localization.d.ts +2 -1
  99. package/dist/mjs/src/toolbar/localization.mjs +3 -2
  100. package/dist/mjs/src/toolbar/widgets/BaseWidget.mjs +1 -1
  101. package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +5 -0
  102. package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.mjs +54 -2
  103. package/dist/mjs/src/toolbar/widgets/PenToolWidget.mjs +1 -1
  104. package/dist/mjs/src/tools/FindTool.mjs +1 -1
  105. package/dist/mjs/src/tools/SoundUITool.d.ts +24 -0
  106. package/dist/mjs/src/tools/SoundUITool.mjs +158 -0
  107. package/dist/mjs/src/tools/TextTool.d.ts +2 -2
  108. package/dist/mjs/src/tools/ToolController.mjs +6 -1
  109. package/dist/mjs/src/tools/lib.d.ts +1 -0
  110. package/dist/mjs/src/tools/lib.mjs +1 -0
  111. package/dist/mjs/src/tools/localization.d.ts +3 -0
  112. package/dist/mjs/src/tools/localization.mjs +3 -0
  113. package/jest.config.js +1 -1
  114. package/package.json +19 -17
  115. package/src/Editor.css +2 -2
  116. package/src/tools/SoundUITool.css +15 -0
  117. package/src/tools/tools.css +4 -0
  118. package/dist/cjs/src/components/ImageBackground.js +0 -146
  119. package/dist/mjs/src/components/ImageBackground.mjs +0 -139
  120. package/src/Color4.test.ts +0 -40
  121. package/src/Color4.ts +0 -236
  122. package/src/Editor.loadFrom.test.ts +0 -24
  123. package/src/Editor.toSVG.test.ts +0 -111
  124. package/src/Editor.ts +0 -1122
  125. package/src/EditorImage.test.ts +0 -120
  126. package/src/EditorImage.ts +0 -603
  127. package/src/EventDispatcher.test.ts +0 -123
  128. package/src/EventDispatcher.ts +0 -71
  129. package/src/Pointer.ts +0 -127
  130. package/src/SVGLoader.test.ts +0 -114
  131. package/src/SVGLoader.ts +0 -511
  132. package/src/UndoRedoHistory.test.ts +0 -33
  133. package/src/UndoRedoHistory.ts +0 -102
  134. package/src/Viewport.ts +0 -319
  135. package/src/bundle/bundled.ts +0 -7
  136. package/src/commands/Command.ts +0 -45
  137. package/src/commands/Duplicate.ts +0 -48
  138. package/src/commands/Erase.ts +0 -74
  139. package/src/commands/SerializableCommand.ts +0 -49
  140. package/src/commands/UnresolvedCommand.ts +0 -37
  141. package/src/commands/invertCommand.ts +0 -51
  142. package/src/commands/lib.ts +0 -16
  143. package/src/commands/localization.ts +0 -47
  144. package/src/commands/uniteCommands.test.ts +0 -23
  145. package/src/commands/uniteCommands.ts +0 -135
  146. package/src/components/AbstractComponent.transformBy.test.ts +0 -22
  147. package/src/components/AbstractComponent.ts +0 -364
  148. package/src/components/ImageBackground.test.ts +0 -35
  149. package/src/components/ImageBackground.ts +0 -176
  150. package/src/components/ImageComponent.ts +0 -171
  151. package/src/components/RestylableComponent.ts +0 -142
  152. package/src/components/SVGGlobalAttributesObject.ts +0 -81
  153. package/src/components/Stroke.test.ts +0 -139
  154. package/src/components/Stroke.ts +0 -245
  155. package/src/components/TextComponent.test.ts +0 -99
  156. package/src/components/TextComponent.ts +0 -315
  157. package/src/components/UnknownSVGObject.test.ts +0 -10
  158. package/src/components/UnknownSVGObject.ts +0 -60
  159. package/src/components/builders/ArrowBuilder.ts +0 -107
  160. package/src/components/builders/FreehandLineBuilder.ts +0 -212
  161. package/src/components/builders/LineBuilder.ts +0 -77
  162. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -454
  163. package/src/components/builders/RectangleBuilder.ts +0 -74
  164. package/src/components/builders/types.ts +0 -15
  165. package/src/components/lib.ts +0 -25
  166. package/src/components/localization.ts +0 -22
  167. package/src/components/util/StrokeSmoother.ts +0 -293
  168. package/src/components/util/describeComponentList.ts +0 -18
  169. package/src/lib.ts +0 -37
  170. package/src/localization.ts +0 -34
  171. package/src/localizations/de.ts +0 -98
  172. package/src/localizations/en.ts +0 -8
  173. package/src/localizations/es.ts +0 -74
  174. package/src/localizations/getLocalizationTable.test.ts +0 -27
  175. package/src/localizations/getLocalizationTable.ts +0 -55
  176. package/src/math/LineSegment2.test.ts +0 -99
  177. package/src/math/LineSegment2.ts +0 -160
  178. package/src/math/Mat33.test.ts +0 -244
  179. package/src/math/Mat33.ts +0 -437
  180. package/src/math/Path.fromString.test.ts +0 -223
  181. package/src/math/Path.test.ts +0 -198
  182. package/src/math/Path.toString.test.ts +0 -77
  183. package/src/math/Path.ts +0 -790
  184. package/src/math/Rect2.test.ts +0 -204
  185. package/src/math/Rect2.ts +0 -315
  186. package/src/math/Triangle.ts +0 -29
  187. package/src/math/Vec2.test.ts +0 -30
  188. package/src/math/Vec2.ts +0 -18
  189. package/src/math/Vec3.test.ts +0 -44
  190. package/src/math/Vec3.ts +0 -218
  191. package/src/math/lib.ts +0 -15
  192. package/src/math/rounding.test.ts +0 -65
  193. package/src/math/rounding.ts +0 -156
  194. package/src/rendering/Display.ts +0 -249
  195. package/src/rendering/RenderingStyle.test.ts +0 -68
  196. package/src/rendering/RenderingStyle.ts +0 -55
  197. package/src/rendering/TextRenderingStyle.ts +0 -45
  198. package/src/rendering/caching/CacheRecord.test.ts +0 -49
  199. package/src/rendering/caching/CacheRecord.ts +0 -77
  200. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  201. package/src/rendering/caching/RenderingCache.test.ts +0 -44
  202. package/src/rendering/caching/RenderingCache.ts +0 -66
  203. package/src/rendering/caching/RenderingCacheNode.ts +0 -405
  204. package/src/rendering/caching/testUtils.ts +0 -35
  205. package/src/rendering/caching/types.ts +0 -34
  206. package/src/rendering/lib.ts +0 -6
  207. package/src/rendering/localization.ts +0 -20
  208. package/src/rendering/renderers/AbstractRenderer.ts +0 -222
  209. package/src/rendering/renderers/CanvasRenderer.ts +0 -296
  210. package/src/rendering/renderers/DummyRenderer.test.ts +0 -42
  211. package/src/rendering/renderers/DummyRenderer.ts +0 -136
  212. package/src/rendering/renderers/SVGRenderer.ts +0 -354
  213. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -70
  214. package/src/testing/beforeEachFile.ts +0 -8
  215. package/src/testing/createEditor.ts +0 -11
  216. package/src/testing/global.d.ts +0 -17
  217. package/src/testing/lib.ts +0 -3
  218. package/src/testing/loadExpectExtensions.ts +0 -25
  219. package/src/testing/sendPenEvent.ts +0 -31
  220. package/src/testing/sendTouchEvent.ts +0 -78
  221. package/src/toolbar/HTMLToolbar.ts +0 -492
  222. package/src/toolbar/IconProvider.ts +0 -736
  223. package/src/toolbar/lib.ts +0 -4
  224. package/src/toolbar/localization.ts +0 -106
  225. package/src/toolbar/makeColorInput.ts +0 -145
  226. package/src/toolbar/types.ts +0 -5
  227. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  228. package/src/toolbar/widgets/BaseToolWidget.ts +0 -56
  229. package/src/toolbar/widgets/BaseWidget.ts +0 -377
  230. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -167
  231. package/src/toolbar/widgets/EraserToolWidget.ts +0 -85
  232. package/src/toolbar/widgets/HandToolWidget.ts +0 -250
  233. package/src/toolbar/widgets/InsertImageWidget.ts +0 -223
  234. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  235. package/src/toolbar/widgets/PenToolWidget.ts +0 -288
  236. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -190
  237. package/src/toolbar/widgets/TextToolWidget.ts +0 -145
  238. package/src/toolbar/widgets/lib.ts +0 -13
  239. package/src/tools/BaseTool.ts +0 -76
  240. package/src/tools/Eraser.test.ts +0 -103
  241. package/src/tools/Eraser.ts +0 -139
  242. package/src/tools/FindTool.ts +0 -152
  243. package/src/tools/PanZoom.test.ts +0 -310
  244. package/src/tools/PanZoom.ts +0 -520
  245. package/src/tools/PasteHandler.ts +0 -95
  246. package/src/tools/Pen.test.ts +0 -194
  247. package/src/tools/Pen.ts +0 -226
  248. package/src/tools/PipetteTool.ts +0 -55
  249. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -28
  250. package/src/tools/SelectionTool/Selection.ts +0 -607
  251. package/src/tools/SelectionTool/SelectionHandle.ts +0 -108
  252. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -261
  253. package/src/tools/SelectionTool/SelectionTool.ts +0 -480
  254. package/src/tools/SelectionTool/TransformMode.ts +0 -114
  255. package/src/tools/SelectionTool/types.ts +0 -11
  256. package/src/tools/TextTool.ts +0 -326
  257. package/src/tools/ToolController.ts +0 -178
  258. package/src/tools/ToolEnabledGroup.ts +0 -14
  259. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  260. package/src/tools/ToolbarShortcutHandler.ts +0 -34
  261. package/src/tools/UndoRedoShortcut.test.ts +0 -56
  262. package/src/tools/UndoRedoShortcut.ts +0 -25
  263. package/src/tools/lib.ts +0 -21
  264. package/src/tools/localization.ts +0 -66
  265. package/src/types.ts +0 -234
  266. package/src/util/assertions.ts +0 -55
  267. package/src/util/fileToBase64.ts +0 -18
  268. package/src/util/untilNextAnimationFrame.ts +0 -9
  269. package/src/util/waitForTimeout.ts +0 -9
package/src/math/Path.ts DELETED
@@ -1,790 +0,0 @@
1
- import { Bezier } from 'bezier-js';
2
- import { RenderablePathSpec } from '../rendering/renderers/AbstractRenderer';
3
- import RenderingStyle from '../rendering/RenderingStyle';
4
- import { toRoundedString, toStringOfSamePrecision } from './rounding';
5
- import LineSegment2 from './LineSegment2';
6
- import Mat33 from './Mat33';
7
- import Rect2 from './Rect2';
8
- import { Point2, Vec2 } from './Vec2';
9
-
10
- export enum PathCommandType {
11
- LineTo,
12
- MoveTo,
13
- CubicBezierTo,
14
- QuadraticBezierTo,
15
- }
16
-
17
- export interface CubicBezierPathCommand {
18
- kind: PathCommandType.CubicBezierTo;
19
- controlPoint1: Point2;
20
- controlPoint2: Point2;
21
- endPoint: Point2;
22
- }
23
-
24
- export interface QuadraticBezierPathCommand {
25
- kind: PathCommandType.QuadraticBezierTo;
26
- controlPoint: Point2;
27
- endPoint: Point2;
28
- }
29
-
30
- export interface LinePathCommand {
31
- kind: PathCommandType.LineTo;
32
- point: Point2;
33
- }
34
-
35
- export interface MoveToPathCommand {
36
- kind: PathCommandType.MoveTo;
37
- point: Point2;
38
- }
39
-
40
- export type PathCommand = CubicBezierPathCommand | LinePathCommand | QuadraticBezierPathCommand | MoveToPathCommand;
41
-
42
- interface IntersectionResult {
43
- curve: LineSegment2|Bezier;
44
- parameterValue: number;
45
- point: Point2;
46
- }
47
-
48
- type GeometryArrayType = Array<LineSegment2|Bezier>;
49
- export default class Path {
50
- public readonly bbox: Rect2;
51
-
52
- public constructor(public readonly startPoint: Point2, public readonly parts: PathCommand[]) {
53
- // Initial bounding box contains one point: the start point.
54
- this.bbox = Rect2.bboxOf([startPoint]);
55
-
56
- // Convert into a representation of the geometry (cache for faster intersection
57
- // calculation)
58
- for (const part of parts) {
59
- this.bbox = this.bbox.union(Path.computeBBoxForSegment(startPoint, part));
60
- }
61
- }
62
-
63
- private cachedGeometry: GeometryArrayType|null = null;
64
-
65
- // Lazy-loads and returns this path's geometry
66
- public get geometry(): Array<LineSegment2|Bezier> {
67
- if (this.cachedGeometry) {
68
- return this.cachedGeometry;
69
- }
70
-
71
- let startPoint = this.startPoint;
72
- const geometry: GeometryArrayType = [];
73
-
74
- for (const part of this.parts) {
75
- switch (part.kind) {
76
- case PathCommandType.CubicBezierTo:
77
- geometry.push(
78
- new Bezier(
79
- startPoint.xy, part.controlPoint1.xy, part.controlPoint2.xy, part.endPoint.xy
80
- )
81
- );
82
- startPoint = part.endPoint;
83
- break;
84
- case PathCommandType.QuadraticBezierTo:
85
- geometry.push(
86
- new Bezier(
87
- startPoint.xy, part.controlPoint.xy, part.endPoint.xy
88
- )
89
- );
90
- startPoint = part.endPoint;
91
- break;
92
- case PathCommandType.LineTo:
93
- geometry.push(
94
- new LineSegment2(startPoint, part.point)
95
- );
96
- startPoint = part.point;
97
- break;
98
- case PathCommandType.MoveTo:
99
- startPoint = part.point;
100
- break;
101
- }
102
- }
103
-
104
- this.cachedGeometry = geometry;
105
- return this.cachedGeometry;
106
- }
107
-
108
- private cachedPolylineApproximation: LineSegment2[]|null = null;
109
-
110
- // Approximates this path with a group of line segments.
111
- public polylineApproximation(): LineSegment2[] {
112
- if (this.cachedPolylineApproximation) {
113
- return this.cachedPolylineApproximation;
114
- }
115
-
116
- const points: Point2[] = [];
117
-
118
- for (const part of this.parts) {
119
- switch (part.kind) {
120
- case PathCommandType.CubicBezierTo:
121
- points.push(part.controlPoint1, part.controlPoint2, part.endPoint);
122
- break;
123
- case PathCommandType.QuadraticBezierTo:
124
- points.push(part.controlPoint, part.endPoint);
125
- break;
126
- case PathCommandType.MoveTo:
127
- case PathCommandType.LineTo:
128
- points.push(part.point);
129
- break;
130
- }
131
- }
132
-
133
- const result: LineSegment2[] = [];
134
- let prevPoint = this.startPoint;
135
- for (const point of points) {
136
- result.push(new LineSegment2(prevPoint, point));
137
- prevPoint = point;
138
- }
139
-
140
- return result;
141
- }
142
-
143
- public static computeBBoxForSegment(startPoint: Point2, part: PathCommand): Rect2 {
144
- const points = [startPoint];
145
- let exhaustivenessCheck: never;
146
- switch (part.kind) {
147
- case PathCommandType.MoveTo:
148
- case PathCommandType.LineTo:
149
- points.push(part.point);
150
- break;
151
- case PathCommandType.CubicBezierTo:
152
- points.push(part.controlPoint1, part.controlPoint2, part.endPoint);
153
- break;
154
- case PathCommandType.QuadraticBezierTo:
155
- points.push(part.controlPoint, part.endPoint);
156
- break;
157
- default:
158
- exhaustivenessCheck = part;
159
- return exhaustivenessCheck;
160
- }
161
-
162
- return Rect2.bboxOf(points);
163
- }
164
-
165
- public intersection(line: LineSegment2): IntersectionResult[] {
166
- if (!line.bbox.intersects(this.bbox)) {
167
- return [];
168
- }
169
-
170
- const result: IntersectionResult[] = [];
171
- for (const part of this.geometry) {
172
- if (part instanceof LineSegment2) {
173
- const intersection = part.intersection(line);
174
-
175
- if (intersection) {
176
- result.push({
177
- curve: part,
178
- parameterValue: intersection.t,
179
- point: intersection.point,
180
- });
181
- }
182
- } else {
183
- const intersectionPoints = part.intersects(line).map(t => {
184
- // We're using the .intersects(line) function, which is documented
185
- // to always return numbers. However, to satisfy the type checker (and
186
- // possibly improperly-defined types),
187
- if (typeof t === 'string') {
188
- t = parseFloat(t);
189
- }
190
-
191
- const point = Vec2.ofXY(part.get(t));
192
-
193
- // Ensure that the intersection is on the line
194
- if (point.minus(line.p1).magnitude() > line.length
195
- || point.minus(line.p2).magnitude() > line.length) {
196
- return null;
197
- }
198
-
199
- return {
200
- point,
201
- parameterValue: t,
202
- curve: part,
203
- };
204
- }).filter(entry => entry !== null) as IntersectionResult[];
205
- result.push(...intersectionPoints);
206
- }
207
- }
208
-
209
- return result;
210
- }
211
-
212
- private static mapPathCommand(part: PathCommand, mapping: (point: Point2)=> Point2): PathCommand {
213
- switch (part.kind) {
214
- case PathCommandType.MoveTo:
215
- case PathCommandType.LineTo:
216
- return {
217
- kind: part.kind,
218
- point: mapping(part.point),
219
- };
220
- break;
221
- case PathCommandType.CubicBezierTo:
222
- return {
223
- kind: part.kind,
224
- controlPoint1: mapping(part.controlPoint1),
225
- controlPoint2: mapping(part.controlPoint2),
226
- endPoint: mapping(part.endPoint),
227
- };
228
- break;
229
- case PathCommandType.QuadraticBezierTo:
230
- return {
231
- kind: part.kind,
232
- controlPoint: mapping(part.controlPoint),
233
- endPoint: mapping(part.endPoint),
234
- };
235
- break;
236
- }
237
-
238
- const exhaustivenessCheck: never = part;
239
- return exhaustivenessCheck;
240
- }
241
-
242
- public mapPoints(mapping: (point: Point2)=>Point2): Path {
243
- const startPoint = mapping(this.startPoint);
244
- const newParts: PathCommand[] = [];
245
-
246
- for (const part of this.parts) {
247
- newParts.push(Path.mapPathCommand(part, mapping));
248
- }
249
-
250
- return new Path(startPoint, newParts);
251
- }
252
-
253
- public transformedBy(affineTransfm: Mat33): Path {
254
- if (affineTransfm.isIdentity()) {
255
- return this;
256
- }
257
-
258
- return this.mapPoints(point => affineTransfm.transformVec2(point));
259
- }
260
-
261
- // Creates a new path by joining [other] to the end of this path
262
- public union(other: Path|null): Path {
263
- if (!other) {
264
- return this;
265
- }
266
-
267
- return new Path(this.startPoint, [
268
- ...this.parts,
269
- {
270
- kind: PathCommandType.MoveTo,
271
- point: other.startPoint,
272
- },
273
- ...other.parts,
274
- ]);
275
- }
276
-
277
- private getEndPoint() {
278
- if (this.parts.length === 0) {
279
- return this.startPoint;
280
- }
281
- const lastPart = this.parts[this.parts.length - 1];
282
- if (lastPart.kind === PathCommandType.QuadraticBezierTo || lastPart.kind === PathCommandType.CubicBezierTo) {
283
- return lastPart.endPoint;
284
- } else {
285
- return lastPart.point;
286
- }
287
- }
288
-
289
- public roughlyIntersects(rect: Rect2, strokeWidth: number = 0) {
290
- if (this.parts.length === 0) {
291
- return rect.containsPoint(this.startPoint);
292
- }
293
- const isClosed = this.startPoint.eq(this.getEndPoint());
294
-
295
- if (isClosed && strokeWidth === 0) {
296
- return this.closedRoughlyIntersects(rect);
297
- }
298
-
299
- if (rect.containsRect(this.bbox)) {
300
- return true;
301
- }
302
-
303
- // Does the rectangle intersect the bounding boxes of any of this' parts?
304
- let startPoint = this.startPoint;
305
- for (const part of this.parts) {
306
- const bbox = Path.computeBBoxForSegment(startPoint, part).grownBy(strokeWidth);
307
-
308
- if (part.kind === PathCommandType.LineTo || part.kind === PathCommandType.MoveTo) {
309
- startPoint = part.point;
310
- } else {
311
- startPoint = part.endPoint;
312
- }
313
-
314
- if (rect.intersects(bbox)) {
315
- return true;
316
- }
317
- }
318
-
319
- return false;
320
- }
321
-
322
- // Treats this as a closed path and returns true if part of `rect` is roughly within
323
- // this path's interior.
324
- //
325
- // Note: Assumes that this is a closed, non-self-intersecting path.
326
- public closedRoughlyIntersects(rect: Rect2): boolean {
327
- if (rect.containsRect(this.bbox)) {
328
- return true;
329
- }
330
-
331
- // Choose a point outside of the path.
332
- const startPt = this.bbox.topLeft.minus(Vec2.of(1, 1));
333
- const testPts = rect.corners;
334
- const polygon = this.polylineApproximation();
335
-
336
- for (const point of testPts) {
337
- const testLine = new LineSegment2(point, startPt);
338
-
339
- let intersectionCount = 0;
340
- for (const line of polygon) {
341
- if (line.intersects(testLine)) {
342
- intersectionCount ++;
343
- }
344
- }
345
-
346
- // Odd? The point is within the polygon!
347
- if (intersectionCount % 2 === 1) {
348
- return true;
349
- }
350
- }
351
-
352
- // Grow the rectangle for possible additional precision.
353
- const grownRect = rect.grownBy(Math.min(rect.size.x, rect.size.y));
354
- const edges = [];
355
- for (const subrect of grownRect.divideIntoGrid(4, 4)) {
356
- edges.push(...subrect.getEdges());
357
- }
358
-
359
- for (const edge of edges) {
360
- for (const line of polygon) {
361
- if (edge.intersects(line)) {
362
- return true;
363
- }
364
- }
365
- }
366
-
367
- // Even? Probably no intersection.
368
- return false;
369
- }
370
-
371
- // Returns a path that outlines [rect]. If [lineWidth] is not given, the resultant path is
372
- // the outline of [rect]. Otherwise, the resultant path represents a line of width [lineWidth]
373
- // that traces [rect].
374
- public static fromRect(rect: Rect2, lineWidth: number|null = null): Path {
375
- const commands: PathCommand[] = [];
376
-
377
- let corners;
378
- let startPoint;
379
-
380
- if (lineWidth !== null) {
381
- // Vector from the top left corner or bottom right corner to the edge of the
382
- // stroked region.
383
- const cornerToEdge = Vec2.of(lineWidth, lineWidth).times(0.5);
384
- const innerRect = Rect2.fromCorners(
385
- rect.topLeft.plus(cornerToEdge),
386
- rect.bottomRight.minus(cornerToEdge)
387
- );
388
- const outerRect = Rect2.fromCorners(
389
- rect.topLeft.minus(cornerToEdge),
390
- rect.bottomRight.plus(cornerToEdge)
391
- );
392
-
393
- corners = [
394
- innerRect.corners[3],
395
- ...innerRect.corners,
396
- ...outerRect.corners.reverse(),
397
- ];
398
- startPoint = outerRect.corners[3];
399
- } else {
400
- corners = rect.corners.slice(1);
401
- startPoint = rect.corners[0];
402
- }
403
-
404
- for (const corner of corners) {
405
- commands.push({
406
- kind: PathCommandType.LineTo,
407
- point: corner,
408
- });
409
- }
410
-
411
- // Close the shape
412
- commands.push({
413
- kind: PathCommandType.LineTo,
414
- point: startPoint,
415
- });
416
-
417
- return new Path(startPoint, commands);
418
- }
419
-
420
- public static fromRenderable(renderable: RenderablePathSpec): Path {
421
- if (renderable.path) {
422
- return renderable.path;
423
- }
424
-
425
- return new Path(renderable.startPoint, renderable.commands);
426
- }
427
-
428
- public toRenderable(fill: RenderingStyle): RenderablePathSpec {
429
- return {
430
- startPoint: this.startPoint,
431
- style: fill,
432
- commands: this.parts,
433
- path: this,
434
- };
435
- }
436
-
437
- /**
438
- * @returns a Path that, when rendered, looks roughly equivalent to the given path.
439
- */
440
- public static visualEquivalent(renderablePath: RenderablePathSpec, visibleRect: Rect2): RenderablePathSpec {
441
- const path = Path.fromRenderable(renderablePath);
442
- const strokeWidth = renderablePath.style.stroke?.width ?? 0;
443
- const onlyStroked = strokeWidth > 0 && renderablePath.style.fill.a === 0;
444
-
445
- // Scale the expanded rect --- the visual equivalent is only close for huge strokes.
446
- const expandedRect = visibleRect.grownBy(strokeWidth)
447
- .transformedBoundingBox(Mat33.scaling2D(2, visibleRect.center));
448
-
449
- // TODO: Handle simplifying very small paths.
450
- if (expandedRect.containsRect(path.bbox.grownBy(strokeWidth))) {
451
- return renderablePath;
452
- }
453
- const parts: PathCommand[] = [];
454
- let startPoint = path.startPoint;
455
-
456
- for (const part of path.parts) {
457
- const partBBox = Path.computeBBoxForSegment(startPoint, part).grownBy(strokeWidth);
458
- let endPoint;
459
-
460
- if (part.kind === PathCommandType.LineTo || part.kind === PathCommandType.MoveTo) {
461
- endPoint = part.point;
462
- } else {
463
- endPoint = part.endPoint;
464
- }
465
-
466
- const intersectsVisible = partBBox.intersects(visibleRect);
467
-
468
- if (intersectsVisible) {
469
- // TODO: Can we trim parts of paths that intersect the visible rectangle?
470
- parts.push(part);
471
- } else if (onlyStroked || part.kind === PathCommandType.MoveTo) {
472
- // We're stroking (not filling) and the path doesn't intersect the bounding box.
473
- // Don't draw it, but preserve the endpoints.
474
- parts.push({
475
- kind: PathCommandType.MoveTo,
476
- point: endPoint,
477
- });
478
- }
479
- else {
480
- // Otherwise, we may be filling. Try to roughly preserve the filled region.
481
- parts.push({
482
- kind: PathCommandType.LineTo,
483
- point: endPoint,
484
- });
485
- }
486
-
487
- startPoint = endPoint;
488
- }
489
-
490
- return new Path(path.startPoint, parts).toRenderable(renderablePath.style);
491
- }
492
-
493
- private cachedStringVersion: string|null = null;
494
-
495
- public toString(useNonAbsCommands?: boolean): string {
496
- if (this.cachedStringVersion) {
497
- return this.cachedStringVersion;
498
- }
499
-
500
- if (useNonAbsCommands === undefined) {
501
- // Hueristic: Try to determine whether converting absolute to relative commands is worth it.
502
- useNonAbsCommands = Math.abs(this.bbox.topLeft.x) > 10 && Math.abs(this.bbox.topLeft.y) > 10;
503
- }
504
-
505
- const result = Path.toString(this.startPoint, this.parts, !useNonAbsCommands);
506
- this.cachedStringVersion = result;
507
- return result;
508
- }
509
-
510
- public serialize(): string {
511
- return this.toString();
512
- }
513
-
514
- // @param onlyAbsCommands - True if we should avoid converting absolute coordinates to relative offsets -- such
515
- // conversions can lead to smaller output strings, but also take time.
516
- public static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands?: boolean): string {
517
- const result: string[] = [];
518
-
519
- let prevPoint: Point2|undefined;
520
- const addCommand = (command: string, ...points: Point2[]) => {
521
- const absoluteCommandParts: string[] = [];
522
- const relativeCommandParts: string[] = [];
523
- const makeAbsCommand = !prevPoint || onlyAbsCommands;
524
- const roundedPrevX = prevPoint ? toRoundedString(prevPoint.x) : '';
525
- const roundedPrevY = prevPoint ? toRoundedString(prevPoint.y) : '';
526
-
527
- for (const point of points) {
528
- const xComponent = toRoundedString(point.x);
529
- const yComponent = toRoundedString(point.y);
530
-
531
- // Relative commands are often shorter as strings than absolute commands.
532
- if (!makeAbsCommand) {
533
- const xComponentRelative = toStringOfSamePrecision(point.x - prevPoint!.x, xComponent, roundedPrevX, roundedPrevY);
534
- const yComponentRelative = toStringOfSamePrecision(point.y - prevPoint!.y, yComponent, roundedPrevX, roundedPrevY);
535
-
536
- // No need for an additional separator if it starts with a '-'
537
- if (yComponentRelative.charAt(0) === '-') {
538
- relativeCommandParts.push(`${xComponentRelative}${yComponentRelative}`);
539
- } else {
540
- relativeCommandParts.push(`${xComponentRelative},${yComponentRelative}`);
541
- }
542
- } else {
543
- absoluteCommandParts.push(`${xComponent},${yComponent}`);
544
- }
545
- }
546
-
547
- let commandString;
548
- if (makeAbsCommand) {
549
- commandString = `${command}${absoluteCommandParts.join(' ')}`;
550
- } else {
551
- commandString = `${command.toLowerCase()}${relativeCommandParts.join(' ')}`;
552
- }
553
-
554
- // Don't add no-ops.
555
- if (commandString === 'l0,0' || commandString === 'm0,0') {
556
- return;
557
- }
558
- result.push(commandString);
559
-
560
- if (points.length > 0) {
561
- prevPoint = points[points.length - 1];
562
- }
563
- };
564
-
565
- // Don't add two moveTos in a row (this can happen if
566
- // the start point corresponds to a moveTo _and_ the first command is
567
- // also a moveTo)
568
- if (parts[0]?.kind !== PathCommandType.MoveTo) {
569
- addCommand('M', startPoint);
570
- }
571
-
572
- let exhaustivenessCheck: never;
573
- for (let i = 0; i < parts.length; i++) {
574
- const part = parts[i];
575
-
576
- switch (part.kind) {
577
- case PathCommandType.MoveTo:
578
- addCommand('M', part.point);
579
- break;
580
- case PathCommandType.LineTo:
581
- addCommand('L', part.point);
582
- break;
583
- case PathCommandType.CubicBezierTo:
584
- addCommand('C', part.controlPoint1, part.controlPoint2, part.endPoint);
585
- break;
586
- case PathCommandType.QuadraticBezierTo:
587
- addCommand('Q', part.controlPoint, part.endPoint);
588
- break;
589
- default:
590
- exhaustivenessCheck = part;
591
- return exhaustivenessCheck;
592
- }
593
- }
594
-
595
- return result.join('');
596
- }
597
-
598
- // Create a Path from a SVG path specification.
599
- // TODO: Support a larger subset of SVG paths.
600
- // TODO: Support `s`,`t` commands shorthands.
601
- public static fromString(pathString: string): Path {
602
- // See the MDN reference:
603
- // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
604
- // and
605
- // https://www.w3.org/TR/SVG2/paths.html
606
-
607
- // Remove linebreaks
608
- pathString = pathString.split('\n').join(' ');
609
-
610
- let lastPos: Point2 = Vec2.zero;
611
- let firstPos: Point2|null = null;
612
- let startPos: Point2|null = null;
613
- let isFirstCommand: boolean = true;
614
- const commands: PathCommand[] = [];
615
-
616
-
617
- const moveTo = (point: Point2) => {
618
- // The first moveTo/lineTo is already handled by the [startPoint] parameter of the Path constructor.
619
- if (isFirstCommand) {
620
- isFirstCommand = false;
621
- return;
622
- }
623
-
624
- commands.push({
625
- kind: PathCommandType.MoveTo,
626
- point,
627
- });
628
- };
629
- const lineTo = (point: Point2) => {
630
- if (isFirstCommand) {
631
- isFirstCommand = false;
632
- return;
633
- }
634
-
635
- commands.push({
636
- kind: PathCommandType.LineTo,
637
- point,
638
- });
639
- };
640
- const cubicBezierTo = (cp1: Point2, cp2: Point2, end: Point2) => {
641
- commands.push({
642
- kind: PathCommandType.CubicBezierTo,
643
- controlPoint1: cp1,
644
- controlPoint2: cp2,
645
- endPoint: end,
646
- });
647
- };
648
- const quadraticBeierTo = (controlPoint: Point2, endPoint: Point2) => {
649
- commands.push({
650
- kind: PathCommandType.QuadraticBezierTo,
651
- controlPoint,
652
- endPoint,
653
- });
654
- };
655
- const commandArgCounts: Record<string, number> = {
656
- 'm': 1,
657
- 'l': 1,
658
- 'c': 3,
659
- 'q': 2,
660
- 'z': 0,
661
- 'h': 1,
662
- 'v': 1,
663
- };
664
-
665
- // Each command: Command character followed by anything that isn't a command character
666
- const commandExp = /([MZLHVCSQTA])\s*([^MZLHVCSQTA]*)/ig;
667
- let current;
668
- while ((current = commandExp.exec(pathString)) !== null) {
669
- const argParts = current[2].trim().split(/[^0-9Ee.-]/).filter(
670
- part => part.length > 0
671
- ).reduce((accumualtor: string[], current: string): string[] => {
672
- // As of 09/2022, iOS Safari doesn't support support lookbehind in regular
673
- // expressions. As such, we need an alternative.
674
- // Because '-' can be used as a path separator, unless preceeded by an 'e' (as in 1e-5),
675
- // we need special cases:
676
- current = current.replace(/([^eE])[-]/g, '$1 -');
677
- const parts = current.split(' -');
678
- if (parts[0] !== '') {
679
- accumualtor.push(parts[0]);
680
- }
681
- accumualtor.push(...parts.slice(1).map(part => `-${part}`));
682
- return accumualtor;
683
- }, []);
684
-
685
- let numericArgs = argParts.map(arg => parseFloat(arg));
686
-
687
- let commandChar = current[1].toLowerCase();
688
- let uppercaseCommand = current[1] !== commandChar;
689
-
690
- // Convert commands that don't take points into commands that do.
691
- if (commandChar === 'v' || commandChar === 'h') {
692
- numericArgs = numericArgs.reduce((accumulator: number[], current: number): number[] => {
693
- if (commandChar === 'v') {
694
- return accumulator.concat(uppercaseCommand ? lastPos.x : 0, current);
695
- } else {
696
- return accumulator.concat(current, uppercaseCommand ? lastPos.y : 0);
697
- }
698
- }, []);
699
- commandChar = 'l';
700
- } else if (commandChar === 'z') {
701
- if (firstPos) {
702
- numericArgs = [ firstPos.x, firstPos.y ];
703
- firstPos = lastPos;
704
- } else {
705
- continue;
706
- }
707
-
708
- // 'z' always acts like an uppercase lineTo(startPos)
709
- uppercaseCommand = true;
710
- commandChar = 'l';
711
- }
712
-
713
-
714
- const commandArgCount: number = commandArgCounts[commandChar] ?? 0;
715
- const allArgs = numericArgs.reduce((
716
- accumulator: Point2[], current, index, parts
717
- ): Point2[] => {
718
- if (index % 2 !== 0) {
719
- const currentAsFloat = current;
720
- const prevAsFloat = parts[index - 1];
721
- return accumulator.concat(Vec2.of(prevAsFloat, currentAsFloat));
722
- } else {
723
- return accumulator;
724
- }
725
- }, []).map((coordinate, index): Point2 => {
726
- // Lowercase commands are relative, uppercase commands use absolute
727
- // positioning
728
- let newPos;
729
- if (uppercaseCommand) {
730
- newPos = coordinate;
731
- } else {
732
- newPos = lastPos.plus(coordinate);
733
- }
734
-
735
- if ((index + 1) % commandArgCount === 0) {
736
- lastPos = newPos;
737
- }
738
-
739
- return newPos;
740
- });
741
-
742
- if (allArgs.length % commandArgCount !== 0) {
743
- throw new Error([
744
- `Incorrect number of arguments: got ${JSON.stringify(allArgs)} with a length of ${allArgs.length} ≠ ${commandArgCount}k, k ∈ ℤ.`,
745
- `The number of arguments to ${commandChar} must be a multiple of ${commandArgCount}!`,
746
- `Command: ${current[0]}`,
747
- ].join('\n'));
748
- }
749
-
750
- for (let argPos = 0; argPos < allArgs.length; argPos += commandArgCount) {
751
- const args = allArgs.slice(argPos, argPos + commandArgCount);
752
-
753
- switch (commandChar.toLowerCase()) {
754
- case 'm':
755
- if (argPos === 0) {
756
- moveTo(args[0]);
757
- } else {
758
- lineTo(args[0]);
759
- }
760
- break;
761
- case 'l':
762
- lineTo(args[0]);
763
- break;
764
- case 'c':
765
- cubicBezierTo(args[0], args[1], args[2]);
766
- break;
767
- case 'q':
768
- quadraticBeierTo(args[0], args[1]);
769
- break;
770
- default:
771
- throw new Error(`Unknown path command ${commandChar}`);
772
- }
773
-
774
- isFirstCommand = false;
775
- }
776
-
777
- if (allArgs.length > 0) {
778
- firstPos ??= allArgs[0];
779
- startPos ??= firstPos;
780
- lastPos = allArgs[allArgs.length - 1];
781
- }
782
- }
783
-
784
- const result = new Path(startPos ?? Vec2.zero, commands);
785
- result.cachedStringVersion = pathString;
786
- return result;
787
- }
788
-
789
- public static empty: Path = new Path(Vec2.zero, []);
790
- }