js-draw 0.4.0 → 0.4.1
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/.github/pull_request_template.md +15 -0
- package/.github/workflows/firebase-hosting-merge.yml +7 -0
- package/.github/workflows/firebase-hosting-pull-request.yml +10 -0
- package/.github/workflows/github-pages.yml +2 -0
- package/CHANGELOG.md +7 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.js +3 -1
- package/dist/src/components/AbstractComponent.js +1 -0
- package/dist/src/components/Stroke.js +15 -9
- package/dist/src/components/Text.d.ts +1 -1
- package/dist/src/components/Text.js +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +1 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +34 -36
- package/dist/src/math/Vec3.d.ts +1 -1
- package/dist/src/math/Vec3.js +1 -1
- package/dist/src/testing/beforeEachFile.d.ts +1 -0
- package/dist/src/testing/beforeEachFile.js +3 -0
- package/dist/src/testing/createEditor.d.ts +1 -0
- package/dist/src/testing/createEditor.js +7 -1
- package/dist/src/testing/loadExpectExtensions.d.ts +0 -15
- package/dist/src/tools/SelectionTool/SelectionTool.js +8 -0
- package/jest.config.js +5 -0
- package/package.json +15 -14
- package/src/Editor.ts +2 -0
- package/src/components/AbstractComponent.ts +2 -0
- package/src/components/Stroke.test.ts +0 -3
- package/src/components/Stroke.ts +14 -7
- package/src/components/Text.test.ts +0 -3
- package/src/components/Text.ts +2 -2
- package/src/components/builders/FreehandLineBuilder.ts +37 -43
- package/src/language/assertions.ts +2 -2
- package/src/math/LineSegment2.test.ts +8 -10
- package/src/math/Mat33.test.ts +0 -2
- package/src/math/Rect2.test.ts +0 -3
- package/src/math/Vec2.test.ts +0 -3
- package/src/math/Vec3.test.ts +0 -3
- package/src/math/Vec3.ts +1 -1
- package/src/testing/beforeEachFile.ts +3 -0
- package/src/testing/createEditor.ts +8 -1
- package/src/testing/global.d.ts +17 -0
- package/src/testing/loadExpectExtensions.ts +0 -15
- package/src/toolbar/toolbar.css +3 -2
- package/src/tools/Pen.test.ts +150 -0
- package/src/tools/SelectionTool/SelectionTool.ts +9 -0
- package/tsconfig.json +3 -1
@@ -173,7 +173,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
173
173
|
}
|
174
174
|
|
175
175
|
public build(): Stroke {
|
176
|
-
if (this.lastPoint) {
|
176
|
+
if (this.lastPoint && (this.lowerSegments.length === 0 || this.approxCurrentCurveLength() > this.curveStartWidth * 2)) {
|
177
177
|
this.finalizeCurrentCurve();
|
178
178
|
}
|
179
179
|
return this.previewStroke()!;
|
@@ -189,6 +189,19 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
189
189
|
return Viewport.roundPoint(point, minFit);
|
190
190
|
}
|
191
191
|
|
192
|
+
// Returns the distance between the start, control, and end points of the curve.
|
193
|
+
private approxCurrentCurveLength() {
|
194
|
+
if (!this.currentCurve) {
|
195
|
+
return 0;
|
196
|
+
}
|
197
|
+
const startPt = Vec2.ofXY(this.currentCurve.points[0]);
|
198
|
+
const controlPt = Vec2.ofXY(this.currentCurve.points[1]);
|
199
|
+
const endPt = Vec2.ofXY(this.currentCurve.points[2]);
|
200
|
+
const toControlDist = startPt.minus(controlPt).length();
|
201
|
+
const toEndDist = endPt.minus(controlPt).length();
|
202
|
+
return toControlDist + toEndDist;
|
203
|
+
}
|
204
|
+
|
192
205
|
private finalizeCurrentCurve() {
|
193
206
|
// Case where no points have been added
|
194
207
|
if (!this.currentCurve) {
|
@@ -285,10 +298,8 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
285
298
|
startVec = startVec.times(this.curveStartWidth / 2);
|
286
299
|
endVec = endVec.times(this.curveEndWidth / 2);
|
287
300
|
|
288
|
-
if (
|
289
|
-
|
290
|
-
// fix.
|
291
|
-
console.error('startVec is NaN', startVec, endVec, this.currentCurve);
|
301
|
+
if (!isFinite(startVec.magnitude())) {
|
302
|
+
console.error('Warning: startVec is NaN or ∞', startVec, endVec, this.currentCurve);
|
292
303
|
startVec = endVec;
|
293
304
|
}
|
294
305
|
|
@@ -307,39 +318,22 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
307
318
|
}
|
308
319
|
|
309
320
|
const halfVecT = projectionT;
|
310
|
-
|
321
|
+
const halfVec = Vec2.ofXY(this.currentCurve.normal(halfVecT))
|
311
322
|
.normalized().times(
|
312
323
|
this.curveStartWidth / 2 * halfVecT
|
313
324
|
+ this.curveEndWidth / 2 * (1 - halfVecT)
|
314
325
|
);
|
315
326
|
|
316
|
-
// Computes a boundary curve. [direction] should be either +1 or -1 (determines the side
|
317
|
-
// of the center curve to place the boundary).
|
318
|
-
const computeBoundaryCurve = (direction: number, halfVec: Vec2) => {
|
319
|
-
return new Bezier(
|
320
|
-
startPt.plus(startVec.times(direction)),
|
321
|
-
controlPoint.plus(halfVec.times(direction)),
|
322
|
-
endPt.plus(endVec.times(direction)),
|
323
|
-
);
|
324
|
-
};
|
325
|
-
|
326
|
-
const boundariesIntersect = () => {
|
327
|
-
const upperBoundary = computeBoundaryCurve(1, halfVec);
|
328
|
-
const lowerBoundary = computeBoundaryCurve(-1, halfVec);
|
329
|
-
return upperBoundary.intersects(lowerBoundary).length > 0;
|
330
|
-
};
|
331
|
-
|
332
|
-
// If the boundaries have intersections, increasing the half vector's length could fix this.
|
333
|
-
if (boundariesIntersect()) {
|
334
|
-
halfVec = halfVec.times(1.1);
|
335
|
-
}
|
336
|
-
|
337
327
|
// Each starts at startPt ± startVec
|
328
|
+
const lowerCurveControlPoint = this.roundPoint(controlPoint.plus(halfVec));
|
329
|
+
const lowerCurveEndPoint = this.roundPoint(endPt.plus(endVec));
|
330
|
+
const upperCurveControlPoint = this.roundPoint(controlPoint.minus(halfVec));
|
331
|
+
const upperCurveStartPoint = this.roundPoint(endPt.minus(endVec));
|
338
332
|
|
339
333
|
const lowerCurve: QuadraticBezierPathCommand = {
|
340
334
|
kind: PathCommandType.QuadraticBezierTo,
|
341
|
-
controlPoint:
|
342
|
-
endPoint:
|
335
|
+
controlPoint: lowerCurveControlPoint,
|
336
|
+
endPoint: lowerCurveEndPoint,
|
343
337
|
};
|
344
338
|
|
345
339
|
// From the end of the upperCurve to the start of the lowerCurve:
|
@@ -351,12 +345,12 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
351
345
|
// From the end of lowerCurve to the start of upperCurve:
|
352
346
|
const lowerToUpperConnector: LinePathCommand = {
|
353
347
|
kind: PathCommandType.LineTo,
|
354
|
-
point:
|
348
|
+
point: upperCurveStartPoint,
|
355
349
|
};
|
356
350
|
|
357
351
|
const upperCurve: QuadraticBezierPathCommand = {
|
358
352
|
kind: PathCommandType.QuadraticBezierTo,
|
359
|
-
controlPoint:
|
353
|
+
controlPoint: upperCurveControlPoint,
|
360
354
|
endPoint: this.roundPoint(startPt.minus(startVec)),
|
361
355
|
};
|
362
356
|
|
@@ -374,7 +368,6 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
374
368
|
const fuzzEq = 1e-10;
|
375
369
|
const deltaTime = newPoint.time - this.lastPoint.time;
|
376
370
|
if (newPoint.pos.eq(this.lastPoint.pos, fuzzEq) || deltaTime === 0) {
|
377
|
-
console.warn('Discarding identical point');
|
378
371
|
return;
|
379
372
|
} else if (isNaN(newPoint.pos.magnitude())) {
|
380
373
|
console.warn('Discarding NaN point.', newPoint);
|
@@ -433,23 +426,23 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
433
426
|
let exitingVec = this.computeExitingVec();
|
434
427
|
|
435
428
|
// Find the intersection between the entering vector and the exiting vector
|
436
|
-
const maxRelativeLength =
|
429
|
+
const maxRelativeLength = 3;
|
437
430
|
const segmentStart = this.buffer[0];
|
438
431
|
const segmentEnd = newPoint.pos;
|
439
432
|
const startEndDist = segmentEnd.minus(segmentStart).magnitude();
|
440
433
|
const maxControlPointDist = maxRelativeLength * startEndDist;
|
441
434
|
|
442
435
|
// Exit in cases where we would divide by zero
|
443
|
-
if (maxControlPointDist === 0 || exitingVec.magnitude() === 0 ||
|
436
|
+
if (maxControlPointDist === 0 || exitingVec.magnitude() === 0 || !isFinite(exitingVec.magnitude())) {
|
444
437
|
return;
|
445
438
|
}
|
446
439
|
|
447
|
-
console.assert(
|
440
|
+
console.assert(isFinite(enteringVec.magnitude()), 'Pre-normalized enteringVec has NaN or ∞ magnitude!');
|
448
441
|
|
449
442
|
enteringVec = enteringVec.normalized();
|
450
443
|
exitingVec = exitingVec.normalized();
|
451
444
|
|
452
|
-
console.assert(
|
445
|
+
console.assert(isFinite(enteringVec.magnitude()), 'Normalized enteringVec has NaN or ∞ magnitude!');
|
453
446
|
|
454
447
|
const lineFromStart = new LineSegment2(
|
455
448
|
segmentStart,
|
@@ -462,18 +455,20 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
462
455
|
const intersection = lineFromEnd.intersection(lineFromStart);
|
463
456
|
|
464
457
|
// Position the control point at this intersection
|
465
|
-
let controlPoint: Point2;
|
458
|
+
let controlPoint: Point2|null = null;
|
466
459
|
if (intersection) {
|
467
460
|
controlPoint = intersection.point;
|
468
|
-
}
|
461
|
+
}
|
462
|
+
|
463
|
+
// No intersection or the intersection is one of the end points?
|
464
|
+
if (!controlPoint || segmentStart.eq(controlPoint) || segmentEnd.eq(controlPoint)) {
|
469
465
|
// Position the control point closer to the first -- the connecting
|
470
466
|
// segment will be roughly a line.
|
471
467
|
controlPoint = segmentStart.plus(enteringVec.times(startEndDist / 4));
|
472
468
|
}
|
473
469
|
|
474
|
-
|
475
|
-
|
476
|
-
}
|
470
|
+
console.assert(!segmentStart.eq(controlPoint, 1e-11), 'Start and control points are equal!');
|
471
|
+
console.assert(!controlPoint.eq(segmentEnd, 1e-11), 'Control and end points are equal!');
|
477
472
|
|
478
473
|
const prevCurve = this.currentCurve;
|
479
474
|
this.currentCurve = new Bezier(segmentStart.xy, controlPoint.xy, segmentEnd.xy);
|
@@ -502,8 +497,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
502
497
|
return true;
|
503
498
|
};
|
504
499
|
|
505
|
-
|
506
|
-
if (this.buffer.length > 3 && approxCurveLen > this.curveEndWidth / 3) {
|
500
|
+
if (this.buffer.length > 3 && this.approxCurrentCurveLength() > this.curveStartWidth) {
|
507
501
|
if (!curveMatchesPoints(this.currentCurve)) {
|
508
502
|
// Use a curve that better fits the points
|
509
503
|
this.currentCurve = prevCurve;
|
@@ -2,5 +2,5 @@
|
|
2
2
|
// Compile-time assertion that a branch of code is unreachable.
|
3
3
|
// See https://stackoverflow.com/a/39419171/17055750
|
4
4
|
export const assertUnreachable = (key: never): never => {
|
5
|
-
|
6
|
-
};
|
5
|
+
throw new Error(`Should be unreachable. Key: ${key}.`);
|
6
|
+
};
|
@@ -1,9 +1,7 @@
|
|
1
1
|
import LineSegment2 from './LineSegment2';
|
2
|
-
import { loadExpectExtensions } from '../testing/loadExpectExtensions';
|
3
2
|
import { Vec2 } from './Vec2';
|
4
3
|
import Mat33 from './Mat33';
|
5
4
|
|
6
|
-
loadExpectExtensions();
|
7
5
|
|
8
6
|
describe('Line2', () => {
|
9
7
|
it('x and y axes should intersect at (0, 0)', () => {
|
@@ -14,13 +12,13 @@ describe('Line2', () => {
|
|
14
12
|
});
|
15
13
|
|
16
14
|
it('y = -2x + 2 and y = 2x - 2 should intersect at (1,0)', () => {
|
17
|
-
// y = -
|
15
|
+
// y = -4x + 2
|
18
16
|
const line1 = new LineSegment2(Vec2.of(0, 2), Vec2.of(1, -2));
|
19
|
-
// y =
|
17
|
+
// y = 4x - 2
|
20
18
|
const line2 = new LineSegment2(Vec2.of(0, -2), Vec2.of(1, 2));
|
21
19
|
|
22
|
-
expect(line1.intersection(line2)?.point).objEq(Vec2.of(
|
23
|
-
expect(line2.intersection(line1)?.point).objEq(Vec2.of(
|
20
|
+
expect(line1.intersection(line2)?.point).objEq(Vec2.of(0.5, 0));
|
21
|
+
expect(line2.intersection(line1)?.point).objEq(Vec2.of(0.5, 0));
|
24
22
|
});
|
25
23
|
|
26
24
|
it('line from (10, 10) to (-100, 10) should intersect with the y-axis at t = 10', () => {
|
@@ -81,14 +79,14 @@ describe('Line2', () => {
|
|
81
79
|
expect(line.closestPointTo(Vec2.zero)).objEq(Vec2.of(1, 0));
|
82
80
|
});
|
83
81
|
|
84
|
-
it('Closest point from (-1
|
82
|
+
it('Closest point from (-1,-2) to segment((1,1) -> (2,4)) should be (1,1)', () => {
|
85
83
|
const line = new LineSegment2(Vec2.of(1, 1), Vec2.of(2, 4));
|
86
|
-
expect(line.closestPointTo(Vec2.of(-1, 2))).objEq(Vec2.of(1, 1));
|
84
|
+
expect(line.closestPointTo(Vec2.of(-1, -2))).objEq(Vec2.of(1, 1));
|
87
85
|
});
|
88
86
|
|
89
|
-
it('Closest point from (5,
|
87
|
+
it('Closest point from (5,8) to segment((1,1) -> (2,4)) should be (2,4)', () => {
|
90
88
|
const line = new LineSegment2(Vec2.of(1, 1), Vec2.of(2, 4));
|
91
|
-
expect(line.closestPointTo(Vec2.of(5,
|
89
|
+
expect(line.closestPointTo(Vec2.of(5, 8))).objEq(Vec2.of(2, 4));
|
92
90
|
});
|
93
91
|
|
94
92
|
it('Should translate when translated by a translation matrix', () => {
|
package/src/math/Mat33.test.ts
CHANGED
package/src/math/Rect2.test.ts
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
|
2
2
|
import Rect2 from './Rect2';
|
3
3
|
import { Vec2 } from './Vec2';
|
4
|
-
import loadExpectExtensions from '../testing/loadExpectExtensions';
|
5
4
|
import Mat33 from './Mat33';
|
6
5
|
|
7
|
-
loadExpectExtensions();
|
8
|
-
|
9
6
|
describe('Rect2', () => {
|
10
7
|
it('width, height should always be positive', () => {
|
11
8
|
expect(new Rect2(-1, -2, -3, 4)).objEq(new Rect2(-4, -2, 3, 4));
|
package/src/math/Vec2.test.ts
CHANGED
package/src/math/Vec3.test.ts
CHANGED
package/src/math/Vec3.ts
CHANGED
@@ -196,7 +196,7 @@ export default class Vec3 {
|
|
196
196
|
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 2.99); // → false
|
197
197
|
* ```
|
198
198
|
*/
|
199
|
-
public eq(other: Vec3, fuzz: number): boolean {
|
199
|
+
public eq(other: Vec3, fuzz: number = 1e-10): boolean {
|
200
200
|
for (let i = 0; i < 3; i++) {
|
201
201
|
if (Math.abs(other.at(i) - this.at(i)) > fuzz) {
|
202
202
|
return false;
|
@@ -1,4 +1,11 @@
|
|
1
1
|
import { RenderingMode } from '../rendering/Display';
|
2
2
|
import Editor from '../Editor';
|
3
3
|
|
4
|
-
|
4
|
+
/** Creates an editor. Should only be used in test files. */
|
5
|
+
export default () => {
|
6
|
+
if (jest === undefined) {
|
7
|
+
throw new Error('Files in the testing/ folder should only be used in tests!');
|
8
|
+
}
|
9
|
+
|
10
|
+
return new Editor(document.body, { renderingMode: RenderingMode.DummyRenderer });
|
11
|
+
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
// Type declarations for custom matchers
|
4
|
+
interface CustomMatchers<R = unknown> {
|
5
|
+
objEq(expected: {
|
6
|
+
eq: (other: any, ...args: any)=> boolean;
|
7
|
+
}, ...opts: any): R;
|
8
|
+
}
|
9
|
+
|
10
|
+
declare namespace jest {
|
11
|
+
interface Expect extends CustomMatchers {}
|
12
|
+
interface Matchers<R> extends CustomMatchers<R> {}
|
13
|
+
interface AsyncAsymmetricMatchers extends CustomMatchers {}
|
14
|
+
}
|
15
|
+
|
16
|
+
declare interface JestMatchers<T> extends CustomMatchers<T> {
|
17
|
+
}
|
@@ -22,19 +22,4 @@ export const loadExpectExtensions = () => {
|
|
22
22
|
});
|
23
23
|
};
|
24
24
|
|
25
|
-
// Type declarations for custom matchers
|
26
|
-
export interface CustomMatchers<R = unknown> {
|
27
|
-
objEq(expected: {
|
28
|
-
eq: (other: any, ...args: any)=> boolean;
|
29
|
-
}, ...opts: any): R;
|
30
|
-
}
|
31
|
-
|
32
|
-
declare global {
|
33
|
-
export namespace jest {
|
34
|
-
interface Expect extends CustomMatchers {}
|
35
|
-
interface Matchers<R> extends CustomMatchers<R> {}
|
36
|
-
interface AsyncAsymmetricMatchers extends CustomMatchers {}
|
37
|
-
}
|
38
|
-
}
|
39
|
-
|
40
25
|
export default loadExpectExtensions;
|
package/src/toolbar/toolbar.css
CHANGED
@@ -34,7 +34,8 @@
|
|
34
34
|
}
|
35
35
|
|
36
36
|
.toolbar-button.disabled {
|
37
|
-
filter: opacity(0.
|
37
|
+
filter: opacity(0.5) sepia(0.2);
|
38
|
+
cursor: unset;
|
38
39
|
}
|
39
40
|
|
40
41
|
.toolbar-button, .toolbar-root button {
|
@@ -75,7 +76,7 @@
|
|
75
76
|
width: 6em;
|
76
77
|
}
|
77
78
|
|
78
|
-
.toolbar-button:hover, .toolbar-root button:not(:disabled):hover {
|
79
|
+
.toolbar-button:not(.disabled):hover, .toolbar-root button:not(:disabled):hover {
|
79
80
|
box-shadow: 0px 2px 4px var(--primary-shadow-color);
|
80
81
|
}
|
81
82
|
|
@@ -0,0 +1,150 @@
|
|
1
|
+
|
2
|
+
import { Rect2 } from '../lib';
|
3
|
+
import { Vec2 } from '../math/Vec2';
|
4
|
+
import createEditor from '../testing/createEditor';
|
5
|
+
import { InputEvtType } from '../types';
|
6
|
+
|
7
|
+
describe('Pen', () => {
|
8
|
+
it('should draw horizontal lines', () => {
|
9
|
+
const editor = createEditor();
|
10
|
+
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
|
11
|
+
for (let i = 0; i < 10; i++) {
|
12
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(i, 0));
|
13
|
+
jest.advanceTimersByTime(200);
|
14
|
+
}
|
15
|
+
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(200, 0));
|
16
|
+
|
17
|
+
const elems = editor.image.getElementsIntersectingRegion(new Rect2(0, 10, 10, -10));
|
18
|
+
expect(elems).toHaveLength(1);
|
19
|
+
|
20
|
+
// Account for stroke width
|
21
|
+
const tolerableError = 8;
|
22
|
+
expect(elems[0].getBBox().topLeft).objEq(Vec2.of(0, 0), tolerableError);
|
23
|
+
expect(elems[0].getBBox().bottomRight).objEq(Vec2.of(200, 0), tolerableError);
|
24
|
+
});
|
25
|
+
|
26
|
+
it('should draw vertical line', () => {
|
27
|
+
const editor = createEditor();
|
28
|
+
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
|
29
|
+
for (let i = 0; i < 10; i++) {
|
30
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(0, i * 20));
|
31
|
+
jest.advanceTimersByTime(200);
|
32
|
+
}
|
33
|
+
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(0, 150));
|
34
|
+
|
35
|
+
const elems = editor.image.getElementsIntersectingRegion(Rect2.unitSquare);
|
36
|
+
expect(elems).toHaveLength(1);
|
37
|
+
|
38
|
+
expect(elems[0].getBBox().topLeft).objEq(Vec2.of(0, 0), 8); // ± 8
|
39
|
+
expect(elems[0].getBBox().bottomRight).objEq(Vec2.of(0, 175), 25); // ± 25
|
40
|
+
});
|
41
|
+
|
42
|
+
it('should draw vertical line with slight bend', () => {
|
43
|
+
const editor = createEditor();
|
44
|
+
|
45
|
+
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(417, 24));
|
46
|
+
jest.advanceTimersByTime(245);
|
47
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 197));
|
48
|
+
jest.advanceTimersByTime(20);
|
49
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 199));
|
50
|
+
jest.advanceTimersByTime(12);
|
51
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 201));
|
52
|
+
jest.advanceTimersByTime(40);
|
53
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 203));
|
54
|
+
jest.advanceTimersByTime(14);
|
55
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 206));
|
56
|
+
jest.advanceTimersByTime(35);
|
57
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 208));
|
58
|
+
jest.advanceTimersByTime(16);
|
59
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 211));
|
60
|
+
jest.advanceTimersByTime(51);
|
61
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 215));
|
62
|
+
jest.advanceTimersByTime(32);
|
63
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 218));
|
64
|
+
jest.advanceTimersByTime(30);
|
65
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 220));
|
66
|
+
jest.advanceTimersByTime(24);
|
67
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 222));
|
68
|
+
jest.advanceTimersByTime(14);
|
69
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 224));
|
70
|
+
jest.advanceTimersByTime(32);
|
71
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 227));
|
72
|
+
jest.advanceTimersByTime(17);
|
73
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 229));
|
74
|
+
jest.advanceTimersByTime(53);
|
75
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 234));
|
76
|
+
jest.advanceTimersByTime(34);
|
77
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 236));
|
78
|
+
jest.advanceTimersByTime(17);
|
79
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 238));
|
80
|
+
jest.advanceTimersByTime(39);
|
81
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 240));
|
82
|
+
jest.advanceTimersByTime(10);
|
83
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 243));
|
84
|
+
jest.advanceTimersByTime(34);
|
85
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 250));
|
86
|
+
jest.advanceTimersByTime(57);
|
87
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(423, 252));
|
88
|
+
jest.advanceTimersByTime(8);
|
89
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(422, 256));
|
90
|
+
jest.advanceTimersByTime(28);
|
91
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(422, 258));
|
92
|
+
jest.advanceTimersByTime(21);
|
93
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(421, 262));
|
94
|
+
jest.advanceTimersByTime(34);
|
95
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 264));
|
96
|
+
jest.advanceTimersByTime(5);
|
97
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 266));
|
98
|
+
jest.advanceTimersByTime(22);
|
99
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 268));
|
100
|
+
jest.advanceTimersByTime(22);
|
101
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 271));
|
102
|
+
jest.advanceTimersByTime(18);
|
103
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 274));
|
104
|
+
jest.advanceTimersByTime(33);
|
105
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 277));
|
106
|
+
jest.advanceTimersByTime(16);
|
107
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 279));
|
108
|
+
jest.advanceTimersByTime(36);
|
109
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 282));
|
110
|
+
jest.advanceTimersByTime(15);
|
111
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 284));
|
112
|
+
jest.advanceTimersByTime(48);
|
113
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 289));
|
114
|
+
jest.advanceTimersByTime(16);
|
115
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 291));
|
116
|
+
jest.advanceTimersByTime(31);
|
117
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 295));
|
118
|
+
jest.advanceTimersByTime(23);
|
119
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 301));
|
120
|
+
jest.advanceTimersByTime(31);
|
121
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 306));
|
122
|
+
jest.advanceTimersByTime(18);
|
123
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 308));
|
124
|
+
jest.advanceTimersByTime(20);
|
125
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 310));
|
126
|
+
jest.advanceTimersByTime(13);
|
127
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 313));
|
128
|
+
jest.advanceTimersByTime(17);
|
129
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 317));
|
130
|
+
jest.advanceTimersByTime(33);
|
131
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 321));
|
132
|
+
jest.advanceTimersByTime(15);
|
133
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 324));
|
134
|
+
jest.advanceTimersByTime(23);
|
135
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 326));
|
136
|
+
jest.advanceTimersByTime(14);
|
137
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(419, 329));
|
138
|
+
jest.advanceTimersByTime(36);
|
139
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 333));
|
140
|
+
jest.advanceTimersByTime(8);
|
141
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(420, 340));
|
142
|
+
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(420, 340));
|
143
|
+
|
144
|
+
const elems = editor.image.getElementsIntersectingRegion(new Rect2(0, 0, 1000, 1000));
|
145
|
+
expect(elems).toHaveLength(1);
|
146
|
+
|
147
|
+
expect(elems[0].getBBox().topLeft).objEq(Vec2.of(420, 24), 8); // ± 8
|
148
|
+
expect(elems[0].getBBox().bottomRight).objEq(Vec2.of(420, 340), 25); // ± 25
|
149
|
+
});
|
150
|
+
});
|
@@ -12,6 +12,7 @@ import Viewport from '../../Viewport';
|
|
12
12
|
import BaseTool from '../BaseTool';
|
13
13
|
import SVGRenderer from '../../rendering/renderers/SVGRenderer';
|
14
14
|
import Selection from './Selection';
|
15
|
+
import TextComponent from '../../components/Text';
|
15
16
|
|
16
17
|
export const cssPrefix = 'selection-tool-';
|
17
18
|
|
@@ -272,11 +273,19 @@ export default class SelectionTool extends BaseTool {
|
|
272
273
|
const sanitize = true;
|
273
274
|
const renderer = new SVGRenderer(exportElem, exportViewport, sanitize);
|
274
275
|
|
276
|
+
const text: string[] = [];
|
275
277
|
for (const elem of selectedElems) {
|
276
278
|
elem.render(renderer);
|
279
|
+
|
280
|
+
if (elem instanceof TextComponent) {
|
281
|
+
text.push(elem.getText());
|
282
|
+
}
|
277
283
|
}
|
278
284
|
|
279
285
|
event.setData('image/svg+xml', exportElem.outerHTML);
|
286
|
+
if (text.length > 0) {
|
287
|
+
event.setData('text/plain', text.join('\n'));
|
288
|
+
}
|
280
289
|
return true;
|
281
290
|
}
|
282
291
|
|
package/tsconfig.json
CHANGED
@@ -23,10 +23,12 @@
|
|
23
23
|
"**/node_modules",
|
24
24
|
|
25
25
|
// Files that don't need transpilation
|
26
|
-
"**/*.test.ts",
|
26
|
+
// "**/*.test.ts", <- vscode requires .test.ts files to be transpiled for other settings to apply.
|
27
27
|
"__mocks__/*",
|
28
28
|
|
29
29
|
// Output files
|
30
30
|
"./dist/**"
|
31
31
|
],
|
32
|
+
|
33
|
+
"files": [ "./src/testing/global.d.ts" ]
|
32
34
|
}
|