js-draw 0.5.0 → 0.7.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 (92) hide show
  1. package/.firebase/hosting.ZG9jcw.cache +338 -0
  2. package/.github/ISSUE_TEMPLATE/translation.md +1 -1
  3. package/CHANGELOG.md +19 -0
  4. package/dist/bundle.js +1 -1
  5. package/dist/src/Editor.d.ts +8 -6
  6. package/dist/src/Editor.js +8 -4
  7. package/dist/src/EditorImage.d.ts +3 -0
  8. package/dist/src/EditorImage.js +7 -0
  9. package/dist/src/SVGLoader.js +7 -8
  10. package/dist/src/components/AbstractComponent.d.ts +1 -0
  11. package/dist/src/components/AbstractComponent.js +4 -0
  12. package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -0
  13. package/dist/src/components/SVGGlobalAttributesObject.js +3 -0
  14. package/dist/src/components/Stroke.js +1 -0
  15. package/dist/src/components/Text.d.ts +11 -8
  16. package/dist/src/components/Text.js +63 -20
  17. package/dist/src/components/UnknownSVGObject.d.ts +1 -0
  18. package/dist/src/components/UnknownSVGObject.js +3 -0
  19. package/dist/src/components/builders/FreehandLineBuilder.d.ts +9 -2
  20. package/dist/src/components/builders/FreehandLineBuilder.js +129 -30
  21. package/dist/src/components/lib.d.ts +2 -2
  22. package/dist/src/components/lib.js +2 -2
  23. package/dist/src/rendering/renderers/CanvasRenderer.js +2 -2
  24. package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -0
  25. package/dist/src/rendering/renderers/SVGRenderer.js +49 -22
  26. package/dist/src/testing/beforeEachFile.js +4 -0
  27. package/dist/src/toolbar/HTMLToolbar.js +2 -3
  28. package/dist/src/toolbar/IconProvider.d.ts +30 -0
  29. package/dist/src/toolbar/IconProvider.js +417 -0
  30. package/dist/src/toolbar/lib.d.ts +1 -1
  31. package/dist/src/toolbar/lib.js +1 -2
  32. package/dist/src/toolbar/localization.d.ts +0 -1
  33. package/dist/src/toolbar/localization.js +0 -1
  34. package/dist/src/toolbar/makeColorInput.js +1 -2
  35. package/dist/src/toolbar/widgets/BaseWidget.js +1 -2
  36. package/dist/src/toolbar/widgets/EraserToolWidget.js +1 -2
  37. package/dist/src/toolbar/widgets/HandToolWidget.d.ts +5 -3
  38. package/dist/src/toolbar/widgets/HandToolWidget.js +35 -12
  39. package/dist/src/toolbar/widgets/PenToolWidget.js +10 -8
  40. package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +3 -0
  41. package/dist/src/toolbar/widgets/SelectionToolWidget.js +20 -7
  42. package/dist/src/toolbar/widgets/TextToolWidget.js +1 -2
  43. package/dist/src/tools/PanZoom.d.ts +1 -1
  44. package/dist/src/tools/PanZoom.js +4 -1
  45. package/dist/src/tools/PasteHandler.js +2 -22
  46. package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -0
  47. package/dist/src/tools/SelectionTool/SelectionTool.js +66 -3
  48. package/dist/src/tools/TextTool.d.ts +4 -0
  49. package/dist/src/tools/TextTool.js +73 -15
  50. package/dist/src/tools/ToolController.js +1 -0
  51. package/dist/src/tools/localization.d.ts +1 -0
  52. package/dist/src/tools/localization.js +1 -0
  53. package/package.json +1 -1
  54. package/src/Editor.toSVG.test.ts +27 -0
  55. package/src/Editor.ts +15 -9
  56. package/src/EditorImage.ts +9 -0
  57. package/src/SVGLoader.test.ts +57 -0
  58. package/src/SVGLoader.ts +9 -10
  59. package/src/components/AbstractComponent.ts +5 -0
  60. package/src/components/SVGGlobalAttributesObject.ts +4 -0
  61. package/src/components/Stroke.ts +1 -0
  62. package/src/components/Text.test.ts +3 -18
  63. package/src/components/Text.ts +78 -25
  64. package/src/components/UnknownSVGObject.ts +4 -0
  65. package/src/components/builders/FreehandLineBuilder.ts +162 -34
  66. package/src/components/lib.ts +3 -3
  67. package/src/rendering/renderers/CanvasRenderer.ts +2 -2
  68. package/src/rendering/renderers/SVGRenderer.ts +50 -24
  69. package/src/testing/beforeEachFile.ts +6 -1
  70. package/src/toolbar/HTMLToolbar.ts +2 -3
  71. package/src/toolbar/IconProvider.ts +480 -0
  72. package/src/toolbar/lib.ts +1 -1
  73. package/src/toolbar/localization.ts +0 -2
  74. package/src/toolbar/makeColorInput.ts +1 -2
  75. package/src/toolbar/widgets/BaseWidget.ts +1 -2
  76. package/src/toolbar/widgets/EraserToolWidget.ts +1 -2
  77. package/src/toolbar/widgets/HandToolWidget.ts +42 -20
  78. package/src/toolbar/widgets/PenToolWidget.ts +11 -8
  79. package/src/toolbar/widgets/SelectionToolWidget.ts +24 -8
  80. package/src/toolbar/widgets/TextToolWidget.ts +1 -2
  81. package/src/tools/PanZoom.ts +4 -1
  82. package/src/tools/PasteHandler.ts +2 -24
  83. package/src/tools/SelectionTool/SelectionTool.css +1 -0
  84. package/src/tools/SelectionTool/SelectionTool.test.ts +40 -0
  85. package/src/tools/SelectionTool/SelectionTool.ts +73 -4
  86. package/src/tools/TextTool.ts +82 -17
  87. package/src/tools/ToolController.ts +1 -0
  88. package/src/tools/localization.ts +4 -0
  89. package/typedoc.json +5 -1
  90. package/dist/src/toolbar/icons.d.ts +0 -20
  91. package/dist/src/toolbar/icons.js +0 -385
  92. package/src/toolbar/icons.ts +0 -443
@@ -0,0 +1,57 @@
1
+ import { Rect2, TextComponent, Vec2 } from './lib';
2
+ import SVGLoader from './SVGLoader';
3
+ import createEditor from './testing/createEditor';
4
+
5
+ describe('SVGLoader', () => {
6
+ it('should correctly load x/y-positioned text nodes', async () => {
7
+ const editor = createEditor();
8
+ await editor.loadFrom(SVGLoader.fromString(`
9
+ <svg>
10
+ <text>Testing...</text>
11
+ <text y=100>Test 2...</text>
12
+ <text x=100>Test 3...</text>
13
+ <text x=100 y=100>Test 3...</text>
14
+
15
+ <!-- Transform matrix: translate by (100,0) -->
16
+ <text style='transform: matrix(1,0,0,1,100,0);'>Test 3...</text>
17
+ </svg>
18
+ `, true));
19
+ const elems = editor.image
20
+ .getElementsIntersectingRegion(new Rect2(-1000, -1000, 10000, 10000))
21
+ .filter(elem => elem instanceof TextComponent);
22
+ expect(elems).toHaveLength(5);
23
+ const topLefts = elems.map(elem => elem.getBBox().topLeft);
24
+
25
+ // Top-left of Testing... should be (0, 0) ± 10 pixels (objects are aligned based on baseline)
26
+ expect(topLefts[0]).objEq(Vec2.of(0, 0), 10);
27
+
28
+ expect(topLefts[1].y - topLefts[0].y).toBe(100);
29
+ expect(topLefts[1].x - topLefts[0].x).toBe(0);
30
+
31
+ expect(topLefts[2].y - topLefts[0].y).toBe(0);
32
+ expect(topLefts[2].x - topLefts[0].x).toBe(100);
33
+
34
+ expect(topLefts[4].x - topLefts[0].x).toBe(100);
35
+ expect(topLefts[4].y - topLefts[0].y).toBe(0);
36
+ });
37
+
38
+ it('should correctly load tspans within texts nodes', async () => {
39
+ const editor = createEditor();
40
+ await editor.loadFrom(SVGLoader.fromString(`
41
+ <svg>
42
+ <text>
43
+ Testing...
44
+ <tspan x=0 y=100>Test 2...</tspan>
45
+ <tspan x=0 y=200>Test 2...</tspan>
46
+ </text>
47
+ </svg>
48
+ `, true));
49
+ const elem = editor.image
50
+ .getElementsIntersectingRegion(new Rect2(-1000, -1000, 10000, 10000))
51
+ .filter(elem => elem instanceof TextComponent)[0];
52
+ expect(elem).not.toBeNull();
53
+ expect(elem.getBBox().topLeft.y).toBeLessThan(0);
54
+ expect(elem.getBBox().topLeft.x).toBe(0);
55
+ expect(elem.getBBox().h).toBeGreaterThan(200);
56
+ });
57
+ });
package/src/SVGLoader.ts CHANGED
@@ -3,7 +3,7 @@ import AbstractComponent from './components/AbstractComponent';
3
3
  import ImageComponent from './components/ImageComponent';
4
4
  import Stroke from './components/Stroke';
5
5
  import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
6
- import Text, { TextStyle } from './components/Text';
6
+ import TextComponent, { TextStyle } from './components/Text';
7
7
  import UnknownSVGObject from './components/UnknownSVGObject';
8
8
  import Mat33 from './math/Mat33';
9
9
  import Path from './math/Path';
@@ -124,7 +124,7 @@ export default class SVGLoader implements ImageLoader {
124
124
  );
125
125
  }
126
126
 
127
- if (supportedStyleAttrs) {
127
+ if (supportedStyleAttrs && node.style) {
128
128
  for (const attr of node.style) {
129
129
  if (attr === '' || !attr) {
130
130
  continue;
@@ -198,9 +198,9 @@ export default class SVGLoader implements ImageLoader {
198
198
 
199
199
  const elemX = elem.getAttribute('x');
200
200
  const elemY = elem.getAttribute('y');
201
- if (elemX && elemY) {
202
- const x = parseFloat(elemX);
203
- const y = parseFloat(elemY);
201
+ if (elemX || elemY) {
202
+ const x = parseFloat(elemX ?? '0');
203
+ const y = parseFloat(elemY ?? '0');
204
204
  if (!isNaN(x) && !isNaN(y)) {
205
205
  supportedAttrs?.push('x', 'y');
206
206
  transform = transform.rightMul(Mat33.translation(Vec2.of(x, y)));
@@ -210,8 +210,8 @@ export default class SVGLoader implements ImageLoader {
210
210
  return transform;
211
211
  }
212
212
 
213
- private makeText(elem: SVGTextElement|SVGTSpanElement): Text {
214
- const contentList: Array<Text|string> = [];
213
+ private makeText(elem: SVGTextElement|SVGTSpanElement): TextComponent {
214
+ const contentList: Array<TextComponent|string> = [];
215
215
  for (const child of elem.childNodes) {
216
216
  if (child.nodeType === Node.TEXT_NODE) {
217
217
  contentList.push(child.nodeValue ?? '');
@@ -245,13 +245,13 @@ export default class SVGLoader implements ImageLoader {
245
245
  size: fontSize,
246
246
  fontFamily: computedStyles.fontFamily || elem.style.fontFamily || 'sans-serif',
247
247
  renderingStyle: {
248
- fill: Color4.fromString(computedStyles.fill)
248
+ fill: Color4.fromString(computedStyles.fill || elem.style.fill || '#000')
249
249
  },
250
250
  };
251
251
 
252
252
  const supportedAttrs: string[] = [];
253
253
  const transform = this.getTransform(elem, supportedAttrs, computedStyles);
254
- const result = new Text(contentList, transform, style);
254
+ const result = new TextComponent(contentList, transform, style);
255
255
  this.attachUnrecognisedAttrs(
256
256
  result,
257
257
  elem,
@@ -406,7 +406,6 @@ export default class SVGLoader implements ImageLoader {
406
406
  this.onFinish?.();
407
407
  }
408
408
 
409
- // TODO: Handling unsafe data! Tripple-check that this is secure!
410
409
  // @param sanitize - if `true`, don't store unknown attributes.
411
410
  public static fromString(text: string, sanitize: boolean = false): SVGLoader {
412
411
  const sandbox = document.createElement('iframe');
@@ -89,6 +89,11 @@ export default abstract class AbstractComponent {
89
89
  return new AbstractComponent.TransformElementCommand(affineTransfm, this);
90
90
  }
91
91
 
92
+ // @returns true iff this component can be selected (e.g. by the selection tool.)
93
+ public isSelectable(): boolean {
94
+ return true;
95
+ }
96
+
92
97
  private static transformElementCommandId = 'transform-element';
93
98
 
94
99
  private static UnresolvedTransformElementCommand = class extends SerializableCommand {
@@ -43,6 +43,10 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
43
43
  protected applyTransformation(_affineTransfm: Mat33): void {
44
44
  }
45
45
 
46
+ public isSelectable() {
47
+ return false;
48
+ }
49
+
46
50
  protected createClone() {
47
51
  return new SVGGlobalAttributesObject(this.attrs);
48
52
  }
@@ -15,6 +15,7 @@ export default class Stroke extends AbstractComponent {
15
15
  private parts: StrokePart[];
16
16
  protected contentBBox: Rect2;
17
17
 
18
+ // Creates a `Stroke` from the given `parts`.
18
19
  public constructor(parts: RenderablePathSpec[]) {
19
20
  super('stroke');
20
21
 
@@ -1,21 +1,8 @@
1
1
  import Color4 from '../Color4';
2
2
  import Mat33 from '../math/Mat33';
3
- import Rect2 from '../math/Rect2';
4
3
  import AbstractComponent from './AbstractComponent';
5
- import Text, { TextStyle } from './Text';
4
+ import TextComponent, { TextStyle } from './Text';
6
5
 
7
- const estimateTextBounds = (text: string, style: TextStyle): Rect2 => {
8
- const widthEst = text.length * style.size;
9
- const heightEst = style.size;
10
-
11
- // Text is drawn with (0, 0) as its baseline. As such, the majority of the text's height should
12
- // be above (0, 0).
13
- return new Rect2(0, -heightEst * 2/3, widthEst, heightEst);
14
- };
15
-
16
- // Don't use the default Canvas-based text bounding code. The canvas-based code may not work
17
- // with jsdom.
18
- AbstractComponent.registerComponent('text', (data: string) => Text.deserializeFromString(data, estimateTextBounds));
19
6
 
20
7
  describe('Text', () => {
21
8
  it('should be serializable', () => {
@@ -24,11 +11,9 @@ describe('Text', () => {
24
11
  fontFamily: 'serif',
25
12
  renderingStyle: { fill: Color4.black },
26
13
  };
27
- const text = new Text(
28
- [ 'Foo' ], Mat33.identity, style, estimateTextBounds
29
- );
14
+ const text = new TextComponent([ 'Foo' ], Mat33.identity, style);
30
15
  const serialized = text.serialize();
31
- const deserialized = AbstractComponent.deserialize(serialized) as Text;
16
+ const deserialized = AbstractComponent.deserialize(serialized) as TextComponent;
32
17
  expect(deserialized.getBBox()).objEq(text.getBBox());
33
18
  expect(deserialized['getText']()).toContain('Foo');
34
19
  });
@@ -1,6 +1,7 @@
1
1
  import LineSegment2 from '../math/LineSegment2';
2
2
  import Mat33, { Mat33Array } from '../math/Mat33';
3
3
  import Rect2 from '../math/Rect2';
4
+ import { Vec2 } from '../math/Vec2';
4
5
  import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
5
6
  import RenderingStyle, { styleFromJSON, styleToJSON } from '../rendering/RenderingStyle';
6
7
  import AbstractComponent from './AbstractComponent';
@@ -14,23 +15,24 @@ export interface TextStyle {
14
15
  renderingStyle: RenderingStyle;
15
16
  }
16
17
 
17
- type GetTextDimensCallback = (text: string, style: TextStyle) => Rect2;
18
-
19
18
  const componentTypeId = 'text';
20
- export default class Text extends AbstractComponent {
19
+ export default class TextComponent extends AbstractComponent {
21
20
  protected contentBBox: Rect2;
22
21
 
23
22
  public constructor(
24
- protected readonly textObjects: Array<string|Text>,
23
+ protected readonly textObjects: Array<string|TextComponent>,
25
24
  private transform: Mat33,
26
- private readonly style: TextStyle,
27
-
28
- // If not given, an HtmlCanvasElement is used to determine text boundaries.
29
- // @internal
30
- private readonly getTextDimens: GetTextDimensCallback = Text.getTextDimens,
25
+ private style: TextStyle,
31
26
  ) {
32
27
  super(componentTypeId);
33
28
  this.recomputeBBox();
29
+
30
+ // If this has no direct children, choose a style representative of this' content
31
+ // (useful for estimating the style of the TextComponent).
32
+ const hasDirectContent = textObjects.some(obj => typeof obj === 'string');
33
+ if (!hasDirectContent && textObjects.length > 0) {
34
+ this.style = (textObjects[0] as TextComponent).getTextStyle();
35
+ }
34
36
  }
35
37
 
36
38
  public static applyTextStyles(ctx: CanvasRenderingContext2D, style: TextStyle) {
@@ -47,11 +49,27 @@ export default class Text extends AbstractComponent {
47
49
  ctx.textAlign = 'left';
48
50
  }
49
51
 
50
- private static textMeasuringCtx: CanvasRenderingContext2D;
52
+ private static textMeasuringCtx: CanvasRenderingContext2D|null = null;
53
+
54
+ // Roughly estimate the bounding box of `text`. Use if no CanvasRenderingContext2D is available.
55
+ private static estimateTextDimens(text: string, style: TextStyle): Rect2 {
56
+ const widthEst = text.length * style.size;
57
+ const heightEst = style.size;
58
+
59
+ // Text is drawn with (0, 0) as its baseline. As such, the majority of the text's height should
60
+ // be above (0, 0).
61
+ return new Rect2(0, -heightEst * 2/3, widthEst, heightEst);
62
+ }
63
+
64
+ // Returns the bounding box of `text`. This is approximate if no Canvas is available.
51
65
  private static getTextDimens(text: string, style: TextStyle): Rect2 {
52
- Text.textMeasuringCtx ??= document.createElement('canvas').getContext('2d')!;
53
- const ctx = Text.textMeasuringCtx;
54
- Text.applyTextStyles(ctx, style);
66
+ TextComponent.textMeasuringCtx ??= document.createElement('canvas').getContext('2d') ?? null;
67
+ if (!TextComponent.textMeasuringCtx) {
68
+ return this.estimateTextDimens(text, style);
69
+ }
70
+
71
+ const ctx = TextComponent.textMeasuringCtx;
72
+ TextComponent.applyTextStyles(ctx, style);
55
73
 
56
74
  const measure = ctx.measureText(text);
57
75
 
@@ -61,9 +79,9 @@ export default class Text extends AbstractComponent {
61
79
  return new Rect2(0, textY, measure.width, textHeight);
62
80
  }
63
81
 
64
- private computeBBoxOfPart(part: string|Text) {
82
+ private computeBBoxOfPart(part: string|TextComponent) {
65
83
  if (typeof part === 'string') {
66
- const textBBox = this.getTextDimens(part, this.style);
84
+ const textBBox = TextComponent.getTextDimens(part, this.style);
67
85
  return textBBox.transformedBoundingBox(this.transform);
68
86
  } else {
69
87
  const bbox = part.contentBBox.transformedBoundingBox(this.transform);
@@ -83,19 +101,23 @@ export default class Text extends AbstractComponent {
83
101
  this.contentBBox = bbox ?? Rect2.empty;
84
102
  }
85
103
 
86
- public render(canvas: AbstractRenderer, _visibleRect?: Rect2): void {
104
+ private renderInternal(canvas: AbstractRenderer) {
87
105
  const cursor = this.transform;
88
106
 
89
- canvas.startObject(this.contentBBox);
90
107
  for (const textObject of this.textObjects) {
91
108
  if (typeof textObject === 'string') {
92
109
  canvas.drawText(textObject, cursor, this.style);
93
110
  } else {
94
111
  canvas.pushTransform(cursor);
95
- textObject.render(canvas);
112
+ textObject.renderInternal(canvas);
96
113
  canvas.popTransform();
97
114
  }
98
115
  }
116
+ }
117
+
118
+ public render(canvas: AbstractRenderer, _visibleRect?: Rect2): void {
119
+ canvas.startObject(this.contentBBox);
120
+ this.renderInternal(canvas);
99
121
  canvas.endObject(this.getLoadSaveData());
100
122
  }
101
123
 
@@ -109,7 +131,7 @@ export default class Text extends AbstractComponent {
109
131
 
110
132
  for (const subObject of this.textObjects) {
111
133
  if (typeof subObject === 'string') {
112
- const textBBox = Text.getTextDimens(subObject, this.style);
134
+ const textBBox = TextComponent.getTextDimens(subObject, this.style);
113
135
 
114
136
  // TODO: Use a better intersection check. Perhaps draw the text onto a CanvasElement and
115
137
  // use pixel-testing to check for intersection with its contour.
@@ -126,13 +148,25 @@ export default class Text extends AbstractComponent {
126
148
  return false;
127
149
  }
128
150
 
151
+ public getBaselinePos() {
152
+ return this.transform.transformVec2(Vec2.zero);
153
+ }
154
+
155
+ public getTextStyle() {
156
+ return this.style;
157
+ }
158
+
159
+ public getTransform(): Mat33 {
160
+ return this.transform;
161
+ }
162
+
129
163
  protected applyTransformation(affineTransfm: Mat33): void {
130
164
  this.transform = affineTransfm.rightMul(this.transform);
131
165
  this.recomputeBBox();
132
166
  }
133
167
 
134
168
  protected createClone(): AbstractComponent {
135
- return new Text(this.textObjects, this.transform, this.style);
169
+ return new TextComponent(this.textObjects, this.transform, this.style);
136
170
  }
137
171
 
138
172
  public getText() {
@@ -178,7 +212,7 @@ export default class Text extends AbstractComponent {
178
212
  };
179
213
  }
180
214
 
181
- public static deserializeFromString(json: any, getTextDimens: GetTextDimensCallback = Text.getTextDimens): Text {
215
+ public static deserializeFromString(json: any): TextComponent {
182
216
  const style: TextStyle = {
183
217
  renderingStyle: styleFromJSON(json.style.renderingStyle),
184
218
  size: json.style.size,
@@ -187,12 +221,12 @@ export default class Text extends AbstractComponent {
187
221
  fontFamily: json.style.fontFamily,
188
222
  };
189
223
 
190
- const textObjects: Array<string|Text> = json.textObjects.map((data: any) => {
224
+ const textObjects: Array<string|TextComponent> = json.textObjects.map((data: any) => {
191
225
  if ((data.text ?? null) !== null) {
192
226
  return data.text;
193
227
  }
194
228
 
195
- return Text.deserializeFromString(data.json);
229
+ return TextComponent.deserializeFromString(data.json);
196
230
  });
197
231
 
198
232
  json.transform = json.transform.filter((elem: any) => typeof elem === 'number');
@@ -203,8 +237,27 @@ export default class Text extends AbstractComponent {
203
237
  const transformData = json.transform as Mat33Array;
204
238
  const transform = new Mat33(...transformData);
205
239
 
206
- return new Text(textObjects, transform, style, getTextDimens);
240
+ return new TextComponent(textObjects, transform, style);
241
+ }
242
+
243
+ public static fromLines(lines: string[], transform: Mat33, style: TextStyle): AbstractComponent {
244
+ let lastComponent: TextComponent|null = null;
245
+ const components: TextComponent[] = [];
246
+
247
+ for (const line of lines) {
248
+ let position = Vec2.zero;
249
+ if (lastComponent) {
250
+ const lineMargin = Math.floor(style.size);
251
+ position = lastComponent.getBBox().bottomLeft.plus(Vec2.unitY.times(lineMargin));
252
+ }
253
+
254
+ const component = new TextComponent([ line ], Mat33.translation(position), style);
255
+ components.push(component);
256
+ lastComponent = component;
257
+ }
258
+
259
+ return new TextComponent(components, transform, style);
207
260
  }
208
261
  }
209
262
 
210
- AbstractComponent.registerComponent(componentTypeId, (data: string) => Text.deserializeFromString(data));
263
+ AbstractComponent.registerComponent(componentTypeId, (data: string) => TextComponent.deserializeFromString(data));
@@ -37,6 +37,10 @@ export default class UnknownSVGObject extends AbstractComponent {
37
37
  protected applyTransformation(_affineTransfm: Mat33): void {
38
38
  }
39
39
 
40
+ public isSelectable() {
41
+ return false;
42
+ }
43
+
40
44
  protected createClone(): AbstractComponent {
41
45
  return new UnknownSVGObject(this.svgObject.cloneNode(true) as SVGElement);
42
46
  }