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.
- package/README.md +1 -1
- package/dist/bundle.js +2 -2
- package/dist/cjs/components/BackgroundComponent.js +12 -6
- package/dist/cjs/components/SVGGlobalAttributesObject.d.ts +2 -2
- package/dist/cjs/components/SVGGlobalAttributesObject.js +10 -14
- package/dist/cjs/image/export/adjustExportedSVGSize.d.ts +6 -0
- package/dist/cjs/image/export/{setExportedSVGSize.js → adjustExportedSVGSize.js} +4 -7
- package/dist/cjs/image/export/editorImageToSVG.d.ts +1 -1
- package/dist/cjs/image/export/editorImageToSVG.js +22 -8
- package/dist/cjs/rendering/renderers/AbstractRenderer.d.ts +1 -0
- package/dist/cjs/rendering/renderers/AbstractRenderer.js +8 -0
- package/dist/cjs/rendering/renderers/SVGRenderer.d.ts +28 -1
- package/dist/cjs/rendering/renderers/SVGRenderer.js +58 -7
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/components/BackgroundComponent.mjs +12 -6
- package/dist/mjs/components/SVGGlobalAttributesObject.d.ts +2 -2
- package/dist/mjs/components/SVGGlobalAttributesObject.mjs +10 -14
- package/dist/mjs/image/export/adjustExportedSVGSize.d.ts +6 -0
- package/dist/mjs/image/export/{setExportedSVGSize.mjs → adjustExportedSVGSize.mjs} +4 -7
- package/dist/mjs/image/export/editorImageToSVG.d.ts +1 -1
- package/dist/mjs/image/export/editorImageToSVG.mjs +23 -9
- package/dist/mjs/rendering/renderers/AbstractRenderer.d.ts +1 -0
- package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +8 -0
- package/dist/mjs/rendering/renderers/SVGRenderer.d.ts +28 -1
- package/dist/mjs/rendering/renderers/SVGRenderer.mjs +58 -7
- package/dist/mjs/version.mjs +1 -1
- package/package.json +2 -2
- package/dist/cjs/image/export/setExportedSVGSize.d.ts +0 -6
- 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
|
-
|
230
|
-
|
231
|
-
|
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(
|
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(
|
79
|
-
|
80
|
-
|
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;
|
@@ -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
|
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 =
|
11
|
-
let height =
|
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 =
|
22
|
+
exports.default = adjustExportedSVGSize;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import EditorImage, { PreRenderComponentCallback } from '../EditorImage';
|
2
|
-
import { SVGSizingOptions } from './
|
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
|
10
|
+
const adjustExportedSVGSize_1 = __importDefault(require("./adjustExportedSVGSize"));
|
11
11
|
const toSVGInternal = (image, renderFunction, options) => {
|
12
12
|
const importExportViewport = image.getImportExportViewport().getTemporaryClone();
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
41
|
+
const exportRect = importExportViewport.visibleRect;
|
42
|
+
(0, adjustExportedSVGSize_1.default)(result, exportRect, options);
|
29
43
|
return result;
|
30
44
|
});
|
31
45
|
return result;
|
@@ -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
|
-
|
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
|
-
|
348
|
-
|
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
|
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',
|
354
|
-
result.setAttribute('width', (0, math_1.toRoundedString)(
|
355
|
-
result.setAttribute('height', (0, math_1.toRoundedString)(
|
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
|
-
|
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;
|
package/dist/cjs/version.js
CHANGED
@@ -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
|
-
|
201
|
-
|
202
|
-
|
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(
|
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(
|
51
|
-
|
52
|
-
|
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);
|
@@ -1,12 +1,9 @@
|
|
1
1
|
import { toRoundedString } from '@js-draw/math';
|
2
2
|
// @internal
|
3
|
-
const
|
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 =
|
9
|
-
let height =
|
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
|
20
|
+
export default adjustExportedSVGSize;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import EditorImage, { PreRenderComponentCallback } from '../EditorImage';
|
2
|
-
import { SVGSizingOptions } from './
|
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 {
|
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
|
4
|
+
import adjustExportedSVGSize from './adjustExportedSVGSize.mjs';
|
5
5
|
const toSVGInternal = (image, renderFunction, options) => {
|
6
6
|
const importExportViewport = image.getImportExportViewport().getTemporaryClone();
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
35
|
+
const exportRect = importExportViewport.visibleRect;
|
36
|
+
adjustExportedSVGSize(result, exportRect, options);
|
23
37
|
return result;
|
24
38
|
});
|
25
39
|
return result;
|
@@ -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
|
-
|
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 {};
|