js-draw 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/bundle.js +1 -1
- package/dist/src/EditorImage.js +0 -1
- package/dist/src/components/Stroke.js +11 -6
- package/dist/src/components/builders/FreehandLineBuilder.js +6 -6
- package/dist/src/components/lib.d.ts +2 -0
- package/dist/src/components/lib.js +2 -0
- package/dist/src/lib.d.ts +5 -1
- package/dist/src/lib.js +5 -1
- package/dist/src/math/LineSegment2.d.ts +2 -0
- package/dist/src/math/LineSegment2.js +6 -0
- package/dist/src/math/Path.d.ts +5 -1
- package/dist/src/math/Path.js +89 -7
- package/dist/src/math/Rect2.js +1 -1
- package/dist/src/math/Triangle.d.ts +11 -0
- package/dist/src/math/Triangle.js +19 -0
- package/dist/src/rendering/Display.js +3 -3
- package/dist/src/rendering/caching/RenderingCacheNode.js +2 -1
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +6 -6
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +10 -11
- package/dist/src/rendering/renderers/SVGRenderer.js +27 -68
- package/dist/src/toolbar/HTMLToolbar.d.ts +5 -1
- package/dist/src/toolbar/HTMLToolbar.js +30 -31
- package/dist/src/toolbar/icons.d.ts +1 -1
- package/dist/src/toolbar/icons.js +4 -0
- package/dist/src/toolbar/lib.d.ts +3 -0
- package/dist/src/toolbar/lib.js +4 -0
- package/dist/src/toolbar/makeColorInput.js +2 -2
- package/dist/src/toolbar/types.d.ts +0 -4
- package/dist/src/toolbar/types.js +1 -5
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +3 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +21 -1
- package/dist/src/toolbar/widgets/{EraserWidget.d.ts → EraserToolWidget.d.ts} +1 -1
- package/dist/src/toolbar/widgets/{EraserWidget.js → EraserToolWidget.js} +1 -1
- package/dist/src/toolbar/widgets/{PenWidget.d.ts → PenToolWidget.d.ts} +2 -3
- package/dist/src/toolbar/widgets/{PenWidget.js → PenToolWidget.js} +6 -7
- package/dist/src/toolbar/widgets/{SelectionWidget.d.ts → SelectionToolWidget.d.ts} +1 -1
- package/dist/src/toolbar/widgets/{SelectionWidget.js → SelectionToolWidget.js} +1 -1
- package/dist/src/toolbar/widgets/lib.d.ts +8 -0
- package/dist/src/toolbar/widgets/lib.js +8 -0
- package/dist/src/tools/BaseTool.d.ts +1 -2
- package/dist/src/tools/BaseTool.js +6 -0
- package/dist/src/tools/Eraser.d.ts +0 -2
- package/dist/src/tools/Eraser.js +0 -2
- package/dist/src/tools/PanZoom.d.ts +0 -2
- package/dist/src/tools/PanZoom.js +0 -2
- package/dist/src/tools/Pen.d.ts +9 -9
- package/dist/src/tools/Pen.js +23 -6
- package/dist/src/tools/PipetteTool.d.ts +0 -2
- package/dist/src/tools/PipetteTool.js +0 -2
- package/dist/src/tools/SelectionTool.d.ts +0 -2
- package/dist/src/tools/SelectionTool.js +4 -3
- package/dist/src/tools/TextTool.d.ts +0 -2
- package/dist/src/tools/TextTool.js +0 -2
- package/dist/src/tools/ToolController.d.ts +8 -11
- package/dist/src/tools/ToolController.js +37 -18
- package/dist/src/tools/ToolEnabledGroup.js +1 -1
- package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
- package/dist/src/tools/ToolSwitcherShortcut.js +26 -0
- package/dist/src/tools/UndoRedoShortcut.d.ts +0 -2
- package/dist/src/tools/UndoRedoShortcut.js +3 -2
- package/dist/src/tools/lib.d.ts +13 -0
- package/dist/src/tools/lib.js +13 -0
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/dist/src/types.d.ts +8 -2
- package/dist/src/types.js +1 -0
- package/package.json +2 -2
- package/src/EditorImage.ts +0 -1
- package/src/components/Stroke.test.ts +5 -0
- package/src/components/Stroke.ts +13 -7
- package/src/components/builders/FreehandLineBuilder.ts +6 -6
- package/src/components/lib.ts +3 -0
- package/src/lib.ts +5 -1
- package/src/math/LineSegment2.ts +8 -0
- package/src/math/Path.test.ts +53 -0
- package/src/math/Path.toString.test.ts +4 -2
- package/src/math/Path.ts +109 -11
- package/src/math/Rect2.ts +1 -1
- package/src/math/Triangle.ts +29 -0
- package/src/rendering/Display.ts +3 -3
- package/src/rendering/caching/RenderingCacheNode.ts +3 -1
- package/src/rendering/renderers/AbstractRenderer.ts +1 -0
- package/src/rendering/renderers/CanvasRenderer.ts +6 -6
- package/src/rendering/renderers/SVGRenderer.ts +30 -84
- package/src/toolbar/HTMLToolbar.ts +35 -38
- package/src/toolbar/icons.ts +5 -1
- package/src/toolbar/lib.ts +4 -0
- package/src/toolbar/makeColorInput.ts +1 -2
- package/src/toolbar/types.ts +1 -5
- package/src/toolbar/widgets/BaseWidget.ts +27 -1
- package/src/toolbar/widgets/{EraserWidget.ts → EraserToolWidget.ts} +1 -1
- package/src/toolbar/widgets/{PenWidget.ts → PenToolWidget.ts} +10 -9
- package/src/toolbar/widgets/{SelectionWidget.ts → SelectionToolWidget.ts} +1 -1
- package/src/toolbar/widgets/lib.ts +10 -0
- package/src/tools/BaseTool.ts +8 -3
- package/src/tools/Eraser.ts +0 -2
- package/src/tools/PanZoom.ts +0 -2
- package/src/tools/Pen.ts +32 -13
- package/src/tools/PipetteTool.ts +0 -3
- package/src/tools/SelectionTool.test.ts +1 -2
- package/src/tools/SelectionTool.ts +5 -3
- package/src/tools/TextTool.ts +0 -2
- package/src/tools/ToolController.ts +44 -20
- package/src/tools/ToolEnabledGroup.ts +1 -1
- package/src/tools/ToolSwitcherShortcut.ts +34 -0
- package/src/tools/UndoRedoShortcut.ts +4 -4
- package/src/tools/lib.ts +18 -0
- package/src/tools/localization.ts +2 -0
- package/src/types.ts +13 -1
@@ -1,11 +1,9 @@
|
|
1
1
|
// @internal @packageDocumentation
|
2
2
|
import BaseTool from './BaseTool';
|
3
|
-
import { ToolType } from './ToolController';
|
4
3
|
export default class PipetteTool extends BaseTool {
|
5
4
|
constructor(editor, description) {
|
6
5
|
super(editor.notifier, description);
|
7
6
|
this.editor = editor;
|
8
|
-
this.kind = ToolType.Pipette;
|
9
7
|
this.colorPreviewListener = null;
|
10
8
|
this.colorSelectListener = null;
|
11
9
|
}
|
@@ -5,7 +5,6 @@ import Rect2 from '../math/Rect2';
|
|
5
5
|
import { Point2, Vec2 } from '../math/Vec2';
|
6
6
|
import { KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';
|
7
7
|
import BaseTool from './BaseTool';
|
8
|
-
import { ToolType } from './ToolController';
|
9
8
|
declare class Selection {
|
10
9
|
startPoint: Point2;
|
11
10
|
private editor;
|
@@ -43,7 +42,6 @@ export default class SelectionTool extends BaseTool {
|
|
43
42
|
private handleOverlay;
|
44
43
|
private prevSelectionBox;
|
45
44
|
private selectionBox;
|
46
|
-
readonly kind: ToolType;
|
47
45
|
constructor(editor: Editor, description: string);
|
48
46
|
onPointerDown(event: PointerEvt): boolean;
|
49
47
|
onPointerMove(event: PointerEvt): void;
|
@@ -1,3 +1,6 @@
|
|
1
|
+
// Allows users to select/transform portions of the `EditorImage`.
|
2
|
+
// With respect to `extend`ing, `SelectionTool` is not stable.
|
3
|
+
// @packageDocumentation
|
1
4
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
2
5
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
3
6
|
return new (P || (P = Promise))(function (resolve, reject) {
|
@@ -11,13 +14,11 @@ var _a;
|
|
11
14
|
import Duplicate from '../commands/Duplicate';
|
12
15
|
import Erase from '../commands/Erase';
|
13
16
|
import Mat33 from '../math/Mat33';
|
14
|
-
// import Mat33 from "../geometry/Mat33";
|
15
17
|
import Rect2 from '../math/Rect2';
|
16
18
|
import { Vec2 } from '../math/Vec2';
|
17
19
|
import { EditorEventType } from '../types';
|
18
20
|
import Viewport from '../Viewport';
|
19
21
|
import BaseTool from './BaseTool';
|
20
|
-
import { ToolType } from './ToolController';
|
21
22
|
import SerializableCommand from '../commands/SerializableCommand';
|
22
23
|
const handleScreenSize = 30;
|
23
24
|
const styles = `
|
@@ -409,11 +410,11 @@ Selection.ApplyTransformationCommand = class extends SerializableCommand {
|
|
409
410
|
return localizationTable.transformedElements(this.currentTransfmCommands.length);
|
410
411
|
}
|
411
412
|
};
|
413
|
+
// {@inheritDoc SelectionTool!}
|
412
414
|
export default class SelectionTool extends BaseTool {
|
413
415
|
constructor(editor, description) {
|
414
416
|
super(editor.notifier, description);
|
415
417
|
this.editor = editor;
|
416
|
-
this.kind = ToolType.Selection;
|
417
418
|
this.handleOverlay = document.createElement('div');
|
418
419
|
editor.createHTMLOverlay(this.handleOverlay);
|
419
420
|
editor.addStyleSheet(styles);
|
@@ -4,11 +4,9 @@ import Editor from '../Editor';
|
|
4
4
|
import { PointerEvt } from '../types';
|
5
5
|
import BaseTool from './BaseTool';
|
6
6
|
import { ToolLocalization } from './localization';
|
7
|
-
import { ToolType } from './ToolController';
|
8
7
|
export default class TextTool extends BaseTool {
|
9
8
|
private editor;
|
10
9
|
private localizationTable;
|
11
|
-
kind: ToolType;
|
12
10
|
private textStyle;
|
13
11
|
private textEditOverlay;
|
14
12
|
private textInputElem;
|
@@ -5,14 +5,12 @@ import Mat33 from '../math/Mat33';
|
|
5
5
|
import { PointerDevice } from '../Pointer';
|
6
6
|
import { EditorEventType } from '../types';
|
7
7
|
import BaseTool from './BaseTool';
|
8
|
-
import { ToolType } from './ToolController';
|
9
8
|
const overlayCssClass = 'textEditorOverlay';
|
10
9
|
export default class TextTool extends BaseTool {
|
11
10
|
constructor(editor, description, localizationTable) {
|
12
11
|
super(editor.notifier, description);
|
13
12
|
this.editor = editor;
|
14
13
|
this.localizationTable = localizationTable;
|
15
|
-
this.kind = ToolType.Text;
|
16
14
|
this.textInputElem = null;
|
17
15
|
this.textTargetPosition = null;
|
18
16
|
this.textMeasuringCtx = null;
|
@@ -1,21 +1,18 @@
|
|
1
1
|
import { InputEvt } from '../types';
|
2
2
|
import Editor from '../Editor';
|
3
3
|
import BaseTool from './BaseTool';
|
4
|
+
import ToolEnabledGroup from './ToolEnabledGroup';
|
4
5
|
import { ToolLocalization } from './localization';
|
5
|
-
export declare enum ToolType {
|
6
|
-
Pen = 0,
|
7
|
-
Selection = 1,
|
8
|
-
Eraser = 2,
|
9
|
-
PanZoom = 3,
|
10
|
-
Text = 4,
|
11
|
-
UndoRedoShortcut = 5,
|
12
|
-
Pipette = 6,
|
13
|
-
Other = 7
|
14
|
-
}
|
15
6
|
export default class ToolController {
|
16
7
|
private tools;
|
17
8
|
private activeTool;
|
9
|
+
private primaryToolGroup;
|
10
|
+
/** @internal */
|
18
11
|
constructor(editor: Editor, localization: ToolLocalization);
|
12
|
+
setTools(tools: BaseTool[], primaryToolGroup?: ToolEnabledGroup): void;
|
13
|
+
addPrimaryTool(tool: BaseTool): void;
|
14
|
+
getPrimaryTools(): BaseTool[];
|
15
|
+
addTool(tool: BaseTool): void;
|
19
16
|
dispatchInputEvent(event: InputEvt): boolean;
|
20
|
-
getMatchingTools(
|
17
|
+
getMatchingTools<Type extends BaseTool>(type: new (...args: any[]) => Type): Type[];
|
21
18
|
}
|
@@ -8,31 +8,24 @@ import Color4 from '../Color4';
|
|
8
8
|
import UndoRedoShortcut from './UndoRedoShortcut';
|
9
9
|
import TextTool from './TextTool';
|
10
10
|
import PipetteTool from './PipetteTool';
|
11
|
-
|
12
|
-
(function (ToolType) {
|
13
|
-
ToolType[ToolType["Pen"] = 0] = "Pen";
|
14
|
-
ToolType[ToolType["Selection"] = 1] = "Selection";
|
15
|
-
ToolType[ToolType["Eraser"] = 2] = "Eraser";
|
16
|
-
ToolType[ToolType["PanZoom"] = 3] = "PanZoom";
|
17
|
-
ToolType[ToolType["Text"] = 4] = "Text";
|
18
|
-
ToolType[ToolType["UndoRedoShortcut"] = 5] = "UndoRedoShortcut";
|
19
|
-
ToolType[ToolType["Pipette"] = 6] = "Pipette";
|
20
|
-
ToolType[ToolType["Other"] = 7] = "Other";
|
21
|
-
})(ToolType || (ToolType = {}));
|
11
|
+
import ToolSwitcherShortcut from './ToolSwitcherShortcut';
|
22
12
|
export default class ToolController {
|
13
|
+
/** @internal */
|
23
14
|
constructor(editor, localization) {
|
24
|
-
|
15
|
+
this.activeTool = null;
|
16
|
+
const primaryToolGroup = new ToolEnabledGroup();
|
17
|
+
this.primaryToolGroup = primaryToolGroup;
|
25
18
|
const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
|
26
19
|
const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
|
27
20
|
const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });
|
28
21
|
const primaryTools = [
|
29
|
-
new SelectionTool(editor, localization.selectionTool),
|
30
|
-
new Eraser(editor, localization.eraserTool),
|
31
22
|
// Three pens
|
32
23
|
primaryPenTool,
|
33
|
-
new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness:
|
24
|
+
new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
|
34
25
|
// Highlighter-like pen with width=64
|
35
26
|
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
|
27
|
+
new Eraser(editor, localization.eraserTool),
|
28
|
+
new SelectionTool(editor, localization.selectionTool),
|
36
29
|
new TextTool(editor, localization.textTool, localization),
|
37
30
|
];
|
38
31
|
this.tools = [
|
@@ -41,8 +34,9 @@ export default class ToolController {
|
|
41
34
|
...primaryTools,
|
42
35
|
keyboardPanZoomTool,
|
43
36
|
new UndoRedoShortcut(editor),
|
37
|
+
new ToolSwitcherShortcut(editor),
|
44
38
|
];
|
45
|
-
primaryTools.forEach(tool => tool.setToolGroup(
|
39
|
+
primaryTools.forEach(tool => tool.setToolGroup(primaryToolGroup));
|
46
40
|
panZoomTool.setEnabled(true);
|
47
41
|
primaryPenTool.setEnabled(true);
|
48
42
|
editor.notifier.on(EditorEventType.ToolEnabled, event => {
|
@@ -57,6 +51,31 @@ export default class ToolController {
|
|
57
51
|
});
|
58
52
|
this.activeTool = null;
|
59
53
|
}
|
54
|
+
// Replaces the current set of tools with `tools`. This should only be done before
|
55
|
+
// the creation of the app's toolbar (if using `HTMLToolbar`).
|
56
|
+
setTools(tools, primaryToolGroup) {
|
57
|
+
this.tools = tools;
|
58
|
+
this.primaryToolGroup = primaryToolGroup !== null && primaryToolGroup !== void 0 ? primaryToolGroup : new ToolEnabledGroup();
|
59
|
+
}
|
60
|
+
// Add a tool that acts like one of the primary tools (only one primary tool can be enabled at a time).
|
61
|
+
// This should be called before creating the app's toolbar.
|
62
|
+
addPrimaryTool(tool) {
|
63
|
+
tool.setToolGroup(this.primaryToolGroup);
|
64
|
+
if (tool.isEnabled()) {
|
65
|
+
this.primaryToolGroup.notifyEnabled(tool);
|
66
|
+
}
|
67
|
+
this.addTool(tool);
|
68
|
+
}
|
69
|
+
getPrimaryTools() {
|
70
|
+
return this.tools.filter(tool => {
|
71
|
+
return tool.getToolGroup() === this.primaryToolGroup;
|
72
|
+
});
|
73
|
+
}
|
74
|
+
// Add a tool to the end of this' tool list (the added tool receives events after tools already added to this).
|
75
|
+
// This should be called before creating the app's toolbar.
|
76
|
+
addTool(tool) {
|
77
|
+
this.tools.push(tool);
|
78
|
+
}
|
60
79
|
// Returns true if the event was handled
|
61
80
|
dispatchInputEvent(event) {
|
62
81
|
var _a, _b;
|
@@ -116,7 +135,7 @@ export default class ToolController {
|
|
116
135
|
}
|
117
136
|
return handled;
|
118
137
|
}
|
119
|
-
getMatchingTools(
|
120
|
-
return this.tools.filter(tool => tool
|
138
|
+
getMatchingTools(type) {
|
139
|
+
return this.tools.filter(tool => tool instanceof type);
|
121
140
|
}
|
122
141
|
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import Editor from '../Editor';
|
2
|
+
import { KeyPressEvent } from '../types';
|
3
|
+
import BaseTool from './BaseTool';
|
4
|
+
export default class ToolSwitcherShortcut extends BaseTool {
|
5
|
+
private editor;
|
6
|
+
constructor(editor: Editor);
|
7
|
+
onKeyPress({ key }: KeyPressEvent): boolean;
|
8
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
// Handles ctrl+1, ctrl+2, ctrl+3, ..., shortcuts for switching tools.
|
2
|
+
// @packageDocumentation
|
3
|
+
import BaseTool from './BaseTool';
|
4
|
+
// {@inheritDoc ToolSwitcherShortcut!}
|
5
|
+
export default class ToolSwitcherShortcut extends BaseTool {
|
6
|
+
constructor(editor) {
|
7
|
+
super(editor.notifier, editor.localization.changeTool);
|
8
|
+
this.editor = editor;
|
9
|
+
}
|
10
|
+
onKeyPress({ key }) {
|
11
|
+
const toolController = this.editor.toolController;
|
12
|
+
const primaryTools = toolController.getPrimaryTools();
|
13
|
+
// Map keys 0-9 to primary tools.
|
14
|
+
const keyMatch = /^[0-9]$/.exec(key);
|
15
|
+
let targetTool;
|
16
|
+
if (keyMatch) {
|
17
|
+
const targetIdx = parseInt(keyMatch[0], 10) - 1;
|
18
|
+
targetTool = primaryTools[targetIdx];
|
19
|
+
}
|
20
|
+
if (targetTool) {
|
21
|
+
targetTool.setEnabled(true);
|
22
|
+
return true;
|
23
|
+
}
|
24
|
+
return false;
|
25
|
+
}
|
26
|
+
}
|
@@ -1,10 +1,8 @@
|
|
1
1
|
import Editor from '../Editor';
|
2
2
|
import { KeyPressEvent } from '../types';
|
3
3
|
import BaseTool from './BaseTool';
|
4
|
-
import { ToolType } from './ToolController';
|
5
4
|
export default class UndoRedoShortcut extends BaseTool {
|
6
5
|
private editor;
|
7
|
-
kind: ToolType.UndoRedoShortcut;
|
8
6
|
constructor(editor: Editor);
|
9
7
|
onKeyPress({ key, ctrlKey }: KeyPressEvent): boolean;
|
10
8
|
}
|
@@ -1,10 +1,11 @@
|
|
1
|
+
// Handles ctrl+Z, ctrl+Shift+Z keyboard shortcuts.
|
2
|
+
// @packageDocumentation
|
1
3
|
import BaseTool from './BaseTool';
|
2
|
-
|
4
|
+
// {@inheritDoc UndoRedoShortcut!}
|
3
5
|
export default class UndoRedoShortcut extends BaseTool {
|
4
6
|
constructor(editor) {
|
5
7
|
super(editor.notifier, editor.localization.undoRedoTool);
|
6
8
|
this.editor = editor;
|
7
|
-
this.kind = ToolType.UndoRedoShortcut;
|
8
9
|
}
|
9
10
|
// Activate undo/redo
|
10
11
|
onKeyPress({ key, ctrlKey }) {
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/**
|
2
|
+
* @packageDocumentation
|
3
|
+
*/
|
4
|
+
export { default as BaseTool } from './BaseTool';
|
5
|
+
export { default as ToolController } from './ToolController';
|
6
|
+
export { default as ToolEnabledGroup } from './ToolEnabledGroup';
|
7
|
+
export { default as UndoRedoShortcut } from './UndoRedoShortcut';
|
8
|
+
export { default as ToolSwitcherShortcut } from './ToolSwitcherShortcut';
|
9
|
+
export { default as PanZoomTool, PanZoomMode } from './PanZoom';
|
10
|
+
export { default as PenTool, PenStyle } from './Pen';
|
11
|
+
export { default as TextTool } from './TextTool';
|
12
|
+
export { default as SelectionTool } from './SelectionTool';
|
13
|
+
export { default as EraserTool } from './Eraser';
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/**
|
2
|
+
* @packageDocumentation
|
3
|
+
*/
|
4
|
+
export { default as BaseTool } from './BaseTool';
|
5
|
+
export { default as ToolController } from './ToolController';
|
6
|
+
export { default as ToolEnabledGroup } from './ToolEnabledGroup';
|
7
|
+
export { default as UndoRedoShortcut } from './UndoRedoShortcut';
|
8
|
+
export { default as ToolSwitcherShortcut } from './ToolSwitcherShortcut';
|
9
|
+
export { default as PanZoomTool, PanZoomMode } from './PanZoom';
|
10
|
+
export { default as PenTool } from './Pen';
|
11
|
+
export { default as TextTool } from './TextTool';
|
12
|
+
export { default as SelectionTool } from './SelectionTool';
|
13
|
+
export { default as EraserTool } from './Eraser';
|
@@ -10,6 +10,7 @@ export interface ToolLocalization {
|
|
10
10
|
rightClickDragPanTool: string;
|
11
11
|
textTool: string;
|
12
12
|
enterTextToInsert: string;
|
13
|
+
changeTool: string;
|
13
14
|
toolEnabledAnnouncement: (toolName: string) => string;
|
14
15
|
toolDisabledAnnouncement: (toolName: string) => string;
|
15
16
|
}
|
@@ -10,6 +10,7 @@ export const defaultToolLocalization = {
|
|
10
10
|
keyboardPanZoom: 'Keyboard pan/zoom shortcuts',
|
11
11
|
textTool: 'Text',
|
12
12
|
enterTextToInsert: 'Text to insert',
|
13
|
+
changeTool: 'Change tool',
|
13
14
|
toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
|
14
15
|
toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
|
15
16
|
};
|
package/dist/src/types.d.ts
CHANGED
@@ -8,6 +8,7 @@ import Rect2 from './math/Rect2';
|
|
8
8
|
import Pointer from './Pointer';
|
9
9
|
import Color4 from './Color4';
|
10
10
|
import Command from './commands/Command';
|
11
|
+
import { BaseWidget } from './lib';
|
11
12
|
export interface PointerEvtListener {
|
12
13
|
onPointerDown(event: PointerEvt): boolean;
|
13
14
|
onPointerMove(event: PointerEvt): void;
|
@@ -68,7 +69,8 @@ export declare enum EditorEventType {
|
|
68
69
|
ViewportChanged = 7,
|
69
70
|
DisplayResized = 8,
|
70
71
|
ColorPickerToggled = 9,
|
71
|
-
ColorPickerColorSelected = 10
|
72
|
+
ColorPickerColorSelected = 10,
|
73
|
+
ToolbarDropdownShown = 11
|
72
74
|
}
|
73
75
|
declare type EditorToolEventType = EditorEventType.ToolEnabled | EditorEventType.ToolDisabled | EditorEventType.ToolUpdated;
|
74
76
|
export interface EditorToolEvent {
|
@@ -109,7 +111,11 @@ export interface ColorPickerColorSelected {
|
|
109
111
|
readonly kind: EditorEventType.ColorPickerColorSelected;
|
110
112
|
readonly color: Color4;
|
111
113
|
}
|
112
|
-
export
|
114
|
+
export interface ToolbarDropdownShownEvent {
|
115
|
+
readonly kind: EditorEventType.ToolbarDropdownShown;
|
116
|
+
readonly parentWidget: BaseWidget;
|
117
|
+
}
|
118
|
+
export declare type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent | ColorPickerToggled | ColorPickerColorSelected | ToolbarDropdownShownEvent;
|
113
119
|
export declare type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null;
|
114
120
|
export declare type ComponentAddedListener = (component: AbstractComponent) => void;
|
115
121
|
export declare type OnDetermineExportRectListener = (exportRect: Rect2) => void;
|
package/dist/src/types.js
CHANGED
@@ -22,4 +22,5 @@ export var EditorEventType;
|
|
22
22
|
EditorEventType[EditorEventType["DisplayResized"] = 8] = "DisplayResized";
|
23
23
|
EditorEventType[EditorEventType["ColorPickerToggled"] = 9] = "ColorPickerToggled";
|
24
24
|
EditorEventType[EditorEventType["ColorPickerColorSelected"] = 10] = "ColorPickerColorSelected";
|
25
|
+
EditorEventType[EditorEventType["ToolbarDropdownShown"] = 11] = "ToolbarDropdownShown";
|
25
26
|
})(EditorEventType || (EditorEventType = {}));
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "js-draw",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.3.1",
|
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",
|
@@ -72,7 +72,7 @@
|
|
72
72
|
"linter-precommit": "eslint --fix --ext .js --ext .ts",
|
73
73
|
"lint-staged": "lint-staged",
|
74
74
|
"prepare": "husky install && yarn build",
|
75
|
-
"prepack": "yarn build && pinst --disable",
|
75
|
+
"prepack": "yarn build && yarn test && pinst --disable",
|
76
76
|
"postpack": "pinst --enable"
|
77
77
|
},
|
78
78
|
"dependencies": {
|
package/src/EditorImage.ts
CHANGED
@@ -388,7 +388,6 @@ export class ImageNode {
|
|
388
388
|
}
|
389
389
|
|
390
390
|
public render(renderer: AbstractRenderer, visibleRect: Rect2) {
|
391
|
-
// Don't render components that are < 0.1% of the viewport.
|
392
391
|
const leaves = this.getLeavesIntersectingRegion(visibleRect, rect => renderer.isTooSmallToRender(rect));
|
393
392
|
sortLeavesByZIndex(leaves);
|
394
393
|
|
@@ -64,6 +64,11 @@ describe('Stroke', () => {
|
|
64
64
|
"path": "m0,0 l10,10z"
|
65
65
|
}
|
66
66
|
]`);
|
67
|
+
const path = deserialized.getPath();
|
68
|
+
|
69
|
+
// Should cache the original string representation.
|
70
|
+
expect(deserialized.getPath().toString()).toBe('m0,0 l10,10z');
|
71
|
+
path['cachedStringVersion'] = null;
|
67
72
|
expect(deserialized.getPath().toString()).toBe('M0,0L10,10L0,0');
|
68
73
|
});
|
69
74
|
});
|
package/src/components/Stroke.ts
CHANGED
@@ -9,7 +9,6 @@ import { ImageComponentLocalization } from './localization';
|
|
9
9
|
|
10
10
|
interface StrokePart extends RenderablePathSpec {
|
11
11
|
path: Path;
|
12
|
-
bbox: Rect2;
|
13
12
|
}
|
14
13
|
|
15
14
|
export default class Stroke extends AbstractComponent {
|
@@ -19,7 +18,7 @@ export default class Stroke extends AbstractComponent {
|
|
19
18
|
public constructor(parts: RenderablePathSpec[]) {
|
20
19
|
super('stroke');
|
21
20
|
|
22
|
-
this.parts = parts.map(section => {
|
21
|
+
this.parts = parts.map((section): StrokePart => {
|
23
22
|
const path = Path.fromRenderable(section);
|
24
23
|
const pathBBox = this.bboxForPart(path.bbox, section.style);
|
25
24
|
|
@@ -31,7 +30,6 @@ export default class Stroke extends AbstractComponent {
|
|
31
30
|
|
32
31
|
return {
|
33
32
|
path,
|
34
|
-
bbox: pathBBox,
|
35
33
|
|
36
34
|
// To implement RenderablePathSpec
|
37
35
|
startPoint: path.startPoint,
|
@@ -54,10 +52,19 @@ export default class Stroke extends AbstractComponent {
|
|
54
52
|
public render(canvas: AbstractRenderer, visibleRect?: Rect2): void {
|
55
53
|
canvas.startObject(this.getBBox());
|
56
54
|
for (const part of this.parts) {
|
57
|
-
const bbox = part.bbox;
|
58
|
-
if (
|
59
|
-
|
55
|
+
const bbox = this.bboxForPart(part.path.bbox, part.style);
|
56
|
+
if (visibleRect) {
|
57
|
+
if (!bbox.intersects(visibleRect)) {
|
58
|
+
continue;
|
59
|
+
}
|
60
|
+
|
61
|
+
const muchBiggerThanVisible = bbox.size.x > visibleRect.size.x * 2 || bbox.size.y > visibleRect.size.y * 2;
|
62
|
+
if (muchBiggerThanVisible && !part.path.closedRoughlyIntersects(visibleRect)) {
|
63
|
+
continue;
|
64
|
+
}
|
60
65
|
}
|
66
|
+
|
67
|
+
canvas.drawPath(part);
|
61
68
|
}
|
62
69
|
canvas.endObject(this.getLoadSaveData());
|
63
70
|
}
|
@@ -89,7 +96,6 @@ export default class Stroke extends AbstractComponent {
|
|
89
96
|
|
90
97
|
return {
|
91
98
|
path: newPath,
|
92
|
-
bbox: newBBox,
|
93
99
|
startPoint: newPath.startPoint,
|
94
100
|
commands: newPath.parts,
|
95
101
|
style: part.style,
|
@@ -12,9 +12,9 @@ import RenderingStyle from '../../rendering/RenderingStyle';
|
|
12
12
|
|
13
13
|
export const makeFreehandLineBuilder: ComponentBuilderFactory = (initialPoint: StrokeDataPoint, viewport: Viewport) => {
|
14
14
|
// Don't smooth if input is more than ± 7 pixels from the true curve, do smooth if
|
15
|
-
// less than ±
|
15
|
+
// less than ±1 px from the curve.
|
16
16
|
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 7;
|
17
|
-
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas()
|
17
|
+
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
|
18
18
|
|
19
19
|
return new FreehandLineBuilder(
|
20
20
|
initialPoint, minSmoothingDist, maxSmoothingDist
|
@@ -180,7 +180,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
180
180
|
}
|
181
181
|
|
182
182
|
private roundPoint(point: Point2): Point2 {
|
183
|
-
let minFit = Math.min(this.minFitAllowed, this.curveStartWidth);
|
183
|
+
let minFit = Math.min(this.minFitAllowed, this.curveStartWidth / 2);
|
184
184
|
|
185
185
|
if (minFit < 1e-10) {
|
186
186
|
minFit = this.minFitAllowed;
|
@@ -197,7 +197,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
197
197
|
return;
|
198
198
|
}
|
199
199
|
|
200
|
-
const width = Viewport.roundPoint(this.startPoint.width / 3.5, this.minFitAllowed);
|
200
|
+
const width = Viewport.roundPoint(this.startPoint.width / 3.5, Math.min(this.minFitAllowed, this.startPoint.width / 4));
|
201
201
|
const center = this.roundPoint(this.startPoint.pos);
|
202
202
|
|
203
203
|
// Start on the right, cycle clockwise:
|
@@ -492,7 +492,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
492
492
|
const dist = proj.minus(point).magnitude();
|
493
493
|
|
494
494
|
const minFit = Math.max(
|
495
|
-
Math.min(this.curveStartWidth, this.curveEndWidth) /
|
495
|
+
Math.min(this.curveStartWidth, this.curveEndWidth) / 3,
|
496
496
|
this.minFitAllowed
|
497
497
|
);
|
498
498
|
if (dist > minFit || dist > this.maxFitAllowed) {
|
@@ -503,7 +503,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
503
503
|
};
|
504
504
|
|
505
505
|
const approxCurveLen = controlPoint.minus(segmentStart).magnitude() + segmentEnd.minus(controlPoint).magnitude();
|
506
|
-
if (this.buffer.length > 3 && approxCurveLen > this.curveEndWidth /
|
506
|
+
if (this.buffer.length > 3 && approxCurveLen > this.curveEndWidth / 3) {
|
507
507
|
if (!curveMatchesPoints(this.currentCurve)) {
|
508
508
|
// Use a curve that better fits the points
|
509
509
|
this.currentCurve = prevCurve;
|
package/src/components/lib.ts
CHANGED
package/src/lib.ts
CHANGED
@@ -14,7 +14,8 @@
|
|
14
14
|
*/
|
15
15
|
|
16
16
|
import Editor from './Editor';
|
17
|
-
export {
|
17
|
+
export { default as EditorImage } from './EditorImage';
|
18
|
+
export * from './types';
|
18
19
|
export { default as getLocalizationTable } from './localizations/getLocalizationTable';
|
19
20
|
export * from './localization';
|
20
21
|
|
@@ -22,6 +23,9 @@ export { default as Color4 } from './Color4';
|
|
22
23
|
export * from './math/lib';
|
23
24
|
export * from './components/lib';
|
24
25
|
export * from './commands/lib';
|
26
|
+
export * from './tools/lib';
|
27
|
+
export * from './toolbar/lib';
|
28
|
+
export { default as Pointer, PointerDevice } from './Pointer';
|
25
29
|
export { default as HTMLToolbar } from './toolbar/HTMLToolbar';
|
26
30
|
|
27
31
|
export { Editor };
|
package/src/math/LineSegment2.ts
CHANGED
@@ -126,6 +126,10 @@ export default class LineSegment2 {
|
|
126
126
|
};
|
127
127
|
}
|
128
128
|
|
129
|
+
public intersects(other: LineSegment2) {
|
130
|
+
return this.intersection(other) !== null;
|
131
|
+
}
|
132
|
+
|
129
133
|
// Returns the closest point on this to [target]
|
130
134
|
public closestPointTo(target: Point2) {
|
131
135
|
// Distance from P1 along this' direction.
|
@@ -144,4 +148,8 @@ export default class LineSegment2 {
|
|
144
148
|
return this.p1;
|
145
149
|
}
|
146
150
|
}
|
151
|
+
|
152
|
+
public toString() {
|
153
|
+
return `LineSegment(${this.p1.toString()}, ${this.p2.toString()})`;
|
154
|
+
}
|
147
155
|
}
|
package/src/math/Path.test.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
2
|
import LineSegment2 from './LineSegment2';
|
3
3
|
import Path, { PathCommandType } from './Path';
|
4
|
+
import Rect2 from './Rect2';
|
4
5
|
import { Vec2 } from './Vec2';
|
5
6
|
|
6
7
|
describe('Path', () => {
|
@@ -93,4 +94,56 @@ describe('Path', () => {
|
|
93
94
|
y: 100,
|
94
95
|
});
|
95
96
|
});
|
97
|
+
|
98
|
+
describe('polylineApproximation', () => {
|
99
|
+
it('should approximate Bézier curves with polylines', () => {
|
100
|
+
const path = Path.fromString('m0,0 l4,4 Q 1,4 4,1z');
|
101
|
+
|
102
|
+
expect(path.polylineApproximation()).toMatchObject([
|
103
|
+
new LineSegment2(Vec2.of(0, 0), Vec2.of(4, 4)),
|
104
|
+
new LineSegment2(Vec2.of(4, 4), Vec2.of(1, 4)),
|
105
|
+
new LineSegment2(Vec2.of(1, 4), Vec2.of(4, 1)),
|
106
|
+
new LineSegment2(Vec2.of(4, 1), Vec2.of(0, 0)),
|
107
|
+
]);
|
108
|
+
});
|
109
|
+
});
|
110
|
+
|
111
|
+
describe('roughlyIntersectsClosed', () => {
|
112
|
+
it('small, line-only path', () => {
|
113
|
+
const path = Path.fromString('m0,0 l10,10 L0,10 z');
|
114
|
+
expect(
|
115
|
+
path.closedRoughlyIntersects(Rect2.fromCorners(Vec2.zero, Vec2.of(20, 20)))
|
116
|
+
).toBe(true);
|
117
|
+
expect(
|
118
|
+
path.closedRoughlyIntersects(Rect2.fromCorners(Vec2.zero, Vec2.of(2, 2)))
|
119
|
+
).toBe(true);
|
120
|
+
expect(
|
121
|
+
path.closedRoughlyIntersects(new Rect2(10, 1, 1, 1))
|
122
|
+
).toBe(false);
|
123
|
+
expect(
|
124
|
+
path.closedRoughlyIntersects(new Rect2(1, 5, 1, 1))
|
125
|
+
).toBe(true);
|
126
|
+
});
|
127
|
+
|
128
|
+
it('path with Bézier curves', () => {
|
129
|
+
const path = Path.fromString(`
|
130
|
+
M1090,2560
|
131
|
+
L1570,2620
|
132
|
+
Q1710,1300 1380,720
|
133
|
+
Q980,100 -460,-640
|
134
|
+
L-680,-200
|
135
|
+
Q670,470 960,980
|
136
|
+
Q1230,1370 1090,2560
|
137
|
+
`);
|
138
|
+
expect(
|
139
|
+
path.closedRoughlyIntersects(new Rect2(0, 0, 500, 500))
|
140
|
+
).toBe(true);
|
141
|
+
expect(
|
142
|
+
path.closedRoughlyIntersects(new Rect2(0, 0, 5, 5))
|
143
|
+
).toBe(true);
|
144
|
+
expect(
|
145
|
+
path.closedRoughlyIntersects(new Rect2(-10000, 0, 500, 500))
|
146
|
+
).toBe(false);
|
147
|
+
});
|
148
|
+
});
|
96
149
|
});
|
@@ -42,13 +42,15 @@ describe('Path.toString', () => {
|
|
42
42
|
},
|
43
43
|
]);
|
44
44
|
|
45
|
-
expect(path.toString()).toBe('M1000,
|
45
|
+
expect(path.toString()).toBe('M1000,2000000l-970-1999960');
|
46
46
|
});
|
47
47
|
|
48
48
|
it('deserialized path should serialize to the same/similar path, but with rounded components', () => {
|
49
49
|
const path1 = Path.fromString('M100,100 L101,101 Q102,102 90.000000001,89.99999999 Z');
|
50
|
+
path1['cachedStringVersion'] = null; // Clear the cache.
|
51
|
+
|
50
52
|
expect(path1.toString()).toBe([
|
51
|
-
'M100,100', '
|
53
|
+
'M100,100', 'l1,1', 'q1,1 -11-11', 'l10,10'
|
52
54
|
].join(''));
|
53
55
|
});
|
54
56
|
});
|