js-draw 0.3.0 → 0.3.2

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 (113) hide show
  1. package/.github/ISSUE_TEMPLATE/translation.md +4 -1
  2. package/CHANGELOG.md +15 -0
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/Editor.d.ts +4 -1
  5. package/dist/src/Editor.js +117 -2
  6. package/dist/src/EditorImage.js +4 -1
  7. package/dist/src/SVGLoader.d.ts +4 -1
  8. package/dist/src/SVGLoader.js +78 -33
  9. package/dist/src/UndoRedoHistory.d.ts +1 -0
  10. package/dist/src/UndoRedoHistory.js +6 -0
  11. package/dist/src/Viewport.d.ts +1 -0
  12. package/dist/src/Viewport.js +12 -4
  13. package/dist/src/commands/lib.d.ts +2 -1
  14. package/dist/src/commands/lib.js +2 -1
  15. package/dist/src/commands/localization.d.ts +1 -0
  16. package/dist/src/commands/localization.js +1 -0
  17. package/dist/src/commands/uniteCommands.d.ts +4 -0
  18. package/dist/src/commands/uniteCommands.js +105 -0
  19. package/dist/src/components/AbstractComponent.d.ts +2 -0
  20. package/dist/src/components/AbstractComponent.js +41 -5
  21. package/dist/src/components/ImageComponent.d.ts +27 -0
  22. package/dist/src/components/ImageComponent.js +129 -0
  23. package/dist/src/components/Stroke.js +11 -6
  24. package/dist/src/components/builders/FreehandLineBuilder.js +7 -7
  25. package/dist/src/components/lib.d.ts +4 -2
  26. package/dist/src/components/lib.js +4 -2
  27. package/dist/src/components/localization.d.ts +2 -0
  28. package/dist/src/components/localization.js +2 -0
  29. package/dist/src/math/LineSegment2.d.ts +4 -0
  30. package/dist/src/math/LineSegment2.js +9 -0
  31. package/dist/src/math/Path.d.ts +5 -1
  32. package/dist/src/math/Path.js +89 -7
  33. package/dist/src/math/Rect2.js +1 -1
  34. package/dist/src/math/Triangle.d.ts +11 -0
  35. package/dist/src/math/Triangle.js +19 -0
  36. package/dist/src/rendering/Display.js +2 -2
  37. package/dist/src/rendering/localization.d.ts +3 -0
  38. package/dist/src/rendering/localization.js +3 -0
  39. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +9 -1
  40. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
  41. package/dist/src/rendering/renderers/CanvasRenderer.js +7 -0
  42. package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -1
  43. package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
  44. package/dist/src/rendering/renderers/SVGRenderer.d.ts +14 -12
  45. package/dist/src/rendering/renderers/SVGRenderer.js +71 -87
  46. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +3 -1
  47. package/dist/src/rendering/renderers/TextOnlyRenderer.js +8 -1
  48. package/dist/src/toolbar/HTMLToolbar.d.ts +1 -0
  49. package/dist/src/toolbar/HTMLToolbar.js +1 -0
  50. package/dist/src/toolbar/widgets/BaseWidget.d.ts +3 -0
  51. package/dist/src/toolbar/widgets/BaseWidget.js +21 -1
  52. package/dist/src/tools/BaseTool.d.ts +4 -1
  53. package/dist/src/tools/BaseTool.js +12 -0
  54. package/dist/src/tools/PasteHandler.d.ts +16 -0
  55. package/dist/src/tools/PasteHandler.js +142 -0
  56. package/dist/src/tools/Pen.d.ts +2 -1
  57. package/dist/src/tools/Pen.js +16 -0
  58. package/dist/src/tools/SelectionTool.d.ts +7 -1
  59. package/dist/src/tools/SelectionTool.js +63 -5
  60. package/dist/src/tools/ToolController.d.ts +1 -0
  61. package/dist/src/tools/ToolController.js +45 -29
  62. package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
  63. package/dist/src/tools/ToolSwitcherShortcut.js +26 -0
  64. package/dist/src/tools/lib.d.ts +2 -0
  65. package/dist/src/tools/lib.js +2 -0
  66. package/dist/src/tools/localization.d.ts +4 -0
  67. package/dist/src/tools/localization.js +4 -0
  68. package/dist/src/types.d.ts +21 -4
  69. package/dist/src/types.js +3 -0
  70. package/package.json +2 -2
  71. package/src/Editor.ts +131 -2
  72. package/src/EditorImage.ts +7 -1
  73. package/src/SVGLoader.ts +90 -36
  74. package/src/UndoRedoHistory.test.ts +33 -0
  75. package/src/UndoRedoHistory.ts +8 -0
  76. package/src/Viewport.ts +13 -4
  77. package/src/commands/lib.ts +2 -0
  78. package/src/commands/localization.ts +2 -0
  79. package/src/commands/uniteCommands.test.ts +23 -0
  80. package/src/commands/uniteCommands.ts +121 -0
  81. package/src/components/AbstractComponent.ts +55 -9
  82. package/src/components/ImageComponent.ts +153 -0
  83. package/src/components/Stroke.test.ts +5 -0
  84. package/src/components/Stroke.ts +13 -7
  85. package/src/components/builders/FreehandLineBuilder.ts +7 -7
  86. package/src/components/lib.ts +7 -2
  87. package/src/components/localization.ts +4 -0
  88. package/src/math/LineSegment2.test.ts +9 -0
  89. package/src/math/LineSegment2.ts +13 -0
  90. package/src/math/Path.test.ts +53 -0
  91. package/src/math/Path.toString.test.ts +4 -2
  92. package/src/math/Path.ts +109 -11
  93. package/src/math/Rect2.ts +1 -1
  94. package/src/math/Triangle.ts +29 -0
  95. package/src/rendering/Display.ts +2 -2
  96. package/src/rendering/localization.ts +6 -0
  97. package/src/rendering/renderers/AbstractRenderer.ts +17 -0
  98. package/src/rendering/renderers/CanvasRenderer.ts +10 -1
  99. package/src/rendering/renderers/DummyRenderer.ts +6 -1
  100. package/src/rendering/renderers/SVGRenderer.ts +76 -101
  101. package/src/rendering/renderers/TextOnlyRenderer.ts +10 -2
  102. package/src/toolbar/HTMLToolbar.ts +1 -1
  103. package/src/toolbar/types.ts +1 -1
  104. package/src/toolbar/widgets/BaseWidget.ts +27 -1
  105. package/src/tools/BaseTool.ts +17 -1
  106. package/src/tools/PasteHandler.ts +156 -0
  107. package/src/tools/Pen.ts +20 -1
  108. package/src/tools/SelectionTool.ts +80 -8
  109. package/src/tools/ToolController.ts +60 -46
  110. package/src/tools/ToolSwitcherShortcut.ts +34 -0
  111. package/src/tools/lib.ts +2 -0
  112. package/src/tools/localization.ts +10 -0
  113. package/src/types.ts +29 -3
@@ -1,7 +1,7 @@
1
1
  import { LoadSaveDataTable } from '../../components/AbstractComponent';
2
2
  import { TextStyle } from '../../components/Text';
3
3
  import Mat33 from '../../math/Mat33';
4
- import { PathCommand } from '../../math/Path';
4
+ import Path, { PathCommand } from '../../math/Path';
5
5
  import Rect2 from '../../math/Rect2';
6
6
  import { Point2, Vec2 } from '../../math/Vec2';
7
7
  import Viewport from '../../Viewport';
@@ -10,6 +10,13 @@ export interface RenderablePathSpec {
10
10
  startPoint: Point2;
11
11
  commands: PathCommand[];
12
12
  style: RenderingStyle;
13
+ path?: Path;
14
+ }
15
+ export interface RenderableImage {
16
+ transform: Mat33;
17
+ image: HTMLImageElement | HTMLCanvasElement;
18
+ base64Url: string;
19
+ label?: string;
13
20
  }
14
21
  export default abstract class AbstractRenderer {
15
22
  private viewport;
@@ -26,6 +33,7 @@ export default abstract class AbstractRenderer {
26
33
  protected abstract traceCubicBezierCurve(p1: Point2, p2: Point2, p3: Point2): void;
27
34
  protected abstract traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2): void;
28
35
  abstract drawText(text: string, transform: Mat33, style: TextStyle): void;
36
+ abstract drawImage(image: RenderableImage): void;
29
37
  abstract isTooSmallToRender(rect: Rect2): boolean;
30
38
  setDraftMode(_draftMode: boolean): void;
31
39
  protected objectLevel: number;
@@ -5,7 +5,7 @@ import { Point2, Vec2 } from '../../math/Vec2';
5
5
  import Vec3 from '../../math/Vec3';
6
6
  import Viewport from '../../Viewport';
7
7
  import RenderingStyle from '../RenderingStyle';
8
- import AbstractRenderer, { RenderablePathSpec } from './AbstractRenderer';
8
+ import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
9
9
  export default class CanvasRenderer extends AbstractRenderer {
10
10
  private ctx;
11
11
  private ignoreObjectsAboveLevel;
@@ -28,6 +28,7 @@ export default class CanvasRenderer extends AbstractRenderer {
28
28
  protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3): void;
29
29
  drawPath(path: RenderablePathSpec): void;
30
30
  drawText(text: string, transform: Mat33, style: TextStyle): void;
31
+ drawImage(image: RenderableImage): void;
31
32
  private clipLevels;
32
33
  startObject(boundingBox: Rect2, clip: boolean): void;
33
34
  endObject(): void;
@@ -125,6 +125,13 @@ export default class CanvasRenderer extends AbstractRenderer {
125
125
  }
126
126
  this.ctx.restore();
127
127
  }
128
+ drawImage(image) {
129
+ this.ctx.save();
130
+ const transform = this.getCanvasToScreenTransform().rightMul(image.transform);
131
+ this.transformBy(transform);
132
+ this.ctx.drawImage(image.image, 0, 0);
133
+ this.ctx.restore();
134
+ }
128
135
  startObject(boundingBox, clip) {
129
136
  if (this.isTooSmallToRender(boundingBox)) {
130
137
  this.ignoreObjectsAboveLevel = this.getNestingLevel();
@@ -5,7 +5,7 @@ import { Point2, Vec2 } from '../../math/Vec2';
5
5
  import Vec3 from '../../math/Vec3';
6
6
  import Viewport from '../../Viewport';
7
7
  import RenderingStyle from '../RenderingStyle';
8
- import AbstractRenderer from './AbstractRenderer';
8
+ import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
9
9
  export default class DummyRenderer extends AbstractRenderer {
10
10
  clearedCount: number;
11
11
  renderedPathCount: number;
@@ -13,6 +13,7 @@ export default class DummyRenderer extends AbstractRenderer {
13
13
  lastPoint: Point2 | null;
14
14
  objectNestingLevel: number;
15
15
  lastText: string | null;
16
+ lastImage: RenderableImage | null;
16
17
  pointBuffer: Point2[];
17
18
  constructor(viewport: Viewport);
18
19
  displaySize(): Vec2;
@@ -25,6 +26,7 @@ export default class DummyRenderer extends AbstractRenderer {
25
26
  protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3): void;
26
27
  drawPoints(..._points: Vec3[]): void;
27
28
  drawText(text: string, _transform: Mat33, _style: TextStyle): void;
29
+ drawImage(image: RenderableImage): void;
28
30
  startObject(boundingBox: Rect2, _clip: boolean): void;
29
31
  endObject(): void;
30
32
  isTooSmallToRender(_rect: Rect2): boolean;
@@ -11,6 +11,7 @@ export default class DummyRenderer extends AbstractRenderer {
11
11
  this.lastPoint = null;
12
12
  this.objectNestingLevel = 0;
13
13
  this.lastText = null;
14
+ this.lastImage = null;
14
15
  // List of points drawn since the last clear.
15
16
  this.pointBuffer = [];
16
17
  }
@@ -30,6 +31,7 @@ export default class DummyRenderer extends AbstractRenderer {
30
31
  this.renderedPathCount = 0;
31
32
  this.pointBuffer = [];
32
33
  this.lastText = null;
34
+ this.lastImage = null;
33
35
  // Ensure all objects finished rendering
34
36
  if (this.objectNestingLevel > 0) {
35
37
  throw new Error(`Within an object while clearing! Nesting level: ${this.objectNestingLevel}`);
@@ -73,6 +75,9 @@ export default class DummyRenderer extends AbstractRenderer {
73
75
  drawText(text, _transform, _style) {
74
76
  this.lastText = text;
75
77
  }
78
+ drawImage(image) {
79
+ this.lastImage = image;
80
+ }
76
81
  startObject(boundingBox, _clip) {
77
82
  super.startObject(boundingBox);
78
83
  this.objectNestingLevel += 1;
@@ -5,30 +5,32 @@ import Rect2 from '../../math/Rect2';
5
5
  import { Point2, Vec2 } from '../../math/Vec2';
6
6
  import Viewport from '../../Viewport';
7
7
  import RenderingStyle from '../RenderingStyle';
8
- import AbstractRenderer from './AbstractRenderer';
8
+ import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
9
9
  export default class SVGRenderer extends AbstractRenderer {
10
10
  private elem;
11
- private currentPath;
12
- private pathStart;
11
+ private sanitize;
13
12
  private lastPathStyle;
14
- private lastPath;
15
- private lastPathStart;
13
+ private lastPathString;
16
14
  private objectElems;
17
15
  private overwrittenAttrs;
18
- constructor(elem: SVGSVGElement, viewport: Viewport);
16
+ constructor(elem: SVGSVGElement, viewport: Viewport, sanitize?: boolean);
19
17
  setRootSVGAttribute(name: string, value: string | null): void;
20
18
  displaySize(): Vec2;
21
19
  clear(): void;
22
- protected beginPath(startPoint: Point2): void;
23
- protected endPath(style: RenderingStyle): void;
24
20
  private addPathToSVG;
21
+ drawPath(pathSpec: RenderablePathSpec): void;
22
+ private transformFrom;
25
23
  drawText(text: string, transform: Mat33, style: TextStyle): void;
24
+ drawImage(image: RenderableImage): void;
26
25
  startObject(boundingBox: Rect2): void;
27
26
  endObject(loaderData?: LoadSaveDataTable): void;
28
- protected lineTo(point: Point2): void;
29
- protected moveTo(point: Point2): void;
30
- protected traceCubicBezierCurve(controlPoint1: Point2, controlPoint2: Point2, endPoint: Point2): void;
31
- protected traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2): void;
27
+ private unimplementedMessage;
28
+ protected beginPath(_startPoint: Point2): void;
29
+ protected endPath(_style: RenderingStyle): void;
30
+ protected lineTo(_point: Point2): void;
31
+ protected moveTo(_point: Point2): void;
32
+ protected traceCubicBezierCurve(_controlPoint1: Point2, _controlPoint2: Point2, _endPoint: Point2): void;
33
+ protected traceQuadraticBezierCurve(_controlPoint: Point2, _endPoint: Point2): void;
32
34
  drawPoints(...points: Point2[]): void;
33
35
  drawSVGElem(elem: SVGElement): void;
34
36
  isTooSmallToRender(_rect: Rect2): boolean;
@@ -1,20 +1,27 @@
1
1
  import Mat33 from '../../math/Mat33';
2
- import Path, { PathCommandType } from '../../math/Path';
2
+ import Path from '../../math/Path';
3
3
  import { toRoundedString } from '../../math/rounding';
4
4
  import { Vec2 } from '../../math/Vec2';
5
5
  import { svgAttributesDataKey, svgStyleAttributesDataKey } from '../../SVGLoader';
6
6
  import AbstractRenderer from './AbstractRenderer';
7
7
  const svgNameSpace = 'http://www.w3.org/2000/svg';
8
8
  export default class SVGRenderer extends AbstractRenderer {
9
- constructor(elem, viewport) {
9
+ // Renders onto `elem`. If `sanitize`, don't render potentially untrusted data.
10
+ constructor(elem, viewport, sanitize = false) {
10
11
  super(viewport);
11
12
  this.elem = elem;
13
+ this.sanitize = sanitize;
14
+ this.lastPathStyle = null;
15
+ this.lastPathString = [];
12
16
  this.objectElems = null;
13
17
  this.overwrittenAttrs = {};
14
18
  this.clear();
15
19
  }
16
20
  // Sets an attribute on the root SVG element.
17
21
  setRootSVGAttribute(name, value) {
22
+ if (this.sanitize) {
23
+ return;
24
+ }
18
25
  // Make the original value of the attribute restorable on clear
19
26
  if (!(name in this.overwrittenAttrs)) {
20
27
  this.overwrittenAttrs[name] = this.elem.getAttribute(name);
@@ -30,55 +37,29 @@ export default class SVGRenderer extends AbstractRenderer {
30
37
  return Vec2.of(this.elem.clientWidth, this.elem.clientHeight);
31
38
  }
32
39
  clear() {
33
- // Restore all alltributes
34
- for (const attrName in this.overwrittenAttrs) {
35
- const value = this.overwrittenAttrs[attrName];
36
- if (value) {
37
- this.elem.setAttribute(attrName, value);
38
- }
39
- else {
40
- this.elem.removeAttribute(attrName);
40
+ this.lastPathString = [];
41
+ if (!this.sanitize) {
42
+ // Restore all all attributes
43
+ for (const attrName in this.overwrittenAttrs) {
44
+ const value = this.overwrittenAttrs[attrName];
45
+ if (value) {
46
+ this.elem.setAttribute(attrName, value);
47
+ }
48
+ else {
49
+ this.elem.removeAttribute(attrName);
50
+ }
41
51
  }
42
- }
43
- this.overwrittenAttrs = {};
44
- }
45
- beginPath(startPoint) {
46
- var _a;
47
- this.currentPath = [];
48
- this.pathStart = this.canvasToScreen(startPoint);
49
- (_a = this.lastPathStart) !== null && _a !== void 0 ? _a : (this.lastPathStart = this.pathStart);
50
- }
51
- endPath(style) {
52
- var _a;
53
- if (this.currentPath == null) {
54
- throw new Error('No path exists to end! Make sure beginPath was called!');
55
- }
56
- // Try to extend the previous path, if possible
57
- if (style.fill.eq((_a = this.lastPathStyle) === null || _a === void 0 ? void 0 : _a.fill) && this.lastPath != null) {
58
- this.lastPath.push({
59
- kind: PathCommandType.MoveTo,
60
- point: this.pathStart,
61
- }, ...this.currentPath);
62
- this.pathStart = null;
63
- this.currentPath = null;
64
- }
65
- else {
66
- this.addPathToSVG();
67
- this.lastPathStart = this.pathStart;
68
- this.lastPathStyle = style;
69
- this.lastPath = this.currentPath;
70
- this.pathStart = null;
71
- this.currentPath = null;
52
+ this.overwrittenAttrs = {};
72
53
  }
73
54
  }
74
55
  // Push [this.fullPath] to the SVG
75
56
  addPathToSVG() {
76
57
  var _a;
77
- if (!this.lastPathStyle || !this.lastPath) {
58
+ if (!this.lastPathStyle || this.lastPathString.length === 0) {
78
59
  return;
79
60
  }
80
61
  const pathElem = document.createElementNS(svgNameSpace, 'path');
81
- pathElem.setAttribute('d', Path.toString(this.lastPathStart, this.lastPath));
62
+ pathElem.setAttribute('d', this.lastPathString.join(' '));
82
63
  const style = this.lastPathStyle;
83
64
  pathElem.setAttribute('fill', style.fill.toHexString());
84
65
  if (style.stroke) {
@@ -88,25 +69,41 @@ export default class SVGRenderer extends AbstractRenderer {
88
69
  this.elem.appendChild(pathElem);
89
70
  (_a = this.objectElems) === null || _a === void 0 ? void 0 : _a.push(pathElem);
90
71
  }
91
- drawText(text, transform, style) {
92
- var _a, _b, _c;
93
- transform = this.getCanvasToScreenTransform().rightMul(transform);
72
+ drawPath(pathSpec) {
73
+ var _a;
74
+ const style = pathSpec.style;
75
+ const path = Path.fromRenderable(pathSpec);
76
+ // Try to extend the previous path, if possible
77
+ if (!style.fill.eq((_a = this.lastPathStyle) === null || _a === void 0 ? void 0 : _a.fill) || this.lastPathString.length === 0) {
78
+ this.addPathToSVG();
79
+ this.lastPathStyle = style;
80
+ this.lastPathString = [];
81
+ }
82
+ this.lastPathString.push(path.toString());
83
+ }
84
+ // Apply [elemTransform] to [elem].
85
+ transformFrom(elemTransform, elem) {
86
+ let transform = this.getCanvasToScreenTransform().rightMul(elemTransform);
94
87
  const translation = transform.transformVec2(Vec2.zero);
95
88
  transform = transform.rightMul(Mat33.translation(translation.times(-1)));
96
- const textElem = document.createElementNS(svgNameSpace, 'text');
97
- textElem.appendChild(document.createTextNode(text));
98
- textElem.style.transform = `matrix(
89
+ elem.style.transform = `matrix(
99
90
  ${transform.a1}, ${transform.b1},
100
91
  ${transform.a2}, ${transform.b2},
101
92
  ${transform.a3}, ${transform.b3}
102
93
  )`;
94
+ elem.setAttribute('x', `${toRoundedString(translation.x)}`);
95
+ elem.setAttribute('y', `${toRoundedString(translation.y)}`);
96
+ }
97
+ drawText(text, transform, style) {
98
+ var _a, _b, _c;
99
+ const textElem = document.createElementNS(svgNameSpace, 'text');
100
+ textElem.appendChild(document.createTextNode(text));
101
+ this.transformFrom(transform, textElem);
103
102
  textElem.style.fontFamily = style.fontFamily;
104
103
  textElem.style.fontVariant = (_a = style.fontVariant) !== null && _a !== void 0 ? _a : '';
105
104
  textElem.style.fontWeight = (_b = style.fontWeight) !== null && _b !== void 0 ? _b : '';
106
105
  textElem.style.fontSize = style.size + 'px';
107
106
  textElem.style.fill = style.renderingStyle.fill.toHexString();
108
- textElem.setAttribute('x', `${toRoundedString(translation.x)}`);
109
- textElem.setAttribute('y', `${toRoundedString(translation.y)}`);
110
107
  if (style.renderingStyle.stroke) {
111
108
  const strokeStyle = style.renderingStyle.stroke;
112
109
  textElem.style.stroke = strokeStyle.color.toHexString();
@@ -115,11 +112,21 @@ export default class SVGRenderer extends AbstractRenderer {
115
112
  this.elem.appendChild(textElem);
116
113
  (_c = this.objectElems) === null || _c === void 0 ? void 0 : _c.push(textElem);
117
114
  }
115
+ drawImage(image) {
116
+ var _a, _b, _c, _d, _e;
117
+ const svgImgElem = document.createElementNS(svgNameSpace, 'image');
118
+ svgImgElem.setAttribute('href', image.base64Url);
119
+ svgImgElem.setAttribute('width', (_a = image.image.getAttribute('width')) !== null && _a !== void 0 ? _a : '');
120
+ svgImgElem.setAttribute('height', (_b = image.image.getAttribute('height')) !== null && _b !== void 0 ? _b : '');
121
+ svgImgElem.setAttribute('aria-label', (_d = (_c = image.image.getAttribute('aria-label')) !== null && _c !== void 0 ? _c : image.image.getAttribute('alt')) !== null && _d !== void 0 ? _d : '');
122
+ this.transformFrom(image.transform, svgImgElem);
123
+ this.elem.appendChild(svgImgElem);
124
+ (_e = this.objectElems) === null || _e === void 0 ? void 0 : _e.push(svgImgElem);
125
+ }
118
126
  startObject(boundingBox) {
119
127
  super.startObject(boundingBox);
120
128
  // Only accumulate a path within an object
121
- this.lastPath = null;
122
- this.lastPathStart = null;
129
+ this.lastPathString = [];
123
130
  this.lastPathStyle = null;
124
131
  this.objectElems = [];
125
132
  }
@@ -128,7 +135,7 @@ export default class SVGRenderer extends AbstractRenderer {
128
135
  super.endObject(loaderData);
129
136
  // Don't extend paths across objects
130
137
  this.addPathToSVG();
131
- if (loaderData) {
138
+ if (loaderData && !this.sanitize) {
132
139
  // Restore any attributes unsupported by the app.
133
140
  for (const elem of (_a = this.objectElems) !== null && _a !== void 0 ? _a : []) {
134
141
  const attrs = loaderData[svgAttributesDataKey];
@@ -146,40 +153,14 @@ export default class SVGRenderer extends AbstractRenderer {
146
153
  }
147
154
  }
148
155
  }
149
- lineTo(point) {
150
- point = this.canvasToScreen(point);
151
- this.currentPath.push({
152
- kind: PathCommandType.LineTo,
153
- point,
154
- });
155
- }
156
- moveTo(point) {
157
- point = this.canvasToScreen(point);
158
- this.currentPath.push({
159
- kind: PathCommandType.MoveTo,
160
- point,
161
- });
162
- }
163
- traceCubicBezierCurve(controlPoint1, controlPoint2, endPoint) {
164
- controlPoint1 = this.canvasToScreen(controlPoint1);
165
- controlPoint2 = this.canvasToScreen(controlPoint2);
166
- endPoint = this.canvasToScreen(endPoint);
167
- this.currentPath.push({
168
- kind: PathCommandType.CubicBezierTo,
169
- controlPoint1,
170
- controlPoint2,
171
- endPoint,
172
- });
173
- }
174
- traceQuadraticBezierCurve(controlPoint, endPoint) {
175
- controlPoint = this.canvasToScreen(controlPoint);
176
- endPoint = this.canvasToScreen(endPoint);
177
- this.currentPath.push({
178
- kind: PathCommandType.QuadraticBezierTo,
179
- controlPoint,
180
- endPoint,
181
- });
182
- }
156
+ // Not implemented -- use drawPath instead.
157
+ unimplementedMessage() { throw new Error('Not implemenented!'); }
158
+ beginPath(_startPoint) { this.unimplementedMessage(); }
159
+ endPath(_style) { this.unimplementedMessage(); }
160
+ lineTo(_point) { this.unimplementedMessage(); }
161
+ moveTo(_point) { this.unimplementedMessage(); }
162
+ traceCubicBezierCurve(_controlPoint1, _controlPoint2, _endPoint) { this.unimplementedMessage(); }
163
+ traceQuadraticBezierCurve(_controlPoint, _endPoint) { this.unimplementedMessage(); }
183
164
  drawPoints(...points) {
184
165
  points.map(point => {
185
166
  const elem = document.createElementNS(svgNameSpace, 'circle');
@@ -191,6 +172,9 @@ export default class SVGRenderer extends AbstractRenderer {
191
172
  }
192
173
  // Renders a **copy** of the given element.
193
174
  drawSVGElem(elem) {
175
+ if (this.sanitize) {
176
+ return;
177
+ }
194
178
  this.elem.appendChild(elem.cloneNode(true));
195
179
  }
196
180
  isTooSmallToRender(_rect) {
@@ -5,12 +5,13 @@ import Vec3 from '../../math/Vec3';
5
5
  import Viewport from '../../Viewport';
6
6
  import { TextRendererLocalization } from '../localization';
7
7
  import RenderingStyle from '../RenderingStyle';
8
- import AbstractRenderer from './AbstractRenderer';
8
+ import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
9
9
  export default class TextOnlyRenderer extends AbstractRenderer {
10
10
  private localizationTable;
11
11
  private descriptionBuilder;
12
12
  private pathCount;
13
13
  private textNodeCount;
14
+ private imageNodeCount;
14
15
  constructor(viewport: Viewport, localizationTable: TextRendererLocalization);
15
16
  displaySize(): Vec3;
16
17
  clear(): void;
@@ -22,6 +23,7 @@ export default class TextOnlyRenderer extends AbstractRenderer {
22
23
  protected traceCubicBezierCurve(_p1: Vec3, _p2: Vec3, _p3: Vec3): void;
23
24
  protected traceQuadraticBezierCurve(_controlPoint: Vec3, _endPoint: Vec3): void;
24
25
  drawText(text: string, _transform: Mat33, _style: TextStyle): void;
26
+ drawImage(image: RenderableImage): void;
25
27
  isTooSmallToRender(rect: Rect2): boolean;
26
28
  drawPoints(..._points: Vec3[]): void;
27
29
  }
@@ -8,6 +8,7 @@ export default class TextOnlyRenderer extends AbstractRenderer {
8
8
  this.descriptionBuilder = [];
9
9
  this.pathCount = 0;
10
10
  this.textNodeCount = 0;
11
+ this.imageNodeCount = 0;
11
12
  }
12
13
  displaySize() {
13
14
  // We don't have a graphical display, export a reasonable size.
@@ -21,7 +22,8 @@ export default class TextOnlyRenderer extends AbstractRenderer {
21
22
  getDescription() {
22
23
  return [
23
24
  this.localizationTable.pathNodeCount(this.pathCount),
24
- this.localizationTable.textNodeCount(this.textNodeCount),
25
+ ...(this.textNodeCount > 0 ? this.localizationTable.textNodeCount(this.textNodeCount) : []),
26
+ ...(this.imageNodeCount > 0 ? this.localizationTable.imageNodeCount(this.imageNodeCount) : []),
25
27
  ...this.descriptionBuilder
26
28
  ].join('\n');
27
29
  }
@@ -42,6 +44,11 @@ export default class TextOnlyRenderer extends AbstractRenderer {
42
44
  this.descriptionBuilder.push(this.localizationTable.textNode(text));
43
45
  this.textNodeCount++;
44
46
  }
47
+ drawImage(image) {
48
+ const label = image.label ? this.localizationTable.imageNode(image.label) : this.localizationTable.unlabeledImageNode;
49
+ this.descriptionBuilder.push(label);
50
+ this.imageNodeCount++;
51
+ }
45
52
  isTooSmallToRender(rect) {
46
53
  return rect.maxDimension < 15 / this.getSizeOfCanvasPixelOnScreen();
47
54
  }
@@ -9,6 +9,7 @@ export default class HTMLToolbar {
9
9
  private container;
10
10
  private static colorisStarted;
11
11
  private updateColoris;
12
+ /** @internal */
12
13
  constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
13
14
  setupColorPickers(): void;
14
15
  addWidget(widget: BaseWidget): void;
@@ -14,6 +14,7 @@ import HandToolWidget from './widgets/HandToolWidget';
14
14
  import { EraserTool, PenTool } from '../tools/lib';
15
15
  export const toolbarCSSPrefix = 'toolbar-';
16
16
  export default class HTMLToolbar {
17
+ /** @internal */
17
18
  constructor(editor, parent, localizationTable = defaultToolbarLocalization) {
18
19
  this.editor = editor;
19
20
  this.localizationTable = localizationTable;
@@ -12,6 +12,7 @@ export default abstract class BaseWidget {
12
12
  private label;
13
13
  private disabled;
14
14
  private subWidgets;
15
+ private toplevel;
15
16
  constructor(editor: Editor, localizationTable: ToolbarLocalization);
16
17
  protected abstract getTitle(): string;
17
18
  protected abstract createIcon(): Element;
@@ -26,6 +27,8 @@ export default abstract class BaseWidget {
26
27
  setSelected(selected: boolean): void;
27
28
  protected setDropdownVisible(visible: boolean): void;
28
29
  protected repositionDropdown(): void;
30
+ /** Set whether the widget is contained within another. @internal */
31
+ protected setIsToplevel(toplevel: boolean): void;
29
32
  protected isDropdownVisible(): boolean;
30
33
  protected isSelected(): boolean;
31
34
  private createDropdownIcon;
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
12
  var _BaseWidget_hasDropdown;
13
- import { InputEvtType } from '../../types';
13
+ import { EditorEventType, InputEvtType } from '../../types';
14
14
  import { toolbarCSSPrefix } from '../HTMLToolbar';
15
15
  import { makeDropdownIcon } from '../icons';
16
16
  export default class BaseWidget {
@@ -20,6 +20,7 @@ export default class BaseWidget {
20
20
  _BaseWidget_hasDropdown.set(this, void 0);
21
21
  this.disabled = false;
22
22
  this.subWidgets = [];
23
+ this.toplevel = true;
23
24
  this.icon = null;
24
25
  this.container = document.createElement('div');
25
26
  this.container.classList.add(`${toolbarCSSPrefix}toolContainer`);
@@ -41,6 +42,7 @@ export default class BaseWidget {
41
42
  }
42
43
  for (const widget of this.subWidgets) {
43
44
  widget.addTo(dropdown);
45
+ widget.setIsToplevel(false);
44
46
  }
45
47
  return true;
46
48
  }
@@ -87,6 +89,7 @@ export default class BaseWidget {
87
89
  this.subWidgets.push(widget);
88
90
  }
89
91
  // Adds this to [parent]. This can only be called once for each ToolbarWidget.
92
+ // @internal
90
93
  addTo(parent) {
91
94
  this.label.innerText = this.getTitle();
92
95
  this.setupActionBtnClickListener(this.button);
@@ -99,6 +102,15 @@ export default class BaseWidget {
99
102
  this.dropdownIcon = this.createDropdownIcon();
100
103
  this.button.appendChild(this.dropdownIcon);
101
104
  this.container.appendChild(this.dropdownContainer);
105
+ this.editor.notifier.on(EditorEventType.ToolbarDropdownShown, (evt) => {
106
+ if (evt.kind === EditorEventType.ToolbarDropdownShown
107
+ && evt.parentWidget !== this
108
+ // Don't hide if a submenu wash shown (it might be a submenu of
109
+ // the current menu).
110
+ && evt.parentWidget.toplevel) {
111
+ this.setDropdownVisible(false);
112
+ }
113
+ });
102
114
  }
103
115
  this.setDropdownVisible(false);
104
116
  parent.appendChild(this.container);
@@ -144,6 +156,10 @@ export default class BaseWidget {
144
156
  this.dropdownContainer.classList.remove('hidden');
145
157
  this.container.classList.add('dropdownVisible');
146
158
  this.editor.announceForAccessibility(this.localizationTable.dropdownShown(this.getTitle()));
159
+ this.editor.notifier.dispatch(EditorEventType.ToolbarDropdownShown, {
160
+ kind: EditorEventType.ToolbarDropdownShown,
161
+ parentWidget: this,
162
+ });
147
163
  }
148
164
  else {
149
165
  this.dropdownContainer.classList.add('hidden');
@@ -164,6 +180,10 @@ export default class BaseWidget {
164
180
  this.dropdownContainer.style.transform = '';
165
181
  }
166
182
  }
183
+ /** Set whether the widget is contained within another. @internal */
184
+ setIsToplevel(toplevel) {
185
+ this.toplevel = toplevel;
186
+ }
167
187
  isDropdownVisible() {
168
188
  return !this.dropdownContainer.classList.contains('hidden');
169
189
  }
@@ -1,4 +1,4 @@
1
- import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, KeyPressEvent, KeyUpEvent } from '../types';
1
+ import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, KeyPressEvent, KeyUpEvent, PasteEvent, CopyEvent } from '../types';
2
2
  import ToolEnabledGroup from './ToolEnabledGroup';
3
3
  export default abstract class BaseTool implements PointerEvtListener {
4
4
  private notifier;
@@ -11,9 +11,12 @@ export default abstract class BaseTool implements PointerEvtListener {
11
11
  onGestureCancel(): void;
12
12
  protected constructor(notifier: EditorNotifier, description: string);
13
13
  onWheel(_event: WheelEvt): boolean;
14
+ onCopy(_event: CopyEvent): boolean;
15
+ onPaste(_event: PasteEvent): boolean;
14
16
  onKeyPress(_event: KeyPressEvent): boolean;
15
17
  onKeyUp(_event: KeyUpEvent): boolean;
16
18
  setEnabled(enabled: boolean): void;
17
19
  isEnabled(): boolean;
18
20
  setToolGroup(group: ToolEnabledGroup): void;
21
+ getToolGroup(): ToolEnabledGroup | null;
19
22
  }
@@ -13,6 +13,12 @@ export default class BaseTool {
13
13
  onWheel(_event) {
14
14
  return false;
15
15
  }
16
+ onCopy(_event) {
17
+ return false;
18
+ }
19
+ onPaste(_event) {
20
+ return false;
21
+ }
16
22
  onKeyPress(_event) {
17
23
  return false;
18
24
  }
@@ -48,4 +54,10 @@ export default class BaseTool {
48
54
  }
49
55
  this.group = group;
50
56
  }
57
+ getToolGroup() {
58
+ if (this.group) {
59
+ return this.group;
60
+ }
61
+ return null;
62
+ }
51
63
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A tool that handles paste events.
3
+ * @packageDocumentation
4
+ */
5
+ import Editor from '../Editor';
6
+ import { PasteEvent } from '../types';
7
+ import BaseTool from './BaseTool';
8
+ export default class PasteHandler extends BaseTool {
9
+ private editor;
10
+ constructor(editor: Editor);
11
+ onPaste(event: PasteEvent): boolean;
12
+ private addComponentsFromPaste;
13
+ private doSVGPaste;
14
+ private doTextPaste;
15
+ private doImagePaste;
16
+ }