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.
- package/.firebase/hosting.ZG9jcw.cache +338 -0
- package/.github/ISSUE_TEMPLATE/translation.md +1 -1
- package/CHANGELOG.md +19 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +8 -6
- package/dist/src/Editor.js +8 -4
- package/dist/src/EditorImage.d.ts +3 -0
- package/dist/src/EditorImage.js +7 -0
- package/dist/src/SVGLoader.js +7 -8
- package/dist/src/components/AbstractComponent.d.ts +1 -0
- package/dist/src/components/AbstractComponent.js +4 -0
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -0
- package/dist/src/components/SVGGlobalAttributesObject.js +3 -0
- package/dist/src/components/Stroke.js +1 -0
- package/dist/src/components/Text.d.ts +11 -8
- package/dist/src/components/Text.js +63 -20
- package/dist/src/components/UnknownSVGObject.d.ts +1 -0
- package/dist/src/components/UnknownSVGObject.js +3 -0
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +9 -2
- package/dist/src/components/builders/FreehandLineBuilder.js +129 -30
- package/dist/src/components/lib.d.ts +2 -2
- package/dist/src/components/lib.js +2 -2
- package/dist/src/rendering/renderers/CanvasRenderer.js +2 -2
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -0
- package/dist/src/rendering/renderers/SVGRenderer.js +49 -22
- package/dist/src/testing/beforeEachFile.js +4 -0
- package/dist/src/toolbar/HTMLToolbar.js +2 -3
- package/dist/src/toolbar/IconProvider.d.ts +30 -0
- package/dist/src/toolbar/IconProvider.js +417 -0
- package/dist/src/toolbar/lib.d.ts +1 -1
- package/dist/src/toolbar/lib.js +1 -2
- package/dist/src/toolbar/localization.d.ts +0 -1
- package/dist/src/toolbar/localization.js +0 -1
- package/dist/src/toolbar/makeColorInput.js +1 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +1 -2
- package/dist/src/toolbar/widgets/EraserToolWidget.js +1 -2
- package/dist/src/toolbar/widgets/HandToolWidget.d.ts +5 -3
- package/dist/src/toolbar/widgets/HandToolWidget.js +35 -12
- package/dist/src/toolbar/widgets/PenToolWidget.js +10 -8
- package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +3 -0
- package/dist/src/toolbar/widgets/SelectionToolWidget.js +20 -7
- package/dist/src/toolbar/widgets/TextToolWidget.js +1 -2
- package/dist/src/tools/PanZoom.d.ts +1 -1
- package/dist/src/tools/PanZoom.js +4 -1
- package/dist/src/tools/PasteHandler.js +2 -22
- package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -0
- package/dist/src/tools/SelectionTool/SelectionTool.js +66 -3
- package/dist/src/tools/TextTool.d.ts +4 -0
- package/dist/src/tools/TextTool.js +73 -15
- package/dist/src/tools/ToolController.js +1 -0
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/package.json +1 -1
- package/src/Editor.toSVG.test.ts +27 -0
- package/src/Editor.ts +15 -9
- package/src/EditorImage.ts +9 -0
- package/src/SVGLoader.test.ts +57 -0
- package/src/SVGLoader.ts +9 -10
- package/src/components/AbstractComponent.ts +5 -0
- package/src/components/SVGGlobalAttributesObject.ts +4 -0
- package/src/components/Stroke.ts +1 -0
- package/src/components/Text.test.ts +3 -18
- package/src/components/Text.ts +78 -25
- package/src/components/UnknownSVGObject.ts +4 -0
- package/src/components/builders/FreehandLineBuilder.ts +162 -34
- package/src/components/lib.ts +3 -3
- package/src/rendering/renderers/CanvasRenderer.ts +2 -2
- package/src/rendering/renderers/SVGRenderer.ts +50 -24
- package/src/testing/beforeEachFile.ts +6 -1
- package/src/toolbar/HTMLToolbar.ts +2 -3
- package/src/toolbar/IconProvider.ts +480 -0
- package/src/toolbar/lib.ts +1 -1
- package/src/toolbar/localization.ts +0 -2
- package/src/toolbar/makeColorInput.ts +1 -2
- package/src/toolbar/widgets/BaseWidget.ts +1 -2
- package/src/toolbar/widgets/EraserToolWidget.ts +1 -2
- package/src/toolbar/widgets/HandToolWidget.ts +42 -20
- package/src/toolbar/widgets/PenToolWidget.ts +11 -8
- package/src/toolbar/widgets/SelectionToolWidget.ts +24 -8
- package/src/toolbar/widgets/TextToolWidget.ts +1 -2
- package/src/tools/PanZoom.ts +4 -1
- package/src/tools/PasteHandler.ts +2 -24
- package/src/tools/SelectionTool/SelectionTool.css +1 -0
- package/src/tools/SelectionTool/SelectionTool.test.ts +40 -0
- package/src/tools/SelectionTool/SelectionTool.ts +73 -4
- package/src/tools/TextTool.ts +82 -17
- package/src/tools/ToolController.ts +1 -0
- package/src/tools/localization.ts +4 -0
- package/typedoc.json +5 -1
- package/dist/src/toolbar/icons.d.ts +0 -20
- package/dist/src/toolbar/icons.js +0 -385
- 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
|
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
|
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):
|
214
|
-
const contentList: Array<
|
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
|
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
|
}
|
package/src/components/Stroke.ts
CHANGED
@@ -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
|
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
|
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
|
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
|
});
|
package/src/components/Text.ts
CHANGED
@@ -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
|
19
|
+
export default class TextComponent extends AbstractComponent {
|
21
20
|
protected contentBBox: Rect2;
|
22
21
|
|
23
22
|
public constructor(
|
24
|
-
protected readonly textObjects: Array<string|
|
23
|
+
protected readonly textObjects: Array<string|TextComponent>,
|
25
24
|
private transform: Mat33,
|
26
|
-
private
|
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
|
-
|
53
|
-
|
54
|
-
|
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|
|
82
|
+
private computeBBoxOfPart(part: string|TextComponent) {
|
65
83
|
if (typeof part === 'string') {
|
66
|
-
const textBBox =
|
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
|
-
|
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.
|
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 =
|
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
|
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
|
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|
|
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
|
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
|
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) =>
|
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
|
}
|