js-draw 1.3.0 → 1.4.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 (164) hide show
  1. package/README.md +1 -1
  2. package/dist/Editor.css +55 -13
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/Editor.d.ts +36 -3
  6. package/dist/cjs/Editor.js +63 -26
  7. package/dist/cjs/SVGLoader.js +37 -22
  8. package/dist/cjs/commands/Erase.js +1 -1
  9. package/dist/cjs/commands/UnresolvedCommand.d.ts +1 -1
  10. package/dist/cjs/components/AbstractComponent.d.ts +1 -1
  11. package/dist/cjs/components/AbstractComponent.js +1 -1
  12. package/dist/cjs/components/BackgroundComponent.d.ts +1 -1
  13. package/dist/cjs/components/BackgroundComponent.js +3 -2
  14. package/dist/cjs/{EditorImage.d.ts → image/EditorImage.d.ts} +30 -8
  15. package/dist/cjs/{EditorImage.js → image/EditorImage.js} +51 -7
  16. package/dist/cjs/image/export/editorImageToSVG.d.ts +8 -0
  17. package/dist/cjs/image/export/editorImageToSVG.js +49 -0
  18. package/dist/cjs/image/export/setExportedSVGSize.d.ts +6 -0
  19. package/dist/cjs/image/export/setExportedSVGSize.js +25 -0
  20. package/dist/cjs/image/lib.d.ts +1 -0
  21. package/dist/cjs/image/lib.js +8 -0
  22. package/dist/cjs/lib.d.ts +1 -1
  23. package/dist/cjs/lib.js +2 -3
  24. package/dist/cjs/localizations/comments.d.ts +6 -0
  25. package/dist/cjs/localizations/comments.js +10 -0
  26. package/dist/cjs/localizations/es.js +68 -48
  27. package/dist/cjs/rendering/caching/RenderingCache.d.ts +1 -1
  28. package/dist/cjs/rendering/caching/RenderingCacheNode.d.ts +1 -1
  29. package/dist/cjs/rendering/caching/RenderingCacheNode.js +4 -3
  30. package/dist/cjs/rendering/renderers/SVGRenderer.js +8 -19
  31. package/dist/cjs/rendering/renderers/SVGRenderer.test.d.ts +1 -0
  32. package/dist/cjs/toolbar/AbstractToolbar.d.ts +11 -3
  33. package/dist/cjs/toolbar/AbstractToolbar.js +20 -6
  34. package/dist/cjs/toolbar/EdgeToolbar.js +5 -6
  35. package/dist/cjs/toolbar/IconProvider.d.ts +1 -0
  36. package/dist/cjs/toolbar/IconProvider.js +43 -0
  37. package/dist/cjs/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
  38. package/dist/cjs/toolbar/widgets/ActionButtonWidget.js +19 -1
  39. package/dist/cjs/toolbar/widgets/BaseToolWidget.d.ts +1 -0
  40. package/dist/cjs/toolbar/widgets/BaseToolWidget.js +3 -0
  41. package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +5 -0
  42. package/dist/cjs/toolbar/widgets/BaseWidget.js +30 -2
  43. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +1 -1
  44. package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +1 -0
  45. package/dist/cjs/toolbar/widgets/HandToolWidget.js +6 -0
  46. package/dist/cjs/toolbar/widgets/InsertImageWidget.js +1 -1
  47. package/dist/cjs/toolbar/widgets/OverflowWidget.d.ts +1 -0
  48. package/dist/cjs/toolbar/widgets/OverflowWidget.js +3 -0
  49. package/dist/cjs/toolbar/widgets/SaveActionWidget.d.ts +1 -0
  50. package/dist/cjs/toolbar/widgets/SaveActionWidget.js +3 -0
  51. package/dist/cjs/tools/BaseTool.d.ts +3 -0
  52. package/dist/cjs/tools/BaseTool.js +13 -2
  53. package/dist/cjs/tools/FindTool.d.ts +1 -0
  54. package/dist/cjs/tools/FindTool.js +4 -1
  55. package/dist/cjs/tools/PanZoom.d.ts +1 -0
  56. package/dist/cjs/tools/PanZoom.js +4 -0
  57. package/dist/cjs/tools/Pen.d.ts +0 -1
  58. package/dist/cjs/tools/Pen.js +1 -4
  59. package/dist/cjs/tools/PipetteTool.d.ts +1 -0
  60. package/dist/cjs/tools/PipetteTool.js +3 -0
  61. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.d.ts +1 -0
  62. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.js +3 -0
  63. package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -0
  64. package/dist/cjs/tools/SelectionTool/Selection.js +44 -8
  65. package/dist/cjs/tools/SelectionTool/SelectionHandle.d.ts +14 -6
  66. package/dist/cjs/tools/SelectionTool/SelectionHandle.js +26 -8
  67. package/dist/cjs/tools/SelectionTool/SelectionTool.js +5 -0
  68. package/dist/cjs/tools/SoundUITool.d.ts +1 -0
  69. package/dist/cjs/tools/SoundUITool.js +4 -1
  70. package/dist/cjs/tools/TextTool.js +2 -2
  71. package/dist/cjs/tools/ToolController.d.ts +2 -0
  72. package/dist/cjs/tools/ToolController.js +13 -2
  73. package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +1 -0
  74. package/dist/cjs/tools/ToolSwitcherShortcut.js +3 -0
  75. package/dist/cjs/types.d.ts +9 -4
  76. package/dist/cjs/types.js +4 -3
  77. package/dist/cjs/util/ReactiveValue.d.ts +1 -1
  78. package/dist/cjs/util/ReactiveValue.js +2 -2
  79. package/dist/cjs/version.js +1 -1
  80. package/dist/mjs/Editor.d.ts +36 -3
  81. package/dist/mjs/Editor.mjs +64 -27
  82. package/dist/mjs/Editor.toSVGAsync.test.d.ts +1 -0
  83. package/dist/mjs/SVGLoader.mjs +37 -22
  84. package/dist/mjs/commands/Erase.mjs +1 -1
  85. package/dist/mjs/commands/UnresolvedCommand.d.ts +1 -1
  86. package/dist/mjs/components/AbstractComponent.d.ts +1 -1
  87. package/dist/mjs/components/AbstractComponent.mjs +1 -1
  88. package/dist/mjs/components/BackgroundComponent.d.ts +1 -1
  89. package/dist/mjs/components/BackgroundComponent.mjs +3 -2
  90. package/dist/mjs/{EditorImage.d.ts → image/EditorImage.d.ts} +30 -8
  91. package/dist/mjs/{EditorImage.mjs → image/EditorImage.mjs} +51 -7
  92. package/dist/mjs/image/EditorImage.test.d.ts +1 -0
  93. package/dist/mjs/image/export/editorImageToSVG.d.ts +8 -0
  94. package/dist/mjs/image/export/editorImageToSVG.mjs +41 -0
  95. package/dist/mjs/image/export/setExportedSVGSize.d.ts +6 -0
  96. package/dist/mjs/image/export/setExportedSVGSize.mjs +23 -0
  97. package/dist/mjs/image/lib.d.ts +1 -0
  98. package/dist/mjs/image/lib.mjs +1 -0
  99. package/dist/mjs/lib.d.ts +1 -1
  100. package/dist/mjs/lib.mjs +1 -1
  101. package/dist/mjs/localizations/comments.d.ts +6 -0
  102. package/dist/mjs/localizations/comments.mjs +8 -0
  103. package/dist/mjs/localizations/es.mjs +68 -48
  104. package/dist/mjs/rendering/caching/RenderingCache.d.ts +1 -1
  105. package/dist/mjs/rendering/caching/RenderingCacheNode.d.ts +1 -1
  106. package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +4 -3
  107. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +8 -19
  108. package/dist/mjs/rendering/renderers/SVGRenderer.test.d.ts +1 -0
  109. package/dist/mjs/toolbar/AbstractToolbar.d.ts +11 -3
  110. package/dist/mjs/toolbar/AbstractToolbar.mjs +20 -6
  111. package/dist/mjs/toolbar/EdgeToolbar.mjs +5 -6
  112. package/dist/mjs/toolbar/IconProvider.d.ts +1 -0
  113. package/dist/mjs/toolbar/IconProvider.mjs +43 -0
  114. package/dist/mjs/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
  115. package/dist/mjs/toolbar/widgets/ActionButtonWidget.mjs +21 -2
  116. package/dist/mjs/toolbar/widgets/BaseToolWidget.d.ts +1 -0
  117. package/dist/mjs/toolbar/widgets/BaseToolWidget.mjs +3 -0
  118. package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +5 -0
  119. package/dist/mjs/toolbar/widgets/BaseWidget.mjs +30 -2
  120. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +1 -1
  121. package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +1 -0
  122. package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +6 -0
  123. package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +1 -1
  124. package/dist/mjs/toolbar/widgets/OverflowWidget.d.ts +1 -0
  125. package/dist/mjs/toolbar/widgets/OverflowWidget.mjs +3 -0
  126. package/dist/mjs/toolbar/widgets/SaveActionWidget.d.ts +1 -0
  127. package/dist/mjs/toolbar/widgets/SaveActionWidget.mjs +3 -0
  128. package/dist/mjs/tools/BaseTool.d.ts +3 -0
  129. package/dist/mjs/tools/BaseTool.mjs +13 -2
  130. package/dist/mjs/tools/FindTool.d.ts +1 -0
  131. package/dist/mjs/tools/FindTool.mjs +4 -1
  132. package/dist/mjs/tools/PanZoom.d.ts +1 -0
  133. package/dist/mjs/tools/PanZoom.mjs +4 -0
  134. package/dist/mjs/tools/Pen.d.ts +0 -1
  135. package/dist/mjs/tools/Pen.mjs +1 -4
  136. package/dist/mjs/tools/PipetteTool.d.ts +1 -0
  137. package/dist/mjs/tools/PipetteTool.mjs +3 -0
  138. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.d.ts +1 -0
  139. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.mjs +3 -0
  140. package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -0
  141. package/dist/mjs/tools/SelectionTool/Selection.mjs +45 -9
  142. package/dist/mjs/tools/SelectionTool/SelectionHandle.d.ts +14 -6
  143. package/dist/mjs/tools/SelectionTool/SelectionHandle.mjs +25 -7
  144. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +5 -0
  145. package/dist/mjs/tools/SoundUITool.d.ts +1 -0
  146. package/dist/mjs/tools/SoundUITool.mjs +4 -1
  147. package/dist/mjs/tools/TextTool.mjs +2 -2
  148. package/dist/mjs/tools/ToolController.d.ts +2 -0
  149. package/dist/mjs/tools/ToolController.mjs +13 -2
  150. package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +1 -0
  151. package/dist/mjs/tools/ToolSwitcherShortcut.mjs +3 -0
  152. package/dist/mjs/types.d.ts +9 -4
  153. package/dist/mjs/types.mjs +4 -3
  154. package/dist/mjs/util/ReactiveValue.d.ts +1 -1
  155. package/dist/mjs/util/ReactiveValue.mjs +2 -2
  156. package/dist/mjs/version.mjs +1 -1
  157. package/package.json +5 -5
  158. package/src/Editor.scss +6 -0
  159. package/src/toolbar/EdgeToolbar.scss +19 -2
  160. package/src/tools/SelectionTool/SelectionTool.scss +74 -0
  161. package/src/tools/tools.scss +1 -1
  162. package/src/tools/SelectionTool/SelectionTool.css +0 -35
  163. /package/dist/cjs/{EditorImage.test.d.ts → Editor.toSVGAsync.test.d.ts} +0 -0
  164. /package/dist/{mjs → cjs/image}/EditorImage.test.d.ts +0 -0
@@ -1,14 +1,13 @@
1
- import EditorImage from './EditorImage.mjs';
1
+ import EditorImage from './image/EditorImage.mjs';
2
2
  import ToolController from './tools/ToolController.mjs';
3
3
  import { EditorEventType } from './types.mjs';
4
4
  import { InputEvtType, keyUpEventFromHTMLEvent, keyPressEventFromHTMLEvent } from './inputEvents.mjs';
5
5
  import UndoRedoHistory from './UndoRedoHistory.mjs';
6
6
  import Viewport from './Viewport.mjs';
7
7
  import EventDispatcher from './EventDispatcher.mjs';
8
- import { Vec2, Vec3, Color4, Mat33, toRoundedString } from '@js-draw/math';
8
+ import { Vec2, Vec3, Color4, Mat33 } from '@js-draw/math';
9
9
  import Display, { RenderingMode } from './rendering/Display.mjs';
10
- import SVGRenderer from './rendering/renderers/SVGRenderer.mjs';
11
- import SVGLoader, { svgLoaderAutoresizeClassName } from './SVGLoader.mjs';
10
+ import SVGLoader from './SVGLoader.mjs';
12
11
  import Pointer from './Pointer.mjs';
13
12
  import getLocalizationTable from './localizations/getLocalizationTable.mjs';
14
13
  import IconProvider from './toolbar/IconProvider.mjs';
@@ -26,6 +25,8 @@ import StrokeKeyboardControl from './tools/InputFilter/StrokeKeyboardControl.m
26
25
  import guessKeyCodeFromKey from './util/guessKeyCodeFromKey.mjs';
27
26
  import makeAboutDialog from './dialogs/makeAboutDialog.mjs';
28
27
  import version from './version.mjs';
28
+ import { editorImageToSVGSync, editorImageToSVGAsync } from './image/export/editorImageToSVG.mjs';
29
+ import { MutableReactiveValue } from './util/ReactiveValue.mjs';
29
30
  /**
30
31
  * The main entrypoint for the full editor.
31
32
  *
@@ -110,6 +111,7 @@ export class Editor {
110
111
  if (this.settings.minZoom > this.settings.maxZoom) {
111
112
  throw new Error('Minimum zoom must be lesser than maximum zoom!');
112
113
  }
114
+ this.readOnly = MutableReactiveValue.fromInitialValue(false);
113
115
  this.icons = this.settings.iconProvider;
114
116
  this.shortcuts = new KeyboardShortcutManager(this.settings.keyboardShortcutOverrides);
115
117
  this.container = document.createElement('div');
@@ -660,6 +662,26 @@ export class Editor {
660
662
  };
661
663
  this.eventListenerTargets.push(elem);
662
664
  }
665
+ /**
666
+ * Attempts to prevent **user-triggered** events from modifying
667
+ * the content of the image.
668
+ */
669
+ setReadOnly(readOnly) {
670
+ if (readOnly !== this.readOnly.get()) {
671
+ this.readOnly.set(readOnly);
672
+ this.notifier.dispatch(EditorEventType.ReadOnlyModeToggled, {
673
+ kind: EditorEventType.ReadOnlyModeToggled,
674
+ editorIsReadOnly: readOnly,
675
+ });
676
+ }
677
+ }
678
+ // @internal
679
+ isReadOnlyReactiveValue() {
680
+ return this.readOnly;
681
+ }
682
+ isReadOnly() {
683
+ return this.readOnly;
684
+ }
663
685
  /** `apply` a command. `command` will be announced for accessibility. */
664
686
  dispatch(command, addToHistory = true) {
665
687
  const dispatchResult = this.dispatchNoAnnounce(command, addToHistory);
@@ -938,31 +960,42 @@ export class Editor {
938
960
  /**
939
961
  * Converts the editor's content into an SVG image.
940
962
  *
963
+ * If the output SVG has width or height less than `options.minDimension`, its size
964
+ * will be increased.
965
+ *
941
966
  * @see
942
967
  * {@link SVGRenderer}
943
968
  */
944
- toSVG() {
945
- const importExportViewport = this.image.getImportExportViewport().getTemporaryClone();
946
- const sanitize = false;
947
- const { element: result, renderer } = SVGRenderer.fromViewport(importExportViewport, sanitize);
948
- const origTransform = importExportViewport.canvasToScreenTransform;
949
- // Render with (0,0) at (0,0) — we'll handle translation with
950
- // the viewBox property.
951
- importExportViewport.resetTransform(Mat33.identity);
952
- this.image.renderAll(renderer);
953
- importExportViewport.resetTransform(origTransform);
954
- // Just show the main region
955
- const rect = importExportViewport.visibleRect;
956
- result.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => toRoundedString(part)).join(' '));
957
- result.setAttribute('width', toRoundedString(rect.w));
958
- result.setAttribute('height', toRoundedString(rect.h));
959
- if (this.image.getAutoresizeEnabled()) {
960
- result.classList.add(svgLoaderAutoresizeClassName);
961
- }
962
- else {
963
- result.classList.remove(svgLoaderAutoresizeClassName);
964
- }
965
- return result;
969
+ toSVG(options) {
970
+ return editorImageToSVGSync(this.image, options ?? {});
971
+ }
972
+ /**
973
+ * Converts the editor's content into an SVG image in an asynchronous,
974
+ * but **potentially lossy** way.
975
+ *
976
+ * **Warning**: If the image is being edited during an async rendering, edited components
977
+ * may not be rendered.
978
+ *
979
+ * Like {@link toSVG}, but can be configured to briefly pause after processing every
980
+ * `pauseAfterCount` items. This can prevent the editor from becoming unresponsive
981
+ * when saving very large images.
982
+ */
983
+ async toSVGAsync(options = {}) {
984
+ const pauseAfterCount = options.pauseAfterCount ?? 100;
985
+ return await editorImageToSVGAsync(this.image, async (_component, processedCount, totalComponents) => {
986
+ if (options.onProgress) {
987
+ const shouldContinue = await options.onProgress(processedCount, totalComponents);
988
+ if (shouldContinue === false) {
989
+ return false;
990
+ }
991
+ }
992
+ if (processedCount % pauseAfterCount === 0) {
993
+ await untilNextAnimationFrame();
994
+ }
995
+ return true;
996
+ }, {
997
+ minDimension: options.minDimension,
998
+ });
966
999
  }
967
1000
  /**
968
1001
  * Load editor data from an `ImageLoader` (e.g. an {@link SVGLoader}).
@@ -1114,10 +1147,14 @@ export class Editor {
1114
1147
  this.closeAboutDialog?.();
1115
1148
  this.closeAboutDialog = makeAboutDialog(this, notices).close;
1116
1149
  }
1117
- /** Removes and destroys the editor */
1150
+ /**
1151
+ * Removes and **destroys** the editor. The editor cannot be added to a parent
1152
+ * again after calling this method.
1153
+ */
1118
1154
  remove() {
1119
1155
  this.container.remove();
1120
1156
  // TODO: Is additional cleanup necessary here?
1157
+ this.toolController.onEditorDestroyed();
1121
1158
  }
1122
1159
  }
1123
1160
  export default Editor;
@@ -0,0 +1 @@
1
+ export {};
@@ -211,29 +211,44 @@ export default class SVGLoader {
211
211
  // If given, 'supportedAttrs' will have x, y, etc. attributes that were used in computing the transform added to it,
212
212
  // to prevent storing duplicate transform information when saving the component.
213
213
  getTransform(elem, supportedAttrs, computedStyles) {
214
- computedStyles ??= window.getComputedStyle(elem);
215
- let transformProperty = computedStyles.transform;
216
- if (transformProperty === '' || transformProperty === 'none') {
217
- transformProperty = elem.style.transform || 'none';
218
- }
219
- // Prefer the actual .style.transform
220
- // to the computed stylesheet -- in some browsers, the computedStyles version
221
- // can have lower precision.
214
+ // If possible, load the js-draw specific transform attribute
215
+ const highpTransformAttribute = 'data-highp-transform';
216
+ const rawTransformData = elem.getAttribute(highpTransformAttribute);
222
217
  let transform;
223
- try {
224
- transform = Mat33.fromCSSMatrix(elem.style.transform);
225
- }
226
- catch (_e) {
227
- transform = Mat33.fromCSSMatrix(transformProperty);
228
- }
229
- const elemX = elem.getAttribute('x');
230
- const elemY = elem.getAttribute('y');
231
- if (elemX || elemY) {
232
- const x = parseFloat(elemX ?? '0');
233
- const y = parseFloat(elemY ?? '0');
234
- if (!isNaN(x) && !isNaN(y)) {
235
- supportedAttrs?.push('x', 'y');
236
- transform = transform.rightMul(Mat33.translation(Vec2.of(x, y)));
218
+ if (rawTransformData) {
219
+ try {
220
+ transform = Mat33.fromCSSMatrix(rawTransformData);
221
+ supportedAttrs?.push(highpTransformAttribute);
222
+ }
223
+ catch (e) {
224
+ console.warn(`Unable to parse raw transform data, ${rawTransformData}. Falling back to CSS data.`);
225
+ }
226
+ }
227
+ if (!transform) {
228
+ computedStyles ??= window.getComputedStyle(elem);
229
+ let transformProperty = computedStyles.transform;
230
+ if (transformProperty === '' || transformProperty === 'none') {
231
+ transformProperty = elem.style.transform || 'none';
232
+ }
233
+ // Prefer the actual .style.transform
234
+ // to the computed stylesheet -- in some browsers, the computedStyles version
235
+ // can have lower precision.
236
+ try {
237
+ transform = Mat33.fromCSSMatrix(elem.style.transform);
238
+ }
239
+ catch (_e) {
240
+ console.warn('matrix parse error', _e);
241
+ transform = Mat33.fromCSSMatrix(transformProperty);
242
+ }
243
+ const elemX = elem.getAttribute('x');
244
+ const elemY = elem.getAttribute('y');
245
+ if (elemX || elemY) {
246
+ const x = parseFloat(elemX ?? '0');
247
+ const y = parseFloat(elemY ?? '0');
248
+ if (!isNaN(x) && !isNaN(y)) {
249
+ supportedAttrs?.push('x', 'y');
250
+ transform = transform.rightMul(Mat33.translation(Vec2.of(x, y)));
251
+ }
237
252
  }
238
253
  }
239
254
  return transform;
@@ -1,5 +1,5 @@
1
1
  import describeComponentList from '../components/util/describeComponentList.mjs';
2
- import EditorImage from '../EditorImage.mjs';
2
+ import EditorImage from '../image/EditorImage.mjs';
3
3
  import SerializableCommand from './SerializableCommand.mjs';
4
4
  /**
5
5
  * Removes the given {@link AbstractComponent}s from the image.
@@ -1,4 +1,4 @@
1
- import EditorImage from '../EditorImage';
1
+ import EditorImage from '../image/EditorImage';
2
2
  import AbstractComponent from '../components/AbstractComponent';
3
3
  import SerializableCommand from './SerializableCommand';
4
4
  export type ResolveFromComponentCallback = () => SerializableCommand;
@@ -1,5 +1,5 @@
1
1
  import SerializableCommand from '../commands/SerializableCommand';
2
- import EditorImage from '../EditorImage';
2
+ import EditorImage from '../image/EditorImage';
3
3
  import { LineSegment2, Mat33, Rect2 } from '@js-draw/math';
4
4
  import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
5
5
  import { ImageComponentLocalization } from './localization';
@@ -4,7 +4,7 @@ var __setFunctionName = (this && this.__setFunctionName) || function (f, name, p
4
4
  };
5
5
  var _a;
6
6
  import SerializableCommand from '../commands/SerializableCommand.mjs';
7
- import EditorImage from '../EditorImage.mjs';
7
+ import EditorImage from '../image/EditorImage.mjs';
8
8
  import { Mat33 } from '@js-draw/math';
9
9
  import UnresolvedSerializableCommand from '../commands/UnresolvedCommand.mjs';
10
10
  export var ComponentSizingMode;
@@ -1,5 +1,5 @@
1
1
  import Editor from '../Editor';
2
- import EditorImage from '../EditorImage';
2
+ import EditorImage from '../image/EditorImage';
3
3
  import SerializableCommand from '../commands/SerializableCommand';
4
4
  import { LineSegment2, Mat33, Rect2, Color4 } from '@js-draw/math';
5
5
  import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
@@ -1,4 +1,4 @@
1
- import { EditorImageEventType } from '../EditorImage.mjs';
1
+ import { EditorImageEventType } from '../image/EditorImage.mjs';
2
2
  import { Rect2, Color4, toRoundedString, Path, PathCommandType, Vec2 } from '@js-draw/math';
3
3
  import AbstractComponent, { ComponentSizingMode } from './AbstractComponent.mjs';
4
4
  import { createRestyleComponentCommand } from './RestylableComponent.mjs';
@@ -136,7 +136,8 @@ export default class BackgroundComponent extends AbstractComponent {
136
136
  }
137
137
  generateGridPath(visibleRect) {
138
138
  const contentBBox = this.getFullBoundingBox(visibleRect);
139
- const targetRect = visibleRect?.grownBy(this.gridStrokeWidth)?.intersection(contentBBox) ?? contentBBox;
139
+ // .grownBy acts on all sides, so we need only grow by strokeWidth / 2 (1 * the stroke radius)
140
+ const targetRect = (visibleRect?.intersection(contentBBox) ?? contentBBox).grownBy(this.gridStrokeWidth / 2);
140
141
  const roundDownToGrid = (coord) => Math.floor(coord / this.gridSize) * this.gridSize;
141
142
  const roundUpToGrid = (coord) => Math.ceil(coord / this.gridSize) * this.gridSize;
142
143
  const startY = roundUpToGrid(targetRect.y);
@@ -1,11 +1,11 @@
1
- import AbstractRenderer from './rendering/renderers/AbstractRenderer';
2
- import Viewport from './Viewport';
3
- import AbstractComponent from './components/AbstractComponent';
1
+ import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
2
+ import Viewport from '../Viewport';
3
+ import AbstractComponent from '../components/AbstractComponent';
4
4
  import { Rect2 } from '@js-draw/math';
5
- import RenderingCache from './rendering/caching/RenderingCache';
6
- import SerializableCommand from './commands/SerializableCommand';
7
- import EventDispatcher from './EventDispatcher';
8
- import Command from './commands/Command';
5
+ import RenderingCache from '../rendering/caching/RenderingCache';
6
+ import SerializableCommand from '../commands/SerializableCommand';
7
+ import EventDispatcher from '../EventDispatcher';
8
+ import Command from '../commands/Command';
9
9
  export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;
10
10
  export declare enum EditorImageEventType {
11
11
  ExportViewportChanged = 0,
@@ -14,6 +14,13 @@ export declare enum EditorImageEventType {
14
14
  export type EditorImageNotifier = EventDispatcher<EditorImageEventType, {
15
15
  image: EditorImage;
16
16
  }>;
17
+ /**
18
+ * A callback used to
19
+ * 1. pause the render process
20
+ * 2. observe progress through `componentsProcessed` and `totalComponents`
21
+ * 3. stop the render process early by returning `false`.
22
+ */
23
+ export type PreRenderComponentCallback = (component: AbstractComponent, componentsProcessed: number, totalComponents: number) => Promise<boolean>;
17
24
  export default class EditorImage {
18
25
  private root;
19
26
  private background;
@@ -36,7 +43,21 @@ export default class EditorImage {
36
43
  * the viewport used by the `renderer` (if any).
37
44
  */
38
45
  render(renderer: AbstractRenderer, viewport: Viewport | null): void;
39
- /** Renders all nodes, even ones not within the viewport. @internal */
46
+ /**
47
+ * Like {@link renderAll}, but can be stopped early and paused.
48
+ *
49
+ * **Note**: If the image is being edited during an async rendering, there is no
50
+ * guarantee that all nodes will be rendered correctly (some may be missing).
51
+ *
52
+ * @internal
53
+ */
54
+ renderAllAsync(renderer: AbstractRenderer, preRenderComponent: PreRenderComponentCallback): Promise<boolean>;
55
+ /**
56
+ * Renders all nodes, even ones not within the viewport.
57
+ *
58
+ * This can be slow for large images
59
+ * @internal
60
+ */
40
61
  renderAll(renderer: AbstractRenderer): void;
41
62
  /**
42
63
  * @returns all elements in the image, sorted by z-index. This can be slow for large images.
@@ -136,6 +157,7 @@ export declare class ImageNode {
136
157
  private rebalance;
137
158
  protected removeChild(child: ImageNode): void;
138
159
  remove(): void;
160
+ renderAllAsync(renderer: AbstractRenderer, preRenderComponent: PreRenderComponentCallback): Promise<boolean>;
139
161
  render(renderer: AbstractRenderer, visibleRect?: Rect2): void;
140
162
  renderDebugBoundingBoxes(renderer: AbstractRenderer, visibleRect: Rect2, depth?: number): void;
141
163
  }
@@ -3,13 +3,13 @@ var __setFunctionName = (this && this.__setFunctionName) || function (f, name, p
3
3
  return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
4
4
  };
5
5
  var _a, _b, _c;
6
- import Viewport from './Viewport.mjs';
7
- import AbstractComponent, { ComponentSizingMode } from './components/AbstractComponent.mjs';
6
+ import Viewport from '../Viewport.mjs';
7
+ import AbstractComponent, { ComponentSizingMode } from '../components/AbstractComponent.mjs';
8
8
  import { Rect2, Vec2, Mat33, Color4 } from '@js-draw/math';
9
- import SerializableCommand from './commands/SerializableCommand.mjs';
10
- import EventDispatcher from './EventDispatcher.mjs';
11
- import { assertIsBoolean, assertIsNumber, assertIsNumberArray } from './util/assertions.mjs';
12
- import Command from './commands/Command.mjs';
9
+ import SerializableCommand from '../commands/SerializableCommand.mjs';
10
+ import EventDispatcher from '../EventDispatcher.mjs';
11
+ import { assertIsBoolean, assertIsNumber, assertIsNumberArray } from '../util/assertions.mjs';
12
+ import Command from '../commands/Command.mjs';
13
13
  // @internal Sort by z-index, low to high
14
14
  export const sortLeavesByZIndex = (leaves) => {
15
15
  leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
@@ -87,7 +87,27 @@ class EditorImage {
87
87
  this.background.render(renderer, viewport?.visibleRect);
88
88
  this.root.render(renderer, viewport?.visibleRect);
89
89
  }
90
- /** Renders all nodes, even ones not within the viewport. @internal */
90
+ /**
91
+ * Like {@link renderAll}, but can be stopped early and paused.
92
+ *
93
+ * **Note**: If the image is being edited during an async rendering, there is no
94
+ * guarantee that all nodes will be rendered correctly (some may be missing).
95
+ *
96
+ * @internal
97
+ */
98
+ async renderAllAsync(renderer, preRenderComponent) {
99
+ const stoppedEarly = !(await this.background.renderAllAsync(renderer, preRenderComponent));
100
+ if (!stoppedEarly) {
101
+ return await this.root.renderAllAsync(renderer, preRenderComponent);
102
+ }
103
+ return false;
104
+ }
105
+ /**
106
+ * Renders all nodes, even ones not within the viewport.
107
+ *
108
+ * This can be slow for large images
109
+ * @internal
110
+ */
91
111
  renderAll(renderer) {
92
112
  this.render(renderer, null);
93
113
  }
@@ -610,6 +630,30 @@ export class ImageNode {
610
630
  this.content = null;
611
631
  this.children = [];
612
632
  }
633
+ // Creates a (potentially incomplete) async rendering of this image.
634
+ // Returns false if stopped early
635
+ async renderAllAsync(renderer,
636
+ // Used to pause/stop the renderer process
637
+ preRenderComponent) {
638
+ const leaves = this.getLeaves();
639
+ sortLeavesByZIndex(leaves);
640
+ const totalLeaves = leaves.length;
641
+ for (let leafIndex = 0; leafIndex < totalLeaves; leafIndex++) {
642
+ const leaf = leaves[leafIndex];
643
+ const component = leaf.getContent();
644
+ // Even though leaf was originally a leaf, it might not be any longer --
645
+ // rendering is async and the tree can change during that time.
646
+ if (!component) {
647
+ continue;
648
+ }
649
+ const shouldContinue = await preRenderComponent(component, leafIndex, totalLeaves);
650
+ if (!shouldContinue) {
651
+ return false;
652
+ }
653
+ component.render(renderer, undefined);
654
+ }
655
+ return true;
656
+ }
613
657
  render(renderer, visibleRect) {
614
658
  let leaves;
615
659
  if (visibleRect) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import EditorImage, { PreRenderComponentCallback } from '../EditorImage';
2
+ import { SVGSizingOptions } from './setExportedSVGSize';
3
+ export interface SVGExportOptions extends SVGSizingOptions {
4
+ sanitize?: boolean;
5
+ minDimension?: number;
6
+ }
7
+ export declare const editorImageToSVGSync: (image: EditorImage, options: SVGExportOptions) => SVGSVGElement;
8
+ export declare const editorImageToSVGAsync: (image: EditorImage, preRenderComponent: PreRenderComponentCallback, options: SVGExportOptions) => Promise<SVGElement>;
@@ -0,0 +1,41 @@
1
+ import { Mat33 } from '@js-draw/math';
2
+ import SVGRenderer from '../../rendering/renderers/SVGRenderer.mjs';
3
+ import { svgLoaderAutoresizeClassName } from '../../SVGLoader.mjs';
4
+ import setExportedSVGSize from './setExportedSVGSize.mjs';
5
+ const toSVGInternal = (image, renderFunction, options) => {
6
+ const importExportViewport = image.getImportExportViewport().getTemporaryClone();
7
+ const { element: result, renderer } = SVGRenderer.fromViewport(importExportViewport, options.sanitize ?? false);
8
+ const origTransform = importExportViewport.canvasToScreenTransform;
9
+ // Render with (0,0) at (0,0) — we'll handle translation with
10
+ // the viewBox property.
11
+ importExportViewport.resetTransform(Mat33.identity);
12
+ // Use a callback rather than async/await to allow this function to create
13
+ // both sync and async render functions
14
+ renderFunction(renderer, () => {
15
+ importExportViewport.resetTransform(origTransform);
16
+ if (image.getAutoresizeEnabled()) {
17
+ result.classList.add(svgLoaderAutoresizeClassName);
18
+ }
19
+ else {
20
+ result.classList.remove(svgLoaderAutoresizeClassName);
21
+ }
22
+ setExportedSVGSize(result, importExportViewport, options);
23
+ return result;
24
+ });
25
+ return result;
26
+ };
27
+ export const editorImageToSVGSync = (image, options) => {
28
+ return toSVGInternal(image, (renderer, onComplete) => {
29
+ image.renderAll(renderer);
30
+ onComplete();
31
+ }, options);
32
+ };
33
+ export const editorImageToSVGAsync = (image, preRenderComponent, options) => {
34
+ return new Promise(resolve => {
35
+ toSVGInternal(image, async (renderer, onComplete) => {
36
+ await image.renderAllAsync(renderer, preRenderComponent);
37
+ const result = onComplete();
38
+ resolve(result);
39
+ }, options);
40
+ });
41
+ };
@@ -0,0 +1,6 @@
1
+ import Viewport from '../../Viewport';
2
+ export type SVGSizingOptions = {
3
+ minDimension?: number;
4
+ };
5
+ declare const setExportedSVGSize: (svg: SVGElement, viewport: Viewport, options: SVGSizingOptions) => void;
6
+ export default setExportedSVGSize;
@@ -0,0 +1,23 @@
1
+ import { toRoundedString } from '@js-draw/math';
2
+ // @internal
3
+ const setExportedSVGSize = (svg, viewport, options) => {
4
+ // Just show the main region
5
+ const rect = viewport.visibleRect;
6
+ svg.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => toRoundedString(part)).join(' '));
7
+ // Adjust the width/height as necessary
8
+ let width = rect.w;
9
+ let height = rect.h;
10
+ if (options?.minDimension && width < options.minDimension) {
11
+ const newWidth = options.minDimension;
12
+ height *= newWidth / (width || 1);
13
+ width = newWidth;
14
+ }
15
+ if (options?.minDimension && height < options.minDimension) {
16
+ const newHeight = options.minDimension;
17
+ width *= newHeight / (height || 1);
18
+ height = newHeight;
19
+ }
20
+ svg.setAttribute('width', toRoundedString(width));
21
+ svg.setAttribute('height', toRoundedString(height));
22
+ };
23
+ export default setExportedSVGSize;
@@ -0,0 +1 @@
1
+ export { default as EditorImage } from './EditorImage';
@@ -0,0 +1 @@
1
+ export { default as EditorImage } from './EditorImage.mjs';
package/dist/mjs/lib.d.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * @packageDocumentation
16
16
  */
17
17
  import Editor, { EditorSettings } from './Editor';
18
- export { default as EditorImage } from './EditorImage';
18
+ export * from './image/lib';
19
19
  export * from './types';
20
20
  export * from './inputEvents';
21
21
  export { default as getLocalizationTable, matchingLocalizationTable } from './localizations/getLocalizationTable';
package/dist/mjs/lib.mjs CHANGED
@@ -15,7 +15,7 @@
15
15
  * @packageDocumentation
16
16
  */
17
17
  import Editor from './Editor.mjs';
18
- export { default as EditorImage } from './EditorImage.mjs';
18
+ export * from './image/lib.mjs';
19
19
  export * from './types.mjs';
20
20
  export * from './inputEvents.mjs';
21
21
  export { default as getLocalizationTable, matchingLocalizationTable } from './localizations/getLocalizationTable.mjs';
@@ -0,0 +1,6 @@
1
+ import { EditorLocalization } from '../localization';
2
+ /**
3
+ * Comments to help translators create translations.
4
+ */
5
+ declare const comments: Partial<Record<keyof EditorLocalization, string>>;
6
+ export default comments;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Comments to help translators create translations.
3
+ */
4
+ const comments = {
5
+ dragAndDropHereOrBrowse: 'Uses {{curly braces}} to denote bold text',
6
+ closeSidebar: 'Currently used as an accessibilty label',
7
+ };
8
+ export default comments;