js-draw 0.1.1 → 0.1.2
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 +3 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.js +4 -0
- package/dist/src/EditorImage.js +3 -0
- package/dist/src/Pointer.d.ts +3 -2
- package/dist/src/Pointer.js +12 -3
- package/dist/src/SVGLoader.d.ts +3 -0
- package/dist/src/SVGLoader.js +11 -1
- package/dist/src/Viewport.js +10 -0
- package/dist/src/components/AbstractComponent.d.ts +6 -0
- package/dist/src/components/AbstractComponent.js +11 -0
- package/dist/src/components/Stroke.js +1 -1
- package/dist/src/geometry/Path.js +97 -66
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/AbstractRenderer.js +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -2
- package/dist/src/rendering/renderers/SVGRenderer.js +21 -7
- package/dist/src/toolbar/HTMLToolbar.d.ts +2 -1
- package/dist/src/toolbar/HTMLToolbar.js +165 -154
- package/dist/src/toolbar/icons.d.ts +10 -0
- package/dist/src/toolbar/icons.js +180 -0
- package/dist/src/toolbar/localization.d.ts +4 -1
- package/dist/src/toolbar/localization.js +4 -1
- package/dist/src/toolbar/types.d.ts +4 -0
- package/dist/src/tools/PanZoom.d.ts +9 -6
- package/dist/src/tools/PanZoom.js +30 -21
- package/dist/src/tools/Pen.js +8 -3
- package/dist/src/tools/ToolController.d.ts +5 -6
- package/dist/src/tools/ToolController.js +8 -10
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/package.json +1 -1
- package/src/Editor.ts +4 -0
- package/src/EditorImage.ts +4 -0
- package/src/Pointer.ts +13 -4
- package/src/SVGLoader.ts +25 -2
- package/src/Viewport.ts +13 -1
- package/src/components/AbstractComponent.ts +16 -1
- package/src/components/Stroke.ts +1 -1
- package/src/geometry/Path.fromString.test.ts +94 -4
- package/src/geometry/Path.ts +99 -67
- package/src/rendering/renderers/AbstractRenderer.ts +2 -1
- package/src/rendering/renderers/SVGRenderer.ts +22 -10
- package/src/toolbar/HTMLToolbar.ts +199 -170
- package/src/toolbar/icons.ts +203 -0
- package/src/toolbar/localization.ts +9 -2
- package/src/toolbar/toolbar.css +21 -8
- package/src/toolbar/types.ts +5 -0
- package/src/tools/PanZoom.ts +37 -27
- package/src/tools/Pen.ts +7 -3
- package/src/tools/ToolController.ts +3 -5
- package/src/tools/localization.ts +2 -0
@@ -11,22 +11,23 @@ interface PinchData {
|
|
11
11
|
dist: number;
|
12
12
|
}
|
13
13
|
export declare enum PanZoomMode {
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
OneFingerTouchGestures = 1,
|
15
|
+
TwoFingerTouchGestures = 2,
|
16
|
+
RightClickDrags = 4,
|
17
|
+
SinglePointerGestures = 8
|
17
18
|
}
|
18
19
|
export default class PanZoom extends BaseTool {
|
19
20
|
private editor;
|
20
21
|
private mode;
|
21
|
-
readonly kind: ToolType.PanZoom
|
22
|
+
readonly kind: ToolType.PanZoom;
|
22
23
|
private transform;
|
23
24
|
private lastAngle;
|
24
25
|
private lastDist;
|
25
26
|
private lastScreenCenter;
|
26
27
|
constructor(editor: Editor, mode: PanZoomMode, description: string);
|
27
28
|
computePinchData(p1: Pointer, p2: Pointer): PinchData;
|
28
|
-
private
|
29
|
-
onPointerDown({ allPointers }: PointerEvt): boolean;
|
29
|
+
private allPointersAreOfType;
|
30
|
+
onPointerDown({ allPointers: pointers }: PointerEvt): boolean;
|
30
31
|
private getCenterDelta;
|
31
32
|
private handleTwoFingerMove;
|
32
33
|
private handleOneFingerMove;
|
@@ -36,5 +37,7 @@ export default class PanZoom extends BaseTool {
|
|
36
37
|
private updateTransform;
|
37
38
|
onWheel({ delta, screenPos }: WheelEvt): boolean;
|
38
39
|
onKeyPress({ key }: KeyPressEvent): boolean;
|
40
|
+
setMode(mode: PanZoomMode): void;
|
41
|
+
getMode(): PanZoomMode;
|
39
42
|
}
|
40
43
|
export {};
|
@@ -2,17 +2,16 @@ import Mat33 from '../geometry/Mat33';
|
|
2
2
|
import { Vec2 } from '../geometry/Vec2';
|
3
3
|
import Vec3 from '../geometry/Vec3';
|
4
4
|
import { PointerDevice } from '../Pointer';
|
5
|
+
import { EditorEventType } from '../types';
|
5
6
|
import { Viewport } from '../Viewport';
|
6
7
|
import BaseTool from './BaseTool';
|
7
8
|
import { ToolType } from './ToolController';
|
8
9
|
export var PanZoomMode;
|
9
10
|
(function (PanZoomMode) {
|
10
|
-
|
11
|
-
PanZoomMode[PanZoomMode["
|
12
|
-
|
13
|
-
PanZoomMode[PanZoomMode["
|
14
|
-
// / Handle gestures from any device, rather than just touch
|
15
|
-
PanZoomMode[PanZoomMode["AnyDevice"] = 4] = "AnyDevice";
|
11
|
+
PanZoomMode[PanZoomMode["OneFingerTouchGestures"] = 1] = "OneFingerTouchGestures";
|
12
|
+
PanZoomMode[PanZoomMode["TwoFingerTouchGestures"] = 2] = "TwoFingerTouchGestures";
|
13
|
+
PanZoomMode[PanZoomMode["RightClickDrags"] = 4] = "RightClickDrags";
|
14
|
+
PanZoomMode[PanZoomMode["SinglePointerGestures"] = 8] = "SinglePointerGestures";
|
16
15
|
})(PanZoomMode || (PanZoomMode = {}));
|
17
16
|
export default class PanZoom extends BaseTool {
|
18
17
|
constructor(editor, mode, description) {
|
@@ -21,9 +20,6 @@ export default class PanZoom extends BaseTool {
|
|
21
20
|
this.mode = mode;
|
22
21
|
this.kind = ToolType.PanZoom;
|
23
22
|
this.transform = null;
|
24
|
-
if (mode === PanZoomMode.OneFingerGestures) {
|
25
|
-
this.kind = ToolType.TouchPanZoom;
|
26
|
-
}
|
27
23
|
}
|
28
24
|
// Returns information about the pointers in a gesture
|
29
25
|
computePinchData(p1, p2) {
|
@@ -34,24 +30,25 @@ export default class PanZoom extends BaseTool {
|
|
34
30
|
const screenCenter = p2.screenPos.plus(p1.screenPos).times(0.5);
|
35
31
|
return { canvasCenter, screenCenter, angle, dist };
|
36
32
|
}
|
37
|
-
|
38
|
-
return
|
33
|
+
allPointersAreOfType(pointers, kind) {
|
34
|
+
return pointers.every(pointer => pointer.device === kind);
|
39
35
|
}
|
40
|
-
onPointerDown({ allPointers }) {
|
36
|
+
onPointerDown({ allPointers: pointers }) {
|
41
37
|
var _a;
|
42
38
|
let handlingGesture = false;
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
const { screenCenter, angle, dist } = this.computePinchData(allPointers[0], allPointers[1]);
|
39
|
+
const allAreTouch = this.allPointersAreOfType(pointers, PointerDevice.Touch);
|
40
|
+
const isRightClick = this.allPointersAreOfType(pointers, PointerDevice.RightButtonMouse);
|
41
|
+
if (allAreTouch && pointers.length === 2 && this.mode & PanZoomMode.TwoFingerTouchGestures) {
|
42
|
+
const { screenCenter, angle, dist } = this.computePinchData(pointers[0], pointers[1]);
|
48
43
|
this.lastAngle = angle;
|
49
44
|
this.lastDist = dist;
|
50
45
|
this.lastScreenCenter = screenCenter;
|
51
46
|
handlingGesture = true;
|
52
47
|
}
|
53
|
-
else if (
|
54
|
-
this.
|
48
|
+
else if (pointers.length === 1 && ((this.mode & PanZoomMode.OneFingerTouchGestures && allAreTouch)
|
49
|
+
|| (isRightClick && this.mode & PanZoomMode.RightClickDrags)
|
50
|
+
|| (this.mode & PanZoomMode.SinglePointerGestures))) {
|
51
|
+
this.lastScreenCenter = pointers[0].screenPos;
|
55
52
|
handlingGesture = true;
|
56
53
|
}
|
57
54
|
if (handlingGesture) {
|
@@ -87,10 +84,10 @@ export default class PanZoom extends BaseTool {
|
|
87
84
|
var _a;
|
88
85
|
(_a = this.transform) !== null && _a !== void 0 ? _a : (this.transform = new Viewport.ViewportTransform(Mat33.identity));
|
89
86
|
const lastTransform = this.transform;
|
90
|
-
if (allPointers.length === 2
|
87
|
+
if (allPointers.length === 2) {
|
91
88
|
this.handleTwoFingerMove(allPointers);
|
92
89
|
}
|
93
|
-
else if (allPointers.length === 1
|
90
|
+
else if (allPointers.length === 1) {
|
94
91
|
this.handleOneFingerMove(allPointers[0]);
|
95
92
|
}
|
96
93
|
lastTransform.unapply(this.editor);
|
@@ -191,4 +188,16 @@ export default class PanZoom extends BaseTool {
|
|
191
188
|
this.updateTransform(transformUpdate);
|
192
189
|
return true;
|
193
190
|
}
|
191
|
+
setMode(mode) {
|
192
|
+
if (mode !== this.mode) {
|
193
|
+
this.mode = mode;
|
194
|
+
this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
|
195
|
+
kind: EditorEventType.ToolUpdated,
|
196
|
+
tool: this,
|
197
|
+
});
|
198
|
+
}
|
199
|
+
}
|
200
|
+
getMode() {
|
201
|
+
return this.mode;
|
202
|
+
}
|
194
203
|
}
|
package/dist/src/tools/Pen.js
CHANGED
@@ -66,9 +66,14 @@ export default class Pen extends BaseTool {
|
|
66
66
|
if (this.builder && current.isPrimary) {
|
67
67
|
const stroke = this.builder.build();
|
68
68
|
this.previewStroke();
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
if (stroke.getBBox().area > 0) {
|
70
|
+
const canFlatten = true;
|
71
|
+
const action = new EditorImage.AddElementCommand(stroke, canFlatten);
|
72
|
+
this.editor.dispatch(action);
|
73
|
+
}
|
74
|
+
else {
|
75
|
+
console.warn('Pen: Not adding empty stroke', stroke, 'to the canvas.');
|
76
|
+
}
|
72
77
|
}
|
73
78
|
this.builder = null;
|
74
79
|
this.editor.clearWetInk();
|
@@ -3,12 +3,11 @@ import Editor from '../Editor';
|
|
3
3
|
import BaseTool from './BaseTool';
|
4
4
|
import { ToolLocalization } from './localization';
|
5
5
|
export declare enum ToolType {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
UndoRedoShortcut = 5
|
6
|
+
Pen = 0,
|
7
|
+
Selection = 1,
|
8
|
+
Eraser = 2,
|
9
|
+
PanZoom = 3,
|
10
|
+
UndoRedoShortcut = 4
|
12
11
|
}
|
13
12
|
export default class ToolController {
|
14
13
|
private tools;
|
@@ -8,17 +8,16 @@ import Color4 from '../Color4';
|
|
8
8
|
import UndoRedoShortcut from './UndoRedoShortcut';
|
9
9
|
export var ToolType;
|
10
10
|
(function (ToolType) {
|
11
|
-
ToolType[ToolType["
|
12
|
-
ToolType[ToolType["
|
13
|
-
ToolType[ToolType["
|
14
|
-
ToolType[ToolType["
|
15
|
-
ToolType[ToolType["
|
16
|
-
ToolType[ToolType["UndoRedoShortcut"] = 5] = "UndoRedoShortcut";
|
11
|
+
ToolType[ToolType["Pen"] = 0] = "Pen";
|
12
|
+
ToolType[ToolType["Selection"] = 1] = "Selection";
|
13
|
+
ToolType[ToolType["Eraser"] = 2] = "Eraser";
|
14
|
+
ToolType[ToolType["PanZoom"] = 3] = "PanZoom";
|
15
|
+
ToolType[ToolType["UndoRedoShortcut"] = 4] = "UndoRedoShortcut";
|
17
16
|
})(ToolType || (ToolType = {}));
|
18
17
|
export default class ToolController {
|
19
18
|
constructor(editor, localization) {
|
20
19
|
const primaryToolEnabledGroup = new ToolEnabledGroup();
|
21
|
-
const
|
20
|
+
const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
|
22
21
|
const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });
|
23
22
|
const primaryTools = [
|
24
23
|
new SelectionTool(editor, localization.selectionTool),
|
@@ -30,13 +29,12 @@ export default class ToolController {
|
|
30
29
|
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
|
31
30
|
];
|
32
31
|
this.tools = [
|
33
|
-
|
32
|
+
panZoomTool,
|
34
33
|
...primaryTools,
|
35
|
-
new PanZoom(editor, PanZoomMode.TwoFingerGestures | PanZoomMode.AnyDevice, localization.twoFingerPanZoomTool),
|
36
34
|
new UndoRedoShortcut(editor),
|
37
35
|
];
|
38
36
|
primaryTools.forEach(tool => tool.setToolGroup(primaryToolEnabledGroup));
|
39
|
-
|
37
|
+
panZoomTool.setEnabled(true);
|
40
38
|
primaryPenTool.setEnabled(true);
|
41
39
|
editor.notifier.on(EditorEventType.ToolEnabled, event => {
|
42
40
|
if (event.kind === EditorEventType.ToolEnabled) {
|
@@ -5,6 +5,7 @@ export const defaultToolLocalization = {
|
|
5
5
|
touchPanTool: 'Touch Panning',
|
6
6
|
twoFingerPanZoomTool: 'Panning and Zooming',
|
7
7
|
undoRedoTool: 'Undo/Redo',
|
8
|
+
RightClickDragPanTool: 'Right-click drag',
|
8
9
|
toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
|
9
10
|
toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
|
10
11
|
};
|
package/package.json
CHANGED
package/src/Editor.ts
CHANGED
@@ -165,6 +165,10 @@ export class Editor {
|
|
165
165
|
// May be required to prevent text selection on iOS/Safari:
|
166
166
|
// See https://stackoverflow.com/a/70992717/17055750
|
167
167
|
this.renderingRegion.addEventListener('touchstart', evt => evt.preventDefault());
|
168
|
+
this.renderingRegion.addEventListener('contextmenu', evt => {
|
169
|
+
// Don't show a context menu
|
170
|
+
evt.preventDefault();
|
171
|
+
});
|
168
172
|
|
169
173
|
this.renderingRegion.addEventListener('pointerdown', evt => {
|
170
174
|
const pointer = Pointer.ofEvent(evt, true, this.viewport);
|
package/src/EditorImage.ts
CHANGED
@@ -73,6 +73,10 @@ export default class EditorImage {
|
|
73
73
|
) {
|
74
74
|
this.#element = element;
|
75
75
|
this.#applyByFlattening = applyByFlattening;
|
76
|
+
|
77
|
+
if (isNaN(this.#element.getBBox().area)) {
|
78
|
+
throw new Error('Elements in the image cannot have NaN bounding boxes');
|
79
|
+
}
|
76
80
|
}
|
77
81
|
|
78
82
|
public apply(editor: Editor) {
|
package/src/Pointer.ts
CHANGED
@@ -5,7 +5,8 @@ export enum PointerDevice {
|
|
5
5
|
Pen,
|
6
6
|
Eraser,
|
7
7
|
Touch,
|
8
|
-
|
8
|
+
PrimaryButtonMouse,
|
9
|
+
RightButtonMouse,
|
9
10
|
Other,
|
10
11
|
}
|
11
12
|
|
@@ -31,7 +32,7 @@ export default class Pointer {
|
|
31
32
|
public readonly id: number,
|
32
33
|
|
33
34
|
// Numeric timestamp (milliseconds, as from (new Date).getTime())
|
34
|
-
public readonly timeStamp: number
|
35
|
+
public readonly timeStamp: number,
|
35
36
|
) {
|
36
37
|
}
|
37
38
|
|
@@ -39,7 +40,7 @@ export default class Pointer {
|
|
39
40
|
const screenPos = Vec2.of(evt.offsetX, evt.offsetY);
|
40
41
|
|
41
42
|
const pointerTypeToDevice: Record<string, PointerDevice> = {
|
42
|
-
'mouse': PointerDevice.
|
43
|
+
'mouse': PointerDevice.PrimaryButtonMouse,
|
43
44
|
'pen': PointerDevice.Pen,
|
44
45
|
'touch': PointerDevice.Touch,
|
45
46
|
};
|
@@ -53,6 +54,14 @@ export default class Pointer {
|
|
53
54
|
const timeStamp = (new Date()).getTime();
|
54
55
|
const canvasPos = viewport.roundPoint(viewport.screenToCanvas(screenPos));
|
55
56
|
|
57
|
+
if (device === PointerDevice.PrimaryButtonMouse) {
|
58
|
+
if (evt.buttons & 0x2) {
|
59
|
+
device = PointerDevice.RightButtonMouse;
|
60
|
+
} else if (!(evt.buttons & 0x1)) {
|
61
|
+
device = PointerDevice.Other;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
56
65
|
return new Pointer(
|
57
66
|
screenPos,
|
58
67
|
canvasPos,
|
@@ -61,7 +70,7 @@ export default class Pointer {
|
|
61
70
|
isDown,
|
62
71
|
device,
|
63
72
|
evt.pointerId,
|
64
|
-
timeStamp
|
73
|
+
timeStamp,
|
65
74
|
);
|
66
75
|
}
|
67
76
|
|
package/src/SVGLoader.ts
CHANGED
@@ -13,6 +13,12 @@ type OnFinishListener = ()=> void;
|
|
13
13
|
// Size of a loaded image if no size is specified.
|
14
14
|
export const defaultSVGViewRect = new Rect2(0, 0, 500, 500);
|
15
15
|
|
16
|
+
// Key to retrieve unrecognised attributes from an AbstractComponent
|
17
|
+
export const svgAttributesDataKey = 'svgAttrs';
|
18
|
+
|
19
|
+
// [key, value]
|
20
|
+
export type SVGLoaderUnknownAttribute = [ string, string ];
|
21
|
+
|
16
22
|
export default class SVGLoader implements ImageLoader {
|
17
23
|
private onAddComponent: ComponentAddedListener|null = null;
|
18
24
|
private onProgress: OnProgressListener|null = null;
|
@@ -88,12 +94,31 @@ export default class SVGLoader implements ImageLoader {
|
|
88
94
|
return result;
|
89
95
|
}
|
90
96
|
|
97
|
+
private attachUnrecognisedAttrs(
|
98
|
+
elem: AbstractComponent,
|
99
|
+
node: SVGElement,
|
100
|
+
supportedAttrs: Set<string>
|
101
|
+
) {
|
102
|
+
for (const attr of node.getAttributeNames()) {
|
103
|
+
if (supportedAttrs.has(attr)) {
|
104
|
+
continue;
|
105
|
+
}
|
106
|
+
|
107
|
+
elem.attachLoadSaveData(svgAttributesDataKey,
|
108
|
+
[ attr, node.getAttribute(attr) ] as SVGLoaderUnknownAttribute,
|
109
|
+
);
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
91
113
|
// Adds a stroke with a single path
|
92
114
|
private addPath(node: SVGPathElement) {
|
93
115
|
let elem: AbstractComponent;
|
94
116
|
try {
|
95
117
|
const strokeData = this.strokeDataFromElem(node);
|
96
118
|
elem = new Stroke(strokeData);
|
119
|
+
this.attachUnrecognisedAttrs(
|
120
|
+
elem, node, new Set([ 'stroke', 'fill', 'stroke-width', 'd' ]),
|
121
|
+
);
|
97
122
|
} catch (e) {
|
98
123
|
console.error(
|
99
124
|
'Invalid path in node', node,
|
@@ -214,9 +239,7 @@ export default class SVGLoader implements ImageLoader {
|
|
214
239
|
throw new Error('SVG loading iframe is not sandboxed.');
|
215
240
|
}
|
216
241
|
|
217
|
-
// Try running JavaScript within the iframe
|
218
242
|
const sandboxDoc = sandbox.contentWindow?.document ?? sandbox.contentDocument;
|
219
|
-
|
220
243
|
if (sandboxDoc == null) throw new Error('Unable to open a sandboxed iframe!');
|
221
244
|
|
222
245
|
sandboxDoc.open();
|
package/src/Viewport.ts
CHANGED
@@ -173,6 +173,14 @@ export class Viewport {
|
|
173
173
|
public zoomTo(toMakeVisible: Rect2): Command {
|
174
174
|
let transform = Mat33.identity;
|
175
175
|
|
176
|
+
if (toMakeVisible.w === 0 || toMakeVisible.h === 0) {
|
177
|
+
throw new Error(`${toMakeVisible.toString()} rectangle is empty! Cannot zoom to!`);
|
178
|
+
}
|
179
|
+
|
180
|
+
if (isNaN(toMakeVisible.size.magnitude())) {
|
181
|
+
throw new Error(`${toMakeVisible.toString()} rectangle has NaN size! Cannot zoom to!`);
|
182
|
+
}
|
183
|
+
|
176
184
|
// Try to move the selection within the center 2/3rds of the viewport.
|
177
185
|
const recomputeTargetRect = () => {
|
178
186
|
// transform transforms objects on the canvas. As such, we need to invert it
|
@@ -210,7 +218,11 @@ export class Viewport {
|
|
210
218
|
|
211
219
|
transform = transform.rightMul(viewportContentTransform);
|
212
220
|
}
|
213
|
-
|
221
|
+
|
222
|
+
if (!transform.invertable()) {
|
223
|
+
console.warn('Unable to zoom to ', toMakeVisible, '! Computed transform', transform, 'is singular.');
|
224
|
+
transform = Mat33.identity;
|
225
|
+
}
|
214
226
|
|
215
227
|
return new Viewport.ViewportTransform(transform);
|
216
228
|
}
|
@@ -7,6 +7,9 @@ import Rect2 from '../geometry/Rect2';
|
|
7
7
|
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
8
8
|
import { ImageComponentLocalization } from './localization';
|
9
9
|
|
10
|
+
type LoadSaveData = unknown;
|
11
|
+
export type LoadSaveDataTable = Record<string, Array<LoadSaveData>>;
|
12
|
+
|
10
13
|
export default abstract class AbstractComponent {
|
11
14
|
protected lastChangedTime: number;
|
12
15
|
protected abstract contentBBox: Rect2;
|
@@ -20,13 +23,25 @@ export default abstract class AbstractComponent {
|
|
20
23
|
this.zIndex = AbstractComponent.zIndexCounter++;
|
21
24
|
}
|
22
25
|
|
26
|
+
// Get and manage data attached by a loader.
|
27
|
+
private loadSaveData: LoadSaveDataTable = {};
|
28
|
+
public attachLoadSaveData(key: string, data: LoadSaveData) {
|
29
|
+
if (!this.loadSaveData[key]) {
|
30
|
+
this.loadSaveData[key] = [];
|
31
|
+
}
|
32
|
+
this.loadSaveData[key].push(data);
|
33
|
+
}
|
34
|
+
public getLoadSaveData(): LoadSaveDataTable {
|
35
|
+
return this.loadSaveData;
|
36
|
+
}
|
37
|
+
|
23
38
|
public getZIndex(): number {
|
24
39
|
return this.zIndex;
|
25
40
|
}
|
26
|
-
|
27
41
|
public getBBox(): Rect2 {
|
28
42
|
return this.contentBBox;
|
29
43
|
}
|
44
|
+
|
30
45
|
public abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
31
46
|
public abstract intersects(lineSegment: LineSegment2): boolean;
|
32
47
|
|
package/src/components/Stroke.ts
CHANGED
@@ -90,15 +90,38 @@ describe('Path.fromString', () => {
|
|
90
90
|
]);
|
91
91
|
});
|
92
92
|
|
93
|
+
it('should break compoents at -s', () => {
|
94
|
+
const path = Path.fromString('m1-1 L-1-1-3-4-5-6,5-1');
|
95
|
+
expect(path.parts.length).toBe(4);
|
96
|
+
expect(path.parts).toMatchObject([
|
97
|
+
{
|
98
|
+
kind: PathCommandType.LineTo,
|
99
|
+
point: Vec2.of(-1, -1),
|
100
|
+
},
|
101
|
+
{
|
102
|
+
kind: PathCommandType.LineTo,
|
103
|
+
point: Vec2.of(-3, -4),
|
104
|
+
},
|
105
|
+
{
|
106
|
+
kind: PathCommandType.LineTo,
|
107
|
+
point: Vec2.of(-5, -6),
|
108
|
+
},
|
109
|
+
{
|
110
|
+
kind: PathCommandType.LineTo,
|
111
|
+
point: Vec2.of(5, -1),
|
112
|
+
},
|
113
|
+
]);
|
114
|
+
});
|
115
|
+
|
93
116
|
it('should properly handle cubic Bézier curves', () => {
|
94
|
-
const path = Path.fromString('c1,1 0
|
117
|
+
const path = Path.fromString('m1,1 c1,1 0-3 4 5 C1,1 0.1, 0.1 0, 0');
|
95
118
|
expect(path.parts.length).toBe(2);
|
96
119
|
expect(path.parts).toMatchObject([
|
97
120
|
{
|
98
121
|
kind: PathCommandType.CubicBezierTo,
|
99
|
-
controlPoint1: Vec2.of(
|
122
|
+
controlPoint1: Vec2.of(2, 2),
|
100
123
|
controlPoint2: Vec2.of(1, -2),
|
101
|
-
endPoint: Vec2.of(5,
|
124
|
+
endPoint: Vec2.of(5, 6),
|
102
125
|
},
|
103
126
|
{
|
104
127
|
kind: PathCommandType.CubicBezierTo,
|
@@ -120,7 +143,7 @@ describe('Path.fromString', () => {
|
|
120
143
|
{
|
121
144
|
kind: PathCommandType.QuadraticBezierTo,
|
122
145
|
controlPoint: Vec2.of(1, 1),
|
123
|
-
endPoint: Vec2.of(-
|
146
|
+
endPoint: Vec2.of(-1, -1),
|
124
147
|
},
|
125
148
|
{
|
126
149
|
kind: PathCommandType.QuadraticBezierTo,
|
@@ -130,4 +153,71 @@ describe('Path.fromString', () => {
|
|
130
153
|
]);
|
131
154
|
expect(path.startPoint).toMatchObject(Vec2.of(1, 1));
|
132
155
|
});
|
156
|
+
|
157
|
+
it('should correctly handle a command followed by multiple sets of arguments', () => {
|
158
|
+
// Commands followed by multiple sets of arguments, for example,
|
159
|
+
// l 5,10 5,4 3,2,
|
160
|
+
// should be interpreted as multiple commands. Our example, is therefore equivalent to,
|
161
|
+
// l 5,10 l 5,4 l 3,2
|
162
|
+
|
163
|
+
const path = Path.fromString(`
|
164
|
+
L5,10 1,1
|
165
|
+
2,2 -3,-1
|
166
|
+
q 1,2 1,1
|
167
|
+
-1,-1 -3,-4
|
168
|
+
h -4 -1
|
169
|
+
V 3 5 1
|
170
|
+
`);
|
171
|
+
expect(path.parts).toMatchObject([
|
172
|
+
{
|
173
|
+
kind: PathCommandType.LineTo,
|
174
|
+
point: Vec2.of(1, 1),
|
175
|
+
},
|
176
|
+
{
|
177
|
+
kind: PathCommandType.LineTo,
|
178
|
+
point: Vec2.of(2, 2),
|
179
|
+
},
|
180
|
+
{
|
181
|
+
kind: PathCommandType.LineTo,
|
182
|
+
point: Vec2.of(-3, -1),
|
183
|
+
},
|
184
|
+
|
185
|
+
// q 1,2 1,1 -1,-1 -3,-4
|
186
|
+
{
|
187
|
+
kind: PathCommandType.QuadraticBezierTo,
|
188
|
+
controlPoint: Vec2.of(-2, 1),
|
189
|
+
endPoint: Vec2.of(-2, 0),
|
190
|
+
},
|
191
|
+
{
|
192
|
+
kind: PathCommandType.QuadraticBezierTo,
|
193
|
+
controlPoint: Vec2.of(-3, -1),
|
194
|
+
endPoint: Vec2.of(-5, -4),
|
195
|
+
},
|
196
|
+
|
197
|
+
// h -4 -1
|
198
|
+
{
|
199
|
+
kind: PathCommandType.LineTo,
|
200
|
+
point: Vec2.of(-9, -4),
|
201
|
+
},
|
202
|
+
{
|
203
|
+
kind: PathCommandType.LineTo,
|
204
|
+
point: Vec2.of(-10, -4),
|
205
|
+
},
|
206
|
+
|
207
|
+
// V 3 5 1
|
208
|
+
{
|
209
|
+
kind: PathCommandType.LineTo,
|
210
|
+
point: Vec2.of(-10, 3),
|
211
|
+
},
|
212
|
+
{
|
213
|
+
kind: PathCommandType.LineTo,
|
214
|
+
point: Vec2.of(-10, 5),
|
215
|
+
},
|
216
|
+
{
|
217
|
+
kind: PathCommandType.LineTo,
|
218
|
+
point: Vec2.of(-10, 1),
|
219
|
+
},
|
220
|
+
]);
|
221
|
+
expect(path.startPoint).toMatchObject(Vec2.of(5, 10));
|
222
|
+
});
|
133
223
|
});
|