js-draw 0.15.1 → 0.15.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/Color4.d.ts +1 -1
  4. package/dist/src/Color4.js +5 -1
  5. package/dist/src/Editor.d.ts +0 -2
  6. package/dist/src/Editor.js +15 -30
  7. package/dist/src/EditorImage.d.ts +25 -0
  8. package/dist/src/EditorImage.js +57 -2
  9. package/dist/src/EventDispatcher.d.ts +4 -3
  10. package/dist/src/SVGLoader.d.ts +1 -0
  11. package/dist/src/SVGLoader.js +15 -1
  12. package/dist/src/Viewport.d.ts +3 -3
  13. package/dist/src/Viewport.js +4 -8
  14. package/dist/src/components/AbstractComponent.d.ts +5 -1
  15. package/dist/src/components/AbstractComponent.js +10 -2
  16. package/dist/src/components/ImageBackground.d.ts +41 -0
  17. package/dist/src/components/ImageBackground.js +132 -0
  18. package/dist/src/components/ImageComponent.js +2 -0
  19. package/dist/src/components/builders/ArrowBuilder.d.ts +3 -1
  20. package/dist/src/components/builders/ArrowBuilder.js +43 -40
  21. package/dist/src/components/builders/LineBuilder.d.ts +3 -1
  22. package/dist/src/components/builders/LineBuilder.js +25 -28
  23. package/dist/src/components/builders/RectangleBuilder.js +1 -1
  24. package/dist/src/components/lib.d.ts +2 -1
  25. package/dist/src/components/lib.js +2 -1
  26. package/dist/src/components/localization.d.ts +2 -0
  27. package/dist/src/components/localization.js +2 -0
  28. package/dist/src/math/Mat33.js +43 -5
  29. package/dist/src/math/Path.d.ts +5 -0
  30. package/dist/src/math/Path.js +80 -28
  31. package/dist/src/math/Vec3.js +1 -1
  32. package/dist/src/rendering/Display.js +1 -1
  33. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +13 -1
  34. package/dist/src/rendering/renderers/AbstractRenderer.js +18 -3
  35. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
  36. package/dist/src/rendering/renderers/CanvasRenderer.js +12 -2
  37. package/dist/src/rendering/renderers/SVGRenderer.d.ts +1 -1
  38. package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
  39. package/dist/src/testing/sendTouchEvent.d.ts +6 -0
  40. package/dist/src/testing/sendTouchEvent.js +26 -0
  41. package/dist/src/toolbar/IconProvider.js +1 -2
  42. package/dist/src/toolbar/widgets/HandToolWidget.js +1 -1
  43. package/dist/src/tools/Eraser.js +5 -2
  44. package/dist/src/tools/PanZoom.js +12 -0
  45. package/dist/src/tools/SelectionTool/Selection.js +1 -1
  46. package/dist/src/tools/SelectionTool/SelectionTool.js +5 -1
  47. package/package.json +1 -1
  48. package/src/Color4.test.ts +6 -0
  49. package/src/Color4.ts +6 -1
  50. package/src/Editor.ts +15 -36
  51. package/src/EditorImage.ts +74 -2
  52. package/src/EventDispatcher.ts +4 -1
  53. package/src/SVGLoader.ts +12 -1
  54. package/src/Viewport.ts +4 -7
  55. package/src/components/AbstractComponent.ts +11 -1
  56. package/src/components/ImageBackground.ts +167 -0
  57. package/src/components/ImageComponent.ts +2 -0
  58. package/src/components/builders/ArrowBuilder.ts +44 -41
  59. package/src/components/builders/LineBuilder.ts +26 -28
  60. package/src/components/builders/RectangleBuilder.ts +1 -1
  61. package/src/components/lib.ts +2 -0
  62. package/src/components/localization.ts +4 -0
  63. package/src/math/Mat33.test.ts +20 -1
  64. package/src/math/Mat33.ts +47 -5
  65. package/src/math/Path.ts +87 -28
  66. package/src/math/Vec3.test.ts +4 -0
  67. package/src/math/Vec3.ts +1 -1
  68. package/src/rendering/Display.ts +1 -1
  69. package/src/rendering/renderers/AbstractRenderer.ts +20 -3
  70. package/src/rendering/renderers/CanvasRenderer.ts +16 -3
  71. package/src/rendering/renderers/DummyRenderer.test.ts +1 -2
  72. package/src/rendering/renderers/SVGRenderer.ts +8 -1
  73. package/src/testing/sendTouchEvent.ts +43 -0
  74. package/src/toolbar/IconProvider.ts +1 -2
  75. package/src/toolbar/widgets/HandToolWidget.ts +1 -1
  76. package/src/tools/Eraser.test.ts +24 -1
  77. package/src/tools/Eraser.ts +6 -2
  78. package/src/tools/PanZoom.test.ts +267 -23
  79. package/src/tools/PanZoom.ts +15 -1
  80. package/src/tools/SelectionTool/Selection.ts +1 -1
  81. package/src/tools/SelectionTool/SelectionTool.ts +6 -1
  82. package/src/types.ts +1 -0
@@ -5,7 +5,7 @@ export default class Color4 {
5
5
  readonly g: number;
6
6
  /** Blue component. `b` ∈ [0, 1] */
7
7
  readonly b: number;
8
- /** Alpha/transparent component. `a` ∈ [0, 1] */
8
+ /** Alpha/transparent component. `a` ∈ [0, 1]. 0 = transparent */
9
9
  readonly a: number;
10
10
  private constructor();
11
11
  /**
@@ -6,7 +6,7 @@ export default class Color4 {
6
6
  g,
7
7
  /** Blue component. `b` ∈ [0, 1] */
8
8
  b,
9
- /** Alpha/transparent component. `a` ∈ [0, 1] */
9
+ /** Alpha/transparent component. `a` ∈ [0, 1]. 0 = transparent */
10
10
  a) {
11
11
  this.r = r;
12
12
  this.g = g;
@@ -103,6 +103,10 @@ export default class Color4 {
103
103
  if (other == null) {
104
104
  return false;
105
105
  }
106
+ // If both completely transparent,
107
+ if (this.a === 0 && other.a === 0) {
108
+ return true;
109
+ }
106
110
  return this.toHexString() === other.toHexString();
107
111
  }
108
112
  /**
@@ -84,8 +84,6 @@ export declare class Editor {
84
84
  * ```
85
85
  */
86
86
  readonly image: EditorImage;
87
- /** Viewport for the exported/imported image. */
88
- private importExportViewport;
89
87
  /**
90
88
  * Allows transforming the view and querying information about
91
89
  * what is currently visible.
@@ -10,7 +10,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import EditorImage from './EditorImage';
11
11
  import ToolController from './tools/ToolController';
12
12
  import { InputEvtType, EditorEventType } from './types';
13
- import Command from './commands/Command';
14
13
  import UndoRedoHistory from './UndoRedoHistory';
15
14
  import Viewport from './Viewport';
16
15
  import EventDispatcher from './EventDispatcher';
@@ -119,15 +118,18 @@ export class Editor {
119
118
  this.renderingRegion.setAttribute('tabIndex', '0');
120
119
  this.renderingRegion.setAttribute('alt', '');
121
120
  this.notifier = new EventDispatcher();
122
- this.importExportViewport = new Viewport(this.notifier);
123
- this.viewport = new Viewport(this.notifier);
121
+ this.viewport = new Viewport((oldTransform, newTransform) => {
122
+ this.notifier.dispatch(EditorEventType.ViewportChanged, {
123
+ kind: EditorEventType.ViewportChanged,
124
+ newTransform,
125
+ oldTransform,
126
+ });
127
+ });
124
128
  this.display = new Display(this, this.settings.renderingMode, this.renderingRegion);
125
129
  this.image = new EditorImage();
126
130
  this.history = new UndoRedoHistory(this, this.announceRedoCallback, this.announceUndoCallback);
127
131
  this.toolController = new ToolController(this, this.localization);
128
132
  parent.appendChild(this.container);
129
- // Default to a 500x500 image
130
- this.importExportViewport.updateScreenSize(Vec2.of(500, 500));
131
133
  this.viewport.updateScreenSize(Vec2.of(this.display.width, this.display.height));
132
134
  this.registerListeners();
133
135
  this.queueRerender();
@@ -582,7 +584,7 @@ export class Editor {
582
584
  if (showImageBounds) {
583
585
  const exportRectFill = { fill: Color4.fromHex('#44444455') };
584
586
  const exportRectStrokeWidth = 5 * this.viewport.getSizeOfPixelOnCanvas();
585
- renderer.drawRect(this.importExportViewport.visibleRect, exportRectStrokeWidth, exportRectFill);
587
+ renderer.drawRect(this.getImportExportRect(), exportRectStrokeWidth, exportRectFill);
586
588
  }
587
589
  this.rerenderQueued = false;
588
590
  this.nextRerenderListeners.forEach(listener => listener());
@@ -693,21 +695,22 @@ export class Editor {
693
695
  // The export resolution is the same as the size of the drawing canvas.
694
696
  toDataURL(format = 'image/png') {
695
697
  const canvas = document.createElement('canvas');
696
- const resolution = this.importExportViewport.getScreenRectSize();
698
+ const importExportViewport = this.image.getImportExportViewport();
699
+ const resolution = importExportViewport.getScreenRectSize();
697
700
  canvas.width = resolution.x;
698
701
  canvas.height = resolution.y;
699
702
  const ctx = canvas.getContext('2d');
700
- const renderer = new CanvasRenderer(ctx, this.importExportViewport);
703
+ const renderer = new CanvasRenderer(ctx, importExportViewport);
701
704
  this.image.renderAll(renderer);
702
705
  const dataURL = canvas.toDataURL(format);
703
706
  return dataURL;
704
707
  }
705
708
  toSVG() {
706
- const importExportViewport = this.importExportViewport;
709
+ const importExportViewport = this.image.getImportExportViewport();
707
710
  const svgNameSpace = 'http://www.w3.org/2000/svg';
708
711
  const result = document.createElementNS(svgNameSpace, 'svg');
709
712
  const renderer = new SVGRenderer(result, importExportViewport);
710
- const origTransform = this.importExportViewport.canvasToScreenTransform;
713
+ const origTransform = importExportViewport.canvasToScreenTransform;
711
714
  // Render with (0,0) at (0,0) — we'll handle translation with
712
715
  // the viewBox property.
713
716
  importExportViewport.resetTransform(Mat33.identity);
@@ -749,29 +752,11 @@ export class Editor {
749
752
  }
750
753
  // Returns the size of the visible region of the output SVG
751
754
  getImportExportRect() {
752
- return this.importExportViewport.visibleRect;
755
+ return this.image.getImportExportViewport().visibleRect;
753
756
  }
754
757
  // Resize the output SVG to match `imageRect`.
755
758
  setImportExportRect(imageRect) {
756
- const origSize = this.importExportViewport.visibleRect.size;
757
- const origTransform = this.importExportViewport.canvasToScreenTransform;
758
- return new class extends Command {
759
- apply(editor) {
760
- const viewport = editor.importExportViewport;
761
- viewport.updateScreenSize(imageRect.size);
762
- viewport.resetTransform(Mat33.translation(imageRect.topLeft.times(-1)));
763
- editor.queueRerender();
764
- }
765
- unapply(editor) {
766
- const viewport = editor.importExportViewport;
767
- viewport.updateScreenSize(origSize);
768
- viewport.resetTransform(origTransform);
769
- editor.queueRerender();
770
- }
771
- description(_editor, localizationTable) {
772
- return localizationTable.resizeOutputCommand(imageRect);
773
- }
774
- };
759
+ return this.image.setImportExportRect(imageRect);
775
760
  }
776
761
  /**
777
762
  * Alias for loadFrom(SVGLoader.fromString).
@@ -1,14 +1,36 @@
1
+ import Editor from './Editor';
1
2
  import AbstractRenderer from './rendering/renderers/AbstractRenderer';
2
3
  import Viewport from './Viewport';
3
4
  import AbstractComponent from './components/AbstractComponent';
4
5
  import Rect2 from './math/Rect2';
6
+ import { EditorLocalization } from './localization';
5
7
  import RenderingCache from './rendering/caching/RenderingCache';
6
8
  import SerializableCommand from './commands/SerializableCommand';
9
+ import EventDispatcher from './EventDispatcher';
7
10
  export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;
11
+ export declare enum EditorImageEventType {
12
+ ExportViewportChanged = 0
13
+ }
14
+ export type EditorImageNotifier = EventDispatcher<EditorImageEventType, {
15
+ image: EditorImage;
16
+ }>;
8
17
  export default class EditorImage {
9
18
  private root;
10
19
  private componentsById;
20
+ /** Viewport for the exported/imported image. */
21
+ private importExportViewport;
22
+ readonly notifier: EditorImageNotifier;
11
23
  constructor();
24
+ /**
25
+ * @returns a `Viewport` for rendering the image when importing/exporting.
26
+ */
27
+ getImportExportViewport(): Viewport;
28
+ setImportExportRect(imageRect: Rect2): {
29
+ apply(editor: Editor): void;
30
+ unapply(editor: Editor): void;
31
+ description(_editor: Editor, localizationTable: EditorLocalization): string;
32
+ onDrop(_editor: Editor): void;
33
+ };
12
34
  findParent(elem: AbstractComponent): ImageNode | null;
13
35
  queueRerenderOf(elem: AbstractComponent): void;
14
36
  /** @internal */
@@ -30,6 +52,7 @@ export default class EditorImage {
30
52
  */
31
53
  lookupElement(id: string): AbstractComponent | null;
32
54
  private addElementDirectly;
55
+ private removeElementDirectly;
33
56
  /**
34
57
  * Returns a command that adds the given element to the `EditorImage`.
35
58
  * If `applyByFlattening` is true, the content of the wet ink renderer is
@@ -38,6 +61,8 @@ export default class EditorImage {
38
61
  * @see {@link Display.flatten}
39
62
  */
40
63
  static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
64
+ /** @see EditorImage.addElement */
65
+ addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
41
66
  private static AddElementCommand;
42
67
  }
43
68
  type TooSmallToRenderCheck = (rect: Rect2) => boolean;
@@ -1,17 +1,62 @@
1
1
  var _a;
2
+ import Viewport from './Viewport';
2
3
  import AbstractComponent from './components/AbstractComponent';
3
4
  import Rect2 from './math/Rect2';
4
5
  import SerializableCommand from './commands/SerializableCommand';
6
+ import EventDispatcher from './EventDispatcher';
7
+ import { Vec2 } from './math/Vec2';
8
+ import Command from './commands/Command';
9
+ import Mat33 from './math/Mat33';
5
10
  // @internal Sort by z-index, low to high
6
11
  export const sortLeavesByZIndex = (leaves) => {
7
12
  leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
8
13
  };
14
+ export var EditorImageEventType;
15
+ (function (EditorImageEventType) {
16
+ EditorImageEventType[EditorImageEventType["ExportViewportChanged"] = 0] = "ExportViewportChanged";
17
+ })(EditorImageEventType || (EditorImageEventType = {}));
9
18
  // Handles lookup/storage of elements in the image
10
19
  export default class EditorImage {
11
20
  // @internal
12
21
  constructor() {
13
22
  this.root = new ImageNode();
14
23
  this.componentsById = {};
24
+ this.notifier = new EventDispatcher();
25
+ this.importExportViewport = new Viewport(() => {
26
+ this.notifier.dispatch(EditorImageEventType.ExportViewportChanged, {
27
+ image: this,
28
+ });
29
+ });
30
+ // Default to a 500x500 image
31
+ this.importExportViewport.updateScreenSize(Vec2.of(500, 500));
32
+ }
33
+ /**
34
+ * @returns a `Viewport` for rendering the image when importing/exporting.
35
+ */
36
+ getImportExportViewport() {
37
+ return this.importExportViewport;
38
+ }
39
+ setImportExportRect(imageRect) {
40
+ const importExportViewport = this.getImportExportViewport();
41
+ const origSize = importExportViewport.visibleRect.size;
42
+ const origTransform = importExportViewport.canvasToScreenTransform;
43
+ return new class extends Command {
44
+ apply(editor) {
45
+ const viewport = editor.image.getImportExportViewport();
46
+ viewport.updateScreenSize(imageRect.size);
47
+ viewport.resetTransform(Mat33.translation(imageRect.topLeft.times(-1)));
48
+ editor.queueRerender();
49
+ }
50
+ unapply(editor) {
51
+ const viewport = editor.image.getImportExportViewport();
52
+ viewport.updateScreenSize(origSize);
53
+ viewport.resetTransform(origTransform);
54
+ editor.queueRerender();
55
+ }
56
+ description(_editor, localizationTable) {
57
+ return localizationTable.resizeOutputCommand(imageRect);
58
+ }
59
+ };
15
60
  }
16
61
  // Returns the parent of the given element, if it exists.
17
62
  findParent(elem) {
@@ -76,9 +121,14 @@ export default class EditorImage {
76
121
  return (_a = this.componentsById[id]) !== null && _a !== void 0 ? _a : null;
77
122
  }
78
123
  addElementDirectly(elem) {
124
+ elem.onAddToImage(this);
79
125
  this.componentsById[elem.getId()] = elem;
80
126
  return this.root.addLeaf(elem);
81
127
  }
128
+ removeElementDirectly(element) {
129
+ const container = this.findParent(element);
130
+ container === null || container === void 0 ? void 0 : container.remove();
131
+ }
82
132
  /**
83
133
  * Returns a command that adds the given element to the `EditorImage`.
84
134
  * If `applyByFlattening` is true, the content of the wet ink renderer is
@@ -89,6 +139,10 @@ export default class EditorImage {
89
139
  static addElement(elem, applyByFlattening = false) {
90
140
  return new EditorImage.AddElementCommand(elem, applyByFlattening);
91
141
  }
142
+ /** @see EditorImage.addElement */
143
+ addElement(elem, applyByFlattening = true) {
144
+ return EditorImage.addElement(elem, applyByFlattening);
145
+ }
92
146
  }
93
147
  // A Command that can access private [EditorImage] functionality
94
148
  EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
@@ -117,8 +171,7 @@ EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
117
171
  }
118
172
  }
119
173
  unapply(editor) {
120
- const container = editor.image.findParent(this.element);
121
- container === null || container === void 0 ? void 0 : container.remove();
174
+ editor.image.removeElementDirectly(this.element);
122
175
  editor.queueRerender();
123
176
  }
124
177
  description(_editor, localization) {
@@ -305,11 +358,13 @@ export class ImageNode {
305
358
  }
306
359
  // Remove this node and all of its children
307
360
  remove() {
361
+ var _a;
308
362
  if (!this.parent) {
309
363
  this.content = null;
310
364
  this.children = [];
311
365
  return;
312
366
  }
367
+ (_a = this.content) === null || _a === void 0 ? void 0 : _a.onRemoveFromImage();
313
368
  const oldChildCount = this.parent.children.length;
314
369
  this.parent.children = this.parent.children.filter(node => {
315
370
  return node !== this;
@@ -16,13 +16,14 @@
16
16
  * @packageDocumentation
17
17
  */
18
18
  type CallbackHandler<EventType> = (data: EventType) => void;
19
+ export interface DispatcherEventListener {
20
+ remove: () => void;
21
+ }
19
22
  export default class EventDispatcher<EventKeyType extends string | symbol | number, EventMessageType> {
20
23
  private listeners;
21
24
  constructor();
22
25
  dispatch(eventName: EventKeyType, event: EventMessageType): void;
23
- on(eventName: EventKeyType, callback: CallbackHandler<EventMessageType>): {
24
- remove: () => boolean;
25
- };
26
+ on(eventName: EventKeyType, callback: CallbackHandler<EventMessageType>): DispatcherEventListener;
26
27
  /** Removes an event listener. This is equivalent to calling `.remove()` on the object returned by `.on`. */
27
28
  off(eventName: EventKeyType, callback: CallbackHandler<EventMessageType>): void;
28
29
  }
@@ -24,6 +24,7 @@ export default class SVGLoader implements ImageLoader {
24
24
  private strokeDataFromElem;
25
25
  private attachUnrecognisedAttrs;
26
26
  private addPath;
27
+ private addBackground;
27
28
  private getTransform;
28
29
  private makeText;
29
30
  private addText;
@@ -8,6 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import Color4 from './Color4';
11
+ import ImageBackground, { BackgroundType, imageBackgroundCSSClassName } from './components/ImageBackground';
11
12
  import ImageComponent from './components/ImageComponent';
12
13
  import Stroke from './components/Stroke';
13
14
  import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
@@ -145,6 +146,14 @@ export default class SVGLoader {
145
146
  yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, elem));
146
147
  });
147
148
  }
149
+ addBackground(node) {
150
+ var _a, _b, _c;
151
+ return __awaiter(this, void 0, void 0, function* () {
152
+ const fill = Color4.fromString((_b = (_a = node.getAttribute('fill')) !== null && _a !== void 0 ? _a : node.style.fill) !== null && _b !== void 0 ? _b : 'black');
153
+ const elem = new ImageBackground(BackgroundType.SolidColor, fill);
154
+ yield ((_c = this.onAddComponent) === null || _c === void 0 ? void 0 : _c.call(this, elem));
155
+ });
156
+ }
148
157
  // If given, 'supportedAttrs' will have x, y, etc. attributes that were used in computing the transform added to it,
149
158
  // to prevent storing duplicate transform information when saving the component.
150
159
  getTransform(elem, supportedAttrs, computedStyles) {
@@ -307,7 +316,12 @@ export default class SVGLoader {
307
316
  // Continue -- visit the node's children.
308
317
  break;
309
318
  case 'path':
310
- yield this.addPath(node);
319
+ if (node.classList.contains(imageBackgroundCSSClassName)) {
320
+ yield this.addBackground(node);
321
+ }
322
+ else {
323
+ yield this.addPath(node);
324
+ }
311
325
  break;
312
326
  case 'text':
313
327
  yield this.addText(node);
@@ -4,18 +4,18 @@ import Rect2 from './math/Rect2';
4
4
  import { Point2, Vec2 } from './math/Vec2';
5
5
  import Vec3 from './math/Vec3';
6
6
  import { StrokeDataPoint } from './types';
7
- import { EditorNotifier } from './types';
8
7
  type PointDataType<T extends Point2 | StrokeDataPoint | number> = T extends Point2 ? Point2 : number;
9
8
  export declare abstract class ViewportTransform extends Command {
10
9
  abstract readonly transform: Mat33;
11
10
  }
11
+ type TransformChangeCallback = (oldTransform: Mat33, newTransform: Mat33) => void;
12
12
  export declare class Viewport {
13
- private notifier;
13
+ private onTransformChangeCallback;
14
14
  private static ViewportTransform;
15
15
  private transform;
16
16
  private inverseTransform;
17
17
  private screenRect;
18
- constructor(notifier: EditorNotifier);
18
+ constructor(onTransformChangeCallback: TransformChangeCallback);
19
19
  updateScreenSize(screenSize: Vec2): void;
20
20
  /** Get the screen's visible region transformed into canvas space. */
21
21
  get visibleRect(): Rect2;
@@ -15,13 +15,12 @@ import Mat33 from './math/Mat33';
15
15
  import Rect2 from './math/Rect2';
16
16
  import { Vec2 } from './math/Vec2';
17
17
  import Vec3 from './math/Vec3';
18
- import { EditorEventType } from './types';
19
18
  export class ViewportTransform extends Command {
20
19
  }
21
20
  export class Viewport {
22
21
  // @internal
23
- constructor(notifier) {
24
- this.notifier = notifier;
22
+ constructor(onTransformChangeCallback) {
23
+ this.onTransformChangeCallback = onTransformChangeCallback;
25
24
  this.resetTransform(Mat33.identity);
26
25
  this.screenRect = Rect2.empty;
27
26
  }
@@ -50,14 +49,11 @@ export class Viewport {
50
49
  * @param newTransform - should map from canvas coordinates to screen coordinates.
51
50
  */
52
51
  resetTransform(newTransform = Mat33.identity) {
52
+ var _a;
53
53
  const oldTransform = this.transform;
54
54
  this.transform = newTransform;
55
55
  this.inverseTransform = newTransform.inverse();
56
- this.notifier.dispatch(EditorEventType.ViewportChanged, {
57
- kind: EditorEventType.ViewportChanged,
58
- newTransform,
59
- oldTransform,
60
- });
56
+ (_a = this.onTransformChangeCallback) === null || _a === void 0 ? void 0 : _a.call(this, oldTransform, newTransform);
61
57
  }
62
58
  get screenToCanvasTransform() {
63
59
  return this.inverseTransform;
@@ -1,4 +1,5 @@
1
1
  import SerializableCommand from '../commands/SerializableCommand';
2
+ import EditorImage from '../EditorImage';
2
3
  import LineSegment2 from '../math/LineSegment2';
3
4
  import Mat33 from '../math/Mat33';
4
5
  import Rect2 from '../math/Rect2';
@@ -17,7 +18,7 @@ export default abstract class AbstractComponent {
17
18
  private zIndex;
18
19
  private id;
19
20
  private static zIndexCounter;
20
- protected constructor(componentKind: string);
21
+ protected constructor(componentKind: string, initialZIndex?: number);
21
22
  getId(): string;
22
23
  private static deserializationCallbacks;
23
24
  static registerComponent(componentKind: string, deserialize: DeserializeCallback | null): void;
@@ -33,6 +34,9 @@ export default abstract class AbstractComponent {
33
34
  getZIndex(): number;
34
35
  /** @returns the bounding box of */
35
36
  getBBox(): Rect2;
37
+ /** Called when this component is added to the given image. */
38
+ onAddToImage(_image: EditorImage): void;
39
+ onRemoveFromImage(): void;
36
40
  abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
37
41
  /** @return true if `lineSegment` intersects this component. */
38
42
  abstract intersects(lineSegment: LineSegment2): boolean;
@@ -9,12 +9,17 @@ import UnresolvedSerializableCommand from '../commands/UnresolvedCommand';
9
9
  export default class AbstractComponent {
10
10
  constructor(
11
11
  // A unique identifier for the type of component
12
- componentKind) {
12
+ componentKind, initialZIndex) {
13
13
  this.componentKind = componentKind;
14
14
  // Stores data attached by a loader.
15
15
  this.loadSaveData = {};
16
16
  this.lastChangedTime = (new Date()).getTime();
17
- this.zIndex = AbstractComponent.zIndexCounter++;
17
+ if (initialZIndex !== undefined) {
18
+ this.zIndex = initialZIndex;
19
+ }
20
+ else {
21
+ this.zIndex = AbstractComponent.zIndexCounter++;
22
+ }
18
23
  // Create a unique ID.
19
24
  this.id = `${new Date().getTime()}-${Math.random()}`;
20
25
  if (AbstractComponent.deserializationCallbacks[componentKind] === undefined) {
@@ -54,6 +59,9 @@ export default class AbstractComponent {
54
59
  getBBox() {
55
60
  return this.contentBBox;
56
61
  }
62
+ /** Called when this component is added to the given image. */
63
+ onAddToImage(_image) { }
64
+ onRemoveFromImage() { }
57
65
  /**
58
66
  * @returns true if this component intersects `rect` -- it is entirely contained
59
67
  * within the rectangle or one of the rectangle's edges intersects this component.
@@ -0,0 +1,41 @@
1
+ import Color4 from '../Color4';
2
+ import Editor from '../Editor';
3
+ import EditorImage from '../EditorImage';
4
+ import SerializableCommand from '../commands/SerializableCommand';
5
+ import LineSegment2 from '../math/LineSegment2';
6
+ import Mat33 from '../math/Mat33';
7
+ import Rect2 from '../math/Rect2';
8
+ import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
9
+ import AbstractComponent from './AbstractComponent';
10
+ import { ImageComponentLocalization } from './localization';
11
+ import RestyleableComponent, { ComponentStyle } from './RestylableComponent';
12
+ export declare enum BackgroundType {
13
+ SolidColor = 0,
14
+ None = 1
15
+ }
16
+ export declare const imageBackgroundCSSClassName = "js-draw-image-background";
17
+ export default class ImageBackground extends AbstractComponent implements RestyleableComponent {
18
+ private backgroundType;
19
+ private mainColor;
20
+ protected contentBBox: Rect2;
21
+ private viewportSizeChangeListener;
22
+ readonly isRestylableComponent: true;
23
+ constructor(backgroundType: BackgroundType, mainColor: Color4);
24
+ getStyle(): ComponentStyle;
25
+ updateStyle(style: ComponentStyle): SerializableCommand;
26
+ forceStyle(style: ComponentStyle, _editor: Editor | null): void;
27
+ onAddToImage(image: EditorImage): void;
28
+ onRemoveFromImage(): void;
29
+ private recomputeBBox;
30
+ render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
31
+ intersects(lineSegment: LineSegment2): boolean;
32
+ isSelectable(): boolean;
33
+ protected serializeToJSON(): {
34
+ mainColor: string;
35
+ backgroundType: BackgroundType;
36
+ };
37
+ protected applyTransformation(_affineTransfm: Mat33): void;
38
+ description(localizationTable: ImageComponentLocalization): string;
39
+ protected createClone(): AbstractComponent;
40
+ static deserializeFromJSON(json: any): ImageBackground;
41
+ }
@@ -0,0 +1,132 @@
1
+ import Color4 from '../Color4';
2
+ import { EditorImageEventType } from '../EditorImage';
3
+ import Rect2 from '../math/Rect2';
4
+ import AbstractComponent from './AbstractComponent';
5
+ import { createRestyleComponentCommand } from './RestylableComponent';
6
+ export var BackgroundType;
7
+ (function (BackgroundType) {
8
+ BackgroundType[BackgroundType["SolidColor"] = 0] = "SolidColor";
9
+ BackgroundType[BackgroundType["None"] = 1] = "None";
10
+ })(BackgroundType || (BackgroundType = {}));
11
+ export const imageBackgroundCSSClassName = 'js-draw-image-background';
12
+ // Represents the background of an image in the editor.
13
+ export default class ImageBackground extends AbstractComponent {
14
+ constructor(backgroundType, mainColor) {
15
+ super('image-background', 0);
16
+ this.backgroundType = backgroundType;
17
+ this.mainColor = mainColor;
18
+ this.viewportSizeChangeListener = null;
19
+ // eslint-disable-next-line @typescript-eslint/prefer-as-const
20
+ this.isRestylableComponent = true;
21
+ this.contentBBox = Rect2.empty;
22
+ }
23
+ getStyle() {
24
+ let color = this.mainColor;
25
+ if (this.backgroundType === BackgroundType.None) {
26
+ color = undefined;
27
+ }
28
+ return {
29
+ color,
30
+ };
31
+ }
32
+ updateStyle(style) {
33
+ return createRestyleComponentCommand(this.getStyle(), style, this);
34
+ }
35
+ // @internal
36
+ forceStyle(style, _editor) {
37
+ const fill = style.color;
38
+ if (!fill) {
39
+ return;
40
+ }
41
+ this.mainColor = fill;
42
+ if (fill.eq(Color4.transparent)) {
43
+ this.backgroundType = BackgroundType.None;
44
+ }
45
+ else {
46
+ this.backgroundType = BackgroundType.SolidColor;
47
+ }
48
+ }
49
+ onAddToImage(image) {
50
+ if (this.viewportSizeChangeListener) {
51
+ console.warn('onAddToImage called when background is already in an image');
52
+ this.onRemoveFromImage();
53
+ }
54
+ this.viewportSizeChangeListener = image.notifier.on(EditorImageEventType.ExportViewportChanged, () => {
55
+ this.recomputeBBox(image);
56
+ });
57
+ this.recomputeBBox(image);
58
+ }
59
+ onRemoveFromImage() {
60
+ var _a;
61
+ (_a = this.viewportSizeChangeListener) === null || _a === void 0 ? void 0 : _a.remove();
62
+ this.viewportSizeChangeListener = null;
63
+ }
64
+ recomputeBBox(image) {
65
+ const importExportRect = image.getImportExportViewport().visibleRect;
66
+ if (!this.contentBBox.eq(importExportRect)) {
67
+ this.contentBBox = importExportRect;
68
+ // Re-render this if already added to the EditorImage.
69
+ image.queueRerenderOf(this);
70
+ }
71
+ }
72
+ render(canvas, visibleRect) {
73
+ if (this.backgroundType === BackgroundType.None) {
74
+ return;
75
+ }
76
+ canvas.startObject(this.contentBBox);
77
+ if (this.backgroundType === BackgroundType.SolidColor) {
78
+ // If the rectangle for this region contains the visible rect,
79
+ // we can fill the entire visible rectangle (which may be more efficient than
80
+ // filling the entire region for this.)
81
+ if (visibleRect) {
82
+ const intersection = visibleRect.intersection(this.contentBBox);
83
+ if (intersection) {
84
+ canvas.fillRect(intersection, this.mainColor);
85
+ }
86
+ }
87
+ else {
88
+ canvas.fillRect(this.contentBBox, this.mainColor);
89
+ }
90
+ }
91
+ canvas.endObject(this.getLoadSaveData(), [imageBackgroundCSSClassName]);
92
+ }
93
+ intersects(lineSegment) {
94
+ return this.contentBBox.getEdges().some(edge => edge.intersects(lineSegment));
95
+ }
96
+ isSelectable() {
97
+ return false;
98
+ }
99
+ serializeToJSON() {
100
+ return {
101
+ mainColor: this.mainColor.toHexString(),
102
+ backgroundType: this.backgroundType,
103
+ };
104
+ }
105
+ applyTransformation(_affineTransfm) {
106
+ // Do nothing — it doesn't make sense to transform the background.
107
+ }
108
+ description(localizationTable) {
109
+ if (this.backgroundType === BackgroundType.SolidColor) {
110
+ return localizationTable.filledBackgroundWithColor(this.mainColor.toString());
111
+ }
112
+ else {
113
+ return localizationTable.emptyBackground;
114
+ }
115
+ }
116
+ createClone() {
117
+ return new ImageBackground(this.backgroundType, this.mainColor);
118
+ }
119
+ // @internal
120
+ static deserializeFromJSON(json) {
121
+ if (typeof json === 'string') {
122
+ json = JSON.parse(json);
123
+ }
124
+ if (typeof json.mainColor !== 'string') {
125
+ throw new Error('Error deserializing — mainColor must be of type string.');
126
+ }
127
+ const backgroundType = json.backgroundType === BackgroundType.SolidColor ? BackgroundType.SolidColor : BackgroundType.None;
128
+ const mainColor = Color4.fromHex(json.mainColor);
129
+ return new ImageBackground(backgroundType, mainColor);
130
+ }
131
+ }
132
+ AbstractComponent.registerComponent('image-background', ImageBackground.deserializeFromJSON);
@@ -80,7 +80,9 @@ export default class ImageComponent extends AbstractComponent {
80
80
  });
81
81
  }
82
82
  render(canvas, _visibleRect) {
83
+ canvas.startObject(this.contentBBox);
83
84
  canvas.drawImage(this.image);
85
+ canvas.endObject(this.getLoadSaveData());
84
86
  }
85
87
  getProportionalRenderingTime() {
86
88
  // Estimate: Equivalent to a stroke with 10 segments.