js-draw 0.7.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/translation.md +1 -0
- package/CHANGELOG.md +12 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.js +3 -0
- package/dist/src/SVGLoader.js +5 -7
- package/dist/src/components/Stroke.js +10 -3
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +10 -23
- package/dist/src/components/builders/FreehandLineBuilder.js +70 -396
- package/dist/src/components/builders/PressureSensitiveFreehandLineBuilder.d.ts +36 -0
- package/dist/src/components/builders/PressureSensitiveFreehandLineBuilder.js +339 -0
- package/dist/src/components/lib.d.ts +2 -0
- package/dist/src/components/lib.js +2 -0
- package/dist/src/components/util/StrokeSmoother.d.ts +35 -0
- package/dist/src/components/util/StrokeSmoother.js +206 -0
- package/dist/src/math/Mat33.d.ts +2 -0
- package/dist/src/math/Mat33.js +4 -0
- package/dist/src/math/Path.d.ts +2 -0
- package/dist/src/math/Path.js +44 -0
- package/dist/src/rendering/renderers/CanvasRenderer.js +2 -0
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -0
- package/dist/src/rendering/renderers/SVGRenderer.js +20 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +3 -0
- package/dist/src/toolbar/HTMLToolbar.js +24 -0
- package/dist/src/toolbar/IconProvider.d.ts +1 -0
- package/dist/src/toolbar/IconProvider.js +43 -1
- package/dist/src/toolbar/localization.d.ts +2 -0
- package/dist/src/toolbar/localization.js +2 -0
- package/dist/src/toolbar/makeColorInput.d.ts +2 -1
- package/dist/src/toolbar/makeColorInput.js +13 -2
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +2 -2
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +1 -2
- package/dist/src/toolbar/widgets/BaseToolWidget.js +2 -3
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +32 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +67 -6
- package/dist/src/toolbar/widgets/EraserToolWidget.d.ts +4 -0
- package/dist/src/toolbar/widgets/EraserToolWidget.js +3 -0
- package/dist/src/toolbar/widgets/HandToolWidget.d.ts +3 -1
- package/dist/src/toolbar/widgets/HandToolWidget.js +22 -13
- package/dist/src/toolbar/widgets/PenToolWidget.d.ts +7 -1
- package/dist/src/toolbar/widgets/PenToolWidget.js +83 -12
- package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/SelectionToolWidget.js +7 -7
- package/dist/src/toolbar/widgets/TextToolWidget.d.ts +4 -1
- package/dist/src/toolbar/widgets/TextToolWidget.js +17 -3
- package/dist/src/tools/PanZoom.d.ts +4 -1
- package/dist/src/tools/PanZoom.js +24 -1
- package/dist/src/tools/Pen.d.ts +2 -2
- package/dist/src/tools/Pen.js +2 -2
- package/dist/src/tools/SelectionTool/Selection.d.ts +1 -0
- package/dist/src/tools/SelectionTool/Selection.js +8 -1
- package/dist/src/tools/ToolController.js +2 -1
- package/package.json +1 -1
- package/src/Color4.ts +2 -0
- package/src/SVGLoader.ts +8 -8
- package/src/components/Stroke.ts +16 -3
- package/src/components/builders/FreehandLineBuilder.ts +54 -495
- package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +454 -0
- package/src/components/lib.ts +3 -1
- package/src/components/util/StrokeSmoother.ts +290 -0
- package/src/math/Mat33.ts +5 -0
- package/src/math/Path.test.ts +49 -0
- package/src/math/Path.ts +51 -0
- package/src/rendering/renderers/CanvasRenderer.ts +2 -0
- package/src/rendering/renderers/SVGRenderer.ts +24 -0
- package/src/toolbar/HTMLToolbar.ts +33 -0
- package/src/toolbar/IconProvider.ts +49 -1
- package/src/toolbar/localization.ts +4 -0
- package/src/toolbar/makeColorInput.ts +21 -3
- package/src/toolbar/widgets/ActionButtonWidget.ts +6 -3
- package/src/toolbar/widgets/BaseToolWidget.ts +4 -3
- package/src/toolbar/widgets/BaseWidget.ts +83 -5
- package/src/toolbar/widgets/EraserToolWidget.ts +11 -0
- package/src/toolbar/widgets/HandToolWidget.ts +48 -17
- package/src/toolbar/widgets/PenToolWidget.ts +110 -13
- package/src/toolbar/widgets/SelectionToolWidget.ts +8 -5
- package/src/toolbar/widgets/TextToolWidget.ts +29 -4
- package/src/tools/PanZoom.ts +28 -1
- package/src/tools/Pen.test.ts +2 -2
- package/src/tools/Pen.ts +1 -1
- package/src/tools/SelectionTool/Selection.ts +10 -1
- package/src/tools/ToolController.ts +2 -1
@@ -4,18 +4,24 @@ import Pen from '../../tools/Pen';
|
|
4
4
|
import { KeyPressEvent } from '../../types';
|
5
5
|
import { ToolbarLocalization } from '../localization';
|
6
6
|
import BaseToolWidget from './BaseToolWidget';
|
7
|
+
import { SavedToolbuttonState } from './BaseWidget';
|
7
8
|
export interface PenTypeRecord {
|
8
9
|
name: string;
|
10
|
+
id: string;
|
9
11
|
factory: ComponentBuilderFactory;
|
10
12
|
}
|
11
13
|
export default class PenToolWidget extends BaseToolWidget {
|
12
14
|
private tool;
|
13
15
|
private updateInputs;
|
14
16
|
protected penTypes: PenTypeRecord[];
|
15
|
-
constructor(editor: Editor, tool: Pen, localization
|
17
|
+
constructor(editor: Editor, tool: Pen, localization?: ToolbarLocalization);
|
16
18
|
protected getTitle(): string;
|
19
|
+
private getCurrentPenTypeIdx;
|
20
|
+
private getCurrentPenType;
|
17
21
|
protected createIcon(): Element;
|
18
22
|
private static idCounter;
|
19
23
|
protected fillDropdown(dropdown: HTMLElement): boolean;
|
20
24
|
protected onKeyPress(event: KeyPressEvent): boolean;
|
25
|
+
serializeState(): SavedToolbuttonState;
|
26
|
+
deserializeFrom(state: SavedToolbuttonState): void;
|
21
27
|
}
|
@@ -1,36 +1,48 @@
|
|
1
1
|
import { makeArrowBuilder } from '../../components/builders/ArrowBuilder';
|
2
2
|
import { makeFreehandLineBuilder } from '../../components/builders/FreehandLineBuilder';
|
3
|
+
import { makePressureSensitiveFreehandLineBuilder } from '../../components/builders/PressureSensitiveFreehandLineBuilder';
|
3
4
|
import { makeLineBuilder } from '../../components/builders/LineBuilder';
|
4
5
|
import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../../components/builders/RectangleBuilder';
|
5
6
|
import { EditorEventType } from '../../types';
|
6
7
|
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
7
8
|
import makeColorInput from '../makeColorInput';
|
8
9
|
import BaseToolWidget from './BaseToolWidget';
|
10
|
+
import Color4 from '../../Color4';
|
9
11
|
export default class PenToolWidget extends BaseToolWidget {
|
10
12
|
constructor(editor, tool, localization) {
|
11
|
-
super(editor, tool, localization);
|
13
|
+
super(editor, tool, 'pen', localization);
|
12
14
|
this.tool = tool;
|
13
15
|
this.updateInputs = () => { };
|
14
16
|
// Default pen types
|
15
17
|
this.penTypes = [
|
16
18
|
{
|
17
|
-
name:
|
19
|
+
name: this.localizationTable.pressureSensitiveFreehandPen,
|
20
|
+
id: 'pressure-sensitive-pen',
|
21
|
+
factory: makePressureSensitiveFreehandLineBuilder,
|
22
|
+
},
|
23
|
+
{
|
24
|
+
name: this.localizationTable.freehandPen,
|
25
|
+
id: 'freehand-pen',
|
18
26
|
factory: makeFreehandLineBuilder,
|
19
27
|
},
|
20
28
|
{
|
21
|
-
name:
|
29
|
+
name: this.localizationTable.arrowPen,
|
30
|
+
id: 'arrow',
|
22
31
|
factory: makeArrowBuilder,
|
23
32
|
},
|
24
33
|
{
|
25
|
-
name:
|
34
|
+
name: this.localizationTable.linePen,
|
35
|
+
id: 'line',
|
26
36
|
factory: makeLineBuilder,
|
27
37
|
},
|
28
38
|
{
|
29
|
-
name:
|
39
|
+
name: this.localizationTable.filledRectanglePen,
|
40
|
+
id: 'filled-rectangle',
|
30
41
|
factory: makeFilledRectangleBuilder,
|
31
42
|
},
|
32
43
|
{
|
33
|
-
name:
|
44
|
+
name: this.localizationTable.outlinedRectanglePen,
|
45
|
+
id: 'outlined-rectangle',
|
34
46
|
factory: makeOutlinedRectangleBuilder,
|
35
47
|
},
|
36
48
|
];
|
@@ -48,9 +60,30 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
48
60
|
getTitle() {
|
49
61
|
return this.targetTool.description;
|
50
62
|
}
|
63
|
+
// Return the index of this tool's stroke factory in the list of
|
64
|
+
// all stroke factories.
|
65
|
+
//
|
66
|
+
// Returns -1 if the stroke factory is not in the list of all stroke factories.
|
67
|
+
getCurrentPenTypeIdx() {
|
68
|
+
const currentFactory = this.tool.getStrokeFactory();
|
69
|
+
for (let i = 0; i < this.penTypes.length; i++) {
|
70
|
+
if (this.penTypes[i].factory === currentFactory) {
|
71
|
+
return i;
|
72
|
+
}
|
73
|
+
}
|
74
|
+
return -1;
|
75
|
+
}
|
76
|
+
getCurrentPenType() {
|
77
|
+
for (const penType of this.penTypes) {
|
78
|
+
if (penType.factory === this.tool.getStrokeFactory()) {
|
79
|
+
return penType;
|
80
|
+
}
|
81
|
+
}
|
82
|
+
return null;
|
83
|
+
}
|
51
84
|
createIcon() {
|
52
85
|
const strokeFactory = this.tool.getStrokeFactory();
|
53
|
-
if (strokeFactory === makeFreehandLineBuilder) {
|
86
|
+
if (strokeFactory === makeFreehandLineBuilder || strokeFactory === makePressureSensitiveFreehandLineBuilder) {
|
54
87
|
// Use a square-root scale to prevent the pen's tip from overflowing.
|
55
88
|
const scale = Math.round(Math.sqrt(this.tool.getThickness()) * 4);
|
56
89
|
const color = this.tool.getColor();
|
@@ -101,7 +134,7 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
101
134
|
objectTypeRow.appendChild(objectTypeSelect);
|
102
135
|
const colorRow = document.createElement('div');
|
103
136
|
const colorLabel = document.createElement('label');
|
104
|
-
const [colorInput, colorInputContainer] = makeColorInput(this.editor, color => {
|
137
|
+
const [colorInput, colorInputContainer, setColorInputValue] = makeColorInput(this.editor, color => {
|
105
138
|
this.tool.setColor(color);
|
106
139
|
});
|
107
140
|
colorInput.id = `${toolbarCSSPrefix}colorInput${PenToolWidget.idCounter++}`;
|
@@ -110,8 +143,9 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
110
143
|
colorRow.appendChild(colorLabel);
|
111
144
|
colorRow.appendChild(colorInputContainer);
|
112
145
|
this.updateInputs = () => {
|
113
|
-
|
146
|
+
setColorInputValue(this.tool.getColor());
|
114
147
|
thicknessInput.value = inverseThicknessInputFn(this.tool.getThickness()).toString();
|
148
|
+
// Update the list of stroke factories
|
115
149
|
objectTypeSelect.replaceChildren();
|
116
150
|
for (let i = 0; i < this.penTypes.length; i++) {
|
117
151
|
const penType = this.penTypes[i];
|
@@ -119,9 +153,14 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
119
153
|
option.value = i.toString();
|
120
154
|
option.innerText = penType.name;
|
121
155
|
objectTypeSelect.appendChild(option);
|
122
|
-
|
123
|
-
|
124
|
-
|
156
|
+
}
|
157
|
+
// Update the selected stroke factory.
|
158
|
+
const strokeFactoryIdx = this.getCurrentPenTypeIdx();
|
159
|
+
if (strokeFactoryIdx === -1) {
|
160
|
+
objectTypeSelect.value = '';
|
161
|
+
}
|
162
|
+
else {
|
163
|
+
objectTypeSelect.value = strokeFactoryIdx.toString();
|
125
164
|
}
|
126
165
|
};
|
127
166
|
this.updateInputs();
|
@@ -143,5 +182,37 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
143
182
|
}
|
144
183
|
return false;
|
145
184
|
}
|
185
|
+
serializeState() {
|
186
|
+
var _a;
|
187
|
+
return Object.assign(Object.assign({}, super.serializeState()), { color: this.tool.getColor().toHexString(), thickness: this.tool.getThickness(), strokeFactoryId: (_a = this.getCurrentPenType()) === null || _a === void 0 ? void 0 : _a.id });
|
188
|
+
}
|
189
|
+
deserializeFrom(state) {
|
190
|
+
super.deserializeFrom(state);
|
191
|
+
const verifyPropertyType = (propertyName, expectedType) => {
|
192
|
+
const actualType = typeof (state[propertyName]);
|
193
|
+
if (actualType !== expectedType) {
|
194
|
+
throw new Error(`Deserializing property ${propertyName}: Invalid type. Expected ${expectedType},` +
|
195
|
+
` was ${actualType}.`);
|
196
|
+
}
|
197
|
+
};
|
198
|
+
if (state.color) {
|
199
|
+
verifyPropertyType('color', 'string');
|
200
|
+
this.tool.setColor(Color4.fromHex(state.color));
|
201
|
+
}
|
202
|
+
if (state.thickness) {
|
203
|
+
verifyPropertyType('thickness', 'number');
|
204
|
+
this.tool.setThickness(state.thickness);
|
205
|
+
}
|
206
|
+
if (state.strokeFactoryId) {
|
207
|
+
verifyPropertyType('strokeFactoryId', 'string');
|
208
|
+
const factoryId = state.strokeFactoryId;
|
209
|
+
for (const penType of this.penTypes) {
|
210
|
+
if (factoryId === penType.id) {
|
211
|
+
this.tool.setStrokeFactory(penType.factory);
|
212
|
+
break;
|
213
|
+
}
|
214
|
+
}
|
215
|
+
}
|
216
|
+
}
|
146
217
|
}
|
147
218
|
PenToolWidget.idCounter = 0;
|
@@ -5,7 +5,7 @@ import { ToolbarLocalization } from '../localization';
|
|
5
5
|
import BaseToolWidget from './BaseToolWidget';
|
6
6
|
export default class SelectionToolWidget extends BaseToolWidget {
|
7
7
|
private tool;
|
8
|
-
constructor(editor: Editor, tool: SelectionTool, localization
|
8
|
+
constructor(editor: Editor, tool: SelectionTool, localization?: ToolbarLocalization);
|
9
9
|
private resizeImageToSelection;
|
10
10
|
protected onKeyPress(event: KeyPressEvent): boolean;
|
11
11
|
protected getTitle(): string;
|
@@ -3,20 +3,20 @@ import ActionButtonWidget from './ActionButtonWidget';
|
|
3
3
|
import BaseToolWidget from './BaseToolWidget';
|
4
4
|
export default class SelectionToolWidget extends BaseToolWidget {
|
5
5
|
constructor(editor, tool, localization) {
|
6
|
-
super(editor, tool, localization);
|
6
|
+
super(editor, tool, 'selection-tool-widget', localization);
|
7
7
|
this.tool = tool;
|
8
|
-
const resizeButton = new ActionButtonWidget(editor,
|
8
|
+
const resizeButton = new ActionButtonWidget(editor, 'resize-btn', () => editor.icons.makeResizeViewportIcon(), this.localizationTable.resizeImageToSelection, () => {
|
9
9
|
this.resizeImageToSelection();
|
10
|
-
});
|
11
|
-
const deleteButton = new ActionButtonWidget(editor,
|
10
|
+
}, localization);
|
11
|
+
const deleteButton = new ActionButtonWidget(editor, 'delete-btn', () => editor.icons.makeDeleteSelectionIcon(), this.localizationTable.deleteSelection, () => {
|
12
12
|
const selection = this.tool.getSelection();
|
13
13
|
this.editor.dispatch(selection.deleteSelectedObjects());
|
14
14
|
this.tool.clearSelection();
|
15
|
-
});
|
16
|
-
const duplicateButton = new ActionButtonWidget(editor,
|
15
|
+
}, localization);
|
16
|
+
const duplicateButton = new ActionButtonWidget(editor, 'duplicate-btn', () => editor.icons.makeDuplicateSelectionIcon(), this.localizationTable.duplicateSelection, () => {
|
17
17
|
const selection = this.tool.getSelection();
|
18
18
|
this.editor.dispatch(selection.duplicateSelectedObjects());
|
19
|
-
});
|
19
|
+
}, localization);
|
20
20
|
this.addSubWidget(resizeButton);
|
21
21
|
this.addSubWidget(deleteButton);
|
22
22
|
this.addSubWidget(duplicateButton);
|
@@ -2,12 +2,15 @@ import Editor from '../../Editor';
|
|
2
2
|
import TextTool from '../../tools/TextTool';
|
3
3
|
import { ToolbarLocalization } from '../localization';
|
4
4
|
import BaseToolWidget from './BaseToolWidget';
|
5
|
+
import { SavedToolbuttonState } from './BaseWidget';
|
5
6
|
export default class TextToolWidget extends BaseToolWidget {
|
6
7
|
private tool;
|
7
8
|
private updateDropdownInputs;
|
8
|
-
constructor(editor: Editor, tool: TextTool, localization
|
9
|
+
constructor(editor: Editor, tool: TextTool, localization?: ToolbarLocalization);
|
9
10
|
protected getTitle(): string;
|
10
11
|
protected createIcon(): Element;
|
11
12
|
private static idCounter;
|
12
13
|
protected fillDropdown(dropdown: HTMLElement): boolean;
|
14
|
+
serializeState(): SavedToolbuttonState;
|
15
|
+
deserializeFrom(state: SavedToolbuttonState): void;
|
13
16
|
}
|
@@ -1,10 +1,11 @@
|
|
1
|
+
import Color4 from '../../Color4';
|
1
2
|
import { EditorEventType } from '../../types';
|
2
3
|
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
3
4
|
import makeColorInput from '../makeColorInput';
|
4
5
|
import BaseToolWidget from './BaseToolWidget';
|
5
6
|
export default class TextToolWidget extends BaseToolWidget {
|
6
7
|
constructor(editor, tool, localization) {
|
7
|
-
super(editor, tool, localization);
|
8
|
+
super(editor, tool, 'text-tool-widget', localization);
|
8
9
|
this.tool = tool;
|
9
10
|
this.updateDropdownInputs = null;
|
10
11
|
editor.notifier.on(EditorEventType.ToolUpdated, evt => {
|
@@ -27,7 +28,7 @@ export default class TextToolWidget extends BaseToolWidget {
|
|
27
28
|
const colorRow = document.createElement('div');
|
28
29
|
const fontInput = document.createElement('select');
|
29
30
|
const fontLabel = document.createElement('label');
|
30
|
-
const [colorInput, colorInputContainer] = makeColorInput(this.editor, color => {
|
31
|
+
const [colorInput, colorInputContainer, setColorInputValue] = makeColorInput(this.editor, color => {
|
31
32
|
this.tool.setColor(color);
|
32
33
|
});
|
33
34
|
const colorLabel = document.createElement('label');
|
@@ -57,7 +58,7 @@ export default class TextToolWidget extends BaseToolWidget {
|
|
57
58
|
fontRow.appendChild(fontInput);
|
58
59
|
this.updateDropdownInputs = () => {
|
59
60
|
const style = this.tool.getTextStyle();
|
60
|
-
|
61
|
+
setColorInputValue(style.renderingStyle.fill);
|
61
62
|
if (!fontsInInput.has(style.fontFamily)) {
|
62
63
|
addFontToInput(style.fontFamily);
|
63
64
|
}
|
@@ -67,5 +68,18 @@ export default class TextToolWidget extends BaseToolWidget {
|
|
67
68
|
dropdown.replaceChildren(colorRow, fontRow);
|
68
69
|
return true;
|
69
70
|
}
|
71
|
+
serializeState() {
|
72
|
+
const textStyle = this.tool.getTextStyle();
|
73
|
+
return Object.assign(Object.assign({}, super.serializeState()), { fontFamily: textStyle.fontFamily, color: textStyle.renderingStyle.fill.toHexString() });
|
74
|
+
}
|
75
|
+
deserializeFrom(state) {
|
76
|
+
if (state.fontFamily && typeof (state.fontFamily) === 'string') {
|
77
|
+
this.tool.setFontFamily(state.fontFamily);
|
78
|
+
}
|
79
|
+
if (state.color && typeof (state.color) === 'string') {
|
80
|
+
this.tool.setColor(Color4.fromHex(state.color));
|
81
|
+
}
|
82
|
+
super.deserializeFrom(state);
|
83
|
+
}
|
70
84
|
}
|
71
85
|
TextToolWidget.idCounter = 0;
|
@@ -14,7 +14,8 @@ export declare enum PanZoomMode {
|
|
14
14
|
TwoFingerTouchGestures = 2,
|
15
15
|
RightClickDrags = 4,
|
16
16
|
SinglePointerGestures = 8,
|
17
|
-
Keyboard = 16
|
17
|
+
Keyboard = 16,
|
18
|
+
RotationLocked = 32
|
18
19
|
}
|
19
20
|
export default class PanZoom extends BaseTool {
|
20
21
|
private editor;
|
@@ -36,6 +37,8 @@ export default class PanZoom extends BaseTool {
|
|
36
37
|
private updateTransform;
|
37
38
|
onWheel({ delta, screenPos }: WheelEvt): boolean;
|
38
39
|
onKeyPress({ key, ctrlKey, altKey }: KeyPressEvent): boolean;
|
40
|
+
private isRotationLocked;
|
41
|
+
setModeEnabled(mode: PanZoomMode, enabled: boolean): void;
|
39
42
|
setMode(mode: PanZoomMode): void;
|
40
43
|
getMode(): PanZoomMode;
|
41
44
|
}
|
@@ -12,6 +12,7 @@ export var PanZoomMode;
|
|
12
12
|
PanZoomMode[PanZoomMode["RightClickDrags"] = 4] = "RightClickDrags";
|
13
13
|
PanZoomMode[PanZoomMode["SinglePointerGestures"] = 8] = "SinglePointerGestures";
|
14
14
|
PanZoomMode[PanZoomMode["Keyboard"] = 16] = "Keyboard";
|
15
|
+
PanZoomMode[PanZoomMode["RotationLocked"] = 32] = "RotationLocked";
|
15
16
|
})(PanZoomMode || (PanZoomMode = {}));
|
16
17
|
export default class PanZoom extends BaseTool {
|
17
18
|
constructor(editor, mode, description) {
|
@@ -66,9 +67,13 @@ export default class PanZoom extends BaseTool {
|
|
66
67
|
handleTwoFingerMove(allPointers) {
|
67
68
|
const { screenCenter, canvasCenter, angle, dist } = this.computePinchData(allPointers[0], allPointers[1]);
|
68
69
|
const delta = this.getCenterDelta(screenCenter);
|
70
|
+
let rotation = angle - this.lastAngle;
|
71
|
+
if (this.isRotationLocked()) {
|
72
|
+
rotation = 0;
|
73
|
+
}
|
69
74
|
const transformUpdate = Mat33.translation(delta)
|
70
75
|
.rightMul(Mat33.scaling2D(dist / this.lastDist, canvasCenter))
|
71
|
-
.rightMul(Mat33.zRotation(
|
76
|
+
.rightMul(Mat33.zRotation(rotation, canvasCenter));
|
72
77
|
this.lastScreenCenter = screenCenter;
|
73
78
|
this.lastDist = dist;
|
74
79
|
this.lastAngle = angle;
|
@@ -195,6 +200,9 @@ export default class PanZoom extends BaseTool {
|
|
195
200
|
if (rotation !== 0) {
|
196
201
|
rotation += 0.0001;
|
197
202
|
}
|
203
|
+
if (this.isRotationLocked()) {
|
204
|
+
rotation = 0;
|
205
|
+
}
|
198
206
|
const toCanvas = this.editor.viewport.screenToCanvasTransform;
|
199
207
|
// Transform without translating (treat toCanvas as a linear instead of
|
200
208
|
// an affine transformation).
|
@@ -205,6 +213,21 @@ export default class PanZoom extends BaseTool {
|
|
205
213
|
this.updateTransform(transformUpdate, true);
|
206
214
|
return true;
|
207
215
|
}
|
216
|
+
isRotationLocked() {
|
217
|
+
return !!(this.mode & PanZoomMode.RotationLocked);
|
218
|
+
}
|
219
|
+
// Sets whether the given `mode` is enabled. `mode` should be a single
|
220
|
+
// mode from the `PanZoomMode` enum.
|
221
|
+
setModeEnabled(mode, enabled) {
|
222
|
+
let newMode = this.mode;
|
223
|
+
if (enabled) {
|
224
|
+
newMode |= mode;
|
225
|
+
}
|
226
|
+
else {
|
227
|
+
newMode &= ~mode;
|
228
|
+
}
|
229
|
+
this.setMode(newMode);
|
230
|
+
}
|
208
231
|
setMode(mode) {
|
209
232
|
if (mode !== this.mode) {
|
210
233
|
this.mode = mode;
|
package/dist/src/tools/Pen.d.ts
CHANGED
@@ -11,10 +11,10 @@ export interface PenStyle {
|
|
11
11
|
export default class Pen extends BaseTool {
|
12
12
|
private editor;
|
13
13
|
private style;
|
14
|
+
private builderFactory;
|
14
15
|
protected builder: ComponentBuilder | null;
|
15
|
-
protected builderFactory: ComponentBuilderFactory;
|
16
16
|
private lastPoint;
|
17
|
-
constructor(editor: Editor, description: string, style: PenStyle);
|
17
|
+
constructor(editor: Editor, description: string, style: PenStyle, builderFactory?: ComponentBuilderFactory);
|
18
18
|
private getPressureMultiplier;
|
19
19
|
protected toStrokePoint(pointer: Pointer): StrokeDataPoint;
|
20
20
|
protected previewStroke(): void;
|
package/dist/src/tools/Pen.js
CHANGED
@@ -4,12 +4,12 @@ import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuil
|
|
4
4
|
import { EditorEventType } from '../types';
|
5
5
|
import BaseTool from './BaseTool';
|
6
6
|
export default class Pen extends BaseTool {
|
7
|
-
constructor(editor, description, style) {
|
7
|
+
constructor(editor, description, style, builderFactory = makeFreehandLineBuilder) {
|
8
8
|
super(editor.notifier, description);
|
9
9
|
this.editor = editor;
|
10
10
|
this.style = style;
|
11
|
+
this.builderFactory = builderFactory;
|
11
12
|
this.builder = null;
|
12
|
-
this.builderFactory = makeFreehandLineBuilder;
|
13
13
|
this.lastPoint = null;
|
14
14
|
}
|
15
15
|
getPressureMultiplier() {
|
@@ -30,6 +30,7 @@ export default class Selection {
|
|
30
30
|
this.transform = Mat33.identity;
|
31
31
|
this.transformCommands = [];
|
32
32
|
this.selectedElems = [];
|
33
|
+
this.hasParent = true;
|
33
34
|
this.targetHandle = null;
|
34
35
|
this.backgroundDragging = false;
|
35
36
|
this.originalRegion = new Rect2(startPoint.x, startPoint.y, 0, 0);
|
@@ -96,7 +97,7 @@ export default class Selection {
|
|
96
97
|
// Applies, previews, but doesn't finalize the given transformation.
|
97
98
|
setTransform(transform, preview = true) {
|
98
99
|
this.transform = transform;
|
99
|
-
if (preview) {
|
100
|
+
if (preview && this.hasParent) {
|
100
101
|
this.previewTransformCmds();
|
101
102
|
this.scrollTo();
|
102
103
|
}
|
@@ -190,6 +191,10 @@ export default class Selection {
|
|
190
191
|
}
|
191
192
|
// @internal
|
192
193
|
updateUI() {
|
194
|
+
// Don't update old selections.
|
195
|
+
if (!this.hasParent) {
|
196
|
+
return;
|
197
|
+
}
|
193
198
|
// marginLeft, marginTop: Display relative to the top left of the selection overlay.
|
194
199
|
// left, top don't work for this.
|
195
200
|
this.backgroundElem.style.marginLeft = `${this.screenRegion.topLeft.x}px`;
|
@@ -267,6 +272,7 @@ export default class Selection {
|
|
267
272
|
this.container.remove();
|
268
273
|
}
|
269
274
|
elem.appendChild(this.container);
|
275
|
+
this.hasParent = true;
|
270
276
|
}
|
271
277
|
setToPoint(point) {
|
272
278
|
this.originalRegion = this.originalRegion.grownToPoint(point);
|
@@ -277,6 +283,7 @@ export default class Selection {
|
|
277
283
|
this.container.remove();
|
278
284
|
}
|
279
285
|
this.originalRegion = Rect2.empty;
|
286
|
+
this.hasParent = false;
|
280
287
|
}
|
281
288
|
setSelectedObjects(objects, bbox) {
|
282
289
|
this.originalRegion = bbox;
|
@@ -11,6 +11,7 @@ import PipetteTool from './PipetteTool';
|
|
11
11
|
import ToolSwitcherShortcut from './ToolSwitcherShortcut';
|
12
12
|
import PasteHandler from './PasteHandler';
|
13
13
|
import ToolbarShortcutHandler from './ToolbarShortcutHandler';
|
14
|
+
import { makePressureSensitiveFreehandLineBuilder } from '../components/builders/PressureSensitiveFreehandLineBuilder';
|
14
15
|
export default class ToolController {
|
15
16
|
/** @internal */
|
16
17
|
constructor(editor, localization) {
|
@@ -25,7 +26,7 @@ export default class ToolController {
|
|
25
26
|
primaryPenTool,
|
26
27
|
new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
|
27
28
|
// Highlighter-like pen with width=64
|
28
|
-
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
|
29
|
+
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }, makePressureSensitiveFreehandLineBuilder),
|
29
30
|
new Eraser(editor, localization.eraserTool),
|
30
31
|
new SelectionTool(editor, localization.selectionTool),
|
31
32
|
new TextTool(editor, localization.textTool, localization),
|
package/package.json
CHANGED
package/src/Color4.ts
CHANGED
@@ -73,6 +73,8 @@ export default class Color4 {
|
|
73
73
|
public static fromString(text: string): Color4 {
|
74
74
|
if (text.startsWith('#')) {
|
75
75
|
return Color4.fromHex(text);
|
76
|
+
} else if (text === 'none' || text === 'transparent') {
|
77
|
+
return Color4.transparent;
|
76
78
|
} else {
|
77
79
|
// Otherwise, try to use an HTML5Canvas to determine the color
|
78
80
|
const canvas = document.createElement('canvas');
|
package/src/SVGLoader.ts
CHANGED
@@ -28,6 +28,9 @@ export type SVGLoaderUnknownAttribute = [ string, string ];
|
|
28
28
|
// [key, value, priority]
|
29
29
|
export type SVGLoaderUnknownStyleAttribute = { key: string, value: string, priority?: string };
|
30
30
|
|
31
|
+
|
32
|
+
const supportedStrokeFillStyleAttrs = [ 'stroke', 'fill', 'stroke-width' ];
|
33
|
+
|
31
34
|
export default class SVGLoader implements ImageLoader {
|
32
35
|
private onAddComponent: ComponentAddedListener|null = null;
|
33
36
|
private onProgress: OnProgressListener|null = null;
|
@@ -154,11 +157,10 @@ export default class SVGLoader implements ImageLoader {
|
|
154
157
|
|
155
158
|
elem = new Stroke(strokeData);
|
156
159
|
|
157
|
-
const supportedStyleAttrs = [ 'stroke', 'fill', 'stroke-width' ];
|
158
160
|
this.attachUnrecognisedAttrs(
|
159
161
|
elem, node,
|
160
|
-
new Set([ ...
|
161
|
-
new Set(
|
162
|
+
new Set([ ...supportedStrokeFillStyleAttrs, 'd' ]),
|
163
|
+
new Set(supportedStrokeFillStyleAttrs)
|
162
164
|
);
|
163
165
|
} catch (e) {
|
164
166
|
console.error(
|
@@ -234,8 +236,8 @@ export default class SVGLoader implements ImageLoader {
|
|
234
236
|
|
235
237
|
const supportedStyleAttrs = [
|
236
238
|
'fontFamily',
|
237
|
-
'
|
238
|
-
|
239
|
+
'transform',
|
240
|
+
...supportedStrokeFillStyleAttrs,
|
239
241
|
];
|
240
242
|
let fontSize = 12;
|
241
243
|
if (fontSizeMatch) {
|
@@ -245,9 +247,7 @@ export default class SVGLoader implements ImageLoader {
|
|
245
247
|
const style: TextStyle = {
|
246
248
|
size: fontSize,
|
247
249
|
fontFamily: computedStyles.fontFamily || elem.style.fontFamily || 'sans-serif',
|
248
|
-
renderingStyle:
|
249
|
-
fill: Color4.fromString(computedStyles.fill || elem.style.fill || '#000')
|
250
|
-
},
|
250
|
+
renderingStyle: this.getStyle(elem),
|
251
251
|
};
|
252
252
|
|
253
253
|
const supportedAttrs: string[] = [];
|
package/src/components/Stroke.ts
CHANGED
@@ -61,7 +61,7 @@ export default class Stroke extends AbstractComponent {
|
|
61
61
|
}
|
62
62
|
|
63
63
|
const muchBiggerThanVisible = bbox.size.x > visibleRect.size.x * 2 || bbox.size.y > visibleRect.size.y * 2;
|
64
|
-
if (muchBiggerThanVisible && !part.path.
|
64
|
+
if (muchBiggerThanVisible && !part.path.roughlyIntersects(visibleRect, part.style.stroke?.width ?? 0)) {
|
65
65
|
continue;
|
66
66
|
}
|
67
67
|
}
|
@@ -87,7 +87,20 @@ export default class Stroke extends AbstractComponent {
|
|
87
87
|
// Update each part
|
88
88
|
this.parts = this.parts.map((part) => {
|
89
89
|
const newPath = part.path.transformedBy(affineTransfm);
|
90
|
-
const
|
90
|
+
const newStyle = {
|
91
|
+
...part.style,
|
92
|
+
stroke: part.style.stroke ? {
|
93
|
+
...part.style.stroke,
|
94
|
+
} : undefined,
|
95
|
+
};
|
96
|
+
|
97
|
+
// Approximate the scale factor.
|
98
|
+
if (newStyle.stroke) {
|
99
|
+
const scaleFactor = affineTransfm.getScaleFactor();
|
100
|
+
newStyle.stroke.width *= scaleFactor;
|
101
|
+
}
|
102
|
+
|
103
|
+
const newBBox = this.bboxForPart(newPath.bbox, newStyle);
|
91
104
|
|
92
105
|
if (isFirstPart) {
|
93
106
|
this.contentBBox = newBBox;
|
@@ -100,7 +113,7 @@ export default class Stroke extends AbstractComponent {
|
|
100
113
|
path: newPath,
|
101
114
|
startPoint: newPath.startPoint,
|
102
115
|
commands: newPath.parts,
|
103
|
-
style:
|
116
|
+
style: newStyle,
|
104
117
|
};
|
105
118
|
});
|
106
119
|
}
|