js-draw 0.1.11 → 0.1.12
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/CHANGELOG.md +7 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +4 -2
- package/dist/src/Editor.js +30 -10
- package/dist/src/EditorImage.d.ts +1 -1
- package/dist/src/EditorImage.js +2 -2
- package/dist/src/Pointer.d.ts +1 -1
- package/dist/src/Pointer.js +1 -1
- package/dist/src/SVGLoader.d.ts +1 -1
- package/dist/src/SVGLoader.js +14 -6
- package/dist/src/Viewport.d.ts +8 -25
- package/dist/src/Viewport.js +15 -10
- package/dist/src/commands/Command.d.ts +2 -2
- package/dist/src/commands/Command.js +4 -4
- package/dist/src/commands/Duplicate.d.ts +1 -1
- package/dist/src/commands/Duplicate.js +1 -1
- package/dist/src/commands/Erase.d.ts +1 -1
- package/dist/src/commands/Erase.js +1 -1
- package/dist/src/commands/localization.d.ts +1 -1
- package/dist/src/components/AbstractComponent.d.ts +3 -3
- package/dist/src/components/AbstractComponent.js +2 -2
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +3 -3
- package/dist/src/components/SVGGlobalAttributesObject.js +1 -1
- package/dist/src/components/Stroke.d.ts +4 -4
- package/dist/src/components/Stroke.js +2 -2
- package/dist/src/components/Text.d.ts +3 -3
- package/dist/src/components/Text.js +3 -3
- package/dist/src/components/UnknownSVGObject.d.ts +3 -3
- package/dist/src/components/UnknownSVGObject.js +1 -1
- package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
- package/dist/src/components/builders/ArrowBuilder.js +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +8 -3
- package/dist/src/components/builders/FreehandLineBuilder.js +142 -71
- package/dist/src/components/builders/LineBuilder.d.ts +1 -1
- package/dist/src/components/builders/LineBuilder.js +1 -1
- package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
- package/dist/src/components/builders/RectangleBuilder.js +3 -3
- package/dist/src/components/builders/types.d.ts +1 -1
- package/dist/src/localization.d.ts +1 -0
- package/dist/src/localization.js +5 -1
- package/dist/src/localizations/es.js +1 -1
- package/dist/src/{geometry → math}/LineSegment2.d.ts +0 -0
- package/dist/src/{geometry → math}/LineSegment2.js +0 -0
- package/dist/src/{geometry → math}/Mat33.d.ts +0 -0
- package/dist/src/{geometry → math}/Mat33.js +0 -0
- package/dist/src/{geometry → math}/Path.d.ts +2 -1
- package/dist/src/{geometry → math}/Path.js +58 -51
- package/dist/src/{geometry → math}/Rect2.d.ts +0 -0
- package/dist/src/{geometry → math}/Rect2.js +0 -0
- package/dist/src/{geometry → math}/Vec2.d.ts +0 -0
- package/dist/src/{geometry → math}/Vec2.js +0 -0
- package/dist/src/{geometry → math}/Vec3.d.ts +1 -1
- package/dist/src/{geometry → math}/Vec3.js +1 -1
- package/dist/src/math/rounding.d.ts +3 -0
- package/dist/src/math/rounding.js +120 -0
- package/dist/src/rendering/Display.d.ts +3 -1
- package/dist/src/rendering/Display.js +16 -10
- package/dist/src/rendering/caching/CacheRecord.d.ts +2 -2
- package/dist/src/rendering/caching/CacheRecord.js +1 -1
- package/dist/src/rendering/caching/CacheRecordManager.d.ts +1 -1
- package/dist/src/rendering/caching/RenderingCache.js +1 -1
- package/dist/src/rendering/caching/RenderingCacheNode.d.ts +2 -1
- package/dist/src/rendering/caching/RenderingCacheNode.js +18 -7
- package/dist/src/rendering/caching/testUtils.js +1 -1
- package/dist/src/rendering/caching/types.d.ts +1 -1
- package/dist/src/rendering/localization.d.ts +2 -0
- package/dist/src/rendering/localization.js +2 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/AbstractRenderer.js +2 -2
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/CanvasRenderer.js +1 -1
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/DummyRenderer.js +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -3
- package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +5 -3
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +13 -3
- package/dist/src/toolbar/icons.d.ts +3 -0
- package/dist/src/toolbar/icons.js +142 -132
- package/dist/src/toolbar/localization.d.ts +2 -1
- package/dist/src/toolbar/localization.js +2 -1
- package/dist/src/toolbar/makeColorInput.js +2 -1
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +21 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +2 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +3 -3
- package/dist/src/toolbar/widgets/SelectionWidget.d.ts +0 -1
- package/dist/src/toolbar/widgets/SelectionWidget.js +23 -30
- package/dist/src/tools/Eraser.js +1 -1
- package/dist/src/tools/PanZoom.d.ts +1 -1
- package/dist/src/tools/PanZoom.js +24 -14
- package/dist/src/tools/SelectionTool.d.ts +3 -3
- package/dist/src/tools/SelectionTool.js +6 -6
- package/dist/src/tools/TextTool.js +1 -1
- package/dist/src/types.d.ts +4 -4
- package/package.json +1 -1
- package/src/Editor.ts +34 -12
- package/src/EditorImage.test.ts +2 -4
- package/src/EditorImage.ts +2 -2
- package/src/Pointer.ts +1 -1
- package/src/SVGLoader.ts +14 -6
- package/src/Viewport.ts +19 -17
- package/src/commands/Command.ts +5 -5
- package/src/commands/Duplicate.ts +1 -1
- package/src/commands/Erase.ts +1 -1
- package/src/commands/localization.ts +1 -1
- package/src/components/AbstractComponent.ts +4 -4
- package/src/components/SVGGlobalAttributesObject.ts +3 -3
- package/src/components/Stroke.test.ts +3 -5
- package/src/components/Stroke.ts +4 -4
- package/src/components/Text.test.ts +2 -2
- package/src/components/Text.ts +3 -3
- package/src/components/UnknownSVGObject.ts +3 -3
- package/src/components/builders/ArrowBuilder.ts +2 -2
- package/src/components/builders/FreehandLineBuilder.ts +190 -80
- package/src/components/builders/LineBuilder.ts +2 -2
- package/src/components/builders/RectangleBuilder.ts +3 -3
- package/src/components/builders/types.ts +1 -1
- package/src/localization.ts +6 -0
- package/src/localizations/es.ts +2 -1
- package/src/{geometry → math}/LineSegment2.test.ts +0 -0
- package/src/{geometry → math}/LineSegment2.ts +0 -0
- package/src/{geometry → math}/Mat33.test.ts +0 -0
- package/src/{geometry → math}/Mat33.ts +0 -0
- package/src/{geometry → math}/Path.fromString.test.ts +0 -0
- package/src/{geometry → math}/Path.test.ts +0 -0
- package/src/{geometry → math}/Path.toString.test.ts +11 -2
- package/src/{geometry → math}/Path.ts +60 -57
- package/src/{geometry → math}/Rect2.test.ts +0 -0
- package/src/{geometry → math}/Rect2.ts +0 -0
- package/src/{geometry → math}/Vec2.test.ts +0 -0
- package/src/{geometry → math}/Vec2.ts +0 -0
- package/src/{geometry → math}/Vec3.test.ts +0 -0
- package/src/{geometry → math}/Vec3.ts +2 -2
- package/src/math/rounding.test.ts +40 -0
- package/src/math/rounding.ts +145 -0
- package/src/rendering/Display.ts +18 -10
- package/src/rendering/caching/CacheRecord.test.ts +2 -2
- package/src/rendering/caching/CacheRecord.ts +2 -2
- package/src/rendering/caching/CacheRecordManager.ts +1 -1
- package/src/rendering/caching/RenderingCache.test.ts +3 -3
- package/src/rendering/caching/RenderingCache.ts +1 -1
- package/src/rendering/caching/RenderingCacheNode.ts +23 -7
- package/src/rendering/caching/testUtils.ts +1 -1
- package/src/rendering/caching/types.ts +1 -1
- package/src/rendering/localization.ts +4 -0
- package/src/rendering/renderers/AbstractRenderer.ts +4 -4
- package/src/rendering/renderers/CanvasRenderer.ts +4 -4
- package/src/rendering/renderers/DummyRenderer.test.ts +2 -2
- package/src/rendering/renderers/DummyRenderer.ts +4 -4
- package/src/rendering/renderers/SVGRenderer.ts +10 -4
- package/src/rendering/renderers/TextOnlyRenderer.ts +17 -6
- package/src/toolbar/icons.ts +157 -137
- package/src/toolbar/localization.ts +4 -2
- package/src/toolbar/makeColorInput.ts +2 -1
- package/src/toolbar/toolbar.css +1 -1
- package/src/toolbar/widgets/ActionButtonWidget.ts +31 -0
- package/src/toolbar/widgets/BaseWidget.ts +2 -0
- package/src/toolbar/widgets/HandToolWidget.ts +3 -3
- package/src/toolbar/widgets/SelectionWidget.ts +46 -41
- package/src/tools/Eraser.ts +2 -2
- package/src/tools/PanZoom.ts +28 -16
- package/src/tools/SelectionTool.test.ts +2 -4
- package/src/tools/SelectionTool.ts +6 -6
- package/src/tools/TextTool.ts +2 -2
- package/src/tools/UndoRedoShortcut.test.ts +1 -1
- package/src/types.ts +4 -4
@@ -1,9 +1,9 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
2
|
import AbstractRenderer, { RenderablePathSpec } from '../../rendering/renderers/AbstractRenderer';
|
3
|
-
import { Point2, Vec2 } from '../../
|
4
|
-
import Rect2 from '../../
|
5
|
-
import {
|
6
|
-
import LineSegment2 from '../../
|
3
|
+
import { Point2, Vec2 } from '../../math/Vec2';
|
4
|
+
import Rect2 from '../../math/Rect2';
|
5
|
+
import { LinePathCommand, PathCommandType, QuadraticBezierPathCommand } from '../../math/Path';
|
6
|
+
import LineSegment2 from '../../math/LineSegment2';
|
7
7
|
import Stroke from '../Stroke';
|
8
8
|
import Viewport from '../../Viewport';
|
9
9
|
import { StrokeDataPoint } from '../../types';
|
@@ -22,9 +22,35 @@ export const makeFreehandLineBuilder: ComponentBuilderFactory = (initialPoint: S
|
|
22
22
|
);
|
23
23
|
};
|
24
24
|
|
25
|
+
type CurrentSegmentToPathResult = {
|
26
|
+
upperCurve: QuadraticBezierPathCommand,
|
27
|
+
lowerToUpperConnector: LinePathCommand,
|
28
|
+
upperToLowerConnector: LinePathCommand,
|
29
|
+
lowerCurve: QuadraticBezierPathCommand,
|
30
|
+
};
|
31
|
+
|
25
32
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
26
33
|
export default class FreehandLineBuilder implements ComponentBuilder {
|
27
|
-
private
|
34
|
+
private isFirstSegment: boolean = true;
|
35
|
+
private pathStartConnector: LinePathCommand|null = null;
|
36
|
+
private mostRecentConnector: LinePathCommand|null = null;
|
37
|
+
|
38
|
+
// Beginning of the list of lower parts
|
39
|
+
// ↓
|
40
|
+
// /---pathStartConnector---/ ← Beginning of the list of upper parts
|
41
|
+
// ___/ __/
|
42
|
+
// / /
|
43
|
+
// /--Most recent connector--/ ← most recent upper part goes here
|
44
|
+
// ↑
|
45
|
+
// most recent lower part goes here
|
46
|
+
//
|
47
|
+
// The upperSegments form a path that goes in reverse from the most recent edge to the
|
48
|
+
// least recent edge.
|
49
|
+
// The lowerSegments form a path that goes from the least recent edge to the most
|
50
|
+
// recent edge.
|
51
|
+
private upperSegments: QuadraticBezierPathCommand[];
|
52
|
+
private lowerSegments: QuadraticBezierPathCommand[];
|
53
|
+
|
28
54
|
private buffer: Point2[];
|
29
55
|
private lastPoint: StrokeDataPoint;
|
30
56
|
private lastExitingVec: Vec2;
|
@@ -47,7 +73,9 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
47
73
|
private maxFitAllowed: number
|
48
74
|
) {
|
49
75
|
this.lastPoint = this.startPoint;
|
50
|
-
this.
|
76
|
+
this.upperSegments = [];
|
77
|
+
this.lowerSegments = [];
|
78
|
+
|
51
79
|
this.buffer = [this.startPoint.pos];
|
52
80
|
this.momentum = Vec2.zero;
|
53
81
|
this.currentCurve = null;
|
@@ -65,19 +93,82 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
65
93
|
};
|
66
94
|
}
|
67
95
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
96
|
+
private previewPath(): RenderablePathSpec|null {
|
97
|
+
let upperPath: QuadraticBezierPathCommand[];
|
98
|
+
let lowerPath: QuadraticBezierPathCommand[];
|
99
|
+
let lowerToUpperCap: LinePathCommand;
|
100
|
+
let pathStartConnector: LinePathCommand;
|
101
|
+
if (this.currentCurve) {
|
102
|
+
const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
|
103
|
+
upperPath = this.upperSegments.concat(upperCurve);
|
104
|
+
lowerPath = this.lowerSegments.concat(lowerCurve);
|
105
|
+
lowerToUpperCap = lowerToUpperConnector;
|
106
|
+
pathStartConnector = this.pathStartConnector ?? upperToLowerConnector;
|
107
|
+
} else {
|
108
|
+
if (this.mostRecentConnector === null || this.pathStartConnector === null) {
|
109
|
+
return null;
|
110
|
+
}
|
111
|
+
|
112
|
+
upperPath = this.upperSegments.slice();
|
113
|
+
lowerPath = this.lowerSegments.slice();
|
114
|
+
lowerToUpperCap = this.mostRecentConnector;
|
115
|
+
pathStartConnector = this.pathStartConnector;
|
73
116
|
}
|
117
|
+
const startPoint = lowerPath[lowerPath.length - 1].endPoint;
|
74
118
|
|
75
|
-
|
119
|
+
|
120
|
+
return {
|
121
|
+
// Start at the end of the lower curve:
|
122
|
+
// Start point
|
123
|
+
// ↓
|
124
|
+
// __/ __/ ← Most recent points on this end
|
125
|
+
// /___ /
|
126
|
+
// ↑
|
127
|
+
// Oldest points
|
128
|
+
startPoint,
|
129
|
+
|
130
|
+
commands: [
|
131
|
+
// Move to the most recent point on the upperPath:
|
132
|
+
// ----→•
|
133
|
+
// __/ __/
|
134
|
+
// /___ /
|
135
|
+
lowerToUpperCap,
|
136
|
+
|
137
|
+
// Move to the beginning of the upperPath:
|
138
|
+
// __/ __/
|
139
|
+
// /___ /
|
140
|
+
// • ←-
|
141
|
+
...upperPath.reverse(),
|
142
|
+
|
143
|
+
// Move to the beginning of the lowerPath:
|
144
|
+
// __/ __/
|
145
|
+
// /___ /
|
146
|
+
// •
|
147
|
+
pathStartConnector,
|
148
|
+
|
149
|
+
// Move back to the start point:
|
150
|
+
// •
|
151
|
+
// __/ __/
|
152
|
+
// /___ /
|
153
|
+
...lowerPath,
|
154
|
+
],
|
155
|
+
style: this.getRenderingStyle(),
|
156
|
+
};
|
157
|
+
}
|
158
|
+
|
159
|
+
private previewStroke(): Stroke|null {
|
160
|
+
const pathPreview = this.previewPath();
|
161
|
+
|
162
|
+
if (pathPreview) {
|
163
|
+
return new Stroke([ pathPreview ]);
|
164
|
+
}
|
165
|
+
return null;
|
76
166
|
}
|
77
167
|
|
78
168
|
public preview(renderer: AbstractRenderer) {
|
79
|
-
|
80
|
-
|
169
|
+
const path = this.previewPath();
|
170
|
+
if (path) {
|
171
|
+
renderer.drawPath(path);
|
81
172
|
}
|
82
173
|
}
|
83
174
|
|
@@ -85,9 +176,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
85
176
|
if (this.lastPoint) {
|
86
177
|
this.finalizeCurrentCurve();
|
87
178
|
}
|
88
|
-
return
|
89
|
-
this.segments,
|
90
|
-
);
|
179
|
+
return this.previewStroke()!;
|
91
180
|
}
|
92
181
|
|
93
182
|
private roundPoint(point: Point2): Point2 {
|
@@ -98,59 +187,77 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
98
187
|
// Case where no points have been added
|
99
188
|
if (!this.currentCurve) {
|
100
189
|
// Don't create a circle around the initial point if the stroke has more than one point.
|
101
|
-
if (this.
|
190
|
+
if (!this.isFirstSegment) {
|
102
191
|
return;
|
103
192
|
}
|
104
193
|
|
105
|
-
const width = Viewport.roundPoint(this.startPoint.width / 3, this.minFitAllowed);
|
194
|
+
const width = Viewport.roundPoint(this.startPoint.width / 3.5, this.minFitAllowed);
|
106
195
|
const center = this.roundPoint(this.startPoint.pos);
|
107
196
|
|
197
|
+
// Start on the right, cycle clockwise:
|
198
|
+
// |
|
199
|
+
// ----- ←
|
200
|
+
// |
|
201
|
+
const startPoint = this.startPoint.pos.plus(Vec2.of(width, 0));
|
202
|
+
|
108
203
|
// Draw a circle-ish shape around the start point
|
109
|
-
this.
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
style: this.getRenderingStyle(),
|
144
|
-
});
|
204
|
+
this.lowerSegments.push(
|
205
|
+
{
|
206
|
+
kind: PathCommandType.QuadraticBezierTo,
|
207
|
+
controlPoint: center.plus(Vec2.of(width, width)),
|
208
|
+
|
209
|
+
// Bottom of the circle
|
210
|
+
// |
|
211
|
+
// -----
|
212
|
+
// |
|
213
|
+
// ↑
|
214
|
+
endPoint: center.plus(Vec2.of(0, width)),
|
215
|
+
},
|
216
|
+
{
|
217
|
+
kind: PathCommandType.QuadraticBezierTo,
|
218
|
+
controlPoint: center.plus(Vec2.of(-width, width)),
|
219
|
+
endPoint: center.plus(Vec2.of(-width, 0)),
|
220
|
+
},
|
221
|
+
{
|
222
|
+
kind: PathCommandType.QuadraticBezierTo,
|
223
|
+
controlPoint: center.plus(Vec2.of(-width, -width)),
|
224
|
+
endPoint: center.plus(Vec2.of(0, -width)),
|
225
|
+
},
|
226
|
+
{
|
227
|
+
kind: PathCommandType.QuadraticBezierTo,
|
228
|
+
controlPoint: center.plus(Vec2.of(width, -width)),
|
229
|
+
endPoint: center.plus(Vec2.of(width, 0)),
|
230
|
+
}
|
231
|
+
);
|
232
|
+
this.pathStartConnector = {
|
233
|
+
kind: PathCommandType.LineTo,
|
234
|
+
point: startPoint,
|
235
|
+
};
|
236
|
+
this.mostRecentConnector = this.pathStartConnector;
|
237
|
+
|
145
238
|
return;
|
146
239
|
}
|
147
240
|
|
148
|
-
this.
|
241
|
+
const { upperCurve, lowerToUpperConnector, upperToLowerConnector, lowerCurve } = this.currentSegmentToPath();
|
242
|
+
|
243
|
+
if (this.isFirstSegment) {
|
244
|
+
// We draw the upper path (reversed), then the lower path, so we need the
|
245
|
+
// upperToLowerConnector to join the two paths.
|
246
|
+
this.pathStartConnector = upperToLowerConnector;
|
247
|
+
this.isFirstSegment = false;
|
248
|
+
}
|
249
|
+
// With the most recent connector, we're joining the end of the lowerPath to the most recent
|
250
|
+
// upperPath:
|
251
|
+
this.mostRecentConnector = lowerToUpperConnector;
|
252
|
+
|
253
|
+
this.upperSegments.push(upperCurve);
|
254
|
+
this.lowerSegments.push(lowerCurve);
|
255
|
+
|
149
256
|
const lastPoint = this.buffer[this.buffer.length - 1];
|
150
257
|
this.lastExitingVec = Vec2.ofXY(
|
151
258
|
this.currentCurve.points[2]
|
152
259
|
).minus(Vec2.ofXY(this.currentCurve.points[1]));
|
153
|
-
console.assert(this.lastExitingVec.magnitude() !== 0);
|
260
|
+
console.assert(this.lastExitingVec.magnitude() !== 0, 'lastExitingVec has zero length!');
|
154
261
|
|
155
262
|
// Use the last two points to start a new curve (the last point isn't used
|
156
263
|
// in the current curve and we want connected curves to share end points)
|
@@ -160,7 +267,8 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
160
267
|
this.currentCurve = null;
|
161
268
|
}
|
162
269
|
|
163
|
-
|
270
|
+
// Returns [upper curve, connector, lower curve]
|
271
|
+
private currentSegmentToPath(): CurrentSegmentToPathResult {
|
164
272
|
if (this.currentCurve == null) {
|
165
273
|
throw new Error('Invalid State: currentCurve is null!');
|
166
274
|
}
|
@@ -217,31 +325,33 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
217
325
|
halfVec = halfVec.times(2);
|
218
326
|
}
|
219
327
|
|
328
|
+
// Each starts at startPt ± startVec
|
220
329
|
|
221
|
-
const
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
},
|
330
|
+
const lowerCurve: QuadraticBezierPathCommand = {
|
331
|
+
kind: PathCommandType.QuadraticBezierTo,
|
332
|
+
controlPoint: this.roundPoint(controlPoint.plus(halfVec)),
|
333
|
+
endPoint: this.roundPoint(endPt.plus(endVec)),
|
334
|
+
};
|
227
335
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
{
|
234
|
-
kind: PathCommandType.QuadraticBezierTo,
|
235
|
-
controlPoint: this.roundPoint(controlPoint.minus(halfVec)),
|
236
|
-
endPoint: this.roundPoint(startPt.minus(startVec)),
|
237
|
-
},
|
238
|
-
];
|
336
|
+
// From the end of the upperCurve to the start of the lowerCurve:
|
337
|
+
const upperToLowerConnector: LinePathCommand = {
|
338
|
+
kind: PathCommandType.LineTo,
|
339
|
+
point: this.roundPoint(startPt.plus(startVec)),
|
340
|
+
};
|
239
341
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
342
|
+
// From the end of lowerCurve to the start of upperCurve:
|
343
|
+
const lowerToUpperConnector: LinePathCommand = {
|
344
|
+
kind: PathCommandType.LineTo,
|
345
|
+
point: this.roundPoint(endPt.minus(endVec))
|
346
|
+
};
|
347
|
+
|
348
|
+
const upperCurve: QuadraticBezierPathCommand = {
|
349
|
+
kind: PathCommandType.QuadraticBezierTo,
|
350
|
+
controlPoint: this.roundPoint(controlPoint.minus(halfVec)),
|
351
|
+
endPoint: this.roundPoint(startPt.minus(startVec)),
|
244
352
|
};
|
353
|
+
|
354
|
+
return { upperCurve, upperToLowerConnector, lowerToUpperConnector, lowerCurve };
|
245
355
|
}
|
246
356
|
|
247
357
|
// Compute the direction of the velocity at the end of this.buffer
|
@@ -264,7 +374,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
264
374
|
|
265
375
|
const threshold = Math.min(this.lastPoint.width, newPoint.width) / 4;
|
266
376
|
const shouldSnapToInitial = this.startPoint.pos.minus(newPoint.pos).magnitude() < threshold
|
267
|
-
&& this.
|
377
|
+
&& this.isFirstSegment;
|
268
378
|
|
269
379
|
// Snap to the starting point if the stroke is contained within a small ball centered
|
270
380
|
// at the starting point.
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import { PathCommandType } from '../../
|
2
|
-
import Rect2 from '../../
|
1
|
+
import { PathCommandType } from '../../math/Path';
|
2
|
+
import Rect2 from '../../math/Rect2';
|
3
3
|
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
|
4
4
|
import { StrokeDataPoint } from '../../types';
|
5
5
|
import Viewport from '../../Viewport';
|
@@ -1,6 +1,6 @@
|
|
1
|
-
import Mat33 from '../../
|
2
|
-
import Path from '../../
|
3
|
-
import Rect2 from '../../
|
1
|
+
import Mat33 from '../../math/Mat33';
|
2
|
+
import Path from '../../math/Path';
|
3
|
+
import Rect2 from '../../math/Rect2';
|
4
4
|
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
|
5
5
|
import { StrokeDataPoint } from '../../types';
|
6
6
|
import Viewport from '../../Viewport';
|
package/src/localization.ts
CHANGED
@@ -6,6 +6,7 @@ import { defaultToolLocalization, ToolLocalization } from './tools/localization'
|
|
6
6
|
|
7
7
|
|
8
8
|
export interface EditorLocalization extends ToolbarLocalization, ToolLocalization, CommandLocalization, ImageComponentLocalization, TextRendererLocalization {
|
9
|
+
accessibilityInputInstructions: string;
|
9
10
|
undoAnnouncement: (actionDescription: string)=> string;
|
10
11
|
redoAnnouncement: (actionDescription: string)=> string;
|
11
12
|
doneLoading: string;
|
@@ -19,6 +20,11 @@ export const defaultEditorLocalization: EditorLocalization = {
|
|
19
20
|
...defaultCommandLocalization,
|
20
21
|
...defaultComponentLocalization,
|
21
22
|
...defaultTextRendererLocalization,
|
23
|
+
accessibilityInputInstructions: [
|
24
|
+
'Press "t" to read the contents of the viewport as text.',
|
25
|
+
'Use the arrow keys to move the viewport, click and drag to draw strokes.',
|
26
|
+
'Press "w" to zoom in and "s" to zoom out.',
|
27
|
+
].join(' '),
|
22
28
|
loading: (percentage: number) => `Loading ${percentage}%...`,
|
23
29
|
imageEditor: 'Image Editor',
|
24
30
|
doneLoading: 'Done loading',
|
package/src/localizations/es.ts
CHANGED
@@ -38,7 +38,8 @@ const localization: EditorLocalization = {
|
|
38
38
|
resizeImageToSelection: 'Redimensionar la imagen a lo que está seleccionado',
|
39
39
|
deleteSelection: 'Borra la selección',
|
40
40
|
duplicateSelection: 'Duplica la selección',
|
41
|
-
|
41
|
+
pickColorFromScreen: 'Selecciona un color de la pantalla',
|
42
|
+
clickToPickColorAnnouncement: 'Haga un clic en la pantalla para seleccionar un color',
|
42
43
|
dropdownShown(toolName: string): string {
|
43
44
|
return `Menú por ${toolName} es visible`;
|
44
45
|
},
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -15,7 +15,7 @@ describe('Path.toString', () => {
|
|
15
15
|
point: Vec2.of(0.3, 0.4),
|
16
16
|
},
|
17
17
|
]);
|
18
|
-
expect(path.toString()).toBe('
|
18
|
+
expect(path.toString()).toBe('M.1,.2L.3,.4');
|
19
19
|
});
|
20
20
|
|
21
21
|
it('should fix rounding errors', () => {
|
@@ -30,7 +30,8 @@ describe('Path.toString', () => {
|
|
30
30
|
point: Vec2.of(184.00482359999998, 1)
|
31
31
|
}
|
32
32
|
]);
|
33
|
-
|
33
|
+
|
34
|
+
expect(path.toString()).toBe('M.1,.2Q9999,-11 .0003,1.4L184.0048236,1');
|
34
35
|
});
|
35
36
|
|
36
37
|
it('should not remove trailing zeroes before decimal points', () => {
|
@@ -40,6 +41,14 @@ describe('Path.toString', () => {
|
|
40
41
|
point: Vec2.of(30.0001, 40.000000001),
|
41
42
|
},
|
42
43
|
]);
|
44
|
+
|
43
45
|
expect(path.toString()).toBe('M1000,2000000L30.0001,40');
|
44
46
|
});
|
47
|
+
|
48
|
+
it('deserialized path should serialize to the same/similar path, but with rounded components', () => {
|
49
|
+
const path1 = Path.fromString('M100,100 L101,101 Q102,102 90.000000001,89.99999999 Z');
|
50
|
+
expect(path1.toString()).toBe([
|
51
|
+
'M100,100', 'L101,101', 'Q102,102 90,90', 'L100,100'
|
52
|
+
].join(''));
|
53
|
+
});
|
45
54
|
});
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
2
|
import { RenderablePathSpec } from '../rendering/renderers/AbstractRenderer';
|
3
3
|
import RenderingStyle from '../rendering/RenderingStyle';
|
4
|
+
import { toRoundedString, toStringOfSamePrecision } from './rounding';
|
4
5
|
import LineSegment2 from './LineSegment2';
|
5
6
|
import Mat33 from './Mat33';
|
6
7
|
import Rect2 from './Rect2';
|
@@ -170,8 +171,8 @@ export default class Path {
|
|
170
171
|
return result;
|
171
172
|
}
|
172
173
|
|
173
|
-
public
|
174
|
-
const startPoint =
|
174
|
+
public mapPoints(mapping: (point: Point2)=>Point2): Path {
|
175
|
+
const startPoint = mapping(this.startPoint);
|
175
176
|
const newParts: PathCommand[] = [];
|
176
177
|
|
177
178
|
let exhaustivenessCheck: never;
|
@@ -181,22 +182,22 @@ export default class Path {
|
|
181
182
|
case PathCommandType.LineTo:
|
182
183
|
newParts.push({
|
183
184
|
kind: part.kind,
|
184
|
-
point:
|
185
|
+
point: mapping(part.point),
|
185
186
|
});
|
186
187
|
break;
|
187
188
|
case PathCommandType.CubicBezierTo:
|
188
189
|
newParts.push({
|
189
190
|
kind: part.kind,
|
190
|
-
controlPoint1:
|
191
|
-
controlPoint2:
|
192
|
-
endPoint:
|
191
|
+
controlPoint1: mapping(part.controlPoint1),
|
192
|
+
controlPoint2: mapping(part.controlPoint2),
|
193
|
+
endPoint: mapping(part.endPoint),
|
193
194
|
});
|
194
195
|
break;
|
195
196
|
case PathCommandType.QuadraticBezierTo:
|
196
197
|
newParts.push({
|
197
198
|
kind: part.kind,
|
198
|
-
controlPoint:
|
199
|
-
endPoint:
|
199
|
+
controlPoint: mapping(part.controlPoint),
|
200
|
+
endPoint: mapping(part.endPoint),
|
200
201
|
});
|
201
202
|
break;
|
202
203
|
default:
|
@@ -208,6 +209,10 @@ export default class Path {
|
|
208
209
|
return new Path(startPoint, newParts);
|
209
210
|
}
|
210
211
|
|
212
|
+
public transformedBy(affineTransfm: Mat33): Path {
|
213
|
+
return this.mapPoints(point => affineTransfm.transformVec2(point));
|
214
|
+
}
|
215
|
+
|
211
216
|
// Creates a new path by joining [other] to the end of this path
|
212
217
|
public union(other: Path|null): Path {
|
213
218
|
if (!other) {
|
@@ -280,71 +285,69 @@ export default class Path {
|
|
280
285
|
}
|
281
286
|
|
282
287
|
public toString(): string {
|
283
|
-
|
288
|
+
// Hueristic: Try to determine whether converting absolute to relative commands is worth it.
|
289
|
+
// If we're near (0, 0), it probably isn't worth it and if bounding boxes are large,
|
290
|
+
// it also probably isn't worth it.
|
291
|
+
const makeRelativeCommands =
|
292
|
+
Math.abs(this.bbox.topLeft.x) > 10 && Math.abs(this.bbox.size.x) < 2
|
293
|
+
&& Math.abs(this.bbox.topLeft.y) > 10 && Math.abs(this.bbox.size.y) < 2;
|
294
|
+
|
295
|
+
return Path.toString(this.startPoint, this.parts, !makeRelativeCommands);
|
284
296
|
}
|
285
297
|
|
286
298
|
public serialize(): string {
|
287
299
|
return this.toString();
|
288
300
|
}
|
289
301
|
|
290
|
-
|
302
|
+
// [onlyAbsCommands]: True if we should avoid converting absolute coordinates to relative offsets -- such
|
303
|
+
// conversions can lead to smaller output strings, but also take time.
|
304
|
+
public static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands: boolean = true): string {
|
291
305
|
const result: string[] = [];
|
292
306
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
const
|
297
|
-
const
|
298
|
-
|
299
|
-
|
300
|
-
if (text.indexOf('.') === -1) {
|
301
|
-
return text;
|
302
|
-
}
|
307
|
+
let prevPoint: Point2|undefined;
|
308
|
+
const addCommand = (command: string, ...points: Point2[]) => {
|
309
|
+
const absoluteCommandParts: string[] = [];
|
310
|
+
const relativeCommandParts: string[] = [];
|
311
|
+
const makeAbsCommand = !prevPoint || onlyAbsCommands;
|
312
|
+
const roundedPrevX = prevPoint ? toRoundedString(prevPoint.x) : '';
|
313
|
+
const roundedPrevY = prevPoint ? toRoundedString(prevPoint.y) : '';
|
303
314
|
|
304
|
-
const
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
}
|
315
|
+
for (const point of points) {
|
316
|
+
// Relative commands are often shorter as strings than absolute commands.
|
317
|
+
if (!makeAbsCommand) {
|
318
|
+
const xComponentRelative = toStringOfSamePrecision(point.x - prevPoint!.x, roundedPrevX, roundedPrevY);
|
319
|
+
const yComponentRelative = toStringOfSamePrecision(point.y - prevPoint!.y, roundedPrevX, roundedPrevY);
|
320
|
+
|
321
|
+
// No need for an additional separator if it starts with a '-'
|
322
|
+
if (yComponentRelative.charAt(0) === '-') {
|
323
|
+
relativeCommandParts.push(`${xComponentRelative}${yComponentRelative}`);
|
324
|
+
} else {
|
325
|
+
relativeCommandParts.push(`${xComponentRelative},${yComponentRelative}`);
|
326
|
+
}
|
327
|
+
} else {
|
328
|
+
const xComponent = toRoundedString(point.x);
|
329
|
+
const yComponent = toRoundedString(point.y);
|
320
330
|
|
321
|
-
|
322
|
-
while (newPostDecimal.length < origPostDecimalString.length) {
|
323
|
-
newPostDecimal = carry.toString(10) + newPostDecimal;
|
324
|
-
carry = 0;
|
331
|
+
absoluteCommandParts.push(`${xComponent},${yComponent}`);
|
325
332
|
}
|
326
|
-
|
327
|
-
text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
|
328
333
|
}
|
329
334
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
+
let commandString;
|
336
|
+
if (makeAbsCommand) {
|
337
|
+
commandString = `${command}${absoluteCommandParts.join(' ')}`;
|
338
|
+
} else {
|
339
|
+
commandString = `${command.toLowerCase()}${relativeCommandParts.join(' ')}`;
|
340
|
+
}
|
335
341
|
|
336
|
-
//
|
337
|
-
|
338
|
-
|
342
|
+
// Don't add no-ops.
|
343
|
+
if (commandString === 'l0,0') {
|
344
|
+
return;
|
345
|
+
}
|
346
|
+
result.push(commandString);
|
339
347
|
|
340
|
-
|
341
|
-
|
342
|
-
for (const point of points) {
|
343
|
-
const xComponent = toRoundedString(point.x);
|
344
|
-
const yComponent = toRoundedString(point.y);
|
345
|
-
parts.push(`${xComponent},${yComponent}`);
|
348
|
+
if (points.length > 0) {
|
349
|
+
prevPoint = points[points.length - 1];
|
346
350
|
}
|
347
|
-
result.push(`${command}${parts.join(' ')}`);
|
348
351
|
};
|
349
352
|
|
350
353
|
addCommand('M', startPoint);
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|