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.
Files changed (51) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +108 -0
  3. package/build_tools/BundledFile.ts +167 -0
  4. package/build_tools/bundle.ts +11 -0
  5. package/dist/build_tools/BundledFile.d.ts +13 -0
  6. package/dist/build_tools/BundledFile.js +157 -0
  7. package/dist/build_tools/bundle.d.ts +1 -0
  8. package/dist/build_tools/bundle.js +5 -0
  9. package/dist/bundle.js +1 -0
  10. package/dist/src/Display.js +4 -1
  11. package/dist/src/Editor.d.ts +8 -1
  12. package/dist/src/Editor.js +30 -5
  13. package/dist/src/EditorImage.d.ts +2 -2
  14. package/dist/src/SVGLoader.d.ts +2 -0
  15. package/dist/src/SVGLoader.js +15 -1
  16. package/dist/src/Viewport.d.ts +1 -1
  17. package/dist/src/bundle/bundled.d.ts +4 -0
  18. package/dist/src/bundle/bundled.js +5 -0
  19. package/dist/src/components/SVGGlobalAttributesObject.d.ts +15 -0
  20. package/dist/src/components/SVGGlobalAttributesObject.js +29 -0
  21. package/dist/src/components/builders/FreehandLineBuilder.js +15 -3
  22. package/dist/src/geometry/Path.js +11 -0
  23. package/dist/src/localization.d.ts +1 -1
  24. package/dist/src/localization.js +2 -2
  25. package/dist/src/rendering/SVGRenderer.d.ts +2 -0
  26. package/dist/src/rendering/SVGRenderer.js +25 -0
  27. package/dist/src/toolbar/HTMLToolbar.d.ts +1 -2
  28. package/dist/src/toolbar/HTMLToolbar.js +2 -20
  29. package/dist/src/toolbar/localization.d.ts +20 -0
  30. package/dist/src/toolbar/localization.js +19 -0
  31. package/dist/src/toolbar/types.d.ts +0 -19
  32. package/dist/src/tools/SelectionTool.js +1 -1
  33. package/dist-test/test-dist-bundle.html +35 -0
  34. package/package.json +12 -3
  35. package/src/Display.ts +3 -1
  36. package/src/Editor.css +0 -1
  37. package/src/Editor.ts +54 -11
  38. package/src/EditorImage.test.ts +5 -3
  39. package/src/SVGLoader.ts +18 -1
  40. package/src/bundle/bundled.ts +7 -0
  41. package/src/components/SVGGlobalAttributesObject.ts +39 -0
  42. package/src/components/builders/FreehandLineBuilder.ts +23 -4
  43. package/src/geometry/Path.fromString.test.ts +11 -24
  44. package/src/geometry/Path.ts +13 -0
  45. package/src/localization.ts +2 -3
  46. package/src/rendering/SVGRenderer.ts +27 -0
  47. package/src/toolbar/HTMLToolbar.ts +2 -24
  48. package/src/toolbar/localization.ts +44 -0
  49. package/src/toolbar/types.ts +0 -22
  50. package/src/tools/SelectionTool.test.ts +1 -1
  51. 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 halfVec = Vec2.ofXY(this.currentCurve.normal(projectionT))
194
+ const halfVecT = projectionT;
195
+ let halfVec = Vec2.ofXY(this.currentCurve.normal(halfVecT))
196
196
  .normalized().times(
197
- this.curveStartWidth / 2 * projectionT
198
- + this.curveEndWidth / 2 * (1 - projectionT)
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[0]).toMatchObject({
18
- kind: PathCommandType.MoveTo,
19
- point: Vec2.zero,
20
- });
21
- expect(path2.parts).toMatchObject(path1.parts);
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),
@@ -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);
@@ -1,7 +1,6 @@
1
1
  import { CommandLocalization, defaultCommandLocalization } from './commands/localization';
2
2
  import { defaultComponentLocalization, ImageComponentLocalization } from './components/localization';
3
- import HTMLToolbar from './toolbar/HTMLToolbar';
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
- ...HTMLToolbar.defaultLocalization,
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 = HTMLToolbar.defaultLocalization,
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
+ };
@@ -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([
@@ -16,7 +16,7 @@ const styles = `
16
16
  }
17
17
 
18
18
  .handleOverlay > .selectionBox {
19
- position: fixed;
19
+ position: absolute;
20
20
  z-index: 0;
21
21
  transform-origin: center;
22
22
  }