js-draw 0.0.1

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 (156) hide show
  1. package/.eslintrc.js +57 -0
  2. package/.husky/pre-commit +4 -0
  3. package/LICENSE +21 -0
  4. package/README.md +74 -0
  5. package/__mocks__/coloris.ts +8 -0
  6. package/__mocks__/styleMock.js +1 -0
  7. package/dist/__mocks__/coloris.d.ts +2 -0
  8. package/dist/__mocks__/coloris.js +5 -0
  9. package/dist/build_tools/BundledFile.d.ts +12 -0
  10. package/dist/build_tools/BundledFile.js +153 -0
  11. package/dist/scripts/bundle.d.ts +1 -0
  12. package/dist/scripts/bundle.js +19 -0
  13. package/dist/scripts/watchBundle.d.ts +1 -0
  14. package/dist/scripts/watchBundle.js +9 -0
  15. package/dist/src/Color4.d.ts +23 -0
  16. package/dist/src/Color4.js +102 -0
  17. package/dist/src/Display.d.ts +22 -0
  18. package/dist/src/Display.js +93 -0
  19. package/dist/src/Editor.d.ts +55 -0
  20. package/dist/src/Editor.js +366 -0
  21. package/dist/src/EditorImage.d.ts +44 -0
  22. package/dist/src/EditorImage.js +243 -0
  23. package/dist/src/EventDispatcher.d.ts +11 -0
  24. package/dist/src/EventDispatcher.js +39 -0
  25. package/dist/src/Pointer.d.ts +22 -0
  26. package/dist/src/Pointer.js +57 -0
  27. package/dist/src/SVGLoader.d.ts +21 -0
  28. package/dist/src/SVGLoader.js +204 -0
  29. package/dist/src/StrokeBuilder.d.ts +35 -0
  30. package/dist/src/StrokeBuilder.js +275 -0
  31. package/dist/src/UndoRedoHistory.d.ts +17 -0
  32. package/dist/src/UndoRedoHistory.js +46 -0
  33. package/dist/src/Viewport.d.ts +39 -0
  34. package/dist/src/Viewport.js +134 -0
  35. package/dist/src/commands/Command.d.ts +15 -0
  36. package/dist/src/commands/Command.js +29 -0
  37. package/dist/src/commands/Erase.d.ts +11 -0
  38. package/dist/src/commands/Erase.js +37 -0
  39. package/dist/src/commands/localization.d.ts +19 -0
  40. package/dist/src/commands/localization.js +17 -0
  41. package/dist/src/components/AbstractComponent.d.ts +19 -0
  42. package/dist/src/components/AbstractComponent.js +46 -0
  43. package/dist/src/components/Stroke.d.ts +16 -0
  44. package/dist/src/components/Stroke.js +79 -0
  45. package/dist/src/components/UnknownSVGObject.d.ts +15 -0
  46. package/dist/src/components/UnknownSVGObject.js +25 -0
  47. package/dist/src/components/localization.d.ts +5 -0
  48. package/dist/src/components/localization.js +4 -0
  49. package/dist/src/geometry/LineSegment2.d.ts +19 -0
  50. package/dist/src/geometry/LineSegment2.js +100 -0
  51. package/dist/src/geometry/Mat33.d.ts +31 -0
  52. package/dist/src/geometry/Mat33.js +187 -0
  53. package/dist/src/geometry/Path.d.ts +55 -0
  54. package/dist/src/geometry/Path.js +364 -0
  55. package/dist/src/geometry/Rect2.d.ts +47 -0
  56. package/dist/src/geometry/Rect2.js +148 -0
  57. package/dist/src/geometry/Vec2.d.ts +13 -0
  58. package/dist/src/geometry/Vec2.js +13 -0
  59. package/dist/src/geometry/Vec3.d.ts +32 -0
  60. package/dist/src/geometry/Vec3.js +98 -0
  61. package/dist/src/localization.d.ts +12 -0
  62. package/dist/src/localization.js +5 -0
  63. package/dist/src/main.d.ts +3 -0
  64. package/dist/src/main.js +4 -0
  65. package/dist/src/rendering/AbstractRenderer.d.ts +38 -0
  66. package/dist/src/rendering/AbstractRenderer.js +108 -0
  67. package/dist/src/rendering/CanvasRenderer.d.ts +23 -0
  68. package/dist/src/rendering/CanvasRenderer.js +108 -0
  69. package/dist/src/rendering/DummyRenderer.d.ts +25 -0
  70. package/dist/src/rendering/DummyRenderer.js +65 -0
  71. package/dist/src/rendering/SVGRenderer.d.ts +27 -0
  72. package/dist/src/rendering/SVGRenderer.js +122 -0
  73. package/dist/src/testing/loadExpectExtensions.d.ts +17 -0
  74. package/dist/src/testing/loadExpectExtensions.js +27 -0
  75. package/dist/src/toolbar/HTMLToolbar.d.ts +12 -0
  76. package/dist/src/toolbar/HTMLToolbar.js +444 -0
  77. package/dist/src/toolbar/types.d.ts +17 -0
  78. package/dist/src/toolbar/types.js +5 -0
  79. package/dist/src/tools/BaseTool.d.ts +20 -0
  80. package/dist/src/tools/BaseTool.js +44 -0
  81. package/dist/src/tools/Eraser.d.ts +16 -0
  82. package/dist/src/tools/Eraser.js +53 -0
  83. package/dist/src/tools/PanZoom.d.ts +40 -0
  84. package/dist/src/tools/PanZoom.js +191 -0
  85. package/dist/src/tools/Pen.d.ts +25 -0
  86. package/dist/src/tools/Pen.js +97 -0
  87. package/dist/src/tools/SelectionTool.d.ts +49 -0
  88. package/dist/src/tools/SelectionTool.js +437 -0
  89. package/dist/src/tools/ToolController.d.ts +18 -0
  90. package/dist/src/tools/ToolController.js +110 -0
  91. package/dist/src/tools/ToolEnabledGroup.d.ts +6 -0
  92. package/dist/src/tools/ToolEnabledGroup.js +11 -0
  93. package/dist/src/tools/localization.d.ts +10 -0
  94. package/dist/src/tools/localization.js +9 -0
  95. package/dist/src/types.d.ts +88 -0
  96. package/dist/src/types.js +20 -0
  97. package/jest.config.js +22 -0
  98. package/lint-staged.config.js +6 -0
  99. package/package.json +82 -0
  100. package/src/Color4.test.ts +12 -0
  101. package/src/Color4.ts +122 -0
  102. package/src/Display.ts +118 -0
  103. package/src/Editor.css +58 -0
  104. package/src/Editor.ts +469 -0
  105. package/src/EditorImage.test.ts +90 -0
  106. package/src/EditorImage.ts +297 -0
  107. package/src/EventDispatcher.test.ts +123 -0
  108. package/src/EventDispatcher.ts +53 -0
  109. package/src/Pointer.ts +93 -0
  110. package/src/SVGLoader.ts +230 -0
  111. package/src/StrokeBuilder.ts +362 -0
  112. package/src/UndoRedoHistory.ts +61 -0
  113. package/src/Viewport.ts +168 -0
  114. package/src/commands/Command.ts +43 -0
  115. package/src/commands/Erase.ts +52 -0
  116. package/src/commands/localization.ts +38 -0
  117. package/src/components/AbstractComponent.ts +73 -0
  118. package/src/components/Stroke.test.ts +18 -0
  119. package/src/components/Stroke.ts +102 -0
  120. package/src/components/UnknownSVGObject.ts +36 -0
  121. package/src/components/localization.ts +9 -0
  122. package/src/editorStyles.js +3 -0
  123. package/src/geometry/LineSegment2.test.ts +77 -0
  124. package/src/geometry/LineSegment2.ts +127 -0
  125. package/src/geometry/Mat33.test.ts +144 -0
  126. package/src/geometry/Mat33.ts +268 -0
  127. package/src/geometry/Path.fromString.test.ts +146 -0
  128. package/src/geometry/Path.test.ts +96 -0
  129. package/src/geometry/Path.toString.test.ts +31 -0
  130. package/src/geometry/Path.ts +456 -0
  131. package/src/geometry/Rect2.test.ts +121 -0
  132. package/src/geometry/Rect2.ts +215 -0
  133. package/src/geometry/Vec2.test.ts +32 -0
  134. package/src/geometry/Vec2.ts +18 -0
  135. package/src/geometry/Vec3.test.ts +29 -0
  136. package/src/geometry/Vec3.ts +133 -0
  137. package/src/localization.ts +27 -0
  138. package/src/rendering/AbstractRenderer.ts +164 -0
  139. package/src/rendering/CanvasRenderer.ts +141 -0
  140. package/src/rendering/DummyRenderer.ts +80 -0
  141. package/src/rendering/SVGRenderer.ts +159 -0
  142. package/src/testing/loadExpectExtensions.ts +43 -0
  143. package/src/toolbar/HTMLToolbar.ts +551 -0
  144. package/src/toolbar/toolbar.css +110 -0
  145. package/src/toolbar/types.ts +20 -0
  146. package/src/tools/BaseTool.ts +58 -0
  147. package/src/tools/Eraser.ts +67 -0
  148. package/src/tools/PanZoom.ts +253 -0
  149. package/src/tools/Pen.ts +121 -0
  150. package/src/tools/SelectionTool.test.ts +85 -0
  151. package/src/tools/SelectionTool.ts +545 -0
  152. package/src/tools/ToolController.ts +126 -0
  153. package/src/tools/ToolEnabledGroup.ts +14 -0
  154. package/src/tools/localization.ts +22 -0
  155. package/src/types.ts +133 -0
  156. package/tsconfig.json +28 -0
@@ -0,0 +1,46 @@
1
+ import EditorImage from '../EditorImage';
2
+ export default class AbstractComponent {
3
+ constructor() {
4
+ this.lastChangedTime = (new Date()).getTime();
5
+ this.zIndex = AbstractComponent.zIndexCounter++;
6
+ }
7
+ getBBox() {
8
+ return this.contentBBox;
9
+ }
10
+ // Returns a command that, when applied, transforms this by [affineTransfm] and
11
+ // updates the editor.
12
+ transformBy(affineTransfm) {
13
+ const updateTransform = (editor, newTransfm) => {
14
+ // Any parent should have only one direct child.
15
+ const parent = editor.image.findParent(this);
16
+ let hadParent = false;
17
+ if (parent) {
18
+ parent.remove();
19
+ hadParent = true;
20
+ }
21
+ this.applyTransformation(newTransfm);
22
+ // Add the element back to the document.
23
+ if (hadParent) {
24
+ new EditorImage.AddElementCommand(this).apply(editor);
25
+ }
26
+ };
27
+ const origZIndex = this.zIndex;
28
+ return {
29
+ apply: (editor) => {
30
+ this.zIndex = AbstractComponent.zIndexCounter++;
31
+ updateTransform(editor, affineTransfm);
32
+ editor.queueRerender();
33
+ },
34
+ unapply: (editor) => {
35
+ this.zIndex = origZIndex;
36
+ updateTransform(editor, affineTransfm.inverse());
37
+ editor.queueRerender();
38
+ },
39
+ description(localizationTable) {
40
+ return localizationTable.transformedElements(1);
41
+ },
42
+ };
43
+ }
44
+ }
45
+ // Topmost z-index
46
+ AbstractComponent.zIndexCounter = 0;
@@ -0,0 +1,16 @@
1
+ import LineSegment2 from '../geometry/LineSegment2';
2
+ import Mat33 from '../geometry/Mat33';
3
+ import Rect2 from '../geometry/Rect2';
4
+ import AbstractRenderer, { RenderablePathSpec } from '../rendering/AbstractRenderer';
5
+ import AbstractComponent from './AbstractComponent';
6
+ import { ImageComponentLocalization } from './localization';
7
+ export default class Stroke extends AbstractComponent {
8
+ private parts;
9
+ protected contentBBox: Rect2;
10
+ constructor(parts: RenderablePathSpec[]);
11
+ intersects(line: LineSegment2): boolean;
12
+ render(canvas: AbstractRenderer, visibleRect: Rect2): void;
13
+ private bboxForPart;
14
+ protected applyTransformation(affineTransfm: Mat33): void;
15
+ description(localization: ImageComponentLocalization): string;
16
+ }
@@ -0,0 +1,79 @@
1
+ import Path from '../geometry/Path';
2
+ import Rect2 from '../geometry/Rect2';
3
+ import AbstractComponent from './AbstractComponent';
4
+ export default class Stroke extends AbstractComponent {
5
+ constructor(parts) {
6
+ var _a;
7
+ super();
8
+ this.parts = parts.map(section => {
9
+ const path = Path.fromRenderable(section);
10
+ const pathBBox = this.bboxForPart(path.bbox, section.style);
11
+ if (!this.contentBBox) {
12
+ this.contentBBox = pathBBox;
13
+ }
14
+ else {
15
+ this.contentBBox = this.contentBBox.union(pathBBox);
16
+ }
17
+ return {
18
+ path,
19
+ bbox: pathBBox,
20
+ // To implement RenderablePathSpec
21
+ startPoint: path.startPoint,
22
+ style: section.style,
23
+ commands: path.parts,
24
+ };
25
+ });
26
+ (_a = this.contentBBox) !== null && _a !== void 0 ? _a : (this.contentBBox = Rect2.empty);
27
+ }
28
+ intersects(line) {
29
+ for (const part of this.parts) {
30
+ if (part.path.intersection(line).length > 0) {
31
+ return true;
32
+ }
33
+ }
34
+ return false;
35
+ }
36
+ render(canvas, visibleRect) {
37
+ canvas.startObject(this.getBBox());
38
+ for (const part of this.parts) {
39
+ const bbox = part.bbox;
40
+ if (bbox.intersects(visibleRect)) {
41
+ canvas.drawPath(part);
42
+ }
43
+ }
44
+ canvas.endObject();
45
+ }
46
+ // Grows the bounding box for a given stroke part based on that part's style.
47
+ bboxForPart(origBBox, style) {
48
+ if (!style.stroke) {
49
+ return origBBox;
50
+ }
51
+ return origBBox.grownBy(style.stroke.width / 2);
52
+ }
53
+ applyTransformation(affineTransfm) {
54
+ this.contentBBox = Rect2.empty;
55
+ let isFirstPart = true;
56
+ // Update each part
57
+ this.parts = this.parts.map((part) => {
58
+ const newPath = part.path.transformedBy(affineTransfm);
59
+ const newBBox = this.bboxForPart(newPath.bbox, part.style);
60
+ if (isFirstPart) {
61
+ this.contentBBox = newBBox;
62
+ isFirstPart = false;
63
+ }
64
+ else {
65
+ this.contentBBox = this.contentBBox.union(newBBox);
66
+ }
67
+ return {
68
+ path: newPath,
69
+ bbox: newBBox,
70
+ startPoint: newPath.startPoint,
71
+ commands: newPath.parts,
72
+ style: part.style,
73
+ };
74
+ });
75
+ }
76
+ description(localization) {
77
+ return localization.stroke;
78
+ }
79
+ }
@@ -0,0 +1,15 @@
1
+ import LineSegment2 from '../geometry/LineSegment2';
2
+ import Mat33 from '../geometry/Mat33';
3
+ import Rect2 from '../geometry/Rect2';
4
+ import AbstractRenderer from '../rendering/AbstractRenderer';
5
+ import AbstractComponent from './AbstractComponent';
6
+ import { ImageComponentLocalization } from './localization';
7
+ export default class UnknownSVGObject extends AbstractComponent {
8
+ private svgObject;
9
+ protected contentBBox: Rect2;
10
+ constructor(svgObject: SVGElement);
11
+ render(canvas: AbstractRenderer, _visibleRect: Rect2): void;
12
+ intersects(lineSegment: LineSegment2): boolean;
13
+ protected applyTransformation(_affineTransfm: Mat33): void;
14
+ description(localization: ImageComponentLocalization): string;
15
+ }
@@ -0,0 +1,25 @@
1
+ import Rect2 from '../geometry/Rect2';
2
+ import SVGRenderer from '../rendering/SVGRenderer';
3
+ import AbstractComponent from './AbstractComponent';
4
+ export default class UnknownSVGObject extends AbstractComponent {
5
+ constructor(svgObject) {
6
+ super();
7
+ this.svgObject = svgObject;
8
+ this.contentBBox = Rect2.of(svgObject.getBoundingClientRect());
9
+ }
10
+ render(canvas, _visibleRect) {
11
+ if (!(canvas instanceof SVGRenderer)) {
12
+ // Don't draw unrenderable objects if we can't
13
+ return;
14
+ }
15
+ canvas.drawSVGElem(this.svgObject);
16
+ }
17
+ intersects(lineSegment) {
18
+ return this.contentBBox.getEdges().some(edge => edge.intersection(lineSegment) !== null);
19
+ }
20
+ applyTransformation(_affineTransfm) {
21
+ }
22
+ description(localization) {
23
+ return localization.svgObject;
24
+ }
25
+ }
@@ -0,0 +1,5 @@
1
+ export interface ImageComponentLocalization {
2
+ stroke: string;
3
+ svgObject: string;
4
+ }
5
+ export declare const defaultComponentLocalization: ImageComponentLocalization;
@@ -0,0 +1,4 @@
1
+ export const defaultComponentLocalization = {
2
+ stroke: 'Stroke',
3
+ svgObject: 'SVG Object',
4
+ };
@@ -0,0 +1,19 @@
1
+ import Rect2 from './Rect2';
2
+ import { Vec2, Point2 } from './Vec2';
3
+ interface IntersectionResult {
4
+ point: Point2;
5
+ t: number;
6
+ }
7
+ export default class LineSegment2 {
8
+ private readonly point1;
9
+ private readonly point2;
10
+ readonly direction: Vec2;
11
+ readonly length: number;
12
+ readonly bbox: Rect2;
13
+ constructor(point1: Point2, point2: Point2);
14
+ get p1(): Point2;
15
+ get p2(): Point2;
16
+ get(t: number): Point2;
17
+ intersection(other: LineSegment2): IntersectionResult | null;
18
+ }
19
+ export {};
@@ -0,0 +1,100 @@
1
+ import Rect2 from './Rect2';
2
+ import { Vec2 } from './Vec2';
3
+ export default class LineSegment2 {
4
+ constructor(point1, point2) {
5
+ this.point1 = point1;
6
+ this.point2 = point2;
7
+ this.bbox = Rect2.bboxOf([point1, point2]);
8
+ this.direction = point2.minus(point1);
9
+ this.length = this.direction.magnitude();
10
+ // Normalize
11
+ if (this.length > 0) {
12
+ this.direction = this.direction.times(1 / this.length);
13
+ }
14
+ }
15
+ // Accessors to make LineSegment2 compatible with bezier-js's
16
+ // interface
17
+ get p1() {
18
+ return this.point1;
19
+ }
20
+ get p2() {
21
+ return this.point2;
22
+ }
23
+ get(t) {
24
+ return this.point1.plus(this.direction.times(t));
25
+ }
26
+ intersection(other) {
27
+ // We want x₁(t) = x₂(t) and y₁(t) = y₂(t)
28
+ // Observe that
29
+ // x = this.point1.x + this.direction.x · t₁
30
+ // = other.point1.x + other.direction.x · t₂
31
+ // Thus,
32
+ // t₁ = (x - this.point1.x) / this.direction.x
33
+ // = (y - this.point1.y) / this.direction.y
34
+ // and
35
+ // t₂ = (x - other.point1.x) / other.direction.x
36
+ // (and similarly for y)
37
+ //
38
+ // Letting o₁ₓ = this.point1.x, o₂ₓ = other.point1.x,
39
+ // d₁ᵧ = this.direction.y, ...
40
+ //
41
+ // We can substitute these into the equations for y:
42
+ // y = o₁ᵧ + d₁ᵧ · (x - o₁ₓ) / d₁ₓ
43
+ // = o₂ᵧ + d₂ᵧ · (x - o₂ₓ) / d₂ₓ
44
+ // ⇒ o₁ᵧ - o₂ᵧ = d₂ᵧ · (x - o₂ₓ) / d₂ₓ - d₁ᵧ · (x - o₁ₓ) / d₁ₓ
45
+ // = (d₂ᵧ/d₂ₓ)(x) - (d₂ᵧ/d₂ₓ)(o₂ₓ) - (d₁ᵧ/d₁ₓ)(x) + (d₁ᵧ/d₁ₓ)(o₁ₓ)
46
+ // = (x)(d₂ᵧ/d₂ₓ - d₁ᵧ/d₁ₓ) - (d₂ᵧ/d₂ₓ)(o₂ₓ) + (d₁ᵧ/d₁ₓ)(o₁ₓ)
47
+ // ⇒ (x)(d₂ᵧ/d₂ₓ - d₁ᵧ/d₁ₓ) = o₁ᵧ - o₂ᵧ + (d₂ᵧ/d₂ₓ)(o₂ₓ) - (d₁ᵧ/d₁ₓ)(o₁ₓ)
48
+ // ⇒ x = (o₁ᵧ - o₂ᵧ + (d₂ᵧ/d₂ₓ)(o₂ₓ) - (d₁ᵧ/d₁ₓ)(o₁ₓ))/(d₂ᵧ/d₂ₓ - d₁ᵧ/d₁ₓ)
49
+ // = (d₁ₓd₂ₓ)(o₁ᵧ - o₂ᵧ + (d₂ᵧ/d₂ₓ)(o₂ₓ) - (d₁ᵧ/d₁ₓ)(o₁ₓ))/(d₂ᵧd₁ₓ - d₁ᵧd₂ₓ)
50
+ // = ((o₁ᵧ - o₂ᵧ)((d₁ₓd₂ₓ)) + (d₂ᵧd₁ₓ)(o₂ₓ) - (d₁ᵧd₂ₓ)(o₁ₓ))/(d₂ᵧd₁ₓ - d₁ᵧd₂ₓ)
51
+ // ⇒ y = o₁ᵧ + d₁ᵧ · (x - o₁ₓ) / d₁ₓ = ...
52
+ let resultPoint, resultT;
53
+ if (this.direction.x === 0) {
54
+ // Vertical line: Where does the other have x = this.point1.x?
55
+ // x = o₁ₓ = o₂ₓ + d₂ₓ · (y - o₂ᵧ) / d₂ᵧ
56
+ // ⇒ (o₁ₓ - o₂ₓ)(d₂ᵧ/d₂ₓ) + o₂ᵧ = y
57
+ // Avoid division by zero
58
+ if (other.direction.x === 0 || this.direction.y === 0) {
59
+ return null;
60
+ }
61
+ const xIntersect = this.point1.x;
62
+ const yIntersect = (this.point1.x - other.point1.x) * other.direction.y / other.direction.x + other.point1.y;
63
+ resultPoint = Vec2.of(xIntersect, yIntersect);
64
+ resultT = (yIntersect - this.point1.y) / this.direction.y;
65
+ }
66
+ else {
67
+ // From above,
68
+ // x = ((o₁ᵧ - o₂ᵧ)(d₁ₓd₂ₓ) + (d₂ᵧd₁ₓ)(o₂ₓ) - (d₁ᵧd₂ₓ)(o₁ₓ))/(d₂ᵧd₁ₓ - d₁ᵧd₂ₓ)
69
+ const numerator = ((this.point1.y - other.point1.y) * this.direction.x * other.direction.x
70
+ + this.direction.x * other.direction.y * other.point1.x
71
+ - this.direction.y * other.direction.x * this.point1.x);
72
+ const denominator = (other.direction.y * this.direction.x
73
+ - this.direction.y * other.direction.x);
74
+ // Avoid dividing by zero. It means there is no intersection
75
+ if (denominator === 0) {
76
+ return null;
77
+ }
78
+ const xIntersect = numerator / denominator;
79
+ const t1 = (xIntersect - this.point1.x) / this.direction.x;
80
+ const yIntersect = this.point1.y + this.direction.y * t1;
81
+ resultPoint = Vec2.of(xIntersect, yIntersect);
82
+ resultT = (xIntersect - this.point1.x) / this.direction.x;
83
+ }
84
+ // Ensure the result is in this/the other segment.
85
+ const resultToP1 = resultPoint.minus(this.point1).magnitude();
86
+ const resultToP2 = resultPoint.minus(this.point2).magnitude();
87
+ const resultToP3 = resultPoint.minus(other.point1).magnitude();
88
+ const resultToP4 = resultPoint.minus(other.point2).magnitude();
89
+ if (resultToP1 > this.length
90
+ || resultToP2 > this.length
91
+ || resultToP3 > other.length
92
+ || resultToP4 > other.length) {
93
+ return null;
94
+ }
95
+ return {
96
+ point: resultPoint,
97
+ t: resultT,
98
+ };
99
+ }
100
+ }
@@ -0,0 +1,31 @@
1
+ import { Point2, Vec2 } from './Vec2';
2
+ import Vec3 from './Vec3';
3
+ export default class Mat33 {
4
+ readonly a1: number;
5
+ readonly a2: number;
6
+ readonly a3: number;
7
+ readonly b1: number;
8
+ readonly b2: number;
9
+ readonly b3: number;
10
+ readonly c1: number;
11
+ readonly c2: number;
12
+ readonly c3: number;
13
+ private readonly rows;
14
+ constructor(a1: number, a2: number, a3: number, b1: number, b2: number, b3: number, c1: number, c2: number, c3: number);
15
+ static ofRows(r1: Vec3, r2: Vec3, r3: Vec3): Mat33;
16
+ static identity: Mat33;
17
+ inverse(): Mat33;
18
+ invertable(): boolean;
19
+ private cachedInverse;
20
+ private computeInverse;
21
+ transposed(): Mat33;
22
+ rightMul(other: Mat33): Mat33;
23
+ transformVec2(other: Vec3): Vec2;
24
+ transformVec3(other: Vec3): Vec3;
25
+ eq(other: Mat33, fuzz?: number): boolean;
26
+ toString(): string;
27
+ toArray(): number[];
28
+ static translation(amount: Vec2): Mat33;
29
+ static zRotation(radians: number, center?: Point2): Mat33;
30
+ static scaling2D(amount: number | Vec2, center?: Point2): Mat33;
31
+ }
@@ -0,0 +1,187 @@
1
+ import { Vec2 } from './Vec2';
2
+ import Vec3 from './Vec3';
3
+ // Represents a three dimensional linear transformation or
4
+ // a two-dimensional affine transformation. (An affine transformation scales/rotates/shears
5
+ // **and** translates while a linear transformation just scales/rotates/shears).
6
+ export default class Mat33 {
7
+ constructor(a1, a2, a3, b1, b2, b3, c1, c2, c3) {
8
+ this.a1 = a1;
9
+ this.a2 = a2;
10
+ this.a3 = a3;
11
+ this.b1 = b1;
12
+ this.b2 = b2;
13
+ this.b3 = b3;
14
+ this.c1 = c1;
15
+ this.c2 = c2;
16
+ this.c3 = c3;
17
+ this.cachedInverse = undefined;
18
+ this.rows = [
19
+ Vec3.of(a1, a2, a3),
20
+ Vec3.of(b1, b2, b3),
21
+ Vec3.of(c1, c2, c3),
22
+ ];
23
+ }
24
+ static ofRows(r1, r2, r3) {
25
+ return new Mat33(r1.x, r1.y, r1.z, r2.x, r2.y, r2.z, r3.x, r3.y, r3.z);
26
+ }
27
+ // Either returns the inverse of this, or, if this matrix is singular/uninvertable,
28
+ // returns Mat33.identity.
29
+ inverse() {
30
+ var _a;
31
+ return (_a = this.computeInverse()) !== null && _a !== void 0 ? _a : Mat33.identity;
32
+ }
33
+ invertable() {
34
+ return this.computeInverse() !== null;
35
+ }
36
+ computeInverse() {
37
+ if (this.cachedInverse !== undefined) {
38
+ return this.cachedInverse;
39
+ }
40
+ const toIdentity = [
41
+ this.rows[0],
42
+ this.rows[1],
43
+ this.rows[2],
44
+ ];
45
+ const toResult = [
46
+ Vec3.unitX,
47
+ Vec3.unitY,
48
+ Vec3.unitZ,
49
+ ];
50
+ // Convert toIdentity to the identity matrix and
51
+ // toResult to the inverse through elementary row operations
52
+ for (let cursor = 0; cursor < 3; cursor++) {
53
+ // Select the [cursor]th diagonal entry
54
+ let pivot = toIdentity[cursor].at(cursor);
55
+ // Don't divide by zero (treat very small numbers as zero).
56
+ const minDivideBy = 1e-10;
57
+ if (Math.abs(pivot) < minDivideBy) {
58
+ let swapIndex = -1;
59
+ // For all other rows,
60
+ for (let i = 1; i <= 2; i++) {
61
+ const otherRowIdx = (cursor + i) % 3;
62
+ if (Math.abs(toIdentity[otherRowIdx].at(cursor)) >= minDivideBy) {
63
+ swapIndex = otherRowIdx;
64
+ break;
65
+ }
66
+ }
67
+ // Can't swap with another row?
68
+ if (swapIndex === -1) {
69
+ this.cachedInverse = null;
70
+ return null;
71
+ }
72
+ const tmpIdentityRow = toIdentity[cursor];
73
+ const tmpResultRow = toResult[cursor];
74
+ // Swap!
75
+ toIdentity[cursor] = toIdentity[swapIndex];
76
+ toResult[cursor] = toResult[swapIndex];
77
+ toIdentity[swapIndex] = tmpIdentityRow;
78
+ toResult[swapIndex] = tmpResultRow;
79
+ pivot = toIdentity[cursor].at(cursor);
80
+ }
81
+ // Make toIdentity[k = cursor] = 1
82
+ let scale = 1.0 / pivot;
83
+ toIdentity[cursor] = toIdentity[cursor].times(scale);
84
+ toResult[cursor] = toResult[cursor].times(scale);
85
+ const cursorToIdentityRow = toIdentity[cursor];
86
+ const cursorToResultRow = toResult[cursor];
87
+ // Make toIdentity[k ≠ cursor] = 0
88
+ for (let i = 1; i <= 2; i++) {
89
+ const otherRowIdx = (cursor + i) % 3;
90
+ scale = -toIdentity[otherRowIdx].at(cursor);
91
+ toIdentity[otherRowIdx] = toIdentity[otherRowIdx].plus(cursorToIdentityRow.times(scale));
92
+ toResult[otherRowIdx] = toResult[otherRowIdx].plus(cursorToResultRow.times(scale));
93
+ }
94
+ }
95
+ const inverse = Mat33.ofRows(toResult[0], toResult[1], toResult[2]);
96
+ this.cachedInverse = inverse;
97
+ return inverse;
98
+ }
99
+ transposed() {
100
+ return new Mat33(this.a1, this.b1, this.c1, this.a2, this.b2, this.c2, this.a3, this.b3, this.c3);
101
+ }
102
+ rightMul(other) {
103
+ other = other.transposed();
104
+ const at = (row, col) => {
105
+ return this.rows[row].dot(other.rows[col]);
106
+ };
107
+ return new Mat33(at(0, 0), at(0, 1), at(0, 2), at(1, 0), at(1, 1), at(1, 2), at(2, 0), at(2, 1), at(2, 2));
108
+ }
109
+ // Applies this as an affine transformation to the given vector.
110
+ // Returns a transformed version of [other].
111
+ transformVec2(other) {
112
+ // When transforming a Vec2, we want to use the z transformation
113
+ // components of this for translation:
114
+ // ⎡ . . tX ⎤
115
+ // ⎢ . . tY ⎥
116
+ // ⎣ 0 0 1 ⎦
117
+ // For this, we need other's z component to be 1 (so that tX and tY
118
+ // are scaled by 1):
119
+ let intermediate = Vec3.of(other.x, other.y, 1);
120
+ intermediate = this.transformVec3(intermediate);
121
+ // Drop the z=1 to allow magnitude to work as expected
122
+ return Vec2.of(intermediate.x, intermediate.y);
123
+ }
124
+ // Applies this as a linear transformation to the given vector (doesn't translate).
125
+ // This is the standard way of transforming vectors in ℝ³.
126
+ transformVec3(other) {
127
+ return Vec3.of(this.rows[0].dot(other), this.rows[1].dot(other), this.rows[2].dot(other));
128
+ }
129
+ // Returns true iff this = other ± fuzz
130
+ eq(other, fuzz = 0) {
131
+ for (let i = 0; i < 3; i++) {
132
+ if (!this.rows[i].eq(other.rows[i], fuzz)) {
133
+ return false;
134
+ }
135
+ }
136
+ return true;
137
+ }
138
+ toString() {
139
+ return `
140
+ ⎡ ${this.a1},\t ${this.a2},\t ${this.a3}\t ⎤
141
+ ⎢ ${this.b1},\t ${this.b2},\t ${this.b3}\t ⎥
142
+ ⎣ ${this.c1},\t ${this.c2},\t ${this.c3}\t ⎦
143
+ `.trimRight();
144
+ }
145
+ // result[0] = top left element
146
+ // result[1] = element at row zero, column 1
147
+ // ...
148
+ toArray() {
149
+ return [
150
+ this.a1, this.a2, this.a3,
151
+ this.b1, this.b2, this.b3,
152
+ this.c1, this.c2, this.c3,
153
+ ];
154
+ }
155
+ // Constructs a 3x3 translation matrix (for translating Vec2s)
156
+ static translation(amount) {
157
+ // When transforming Vec2s by a 3x3 matrix, we give the input
158
+ // Vec2s z = 1. As such,
159
+ // outVec2.x = inVec2.x * 1 + inVec2.y * 0 + 1 * amount.x
160
+ // ...
161
+ return new Mat33(1, 0, amount.x, 0, 1, amount.y, 0, 0, 1);
162
+ }
163
+ static zRotation(radians, center = Vec2.zero) {
164
+ const cos = Math.cos(radians);
165
+ const sin = Math.sin(radians);
166
+ // Translate everything so that rotation is about the origin
167
+ let result = Mat33.translation(center);
168
+ result = result.rightMul(new Mat33(cos, -sin, 0, sin, cos, 0, 0, 0, 1));
169
+ return result.rightMul(Mat33.translation(center.times(-1)));
170
+ }
171
+ static scaling2D(amount, center = Vec2.zero) {
172
+ let result = Mat33.translation(center);
173
+ let xAmount, yAmount;
174
+ if (typeof amount === 'number') {
175
+ xAmount = amount;
176
+ yAmount = amount;
177
+ }
178
+ else {
179
+ xAmount = amount.x;
180
+ yAmount = amount.y;
181
+ }
182
+ result = result.rightMul(new Mat33(xAmount, 0, 0, 0, yAmount, 0, 0, 0, 1));
183
+ // Translate such that [center] goes to (0, 0)
184
+ return result.rightMul(Mat33.translation(center.times(-1)));
185
+ }
186
+ }
187
+ Mat33.identity = new Mat33(1, 0, 0, 0, 1, 0, 0, 0, 1);
@@ -0,0 +1,55 @@
1
+ import { Bezier } from 'bezier-js';
2
+ import { RenderingStyle, RenderablePathSpec } from '../rendering/AbstractRenderer';
3
+ import LineSegment2 from './LineSegment2';
4
+ import Mat33 from './Mat33';
5
+ import Rect2 from './Rect2';
6
+ import { Point2 } from './Vec2';
7
+ export declare enum PathCommandType {
8
+ LineTo = 0,
9
+ MoveTo = 1,
10
+ CubicBezierTo = 2,
11
+ QuadraticBezierTo = 3
12
+ }
13
+ export interface CubicBezierPathCommand {
14
+ kind: PathCommandType.CubicBezierTo;
15
+ controlPoint1: Point2;
16
+ controlPoint2: Point2;
17
+ endPoint: Point2;
18
+ }
19
+ export interface QuadraticBezierPathCommand {
20
+ kind: PathCommandType.QuadraticBezierTo;
21
+ controlPoint: Point2;
22
+ endPoint: Point2;
23
+ }
24
+ export interface LinePathCommand {
25
+ kind: PathCommandType.LineTo;
26
+ point: Point2;
27
+ }
28
+ export interface MoveToPathCommand {
29
+ kind: PathCommandType.MoveTo;
30
+ point: Point2;
31
+ }
32
+ export declare type PathCommand = CubicBezierPathCommand | LinePathCommand | QuadraticBezierPathCommand | MoveToPathCommand;
33
+ interface IntersectionResult {
34
+ curve: LineSegment2 | Bezier;
35
+ parameterValue: number;
36
+ point: Point2;
37
+ }
38
+ export default class Path {
39
+ readonly startPoint: Point2;
40
+ readonly parts: PathCommand[];
41
+ private cachedGeometry;
42
+ readonly bbox: Rect2;
43
+ constructor(startPoint: Point2, parts: PathCommand[]);
44
+ get geometry(): Array<LineSegment2 | Bezier>;
45
+ static computeBBoxForSegment(startPoint: Point2, part: PathCommand): Rect2;
46
+ intersection(line: LineSegment2): IntersectionResult[];
47
+ transformedBy(affineTransfm: Mat33): Path;
48
+ union(other: Path | null): Path;
49
+ static fromRenderable(renderable: RenderablePathSpec): Path;
50
+ toRenderable(fill: RenderingStyle): RenderablePathSpec;
51
+ toString(): string;
52
+ static toString(startPoint: Point2, parts: PathCommand[]): string;
53
+ static fromString(pathString: string): Path;
54
+ }
55
+ export {};