js-draw 0.17.3 → 0.17.4

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 (47) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/README.md +17 -8
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/Editor.d.ts +57 -1
  5. package/dist/src/Editor.js +61 -24
  6. package/dist/src/EditorImage.d.ts +4 -2
  7. package/dist/src/EditorImage.js +4 -2
  8. package/dist/src/SVGLoader.d.ts +4 -0
  9. package/dist/src/SVGLoader.js +4 -0
  10. package/dist/src/lib.d.ts +2 -1
  11. package/dist/src/lib.js +2 -1
  12. package/dist/src/rendering/lib.d.ts +2 -0
  13. package/dist/src/rendering/lib.js +2 -0
  14. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +25 -0
  15. package/dist/src/rendering/renderers/CanvasRenderer.js +27 -0
  16. package/dist/src/rendering/renderers/SVGRenderer.d.ts +15 -0
  17. package/dist/src/rendering/renderers/SVGRenderer.js +27 -1
  18. package/dist/src/testing/lib.d.ts +2 -0
  19. package/dist/src/testing/lib.js +2 -0
  20. package/dist/src/testing/sendPenEvent.d.ts +12 -0
  21. package/dist/src/testing/sendPenEvent.js +19 -0
  22. package/dist/src/testing/sendTouchEvent.d.ts +36 -0
  23. package/dist/src/testing/sendTouchEvent.js +36 -0
  24. package/dist/src/toolbar/widgets/DocumentPropertiesWidget.js +4 -2
  25. package/dist/src/toolbar/widgets/PenToolWidget.js +1 -0
  26. package/dist/src/toolbar/widgets/TextToolWidget.js +4 -1
  27. package/dist/src/tools/SelectionTool/SelectionTool.js +5 -6
  28. package/package.json +1 -1
  29. package/src/Editor.ts +62 -28
  30. package/src/EditorImage.ts +4 -2
  31. package/src/SVGLoader.ts +4 -0
  32. package/src/lib.ts +2 -1
  33. package/src/rendering/lib.ts +2 -0
  34. package/src/rendering/renderers/CanvasRenderer.ts +27 -0
  35. package/src/rendering/renderers/SVGRenderer.ts +32 -1
  36. package/src/testing/lib.ts +3 -0
  37. package/src/testing/sendPenEvent.ts +31 -0
  38. package/src/testing/sendTouchEvent.ts +36 -1
  39. package/src/toolbar/toolbar.css +5 -0
  40. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +4 -2
  41. package/src/toolbar/widgets/PenToolWidget.ts +1 -0
  42. package/src/toolbar/widgets/TextToolWidget.ts +4 -1
  43. package/src/tools/Eraser.test.ts +11 -10
  44. package/src/tools/PanZoom.test.ts +1 -1
  45. package/src/tools/Pen.test.ts +63 -62
  46. package/src/tools/SelectionTool/SelectionTool.test.ts +15 -14
  47. package/src/tools/SelectionTool/SelectionTool.ts +5 -7
@@ -46,6 +46,9 @@ export interface EditorSettings {
46
46
  * // Do something with saveData...
47
47
  * });
48
48
  * ```
49
+ *
50
+ * See also
51
+ * [`docs/example/example.ts`](https://github.com/personalizedrefrigerator/js-draw/blob/main/docs/example/example.ts#L15).
49
52
  */
50
53
  export declare class Editor {
51
54
  private container;
@@ -123,6 +126,8 @@ export declare class Editor {
123
126
  *
124
127
  * // Add the default toolbar
125
128
  * const toolbar = editor.addToolbar();
129
+ *
130
+ * // Add a save button
126
131
  * toolbar.addActionButton({
127
132
  * label: 'Save'
128
133
  * icon: createSaveIcon(),
@@ -138,6 +143,7 @@ export declare class Editor {
138
143
  *
139
144
  * @example
140
145
  * ```
146
+ * // Set the editor's height to 500px
141
147
  * editor.getRootElement().style.height = '500px';
142
148
  * ```
143
149
  */
@@ -146,6 +152,10 @@ export declare class Editor {
146
152
  showLoadingWarning(fractionLoaded: number): void;
147
153
  hideLoadingWarning(): void;
148
154
  private previousAccessibilityAnnouncement;
155
+ /**
156
+ * Announce `message` for screen readers. If `message` is the same as the previous
157
+ * message, it is re-announced.
158
+ */
149
159
  announceForAccessibility(message: string): void;
150
160
  /**
151
161
  * Creates a toolbar. If `defaultLayout` is true, default buttons are used.
@@ -162,6 +172,25 @@ export declare class Editor {
162
172
  handleHTMLPointerEvent(eventType: 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel', evt: PointerEvent): boolean;
163
173
  private isEventSink;
164
174
  private handlePaste;
175
+ /**
176
+ * Forward pointer events from `elem` to this editor. Such that right-click/right-click drag
177
+ * events are also forwarded, `elem`'s contextmenu is disabled.
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const overlay = document.createElement('div');
182
+ * editor.createHTMLOverlay(overlay);
183
+ *
184
+ * // Send all pointer events that don't have the control key pressed
185
+ * // to the editor.
186
+ * editor.handlePointerEventsFrom(overlay, (event) => {
187
+ * if (event.ctrlKey) {
188
+ * return false;
189
+ * }
190
+ * return true;
191
+ * });
192
+ * ```
193
+ */
165
194
  handlePointerEventsFrom(elem: HTMLElement, filter?: HTMLPointerEventFilter): void;
166
195
  /** Adds event listners for keypresses to `elem` and forwards those events to the editor. */
167
196
  handleKeyEventsFrom(elem: HTMLElement): void;
@@ -200,25 +229,52 @@ export declare class Editor {
200
229
  * Schedule a re-render for some time in the near future. Does not schedule an additional
201
230
  * re-render if a re-render is already queued.
202
231
  *
203
- * @returns a promise that resolves when
232
+ * @returns a promise that resolves when re-rendering has completed.
204
233
  */
205
234
  queueRerender(): Promise<void>;
206
235
  isRerenderQueued(): boolean;
236
+ /**
237
+ * Re-renders the entire image.
238
+ *
239
+ * @see {@link Editor.queueRerender}
240
+ */
207
241
  rerender(showImageBounds?: boolean): void;
208
242
  /**
243
+ * Draws the given path onto the wet ink renderer. The given path will
244
+ * be displayed on top of the main image.
245
+ *
209
246
  * @see {@link Display.getWetInkRenderer} {@link Display.flatten}
210
247
  */
211
248
  drawWetInk(...path: RenderablePathSpec[]): void;
212
249
  /**
250
+ * Clears the wet ink display.
251
+ *
213
252
  * @see {@link Display.getWetInkRenderer}
214
253
  */
215
254
  clearWetInk(): void;
255
+ /**
256
+ * Focuses the region used for text input/key commands.
257
+ */
216
258
  focus(): void;
259
+ /**
260
+ * Creates an element that will be positioned on top of the dry/wet ink
261
+ * renderers.
262
+ *
263
+ * This is useful for displaying content on top of the rendered content
264
+ * (e.g. a selection box).
265
+ */
217
266
  createHTMLOverlay(overlay: HTMLElement): {
218
267
  remove: () => void;
219
268
  };
220
269
  addStyleSheet(content: string): HTMLStyleElement;
221
270
  sendKeyboardEvent(eventType: InputEvtType.KeyPressEvent | InputEvtType.KeyUpEvent, key: string, ctrlKey?: boolean, altKey?: boolean): void;
271
+ /**
272
+ * Dispatch a pen event to the currently selected tool.
273
+ * Intended primarially for unit tests.
274
+ *
275
+ * @deprecated
276
+ * @see {@link sendPenEvent} {@link sendTouchEvent}
277
+ */
222
278
  sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
223
279
  addAndCenterComponents(components: AbstractComponent[], selectComponents?: boolean): Promise<void>;
224
280
  toDataURL(format?: 'image/png' | 'image/jpeg' | 'image/webp'): string;
@@ -32,6 +32,7 @@ import uniteCommands from './commands/uniteCommands';
32
32
  import SelectionTool from './tools/SelectionTool/SelectionTool';
33
33
  import Erase from './commands/Erase';
34
34
  import ImageBackground, { BackgroundType } from './components/ImageBackground';
35
+ import sendPenEvent from './testing/sendPenEvent';
35
36
  /**
36
37
  * The main entrypoint for the full editor.
37
38
  *
@@ -46,6 +47,9 @@ import ImageBackground, { BackgroundType } from './components/ImageBackground';
46
47
  * // Do something with saveData...
47
48
  * });
48
49
  * ```
50
+ *
51
+ * See also
52
+ * [`docs/example/example.ts`](https://github.com/personalizedrefrigerator/js-draw/blob/main/docs/example/example.ts#L15).
49
53
  */
50
54
  export class Editor {
51
55
  /**
@@ -62,6 +66,8 @@ export class Editor {
62
66
  *
63
67
  * // Add the default toolbar
64
68
  * const toolbar = editor.addToolbar();
69
+ *
70
+ * // Add a save button
65
71
  * toolbar.addActionButton({
66
72
  * label: 'Save'
67
73
  * icon: createSaveIcon(),
@@ -156,6 +162,7 @@ export class Editor {
156
162
  *
157
163
  * @example
158
164
  * ```
165
+ * // Set the editor's height to 500px
159
166
  * editor.getRootElement().style.height = '500px';
160
167
  * ```
161
168
  */
@@ -172,8 +179,10 @@ export class Editor {
172
179
  this.loadingWarning.style.display = 'none';
173
180
  this.announceForAccessibility(this.localization.doneLoading);
174
181
  }
175
- // Announce `message` for screen readers. If `message` is the same as the previous
176
- // message, it is re-announced.
182
+ /**
183
+ * Announce `message` for screen readers. If `message` is the same as the previous
184
+ * message, it is re-announced.
185
+ */
177
186
  announceForAccessibility(message) {
178
187
  // Force re-announcing an announcement if announced again.
179
188
  if (message === this.previousAccessibilityAnnouncement) {
@@ -424,6 +433,25 @@ export class Editor {
424
433
  }
425
434
  });
426
435
  }
436
+ /**
437
+ * Forward pointer events from `elem` to this editor. Such that right-click/right-click drag
438
+ * events are also forwarded, `elem`'s contextmenu is disabled.
439
+ *
440
+ * @example
441
+ * ```ts
442
+ * const overlay = document.createElement('div');
443
+ * editor.createHTMLOverlay(overlay);
444
+ *
445
+ * // Send all pointer events that don't have the control key pressed
446
+ * // to the editor.
447
+ * editor.handlePointerEventsFrom(overlay, (event) => {
448
+ * if (event.ctrlKey) {
449
+ * return false;
450
+ * }
451
+ * return true;
452
+ * });
453
+ * ```
454
+ */
427
455
  handlePointerEventsFrom(elem, filter) {
428
456
  // May be required to prevent text selection on iOS/Safari:
429
457
  // See https://stackoverflow.com/a/70992717/17055750
@@ -560,7 +588,7 @@ export class Editor {
560
588
  * Schedule a re-render for some time in the near future. Does not schedule an additional
561
589
  * re-render if a re-render is already queued.
562
590
  *
563
- * @returns a promise that resolves when
591
+ * @returns a promise that resolves when re-rendering has completed.
564
592
  */
565
593
  queueRerender() {
566
594
  if (!this.rerenderQueued) {
@@ -582,6 +610,11 @@ export class Editor {
582
610
  isRerenderQueued() {
583
611
  return this.rerenderQueued;
584
612
  }
613
+ /**
614
+ * Re-renders the entire image.
615
+ *
616
+ * @see {@link Editor.queueRerender}
617
+ */
585
618
  rerender(showImageBounds = true) {
586
619
  this.display.startRerender();
587
620
  // Don't render if the display has zero size.
@@ -601,6 +634,9 @@ export class Editor {
601
634
  this.nextRerenderListeners = [];
602
635
  }
603
636
  /**
637
+ * Draws the given path onto the wet ink renderer. The given path will
638
+ * be displayed on top of the main image.
639
+ *
604
640
  * @see {@link Display.getWetInkRenderer} {@link Display.flatten}
605
641
  */
606
642
  drawWetInk(...path) {
@@ -609,17 +645,26 @@ export class Editor {
609
645
  }
610
646
  }
611
647
  /**
648
+ * Clears the wet ink display.
649
+ *
612
650
  * @see {@link Display.getWetInkRenderer}
613
651
  */
614
652
  clearWetInk() {
615
653
  this.display.getWetInkRenderer().clear();
616
654
  }
617
- // Focuses the region used for text input/key commands.
655
+ /**
656
+ * Focuses the region used for text input/key commands.
657
+ */
618
658
  focus() {
619
659
  this.renderingRegion.focus();
620
660
  }
621
- // Creates an element that will be positioned on top of the dry/wet ink
622
- // renderers.
661
+ /**
662
+ * Creates an element that will be positioned on top of the dry/wet ink
663
+ * renderers.
664
+ *
665
+ * This is useful for displaying content on top of the rendered content
666
+ * (e.g. a selection box).
667
+ */
623
668
  createHTMLOverlay(overlay) {
624
669
  overlay.classList.add('overlay');
625
670
  this.container.appendChild(overlay);
@@ -643,19 +688,17 @@ export class Editor {
643
688
  altKey,
644
689
  });
645
690
  }
646
- // Dispatch a pen event to the currently selected tool.
647
- // Intended primarially for unit tests.
691
+ /**
692
+ * Dispatch a pen event to the currently selected tool.
693
+ * Intended primarially for unit tests.
694
+ *
695
+ * @deprecated
696
+ * @see {@link sendPenEvent} {@link sendTouchEvent}
697
+ */
648
698
  sendPenEvent(eventType, point,
649
699
  // @deprecated
650
700
  allPointers) {
651
- const mainPointer = Pointer.ofCanvasPoint(point, eventType !== InputEvtType.PointerUpEvt, this.viewport);
652
- this.toolController.dispatchInputEvent({
653
- kind: eventType,
654
- allPointers: allPointers !== null && allPointers !== void 0 ? allPointers : [
655
- mainPointer,
656
- ],
657
- current: mainPointer,
658
- });
701
+ sendPenEvent(this, eventType, point, allPointers);
659
702
  }
660
703
  addAndCenterComponents(components, selectComponents = true) {
661
704
  return __awaiter(this, void 0, void 0, function* () {
@@ -717,9 +760,8 @@ export class Editor {
717
760
  }
718
761
  toSVG() {
719
762
  const importExportViewport = this.image.getImportExportViewport().getTemporaryClone();
720
- const svgNameSpace = 'http://www.w3.org/2000/svg';
721
- const result = document.createElementNS(svgNameSpace, 'svg');
722
- const renderer = new SVGRenderer(result, importExportViewport);
763
+ const sanitize = false;
764
+ const { element: result, renderer } = SVGRenderer.fromViewport(importExportViewport, sanitize);
723
765
  const origTransform = importExportViewport.canvasToScreenTransform;
724
766
  // Render with (0,0) at (0,0) — we'll handle translation with
725
767
  // the viewBox property.
@@ -731,11 +773,6 @@ export class Editor {
731
773
  result.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => toRoundedString(part)).join(' '));
732
774
  result.setAttribute('width', toRoundedString(rect.w));
733
775
  result.setAttribute('height', toRoundedString(rect.h));
734
- // Ensure the image can be identified as an SVG if downloaded.
735
- // See https://jwatt.org/svg/authoring/
736
- result.setAttribute('version', '1.1');
737
- result.setAttribute('baseProfile', 'full');
738
- result.setAttribute('xmlns', svgNameSpace);
739
776
  return result;
740
777
  }
741
778
  /**
@@ -26,8 +26,10 @@ export default class EditorImage {
26
26
  /** @internal */
27
27
  renderWithCache(screenRenderer: AbstractRenderer, cache: RenderingCache, viewport: Viewport): void;
28
28
  /**
29
- * Renders all nodes visible from `viewport` (or all nodes if `viewport = null`)
30
- * @internal
29
+ * Renders all nodes visible from `viewport` (or all nodes if `viewport = null`).
30
+ *
31
+ * `viewport` is used to improve rendering performance. If given, it must match
32
+ * the viewport used by the `renderer` (if any).
31
33
  */
32
34
  render(renderer: AbstractRenderer, viewport: Viewport | null): void;
33
35
  /** Renders all nodes, even ones not within the viewport. @internal */
@@ -67,8 +67,10 @@ export default class EditorImage {
67
67
  cache.render(screenRenderer, this.root, viewport);
68
68
  }
69
69
  /**
70
- * Renders all nodes visible from `viewport` (or all nodes if `viewport = null`)
71
- * @internal
70
+ * Renders all nodes visible from `viewport` (or all nodes if `viewport = null`).
71
+ *
72
+ * `viewport` is used to improve rendering performance. If given, it must match
73
+ * the viewport used by the `renderer` (if any).
72
74
  */
73
75
  render(renderer, viewport) {
74
76
  this.background.render(renderer, viewport === null || viewport === void 0 ? void 0 : viewport.visibleRect);
@@ -36,6 +36,10 @@ export default class SVGLoader implements ImageLoader {
36
36
  private getSourceAttrs;
37
37
  start(onAddComponent: ComponentAddedListener, onProgress: OnProgressListener, onDetermineExportRect?: OnDetermineExportRectListener | null): Promise<void>;
38
38
  /**
39
+ * Create an `SVGLoader` from the content of an SVG image. SVGs are loaded within a sandboxed
40
+ * iframe with `sandbox="allow-same-origin"`
41
+ * [thereby disabling JavaScript](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox).
42
+ *
39
43
  * @see {@link Editor.loadFrom}
40
44
  * @param text - Textual representation of the SVG (e.g. `<svg viewbox='...'>...</svg>`).
41
45
  * @param sanitize - if `true`, don't store unknown attributes.
@@ -387,6 +387,10 @@ export default class SVGLoader {
387
387
  });
388
388
  }
389
389
  /**
390
+ * Create an `SVGLoader` from the content of an SVG image. SVGs are loaded within a sandboxed
391
+ * iframe with `sandbox="allow-same-origin"`
392
+ * [thereby disabling JavaScript](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox).
393
+ *
390
394
  * @see {@link Editor.loadFrom}
391
395
  * @param text - Textual representation of the SVG (e.g. `<svg viewbox='...'>...</svg>`).
392
396
  * @param sanitize - if `true`, don't store unknown attributes.
package/dist/src/lib.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The main entrypoint for the NPM package. Everything exported by this file
3
- * is available through the `js-draw` package.
3
+ * is available through the [`js-draw` package](https://www.npmjs.com/package/js-draw).
4
4
  *
5
5
  * @example
6
6
  * ```
@@ -26,6 +26,7 @@ export * from './commands/lib';
26
26
  export * from './tools/lib';
27
27
  export * from './toolbar/lib';
28
28
  export * from './rendering/lib';
29
+ export * from './testing/lib';
29
30
  export { default as Pointer, PointerDevice } from './Pointer';
30
31
  export { default as HTMLToolbar } from './toolbar/HTMLToolbar';
31
32
  export { default as UndoRedoHistory } from './UndoRedoHistory';
package/dist/src/lib.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The main entrypoint for the NPM package. Everything exported by this file
3
- * is available through the `js-draw` package.
3
+ * is available through the [`js-draw` package](https://www.npmjs.com/package/js-draw).
4
4
  *
5
5
  * @example
6
6
  * ```
@@ -26,6 +26,7 @@ export * from './commands/lib';
26
26
  export * from './tools/lib';
27
27
  export * from './toolbar/lib';
28
28
  export * from './rendering/lib';
29
+ export * from './testing/lib';
29
30
  export { default as Pointer, PointerDevice } from './Pointer';
30
31
  export { default as HTMLToolbar } from './toolbar/HTMLToolbar';
31
32
  export { default as UndoRedoHistory } from './UndoRedoHistory';
@@ -1,3 +1,5 @@
1
1
  export { default as AbstractRenderer } from './renderers/AbstractRenderer';
2
2
  export { default as DummyRenderer } from './renderers/DummyRenderer';
3
+ export { default as SVGRenderer } from './renderers/SVGRenderer';
4
+ export { default as CanvasRenderer } from './renderers/CanvasRenderer';
3
5
  export { default as Display } from './Display';
@@ -1,3 +1,5 @@
1
1
  export { default as AbstractRenderer } from './renderers/AbstractRenderer';
2
2
  export { default as DummyRenderer } from './renderers/DummyRenderer';
3
+ export { default as SVGRenderer } from './renderers/SVGRenderer';
4
+ export { default as CanvasRenderer } from './renderers/CanvasRenderer';
3
5
  export { default as Display } from './Display';
@@ -6,6 +6,26 @@ import Viewport from '../../Viewport';
6
6
  import RenderingStyle from '../RenderingStyle';
7
7
  import TextStyle from '../TextRenderingStyle';
8
8
  import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
9
+ /**
10
+ * Renders onto a `CanvasRenderingContext2D`.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const editor = new Editor(document.body);
15
+ *
16
+ * const canvas = document.createElement('canvas');
17
+ * const ctx = canvas.getContext('2d');
18
+ *
19
+ * // Ensure that the canvas can fit the entire rendering
20
+ * const viewport = editor.image.getImportExportViewport();
21
+ * canvas.width = viewport.getScreenRectSize().x;
22
+ * canvas.height = viewport.getScreenRectSize().y;
23
+ *
24
+ * // Render editor.image onto the renderer
25
+ * const renderer = new CanvasRenderer(ctx, viewport);
26
+ * editor.image.render(renderer, viewport);
27
+ * ```
28
+ */
9
29
  export default class CanvasRenderer extends AbstractRenderer {
10
30
  private ctx;
11
31
  private ignoreObjectsAboveLevel;
@@ -14,6 +34,11 @@ export default class CanvasRenderer extends AbstractRenderer {
14
34
  private minSquareCurveApproxDist;
15
35
  private minRenderSizeAnyDimen;
16
36
  private minRenderSizeBothDimens;
37
+ /**
38
+ * Creates a new `CanvasRenderer` that renders to the given rendering context.
39
+ * The `viewport` is used to determine the translation/rotation/scaling of the content
40
+ * to draw.
41
+ */
17
42
  constructor(ctx: CanvasRenderingContext2D, viewport: Viewport);
18
43
  private transformBy;
19
44
  canRenderFromWithoutDataLoss(other: AbstractRenderer): boolean;
@@ -3,7 +3,32 @@ import TextComponent from '../../components/TextComponent';
3
3
  import Path from '../../math/Path';
4
4
  import { Vec2 } from '../../math/Vec2';
5
5
  import AbstractRenderer from './AbstractRenderer';
6
+ /**
7
+ * Renders onto a `CanvasRenderingContext2D`.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const editor = new Editor(document.body);
12
+ *
13
+ * const canvas = document.createElement('canvas');
14
+ * const ctx = canvas.getContext('2d');
15
+ *
16
+ * // Ensure that the canvas can fit the entire rendering
17
+ * const viewport = editor.image.getImportExportViewport();
18
+ * canvas.width = viewport.getScreenRectSize().x;
19
+ * canvas.height = viewport.getScreenRectSize().y;
20
+ *
21
+ * // Render editor.image onto the renderer
22
+ * const renderer = new CanvasRenderer(ctx, viewport);
23
+ * editor.image.render(renderer, viewport);
24
+ * ```
25
+ */
6
26
  export default class CanvasRenderer extends AbstractRenderer {
27
+ /**
28
+ * Creates a new `CanvasRenderer` that renders to the given rendering context.
29
+ * The `viewport` is used to determine the translation/rotation/scaling of the content
30
+ * to draw.
31
+ */
7
32
  constructor(ctx, viewport) {
8
33
  super(viewport);
9
34
  this.ctx = ctx;
@@ -175,6 +200,7 @@ export default class CanvasRenderer extends AbstractRenderer {
175
200
  this.ignoringObject = false;
176
201
  }
177
202
  }
203
+ // @internal
178
204
  drawPoints(...points) {
179
205
  const pointRadius = 10;
180
206
  for (let i = 0; i < points.length; i++) {
@@ -191,6 +217,7 @@ export default class CanvasRenderer extends AbstractRenderer {
191
217
  this.ctx.fillText(`${i}`, point.x, point.y, pointRadius * 2);
192
218
  }
193
219
  }
220
+ // @internal
194
221
  isTooSmallToRender(rect) {
195
222
  // Should we ignore all objects within this object's bbox?
196
223
  const diagonal = this.getCanvasToScreenTransform().transformVec3(rect.size);
@@ -7,6 +7,11 @@ import RenderingStyle from '../RenderingStyle';
7
7
  import TextStyle from '../TextRenderingStyle';
8
8
  import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
9
9
  export declare const renderedStylesheetId = "js-draw-style-sheet";
10
+ /**
11
+ * Renders onto an `SVGElement`.
12
+ *
13
+ * @see {@link Editor.toSVG}
14
+ */
10
15
  export default class SVGRenderer extends AbstractRenderer {
11
16
  private elem;
12
17
  private sanitize;
@@ -14,6 +19,12 @@ export default class SVGRenderer extends AbstractRenderer {
14
19
  private lastPathString;
15
20
  private objectElems;
16
21
  private overwrittenAttrs;
22
+ /**
23
+ * Creates a renderer that renders onto `elem`. If `sanitize`, don't render potentially untrusted data.
24
+ *
25
+ * `viewport` is used to determine the translation/rotation/scaling/output size of the rendered
26
+ * data.
27
+ */
17
28
  constructor(elem: SVGSVGElement, viewport: Viewport, sanitize?: boolean);
18
29
  private addStyleSheet;
19
30
  setRootSVGAttribute(name: string, value: string | null): void;
@@ -39,4 +50,8 @@ export default class SVGRenderer extends AbstractRenderer {
39
50
  drawPoints(...points: Point2[]): void;
40
51
  drawSVGElem(elem: SVGElement): void;
41
52
  isTooSmallToRender(_rect: Rect2): boolean;
53
+ static fromViewport(viewport: Viewport, sanitize?: boolean): {
54
+ element: SVGSVGElement;
55
+ renderer: SVGRenderer;
56
+ };
42
57
  }
@@ -6,8 +6,18 @@ import { svgAttributesDataKey, svgStyleAttributesDataKey } from '../../SVGLoader
6
6
  import AbstractRenderer from './AbstractRenderer';
7
7
  export const renderedStylesheetId = 'js-draw-style-sheet';
8
8
  const svgNameSpace = 'http://www.w3.org/2000/svg';
9
+ /**
10
+ * Renders onto an `SVGElement`.
11
+ *
12
+ * @see {@link Editor.toSVG}
13
+ */
9
14
  export default class SVGRenderer extends AbstractRenderer {
10
- // Renders onto `elem`. If `sanitize`, don't render potentially untrusted data.
15
+ /**
16
+ * Creates a renderer that renders onto `elem`. If `sanitize`, don't render potentially untrusted data.
17
+ *
18
+ * `viewport` is used to determine the translation/rotation/scaling/output size of the rendered
19
+ * data.
20
+ */
11
21
  constructor(elem, viewport, sanitize = false) {
12
22
  super(viewport);
13
23
  this.elem = elem;
@@ -275,4 +285,20 @@ export default class SVGRenderer extends AbstractRenderer {
275
285
  isTooSmallToRender(_rect) {
276
286
  return false;
277
287
  }
288
+ // Creates a new SVG element and SVGRenerer with attributes set for the given Viewport.
289
+ static fromViewport(viewport, sanitize = true) {
290
+ const svgNameSpace = 'http://www.w3.org/2000/svg';
291
+ const result = document.createElementNS(svgNameSpace, 'svg');
292
+ const rect = viewport.getScreenRectSize();
293
+ // rect.x -> size of rect in x direction, rect.y -> size of rect in y direction.
294
+ result.setAttribute('viewBox', [0, 0, rect.x, rect.y].map(part => toRoundedString(part)).join(' '));
295
+ result.setAttribute('width', toRoundedString(rect.x));
296
+ result.setAttribute('height', toRoundedString(rect.y));
297
+ // Ensure the image can be identified as an SVG if downloaded.
298
+ // See https://jwatt.org/svg/authoring/
299
+ result.setAttribute('version', '1.1');
300
+ result.setAttribute('baseProfile', 'full');
301
+ result.setAttribute('xmlns', svgNameSpace);
302
+ return { element: result, renderer: new SVGRenderer(result, viewport, sanitize) };
303
+ }
278
304
  }
@@ -0,0 +1,2 @@
1
+ export { default as sendPenEvent } from './sendPenEvent';
2
+ export { default as sendTouchEvent } from './sendTouchEvent';
@@ -0,0 +1,2 @@
1
+ export { default as sendPenEvent } from './sendPenEvent';
2
+ export { default as sendTouchEvent } from './sendTouchEvent';
@@ -0,0 +1,12 @@
1
+ import Editor from '../Editor';
2
+ import { Point2 } from '../math/Vec2';
3
+ import Pointer from '../Pointer';
4
+ import { InputEvtType } from '../types';
5
+ /**
6
+ * Dispatch a pen event to the currently selected tool.
7
+ * Intended for unit tests.
8
+ *
9
+ * @see {@link sendTouchEvent}
10
+ */
11
+ declare const sendPenEvent: (editor: Editor, eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]) => void;
12
+ export default sendPenEvent;
@@ -0,0 +1,19 @@
1
+ import Pointer from '../Pointer';
2
+ import { InputEvtType } from '../types';
3
+ /**
4
+ * Dispatch a pen event to the currently selected tool.
5
+ * Intended for unit tests.
6
+ *
7
+ * @see {@link sendTouchEvent}
8
+ */
9
+ const sendPenEvent = (editor, eventType, point, allPointers) => {
10
+ const mainPointer = Pointer.ofCanvasPoint(point, eventType !== InputEvtType.PointerUpEvt, editor.viewport);
11
+ editor.toolController.dispatchInputEvent({
12
+ kind: eventType,
13
+ allPointers: allPointers !== null && allPointers !== void 0 ? allPointers : [
14
+ mainPointer,
15
+ ],
16
+ current: mainPointer,
17
+ });
18
+ };
19
+ export default sendPenEvent;
@@ -2,5 +2,41 @@ import Editor from '../Editor';
2
2
  import { Vec2 } from '../math/Vec2';
3
3
  import Pointer from '../Pointer';
4
4
  import { InputEvtType } from '../types';
5
+ /**
6
+ * Dispatch a touch event to the currently selected tool. Intended for unit tests.
7
+ *
8
+ * @see {@link sendPenEvent}
9
+ *
10
+ * @example
11
+ * **Simulating a horizontal swipe gesture:**
12
+ * ```ts
13
+ * sendTouchEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(0, 0));
14
+ * for (let i = 1; i <= 10; i++) {
15
+ * jest.advanceTimersByTime(10);
16
+ * sendTouchEvent(editor, InputEvtType.PointerMoveEvt, Vec2.of(i * 10, 0));
17
+ * }
18
+ * ```
19
+ *
20
+ * @example
21
+ * **Simulating a pinch gesture.** This example assumes that you're using [Jest with timer mocks enabled](https://jestjs.io/docs/timer-mocks).
22
+ * ```ts
23
+ * let firstPointer = sendTouchEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(0, 0));
24
+ * let secondPointer = sendTouchEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(100, 0), [ firstPointer ]);
25
+ *
26
+ * // Simulate a pinch
27
+ * const maxIterations = 10;
28
+ * for (let i = 0; i < maxIterations; i++) {
29
+ * // Use the unit testing framework's tool for increasing the current time
30
+ * // returned by (new Date()).getTime(), etc.
31
+ * jest.advanceTimersByTime(100);
32
+ *
33
+ * const point1 = Vec2.of(-i * 5, 0);
34
+ * const point2 = Vec2.of(i * 5 + 100, 0);
35
+ *
36
+ * firstPointer = sendTouchEvent(editor, InputEvtType.PointerMoveEvt, point1, [ secondPointer ]);
37
+ * secondPointer = sendTouchEvent(editor, InputEvtType.PointerMoveEvt, point2, [ firstPointer ]);
38
+ * }
39
+ * ```
40
+ */
5
41
  declare const sendTouchEvent: (editor: Editor, eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, screenPos: Vec2, allOtherPointers?: Pointer[]) => Pointer;
6
42
  export default sendTouchEvent;