js-draw 0.13.1 → 0.14.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.
@@ -196,7 +196,7 @@ export class Editor {
196
196
  let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);
197
197
  // Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
198
198
  // pinch-zooming.
199
- if (!evt.ctrlKey) {
199
+ if (!evt.ctrlKey && !evt.metaKey) {
200
200
  if (!this.settings.wheelEventsEnabled) {
201
201
  return;
202
202
  }
@@ -213,7 +213,7 @@ export class Editor {
213
213
  else if (evt.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
214
214
  delta = delta.times(100);
215
215
  }
216
- if (evt.ctrlKey) {
216
+ if (evt.ctrlKey || evt.metaKey) {
217
217
  delta = Vec3.of(0, 0, evt.deltaY);
218
218
  }
219
219
  // Ensure that `pos` is relative to `this.container`
@@ -441,7 +441,7 @@ export class Editor {
441
441
  else if (this.toolController.dispatchInputEvent({
442
442
  kind: InputEvtType.KeyPressEvent,
443
443
  key: evt.key,
444
- ctrlKey: evt.ctrlKey,
444
+ ctrlKey: evt.ctrlKey || evt.metaKey,
445
445
  altKey: evt.altKey,
446
446
  })) {
447
447
  evt.preventDefault();
@@ -454,7 +454,7 @@ export class Editor {
454
454
  if (this.toolController.dispatchInputEvent({
455
455
  kind: InputEvtType.KeyUpEvent,
456
456
  key: evt.key,
457
- ctrlKey: evt.ctrlKey,
457
+ ctrlKey: evt.ctrlKey || evt.metaKey,
458
458
  altKey: evt.altKey,
459
459
  })) {
460
460
  evt.preventDefault();
@@ -202,7 +202,13 @@ export default class SVGLoader {
202
202
  }
203
203
  // Compute styles.
204
204
  const computedStyles = window.getComputedStyle(elem);
205
- const fontSizeMatch = /^([-0-9.e]+)px/i.exec(computedStyles.fontSize);
205
+ const fontSizeExp = /^([-0-9.e]+)px/i;
206
+ // In some environments, computedStyles.fontSize can be increased by the system.
207
+ // Thus, to prevent text from growing on load/save, prefer .style.fontSize.
208
+ let fontSizeMatch = fontSizeExp.exec(elem.style.fontSize);
209
+ if (!fontSizeMatch) {
210
+ fontSizeMatch = fontSizeExp.exec(computedStyles.fontSize);
211
+ }
206
212
  const supportedStyleAttrs = [
207
213
  'fontFamily',
208
214
  'transform',
@@ -390,7 +396,7 @@ export default class SVGLoader {
390
396
  <meta name='viewport' conent='width=device-width,initial-scale=1.0'/>
391
397
  <meta charset='utf-8'/>
392
398
  </head>
393
- <body>
399
+ <body style='font-size: 12px;'>
394
400
  <script>
395
401
  console.error('JavaScript should not be able to run here!');
396
402
  throw new Error(
@@ -44,6 +44,7 @@ export declare class Viewport {
44
44
  * should return `100` because `100` is the nearest power of 10 to 101.
45
45
  */
46
46
  getScaleFactorToNearestPowerOfTen(): number;
47
+ private getScaleFactorToNearestPowerOf;
47
48
  snapToGrid(canvasPos: Point2): Vec3;
48
49
  /** Returns the size of one screen pixel in canvas units. */
49
50
  getSizeOfPixelOnCanvas(): number;
@@ -84,13 +84,16 @@ export class Viewport {
84
84
  * should return `100` because `100` is the nearest power of 10 to 101.
85
85
  */
86
86
  getScaleFactorToNearestPowerOfTen() {
87
+ return this.getScaleFactorToNearestPowerOf(10);
88
+ }
89
+ getScaleFactorToNearestPowerOf(powerOf) {
87
90
  const scaleFactor = this.getScaleFactor();
88
- return Math.pow(10, Math.round(Math.log10(scaleFactor)));
91
+ return Math.pow(powerOf, Math.round(Math.log(scaleFactor) / Math.log(powerOf)));
89
92
  }
90
93
  snapToGrid(canvasPos) {
91
94
  const snapCoordinate = (coordinate) => {
92
- const scaleFactor = this.getScaleFactorToNearestPowerOfTen();
93
- const roundFactor = scaleFactor / 100;
95
+ const scaleFactor = this.getScaleFactorToNearestPowerOf(2);
96
+ const roundFactor = scaleFactor / 50;
94
97
  const snapped = Math.round(coordinate * roundFactor) / roundFactor;
95
98
  return snapped;
96
99
  };
@@ -347,6 +347,7 @@ export default class Path {
347
347
  // @param onlyAbsCommands - True if we should avoid converting absolute coordinates to relative offsets -- such
348
348
  // conversions can lead to smaller output strings, but also take time.
349
349
  static toString(startPoint, parts, onlyAbsCommands) {
350
+ var _a;
350
351
  const result = [];
351
352
  let prevPoint;
352
353
  const addCommand = (command, ...points) => {
@@ -382,7 +383,7 @@ export default class Path {
382
383
  commandString = `${command.toLowerCase()}${relativeCommandParts.join(' ')}`;
383
384
  }
384
385
  // Don't add no-ops.
385
- if (commandString === 'l0,0') {
386
+ if (commandString === 'l0,0' || commandString === 'm0,0') {
386
387
  return;
387
388
  }
388
389
  result.push(commandString);
@@ -390,9 +391,15 @@ export default class Path {
390
391
  prevPoint = points[points.length - 1];
391
392
  }
392
393
  };
393
- addCommand('M', startPoint);
394
+ // Don't add two moveTos in a row (this can happen if
395
+ // the start point corresponds to a moveTo _and_ the first command is
396
+ // also a moveTo)
397
+ if (((_a = parts[0]) === null || _a === void 0 ? void 0 : _a.kind) !== PathCommandType.MoveTo) {
398
+ addCommand('M', startPoint);
399
+ }
394
400
  let exhaustivenessCheck;
395
- for (const part of parts) {
401
+ for (let i = 0; i < parts.length; i++) {
402
+ const part = parts[i];
396
403
  switch (part.kind) {
397
404
  case PathCommandType.MoveTo:
398
405
  addCommand('M', part.point);
@@ -2,7 +2,34 @@ import Color4 from '../Color4';
2
2
  import { ComponentBuilderFactory } from '../components/builders/types';
3
3
  import { TextStyle } from '../components/TextComponent';
4
4
  import Pen from '../tools/Pen';
5
- type IconType = SVGSVGElement | HTMLImageElement;
5
+ export type IconType = HTMLImageElement | SVGElement;
6
+ /**
7
+ * Provides icons that can be used in the toolbar, etc.
8
+ * Extend this class and override methods to customize icons.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * class CustomIconProvider extends jsdraw.IconProvider {
13
+ * // Use '☺' instead of the default dropdown symbol.
14
+ * public makeDropdownIcon() {
15
+ * const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
16
+ * icon.innerHTML = `
17
+ * <text x='5' y='55' style='fill: var(--icon-color); font-size: 50pt;'>☺</text>
18
+ * `;
19
+ * icon.setAttribute('viewBox', '0 0 100 100');
20
+ * return icon;
21
+ * }
22
+ * }
23
+ *
24
+ * const icons = new CustomIconProvider();
25
+ * const editor = new jsdraw.Editor(document.body, {
26
+ * iconProvider: icons,
27
+ * });
28
+ *
29
+ * // Add a toolbar that uses these icons
30
+ * editor.addToolbar();
31
+ * ```
32
+ */
6
33
  export default class IconProvider {
7
34
  makeUndoIcon(): IconType;
8
35
  makeRedoIcon(mirror?: boolean): IconType;
@@ -30,4 +57,3 @@ export default class IconProvider {
30
57
  makeDeleteSelectionIcon(): IconType;
31
58
  makeSaveIcon(): IconType;
32
59
  }
33
- export {};
@@ -24,8 +24,33 @@ const checkerboardPatternDef = `
24
24
  </pattern>
25
25
  `;
26
26
  const checkerboardPatternRef = 'url(#checkerboard)';
27
- // Provides icons that can be used in the toolbar, etc.
28
- // Extend this class and override methods to customize icons.
27
+ /**
28
+ * Provides icons that can be used in the toolbar, etc.
29
+ * Extend this class and override methods to customize icons.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * class CustomIconProvider extends jsdraw.IconProvider {
34
+ * // Use '☺' instead of the default dropdown symbol.
35
+ * public makeDropdownIcon() {
36
+ * const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
37
+ * icon.innerHTML = `
38
+ * <text x='5' y='55' style='fill: var(--icon-color); font-size: 50pt;'>☺</text>
39
+ * `;
40
+ * icon.setAttribute('viewBox', '0 0 100 100');
41
+ * return icon;
42
+ * }
43
+ * }
44
+ *
45
+ * const icons = new CustomIconProvider();
46
+ * const editor = new jsdraw.Editor(document.body, {
47
+ * iconProvider: icons,
48
+ * });
49
+ *
50
+ * // Add a toolbar that uses these icons
51
+ * editor.addToolbar();
52
+ * ```
53
+ */
29
54
  export default class IconProvider {
30
55
  makeUndoIcon() {
31
56
  return this.makeRedoIcon(true);
@@ -23,6 +23,12 @@ export default class Selection {
23
23
  getTransform(): Mat33;
24
24
  get preTransformRegion(): Rect2;
25
25
  get region(): Rect2;
26
+ /**
27
+ * Computes and returns the bounding box of the selection without
28
+ * any additional padding. Computes directly from the elements that are selected.
29
+ * @internal
30
+ */
31
+ computeTightBoundingBox(): Rect2;
26
32
  get regionRotation(): number;
27
33
  get preTransformedScreenRegion(): Rect2;
28
34
  get preTransformedScreenRegionRotation(): number;
@@ -77,6 +77,17 @@ export default class Selection {
77
77
  const scaleAndTranslateMat = this.transform.rightMul(rotationMatrix.inverse());
78
78
  return this.originalRegion.transformedBoundingBox(scaleAndTranslateMat);
79
79
  }
80
+ /**
81
+ * Computes and returns the bounding box of the selection without
82
+ * any additional padding. Computes directly from the elements that are selected.
83
+ * @internal
84
+ */
85
+ computeTightBoundingBox() {
86
+ const bbox = this.selectedElems.reduce((accumulator, elem) => {
87
+ return (accumulator !== null && accumulator !== void 0 ? accumulator : elem.getBBox()).union(elem.getBBox());
88
+ }, null);
89
+ return bbox !== null && bbox !== void 0 ? bbox : Rect2.empty;
90
+ }
80
91
  get regionRotation() {
81
92
  return this.transform.transformVec3(Vec2.unitX).angle();
82
93
  }
@@ -161,9 +172,7 @@ export default class Selection {
161
172
  // Recompute this' region from the selected elements.
162
173
  // Returns false if the selection is empty.
163
174
  recomputeRegion() {
164
- const newRegion = this.selectedElems.reduce((accumulator, elem) => {
165
- return (accumulator !== null && accumulator !== void 0 ? accumulator : elem.getBBox()).union(elem.getBBox());
166
- }, null);
175
+ const newRegion = this.computeTightBoundingBox();
167
176
  if (!newRegion) {
168
177
  this.cancelSelection();
169
178
  return false;
@@ -47,10 +47,12 @@ export default class SelectionTool extends BaseTool {
47
47
  snapSelectionToGrid() {
48
48
  if (!this.selectionBox)
49
49
  throw new Error('No selection to snap!');
50
- const topLeftOfBBox = this.selectionBox.region.topLeft;
51
- const snapDistance = this.editor.viewport.snapToGrid(topLeftOfBBox).minus(topLeftOfBBox);
50
+ // Snap the top left corner of what we have selected.
51
+ const topLeftOfBBox = this.selectionBox.computeTightBoundingBox().topLeft;
52
+ const snappedTopLeft = this.editor.viewport.snapToGrid(topLeftOfBBox);
53
+ const snapDelta = snappedTopLeft.minus(topLeftOfBBox);
52
54
  const oldTransform = this.selectionBox.getTransform();
53
- this.selectionBox.setTransform(oldTransform.rightMul(Mat33.translation(snapDistance)));
55
+ this.selectionBox.setTransform(oldTransform.rightMul(Mat33.translation(snapDelta)));
54
56
  this.selectionBox.finalizeTransform();
55
57
  }
56
58
  onPointerDown({ allPointers, current }) {
@@ -51,7 +51,7 @@ export class ResizeTransformer {
51
51
  // Round: If this isn't done, scaling can create numbers with long decimal representations.
52
52
  // long decimal representations => large file sizes.
53
53
  scale = scale.map(component => Viewport.roundScaleRatio(component, 2));
54
- if (scale.x > 0 && scale.y > 0) {
54
+ if (scale.x !== 0 && scale.y !== 0) {
55
55
  const origin = this.editor.viewport.roundPoint(this.selection.preTransformRegion.topLeft);
56
56
  this.selection.setTransform(Mat33.scaling2D(scale, origin));
57
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "main": "./dist/src/lib.d.ts",
6
6
  "types": "./dist/src/lib.js",
package/src/Editor.ts CHANGED
@@ -315,7 +315,7 @@ export class Editor {
315
315
 
316
316
  // Process wheel events if the ctrl key is down, even if disabled -- we do want to handle
317
317
  // pinch-zooming.
318
- if (!evt.ctrlKey) {
318
+ if (!evt.ctrlKey && !evt.metaKey) {
319
319
  if (!this.settings.wheelEventsEnabled) {
320
320
  return;
321
321
  } else if (this.settings.wheelEventsEnabled === 'only-if-focused') {
@@ -333,7 +333,7 @@ export class Editor {
333
333
  delta = delta.times(100);
334
334
  }
335
335
 
336
- if (evt.ctrlKey) {
336
+ if (evt.ctrlKey || evt.metaKey) {
337
337
  delta = Vec3.of(0, 0, evt.deltaY);
338
338
  }
339
339
 
@@ -598,7 +598,7 @@ export class Editor {
598
598
  } else if (this.toolController.dispatchInputEvent({
599
599
  kind: InputEvtType.KeyPressEvent,
600
600
  key: evt.key,
601
- ctrlKey: evt.ctrlKey,
601
+ ctrlKey: evt.ctrlKey || evt.metaKey,
602
602
  altKey: evt.altKey,
603
603
  })) {
604
604
  evt.preventDefault();
@@ -611,7 +611,7 @@ export class Editor {
611
611
  if (this.toolController.dispatchInputEvent({
612
612
  kind: InputEvtType.KeyUpEvent,
613
613
  key: evt.key,
614
- ctrlKey: evt.ctrlKey,
614
+ ctrlKey: evt.ctrlKey || evt.metaKey,
615
615
  altKey: evt.altKey,
616
616
  })) {
617
617
  evt.preventDefault();
package/src/SVGLoader.ts CHANGED
@@ -244,7 +244,14 @@ export default class SVGLoader implements ImageLoader {
244
244
 
245
245
  // Compute styles.
246
246
  const computedStyles = window.getComputedStyle(elem);
247
- const fontSizeMatch = /^([-0-9.e]+)px/i.exec(computedStyles.fontSize);
247
+ const fontSizeExp = /^([-0-9.e]+)px/i;
248
+
249
+ // In some environments, computedStyles.fontSize can be increased by the system.
250
+ // Thus, to prevent text from growing on load/save, prefer .style.fontSize.
251
+ let fontSizeMatch = fontSizeExp.exec(elem.style.fontSize);
252
+ if (!fontSizeMatch) {
253
+ fontSizeMatch = fontSizeExp.exec(computedStyles.fontSize);
254
+ }
248
255
 
249
256
  const supportedStyleAttrs = [
250
257
  'fontFamily',
@@ -455,7 +462,7 @@ export default class SVGLoader implements ImageLoader {
455
462
  <meta name='viewport' conent='width=device-width,initial-scale=1.0'/>
456
463
  <meta charset='utf-8'/>
457
464
  </head>
458
- <body>
465
+ <body style='font-size: 12px;'>
459
466
  <script>
460
467
  console.error('JavaScript should not be able to run here!');
461
468
  throw new Error(
package/src/Viewport.ts CHANGED
@@ -157,14 +157,18 @@ export class Viewport {
157
157
  * should return `100` because `100` is the nearest power of 10 to 101.
158
158
  */
159
159
  public getScaleFactorToNearestPowerOfTen() {
160
+ return this.getScaleFactorToNearestPowerOf(10);
161
+ }
162
+
163
+ private getScaleFactorToNearestPowerOf(powerOf: number) {
160
164
  const scaleFactor = this.getScaleFactor();
161
- return Math.pow(10, Math.round(Math.log10(scaleFactor)));
165
+ return Math.pow(powerOf, Math.round(Math.log(scaleFactor) / Math.log(powerOf)));
162
166
  }
163
167
 
164
168
  public snapToGrid(canvasPos: Point2) {
165
169
  const snapCoordinate = (coordinate: number) => {
166
- const scaleFactor = this.getScaleFactorToNearestPowerOfTen();
167
- const roundFactor = scaleFactor / 100;
170
+ const scaleFactor = this.getScaleFactorToNearestPowerOf(2);
171
+ const roundFactor = scaleFactor / 50;
168
172
  const snapped = Math.round(coordinate * roundFactor) / roundFactor;
169
173
 
170
174
  return snapped;
@@ -64,4 +64,14 @@ describe('Path.toString', () => {
64
64
 
65
65
  expect(path.toString(true)).toBe(path1.toString(true));
66
66
  });
67
+
68
+ it('should remove no-op move-tos', () => {
69
+ const path1 = Path.fromString('M50,75m0,0q0,12.5 0,50q0,6.3 25,0');
70
+ path1['cachedStringVersion'] = null;
71
+ const path2 = Path.fromString('M150,175M150,175q0,12.5 0,50q0,6.3 25,0');
72
+ path2['cachedStringVersion'] = null;
73
+
74
+ expect(path1.toString()).toBe('M50,75q0,12.5 0,50q0,6.3 25,0');
75
+ expect(path2.toString()).toBe('M150,175q0,12.5 0,50q0,6.3 25,0');
76
+ });
67
77
  });
package/src/math/Path.ts CHANGED
@@ -493,7 +493,7 @@ export default class Path {
493
493
  }
494
494
 
495
495
  // Don't add no-ops.
496
- if (commandString === 'l0,0') {
496
+ if (commandString === 'l0,0' || commandString === 'm0,0') {
497
497
  return;
498
498
  }
499
499
  result.push(commandString);
@@ -503,9 +503,17 @@ export default class Path {
503
503
  }
504
504
  };
505
505
 
506
- addCommand('M', startPoint);
506
+ // Don't add two moveTos in a row (this can happen if
507
+ // the start point corresponds to a moveTo _and_ the first command is
508
+ // also a moveTo)
509
+ if (parts[0]?.kind !== PathCommandType.MoveTo) {
510
+ addCommand('M', startPoint);
511
+ }
512
+
507
513
  let exhaustivenessCheck: never;
508
- for (const part of parts) {
514
+ for (let i = 0; i < parts.length; i++) {
515
+ const part = parts[i];
516
+
509
517
  switch (part.kind) {
510
518
  case PathCommandType.MoveTo:
511
519
  addCommand('M', part.point);
@@ -8,10 +8,7 @@ import Pen from '../tools/Pen';
8
8
  import { StrokeDataPoint } from '../types';
9
9
  import Viewport from '../Viewport';
10
10
 
11
- // Provides a default set of icons for the editor.
12
- // Many of the icons were created with Inkscape.
13
-
14
- type IconType = SVGSVGElement|HTMLImageElement;
11
+ export type IconType = HTMLImageElement|SVGElement;
15
12
 
16
13
  const svgNamespace = 'http://www.w3.org/2000/svg';
17
14
  const iconColorFill = `
@@ -35,8 +32,33 @@ const checkerboardPatternDef = `
35
32
  `;
36
33
  const checkerboardPatternRef = 'url(#checkerboard)';
37
34
 
38
- // Provides icons that can be used in the toolbar, etc.
39
- // Extend this class and override methods to customize icons.
35
+ /**
36
+ * Provides icons that can be used in the toolbar, etc.
37
+ * Extend this class and override methods to customize icons.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * class CustomIconProvider extends jsdraw.IconProvider {
42
+ * // Use '☺' instead of the default dropdown symbol.
43
+ * public makeDropdownIcon() {
44
+ * const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
45
+ * icon.innerHTML = `
46
+ * <text x='5' y='55' style='fill: var(--icon-color); font-size: 50pt;'>☺</text>
47
+ * `;
48
+ * icon.setAttribute('viewBox', '0 0 100 100');
49
+ * return icon;
50
+ * }
51
+ * }
52
+ *
53
+ * const icons = new CustomIconProvider();
54
+ * const editor = new jsdraw.Editor(document.body, {
55
+ * iconProvider: icons,
56
+ * });
57
+ *
58
+ * // Add a toolbar that uses these icons
59
+ * editor.addToolbar();
60
+ * ```
61
+ */
40
62
  export default class IconProvider {
41
63
 
42
64
  public makeUndoIcon(): IconType {
@@ -33,6 +33,7 @@
33
33
 
34
34
  .toolbar-dropdown .toolbar-button > .toolbar-icon {
35
35
  max-width: 50px;
36
+ width: 100%;
36
37
  }
37
38
 
38
39
  .toolbar-button.disabled {
@@ -89,6 +90,8 @@
89
90
 
90
91
  .toolbar-root .toolbar-icon {
91
92
  flex-shrink: 1;
93
+
94
+ width: 100%;
92
95
  min-width: 30px;
93
96
  min-height: 30px;
94
97
  }
@@ -121,6 +121,21 @@ export default class Selection {
121
121
  return this.originalRegion.transformedBoundingBox(scaleAndTranslateMat);
122
122
  }
123
123
 
124
+ /**
125
+ * Computes and returns the bounding box of the selection without
126
+ * any additional padding. Computes directly from the elements that are selected.
127
+ * @internal
128
+ */
129
+ public computeTightBoundingBox() {
130
+ const bbox = this.selectedElems.reduce((
131
+ accumulator: Rect2|null, elem: AbstractComponent
132
+ ): Rect2 => {
133
+ return (accumulator ?? elem.getBBox()).union(elem.getBBox());
134
+ }, null);
135
+
136
+ return bbox ?? Rect2.empty;
137
+ }
138
+
124
139
  public get regionRotation(): number {
125
140
  return this.transform.transformVec3(Vec2.unitX).angle();
126
141
  }
@@ -328,11 +343,7 @@ export default class Selection {
328
343
  // Recompute this' region from the selected elements.
329
344
  // Returns false if the selection is empty.
330
345
  public recomputeRegion(): boolean {
331
- const newRegion = this.selectedElems.reduce((
332
- accumulator: Rect2|null, elem: AbstractComponent
333
- ): Rect2 => {
334
- return (accumulator ?? elem.getBBox()).union(elem.getBBox());
335
- }, null);
346
+ const newRegion = this.computeTightBoundingBox();
336
347
 
337
348
  if (!newRegion) {
338
349
  this.cancelSelection();
@@ -62,12 +62,13 @@ export default class SelectionTool extends BaseTool {
62
62
  private snapSelectionToGrid() {
63
63
  if (!this.selectionBox) throw new Error('No selection to snap!');
64
64
 
65
- const topLeftOfBBox = this.selectionBox.region.topLeft;
66
- const snapDistance =
67
- this.editor.viewport.snapToGrid(topLeftOfBBox).minus(topLeftOfBBox);
65
+ // Snap the top left corner of what we have selected.
66
+ const topLeftOfBBox = this.selectionBox.computeTightBoundingBox().topLeft;
67
+ const snappedTopLeft = this.editor.viewport.snapToGrid(topLeftOfBBox);
68
+ const snapDelta = snappedTopLeft.minus(topLeftOfBBox);
68
69
 
69
70
  const oldTransform = this.selectionBox.getTransform();
70
- this.selectionBox.setTransform(oldTransform.rightMul(Mat33.translation(snapDistance)));
71
+ this.selectionBox.setTransform(oldTransform.rightMul(Mat33.translation(snapDelta)));
71
72
  this.selectionBox.finalizeTransform();
72
73
  }
73
74
 
@@ -62,7 +62,7 @@ export class ResizeTransformer {
62
62
  // long decimal representations => large file sizes.
63
63
  scale = scale.map(component => Viewport.roundScaleRatio(component, 2));
64
64
 
65
- if (scale.x > 0 && scale.y > 0) {
65
+ if (scale.x !== 0 && scale.y !== 0) {
66
66
  const origin = this.editor.viewport.roundPoint(this.selection.preTransformRegion.topLeft);
67
67
  this.selection.setTransform(Mat33.scaling2D(scale, origin));
68
68
  }