js-draw 0.12.0 → 0.13.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/CHANGELOG.md +9 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +12 -0
- package/dist/src/Color4.js +16 -0
- package/dist/src/Editor.d.ts +33 -18
- package/dist/src/Editor.js +22 -19
- package/dist/src/EditorImage.d.ts +12 -0
- package/dist/src/EditorImage.js +12 -0
- package/dist/src/Pointer.d.ts +1 -0
- package/dist/src/Pointer.js +8 -0
- package/dist/src/SVGLoader.d.ts +5 -0
- package/dist/src/SVGLoader.js +6 -1
- package/dist/src/Viewport.d.ts +30 -1
- package/dist/src/Viewport.js +39 -9
- package/dist/src/commands/invertCommand.js +1 -1
- package/dist/src/components/AbstractComponent.d.ts +19 -0
- package/dist/src/components/AbstractComponent.js +17 -2
- package/dist/src/lib.d.ts +6 -3
- package/dist/src/lib.js +4 -1
- package/dist/src/math/Mat33.d.ts +1 -1
- package/dist/src/math/Mat33.js +1 -1
- package/dist/src/rendering/Display.d.ts +9 -11
- package/dist/src/rendering/Display.js +12 -14
- package/dist/src/rendering/lib.d.ts +3 -0
- package/dist/src/rendering/lib.js +3 -0
- package/dist/src/rendering/renderers/DummyRenderer.js +2 -2
- package/dist/src/rendering/renderers/SVGRenderer.js +4 -0
- package/dist/src/toolbar/IconProvider.d.ts +1 -1
- package/dist/src/toolbar/IconProvider.js +90 -29
- package/dist/src/tools/PanZoom.js +1 -1
- package/dist/src/tools/PasteHandler.d.ts +11 -4
- package/dist/src/tools/PasteHandler.js +12 -5
- package/dist/src/tools/Pen.d.ts +7 -2
- package/dist/src/tools/Pen.js +39 -6
- package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +3 -0
- package/dist/src/tools/SelectionTool/SelectionHandle.js +6 -0
- package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -1
- package/dist/src/tools/SelectionTool/SelectionTool.js +53 -15
- package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
- package/dist/src/tools/ToolSwitcherShortcut.js +9 -3
- package/dist/src/tools/UndoRedoShortcut.js +2 -4
- package/package.json +2 -2
- package/src/Color4.test.ts +11 -0
- package/src/Color4.ts +22 -0
- package/src/Editor.ts +36 -22
- package/src/EditorImage.ts +12 -0
- package/src/Pointer.ts +19 -0
- package/src/SVGLoader.ts +6 -1
- package/src/Viewport.ts +50 -11
- package/src/commands/invertCommand.ts +1 -1
- package/src/components/AbstractComponent.ts +33 -2
- package/src/lib.ts +6 -3
- package/src/math/Mat33.ts +1 -1
- package/src/rendering/Display.ts +12 -15
- package/src/rendering/RenderingStyle.ts +1 -1
- package/src/rendering/lib.ts +4 -0
- package/src/rendering/renderers/DummyRenderer.ts +2 -3
- package/src/rendering/renderers/SVGRenderer.ts +4 -0
- package/src/rendering/renderers/TextOnlyRenderer.ts +0 -1
- package/src/toolbar/HTMLToolbar.ts +1 -1
- package/src/toolbar/IconProvider.ts +98 -31
- package/src/tools/PanZoom.ts +1 -1
- package/src/tools/PasteHandler.ts +12 -6
- package/src/tools/Pen.test.ts +44 -1
- package/src/tools/Pen.ts +53 -8
- package/src/tools/SelectionTool/SelectionHandle.ts +9 -0
- package/src/tools/SelectionTool/SelectionTool.ts +67 -15
- package/src/tools/ToolSwitcherShortcut.ts +10 -5
- package/src/tools/UndoRedoShortcut.ts +2 -5
- package/typedoc.json +2 -2
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "js-draw",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.13.0",
|
4
4
|
"description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
|
5
5
|
"main": "./dist/src/lib.d.ts",
|
6
6
|
"types": "./dist/src/lib.js",
|
@@ -101,7 +101,7 @@
|
|
101
101
|
"ts-jest": "^29.0.3",
|
102
102
|
"ts-loader": "^9.4.2",
|
103
103
|
"ts-node": "^10.9.1",
|
104
|
-
"typedoc": "^0.23.
|
104
|
+
"typedoc": "^0.23.24",
|
105
105
|
"typescript": "^4.9.4",
|
106
106
|
"webpack": "^5.75.0"
|
107
107
|
},
|
package/src/Color4.test.ts
CHANGED
@@ -16,4 +16,15 @@ describe('Color4', () => {
|
|
16
16
|
expect(Color4.fromString('rgba ( 255, 0,\t 0, 0.5)')).objEq(Color4.ofRGBA(1, 0, 0, 0.5));
|
17
17
|
expect(Color4.fromString('rgba( 0, 0, 128, 0)')).objEq(Color4.ofRGBA(0, 0, 128/255, 0));
|
18
18
|
});
|
19
|
+
|
20
|
+
it('should mix blue and red to get dark purple', () => {
|
21
|
+
expect(Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 0, 1), 0.5)).objEq(Color4.ofRGB(0.5, 0, 0.5));
|
22
|
+
expect(Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 0, 1), 0.1)).objEq(Color4.ofRGB(0.9, 0, 0.1));
|
23
|
+
});
|
24
|
+
|
25
|
+
it('should mix red and green to get yellow', () => {
|
26
|
+
expect(Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 1, 0), 0.3)).objEq(
|
27
|
+
Color4.ofRGB(0.7, 0.3, 0)
|
28
|
+
);
|
29
|
+
});
|
19
30
|
});
|
package/src/Color4.ts
CHANGED
@@ -129,6 +129,28 @@ export default class Color4 {
|
|
129
129
|
return this.toHexString() === other.toHexString();
|
130
130
|
}
|
131
131
|
|
132
|
+
/**
|
133
|
+
* If `fractionTo` is not in the range [0, 1], it will be clamped to the nearest number
|
134
|
+
* in that range. For example, `a.mix(b, -1)` is equivalent to `a.mix(b, 0)`.
|
135
|
+
*
|
136
|
+
* @returns a color `fractionTo` of the way from this color to `other`.
|
137
|
+
*
|
138
|
+
* @example
|
139
|
+
* ```ts
|
140
|
+
* Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 1, 0), 0.1) // -> Color4(0.9, 0.1, 0)
|
141
|
+
* ```
|
142
|
+
*/
|
143
|
+
public mix(other: Color4, fractionTo: number): Color4 {
|
144
|
+
fractionTo = Math.min(Math.max(fractionTo, 0), 1);
|
145
|
+
const fractionOfThis = 1 - fractionTo;
|
146
|
+
return new Color4(
|
147
|
+
this.r * fractionOfThis + other.r * fractionTo,
|
148
|
+
this.g * fractionOfThis + other.g * fractionTo,
|
149
|
+
this.b * fractionOfThis + other.b * fractionTo,
|
150
|
+
this.a * fractionOfThis + other.a * fractionTo,
|
151
|
+
);
|
152
|
+
}
|
153
|
+
|
132
154
|
private hexString: string|null = null;
|
133
155
|
|
134
156
|
/**
|
package/src/Editor.ts
CHANGED
@@ -1,22 +1,3 @@
|
|
1
|
-
/**
|
2
|
-
* The main entrypoint for the full editor.
|
3
|
-
*
|
4
|
-
* @example
|
5
|
-
* To create an editor with a toolbar,
|
6
|
-
* ```
|
7
|
-
* const editor = new Editor(document.body);
|
8
|
-
*
|
9
|
-
* const toolbar = editor.addToolbar();
|
10
|
-
* toolbar.addActionButton('Save', () => {
|
11
|
-
* const saveData = editor.toSVG().outerHTML;
|
12
|
-
* // Do something with saveData...
|
13
|
-
* });
|
14
|
-
* ```
|
15
|
-
*
|
16
|
-
* @packageDocumentation
|
17
|
-
*/
|
18
|
-
|
19
|
-
|
20
1
|
import EditorImage from './EditorImage';
|
21
2
|
import ToolController from './tools/ToolController';
|
22
3
|
import { InputEvtType, PointerEvt, EditorNotifier, EditorEventType, ImageLoader } from './types';
|
@@ -70,12 +51,27 @@ export interface EditorSettings {
|
|
70
51
|
iconProvider: IconProvider,
|
71
52
|
}
|
72
53
|
|
73
|
-
|
54
|
+
/**
|
55
|
+
* The main entrypoint for the full editor.
|
56
|
+
*
|
57
|
+
* @example
|
58
|
+
* To create an editor with a toolbar,
|
59
|
+
* ```
|
60
|
+
* const editor = new Editor(document.body);
|
61
|
+
*
|
62
|
+
* const toolbar = editor.addToolbar();
|
63
|
+
* toolbar.addActionButton('Save', () => {
|
64
|
+
* const saveData = editor.toSVG().outerHTML;
|
65
|
+
* // Do something with saveData...
|
66
|
+
* });
|
67
|
+
* ```
|
68
|
+
*/
|
74
69
|
export class Editor {
|
75
70
|
// Wrapper around the viewport and toolbar
|
76
71
|
private container: HTMLElement;
|
77
72
|
private renderingRegion: HTMLElement;
|
78
73
|
|
74
|
+
/** Manages drawing surfaces/{@link lib!AbstractRenderer}s. */
|
79
75
|
public display: Display;
|
80
76
|
|
81
77
|
/**
|
@@ -116,11 +112,23 @@ export class Editor {
|
|
116
112
|
/** Viewport for the exported/imported image. */
|
117
113
|
private importExportViewport: Viewport;
|
118
114
|
|
115
|
+
/**
|
116
|
+
* Allows transforming the view and querying information about
|
117
|
+
* what is currently visible.
|
118
|
+
*/
|
119
|
+
public readonly viewport: Viewport;
|
120
|
+
|
119
121
|
/** @internal */
|
120
122
|
public readonly localization: EditorLocalization;
|
121
123
|
|
124
|
+
/** {@link lib!EditorSettings.iconProvider} */
|
122
125
|
public readonly icons: IconProvider;
|
123
|
-
|
126
|
+
|
127
|
+
/**
|
128
|
+
* Controls the list of tools. See
|
129
|
+
* [the custom tool example](https://github.com/personalizedrefrigerator/js-draw/tree/main/docs/example-custom-tools)
|
130
|
+
* for more.
|
131
|
+
*/
|
124
132
|
public readonly toolController: ToolController;
|
125
133
|
|
126
134
|
/**
|
@@ -765,12 +773,18 @@ export class Editor {
|
|
765
773
|
this.nextRerenderListeners = [];
|
766
774
|
}
|
767
775
|
|
776
|
+
/**
|
777
|
+
* @see {@link Display.getWetInkRenderer} {@link Display.flatten}
|
778
|
+
*/
|
768
779
|
public drawWetInk(...path: RenderablePathSpec[]) {
|
769
780
|
for (const part of path) {
|
770
781
|
this.display.getWetInkRenderer().drawPath(part);
|
771
782
|
}
|
772
783
|
}
|
773
784
|
|
785
|
+
/**
|
786
|
+
* @see {@link Display.getWetInkRenderer}
|
787
|
+
*/
|
774
788
|
public clearWetInk() {
|
775
789
|
this.display.getWetInkRenderer().clear();
|
776
790
|
}
|
@@ -895,7 +909,7 @@ export class Editor {
|
|
895
909
|
public toDataURL(format: 'image/png'|'image/jpeg'|'image/webp' = 'image/png'): string {
|
896
910
|
const canvas = document.createElement('canvas');
|
897
911
|
|
898
|
-
const resolution = this.importExportViewport.
|
912
|
+
const resolution = this.importExportViewport.getScreenRectSize();
|
899
913
|
|
900
914
|
canvas.width = resolution.x;
|
901
915
|
canvas.height = resolution.y;
|
package/src/EditorImage.ts
CHANGED
@@ -75,6 +75,11 @@ export default class EditorImage {
|
|
75
75
|
delete this.componentsById[elem.getId()];
|
76
76
|
}
|
77
77
|
|
78
|
+
/**
|
79
|
+
* @returns the AbstractComponent with `id`, if it exists.
|
80
|
+
*
|
81
|
+
* @see {@link AbstractComponent.getId}
|
82
|
+
*/
|
78
83
|
public lookupElement(id: string): AbstractComponent|null {
|
79
84
|
return this.componentsById[id] ?? null;
|
80
85
|
}
|
@@ -84,6 +89,13 @@ export default class EditorImage {
|
|
84
89
|
return this.root.addLeaf(elem);
|
85
90
|
}
|
86
91
|
|
92
|
+
/**
|
93
|
+
* Returns a command that adds the given element to the `EditorImage`.
|
94
|
+
* If `applyByFlattening` is true, the content of the wet ink renderer is
|
95
|
+
* rendered onto the main rendering canvas instead of doing a full re-render.
|
96
|
+
*
|
97
|
+
* @see {@link Display.flatten}
|
98
|
+
*/
|
87
99
|
public static addElement(elem: AbstractComponent, applyByFlattening: boolean = false): SerializableCommand {
|
88
100
|
return new EditorImage.AddElementCommand(elem, applyByFlattening);
|
89
101
|
}
|
package/src/Pointer.ts
CHANGED
@@ -36,6 +36,25 @@ export default class Pointer {
|
|
36
36
|
) {
|
37
37
|
}
|
38
38
|
|
39
|
+
// Snaps this pointer to the nearest grid point (rounds the coordinates of this
|
40
|
+
// pointer based on the current zoom). Returns a new Pointer and does not modify
|
41
|
+
// this.
|
42
|
+
public snappedToGrid(viewport: Viewport): Pointer {
|
43
|
+
const snappedCanvasPos = viewport.snapToGrid(this.canvasPos);
|
44
|
+
const snappedScreenPos = viewport.canvasToScreen(snappedCanvasPos);
|
45
|
+
|
46
|
+
return new Pointer(
|
47
|
+
snappedScreenPos,
|
48
|
+
snappedCanvasPos,
|
49
|
+
this.pressure,
|
50
|
+
this.isPrimary,
|
51
|
+
this.down,
|
52
|
+
this.device,
|
53
|
+
this.id,
|
54
|
+
this.timeStamp,
|
55
|
+
);
|
56
|
+
}
|
57
|
+
|
39
58
|
// Creates a Pointer from a DOM event. If `relativeTo` is given, (0, 0) in screen coordinates is
|
40
59
|
// considered the top left of `relativeTo`.
|
41
60
|
public static ofEvent(evt: PointerEvent, isDown: boolean, viewport: Viewport, relativeTo?: HTMLElement): Pointer {
|
package/src/SVGLoader.ts
CHANGED
@@ -31,6 +31,7 @@ export type SVGLoaderUnknownStyleAttribute = { key: string, value: string, prior
|
|
31
31
|
|
32
32
|
const supportedStrokeFillStyleAttrs = [ 'stroke', 'fill', 'stroke-width' ];
|
33
33
|
|
34
|
+
// Handles loading images from SVG.
|
34
35
|
export default class SVGLoader implements ImageLoader {
|
35
36
|
private onAddComponent: ComponentAddedListener|null = null;
|
36
37
|
private onProgress: OnProgressListener|null = null;
|
@@ -422,7 +423,11 @@ export default class SVGLoader implements ImageLoader {
|
|
422
423
|
this.onFinish?.();
|
423
424
|
}
|
424
425
|
|
425
|
-
|
426
|
+
/**
|
427
|
+
* @see {@link Editor.loadFrom}
|
428
|
+
* @param text - Textual representation of the SVG (e.g. `<svg viewbox='...'>...</svg>`).
|
429
|
+
* @param sanitize - if `true`, don't store unknown attributes.
|
430
|
+
*/
|
426
431
|
public static fromString(text: string, sanitize: boolean = false): SVGLoader {
|
427
432
|
const sandbox = document.createElement('iframe');
|
428
433
|
sandbox.src = 'about:blank';
|
package/src/Viewport.ts
CHANGED
@@ -92,26 +92,30 @@ export class Viewport {
|
|
92
92
|
this.screenRect = this.screenRect.resizedTo(screenSize);
|
93
93
|
}
|
94
94
|
|
95
|
-
|
95
|
+
/** Get the screen's visible region transformed into canvas space. */
|
96
96
|
public get visibleRect(): Rect2 {
|
97
97
|
return this.screenRect.transformedBoundingBox(this.inverseTransform);
|
98
98
|
}
|
99
99
|
|
100
|
-
|
100
|
+
/** @returns the given point, but in canvas coordinates */
|
101
101
|
public screenToCanvas(screenPoint: Point2): Point2 {
|
102
102
|
return this.inverseTransform.transformVec2(screenPoint);
|
103
103
|
}
|
104
104
|
|
105
|
+
/** @returns the given point transformed into screen coordinates. */
|
105
106
|
public canvasToScreen(canvasPoint: Point2): Point2 {
|
106
107
|
return this.transform.transformVec2(canvasPoint);
|
107
108
|
}
|
108
109
|
|
110
|
+
/** @returns a command that transforms the canvas by `transform`. */
|
109
111
|
public static transformBy(transform: Mat33): ViewportTransform {
|
110
112
|
return new Viewport.ViewportTransform(transform);
|
111
113
|
}
|
112
114
|
|
113
|
-
|
114
|
-
|
115
|
+
/**
|
116
|
+
* Updates the transformation directly. Using `transformBy` is preferred.
|
117
|
+
* @param newTransform - should map from canvas coordinates to screen coordinates.
|
118
|
+
*/
|
115
119
|
public resetTransform(newTransform: Mat33 = Mat33.identity) {
|
116
120
|
const oldTransform = this.transform;
|
117
121
|
this.transform = newTransform;
|
@@ -131,29 +135,64 @@ export class Viewport {
|
|
131
135
|
return this.transform;
|
132
136
|
}
|
133
137
|
|
134
|
-
|
138
|
+
/** @returns the size of the visible region in pixels. */
|
139
|
+
public getScreenRectSize(): Vec2 {
|
135
140
|
return this.screenRect.size;
|
136
141
|
}
|
137
142
|
|
138
|
-
|
143
|
+
/** Alias for `getScreenRectSize`. @deprecated */
|
144
|
+
public getResolution() {
|
145
|
+
return this.getScreenRectSize();
|
146
|
+
}
|
147
|
+
|
148
|
+
/** @returns the amount a vector on the canvas is scaled to become a vector on the screen. */
|
139
149
|
public getScaleFactor(): number {
|
140
150
|
// Use transformVec3 to avoid translating the vector
|
141
151
|
return this.transform.transformVec3(Vec3.unitX).magnitude();
|
142
152
|
}
|
143
153
|
|
144
|
-
|
154
|
+
/**
|
155
|
+
* @returns `getScaleFactor()` rounded to the nearest power of 10.
|
156
|
+
* For example, if `getScaleFactor()` returns 101, `getScaleFactorToNearestPowerOfTen()`
|
157
|
+
* should return `100` because `100` is the nearest power of 10 to 101.
|
158
|
+
*/
|
159
|
+
public getScaleFactorToNearestPowerOfTen() {
|
160
|
+
const scaleFactor = this.getScaleFactor();
|
161
|
+
return Math.pow(10, Math.round(Math.log10(scaleFactor)));
|
162
|
+
}
|
163
|
+
|
164
|
+
public snapToGrid(canvasPos: Point2) {
|
165
|
+
const snapCoordinate = (coordinate: number) => {
|
166
|
+
const scaleFactor = this.getScaleFactorToNearestPowerOfTen();
|
167
|
+
const roundFactor = scaleFactor / 100;
|
168
|
+
const snapped = Math.round(coordinate * roundFactor) / roundFactor;
|
169
|
+
|
170
|
+
return snapped;
|
171
|
+
};
|
172
|
+
|
173
|
+
const snappedCanvasPos = Vec2.of(
|
174
|
+
snapCoordinate(canvasPos.x), snapCoordinate(canvasPos.y)
|
175
|
+
);
|
176
|
+
return snappedCanvasPos;
|
177
|
+
}
|
178
|
+
|
179
|
+
/** Returns the size of one screen pixel in canvas units. */
|
145
180
|
public getSizeOfPixelOnCanvas(): number {
|
146
181
|
return 1/this.getScaleFactor();
|
147
182
|
}
|
148
183
|
|
149
|
-
|
150
|
-
|
184
|
+
/**
|
185
|
+
* @returns the angle of the canvas in radians.
|
186
|
+
* This is the angle by which the canvas is rotated relative to the screen.
|
187
|
+
*/
|
151
188
|
public getRotationAngle(): number {
|
152
189
|
return this.transform.transformVec3(Vec3.unitX).angle();
|
153
190
|
}
|
154
191
|
|
155
|
-
|
156
|
-
|
192
|
+
/**
|
193
|
+
* Rounds the given `point` to a multiple of 10 such that it is within `tolerance` of
|
194
|
+
* its original location. This is useful for preparing data for base-10 conversion.
|
195
|
+
*/
|
157
196
|
public static roundPoint<T extends Point2|number>(
|
158
197
|
point: T, tolerance: number,
|
159
198
|
): PointDataType<T>;
|
@@ -3,7 +3,7 @@ import { EditorLocalization } from '../localization';
|
|
3
3
|
import Command from './Command';
|
4
4
|
import SerializableCommand from './SerializableCommand';
|
5
5
|
|
6
|
-
// Returns a command
|
6
|
+
// Returns a command that does the opposite of the given command --- `result.apply()` calls
|
7
7
|
// `command.unapply()` and `result.unapply()` calls `command.apply()`.
|
8
8
|
const invertCommand = <T extends Command> (command: T): T extends SerializableCommand ? SerializableCommand : Command => {
|
9
9
|
if (command instanceof SerializableCommand) {
|
@@ -13,9 +13,20 @@ export type LoadSaveDataTable = Record<string, Array<LoadSaveData>>;
|
|
13
13
|
export type DeserializeCallback = (data: string)=>AbstractComponent;
|
14
14
|
type ComponentId = string;
|
15
15
|
|
16
|
+
/**
|
17
|
+
* A base class for everything that can be added to an {@link EditorImage}.
|
18
|
+
*/
|
16
19
|
export default abstract class AbstractComponent {
|
20
|
+
// The timestamp (milliseconds) at which the component was
|
21
|
+
// last changed (i.e. created/translated).
|
22
|
+
// @deprecated
|
17
23
|
protected lastChangedTime: number;
|
24
|
+
|
25
|
+
// The bounding box of this component.
|
26
|
+
// {@link getBBox}, by default, returns `contentBBox`.
|
27
|
+
// This must be set by components.
|
18
28
|
protected abstract contentBBox: Rect2;
|
29
|
+
|
19
30
|
private zIndex: number;
|
20
31
|
private id: string;
|
21
32
|
|
@@ -38,7 +49,7 @@ export default abstract class AbstractComponent {
|
|
38
49
|
}
|
39
50
|
|
40
51
|
// Returns a unique ID for this element.
|
41
|
-
// @see { @link EditorImage
|
52
|
+
// @see { @link lib!EditorImage.lookupElement }
|
42
53
|
public getId() {
|
43
54
|
return this.id;
|
44
55
|
}
|
@@ -55,14 +66,22 @@ export default abstract class AbstractComponent {
|
|
55
66
|
this.deserializationCallbacks[componentKind] = deserialize ?? null;
|
56
67
|
}
|
57
68
|
|
58
|
-
//
|
69
|
+
// Stores data attached by a loader.
|
59
70
|
private loadSaveData: LoadSaveDataTable = {};
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Attach data that can be used while exporting the component (e.g. to SVG).
|
74
|
+
*
|
75
|
+
* This is intended for use by a {@link ImageLoader}.
|
76
|
+
*/
|
60
77
|
public attachLoadSaveData(key: string, data: LoadSaveData) {
|
61
78
|
if (!this.loadSaveData[key]) {
|
62
79
|
this.loadSaveData[key] = [];
|
63
80
|
}
|
64
81
|
this.loadSaveData[key].push(data);
|
65
82
|
}
|
83
|
+
|
84
|
+
/** See {@link attachLoadSaveData} */
|
66
85
|
public getLoadSaveData(): LoadSaveDataTable {
|
67
86
|
return this.loadSaveData;
|
68
87
|
}
|
@@ -71,13 +90,20 @@ export default abstract class AbstractComponent {
|
|
71
90
|
return this.zIndex;
|
72
91
|
}
|
73
92
|
|
93
|
+
/** @returns the bounding box of */
|
74
94
|
public getBBox(): Rect2 {
|
75
95
|
return this.contentBBox;
|
76
96
|
}
|
77
97
|
|
78
98
|
public abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
99
|
+
|
100
|
+
/** @return true if `lineSegment` intersects this component. */
|
79
101
|
public abstract intersects(lineSegment: LineSegment2): boolean;
|
80
102
|
|
103
|
+
/**
|
104
|
+
* @returns true if this component intersects `rect` -- it is entirely contained
|
105
|
+
* within the rectangle or one of the rectangle's edges intersects this component.
|
106
|
+
*/
|
81
107
|
public intersectsRect(rect: Rect2): boolean {
|
82
108
|
// If this component intersects rect,
|
83
109
|
// it is either contained entirely within rect or intersects one of rect's edges.
|
@@ -200,6 +226,7 @@ export default abstract class AbstractComponent {
|
|
200
226
|
}
|
201
227
|
|
202
228
|
this.component.applyTransformation(newTransfm);
|
229
|
+
this.component.lastChangedTime = (new Date()).getTime();
|
203
230
|
|
204
231
|
// Add the element back to the document.
|
205
232
|
if (hadParent) {
|
@@ -250,6 +277,10 @@ export default abstract class AbstractComponent {
|
|
250
277
|
}
|
251
278
|
};
|
252
279
|
|
280
|
+
/**
|
281
|
+
* @return a description that could be read by a screen reader
|
282
|
+
* (e.g. when adding/erasing the component)
|
283
|
+
*/
|
253
284
|
public abstract description(localizationTable: ImageComponentLocalization): string;
|
254
285
|
|
255
286
|
// Component-specific implementation of {@link clone}.
|
package/src/lib.ts
CHANGED
@@ -8,25 +8,28 @@
|
|
8
8
|
* ```
|
9
9
|
*
|
10
10
|
* @see
|
11
|
-
* {@link Editor
|
11
|
+
* {@link Editor}
|
12
12
|
*
|
13
13
|
* @packageDocumentation
|
14
14
|
*/
|
15
15
|
|
16
|
-
import Editor from './Editor';
|
16
|
+
import Editor, { EditorSettings } from './Editor';
|
17
17
|
export { default as EditorImage } from './EditorImage';
|
18
18
|
export * from './types';
|
19
19
|
export { default as getLocalizationTable } from './localizations/getLocalizationTable';
|
20
20
|
export * from './localization';
|
21
21
|
|
22
22
|
export { default as Color4 } from './Color4';
|
23
|
+
export { default as SVGLoader } from './SVGLoader';
|
24
|
+
export { default as Viewport } from './Viewport';
|
23
25
|
export * from './math/lib';
|
24
26
|
export * from './components/lib';
|
25
27
|
export * from './commands/lib';
|
26
28
|
export * from './tools/lib';
|
27
29
|
export * from './toolbar/lib';
|
30
|
+
export * from './rendering/lib';
|
28
31
|
export { default as Pointer, PointerDevice } from './Pointer';
|
29
32
|
export { default as HTMLToolbar } from './toolbar/HTMLToolbar';
|
30
33
|
|
31
|
-
export { Editor };
|
34
|
+
export { Editor, EditorSettings };
|
32
35
|
export default Editor;
|
package/src/math/Mat33.ts
CHANGED
@@ -338,7 +338,7 @@ export default class Mat33 {
|
|
338
338
|
return result.rightMul(Mat33.translation(center.times(-1)));
|
339
339
|
}
|
340
340
|
|
341
|
-
/** @see {@link
|
341
|
+
/** @see {@link fromCSSMatrix} */
|
342
342
|
public toCSSMatrix(): string {
|
343
343
|
return `matrix(${this.a1},${this.b1},${this.a2},${this.b2},${this.a3},${this.b3})`;
|
344
344
|
}
|
package/src/rendering/Display.ts
CHANGED
@@ -1,18 +1,3 @@
|
|
1
|
-
/**
|
2
|
-
* Handles `HTMLCanvasElement`s (or other drawing surfaces if being used) used to display the editor's contents.
|
3
|
-
*
|
4
|
-
* @example
|
5
|
-
* ```
|
6
|
-
* const editor = new Editor(document.body);
|
7
|
-
* const w = editor.display.width;
|
8
|
-
* const h = editor.display.height;
|
9
|
-
* const center = Vec2.of(w / 2, h / 2);
|
10
|
-
* const colorAtCenter = editor.display.getColorAt(center);
|
11
|
-
* ```
|
12
|
-
*
|
13
|
-
* @packageDocumentation
|
14
|
-
*/
|
15
|
-
|
16
1
|
import AbstractRenderer from './renderers/AbstractRenderer';
|
17
2
|
import CanvasRenderer from './renderers/CanvasRenderer';
|
18
3
|
import { Editor } from '../Editor';
|
@@ -29,6 +14,18 @@ export enum RenderingMode {
|
|
29
14
|
// SVGRenderer is not supported by the main display
|
30
15
|
}
|
31
16
|
|
17
|
+
/**
|
18
|
+
* Handles `HTMLCanvasElement`s (or other drawing surfaces if being used) used to display the editor's contents.
|
19
|
+
*
|
20
|
+
* @example
|
21
|
+
* ```
|
22
|
+
* const editor = new Editor(document.body);
|
23
|
+
* const w = editor.display.width;
|
24
|
+
* const h = editor.display.height;
|
25
|
+
* const center = Vec2.of(w / 2, h / 2);
|
26
|
+
* const colorAtCenter = editor.display.getColorAt(center);
|
27
|
+
* ```
|
28
|
+
*/
|
32
29
|
export default class Display {
|
33
30
|
private dryInkRenderer: AbstractRenderer;
|
34
31
|
private wetInkRenderer: AbstractRenderer;
|
@@ -13,7 +13,7 @@ export default RenderingStyle;
|
|
13
13
|
export const stylesEqual = (a: RenderingStyle, b: RenderingStyle): boolean => {
|
14
14
|
const result = a === b || (a.fill.eq(b.fill)
|
15
15
|
&& (a.stroke == undefined) === (b.stroke == undefined)
|
16
|
-
|
16
|
+
&& (a.stroke?.color?.eq(b.stroke?.color) ?? true)
|
17
17
|
&& a.stroke?.width === b.stroke?.width);
|
18
18
|
|
19
19
|
// Map undefined/null -> false
|
@@ -1,5 +1,3 @@
|
|
1
|
-
// Renderer that outputs nothing. Useful for automated tests.
|
2
|
-
|
3
1
|
import { TextStyle } from '../../components/TextComponent';
|
4
2
|
import Mat33 from '../../math/Mat33';
|
5
3
|
import Rect2 from '../../math/Rect2';
|
@@ -9,6 +7,7 @@ import Viewport from '../../Viewport';
|
|
9
7
|
import RenderingStyle from '../RenderingStyle';
|
10
8
|
import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
|
11
9
|
|
10
|
+
// Renderer that outputs almost nothing. Useful for automated tests.
|
12
11
|
export default class DummyRenderer extends AbstractRenderer {
|
13
12
|
// Variables that track the state of what's been rendered
|
14
13
|
public clearedCount: number = 0;
|
@@ -28,7 +27,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
28
27
|
|
29
28
|
public displaySize(): Vec2 {
|
30
29
|
// Do we have a stored viewport size?
|
31
|
-
const viewportSize = this.getViewport().
|
30
|
+
const viewportSize = this.getViewport().getScreenRectSize();
|
32
31
|
|
33
32
|
// Don't use a 0x0 viewport — DummyRenderer is often used
|
34
33
|
// for tests that run without a display, so pretend we have a
|
@@ -38,6 +38,10 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
38
38
|
stroke-linecap: round;
|
39
39
|
stroke-linejoin: round;
|
40
40
|
}
|
41
|
+
|
42
|
+
text {
|
43
|
+
white-space: pre;
|
44
|
+
}
|
41
45
|
`.replace(/\s+/g, '');
|
42
46
|
styleSheet.setAttribute('id', renderedStylesheetId);
|
43
47
|
this.elem.appendChild(styleSheet);
|
@@ -9,7 +9,6 @@ import RenderingStyle from '../RenderingStyle';
|
|
9
9
|
import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
|
10
10
|
|
11
11
|
// Outputs a description of what was rendered.
|
12
|
-
|
13
12
|
export default class TextOnlyRenderer extends AbstractRenderer {
|
14
13
|
private descriptionBuilder: string[] = [];
|
15
14
|
private pathCount: number = 0;
|