js-draw 1.27.2 → 1.28.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 (112) hide show
  1. package/README.md +1 -1
  2. package/dist/Editor.css +1 -1
  3. package/dist/bundle.js +28 -28
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/Editor.d.ts +7 -2
  6. package/dist/cjs/Editor.js +11 -5
  7. package/dist/cjs/SVGLoader/SVGLoader.d.ts +21 -0
  8. package/dist/cjs/SVGLoader/SVGLoader.js +74 -47
  9. package/dist/cjs/SVGLoader/SVGLoader.plugins.test.d.ts +1 -0
  10. package/dist/cjs/Viewport.js +2 -32
  11. package/dist/cjs/commands/Duplicate.d.ts +7 -4
  12. package/dist/cjs/commands/Duplicate.js +48 -7
  13. package/dist/cjs/commands/Duplicate.test.d.ts +1 -0
  14. package/dist/cjs/commands/Erase.d.ts +1 -1
  15. package/dist/cjs/commands/Erase.js +2 -2
  16. package/dist/cjs/commands/localization.d.ts +2 -2
  17. package/dist/cjs/commands/localization.js +2 -2
  18. package/dist/cjs/components/AbstractComponent.d.ts +7 -0
  19. package/dist/cjs/components/AbstractComponent.js +16 -2
  20. package/dist/cjs/components/Stroke.d.ts +21 -1
  21. package/dist/cjs/components/Stroke.js +29 -0
  22. package/dist/cjs/components/TextComponent.d.ts +2 -2
  23. package/dist/cjs/components/TextComponent.js +2 -2
  24. package/dist/cjs/image/EditorImage.d.ts +17 -9
  25. package/dist/cjs/image/EditorImage.js +33 -17
  26. package/dist/cjs/lib.d.ts +1 -1
  27. package/dist/cjs/localizations/de.js +2 -2
  28. package/dist/cjs/rendering/RenderingStyle.d.ts +7 -6
  29. package/dist/cjs/rendering/lib.d.ts +1 -1
  30. package/dist/cjs/rendering/renderers/AbstractRenderer.js +4 -0
  31. package/dist/cjs/rendering/renderers/CanvasRenderer.d.ts +9 -0
  32. package/dist/cjs/rendering/renderers/CanvasRenderer.js +14 -0
  33. package/dist/cjs/rendering/renderers/SVGRenderer.d.ts +18 -0
  34. package/dist/cjs/rendering/renderers/SVGRenderer.js +21 -1
  35. package/dist/cjs/toolbar/utils/HelpDisplay.js +6 -4
  36. package/dist/cjs/toolbar/utils/localization.d.ts +1 -0
  37. package/dist/cjs/toolbar/utils/localization.js +1 -0
  38. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +1 -1
  39. package/dist/cjs/toolbar/widgets/InsertImageWidget/InsertImageWidget.js +1 -1
  40. package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +1 -1
  41. package/dist/cjs/tools/Eraser.js +3 -3
  42. package/dist/cjs/tools/FindTool.js +1 -1
  43. package/dist/cjs/tools/PasteHandler.js +4 -1
  44. package/dist/cjs/tools/Pen.js +1 -1
  45. package/dist/cjs/tools/SelectionTool/SelectAllShortcutHandler.js +1 -1
  46. package/dist/cjs/tools/SelectionTool/Selection.js +23 -10
  47. package/dist/cjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.js +1 -1
  48. package/dist/cjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.js +1 -1
  49. package/dist/cjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.js +1 -1
  50. package/dist/cjs/tools/SelectionTool/SelectionTool.js +3 -2
  51. package/dist/cjs/tools/SoundUITool.js +1 -1
  52. package/dist/cjs/tools/TextTool.js +2 -2
  53. package/dist/cjs/util/assertions.d.ts +6 -0
  54. package/dist/cjs/util/assertions.js +18 -0
  55. package/dist/cjs/util/describeTransformation.d.ts +12 -0
  56. package/dist/cjs/util/describeTransformation.js +44 -0
  57. package/dist/cjs/version.js +1 -1
  58. package/dist/mjs/Editor.d.ts +7 -2
  59. package/dist/mjs/Editor.mjs +11 -5
  60. package/dist/mjs/SVGLoader/SVGLoader.d.ts +21 -0
  61. package/dist/mjs/SVGLoader/SVGLoader.mjs +74 -47
  62. package/dist/mjs/SVGLoader/SVGLoader.plugins.test.d.ts +1 -0
  63. package/dist/mjs/Viewport.mjs +2 -32
  64. package/dist/mjs/commands/Duplicate.d.ts +7 -4
  65. package/dist/mjs/commands/Duplicate.mjs +48 -7
  66. package/dist/mjs/commands/Duplicate.test.d.ts +1 -0
  67. package/dist/mjs/commands/Erase.d.ts +1 -1
  68. package/dist/mjs/commands/Erase.mjs +2 -2
  69. package/dist/mjs/commands/localization.d.ts +2 -2
  70. package/dist/mjs/commands/localization.mjs +2 -2
  71. package/dist/mjs/components/AbstractComponent.d.ts +7 -0
  72. package/dist/mjs/components/AbstractComponent.mjs +17 -3
  73. package/dist/mjs/components/Stroke.d.ts +21 -1
  74. package/dist/mjs/components/Stroke.mjs +31 -2
  75. package/dist/mjs/components/TextComponent.d.ts +2 -2
  76. package/dist/mjs/components/TextComponent.mjs +2 -2
  77. package/dist/mjs/image/EditorImage.d.ts +17 -9
  78. package/dist/mjs/image/EditorImage.mjs +33 -17
  79. package/dist/mjs/lib.d.ts +1 -1
  80. package/dist/mjs/localizations/de.mjs +2 -2
  81. package/dist/mjs/rendering/RenderingStyle.d.ts +7 -6
  82. package/dist/mjs/rendering/lib.d.ts +1 -1
  83. package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +4 -0
  84. package/dist/mjs/rendering/renderers/CanvasRenderer.d.ts +9 -0
  85. package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +14 -0
  86. package/dist/mjs/rendering/renderers/SVGRenderer.d.ts +18 -0
  87. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +21 -1
  88. package/dist/mjs/toolbar/utils/HelpDisplay.mjs +6 -4
  89. package/dist/mjs/toolbar/utils/localization.d.ts +1 -0
  90. package/dist/mjs/toolbar/utils/localization.mjs +1 -0
  91. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +1 -1
  92. package/dist/mjs/toolbar/widgets/InsertImageWidget/InsertImageWidget.mjs +1 -1
  93. package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +1 -1
  94. package/dist/mjs/tools/Eraser.mjs +3 -3
  95. package/dist/mjs/tools/FindTool.mjs +1 -1
  96. package/dist/mjs/tools/PasteHandler.mjs +4 -1
  97. package/dist/mjs/tools/Pen.mjs +1 -1
  98. package/dist/mjs/tools/SelectionTool/SelectAllShortcutHandler.mjs +1 -1
  99. package/dist/mjs/tools/SelectionTool/Selection.mjs +23 -10
  100. package/dist/mjs/tools/SelectionTool/SelectionBuilders/LassoSelectionBuilder.mjs +1 -1
  101. package/dist/mjs/tools/SelectionTool/SelectionBuilders/RectSelectionBuilder.mjs +1 -1
  102. package/dist/mjs/tools/SelectionTool/SelectionBuilders/SelectionBuilder.mjs +1 -1
  103. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +3 -2
  104. package/dist/mjs/tools/SoundUITool.mjs +1 -1
  105. package/dist/mjs/tools/TextTool.mjs +2 -2
  106. package/dist/mjs/util/assertions.d.ts +6 -0
  107. package/dist/mjs/util/assertions.mjs +16 -0
  108. package/dist/mjs/util/describeTransformation.d.ts +12 -0
  109. package/dist/mjs/util/describeTransformation.mjs +42 -0
  110. package/dist/mjs/version.mjs +1 -1
  111. package/package.json +4 -4
  112. package/src/toolbar/utils/HelpDisplay.scss +7 -1
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const describeComponentList_1 = __importDefault(require("../components/util/describeComponentList"));
7
+ const assertions_1 = require("../util/assertions");
7
8
  const Erase_1 = __importDefault(require("./Erase"));
8
9
  const SerializableCommand_1 = __importDefault(require("./SerializableCommand"));
9
10
  /**
@@ -16,7 +17,7 @@ const SerializableCommand_1 = __importDefault(require("./SerializableCommand"));
16
17
  *
17
18
  * // Find all elements intersecting the rectangle with top left (0,0) and
18
19
  * // (width,height)=(100,100).
19
- * const elems = editor.image.getElementsIntersectingRegion(
20
+ * const elems = editor.image.getComponentsIntersecting(
20
21
  * new Rect2(0, 0, 100, 100)
21
22
  * );
22
23
  *
@@ -27,13 +28,24 @@ const SerializableCommand_1 = __importDefault(require("./SerializableCommand"));
27
28
  * editor.dispatch(duplicateElems);
28
29
  * ```
29
30
  *
30
- * @see {@link Editor.dispatch} {@link EditorImage.getElementsIntersectingRegion}
31
+ * @see {@link Editor.dispatch} {@link EditorImage.getComponentsIntersecting}
31
32
  */
32
33
  class Duplicate extends SerializableCommand_1.default {
33
- constructor(toDuplicate) {
34
+ constructor(toDuplicate,
35
+ // @internal -- IDs given to the duplicate elements
36
+ idsForDuplicates) {
34
37
  super('duplicate');
35
38
  this.toDuplicate = toDuplicate;
36
- this.duplicates = toDuplicate.map((elem) => elem.clone());
39
+ this.duplicates = toDuplicate.map((elem, idx) => {
40
+ // For collaborative editing, it's important for the clones to have
41
+ // the same IDs as the originals
42
+ if (idsForDuplicates && idsForDuplicates[idx]) {
43
+ return elem.cloneWithId(idsForDuplicates[idx]);
44
+ }
45
+ else {
46
+ return elem.clone();
47
+ }
48
+ });
37
49
  this.reverse = new Erase_1.default(this.duplicates);
38
50
  }
39
51
  apply(editor) {
@@ -52,13 +64,42 @@ class Duplicate extends SerializableCommand_1.default {
52
64
  return localizationTable.duplicateAction((0, describeComponentList_1.default)(localizationTable, this.duplicates) ?? localizationTable.elements, this.duplicates.length);
53
65
  }
54
66
  serializeToJSON() {
55
- return this.toDuplicate.map((elem) => elem.getId());
67
+ return {
68
+ originalIds: this.toDuplicate.map((elem) => elem.getId()),
69
+ cloneIds: this.duplicates.map((elem) => elem.getId()),
70
+ };
56
71
  }
57
72
  }
58
73
  (() => {
59
74
  SerializableCommand_1.default.register('duplicate', (json, editor) => {
60
- const elems = json.map((id) => editor.image.lookupElement(id));
61
- return new Duplicate(elems);
75
+ let originalIds;
76
+ let cloneIds;
77
+ // Compatibility with older editors
78
+ if (Array.isArray(json)) {
79
+ originalIds = json;
80
+ cloneIds = [];
81
+ }
82
+ else {
83
+ originalIds = json.originalIds;
84
+ cloneIds = json.cloneIds;
85
+ }
86
+ (0, assertions_1.assertIsStringArray)(originalIds);
87
+ (0, assertions_1.assertIsStringArray)(cloneIds);
88
+ // Resolve to elements -- only keep the elements that can be found in the image.
89
+ const resolvedElements = [];
90
+ const filteredCloneIds = [];
91
+ for (let i = 0; i < originalIds.length; i++) {
92
+ const originalId = originalIds[i];
93
+ const foundElement = editor.image.lookupElement(originalId);
94
+ if (!foundElement) {
95
+ console.warn('Duplicate command: Could not find element with ID', originalId);
96
+ }
97
+ else {
98
+ filteredCloneIds.push(cloneIds[i]);
99
+ resolvedElements.push(foundElement);
100
+ }
101
+ }
102
+ return new Duplicate(resolvedElements, filteredCloneIds);
62
103
  });
63
104
  })();
64
105
  exports.default = Duplicate;
@@ -0,0 +1 @@
1
+ export {};
@@ -31,7 +31,7 @@ import SerializableCommand from './SerializableCommand';
31
31
  *
32
32
  * // Find all elements intersecting the rectangle with top left (-10,-30) and
33
33
  * // (width,height)=(50,100).
34
- * const elems = editor.image.getElementsIntersectingRegion(
34
+ * const elems = editor.image.getComponentsIntersecting(
35
35
  * new Rect2(-10, -30, 50, 100)
36
36
  * );
37
37
  *
@@ -36,7 +36,7 @@ const SerializableCommand_1 = __importDefault(require("./SerializableCommand"));
36
36
  *
37
37
  * // Find all elements intersecting the rectangle with top left (-10,-30) and
38
38
  * // (width,height)=(50,100).
39
- * const elems = editor.image.getElementsIntersectingRegion(
39
+ * const elems = editor.image.getComponentsIntersecting(
40
40
  * new Rect2(-10, -30, 50, 100)
41
41
  * );
42
42
  *
@@ -68,7 +68,7 @@ class Erase extends SerializableCommand_1.default {
68
68
  unapply(editor) {
69
69
  for (const part of this.toRemove) {
70
70
  if (!editor.image.findParent(part)) {
71
- EditorImage_1.default.addElement(part).apply(editor);
71
+ EditorImage_1.default.addComponent(part).apply(editor);
72
72
  }
73
73
  }
74
74
  this.applied = false;
@@ -11,11 +11,11 @@ export interface CommandLocalization {
11
11
  duplicatedNoElements: string;
12
12
  elements: string;
13
13
  updatedViewport: string;
14
- transformedElements: (elemCount: number) => string;
14
+ transformedElements: (elemCount: number, transformDescription: string) => string;
15
15
  resizeOutputCommand: (newSize: Rect2) => string;
16
16
  enabledAutoresizeOutputCommand: string;
17
17
  disabledAutoresizeOutputCommand: string;
18
- addElementAction: (elemDescription: string) => string;
18
+ addComponentAction: (elemDescription: string) => string;
19
19
  eraseAction: (elemDescription: string, numElems: number) => string;
20
20
  duplicateAction: (elemDescription: string, count: number) => string;
21
21
  inverseOf: (actionDescription: string) => string;
@@ -3,11 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.defaultCommandLocalization = void 0;
4
4
  exports.defaultCommandLocalization = {
5
5
  updatedViewport: 'Transformed Viewport',
6
- transformedElements: (elemCount) => `Transformed ${elemCount} element${elemCount === 1 ? '' : 's'}`,
6
+ transformedElements: (elemCount, action) => `Transformed ${elemCount} element${elemCount === 1 ? '' : 's'} (${action})`,
7
7
  resizeOutputCommand: (newSize) => `Resized image to ${newSize.w}x${newSize.h}`,
8
8
  enabledAutoresizeOutputCommand: 'Enabled output autoresize',
9
9
  disabledAutoresizeOutputCommand: 'Disabled output autoresize',
10
- addElementAction: (componentDescription) => `Added ${componentDescription}`,
10
+ addComponentAction: (componentDescription) => `Added ${componentDescription}`,
11
11
  eraseAction: (componentDescription, numElems) => `Erased ${numElems} ${componentDescription}`,
12
12
  duplicateAction: (componentDescription, numElems) => `Duplicated ${numElems} ${componentDescription}`,
13
13
  unionOf: (actionDescription, actionCount) => `Union: ${actionCount} ${actionDescription}`,
@@ -158,6 +158,13 @@ export default abstract class AbstractComponent {
158
158
  abstract description(localizationTable: ImageComponentLocalization): string;
159
159
  protected abstract createClone(): AbstractComponent;
160
160
  clone(): AbstractComponent;
161
+ /**
162
+ * Creates a copy of this component with a particular `id`.
163
+ * This is used internally by {@link Duplicate} when deserializing.
164
+ *
165
+ * @internal -- users of the library shouldn't need this.
166
+ */
167
+ cloneWithId(cloneId: string): AbstractComponent;
161
168
  /**
162
169
  * **Optional method**: Divides this component into sections roughly along the given path,
163
170
  * removing parts that are roughly within `shape`.
@@ -13,6 +13,8 @@ const SerializableCommand_1 = __importDefault(require("../commands/SerializableC
13
13
  const EditorImage_1 = __importDefault(require("../image/EditorImage"));
14
14
  const math_1 = require("@js-draw/math");
15
15
  const UnresolvedCommand_1 = __importDefault(require("../commands/UnresolvedCommand"));
16
+ const describeTransformation_1 = __importDefault(require("../util/describeTransformation"));
17
+ const assertions_1 = require("../util/assertions");
16
18
  var ComponentSizingMode;
17
19
  (function (ComponentSizingMode) {
18
20
  /** The default. The compnent gets its size from its bounding box. */
@@ -207,6 +209,17 @@ class AbstractComponent {
207
209
  }
208
210
  return clone;
209
211
  }
212
+ /**
213
+ * Creates a copy of this component with a particular `id`.
214
+ * This is used internally by {@link Duplicate} when deserializing.
215
+ *
216
+ * @internal -- users of the library shouldn't need this.
217
+ */
218
+ cloneWithId(cloneId) {
219
+ const clone = this.clone();
220
+ clone.id = cloneId;
221
+ return clone;
222
+ }
210
223
  // Convert the component to an object that can be passed to
211
224
  // `JSON.stringify`.
212
225
  //
@@ -250,6 +263,7 @@ class AbstractComponent {
250
263
  if (AbstractComponent.isNotDeserializable(json)) {
251
264
  throw new Error(`Element with data ${json} cannot be deserialized.`);
252
265
  }
266
+ (0, assertions_1.assertIsString)(json.id);
253
267
  const instance = this.deserializationCallbacks[json.name](json.data);
254
268
  instance.id = json.id;
255
269
  if (isFinite(json.zIndex)) {
@@ -313,7 +327,7 @@ AbstractComponent.TransformElementCommand = (_a = class extends UnresolvedComman
313
327
  }
314
328
  // Add the element back to the document.
315
329
  if (hadParent) {
316
- EditorImage_1.default.addElement(this.component).apply(editor);
330
+ EditorImage_1.default.addComponent(this.component).apply(editor);
317
331
  }
318
332
  }
319
333
  apply(editor) {
@@ -327,7 +341,7 @@ AbstractComponent.TransformElementCommand = (_a = class extends UnresolvedComman
327
341
  editor.queueRerender();
328
342
  }
329
343
  description(_editor, localizationTable) {
330
- return localizationTable.transformedElements(1);
344
+ return localizationTable.transformedElements(1, (0, describeTransformation_1.default)(math_1.Vec2.zero, this.affineTransfm, false, localizationTable));
331
345
  }
332
346
  serializeToJSON() {
333
347
  return {
@@ -1,7 +1,8 @@
1
1
  import SerializableCommand from '../commands/SerializableCommand';
2
- import { Mat33, Path, Rect2, LineSegment2 } from '@js-draw/math';
2
+ import { Mat33, Path, Rect2, LineSegment2, Color4 } from '@js-draw/math';
3
3
  import Editor from '../Editor';
4
4
  import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
5
+ import { StrokeStyle } from '../rendering/RenderingStyle';
5
6
  import AbstractComponent from './AbstractComponent';
6
7
  import { ImageComponentLocalization } from './localization';
7
8
  import RestyleableComponent, { ComponentStyle } from './RestylableComponent';
@@ -48,6 +49,25 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
48
49
  * ```
49
50
  */
50
51
  constructor(parts: RenderablePathSpec[], initialZIndex?: number);
52
+ /**
53
+ * Creates a new `Stroke` from a {@link Path} and `style`. Strokes created
54
+ * with this method have transparent fill.
55
+ *
56
+ * Example:
57
+ * ```ts,runnable
58
+ * import { Editor, Stroke, Color4 } from 'js-draw';
59
+ * const editor = new Editor(document.body);
60
+ * ---visible---
61
+ * const stroke = Stroke.fromStroked('m0,0 l10,10', { width: 10, color: Color4.red });
62
+ * editor.dispatch(editor.image.addComponent(stroke));
63
+ * ```
64
+ * Notice that `path` can be a string that specifies an SVG path
65
+ *
66
+ * @see fromFilled
67
+ */
68
+ static fromStroked(path: Path | string, style: StrokeStyle): Stroke;
69
+ /** @see fromStroked */
70
+ static fromFilled(path: Path | string, fill: Color4): Stroke;
51
71
  getStyle(): ComponentStyle;
52
72
  updateStyle(style: ComponentStyle): SerializableCommand;
53
73
  forceStyle(style: ComponentStyle, editor: Editor | null): void;
@@ -74,6 +74,35 @@ class Stroke extends AbstractComponent_1.default {
74
74
  }
75
75
  this.contentBBox ??= math_1.Rect2.empty;
76
76
  }
77
+ /**
78
+ * Creates a new `Stroke` from a {@link Path} and `style`. Strokes created
79
+ * with this method have transparent fill.
80
+ *
81
+ * Example:
82
+ * ```ts,runnable
83
+ * import { Editor, Stroke, Color4 } from 'js-draw';
84
+ * const editor = new Editor(document.body);
85
+ * ---visible---
86
+ * const stroke = Stroke.fromStroked('m0,0 l10,10', { width: 10, color: Color4.red });
87
+ * editor.dispatch(editor.image.addComponent(stroke));
88
+ * ```
89
+ * Notice that `path` can be a string that specifies an SVG path
90
+ *
91
+ * @see fromFilled
92
+ */
93
+ static fromStroked(path, style) {
94
+ if (typeof path === 'string') {
95
+ path = math_1.Path.fromString(path);
96
+ }
97
+ return new Stroke([(0, RenderablePathSpec_1.pathToRenderable)(path, { fill: math_1.Color4.transparent, stroke: style })]);
98
+ }
99
+ /** @see fromStroked */
100
+ static fromFilled(path, fill) {
101
+ if (typeof path === 'string') {
102
+ path = math_1.Path.fromString(path);
103
+ }
104
+ return new Stroke([(0, RenderablePathSpec_1.pathToRenderable)(path, { fill })]);
105
+ }
77
106
  getStyle() {
78
107
  if (this.parts.length === 0) {
79
108
  return {};
@@ -38,7 +38,7 @@ type TextElement = TextComponent | string;
38
38
  * };
39
39
  *
40
40
  * editor.dispatch(
41
- * editor.image.addElement(new TextComponent(['Hello, world'], positioning1, style)),
41
+ * editor.image.addComponent(new TextComponent(['Hello, world'], positioning1, style)),
42
42
  * );
43
43
  *
44
44
  *
@@ -49,7 +49,7 @@ type TextElement = TextComponent | string;
49
49
  * // is placed directly after 'Test'.
50
50
  * const positioning2 = Mat33.translation(Vec2.of(10, 50));
51
51
  * editor.dispatch(
52
- * editor.image.addElement(
52
+ * editor.image.addComponent(
53
53
  * new TextComponent([ new TextComponent(['Test'], positioning1, style), '[Test]' ], positioning2, style)
54
54
  * ),
55
55
  * );
@@ -46,7 +46,7 @@ const defaultTextStyle = {
46
46
  * };
47
47
  *
48
48
  * editor.dispatch(
49
- * editor.image.addElement(new TextComponent(['Hello, world'], positioning1, style)),
49
+ * editor.image.addComponent(new TextComponent(['Hello, world'], positioning1, style)),
50
50
  * );
51
51
  *
52
52
  *
@@ -57,7 +57,7 @@ const defaultTextStyle = {
57
57
  * // is placed directly after 'Test'.
58
58
  * const positioning2 = Mat33.translation(Vec2.of(10, 50));
59
59
  * editor.dispatch(
60
- * editor.image.addElement(
60
+ * editor.image.addComponent(
61
61
  * new TextComponent([ new TextComponent(['Test'], positioning1, style), '[Test]' ], positioning2, style)
62
62
  * ),
63
63
  * );
@@ -22,7 +22,7 @@ export type EditorImageNotifier = EventDispatcher<EditorImageEventType, {
22
22
  */
23
23
  export type PreRenderComponentCallback = (component: AbstractComponent, componentsProcessed: number, totalComponents: number) => Promise<boolean>;
24
24
  /**
25
- * @summary Handles lookup/storage of elements in the image.
25
+ * Handles lookup/storage of elements in the image.
26
26
  *
27
27
  * `js-draw` images are made up of a collection of {@link AbstractComponent}s (which
28
28
  * includes {@link Stroke}s, {@link TextComponent}s, etc.). An `EditorImage`
@@ -30,9 +30,9 @@ export type PreRenderComponentCallback = (component: AbstractComponent, componen
30
30
  *
31
31
  * Here's how to do a few common operations:
32
32
  * - **Get all components in a {@link @js-draw/math!Rect2 | Rect2}**:
33
- * {@link EditorImage.getElementsIntersectingRegion}.
33
+ * {@link EditorImage.getComponentsIntersecting}.
34
34
  * - **Draw an `EditorImage` onto a canvas/SVG**: {@link EditorImage.render}.
35
- * - **Adding a new component**: {@link EditorImage.addElement}.
35
+ * - **Adding a new component**: {@link EditorImage.addComponent}.
36
36
  *
37
37
  * **Example**:
38
38
  * [[include:doc-pages/inline-examples/image-add-and-lookup.md]]
@@ -82,19 +82,23 @@ export default class EditorImage {
82
82
  * @returns all elements in the image, sorted by z-index (low to high).
83
83
  *
84
84
  * This can be slow for large images. If you only need all elemenst in part of the image,
85
- * consider using {@link getElementsIntersectingRegion} instead.
85
+ * consider using {@link getComponentsIntersecting} instead.
86
86
  *
87
87
  * **Note**: The result does not include background elements. See {@link getBackgroundComponents}.
88
88
  */
89
+ getAllComponents(): AbstractComponent[];
90
+ /** @deprecated in favor of {@link getAllComponents} */
89
91
  getAllElements(): AbstractComponent[];
90
92
  /** Returns the number of elements added to this image. @internal */
91
93
  estimateNumElements(): number;
94
+ /** @deprecated @see getComponentsIntersecting */
95
+ getElementsIntersectingRegion(region: Rect2, includeBackground?: boolean): AbstractComponent[];
92
96
  /**
93
97
  * @returns a list of `AbstractComponent`s intersecting `region`, sorted by increasing z-index.
94
98
  *
95
99
  * Components in the background layer are only included if `includeBackground` is `true`.
96
100
  */
97
- getElementsIntersectingRegion(region: Rect2, includeBackground?: boolean): AbstractComponent[];
101
+ getComponentsIntersecting(region: Rect2, includeBackground?: boolean): AbstractComponent[];
98
102
  /** Called whenever (just after) an element is completely removed. @internal */
99
103
  onDestroyElement(elem: AbstractComponent): void;
100
104
  /** Called just after an element is added. @internal */
@@ -105,7 +109,7 @@ export default class EditorImage {
105
109
  * @see {@link AbstractComponent.getId}
106
110
  */
107
111
  lookupElement(id: string): AbstractComponent | null;
108
- private addElementDirectly;
112
+ private addComponentDirectly;
109
113
  private removeElementDirectly;
110
114
  /**
111
115
  * Returns a command that adds the given element to the `EditorImage`.
@@ -118,10 +122,14 @@ export default class EditorImage {
118
122
  *
119
123
  * [[include:doc-pages/inline-examples/adding-a-stroke.md]]
120
124
  */
121
- static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
122
- /** @see EditorImage.addElement */
125
+ static addComponent(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
126
+ /** @see EditorImage.addComponent */
127
+ addComponent(component: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
128
+ /** Alias for {@link addComponent}. @deprecated Prefer `.addComponent` */
123
129
  addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
124
- private static AddElementCommand;
130
+ /** Alias for {@link addComponent}. @deprecated Prefer `.addComponent`. */
131
+ static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
132
+ private static AddComponentCommand;
125
133
  /**
126
134
  * @returns a `Viewport` for rendering the image when importing/exporting.
127
135
  */
@@ -61,7 +61,7 @@ var EditorImageEventType;
61
61
  })(EditorImageEventType || (exports.EditorImageEventType = EditorImageEventType = {}));
62
62
  let debugMode = false;
63
63
  /**
64
- * @summary Handles lookup/storage of elements in the image.
64
+ * Handles lookup/storage of elements in the image.
65
65
  *
66
66
  * `js-draw` images are made up of a collection of {@link AbstractComponent}s (which
67
67
  * includes {@link Stroke}s, {@link TextComponent}s, etc.). An `EditorImage`
@@ -69,9 +69,9 @@ let debugMode = false;
69
69
  *
70
70
  * Here's how to do a few common operations:
71
71
  * - **Get all components in a {@link @js-draw/math!Rect2 | Rect2}**:
72
- * {@link EditorImage.getElementsIntersectingRegion}.
72
+ * {@link EditorImage.getComponentsIntersecting}.
73
73
  * - **Draw an `EditorImage` onto a canvas/SVG**: {@link EditorImage.render}.
74
- * - **Adding a new component**: {@link EditorImage.addElement}.
74
+ * - **Adding a new component**: {@link EditorImage.addComponent}.
75
75
  *
76
76
  * **Example**:
77
77
  * [[include:doc-pages/inline-examples/image-add-and-lookup.md]]
@@ -118,7 +118,7 @@ class EditorImage {
118
118
  const parent = this.findParent(elem);
119
119
  if (parent) {
120
120
  parent.remove();
121
- this.addElementDirectly(elem);
121
+ this.addComponentDirectly(elem);
122
122
  }
123
123
  }
124
124
  /** @internal */
@@ -173,25 +173,33 @@ class EditorImage {
173
173
  * @returns all elements in the image, sorted by z-index (low to high).
174
174
  *
175
175
  * This can be slow for large images. If you only need all elemenst in part of the image,
176
- * consider using {@link getElementsIntersectingRegion} instead.
176
+ * consider using {@link getComponentsIntersecting} instead.
177
177
  *
178
178
  * **Note**: The result does not include background elements. See {@link getBackgroundComponents}.
179
179
  */
180
- getAllElements() {
180
+ getAllComponents() {
181
181
  const leaves = this.root.getLeaves();
182
182
  (0, exports.sortLeavesByZIndex)(leaves);
183
183
  return leaves.map((leaf) => leaf.getContent());
184
184
  }
185
+ /** @deprecated in favor of {@link getAllComponents} */
186
+ getAllElements() {
187
+ return this.getAllComponents();
188
+ }
185
189
  /** Returns the number of elements added to this image. @internal */
186
190
  estimateNumElements() {
187
191
  return this.componentCount;
188
192
  }
193
+ /** @deprecated @see getComponentsIntersecting */
194
+ getElementsIntersectingRegion(region, includeBackground = false) {
195
+ return this.getComponentsIntersecting(region, includeBackground);
196
+ }
189
197
  /**
190
198
  * @returns a list of `AbstractComponent`s intersecting `region`, sorted by increasing z-index.
191
199
  *
192
200
  * Components in the background layer are only included if `includeBackground` is `true`.
193
201
  */
194
- getElementsIntersectingRegion(region, includeBackground = false) {
202
+ getComponentsIntersecting(region, includeBackground = false) {
195
203
  let leaves = this.root.getLeavesIntersectingRegion(region);
196
204
  if (includeBackground) {
197
205
  leaves = leaves.concat(this.background.getLeavesIntersectingRegion(region));
@@ -219,7 +227,7 @@ class EditorImage {
219
227
  lookupElement(id) {
220
228
  return this.componentsById[id] ?? null;
221
229
  }
222
- addElementDirectly(elem) {
230
+ addComponentDirectly(elem) {
223
231
  // Because onAddToImage can affect the element's bounding box,
224
232
  // this needs to be called before parentTree.addLeaf.
225
233
  elem.onAddToImage(this);
@@ -250,12 +258,20 @@ class EditorImage {
250
258
  *
251
259
  * [[include:doc-pages/inline-examples/adding-a-stroke.md]]
252
260
  */
253
- static addElement(elem, applyByFlattening = false) {
254
- return new _a.AddElementCommand(elem, applyByFlattening);
261
+ static addComponent(elem, applyByFlattening = false) {
262
+ return new _a.AddComponentCommand(elem, applyByFlattening);
263
+ }
264
+ /** @see EditorImage.addComponent */
265
+ addComponent(component, applyByFlattening) {
266
+ return _a.addComponent(component, applyByFlattening);
255
267
  }
256
- /** @see EditorImage.addElement */
268
+ /** Alias for {@link addComponent}. @deprecated Prefer `.addComponent` */
257
269
  addElement(elem, applyByFlattening) {
258
- return _a.addElement(elem, applyByFlattening);
270
+ return this.addComponent(elem, applyByFlattening);
271
+ }
272
+ /** Alias for {@link addComponent}. @deprecated Prefer `.addComponent`. */
273
+ static addElement(elem, applyByFlattening = false) {
274
+ return this.addComponent(elem, applyByFlattening);
259
275
  }
260
276
  /**
261
277
  * @returns a `Viewport` for rendering the image when importing/exporting.
@@ -386,7 +402,7 @@ class EditorImage {
386
402
  }
387
403
  _a = EditorImage;
388
404
  // A Command that can access private [EditorImage] functionality
389
- EditorImage.AddElementCommand = (_b = class extends SerializableCommand_1.default {
405
+ EditorImage.AddComponentCommand = (_b = class extends SerializableCommand_1.default {
390
406
  // If [applyByFlattening], then the rendered content of this element
391
407
  // is present on the display's wet ink canvas. As such, no re-render is necessary
392
408
  // the first time this command is applied (the surfaces are joined instead).
@@ -406,7 +422,7 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand_1.defaul
406
422
  }
407
423
  }
408
424
  apply(editor) {
409
- editor.image.addElementDirectly(this.element);
425
+ editor.image.addComponentDirectly(this.element);
410
426
  if (!this.applyByFlattening) {
411
427
  editor.queueRerender();
412
428
  }
@@ -420,7 +436,7 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand_1.defaul
420
436
  editor.queueRerender();
421
437
  }
422
438
  description(_editor, localization) {
423
- return localization.addElementAction(this.element.description(localization));
439
+ return localization.addComponentAction(this.element.description(localization));
424
440
  }
425
441
  serializeToJSON() {
426
442
  return {
@@ -428,13 +444,13 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand_1.defaul
428
444
  };
429
445
  }
430
446
  },
431
- __setFunctionName(_b, "AddElementCommand"),
447
+ __setFunctionName(_b, "AddComponentCommand"),
432
448
  (() => {
433
449
  SerializableCommand_1.default.register('add-element', (json, editor) => {
434
450
  const id = json.elemData.id;
435
451
  const foundElem = editor.image.lookupElement(id);
436
452
  const elem = foundElem ?? AbstractComponent_1.default.deserialize(json.elemData);
437
- const result = new _a.AddElementCommand(elem);
453
+ const result = new _a.AddComponentCommand(elem);
438
454
  result.serializedElem = json.elemData;
439
455
  return result;
440
456
  });
package/dist/cjs/lib.d.ts CHANGED
@@ -20,7 +20,7 @@ export * from './types';
20
20
  export * from './inputEvents';
21
21
  export { default as getLocalizationTable, matchingLocalizationTable, } from './localizations/getLocalizationTable';
22
22
  export * from './localization';
23
- export { default as SVGLoader } from './SVGLoader/SVGLoader';
23
+ export { default as SVGLoader, SVGLoaderPlugin } from './SVGLoader/SVGLoader';
24
24
  export { default as Viewport } from './Viewport';
25
25
  export * from '@js-draw/math';
26
26
  export * from './components/lib';
@@ -67,9 +67,9 @@ const localization = {
67
67
  toolEnabledAnnouncement: (toolName) => `${toolName} aktiviert`,
68
68
  toolDisabledAnnouncement: (toolName) => `${toolName} deaktiviert`,
69
69
  updatedViewport: 'Transformierte Ansicht',
70
- transformedElements: (elemCount) => `${elemCount} Element${1 === elemCount ? '' : 'e'} transformiert`,
70
+ transformedElements: (elemCount, action) => `${elemCount} Element${1 === elemCount ? '' : 'e'} transformiert (${action})`,
71
71
  resizeOutputCommand: (newSize) => `Bildgröße auf ${newSize.w}x${newSize.h} geändert`,
72
- addElementAction: (componentDescription) => `${componentDescription} hinzugefügt`,
72
+ addComponentAction: (componentDescription) => `${componentDescription} hinzugefügt`,
73
73
  eraseAction: (elemDescription, countErased) => `${countErased} ${elemDescription} gelöscht`,
74
74
  duplicateAction: (elemDescription, countErased) => `${countErased} ${elemDescription} dupliziert`,
75
75
  inverseOf: (actionDescription) => `${actionDescription} umgekehrt`,
@@ -1,11 +1,12 @@
1
1
  import { Color4 } from '@js-draw/math';
2
- interface RenderingStyle {
2
+ export interface StrokeStyle {
3
+ readonly color: Color4;
4
+ /** Note: The stroke `width` is twice the stroke radius. */
5
+ readonly width: number;
6
+ }
7
+ export interface RenderingStyle {
3
8
  readonly fill: Color4;
4
- readonly stroke?: {
5
- readonly color: Color4;
6
- /** Note: The stroke `width` is twice the stroke radius. */
7
- readonly width: number;
8
- };
9
+ readonly stroke?: StrokeStyle;
9
10
  }
10
11
  export default RenderingStyle;
11
12
  export declare const cloneStyle: (style: RenderingStyle) => {
@@ -4,5 +4,5 @@ export { default as SVGRenderer } from './renderers/SVGRenderer';
4
4
  export { default as CanvasRenderer } from './renderers/CanvasRenderer';
5
5
  export { default as Display, RenderingMode } from './Display';
6
6
  export { default as TextRenderingStyle } from './TextRenderingStyle';
7
- export { default as RenderingStyle } from './RenderingStyle';
7
+ export { default as RenderingStyle, StrokeStyle as StrokeRenerdingStyle } from './RenderingStyle';
8
8
  export { pathToRenderable, pathFromRenderable, visualEquivalent as pathVisualEquivalent, default as RenderablePathSpec, } from './RenderablePathSpec';
@@ -142,6 +142,8 @@ class AbstractRenderer {
142
142
  this.selfTransform = transform;
143
143
  }
144
144
  pushTransform(transform) {
145
+ // Draw all pending paths that used the previous transform (if any).
146
+ this.flushPath();
145
147
  this.transformStack.push(this.selfTransform);
146
148
  this.setTransform(this.getCanvasToScreenTransform().rightMul(transform));
147
149
  }
@@ -149,6 +151,8 @@ class AbstractRenderer {
149
151
  if (this.transformStack.length === 0) {
150
152
  throw new Error('Unable to pop more transforms than have been pushed!');
151
153
  }
154
+ // Draw all pending paths that used the old transform (if any):
155
+ this.flushPath();
152
156
  this.setTransform(this.transformStack.pop() ?? null);
153
157
  }
154
158
  // Get the matrix that transforms a vector on the canvas to a vector on this'
@@ -42,6 +42,15 @@ export default class CanvasRenderer extends AbstractRenderer {
42
42
  private clipLevels;
43
43
  startObject(boundingBox: Rect2, clip?: boolean): void;
44
44
  endObject(): void;
45
+ /**
46
+ * Returns a reference to the underlying `CanvasRenderingContext2D`.
47
+ * This can be used to render custom content not supported by {@link AbstractRenderer}.
48
+ * However, such content won't support {@link SVGRenderer} or {@link TextOnlyRenderer}
49
+ * by default.
50
+ *
51
+ * Use with caution.
52
+ */
53
+ drawWithRawRenderingContext(callback: (ctx: CanvasRenderingContext2D) => void): void;
45
54
  drawPoints(...points: Point2[]): void;
46
55
  isTooSmallToRender(rect: Rect2): boolean;
47
56
  static fromViewport(exportViewport: Viewport, options?: {
@@ -213,6 +213,20 @@ class CanvasRenderer extends AbstractRenderer_1.default {
213
213
  this.ignoringObject = false;
214
214
  }
215
215
  }
216
+ /**
217
+ * Returns a reference to the underlying `CanvasRenderingContext2D`.
218
+ * This can be used to render custom content not supported by {@link AbstractRenderer}.
219
+ * However, such content won't support {@link SVGRenderer} or {@link TextOnlyRenderer}
220
+ * by default.
221
+ *
222
+ * Use with caution.
223
+ */
224
+ drawWithRawRenderingContext(callback) {
225
+ this.ctx.save();
226
+ this.transformBy(this.getCanvasToScreenTransform());
227
+ callback(this.ctx);
228
+ this.ctx.restore();
229
+ }
216
230
  // @internal
217
231
  drawPoints(...points) {
218
232
  const pointRadius = 10;