js-draw 0.19.0 → 0.21.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 (88) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +11 -0
  3. package/README.md +4 -4
  4. package/dist/bundle.js +2 -2
  5. package/dist/bundledStyles.js +1 -1
  6. package/dist/cjs/src/Color4.js +3 -3
  7. package/dist/cjs/src/Editor.d.ts +4 -1
  8. package/dist/cjs/src/Editor.js +30 -12
  9. package/dist/cjs/src/SVGLoader.js +69 -7
  10. package/dist/cjs/src/Viewport.d.ts +2 -0
  11. package/dist/cjs/src/Viewport.js +6 -2
  12. package/dist/cjs/src/components/AbstractComponent.d.ts +13 -1
  13. package/dist/cjs/src/components/AbstractComponent.js +19 -9
  14. package/dist/cjs/src/components/{ImageBackground.d.ts → BackgroundComponent.d.ts} +23 -3
  15. package/dist/cjs/src/components/BackgroundComponent.js +309 -0
  16. package/dist/cjs/src/components/Stroke.d.ts +1 -0
  17. package/dist/cjs/src/components/Stroke.js +15 -2
  18. package/dist/cjs/src/components/TextComponent.d.ts +1 -13
  19. package/dist/cjs/src/components/TextComponent.js +1 -1
  20. package/dist/cjs/src/components/lib.d.ts +2 -2
  21. package/dist/cjs/src/components/lib.js +2 -2
  22. package/dist/cjs/src/components/util/StrokeSmoother.js +26 -18
  23. package/dist/cjs/src/localizations/de.js +1 -1
  24. package/dist/cjs/src/localizations/es.js +1 -1
  25. package/dist/cjs/src/math/LineSegment2.d.ts +2 -0
  26. package/dist/cjs/src/math/LineSegment2.js +4 -0
  27. package/dist/cjs/src/math/Path.d.ts +24 -3
  28. package/dist/cjs/src/math/Path.js +225 -4
  29. package/dist/cjs/src/math/Rect2.js +4 -3
  30. package/dist/cjs/src/math/polynomial/QuadraticBezier.d.ts +28 -0
  31. package/dist/cjs/src/math/polynomial/QuadraticBezier.js +114 -0
  32. package/dist/cjs/src/math/polynomial/solveQuadratic.d.ts +6 -0
  33. package/dist/cjs/src/math/polynomial/solveQuadratic.js +36 -0
  34. package/dist/cjs/src/rendering/renderers/CanvasRenderer.js +5 -3
  35. package/dist/cjs/src/rendering/renderers/SVGRenderer.js +15 -6
  36. package/dist/cjs/src/toolbar/HTMLToolbar.js +7 -0
  37. package/dist/cjs/src/toolbar/localization.d.ts +2 -1
  38. package/dist/cjs/src/toolbar/localization.js +2 -1
  39. package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +5 -0
  40. package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.js +77 -2
  41. package/dist/cjs/src/toolbar/widgets/PenToolWidget.js +1 -1
  42. package/dist/cjs/src/tools/FindTool.js +1 -1
  43. package/dist/cjs/src/tools/SoundUITool.js +1 -1
  44. package/dist/mjs/src/Color4.mjs +3 -3
  45. package/dist/mjs/src/Editor.d.ts +4 -1
  46. package/dist/mjs/src/Editor.mjs +29 -11
  47. package/dist/mjs/src/SVGLoader.mjs +68 -6
  48. package/dist/mjs/src/Viewport.d.ts +2 -0
  49. package/dist/mjs/src/Viewport.mjs +6 -2
  50. package/dist/mjs/src/components/AbstractComponent.d.ts +13 -1
  51. package/dist/mjs/src/components/AbstractComponent.mjs +19 -9
  52. package/dist/mjs/src/components/{ImageBackground.d.ts → BackgroundComponent.d.ts} +23 -3
  53. package/dist/mjs/src/components/BackgroundComponent.mjs +279 -0
  54. package/dist/mjs/src/components/Stroke.d.ts +1 -0
  55. package/dist/mjs/src/components/Stroke.mjs +15 -2
  56. package/dist/mjs/src/components/TextComponent.d.ts +1 -13
  57. package/dist/mjs/src/components/TextComponent.mjs +1 -1
  58. package/dist/mjs/src/components/lib.d.ts +2 -2
  59. package/dist/mjs/src/components/lib.mjs +2 -2
  60. package/dist/mjs/src/components/util/StrokeSmoother.mjs +26 -18
  61. package/dist/mjs/src/localizations/de.mjs +1 -1
  62. package/dist/mjs/src/localizations/es.mjs +1 -1
  63. package/dist/mjs/src/math/LineSegment2.d.ts +2 -0
  64. package/dist/mjs/src/math/LineSegment2.mjs +4 -0
  65. package/dist/mjs/src/math/Path.d.ts +24 -3
  66. package/dist/mjs/src/math/Path.mjs +225 -4
  67. package/dist/mjs/src/math/Rect2.mjs +4 -3
  68. package/dist/mjs/src/math/polynomial/QuadraticBezier.d.ts +28 -0
  69. package/dist/mjs/src/math/polynomial/QuadraticBezier.mjs +108 -0
  70. package/dist/mjs/src/math/polynomial/solveQuadratic.d.ts +6 -0
  71. package/dist/mjs/src/math/polynomial/solveQuadratic.mjs +34 -0
  72. package/dist/mjs/src/rendering/renderers/CanvasRenderer.mjs +5 -3
  73. package/dist/mjs/src/rendering/renderers/SVGRenderer.mjs +15 -6
  74. package/dist/mjs/src/toolbar/HTMLToolbar.mjs +8 -1
  75. package/dist/mjs/src/toolbar/localization.d.ts +2 -1
  76. package/dist/mjs/src/toolbar/localization.mjs +2 -1
  77. package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +5 -0
  78. package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.mjs +54 -2
  79. package/dist/mjs/src/toolbar/widgets/PenToolWidget.mjs +1 -1
  80. package/dist/mjs/src/tools/FindTool.mjs +1 -1
  81. package/dist/mjs/src/tools/SoundUITool.mjs +1 -1
  82. package/jest.config.js +1 -1
  83. package/package.json +14 -14
  84. package/src/Coloris.css +52 -0
  85. package/src/Editor.css +12 -0
  86. package/src/toolbar/toolbar.css +9 -0
  87. package/dist/cjs/src/components/ImageBackground.js +0 -146
  88. package/dist/mjs/src/components/ImageBackground.mjs +0 -139
@@ -1,8 +1,34 @@
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 __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
+ const Erase_1 = __importDefault(require("../../commands/Erase"));
30
+ const uniteCommands_1 = __importDefault(require("../../commands/uniteCommands"));
31
+ const BackgroundComponent_1 = __importStar(require("../../components/BackgroundComponent"));
6
32
  const EditorImage_1 = require("../../EditorImage");
7
33
  const Rect2_1 = __importDefault(require("../../math/Rect2"));
8
34
  const types_1 = require("../../types");
@@ -11,7 +37,7 @@ const makeColorInput_1 = __importDefault(require("../makeColorInput"));
11
37
  const BaseWidget_1 = __importDefault(require("./BaseWidget"));
12
38
  class DocumentPropertiesWidget extends BaseWidget_1.default {
13
39
  constructor(editor, localizationTable) {
14
- super(editor, 'zoom-widget', localizationTable);
40
+ super(editor, 'document-properties-widget', localizationTable);
15
41
  this.updateDropdownContent = () => { };
16
42
  this.dropdownUpdateQueued = false;
17
43
  // Make it possible to open the dropdown, even if this widget isn't selected.
@@ -51,6 +77,33 @@ class DocumentPropertiesWidget extends BaseWidget_1.default {
51
77
  getBackgroundColor() {
52
78
  return this.editor.estimateBackgroundColor();
53
79
  }
80
+ removeBackgroundComponents() {
81
+ const previousBackgrounds = [];
82
+ for (const component of this.editor.image.getBackgroundComponents()) {
83
+ if (component instanceof BackgroundComponent_1.default) {
84
+ previousBackgrounds.push(component);
85
+ }
86
+ }
87
+ return new Erase_1.default(previousBackgrounds);
88
+ }
89
+ /** Replace existing background components with a background of the given type. */
90
+ setBackgroundType(backgroundType) {
91
+ const prevBackgroundColor = this.editor.estimateBackgroundColor();
92
+ const newBackground = new BackgroundComponent_1.default(backgroundType, prevBackgroundColor);
93
+ const addBackgroundCommand = this.editor.image.addElement(newBackground);
94
+ return (0, uniteCommands_1.default)([this.removeBackgroundComponents(), addBackgroundCommand]);
95
+ }
96
+ /** Returns the type of the topmost background component */
97
+ getBackgroundType() {
98
+ const backgroundComponents = this.editor.image.getBackgroundComponents();
99
+ for (let i = backgroundComponents.length - 1; i >= 0; i--) {
100
+ const component = backgroundComponents[i];
101
+ if (component instanceof BackgroundComponent_1.default) {
102
+ return component.getBackgroundType();
103
+ }
104
+ }
105
+ return BackgroundComponent_1.BackgroundType.None;
106
+ }
54
107
  updateImportExportRectSize(size) {
55
108
  const filterDimension = (dim) => {
56
109
  if (dim !== undefined && (!isFinite(dim) || dim <= 0)) {
@@ -79,6 +132,27 @@ class DocumentPropertiesWidget extends BaseWidget_1.default {
79
132
  colorInput.id = `${HTMLToolbar_1.toolbarCSSPrefix}docPropertiesColorInput-${DocumentPropertiesWidget.idCounter++}`;
80
133
  backgroundColorLabel.htmlFor = colorInput.id;
81
134
  backgroundColorRow.replaceChildren(backgroundColorLabel, backgroundColorInputContainer);
135
+ const useGridRow = document.createElement('div');
136
+ const useGridLabel = document.createElement('label');
137
+ const useGridCheckbox = document.createElement('input');
138
+ useGridCheckbox.id = `${HTMLToolbar_1.toolbarCSSPrefix}docPropertiesUseGridCheckbox-${DocumentPropertiesWidget.idCounter++}`;
139
+ useGridLabel.htmlFor = useGridCheckbox.id;
140
+ useGridCheckbox.type = 'checkbox';
141
+ useGridLabel.innerText = this.localizationTable.useGridOption;
142
+ useGridCheckbox.oninput = () => {
143
+ const prevBackgroundType = this.getBackgroundType();
144
+ const wasGrid = prevBackgroundType === BackgroundComponent_1.BackgroundType.Grid;
145
+ if (wasGrid === useGridCheckbox.checked) {
146
+ // Already the requested background type.
147
+ return;
148
+ }
149
+ let newBackgroundType = BackgroundComponent_1.BackgroundType.SolidColor;
150
+ if (useGridCheckbox.checked) {
151
+ newBackgroundType = BackgroundComponent_1.BackgroundType.Grid;
152
+ }
153
+ this.editor.dispatch(this.setBackgroundType(newBackgroundType));
154
+ };
155
+ useGridRow.replaceChildren(useGridLabel, useGridCheckbox);
82
156
  const addDimensionRow = (labelContent, onChange) => {
83
157
  const row = document.createElement('div');
84
158
  const label = document.createElement('label');
@@ -115,9 +189,10 @@ class DocumentPropertiesWidget extends BaseWidget_1.default {
115
189
  const importExportRect = this.editor.getImportExportRect();
116
190
  imageWidthRow.setValue(importExportRect.width);
117
191
  imageHeightRow.setValue(importExportRect.height);
192
+ useGridCheckbox.checked = this.getBackgroundType() === BackgroundComponent_1.BackgroundType.Grid;
118
193
  };
119
194
  this.updateDropdownContent();
120
- container.replaceChildren(backgroundColorRow, imageWidthRow.element, imageHeightRow.element);
195
+ container.replaceChildren(backgroundColorRow, useGridRow, imageWidthRow.element, imageHeightRow.element);
121
196
  dropdown.replaceChildren(container);
122
197
  return true;
123
198
  }
@@ -115,7 +115,7 @@ class PenToolWidget extends BaseToolWidget_1.default {
115
115
  objectTypeSelect.id = `${HTMLToolbar_1.toolbarCSSPrefix}penBuilderSelect${PenToolWidget.idCounter++}`;
116
116
  thicknessLabel.innerText = this.localizationTable.thicknessLabel;
117
117
  thicknessLabel.setAttribute('for', thicknessInput.id);
118
- objectSelectLabel.innerText = this.localizationTable.selectObjectType;
118
+ objectSelectLabel.innerText = this.localizationTable.selectPenType;
119
119
  objectSelectLabel.setAttribute('for', objectTypeSelect.id);
120
120
  // Use a logarithmic scale for thicknessInput (finer control over thinner strokewidths.)
121
121
  const inverseThicknessInputFn = (t) => Math.log10(t);
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // Displays a find dialog that allows the user to search for and focus text.
2
+ // Displays a find dialog that allows the user to search for and focus text.
3
3
  //
4
4
  // @packageDocumentation
5
5
  var __importDefault = (this && this.__importDefault) || function (mod) {
@@ -74,7 +74,7 @@ class SoundFeedback {
74
74
  announceBoundaryCross(boundaryCrossCount) {
75
75
  this.boundaryGain.gain.cancelScheduledValues(this.ctx.currentTime);
76
76
  this.boundaryGain.gain.setValueAtTime(0, this.ctx.currentTime);
77
- this.boundaryGain.gain.linearRampToValueAtTime(0.01, this.ctx.currentTime + 0.1);
77
+ this.boundaryGain.gain.linearRampToValueAtTime(0.018, this.ctx.currentTime + 0.1);
78
78
  this.boundaryOsc.frequency.setValueAtTime(440 + Math.atan(boundaryCrossCount / 2) * 100, this.ctx.currentTime);
79
79
  this.boundaryGain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + 0.25);
80
80
  }
@@ -167,8 +167,8 @@ export default class Color4 {
167
167
  // \ | /
168
168
  // \ | /
169
169
  // . \/ .
170
- //
171
- // .
170
+ //
171
+ // .
172
172
  //
173
173
  // Let z be up and (x, y, 0) be in the plane.
174
174
  //
@@ -181,7 +181,7 @@ export default class Color4 {
181
181
  // /|
182
182
  // 1/ | (√3)/2
183
183
  // / |
184
- // 1/2
184
+ // 1/2
185
185
  //
186
186
  const minComponent = Math.min(this.r, this.g, this.b);
187
187
  const maxComponent = Math.max(this.r, this.g, this.b);
@@ -191,7 +191,10 @@ export declare class Editor {
191
191
  * });
192
192
  * ```
193
193
  */
194
- handlePointerEventsFrom(elem: HTMLElement, filter?: HTMLPointerEventFilter): void;
194
+ handlePointerEventsFrom(elem: HTMLElement, filter?: HTMLPointerEventFilter): {
195
+ /** Remove all event listeners registered by this function. */
196
+ remove: () => void;
197
+ };
195
198
  /** Adds event listners for keypresses to `elem` and forwards those events to the editor. */
196
199
  handleKeyEventsFrom(elem: HTMLElement): void;
197
200
  /** `apply` a command. `command` will be announced for accessibility. */
@@ -31,7 +31,7 @@ import fileToBase64 from './util/fileToBase64.mjs';
31
31
  import uniteCommands from './commands/uniteCommands.mjs';
32
32
  import SelectionTool from './tools/SelectionTool/SelectionTool.mjs';
33
33
  import Erase from './commands/Erase.mjs';
34
- import ImageBackground, { BackgroundType } from './components/ImageBackground.mjs';
34
+ import BackgroundComponent, { BackgroundType } from './components/BackgroundComponent.mjs';
35
35
  import sendPenEvent from './testing/sendPenEvent.mjs';
36
36
  /**
37
37
  * The main entrypoint for the full editor.
@@ -455,20 +455,38 @@ export class Editor {
455
455
  handlePointerEventsFrom(elem, filter) {
456
456
  // May be required to prevent text selection on iOS/Safari:
457
457
  // See https://stackoverflow.com/a/70992717/17055750
458
- elem.addEventListener('touchstart', evt => evt.preventDefault());
459
- elem.addEventListener('contextmenu', evt => {
458
+ const touchstartListener = (evt) => evt.preventDefault();
459
+ const contextmenuListener = (evt) => {
460
460
  // Don't show a context menu
461
461
  evt.preventDefault();
462
- });
462
+ };
463
+ const listeners = {
464
+ 'touchstart': touchstartListener,
465
+ 'contextmenu': contextmenuListener,
466
+ };
463
467
  const eventNames = ['pointerdown', 'pointermove', 'pointerup', 'pointercancel'];
464
468
  for (const eventName of eventNames) {
465
- elem.addEventListener(eventName, evt => {
466
- if (filter && !filter(eventName, evt)) {
469
+ listeners[eventName] = (evt) => {
470
+ // This listener will only be called in the context of PointerEvents.
471
+ const event = evt;
472
+ if (filter && !filter(eventName, event)) {
467
473
  return true;
468
474
  }
469
- return this.handleHTMLPointerEvent(eventName, evt);
470
- });
475
+ return this.handleHTMLPointerEvent(eventName, event);
476
+ };
471
477
  }
478
+ // Add all listeners.
479
+ for (const eventName in listeners) {
480
+ elem.addEventListener(eventName, listeners[eventName]);
481
+ }
482
+ return {
483
+ /** Remove all event listeners registered by this function. */
484
+ remove: () => {
485
+ for (const eventName in listeners) {
486
+ elem.removeEventListener(eventName, listeners[eventName]);
487
+ }
488
+ },
489
+ };
472
490
  }
473
491
  /** Adds event listners for keypresses to `elem` and forwards those events to the editor. */
474
492
  handleKeyEventsFrom(elem) {
@@ -816,7 +834,7 @@ export class Editor {
816
834
  // Find a background component, if one exists.
817
835
  // Use the last (topmost) background component if there are multiple.
818
836
  for (const component of this.image.getBackgroundComponents()) {
819
- if (component instanceof ImageBackground) {
837
+ if (component instanceof BackgroundComponent) {
820
838
  background = component;
821
839
  }
822
840
  }
@@ -829,7 +847,7 @@ export class Editor {
829
847
  let background = this.getTopmostBackgroundComponent();
830
848
  if (!background) {
831
849
  const backgroundType = color.eq(Color4.transparent) ? BackgroundType.None : BackgroundType.SolidColor;
832
- background = new ImageBackground(backgroundType, color);
850
+ background = new BackgroundComponent(backgroundType, color);
833
851
  return this.image.addElement(background);
834
852
  }
835
853
  else {
@@ -844,7 +862,7 @@ export class Editor {
844
862
  var _a;
845
863
  const backgroundColors = [];
846
864
  for (const component of this.image.getBackgroundComponents()) {
847
- if (component instanceof ImageBackground) {
865
+ if (component instanceof BackgroundComponent) {
848
866
  backgroundColors.push((_a = component.getStyle().color) !== null && _a !== void 0 ? _a : Color4.transparent);
849
867
  }
850
868
  }
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import Color4 from './Color4.mjs';
11
- import ImageBackground, { BackgroundType, imageBackgroundCSSClassName } from './components/ImageBackground.mjs';
11
+ import BackgroundComponent, { BackgroundType, backgroundTypeToClassNameMap, imageBackgroundCSSClassName, imageBackgroundGridSizeCSSPrefix, imageBackgroundNonAutomaticSecondaryColorCSSClassName } from './components/BackgroundComponent.mjs';
12
12
  import ImageComponent from './components/ImageComponent.mjs';
13
13
  import Stroke from './components/Stroke.mjs';
14
14
  import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject.mjs';
@@ -150,11 +150,69 @@ export default class SVGLoader {
150
150
  });
151
151
  }
152
152
  addBackground(node) {
153
- var _a, _b, _c;
153
+ var _a, _b, _c, _d;
154
154
  return __awaiter(this, void 0, void 0, function* () {
155
- const fill = Color4.fromString((_b = (_a = node.getAttribute('fill')) !== null && _a !== void 0 ? _a : node.style.fill) !== null && _b !== void 0 ? _b : 'black');
156
- const elem = new ImageBackground(BackgroundType.SolidColor, fill);
157
- yield ((_c = this.onAddComponent) === null || _c === void 0 ? void 0 : _c.call(this, elem));
155
+ // If a grid background,
156
+ if (node.classList.contains(backgroundTypeToClassNameMap[BackgroundType.Grid])) {
157
+ let foregroundStr;
158
+ let backgroundStr;
159
+ let gridStrokeWidthStr;
160
+ // If a group,
161
+ if (node.tagName.toLowerCase() === 'g') {
162
+ // We expect exactly two children. One of these is the solid
163
+ // background of the grid
164
+ if (node.children.length !== 2) {
165
+ yield this.addUnknownNode(node);
166
+ return;
167
+ }
168
+ const background = node.children[0];
169
+ const grid = node.children[1];
170
+ backgroundStr = background.getAttribute('fill');
171
+ foregroundStr = grid.getAttribute('stroke');
172
+ gridStrokeWidthStr = grid.getAttribute('stroke-width');
173
+ }
174
+ else {
175
+ backgroundStr = node.getAttribute('fill');
176
+ foregroundStr = node.getAttribute('stroke');
177
+ gridStrokeWidthStr = node.getAttribute('stroke-width');
178
+ }
179
+ // Default to a transparent background.
180
+ backgroundStr !== null && backgroundStr !== void 0 ? backgroundStr : (backgroundStr = Color4.transparent.toHexString());
181
+ // A grid must have a foreground color specified.
182
+ if (!foregroundStr) {
183
+ yield this.addUnknownNode(node);
184
+ return;
185
+ }
186
+ // Extract the grid size from the class name
187
+ let gridSize = undefined;
188
+ for (const className of node.classList) {
189
+ if (className.startsWith(imageBackgroundGridSizeCSSPrefix)) {
190
+ const sizeStr = className.substring(imageBackgroundGridSizeCSSPrefix.length);
191
+ gridSize = parseFloat(sizeStr.replace(/p/g, '.'));
192
+ }
193
+ }
194
+ let gridStrokeWidth = undefined;
195
+ if (gridStrokeWidthStr) {
196
+ gridStrokeWidth = parseFloat(gridStrokeWidthStr);
197
+ }
198
+ const backgroundColor = Color4.fromString(backgroundStr);
199
+ let foregroundColor = Color4.fromString(foregroundStr);
200
+ // Should the foreground color be determined automatically?
201
+ if (!node.classList.contains(imageBackgroundNonAutomaticSecondaryColorCSSClassName)) {
202
+ foregroundColor = undefined;
203
+ }
204
+ const elem = BackgroundComponent.ofGrid(backgroundColor, gridSize, foregroundColor, gridStrokeWidth);
205
+ yield ((_a = this.onAddComponent) === null || _a === void 0 ? void 0 : _a.call(this, elem));
206
+ }
207
+ // Otherwise, if just a <path/>, it's a solid color background.
208
+ else if (node.tagName.toLowerCase() === 'path') {
209
+ const fill = Color4.fromString((_c = (_b = node.getAttribute('fill')) !== null && _b !== void 0 ? _b : node.style.fill) !== null && _c !== void 0 ? _c : 'black');
210
+ const elem = new BackgroundComponent(BackgroundType.SolidColor, fill);
211
+ yield ((_d = this.onAddComponent) === null || _d === void 0 ? void 0 : _d.call(this, elem));
212
+ }
213
+ else {
214
+ yield this.addUnknownNode(node);
215
+ }
158
216
  });
159
217
  }
160
218
  // If given, 'supportedAttrs' will have x, y, etc. attributes that were used in computing the transform added to it,
@@ -322,7 +380,11 @@ export default class SVGLoader {
322
380
  let visitChildren = true;
323
381
  switch (node.tagName.toLowerCase()) {
324
382
  case 'g':
325
- // Continue -- visit the node's children.
383
+ if (node.classList.contains(imageBackgroundCSSClassName)) {
384
+ yield this.addBackground(node);
385
+ visitChildren = false;
386
+ }
387
+ // Otherwise, continue -- visit the node's children.
326
388
  break;
327
389
  case 'path':
328
390
  if (node.classList.contains(imageBackgroundCSSClassName)) {
@@ -50,6 +50,8 @@ export declare class Viewport {
50
50
  */
51
51
  getScaleFactorToNearestPowerOfTen(): number;
52
52
  private getScaleFactorToNearestPowerOf;
53
+ /** Returns the size of a grid cell (in canvas units) as used by {@link snapToGrid}. */
54
+ static getGridSize(scaleFactor: number): number;
53
55
  snapToGrid(canvasPos: Point2): Vec3;
54
56
  /** Returns the size of one screen pixel in canvas units. */
55
57
  getSizeOfPixelOnCanvas(): number;
@@ -97,10 +97,14 @@ export class Viewport {
97
97
  const scaleFactor = this.getScaleFactor();
98
98
  return Math.pow(powerOf, Math.round(Math.log(scaleFactor) / Math.log(powerOf)));
99
99
  }
100
+ /** Returns the size of a grid cell (in canvas units) as used by {@link snapToGrid}. */
101
+ static getGridSize(scaleFactor) {
102
+ return 50 / scaleFactor;
103
+ }
100
104
  snapToGrid(canvasPos) {
105
+ const scaleFactor = this.getScaleFactorToNearestPowerOf(2);
101
106
  const snapCoordinate = (coordinate) => {
102
- const scaleFactor = this.getScaleFactorToNearestPowerOf(2);
103
- const roundFactor = scaleFactor / 50;
107
+ const roundFactor = 1 / Viewport.getGridSize(scaleFactor);
104
108
  const snapped = Math.round(coordinate * roundFactor) / roundFactor;
105
109
  return snapped;
106
110
  };
@@ -32,8 +32,15 @@ export default abstract class AbstractComponent {
32
32
  /** See {@link attachLoadSaveData} */
33
33
  getLoadSaveData(): LoadSaveDataTable;
34
34
  getZIndex(): number;
35
- /** @returns the bounding box of this. */
35
+ /**
36
+ * @returns the bounding box of this. This can be a slight overestimate if doing so
37
+ * significantly improves performance.
38
+ */
36
39
  getBBox(): Rect2;
40
+ /**
41
+ * @returns the bounding box of this. Unlike `getBBox`, this should **not** be a rough estimate.
42
+ */
43
+ getExactBBox(): Rect2;
37
44
  /** Called when this component is added to the given image. */
38
45
  onAddToImage(_image: EditorImage): void;
39
46
  onRemoveFromImage(): void;
@@ -43,6 +50,11 @@ export default abstract class AbstractComponent {
43
50
  /**
44
51
  * @returns true if this component intersects `rect` -- it is entirely contained
45
52
  * within the rectangle or one of the rectangle's edges intersects this component.
53
+ *
54
+ * The default implementation assumes that `this.getExactBBox()` returns a tight bounding box
55
+ * -- that any horiziontal/vertical line that intersects this' boounding box also
56
+ * intersects a point in this component. If this is not the case, components must override
57
+ * this function.
46
58
  */
47
59
  intersectsRect(rect: Rect2): boolean;
48
60
  protected abstract serializeToJSON(): any[] | Record<string, any> | number | string | null;
@@ -55,30 +55,40 @@ export default class AbstractComponent {
55
55
  getZIndex() {
56
56
  return this.zIndex;
57
57
  }
58
- /** @returns the bounding box of this. */
58
+ /**
59
+ * @returns the bounding box of this. This can be a slight overestimate if doing so
60
+ * significantly improves performance.
61
+ */
59
62
  getBBox() {
60
63
  return this.contentBBox;
61
64
  }
65
+ /**
66
+ * @returns the bounding box of this. Unlike `getBBox`, this should **not** be a rough estimate.
67
+ */
68
+ getExactBBox() {
69
+ return this.getBBox();
70
+ }
62
71
  /** Called when this component is added to the given image. */
63
72
  onAddToImage(_image) { }
64
73
  onRemoveFromImage() { }
65
74
  /**
66
75
  * @returns true if this component intersects `rect` -- it is entirely contained
67
76
  * within the rectangle or one of the rectangle's edges intersects this component.
77
+ *
78
+ * The default implementation assumes that `this.getExactBBox()` returns a tight bounding box
79
+ * -- that any horiziontal/vertical line that intersects this' boounding box also
80
+ * intersects a point in this component. If this is not the case, components must override
81
+ * this function.
68
82
  */
69
83
  intersectsRect(rect) {
70
- // If this component intersects rect,
84
+ // If this component intersects the given rectangle,
71
85
  // it is either contained entirely within rect or intersects one of rect's edges.
72
86
  // If contained within,
73
- if (rect.containsRect(this.getBBox())) {
87
+ if (rect.containsRect(this.getExactBBox())) {
74
88
  return true;
75
89
  }
76
- // Calculated bounding boxes can be slightly larger than their actual contents' bounding box.
77
- // As such, test with more lines than just the rect's edges.
78
- const testLines = [];
79
- for (const subregion of rect.divideIntoGrid(2, 2)) {
80
- testLines.push(...subregion.getEdges());
81
- }
90
+ // Otherwise check if it intersects one of the rectangle's edges.
91
+ const testLines = rect.getEdges();
82
92
  return testLines.some(edge => this.intersects(edge));
83
93
  }
84
94
  // Returns a command that, when applied, transforms this by [affineTransfm] and
@@ -11,32 +11,52 @@ import { ImageComponentLocalization } from './localization';
11
11
  import RestyleableComponent, { ComponentStyle } from './RestylableComponent';
12
12
  export declare enum BackgroundType {
13
13
  SolidColor = 0,
14
- None = 1
14
+ Grid = 1,
15
+ None = 2
15
16
  }
16
17
  export declare const imageBackgroundCSSClassName = "js-draw-image-background";
17
- export default class ImageBackground extends AbstractComponent implements RestyleableComponent {
18
+ export declare const imageBackgroundGridSizeCSSPrefix = "js-draw-image-background-grid-";
19
+ export declare const imageBackgroundNonAutomaticSecondaryColorCSSClassName = "js-draw-image-background-non-automatic-secondary-color";
20
+ export declare const backgroundTypeToClassNameMap: {
21
+ 1: string;
22
+ 0: string;
23
+ 2: string;
24
+ };
25
+ export default class BackgroundComponent extends AbstractComponent implements RestyleableComponent {
18
26
  private backgroundType;
19
27
  private mainColor;
20
28
  protected contentBBox: Rect2;
21
29
  private viewportSizeChangeListener;
30
+ private gridSize;
31
+ private gridStrokeWidth;
32
+ private secondaryColor;
22
33
  readonly isRestylableComponent: true;
23
34
  constructor(backgroundType: BackgroundType, mainColor: Color4);
35
+ static ofGrid(backgroundColor: Color4, gridSize?: number, gridColor?: Color4, gridStrokeWidth?: number): BackgroundComponent;
36
+ getBackgroundType(): BackgroundType;
37
+ getMainColor(): Color4;
38
+ getSecondaryColor(): Color4 | null;
39
+ getGridSize(): number;
24
40
  getStyle(): ComponentStyle;
25
41
  updateStyle(style: ComponentStyle): SerializableCommand;
26
42
  forceStyle(style: ComponentStyle, editor: Editor | null): void;
27
43
  onAddToImage(image: EditorImage): void;
28
44
  onRemoveFromImage(): void;
29
45
  private recomputeBBox;
46
+ private generateGridPath;
30
47
  render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
31
48
  intersects(lineSegment: LineSegment2): boolean;
32
49
  isSelectable(): boolean;
33
50
  isBackground(): boolean;
34
51
  protected serializeToJSON(): {
35
52
  mainColor: string;
53
+ secondaryColor: string | undefined;
36
54
  backgroundType: BackgroundType;
55
+ gridSize: number;
56
+ gridStrokeWidth: number;
37
57
  };
38
58
  protected applyTransformation(_affineTransfm: Mat33): void;
39
59
  description(localizationTable: ImageComponentLocalization): string;
40
60
  protected createClone(): AbstractComponent;
41
- static deserializeFromJSON(json: any): ImageBackground;
61
+ static deserializeFromJSON(json: any): BackgroundComponent;
42
62
  }