js-draw 1.4.0 → 1.4.1

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 (29) hide show
  1. package/README.md +1 -1
  2. package/dist/bundle.js +2 -2
  3. package/dist/cjs/components/BackgroundComponent.js +12 -6
  4. package/dist/cjs/components/SVGGlobalAttributesObject.d.ts +2 -2
  5. package/dist/cjs/components/SVGGlobalAttributesObject.js +10 -14
  6. package/dist/cjs/image/export/adjustExportedSVGSize.d.ts +6 -0
  7. package/dist/cjs/image/export/{setExportedSVGSize.js → adjustExportedSVGSize.js} +4 -7
  8. package/dist/cjs/image/export/editorImageToSVG.d.ts +1 -1
  9. package/dist/cjs/image/export/editorImageToSVG.js +22 -8
  10. package/dist/cjs/rendering/renderers/AbstractRenderer.d.ts +1 -0
  11. package/dist/cjs/rendering/renderers/AbstractRenderer.js +8 -0
  12. package/dist/cjs/rendering/renderers/SVGRenderer.d.ts +28 -1
  13. package/dist/cjs/rendering/renderers/SVGRenderer.js +58 -7
  14. package/dist/cjs/version.js +1 -1
  15. package/dist/mjs/components/BackgroundComponent.mjs +12 -6
  16. package/dist/mjs/components/SVGGlobalAttributesObject.d.ts +2 -2
  17. package/dist/mjs/components/SVGGlobalAttributesObject.mjs +10 -14
  18. package/dist/mjs/image/export/adjustExportedSVGSize.d.ts +6 -0
  19. package/dist/mjs/image/export/{setExportedSVGSize.mjs → adjustExportedSVGSize.mjs} +4 -7
  20. package/dist/mjs/image/export/editorImageToSVG.d.ts +1 -1
  21. package/dist/mjs/image/export/editorImageToSVG.mjs +23 -9
  22. package/dist/mjs/rendering/renderers/AbstractRenderer.d.ts +1 -0
  23. package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +8 -0
  24. package/dist/mjs/rendering/renderers/SVGRenderer.d.ts +28 -1
  25. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +58 -7
  26. package/dist/mjs/version.mjs +1 -1
  27. package/package.json +2 -2
  28. package/dist/cjs/image/export/setExportedSVGSize.d.ts +0 -6
  29. package/dist/mjs/image/export/setExportedSVGSize.d.ts +0 -6
@@ -219,6 +219,14 @@ class BackgroundComponent extends AbstractComponent_1.default {
219
219
  if (this.backgroundType === BackgroundType.None) {
220
220
  return;
221
221
  }
222
+ // If visibleRect is null, components should render everything.
223
+ // In that case, a full render is being done.
224
+ const mustRender = !visibleRect;
225
+ // If this.fillsScreen, the visibleRect needs to be known.
226
+ // Use the screen rect.
227
+ if (this.fillsScreen) {
228
+ visibleRect ??= canvas.getVisibleRect();
229
+ }
222
230
  const clip = this.backgroundType === BackgroundType.Grid;
223
231
  const contentBBox = this.getFullBoundingBox(visibleRect);
224
232
  canvas.startObject(contentBBox, clip);
@@ -226,13 +234,11 @@ class BackgroundComponent extends AbstractComponent_1.default {
226
234
  // If the rectangle for this region contains the visible rect,
227
235
  // we can fill the entire visible rectangle (which may be more efficient than
228
236
  // filling the entire region for this.)
229
- if (visibleRect) {
230
- const intersection = visibleRect.intersection(contentBBox);
231
- if (intersection) {
232
- canvas.fillRect(intersection, this.mainColor);
233
- }
237
+ const intersection = visibleRect?.intersection(contentBBox);
238
+ if (intersection) {
239
+ canvas.fillRect(intersection, this.mainColor);
234
240
  }
235
- else {
241
+ else if (mustRender) {
236
242
  canvas.fillRect(contentBBox, this.mainColor);
237
243
  }
238
244
  }
@@ -4,8 +4,8 @@ import AbstractComponent, { ComponentSizingMode } from './AbstractComponent';
4
4
  import { ImageComponentLocalization } from './localization';
5
5
  type GlobalAttrsList = Array<[string, string | null]>;
6
6
  export default class SVGGlobalAttributesObject extends AbstractComponent {
7
- private readonly attrs;
8
7
  protected contentBBox: Rect2;
8
+ private readonly attrs;
9
9
  constructor(attrs: GlobalAttrsList);
10
10
  render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
11
11
  intersects(_lineSegment: LineSegment2): boolean;
@@ -15,6 +15,6 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
15
15
  protected createClone(): SVGGlobalAttributesObject;
16
16
  description(localization: ImageComponentLocalization): string;
17
17
  protected serializeToJSON(): string | null;
18
- static deserializeFromString(data: string): AbstractComponent;
18
+ static deserializeFromString(_data: string): AbstractComponent;
19
19
  }
20
20
  export {};
@@ -38,10 +38,16 @@ const AbstractComponent_1 = __importStar(require("./AbstractComponent"));
38
38
  const componentKind = 'svg-global-attributes';
39
39
  // Stores global SVG attributes (e.g. namespace identifiers.)
40
40
  class SVGGlobalAttributesObject extends AbstractComponent_1.default {
41
+ // Does not modify `attrs`
41
42
  constructor(attrs) {
42
43
  super(componentKind);
43
- this.attrs = attrs;
44
44
  this.contentBBox = math_1.Rect2.empty;
45
+ // Already stored/managed in `editor.image`.
46
+ const attrsManagedByRenderer = ['viewBox', 'width', 'height'];
47
+ // Only store attributes that aren't managed by other parts of the app.
48
+ this.attrs = attrs.filter(([attr, _value]) => {
49
+ return !attrsManagedByRenderer.includes(attr);
50
+ });
45
51
  }
46
52
  render(canvas, _visibleRect) {
47
53
  if (!(canvas instanceof SVGRenderer_1.default)) {
@@ -75,19 +81,9 @@ class SVGGlobalAttributesObject extends AbstractComponent_1.default {
75
81
  serializeToJSON() {
76
82
  return JSON.stringify(this.attrs);
77
83
  }
78
- static deserializeFromString(data) {
79
- const json = JSON.parse(data);
80
- const attrs = [];
81
- const numericAndSpaceContentExp = /^[ \t\n0-9.-eE]+$/;
82
- // Don't deserialize all attributes, just those that should be safe.
83
- for (const [key, val] of json) {
84
- if (key === 'viewBox' || key === 'width' || key === 'height') {
85
- if (val && numericAndSpaceContentExp.exec(val)) {
86
- attrs.push([key, val]);
87
- }
88
- }
89
- }
90
- return new SVGGlobalAttributesObject(attrs);
84
+ static deserializeFromString(_data) {
85
+ // To be safe, don't deserialize any attributes
86
+ return new SVGGlobalAttributesObject([]);
91
87
  }
92
88
  }
93
89
  exports.default = SVGGlobalAttributesObject;
@@ -0,0 +1,6 @@
1
+ import { Rect2 } from '@js-draw/math';
2
+ export type SVGSizingOptions = {
3
+ minDimension?: number;
4
+ };
5
+ declare const adjustExportedSVGSize: (svg: SVGElement, exportRect: Rect2, options: SVGSizingOptions) => void;
6
+ export default adjustExportedSVGSize;
@@ -2,13 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const math_1 = require("@js-draw/math");
4
4
  // @internal
5
- const setExportedSVGSize = (svg, viewport, options) => {
6
- // Just show the main region
7
- const rect = viewport.visibleRect;
8
- svg.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => (0, math_1.toRoundedString)(part)).join(' '));
5
+ const adjustExportedSVGSize = (svg, exportRect, options) => {
9
6
  // Adjust the width/height as necessary
10
- let width = rect.w;
11
- let height = rect.h;
7
+ let width = exportRect.w;
8
+ let height = exportRect.h;
12
9
  if (options?.minDimension && width < options.minDimension) {
13
10
  const newWidth = options.minDimension;
14
11
  height *= newWidth / (width || 1);
@@ -22,4 +19,4 @@ const setExportedSVGSize = (svg, viewport, options) => {
22
19
  svg.setAttribute('width', (0, math_1.toRoundedString)(width));
23
20
  svg.setAttribute('height', (0, math_1.toRoundedString)(height));
24
21
  };
25
- exports.default = setExportedSVGSize;
22
+ exports.default = adjustExportedSVGSize;
@@ -1,5 +1,5 @@
1
1
  import EditorImage, { PreRenderComponentCallback } from '../EditorImage';
2
- import { SVGSizingOptions } from './setExportedSVGSize';
2
+ import { SVGSizingOptions } from './adjustExportedSVGSize';
3
3
  export interface SVGExportOptions extends SVGSizingOptions {
4
4
  sanitize?: boolean;
5
5
  minDimension?: number;
@@ -7,25 +7,39 @@ exports.editorImageToSVGAsync = exports.editorImageToSVGSync = void 0;
7
7
  const math_1 = require("@js-draw/math");
8
8
  const SVGRenderer_1 = __importDefault(require("../../rendering/renderers/SVGRenderer"));
9
9
  const SVGLoader_1 = require("../../SVGLoader");
10
- const setExportedSVGSize_1 = __importDefault(require("./setExportedSVGSize"));
10
+ const adjustExportedSVGSize_1 = __importDefault(require("./adjustExportedSVGSize"));
11
11
  const toSVGInternal = (image, renderFunction, options) => {
12
12
  const importExportViewport = image.getImportExportViewport().getTemporaryClone();
13
- const { element: result, renderer } = SVGRenderer_1.default.fromViewport(importExportViewport, options.sanitize ?? false);
14
- const origTransform = importExportViewport.canvasToScreenTransform;
15
- // Render with (0,0) at (0,0) — we'll handle translation with
16
- // the viewBox property.
17
- importExportViewport.resetTransform(math_1.Mat33.identity);
13
+ // If the rectangle has zero width or height, its size can't be increased
14
+ // -- set its size to the minimum.
15
+ if (options?.minDimension) {
16
+ const originalRect = importExportViewport.visibleRect;
17
+ let rect = originalRect;
18
+ if (rect.w <= 0) {
19
+ rect = new math_1.Rect2(rect.x, rect.y, options.minDimension, rect.h);
20
+ }
21
+ if (rect.h <= 0) {
22
+ rect = new math_1.Rect2(rect.x, rect.y, rect.w, options.minDimension);
23
+ }
24
+ if (!rect.eq(originalRect)) {
25
+ importExportViewport.updateScreenSize(rect.size);
26
+ }
27
+ }
28
+ const { element: result, renderer } = SVGRenderer_1.default.fromViewport(importExportViewport, {
29
+ sanitize: options.sanitize ?? false,
30
+ useViewBoxForPositioning: true,
31
+ });
18
32
  // Use a callback rather than async/await to allow this function to create
19
33
  // both sync and async render functions
20
34
  renderFunction(renderer, () => {
21
- importExportViewport.resetTransform(origTransform);
22
35
  if (image.getAutoresizeEnabled()) {
23
36
  result.classList.add(SVGLoader_1.svgLoaderAutoresizeClassName);
24
37
  }
25
38
  else {
26
39
  result.classList.remove(SVGLoader_1.svgLoaderAutoresizeClassName);
27
40
  }
28
- (0, setExportedSVGSize_1.default)(result, importExportViewport, options);
41
+ const exportRect = importExportViewport.visibleRect;
42
+ (0, adjustExportedSVGSize_1.default)(result, exportRect, options);
29
43
  return result;
30
44
  });
31
45
  return result;
@@ -73,4 +73,5 @@ export default abstract class AbstractRenderer {
73
73
  getCanvasToScreenTransform(): Mat33;
74
74
  canvasToScreen(vec: Vec2): Vec2;
75
75
  getSizeOfCanvasPixelOnScreen(): number;
76
+ getVisibleRect(): Rect2;
76
77
  }
@@ -160,5 +160,13 @@ class AbstractRenderer {
160
160
  getSizeOfCanvasPixelOnScreen() {
161
161
  return this.getCanvasToScreenTransform().transformVec3(math_1.Vec2.unitX).length();
162
162
  }
163
+ // Returns the region in canvas space that is visible within the viewport this
164
+ // canvas is rendering to.
165
+ //
166
+ // Note that in some cases this might not be the same as the `visibleRect` given
167
+ // to components in their `render` method.
168
+ getVisibleRect() {
169
+ return this.viewport.visibleRect;
170
+ }
163
171
  }
164
172
  exports.default = AbstractRenderer;
@@ -6,6 +6,15 @@ import TextRenderingStyle from '../TextRenderingStyle';
6
6
  import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
7
7
  import RenderablePathSpec from '../RenderablePathSpec';
8
8
  export declare const renderedStylesheetId = "js-draw-style-sheet";
9
+ type FromViewportOptions = {
10
+ sanitize?: boolean;
11
+ /**
12
+ * Rather than having the top left of the `viewBox` set to (0, 0),
13
+ * if `useViewBoxForPositioning` is `true`, the `viewBox`'s top left
14
+ * is based on the top left of the rendering viewport's `visibleRect`.
15
+ */
16
+ useViewBoxForPositioning?: boolean;
17
+ };
9
18
  /**
10
19
  * Renders onto an `SVGElement`.
11
20
  *
@@ -50,8 +59,26 @@ export default class SVGRenderer extends AbstractRenderer {
50
59
  drawPoints(...points: Point2[]): void;
51
60
  drawSVGElem(elem: SVGElement): void;
52
61
  isTooSmallToRender(_rect: Rect2): boolean;
53
- static fromViewport(viewport: Viewport, sanitize?: boolean): {
62
+ private visibleRectOverride;
63
+ /**
64
+ * Overrides the visible region returned by `getVisibleRect`.
65
+ *
66
+ * This is useful when the `viewport`'s transform has been modified,
67
+ * for example, to compensate for storing part of the image's
68
+ * transformation in an SVG property.
69
+ */
70
+ private overrideVisibleRect;
71
+ getVisibleRect(): Rect2;
72
+ /**
73
+ * Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
74
+ * and other metadata attributes set for the given `Viewport`.
75
+ *
76
+ * If `options` is a `boolean`, it is interpreted as whether to sanitize (not add unknown
77
+ * SVG entities to) the output.
78
+ */
79
+ static fromViewport(viewport: Viewport, options?: FromViewportOptions | boolean): {
54
80
  element: SVGSVGElement;
55
81
  renderer: SVGRenderer;
56
82
  };
57
83
  }
84
+ export {};
@@ -42,6 +42,7 @@ class SVGRenderer extends AbstractRenderer_1.default {
42
42
  this.textContainer = null;
43
43
  this.textContainerTransform = null;
44
44
  this.textParentStyle = defaultTextStyle;
45
+ this.visibleRectOverride = null;
45
46
  this.clear();
46
47
  this.addStyleSheet();
47
48
  }
@@ -344,21 +345,71 @@ class SVGRenderer extends AbstractRenderer_1.default {
344
345
  isTooSmallToRender(_rect) {
345
346
  return false;
346
347
  }
347
- // Creates a new SVG element and SVGRenerer with attributes set for the given Viewport.
348
- static fromViewport(viewport, sanitize = true) {
348
+ /**
349
+ * Overrides the visible region returned by `getVisibleRect`.
350
+ *
351
+ * This is useful when the `viewport`'s transform has been modified,
352
+ * for example, to compensate for storing part of the image's
353
+ * transformation in an SVG property.
354
+ */
355
+ overrideVisibleRect(newRect) {
356
+ this.visibleRectOverride = newRect;
357
+ }
358
+ getVisibleRect() {
359
+ return this.visibleRectOverride ?? super.getVisibleRect();
360
+ }
361
+ /**
362
+ * Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
363
+ * and other metadata attributes set for the given `Viewport`.
364
+ *
365
+ * If `options` is a `boolean`, it is interpreted as whether to sanitize (not add unknown
366
+ * SVG entities to) the output.
367
+ */
368
+ static fromViewport(viewport, options = true) {
369
+ let sanitize;
370
+ let useViewBoxForPositioning;
371
+ if (typeof options === 'boolean') {
372
+ sanitize = options;
373
+ useViewBoxForPositioning = false;
374
+ }
375
+ else {
376
+ sanitize = options.sanitize ?? true;
377
+ useViewBoxForPositioning = options.useViewBoxForPositioning ?? false;
378
+ }
349
379
  const svgNameSpace = 'http://www.w3.org/2000/svg';
350
380
  const result = document.createElementNS(svgNameSpace, 'svg');
351
- const rect = viewport.getScreenRectSize();
381
+ const screenRectSize = viewport.getScreenRectSize();
382
+ const visibleRect = viewport.visibleRect;
383
+ let viewBoxComponents;
384
+ if (useViewBoxForPositioning) {
385
+ const exportRect = viewport.visibleRect;
386
+ viewBoxComponents = [
387
+ exportRect.x, exportRect.y, exportRect.w, exportRect.h,
388
+ ];
389
+ // Replace the viewport with a copy that has a modified transform.
390
+ // (Avoids modifying the original viewport).
391
+ viewport = viewport.getTemporaryClone();
392
+ // TODO: This currently discards any rotation information.
393
+ // Render with (0,0) at (0,0) -- the translation is handled by the viewBox.
394
+ viewport.resetTransform(math_1.Mat33.identity);
395
+ }
396
+ else {
397
+ viewBoxComponents = [0, 0, screenRectSize.x, screenRectSize.y];
398
+ }
352
399
  // rect.x -> size of rect in x direction, rect.y -> size of rect in y direction.
353
- result.setAttribute('viewBox', [0, 0, rect.x, rect.y].map(part => (0, math_1.toRoundedString)(part)).join(' '));
354
- result.setAttribute('width', (0, math_1.toRoundedString)(rect.x));
355
- result.setAttribute('height', (0, math_1.toRoundedString)(rect.y));
400
+ result.setAttribute('viewBox', viewBoxComponents.map(part => (0, math_1.toRoundedString)(part)).join(' '));
401
+ result.setAttribute('width', (0, math_1.toRoundedString)(screenRectSize.x));
402
+ result.setAttribute('height', (0, math_1.toRoundedString)(screenRectSize.y));
356
403
  // Ensure the image can be identified as an SVG if downloaded.
357
404
  // See https://jwatt.org/svg/authoring/
358
405
  result.setAttribute('version', '1.1');
359
406
  result.setAttribute('baseProfile', 'full');
360
407
  result.setAttribute('xmlns', svgNameSpace);
361
- return { element: result, renderer: new SVGRenderer(result, viewport, sanitize) };
408
+ const renderer = new SVGRenderer(result, viewport, sanitize);
409
+ if (!visibleRect.eq(viewport.visibleRect)) {
410
+ renderer.overrideVisibleRect(visibleRect);
411
+ }
412
+ return { element: result, renderer };
362
413
  }
363
414
  }
364
415
  exports.default = SVGRenderer;
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = {
4
- number: '1.4.0',
4
+ number: '1.4.1',
5
5
  };
@@ -190,6 +190,14 @@ export default class BackgroundComponent extends AbstractComponent {
190
190
  if (this.backgroundType === BackgroundType.None) {
191
191
  return;
192
192
  }
193
+ // If visibleRect is null, components should render everything.
194
+ // In that case, a full render is being done.
195
+ const mustRender = !visibleRect;
196
+ // If this.fillsScreen, the visibleRect needs to be known.
197
+ // Use the screen rect.
198
+ if (this.fillsScreen) {
199
+ visibleRect ??= canvas.getVisibleRect();
200
+ }
193
201
  const clip = this.backgroundType === BackgroundType.Grid;
194
202
  const contentBBox = this.getFullBoundingBox(visibleRect);
195
203
  canvas.startObject(contentBBox, clip);
@@ -197,13 +205,11 @@ export default class BackgroundComponent extends AbstractComponent {
197
205
  // If the rectangle for this region contains the visible rect,
198
206
  // we can fill the entire visible rectangle (which may be more efficient than
199
207
  // filling the entire region for this.)
200
- if (visibleRect) {
201
- const intersection = visibleRect.intersection(contentBBox);
202
- if (intersection) {
203
- canvas.fillRect(intersection, this.mainColor);
204
- }
208
+ const intersection = visibleRect?.intersection(contentBBox);
209
+ if (intersection) {
210
+ canvas.fillRect(intersection, this.mainColor);
205
211
  }
206
- else {
212
+ else if (mustRender) {
207
213
  canvas.fillRect(contentBBox, this.mainColor);
208
214
  }
209
215
  }
@@ -4,8 +4,8 @@ import AbstractComponent, { ComponentSizingMode } from './AbstractComponent';
4
4
  import { ImageComponentLocalization } from './localization';
5
5
  type GlobalAttrsList = Array<[string, string | null]>;
6
6
  export default class SVGGlobalAttributesObject extends AbstractComponent {
7
- private readonly attrs;
8
7
  protected contentBBox: Rect2;
8
+ private readonly attrs;
9
9
  constructor(attrs: GlobalAttrsList);
10
10
  render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
11
11
  intersects(_lineSegment: LineSegment2): boolean;
@@ -15,6 +15,6 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
15
15
  protected createClone(): SVGGlobalAttributesObject;
16
16
  description(localization: ImageComponentLocalization): string;
17
17
  protected serializeToJSON(): string | null;
18
- static deserializeFromString(data: string): AbstractComponent;
18
+ static deserializeFromString(_data: string): AbstractComponent;
19
19
  }
20
20
  export {};
@@ -10,10 +10,16 @@ import AbstractComponent, { ComponentSizingMode } from './AbstractComponent.mj
10
10
  const componentKind = 'svg-global-attributes';
11
11
  // Stores global SVG attributes (e.g. namespace identifiers.)
12
12
  export default class SVGGlobalAttributesObject extends AbstractComponent {
13
+ // Does not modify `attrs`
13
14
  constructor(attrs) {
14
15
  super(componentKind);
15
- this.attrs = attrs;
16
16
  this.contentBBox = Rect2.empty;
17
+ // Already stored/managed in `editor.image`.
18
+ const attrsManagedByRenderer = ['viewBox', 'width', 'height'];
19
+ // Only store attributes that aren't managed by other parts of the app.
20
+ this.attrs = attrs.filter(([attr, _value]) => {
21
+ return !attrsManagedByRenderer.includes(attr);
22
+ });
17
23
  }
18
24
  render(canvas, _visibleRect) {
19
25
  if (!(canvas instanceof SVGRenderer)) {
@@ -47,19 +53,9 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
47
53
  serializeToJSON() {
48
54
  return JSON.stringify(this.attrs);
49
55
  }
50
- static deserializeFromString(data) {
51
- const json = JSON.parse(data);
52
- const attrs = [];
53
- const numericAndSpaceContentExp = /^[ \t\n0-9.-eE]+$/;
54
- // Don't deserialize all attributes, just those that should be safe.
55
- for (const [key, val] of json) {
56
- if (key === 'viewBox' || key === 'width' || key === 'height') {
57
- if (val && numericAndSpaceContentExp.exec(val)) {
58
- attrs.push([key, val]);
59
- }
60
- }
61
- }
62
- return new SVGGlobalAttributesObject(attrs);
56
+ static deserializeFromString(_data) {
57
+ // To be safe, don't deserialize any attributes
58
+ return new SVGGlobalAttributesObject([]);
63
59
  }
64
60
  }
65
61
  AbstractComponent.registerComponent(componentKind, SVGGlobalAttributesObject.deserializeFromString);
@@ -0,0 +1,6 @@
1
+ import { Rect2 } from '@js-draw/math';
2
+ export type SVGSizingOptions = {
3
+ minDimension?: number;
4
+ };
5
+ declare const adjustExportedSVGSize: (svg: SVGElement, exportRect: Rect2, options: SVGSizingOptions) => void;
6
+ export default adjustExportedSVGSize;
@@ -1,12 +1,9 @@
1
1
  import { toRoundedString } from '@js-draw/math';
2
2
  // @internal
3
- const setExportedSVGSize = (svg, viewport, options) => {
4
- // Just show the main region
5
- const rect = viewport.visibleRect;
6
- svg.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => toRoundedString(part)).join(' '));
3
+ const adjustExportedSVGSize = (svg, exportRect, options) => {
7
4
  // Adjust the width/height as necessary
8
- let width = rect.w;
9
- let height = rect.h;
5
+ let width = exportRect.w;
6
+ let height = exportRect.h;
10
7
  if (options?.minDimension && width < options.minDimension) {
11
8
  const newWidth = options.minDimension;
12
9
  height *= newWidth / (width || 1);
@@ -20,4 +17,4 @@ const setExportedSVGSize = (svg, viewport, options) => {
20
17
  svg.setAttribute('width', toRoundedString(width));
21
18
  svg.setAttribute('height', toRoundedString(height));
22
19
  };
23
- export default setExportedSVGSize;
20
+ export default adjustExportedSVGSize;
@@ -1,5 +1,5 @@
1
1
  import EditorImage, { PreRenderComponentCallback } from '../EditorImage';
2
- import { SVGSizingOptions } from './setExportedSVGSize';
2
+ import { SVGSizingOptions } from './adjustExportedSVGSize';
3
3
  export interface SVGExportOptions extends SVGSizingOptions {
4
4
  sanitize?: boolean;
5
5
  minDimension?: number;
@@ -1,25 +1,39 @@
1
- import { Mat33 } from '@js-draw/math';
1
+ import { Rect2 } from '@js-draw/math';
2
2
  import SVGRenderer from '../../rendering/renderers/SVGRenderer.mjs';
3
3
  import { svgLoaderAutoresizeClassName } from '../../SVGLoader.mjs';
4
- import setExportedSVGSize from './setExportedSVGSize.mjs';
4
+ import adjustExportedSVGSize from './adjustExportedSVGSize.mjs';
5
5
  const toSVGInternal = (image, renderFunction, options) => {
6
6
  const importExportViewport = image.getImportExportViewport().getTemporaryClone();
7
- const { element: result, renderer } = SVGRenderer.fromViewport(importExportViewport, options.sanitize ?? false);
8
- const origTransform = importExportViewport.canvasToScreenTransform;
9
- // Render with (0,0) at (0,0) — we'll handle translation with
10
- // the viewBox property.
11
- importExportViewport.resetTransform(Mat33.identity);
7
+ // If the rectangle has zero width or height, its size can't be increased
8
+ // -- set its size to the minimum.
9
+ if (options?.minDimension) {
10
+ const originalRect = importExportViewport.visibleRect;
11
+ let rect = originalRect;
12
+ if (rect.w <= 0) {
13
+ rect = new Rect2(rect.x, rect.y, options.minDimension, rect.h);
14
+ }
15
+ if (rect.h <= 0) {
16
+ rect = new Rect2(rect.x, rect.y, rect.w, options.minDimension);
17
+ }
18
+ if (!rect.eq(originalRect)) {
19
+ importExportViewport.updateScreenSize(rect.size);
20
+ }
21
+ }
22
+ const { element: result, renderer } = SVGRenderer.fromViewport(importExportViewport, {
23
+ sanitize: options.sanitize ?? false,
24
+ useViewBoxForPositioning: true,
25
+ });
12
26
  // Use a callback rather than async/await to allow this function to create
13
27
  // both sync and async render functions
14
28
  renderFunction(renderer, () => {
15
- importExportViewport.resetTransform(origTransform);
16
29
  if (image.getAutoresizeEnabled()) {
17
30
  result.classList.add(svgLoaderAutoresizeClassName);
18
31
  }
19
32
  else {
20
33
  result.classList.remove(svgLoaderAutoresizeClassName);
21
34
  }
22
- setExportedSVGSize(result, importExportViewport, options);
35
+ const exportRect = importExportViewport.visibleRect;
36
+ adjustExportedSVGSize(result, exportRect, options);
23
37
  return result;
24
38
  });
25
39
  return result;
@@ -73,4 +73,5 @@ export default abstract class AbstractRenderer {
73
73
  getCanvasToScreenTransform(): Mat33;
74
74
  canvasToScreen(vec: Vec2): Vec2;
75
75
  getSizeOfCanvasPixelOnScreen(): number;
76
+ getVisibleRect(): Rect2;
76
77
  }
@@ -158,4 +158,12 @@ export default class AbstractRenderer {
158
158
  getSizeOfCanvasPixelOnScreen() {
159
159
  return this.getCanvasToScreenTransform().transformVec3(Vec2.unitX).length();
160
160
  }
161
+ // Returns the region in canvas space that is visible within the viewport this
162
+ // canvas is rendering to.
163
+ //
164
+ // Note that in some cases this might not be the same as the `visibleRect` given
165
+ // to components in their `render` method.
166
+ getVisibleRect() {
167
+ return this.viewport.visibleRect;
168
+ }
161
169
  }
@@ -6,6 +6,15 @@ import TextRenderingStyle from '../TextRenderingStyle';
6
6
  import AbstractRenderer, { RenderableImage } from './AbstractRenderer';
7
7
  import RenderablePathSpec from '../RenderablePathSpec';
8
8
  export declare const renderedStylesheetId = "js-draw-style-sheet";
9
+ type FromViewportOptions = {
10
+ sanitize?: boolean;
11
+ /**
12
+ * Rather than having the top left of the `viewBox` set to (0, 0),
13
+ * if `useViewBoxForPositioning` is `true`, the `viewBox`'s top left
14
+ * is based on the top left of the rendering viewport's `visibleRect`.
15
+ */
16
+ useViewBoxForPositioning?: boolean;
17
+ };
9
18
  /**
10
19
  * Renders onto an `SVGElement`.
11
20
  *
@@ -50,8 +59,26 @@ export default class SVGRenderer extends AbstractRenderer {
50
59
  drawPoints(...points: Point2[]): void;
51
60
  drawSVGElem(elem: SVGElement): void;
52
61
  isTooSmallToRender(_rect: Rect2): boolean;
53
- static fromViewport(viewport: Viewport, sanitize?: boolean): {
62
+ private visibleRectOverride;
63
+ /**
64
+ * Overrides the visible region returned by `getVisibleRect`.
65
+ *
66
+ * This is useful when the `viewport`'s transform has been modified,
67
+ * for example, to compensate for storing part of the image's
68
+ * transformation in an SVG property.
69
+ */
70
+ private overrideVisibleRect;
71
+ getVisibleRect(): Rect2;
72
+ /**
73
+ * Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
74
+ * and other metadata attributes set for the given `Viewport`.
75
+ *
76
+ * If `options` is a `boolean`, it is interpreted as whether to sanitize (not add unknown
77
+ * SVG entities to) the output.
78
+ */
79
+ static fromViewport(viewport: Viewport, options?: FromViewportOptions | boolean): {
54
80
  element: SVGSVGElement;
55
81
  renderer: SVGRenderer;
56
82
  };
57
83
  }
84
+ export {};