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
@@ -11,21 +11,24 @@ import { ComponentBuilder, ComponentBuilderFactory } from './types';
|
|
11
11
|
import RenderingStyle from '../../rendering/RenderingStyle';
|
12
12
|
|
13
13
|
export const makeFreehandLineBuilder: ComponentBuilderFactory = (initialPoint: StrokeDataPoint, viewport: Viewport) => {
|
14
|
-
// Don't smooth if input is more than ±
|
14
|
+
// Don't smooth if input is more than ± 3 pixels from the true curve, do smooth if
|
15
15
|
// less than ±1 px from the curve.
|
16
|
-
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() *
|
16
|
+
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 3;
|
17
17
|
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
|
18
18
|
|
19
19
|
return new FreehandLineBuilder(
|
20
|
-
initialPoint, minSmoothingDist, maxSmoothingDist
|
20
|
+
initialPoint, minSmoothingDist, maxSmoothingDist, viewport
|
21
21
|
);
|
22
22
|
};
|
23
23
|
|
24
24
|
type CurrentSegmentToPathResult = {
|
25
|
-
|
25
|
+
upperCurveCommand: QuadraticBezierPathCommand,
|
26
26
|
lowerToUpperConnector: PathCommand,
|
27
27
|
upperToLowerConnector: PathCommand,
|
28
|
-
|
28
|
+
lowerCurveCommand: QuadraticBezierPathCommand,
|
29
|
+
|
30
|
+
upperCurve: Bezier,
|
31
|
+
lowerCurve: Bezier,
|
29
32
|
};
|
30
33
|
|
31
34
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
@@ -47,8 +50,11 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
47
50
|
// least recent edge.
|
48
51
|
// The lowerSegments form a path that goes from the least recent edge to the most
|
49
52
|
// recent edge.
|
50
|
-
private upperSegments:
|
51
|
-
private lowerSegments:
|
53
|
+
private upperSegments: PathCommand[];
|
54
|
+
private lowerSegments: PathCommand[];
|
55
|
+
private lastUpperBezier: Bezier|null = null;
|
56
|
+
private lastLowerBezier: Bezier|null = null;
|
57
|
+
private parts: RenderablePathSpec[] = [];
|
52
58
|
|
53
59
|
private buffer: Point2[];
|
54
60
|
private lastPoint: StrokeDataPoint;
|
@@ -69,7 +75,9 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
69
75
|
// Note that the maximum will be smaller if the stroke width is less than
|
70
76
|
// [maxFitAllowed].
|
71
77
|
private minFitAllowed: number,
|
72
|
-
private maxFitAllowed: number
|
78
|
+
private maxFitAllowed: number,
|
79
|
+
|
80
|
+
private viewport: Viewport,
|
73
81
|
) {
|
74
82
|
this.lastPoint = this.startPoint;
|
75
83
|
this.upperSegments = [];
|
@@ -93,15 +101,19 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
93
101
|
};
|
94
102
|
}
|
95
103
|
|
96
|
-
private
|
97
|
-
|
98
|
-
|
104
|
+
private previewCurrentPath(): RenderablePathSpec|null {
|
105
|
+
const upperPath = this.upperSegments.slice();
|
106
|
+
const lowerPath = this.lowerSegments.slice();
|
99
107
|
let lowerToUpperCap: PathCommand;
|
100
108
|
let pathStartConnector: PathCommand;
|
101
109
|
if (this.currentCurve) {
|
102
|
-
const {
|
103
|
-
|
104
|
-
|
110
|
+
const {
|
111
|
+
upperCurveCommand, lowerToUpperConnector, upperToLowerConnector, lowerCurveCommand
|
112
|
+
} = this.currentSegmentToPath();
|
113
|
+
|
114
|
+
upperPath.push(upperCurveCommand);
|
115
|
+
lowerPath.push(lowerCurveCommand);
|
116
|
+
|
105
117
|
lowerToUpperCap = lowerToUpperConnector;
|
106
118
|
pathStartConnector = this.pathStartConnector ?? upperToLowerConnector;
|
107
119
|
} else {
|
@@ -109,13 +121,17 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
109
121
|
return null;
|
110
122
|
}
|
111
123
|
|
112
|
-
upperPath = this.upperSegments.slice();
|
113
|
-
lowerPath = this.lowerSegments.slice();
|
114
124
|
lowerToUpperCap = this.mostRecentConnector;
|
115
125
|
pathStartConnector = this.pathStartConnector;
|
116
126
|
}
|
117
|
-
const startPoint = lowerPath[lowerPath.length - 1].endPoint;
|
118
127
|
|
128
|
+
let startPoint: Point2;
|
129
|
+
const lastLowerSegment = lowerPath[lowerPath.length - 1];
|
130
|
+
if (lastLowerSegment.kind === PathCommandType.LineTo || lastLowerSegment.kind === PathCommandType.MoveTo) {
|
131
|
+
startPoint = lastLowerSegment.point;
|
132
|
+
} else {
|
133
|
+
startPoint = lastLowerSegment.endPoint;
|
134
|
+
}
|
119
135
|
|
120
136
|
return {
|
121
137
|
// Start at the end of the lower curve:
|
@@ -156,24 +172,37 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
156
172
|
};
|
157
173
|
}
|
158
174
|
|
175
|
+
private previewFullPath(): RenderablePathSpec[]|null {
|
176
|
+
const preview = this.previewCurrentPath();
|
177
|
+
if (preview) {
|
178
|
+
return [ ...this.parts, preview ];
|
179
|
+
}
|
180
|
+
return null;
|
181
|
+
}
|
182
|
+
|
159
183
|
private previewStroke(): Stroke|null {
|
160
|
-
const pathPreview = this.
|
184
|
+
const pathPreview = this.previewFullPath();
|
161
185
|
|
162
186
|
if (pathPreview) {
|
163
|
-
return new Stroke(
|
187
|
+
return new Stroke(pathPreview);
|
164
188
|
}
|
165
189
|
return null;
|
166
190
|
}
|
167
191
|
|
168
192
|
public preview(renderer: AbstractRenderer) {
|
169
|
-
const
|
170
|
-
if (
|
171
|
-
|
193
|
+
const paths = this.previewFullPath();
|
194
|
+
if (paths) {
|
195
|
+
const approxBBox = this.viewport.visibleRect;
|
196
|
+
renderer.startObject(approxBBox);
|
197
|
+
for (const path of paths) {
|
198
|
+
renderer.drawPath(path);
|
199
|
+
}
|
200
|
+
renderer.endObject();
|
172
201
|
}
|
173
202
|
}
|
174
203
|
|
175
204
|
public build(): Stroke {
|
176
|
-
if (this.lastPoint
|
205
|
+
if (this.lastPoint) {
|
177
206
|
this.finalizeCurrentCurve();
|
178
207
|
}
|
179
208
|
return this.previewStroke()!;
|
@@ -189,6 +218,74 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
189
218
|
return Viewport.roundPoint(point, minFit);
|
190
219
|
}
|
191
220
|
|
221
|
+
// Returns true if, due to overlap with previous segments, a new RenderablePathSpec should be created.
|
222
|
+
private shouldStartNewSegment(lowerCurve: Bezier, upperCurve: Bezier): boolean {
|
223
|
+
if (!this.lastLowerBezier || !this.lastUpperBezier) {
|
224
|
+
return false;
|
225
|
+
}
|
226
|
+
|
227
|
+
const getIntersection = (curve1: Bezier, curve2: Bezier): Point2|null => {
|
228
|
+
const intersection = curve1.intersects(curve2) as (string[] | null | undefined);
|
229
|
+
if (!intersection || intersection.length === 0) {
|
230
|
+
return null;
|
231
|
+
}
|
232
|
+
|
233
|
+
// From http://pomax.github.io/bezierjs/#intersect-curve,
|
234
|
+
// .intersects returns an array of 't1/t2' pairs, where curve1.at(t1) gives the point.
|
235
|
+
const firstTPair = intersection[0];
|
236
|
+
const match = /^([-0-9.eE]+)\/([-0-9.eE]+)$/.exec(firstTPair);
|
237
|
+
|
238
|
+
if (!match) {
|
239
|
+
throw new Error(
|
240
|
+
`Incorrect format returned by .intersects: ${intersection} should be array of "number/number"!`
|
241
|
+
);
|
242
|
+
}
|
243
|
+
|
244
|
+
const t = parseFloat(match[1]);
|
245
|
+
return Vec2.ofXY(curve1.get(t));
|
246
|
+
};
|
247
|
+
|
248
|
+
const getExitDirection = (curve: Bezier): Vec2 => {
|
249
|
+
return Vec2.ofXY(curve.points[2]).minus(Vec2.ofXY(curve.points[1])).normalized();
|
250
|
+
};
|
251
|
+
|
252
|
+
const getEnterDirection = (curve: Bezier): Vec2 => {
|
253
|
+
return Vec2.ofXY(curve.points[1]).minus(Vec2.ofXY(curve.points[0])).normalized();
|
254
|
+
};
|
255
|
+
|
256
|
+
// Prevent
|
257
|
+
// /
|
258
|
+
// / /
|
259
|
+
// / / /|
|
260
|
+
// / / |
|
261
|
+
// / |
|
262
|
+
// where the next stroke and the previous stroke are in different directions.
|
263
|
+
//
|
264
|
+
// Are the exit/enter directions of the previous and current curves in different enough directions?
|
265
|
+
if (getEnterDirection(upperCurve).dot(getExitDirection(this.lastUpperBezier)) < 0.3
|
266
|
+
|| getEnterDirection(lowerCurve).dot(getExitDirection(this.lastLowerBezier)) < 0.3
|
267
|
+
|
268
|
+
// Also handle if the curves exit/enter directions differ
|
269
|
+
|| getEnterDirection(upperCurve).dot(getExitDirection(upperCurve)) < 0
|
270
|
+
|| getEnterDirection(lowerCurve).dot(getExitDirection(lowerCurve)) < 0) {
|
271
|
+
return true;
|
272
|
+
}
|
273
|
+
|
274
|
+
// Check whether the lower curve intersects the other wall:
|
275
|
+
// / / ← lower
|
276
|
+
// / / /
|
277
|
+
// / / /
|
278
|
+
// //
|
279
|
+
// / /
|
280
|
+
const lowerIntersection = getIntersection(lowerCurve, this.lastUpperBezier);
|
281
|
+
const upperIntersection = getIntersection(upperCurve, this.lastLowerBezier);
|
282
|
+
if (lowerIntersection || upperIntersection) {
|
283
|
+
return true;
|
284
|
+
}
|
285
|
+
|
286
|
+
return false;
|
287
|
+
}
|
288
|
+
|
192
289
|
// Returns the distance between the start, control, and end points of the curve.
|
193
290
|
private approxCurrentCurveLength() {
|
194
291
|
if (!this.currentCurve) {
|
@@ -257,9 +354,23 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
257
354
|
return;
|
258
355
|
}
|
259
356
|
|
260
|
-
const {
|
357
|
+
const {
|
358
|
+
upperCurveCommand, lowerToUpperConnector, upperToLowerConnector, lowerCurveCommand,
|
359
|
+
lowerCurve, upperCurve,
|
360
|
+
} = this.currentSegmentToPath();
|
261
361
|
|
262
|
-
|
362
|
+
const shouldStartNew = this.shouldStartNewSegment(lowerCurve, upperCurve);
|
363
|
+
if (shouldStartNew) {
|
364
|
+
const part = this.previewCurrentPath();
|
365
|
+
|
366
|
+
if (part) {
|
367
|
+
this.parts.push(part);
|
368
|
+
this.upperSegments = [];
|
369
|
+
this.lowerSegments = [];
|
370
|
+
}
|
371
|
+
}
|
372
|
+
|
373
|
+
if (this.isFirstSegment || shouldStartNew) {
|
263
374
|
// We draw the upper path (reversed), then the lower path, so we need the
|
264
375
|
// upperToLowerConnector to join the two paths.
|
265
376
|
this.pathStartConnector = upperToLowerConnector;
|
@@ -269,8 +380,11 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
269
380
|
// upperPath:
|
270
381
|
this.mostRecentConnector = lowerToUpperConnector;
|
271
382
|
|
272
|
-
this.
|
273
|
-
this.
|
383
|
+
this.lowerSegments.push(lowerCurveCommand);
|
384
|
+
this.upperSegments.push(upperCurveCommand);
|
385
|
+
|
386
|
+
this.lastLowerBezier = lowerCurve;
|
387
|
+
this.lastUpperBezier = upperCurve;
|
274
388
|
|
275
389
|
const lastPoint = this.buffer[this.buffer.length - 1];
|
276
390
|
this.lastExitingVec = Vec2.ofXY(
|
@@ -325,12 +439,14 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
325
439
|
);
|
326
440
|
|
327
441
|
// Each starts at startPt ± startVec
|
442
|
+
const lowerCurveStartPoint = this.roundPoint(startPt.plus(startVec));
|
328
443
|
const lowerCurveControlPoint = this.roundPoint(controlPoint.plus(halfVec));
|
329
444
|
const lowerCurveEndPoint = this.roundPoint(endPt.plus(endVec));
|
330
445
|
const upperCurveControlPoint = this.roundPoint(controlPoint.minus(halfVec));
|
331
446
|
const upperCurveStartPoint = this.roundPoint(endPt.minus(endVec));
|
447
|
+
const upperCurveEndPoint = this.roundPoint(startPt.minus(startVec));
|
332
448
|
|
333
|
-
const
|
449
|
+
const lowerCurveCommand: QuadraticBezierPathCommand = {
|
334
450
|
kind: PathCommandType.QuadraticBezierTo,
|
335
451
|
controlPoint: lowerCurveControlPoint,
|
336
452
|
endPoint: lowerCurveEndPoint,
|
@@ -339,7 +455,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
339
455
|
// From the end of the upperCurve to the start of the lowerCurve:
|
340
456
|
const upperToLowerConnector: LinePathCommand = {
|
341
457
|
kind: PathCommandType.LineTo,
|
342
|
-
point:
|
458
|
+
point: lowerCurveStartPoint,
|
343
459
|
};
|
344
460
|
|
345
461
|
// From the end of lowerCurve to the start of upperCurve:
|
@@ -348,13 +464,19 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
348
464
|
point: upperCurveStartPoint,
|
349
465
|
};
|
350
466
|
|
351
|
-
const
|
467
|
+
const upperCurveCommand: QuadraticBezierPathCommand = {
|
352
468
|
kind: PathCommandType.QuadraticBezierTo,
|
353
469
|
controlPoint: upperCurveControlPoint,
|
354
|
-
endPoint:
|
470
|
+
endPoint: upperCurveEndPoint,
|
355
471
|
};
|
356
472
|
|
357
|
-
|
473
|
+
const upperCurve = new Bezier(upperCurveStartPoint, upperCurveControlPoint, upperCurveEndPoint);
|
474
|
+
const lowerCurve = new Bezier(lowerCurveStartPoint, lowerCurveControlPoint, lowerCurveEndPoint);
|
475
|
+
|
476
|
+
return {
|
477
|
+
upperCurveCommand, upperToLowerConnector, lowerToUpperConnector, lowerCurveCommand,
|
478
|
+
upperCurve, lowerCurve,
|
479
|
+
};
|
358
480
|
}
|
359
481
|
|
360
482
|
// Compute the direction of the velocity at the end of this.buffer
|
@@ -397,6 +519,11 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
397
519
|
const prevEndWidth = this.curveEndWidth;
|
398
520
|
this.curveEndWidth = pointRadius;
|
399
521
|
|
522
|
+
if (this.isFirstSegment) {
|
523
|
+
// The start of a curve often lacks accurate pressure information. Update it.
|
524
|
+
this.curveStartWidth = (this.curveStartWidth + pointRadius) / 2;
|
525
|
+
}
|
526
|
+
|
400
527
|
// recompute bbox
|
401
528
|
this.bbox = this.bbox.grownToPoint(newPoint.pos, pointRadius);
|
402
529
|
|
@@ -413,10 +540,11 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
413
540
|
console.assert(!isNaN(p1.magnitude()) && !isNaN(p2.magnitude()) && !isNaN(p3.magnitude()), 'Expected !NaN');
|
414
541
|
}
|
415
542
|
|
543
|
+
// If there isn't an entering vector (e.g. because this.isFirstCurve), approximate it.
|
416
544
|
let enteringVec = this.lastExitingVec;
|
417
545
|
if (!enteringVec) {
|
418
|
-
let sampleIdx = Math.ceil(this.buffer.length /
|
419
|
-
if (sampleIdx === 0) {
|
546
|
+
let sampleIdx = Math.ceil(this.buffer.length / 2);
|
547
|
+
if (sampleIdx === 0 || sampleIdx >= this.buffer.length) {
|
420
548
|
sampleIdx = this.buffer.length - 1;
|
421
549
|
}
|
422
550
|
|
package/src/components/lib.ts
CHANGED
@@ -4,14 +4,14 @@ export { makeFreehandLineBuilder } from './builders/FreehandLineBuilder';
|
|
4
4
|
export * from './AbstractComponent';
|
5
5
|
export { default as AbstractComponent } from './AbstractComponent';
|
6
6
|
import Stroke from './Stroke';
|
7
|
-
import
|
7
|
+
import TextComponent from './Text';
|
8
8
|
import ImageComponent from './ImageComponent';
|
9
9
|
|
10
10
|
export {
|
11
11
|
Stroke,
|
12
|
-
Text,
|
12
|
+
TextComponent as Text,
|
13
13
|
|
14
|
-
|
14
|
+
TextComponent as TextComponent,
|
15
15
|
Stroke as StrokeComponent,
|
16
16
|
ImageComponent,
|
17
17
|
};
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
|
-
import
|
2
|
+
import TextComponent, { TextStyle } from '../../components/Text';
|
3
3
|
import Mat33 from '../../math/Mat33';
|
4
4
|
import Rect2 from '../../math/Rect2';
|
5
5
|
import { Point2, Vec2 } from '../../math/Vec2';
|
@@ -153,7 +153,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
153
153
|
this.ctx.save();
|
154
154
|
transform = this.getCanvasToScreenTransform().rightMul(transform);
|
155
155
|
this.transformBy(transform);
|
156
|
-
|
156
|
+
TextComponent.applyTextStyles(this.ctx, style);
|
157
157
|
|
158
158
|
if (style.renderingStyle.fill.a !== 0) {
|
159
159
|
this.ctx.fillStyle = style.renderingStyle.fill.toHexString();
|
@@ -100,39 +100,64 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
100
100
|
}
|
101
101
|
|
102
102
|
// Apply [elemTransform] to [elem].
|
103
|
-
private transformFrom(elemTransform: Mat33, elem: SVGElement) {
|
104
|
-
let transform = this.getCanvasToScreenTransform().rightMul(elemTransform);
|
103
|
+
private transformFrom(elemTransform: Mat33, elem: SVGElement, inCanvasSpace: boolean = false) {
|
104
|
+
let transform = !inCanvasSpace ? this.getCanvasToScreenTransform().rightMul(elemTransform) : elemTransform;
|
105
105
|
const translation = transform.transformVec2(Vec2.zero);
|
106
106
|
transform = transform.rightMul(Mat33.translation(translation.times(-1)));
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
108
|
+
if (!transform.eq(Mat33.identity)) {
|
109
|
+
elem.style.transform = `matrix(
|
110
|
+
${transform.a1}, ${transform.b1},
|
111
|
+
${transform.a2}, ${transform.b2},
|
112
|
+
${transform.a3}, ${transform.b3}
|
113
|
+
)`;
|
114
|
+
} else {
|
115
|
+
elem.style.transform = '';
|
116
|
+
}
|
117
|
+
|
113
118
|
elem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
114
119
|
elem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
115
120
|
}
|
116
121
|
|
122
|
+
private textContainer: SVGTextElement|null = null;
|
123
|
+
private textContainerTransform: Mat33|null = null;
|
117
124
|
public drawText(text: string, transform: Mat33, style: TextStyle): void {
|
118
|
-
const
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
125
|
+
const applyTextStyles = (elem: SVGTextElement|SVGTSpanElement, style: TextStyle) => {
|
126
|
+
elem.style.fontFamily = style.fontFamily;
|
127
|
+
elem.style.fontVariant = style.fontVariant ?? '';
|
128
|
+
elem.style.fontWeight = style.fontWeight ?? '';
|
129
|
+
elem.style.fontSize = style.size + 'px';
|
130
|
+
elem.style.fill = style.renderingStyle.fill.toHexString();
|
131
|
+
|
132
|
+
if (style.renderingStyle.stroke) {
|
133
|
+
const strokeStyle = style.renderingStyle.stroke;
|
134
|
+
elem.style.stroke = strokeStyle.color.toHexString();
|
135
|
+
elem.style.strokeWidth = strokeStyle.width + 'px';
|
136
|
+
}
|
137
|
+
};
|
138
|
+
transform = this.getCanvasToScreenTransform().rightMul(transform);
|
139
|
+
|
140
|
+
if (!this.textContainer) {
|
141
|
+
const container = document.createElementNS(svgNameSpace, 'text');
|
142
|
+
container.appendChild(document.createTextNode(text));
|
143
|
+
this.transformFrom(transform, container, true);
|
144
|
+
applyTextStyles(container, style);
|
145
|
+
|
146
|
+
this.elem.appendChild(container);
|
147
|
+
this.objectElems?.push(container);
|
148
|
+
if (this.objectLevel > 0) {
|
149
|
+
this.textContainer = container;
|
150
|
+
this.textContainerTransform = transform;
|
151
|
+
}
|
152
|
+
} else {
|
153
|
+
const elem = document.createElementNS(svgNameSpace, 'tspan');
|
154
|
+
elem.appendChild(document.createTextNode(text));
|
155
|
+
this.textContainer.appendChild(elem);
|
133
156
|
|
134
|
-
|
135
|
-
|
157
|
+
transform = this.textContainerTransform!.inverse().rightMul(transform);
|
158
|
+
this.transformFrom(transform, elem, true);
|
159
|
+
applyTextStyles(elem, style);
|
160
|
+
}
|
136
161
|
}
|
137
162
|
|
138
163
|
public drawImage(image: RenderableImage) {
|
@@ -153,6 +178,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
153
178
|
// Only accumulate a path within an object
|
154
179
|
this.lastPathString = [];
|
155
180
|
this.lastPathStyle = null;
|
181
|
+
this.textContainer = null;
|
156
182
|
this.objectElems = [];
|
157
183
|
}
|
158
184
|
|
@@ -1,3 +1,8 @@
|
|
1
1
|
import loadExpectExtensions from './loadExpectExtensions';
|
2
2
|
loadExpectExtensions();
|
3
|
-
jest.useFakeTimers();
|
3
|
+
jest.useFakeTimers();
|
4
|
+
|
5
|
+
// jsdom doesn't support HTMLCanvasElement#getContext — it logs an error
|
6
|
+
// to the console. Make it return null so we can handle a non-existent Canvas
|
7
|
+
// at runtime (e.g. use something else, if available).
|
8
|
+
HTMLCanvasElement.prototype.getContext = () => null;
|
@@ -5,7 +5,6 @@ import { coloris, init as colorisInit } from '@melloware/coloris';
|
|
5
5
|
import Color4 from '../Color4';
|
6
6
|
import { defaultToolbarLocalization, ToolbarLocalization } from './localization';
|
7
7
|
import { ActionButtonIcon } from './types';
|
8
|
-
import { makeRedoIcon, makeUndoIcon } from './icons';
|
9
8
|
import SelectionTool from '../tools/SelectionTool/SelectionTool';
|
10
9
|
import PanZoomTool from '../tools/PanZoom';
|
11
10
|
import TextTool from '../tools/TextTool';
|
@@ -156,13 +155,13 @@ export default class HTMLToolbar {
|
|
156
155
|
|
157
156
|
const undoButton = this.addActionButton({
|
158
157
|
label: this.localizationTable.undo,
|
159
|
-
icon: makeUndoIcon()
|
158
|
+
icon: this.editor.icons.makeUndoIcon()
|
160
159
|
}, () => {
|
161
160
|
this.editor.history.undo();
|
162
161
|
}, undoRedoGroup);
|
163
162
|
const redoButton = this.addActionButton({
|
164
163
|
label: this.localizationTable.redo,
|
165
|
-
icon: makeRedoIcon(),
|
164
|
+
icon: this.editor.icons.makeRedoIcon(),
|
166
165
|
}, () => {
|
167
166
|
this.editor.history.redo();
|
168
167
|
}, undoRedoGroup);
|