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
package/dist/src/Editor.js
CHANGED
@@ -115,6 +115,10 @@ export class Editor {
|
|
115
115
|
// May be required to prevent text selection on iOS/Safari:
|
116
116
|
// See https://stackoverflow.com/a/70992717/17055750
|
117
117
|
this.renderingRegion.addEventListener('touchstart', evt => evt.preventDefault());
|
118
|
+
this.renderingRegion.addEventListener('contextmenu', evt => {
|
119
|
+
// Don't show a context menu
|
120
|
+
evt.preventDefault();
|
121
|
+
});
|
118
122
|
this.renderingRegion.addEventListener('pointerdown', evt => {
|
119
123
|
const pointer = Pointer.ofEvent(evt, true, this.viewport);
|
120
124
|
pointers[pointer.id] = pointer;
|
package/dist/src/EditorImage.js
CHANGED
@@ -62,6 +62,9 @@ EditorImage.AddElementCommand = (_a = class {
|
|
62
62
|
_applyByFlattening.set(this, false);
|
63
63
|
__classPrivateFieldSet(this, _element, element, "f");
|
64
64
|
__classPrivateFieldSet(this, _applyByFlattening, applyByFlattening, "f");
|
65
|
+
if (isNaN(__classPrivateFieldGet(this, _element, "f").getBBox().area)) {
|
66
|
+
throw new Error('Elements in the image cannot have NaN bounding boxes');
|
67
|
+
}
|
65
68
|
}
|
66
69
|
apply(editor) {
|
67
70
|
editor.image.addElement(__classPrivateFieldGet(this, _element, "f"));
|
package/dist/src/Pointer.d.ts
CHANGED
package/dist/src/Pointer.js
CHANGED
@@ -4,8 +4,9 @@ export var PointerDevice;
|
|
4
4
|
PointerDevice[PointerDevice["Pen"] = 0] = "Pen";
|
5
5
|
PointerDevice[PointerDevice["Eraser"] = 1] = "Eraser";
|
6
6
|
PointerDevice[PointerDevice["Touch"] = 2] = "Touch";
|
7
|
-
PointerDevice[PointerDevice["
|
8
|
-
PointerDevice[PointerDevice["
|
7
|
+
PointerDevice[PointerDevice["PrimaryButtonMouse"] = 3] = "PrimaryButtonMouse";
|
8
|
+
PointerDevice[PointerDevice["RightButtonMouse"] = 4] = "RightButtonMouse";
|
9
|
+
PointerDevice[PointerDevice["Other"] = 5] = "Other";
|
9
10
|
})(PointerDevice || (PointerDevice = {}));
|
10
11
|
// Provides a snapshot containing information about a pointer. A Pointer
|
11
12
|
// object is immutable --- it will not be updated when the pointer's information changes.
|
@@ -34,7 +35,7 @@ export default class Pointer {
|
|
34
35
|
var _a, _b;
|
35
36
|
const screenPos = Vec2.of(evt.offsetX, evt.offsetY);
|
36
37
|
const pointerTypeToDevice = {
|
37
|
-
'mouse': PointerDevice.
|
38
|
+
'mouse': PointerDevice.PrimaryButtonMouse,
|
38
39
|
'pen': PointerDevice.Pen,
|
39
40
|
'touch': PointerDevice.Touch,
|
40
41
|
};
|
@@ -45,6 +46,14 @@ export default class Pointer {
|
|
45
46
|
}
|
46
47
|
const timeStamp = (new Date()).getTime();
|
47
48
|
const canvasPos = viewport.roundPoint(viewport.screenToCanvas(screenPos));
|
49
|
+
if (device === PointerDevice.PrimaryButtonMouse) {
|
50
|
+
if (evt.buttons & 0x2) {
|
51
|
+
device = PointerDevice.RightButtonMouse;
|
52
|
+
}
|
53
|
+
else if (!(evt.buttons & 0x1)) {
|
54
|
+
device = PointerDevice.Other;
|
55
|
+
}
|
56
|
+
}
|
48
57
|
return new Pointer(screenPos, canvasPos, (_b = evt.pressure) !== null && _b !== void 0 ? _b : null, evt.isPrimary, isDown, device, evt.pointerId, timeStamp);
|
49
58
|
}
|
50
59
|
// Create a new Pointer from a point on the canvas.
|
package/dist/src/SVGLoader.d.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
import Rect2 from './geometry/Rect2';
|
2
2
|
import { ComponentAddedListener, ImageLoader, OnDetermineExportRectListener, OnProgressListener } from './types';
|
3
3
|
export declare const defaultSVGViewRect: Rect2;
|
4
|
+
export declare const svgAttributesDataKey = "svgAttrs";
|
5
|
+
export declare type SVGLoaderUnknownAttribute = [string, string];
|
4
6
|
export default class SVGLoader implements ImageLoader {
|
5
7
|
private source;
|
6
8
|
private onFinish?;
|
@@ -13,6 +15,7 @@ export default class SVGLoader implements ImageLoader {
|
|
13
15
|
private constructor();
|
14
16
|
private getStyle;
|
15
17
|
private strokeDataFromElem;
|
18
|
+
private attachUnrecognisedAttrs;
|
16
19
|
private addPath;
|
17
20
|
private addUnknownNode;
|
18
21
|
private updateViewBox;
|
package/dist/src/SVGLoader.js
CHANGED
@@ -15,6 +15,8 @@ import Path from './geometry/Path';
|
|
15
15
|
import Rect2 from './geometry/Rect2';
|
16
16
|
// Size of a loaded image if no size is specified.
|
17
17
|
export const defaultSVGViewRect = new Rect2(0, 0, 500, 500);
|
18
|
+
// Key to retrieve unrecognised attributes from an AbstractComponent
|
19
|
+
export const svgAttributesDataKey = 'svgAttrs';
|
18
20
|
export default class SVGLoader {
|
19
21
|
constructor(source, onFinish) {
|
20
22
|
this.source = source;
|
@@ -81,6 +83,14 @@ export default class SVGLoader {
|
|
81
83
|
}
|
82
84
|
return result;
|
83
85
|
}
|
86
|
+
attachUnrecognisedAttrs(elem, node, supportedAttrs) {
|
87
|
+
for (const attr of node.getAttributeNames()) {
|
88
|
+
if (supportedAttrs.has(attr)) {
|
89
|
+
continue;
|
90
|
+
}
|
91
|
+
elem.attachLoadSaveData(svgAttributesDataKey, [attr, node.getAttribute(attr)]);
|
92
|
+
}
|
93
|
+
}
|
84
94
|
// Adds a stroke with a single path
|
85
95
|
addPath(node) {
|
86
96
|
var _a;
|
@@ -88,6 +98,7 @@ export default class SVGLoader {
|
|
88
98
|
try {
|
89
99
|
const strokeData = this.strokeDataFromElem(node);
|
90
100
|
elem = new Stroke(strokeData);
|
101
|
+
this.attachUnrecognisedAttrs(elem, node, new Set(['stroke', 'fill', 'stroke-width', 'd']));
|
91
102
|
}
|
92
103
|
catch (e) {
|
93
104
|
console.error('Invalid path in node', node, '\nError:', e, '\nAdding as an unknown object.');
|
@@ -189,7 +200,6 @@ export default class SVGLoader {
|
|
189
200
|
sandbox.remove();
|
190
201
|
throw new Error('SVG loading iframe is not sandboxed.');
|
191
202
|
}
|
192
|
-
// Try running JavaScript within the iframe
|
193
203
|
const sandboxDoc = (_b = (_a = sandbox.contentWindow) === null || _a === void 0 ? void 0 : _a.document) !== null && _b !== void 0 ? _b : sandbox.contentDocument;
|
194
204
|
if (sandboxDoc == null)
|
195
205
|
throw new Error('Unable to open a sandboxed iframe!');
|
package/dist/src/Viewport.js
CHANGED
@@ -87,6 +87,12 @@ export class Viewport {
|
|
87
87
|
// Returns null if no transformation is necessary
|
88
88
|
zoomTo(toMakeVisible) {
|
89
89
|
let transform = Mat33.identity;
|
90
|
+
if (toMakeVisible.w === 0 || toMakeVisible.h === 0) {
|
91
|
+
throw new Error(`${toMakeVisible.toString()} rectangle is empty! Cannot zoom to!`);
|
92
|
+
}
|
93
|
+
if (isNaN(toMakeVisible.size.magnitude())) {
|
94
|
+
throw new Error(`${toMakeVisible.toString()} rectangle has NaN size! Cannot zoom to!`);
|
95
|
+
}
|
90
96
|
// Try to move the selection within the center 2/3rds of the viewport.
|
91
97
|
const recomputeTargetRect = () => {
|
92
98
|
// transform transforms objects on the canvas. As such, we need to invert it
|
@@ -115,6 +121,10 @@ export class Viewport {
|
|
115
121
|
const viewportContentTransform = visibleRectTransform.inverse();
|
116
122
|
transform = transform.rightMul(viewportContentTransform);
|
117
123
|
}
|
124
|
+
if (!transform.invertable()) {
|
125
|
+
console.warn('Unable to zoom to ', toMakeVisible, '! Computed transform', transform, 'is singular.');
|
126
|
+
transform = Mat33.identity;
|
127
|
+
}
|
118
128
|
return new Viewport.ViewportTransform(transform);
|
119
129
|
}
|
120
130
|
}
|
@@ -4,12 +4,17 @@ import Mat33 from '../geometry/Mat33';
|
|
4
4
|
import Rect2 from '../geometry/Rect2';
|
5
5
|
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
6
6
|
import { ImageComponentLocalization } from './localization';
|
7
|
+
declare type LoadSaveData = unknown;
|
8
|
+
export declare type LoadSaveDataTable = Record<string, Array<LoadSaveData>>;
|
7
9
|
export default abstract class AbstractComponent {
|
8
10
|
protected lastChangedTime: number;
|
9
11
|
protected abstract contentBBox: Rect2;
|
10
12
|
private zIndex;
|
11
13
|
private static zIndexCounter;
|
12
14
|
protected constructor();
|
15
|
+
private loadSaveData;
|
16
|
+
attachLoadSaveData(key: string, data: LoadSaveData): void;
|
17
|
+
getLoadSaveData(): LoadSaveDataTable;
|
13
18
|
getZIndex(): number;
|
14
19
|
getBBox(): Rect2;
|
15
20
|
abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
@@ -18,3 +23,4 @@ export default abstract class AbstractComponent {
|
|
18
23
|
transformBy(affineTransfm: Mat33): Command;
|
19
24
|
abstract description(localizationTable: ImageComponentLocalization): string;
|
20
25
|
}
|
26
|
+
export {};
|
@@ -1,9 +1,20 @@
|
|
1
1
|
import EditorImage from '../EditorImage';
|
2
2
|
export default class AbstractComponent {
|
3
3
|
constructor() {
|
4
|
+
// Get and manage data attached by a loader.
|
5
|
+
this.loadSaveData = {};
|
4
6
|
this.lastChangedTime = (new Date()).getTime();
|
5
7
|
this.zIndex = AbstractComponent.zIndexCounter++;
|
6
8
|
}
|
9
|
+
attachLoadSaveData(key, data) {
|
10
|
+
if (!this.loadSaveData[key]) {
|
11
|
+
this.loadSaveData[key] = [];
|
12
|
+
}
|
13
|
+
this.loadSaveData[key].push(data);
|
14
|
+
}
|
15
|
+
getLoadSaveData() {
|
16
|
+
return this.loadSaveData;
|
17
|
+
}
|
7
18
|
getZIndex() {
|
8
19
|
return this.zIndex;
|
9
20
|
}
|
@@ -41,7 +41,7 @@ export default class Stroke extends AbstractComponent {
|
|
41
41
|
canvas.drawPath(part);
|
42
42
|
}
|
43
43
|
}
|
44
|
-
canvas.endObject();
|
44
|
+
canvas.endObject(this.getLoadSaveData());
|
45
45
|
}
|
46
46
|
// Grows the bounding box for a given stroke part based on that part's style.
|
47
47
|
bboxForPart(origBBox, style) {
|
@@ -275,10 +275,12 @@ export default class Path {
|
|
275
275
|
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
|
276
276
|
// and
|
277
277
|
// https://www.w3.org/TR/SVG2/paths.html
|
278
|
+
var _a;
|
278
279
|
// Remove linebreaks
|
279
280
|
pathString = pathString.split('\n').join(' ');
|
280
281
|
let lastPos = Vec2.zero;
|
281
282
|
let firstPos = null;
|
283
|
+
let startPos = null;
|
282
284
|
let isFirstCommand = true;
|
283
285
|
const commands = [];
|
284
286
|
const moveTo = (point) => {
|
@@ -317,15 +319,61 @@ export default class Path {
|
|
317
319
|
endPoint,
|
318
320
|
});
|
319
321
|
};
|
322
|
+
const commandArgCounts = {
|
323
|
+
'm': 1,
|
324
|
+
'l': 1,
|
325
|
+
'c': 3,
|
326
|
+
'q': 2,
|
327
|
+
'z': 0,
|
328
|
+
'h': 1,
|
329
|
+
'v': 1,
|
330
|
+
};
|
320
331
|
// Each command: Command character followed by anything that isn't a command character
|
321
|
-
const commandExp = /([
|
332
|
+
const commandExp = /([MZLHVCSQTA])\s*([^MZLHVCSQTA]*)/ig;
|
322
333
|
let current;
|
323
334
|
while ((current = commandExp.exec(pathString)) !== null) {
|
324
|
-
const argParts = current[2].trim().split(/[^0-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
335
|
+
const argParts = current[2].trim().split(/[^0-9Ee.-]/).filter(part => part.length > 0).reduce((accumualtor, current) => {
|
336
|
+
// As of 09/2022, iOS Safari doesn't support support lookbehind in regular
|
337
|
+
// expressions. As such, we need an alternative.
|
338
|
+
// Because '-' can be used as a path separator, unless preceeded by an 'e' (as in 1e-5),
|
339
|
+
// we need special cases:
|
340
|
+
current = current.replace(/([^eE])[-]/g, '$1 -');
|
341
|
+
const parts = current.split(' -');
|
342
|
+
if (parts[0] !== '') {
|
343
|
+
accumualtor.push(parts[0]);
|
344
|
+
}
|
345
|
+
accumualtor.push(...parts.slice(1).map(part => `-${part}`));
|
346
|
+
return accumualtor;
|
347
|
+
}, []);
|
348
|
+
let numericArgs = argParts.map(arg => parseFloat(arg));
|
349
|
+
let commandChar = current[1].toLowerCase();
|
350
|
+
let uppercaseCommand = current[1] !== commandChar;
|
351
|
+
// Convert commands that don't take points into commands that do.
|
352
|
+
if (commandChar === 'v' || commandChar === 'h') {
|
353
|
+
numericArgs = numericArgs.reduce((accumulator, current) => {
|
354
|
+
if (commandChar === 'v') {
|
355
|
+
return accumulator.concat(uppercaseCommand ? lastPos.x : 0, current);
|
356
|
+
}
|
357
|
+
else {
|
358
|
+
return accumulator.concat(current, uppercaseCommand ? lastPos.y : 0);
|
359
|
+
}
|
360
|
+
}, []);
|
361
|
+
commandChar = 'l';
|
362
|
+
}
|
363
|
+
else if (commandChar === 'z') {
|
364
|
+
if (firstPos) {
|
365
|
+
numericArgs = [firstPos.x, firstPos.y];
|
366
|
+
firstPos = lastPos;
|
367
|
+
}
|
368
|
+
else {
|
369
|
+
continue;
|
370
|
+
}
|
371
|
+
// 'z' always acts like an uppercase lineTo(startPos)
|
372
|
+
uppercaseCommand = true;
|
373
|
+
commandChar = 'l';
|
374
|
+
}
|
375
|
+
const commandArgCount = (_a = commandArgCounts[commandChar]) !== null && _a !== void 0 ? _a : 0;
|
376
|
+
const allArgs = numericArgs.reduce((accumulator, current, index, parts) => {
|
329
377
|
if (index % 2 !== 0) {
|
330
378
|
const currentAsFloat = current;
|
331
379
|
const prevAsFloat = parts[index - 1];
|
@@ -334,76 +382,59 @@ export default class Path {
|
|
334
382
|
else {
|
335
383
|
return accumulator;
|
336
384
|
}
|
337
|
-
}, []).map((coordinate) => {
|
385
|
+
}, []).map((coordinate, index) => {
|
338
386
|
// Lowercase commands are relative, uppercase commands use absolute
|
339
387
|
// positioning
|
388
|
+
let newPos;
|
340
389
|
if (uppercaseCommand) {
|
341
|
-
|
342
|
-
return coordinate;
|
390
|
+
newPos = coordinate;
|
343
391
|
}
|
344
392
|
else {
|
345
|
-
|
346
|
-
|
393
|
+
newPos = lastPos.plus(coordinate);
|
394
|
+
}
|
395
|
+
if ((index + 1) % commandArgCount === 0) {
|
396
|
+
lastPos = newPos;
|
347
397
|
}
|
398
|
+
return newPos;
|
348
399
|
});
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
case 'l':
|
356
|
-
expectedPointArgCount = 1;
|
357
|
-
lineTo(args[0]);
|
358
|
-
break;
|
359
|
-
case 'z':
|
360
|
-
expectedPointArgCount = 0;
|
361
|
-
// firstPos can be null if the stroke data is just 'z'.
|
362
|
-
if (firstPos) {
|
363
|
-
lineTo(firstPos);
|
364
|
-
}
|
365
|
-
break;
|
366
|
-
case 'c':
|
367
|
-
expectedPointArgCount = 3;
|
368
|
-
cubicBezierTo(args[0], args[1], args[2]);
|
369
|
-
break;
|
370
|
-
case 'q':
|
371
|
-
expectedPointArgCount = 2;
|
372
|
-
quadraticBeierTo(args[0], args[1]);
|
373
|
-
break;
|
374
|
-
// Horizontal line
|
375
|
-
case 'h':
|
376
|
-
expectedPointArgCount = 0;
|
377
|
-
if (uppercaseCommand) {
|
378
|
-
lineTo(Vec2.of(numericArgs[0], lastPos.y));
|
379
|
-
}
|
380
|
-
else {
|
381
|
-
lineTo(lastPos.plus(Vec2.of(numericArgs[0], 0)));
|
382
|
-
}
|
383
|
-
break;
|
384
|
-
// Vertical line
|
385
|
-
case 'v':
|
386
|
-
expectedPointArgCount = 0;
|
387
|
-
if (uppercaseCommand) {
|
388
|
-
lineTo(Vec2.of(lastPos.x, numericArgs[1]));
|
389
|
-
}
|
390
|
-
else {
|
391
|
-
lineTo(lastPos.plus(Vec2.of(0, numericArgs[1])));
|
392
|
-
}
|
393
|
-
break;
|
394
|
-
default:
|
395
|
-
throw new Error(`Unknown path command ${commandChar}`);
|
400
|
+
if (allArgs.length % commandArgCount !== 0) {
|
401
|
+
throw new Error([
|
402
|
+
`Incorrect number of arguments: got ${JSON.stringify(allArgs)} with a length of ${allArgs.length} ≠ ${commandArgCount}k, k ∈ ℤ.`,
|
403
|
+
`The number of arguments to ${commandChar} must be a multiple of ${commandArgCount}!`,
|
404
|
+
`Command: ${current[0]}`,
|
405
|
+
].join('\n'));
|
396
406
|
}
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
407
|
+
for (let argPos = 0; argPos < allArgs.length; argPos += commandArgCount) {
|
408
|
+
const args = allArgs.slice(argPos, argPos + commandArgCount);
|
409
|
+
switch (commandChar.toLowerCase()) {
|
410
|
+
case 'm':
|
411
|
+
if (argPos === 0) {
|
412
|
+
moveTo(args[0]);
|
413
|
+
}
|
414
|
+
else {
|
415
|
+
lineTo(args[0]);
|
416
|
+
}
|
417
|
+
break;
|
418
|
+
case 'l':
|
419
|
+
lineTo(args[0]);
|
420
|
+
break;
|
421
|
+
case 'c':
|
422
|
+
cubicBezierTo(args[0], args[1], args[2]);
|
423
|
+
break;
|
424
|
+
case 'q':
|
425
|
+
quadraticBeierTo(args[0], args[1]);
|
426
|
+
break;
|
427
|
+
default:
|
428
|
+
throw new Error(`Unknown path command ${commandChar}`);
|
429
|
+
}
|
430
|
+
isFirstCommand = false;
|
401
431
|
}
|
402
|
-
if (
|
403
|
-
firstPos !== null && firstPos !== void 0 ? firstPos : (firstPos =
|
432
|
+
if (allArgs.length > 0) {
|
433
|
+
firstPos !== null && firstPos !== void 0 ? firstPos : (firstPos = allArgs[0]);
|
434
|
+
startPos !== null && startPos !== void 0 ? startPos : (startPos = firstPos);
|
435
|
+
lastPos = allArgs[allArgs.length - 1];
|
404
436
|
}
|
405
|
-
isFirstCommand = false;
|
406
437
|
}
|
407
|
-
return new Path(
|
438
|
+
return new Path(startPos !== null && startPos !== void 0 ? startPos : Vec2.zero, commands);
|
408
439
|
}
|
409
440
|
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
|
+
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
2
3
|
import Mat33 from '../../geometry/Mat33';
|
3
4
|
import { PathCommand } from '../../geometry/Path';
|
4
5
|
import Rect2 from '../../geometry/Rect2';
|
@@ -37,7 +38,7 @@ export default abstract class AbstractRenderer {
|
|
37
38
|
drawPath(path: RenderablePathSpec): void;
|
38
39
|
drawRect(rect: Rect2, lineWidth: number, lineFill: RenderingStyle): void;
|
39
40
|
startObject(_boundingBox: Rect2, _clip?: boolean): void;
|
40
|
-
endObject(): void;
|
41
|
+
endObject(_loaderData?: LoadSaveDataTable): void;
|
41
42
|
protected getNestingLevel(): number;
|
42
43
|
abstract drawPoints(...points: Point2[]): void;
|
43
44
|
canRenderFromWithoutDataLoss(_other: AbstractRenderer): boolean;
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
1
2
|
import Rect2 from '../../geometry/Rect2';
|
2
3
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
3
4
|
import Viewport from '../../Viewport';
|
@@ -9,7 +10,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
9
10
|
private lastPathStyle;
|
10
11
|
private lastPath;
|
11
12
|
private lastPathStart;
|
12
|
-
private
|
13
|
+
private objectElems;
|
13
14
|
private overwrittenAttrs;
|
14
15
|
constructor(elem: SVGSVGElement, viewport: Viewport);
|
15
16
|
setRootSVGAttribute(name: string, value: string | null): void;
|
@@ -19,7 +20,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
19
20
|
protected endPath(style: RenderingStyle): void;
|
20
21
|
private addPathToSVG;
|
21
22
|
startObject(boundingBox: Rect2): void;
|
22
|
-
endObject(): void;
|
23
|
+
endObject(loaderData?: LoadSaveDataTable): void;
|
23
24
|
protected lineTo(point: Point2): void;
|
24
25
|
protected moveTo(point: Point2): void;
|
25
26
|
protected traceCubicBezierCurve(controlPoint1: Point2, controlPoint2: Point2, endPoint: Point2): void;
|
@@ -1,11 +1,13 @@
|
|
1
1
|
import Path, { PathCommandType } from '../../geometry/Path';
|
2
2
|
import { Vec2 } from '../../geometry/Vec2';
|
3
|
+
import { svgAttributesDataKey } from '../../SVGLoader';
|
3
4
|
import AbstractRenderer from './AbstractRenderer';
|
4
5
|
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
5
6
|
export default class SVGRenderer extends AbstractRenderer {
|
6
7
|
constructor(elem, viewport) {
|
7
8
|
super(viewport);
|
8
9
|
this.elem = elem;
|
10
|
+
this.objectElems = null;
|
9
11
|
this.overwrittenAttrs = {};
|
10
12
|
this.clear();
|
11
13
|
}
|
@@ -26,7 +28,6 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
26
28
|
return Vec2.of(this.elem.clientWidth, this.elem.clientHeight);
|
27
29
|
}
|
28
30
|
clear() {
|
29
|
-
this.mainGroup = document.createElementNS(svgNameSpace, 'g');
|
30
31
|
// Restore all alltributes
|
31
32
|
for (const attrName in this.overwrittenAttrs) {
|
32
33
|
const value = this.overwrittenAttrs[attrName];
|
@@ -38,8 +39,6 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
38
39
|
}
|
39
40
|
}
|
40
41
|
this.overwrittenAttrs = {};
|
41
|
-
// Remove all children
|
42
|
-
this.elem.replaceChildren(this.mainGroup);
|
43
42
|
}
|
44
43
|
beginPath(startPoint) {
|
45
44
|
var _a;
|
@@ -72,6 +71,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
72
71
|
}
|
73
72
|
// Push [this.fullPath] to the SVG
|
74
73
|
addPathToSVG() {
|
74
|
+
var _a;
|
75
75
|
if (!this.lastPathStyle || !this.lastPath) {
|
76
76
|
return;
|
77
77
|
}
|
@@ -83,7 +83,8 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
83
83
|
pathElem.setAttribute('stroke', style.stroke.color.toHexString());
|
84
84
|
pathElem.setAttribute('stroke-width', style.stroke.width.toString());
|
85
85
|
}
|
86
|
-
this.
|
86
|
+
this.elem.appendChild(pathElem);
|
87
|
+
(_a = this.objectElems) === null || _a === void 0 ? void 0 : _a.push(pathElem);
|
87
88
|
}
|
88
89
|
startObject(boundingBox) {
|
89
90
|
super.startObject(boundingBox);
|
@@ -91,11 +92,24 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
91
92
|
this.lastPath = null;
|
92
93
|
this.lastPathStart = null;
|
93
94
|
this.lastPathStyle = null;
|
95
|
+
this.objectElems = [];
|
94
96
|
}
|
95
|
-
endObject() {
|
96
|
-
|
97
|
+
endObject(loaderData) {
|
98
|
+
var _a;
|
99
|
+
super.endObject(loaderData);
|
97
100
|
// Don't extend paths across objects
|
98
101
|
this.addPathToSVG();
|
102
|
+
if (loaderData) {
|
103
|
+
// Restore any attributes unsupported by the app.
|
104
|
+
for (const elem of (_a = this.objectElems) !== null && _a !== void 0 ? _a : []) {
|
105
|
+
const attrs = loaderData[svgAttributesDataKey];
|
106
|
+
if (attrs) {
|
107
|
+
for (const [attr, value] of attrs) {
|
108
|
+
elem.setAttribute(attr, value);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
}
|
112
|
+
}
|
99
113
|
}
|
100
114
|
lineTo(point) {
|
101
115
|
point = this.canvasToScreen(point);
|
@@ -137,7 +151,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
137
151
|
elem.setAttribute('cx', `${point.x}`);
|
138
152
|
elem.setAttribute('cy', `${point.y}`);
|
139
153
|
elem.setAttribute('r', '15');
|
140
|
-
this.
|
154
|
+
this.elem.appendChild(elem);
|
141
155
|
});
|
142
156
|
}
|
143
157
|
// Renders a **copy** of the given element.
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import Editor from '../Editor';
|
2
2
|
import { ToolbarLocalization } from './localization';
|
3
|
+
import { ActionButtonIcon } from './types';
|
3
4
|
export default class HTMLToolbar {
|
4
5
|
private editor;
|
5
6
|
private localizationTable;
|
@@ -7,7 +8,7 @@ export default class HTMLToolbar {
|
|
7
8
|
private penTypes;
|
8
9
|
constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
|
9
10
|
setupColorPickers(): void;
|
10
|
-
addActionButton(
|
11
|
+
addActionButton(title: string | ActionButtonIcon, command: () => void, parent?: Element): HTMLButtonElement;
|
11
12
|
private addUndoRedoButtons;
|
12
13
|
addDefaultToolWidgets(): void;
|
13
14
|
addDefaultActionButtons(): void;
|