js-draw 0.3.0 → 0.3.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/CHANGELOG.md +7 -0
- package/dist/bundle.js +1 -1
- package/dist/src/components/Stroke.js +11 -6
- package/dist/src/components/builders/FreehandLineBuilder.js +5 -5
- package/dist/src/math/LineSegment2.d.ts +2 -0
- package/dist/src/math/LineSegment2.js +6 -0
- package/dist/src/math/Path.d.ts +5 -1
- package/dist/src/math/Path.js +89 -7
- package/dist/src/math/Rect2.js +1 -1
- package/dist/src/math/Triangle.d.ts +11 -0
- package/dist/src/math/Triangle.js +19 -0
- package/dist/src/rendering/Display.js +2 -2
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +10 -11
- package/dist/src/rendering/renderers/SVGRenderer.js +27 -68
- package/dist/src/toolbar/HTMLToolbar.d.ts +1 -0
- package/dist/src/toolbar/HTMLToolbar.js +1 -0
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +3 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +21 -1
- package/dist/src/tools/BaseTool.d.ts +1 -0
- package/dist/src/tools/BaseTool.js +6 -0
- package/dist/src/tools/Pen.d.ts +2 -1
- package/dist/src/tools/Pen.js +16 -0
- package/dist/src/tools/ToolController.d.ts +1 -0
- package/dist/src/tools/ToolController.js +9 -2
- package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
- package/dist/src/tools/ToolSwitcherShortcut.js +26 -0
- package/dist/src/tools/lib.d.ts +1 -0
- package/dist/src/tools/lib.js +1 -0
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/dist/src/types.d.ts +8 -2
- package/dist/src/types.js +1 -0
- package/package.json +2 -2
- package/src/components/Stroke.test.ts +5 -0
- package/src/components/Stroke.ts +13 -7
- package/src/components/builders/FreehandLineBuilder.ts +5 -5
- package/src/math/LineSegment2.ts +8 -0
- package/src/math/Path.test.ts +53 -0
- package/src/math/Path.toString.test.ts +4 -2
- package/src/math/Path.ts +109 -11
- package/src/math/Rect2.ts +1 -1
- package/src/math/Triangle.ts +29 -0
- package/src/rendering/Display.ts +2 -2
- package/src/rendering/renderers/AbstractRenderer.ts +1 -0
- package/src/rendering/renderers/SVGRenderer.ts +30 -84
- package/src/toolbar/HTMLToolbar.ts +1 -1
- package/src/toolbar/types.ts +1 -1
- package/src/toolbar/widgets/BaseWidget.ts +27 -1
- package/src/tools/BaseTool.ts +8 -0
- package/src/tools/Pen.ts +20 -1
- package/src/tools/ToolController.ts +10 -3
- package/src/tools/ToolSwitcherShortcut.ts +34 -0
- package/src/tools/lib.ts +1 -0
- package/src/tools/localization.ts +2 -0
- package/src/types.ts +13 -1
@@ -6,7 +6,7 @@ export default class Stroke extends AbstractComponent {
|
|
6
6
|
constructor(parts) {
|
7
7
|
var _a;
|
8
8
|
super('stroke');
|
9
|
-
this.parts = parts.map(section => {
|
9
|
+
this.parts = parts.map((section) => {
|
10
10
|
const path = Path.fromRenderable(section);
|
11
11
|
const pathBBox = this.bboxForPart(path.bbox, section.style);
|
12
12
|
if (!this.contentBBox) {
|
@@ -17,7 +17,6 @@ export default class Stroke extends AbstractComponent {
|
|
17
17
|
}
|
18
18
|
return {
|
19
19
|
path,
|
20
|
-
bbox: pathBBox,
|
21
20
|
// To implement RenderablePathSpec
|
22
21
|
startPoint: path.startPoint,
|
23
22
|
style: section.style,
|
@@ -37,10 +36,17 @@ export default class Stroke extends AbstractComponent {
|
|
37
36
|
render(canvas, visibleRect) {
|
38
37
|
canvas.startObject(this.getBBox());
|
39
38
|
for (const part of this.parts) {
|
40
|
-
const bbox = part.bbox;
|
41
|
-
if (
|
42
|
-
|
39
|
+
const bbox = this.bboxForPart(part.path.bbox, part.style);
|
40
|
+
if (visibleRect) {
|
41
|
+
if (!bbox.intersects(visibleRect)) {
|
42
|
+
continue;
|
43
|
+
}
|
44
|
+
const muchBiggerThanVisible = bbox.size.x > visibleRect.size.x * 2 || bbox.size.y > visibleRect.size.y * 2;
|
45
|
+
if (muchBiggerThanVisible && !part.path.closedRoughlyIntersects(visibleRect)) {
|
46
|
+
continue;
|
47
|
+
}
|
43
48
|
}
|
49
|
+
canvas.drawPath(part);
|
44
50
|
}
|
45
51
|
canvas.endObject(this.getLoadSaveData());
|
46
52
|
}
|
@@ -67,7 +73,6 @@ export default class Stroke extends AbstractComponent {
|
|
67
73
|
}
|
68
74
|
return {
|
69
75
|
path: newPath,
|
70
|
-
bbox: newBBox,
|
71
76
|
startPoint: newPath.startPoint,
|
72
77
|
commands: newPath.parts,
|
73
78
|
style: part.style,
|
@@ -7,9 +7,9 @@ import Stroke from '../Stroke';
|
|
7
7
|
import Viewport from '../../Viewport';
|
8
8
|
export const makeFreehandLineBuilder = (initialPoint, viewport) => {
|
9
9
|
// Don't smooth if input is more than ± 7 pixels from the true curve, do smooth if
|
10
|
-
// less than ±
|
10
|
+
// less than ±1 px from the curve.
|
11
11
|
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 7;
|
12
|
-
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas()
|
12
|
+
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
|
13
13
|
return new FreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist);
|
14
14
|
};
|
15
15
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
@@ -136,7 +136,7 @@ export default class FreehandLineBuilder {
|
|
136
136
|
if (!this.isFirstSegment) {
|
137
137
|
return;
|
138
138
|
}
|
139
|
-
const width = Viewport.roundPoint(this.startPoint.width / 3.5, this.minFitAllowed);
|
139
|
+
const width = Viewport.roundPoint(this.startPoint.width / 3.5, Math.min(this.minFitAllowed, this.startPoint.width / 4));
|
140
140
|
const center = this.roundPoint(this.startPoint.pos);
|
141
141
|
// Start on the right, cycle clockwise:
|
142
142
|
// |
|
@@ -362,7 +362,7 @@ export default class FreehandLineBuilder {
|
|
362
362
|
for (const point of this.buffer) {
|
363
363
|
const proj = Vec2.ofXY(curve.project(point.xy));
|
364
364
|
const dist = proj.minus(point).magnitude();
|
365
|
-
const minFit = Math.max(Math.min(this.curveStartWidth, this.curveEndWidth) /
|
365
|
+
const minFit = Math.max(Math.min(this.curveStartWidth, this.curveEndWidth) / 3, this.minFitAllowed);
|
366
366
|
if (dist > minFit || dist > this.maxFitAllowed) {
|
367
367
|
return false;
|
368
368
|
}
|
@@ -370,7 +370,7 @@ export default class FreehandLineBuilder {
|
|
370
370
|
return true;
|
371
371
|
};
|
372
372
|
const approxCurveLen = controlPoint.minus(segmentStart).magnitude() + segmentEnd.minus(controlPoint).magnitude();
|
373
|
-
if (this.buffer.length > 3 && approxCurveLen > this.curveEndWidth /
|
373
|
+
if (this.buffer.length > 3 && approxCurveLen > this.curveEndWidth / 3) {
|
374
374
|
if (!curveMatchesPoints(this.currentCurve)) {
|
375
375
|
// Use a curve that better fits the points
|
376
376
|
this.currentCurve = prevCurve;
|
@@ -15,6 +15,8 @@ export default class LineSegment2 {
|
|
15
15
|
get p2(): Point2;
|
16
16
|
get(t: number): Point2;
|
17
17
|
intersection(other: LineSegment2): IntersectionResult | null;
|
18
|
+
intersects(other: LineSegment2): boolean;
|
18
19
|
closestPointTo(target: Point2): import("./Vec3").default;
|
20
|
+
toString(): string;
|
19
21
|
}
|
20
22
|
export {};
|
@@ -97,6 +97,9 @@ export default class LineSegment2 {
|
|
97
97
|
t: resultT,
|
98
98
|
};
|
99
99
|
}
|
100
|
+
intersects(other) {
|
101
|
+
return this.intersection(other) !== null;
|
102
|
+
}
|
100
103
|
// Returns the closest point on this to [target]
|
101
104
|
closestPointTo(target) {
|
102
105
|
// Distance from P1 along this' direction.
|
@@ -113,4 +116,7 @@ export default class LineSegment2 {
|
|
113
116
|
return this.p1;
|
114
117
|
}
|
115
118
|
}
|
119
|
+
toString() {
|
120
|
+
return `LineSegment(${this.p1.toString()}, ${this.p2.toString()})`;
|
121
|
+
}
|
116
122
|
}
|
package/dist/src/math/Path.d.ts
CHANGED
@@ -39,18 +39,22 @@ interface IntersectionResult {
|
|
39
39
|
export default class Path {
|
40
40
|
readonly startPoint: Point2;
|
41
41
|
readonly parts: PathCommand[];
|
42
|
-
private cachedGeometry;
|
43
42
|
readonly bbox: Rect2;
|
44
43
|
constructor(startPoint: Point2, parts: PathCommand[]);
|
44
|
+
private cachedGeometry;
|
45
45
|
get geometry(): Array<LineSegment2 | Bezier>;
|
46
|
+
private cachedPolylineApproximation;
|
47
|
+
polylineApproximation(): LineSegment2[];
|
46
48
|
static computeBBoxForSegment(startPoint: Point2, part: PathCommand): Rect2;
|
47
49
|
intersection(line: LineSegment2): IntersectionResult[];
|
48
50
|
mapPoints(mapping: (point: Point2) => Point2): Path;
|
49
51
|
transformedBy(affineTransfm: Mat33): Path;
|
50
52
|
union(other: Path | null): Path;
|
53
|
+
closedRoughlyIntersects(rect: Rect2): boolean;
|
51
54
|
static fromRect(rect: Rect2, lineWidth?: number | null): Path;
|
52
55
|
static fromRenderable(renderable: RenderablePathSpec): Path;
|
53
56
|
toRenderable(fill: RenderingStyle): RenderablePathSpec;
|
57
|
+
private cachedStringVersion;
|
54
58
|
toString(): string;
|
55
59
|
serialize(): string;
|
56
60
|
static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands?: boolean): string;
|
package/dist/src/math/Path.js
CHANGED
@@ -15,6 +15,8 @@ export default class Path {
|
|
15
15
|
this.startPoint = startPoint;
|
16
16
|
this.parts = parts;
|
17
17
|
this.cachedGeometry = null;
|
18
|
+
this.cachedPolylineApproximation = null;
|
19
|
+
this.cachedStringVersion = null;
|
18
20
|
// Initial bounding box contains one point: the start point.
|
19
21
|
this.bbox = Rect2.bboxOf([startPoint]);
|
20
22
|
// Convert into a representation of the geometry (cache for faster intersection
|
@@ -52,6 +54,34 @@ export default class Path {
|
|
52
54
|
this.cachedGeometry = geometry;
|
53
55
|
return this.cachedGeometry;
|
54
56
|
}
|
57
|
+
// Approximates this path with a group of line segments.
|
58
|
+
polylineApproximation() {
|
59
|
+
if (this.cachedPolylineApproximation) {
|
60
|
+
return this.cachedPolylineApproximation;
|
61
|
+
}
|
62
|
+
const points = [];
|
63
|
+
for (const part of this.parts) {
|
64
|
+
switch (part.kind) {
|
65
|
+
case PathCommandType.CubicBezierTo:
|
66
|
+
points.push(part.controlPoint1, part.controlPoint2, part.endPoint);
|
67
|
+
break;
|
68
|
+
case PathCommandType.QuadraticBezierTo:
|
69
|
+
points.push(part.controlPoint, part.endPoint);
|
70
|
+
break;
|
71
|
+
case PathCommandType.MoveTo:
|
72
|
+
case PathCommandType.LineTo:
|
73
|
+
points.push(part.point);
|
74
|
+
break;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
const result = [];
|
78
|
+
let prevPoint = this.startPoint;
|
79
|
+
for (const point of points) {
|
80
|
+
result.push(new LineSegment2(prevPoint, point));
|
81
|
+
prevPoint = point;
|
82
|
+
}
|
83
|
+
return result;
|
84
|
+
}
|
55
85
|
static computeBBoxForSegment(startPoint, part) {
|
56
86
|
const points = [startPoint];
|
57
87
|
let exhaustivenessCheck;
|
@@ -73,6 +103,9 @@ export default class Path {
|
|
73
103
|
return Rect2.bboxOf(points);
|
74
104
|
}
|
75
105
|
intersection(line) {
|
106
|
+
if (!line.bbox.intersects(this.bbox)) {
|
107
|
+
return [];
|
108
|
+
}
|
76
109
|
const result = [];
|
77
110
|
for (const part of this.geometry) {
|
78
111
|
if (part instanceof LineSegment2) {
|
@@ -162,6 +195,47 @@ export default class Path {
|
|
162
195
|
...other.parts,
|
163
196
|
]);
|
164
197
|
}
|
198
|
+
// Treats this as a closed path and returns true if part of `rect` is roughly within
|
199
|
+
// this path's interior.
|
200
|
+
//
|
201
|
+
// Note: Assumes that this is a closed, non-self-intersecting path.
|
202
|
+
closedRoughlyIntersects(rect) {
|
203
|
+
if (rect.containsRect(this.bbox)) {
|
204
|
+
return true;
|
205
|
+
}
|
206
|
+
// Choose a point outside of the path.
|
207
|
+
const startPt = this.bbox.topLeft.minus(Vec2.of(1, 1));
|
208
|
+
const testPts = rect.corners;
|
209
|
+
const polygon = this.polylineApproximation();
|
210
|
+
for (const point of testPts) {
|
211
|
+
const testLine = new LineSegment2(point, startPt);
|
212
|
+
let intersectionCount = 0;
|
213
|
+
for (const line of polygon) {
|
214
|
+
if (line.intersects(testLine)) {
|
215
|
+
intersectionCount++;
|
216
|
+
}
|
217
|
+
}
|
218
|
+
// Odd? The point is within the polygon!
|
219
|
+
if (intersectionCount % 2 === 1) {
|
220
|
+
return true;
|
221
|
+
}
|
222
|
+
}
|
223
|
+
// Grow the rectangle for possible additional precision.
|
224
|
+
const grownRect = rect.grownBy(Math.min(rect.size.x, rect.size.y));
|
225
|
+
const edges = [];
|
226
|
+
for (const subrect of grownRect.divideIntoGrid(4, 4)) {
|
227
|
+
edges.push(...subrect.getEdges());
|
228
|
+
}
|
229
|
+
for (const edge of edges) {
|
230
|
+
for (const line of polygon) {
|
231
|
+
if (edge.intersects(line)) {
|
232
|
+
return true;
|
233
|
+
}
|
234
|
+
}
|
235
|
+
}
|
236
|
+
// Even? Probably no intersection.
|
237
|
+
return false;
|
238
|
+
}
|
165
239
|
// Returns a path that outlines [rect]. If [lineWidth] is not given, the resultant path is
|
166
240
|
// the outline of [rect]. Otherwise, the resultant path represents a line of width [lineWidth]
|
167
241
|
// that traces [rect].
|
@@ -195,6 +269,9 @@ export default class Path {
|
|
195
269
|
return new Path(startPoint, commands);
|
196
270
|
}
|
197
271
|
static fromRenderable(renderable) {
|
272
|
+
if (renderable.path) {
|
273
|
+
return renderable.path;
|
274
|
+
}
|
198
275
|
return new Path(renderable.startPoint, renderable.commands);
|
199
276
|
}
|
200
277
|
toRenderable(fill) {
|
@@ -202,22 +279,25 @@ export default class Path {
|
|
202
279
|
startPoint: this.startPoint,
|
203
280
|
style: fill,
|
204
281
|
commands: this.parts,
|
282
|
+
path: this,
|
205
283
|
};
|
206
284
|
}
|
207
285
|
toString() {
|
286
|
+
if (this.cachedStringVersion) {
|
287
|
+
return this.cachedStringVersion;
|
288
|
+
}
|
208
289
|
// Hueristic: Try to determine whether converting absolute to relative commands is worth it.
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
return Path.toString(this.startPoint, this.parts, !makeRelativeCommands);
|
290
|
+
const makeRelativeCommands = Math.abs(this.bbox.topLeft.x) > 10 && Math.abs(this.bbox.topLeft.y) > 10;
|
291
|
+
const result = Path.toString(this.startPoint, this.parts, !makeRelativeCommands);
|
292
|
+
this.cachedStringVersion = result;
|
293
|
+
return result;
|
214
294
|
}
|
215
295
|
serialize() {
|
216
296
|
return this.toString();
|
217
297
|
}
|
218
298
|
// @param onlyAbsCommands - True if we should avoid converting absolute coordinates to relative offsets -- such
|
219
299
|
// conversions can lead to smaller output strings, but also take time.
|
220
|
-
static toString(startPoint, parts, onlyAbsCommands
|
300
|
+
static toString(startPoint, parts, onlyAbsCommands) {
|
221
301
|
const result = [];
|
222
302
|
let prevPoint;
|
223
303
|
const addCommand = (command, ...points) => {
|
@@ -452,7 +532,9 @@ export default class Path {
|
|
452
532
|
lastPos = allArgs[allArgs.length - 1];
|
453
533
|
}
|
454
534
|
}
|
455
|
-
|
535
|
+
const result = new Path(startPos !== null && startPos !== void 0 ? startPos : Vec2.zero, commands);
|
536
|
+
result.cachedStringVersion = pathString;
|
537
|
+
return result;
|
456
538
|
}
|
457
539
|
}
|
458
540
|
Path.empty = new Path(Vec2.zero, []);
|
package/dist/src/math/Rect2.js
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
import Mat33 from './Mat33';
|
2
|
+
import Vec3 from './Vec3';
|
3
|
+
export default class Triangle {
|
4
|
+
readonly vertex1: Vec3;
|
5
|
+
readonly vertex2: Vec3;
|
6
|
+
readonly vertex3: Vec3;
|
7
|
+
constructor(vertex1: Vec3, vertex2: Vec3, vertex3: Vec3);
|
8
|
+
map(mapping: (vertex: Vec3) => Vec3): Triangle;
|
9
|
+
transformed2DBy(affineTransform: Mat33): Triangle;
|
10
|
+
transformedBy(linearTransform: Mat33): Triangle;
|
11
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
export default class Triangle {
|
2
|
+
constructor(vertex1, vertex2, vertex3) {
|
3
|
+
this.vertex1 = vertex1;
|
4
|
+
this.vertex2 = vertex2;
|
5
|
+
this.vertex3 = vertex3;
|
6
|
+
}
|
7
|
+
map(mapping) {
|
8
|
+
return new Triangle(mapping(this.vertex1), mapping(this.vertex2), mapping(this.vertex3));
|
9
|
+
}
|
10
|
+
// Transform, treating this as composed of 2D points.
|
11
|
+
transformed2DBy(affineTransform) {
|
12
|
+
return this.map(affineTransform.transformVec2);
|
13
|
+
}
|
14
|
+
// Transforms this by a linear transform --- verticies are treated as
|
15
|
+
// 3D points.
|
16
|
+
transformedBy(linearTransform) {
|
17
|
+
return this.map(linearTransform.transformVec3);
|
18
|
+
}
|
19
|
+
}
|
@@ -71,9 +71,9 @@ export default class Display {
|
|
71
71
|
return this.dryInkRenderer.canRenderFromWithoutDataLoss(renderer);
|
72
72
|
},
|
73
73
|
blockResolution: cacheBlockResolution,
|
74
|
-
cacheSize: 600 * 600 * 4 *
|
74
|
+
cacheSize: 600 * 600 * 4 * 90,
|
75
75
|
maxScale: 1.4,
|
76
|
-
minComponentsPerCache:
|
76
|
+
minComponentsPerCache: 20,
|
77
77
|
minComponentsToUseCache: 105,
|
78
78
|
});
|
79
79
|
this.editor.notifier.on(EditorEventType.DisplayResized, event => {
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
2
2
|
import { TextStyle } from '../../components/Text';
|
3
3
|
import Mat33 from '../../math/Mat33';
|
4
|
-
import { PathCommand } from '../../math/Path';
|
4
|
+
import Path, { PathCommand } from '../../math/Path';
|
5
5
|
import Rect2 from '../../math/Rect2';
|
6
6
|
import { Point2, Vec2 } from '../../math/Vec2';
|
7
7
|
import Viewport from '../../Viewport';
|
@@ -10,6 +10,7 @@ export interface RenderablePathSpec {
|
|
10
10
|
startPoint: Point2;
|
11
11
|
commands: PathCommand[];
|
12
12
|
style: RenderingStyle;
|
13
|
+
path?: Path;
|
13
14
|
}
|
14
15
|
export default abstract class AbstractRenderer {
|
15
16
|
private viewport;
|
@@ -5,30 +5,29 @@ import Rect2 from '../../math/Rect2';
|
|
5
5
|
import { Point2, Vec2 } from '../../math/Vec2';
|
6
6
|
import Viewport from '../../Viewport';
|
7
7
|
import RenderingStyle from '../RenderingStyle';
|
8
|
-
import AbstractRenderer from './AbstractRenderer';
|
8
|
+
import AbstractRenderer, { RenderablePathSpec } from './AbstractRenderer';
|
9
9
|
export default class SVGRenderer extends AbstractRenderer {
|
10
10
|
private elem;
|
11
|
-
private currentPath;
|
12
|
-
private pathStart;
|
13
11
|
private lastPathStyle;
|
14
|
-
private
|
15
|
-
private lastPathStart;
|
12
|
+
private lastPathString;
|
16
13
|
private objectElems;
|
17
14
|
private overwrittenAttrs;
|
18
15
|
constructor(elem: SVGSVGElement, viewport: Viewport);
|
19
16
|
setRootSVGAttribute(name: string, value: string | null): void;
|
20
17
|
displaySize(): Vec2;
|
21
18
|
clear(): void;
|
22
|
-
protected beginPath(startPoint: Point2): void;
|
23
|
-
protected endPath(style: RenderingStyle): void;
|
24
19
|
private addPathToSVG;
|
20
|
+
drawPath(pathSpec: RenderablePathSpec): void;
|
25
21
|
drawText(text: string, transform: Mat33, style: TextStyle): void;
|
26
22
|
startObject(boundingBox: Rect2): void;
|
27
23
|
endObject(loaderData?: LoadSaveDataTable): void;
|
28
|
-
|
29
|
-
protected
|
30
|
-
protected
|
31
|
-
protected
|
24
|
+
private unimplementedMessage;
|
25
|
+
protected beginPath(_startPoint: Point2): void;
|
26
|
+
protected endPath(_style: RenderingStyle): void;
|
27
|
+
protected lineTo(_point: Point2): void;
|
28
|
+
protected moveTo(_point: Point2): void;
|
29
|
+
protected traceCubicBezierCurve(_controlPoint1: Point2, _controlPoint2: Point2, _endPoint: Point2): void;
|
30
|
+
protected traceQuadraticBezierCurve(_controlPoint: Point2, _endPoint: Point2): void;
|
32
31
|
drawPoints(...points: Point2[]): void;
|
33
32
|
drawSVGElem(elem: SVGElement): void;
|
34
33
|
isTooSmallToRender(_rect: Rect2): boolean;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import Mat33 from '../../math/Mat33';
|
2
|
-
import Path
|
2
|
+
import Path from '../../math/Path';
|
3
3
|
import { toRoundedString } from '../../math/rounding';
|
4
4
|
import { Vec2 } from '../../math/Vec2';
|
5
5
|
import { svgAttributesDataKey, svgStyleAttributesDataKey } from '../../SVGLoader';
|
@@ -9,6 +9,8 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
9
9
|
constructor(elem, viewport) {
|
10
10
|
super(viewport);
|
11
11
|
this.elem = elem;
|
12
|
+
this.lastPathStyle = null;
|
13
|
+
this.lastPathString = [];
|
12
14
|
this.objectElems = null;
|
13
15
|
this.overwrittenAttrs = {};
|
14
16
|
this.clear();
|
@@ -41,44 +43,16 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
41
43
|
}
|
42
44
|
}
|
43
45
|
this.overwrittenAttrs = {};
|
44
|
-
|
45
|
-
beginPath(startPoint) {
|
46
|
-
var _a;
|
47
|
-
this.currentPath = [];
|
48
|
-
this.pathStart = this.canvasToScreen(startPoint);
|
49
|
-
(_a = this.lastPathStart) !== null && _a !== void 0 ? _a : (this.lastPathStart = this.pathStart);
|
50
|
-
}
|
51
|
-
endPath(style) {
|
52
|
-
var _a;
|
53
|
-
if (this.currentPath == null) {
|
54
|
-
throw new Error('No path exists to end! Make sure beginPath was called!');
|
55
|
-
}
|
56
|
-
// Try to extend the previous path, if possible
|
57
|
-
if (style.fill.eq((_a = this.lastPathStyle) === null || _a === void 0 ? void 0 : _a.fill) && this.lastPath != null) {
|
58
|
-
this.lastPath.push({
|
59
|
-
kind: PathCommandType.MoveTo,
|
60
|
-
point: this.pathStart,
|
61
|
-
}, ...this.currentPath);
|
62
|
-
this.pathStart = null;
|
63
|
-
this.currentPath = null;
|
64
|
-
}
|
65
|
-
else {
|
66
|
-
this.addPathToSVG();
|
67
|
-
this.lastPathStart = this.pathStart;
|
68
|
-
this.lastPathStyle = style;
|
69
|
-
this.lastPath = this.currentPath;
|
70
|
-
this.pathStart = null;
|
71
|
-
this.currentPath = null;
|
72
|
-
}
|
46
|
+
this.lastPathString = [];
|
73
47
|
}
|
74
48
|
// Push [this.fullPath] to the SVG
|
75
49
|
addPathToSVG() {
|
76
50
|
var _a;
|
77
|
-
if (!this.lastPathStyle ||
|
51
|
+
if (!this.lastPathStyle || this.lastPathString.length === 0) {
|
78
52
|
return;
|
79
53
|
}
|
80
54
|
const pathElem = document.createElementNS(svgNameSpace, 'path');
|
81
|
-
pathElem.setAttribute('d',
|
55
|
+
pathElem.setAttribute('d', this.lastPathString.join(' '));
|
82
56
|
const style = this.lastPathStyle;
|
83
57
|
pathElem.setAttribute('fill', style.fill.toHexString());
|
84
58
|
if (style.stroke) {
|
@@ -88,6 +62,18 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
88
62
|
this.elem.appendChild(pathElem);
|
89
63
|
(_a = this.objectElems) === null || _a === void 0 ? void 0 : _a.push(pathElem);
|
90
64
|
}
|
65
|
+
drawPath(pathSpec) {
|
66
|
+
var _a;
|
67
|
+
const style = pathSpec.style;
|
68
|
+
const path = Path.fromRenderable(pathSpec);
|
69
|
+
// Try to extend the previous path, if possible
|
70
|
+
if (!style.fill.eq((_a = this.lastPathStyle) === null || _a === void 0 ? void 0 : _a.fill) || this.lastPathString.length === 0) {
|
71
|
+
this.addPathToSVG();
|
72
|
+
this.lastPathStyle = style;
|
73
|
+
this.lastPathString = [];
|
74
|
+
}
|
75
|
+
this.lastPathString.push(path.toString());
|
76
|
+
}
|
91
77
|
drawText(text, transform, style) {
|
92
78
|
var _a, _b, _c;
|
93
79
|
transform = this.getCanvasToScreenTransform().rightMul(transform);
|
@@ -118,8 +104,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
118
104
|
startObject(boundingBox) {
|
119
105
|
super.startObject(boundingBox);
|
120
106
|
// Only accumulate a path within an object
|
121
|
-
this.
|
122
|
-
this.lastPathStart = null;
|
107
|
+
this.lastPathString = [];
|
123
108
|
this.lastPathStyle = null;
|
124
109
|
this.objectElems = [];
|
125
110
|
}
|
@@ -146,40 +131,14 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
146
131
|
}
|
147
132
|
}
|
148
133
|
}
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
}
|
156
|
-
|
157
|
-
point = this.canvasToScreen(point);
|
158
|
-
this.currentPath.push({
|
159
|
-
kind: PathCommandType.MoveTo,
|
160
|
-
point,
|
161
|
-
});
|
162
|
-
}
|
163
|
-
traceCubicBezierCurve(controlPoint1, controlPoint2, endPoint) {
|
164
|
-
controlPoint1 = this.canvasToScreen(controlPoint1);
|
165
|
-
controlPoint2 = this.canvasToScreen(controlPoint2);
|
166
|
-
endPoint = this.canvasToScreen(endPoint);
|
167
|
-
this.currentPath.push({
|
168
|
-
kind: PathCommandType.CubicBezierTo,
|
169
|
-
controlPoint1,
|
170
|
-
controlPoint2,
|
171
|
-
endPoint,
|
172
|
-
});
|
173
|
-
}
|
174
|
-
traceQuadraticBezierCurve(controlPoint, endPoint) {
|
175
|
-
controlPoint = this.canvasToScreen(controlPoint);
|
176
|
-
endPoint = this.canvasToScreen(endPoint);
|
177
|
-
this.currentPath.push({
|
178
|
-
kind: PathCommandType.QuadraticBezierTo,
|
179
|
-
controlPoint,
|
180
|
-
endPoint,
|
181
|
-
});
|
182
|
-
}
|
134
|
+
// Not implemented -- use drawPath instead.
|
135
|
+
unimplementedMessage() { throw new Error('Not implemenented!'); }
|
136
|
+
beginPath(_startPoint) { this.unimplementedMessage(); }
|
137
|
+
endPath(_style) { this.unimplementedMessage(); }
|
138
|
+
lineTo(_point) { this.unimplementedMessage(); }
|
139
|
+
moveTo(_point) { this.unimplementedMessage(); }
|
140
|
+
traceCubicBezierCurve(_controlPoint1, _controlPoint2, _endPoint) { this.unimplementedMessage(); }
|
141
|
+
traceQuadraticBezierCurve(_controlPoint, _endPoint) { this.unimplementedMessage(); }
|
183
142
|
drawPoints(...points) {
|
184
143
|
points.map(point => {
|
185
144
|
const elem = document.createElementNS(svgNameSpace, 'circle');
|
@@ -9,6 +9,7 @@ export default class HTMLToolbar {
|
|
9
9
|
private container;
|
10
10
|
private static colorisStarted;
|
11
11
|
private updateColoris;
|
12
|
+
/** @internal */
|
12
13
|
constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
|
13
14
|
setupColorPickers(): void;
|
14
15
|
addWidget(widget: BaseWidget): void;
|
@@ -14,6 +14,7 @@ import HandToolWidget from './widgets/HandToolWidget';
|
|
14
14
|
import { EraserTool, PenTool } from '../tools/lib';
|
15
15
|
export const toolbarCSSPrefix = 'toolbar-';
|
16
16
|
export default class HTMLToolbar {
|
17
|
+
/** @internal */
|
17
18
|
constructor(editor, parent, localizationTable = defaultToolbarLocalization) {
|
18
19
|
this.editor = editor;
|
19
20
|
this.localizationTable = localizationTable;
|
@@ -12,6 +12,7 @@ export default abstract class BaseWidget {
|
|
12
12
|
private label;
|
13
13
|
private disabled;
|
14
14
|
private subWidgets;
|
15
|
+
private toplevel;
|
15
16
|
constructor(editor: Editor, localizationTable: ToolbarLocalization);
|
16
17
|
protected abstract getTitle(): string;
|
17
18
|
protected abstract createIcon(): Element;
|
@@ -26,6 +27,8 @@ export default abstract class BaseWidget {
|
|
26
27
|
setSelected(selected: boolean): void;
|
27
28
|
protected setDropdownVisible(visible: boolean): void;
|
28
29
|
protected repositionDropdown(): void;
|
30
|
+
/** Set whether the widget is contained within another. @internal */
|
31
|
+
protected setIsToplevel(toplevel: boolean): void;
|
29
32
|
protected isDropdownVisible(): boolean;
|
30
33
|
protected isSelected(): boolean;
|
31
34
|
private createDropdownIcon;
|
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
11
11
|
};
|
12
12
|
var _BaseWidget_hasDropdown;
|
13
|
-
import { InputEvtType } from '../../types';
|
13
|
+
import { EditorEventType, InputEvtType } from '../../types';
|
14
14
|
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
15
15
|
import { makeDropdownIcon } from '../icons';
|
16
16
|
export default class BaseWidget {
|
@@ -20,6 +20,7 @@ export default class BaseWidget {
|
|
20
20
|
_BaseWidget_hasDropdown.set(this, void 0);
|
21
21
|
this.disabled = false;
|
22
22
|
this.subWidgets = [];
|
23
|
+
this.toplevel = true;
|
23
24
|
this.icon = null;
|
24
25
|
this.container = document.createElement('div');
|
25
26
|
this.container.classList.add(`${toolbarCSSPrefix}toolContainer`);
|
@@ -41,6 +42,7 @@ export default class BaseWidget {
|
|
41
42
|
}
|
42
43
|
for (const widget of this.subWidgets) {
|
43
44
|
widget.addTo(dropdown);
|
45
|
+
widget.setIsToplevel(false);
|
44
46
|
}
|
45
47
|
return true;
|
46
48
|
}
|
@@ -87,6 +89,7 @@ export default class BaseWidget {
|
|
87
89
|
this.subWidgets.push(widget);
|
88
90
|
}
|
89
91
|
// Adds this to [parent]. This can only be called once for each ToolbarWidget.
|
92
|
+
// @internal
|
90
93
|
addTo(parent) {
|
91
94
|
this.label.innerText = this.getTitle();
|
92
95
|
this.setupActionBtnClickListener(this.button);
|
@@ -99,6 +102,15 @@ export default class BaseWidget {
|
|
99
102
|
this.dropdownIcon = this.createDropdownIcon();
|
100
103
|
this.button.appendChild(this.dropdownIcon);
|
101
104
|
this.container.appendChild(this.dropdownContainer);
|
105
|
+
this.editor.notifier.on(EditorEventType.ToolbarDropdownShown, (evt) => {
|
106
|
+
if (evt.kind === EditorEventType.ToolbarDropdownShown
|
107
|
+
&& evt.parentWidget !== this
|
108
|
+
// Don't hide if a submenu wash shown (it might be a submenu of
|
109
|
+
// the current menu).
|
110
|
+
&& evt.parentWidget.toplevel) {
|
111
|
+
this.setDropdownVisible(false);
|
112
|
+
}
|
113
|
+
});
|
102
114
|
}
|
103
115
|
this.setDropdownVisible(false);
|
104
116
|
parent.appendChild(this.container);
|
@@ -144,6 +156,10 @@ export default class BaseWidget {
|
|
144
156
|
this.dropdownContainer.classList.remove('hidden');
|
145
157
|
this.container.classList.add('dropdownVisible');
|
146
158
|
this.editor.announceForAccessibility(this.localizationTable.dropdownShown(this.getTitle()));
|
159
|
+
this.editor.notifier.dispatch(EditorEventType.ToolbarDropdownShown, {
|
160
|
+
kind: EditorEventType.ToolbarDropdownShown,
|
161
|
+
parentWidget: this,
|
162
|
+
});
|
147
163
|
}
|
148
164
|
else {
|
149
165
|
this.dropdownContainer.classList.add('hidden');
|
@@ -164,6 +180,10 @@ export default class BaseWidget {
|
|
164
180
|
this.dropdownContainer.style.transform = '';
|
165
181
|
}
|
166
182
|
}
|
183
|
+
/** Set whether the widget is contained within another. @internal */
|
184
|
+
setIsToplevel(toplevel) {
|
185
|
+
this.toplevel = toplevel;
|
186
|
+
}
|
167
187
|
isDropdownVisible() {
|
168
188
|
return !this.dropdownContainer.classList.contains('hidden');
|
169
189
|
}
|