js-draw 0.3.2 → 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/CHANGELOG.md +9 -1
- package/README.md +1 -3
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +11 -0
- package/dist/src/Editor.js +104 -76
- package/dist/src/Pointer.d.ts +1 -1
- package/dist/src/Pointer.js +8 -3
- package/dist/src/Viewport.d.ts +1 -0
- package/dist/src/Viewport.js +14 -1
- package/dist/src/components/ImageComponent.d.ts +2 -2
- package/dist/src/language/assertions.d.ts +1 -0
- package/dist/src/language/assertions.js +5 -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/renderers/AbstractRenderer.js +2 -1
- package/dist/src/toolbar/HTMLToolbar.js +5 -4
- package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +1 -1
- package/dist/src/tools/PasteHandler.js +3 -1
- 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 +1 -1
- package/dist/src/tools/lib.d.ts +1 -1
- package/dist/src/tools/lib.js +1 -1
- package/dist/src/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/Editor.css +1 -0
- package/src/Editor.ts +145 -108
- package/src/Pointer.ts +8 -3
- package/src/Viewport.ts +17 -2
- package/src/components/AbstractComponent.ts +2 -6
- package/src/components/ImageComponent.ts +2 -6
- package/src/components/Text.ts +2 -6
- package/src/language/assertions.ts +6 -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/renderers/AbstractRenderer.ts +3 -2
- package/src/toolbar/HTMLToolbar.ts +5 -4
- package/src/toolbar/widgets/SelectionToolWidget.ts +1 -1
- package/src/tools/PasteHandler.ts +4 -1
- 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 +1 -1
- package/src/tools/lib.ts +1 -1
- package/src/types.ts +1 -1
- package/dist/src/tools/SelectionTool.d.ts +0 -65
- package/dist/src/tools/SelectionTool.js +0 -647
- package/src/tools/SelectionTool.ts +0 -797
package/dist/src/Editor.d.ts
CHANGED
@@ -28,6 +28,8 @@ import Display, { RenderingMode } from './rendering/Display';
|
|
28
28
|
import Pointer from './Pointer';
|
29
29
|
import Rect2 from './math/Rect2';
|
30
30
|
import { EditorLocalization } from './localization';
|
31
|
+
declare type HTMLPointerEventType = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel';
|
32
|
+
declare type HTMLPointerEventFilter = (eventName: HTMLPointerEventType, event: PointerEvent) => boolean;
|
31
33
|
export interface EditorSettings {
|
32
34
|
/** Defaults to `RenderingMode.CanvasRenderer` */
|
33
35
|
renderingMode: RenderingMode;
|
@@ -140,8 +142,16 @@ export declare class Editor {
|
|
140
142
|
*/
|
141
143
|
addToolbar(defaultLayout?: boolean): HTMLToolbar;
|
142
144
|
private registerListeners;
|
145
|
+
private pointers;
|
146
|
+
private getPointerList;
|
147
|
+
/**
|
148
|
+
* Dispatches a `PointerEvent` to the editor. The target element for `evt` must have the same top left
|
149
|
+
* as the content of the editor.
|
150
|
+
*/
|
151
|
+
handleHTMLPointerEvent(eventType: 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel', evt: PointerEvent): boolean;
|
143
152
|
private isEventSink;
|
144
153
|
private handlePaste;
|
154
|
+
handlePointerEventsFrom(elem: HTMLElement, filter?: HTMLPointerEventFilter): void;
|
145
155
|
/** Adds event listners for keypresses to `elem` and forwards those events to the editor. */
|
146
156
|
handleKeyEventsFrom(elem: HTMLElement): void;
|
147
157
|
/** `apply` a command. `command` will be announced for accessibility. */
|
@@ -183,6 +193,7 @@ export declare class Editor {
|
|
183
193
|
remove: () => void;
|
184
194
|
};
|
185
195
|
addStyleSheet(content: string): HTMLStyleElement;
|
196
|
+
sendKeyboardEvent(eventType: InputEvtType.KeyPressEvent | InputEvtType.KeyUpEvent, key: string, ctrlKey?: boolean): void;
|
186
197
|
sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
|
187
198
|
toSVG(): SVGElement;
|
188
199
|
loadFrom(loader: ImageLoader): Promise<void>;
|
package/dist/src/Editor.js
CHANGED
@@ -70,6 +70,7 @@ export class Editor {
|
|
70
70
|
var _a, _b, _c, _d;
|
71
71
|
this.eventListenerTargets = [];
|
72
72
|
this.previousAccessibilityAnnouncement = '';
|
73
|
+
this.pointers = {};
|
73
74
|
this.announceUndoCallback = (command) => {
|
74
75
|
this.announceForAccessibility(this.localization.undoAnnouncement(command.description(this, this.localization)));
|
75
76
|
};
|
@@ -182,81 +183,7 @@ export class Editor {
|
|
182
183
|
return toolbar;
|
183
184
|
}
|
184
185
|
registerListeners() {
|
185
|
-
|
186
|
-
const getPointerList = () => {
|
187
|
-
const nowTime = (new Date()).getTime();
|
188
|
-
const res = [];
|
189
|
-
for (const id in pointers) {
|
190
|
-
const maxUnupdatedTime = 2000; // Maximum time without a pointer update (ms)
|
191
|
-
if (pointers[id] && (nowTime - pointers[id].timeStamp) < maxUnupdatedTime) {
|
192
|
-
res.push(pointers[id]);
|
193
|
-
}
|
194
|
-
}
|
195
|
-
return res;
|
196
|
-
};
|
197
|
-
// May be required to prevent text selection on iOS/Safari:
|
198
|
-
// See https://stackoverflow.com/a/70992717/17055750
|
199
|
-
this.renderingRegion.addEventListener('touchstart', evt => evt.preventDefault());
|
200
|
-
this.renderingRegion.addEventListener('contextmenu', evt => {
|
201
|
-
// Don't show a context menu
|
202
|
-
evt.preventDefault();
|
203
|
-
});
|
204
|
-
this.renderingRegion.addEventListener('pointerdown', evt => {
|
205
|
-
const pointer = Pointer.ofEvent(evt, true, this.viewport);
|
206
|
-
pointers[pointer.id] = pointer;
|
207
|
-
this.renderingRegion.setPointerCapture(pointer.id);
|
208
|
-
const event = {
|
209
|
-
kind: InputEvtType.PointerDownEvt,
|
210
|
-
current: pointer,
|
211
|
-
allPointers: getPointerList(),
|
212
|
-
};
|
213
|
-
this.toolController.dispatchInputEvent(event);
|
214
|
-
return true;
|
215
|
-
});
|
216
|
-
this.renderingRegion.addEventListener('pointermove', evt => {
|
217
|
-
var _a, _b;
|
218
|
-
const pointer = Pointer.ofEvent(evt, (_b = (_a = pointers[evt.pointerId]) === null || _a === void 0 ? void 0 : _a.down) !== null && _b !== void 0 ? _b : false, this.viewport);
|
219
|
-
if (pointer.down) {
|
220
|
-
const prevData = pointers[pointer.id];
|
221
|
-
if (prevData) {
|
222
|
-
const distanceMoved = pointer.screenPos.minus(prevData.screenPos).magnitude();
|
223
|
-
// If the pointer moved less than two pixels, don't send a new event.
|
224
|
-
if (distanceMoved < 2) {
|
225
|
-
return;
|
226
|
-
}
|
227
|
-
}
|
228
|
-
pointers[pointer.id] = pointer;
|
229
|
-
if (this.toolController.dispatchInputEvent({
|
230
|
-
kind: InputEvtType.PointerMoveEvt,
|
231
|
-
current: pointer,
|
232
|
-
allPointers: getPointerList(),
|
233
|
-
})) {
|
234
|
-
evt.preventDefault();
|
235
|
-
}
|
236
|
-
}
|
237
|
-
});
|
238
|
-
const pointerEnd = (evt) => {
|
239
|
-
const pointer = Pointer.ofEvent(evt, false, this.viewport);
|
240
|
-
if (!pointers[pointer.id]) {
|
241
|
-
return;
|
242
|
-
}
|
243
|
-
pointers[pointer.id] = pointer;
|
244
|
-
this.renderingRegion.releasePointerCapture(pointer.id);
|
245
|
-
if (this.toolController.dispatchInputEvent({
|
246
|
-
kind: InputEvtType.PointerUpEvt,
|
247
|
-
current: pointer,
|
248
|
-
allPointers: getPointerList(),
|
249
|
-
})) {
|
250
|
-
evt.preventDefault();
|
251
|
-
}
|
252
|
-
delete pointers[pointer.id];
|
253
|
-
};
|
254
|
-
this.renderingRegion.addEventListener('pointerup', evt => {
|
255
|
-
pointerEnd(evt);
|
256
|
-
});
|
257
|
-
this.renderingRegion.addEventListener('pointercancel', evt => {
|
258
|
-
pointerEnd(evt);
|
259
|
-
});
|
186
|
+
this.handlePointerEventsFrom(this.renderingRegion);
|
260
187
|
this.handleKeyEventsFrom(this.renderingRegion);
|
261
188
|
this.container.addEventListener('wheel', evt => {
|
262
189
|
let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);
|
@@ -282,7 +209,9 @@ export class Editor {
|
|
282
209
|
if (evt.ctrlKey) {
|
283
210
|
delta = Vec3.of(0, 0, evt.deltaY);
|
284
211
|
}
|
285
|
-
|
212
|
+
// Ensure that `pos` is relative to `this.container`
|
213
|
+
const bbox = this.container.getBoundingClientRect();
|
214
|
+
const pos = Vec2.of(evt.clientX, evt.clientY).minus(Vec2.of(bbox.left, bbox.top));
|
286
215
|
if (this.toolController.dispatchInputEvent({
|
287
216
|
kind: InputEvtType.WheelEvt,
|
288
217
|
delta,
|
@@ -324,6 +253,78 @@ export class Editor {
|
|
324
253
|
this.handlePaste(evt);
|
325
254
|
});
|
326
255
|
}
|
256
|
+
getPointerList() {
|
257
|
+
const nowTime = (new Date()).getTime();
|
258
|
+
const res = [];
|
259
|
+
for (const id in this.pointers) {
|
260
|
+
const maxUnupdatedTime = 2000; // Maximum time without a pointer update (ms)
|
261
|
+
if (this.pointers[id] && (nowTime - this.pointers[id].timeStamp) < maxUnupdatedTime) {
|
262
|
+
res.push(this.pointers[id]);
|
263
|
+
}
|
264
|
+
}
|
265
|
+
return res;
|
266
|
+
}
|
267
|
+
/**
|
268
|
+
* Dispatches a `PointerEvent` to the editor. The target element for `evt` must have the same top left
|
269
|
+
* as the content of the editor.
|
270
|
+
*/
|
271
|
+
handleHTMLPointerEvent(eventType, evt) {
|
272
|
+
var _a, _b, _c;
|
273
|
+
const eventsRelativeTo = this.renderingRegion;
|
274
|
+
const eventTarget = (_a = evt.target) !== null && _a !== void 0 ? _a : this.renderingRegion;
|
275
|
+
if (eventType === 'pointerdown') {
|
276
|
+
const pointer = Pointer.ofEvent(evt, true, this.viewport, eventsRelativeTo);
|
277
|
+
this.pointers[pointer.id] = pointer;
|
278
|
+
eventTarget.setPointerCapture(pointer.id);
|
279
|
+
const event = {
|
280
|
+
kind: InputEvtType.PointerDownEvt,
|
281
|
+
current: pointer,
|
282
|
+
allPointers: this.getPointerList(),
|
283
|
+
};
|
284
|
+
this.toolController.dispatchInputEvent(event);
|
285
|
+
return true;
|
286
|
+
}
|
287
|
+
else if (eventType === 'pointermove') {
|
288
|
+
const pointer = Pointer.ofEvent(evt, (_c = (_b = this.pointers[evt.pointerId]) === null || _b === void 0 ? void 0 : _b.down) !== null && _c !== void 0 ? _c : false, this.viewport, eventsRelativeTo);
|
289
|
+
if (pointer.down) {
|
290
|
+
const prevData = this.pointers[pointer.id];
|
291
|
+
if (prevData) {
|
292
|
+
const distanceMoved = pointer.screenPos.minus(prevData.screenPos).magnitude();
|
293
|
+
// If the pointer moved less than two pixels, don't send a new event.
|
294
|
+
if (distanceMoved < 2) {
|
295
|
+
return false;
|
296
|
+
}
|
297
|
+
}
|
298
|
+
this.pointers[pointer.id] = pointer;
|
299
|
+
if (this.toolController.dispatchInputEvent({
|
300
|
+
kind: InputEvtType.PointerMoveEvt,
|
301
|
+
current: pointer,
|
302
|
+
allPointers: this.getPointerList(),
|
303
|
+
})) {
|
304
|
+
evt.preventDefault();
|
305
|
+
}
|
306
|
+
}
|
307
|
+
return true;
|
308
|
+
}
|
309
|
+
else if (eventType === 'pointercancel' || eventType === 'pointerup') {
|
310
|
+
const pointer = Pointer.ofEvent(evt, false, this.viewport, eventsRelativeTo);
|
311
|
+
if (!this.pointers[pointer.id]) {
|
312
|
+
return false;
|
313
|
+
}
|
314
|
+
this.pointers[pointer.id] = pointer;
|
315
|
+
eventTarget.releasePointerCapture(pointer.id);
|
316
|
+
if (this.toolController.dispatchInputEvent({
|
317
|
+
kind: InputEvtType.PointerUpEvt,
|
318
|
+
current: pointer,
|
319
|
+
allPointers: this.getPointerList(),
|
320
|
+
})) {
|
321
|
+
evt.preventDefault();
|
322
|
+
}
|
323
|
+
delete this.pointers[pointer.id];
|
324
|
+
return true;
|
325
|
+
}
|
326
|
+
return eventType;
|
327
|
+
}
|
327
328
|
isEventSink(evtTarget) {
|
328
329
|
let currentElem = evtTarget;
|
329
330
|
while (currentElem !== null) {
|
@@ -411,6 +412,24 @@ export class Editor {
|
|
411
412
|
}
|
412
413
|
});
|
413
414
|
}
|
415
|
+
handlePointerEventsFrom(elem, filter) {
|
416
|
+
// May be required to prevent text selection on iOS/Safari:
|
417
|
+
// See https://stackoverflow.com/a/70992717/17055750
|
418
|
+
elem.addEventListener('touchstart', evt => evt.preventDefault());
|
419
|
+
elem.addEventListener('contextmenu', evt => {
|
420
|
+
// Don't show a context menu
|
421
|
+
evt.preventDefault();
|
422
|
+
});
|
423
|
+
const eventNames = ['pointerdown', 'pointermove', 'pointerup', 'pointercancel'];
|
424
|
+
for (const eventName of eventNames) {
|
425
|
+
elem.addEventListener(eventName, evt => {
|
426
|
+
if (filter && !filter(eventName, evt)) {
|
427
|
+
return true;
|
428
|
+
}
|
429
|
+
return this.handleHTMLPointerEvent(eventName, evt);
|
430
|
+
});
|
431
|
+
}
|
432
|
+
}
|
414
433
|
/** Adds event listners for keypresses to `elem` and forwards those events to the editor. */
|
415
434
|
handleKeyEventsFrom(elem) {
|
416
435
|
elem.addEventListener('keydown', evt => {
|
@@ -577,6 +596,15 @@ export class Editor {
|
|
577
596
|
this.container.appendChild(styleSheet);
|
578
597
|
return styleSheet;
|
579
598
|
}
|
599
|
+
// Dispatch a keyboard event to the currently selected tool.
|
600
|
+
// Intended for unit testing
|
601
|
+
sendKeyboardEvent(eventType, key, ctrlKey = false) {
|
602
|
+
this.toolController.dispatchInputEvent({
|
603
|
+
kind: eventType,
|
604
|
+
key,
|
605
|
+
ctrlKey
|
606
|
+
});
|
607
|
+
}
|
580
608
|
// Dispatch a pen event to the currently selected tool.
|
581
609
|
// Intended primarially for unit tests.
|
582
610
|
sendPenEvent(eventType, point, allPointers) {
|
package/dist/src/Pointer.d.ts
CHANGED
@@ -18,6 +18,6 @@ export default class Pointer {
|
|
18
18
|
readonly id: number;
|
19
19
|
readonly timeStamp: number;
|
20
20
|
private constructor();
|
21
|
-
static ofEvent(evt: PointerEvent, isDown: boolean, viewport: Viewport): Pointer;
|
21
|
+
static ofEvent(evt: PointerEvent, isDown: boolean, viewport: Viewport, relativeTo?: HTMLElement): Pointer;
|
22
22
|
static ofCanvasPoint(canvasPos: Point2, isDown: boolean, viewport: Viewport, id?: number, device?: PointerDevice, isPrimary?: boolean, pressure?: number | null): Pointer;
|
23
23
|
}
|
package/dist/src/Pointer.js
CHANGED
@@ -31,10 +31,15 @@ export default class Pointer {
|
|
31
31
|
this.id = id;
|
32
32
|
this.timeStamp = timeStamp;
|
33
33
|
}
|
34
|
-
// Creates a Pointer from a DOM event.
|
35
|
-
|
34
|
+
// Creates a Pointer from a DOM event. If `relativeTo` is given, (0, 0) in screen coordinates is
|
35
|
+
// considered the top left of `relativeTo`.
|
36
|
+
static ofEvent(evt, isDown, viewport, relativeTo) {
|
36
37
|
var _a, _b;
|
37
|
-
|
38
|
+
let screenPos = Vec2.of(evt.clientX, evt.clientY);
|
39
|
+
if (relativeTo) {
|
40
|
+
const bbox = relativeTo.getBoundingClientRect();
|
41
|
+
screenPos = screenPos.minus(Vec2.of(bbox.left, bbox.top));
|
42
|
+
}
|
38
43
|
const pointerTypeToDevice = {
|
39
44
|
'mouse': PointerDevice.PrimaryButtonMouse,
|
40
45
|
'pen': PointerDevice.Pen,
|
package/dist/src/Viewport.d.ts
CHANGED
@@ -29,6 +29,7 @@ export declare class Viewport {
|
|
29
29
|
getRotationAngle(): number;
|
30
30
|
static roundPoint<T extends Point2 | number>(point: T, tolerance: number): PointDataType<T>;
|
31
31
|
roundPoint(point: Point2): Point2;
|
32
|
+
static roundScaleRatio(scaleRatio: number, roundAmount?: number): number;
|
32
33
|
computeZoomToTransform(toMakeVisible: Rect2, allowZoomIn?: boolean, allowZoomOut?: boolean): Mat33;
|
33
34
|
zoomTo(toMakeVisible: Rect2, allowZoomIn?: boolean, allowZoomOut?: boolean): Command;
|
34
35
|
}
|
package/dist/src/Viewport.js
CHANGED
@@ -73,7 +73,8 @@ export class Viewport {
|
|
73
73
|
getSizeOfPixelOnCanvas() {
|
74
74
|
return 1 / this.getScaleFactor();
|
75
75
|
}
|
76
|
-
// Returns the angle of the canvas in radians
|
76
|
+
// Returns the angle of the canvas in radians.
|
77
|
+
// This is the angle by which the canvas is rotated relative to the screen.
|
77
78
|
getRotationAngle() {
|
78
79
|
return this.transform.transformVec3(Vec3.unitX).angle();
|
79
80
|
}
|
@@ -94,6 +95,18 @@ export class Viewport {
|
|
94
95
|
roundPoint(point) {
|
95
96
|
return Viewport.roundPoint(point, 1 / this.getScaleFactor());
|
96
97
|
}
|
98
|
+
// `roundAmount`: An integer >= 0, larger numbers cause less rounding. Smaller numbers cause more
|
99
|
+
// (as such `roundAmount = 0` does the most rounding).
|
100
|
+
static roundScaleRatio(scaleRatio, roundAmount = 1) {
|
101
|
+
if (Math.abs(scaleRatio) <= 1e-12) {
|
102
|
+
return 0;
|
103
|
+
}
|
104
|
+
// Represent as k 10ⁿ for some n, k ∈ ℤ.
|
105
|
+
const decimalComponent = Math.pow(10, Math.floor(Math.log10(Math.abs(scaleRatio))));
|
106
|
+
const roundAnountFactor = Math.pow(2, roundAmount);
|
107
|
+
scaleRatio = Math.round(scaleRatio / decimalComponent * roundAnountFactor) / roundAnountFactor * decimalComponent;
|
108
|
+
return scaleRatio;
|
109
|
+
}
|
97
110
|
// Computes and returns an affine transformation that makes `toMakeVisible` visible and roughly centered on the screen.
|
98
111
|
computeZoomToTransform(toMakeVisible, allowZoomIn = true, allowZoomOut = true) {
|
99
112
|
let transform = Mat33.identity;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import LineSegment2 from '../math/LineSegment2';
|
2
|
-
import Mat33 from '../math/Mat33';
|
2
|
+
import Mat33, { Mat33Array } from '../math/Mat33';
|
3
3
|
import Rect2 from '../math/Rect2';
|
4
4
|
import AbstractRenderer, { RenderableImage } from '../rendering/renderers/AbstractRenderer';
|
5
5
|
import AbstractComponent from './AbstractComponent';
|
@@ -18,7 +18,7 @@ export default class ImageComponent extends AbstractComponent {
|
|
18
18
|
label: string | undefined;
|
19
19
|
width: number;
|
20
20
|
height: number;
|
21
|
-
transform:
|
21
|
+
transform: Mat33Array;
|
22
22
|
};
|
23
23
|
protected applyTransformation(affineTransfm: Mat33): void;
|
24
24
|
description(localizationTable: ImageComponentLocalization): string;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const assertUnreachable: (key: never) => never;
|
package/dist/src/math/Mat33.d.ts
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
import { Point2, Vec2 } from './Vec2';
|
2
2
|
import Vec3 from './Vec3';
|
3
|
+
export declare type Mat33Array = [
|
4
|
+
number,
|
5
|
+
number,
|
6
|
+
number,
|
7
|
+
number,
|
8
|
+
number,
|
9
|
+
number,
|
10
|
+
number,
|
11
|
+
number,
|
12
|
+
number
|
13
|
+
];
|
3
14
|
/**
|
4
15
|
* Represents a three dimensional linear transformation or
|
5
16
|
* a two-dimensional affine transformation. (An affine transformation scales/rotates/shears
|
@@ -68,11 +79,36 @@ export default class Mat33 {
|
|
68
79
|
* ...
|
69
80
|
* ```
|
70
81
|
*/
|
71
|
-
toArray():
|
82
|
+
toArray(): Mat33Array;
|
83
|
+
/**
|
84
|
+
* @example
|
85
|
+
* ```
|
86
|
+
* new Mat33(
|
87
|
+
* 1, 2, 3,
|
88
|
+
* 4, 5, 6,
|
89
|
+
* 7, 8, 9,
|
90
|
+
* ).mapEntries(component => component - 1);
|
91
|
+
* // → ⎡ 0, 1, 2 ⎤
|
92
|
+
* // ⎢ 3, 4, 5 ⎥
|
93
|
+
* // ⎣ 6, 7, 8 ⎦
|
94
|
+
* ```
|
95
|
+
*/
|
96
|
+
mapEntries(mapping: (component: number) => number): Mat33;
|
72
97
|
/** Constructs a 3x3 translation matrix (for translating `Vec2`s) */
|
73
98
|
static translation(amount: Vec2): Mat33;
|
74
99
|
static zRotation(radians: number, center?: Point2): Mat33;
|
75
100
|
static scaling2D(amount: number | Vec2, center?: Point2): Mat33;
|
76
|
-
/**
|
101
|
+
/** @see {@link !fromCSSMatrix} */
|
102
|
+
toCSSMatrix(): string;
|
103
|
+
/**
|
104
|
+
* Converts a CSS-form `matrix(a, b, c, d, e, f)` to a Mat33.
|
105
|
+
*
|
106
|
+
* Note that such a matrix has the form,
|
107
|
+
* ```
|
108
|
+
* ⎡ a c e ⎤
|
109
|
+
* ⎢ b d f ⎥
|
110
|
+
* ⎣ 0 0 1 ⎦
|
111
|
+
* ```
|
112
|
+
*/
|
77
113
|
static fromCSSMatrix(cssString: string): Mat33;
|
78
114
|
}
|
package/dist/src/math/Mat33.js
CHANGED
@@ -183,6 +183,22 @@ export default class Mat33 {
|
|
183
183
|
this.c1, this.c2, this.c3,
|
184
184
|
];
|
185
185
|
}
|
186
|
+
/**
|
187
|
+
* @example
|
188
|
+
* ```
|
189
|
+
* new Mat33(
|
190
|
+
* 1, 2, 3,
|
191
|
+
* 4, 5, 6,
|
192
|
+
* 7, 8, 9,
|
193
|
+
* ).mapEntries(component => component - 1);
|
194
|
+
* // → ⎡ 0, 1, 2 ⎤
|
195
|
+
* // ⎢ 3, 4, 5 ⎥
|
196
|
+
* // ⎣ 6, 7, 8 ⎦
|
197
|
+
* ```
|
198
|
+
*/
|
199
|
+
mapEntries(mapping) {
|
200
|
+
return new Mat33(mapping(this.a1), mapping(this.a2), mapping(this.a3), mapping(this.b1), mapping(this.b2), mapping(this.b3), mapping(this.c1), mapping(this.c2), mapping(this.c3));
|
201
|
+
}
|
186
202
|
/** Constructs a 3x3 translation matrix (for translating `Vec2`s) */
|
187
203
|
static translation(amount) {
|
188
204
|
// When transforming Vec2s by a 3x3 matrix, we give the input
|
@@ -214,7 +230,20 @@ export default class Mat33 {
|
|
214
230
|
// Translate such that [center] goes to (0, 0)
|
215
231
|
return result.rightMul(Mat33.translation(center.times(-1)));
|
216
232
|
}
|
217
|
-
/**
|
233
|
+
/** @see {@link !fromCSSMatrix} */
|
234
|
+
toCSSMatrix() {
|
235
|
+
return `matrix(${this.a1},${this.b1},${this.a2},${this.b2},${this.a3},${this.b3})`;
|
236
|
+
}
|
237
|
+
/**
|
238
|
+
* Converts a CSS-form `matrix(a, b, c, d, e, f)` to a Mat33.
|
239
|
+
*
|
240
|
+
* Note that such a matrix has the form,
|
241
|
+
* ```
|
242
|
+
* ⎡ a c e ⎤
|
243
|
+
* ⎢ b d f ⎥
|
244
|
+
* ⎣ 0 0 1 ⎦
|
245
|
+
* ```
|
246
|
+
*/
|
218
247
|
static fromCSSMatrix(cssString) {
|
219
248
|
if (cssString === '' || cssString === 'none') {
|
220
249
|
return Mat33.identity;
|
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;
|
@@ -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));
|
@@ -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 {
|