js-draw 1.9.0 → 1.10.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/dist/Editor.css +48 -1
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +41 -0
- package/dist/cjs/Editor.js +33 -13
- package/dist/cjs/Pointer.js +1 -1
- package/dist/cjs/Viewport.d.ts +6 -0
- package/dist/cjs/Viewport.js +6 -1
- package/dist/cjs/commands/Erase.d.ts +22 -2
- package/dist/cjs/commands/Erase.js +22 -2
- package/dist/cjs/commands/uniteCommands.d.ts +36 -0
- package/dist/cjs/commands/uniteCommands.js +36 -0
- package/dist/cjs/components/ImageComponent.d.ts +12 -0
- package/dist/cjs/components/ImageComponent.js +16 -9
- package/dist/cjs/components/Stroke.d.ts +16 -2
- package/dist/cjs/components/Stroke.js +17 -1
- package/dist/cjs/components/builders/ArrowBuilder.js +3 -3
- package/dist/cjs/components/builders/CircleBuilder.js +3 -3
- package/dist/cjs/components/builders/FreehandLineBuilder.js +3 -3
- package/dist/cjs/components/builders/LineBuilder.js +3 -3
- package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +3 -3
- package/dist/cjs/components/builders/RectangleBuilder.js +5 -6
- package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
- package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.js +168 -0
- package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
- package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.js +46 -0
- package/dist/cjs/components/builders/types.d.ts +1 -0
- package/dist/cjs/image/EditorImage.d.ts +32 -1
- package/dist/cjs/image/EditorImage.js +32 -1
- package/dist/cjs/rendering/Display.js +8 -1
- package/dist/cjs/rendering/RenderablePathSpec.d.ts +5 -1
- package/dist/cjs/rendering/RenderablePathSpec.js +4 -0
- package/dist/cjs/toolbar/IconProvider.d.ts +2 -0
- package/dist/cjs/toolbar/IconProvider.js +17 -0
- package/dist/cjs/toolbar/localization.d.ts +3 -0
- package/dist/cjs/toolbar/localization.js +4 -1
- package/dist/cjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
- package/dist/cjs/toolbar/widgets/InsertImageWidget.js +102 -22
- package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
- package/dist/cjs/toolbar/widgets/PenToolWidget.js +50 -20
- package/dist/cjs/tools/Pen.d.ts +9 -0
- package/dist/cjs/tools/Pen.js +77 -3
- package/dist/cjs/tools/TextTool.js +5 -1
- package/dist/cjs/tools/util/StationaryPenDetector.d.ts +22 -0
- package/dist/cjs/tools/util/StationaryPenDetector.js +95 -0
- package/dist/cjs/util/ReactiveValue.d.ts +2 -0
- package/dist/cjs/util/ReactiveValue.js +2 -0
- package/dist/cjs/util/lib.d.ts +1 -0
- package/dist/cjs/util/lib.js +4 -1
- package/dist/cjs/util/waitForImageLoaded.d.ts +2 -0
- package/dist/cjs/util/waitForImageLoaded.js +12 -0
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +41 -0
- package/dist/mjs/Editor.mjs +33 -13
- package/dist/mjs/Pointer.mjs +1 -1
- package/dist/mjs/Viewport.d.ts +6 -0
- package/dist/mjs/Viewport.mjs +6 -1
- package/dist/mjs/commands/Erase.d.ts +22 -2
- package/dist/mjs/commands/Erase.mjs +22 -2
- package/dist/mjs/commands/uniteCommands.d.ts +36 -0
- package/dist/mjs/commands/uniteCommands.mjs +36 -0
- package/dist/mjs/components/ImageComponent.d.ts +12 -0
- package/dist/mjs/components/ImageComponent.mjs +16 -9
- package/dist/mjs/components/Stroke.d.ts +16 -2
- package/dist/mjs/components/Stroke.mjs +17 -1
- package/dist/mjs/components/builders/ArrowBuilder.mjs +3 -2
- package/dist/mjs/components/builders/CircleBuilder.mjs +3 -2
- package/dist/mjs/components/builders/FreehandLineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/LineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/RectangleBuilder.mjs +5 -4
- package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
- package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.mjs +166 -0
- package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
- package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.mjs +44 -0
- package/dist/mjs/components/builders/types.d.ts +1 -0
- package/dist/mjs/image/EditorImage.d.ts +32 -1
- package/dist/mjs/image/EditorImage.mjs +32 -1
- package/dist/mjs/rendering/Display.mjs +8 -1
- package/dist/mjs/rendering/RenderablePathSpec.d.ts +5 -1
- package/dist/mjs/rendering/RenderablePathSpec.mjs +4 -0
- package/dist/mjs/toolbar/IconProvider.d.ts +2 -0
- package/dist/mjs/toolbar/IconProvider.mjs +17 -0
- package/dist/mjs/toolbar/localization.d.ts +3 -0
- package/dist/mjs/toolbar/localization.mjs +4 -1
- package/dist/mjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
- package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +102 -22
- package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
- package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +50 -20
- package/dist/mjs/tools/Pen.d.ts +9 -0
- package/dist/mjs/tools/Pen.mjs +77 -3
- package/dist/mjs/tools/TextTool.mjs +5 -1
- package/dist/mjs/tools/util/StationaryPenDetector.d.ts +22 -0
- package/dist/mjs/tools/util/StationaryPenDetector.mjs +92 -0
- package/dist/mjs/util/ReactiveValue.d.ts +2 -0
- package/dist/mjs/util/ReactiveValue.mjs +2 -0
- package/dist/mjs/util/lib.d.ts +1 -0
- package/dist/mjs/util/lib.mjs +1 -0
- package/dist/mjs/util/waitForImageLoaded.d.ts +2 -0
- package/dist/mjs/util/waitForImageLoaded.mjs +10 -0
- package/dist/mjs/version.mjs +1 -1
- package/package.json +3 -3
- package/src/Editor.scss +7 -0
- package/src/toolbar/AbstractToolbar.scss +20 -0
- package/src/toolbar/toolbar.scss +1 -1
- package/src/toolbar/widgets/InsertImageWidget.scss +6 -1
- package/src/toolbar/widgets/PenToolWidget.scss +33 -0
- package/src/tools/SelectionTool/SelectionTool.scss +6 -0
- package/src/toolbar/widgets/PenToolWidget.css +0 -2
@@ -34,6 +34,8 @@ const noOpSetUpdateListener = () => {
|
|
34
34
|
*
|
35
35
|
* Static methods in the `ReactiveValue` and `MutableReactiveValue` classes are
|
36
36
|
* constructors (e.g. `fromImmutable`).
|
37
|
+
*
|
38
|
+
* Avoid extending this class from an external library, as that may not be stable.
|
37
39
|
*/
|
38
40
|
class ReactiveValue {
|
39
41
|
/** Creates a `ReactiveValue` with an initial value, `initialValue`. */
|
package/dist/cjs/util/lib.d.ts
CHANGED
package/dist/cjs/util/lib.js
CHANGED
@@ -3,6 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.adjustEditorThemeForContrast = void 0;
|
6
|
+
exports.MutableReactiveValue = exports.ReactiveValue = exports.adjustEditorThemeForContrast = void 0;
|
7
7
|
var adjustEditorThemeForContrast_1 = require("./adjustEditorThemeForContrast");
|
8
8
|
Object.defineProperty(exports, "adjustEditorThemeForContrast", { enumerable: true, get: function () { return __importDefault(adjustEditorThemeForContrast_1).default; } });
|
9
|
+
var ReactiveValue_1 = require("./ReactiveValue");
|
10
|
+
Object.defineProperty(exports, "ReactiveValue", { enumerable: true, get: function () { return ReactiveValue_1.ReactiveValue; } });
|
11
|
+
Object.defineProperty(exports, "MutableReactiveValue", { enumerable: true, get: function () { return ReactiveValue_1.MutableReactiveValue; } });
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const waitForImageLoad = async (image) => {
|
4
|
+
if (!image.complete) {
|
5
|
+
await new Promise((resolve, reject) => {
|
6
|
+
image.onload = event => resolve(event);
|
7
|
+
image.onerror = event => reject(event);
|
8
|
+
image.onabort = event => reject(event);
|
9
|
+
});
|
10
|
+
}
|
11
|
+
};
|
12
|
+
exports.default = waitForImageLoad;
|
package/dist/cjs/version.js
CHANGED
package/dist/mjs/Editor.d.ts
CHANGED
@@ -156,6 +156,38 @@ export declare class Editor {
|
|
156
156
|
readonly toolController: ToolController;
|
157
157
|
/**
|
158
158
|
* Global event dispatcher/subscriber.
|
159
|
+
*
|
160
|
+
* @example
|
161
|
+
*
|
162
|
+
* ```ts,runnable
|
163
|
+
* import { Editor, EditorEventType, SerializableCommand } from 'js-draw';
|
164
|
+
*
|
165
|
+
* // Create a minimal editor
|
166
|
+
* const editor = new Editor(document.body);
|
167
|
+
* editor.addToolbar();
|
168
|
+
*
|
169
|
+
* // Create a place to show text output
|
170
|
+
* const log = document.createElement('textarea');
|
171
|
+
* document.body.appendChild(log);
|
172
|
+
* log.style.width = '100%';
|
173
|
+
* log.style.height = '200px';
|
174
|
+
*
|
175
|
+
* // Listen for CommandDone events (there's also a CommandUndone)
|
176
|
+
* editor.notifier.on(EditorEventType.CommandDone, event => {
|
177
|
+
* // Type narrowing for TypeScript -- event will always be of kind CommandDone,
|
178
|
+
* // but TypeScript doesn't know this.
|
179
|
+
* if (event.kind !== EditorEventType.CommandDone) return;
|
180
|
+
*
|
181
|
+
* log.value = `Command done ${event.command.description(editor, editor.localization)}\n`;
|
182
|
+
*
|
183
|
+
* if (event.command instanceof SerializableCommand) {
|
184
|
+
* log.value += `serializes to: ${JSON.stringify(event.command.serialize())}`;
|
185
|
+
* }
|
186
|
+
* });
|
187
|
+
*
|
188
|
+
* // Dispatch an initial command to trigger the event listener for the first time
|
189
|
+
* editor.dispatch(editor.image.setAutoresizeEnabled(true));
|
190
|
+
* ```
|
159
191
|
*/
|
160
192
|
readonly notifier: EditorNotifier;
|
161
193
|
private loadingWarning;
|
@@ -395,6 +427,12 @@ export declare class Editor {
|
|
395
427
|
* @see {@link sendPenEvent} {@link sendTouchEvent}
|
396
428
|
*/
|
397
429
|
sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
|
430
|
+
/**
|
431
|
+
* Adds all components in `components` such that they are in the center of the screen.
|
432
|
+
* This is a convenience method that creates **and applies** a single command.
|
433
|
+
*
|
434
|
+
* If `selectComponents` is true (the default), the components are selected.
|
435
|
+
*/
|
398
436
|
addAndCenterComponents(components: AbstractComponent[], selectComponents?: boolean): Promise<void>;
|
399
437
|
/**
|
400
438
|
* Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
|
@@ -403,6 +441,9 @@ export declare class Editor {
|
|
403
441
|
*
|
404
442
|
* The export resolution is the same as the size of the drawing canvas, unless `outputSize`
|
405
443
|
* is given.
|
444
|
+
*
|
445
|
+
* **Example**:
|
446
|
+
* [[include:doc-pages/inline-examples/adding-an-image-and-data-urls.md]]
|
406
447
|
*/
|
407
448
|
toDataURL(format?: 'image/png' | 'image/jpeg' | 'image/webp', outputSize?: Vec2): string;
|
408
449
|
/**
|
package/dist/mjs/Editor.mjs
CHANGED
@@ -160,19 +160,30 @@ export class Editor {
|
|
160
160
|
this.hideLoadingWarning();
|
161
161
|
// Enforce zoom limits.
|
162
162
|
this.notifier.on(EditorEventType.ViewportChanged, evt => {
|
163
|
-
if (evt.kind
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
this.
|
163
|
+
if (evt.kind !== EditorEventType.ViewportChanged)
|
164
|
+
return;
|
165
|
+
const getZoom = (mat) => mat.transformVec3(Vec2.unitX).length();
|
166
|
+
const zoom = getZoom(evt.newTransform);
|
167
|
+
if (zoom > this.settings.maxZoom || zoom < this.settings.minZoom) {
|
168
|
+
const oldZoom = getZoom(evt.oldTransform);
|
169
|
+
let resetTransform = Mat33.identity;
|
170
|
+
if (oldZoom <= this.settings.maxZoom && oldZoom >= this.settings.minZoom) {
|
171
|
+
resetTransform = evt.oldTransform;
|
172
|
+
}
|
173
|
+
else {
|
174
|
+
// If 1x zoom isn't acceptable, try a zoom between the minimum and maximum.
|
175
|
+
resetTransform = Mat33.scaling2D((this.settings.minZoom + this.settings.maxZoom) / 2);
|
176
|
+
}
|
177
|
+
this.viewport.resetTransform(resetTransform);
|
178
|
+
}
|
179
|
+
else if (!isFinite(zoom)) {
|
180
|
+
// Recover from possible division-by-zero
|
181
|
+
console.warn(`Non-finite zoom (${zoom}) detected. Resetting the viewport. This was likely caused by division by zero.`);
|
182
|
+
if (isFinite(getZoom(evt.oldTransform))) {
|
183
|
+
this.viewport.resetTransform(evt.oldTransform);
|
184
|
+
}
|
185
|
+
else {
|
186
|
+
this.viewport.resetTransform();
|
176
187
|
}
|
177
188
|
}
|
178
189
|
});
|
@@ -875,6 +886,12 @@ export class Editor {
|
|
875
886
|
allPointers) {
|
876
887
|
sendPenEvent(this, eventType, point, allPointers);
|
877
888
|
}
|
889
|
+
/**
|
890
|
+
* Adds all components in `components` such that they are in the center of the screen.
|
891
|
+
* This is a convenience method that creates **and applies** a single command.
|
892
|
+
*
|
893
|
+
* If `selectComponents` is true (the default), the components are selected.
|
894
|
+
*/
|
878
895
|
async addAndCenterComponents(components, selectComponents = true) {
|
879
896
|
let bbox = null;
|
880
897
|
for (const component of components) {
|
@@ -921,6 +938,9 @@ export class Editor {
|
|
921
938
|
*
|
922
939
|
* The export resolution is the same as the size of the drawing canvas, unless `outputSize`
|
923
940
|
* is given.
|
941
|
+
*
|
942
|
+
* **Example**:
|
943
|
+
* [[include:doc-pages/inline-examples/adding-an-image-and-data-urls.md]]
|
924
944
|
*/
|
925
945
|
toDataURL(format = 'image/png', outputSize) {
|
926
946
|
const canvas = document.createElement('canvas');
|
package/dist/mjs/Pointer.mjs
CHANGED
@@ -112,7 +112,7 @@ export default class Pointer {
|
|
112
112
|
// Intended for unit tests.
|
113
113
|
static ofCanvasPoint(canvasPos, isDown, viewport, id = 0, device = PointerDevice.Pen, isPrimary = true, pressure = null) {
|
114
114
|
const screenPos = viewport.canvasToScreen(canvasPos);
|
115
|
-
const timeStamp =
|
115
|
+
const timeStamp = performance.now();
|
116
116
|
return new Pointer(screenPos, canvasPos, pressure, isPrimary, isDown, device, id, timeStamp);
|
117
117
|
}
|
118
118
|
}
|
package/dist/mjs/Viewport.d.ts
CHANGED
@@ -50,6 +50,11 @@ export declare class Viewport {
|
|
50
50
|
private getScaleFactorToNearestPowerOf;
|
51
51
|
/** Returns the size of a grid cell (in canvas units) as used by {@link snapToGrid}. */
|
52
52
|
static getGridSize(scaleFactor: number): number;
|
53
|
+
/**
|
54
|
+
* Snaps `canvasPos` to the nearest grid cell corner.
|
55
|
+
*
|
56
|
+
* @see {@link getGridSize} and {@link getScaleFactorToNearestPowerOf}.
|
57
|
+
*/
|
53
58
|
snapToGrid(canvasPos: Point2): Vec3;
|
54
59
|
/** Returns the size of one screen pixel in canvas units. */
|
55
60
|
getSizeOfPixelOnCanvas(): number;
|
@@ -65,6 +70,7 @@ export declare class Viewport {
|
|
65
70
|
* its original location. This is useful for preparing data for base-10 conversion.
|
66
71
|
*/
|
67
72
|
static roundPoint<T extends Point2 | number>(point: T, tolerance: number): PointDataType<T>;
|
73
|
+
/** Round a point with a tolerance of ±1 screen unit. */
|
68
74
|
roundPoint(point: Point2): Point2;
|
69
75
|
static roundScaleRatio(scaleRatio: number, roundAmount?: number): number;
|
70
76
|
computeZoomToTransform(toMakeVisible: Rect2, allowZoomIn?: boolean, allowZoomOut?: boolean): Mat33;
|
package/dist/mjs/Viewport.mjs
CHANGED
@@ -97,6 +97,11 @@ export class Viewport {
|
|
97
97
|
static getGridSize(scaleFactor) {
|
98
98
|
return 50 / scaleFactor;
|
99
99
|
}
|
100
|
+
/**
|
101
|
+
* Snaps `canvasPos` to the nearest grid cell corner.
|
102
|
+
*
|
103
|
+
* @see {@link getGridSize} and {@link getScaleFactorToNearestPowerOf}.
|
104
|
+
*/
|
100
105
|
snapToGrid(canvasPos) {
|
101
106
|
const scaleFactor = this.getScaleFactorToNearestPowerOf(2);
|
102
107
|
const snapCoordinate = (coordinate) => {
|
@@ -133,7 +138,7 @@ export class Viewport {
|
|
133
138
|
}
|
134
139
|
return point.map(roundComponent);
|
135
140
|
}
|
136
|
-
|
141
|
+
/** Round a point with a tolerance of ±1 screen unit. */
|
137
142
|
roundPoint(point) {
|
138
143
|
return Viewport.roundPoint(point, 1 / this.getScaleFactor());
|
139
144
|
}
|
@@ -5,8 +5,28 @@ import SerializableCommand from './SerializableCommand';
|
|
5
5
|
/**
|
6
6
|
* Removes the given {@link AbstractComponent}s from the image.
|
7
7
|
*
|
8
|
-
*
|
9
|
-
* ```ts
|
8
|
+
* **Example**:
|
9
|
+
* ```ts,runnable
|
10
|
+
* import { Editor, Erase, uniteCommands, Color4, Path, Stroke, Rect2, pathToRenderable } from 'js-draw';
|
11
|
+
*
|
12
|
+
* const editor = new Editor(document.body);
|
13
|
+
* editor.addToolbar();
|
14
|
+
*
|
15
|
+
* // Add a large number of strokes
|
16
|
+
* const commands = [];
|
17
|
+
* for (let x = -20; x < 20; x++) {
|
18
|
+
* for (let y = 0; y < 60; y++) {
|
19
|
+
* const stroke = new Stroke([
|
20
|
+
* pathToRenderable(
|
21
|
+
* Path.fromString(`m${x * 5},${y * 5}l1,1`),
|
22
|
+
* { fill: Color4.transparent, stroke: {width: 2, color: Color4.ofRGB(x / 10, y / 10, 0.5)}} )
|
23
|
+
* ]);
|
24
|
+
* commands.push(editor.image.addElement(stroke));
|
25
|
+
* }
|
26
|
+
* }
|
27
|
+
* await editor.dispatch(uniteCommands(commands, 100));
|
28
|
+
*
|
29
|
+
* ---visible---
|
10
30
|
* // Given some editor...
|
11
31
|
*
|
12
32
|
* // Find all elements intersecting the rectangle with top left (-10,-30) and
|
@@ -5,8 +5,28 @@ import SerializableCommand from './SerializableCommand.mjs';
|
|
5
5
|
/**
|
6
6
|
* Removes the given {@link AbstractComponent}s from the image.
|
7
7
|
*
|
8
|
-
*
|
9
|
-
* ```ts
|
8
|
+
* **Example**:
|
9
|
+
* ```ts,runnable
|
10
|
+
* import { Editor, Erase, uniteCommands, Color4, Path, Stroke, Rect2, pathToRenderable } from 'js-draw';
|
11
|
+
*
|
12
|
+
* const editor = new Editor(document.body);
|
13
|
+
* editor.addToolbar();
|
14
|
+
*
|
15
|
+
* // Add a large number of strokes
|
16
|
+
* const commands = [];
|
17
|
+
* for (let x = -20; x < 20; x++) {
|
18
|
+
* for (let y = 0; y < 60; y++) {
|
19
|
+
* const stroke = new Stroke([
|
20
|
+
* pathToRenderable(
|
21
|
+
* Path.fromString(`m${x * 5},${y * 5}l1,1`),
|
22
|
+
* { fill: Color4.transparent, stroke: {width: 2, color: Color4.ofRGB(x / 10, y / 10, 0.5)}} )
|
23
|
+
* ]);
|
24
|
+
* commands.push(editor.image.addElement(stroke));
|
25
|
+
* }
|
26
|
+
* }
|
27
|
+
* await editor.dispatch(uniteCommands(commands, 100));
|
28
|
+
*
|
29
|
+
* ---visible---
|
10
30
|
* // Given some editor...
|
11
31
|
*
|
12
32
|
* // Find all elements intersecting the rectangle with top left (-10,-30) and
|
@@ -1,4 +1,40 @@
|
|
1
1
|
import Command from './Command';
|
2
2
|
import SerializableCommand from './SerializableCommand';
|
3
|
+
/**
|
4
|
+
* Creates a single command from `commands`. This is useful when undoing should undo *all* commands
|
5
|
+
* in `commands` at once, rather than one at a time.
|
6
|
+
*
|
7
|
+
* @example
|
8
|
+
*
|
9
|
+
* ```ts,runnable
|
10
|
+
* import { Editor, pathToRenderable, Stroke, uniteCommands } from 'js-draw';
|
11
|
+
* import { Path, Color4 } from '@js-draw/math';
|
12
|
+
*
|
13
|
+
* const editor = new Editor(document.body);
|
14
|
+
* editor.addToolbar();
|
15
|
+
*
|
16
|
+
* // Create strokes!
|
17
|
+
* const strokes = [];
|
18
|
+
* for (let i = 0; i < 10; i++) {
|
19
|
+
* const renderablePath = pathToRenderable(
|
20
|
+
* Path.fromString(`M0,${i * 10} L100,100 L300,30 z`),
|
21
|
+
* { fill: Color4.transparent, stroke: { color: Color4.red, width: 1, } }
|
22
|
+
* );
|
23
|
+
* strokes.push(new Stroke([ renderablePath ]));
|
24
|
+
* }
|
25
|
+
*
|
26
|
+
* // Convert to commands
|
27
|
+
* const addStrokesCommands = strokes.map(stroke => editor.image.addElement(stroke));
|
28
|
+
*
|
29
|
+
* // Apply all as a single undoable command (try applying each in a loop instead!)
|
30
|
+
* await editor.dispatch(uniteCommands(addStrokesCommands));
|
31
|
+
*
|
32
|
+
* // The second parameter to uniteCommands is for very large numbers of commands, when
|
33
|
+
* // applying them shouldn't be done all at once (which would block the UI).
|
34
|
+
*
|
35
|
+
* // The second parameter to uniteCommands is for very large numbers of commands, when
|
36
|
+
* // applying them shouldn't be done all at once (which would block the UI).
|
37
|
+
* ```
|
38
|
+
*/
|
3
39
|
declare const uniteCommands: <T extends Command>(commands: T[], applyChunkSize?: number) => T extends SerializableCommand ? SerializableCommand : Command;
|
4
40
|
export default uniteCommands;
|
@@ -84,6 +84,42 @@ class SerializableUnion extends SerializableCommand {
|
|
84
84
|
return this.nonserializableCommand.description(editor, localizationTable);
|
85
85
|
}
|
86
86
|
}
|
87
|
+
/**
|
88
|
+
* Creates a single command from `commands`. This is useful when undoing should undo *all* commands
|
89
|
+
* in `commands` at once, rather than one at a time.
|
90
|
+
*
|
91
|
+
* @example
|
92
|
+
*
|
93
|
+
* ```ts,runnable
|
94
|
+
* import { Editor, pathToRenderable, Stroke, uniteCommands } from 'js-draw';
|
95
|
+
* import { Path, Color4 } from '@js-draw/math';
|
96
|
+
*
|
97
|
+
* const editor = new Editor(document.body);
|
98
|
+
* editor.addToolbar();
|
99
|
+
*
|
100
|
+
* // Create strokes!
|
101
|
+
* const strokes = [];
|
102
|
+
* for (let i = 0; i < 10; i++) {
|
103
|
+
* const renderablePath = pathToRenderable(
|
104
|
+
* Path.fromString(`M0,${i * 10} L100,100 L300,30 z`),
|
105
|
+
* { fill: Color4.transparent, stroke: { color: Color4.red, width: 1, } }
|
106
|
+
* );
|
107
|
+
* strokes.push(new Stroke([ renderablePath ]));
|
108
|
+
* }
|
109
|
+
*
|
110
|
+
* // Convert to commands
|
111
|
+
* const addStrokesCommands = strokes.map(stroke => editor.image.addElement(stroke));
|
112
|
+
*
|
113
|
+
* // Apply all as a single undoable command (try applying each in a loop instead!)
|
114
|
+
* await editor.dispatch(uniteCommands(addStrokesCommands));
|
115
|
+
*
|
116
|
+
* // The second parameter to uniteCommands is for very large numbers of commands, when
|
117
|
+
* // applying them shouldn't be done all at once (which would block the UI).
|
118
|
+
*
|
119
|
+
* // The second parameter to uniteCommands is for very large numbers of commands, when
|
120
|
+
* // applying them shouldn't be done all at once (which would block the UI).
|
121
|
+
* ```
|
122
|
+
*/
|
87
123
|
const uniteCommands = (commands, applyChunkSize) => {
|
88
124
|
let allSerializable = true;
|
89
125
|
for (const command of commands) {
|
@@ -2,12 +2,24 @@ import { Mat33Array, Rect2, Mat33, LineSegment2 } from '@js-draw/math';
|
|
2
2
|
import AbstractRenderer, { RenderableImage } from '../rendering/renderers/AbstractRenderer';
|
3
3
|
import AbstractComponent from './AbstractComponent';
|
4
4
|
import { ImageComponentLocalization } from './localization';
|
5
|
+
/**
|
6
|
+
* Represents a raster image.
|
7
|
+
*
|
8
|
+
* **Example: Adding images**:
|
9
|
+
* [[include:doc-pages/inline-examples/adding-an-image-and-data-urls.md]]
|
10
|
+
*/
|
5
11
|
export default class ImageComponent extends AbstractComponent {
|
6
12
|
protected contentBBox: Rect2;
|
7
13
|
private image;
|
8
14
|
constructor(image: RenderableImage);
|
9
15
|
private getImageRect;
|
10
16
|
private recomputeBBox;
|
17
|
+
/**
|
18
|
+
* Load from an image. Waits for the image to load if incomplete.
|
19
|
+
*
|
20
|
+
* The image, `elem`, must not [taint](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image#security_and_tainted_canvases)
|
21
|
+
* an HTMLCanvasElement when rendered.
|
22
|
+
*/
|
11
23
|
static fromImage(elem: HTMLImageElement, transform: Mat33): Promise<ImageComponent>;
|
12
24
|
render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
|
13
25
|
getProportionalRenderingTime(): number;
|
@@ -1,7 +1,13 @@
|
|
1
1
|
import { Rect2, Mat33 } from '@js-draw/math';
|
2
2
|
import { assertIsNumber, assertIsNumberArray } from '../util/assertions.mjs';
|
3
3
|
import AbstractComponent from './AbstractComponent.mjs';
|
4
|
-
|
4
|
+
import waitForImageLoaded from '../util/waitForImageLoaded.mjs';
|
5
|
+
/**
|
6
|
+
* Represents a raster image.
|
7
|
+
*
|
8
|
+
* **Example: Adding images**:
|
9
|
+
* [[include:doc-pages/inline-examples/adding-an-image-and-data-urls.md]]
|
10
|
+
*/
|
5
11
|
export default class ImageComponent extends AbstractComponent {
|
6
12
|
constructor(image) {
|
7
13
|
super('image-component');
|
@@ -24,15 +30,14 @@ export default class ImageComponent extends AbstractComponent {
|
|
24
30
|
this.contentBBox = this.getImageRect();
|
25
31
|
this.contentBBox = this.contentBBox.transformedBoundingBox(this.image.transform);
|
26
32
|
}
|
27
|
-
|
33
|
+
/**
|
34
|
+
* Load from an image. Waits for the image to load if incomplete.
|
35
|
+
*
|
36
|
+
* The image, `elem`, must not [taint](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image#security_and_tainted_canvases)
|
37
|
+
* an HTMLCanvasElement when rendered.
|
38
|
+
*/
|
28
39
|
static async fromImage(elem, transform) {
|
29
|
-
|
30
|
-
await new Promise((resolve, reject) => {
|
31
|
-
elem.onload = resolve;
|
32
|
-
elem.onerror = reject;
|
33
|
-
elem.onabort = reject;
|
34
|
-
});
|
35
|
-
}
|
40
|
+
await waitForImageLoaded(elem);
|
36
41
|
let width, height;
|
37
42
|
if (typeof elem.width === 'number' && typeof elem.height === 'number'
|
38
43
|
&& elem.width !== 0 && elem.height !== 0) {
|
@@ -74,6 +79,7 @@ export default class ImageComponent extends AbstractComponent {
|
|
74
79
|
canvas.drawImage(this.image);
|
75
80
|
canvas.endObject(this.getLoadSaveData());
|
76
81
|
}
|
82
|
+
// A *very* rough estimate of how long it takes to render this component
|
77
83
|
getProportionalRenderingTime() {
|
78
84
|
// Estimate: Equivalent to a stroke with 10 segments.
|
79
85
|
return 10;
|
@@ -98,6 +104,7 @@ export default class ImageComponent extends AbstractComponent {
|
|
98
104
|
getAltText() {
|
99
105
|
return this.image.label;
|
100
106
|
}
|
107
|
+
// The base64 image URL of this image.
|
101
108
|
getURL() {
|
102
109
|
return this.image.base64Url;
|
103
110
|
}
|
@@ -5,7 +5,7 @@ import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
|
5
5
|
import AbstractComponent from './AbstractComponent';
|
6
6
|
import { ImageComponentLocalization } from './localization';
|
7
7
|
import RestyleableComponent, { ComponentStyle } from './RestylableComponent';
|
8
|
-
import RenderablePathSpec from '../rendering/RenderablePathSpec';
|
8
|
+
import RenderablePathSpec, { RenderablePathSpecWithPath } from '../rendering/RenderablePathSpec';
|
9
9
|
/**
|
10
10
|
* Represents an {@link AbstractComponent} made up of one or more {@link Path}s.
|
11
11
|
*
|
@@ -21,6 +21,9 @@ import RenderablePathSpec from '../rendering/RenderablePathSpec';
|
|
21
21
|
* ```ts
|
22
22
|
* editor.dispatch(stroke.transformBy(Mat33.translation(Vec2.of(10, 0))));
|
23
23
|
* ```
|
24
|
+
*
|
25
|
+
* **Adding**:
|
26
|
+
* [[include:doc-pages/inline-examples/adding-a-stroke.md]]
|
24
27
|
*/
|
25
28
|
export default class Stroke extends AbstractComponent implements RestyleableComponent {
|
26
29
|
private parts;
|
@@ -39,7 +42,7 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
|
|
39
42
|
*
|
40
43
|
* const stroke = new Stroke([
|
41
44
|
* // Fill with red
|
42
|
-
* pathToRenderable({ fill: Color4.red })
|
45
|
+
* pathToRenderable(path, { fill: Color4.red })
|
43
46
|
* ]);
|
44
47
|
* ```
|
45
48
|
*/
|
@@ -57,6 +60,17 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
|
|
57
60
|
private bboxForPart;
|
58
61
|
getExactBBox(): Rect2;
|
59
62
|
protected applyTransformation(affineTransfm: Mat33): void;
|
63
|
+
/**
|
64
|
+
* @returns A list of the parts that make up this path. Many paths only have one part.
|
65
|
+
*
|
66
|
+
* Each part (a {@link RenderablePathSpec}) contains information about the style and geometry
|
67
|
+
* of that part of the stroke. Use the `.path` property to do collision detection and other
|
68
|
+
* operations involving the stroke's geometry.
|
69
|
+
*
|
70
|
+
* Note that many of {@link Path}'s methods (e.g. {@link Path.intersection}) take a
|
71
|
+
* `strokeWidth` parameter that can be gotten from {@link RenderablePathSpec.style} `.stroke.width`.
|
72
|
+
*/
|
73
|
+
getParts(): Readonly<RenderablePathSpecWithPath>[];
|
60
74
|
/**
|
61
75
|
* @returns the {@link Path.union} of all paths that make up this stroke.
|
62
76
|
*/
|
@@ -18,6 +18,9 @@ import { pathFromRenderable, pathToRenderable, simplifyPathToFullScreenOrEmpty
|
|
18
18
|
* ```ts
|
19
19
|
* editor.dispatch(stroke.transformBy(Mat33.translation(Vec2.of(10, 0))));
|
20
20
|
* ```
|
21
|
+
*
|
22
|
+
* **Adding**:
|
23
|
+
* [[include:doc-pages/inline-examples/adding-a-stroke.md]]
|
21
24
|
*/
|
22
25
|
export default class Stroke extends AbstractComponent {
|
23
26
|
/**
|
@@ -32,7 +35,7 @@ export default class Stroke extends AbstractComponent {
|
|
32
35
|
*
|
33
36
|
* const stroke = new Stroke([
|
34
37
|
* // Fill with red
|
35
|
-
* pathToRenderable({ fill: Color4.red })
|
38
|
+
* pathToRenderable(path, { fill: Color4.red })
|
36
39
|
* ]);
|
37
40
|
* ```
|
38
41
|
*/
|
@@ -290,6 +293,19 @@ export default class Stroke extends AbstractComponent {
|
|
290
293
|
};
|
291
294
|
});
|
292
295
|
}
|
296
|
+
/**
|
297
|
+
* @returns A list of the parts that make up this path. Many paths only have one part.
|
298
|
+
*
|
299
|
+
* Each part (a {@link RenderablePathSpec}) contains information about the style and geometry
|
300
|
+
* of that part of the stroke. Use the `.path` property to do collision detection and other
|
301
|
+
* operations involving the stroke's geometry.
|
302
|
+
*
|
303
|
+
* Note that many of {@link Path}'s methods (e.g. {@link Path.intersection}) take a
|
304
|
+
* `strokeWidth` parameter that can be gotten from {@link RenderablePathSpec.style} `.stroke.width`.
|
305
|
+
*/
|
306
|
+
getParts() {
|
307
|
+
return [...this.parts];
|
308
|
+
}
|
293
309
|
/**
|
294
310
|
* @returns the {@link Path.union} of all paths that make up this stroke.
|
295
311
|
*/
|
@@ -1,8 +1,9 @@
|
|
1
1
|
import { Path, PathCommandType } from '@js-draw/math';
|
2
2
|
import Stroke from '../Stroke.mjs';
|
3
|
-
|
3
|
+
import makeSnapToGridAutocorrect from './autocorrect/makeSnapToGridAutocorrect.mjs';
|
4
|
+
export const makeArrowBuilder = makeSnapToGridAutocorrect((initialPoint, viewport) => {
|
4
5
|
return new ArrowBuilder(initialPoint, viewport);
|
5
|
-
};
|
6
|
+
});
|
6
7
|
export default class ArrowBuilder {
|
7
8
|
constructor(startPoint, viewport) {
|
8
9
|
this.startPoint = startPoint;
|
@@ -2,9 +2,10 @@ import { Vec2, Path, PathCommandType, Color4 } from '@js-draw/math';
|
|
2
2
|
import { pathToRenderable } from '../../rendering/RenderablePathSpec.mjs';
|
3
3
|
import Viewport from '../../Viewport.mjs';
|
4
4
|
import Stroke from '../Stroke.mjs';
|
5
|
-
|
5
|
+
import makeSnapToGridAutocorrect from './autocorrect/makeSnapToGridAutocorrect.mjs';
|
6
|
+
export const makeOutlinedCircleBuilder = makeSnapToGridAutocorrect((initialPoint, viewport) => {
|
6
7
|
return new CircleBuilder(initialPoint, viewport);
|
7
|
-
};
|
8
|
+
});
|
8
9
|
class CircleBuilder {
|
9
10
|
constructor(startPoint, viewport) {
|
10
11
|
this.startPoint = startPoint;
|
@@ -2,13 +2,14 @@ import { Vec2, Rect2, Color4, PathCommandType } from '@js-draw/math';
|
|
2
2
|
import Stroke from '../Stroke.mjs';
|
3
3
|
import Viewport from '../../Viewport.mjs';
|
4
4
|
import { StrokeSmoother } from '../util/StrokeSmoother.mjs';
|
5
|
-
|
5
|
+
import makeShapeFitAutocorrect from './autocorrect/makeShapeFitAutocorrect.mjs';
|
6
|
+
export const makeFreehandLineBuilder = makeShapeFitAutocorrect((initialPoint, viewport) => {
|
6
7
|
// Don't smooth if input is more than ± 3 pixels from the true curve, do smooth if
|
7
8
|
// less than ±1 px from the curve.
|
8
9
|
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 3;
|
9
10
|
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
|
10
11
|
return new FreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist, viewport);
|
11
|
-
};
|
12
|
+
});
|
12
13
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
13
14
|
export default class FreehandLineBuilder {
|
14
15
|
constructor(startPoint, minFitAllowed, maxFitAllowed, viewport) {
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import { Path, PathCommandType } from '@js-draw/math';
|
2
2
|
import { pathToRenderable } from '../../rendering/RenderablePathSpec.mjs';
|
3
3
|
import Stroke from '../Stroke.mjs';
|
4
|
-
|
4
|
+
import makeSnapToGridAutocorrect from './autocorrect/makeSnapToGridAutocorrect.mjs';
|
5
|
+
export const makeLineBuilder = makeSnapToGridAutocorrect((initialPoint, viewport) => {
|
5
6
|
return new LineBuilder(initialPoint, viewport);
|
6
|
-
};
|
7
|
+
});
|
7
8
|
export default class LineBuilder {
|
8
9
|
constructor(startPoint, viewport) {
|
9
10
|
this.startPoint = startPoint;
|
@@ -3,13 +3,14 @@ import { Vec2, Rect2, PathCommandType } from '@js-draw/math';
|
|
3
3
|
import Stroke from '../Stroke.mjs';
|
4
4
|
import Viewport from '../../Viewport.mjs';
|
5
5
|
import { StrokeSmoother } from '../util/StrokeSmoother.mjs';
|
6
|
-
|
6
|
+
import makeShapeFitAutocorrect from './autocorrect/makeShapeFitAutocorrect.mjs';
|
7
|
+
export const makePressureSensitiveFreehandLineBuilder = makeShapeFitAutocorrect((initialPoint, viewport) => {
|
7
8
|
// Don't smooth if input is more than ± 3 pixels from the true curve, do smooth if
|
8
9
|
// less than ±1 px from the curve.
|
9
10
|
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 3;
|
10
11
|
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
|
11
12
|
return new PressureSensitiveFreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist, viewport);
|
12
|
-
};
|
13
|
+
});
|
13
14
|
// Handles stroke smoothing and creates Strokes from user/stylus input.
|
14
15
|
export default class PressureSensitiveFreehandLineBuilder {
|
15
16
|
constructor(startPoint,
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import { Mat33, Rect2, Path } from '@js-draw/math';
|
2
2
|
import { pathToRenderable } from '../../rendering/RenderablePathSpec.mjs';
|
3
3
|
import Stroke from '../Stroke.mjs';
|
4
|
-
|
4
|
+
import makeSnapToGridAutocorrect from './autocorrect/makeSnapToGridAutocorrect.mjs';
|
5
|
+
export const makeFilledRectangleBuilder = makeSnapToGridAutocorrect((initialPoint, viewport) => {
|
5
6
|
return new RectangleBuilder(initialPoint, true, viewport);
|
6
|
-
};
|
7
|
-
export const makeOutlinedRectangleBuilder = (initialPoint, viewport) => {
|
7
|
+
});
|
8
|
+
export const makeOutlinedRectangleBuilder = makeSnapToGridAutocorrect((initialPoint, viewport) => {
|
8
9
|
return new RectangleBuilder(initialPoint, false, viewport);
|
9
|
-
};
|
10
|
+
});
|
10
11
|
export default class RectangleBuilder {
|
11
12
|
constructor(startPoint, filled, viewport) {
|
12
13
|
this.startPoint = startPoint;
|