js-draw 0.3.1 → 0.4.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 (132) hide show
  1. package/.github/ISSUE_TEMPLATE/translation.md +4 -1
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +1 -3
  4. package/dist/bundle.js +1 -1
  5. package/dist/src/Editor.d.ts +15 -1
  6. package/dist/src/Editor.js +221 -78
  7. package/dist/src/EditorImage.js +4 -1
  8. package/dist/src/Pointer.d.ts +1 -1
  9. package/dist/src/Pointer.js +8 -3
  10. package/dist/src/SVGLoader.d.ts +4 -1
  11. package/dist/src/SVGLoader.js +78 -33
  12. package/dist/src/UndoRedoHistory.d.ts +1 -0
  13. package/dist/src/UndoRedoHistory.js +6 -0
  14. package/dist/src/Viewport.d.ts +2 -0
  15. package/dist/src/Viewport.js +26 -5
  16. package/dist/src/commands/lib.d.ts +2 -1
  17. package/dist/src/commands/lib.js +2 -1
  18. package/dist/src/commands/localization.d.ts +1 -0
  19. package/dist/src/commands/localization.js +1 -0
  20. package/dist/src/commands/uniteCommands.d.ts +4 -0
  21. package/dist/src/commands/uniteCommands.js +105 -0
  22. package/dist/src/components/AbstractComponent.d.ts +2 -0
  23. package/dist/src/components/AbstractComponent.js +41 -5
  24. package/dist/src/components/ImageComponent.d.ts +27 -0
  25. package/dist/src/components/ImageComponent.js +129 -0
  26. package/dist/src/components/builders/FreehandLineBuilder.js +2 -2
  27. package/dist/src/components/lib.d.ts +4 -2
  28. package/dist/src/components/lib.js +4 -2
  29. package/dist/src/components/localization.d.ts +2 -0
  30. package/dist/src/components/localization.js +2 -0
  31. package/dist/src/language/assertions.d.ts +1 -0
  32. package/dist/src/language/assertions.js +5 -0
  33. package/dist/src/math/LineSegment2.d.ts +2 -0
  34. package/dist/src/math/LineSegment2.js +3 -0
  35. package/dist/src/math/Mat33.d.ts +38 -2
  36. package/dist/src/math/Mat33.js +30 -1
  37. package/dist/src/math/Path.d.ts +1 -1
  38. package/dist/src/math/Path.js +10 -8
  39. package/dist/src/math/Vec3.d.ts +11 -1
  40. package/dist/src/math/Vec3.js +15 -0
  41. package/dist/src/math/rounding.d.ts +1 -0
  42. package/dist/src/math/rounding.js +13 -6
  43. package/dist/src/rendering/localization.d.ts +3 -0
  44. package/dist/src/rendering/localization.js +3 -0
  45. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +7 -0
  46. package/dist/src/rendering/renderers/AbstractRenderer.js +2 -1
  47. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
  48. package/dist/src/rendering/renderers/CanvasRenderer.js +7 -0
  49. package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -1
  50. package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
  51. package/dist/src/rendering/renderers/SVGRenderer.d.ts +5 -2
  52. package/dist/src/rendering/renderers/SVGRenderer.js +45 -20
  53. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +3 -1
  54. package/dist/src/rendering/renderers/TextOnlyRenderer.js +8 -1
  55. package/dist/src/toolbar/HTMLToolbar.js +5 -4
  56. package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +1 -1
  57. package/dist/src/tools/BaseTool.d.ts +3 -1
  58. package/dist/src/tools/BaseTool.js +6 -0
  59. package/dist/src/tools/PasteHandler.d.ts +16 -0
  60. package/dist/src/tools/PasteHandler.js +144 -0
  61. package/dist/src/tools/Pen.js +1 -1
  62. package/dist/src/tools/SelectionTool/Selection.d.ts +54 -0
  63. package/dist/src/tools/SelectionTool/Selection.js +337 -0
  64. package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +35 -0
  65. package/dist/src/tools/SelectionTool/SelectionHandle.js +75 -0
  66. package/dist/src/tools/SelectionTool/SelectionTool.d.ts +31 -0
  67. package/dist/src/tools/SelectionTool/SelectionTool.js +276 -0
  68. package/dist/src/tools/SelectionTool/TransformMode.d.ts +34 -0
  69. package/dist/src/tools/SelectionTool/TransformMode.js +98 -0
  70. package/dist/src/tools/SelectionTool/types.d.ts +9 -0
  71. package/dist/src/tools/SelectionTool/types.js +11 -0
  72. package/dist/src/tools/ToolController.js +37 -28
  73. package/dist/src/tools/lib.d.ts +2 -1
  74. package/dist/src/tools/lib.js +2 -1
  75. package/dist/src/tools/localization.d.ts +3 -0
  76. package/dist/src/tools/localization.js +3 -0
  77. package/dist/src/types.d.ts +14 -3
  78. package/dist/src/types.js +2 -0
  79. package/package.json +1 -1
  80. package/src/Editor.css +1 -0
  81. package/src/Editor.ts +275 -109
  82. package/src/EditorImage.ts +7 -1
  83. package/src/Pointer.ts +8 -3
  84. package/src/SVGLoader.ts +90 -36
  85. package/src/UndoRedoHistory.test.ts +33 -0
  86. package/src/UndoRedoHistory.ts +8 -0
  87. package/src/Viewport.ts +30 -6
  88. package/src/commands/lib.ts +2 -0
  89. package/src/commands/localization.ts +2 -0
  90. package/src/commands/uniteCommands.test.ts +23 -0
  91. package/src/commands/uniteCommands.ts +121 -0
  92. package/src/components/AbstractComponent.ts +53 -11
  93. package/src/components/ImageComponent.ts +149 -0
  94. package/src/components/Text.ts +2 -6
  95. package/src/components/builders/FreehandLineBuilder.ts +2 -2
  96. package/src/components/lib.ts +7 -2
  97. package/src/components/localization.ts +4 -0
  98. package/src/language/assertions.ts +6 -0
  99. package/src/math/LineSegment2.test.ts +9 -0
  100. package/src/math/LineSegment2.ts +5 -0
  101. package/src/math/Mat33.test.ts +14 -0
  102. package/src/math/Mat33.ts +43 -2
  103. package/src/math/Path.toString.test.ts +12 -1
  104. package/src/math/Path.ts +11 -9
  105. package/src/math/Vec3.ts +22 -1
  106. package/src/math/rounding.test.ts +30 -5
  107. package/src/math/rounding.ts +16 -7
  108. package/src/rendering/localization.ts +6 -0
  109. package/src/rendering/renderers/AbstractRenderer.ts +19 -2
  110. package/src/rendering/renderers/CanvasRenderer.ts +10 -1
  111. package/src/rendering/renderers/DummyRenderer.ts +6 -1
  112. package/src/rendering/renderers/SVGRenderer.ts +50 -21
  113. package/src/rendering/renderers/TextOnlyRenderer.ts +10 -2
  114. package/src/toolbar/HTMLToolbar.ts +5 -4
  115. package/src/toolbar/widgets/SelectionToolWidget.ts +1 -1
  116. package/src/tools/BaseTool.ts +9 -1
  117. package/src/tools/PasteHandler.ts +159 -0
  118. package/src/tools/Pen.ts +1 -1
  119. package/src/tools/SelectionTool/Selection.ts +455 -0
  120. package/src/tools/SelectionTool/SelectionHandle.ts +99 -0
  121. package/src/tools/SelectionTool/SelectionTool.css +22 -0
  122. package/src/tools/{SelectionTool.test.ts → SelectionTool/SelectionTool.test.ts} +21 -21
  123. package/src/tools/SelectionTool/SelectionTool.ts +335 -0
  124. package/src/tools/SelectionTool/TransformMode.ts +114 -0
  125. package/src/tools/SelectionTool/types.ts +11 -0
  126. package/src/tools/ToolController.ts +52 -45
  127. package/src/tools/lib.ts +2 -1
  128. package/src/tools/localization.ts +8 -0
  129. package/src/types.ts +17 -3
  130. package/dist/src/tools/SelectionTool.d.ts +0 -59
  131. package/dist/src/tools/SelectionTool.js +0 -589
  132. package/src/tools/SelectionTool.ts +0 -725
@@ -7,7 +7,7 @@ import { Point2, Vec2 } from '../../math/Vec2';
7
7
  import Vec3 from '../../math/Vec3';
8
8
  import Viewport from '../../Viewport';
9
9
  import RenderingStyle from '../RenderingStyle';
10
- import AbstractRenderer from './AbstractRenderer';
10
+ import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
11
11
 
12
12
  export default class DummyRenderer extends AbstractRenderer {
13
13
  // Variables that track the state of what's been rendered
@@ -17,6 +17,7 @@ export default class DummyRenderer extends AbstractRenderer {
17
17
  public lastPoint: Point2|null = null;
18
18
  public objectNestingLevel: number = 0;
19
19
  public lastText: string|null = null;
20
+ public lastImage: RenderableImage|null = null;
20
21
 
21
22
  // List of points drawn since the last clear.
22
23
  public pointBuffer: Point2[] = [];
@@ -44,6 +45,7 @@ export default class DummyRenderer extends AbstractRenderer {
44
45
  this.renderedPathCount = 0;
45
46
  this.pointBuffer = [];
46
47
  this.lastText = null;
48
+ this.lastImage = null;
47
49
 
48
50
  // Ensure all objects finished rendering
49
51
  if (this.objectNestingLevel > 0) {
@@ -96,6 +98,9 @@ export default class DummyRenderer extends AbstractRenderer {
96
98
  public drawText(text: string, _transform: Mat33, _style: TextStyle): void {
97
99
  this.lastText = text;
98
100
  }
101
+ public drawImage(image: RenderableImage) {
102
+ this.lastImage = image;
103
+ }
99
104
 
100
105
  public startObject(boundingBox: Rect2, _clip: boolean) {
101
106
  super.startObject(boundingBox);
@@ -9,7 +9,7 @@ import { Point2, Vec2 } from '../../math/Vec2';
9
9
  import { svgAttributesDataKey, SVGLoaderUnknownAttribute, SVGLoaderUnknownStyleAttribute, svgStyleAttributesDataKey } from '../../SVGLoader';
10
10
  import Viewport from '../../Viewport';
11
11
  import RenderingStyle from '../RenderingStyle';
12
- import AbstractRenderer, { RenderablePathSpec } from './AbstractRenderer';
12
+ import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
13
13
 
14
14
  const svgNameSpace = 'http://www.w3.org/2000/svg';
15
15
  export default class SVGRenderer extends AbstractRenderer {
@@ -19,13 +19,18 @@ export default class SVGRenderer extends AbstractRenderer {
19
19
 
20
20
  private overwrittenAttrs: Record<string, string|null> = {};
21
21
 
22
- public constructor(private elem: SVGSVGElement, viewport: Viewport) {
22
+ // Renders onto `elem`. If `sanitize`, don't render potentially untrusted data.
23
+ public constructor(private elem: SVGSVGElement, viewport: Viewport, private sanitize: boolean = false) {
23
24
  super(viewport);
24
25
  this.clear();
25
26
  }
26
27
 
27
28
  // Sets an attribute on the root SVG element.
28
29
  public setRootSVGAttribute(name: string, value: string|null) {
30
+ if (this.sanitize) {
31
+ return;
32
+ }
33
+
29
34
  // Make the original value of the attribute restorable on clear
30
35
  if (!(name in this.overwrittenAttrs)) {
31
36
  this.overwrittenAttrs[name] = this.elem.getAttribute(name);
@@ -43,18 +48,21 @@ export default class SVGRenderer extends AbstractRenderer {
43
48
  }
44
49
 
45
50
  public clear() {
46
- // Restore all alltributes
47
- for (const attrName in this.overwrittenAttrs) {
48
- const value = this.overwrittenAttrs[attrName];
49
-
50
- if (value) {
51
- this.elem.setAttribute(attrName, value);
52
- } else {
53
- this.elem.removeAttribute(attrName);
51
+ this.lastPathString = [];
52
+
53
+ if (!this.sanitize) {
54
+ // Restore all all attributes
55
+ for (const attrName in this.overwrittenAttrs) {
56
+ const value = this.overwrittenAttrs[attrName];
57
+
58
+ if (value) {
59
+ this.elem.setAttribute(attrName, value);
60
+ } else {
61
+ this.elem.removeAttribute(attrName);
62
+ }
54
63
  }
64
+ this.overwrittenAttrs = {};
55
65
  }
56
- this.overwrittenAttrs = {};
57
- this.lastPathString = [];
58
66
  }
59
67
 
60
68
  // Push [this.fullPath] to the SVG
@@ -91,26 +99,31 @@ export default class SVGRenderer extends AbstractRenderer {
91
99
  this.lastPathString.push(path.toString());
92
100
  }
93
101
 
94
- public drawText(text: string, transform: Mat33, style: TextStyle): void {
95
- transform = this.getCanvasToScreenTransform().rightMul(transform);
96
-
102
+ // Apply [elemTransform] to [elem].
103
+ private transformFrom(elemTransform: Mat33, elem: SVGElement) {
104
+ let transform = this.getCanvasToScreenTransform().rightMul(elemTransform);
97
105
  const translation = transform.transformVec2(Vec2.zero);
98
106
  transform = transform.rightMul(Mat33.translation(translation.times(-1)));
99
107
 
100
- const textElem = document.createElementNS(svgNameSpace, 'text');
101
- textElem.appendChild(document.createTextNode(text));
102
- textElem.style.transform = `matrix(
108
+ elem.style.transform = `matrix(
103
109
  ${transform.a1}, ${transform.b1},
104
110
  ${transform.a2}, ${transform.b2},
105
111
  ${transform.a3}, ${transform.b3}
106
112
  )`;
113
+ elem.setAttribute('x', `${toRoundedString(translation.x)}`);
114
+ elem.setAttribute('y', `${toRoundedString(translation.y)}`);
115
+ }
116
+
117
+ public drawText(text: string, transform: Mat33, style: TextStyle): void {
118
+ const textElem = document.createElementNS(svgNameSpace, 'text');
119
+ textElem.appendChild(document.createTextNode(text));
120
+ this.transformFrom(transform, textElem);
121
+
107
122
  textElem.style.fontFamily = style.fontFamily;
108
123
  textElem.style.fontVariant = style.fontVariant ?? '';
109
124
  textElem.style.fontWeight = style.fontWeight ?? '';
110
125
  textElem.style.fontSize = style.size + 'px';
111
126
  textElem.style.fill = style.renderingStyle.fill.toHexString();
112
- textElem.setAttribute('x', `${toRoundedString(translation.x)}`);
113
- textElem.setAttribute('y', `${toRoundedString(translation.y)}`);
114
127
 
115
128
  if (style.renderingStyle.stroke) {
116
129
  const strokeStyle = style.renderingStyle.stroke;
@@ -122,6 +135,18 @@ export default class SVGRenderer extends AbstractRenderer {
122
135
  this.objectElems?.push(textElem);
123
136
  }
124
137
 
138
+ public drawImage(image: RenderableImage) {
139
+ const svgImgElem = document.createElementNS(svgNameSpace, 'image');
140
+ svgImgElem.setAttribute('href', image.base64Url);
141
+ svgImgElem.setAttribute('width', image.image.getAttribute('width') ?? '');
142
+ svgImgElem.setAttribute('height', image.image.getAttribute('height') ?? '');
143
+ svgImgElem.setAttribute('aria-label', image.image.getAttribute('aria-label') ?? image.image.getAttribute('alt') ?? '');
144
+ this.transformFrom(image.transform, svgImgElem);
145
+
146
+ this.elem.appendChild(svgImgElem);
147
+ this.objectElems?.push(svgImgElem);
148
+ }
149
+
125
150
  public startObject(boundingBox: Rect2) {
126
151
  super.startObject(boundingBox);
127
152
 
@@ -137,7 +162,7 @@ export default class SVGRenderer extends AbstractRenderer {
137
162
  // Don't extend paths across objects
138
163
  this.addPathToSVG();
139
164
 
140
- if (loaderData) {
165
+ if (loaderData && !this.sanitize) {
141
166
  // Restore any attributes unsupported by the app.
142
167
  for (const elem of this.objectElems ?? []) {
143
168
  const attrs = loaderData[svgAttributesDataKey] as SVGLoaderUnknownAttribute[]|undefined;
@@ -181,6 +206,10 @@ export default class SVGRenderer extends AbstractRenderer {
181
206
 
182
207
  // Renders a **copy** of the given element.
183
208
  public drawSVGElem(elem: SVGElement) {
209
+ if (this.sanitize) {
210
+ return;
211
+ }
212
+
184
213
  this.elem.appendChild(elem.cloneNode(true));
185
214
  }
186
215
 
@@ -6,7 +6,7 @@ import Vec3 from '../../math/Vec3';
6
6
  import Viewport from '../../Viewport';
7
7
  import { TextRendererLocalization } from '../localization';
8
8
  import RenderingStyle from '../RenderingStyle';
9
- import AbstractRenderer from './AbstractRenderer';
9
+ import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
10
10
 
11
11
  // Outputs a description of what was rendered.
12
12
 
@@ -14,6 +14,7 @@ export default class TextOnlyRenderer extends AbstractRenderer {
14
14
  private descriptionBuilder: string[] = [];
15
15
  private pathCount: number = 0;
16
16
  private textNodeCount: number = 0;
17
+ private imageNodeCount: number = 0;
17
18
 
18
19
  public constructor(viewport: Viewport, private localizationTable: TextRendererLocalization) {
19
20
  super(viewport);
@@ -33,7 +34,8 @@ export default class TextOnlyRenderer extends AbstractRenderer {
33
34
  public getDescription(): string {
34
35
  return [
35
36
  this.localizationTable.pathNodeCount(this.pathCount),
36
- this.localizationTable.textNodeCount(this.textNodeCount),
37
+ ...(this.textNodeCount > 0 ? this.localizationTable.textNodeCount(this.textNodeCount) : []),
38
+ ...(this.imageNodeCount > 0 ? this.localizationTable.imageNodeCount(this.imageNodeCount) : []),
37
39
  ...this.descriptionBuilder
38
40
  ].join('\n');
39
41
  }
@@ -55,6 +57,12 @@ export default class TextOnlyRenderer extends AbstractRenderer {
55
57
  this.descriptionBuilder.push(this.localizationTable.textNode(text));
56
58
  this.textNodeCount ++;
57
59
  }
60
+ public drawImage(image: RenderableImage) {
61
+ const label = image.label ? this.localizationTable.imageNode(image.label) : this.localizationTable.unlabeledImageNode;
62
+
63
+ this.descriptionBuilder.push(label);
64
+ this.imageNodeCount ++;
65
+ }
58
66
  public isTooSmallToRender(rect: Rect2): boolean {
59
67
  return rect.maxDimension < 15 / this.getSizeOfCanvasPixelOnScreen();
60
68
  }
@@ -3,19 +3,20 @@ import { EditorEventType } from '../types';
3
3
 
4
4
  import { coloris, init as colorisInit } from '@melloware/coloris';
5
5
  import Color4 from '../Color4';
6
- import SelectionTool from '../tools/SelectionTool';
7
6
  import { defaultToolbarLocalization, ToolbarLocalization } from './localization';
8
7
  import { ActionButtonIcon } from './types';
9
8
  import { makeRedoIcon, makeUndoIcon } from './icons';
10
- import PanZoom from '../tools/PanZoom';
9
+ import SelectionTool from '../tools/SelectionTool/SelectionTool';
10
+ import PanZoomTool from '../tools/PanZoom';
11
11
  import TextTool from '../tools/TextTool';
12
+ import EraserTool from '../tools/Eraser';
13
+ import PenTool from '../tools/Pen';
12
14
  import PenToolWidget from './widgets/PenToolWidget';
13
15
  import EraserWidget from './widgets/EraserToolWidget';
14
16
  import SelectionToolWidget from './widgets/SelectionToolWidget';
15
17
  import TextToolWidget from './widgets/TextToolWidget';
16
18
  import HandToolWidget from './widgets/HandToolWidget';
17
19
  import BaseWidget from './widgets/BaseWidget';
18
- import { EraserTool, PenTool } from '../tools/lib';
19
20
 
20
21
  export const toolbarCSSPrefix = 'toolbar-';
21
22
 
@@ -200,7 +201,7 @@ export default class HTMLToolbar {
200
201
  this.addWidget(new TextToolWidget(this.editor, tool, this.localizationTable));
201
202
  }
202
203
 
203
- const panZoomTool = toolController.getMatchingTools(PanZoom)[0];
204
+ const panZoomTool = toolController.getMatchingTools(PanZoomTool)[0];
204
205
  if (panZoomTool) {
205
206
  this.addWidget(new HandToolWidget(this.editor, panZoomTool, this.localizationTable));
206
207
  }
@@ -1,5 +1,5 @@
1
1
  import Editor from '../../Editor';
2
- import SelectionTool from '../../tools/SelectionTool';
2
+ import SelectionTool from '../../tools/SelectionTool/SelectionTool';
3
3
  import { EditorEventType } from '../../types';
4
4
  import { makeDeleteSelectionIcon, makeDuplicateSelectionIcon, makeResizeViewportIcon, makeSelectionIcon } from '../icons';
5
5
  import { ToolbarLocalization } from '../localization';
@@ -1,4 +1,4 @@
1
- import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, EditorEventType, KeyPressEvent, KeyUpEvent } from '../types';
1
+ import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, EditorEventType, KeyPressEvent, KeyUpEvent, PasteEvent, CopyEvent } from '../types';
2
2
  import ToolEnabledGroup from './ToolEnabledGroup';
3
3
 
4
4
  export default abstract class BaseTool implements PointerEvtListener {
@@ -17,6 +17,14 @@ export default abstract class BaseTool implements PointerEvtListener {
17
17
  return false;
18
18
  }
19
19
 
20
+ public onCopy(_event: CopyEvent): boolean {
21
+ return false;
22
+ }
23
+
24
+ public onPaste(_event: PasteEvent): boolean {
25
+ return false;
26
+ }
27
+
20
28
  public onKeyPress(_event: KeyPressEvent): boolean {
21
29
  return false;
22
30
  }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * A tool that handles paste events.
3
+ * @packageDocumentation
4
+ */
5
+
6
+ import Editor from '../Editor';
7
+ import { AbstractComponent, TextComponent } from '../components/lib';
8
+ import { Command, uniteCommands } from '../commands/lib';
9
+ import SVGLoader from '../SVGLoader';
10
+ import { PasteEvent } from '../types';
11
+ import { Mat33, Rect2, Vec2 } from '../math/lib';
12
+ import BaseTool from './BaseTool';
13
+ import EditorImage from '../EditorImage';
14
+ import SelectionTool from './SelectionTool/SelectionTool';
15
+ import TextTool from './TextTool';
16
+ import Color4 from '../Color4';
17
+ import { TextStyle } from '../components/Text';
18
+ import ImageComponent from '../components/ImageComponent';
19
+ import Viewport from '../Viewport';
20
+
21
+ // { @inheritDoc PasteHandler! }
22
+ export default class PasteHandler extends BaseTool {
23
+ public constructor(private editor: Editor) {
24
+ super(editor.notifier, editor.localization.pasteHandler);
25
+ }
26
+
27
+ public onPaste(event: PasteEvent): boolean {
28
+ const mime = event.mime.toLowerCase();
29
+
30
+ if (mime === 'image/svg+xml') {
31
+ void this.doSVGPaste(event.data);
32
+ return true;
33
+ }
34
+ else if (mime === 'text/plain') {
35
+ void this.doTextPaste(event.data);
36
+ return true;
37
+ }
38
+ else if (mime === 'image/png' || mime === 'image/jpeg') {
39
+ void this.doImagePaste(event.data);
40
+ return true;
41
+ }
42
+
43
+ return false;
44
+ }
45
+
46
+ private async addComponentsFromPaste(components: AbstractComponent[]) {
47
+ let bbox: Rect2|null = null;
48
+ for (const component of components) {
49
+ if (bbox) {
50
+ bbox = bbox.union(component.getBBox());
51
+ } else {
52
+ bbox = component.getBBox();
53
+ }
54
+ }
55
+
56
+ if (!bbox) {
57
+ return;
58
+ }
59
+
60
+ // Find a transform that scales/moves bbox onto the screen.
61
+ const visibleRect = this.editor.viewport.visibleRect;
62
+ const scaleRatioX = visibleRect.width / bbox.width;
63
+ const scaleRatioY = visibleRect.height / bbox.height;
64
+
65
+ let scaleRatio = scaleRatioX;
66
+ if (bbox.width * scaleRatio > visibleRect.width || bbox.height * scaleRatio > visibleRect.height) {
67
+ scaleRatio = scaleRatioY;
68
+ }
69
+ scaleRatio *= 2 / 3;
70
+
71
+ scaleRatio = Viewport.roundScaleRatio(scaleRatio);
72
+
73
+ const transfm = Mat33.translation(
74
+ visibleRect.center.minus(bbox.center)
75
+ ).rightMul(
76
+ Mat33.scaling2D(scaleRatio, bbox.center)
77
+ );
78
+
79
+ const commands: Command[] = [];
80
+ for (const component of components) {
81
+ // To allow deserialization, we need to add first, then transform.
82
+ commands.push(EditorImage.addElement(component));
83
+ commands.push(component.transformBy(transfm));
84
+ }
85
+
86
+ const applyChunkSize = 100;
87
+ this.editor.dispatch(uniteCommands(commands, applyChunkSize), true);
88
+
89
+ for (const selectionTool of this.editor.toolController.getMatchingTools(SelectionTool)) {
90
+ selectionTool.setEnabled(true);
91
+ selectionTool.setSelection(components);
92
+ }
93
+ }
94
+
95
+ private async doSVGPaste(data: string) {
96
+ const sanitize = true;
97
+ const loader = SVGLoader.fromString(data, sanitize);
98
+
99
+ const components: AbstractComponent[] = [];
100
+
101
+ await loader.start((component) => {
102
+ components.push(component);
103
+ },
104
+ (_countProcessed: number, _totalToProcess: number) => null);
105
+
106
+ await this.addComponentsFromPaste(components);
107
+ }
108
+
109
+ private async doTextPaste(text: string) {
110
+ const textTools = this.editor.toolController.getMatchingTools(TextTool);
111
+
112
+ textTools.sort((a, b) => {
113
+ if (!a.isEnabled() && b.isEnabled()) {
114
+ return -1;
115
+ }
116
+
117
+ if (!b.isEnabled() && a.isEnabled()) {
118
+ return 1;
119
+ }
120
+
121
+ return 0;
122
+ });
123
+
124
+ const defaultTextStyle: TextStyle = { size: 12, fontFamily: 'sans', renderingStyle: { fill: Color4.red } };
125
+ const pastedTextStyle: TextStyle = textTools[0]?.getTextStyle() ?? defaultTextStyle;
126
+
127
+ const lines = text.split('\n');
128
+ let lastComponent: TextComponent|null = null;
129
+ const components: TextComponent[] = [];
130
+
131
+ for (const line of lines) {
132
+ let position = Vec2.zero;
133
+ if (lastComponent) {
134
+ const lineMargin = Math.floor(pastedTextStyle.size);
135
+ position = lastComponent.getBBox().bottomLeft.plus(Vec2.unitY.times(lineMargin));
136
+ }
137
+
138
+ const component = new TextComponent([ line ], Mat33.translation(position), pastedTextStyle);
139
+ components.push(component);
140
+ lastComponent = component;
141
+ }
142
+
143
+ if (components.length === 1) {
144
+ await this.addComponentsFromPaste([ components[0] ]);
145
+ } else {
146
+ // Wrap the existing `TextComponent`s --- dragging one component should drag all.
147
+ await this.addComponentsFromPaste([
148
+ new TextComponent(components, Mat33.identity, pastedTextStyle)
149
+ ]);
150
+ }
151
+ }
152
+
153
+ private async doImagePaste(dataURL: string) {
154
+ const image = new Image();
155
+ image.src = dataURL;
156
+ const component = await ImageComponent.fromImage(image, Mat33.identity);
157
+ await this.addComponentsFromPaste([ component ]);
158
+ }
159
+ }
package/src/tools/Pen.ts CHANGED
@@ -171,7 +171,7 @@ export default class Pen extends BaseTool {
171
171
  }
172
172
 
173
173
  if (newThickness !== undefined) {
174
- newThickness = Math.min(Math.max(1, newThickness), 128);
174
+ newThickness = Math.min(Math.max(1, newThickness), 256);
175
175
  this.setThickness(newThickness);
176
176
  return true;
177
177
  }