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
@@ -6,11 +6,11 @@ import LineSegment2 from '../../math/LineSegment2';
|
|
6
6
|
import Stroke from '../Stroke';
|
7
7
|
import Viewport from '../../Viewport';
|
8
8
|
export const makeFreehandLineBuilder = (initialPoint, viewport) => {
|
9
|
-
// Don't smooth if input is more than ±
|
9
|
+
// Don't smooth if input is more than ± 3 pixels from the true curve, do smooth if
|
10
10
|
// less than ±1 px from the curve.
|
11
|
-
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() *
|
11
|
+
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 3;
|
12
12
|
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
|
13
|
-
return new FreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist);
|
13
|
+
return new FreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist, viewport);
|
14
14
|
};
|
15
15
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
16
16
|
export default class FreehandLineBuilder {
|
@@ -19,13 +19,17 @@ export default class FreehandLineBuilder {
|
|
19
19
|
// for which a point is considered 'part of the curve'.
|
20
20
|
// Note that the maximum will be smaller if the stroke width is less than
|
21
21
|
// [maxFitAllowed].
|
22
|
-
minFitAllowed, maxFitAllowed) {
|
22
|
+
minFitAllowed, maxFitAllowed, viewport) {
|
23
23
|
this.startPoint = startPoint;
|
24
24
|
this.minFitAllowed = minFitAllowed;
|
25
25
|
this.maxFitAllowed = maxFitAllowed;
|
26
|
+
this.viewport = viewport;
|
26
27
|
this.isFirstSegment = true;
|
27
28
|
this.pathStartConnector = null;
|
28
29
|
this.mostRecentConnector = null;
|
30
|
+
this.lastUpperBezier = null;
|
31
|
+
this.lastLowerBezier = null;
|
32
|
+
this.parts = [];
|
29
33
|
this.lastExitingVec = null;
|
30
34
|
this.currentCurve = null;
|
31
35
|
this.lastPoint = this.startPoint;
|
@@ -46,16 +50,16 @@ export default class FreehandLineBuilder {
|
|
46
50
|
fill: (_a = this.lastPoint.color) !== null && _a !== void 0 ? _a : null,
|
47
51
|
};
|
48
52
|
}
|
49
|
-
|
53
|
+
previewCurrentPath() {
|
50
54
|
var _a;
|
51
|
-
|
52
|
-
|
55
|
+
const upperPath = this.upperSegments.slice();
|
56
|
+
const lowerPath = this.lowerSegments.slice();
|
53
57
|
let lowerToUpperCap;
|
54
58
|
let pathStartConnector;
|
55
59
|
if (this.currentCurve) {
|
56
|
-
const {
|
57
|
-
upperPath
|
58
|
-
lowerPath
|
60
|
+
const { upperCurveCommand, lowerToUpperConnector, upperToLowerConnector, lowerCurveCommand } = this.currentSegmentToPath();
|
61
|
+
upperPath.push(upperCurveCommand);
|
62
|
+
lowerPath.push(lowerCurveCommand);
|
59
63
|
lowerToUpperCap = lowerToUpperConnector;
|
60
64
|
pathStartConnector = (_a = this.pathStartConnector) !== null && _a !== void 0 ? _a : upperToLowerConnector;
|
61
65
|
}
|
@@ -63,12 +67,17 @@ export default class FreehandLineBuilder {
|
|
63
67
|
if (this.mostRecentConnector === null || this.pathStartConnector === null) {
|
64
68
|
return null;
|
65
69
|
}
|
66
|
-
upperPath = this.upperSegments.slice();
|
67
|
-
lowerPath = this.lowerSegments.slice();
|
68
70
|
lowerToUpperCap = this.mostRecentConnector;
|
69
71
|
pathStartConnector = this.pathStartConnector;
|
70
72
|
}
|
71
|
-
|
73
|
+
let startPoint;
|
74
|
+
const lastLowerSegment = lowerPath[lowerPath.length - 1];
|
75
|
+
if (lastLowerSegment.kind === PathCommandType.LineTo || lastLowerSegment.kind === PathCommandType.MoveTo) {
|
76
|
+
startPoint = lastLowerSegment.point;
|
77
|
+
}
|
78
|
+
else {
|
79
|
+
startPoint = lastLowerSegment.endPoint;
|
80
|
+
}
|
72
81
|
return {
|
73
82
|
// Start at the end of the lower curve:
|
74
83
|
// Start point
|
@@ -103,21 +112,33 @@ export default class FreehandLineBuilder {
|
|
103
112
|
style: this.getRenderingStyle(),
|
104
113
|
};
|
105
114
|
}
|
115
|
+
previewFullPath() {
|
116
|
+
const preview = this.previewCurrentPath();
|
117
|
+
if (preview) {
|
118
|
+
return [...this.parts, preview];
|
119
|
+
}
|
120
|
+
return null;
|
121
|
+
}
|
106
122
|
previewStroke() {
|
107
|
-
const pathPreview = this.
|
123
|
+
const pathPreview = this.previewFullPath();
|
108
124
|
if (pathPreview) {
|
109
|
-
return new Stroke(
|
125
|
+
return new Stroke(pathPreview);
|
110
126
|
}
|
111
127
|
return null;
|
112
128
|
}
|
113
129
|
preview(renderer) {
|
114
|
-
const
|
115
|
-
if (
|
116
|
-
|
130
|
+
const paths = this.previewFullPath();
|
131
|
+
if (paths) {
|
132
|
+
const approxBBox = this.viewport.visibleRect;
|
133
|
+
renderer.startObject(approxBBox);
|
134
|
+
for (const path of paths) {
|
135
|
+
renderer.drawPath(path);
|
136
|
+
}
|
137
|
+
renderer.endObject();
|
117
138
|
}
|
118
139
|
}
|
119
140
|
build() {
|
120
|
-
if (this.lastPoint
|
141
|
+
if (this.lastPoint) {
|
121
142
|
this.finalizeCurrentCurve();
|
122
143
|
}
|
123
144
|
return this.previewStroke();
|
@@ -129,6 +150,61 @@ export default class FreehandLineBuilder {
|
|
129
150
|
}
|
130
151
|
return Viewport.roundPoint(point, minFit);
|
131
152
|
}
|
153
|
+
// Returns true if, due to overlap with previous segments, a new RenderablePathSpec should be created.
|
154
|
+
shouldStartNewSegment(lowerCurve, upperCurve) {
|
155
|
+
if (!this.lastLowerBezier || !this.lastUpperBezier) {
|
156
|
+
return false;
|
157
|
+
}
|
158
|
+
const getIntersection = (curve1, curve2) => {
|
159
|
+
const intersection = curve1.intersects(curve2);
|
160
|
+
if (!intersection || intersection.length === 0) {
|
161
|
+
return null;
|
162
|
+
}
|
163
|
+
// From http://pomax.github.io/bezierjs/#intersect-curve,
|
164
|
+
// .intersects returns an array of 't1/t2' pairs, where curve1.at(t1) gives the point.
|
165
|
+
const firstTPair = intersection[0];
|
166
|
+
const match = /^([-0-9.eE]+)\/([-0-9.eE]+)$/.exec(firstTPair);
|
167
|
+
if (!match) {
|
168
|
+
throw new Error(`Incorrect format returned by .intersects: ${intersection} should be array of "number/number"!`);
|
169
|
+
}
|
170
|
+
const t = parseFloat(match[1]);
|
171
|
+
return Vec2.ofXY(curve1.get(t));
|
172
|
+
};
|
173
|
+
const getExitDirection = (curve) => {
|
174
|
+
return Vec2.ofXY(curve.points[2]).minus(Vec2.ofXY(curve.points[1])).normalized();
|
175
|
+
};
|
176
|
+
const getEnterDirection = (curve) => {
|
177
|
+
return Vec2.ofXY(curve.points[1]).minus(Vec2.ofXY(curve.points[0])).normalized();
|
178
|
+
};
|
179
|
+
// Prevent
|
180
|
+
// /
|
181
|
+
// / /
|
182
|
+
// / / /|
|
183
|
+
// / / |
|
184
|
+
// / |
|
185
|
+
// where the next stroke and the previous stroke are in different directions.
|
186
|
+
//
|
187
|
+
// Are the exit/enter directions of the previous and current curves in different enough directions?
|
188
|
+
if (getEnterDirection(upperCurve).dot(getExitDirection(this.lastUpperBezier)) < 0.3
|
189
|
+
|| getEnterDirection(lowerCurve).dot(getExitDirection(this.lastLowerBezier)) < 0.3
|
190
|
+
// Also handle if the curves exit/enter directions differ
|
191
|
+
|| getEnterDirection(upperCurve).dot(getExitDirection(upperCurve)) < 0
|
192
|
+
|| getEnterDirection(lowerCurve).dot(getExitDirection(lowerCurve)) < 0) {
|
193
|
+
return true;
|
194
|
+
}
|
195
|
+
// Check whether the lower curve intersects the other wall:
|
196
|
+
// / / ← lower
|
197
|
+
// / / /
|
198
|
+
// / / /
|
199
|
+
// //
|
200
|
+
// / /
|
201
|
+
const lowerIntersection = getIntersection(lowerCurve, this.lastUpperBezier);
|
202
|
+
const upperIntersection = getIntersection(upperCurve, this.lastLowerBezier);
|
203
|
+
if (lowerIntersection || upperIntersection) {
|
204
|
+
return true;
|
205
|
+
}
|
206
|
+
return false;
|
207
|
+
}
|
132
208
|
// Returns the distance between the start, control, and end points of the curve.
|
133
209
|
approxCurrentCurveLength() {
|
134
210
|
if (!this.currentCurve) {
|
@@ -185,8 +261,17 @@ export default class FreehandLineBuilder {
|
|
185
261
|
this.mostRecentConnector = this.pathStartConnector;
|
186
262
|
return;
|
187
263
|
}
|
188
|
-
const {
|
189
|
-
|
264
|
+
const { upperCurveCommand, lowerToUpperConnector, upperToLowerConnector, lowerCurveCommand, lowerCurve, upperCurve, } = this.currentSegmentToPath();
|
265
|
+
const shouldStartNew = this.shouldStartNewSegment(lowerCurve, upperCurve);
|
266
|
+
if (shouldStartNew) {
|
267
|
+
const part = this.previewCurrentPath();
|
268
|
+
if (part) {
|
269
|
+
this.parts.push(part);
|
270
|
+
this.upperSegments = [];
|
271
|
+
this.lowerSegments = [];
|
272
|
+
}
|
273
|
+
}
|
274
|
+
if (this.isFirstSegment || shouldStartNew) {
|
190
275
|
// We draw the upper path (reversed), then the lower path, so we need the
|
191
276
|
// upperToLowerConnector to join the two paths.
|
192
277
|
this.pathStartConnector = upperToLowerConnector;
|
@@ -195,8 +280,10 @@ export default class FreehandLineBuilder {
|
|
195
280
|
// With the most recent connector, we're joining the end of the lowerPath to the most recent
|
196
281
|
// upperPath:
|
197
282
|
this.mostRecentConnector = lowerToUpperConnector;
|
198
|
-
this.
|
199
|
-
this.
|
283
|
+
this.lowerSegments.push(lowerCurveCommand);
|
284
|
+
this.upperSegments.push(upperCurveCommand);
|
285
|
+
this.lastLowerBezier = lowerCurve;
|
286
|
+
this.lastUpperBezier = upperCurve;
|
200
287
|
const lastPoint = this.buffer[this.buffer.length - 1];
|
201
288
|
this.lastExitingVec = Vec2.ofXY(this.currentCurve.points[2]).minus(Vec2.ofXY(this.currentCurve.points[1]));
|
202
289
|
console.assert(this.lastExitingVec.magnitude() !== 0, 'lastExitingVec has zero length!');
|
@@ -238,11 +325,13 @@ export default class FreehandLineBuilder {
|
|
238
325
|
.normalized().times(this.curveStartWidth / 2 * halfVecT
|
239
326
|
+ this.curveEndWidth / 2 * (1 - halfVecT));
|
240
327
|
// Each starts at startPt ± startVec
|
328
|
+
const lowerCurveStartPoint = this.roundPoint(startPt.plus(startVec));
|
241
329
|
const lowerCurveControlPoint = this.roundPoint(controlPoint.plus(halfVec));
|
242
330
|
const lowerCurveEndPoint = this.roundPoint(endPt.plus(endVec));
|
243
331
|
const upperCurveControlPoint = this.roundPoint(controlPoint.minus(halfVec));
|
244
332
|
const upperCurveStartPoint = this.roundPoint(endPt.minus(endVec));
|
245
|
-
const
|
333
|
+
const upperCurveEndPoint = this.roundPoint(startPt.minus(startVec));
|
334
|
+
const lowerCurveCommand = {
|
246
335
|
kind: PathCommandType.QuadraticBezierTo,
|
247
336
|
controlPoint: lowerCurveControlPoint,
|
248
337
|
endPoint: lowerCurveEndPoint,
|
@@ -250,19 +339,24 @@ export default class FreehandLineBuilder {
|
|
250
339
|
// From the end of the upperCurve to the start of the lowerCurve:
|
251
340
|
const upperToLowerConnector = {
|
252
341
|
kind: PathCommandType.LineTo,
|
253
|
-
point:
|
342
|
+
point: lowerCurveStartPoint,
|
254
343
|
};
|
255
344
|
// From the end of lowerCurve to the start of upperCurve:
|
256
345
|
const lowerToUpperConnector = {
|
257
346
|
kind: PathCommandType.LineTo,
|
258
347
|
point: upperCurveStartPoint,
|
259
348
|
};
|
260
|
-
const
|
349
|
+
const upperCurveCommand = {
|
261
350
|
kind: PathCommandType.QuadraticBezierTo,
|
262
351
|
controlPoint: upperCurveControlPoint,
|
263
|
-
endPoint:
|
352
|
+
endPoint: upperCurveEndPoint,
|
353
|
+
};
|
354
|
+
const upperCurve = new Bezier(upperCurveStartPoint, upperCurveControlPoint, upperCurveEndPoint);
|
355
|
+
const lowerCurve = new Bezier(lowerCurveStartPoint, lowerCurveControlPoint, lowerCurveEndPoint);
|
356
|
+
return {
|
357
|
+
upperCurveCommand, upperToLowerConnector, lowerToUpperConnector, lowerCurveCommand,
|
358
|
+
upperCurve, lowerCurve,
|
264
359
|
};
|
265
|
-
return { upperCurve, upperToLowerConnector, lowerToUpperConnector, lowerCurve };
|
266
360
|
}
|
267
361
|
// Compute the direction of the velocity at the end of this.buffer
|
268
362
|
computeExitingVec() {
|
@@ -299,6 +393,10 @@ export default class FreehandLineBuilder {
|
|
299
393
|
const pointRadius = newPoint.width / 2;
|
300
394
|
const prevEndWidth = this.curveEndWidth;
|
301
395
|
this.curveEndWidth = pointRadius;
|
396
|
+
if (this.isFirstSegment) {
|
397
|
+
// The start of a curve often lacks accurate pressure information. Update it.
|
398
|
+
this.curveStartWidth = (this.curveStartWidth + pointRadius) / 2;
|
399
|
+
}
|
302
400
|
// recompute bbox
|
303
401
|
this.bbox = this.bbox.grownToPoint(newPoint.pos, pointRadius);
|
304
402
|
if (this.currentCurve === null) {
|
@@ -310,10 +408,11 @@ export default class FreehandLineBuilder {
|
|
310
408
|
this.curveStartWidth = lastPoint.width / 2;
|
311
409
|
console.assert(!isNaN(p1.magnitude()) && !isNaN(p2.magnitude()) && !isNaN(p3.magnitude()), 'Expected !NaN');
|
312
410
|
}
|
411
|
+
// If there isn't an entering vector (e.g. because this.isFirstCurve), approximate it.
|
313
412
|
let enteringVec = this.lastExitingVec;
|
314
413
|
if (!enteringVec) {
|
315
|
-
let sampleIdx = Math.ceil(this.buffer.length /
|
316
|
-
if (sampleIdx === 0) {
|
414
|
+
let sampleIdx = Math.ceil(this.buffer.length / 2);
|
415
|
+
if (sampleIdx === 0 || sampleIdx >= this.buffer.length) {
|
317
416
|
sampleIdx = this.buffer.length - 1;
|
318
417
|
}
|
319
418
|
enteringVec = this.buffer[sampleIdx].minus(this.buffer[0]);
|
@@ -3,6 +3,6 @@ export { makeFreehandLineBuilder } from './builders/FreehandLineBuilder';
|
|
3
3
|
export * from './AbstractComponent';
|
4
4
|
export { default as AbstractComponent } from './AbstractComponent';
|
5
5
|
import Stroke from './Stroke';
|
6
|
-
import
|
6
|
+
import TextComponent from './Text';
|
7
7
|
import ImageComponent from './ImageComponent';
|
8
|
-
export { Stroke, Text,
|
8
|
+
export { Stroke, TextComponent as Text, TextComponent as TextComponent, Stroke as StrokeComponent, ImageComponent, };
|
@@ -3,6 +3,6 @@ export { makeFreehandLineBuilder } from './builders/FreehandLineBuilder';
|
|
3
3
|
export * from './AbstractComponent';
|
4
4
|
export { default as AbstractComponent } from './AbstractComponent';
|
5
5
|
import Stroke from './Stroke';
|
6
|
-
import
|
6
|
+
import TextComponent from './Text';
|
7
7
|
import ImageComponent from './ImageComponent';
|
8
|
-
export { Stroke, Text,
|
8
|
+
export { Stroke, TextComponent as Text, TextComponent as TextComponent, Stroke as StrokeComponent, ImageComponent, };
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
|
-
import
|
2
|
+
import TextComponent from '../../components/Text';
|
3
3
|
import { Vec2 } from '../../math/Vec2';
|
4
4
|
import AbstractRenderer from './AbstractRenderer';
|
5
5
|
export default class CanvasRenderer extends AbstractRenderer {
|
@@ -113,7 +113,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
113
113
|
this.ctx.save();
|
114
114
|
transform = this.getCanvasToScreenTransform().rightMul(transform);
|
115
115
|
this.transformBy(transform);
|
116
|
-
|
116
|
+
TextComponent.applyTextStyles(this.ctx, style);
|
117
117
|
if (style.renderingStyle.fill.a !== 0) {
|
118
118
|
this.ctx.fillStyle = style.renderingStyle.fill.toHexString();
|
119
119
|
this.ctx.fillText(text, 0, 0);
|
@@ -20,6 +20,8 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
20
20
|
private addPathToSVG;
|
21
21
|
drawPath(pathSpec: RenderablePathSpec): void;
|
22
22
|
private transformFrom;
|
23
|
+
private textContainer;
|
24
|
+
private textContainerTransform;
|
23
25
|
drawText(text: string, transform: Mat33, style: TextStyle): void;
|
24
26
|
drawImage(image: RenderableImage): void;
|
25
27
|
startObject(boundingBox: Rect2): void;
|
@@ -15,6 +15,8 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
15
15
|
this.lastPathString = [];
|
16
16
|
this.objectElems = null;
|
17
17
|
this.overwrittenAttrs = {};
|
18
|
+
this.textContainer = null;
|
19
|
+
this.textContainerTransform = null;
|
18
20
|
this.clear();
|
19
21
|
}
|
20
22
|
// Sets an attribute on the root SVG element.
|
@@ -82,35 +84,59 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
82
84
|
this.lastPathString.push(path.toString());
|
83
85
|
}
|
84
86
|
// Apply [elemTransform] to [elem].
|
85
|
-
transformFrom(elemTransform, elem) {
|
86
|
-
let transform = this.getCanvasToScreenTransform().rightMul(elemTransform);
|
87
|
+
transformFrom(elemTransform, elem, inCanvasSpace = false) {
|
88
|
+
let transform = !inCanvasSpace ? this.getCanvasToScreenTransform().rightMul(elemTransform) : elemTransform;
|
87
89
|
const translation = transform.transformVec2(Vec2.zero);
|
88
90
|
transform = transform.rightMul(Mat33.translation(translation.times(-1)));
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
if (!transform.eq(Mat33.identity)) {
|
92
|
+
elem.style.transform = `matrix(
|
93
|
+
${transform.a1}, ${transform.b1},
|
94
|
+
${transform.a2}, ${transform.b2},
|
95
|
+
${transform.a3}, ${transform.b3}
|
96
|
+
)`;
|
97
|
+
}
|
98
|
+
else {
|
99
|
+
elem.style.transform = '';
|
100
|
+
}
|
94
101
|
elem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
95
102
|
elem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
96
103
|
}
|
97
104
|
drawText(text, transform, style) {
|
98
|
-
var _a
|
99
|
-
const
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
105
|
+
var _a;
|
106
|
+
const applyTextStyles = (elem, style) => {
|
107
|
+
var _a, _b;
|
108
|
+
elem.style.fontFamily = style.fontFamily;
|
109
|
+
elem.style.fontVariant = (_a = style.fontVariant) !== null && _a !== void 0 ? _a : '';
|
110
|
+
elem.style.fontWeight = (_b = style.fontWeight) !== null && _b !== void 0 ? _b : '';
|
111
|
+
elem.style.fontSize = style.size + 'px';
|
112
|
+
elem.style.fill = style.renderingStyle.fill.toHexString();
|
113
|
+
if (style.renderingStyle.stroke) {
|
114
|
+
const strokeStyle = style.renderingStyle.stroke;
|
115
|
+
elem.style.stroke = strokeStyle.color.toHexString();
|
116
|
+
elem.style.strokeWidth = strokeStyle.width + 'px';
|
117
|
+
}
|
118
|
+
};
|
119
|
+
transform = this.getCanvasToScreenTransform().rightMul(transform);
|
120
|
+
if (!this.textContainer) {
|
121
|
+
const container = document.createElementNS(svgNameSpace, 'text');
|
122
|
+
container.appendChild(document.createTextNode(text));
|
123
|
+
this.transformFrom(transform, container, true);
|
124
|
+
applyTextStyles(container, style);
|
125
|
+
this.elem.appendChild(container);
|
126
|
+
(_a = this.objectElems) === null || _a === void 0 ? void 0 : _a.push(container);
|
127
|
+
if (this.objectLevel > 0) {
|
128
|
+
this.textContainer = container;
|
129
|
+
this.textContainerTransform = transform;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
else {
|
133
|
+
const elem = document.createElementNS(svgNameSpace, 'tspan');
|
134
|
+
elem.appendChild(document.createTextNode(text));
|
135
|
+
this.textContainer.appendChild(elem);
|
136
|
+
transform = this.textContainerTransform.inverse().rightMul(transform);
|
137
|
+
this.transformFrom(transform, elem, true);
|
138
|
+
applyTextStyles(elem, style);
|
111
139
|
}
|
112
|
-
this.elem.appendChild(textElem);
|
113
|
-
(_c = this.objectElems) === null || _c === void 0 ? void 0 : _c.push(textElem);
|
114
140
|
}
|
115
141
|
drawImage(image) {
|
116
142
|
var _a, _b, _c, _d, _e;
|
@@ -128,6 +154,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
128
154
|
// Only accumulate a path within an object
|
129
155
|
this.lastPathString = [];
|
130
156
|
this.lastPathStyle = null;
|
157
|
+
this.textContainer = null;
|
131
158
|
this.objectElems = [];
|
132
159
|
}
|
133
160
|
endObject(loaderData) {
|
@@ -1,3 +1,7 @@
|
|
1
1
|
import loadExpectExtensions from './loadExpectExtensions';
|
2
2
|
loadExpectExtensions();
|
3
3
|
jest.useFakeTimers();
|
4
|
+
// jsdom doesn't support HTMLCanvasElement#getContext — it logs an error
|
5
|
+
// to the console. Make it return null so we can handle a non-existent Canvas
|
6
|
+
// at runtime (e.g. use something else, if available).
|
7
|
+
HTMLCanvasElement.prototype.getContext = () => null;
|
@@ -2,7 +2,6 @@ import { EditorEventType } from '../types';
|
|
2
2
|
import { coloris, init as colorisInit } from '@melloware/coloris';
|
3
3
|
import Color4 from '../Color4';
|
4
4
|
import { defaultToolbarLocalization } from './localization';
|
5
|
-
import { makeRedoIcon, makeUndoIcon } from './icons';
|
6
5
|
import SelectionTool from '../tools/SelectionTool/SelectionTool';
|
7
6
|
import PanZoomTool from '../tools/PanZoom';
|
8
7
|
import TextTool from '../tools/TextTool';
|
@@ -123,13 +122,13 @@ export default class HTMLToolbar {
|
|
123
122
|
undoRedoGroup.classList.add(`${toolbarCSSPrefix}buttonGroup`);
|
124
123
|
const undoButton = this.addActionButton({
|
125
124
|
label: this.localizationTable.undo,
|
126
|
-
icon: makeUndoIcon()
|
125
|
+
icon: this.editor.icons.makeUndoIcon()
|
127
126
|
}, () => {
|
128
127
|
this.editor.history.undo();
|
129
128
|
}, undoRedoGroup);
|
130
129
|
const redoButton = this.addActionButton({
|
131
130
|
label: this.localizationTable.redo,
|
132
|
-
icon: makeRedoIcon(),
|
131
|
+
icon: this.editor.icons.makeRedoIcon(),
|
133
132
|
}, () => {
|
134
133
|
this.editor.history.redo();
|
135
134
|
}, undoRedoGroup);
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import Color4 from '../Color4';
|
2
|
+
import { ComponentBuilderFactory } from '../components/builders/types';
|
3
|
+
import { TextStyle } from '../components/Text';
|
4
|
+
import Pen from '../tools/Pen';
|
5
|
+
declare type IconType = SVGSVGElement | HTMLImageElement;
|
6
|
+
export default class IconProvider {
|
7
|
+
makeUndoIcon(): IconType;
|
8
|
+
makeRedoIcon(mirror?: boolean): IconType;
|
9
|
+
makeDropdownIcon(): IconType;
|
10
|
+
makeEraserIcon(): IconType;
|
11
|
+
makeSelectionIcon(): IconType;
|
12
|
+
/**
|
13
|
+
* @param pathData - SVG path data (e.g. `m10,10l30,30z`)
|
14
|
+
* @param fill - A valid CSS color (e.g. `var(--icon-color)` or `#f0f`). This can be `none`.
|
15
|
+
*/
|
16
|
+
protected makeIconFromPath(pathData: string, fill?: string, strokeColor?: string, strokeWidth?: string): IconType;
|
17
|
+
makeHandToolIcon(): IconType;
|
18
|
+
makeTouchPanningIcon(): IconType;
|
19
|
+
makeAllDevicePanningIcon(): IconType;
|
20
|
+
makeZoomIcon(): IconType;
|
21
|
+
makeTextIcon(textStyle: TextStyle): IconType;
|
22
|
+
makePenIcon(tipThickness: number, color: string | Color4): IconType;
|
23
|
+
makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory): IconType;
|
24
|
+
makePipetteIcon(color?: Color4): IconType;
|
25
|
+
makeResizeViewportIcon(): IconType;
|
26
|
+
makeDuplicateSelectionIcon(): IconType;
|
27
|
+
makeDeleteSelectionIcon(): IconType;
|
28
|
+
makeSaveIcon(): IconType;
|
29
|
+
}
|
30
|
+
export {};
|