js-draw 0.17.1 → 0.17.2

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.
@@ -5,7 +5,6 @@ import Rect2 from './math/Rect2';
5
5
  import RenderingCache from './rendering/caching/RenderingCache';
6
6
  import SerializableCommand from './commands/SerializableCommand';
7
7
  import EventDispatcher from './EventDispatcher';
8
- import Command from './commands/Command';
9
8
  export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;
10
9
  export declare enum EditorImageEventType {
11
10
  ExportViewportChanged = 0
@@ -21,11 +20,6 @@ export default class EditorImage {
21
20
  private importExportViewport;
22
21
  readonly notifier: EditorImageNotifier;
23
22
  constructor();
24
- /**
25
- * @returns a `Viewport` for rendering the image when importing/exporting.
26
- */
27
- getImportExportViewport(): Viewport;
28
- setImportExportRect(imageRect: Rect2): Command;
29
23
  getBackgroundComponents(): AbstractComponent[];
30
24
  findParent(elem: AbstractComponent): ImageNode | null;
31
25
  queueRerenderOf(elem: AbstractComponent): void;
@@ -63,6 +57,12 @@ export default class EditorImage {
63
57
  /** @see EditorImage.addElement */
64
58
  addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
65
59
  private static AddElementCommand;
60
+ /**
61
+ * @returns a `Viewport` for rendering the image when importing/exporting.
62
+ */
63
+ getImportExportViewport(): Viewport;
64
+ setImportExportRect(imageRect: Rect2): SerializableCommand;
65
+ private static SetImportExportRectCommand;
66
66
  }
67
67
  type TooSmallToRenderCheck = (rect: Rect2) => boolean;
68
68
  /** Part of the Editor's image. @internal */
@@ -1,12 +1,12 @@
1
- var _a;
1
+ var _a, _b, _c;
2
2
  import Viewport from './Viewport';
3
3
  import AbstractComponent from './components/AbstractComponent';
4
4
  import Rect2 from './math/Rect2';
5
5
  import SerializableCommand from './commands/SerializableCommand';
6
6
  import EventDispatcher from './EventDispatcher';
7
7
  import { Vec2 } from './math/Vec2';
8
- import Command from './commands/Command';
9
8
  import Mat33 from './math/Mat33';
9
+ import { assertIsNumber, assertIsNumberArray } from './util/assertions';
10
10
  // @internal Sort by z-index, low to high
11
11
  export const sortLeavesByZIndex = (leaves) => {
12
12
  leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
@@ -31,34 +31,6 @@ export default class EditorImage {
31
31
  // Default to a 500x500 image
32
32
  this.importExportViewport.updateScreenSize(Vec2.of(500, 500));
33
33
  }
34
- /**
35
- * @returns a `Viewport` for rendering the image when importing/exporting.
36
- */
37
- getImportExportViewport() {
38
- return this.importExportViewport;
39
- }
40
- setImportExportRect(imageRect) {
41
- const importExportViewport = this.getImportExportViewport();
42
- const origSize = importExportViewport.visibleRect.size;
43
- const origTransform = importExportViewport.canvasToScreenTransform;
44
- return new class extends Command {
45
- apply(editor) {
46
- const viewport = editor.image.getImportExportViewport();
47
- viewport.updateScreenSize(imageRect.size);
48
- viewport.resetTransform(Mat33.translation(imageRect.topLeft.times(-1)));
49
- editor.queueRerender();
50
- }
51
- unapply(editor) {
52
- const viewport = editor.image.getImportExportViewport();
53
- viewport.updateScreenSize(origSize);
54
- viewport.resetTransform(origTransform);
55
- editor.queueRerender();
56
- }
57
- description(_editor, localizationTable) {
58
- return localizationTable.resizeOutputCommand(imageRect);
59
- }
60
- };
61
- }
62
34
  // Returns all components that make up the background of this image. These
63
35
  // components are rendered below all other components.
64
36
  getBackgroundComponents() {
@@ -75,8 +47,8 @@ export default class EditorImage {
75
47
  }
76
48
  // Returns the parent of the given element, if it exists.
77
49
  findParent(elem) {
78
- var _a;
79
- return (_a = this.background.getChildWithContent(elem)) !== null && _a !== void 0 ? _a : this.root.getChildWithContent(elem);
50
+ var _b;
51
+ return (_b = this.background.getChildWithContent(elem)) !== null && _b !== void 0 ? _b : this.root.getChildWithContent(elem);
80
52
  }
81
53
  // Forces a re-render of `elem` when the image is next re-rendered as a whole.
82
54
  // Does nothing if `elem` is not in this.
@@ -128,8 +100,8 @@ export default class EditorImage {
128
100
  * @see {@link AbstractComponent.getId}
129
101
  */
130
102
  lookupElement(id) {
131
- var _a;
132
- return (_a = this.componentsById[id]) !== null && _a !== void 0 ? _a : null;
103
+ var _b;
104
+ return (_b = this.componentsById[id]) !== null && _b !== void 0 ? _b : null;
133
105
  }
134
106
  addElementDirectly(elem) {
135
107
  elem.onAddToImage(this);
@@ -162,9 +134,22 @@ export default class EditorImage {
162
134
  addElement(elem, applyByFlattening) {
163
135
  return EditorImage.addElement(elem, applyByFlattening);
164
136
  }
137
+ /**
138
+ * @returns a `Viewport` for rendering the image when importing/exporting.
139
+ */
140
+ getImportExportViewport() {
141
+ return this.importExportViewport;
142
+ }
143
+ setImportExportRect(imageRect) {
144
+ const importExportViewport = this.getImportExportViewport();
145
+ const origSize = importExportViewport.visibleRect.size;
146
+ const origTransform = importExportViewport.canvasToScreenTransform;
147
+ return new EditorImage.SetImportExportRectCommand(origSize, origTransform, imageRect);
148
+ }
165
149
  }
150
+ _a = EditorImage;
166
151
  // A Command that can access private [EditorImage] functionality
167
- EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
152
+ EditorImage.AddElementCommand = (_b = class extends SerializableCommand {
168
153
  // If [applyByFlattening], then the rendered content of this element
169
154
  // is present on the display's wet ink canvas. As such, no re-render is necessary
170
155
  // the first time this command is applied (the surfaces are joined instead).
@@ -201,9 +186,9 @@ EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
201
186
  return localization.addElementAction(this.element.description(localization));
202
187
  }
203
188
  serializeToJSON() {
204
- var _a;
189
+ var _b;
205
190
  return {
206
- elemData: (_a = this.serializedElem) !== null && _a !== void 0 ? _a : this.element.serialize(),
191
+ elemData: (_b = this.serializedElem) !== null && _b !== void 0 ? _b : this.element.serialize(),
207
192
  };
208
193
  }
209
194
  },
@@ -217,7 +202,63 @@ EditorImage.AddElementCommand = (_a = class extends SerializableCommand {
217
202
  return result;
218
203
  });
219
204
  })(),
220
- _a);
205
+ _b);
206
+ // Handles resizing the background import/export region of the image.
207
+ EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand {
208
+ constructor(originalSize, originalTransform, finalRect) {
209
+ super(EditorImage.SetImportExportRectCommand.commandId);
210
+ this.originalSize = originalSize;
211
+ this.originalTransform = originalTransform;
212
+ this.finalRect = finalRect;
213
+ }
214
+ apply(editor) {
215
+ const viewport = editor.image.getImportExportViewport();
216
+ viewport.updateScreenSize(this.finalRect.size);
217
+ viewport.resetTransform(Mat33.translation(this.finalRect.topLeft.times(-1)));
218
+ editor.queueRerender();
219
+ }
220
+ unapply(editor) {
221
+ const viewport = editor.image.getImportExportViewport();
222
+ viewport.updateScreenSize(this.originalSize);
223
+ viewport.resetTransform(this.originalTransform);
224
+ editor.queueRerender();
225
+ }
226
+ description(_editor, localization) {
227
+ return localization.resizeOutputCommand(this.finalRect);
228
+ }
229
+ serializeToJSON() {
230
+ return {
231
+ originalSize: this.originalSize.xy,
232
+ originalTransform: this.originalTransform.toArray(),
233
+ newRegion: {
234
+ x: this.finalRect.x,
235
+ y: this.finalRect.y,
236
+ w: this.finalRect.w,
237
+ h: this.finalRect.h,
238
+ },
239
+ };
240
+ }
241
+ },
242
+ _c.commandId = 'set-import-export-rect',
243
+ (() => {
244
+ const commandId = _c.commandId;
245
+ SerializableCommand.register(commandId, (json, _editor) => {
246
+ assertIsNumber(json.originalSize.x);
247
+ assertIsNumber(json.originalSize.y);
248
+ assertIsNumberArray(json.originalTransform);
249
+ assertIsNumberArray([
250
+ json.newRegion.x,
251
+ json.newRegion.y,
252
+ json.newRegion.w,
253
+ json.newRegion.h,
254
+ ]);
255
+ const originalSize = Vec2.ofXY(json.originalSize);
256
+ const originalTransform = new Mat33(...json.originalTransform);
257
+ const finalRect = new Rect2(json.newRegion.x, json.newRegion.y, json.newRegion.w, json.newRegion.h);
258
+ return new EditorImage.SetImportExportRectCommand(originalSize, originalTransform, finalRect);
259
+ });
260
+ })(),
261
+ _c);
221
262
  /** Part of the Editor's image. @internal */
222
263
  export class ImageNode {
223
264
  constructor(parent = null) {
@@ -351,7 +392,7 @@ export class ImageNode {
351
392
  // this' ancestors bounding boxes. This also re-computes this' bounding box
352
393
  // in the z-direction (z-indicies).
353
394
  recomputeBBox(bubbleUp) {
354
- var _a;
395
+ var _b;
355
396
  const oldBBox = this.bbox;
356
397
  if (this.content !== null) {
357
398
  this.bbox = this.content.getBBox();
@@ -360,7 +401,7 @@ export class ImageNode {
360
401
  this.bbox = Rect2.union(...this.children.map(child => child.getBBox()));
361
402
  }
362
403
  if (bubbleUp && !oldBBox.eq(this.bbox)) {
363
- (_a = this.parent) === null || _a === void 0 ? void 0 : _a.recomputeBBox(true);
404
+ (_b = this.parent) === null || _b === void 0 ? void 0 : _b.recomputeBBox(true);
364
405
  }
365
406
  }
366
407
  updateParents(recursive = false) {
@@ -395,8 +436,8 @@ export class ImageNode {
395
436
  }
396
437
  // Remove this node and all of its children
397
438
  remove() {
398
- var _a;
399
- (_a = this.content) === null || _a === void 0 ? void 0 : _a.onRemoveFromImage();
439
+ var _b;
440
+ (_b = this.content) === null || _b === void 0 ? void 0 : _b.onRemoveFromImage();
400
441
  if (!this.parent) {
401
442
  this.content = null;
402
443
  this.children = [];
@@ -31,7 +31,7 @@ export default class TextComponent extends AbstractComponent implements Restylea
31
31
  getTextStyle(): {
32
32
  renderingStyle: {
33
33
  fill: import("../Color4").default;
34
- stroke?: {
34
+ stroke: {
35
35
  color: import("../Color4").default;
36
36
  width: number;
37
37
  } | undefined;
@@ -2,7 +2,7 @@ import LineSegment2 from '../math/LineSegment2';
2
2
  import Mat33 from '../math/Mat33';
3
3
  import Rect2 from '../math/Rect2';
4
4
  import { Vec2 } from '../math/Vec2';
5
- import { textStyleFromJSON, textStyleToJSON } from '../rendering/TextRenderingStyle';
5
+ import { cloneTextStyle, textStyleFromJSON, textStyleToJSON } from '../rendering/TextRenderingStyle';
6
6
  import AbstractComponent from './AbstractComponent';
7
7
  import { createRestyleComponentCommand } from './RestylableComponent';
8
8
  const componentTypeId = 'text';
@@ -132,7 +132,7 @@ export default class TextComponent extends AbstractComponent {
132
132
  }
133
133
  forceStyle(style, editor) {
134
134
  if (style.textStyle) {
135
- this.style = style.textStyle;
135
+ this.style = cloneTextStyle(style.textStyle);
136
136
  }
137
137
  else if (style.color) {
138
138
  this.style = Object.assign(Object.assign({}, this.style), { renderingStyle: Object.assign(Object.assign({}, this.style.renderingStyle), { fill: style.color }) });
@@ -152,7 +152,7 @@ export default class TextComponent extends AbstractComponent {
152
152
  }
153
153
  // See this.getStyle
154
154
  getTextStyle() {
155
- return Object.assign(Object.assign({}, this.style), { renderingStyle: Object.assign({}, this.style.renderingStyle) });
155
+ return cloneTextStyle(this.style);
156
156
  }
157
157
  getBaselinePos() {
158
158
  return this.transform.transformVec2(Vec2.zero);
@@ -165,7 +165,15 @@ export default class TextComponent extends AbstractComponent {
165
165
  this.recomputeBBox();
166
166
  }
167
167
  createClone() {
168
- return new TextComponent(this.textObjects, this.transform, this.style);
168
+ const clonedTextObjects = this.textObjects.map(obj => {
169
+ if (typeof obj === 'string') {
170
+ return obj;
171
+ }
172
+ else {
173
+ return obj.createClone();
174
+ }
175
+ });
176
+ return new TextComponent(clonedTextObjects, this.transform, this.style);
169
177
  }
170
178
  getText() {
171
179
  const result = [];
@@ -7,6 +7,13 @@ interface RenderingStyle {
7
7
  };
8
8
  }
9
9
  export default RenderingStyle;
10
+ export declare const cloneStyle: (style: RenderingStyle) => {
11
+ fill: Color4;
12
+ stroke: {
13
+ color: Color4;
14
+ width: number;
15
+ } | undefined;
16
+ };
10
17
  export declare const stylesEqual: (a: RenderingStyle, b: RenderingStyle) => boolean;
11
18
  export declare const styleToJSON: (style: RenderingStyle) => {
12
19
  fill: string;
@@ -1,4 +1,10 @@
1
1
  import Color4 from '../Color4';
2
+ export const cloneStyle = (style) => {
3
+ return {
4
+ fill: style.fill,
5
+ stroke: style.stroke ? Object.assign({}, style.stroke) : undefined,
6
+ };
7
+ };
2
8
  export const stylesEqual = (a, b) => {
3
9
  var _a, _b, _c, _d, _e, _f;
4
10
  const result = a === b || (a.fill.eq(b.fill)
@@ -7,6 +7,19 @@ export interface TextStyle {
7
7
  renderingStyle: RenderingStyle;
8
8
  }
9
9
  export default TextStyle;
10
+ export declare const cloneTextStyle: (style: TextStyle) => {
11
+ renderingStyle: {
12
+ fill: import("../Color4").default;
13
+ stroke: {
14
+ color: import("../Color4").default;
15
+ width: number;
16
+ } | undefined;
17
+ };
18
+ size: number;
19
+ fontFamily: string;
20
+ fontWeight?: string | undefined;
21
+ fontVariant?: string | undefined;
22
+ };
10
23
  export declare const textStyleFromJSON: (json: any) => TextStyle;
11
24
  export declare const textStyleToJSON: (style: TextStyle) => {
12
25
  renderingStyle: {
@@ -1,4 +1,7 @@
1
- import { styleFromJSON, styleToJSON } from './RenderingStyle';
1
+ import { cloneStyle, styleFromJSON, styleToJSON } from './RenderingStyle';
2
+ export const cloneTextStyle = (style) => {
3
+ return Object.assign(Object.assign({}, style), { renderingStyle: cloneStyle(style.renderingStyle) });
4
+ };
2
5
  export const textStyleFromJSON = (json) => {
3
6
  if (typeof json === 'string') {
4
7
  json = JSON.parse(json);
@@ -29,7 +29,8 @@ export default class FindTool extends BaseTool {
29
29
  matchIdx = matches.length + matchIdx;
30
30
  }
31
31
  if (matchIdx < matches.length) {
32
- this.editor.dispatch(this.editor.viewport.zoomTo(matches[matchIdx], true, true));
32
+ const undoable = false;
33
+ this.editor.dispatch(this.editor.viewport.zoomTo(matches[matchIdx], true, true), undoable);
33
34
  this.editor.announceForAccessibility(this.editor.localization.focusedFoundText(matchIdx + 1, matches.length));
34
35
  }
35
36
  }
@@ -1 +1,23 @@
1
+ /**
2
+ * Compile-time assertion that a branch of code is unreachable.
3
+ * @internal
4
+ */
1
5
  export declare const assertUnreachable: (key: never) => never;
6
+ /**
7
+ * Throws an exception if the typeof given value is not a number or `value` is NaN.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const foo: unknown = 3;
12
+ * assertIsNumber(foo);
13
+ *
14
+ * assertIsNumber('hello, world'); // throws an Error.
15
+ * ```
16
+ *
17
+ *
18
+ */
19
+ export declare const assertIsNumber: (value: any, allowNaN?: boolean) => value is number;
20
+ /**
21
+ * Throws if any of `values` is not of type number.
22
+ */
23
+ export declare const assertIsNumberArray: (values: any[], allowNaN?: boolean) => values is number[];
@@ -1,6 +1,45 @@
1
- // Compile-time assertion that a branch of code is unreachable.
2
- // See https://stackoverflow.com/a/39419171/17055750
3
- // @internal
1
+ /**
2
+ * Compile-time assertion that a branch of code is unreachable.
3
+ * @internal
4
+ */
4
5
  export const assertUnreachable = (key) => {
6
+ // See https://stackoverflow.com/a/39419171/17055750
5
7
  throw new Error(`Should be unreachable. Key: ${key}.`);
6
8
  };
9
+ /**
10
+ * Throws an exception if the typeof given value is not a number or `value` is NaN.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const foo: unknown = 3;
15
+ * assertIsNumber(foo);
16
+ *
17
+ * assertIsNumber('hello, world'); // throws an Error.
18
+ * ```
19
+ *
20
+ *
21
+ */
22
+ export const assertIsNumber = (value, allowNaN = false) => {
23
+ if (typeof value !== 'number' || (!allowNaN && isNaN(value))) {
24
+ throw new Error('Given value is not a number');
25
+ // return false;
26
+ }
27
+ return true;
28
+ };
29
+ /**
30
+ * Throws if any of `values` is not of type number.
31
+ */
32
+ export const assertIsNumberArray = (values, allowNaN = false) => {
33
+ if (typeof values !== 'object') {
34
+ throw new Error('Asserting isNumberArray: Given entity is not an array');
35
+ }
36
+ if (!assertIsNumber(values['length'])) {
37
+ return false;
38
+ }
39
+ for (const value of values) {
40
+ if (!assertIsNumber(value, allowNaN)) {
41
+ return false;
42
+ }
43
+ }
44
+ return true;
45
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
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",
@@ -78,31 +78,31 @@
78
78
  "postpack": "pinst --enable"
79
79
  },
80
80
  "dependencies": {
81
- "@melloware/coloris": "^0.16.1",
82
- "bezier-js": "^6.1.0"
81
+ "@melloware/coloris": "^0.18.0",
82
+ "bezier-js": "^6.1.3"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@types/bezier-js": "^4.1.0",
86
- "@types/jest": "^29.2.5",
87
- "@types/jsdom": "^20.0.1",
88
- "@types/node": "^18.11.18",
89
- "@typescript-eslint/eslint-plugin": "^5.44.0",
90
- "@typescript-eslint/parser": "^5.44.0",
86
+ "@types/jest": "^29.4.0",
87
+ "@types/jsdom": "^21.1.0",
88
+ "@types/node": "^18.14.0",
89
+ "@typescript-eslint/eslint-plugin": "^5.53.0",
90
+ "@typescript-eslint/parser": "^5.53.0",
91
91
  "css-loader": "^6.7.3",
92
- "eslint": "^8.31.0",
92
+ "eslint": "^8.34.0",
93
93
  "husky": "^8.0.3",
94
- "jest": "^29.3.1",
95
- "jest-environment-jsdom": "^29.3.1",
96
- "jsdom": "^20.0.3",
97
- "lint-staged": "^13.1.0",
94
+ "jest": "^29.4.3",
95
+ "jest-environment-jsdom": "^29.4.3",
96
+ "jsdom": "^21.1.0",
97
+ "lint-staged": "^13.1.2",
98
98
  "pinst": "^3.0.0",
99
99
  "style-loader": "^3.3.1",
100
100
  "terser-webpack-plugin": "^5.3.6",
101
- "ts-jest": "^29.0.3",
101
+ "ts-jest": "^29.0.5",
102
102
  "ts-loader": "^9.4.2",
103
103
  "ts-node": "^10.9.1",
104
- "typedoc": "^0.23.24",
105
- "typescript": "^4.9.4",
104
+ "typedoc": "^0.23.25",
105
+ "typescript": "^4.9.5",
106
106
  "webpack": "^5.75.0"
107
107
  },
108
108
  "bugs": {
@@ -6,6 +6,9 @@ import Color4 from './Color4';
6
6
  import DummyRenderer from './rendering/renderers/DummyRenderer';
7
7
  import createEditor from './testing/createEditor';
8
8
  import RenderingStyle from './rendering/RenderingStyle';
9
+ import Rect2 from './math/Rect2';
10
+ import Mat33 from './math/Mat33';
11
+ import { SerializableCommand } from './lib';
9
12
 
10
13
  describe('EditorImage', () => {
11
14
  const testStroke = new Stroke([
@@ -84,4 +87,34 @@ describe('EditorImage', () => {
84
87
  expect(firstParent?.getParent()).toStrictEqual(secondParent?.getParent());
85
88
  expect(firstParent?.getParent()?.getParent()).toBeNull();
86
89
  });
90
+
91
+ it('setImportExportRect should return a serializable command', () => {
92
+ const editor = createEditor();
93
+ const image = editor.image;
94
+
95
+ const originalRect = editor.getImportExportRect();
96
+ const newRect = new Rect2(3, 4, 5, 6);
97
+ const command = image.setImportExportRect(newRect);
98
+ expect(command.serialize().data).toMatchObject({
99
+ originalSize: originalRect.size.xy,
100
+ originalTransform: Mat33.identity.toArray(),
101
+ newRegion: {
102
+ x: 3,
103
+ y: 4,
104
+ w: 5,
105
+ h: 6,
106
+ },
107
+ });
108
+
109
+ expect(editor.getImportExportRect()).objEq(originalRect);
110
+ command.apply(editor);
111
+ expect(editor.getImportExportRect()).objEq(newRect);
112
+
113
+ const deserializedCommand = SerializableCommand.deserialize(command.serialize(), editor);
114
+
115
+ deserializedCommand.unapply(editor);
116
+ expect(editor.getImportExportRect()).objEq(originalRect);
117
+ deserializedCommand.apply(editor);
118
+ expect(editor.getImportExportRect()).objEq(newRect);
119
+ });
87
120
  });
@@ -8,8 +8,8 @@ import RenderingCache from './rendering/caching/RenderingCache';
8
8
  import SerializableCommand from './commands/SerializableCommand';
9
9
  import EventDispatcher from './EventDispatcher';
10
10
  import { Vec2 } from './math/Vec2';
11
- import Command from './commands/Command';
12
- import Mat33 from './math/Mat33';
11
+ import Mat33, { Mat33Array } from './math/Mat33';
12
+ import { assertIsNumber, assertIsNumberArray } from './util/assertions';
13
13
 
14
14
  // @internal Sort by z-index, low to high
15
15
  export const sortLeavesByZIndex = (leaves: Array<ImageNode>) => {
@@ -51,39 +51,6 @@ export default class EditorImage {
51
51
  this.importExportViewport.updateScreenSize(Vec2.of(500, 500));
52
52
  }
53
53
 
54
- /**
55
- * @returns a `Viewport` for rendering the image when importing/exporting.
56
- */
57
- public getImportExportViewport() {
58
- return this.importExportViewport;
59
- }
60
-
61
- public setImportExportRect(imageRect: Rect2): Command {
62
- const importExportViewport = this.getImportExportViewport();
63
- const origSize = importExportViewport.visibleRect.size;
64
- const origTransform = importExportViewport.canvasToScreenTransform;
65
-
66
- return new class extends Command {
67
- public apply(editor: Editor) {
68
- const viewport = editor.image.getImportExportViewport();
69
- viewport.updateScreenSize(imageRect.size);
70
- viewport.resetTransform(Mat33.translation(imageRect.topLeft.times(-1)));
71
- editor.queueRerender();
72
- }
73
-
74
- public unapply(editor: Editor) {
75
- const viewport = editor.image.getImportExportViewport();
76
- viewport.updateScreenSize(origSize);
77
- viewport.resetTransform(origTransform);
78
- editor.queueRerender();
79
- }
80
-
81
- public description(_editor: Editor, localizationTable: EditorLocalization) {
82
- return localizationTable.resizeOutputCommand(imageRect);
83
- }
84
- };
85
- }
86
-
87
54
  // Returns all components that make up the background of this image. These
88
55
  // components are rendered below all other components.
89
56
  public getBackgroundComponents(): AbstractComponent[] {
@@ -271,6 +238,91 @@ export default class EditorImage {
271
238
  });
272
239
  }
273
240
  };
241
+
242
+
243
+
244
+ /**
245
+ * @returns a `Viewport` for rendering the image when importing/exporting.
246
+ */
247
+ public getImportExportViewport() {
248
+ return this.importExportViewport;
249
+ }
250
+
251
+ public setImportExportRect(imageRect: Rect2): SerializableCommand {
252
+ const importExportViewport = this.getImportExportViewport();
253
+ const origSize = importExportViewport.visibleRect.size;
254
+ const origTransform = importExportViewport.canvasToScreenTransform;
255
+
256
+ return new EditorImage.SetImportExportRectCommand(origSize, origTransform, imageRect);
257
+ }
258
+
259
+ // Handles resizing the background import/export region of the image.
260
+ private static SetImportExportRectCommand = class extends SerializableCommand {
261
+ private static commandId = 'set-import-export-rect';
262
+
263
+ public constructor(
264
+ private originalSize: Vec2,
265
+ private originalTransform: Mat33,
266
+ private finalRect: Rect2,
267
+ ) {
268
+ super(EditorImage.SetImportExportRectCommand.commandId);
269
+ }
270
+
271
+ public apply(editor: Editor) {
272
+ const viewport = editor.image.getImportExportViewport();
273
+ viewport.updateScreenSize(this.finalRect.size);
274
+ viewport.resetTransform(Mat33.translation(this.finalRect.topLeft.times(-1)));
275
+ editor.queueRerender();
276
+ }
277
+
278
+ public unapply(editor: Editor) {
279
+ const viewport = editor.image.getImportExportViewport();
280
+ viewport.updateScreenSize(this.originalSize);
281
+ viewport.resetTransform(this.originalTransform);
282
+ editor.queueRerender();
283
+ }
284
+
285
+ public description(_editor: Editor, localization: EditorLocalization) {
286
+ return localization.resizeOutputCommand(this.finalRect);
287
+ }
288
+
289
+ protected serializeToJSON() {
290
+ return {
291
+ originalSize: this.originalSize.xy,
292
+ originalTransform: this.originalTransform.toArray(),
293
+ newRegion: {
294
+ x: this.finalRect.x,
295
+ y: this.finalRect.y,
296
+ w: this.finalRect.w,
297
+ h: this.finalRect.h,
298
+ },
299
+ };
300
+ }
301
+
302
+ static {
303
+ const commandId = this.commandId;
304
+ SerializableCommand.register(commandId, (json: any, _editor: Editor) => {
305
+ assertIsNumber(json.originalSize.x);
306
+ assertIsNumber(json.originalSize.y);
307
+ assertIsNumberArray(json.originalTransform);
308
+ assertIsNumberArray([
309
+ json.newRegion.x,
310
+ json.newRegion.y,
311
+ json.newRegion.w,
312
+ json.newRegion.h,
313
+ ]);
314
+
315
+ const originalSize = Vec2.ofXY(json.originalSize);
316
+ const originalTransform = new Mat33(...(json.originalTransform as Mat33Array));
317
+ const finalRect = new Rect2(
318
+ json.newRegion.x, json.newRegion.y, json.newRegion.w, json.newRegion.h
319
+ );
320
+ return new EditorImage.SetImportExportRectCommand(
321
+ originalSize, originalTransform, finalRect
322
+ );
323
+ });
324
+ }
325
+ };
274
326
  }
275
327
 
276
328
  type TooSmallToRenderCheck = (rect: Rect2)=> boolean;