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
package/dist/src/Editor.js
CHANGED
@@ -607,7 +607,9 @@ export class Editor {
|
|
607
607
|
}
|
608
608
|
// Dispatch a pen event to the currently selected tool.
|
609
609
|
// Intended primarially for unit tests.
|
610
|
-
sendPenEvent(eventType, point,
|
610
|
+
sendPenEvent(eventType, point,
|
611
|
+
// @deprecated
|
612
|
+
allPointers) {
|
611
613
|
const mainPointer = Pointer.ofCanvasPoint(point, eventType !== InputEvtType.PointerUpEvt, this.viewport);
|
612
614
|
this.toolController.dispatchInputEvent({
|
613
615
|
kind: eventType,
|
@@ -48,6 +48,7 @@ export default class AbstractComponent {
|
|
48
48
|
transformBy(affineTransfm) {
|
49
49
|
return new AbstractComponent.TransformElementCommand(affineTransfm, this);
|
50
50
|
}
|
51
|
+
// Returns a copy of this component.
|
51
52
|
clone() {
|
52
53
|
const clone = this.createClone();
|
53
54
|
for (const attachmentKey in this.loadSaveData) {
|
@@ -6,7 +6,8 @@ export default class Stroke extends AbstractComponent {
|
|
6
6
|
constructor(parts) {
|
7
7
|
var _a;
|
8
8
|
super('stroke');
|
9
|
-
this.parts =
|
9
|
+
this.parts = [];
|
10
|
+
for (const section of parts) {
|
10
11
|
const path = Path.fromRenderable(section);
|
11
12
|
const pathBBox = this.bboxForPart(path.bbox, section.style);
|
12
13
|
if (!this.contentBBox) {
|
@@ -15,14 +16,14 @@ export default class Stroke extends AbstractComponent {
|
|
15
16
|
else {
|
16
17
|
this.contentBBox = this.contentBBox.union(pathBBox);
|
17
18
|
}
|
18
|
-
|
19
|
+
this.parts.push({
|
19
20
|
path,
|
20
21
|
// To implement RenderablePathSpec
|
21
22
|
startPoint: path.startPoint,
|
22
23
|
style: section.style,
|
23
24
|
commands: path.parts,
|
24
|
-
};
|
25
|
-
}
|
25
|
+
});
|
26
|
+
}
|
26
27
|
(_a = this.contentBBox) !== null && _a !== void 0 ? _a : (this.contentBBox = Rect2.empty);
|
27
28
|
}
|
28
29
|
intersects(line) {
|
@@ -80,11 +81,16 @@ export default class Stroke extends AbstractComponent {
|
|
80
81
|
});
|
81
82
|
}
|
82
83
|
getPath() {
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
84
|
+
let result = null;
|
85
|
+
for (const part of this.parts) {
|
86
|
+
if (result) {
|
87
|
+
result = result.union(part.path);
|
88
|
+
}
|
89
|
+
else {
|
90
|
+
result !== null && result !== void 0 ? result : (result = part.path);
|
91
|
+
}
|
92
|
+
}
|
93
|
+
return result !== null && result !== void 0 ? result : Path.empty;
|
88
94
|
}
|
89
95
|
description(localization) {
|
90
96
|
return localization.stroke;
|
@@ -29,7 +29,7 @@ export default class Text extends AbstractComponent {
|
|
29
29
|
intersects(lineSegment: LineSegment2): boolean;
|
30
30
|
protected applyTransformation(affineTransfm: Mat33): void;
|
31
31
|
protected createClone(): AbstractComponent;
|
32
|
-
|
32
|
+
getText(): string;
|
33
33
|
description(localizationTable: ImageComponentLocalization): string;
|
34
34
|
protected serializeToJSON(): Record<string, any>;
|
35
35
|
static deserializeFromString(json: any, getTextDimens?: GetTextDimensCallback): Text;
|
@@ -113,7 +113,7 @@ export default class Text extends AbstractComponent {
|
|
113
113
|
result.push(textObject.getText());
|
114
114
|
}
|
115
115
|
}
|
116
|
-
return result.join('
|
116
|
+
return result.join('\n');
|
117
117
|
}
|
118
118
|
description(localizationTable) {
|
119
119
|
return localizationTable.text(this.getText());
|
@@ -29,6 +29,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
29
29
|
preview(renderer: AbstractRenderer): void;
|
30
30
|
build(): Stroke;
|
31
31
|
private roundPoint;
|
32
|
+
private approxCurrentCurveLength;
|
32
33
|
private finalizeCurrentCurve;
|
33
34
|
private currentSegmentToPath;
|
34
35
|
private computeExitingVec;
|
@@ -117,7 +117,7 @@ export default class FreehandLineBuilder {
|
|
117
117
|
}
|
118
118
|
}
|
119
119
|
build() {
|
120
|
-
if (this.lastPoint) {
|
120
|
+
if (this.lastPoint && (this.lowerSegments.length === 0 || this.approxCurrentCurveLength() > this.curveStartWidth * 2)) {
|
121
121
|
this.finalizeCurrentCurve();
|
122
122
|
}
|
123
123
|
return this.previewStroke();
|
@@ -129,6 +129,18 @@ export default class FreehandLineBuilder {
|
|
129
129
|
}
|
130
130
|
return Viewport.roundPoint(point, minFit);
|
131
131
|
}
|
132
|
+
// Returns the distance between the start, control, and end points of the curve.
|
133
|
+
approxCurrentCurveLength() {
|
134
|
+
if (!this.currentCurve) {
|
135
|
+
return 0;
|
136
|
+
}
|
137
|
+
const startPt = Vec2.ofXY(this.currentCurve.points[0]);
|
138
|
+
const controlPt = Vec2.ofXY(this.currentCurve.points[1]);
|
139
|
+
const endPt = Vec2.ofXY(this.currentCurve.points[2]);
|
140
|
+
const toControlDist = startPt.minus(controlPt).length();
|
141
|
+
const toEndDist = endPt.minus(controlPt).length();
|
142
|
+
return toControlDist + toEndDist;
|
143
|
+
}
|
132
144
|
finalizeCurrentCurve() {
|
133
145
|
// Case where no points have been added
|
134
146
|
if (!this.currentCurve) {
|
@@ -204,10 +216,8 @@ export default class FreehandLineBuilder {
|
|
204
216
|
let endVec = Vec2.ofXY(this.currentCurve.normal(1)).normalized();
|
205
217
|
startVec = startVec.times(this.curveStartWidth / 2);
|
206
218
|
endVec = endVec.times(this.curveEndWidth / 2);
|
207
|
-
if (
|
208
|
-
|
209
|
-
// fix.
|
210
|
-
console.error('startVec is NaN', startVec, endVec, this.currentCurve);
|
219
|
+
if (!isFinite(startVec.magnitude())) {
|
220
|
+
console.error('Warning: startVec is NaN or ∞', startVec, endVec, this.currentCurve);
|
211
221
|
startVec = endVec;
|
212
222
|
}
|
213
223
|
const startPt = Vec2.ofXY(this.currentCurve.get(0));
|
@@ -224,28 +234,18 @@ export default class FreehandLineBuilder {
|
|
224
234
|
}
|
225
235
|
}
|
226
236
|
const halfVecT = projectionT;
|
227
|
-
|
237
|
+
const halfVec = Vec2.ofXY(this.currentCurve.normal(halfVecT))
|
228
238
|
.normalized().times(this.curveStartWidth / 2 * halfVecT
|
229
239
|
+ this.curveEndWidth / 2 * (1 - halfVecT));
|
230
|
-
// Computes a boundary curve. [direction] should be either +1 or -1 (determines the side
|
231
|
-
// of the center curve to place the boundary).
|
232
|
-
const computeBoundaryCurve = (direction, halfVec) => {
|
233
|
-
return new Bezier(startPt.plus(startVec.times(direction)), controlPoint.plus(halfVec.times(direction)), endPt.plus(endVec.times(direction)));
|
234
|
-
};
|
235
|
-
const boundariesIntersect = () => {
|
236
|
-
const upperBoundary = computeBoundaryCurve(1, halfVec);
|
237
|
-
const lowerBoundary = computeBoundaryCurve(-1, halfVec);
|
238
|
-
return upperBoundary.intersects(lowerBoundary).length > 0;
|
239
|
-
};
|
240
|
-
// If the boundaries have intersections, increasing the half vector's length could fix this.
|
241
|
-
if (boundariesIntersect()) {
|
242
|
-
halfVec = halfVec.times(1.1);
|
243
|
-
}
|
244
240
|
// Each starts at startPt ± startVec
|
241
|
+
const lowerCurveControlPoint = this.roundPoint(controlPoint.plus(halfVec));
|
242
|
+
const lowerCurveEndPoint = this.roundPoint(endPt.plus(endVec));
|
243
|
+
const upperCurveControlPoint = this.roundPoint(controlPoint.minus(halfVec));
|
244
|
+
const upperCurveStartPoint = this.roundPoint(endPt.minus(endVec));
|
245
245
|
const lowerCurve = {
|
246
246
|
kind: PathCommandType.QuadraticBezierTo,
|
247
|
-
controlPoint:
|
248
|
-
endPoint:
|
247
|
+
controlPoint: lowerCurveControlPoint,
|
248
|
+
endPoint: lowerCurveEndPoint,
|
249
249
|
};
|
250
250
|
// From the end of the upperCurve to the start of the lowerCurve:
|
251
251
|
const upperToLowerConnector = {
|
@@ -255,11 +255,11 @@ export default class FreehandLineBuilder {
|
|
255
255
|
// From the end of lowerCurve to the start of upperCurve:
|
256
256
|
const lowerToUpperConnector = {
|
257
257
|
kind: PathCommandType.LineTo,
|
258
|
-
point:
|
258
|
+
point: upperCurveStartPoint,
|
259
259
|
};
|
260
260
|
const upperCurve = {
|
261
261
|
kind: PathCommandType.QuadraticBezierTo,
|
262
|
-
controlPoint:
|
262
|
+
controlPoint: upperCurveControlPoint,
|
263
263
|
endPoint: this.roundPoint(startPt.minus(startVec)),
|
264
264
|
};
|
265
265
|
return { upperCurve, upperToLowerConnector, lowerToUpperConnector, lowerCurve };
|
@@ -275,7 +275,6 @@ export default class FreehandLineBuilder {
|
|
275
275
|
const fuzzEq = 1e-10;
|
276
276
|
const deltaTime = newPoint.time - this.lastPoint.time;
|
277
277
|
if (newPoint.pos.eq(this.lastPoint.pos, fuzzEq) || deltaTime === 0) {
|
278
|
-
console.warn('Discarding identical point');
|
279
278
|
return;
|
280
279
|
}
|
281
280
|
else if (isNaN(newPoint.pos.magnitude())) {
|
@@ -321,35 +320,35 @@ export default class FreehandLineBuilder {
|
|
321
320
|
}
|
322
321
|
let exitingVec = this.computeExitingVec();
|
323
322
|
// Find the intersection between the entering vector and the exiting vector
|
324
|
-
const maxRelativeLength =
|
323
|
+
const maxRelativeLength = 3;
|
325
324
|
const segmentStart = this.buffer[0];
|
326
325
|
const segmentEnd = newPoint.pos;
|
327
326
|
const startEndDist = segmentEnd.minus(segmentStart).magnitude();
|
328
327
|
const maxControlPointDist = maxRelativeLength * startEndDist;
|
329
328
|
// Exit in cases where we would divide by zero
|
330
|
-
if (maxControlPointDist === 0 || exitingVec.magnitude() === 0 ||
|
329
|
+
if (maxControlPointDist === 0 || exitingVec.magnitude() === 0 || !isFinite(exitingVec.magnitude())) {
|
331
330
|
return;
|
332
331
|
}
|
333
|
-
console.assert(
|
332
|
+
console.assert(isFinite(enteringVec.magnitude()), 'Pre-normalized enteringVec has NaN or ∞ magnitude!');
|
334
333
|
enteringVec = enteringVec.normalized();
|
335
334
|
exitingVec = exitingVec.normalized();
|
336
|
-
console.assert(
|
335
|
+
console.assert(isFinite(enteringVec.magnitude()), 'Normalized enteringVec has NaN or ∞ magnitude!');
|
337
336
|
const lineFromStart = new LineSegment2(segmentStart, segmentStart.plus(enteringVec.times(maxControlPointDist)));
|
338
337
|
const lineFromEnd = new LineSegment2(segmentEnd.minus(exitingVec.times(maxControlPointDist)), segmentEnd);
|
339
338
|
const intersection = lineFromEnd.intersection(lineFromStart);
|
340
339
|
// Position the control point at this intersection
|
341
|
-
let controlPoint;
|
340
|
+
let controlPoint = null;
|
342
341
|
if (intersection) {
|
343
342
|
controlPoint = intersection.point;
|
344
343
|
}
|
345
|
-
|
344
|
+
// No intersection or the intersection is one of the end points?
|
345
|
+
if (!controlPoint || segmentStart.eq(controlPoint) || segmentEnd.eq(controlPoint)) {
|
346
346
|
// Position the control point closer to the first -- the connecting
|
347
347
|
// segment will be roughly a line.
|
348
348
|
controlPoint = segmentStart.plus(enteringVec.times(startEndDist / 4));
|
349
349
|
}
|
350
|
-
|
351
|
-
|
352
|
-
}
|
350
|
+
console.assert(!segmentStart.eq(controlPoint, 1e-11), 'Start and control points are equal!');
|
351
|
+
console.assert(!controlPoint.eq(segmentEnd, 1e-11), 'Control and end points are equal!');
|
353
352
|
const prevCurve = this.currentCurve;
|
354
353
|
this.currentCurve = new Bezier(segmentStart.xy, controlPoint.xy, segmentEnd.xy);
|
355
354
|
if (isNaN(Vec2.ofXY(this.currentCurve.normal(0)).magnitude())) {
|
@@ -369,8 +368,7 @@ export default class FreehandLineBuilder {
|
|
369
368
|
}
|
370
369
|
return true;
|
371
370
|
};
|
372
|
-
|
373
|
-
if (this.buffer.length > 3 && approxCurveLen > this.curveEndWidth / 3) {
|
371
|
+
if (this.buffer.length > 3 && this.approxCurrentCurveLength() > this.curveStartWidth) {
|
374
372
|
if (!curveMatchesPoints(this.currentCurve)) {
|
375
373
|
// Use a curve that better fits the points
|
376
374
|
this.currentCurve = prevCurve;
|
package/dist/src/math/Vec3.d.ts
CHANGED
@@ -97,7 +97,7 @@ export default class Vec3 {
|
|
97
97
|
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 2.99); // → false
|
98
98
|
* ```
|
99
99
|
*/
|
100
|
-
eq(other: Vec3, fuzz
|
100
|
+
eq(other: Vec3, fuzz?: number): boolean;
|
101
101
|
toString(): string;
|
102
102
|
static unitX: Vec3;
|
103
103
|
static unitY: Vec3;
|
package/dist/src/math/Vec3.js
CHANGED
@@ -156,7 +156,7 @@ export default class Vec3 {
|
|
156
156
|
* Vec3.of(1, 2, 3).eq(Vec3.of(4, 5, 6), 2.99); // → false
|
157
157
|
* ```
|
158
158
|
*/
|
159
|
-
eq(other, fuzz) {
|
159
|
+
eq(other, fuzz = 1e-10) {
|
160
160
|
for (let i = 0; i < 3; i++) {
|
161
161
|
if (Math.abs(other.at(i) - this.at(i)) > fuzz) {
|
162
162
|
return false;
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -1,3 +1,9 @@
|
|
1
1
|
import { RenderingMode } from '../rendering/Display';
|
2
2
|
import Editor from '../Editor';
|
3
|
-
|
3
|
+
/** Creates an editor. Should only be used in test files. */
|
4
|
+
export default () => {
|
5
|
+
if (jest === undefined) {
|
6
|
+
throw new Error('Files in the testing/ folder should only be used in tests!');
|
7
|
+
}
|
8
|
+
return new Editor(document.body, { renderingMode: RenderingMode.DummyRenderer });
|
9
|
+
};
|
@@ -1,17 +1,2 @@
|
|
1
1
|
export declare const loadExpectExtensions: () => void;
|
2
|
-
export interface CustomMatchers<R = unknown> {
|
3
|
-
objEq(expected: {
|
4
|
-
eq: (other: any, ...args: any) => boolean;
|
5
|
-
}, ...opts: any): R;
|
6
|
-
}
|
7
|
-
declare global {
|
8
|
-
export namespace jest {
|
9
|
-
interface Expect extends CustomMatchers {
|
10
|
-
}
|
11
|
-
interface Matchers<R> extends CustomMatchers<R> {
|
12
|
-
}
|
13
|
-
interface AsyncAsymmetricMatchers extends CustomMatchers {
|
14
|
-
}
|
15
|
-
}
|
16
|
-
}
|
17
2
|
export default loadExpectExtensions;
|
@@ -8,6 +8,7 @@ import Viewport from '../../Viewport';
|
|
8
8
|
import BaseTool from '../BaseTool';
|
9
9
|
import SVGRenderer from '../../rendering/renderers/SVGRenderer';
|
10
10
|
import Selection from './Selection';
|
11
|
+
import TextComponent from '../../components/Text';
|
11
12
|
export const cssPrefix = 'selection-tool-';
|
12
13
|
// {@inheritDoc SelectionTool!}
|
13
14
|
export default class SelectionTool extends BaseTool {
|
@@ -215,10 +216,17 @@ export default class SelectionTool extends BaseTool {
|
|
215
216
|
const exportElem = document.createElementNS(svgNameSpace, 'svg');
|
216
217
|
const sanitize = true;
|
217
218
|
const renderer = new SVGRenderer(exportElem, exportViewport, sanitize);
|
219
|
+
const text = [];
|
218
220
|
for (const elem of selectedElems) {
|
219
221
|
elem.render(renderer);
|
222
|
+
if (elem instanceof TextComponent) {
|
223
|
+
text.push(elem.getText());
|
224
|
+
}
|
220
225
|
}
|
221
226
|
event.setData('image/svg+xml', exportElem.outerHTML);
|
227
|
+
if (text.length > 0) {
|
228
|
+
event.setData('text/plain', text.join('\n'));
|
229
|
+
}
|
222
230
|
return true;
|
223
231
|
}
|
224
232
|
setEnabled(enabled) {
|
package/jest.config.js
CHANGED
@@ -10,6 +10,10 @@ const config = {
|
|
10
10
|
'js',
|
11
11
|
],
|
12
12
|
|
13
|
+
testPathIgnorePatterns: [
|
14
|
+
'<rootDir>/dist/', '<rootDir>/node_modules/'
|
15
|
+
],
|
16
|
+
|
13
17
|
// Mocks.
|
14
18
|
// See https://jestjs.io/docs/webpack#handling-static-assets
|
15
19
|
moduleNameMapper: {
|
@@ -19,6 +23,7 @@ const config = {
|
|
19
23
|
},
|
20
24
|
|
21
25
|
testEnvironment: 'jsdom',
|
26
|
+
setupFilesAfterEnv: [ '<rootDir>/src/testing/beforeEachFile.ts' ],
|
22
27
|
};
|
23
28
|
|
24
29
|
module.exports = config;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "js-draw",
|
3
|
-
"version": "0.4.
|
3
|
+
"version": "0.4.1",
|
4
4
|
"description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
|
5
5
|
"main": "./dist/src/lib.d.ts",
|
6
6
|
"types": "./dist/src/lib.js",
|
@@ -71,36 +71,37 @@
|
|
71
71
|
"lint": "eslint .",
|
72
72
|
"linter-precommit": "eslint --fix --ext .js --ext .ts",
|
73
73
|
"lint-staged": "lint-staged",
|
74
|
+
"lint-ci": "eslint . --max-warnings=0 --ext .js --ext .ts",
|
74
75
|
"prepare": "husky install && yarn build",
|
75
76
|
"prepack": "yarn build && yarn test && pinst --disable",
|
76
77
|
"postpack": "pinst --enable"
|
77
78
|
},
|
78
79
|
"dependencies": {
|
79
|
-
"@melloware/coloris": "^0.16.
|
80
|
+
"@melloware/coloris": "^0.16.1",
|
80
81
|
"bezier-js": "^6.1.0"
|
81
82
|
},
|
82
83
|
"devDependencies": {
|
83
84
|
"@types/bezier-js": "^4.1.0",
|
84
|
-
"@types/jest": "^
|
85
|
+
"@types/jest": "^29.0.3",
|
85
86
|
"@types/jsdom": "^20.0.0",
|
86
|
-
"@types/node": "^18.7.
|
87
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
88
|
-
"@typescript-eslint/parser": "^5.
|
87
|
+
"@types/node": "^18.7.23",
|
88
|
+
"@typescript-eslint/eslint-plugin": "^5.38.1",
|
89
|
+
"@typescript-eslint/parser": "^5.38.1",
|
89
90
|
"css-loader": "^6.7.1",
|
90
|
-
"eslint": "^8.
|
91
|
+
"eslint": "^8.24.0",
|
91
92
|
"husky": "^8.0.1",
|
92
|
-
"jest": "^
|
93
|
-
"jest-environment-jsdom": "^29.0.
|
93
|
+
"jest": "^29.0.3",
|
94
|
+
"jest-environment-jsdom": "^29.0.3",
|
94
95
|
"jsdom": "^20.0.0",
|
95
96
|
"lint-staged": "^13.0.3",
|
96
97
|
"pinst": "^3.0.0",
|
97
98
|
"style-loader": "^3.3.1",
|
98
|
-
"terser-webpack-plugin": "^5.3.
|
99
|
-
"ts-jest": "^
|
100
|
-
"ts-loader": "^9.
|
99
|
+
"terser-webpack-plugin": "^5.3.6",
|
100
|
+
"ts-jest": "^29.0.2",
|
101
|
+
"ts-loader": "^9.4.1",
|
101
102
|
"ts-node": "^10.9.1",
|
102
|
-
"typedoc": "^0.23.
|
103
|
-
"typescript": "^4.8.
|
103
|
+
"typedoc": "^0.23.15",
|
104
|
+
"typescript": "^4.8.3",
|
104
105
|
"webpack": "^5.74.0"
|
105
106
|
},
|
106
107
|
"bugs": {
|
package/src/Editor.ts
CHANGED
@@ -797,6 +797,8 @@ export class Editor {
|
|
797
797
|
public sendPenEvent(
|
798
798
|
eventType: InputEvtType.PointerDownEvt|InputEvtType.PointerMoveEvt|InputEvtType.PointerUpEvt,
|
799
799
|
point: Point2,
|
800
|
+
|
801
|
+
// @deprecated
|
800
802
|
allPointers?: Pointer[]
|
801
803
|
) {
|
802
804
|
const mainPointer = Pointer.ofCanvasPoint(
|
@@ -206,8 +206,10 @@ export default abstract class AbstractComponent {
|
|
206
206
|
|
207
207
|
public abstract description(localizationTable: ImageComponentLocalization): string;
|
208
208
|
|
209
|
+
// Component-specific implementation of {@link clone}.
|
209
210
|
protected abstract createClone(): AbstractComponent;
|
210
211
|
|
212
|
+
// Returns a copy of this component.
|
211
213
|
public clone() {
|
212
214
|
const clone = this.createClone();
|
213
215
|
|
@@ -2,12 +2,9 @@ import Color4 from '../Color4';
|
|
2
2
|
import Path from '../math/Path';
|
3
3
|
import { Vec2 } from '../math/Vec2';
|
4
4
|
import Stroke from './Stroke';
|
5
|
-
import { loadExpectExtensions } from '../testing/loadExpectExtensions';
|
6
5
|
import createEditor from '../testing/createEditor';
|
7
6
|
import Mat33 from '../math/Mat33';
|
8
7
|
|
9
|
-
loadExpectExtensions();
|
10
|
-
|
11
8
|
describe('Stroke', () => {
|
12
9
|
it('empty stroke should have an empty bounding box', () => {
|
13
10
|
const stroke = new Stroke([{
|
package/src/components/Stroke.ts
CHANGED
@@ -18,7 +18,8 @@ export default class Stroke extends AbstractComponent {
|
|
18
18
|
public constructor(parts: RenderablePathSpec[]) {
|
19
19
|
super('stroke');
|
20
20
|
|
21
|
-
this.parts =
|
21
|
+
this.parts = [];
|
22
|
+
for (const section of parts) {
|
22
23
|
const path = Path.fromRenderable(section);
|
23
24
|
const pathBBox = this.bboxForPart(path.bbox, section.style);
|
24
25
|
|
@@ -28,15 +29,15 @@ export default class Stroke extends AbstractComponent {
|
|
28
29
|
this.contentBBox = this.contentBBox.union(pathBBox);
|
29
30
|
}
|
30
31
|
|
31
|
-
|
32
|
+
this.parts.push({
|
32
33
|
path,
|
33
34
|
|
34
35
|
// To implement RenderablePathSpec
|
35
36
|
startPoint: path.startPoint,
|
36
37
|
style: section.style,
|
37
38
|
commands: path.parts,
|
38
|
-
};
|
39
|
-
}
|
39
|
+
});
|
40
|
+
}
|
40
41
|
this.contentBBox ??= Rect2.empty;
|
41
42
|
}
|
42
43
|
|
@@ -104,9 +105,15 @@ export default class Stroke extends AbstractComponent {
|
|
104
105
|
}
|
105
106
|
|
106
107
|
public getPath() {
|
107
|
-
|
108
|
-
|
109
|
-
|
108
|
+
let result: Path|null = null;
|
109
|
+
for (const part of this.parts) {
|
110
|
+
if (result) {
|
111
|
+
result = result.union(part.path);
|
112
|
+
} else {
|
113
|
+
result ??= part.path;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
return result ?? Path.empty;
|
110
117
|
}
|
111
118
|
|
112
119
|
public description(localization: ImageComponentLocalization): string {
|
@@ -3,9 +3,6 @@ import Mat33 from '../math/Mat33';
|
|
3
3
|
import Rect2 from '../math/Rect2';
|
4
4
|
import AbstractComponent from './AbstractComponent';
|
5
5
|
import Text, { TextStyle } from './Text';
|
6
|
-
import { loadExpectExtensions } from '../testing/loadExpectExtensions';
|
7
|
-
|
8
|
-
loadExpectExtensions();
|
9
6
|
|
10
7
|
const estimateTextBounds = (text: string, style: TextStyle): Rect2 => {
|
11
8
|
const widthEst = text.length * style.size;
|
package/src/components/Text.ts
CHANGED
@@ -135,7 +135,7 @@ export default class Text extends AbstractComponent {
|
|
135
135
|
return new Text(this.textObjects, this.transform, this.style);
|
136
136
|
}
|
137
137
|
|
138
|
-
|
138
|
+
public getText() {
|
139
139
|
const result: string[] = [];
|
140
140
|
|
141
141
|
for (const textObject of this.textObjects) {
|
@@ -146,7 +146,7 @@ export default class Text extends AbstractComponent {
|
|
146
146
|
}
|
147
147
|
}
|
148
148
|
|
149
|
-
return result.join('
|
149
|
+
return result.join('\n');
|
150
150
|
}
|
151
151
|
|
152
152
|
public description(localizationTable: ImageComponentLocalization): string {
|