js-draw 1.2.1 → 1.3.0

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 (122) hide show
  1. package/README.md +30 -30
  2. package/dist/Editor.css +70 -4
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/Editor.d.ts +73 -40
  6. package/dist/cjs/Editor.js +90 -24
  7. package/dist/cjs/EditorImage.d.ts +58 -6
  8. package/dist/cjs/EditorImage.js +336 -60
  9. package/dist/cjs/SVGLoader.d.ts +10 -4
  10. package/dist/cjs/SVGLoader.js +30 -10
  11. package/dist/cjs/UndoRedoHistory.d.ts +2 -2
  12. package/dist/cjs/UndoRedoHistory.js +4 -2
  13. package/dist/cjs/Viewport.d.ts +2 -1
  14. package/dist/cjs/Viewport.js +12 -3
  15. package/dist/cjs/commands/Command.d.ts +1 -0
  16. package/dist/cjs/commands/Command.js +1 -0
  17. package/dist/cjs/commands/Erase.js +1 -1
  18. package/dist/cjs/commands/SerializableCommand.d.ts +1 -1
  19. package/dist/cjs/commands/SerializableCommand.js +16 -2
  20. package/dist/cjs/commands/localization.d.ts +2 -0
  21. package/dist/cjs/commands/localization.js +2 -0
  22. package/dist/cjs/components/AbstractComponent.d.ts +38 -0
  23. package/dist/cjs/components/AbstractComponent.js +31 -0
  24. package/dist/cjs/components/BackgroundComponent.d.ts +10 -1
  25. package/dist/cjs/components/BackgroundComponent.js +60 -6
  26. package/dist/cjs/components/SVGGlobalAttributesObject.d.ts +2 -1
  27. package/dist/cjs/components/SVGGlobalAttributesObject.js +30 -1
  28. package/dist/cjs/components/Stroke.d.ts +1 -0
  29. package/dist/cjs/components/Stroke.js +44 -0
  30. package/dist/cjs/components/UnknownSVGObject.d.ts +2 -1
  31. package/dist/cjs/components/UnknownSVGObject.js +30 -1
  32. package/dist/cjs/components/lib.d.ts +2 -2
  33. package/dist/cjs/components/lib.js +15 -2
  34. package/dist/cjs/lib.d.ts +2 -45
  35. package/dist/cjs/lib.js +2 -45
  36. package/dist/cjs/rendering/RenderablePathSpec.d.ts +1 -0
  37. package/dist/cjs/rendering/RenderablePathSpec.js +1 -0
  38. package/dist/cjs/rendering/RenderingStyle.d.ts +1 -0
  39. package/dist/cjs/rendering/lib.d.ts +1 -0
  40. package/dist/cjs/rendering/lib.js +5 -1
  41. package/dist/cjs/rendering/renderers/AbstractRenderer.js +1 -1
  42. package/dist/cjs/shortcuts/KeyboardShortcutManager.d.ts +2 -2
  43. package/dist/cjs/shortcuts/KeyboardShortcutManager.js +2 -2
  44. package/dist/cjs/toolbar/localization.d.ts +1 -0
  45. package/dist/cjs/toolbar/localization.js +1 -0
  46. package/dist/cjs/toolbar/widgets/BaseWidget.js +5 -0
  47. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +54 -25
  48. package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +8 -0
  49. package/dist/cjs/tools/PanZoom.js +13 -8
  50. package/dist/cjs/tools/ScrollbarTool.d.ts +18 -0
  51. package/dist/cjs/tools/ScrollbarTool.js +85 -0
  52. package/dist/cjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
  53. package/dist/cjs/tools/ToolController.js +2 -0
  54. package/dist/cjs/types.d.ts +3 -1
  55. package/dist/cjs/util/adjustEditorThemeForContrast.js +1 -0
  56. package/dist/cjs/util/assertions.d.ts +4 -0
  57. package/dist/cjs/util/assertions.js +12 -1
  58. package/dist/cjs/version.js +1 -1
  59. package/dist/mjs/Editor.d.ts +73 -40
  60. package/dist/mjs/Editor.mjs +90 -24
  61. package/dist/mjs/EditorImage.d.ts +58 -6
  62. package/dist/mjs/EditorImage.mjs +313 -61
  63. package/dist/mjs/SVGLoader.d.ts +10 -4
  64. package/dist/mjs/SVGLoader.mjs +29 -9
  65. package/dist/mjs/UndoRedoHistory.d.ts +2 -2
  66. package/dist/mjs/UndoRedoHistory.mjs +4 -2
  67. package/dist/mjs/Viewport.d.ts +2 -1
  68. package/dist/mjs/Viewport.mjs +12 -3
  69. package/dist/mjs/commands/Command.d.ts +1 -0
  70. package/dist/mjs/commands/Command.mjs +1 -0
  71. package/dist/mjs/commands/Erase.mjs +1 -1
  72. package/dist/mjs/commands/SerializableCommand.d.ts +1 -1
  73. package/dist/mjs/commands/SerializableCommand.mjs +16 -2
  74. package/dist/mjs/commands/localization.d.ts +2 -0
  75. package/dist/mjs/commands/localization.mjs +2 -0
  76. package/dist/mjs/components/AbstractComponent.d.ts +38 -0
  77. package/dist/mjs/components/AbstractComponent.mjs +30 -0
  78. package/dist/mjs/components/BackgroundComponent.d.ts +10 -1
  79. package/dist/mjs/components/BackgroundComponent.mjs +37 -6
  80. package/dist/mjs/components/SVGGlobalAttributesObject.d.ts +2 -1
  81. package/dist/mjs/components/SVGGlobalAttributesObject.mjs +7 -1
  82. package/dist/mjs/components/Stroke.d.ts +1 -0
  83. package/dist/mjs/components/Stroke.mjs +44 -0
  84. package/dist/mjs/components/UnknownSVGObject.d.ts +2 -1
  85. package/dist/mjs/components/UnknownSVGObject.mjs +7 -1
  86. package/dist/mjs/components/lib.d.ts +2 -2
  87. package/dist/mjs/components/lib.mjs +2 -2
  88. package/dist/mjs/lib.d.ts +2 -45
  89. package/dist/mjs/lib.mjs +2 -45
  90. package/dist/mjs/rendering/RenderablePathSpec.d.ts +1 -0
  91. package/dist/mjs/rendering/RenderablePathSpec.mjs +1 -0
  92. package/dist/mjs/rendering/RenderingStyle.d.ts +1 -0
  93. package/dist/mjs/rendering/lib.d.ts +1 -0
  94. package/dist/mjs/rendering/lib.mjs +1 -0
  95. package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +1 -1
  96. package/dist/mjs/shortcuts/KeyboardShortcutManager.d.ts +2 -2
  97. package/dist/mjs/shortcuts/KeyboardShortcutManager.mjs +2 -2
  98. package/dist/mjs/toolbar/localization.d.ts +1 -0
  99. package/dist/mjs/toolbar/localization.mjs +1 -0
  100. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +5 -0
  101. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +54 -25
  102. package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +8 -0
  103. package/dist/mjs/tools/PanZoom.mjs +13 -8
  104. package/dist/mjs/tools/ScrollbarTool.d.ts +18 -0
  105. package/dist/mjs/tools/ScrollbarTool.mjs +79 -0
  106. package/dist/mjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
  107. package/dist/mjs/tools/ToolController.mjs +2 -0
  108. package/dist/mjs/types.d.ts +3 -1
  109. package/dist/mjs/util/adjustEditorThemeForContrast.mjs +1 -0
  110. package/dist/mjs/util/assertions.d.ts +4 -0
  111. package/dist/mjs/util/assertions.mjs +10 -0
  112. package/dist/mjs/version.mjs +1 -1
  113. package/package.json +3 -4
  114. package/src/Editor.scss +8 -0
  115. package/src/dialogs/dialogs.scss +2 -1
  116. package/src/toolbar/AbstractToolbar.scss +3 -0
  117. package/src/toolbar/EdgeToolbar.scss +4 -1
  118. package/src/toolbar/widgets/DocumentPropertiesWidget.scss +12 -0
  119. package/src/toolbar/widgets/components/makeGridSelector.scss +6 -1
  120. package/src/tools/ScrollbarTool.scss +57 -0
  121. package/src/tools/{SoundUITool.css → SoundUITool.scss} +4 -0
  122. package/src/tools/tools.scss +2 -1
@@ -1,4 +1,27 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
3
26
  if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
4
27
  return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
@@ -8,13 +31,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
31
  };
9
32
  var _a, _b, _c;
10
33
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.ImageNode = exports.EditorImageEventType = exports.sortLeavesByZIndex = void 0;
34
+ exports.RootImageNode = exports.ImageNode = exports.EditorImageEventType = exports.sortLeavesByZIndex = void 0;
12
35
  const Viewport_1 = __importDefault(require("./Viewport"));
13
- const AbstractComponent_1 = __importDefault(require("./components/AbstractComponent"));
36
+ const AbstractComponent_1 = __importStar(require("./components/AbstractComponent"));
14
37
  const math_1 = require("@js-draw/math");
15
38
  const SerializableCommand_1 = __importDefault(require("./commands/SerializableCommand"));
16
39
  const EventDispatcher_1 = __importDefault(require("./EventDispatcher"));
17
40
  const assertions_1 = require("./util/assertions");
41
+ const Command_1 = __importDefault(require("./commands/Command"));
18
42
  // @internal Sort by z-index, low to high
19
43
  const sortLeavesByZIndex = (leaves) => {
20
44
  leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
@@ -23,23 +47,25 @@ exports.sortLeavesByZIndex = sortLeavesByZIndex;
23
47
  var EditorImageEventType;
24
48
  (function (EditorImageEventType) {
25
49
  EditorImageEventType[EditorImageEventType["ExportViewportChanged"] = 0] = "ExportViewportChanged";
50
+ EditorImageEventType[EditorImageEventType["AutoresizeModeChanged"] = 1] = "AutoresizeModeChanged";
26
51
  })(EditorImageEventType || (exports.EditorImageEventType = EditorImageEventType = {}));
52
+ const debugMode = false;
27
53
  // Handles lookup/storage of elements in the image
28
54
  class EditorImage {
29
55
  // @internal
30
56
  constructor() {
31
57
  this.componentCount = 0;
32
- this.root = new ImageNode();
33
- this.background = new ImageNode();
58
+ this.settingExportRect = false;
59
+ this.root = new RootImageNode();
60
+ this.background = new RootImageNode();
34
61
  this.componentsById = {};
35
62
  this.notifier = new EventDispatcher_1.default();
36
63
  this.importExportViewport = new Viewport_1.default(() => {
37
- this.notifier.dispatch(EditorImageEventType.ExportViewportChanged, {
38
- image: this,
39
- });
64
+ this.onExportViewportChanged();
40
65
  });
41
66
  // Default to a 500x500 image
42
67
  this.importExportViewport.updateScreenSize(math_1.Vec2.of(500, 500));
68
+ this.shouldAutoresizeExportViewport = false;
43
69
  }
44
70
  // Returns all components that make up the background of this image. These
45
71
  // components are rendered below all other components.
@@ -73,7 +99,13 @@ class EditorImage {
73
99
  /** @internal */
74
100
  renderWithCache(screenRenderer, cache, viewport) {
75
101
  this.background.render(screenRenderer, viewport.visibleRect);
76
- cache.render(screenRenderer, this.root, viewport);
102
+ // If in debug mode, avoid rendering with cache to show additional debug information
103
+ if (!debugMode) {
104
+ cache.render(screenRenderer, this.root, viewport);
105
+ }
106
+ else {
107
+ this.root.render(screenRenderer, viewport.visibleRect);
108
+ }
77
109
  }
78
110
  /**
79
111
  * Renders all nodes visible from `viewport` (or all nodes if `viewport = null`).
@@ -89,7 +121,11 @@ class EditorImage {
89
121
  renderAll(renderer) {
90
122
  this.render(renderer, null);
91
123
  }
92
- /** @returns all elements in the image, sorted by z-index. This can be slow for large images. */
124
+ /**
125
+ * @returns all elements in the image, sorted by z-index. This can be slow for large images.
126
+ *
127
+ * Does not include background elements. See {@link getBackgroundComponents}.
128
+ */
93
129
  getAllElements() {
94
130
  const leaves = this.root.getLeaves();
95
131
  (0, exports.sortLeavesByZIndex)(leaves);
@@ -100,15 +136,25 @@ class EditorImage {
100
136
  return this.componentCount;
101
137
  }
102
138
  /** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
103
- getElementsIntersectingRegion(region) {
104
- const leaves = this.root.getLeavesIntersectingRegion(region);
139
+ getElementsIntersectingRegion(region, includeBackground = false) {
140
+ let leaves = this.root.getLeavesIntersectingRegion(region);
141
+ if (includeBackground) {
142
+ leaves = leaves.concat(this.background.getLeavesIntersectingRegion(region));
143
+ }
105
144
  (0, exports.sortLeavesByZIndex)(leaves);
106
145
  return leaves.map(leaf => leaf.getContent());
107
146
  }
108
- /** Called whenever an element is completely removed. @internal */
147
+ /** Called whenever (just after) an element is completely removed. @internal */
109
148
  onDestroyElement(elem) {
110
149
  this.componentCount--;
111
150
  delete this.componentsById[elem.getId()];
151
+ this.autoresizeExportViewport();
152
+ }
153
+ /** Called just after an element is added. @internal */
154
+ onElementAdded(elem) {
155
+ this.componentCount++;
156
+ this.componentsById[elem.getId()] = elem;
157
+ this.autoresizeExportViewport();
112
158
  }
113
159
  /**
114
160
  * @returns the AbstractComponent with `id`, if it exists.
@@ -119,13 +165,15 @@ class EditorImage {
119
165
  return this.componentsById[id] ?? null;
120
166
  }
121
167
  addElementDirectly(elem) {
168
+ // Because onAddToImage can affect the element's bounding box,
169
+ // this needs to be called before parentTree.addLeaf.
122
170
  elem.onAddToImage(this);
123
- this.componentCount++;
124
- this.componentsById[elem.getId()] = elem;
125
171
  // If a background component, add to the background. Else,
126
172
  // add to the normal component tree.
127
173
  const parentTree = elem.isBackground() ? this.background : this.root;
128
- return parentTree.addLeaf(elem);
174
+ const result = parentTree.addLeaf(elem);
175
+ this.onElementAdded(elem);
176
+ return result;
129
177
  }
130
178
  removeElementDirectly(element) {
131
179
  const container = this.findParent(element);
@@ -156,11 +204,82 @@ class EditorImage {
156
204
  getImportExportViewport() {
157
205
  return this.importExportViewport;
158
206
  }
207
+ /**
208
+ * @see {@link setImportExportRect}
209
+ */
210
+ getImportExportRect() {
211
+ return this.getImportExportViewport().visibleRect;
212
+ }
213
+ /**
214
+ * Sets the import/export rectangle to the given `imageRect`. Disables
215
+ * autoresize (if it was previously enabled).
216
+ */
159
217
  setImportExportRect(imageRect) {
160
- const importExportViewport = this.getImportExportViewport();
161
- const origSize = importExportViewport.visibleRect.size;
162
- const origTransform = importExportViewport.canvasToScreenTransform;
163
- return new EditorImage.SetImportExportRectCommand(origSize, origTransform, imageRect);
218
+ return EditorImage.SetImportExportRectCommand.of(this, imageRect, false);
219
+ }
220
+ getAutoresizeEnabled() {
221
+ return this.shouldAutoresizeExportViewport;
222
+ }
223
+ /** Returns a `Command` that sets whether the image should autoresize. */
224
+ setAutoresizeEnabled(autoresize) {
225
+ if (autoresize === this.shouldAutoresizeExportViewport) {
226
+ return Command_1.default.empty;
227
+ }
228
+ const newBBox = this.root.getBBox();
229
+ return EditorImage.SetImportExportRectCommand.of(this, newBBox, autoresize);
230
+ }
231
+ setAutoresizeEnabledDirectly(shouldAutoresize) {
232
+ if (shouldAutoresize !== this.shouldAutoresizeExportViewport) {
233
+ this.shouldAutoresizeExportViewport = shouldAutoresize;
234
+ this.notifier.dispatch(EditorImageEventType.AutoresizeModeChanged, {
235
+ image: this,
236
+ });
237
+ }
238
+ }
239
+ /** Updates the size/position of the viewport */
240
+ autoresizeExportViewport() {
241
+ // Only autoresize if in autoresize mode -- otherwise resizing the image
242
+ // should be done with undoable commands.
243
+ if (this.shouldAutoresizeExportViewport) {
244
+ this.setExportRectDirectly(this.root.getBBox());
245
+ }
246
+ }
247
+ /**
248
+ * Sets the import/export viewport directly, without returning a `Command`.
249
+ * As such, this is not undoable.
250
+ *
251
+ * See setImportExportRect
252
+ *
253
+ * Returns true if changes to the viewport were made (and thus
254
+ * ExportViewportChanged was fired.)
255
+ */
256
+ setExportRectDirectly(newRect) {
257
+ const viewport = this.getImportExportViewport();
258
+ const lastSize = viewport.getScreenRectSize();
259
+ const lastTransform = viewport.canvasToScreenTransform;
260
+ const newTransform = math_1.Mat33.translation(newRect.topLeft.times(-1));
261
+ if (!lastSize.eq(newRect.size) || !lastTransform.eq(newTransform)) {
262
+ // Prevent the ExportViewportChanged event from being fired
263
+ // multiple times for the same viewport change: Set settingExportRect
264
+ // to true.
265
+ this.settingExportRect = true;
266
+ viewport.updateScreenSize(newRect.size);
267
+ viewport.resetTransform(newTransform);
268
+ this.settingExportRect = false;
269
+ this.onExportViewportChanged();
270
+ return true;
271
+ }
272
+ return false;
273
+ }
274
+ onExportViewportChanged() {
275
+ // Prevent firing duplicate events -- changes
276
+ // made by exportViewport.resetTransform may cause this method to be
277
+ // called.
278
+ if (!this.settingExportRect) {
279
+ this.notifier.dispatch(EditorImageEventType.ExportViewportChanged, {
280
+ image: this,
281
+ });
282
+ }
164
283
  }
165
284
  }
166
285
  _a = EditorImage;
@@ -221,37 +340,57 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand_1.defaul
221
340
  _b);
222
341
  // Handles resizing the background import/export region of the image.
223
342
  EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand_1.default {
224
- constructor(originalSize, originalTransform, finalRect) {
343
+ constructor(originalSize, originalTransform, originalAutoresize, newExportRect, newAutoresize) {
225
344
  super(EditorImage.SetImportExportRectCommand.commandId);
226
345
  this.originalSize = originalSize;
227
346
  this.originalTransform = originalTransform;
228
- this.finalRect = finalRect;
347
+ this.originalAutoresize = originalAutoresize;
348
+ this.newExportRect = newExportRect;
349
+ this.newAutoresize = newAutoresize;
350
+ }
351
+ // Uses `image` to store the original size/transform
352
+ static of(image, newExportRect, newAutoresize) {
353
+ const importExportViewport = image.getImportExportViewport();
354
+ const originalSize = importExportViewport.visibleRect.size;
355
+ const originalTransform = importExportViewport.canvasToScreenTransform;
356
+ const originalAutoresize = image.getAutoresizeEnabled();
357
+ return new EditorImage.SetImportExportRectCommand(originalSize, originalTransform, originalAutoresize, newExportRect, newAutoresize);
229
358
  }
230
359
  apply(editor) {
231
- const viewport = editor.image.getImportExportViewport();
232
- viewport.updateScreenSize(this.finalRect.size);
233
- viewport.resetTransform(math_1.Mat33.translation(this.finalRect.topLeft.times(-1)));
360
+ editor.image.setAutoresizeEnabledDirectly(this.newAutoresize);
361
+ editor.image.setExportRectDirectly(this.newExportRect);
234
362
  editor.queueRerender();
235
363
  }
236
364
  unapply(editor) {
237
365
  const viewport = editor.image.getImportExportViewport();
366
+ editor.image.setAutoresizeEnabledDirectly(this.originalAutoresize);
238
367
  viewport.updateScreenSize(this.originalSize);
239
368
  viewport.resetTransform(this.originalTransform);
240
369
  editor.queueRerender();
241
370
  }
242
371
  description(_editor, localization) {
243
- return localization.resizeOutputCommand(this.finalRect);
372
+ if (this.newAutoresize !== this.originalAutoresize) {
373
+ if (this.newAutoresize) {
374
+ return localization.enabledAutoresizeOutputCommand;
375
+ }
376
+ else {
377
+ return localization.disabledAutoresizeOutputCommand;
378
+ }
379
+ }
380
+ return localization.resizeOutputCommand(this.newExportRect);
244
381
  }
245
382
  serializeToJSON() {
246
383
  return {
247
384
  originalSize: this.originalSize.xy,
248
385
  originalTransform: this.originalTransform.toArray(),
249
386
  newRegion: {
250
- x: this.finalRect.x,
251
- y: this.finalRect.y,
252
- w: this.finalRect.w,
253
- h: this.finalRect.h,
387
+ x: this.newExportRect.x,
388
+ y: this.newExportRect.y,
389
+ w: this.newExportRect.w,
390
+ h: this.newExportRect.h,
254
391
  },
392
+ autoresize: this.newAutoresize,
393
+ originalAutoresize: this.originalAutoresize,
255
394
  };
256
395
  }
257
396
  },
@@ -269,15 +408,22 @@ EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand
269
408
  json.newRegion.w,
270
409
  json.newRegion.h,
271
410
  ]);
411
+ (0, assertions_1.assertIsBoolean)(json.autoresize ?? false);
412
+ (0, assertions_1.assertIsBoolean)(json.originalAutoresize ?? false);
272
413
  const originalSize = math_1.Vec2.ofXY(json.originalSize);
273
414
  const originalTransform = new math_1.Mat33(...json.originalTransform);
274
415
  const finalRect = new math_1.Rect2(json.newRegion.x, json.newRegion.y, json.newRegion.w, json.newRegion.h);
275
- return new EditorImage.SetImportExportRectCommand(originalSize, originalTransform, finalRect);
416
+ const autoresize = json.autoresize ?? false;
417
+ const originalAutoresize = json.originalAutoresize ?? false;
418
+ return new EditorImage.SetImportExportRectCommand(originalSize, originalTransform, originalAutoresize, finalRect, autoresize);
276
419
  });
277
420
  })(),
278
421
  _c);
279
422
  exports.default = EditorImage;
280
- /** Part of the Editor's image. @internal */
423
+ /**
424
+ * Part of the Editor's image. Does not handle fullscreen/invisible components.
425
+ * @internal
426
+ */
281
427
  class ImageNode {
282
428
  constructor(parent = null) {
283
429
  this.parent = parent;
@@ -299,9 +445,11 @@ class ImageNode {
299
445
  getParent() {
300
446
  return this.parent;
301
447
  }
302
- getChildrenIntersectingRegion(region) {
448
+ // Override this to change how children are considered within a given region.
449
+ getChildrenIntersectingRegion(region, isTooSmallFilter) {
303
450
  return this.children.filter(child => {
304
- return child.getBBox().intersects(region);
451
+ const bbox = child.getBBox();
452
+ return !isTooSmallFilter?.(bbox) && bbox.intersects(region);
305
453
  });
306
454
  }
307
455
  getChildrenOrSelfIntersectingRegion(region) {
@@ -311,29 +459,25 @@ class ImageNode {
311
459
  return this.getChildrenIntersectingRegion(region);
312
460
  }
313
461
  // Returns a list of `ImageNode`s with content (and thus no children).
462
+ // Override getChildrenIntersectingRegion to customize how this method
463
+ // determines whether/which children are in `region`.
314
464
  getLeavesIntersectingRegion(region, isTooSmall) {
315
465
  const result = [];
316
- let current;
317
466
  const workList = [];
318
467
  workList.push(this);
319
- const toNext = () => {
320
- current = undefined;
321
- const next = workList.pop();
322
- if (next && !isTooSmall?.(next.bbox)) {
323
- current = next;
324
- if (current.content !== null && current.getBBox().intersection(region)) {
325
- result.push(current);
326
- }
327
- workList.push(...current.getChildrenIntersectingRegion(region));
328
- }
329
- };
330
468
  while (workList.length > 0) {
331
- toNext();
469
+ const current = workList.pop();
470
+ if (current.content !== null) {
471
+ result.push(current);
472
+ }
473
+ workList.push(...current.getChildrenIntersectingRegion(region, isTooSmall));
332
474
  }
333
475
  return result;
334
476
  }
335
477
  // Returns the child of this with the target content or `null` if no
336
478
  // such child exists.
479
+ //
480
+ // Note: Relies on all children to have valid bounding boxes.
337
481
  getChildWithContent(target) {
338
482
  const candidates = this.getLeavesIntersectingRegion(target.getBBox());
339
483
  for (const candidate of candidates) {
@@ -397,12 +541,19 @@ class ImageNode {
397
541
  result.rebalance();
398
542
  return result;
399
543
  }
400
- const newNode = new ImageNode(this);
544
+ const newNode = ImageNode.createLeafNode(this, leaf);
401
545
  this.children.push(newNode);
402
- newNode.content = leaf;
403
546
  newNode.recomputeBBox(true);
404
547
  return newNode;
405
548
  }
549
+ // Creates a new leaf node with the given content.
550
+ // This only establishes the parent-child linking in one direction. Callers
551
+ // must add the resultant node to the list of children manually.
552
+ static createLeafNode(parent, content) {
553
+ const newNode = new ImageNode(parent);
554
+ newNode.content = content;
555
+ return newNode;
556
+ }
406
557
  getBBox() {
407
558
  return this.bbox;
408
559
  }
@@ -418,9 +569,20 @@ class ImageNode {
418
569
  this.bbox = math_1.Rect2.union(...this.children.map(child => child.getBBox()));
419
570
  }
420
571
  if (bubbleUp && !oldBBox.eq(this.bbox)) {
421
- this.parent?.recomputeBBox(true);
572
+ if (!this.bbox.containsRect(oldBBox)) {
573
+ this.parent?.unionBBoxWith(this.bbox);
574
+ }
575
+ else {
576
+ this.parent?.recomputeBBox(true);
577
+ }
422
578
  }
423
579
  }
580
+ // Grows this' bounding box to also include `other`.
581
+ // Always bubbles up.
582
+ unionBBoxWith(other) {
583
+ this.bbox = this.bbox.union(other);
584
+ this.parent?.unionBBoxWith(other);
585
+ }
424
586
  updateParents(recursive = false) {
425
587
  for (const child of this.children) {
426
588
  child.parent = this;
@@ -451,6 +613,19 @@ class ImageNode {
451
613
  }
452
614
  }
453
615
  }
616
+ // Removes the parent-to-child link.
617
+ // Called internally by `.remove`
618
+ removeChild(child) {
619
+ const oldChildCount = this.children.length;
620
+ this.children = this.children.filter(node => {
621
+ return node !== child;
622
+ });
623
+ console.assert(this.children.length === oldChildCount - 1, `${oldChildCount - 1} ≠ ${this.children.length} after removing all nodes equal to ${child}. Nodes should only be removed once.`);
624
+ this.children.forEach(child => {
625
+ child.rebalance();
626
+ });
627
+ this.recomputeBBox(true);
628
+ }
454
629
  // Remove this node and all of its children
455
630
  remove() {
456
631
  this.content?.onRemoveFromImage();
@@ -459,18 +634,10 @@ class ImageNode {
459
634
  this.children = [];
460
635
  return;
461
636
  }
462
- const oldChildCount = this.parent.children.length;
463
- this.parent.children = this.parent.children.filter(node => {
464
- return node !== this;
465
- });
466
- console.assert(this.parent.children.length === oldChildCount - 1, `${oldChildCount - 1} ≠ ${this.parent.children.length} after removing all nodes equal to ${this}. Nodes should only be removed once.`);
467
- this.parent.children.forEach(child => {
468
- child.rebalance();
469
- });
470
- this.parent.recomputeBBox(true);
471
- // Invalidate/disconnect this.
472
- this.content = null;
637
+ this.parent.removeChild(this);
638
+ // Remove the child-to-parent link and invalid this
473
639
  this.parent = null;
640
+ this.content = null;
474
641
  this.children = [];
475
642
  }
476
643
  render(renderer, visibleRect) {
@@ -486,7 +653,116 @@ class ImageNode {
486
653
  // Leaves by definition have content
487
654
  leaf.getContent().render(renderer, visibleRect);
488
655
  }
656
+ // Show debug information
657
+ if (debugMode && visibleRect) {
658
+ this.renderDebugBoundingBoxes(renderer, visibleRect);
659
+ }
660
+ }
661
+ // Debug only: Shows bounding boxes of this and all children.
662
+ renderDebugBoundingBoxes(renderer, visibleRect, depth = 0) {
663
+ const bbox = this.getBBox();
664
+ const pixelSize = 1 / (renderer.getSizeOfCanvasPixelOnScreen() || 1);
665
+ if (bbox.maxDimension < 3 * pixelSize || !bbox.intersects(visibleRect)) {
666
+ return;
667
+ }
668
+ // Render debug information for this
669
+ renderer.startObject(bbox);
670
+ // Different styling for leaf nodes
671
+ const isLeaf = !!this.content;
672
+ const fill = isLeaf ? math_1.Color4.ofRGBA(1, 0, 1, 0.4) : math_1.Color4.ofRGBA(0, 1, Math.sin(depth), 0.6);
673
+ const lineWidth = isLeaf ? 1 * pixelSize : 2 * pixelSize;
674
+ renderer.drawRect(bbox.intersection(visibleRect), lineWidth, { fill });
675
+ renderer.endObject();
676
+ // Render debug information for children
677
+ for (const child of this.children) {
678
+ child.renderDebugBoundingBoxes(renderer, visibleRect, depth + 1);
679
+ }
489
680
  }
490
681
  }
491
682
  exports.ImageNode = ImageNode;
492
683
  ImageNode.idCounter = 0;
684
+ /** An `ImageNode` that can properly handle fullscreen/data components. @internal */
685
+ class RootImageNode extends ImageNode {
686
+ constructor() {
687
+ super(...arguments);
688
+ // Nodes that will always take up the entire screen
689
+ this.fullscreenChildren = [];
690
+ // Nodes that will never be visible unless a full render is done.
691
+ this.dataComponents = [];
692
+ }
693
+ getChildrenIntersectingRegion(region, _isTooSmall) {
694
+ const result = super.getChildrenIntersectingRegion(region);
695
+ for (const node of this.fullscreenChildren) {
696
+ result.push(node);
697
+ }
698
+ return result;
699
+ }
700
+ getLeaves() {
701
+ const leaves = super.getLeaves();
702
+ // Add fullscreen/data components — this method should
703
+ // return *all* leaves.
704
+ return this.dataComponents.concat(this.fullscreenChildren, leaves);
705
+ }
706
+ removeChild(child) {
707
+ let removed = false;
708
+ const checkTargetChild = (component) => {
709
+ const isTarget = component === child;
710
+ removed ||= isTarget;
711
+ return !isTarget;
712
+ };
713
+ // Check whether the child is stored in the data/fullscreen
714
+ // component arrays first.
715
+ this.dataComponents = this.dataComponents
716
+ .filter(checkTargetChild);
717
+ this.fullscreenChildren = this.fullscreenChildren
718
+ .filter(checkTargetChild);
719
+ if (!removed) {
720
+ super.removeChild(child);
721
+ }
722
+ }
723
+ getChildWithContent(target) {
724
+ const searchExtendedChildren = () => {
725
+ // Search through all extended children
726
+ const candidates = this.fullscreenChildren.concat(this.dataComponents);
727
+ for (const candidate of candidates) {
728
+ if (candidate.getContent() === target) {
729
+ return candidate;
730
+ }
731
+ }
732
+ return null;
733
+ };
734
+ // If positioned as if a standard child, search using the superclass first.
735
+ // Because it could be mislabeled, also search the extended children if the superclass
736
+ // search fails.
737
+ if (target.getSizingMode() === AbstractComponent_1.ComponentSizingMode.BoundingBox) {
738
+ return super.getChildWithContent(target) ?? searchExtendedChildren();
739
+ }
740
+ // Fall back to the superclass -- it's possible that the component has
741
+ // changed labels.
742
+ return super.getChildWithContent(target) ?? searchExtendedChildren();
743
+ }
744
+ addLeaf(leafContent) {
745
+ const sizingMode = leafContent.getSizingMode();
746
+ if (sizingMode === AbstractComponent_1.ComponentSizingMode.BoundingBox) {
747
+ return super.addLeaf(leafContent);
748
+ }
749
+ else if (sizingMode === AbstractComponent_1.ComponentSizingMode.FillScreen) {
750
+ this.onContentChange();
751
+ const newNode = ImageNode.createLeafNode(this, leafContent);
752
+ this.fullscreenChildren.push(newNode);
753
+ return newNode;
754
+ }
755
+ else if (sizingMode === AbstractComponent_1.ComponentSizingMode.Anywhere) {
756
+ this.onContentChange();
757
+ const newNode = ImageNode.createLeafNode(this, leafContent);
758
+ this.dataComponents.push(newNode);
759
+ return newNode;
760
+ }
761
+ else {
762
+ const exhaustivenessCheck = sizingMode;
763
+ throw new Error(`Invalid sizing mode, ${sizingMode}`);
764
+ return exhaustivenessCheck;
765
+ }
766
+ }
767
+ }
768
+ exports.RootImageNode = RootImageNode;
@@ -4,22 +4,28 @@ export declare const defaultSVGViewRect: Rect2;
4
4
  export declare const svgAttributesDataKey = "svgAttrs";
5
5
  export declare const svgStyleAttributesDataKey = "svgStyleAttrs";
6
6
  export declare const svgLoaderAttributeContainerID = "svgContainerID";
7
+ export declare const svgLoaderAutoresizeClassName = "js-draw--autoresize";
7
8
  export type SVGLoaderUnknownAttribute = [string, string];
8
9
  export type SVGLoaderUnknownStyleAttribute = {
9
10
  key: string;
10
11
  value: string;
11
12
  priority?: string;
12
13
  };
14
+ export interface SVGLoaderOptions {
15
+ sanitize?: boolean;
16
+ disableUnknownObjectWarnings?: boolean;
17
+ }
13
18
  export default class SVGLoader implements ImageLoader {
14
19
  private source;
15
- private onFinish?;
16
- private readonly storeUnknown;
20
+ private onFinish;
17
21
  private onAddComponent;
18
22
  private onProgress;
19
23
  private onDetermineExportRect;
20
24
  private processedCount;
21
25
  private totalToProcess;
22
26
  private rootViewBox;
27
+ private readonly storeUnknown;
28
+ private readonly disableUnknownObjectWarnings;
23
29
  private constructor();
24
30
  private getStyle;
25
31
  private strokeDataFromElem;
@@ -48,7 +54,7 @@ export default class SVGLoader implements ImageLoader {
48
54
  *
49
55
  * @see {@link Editor.loadFrom}
50
56
  * @param text - Textual representation of the SVG (e.g. `<svg viewbox='...'>...</svg>`).
51
- * @param sanitize - if `true`, don't store unknown attributes.
57
+ * @param options - if `true` or `false`, treated as the `sanitize` option -- don't store unknown attributes.
52
58
  */
53
- static fromString(text: string, sanitize?: boolean): SVGLoader;
59
+ static fromString(text: string, options?: Partial<SVGLoaderOptions> | boolean): SVGLoader;
54
60
  }