js-draw 0.0.4 → 0.0.7
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 +14 -0
- package/README.md +108 -0
- package/build_tools/BundledFile.ts +167 -0
- package/build_tools/bundle.ts +11 -0
- package/dist/build_tools/BundledFile.d.ts +13 -0
- package/dist/build_tools/BundledFile.js +157 -0
- package/dist/build_tools/bundle.d.ts +1 -0
- package/dist/build_tools/bundle.js +5 -0
- package/dist/bundle.js +1 -0
- package/dist/src/Display.js +4 -1
- package/dist/src/Editor.d.ts +8 -1
- package/dist/src/Editor.js +30 -5
- package/dist/src/EditorImage.d.ts +2 -2
- package/dist/src/SVGLoader.d.ts +2 -0
- package/dist/src/SVGLoader.js +15 -1
- package/dist/src/Viewport.d.ts +1 -1
- package/dist/src/bundle/bundled.d.ts +4 -0
- package/dist/src/bundle/bundled.js +5 -0
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +15 -0
- package/dist/src/components/SVGGlobalAttributesObject.js +29 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +15 -3
- package/dist/src/geometry/Path.js +11 -0
- package/dist/src/localization.d.ts +1 -1
- package/dist/src/localization.js +2 -2
- package/dist/src/rendering/SVGRenderer.d.ts +2 -0
- package/dist/src/rendering/SVGRenderer.js +25 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +1 -2
- package/dist/src/toolbar/HTMLToolbar.js +2 -20
- package/dist/src/toolbar/localization.d.ts +20 -0
- package/dist/src/toolbar/localization.js +19 -0
- package/dist/src/toolbar/types.d.ts +0 -19
- package/dist/src/tools/SelectionTool.js +1 -1
- package/dist-test/test-dist-bundle.html +35 -0
- package/package.json +12 -3
- package/src/Display.ts +3 -1
- package/src/Editor.css +0 -1
- package/src/Editor.ts +54 -11
- package/src/EditorImage.test.ts +5 -3
- package/src/SVGLoader.ts +18 -1
- package/src/bundle/bundled.ts +7 -0
- package/src/components/SVGGlobalAttributesObject.ts +39 -0
- package/src/components/builders/FreehandLineBuilder.ts +23 -4
- package/src/geometry/Path.fromString.test.ts +11 -24
- package/src/geometry/Path.ts +13 -0
- package/src/localization.ts +2 -3
- package/src/rendering/SVGRenderer.ts +27 -0
- package/src/toolbar/HTMLToolbar.ts +2 -24
- package/src/toolbar/localization.ts +44 -0
- package/src/toolbar/types.ts +0 -22
- package/src/tools/SelectionTool.test.ts +1 -1
- package/src/tools/SelectionTool.ts +1 -1
@@ -183,7 +183,6 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
183
183
|
|
184
184
|
// Approximate the normal at the location of the control point
|
185
185
|
let projectionT = this.currentCurve.project(controlPoint.xy).t;
|
186
|
-
|
187
186
|
if (!projectionT) {
|
188
187
|
if (startPt.minus(controlPoint).magnitudeSquared() < endPt.minus(controlPoint).magnitudeSquared()) {
|
189
188
|
projectionT = 0.1;
|
@@ -192,11 +191,31 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
192
191
|
}
|
193
192
|
}
|
194
193
|
|
195
|
-
const
|
194
|
+
const halfVecT = projectionT;
|
195
|
+
let halfVec = Vec2.ofXY(this.currentCurve.normal(halfVecT))
|
196
196
|
.normalized().times(
|
197
|
-
this.curveStartWidth / 2 *
|
198
|
-
+ this.curveEndWidth / 2 * (1 -
|
197
|
+
this.curveStartWidth / 2 * halfVecT
|
198
|
+
+ this.curveEndWidth / 2 * (1 - halfVecT)
|
199
|
+
);
|
200
|
+
|
201
|
+
// Computes a boundary curve. [direction] should be either +1 or -1 (determines the side
|
202
|
+
// of the center curve to place the boundary).
|
203
|
+
const computeBoundaryCurve = (direction: number, halfVec: Vec2) => {
|
204
|
+
return new Bezier(
|
205
|
+
startPt.plus(startVec.times(direction)),
|
206
|
+
controlPoint.plus(halfVec.times(direction)),
|
207
|
+
endPt.plus(endVec.times(direction)),
|
199
208
|
);
|
209
|
+
};
|
210
|
+
|
211
|
+
const upperBoundary = computeBoundaryCurve(1, halfVec);
|
212
|
+
const lowerBoundary = computeBoundaryCurve(-1, halfVec);
|
213
|
+
|
214
|
+
// If the boundaries have two intersections, increasing the half vector's length could fix this.
|
215
|
+
if (upperBoundary.intersects(lowerBoundary).length === 2) {
|
216
|
+
halfVec = halfVec.times(2);
|
217
|
+
}
|
218
|
+
|
200
219
|
|
201
220
|
const pathCommands: PathCommand[] = [
|
202
221
|
{
|
@@ -14,17 +14,13 @@ describe('Path.fromString', () => {
|
|
14
14
|
const path2 = Path.fromString('M 0 0');
|
15
15
|
const path3 = Path.fromString('M 1,1M 2,2 M 3,3');
|
16
16
|
|
17
|
-
expect(path1.parts
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
expect(path2.
|
17
|
+
expect(path1.parts.length).toBe(0);
|
18
|
+
expect(path1.startPoint).toMatchObject(Vec2.zero);
|
19
|
+
|
20
|
+
expect(path2.parts.length).toBe(0);
|
21
|
+
expect(path2.startPoint).toMatchObject(Vec2.zero);
|
22
22
|
|
23
23
|
expect(path3.parts).toMatchObject([
|
24
|
-
{
|
25
|
-
kind: PathCommandType.MoveTo,
|
26
|
-
point: Vec2.of(1, 1),
|
27
|
-
},
|
28
24
|
{
|
29
25
|
kind: PathCommandType.MoveTo,
|
30
26
|
point: Vec2.of(2, 2),
|
@@ -44,27 +40,22 @@ describe('Path.fromString', () => {
|
|
44
40
|
kind: PathCommandType.MoveTo,
|
45
41
|
point: Vec2.of(1, 1),
|
46
42
|
},
|
47
|
-
{
|
48
|
-
kind: PathCommandType.MoveTo,
|
49
|
-
point: Vec2.of(1, 1),
|
50
|
-
},
|
51
43
|
{
|
52
44
|
kind: PathCommandType.MoveTo,
|
53
45
|
point: Vec2.of(4, 4),
|
54
46
|
},
|
55
47
|
]);
|
48
|
+
expect(path.startPoint).toMatchObject(Vec2.of(1, 1));
|
56
49
|
});
|
57
50
|
|
58
51
|
it('should handle lineTo commands', () => {
|
59
52
|
const path = Path.fromString('l1,2L-1,0l0.1,-1.0');
|
53
|
+
// l is a relative lineTo, but because there
|
54
|
+
// is no previous command, it should act like an
|
55
|
+
// absolute moveTo.
|
56
|
+
expect(path.startPoint).toMatchObject(Vec2.of(1, 2));
|
57
|
+
|
60
58
|
expect(path.parts).toMatchObject([
|
61
|
-
{
|
62
|
-
kind: PathCommandType.LineTo,
|
63
|
-
// l is a relative lineTo, but because there
|
64
|
-
// is no previous command, it should act like an
|
65
|
-
// absolute moveTo.
|
66
|
-
point: Vec2.of(1, 2),
|
67
|
-
},
|
68
59
|
{
|
69
60
|
kind: PathCommandType.LineTo,
|
70
61
|
point: Vec2.of(-1, 0),
|
@@ -84,10 +75,6 @@ describe('Path.fromString', () => {
|
|
84
75
|
expect(path2.startPoint).toMatchObject(path1.startPoint);
|
85
76
|
expect(path1.parts).toMatchObject(path2.parts);
|
86
77
|
expect(path1.parts).toMatchObject([
|
87
|
-
{
|
88
|
-
kind: PathCommandType.MoveTo,
|
89
|
-
point: Vec2.of(3, 3),
|
90
|
-
},
|
91
78
|
{
|
92
79
|
kind: PathCommandType.LineTo,
|
93
80
|
point: Vec2.of(4, 5),
|
package/src/geometry/Path.ts
CHANGED
@@ -367,16 +367,28 @@ export default class Path {
|
|
367
367
|
|
368
368
|
let lastPos: Point2 = Vec2.zero;
|
369
369
|
let firstPos: Point2|null = null;
|
370
|
+
let isFirstCommand: boolean = true;
|
370
371
|
const commands: PathCommand[] = [];
|
371
372
|
|
372
373
|
|
373
374
|
const moveTo = (point: Point2) => {
|
375
|
+
// The first moveTo/lineTo is already handled by the [startPoint] parameter of the Path constructor.
|
376
|
+
if (isFirstCommand) {
|
377
|
+
isFirstCommand = false;
|
378
|
+
return;
|
379
|
+
}
|
380
|
+
|
374
381
|
commands.push({
|
375
382
|
kind: PathCommandType.MoveTo,
|
376
383
|
point,
|
377
384
|
});
|
378
385
|
};
|
379
386
|
const lineTo = (point: Point2) => {
|
387
|
+
if (isFirstCommand) {
|
388
|
+
isFirstCommand = false;
|
389
|
+
return;
|
390
|
+
}
|
391
|
+
|
380
392
|
commands.push({
|
381
393
|
kind: PathCommandType.LineTo,
|
382
394
|
point,
|
@@ -492,6 +504,7 @@ export default class Path {
|
|
492
504
|
if (args.length > 0) {
|
493
505
|
firstPos ??= args[0];
|
494
506
|
}
|
507
|
+
isFirstCommand = false;
|
495
508
|
}
|
496
509
|
|
497
510
|
return new Path(firstPos ?? Vec2.zero, commands);
|
package/src/localization.ts
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import { CommandLocalization, defaultCommandLocalization } from './commands/localization';
|
2
2
|
import { defaultComponentLocalization, ImageComponentLocalization } from './components/localization';
|
3
|
-
import
|
4
|
-
import { ToolbarLocalization } from './toolbar/types';
|
3
|
+
import { defaultToolbarLocalization, ToolbarLocalization } from './toolbar/localization';
|
5
4
|
import { defaultToolLocalization, ToolLocalization } from './tools/localization';
|
6
5
|
|
7
6
|
|
@@ -14,7 +13,7 @@ export interface EditorLocalization extends ToolbarLocalization, ToolLocalizatio
|
|
14
13
|
}
|
15
14
|
|
16
15
|
export const defaultEditorLocalization: EditorLocalization = {
|
17
|
-
...
|
16
|
+
...defaultToolbarLocalization,
|
18
17
|
...defaultToolLocalization,
|
19
18
|
...defaultCommandLocalization,
|
20
19
|
...defaultComponentLocalization,
|
@@ -15,12 +15,27 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
15
15
|
private lastPathStart: Point2|null;
|
16
16
|
|
17
17
|
private mainGroup: SVGGElement;
|
18
|
+
private overwrittenAttrs: Record<string, string|null> = {};
|
18
19
|
|
19
20
|
public constructor(private elem: SVGSVGElement, viewport: Viewport) {
|
20
21
|
super(viewport);
|
21
22
|
this.clear();
|
22
23
|
}
|
23
24
|
|
25
|
+
// Sets an attribute on the root SVG element.
|
26
|
+
public setRootSVGAttribute(name: string, value: string|null) {
|
27
|
+
// Make the original value of the attribute restorable on clear
|
28
|
+
if (!(name in this.overwrittenAttrs)) {
|
29
|
+
this.overwrittenAttrs[name] = this.elem.getAttribute(name);
|
30
|
+
}
|
31
|
+
|
32
|
+
if (value !== null) {
|
33
|
+
this.elem.setAttribute(name, value);
|
34
|
+
} else {
|
35
|
+
this.elem.removeAttribute(name);
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
24
39
|
public displaySize(): Vec2 {
|
25
40
|
return Vec2.of(this.elem.clientWidth, this.elem.clientHeight);
|
26
41
|
}
|
@@ -28,6 +43,18 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
28
43
|
public clear() {
|
29
44
|
this.mainGroup = document.createElementNS(svgNameSpace, 'g');
|
30
45
|
|
46
|
+
// Restore all alltributes
|
47
|
+
for (const attrName in this.overwrittenAttrs) {
|
48
|
+
const value = this.overwrittenAttrs[attrName];
|
49
|
+
|
50
|
+
if (value) {
|
51
|
+
this.elem.setAttribute(attrName, value);
|
52
|
+
} else {
|
53
|
+
this.elem.removeAttribute(attrName);
|
54
|
+
}
|
55
|
+
}
|
56
|
+
this.overwrittenAttrs = {};
|
57
|
+
|
31
58
|
// Remove all children
|
32
59
|
this.elem.replaceChildren(this.mainGroup);
|
33
60
|
}
|
@@ -8,7 +8,6 @@ import Pen from '../tools/Pen';
|
|
8
8
|
import Eraser from '../tools/Eraser';
|
9
9
|
import BaseTool from '../tools/BaseTool';
|
10
10
|
import SelectionTool from '../tools/SelectionTool';
|
11
|
-
import { ToolbarLocalization } from './types';
|
12
11
|
import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuilder';
|
13
12
|
import { Vec2 } from '../geometry/Vec2';
|
14
13
|
import SVGRenderer from '../rendering/SVGRenderer';
|
@@ -18,6 +17,7 @@ import { ComponentBuilderFactory } from '../components/builders/types';
|
|
18
17
|
import { makeArrowBuilder } from '../components/builders/ArrowBuilder';
|
19
18
|
import { makeLineBuilder } from '../components/builders/LineBuilder';
|
20
19
|
import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../components/builders/RectangleBuilder';
|
20
|
+
import { defaultToolbarLocalization, ToolbarLocalization } from './localization';
|
21
21
|
|
22
22
|
const primaryForegroundFill = `
|
23
23
|
style='fill: var(--primary-foreground-color);'
|
@@ -533,31 +533,9 @@ export default class HTMLToolbar {
|
|
533
533
|
private container: HTMLElement;
|
534
534
|
private penTypes: PenTypeRecord[];
|
535
535
|
|
536
|
-
public static defaultLocalization: ToolbarLocalization = {
|
537
|
-
pen: 'Pen',
|
538
|
-
eraser: 'Eraser',
|
539
|
-
select: 'Select',
|
540
|
-
touchDrawing: 'Touch Drawing',
|
541
|
-
thicknessLabel: 'Thickness: ',
|
542
|
-
colorLabel: 'Color: ',
|
543
|
-
resizeImageToSelection: 'Resize image to selection',
|
544
|
-
undo: 'Undo',
|
545
|
-
redo: 'Redo',
|
546
|
-
selectObjectType: 'Object type: ',
|
547
|
-
|
548
|
-
freehandPen: 'Freehand',
|
549
|
-
arrowPen: 'Arrow',
|
550
|
-
linePen: 'Line',
|
551
|
-
outlinedRectanglePen: 'Outlined rectangle',
|
552
|
-
filledRectanglePen: 'Filled rectangle',
|
553
|
-
|
554
|
-
dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
|
555
|
-
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
556
|
-
};
|
557
|
-
|
558
536
|
public constructor(
|
559
537
|
private editor: Editor, parent: HTMLElement,
|
560
|
-
private localizationTable: ToolbarLocalization =
|
538
|
+
private localizationTable: ToolbarLocalization = defaultToolbarLocalization,
|
561
539
|
) {
|
562
540
|
this.container = document.createElement('div');
|
563
541
|
this.container.classList.add(`${toolbarCSSPrefix}root`);
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
export interface ToolbarLocalization {
|
4
|
+
outlinedRectanglePen: string;
|
5
|
+
filledRectanglePen: string;
|
6
|
+
linePen: string;
|
7
|
+
arrowPen: string;
|
8
|
+
freehandPen: string;
|
9
|
+
selectObjectType: string;
|
10
|
+
colorLabel: string;
|
11
|
+
pen: string;
|
12
|
+
eraser: string;
|
13
|
+
select: string;
|
14
|
+
touchDrawing: string;
|
15
|
+
thicknessLabel: string;
|
16
|
+
resizeImageToSelection: string;
|
17
|
+
undo: string;
|
18
|
+
redo: string;
|
19
|
+
|
20
|
+
dropdownShown: (toolName: string)=>string;
|
21
|
+
dropdownHidden: (toolName: string)=>string;
|
22
|
+
}
|
23
|
+
|
24
|
+
export const defaultToolbarLocalization: ToolbarLocalization = {
|
25
|
+
pen: 'Pen',
|
26
|
+
eraser: 'Eraser',
|
27
|
+
select: 'Select',
|
28
|
+
touchDrawing: 'Touch Drawing',
|
29
|
+
thicknessLabel: 'Thickness: ',
|
30
|
+
colorLabel: 'Color: ',
|
31
|
+
resizeImageToSelection: 'Resize image to selection',
|
32
|
+
undo: 'Undo',
|
33
|
+
redo: 'Redo',
|
34
|
+
selectObjectType: 'Object type: ',
|
35
|
+
|
36
|
+
freehandPen: 'Freehand',
|
37
|
+
arrowPen: 'Arrow',
|
38
|
+
linePen: 'Line',
|
39
|
+
outlinedRectanglePen: 'Outlined rectangle',
|
40
|
+
filledRectanglePen: 'Filled rectangle',
|
41
|
+
|
42
|
+
dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
|
43
|
+
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
44
|
+
};
|
package/src/toolbar/types.ts
CHANGED
@@ -2,25 +2,3 @@ export enum ToolbarButtonType {
|
|
2
2
|
ToggleButton,
|
3
3
|
ActionButton,
|
4
4
|
}
|
5
|
-
|
6
|
-
|
7
|
-
export interface ToolbarLocalization {
|
8
|
-
outlinedRectanglePen: string;
|
9
|
-
filledRectanglePen: string;
|
10
|
-
linePen: string;
|
11
|
-
arrowPen: string;
|
12
|
-
freehandPen: string;
|
13
|
-
selectObjectType: string;
|
14
|
-
colorLabel: string;
|
15
|
-
pen: string;
|
16
|
-
eraser: string;
|
17
|
-
select: string;
|
18
|
-
touchDrawing: string;
|
19
|
-
thicknessLabel: string;
|
20
|
-
resizeImageToSelection: string;
|
21
|
-
undo: string;
|
22
|
-
redo: string;
|
23
|
-
|
24
|
-
dropdownShown: (toolName: string)=>string;
|
25
|
-
dropdownHidden: (toolName: string)=>string;
|
26
|
-
}
|
@@ -15,7 +15,7 @@ const getSelectionTool = (editor: Editor): SelectionTool => {
|
|
15
15
|
return editor.toolController.getMatchingTools(ToolType.Selection)[0] as SelectionTool;
|
16
16
|
};
|
17
17
|
|
18
|
-
const createEditor = () => new Editor(document.body, RenderingMode.DummyRenderer);
|
18
|
+
const createEditor = () => new Editor(document.body, { renderingMode: RenderingMode.DummyRenderer });
|
19
19
|
|
20
20
|
const createSquareStroke = () => {
|
21
21
|
const testStroke = new Stroke([
|