js-draw 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/translation.md +4 -1
- package/CHANGELOG.md +16 -0
- package/README.md +1 -3
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +15 -1
- package/dist/src/Editor.js +221 -78
- package/dist/src/EditorImage.js +4 -1
- package/dist/src/Pointer.d.ts +1 -1
- package/dist/src/Pointer.js +8 -3
- package/dist/src/SVGLoader.d.ts +4 -1
- package/dist/src/SVGLoader.js +78 -33
- package/dist/src/UndoRedoHistory.d.ts +1 -0
- package/dist/src/UndoRedoHistory.js +6 -0
- package/dist/src/Viewport.d.ts +2 -0
- package/dist/src/Viewport.js +26 -5
- package/dist/src/commands/lib.d.ts +2 -1
- package/dist/src/commands/lib.js +2 -1
- package/dist/src/commands/localization.d.ts +1 -0
- package/dist/src/commands/localization.js +1 -0
- package/dist/src/commands/uniteCommands.d.ts +4 -0
- package/dist/src/commands/uniteCommands.js +105 -0
- package/dist/src/components/AbstractComponent.d.ts +2 -0
- package/dist/src/components/AbstractComponent.js +41 -5
- package/dist/src/components/ImageComponent.d.ts +27 -0
- package/dist/src/components/ImageComponent.js +129 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +2 -2
- package/dist/src/components/lib.d.ts +4 -2
- package/dist/src/components/lib.js +4 -2
- package/dist/src/components/localization.d.ts +2 -0
- package/dist/src/components/localization.js +2 -0
- package/dist/src/language/assertions.d.ts +1 -0
- package/dist/src/language/assertions.js +5 -0
- package/dist/src/math/LineSegment2.d.ts +2 -0
- package/dist/src/math/LineSegment2.js +3 -0
- package/dist/src/math/Mat33.d.ts +38 -2
- package/dist/src/math/Mat33.js +30 -1
- package/dist/src/math/Path.d.ts +1 -1
- package/dist/src/math/Path.js +10 -8
- package/dist/src/math/Vec3.d.ts +11 -1
- package/dist/src/math/Vec3.js +15 -0
- package/dist/src/math/rounding.d.ts +1 -0
- package/dist/src/math/rounding.js +13 -6
- package/dist/src/rendering/localization.d.ts +3 -0
- package/dist/src/rendering/localization.js +3 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +7 -0
- package/dist/src/rendering/renderers/AbstractRenderer.js +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +7 -0
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -1
- package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +5 -2
- package/dist/src/rendering/renderers/SVGRenderer.js +45 -20
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +3 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +8 -1
- package/dist/src/toolbar/HTMLToolbar.js +5 -4
- package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +1 -1
- package/dist/src/tools/BaseTool.d.ts +3 -1
- package/dist/src/tools/BaseTool.js +6 -0
- package/dist/src/tools/PasteHandler.d.ts +16 -0
- package/dist/src/tools/PasteHandler.js +144 -0
- package/dist/src/tools/Pen.js +1 -1
- package/dist/src/tools/SelectionTool/Selection.d.ts +54 -0
- package/dist/src/tools/SelectionTool/Selection.js +337 -0
- package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +35 -0
- package/dist/src/tools/SelectionTool/SelectionHandle.js +75 -0
- package/dist/src/tools/SelectionTool/SelectionTool.d.ts +31 -0
- package/dist/src/tools/SelectionTool/SelectionTool.js +276 -0
- package/dist/src/tools/SelectionTool/TransformMode.d.ts +34 -0
- package/dist/src/tools/SelectionTool/TransformMode.js +98 -0
- package/dist/src/tools/SelectionTool/types.d.ts +9 -0
- package/dist/src/tools/SelectionTool/types.js +11 -0
- package/dist/src/tools/ToolController.js +37 -28
- package/dist/src/tools/lib.d.ts +2 -1
- package/dist/src/tools/lib.js +2 -1
- package/dist/src/tools/localization.d.ts +3 -0
- package/dist/src/tools/localization.js +3 -0
- package/dist/src/types.d.ts +14 -3
- package/dist/src/types.js +2 -0
- package/package.json +1 -1
- package/src/Editor.css +1 -0
- package/src/Editor.ts +275 -109
- package/src/EditorImage.ts +7 -1
- package/src/Pointer.ts +8 -3
- package/src/SVGLoader.ts +90 -36
- package/src/UndoRedoHistory.test.ts +33 -0
- package/src/UndoRedoHistory.ts +8 -0
- package/src/Viewport.ts +30 -6
- package/src/commands/lib.ts +2 -0
- package/src/commands/localization.ts +2 -0
- package/src/commands/uniteCommands.test.ts +23 -0
- package/src/commands/uniteCommands.ts +121 -0
- package/src/components/AbstractComponent.ts +53 -11
- package/src/components/ImageComponent.ts +149 -0
- package/src/components/Text.ts +2 -6
- package/src/components/builders/FreehandLineBuilder.ts +2 -2
- package/src/components/lib.ts +7 -2
- package/src/components/localization.ts +4 -0
- package/src/language/assertions.ts +6 -0
- package/src/math/LineSegment2.test.ts +9 -0
- package/src/math/LineSegment2.ts +5 -0
- package/src/math/Mat33.test.ts +14 -0
- package/src/math/Mat33.ts +43 -2
- package/src/math/Path.toString.test.ts +12 -1
- package/src/math/Path.ts +11 -9
- package/src/math/Vec3.ts +22 -1
- package/src/math/rounding.test.ts +30 -5
- package/src/math/rounding.ts +16 -7
- package/src/rendering/localization.ts +6 -0
- package/src/rendering/renderers/AbstractRenderer.ts +19 -2
- package/src/rendering/renderers/CanvasRenderer.ts +10 -1
- package/src/rendering/renderers/DummyRenderer.ts +6 -1
- package/src/rendering/renderers/SVGRenderer.ts +50 -21
- package/src/rendering/renderers/TextOnlyRenderer.ts +10 -2
- package/src/toolbar/HTMLToolbar.ts +5 -4
- package/src/toolbar/widgets/SelectionToolWidget.ts +1 -1
- package/src/tools/BaseTool.ts +9 -1
- package/src/tools/PasteHandler.ts +159 -0
- package/src/tools/Pen.ts +1 -1
- package/src/tools/SelectionTool/Selection.ts +455 -0
- package/src/tools/SelectionTool/SelectionHandle.ts +99 -0
- package/src/tools/SelectionTool/SelectionTool.css +22 -0
- package/src/tools/{SelectionTool.test.ts → SelectionTool/SelectionTool.test.ts} +21 -21
- package/src/tools/SelectionTool/SelectionTool.ts +335 -0
- package/src/tools/SelectionTool/TransformMode.ts +114 -0
- package/src/tools/SelectionTool/types.ts +11 -0
- package/src/tools/ToolController.ts +52 -45
- package/src/tools/lib.ts +2 -1
- package/src/tools/localization.ts +8 -0
- package/src/types.ts +17 -3
- package/dist/src/tools/SelectionTool.d.ts +0 -59
- package/dist/src/tools/SelectionTool.js +0 -589
- package/src/tools/SelectionTool.ts +0 -725
package/dist/src/math/Path.d.ts
CHANGED
@@ -55,7 +55,7 @@ export default class Path {
|
|
55
55
|
static fromRenderable(renderable: RenderablePathSpec): Path;
|
56
56
|
toRenderable(fill: RenderingStyle): RenderablePathSpec;
|
57
57
|
private cachedStringVersion;
|
58
|
-
toString(): string;
|
58
|
+
toString(useNonAbsCommands?: boolean): string;
|
59
59
|
serialize(): string;
|
60
60
|
static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands?: boolean): string;
|
61
61
|
static fromString(pathString: string): Path;
|
package/dist/src/math/Path.js
CHANGED
@@ -282,13 +282,15 @@ export default class Path {
|
|
282
282
|
path: this,
|
283
283
|
};
|
284
284
|
}
|
285
|
-
toString() {
|
285
|
+
toString(useNonAbsCommands) {
|
286
286
|
if (this.cachedStringVersion) {
|
287
287
|
return this.cachedStringVersion;
|
288
288
|
}
|
289
|
-
|
290
|
-
|
291
|
-
|
289
|
+
if (useNonAbsCommands === undefined) {
|
290
|
+
// Hueristic: Try to determine whether converting absolute to relative commands is worth it.
|
291
|
+
useNonAbsCommands = Math.abs(this.bbox.topLeft.x) > 10 && Math.abs(this.bbox.topLeft.y) > 10;
|
292
|
+
}
|
293
|
+
const result = Path.toString(this.startPoint, this.parts, !useNonAbsCommands);
|
292
294
|
this.cachedStringVersion = result;
|
293
295
|
return result;
|
294
296
|
}
|
@@ -307,10 +309,12 @@ export default class Path {
|
|
307
309
|
const roundedPrevX = prevPoint ? toRoundedString(prevPoint.x) : '';
|
308
310
|
const roundedPrevY = prevPoint ? toRoundedString(prevPoint.y) : '';
|
309
311
|
for (const point of points) {
|
312
|
+
const xComponent = toRoundedString(point.x);
|
313
|
+
const yComponent = toRoundedString(point.y);
|
310
314
|
// Relative commands are often shorter as strings than absolute commands.
|
311
315
|
if (!makeAbsCommand) {
|
312
|
-
const xComponentRelative = toStringOfSamePrecision(point.x - prevPoint.x, roundedPrevX, roundedPrevY);
|
313
|
-
const yComponentRelative = toStringOfSamePrecision(point.y - prevPoint.y, roundedPrevX, roundedPrevY);
|
316
|
+
const xComponentRelative = toStringOfSamePrecision(point.x - prevPoint.x, xComponent, roundedPrevX, roundedPrevY);
|
317
|
+
const yComponentRelative = toStringOfSamePrecision(point.y - prevPoint.y, yComponent, roundedPrevX, roundedPrevY);
|
314
318
|
// No need for an additional separator if it starts with a '-'
|
315
319
|
if (yComponentRelative.charAt(0) === '-') {
|
316
320
|
relativeCommandParts.push(`${xComponentRelative}${yComponentRelative}`);
|
@@ -320,8 +324,6 @@ export default class Path {
|
|
320
324
|
}
|
321
325
|
}
|
322
326
|
else {
|
323
|
-
const xComponent = toRoundedString(point.x);
|
324
|
-
const yComponent = toRoundedString(point.y);
|
325
327
|
absoluteCommandParts.push(`${xComponent},${yComponent}`);
|
326
328
|
}
|
327
329
|
}
|
package/dist/src/math/Vec3.d.ts
CHANGED
@@ -38,6 +38,16 @@ export default class Vec3 {
|
|
38
38
|
minus(v: Vec3): Vec3;
|
39
39
|
dot(other: Vec3): number;
|
40
40
|
cross(other: Vec3): Vec3;
|
41
|
+
/**
|
42
|
+
* If `other` is a `Vec3`, multiplies `this` component-wise by `other`. Otherwise,
|
43
|
+
* if `other is a `number`, returns the result of scalar multiplication.
|
44
|
+
*
|
45
|
+
* @example
|
46
|
+
* ```
|
47
|
+
* Vec3.of(1, 2, 3).scale(Vec3.of(2, 4, 6)); // → Vec3(2, 8, 18)
|
48
|
+
* ```
|
49
|
+
*/
|
50
|
+
scale(other: Vec3 | number): Vec3;
|
41
51
|
/**
|
42
52
|
* Returns a vector orthogonal to this. If this is a Vec2, returns `this` rotated
|
43
53
|
* 90 degrees counter-clockwise.
|
@@ -73,7 +83,7 @@ export default class Vec3 {
|
|
73
83
|
* ```
|
74
84
|
*/
|
75
85
|
map(fn: (component: number, index: number) => number): Vec3;
|
76
|
-
asArray(): number
|
86
|
+
asArray(): [number, number, number];
|
77
87
|
/**
|
78
88
|
* [fuzz] The maximum difference between two components for this and [other]
|
79
89
|
* to be considered equal.
|
package/dist/src/math/Vec3.js
CHANGED
@@ -76,6 +76,21 @@ export default class Vec3 {
|
|
76
76
|
// | x2 y2 z2|
|
77
77
|
return Vec3.of(this.y * other.z - other.y * this.z, other.x * this.z - this.x * other.z, this.x * other.y - other.x * this.y);
|
78
78
|
}
|
79
|
+
/**
|
80
|
+
* If `other` is a `Vec3`, multiplies `this` component-wise by `other`. Otherwise,
|
81
|
+
* if `other is a `number`, returns the result of scalar multiplication.
|
82
|
+
*
|
83
|
+
* @example
|
84
|
+
* ```
|
85
|
+
* Vec3.of(1, 2, 3).scale(Vec3.of(2, 4, 6)); // → Vec3(2, 8, 18)
|
86
|
+
* ```
|
87
|
+
*/
|
88
|
+
scale(other) {
|
89
|
+
if (typeof other === 'number') {
|
90
|
+
return this.times(other);
|
91
|
+
}
|
92
|
+
return Vec3.of(this.x * other.x, this.y * other.y, this.z * other.z);
|
93
|
+
}
|
79
94
|
/**
|
80
95
|
* Returns a vector orthogonal to this. If this is a Vec2, returns `this` rotated
|
81
96
|
* 90 degrees counter-clockwise.
|
@@ -1,3 +1,4 @@
|
|
1
|
+
export declare const cleanUpNumber: (text: string) => string;
|
1
2
|
export declare const toRoundedString: (num: number) => string;
|
2
3
|
export declare const getLenAfterDecimal: (numberAsString: string) => number;
|
3
4
|
export declare const toStringOfSamePrecision: (num: number, ...references: string[]) => string;
|
@@ -1,8 +1,14 @@
|
|
1
1
|
// @packageDocumentation @internal
|
2
2
|
// Clean up stringified numbers
|
3
|
-
const cleanUpNumber = (text) => {
|
3
|
+
export const cleanUpNumber = (text) => {
|
4
4
|
// Regular expression substitions can be somewhat expensive. Only do them
|
5
5
|
// if necessary.
|
6
|
+
if (text.indexOf('e') > 0) {
|
7
|
+
// Round to zero.
|
8
|
+
if (text.match(/[eE][-]\d{2,}$/)) {
|
9
|
+
return '0';
|
10
|
+
}
|
11
|
+
}
|
6
12
|
const lastChar = text.charAt(text.length - 1);
|
7
13
|
if (lastChar === '0' || lastChar === '.') {
|
8
14
|
// Remove trailing zeroes
|
@@ -10,23 +16,24 @@ const cleanUpNumber = (text) => {
|
|
10
16
|
text = text.replace(/[.]0+$/, '.');
|
11
17
|
// Remove trailing period
|
12
18
|
text = text.replace(/[.]$/, '');
|
13
|
-
if (text === '-0') {
|
14
|
-
return '0';
|
15
|
-
}
|
16
19
|
}
|
17
20
|
const firstChar = text.charAt(0);
|
18
21
|
if (firstChar === '0' || firstChar === '-') {
|
19
22
|
// Remove unnecessary leading zeroes.
|
20
23
|
text = text.replace(/^(0+)[.]/, '.');
|
21
24
|
text = text.replace(/^-(0+)[.]/, '-.');
|
25
|
+
text = text.replace(/^(-?)0+$/, '$10');
|
26
|
+
}
|
27
|
+
if (text === '-0') {
|
28
|
+
return '0';
|
22
29
|
}
|
23
30
|
return text;
|
24
31
|
};
|
25
32
|
export const toRoundedString = (num) => {
|
26
33
|
// Try to remove rounding errors. If the number ends in at least three/four zeroes
|
27
34
|
// (or nines) just one or two digits, it's probably a rounding error.
|
28
|
-
const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d{1,
|
29
|
-
const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,})\d{1,
|
35
|
+
const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d{1,4}$/;
|
36
|
+
const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,})\d{1,4}$/;
|
30
37
|
let text = num.toString(10);
|
31
38
|
if (text.indexOf('.') === -1) {
|
32
39
|
return text;
|
@@ -1,7 +1,10 @@
|
|
1
1
|
export interface TextRendererLocalization {
|
2
2
|
pathNodeCount(pathCount: number): string;
|
3
3
|
textNodeCount(nodeCount: number): string;
|
4
|
+
imageNodeCount(nodeCount: number): string;
|
4
5
|
textNode(content: string): string;
|
6
|
+
unlabeledImageNode: string;
|
7
|
+
imageNode(label: string): string;
|
5
8
|
rerenderAsText: string;
|
6
9
|
}
|
7
10
|
export declare const defaultTextRendererLocalization: TextRendererLocalization;
|
@@ -1,6 +1,9 @@
|
|
1
1
|
export const defaultTextRendererLocalization = {
|
2
2
|
pathNodeCount: (count) => `There are ${count} visible path objects.`,
|
3
3
|
textNodeCount: (count) => `There are ${count} visible text nodes.`,
|
4
|
+
imageNodeCount: (nodeCount) => `There are ${nodeCount} visible image nodes.`,
|
4
5
|
textNode: (content) => `Text: ${content}`,
|
6
|
+
imageNode: (label) => `Image: ${label}`,
|
7
|
+
unlabeledImageNode: 'Unlabeled image',
|
5
8
|
rerenderAsText: 'Re-render as text',
|
6
9
|
};
|
@@ -12,6 +12,12 @@ export interface RenderablePathSpec {
|
|
12
12
|
style: RenderingStyle;
|
13
13
|
path?: Path;
|
14
14
|
}
|
15
|
+
export interface RenderableImage {
|
16
|
+
transform: Mat33;
|
17
|
+
image: HTMLImageElement | HTMLCanvasElement;
|
18
|
+
base64Url: string;
|
19
|
+
label?: string;
|
20
|
+
}
|
15
21
|
export default abstract class AbstractRenderer {
|
16
22
|
private viewport;
|
17
23
|
private selfTransform;
|
@@ -27,6 +33,7 @@ export default abstract class AbstractRenderer {
|
|
27
33
|
protected abstract traceCubicBezierCurve(p1: Point2, p2: Point2, p3: Point2): void;
|
28
34
|
protected abstract traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2): void;
|
29
35
|
abstract drawText(text: string, transform: Mat33, style: TextStyle): void;
|
36
|
+
abstract drawImage(image: RenderableImage): void;
|
30
37
|
abstract isTooSmallToRender(rect: Rect2): boolean;
|
31
38
|
setDraftMode(_draftMode: boolean): void;
|
32
39
|
protected objectLevel: number;
|
@@ -64,7 +64,8 @@ export default class AbstractRenderer {
|
|
64
64
|
this.currentPaths.push(path);
|
65
65
|
}
|
66
66
|
}
|
67
|
-
// Draw a rectangle. Boundary lines have width [lineWidth] and are filled with [lineFill]
|
67
|
+
// Draw a rectangle. Boundary lines have width [lineWidth] and are filled with [lineFill].
|
68
|
+
// This is equivalent to `drawPath(Path.fromRect(...).toRenderable(...))`.
|
68
69
|
drawRect(rect, lineWidth, lineFill) {
|
69
70
|
const path = Path.fromRect(rect, lineWidth);
|
70
71
|
this.drawPath(path.toRenderable(lineFill));
|
@@ -5,7 +5,7 @@ import { Point2, Vec2 } from '../../math/Vec2';
|
|
5
5
|
import Vec3 from '../../math/Vec3';
|
6
6
|
import Viewport from '../../Viewport';
|
7
7
|
import RenderingStyle from '../RenderingStyle';
|
8
|
-
import AbstractRenderer, { RenderablePathSpec } from './AbstractRenderer';
|
8
|
+
import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
|
9
9
|
export default class CanvasRenderer extends AbstractRenderer {
|
10
10
|
private ctx;
|
11
11
|
private ignoreObjectsAboveLevel;
|
@@ -28,6 +28,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
28
28
|
protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3): void;
|
29
29
|
drawPath(path: RenderablePathSpec): void;
|
30
30
|
drawText(text: string, transform: Mat33, style: TextStyle): void;
|
31
|
+
drawImage(image: RenderableImage): void;
|
31
32
|
private clipLevels;
|
32
33
|
startObject(boundingBox: Rect2, clip: boolean): void;
|
33
34
|
endObject(): void;
|
@@ -125,6 +125,13 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
125
125
|
}
|
126
126
|
this.ctx.restore();
|
127
127
|
}
|
128
|
+
drawImage(image) {
|
129
|
+
this.ctx.save();
|
130
|
+
const transform = this.getCanvasToScreenTransform().rightMul(image.transform);
|
131
|
+
this.transformBy(transform);
|
132
|
+
this.ctx.drawImage(image.image, 0, 0);
|
133
|
+
this.ctx.restore();
|
134
|
+
}
|
128
135
|
startObject(boundingBox, clip) {
|
129
136
|
if (this.isTooSmallToRender(boundingBox)) {
|
130
137
|
this.ignoreObjectsAboveLevel = this.getNestingLevel();
|
@@ -5,7 +5,7 @@ import { Point2, Vec2 } from '../../math/Vec2';
|
|
5
5
|
import Vec3 from '../../math/Vec3';
|
6
6
|
import Viewport from '../../Viewport';
|
7
7
|
import RenderingStyle from '../RenderingStyle';
|
8
|
-
import AbstractRenderer from './AbstractRenderer';
|
8
|
+
import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
|
9
9
|
export default class DummyRenderer extends AbstractRenderer {
|
10
10
|
clearedCount: number;
|
11
11
|
renderedPathCount: number;
|
@@ -13,6 +13,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
13
13
|
lastPoint: Point2 | null;
|
14
14
|
objectNestingLevel: number;
|
15
15
|
lastText: string | null;
|
16
|
+
lastImage: RenderableImage | null;
|
16
17
|
pointBuffer: Point2[];
|
17
18
|
constructor(viewport: Viewport);
|
18
19
|
displaySize(): Vec2;
|
@@ -25,6 +26,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
25
26
|
protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3): void;
|
26
27
|
drawPoints(..._points: Vec3[]): void;
|
27
28
|
drawText(text: string, _transform: Mat33, _style: TextStyle): void;
|
29
|
+
drawImage(image: RenderableImage): void;
|
28
30
|
startObject(boundingBox: Rect2, _clip: boolean): void;
|
29
31
|
endObject(): void;
|
30
32
|
isTooSmallToRender(_rect: Rect2): boolean;
|
@@ -11,6 +11,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
11
11
|
this.lastPoint = null;
|
12
12
|
this.objectNestingLevel = 0;
|
13
13
|
this.lastText = null;
|
14
|
+
this.lastImage = null;
|
14
15
|
// List of points drawn since the last clear.
|
15
16
|
this.pointBuffer = [];
|
16
17
|
}
|
@@ -30,6 +31,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
30
31
|
this.renderedPathCount = 0;
|
31
32
|
this.pointBuffer = [];
|
32
33
|
this.lastText = null;
|
34
|
+
this.lastImage = null;
|
33
35
|
// Ensure all objects finished rendering
|
34
36
|
if (this.objectNestingLevel > 0) {
|
35
37
|
throw new Error(`Within an object while clearing! Nesting level: ${this.objectNestingLevel}`);
|
@@ -73,6 +75,9 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
73
75
|
drawText(text, _transform, _style) {
|
74
76
|
this.lastText = text;
|
75
77
|
}
|
78
|
+
drawImage(image) {
|
79
|
+
this.lastImage = image;
|
80
|
+
}
|
76
81
|
startObject(boundingBox, _clip) {
|
77
82
|
super.startObject(boundingBox);
|
78
83
|
this.objectNestingLevel += 1;
|
@@ -5,20 +5,23 @@ 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, { RenderablePathSpec } from './AbstractRenderer';
|
8
|
+
import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
|
9
9
|
export default class SVGRenderer extends AbstractRenderer {
|
10
10
|
private elem;
|
11
|
+
private sanitize;
|
11
12
|
private lastPathStyle;
|
12
13
|
private lastPathString;
|
13
14
|
private objectElems;
|
14
15
|
private overwrittenAttrs;
|
15
|
-
constructor(elem: SVGSVGElement, viewport: Viewport);
|
16
|
+
constructor(elem: SVGSVGElement, viewport: Viewport, sanitize?: boolean);
|
16
17
|
setRootSVGAttribute(name: string, value: string | null): void;
|
17
18
|
displaySize(): Vec2;
|
18
19
|
clear(): void;
|
19
20
|
private addPathToSVG;
|
20
21
|
drawPath(pathSpec: RenderablePathSpec): void;
|
22
|
+
private transformFrom;
|
21
23
|
drawText(text: string, transform: Mat33, style: TextStyle): void;
|
24
|
+
drawImage(image: RenderableImage): void;
|
22
25
|
startObject(boundingBox: Rect2): void;
|
23
26
|
endObject(loaderData?: LoadSaveDataTable): void;
|
24
27
|
private unimplementedMessage;
|
@@ -6,9 +6,11 @@ import { svgAttributesDataKey, svgStyleAttributesDataKey } from '../../SVGLoader
|
|
6
6
|
import AbstractRenderer from './AbstractRenderer';
|
7
7
|
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
8
8
|
export default class SVGRenderer extends AbstractRenderer {
|
9
|
-
|
9
|
+
// Renders onto `elem`. If `sanitize`, don't render potentially untrusted data.
|
10
|
+
constructor(elem, viewport, sanitize = false) {
|
10
11
|
super(viewport);
|
11
12
|
this.elem = elem;
|
13
|
+
this.sanitize = sanitize;
|
12
14
|
this.lastPathStyle = null;
|
13
15
|
this.lastPathString = [];
|
14
16
|
this.objectElems = null;
|
@@ -17,6 +19,9 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
17
19
|
}
|
18
20
|
// Sets an attribute on the root SVG element.
|
19
21
|
setRootSVGAttribute(name, value) {
|
22
|
+
if (this.sanitize) {
|
23
|
+
return;
|
24
|
+
}
|
20
25
|
// Make the original value of the attribute restorable on clear
|
21
26
|
if (!(name in this.overwrittenAttrs)) {
|
22
27
|
this.overwrittenAttrs[name] = this.elem.getAttribute(name);
|
@@ -32,18 +37,20 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
32
37
|
return Vec2.of(this.elem.clientWidth, this.elem.clientHeight);
|
33
38
|
}
|
34
39
|
clear() {
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
this.
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
this.lastPathString = [];
|
41
|
+
if (!this.sanitize) {
|
42
|
+
// Restore all all attributes
|
43
|
+
for (const attrName in this.overwrittenAttrs) {
|
44
|
+
const value = this.overwrittenAttrs[attrName];
|
45
|
+
if (value) {
|
46
|
+
this.elem.setAttribute(attrName, value);
|
47
|
+
}
|
48
|
+
else {
|
49
|
+
this.elem.removeAttribute(attrName);
|
50
|
+
}
|
43
51
|
}
|
52
|
+
this.overwrittenAttrs = {};
|
44
53
|
}
|
45
|
-
this.overwrittenAttrs = {};
|
46
|
-
this.lastPathString = [];
|
47
54
|
}
|
48
55
|
// Push [this.fullPath] to the SVG
|
49
56
|
addPathToSVG() {
|
@@ -74,25 +81,29 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
74
81
|
}
|
75
82
|
this.lastPathString.push(path.toString());
|
76
83
|
}
|
77
|
-
|
78
|
-
|
79
|
-
transform = this.getCanvasToScreenTransform().rightMul(
|
84
|
+
// Apply [elemTransform] to [elem].
|
85
|
+
transformFrom(elemTransform, elem) {
|
86
|
+
let transform = this.getCanvasToScreenTransform().rightMul(elemTransform);
|
80
87
|
const translation = transform.transformVec2(Vec2.zero);
|
81
88
|
transform = transform.rightMul(Mat33.translation(translation.times(-1)));
|
82
|
-
|
83
|
-
textElem.appendChild(document.createTextNode(text));
|
84
|
-
textElem.style.transform = `matrix(
|
89
|
+
elem.style.transform = `matrix(
|
85
90
|
${transform.a1}, ${transform.b1},
|
86
91
|
${transform.a2}, ${transform.b2},
|
87
92
|
${transform.a3}, ${transform.b3}
|
88
93
|
)`;
|
94
|
+
elem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
95
|
+
elem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
96
|
+
}
|
97
|
+
drawText(text, transform, style) {
|
98
|
+
var _a, _b, _c;
|
99
|
+
const textElem = document.createElementNS(svgNameSpace, 'text');
|
100
|
+
textElem.appendChild(document.createTextNode(text));
|
101
|
+
this.transformFrom(transform, textElem);
|
89
102
|
textElem.style.fontFamily = style.fontFamily;
|
90
103
|
textElem.style.fontVariant = (_a = style.fontVariant) !== null && _a !== void 0 ? _a : '';
|
91
104
|
textElem.style.fontWeight = (_b = style.fontWeight) !== null && _b !== void 0 ? _b : '';
|
92
105
|
textElem.style.fontSize = style.size + 'px';
|
93
106
|
textElem.style.fill = style.renderingStyle.fill.toHexString();
|
94
|
-
textElem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
95
|
-
textElem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
96
107
|
if (style.renderingStyle.stroke) {
|
97
108
|
const strokeStyle = style.renderingStyle.stroke;
|
98
109
|
textElem.style.stroke = strokeStyle.color.toHexString();
|
@@ -101,6 +112,17 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
101
112
|
this.elem.appendChild(textElem);
|
102
113
|
(_c = this.objectElems) === null || _c === void 0 ? void 0 : _c.push(textElem);
|
103
114
|
}
|
115
|
+
drawImage(image) {
|
116
|
+
var _a, _b, _c, _d, _e;
|
117
|
+
const svgImgElem = document.createElementNS(svgNameSpace, 'image');
|
118
|
+
svgImgElem.setAttribute('href', image.base64Url);
|
119
|
+
svgImgElem.setAttribute('width', (_a = image.image.getAttribute('width')) !== null && _a !== void 0 ? _a : '');
|
120
|
+
svgImgElem.setAttribute('height', (_b = image.image.getAttribute('height')) !== null && _b !== void 0 ? _b : '');
|
121
|
+
svgImgElem.setAttribute('aria-label', (_d = (_c = image.image.getAttribute('aria-label')) !== null && _c !== void 0 ? _c : image.image.getAttribute('alt')) !== null && _d !== void 0 ? _d : '');
|
122
|
+
this.transformFrom(image.transform, svgImgElem);
|
123
|
+
this.elem.appendChild(svgImgElem);
|
124
|
+
(_e = this.objectElems) === null || _e === void 0 ? void 0 : _e.push(svgImgElem);
|
125
|
+
}
|
104
126
|
startObject(boundingBox) {
|
105
127
|
super.startObject(boundingBox);
|
106
128
|
// Only accumulate a path within an object
|
@@ -113,7 +135,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
113
135
|
super.endObject(loaderData);
|
114
136
|
// Don't extend paths across objects
|
115
137
|
this.addPathToSVG();
|
116
|
-
if (loaderData) {
|
138
|
+
if (loaderData && !this.sanitize) {
|
117
139
|
// Restore any attributes unsupported by the app.
|
118
140
|
for (const elem of (_a = this.objectElems) !== null && _a !== void 0 ? _a : []) {
|
119
141
|
const attrs = loaderData[svgAttributesDataKey];
|
@@ -150,6 +172,9 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
150
172
|
}
|
151
173
|
// Renders a **copy** of the given element.
|
152
174
|
drawSVGElem(elem) {
|
175
|
+
if (this.sanitize) {
|
176
|
+
return;
|
177
|
+
}
|
153
178
|
this.elem.appendChild(elem.cloneNode(true));
|
154
179
|
}
|
155
180
|
isTooSmallToRender(_rect) {
|
@@ -5,12 +5,13 @@ import Vec3 from '../../math/Vec3';
|
|
5
5
|
import Viewport from '../../Viewport';
|
6
6
|
import { TextRendererLocalization } from '../localization';
|
7
7
|
import RenderingStyle from '../RenderingStyle';
|
8
|
-
import AbstractRenderer from './AbstractRenderer';
|
8
|
+
import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
|
9
9
|
export default class TextOnlyRenderer extends AbstractRenderer {
|
10
10
|
private localizationTable;
|
11
11
|
private descriptionBuilder;
|
12
12
|
private pathCount;
|
13
13
|
private textNodeCount;
|
14
|
+
private imageNodeCount;
|
14
15
|
constructor(viewport: Viewport, localizationTable: TextRendererLocalization);
|
15
16
|
displaySize(): Vec3;
|
16
17
|
clear(): void;
|
@@ -22,6 +23,7 @@ export default class TextOnlyRenderer extends AbstractRenderer {
|
|
22
23
|
protected traceCubicBezierCurve(_p1: Vec3, _p2: Vec3, _p3: Vec3): void;
|
23
24
|
protected traceQuadraticBezierCurve(_controlPoint: Vec3, _endPoint: Vec3): void;
|
24
25
|
drawText(text: string, _transform: Mat33, _style: TextStyle): void;
|
26
|
+
drawImage(image: RenderableImage): void;
|
25
27
|
isTooSmallToRender(rect: Rect2): boolean;
|
26
28
|
drawPoints(..._points: Vec3[]): void;
|
27
29
|
}
|
@@ -8,6 +8,7 @@ export default class TextOnlyRenderer extends AbstractRenderer {
|
|
8
8
|
this.descriptionBuilder = [];
|
9
9
|
this.pathCount = 0;
|
10
10
|
this.textNodeCount = 0;
|
11
|
+
this.imageNodeCount = 0;
|
11
12
|
}
|
12
13
|
displaySize() {
|
13
14
|
// We don't have a graphical display, export a reasonable size.
|
@@ -21,7 +22,8 @@ export default class TextOnlyRenderer extends AbstractRenderer {
|
|
21
22
|
getDescription() {
|
22
23
|
return [
|
23
24
|
this.localizationTable.pathNodeCount(this.pathCount),
|
24
|
-
this.localizationTable.textNodeCount(this.textNodeCount),
|
25
|
+
...(this.textNodeCount > 0 ? this.localizationTable.textNodeCount(this.textNodeCount) : []),
|
26
|
+
...(this.imageNodeCount > 0 ? this.localizationTable.imageNodeCount(this.imageNodeCount) : []),
|
25
27
|
...this.descriptionBuilder
|
26
28
|
].join('\n');
|
27
29
|
}
|
@@ -42,6 +44,11 @@ export default class TextOnlyRenderer extends AbstractRenderer {
|
|
42
44
|
this.descriptionBuilder.push(this.localizationTable.textNode(text));
|
43
45
|
this.textNodeCount++;
|
44
46
|
}
|
47
|
+
drawImage(image) {
|
48
|
+
const label = image.label ? this.localizationTable.imageNode(image.label) : this.localizationTable.unlabeledImageNode;
|
49
|
+
this.descriptionBuilder.push(label);
|
50
|
+
this.imageNodeCount++;
|
51
|
+
}
|
45
52
|
isTooSmallToRender(rect) {
|
46
53
|
return rect.maxDimension < 15 / this.getSizeOfCanvasPixelOnScreen();
|
47
54
|
}
|
@@ -1,17 +1,18 @@
|
|
1
1
|
import { EditorEventType } from '../types';
|
2
2
|
import { coloris, init as colorisInit } from '@melloware/coloris';
|
3
3
|
import Color4 from '../Color4';
|
4
|
-
import SelectionTool from '../tools/SelectionTool';
|
5
4
|
import { defaultToolbarLocalization } from './localization';
|
6
5
|
import { makeRedoIcon, makeUndoIcon } from './icons';
|
7
|
-
import
|
6
|
+
import SelectionTool from '../tools/SelectionTool/SelectionTool';
|
7
|
+
import PanZoomTool from '../tools/PanZoom';
|
8
8
|
import TextTool from '../tools/TextTool';
|
9
|
+
import EraserTool from '../tools/Eraser';
|
10
|
+
import PenTool from '../tools/Pen';
|
9
11
|
import PenToolWidget from './widgets/PenToolWidget';
|
10
12
|
import EraserWidget from './widgets/EraserToolWidget';
|
11
13
|
import SelectionToolWidget from './widgets/SelectionToolWidget';
|
12
14
|
import TextToolWidget from './widgets/TextToolWidget';
|
13
15
|
import HandToolWidget from './widgets/HandToolWidget';
|
14
|
-
import { EraserTool, PenTool } from '../tools/lib';
|
15
16
|
export const toolbarCSSPrefix = 'toolbar-';
|
16
17
|
export default class HTMLToolbar {
|
17
18
|
/** @internal */
|
@@ -158,7 +159,7 @@ export default class HTMLToolbar {
|
|
158
159
|
for (const tool of toolController.getMatchingTools(TextTool)) {
|
159
160
|
this.addWidget(new TextToolWidget(this.editor, tool, this.localizationTable));
|
160
161
|
}
|
161
|
-
const panZoomTool = toolController.getMatchingTools(
|
162
|
+
const panZoomTool = toolController.getMatchingTools(PanZoomTool)[0];
|
162
163
|
if (panZoomTool) {
|
163
164
|
this.addWidget(new HandToolWidget(this.editor, panZoomTool, this.localizationTable));
|
164
165
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import Editor from '../../Editor';
|
2
|
-
import SelectionTool from '../../tools/SelectionTool';
|
2
|
+
import SelectionTool from '../../tools/SelectionTool/SelectionTool';
|
3
3
|
import { ToolbarLocalization } from '../localization';
|
4
4
|
import BaseToolWidget from './BaseToolWidget';
|
5
5
|
export default class SelectionToolWidget extends BaseToolWidget {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, KeyPressEvent, KeyUpEvent } from '../types';
|
1
|
+
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, KeyPressEvent, KeyUpEvent, PasteEvent, CopyEvent } from '../types';
|
2
2
|
import ToolEnabledGroup from './ToolEnabledGroup';
|
3
3
|
export default abstract class BaseTool implements PointerEvtListener {
|
4
4
|
private notifier;
|
@@ -11,6 +11,8 @@ export default abstract class BaseTool implements PointerEvtListener {
|
|
11
11
|
onGestureCancel(): void;
|
12
12
|
protected constructor(notifier: EditorNotifier, description: string);
|
13
13
|
onWheel(_event: WheelEvt): boolean;
|
14
|
+
onCopy(_event: CopyEvent): boolean;
|
15
|
+
onPaste(_event: PasteEvent): boolean;
|
14
16
|
onKeyPress(_event: KeyPressEvent): boolean;
|
15
17
|
onKeyUp(_event: KeyUpEvent): boolean;
|
16
18
|
setEnabled(enabled: boolean): void;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/**
|
2
|
+
* A tool that handles paste events.
|
3
|
+
* @packageDocumentation
|
4
|
+
*/
|
5
|
+
import Editor from '../Editor';
|
6
|
+
import { PasteEvent } from '../types';
|
7
|
+
import BaseTool from './BaseTool';
|
8
|
+
export default class PasteHandler extends BaseTool {
|
9
|
+
private editor;
|
10
|
+
constructor(editor: Editor);
|
11
|
+
onPaste(event: PasteEvent): boolean;
|
12
|
+
private addComponentsFromPaste;
|
13
|
+
private doSVGPaste;
|
14
|
+
private doTextPaste;
|
15
|
+
private doImagePaste;
|
16
|
+
}
|