js-draw 1.8.0 → 1.9.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/LICENSE +1 -1
- package/dist/bundle.js +2 -2
- package/dist/cjs/Editor.js +24 -13
- package/dist/cjs/SVGLoader.d.ts +1 -1
- package/dist/cjs/SVGLoader.js +11 -2
- package/dist/cjs/Viewport.d.ts +6 -0
- package/dist/cjs/Viewport.js +6 -1
- package/dist/cjs/image/EditorImage.d.ts +20 -0
- package/dist/cjs/image/EditorImage.js +40 -11
- package/dist/cjs/rendering/Display.d.ts +9 -0
- package/dist/cjs/rendering/Display.js +47 -6
- package/dist/cjs/rendering/caching/CacheRecordManager.js +3 -4
- package/dist/cjs/rendering/caching/RenderingCache.d.ts +1 -0
- package/dist/cjs/rendering/caching/RenderingCache.js +4 -0
- package/dist/cjs/rendering/caching/RenderingCacheNode.js +19 -11
- package/dist/cjs/rendering/caching/types.d.ts +1 -0
- package/dist/cjs/rendering/renderers/CanvasRenderer.js +3 -0
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.mjs +24 -13
- package/dist/mjs/SVGLoader.d.ts +1 -1
- package/dist/mjs/SVGLoader.mjs +11 -2
- package/dist/mjs/Viewport.d.ts +6 -0
- package/dist/mjs/Viewport.mjs +6 -1
- package/dist/mjs/image/EditorImage.d.ts +20 -0
- package/dist/mjs/image/EditorImage.mjs +38 -10
- package/dist/mjs/rendering/Display.d.ts +9 -0
- package/dist/mjs/rendering/Display.mjs +47 -6
- package/dist/mjs/rendering/caching/CacheRecordManager.mjs +3 -4
- package/dist/mjs/rendering/caching/RenderingCache.d.ts +1 -0
- package/dist/mjs/rendering/caching/RenderingCache.mjs +4 -0
- package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +20 -12
- package/dist/mjs/rendering/caching/types.d.ts +1 -0
- package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +3 -0
- package/dist/mjs/version.mjs +1 -1
- package/package.json +4 -4
package/dist/cjs/Editor.js
CHANGED
@@ -189,19 +189,30 @@ class Editor {
|
|
189
189
|
this.hideLoadingWarning();
|
190
190
|
// Enforce zoom limits.
|
191
191
|
this.notifier.on(types_1.EditorEventType.ViewportChanged, evt => {
|
192
|
-
if (evt.kind
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
this.
|
192
|
+
if (evt.kind !== types_1.EditorEventType.ViewportChanged)
|
193
|
+
return;
|
194
|
+
const getZoom = (mat) => mat.transformVec3(math_1.Vec2.unitX).length();
|
195
|
+
const zoom = getZoom(evt.newTransform);
|
196
|
+
if (zoom > this.settings.maxZoom || zoom < this.settings.minZoom) {
|
197
|
+
const oldZoom = getZoom(evt.oldTransform);
|
198
|
+
let resetTransform = math_1.Mat33.identity;
|
199
|
+
if (oldZoom <= this.settings.maxZoom && oldZoom >= this.settings.minZoom) {
|
200
|
+
resetTransform = evt.oldTransform;
|
201
|
+
}
|
202
|
+
else {
|
203
|
+
// If 1x zoom isn't acceptable, try a zoom between the minimum and maximum.
|
204
|
+
resetTransform = math_1.Mat33.scaling2D((this.settings.minZoom + this.settings.maxZoom) / 2);
|
205
|
+
}
|
206
|
+
this.viewport.resetTransform(resetTransform);
|
207
|
+
}
|
208
|
+
else if (!isFinite(zoom)) {
|
209
|
+
// Recover from possible division-by-zero
|
210
|
+
console.warn(`Non-finite zoom (${zoom}) detected. Resetting the viewport. This was likely caused by division by zero.`);
|
211
|
+
if (isFinite(getZoom(evt.oldTransform))) {
|
212
|
+
this.viewport.resetTransform(evt.oldTransform);
|
213
|
+
}
|
214
|
+
else {
|
215
|
+
this.viewport.resetTransform();
|
205
216
|
}
|
206
217
|
}
|
207
218
|
});
|
package/dist/cjs/SVGLoader.d.ts
CHANGED
@@ -18,7 +18,7 @@ export declare enum SVGLoaderLoadMethod {
|
|
18
18
|
export interface SVGLoaderOptions {
|
19
19
|
sanitize?: boolean;
|
20
20
|
disableUnknownObjectWarnings?: boolean;
|
21
|
-
loadMethod?:
|
21
|
+
loadMethod?: SVGLoaderLoadMethod;
|
22
22
|
}
|
23
23
|
export default class SVGLoader implements ImageLoader {
|
24
24
|
private source;
|
package/dist/cjs/SVGLoader.js
CHANGED
@@ -35,6 +35,7 @@ const SVGGlobalAttributesObject_1 = __importDefault(require("./components/SVGGlo
|
|
35
35
|
const TextComponent_1 = __importStar(require("./components/TextComponent"));
|
36
36
|
const UnknownSVGObject_1 = __importDefault(require("./components/UnknownSVGObject"));
|
37
37
|
const RenderablePathSpec_1 = require("./rendering/RenderablePathSpec");
|
38
|
+
const SVGRenderer_1 = require("./rendering/renderers/SVGRenderer");
|
38
39
|
// Size of a loaded image if no size is specified.
|
39
40
|
exports.defaultSVGViewRect = new math_1.Rect2(0, 0, 500, 500);
|
40
41
|
// Key to retrieve unrecognised attributes from an AbstractComponent
|
@@ -504,7 +505,13 @@ class SVGLoader {
|
|
504
505
|
this.updateSVGAttrs(node);
|
505
506
|
break;
|
506
507
|
case 'style':
|
507
|
-
|
508
|
+
// Keeping unnecessary style sheets can cause the browser to keep all
|
509
|
+
// SVG elements *referenced* by the style sheet in some browsers.
|
510
|
+
//
|
511
|
+
// Only keep the style sheet if it won't be discarded on save.
|
512
|
+
if (node.getAttribute('id') !== SVGRenderer_1.renderedStylesheetId) {
|
513
|
+
await this.addUnknownNode(node);
|
514
|
+
}
|
508
515
|
break;
|
509
516
|
default:
|
510
517
|
if (!this.disableUnknownObjectWarnings) {
|
@@ -547,6 +554,7 @@ class SVGLoader {
|
|
547
554
|
this.onDetermineExportRect?.(exports.defaultSVGViewRect);
|
548
555
|
}
|
549
556
|
this.onFinish?.();
|
557
|
+
this.onFinish = null;
|
550
558
|
}
|
551
559
|
/**
|
552
560
|
* Create an `SVGLoader` from the content of an SVG image. SVGs are loaded within a sandboxed
|
@@ -558,7 +566,7 @@ class SVGLoader {
|
|
558
566
|
* @param options - if `true` or `false`, treated as the `sanitize` option -- don't store unknown attributes.
|
559
567
|
*/
|
560
568
|
static fromString(text, options = false) {
|
561
|
-
const domParserLoad = typeof options !== 'boolean' && options?.loadMethod ===
|
569
|
+
const domParserLoad = typeof options !== 'boolean' && options?.loadMethod === SVGLoaderLoadMethod.DOMParser;
|
562
570
|
const { svgElem, cleanUp } = (() => {
|
563
571
|
// If the user requested an iframe load (the default) try to load with an iframe.
|
564
572
|
// There are some cases (e.g. in a sandboxed iframe) where this doesn't work.
|
@@ -606,6 +614,7 @@ class SVGLoader {
|
|
606
614
|
const cleanUp = () => {
|
607
615
|
svgElem.remove();
|
608
616
|
sandbox.remove();
|
617
|
+
sandbox.src = '';
|
609
618
|
};
|
610
619
|
return { svgElem, cleanUp };
|
611
620
|
}
|
package/dist/cjs/Viewport.d.ts
CHANGED
@@ -50,6 +50,11 @@ export declare class Viewport {
|
|
50
50
|
private getScaleFactorToNearestPowerOf;
|
51
51
|
/** Returns the size of a grid cell (in canvas units) as used by {@link snapToGrid}. */
|
52
52
|
static getGridSize(scaleFactor: number): number;
|
53
|
+
/**
|
54
|
+
* Snaps `canvasPos` to the nearest grid cell corner.
|
55
|
+
*
|
56
|
+
* @see {@link getGridSize} and {@link getScaleFactorToNearestPowerOf}.
|
57
|
+
*/
|
53
58
|
snapToGrid(canvasPos: Point2): Vec3;
|
54
59
|
/** Returns the size of one screen pixel in canvas units. */
|
55
60
|
getSizeOfPixelOnCanvas(): number;
|
@@ -65,6 +70,7 @@ export declare class Viewport {
|
|
65
70
|
* its original location. This is useful for preparing data for base-10 conversion.
|
66
71
|
*/
|
67
72
|
static roundPoint<T extends Point2 | number>(point: T, tolerance: number): PointDataType<T>;
|
73
|
+
/** Round a point with a tolerance of ±1 screen unit. */
|
68
74
|
roundPoint(point: Point2): Point2;
|
69
75
|
static roundScaleRatio(scaleRatio: number, roundAmount?: number): number;
|
70
76
|
computeZoomToTransform(toMakeVisible: Rect2, allowZoomIn?: boolean, allowZoomOut?: boolean): Mat33;
|
package/dist/cjs/Viewport.js
CHANGED
@@ -104,6 +104,11 @@ class Viewport {
|
|
104
104
|
static getGridSize(scaleFactor) {
|
105
105
|
return 50 / scaleFactor;
|
106
106
|
}
|
107
|
+
/**
|
108
|
+
* Snaps `canvasPos` to the nearest grid cell corner.
|
109
|
+
*
|
110
|
+
* @see {@link getGridSize} and {@link getScaleFactorToNearestPowerOf}.
|
111
|
+
*/
|
107
112
|
snapToGrid(canvasPos) {
|
108
113
|
const scaleFactor = this.getScaleFactorToNearestPowerOf(2);
|
109
114
|
const snapCoordinate = (coordinate) => {
|
@@ -140,7 +145,7 @@ class Viewport {
|
|
140
145
|
}
|
141
146
|
return point.map(roundComponent);
|
142
147
|
}
|
143
|
-
|
148
|
+
/** Round a point with a tolerance of ±1 screen unit. */
|
144
149
|
roundPoint(point) {
|
145
150
|
return Viewport.roundPoint(point, 1 / this.getScaleFactor());
|
146
151
|
}
|
@@ -123,8 +123,28 @@ export default class EditorImage {
|
|
123
123
|
*/
|
124
124
|
private setExportRectDirectly;
|
125
125
|
private onExportViewportChanged;
|
126
|
+
/**
|
127
|
+
* @internal
|
128
|
+
*
|
129
|
+
* Enables debug mode for **all** `EditorImage`s.
|
130
|
+
*
|
131
|
+
* **Only use for debugging**.
|
132
|
+
*
|
133
|
+
* @internal
|
134
|
+
*/
|
135
|
+
setDebugMode(newDebugMode: boolean): void;
|
126
136
|
private static SetImportExportRectCommand;
|
127
137
|
}
|
138
|
+
/**
|
139
|
+
* Determines the first index in `sortedLeaves` that needs to be rendered
|
140
|
+
* (based on occlusion -- everything before that index can be skipped and
|
141
|
+
* produce a visually-equivalent image).
|
142
|
+
*
|
143
|
+
* Does nothing if visibleRect is not provided
|
144
|
+
*
|
145
|
+
* @internal
|
146
|
+
*/
|
147
|
+
export declare const computeFirstIndexToRender: (sortedLeaves: Array<ImageNode>, visibleRect?: Rect2) => number;
|
128
148
|
type TooSmallToRenderCheck = (rect: Rect2) => boolean;
|
129
149
|
/**
|
130
150
|
* Part of the Editor's image. Does not handle fullscreen/invisible components.
|
@@ -31,7 +31,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
31
|
};
|
32
32
|
var _a, _b, _c;
|
33
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
34
|
-
exports.RootImageNode = exports.ImageNode = exports.EditorImageEventType = exports.sortLeavesByZIndex = void 0;
|
34
|
+
exports.RootImageNode = exports.ImageNode = exports.computeFirstIndexToRender = exports.EditorImageEventType = exports.sortLeavesByZIndex = void 0;
|
35
35
|
const Viewport_1 = __importDefault(require("../Viewport"));
|
36
36
|
const AbstractComponent_1 = __importStar(require("../components/AbstractComponent"));
|
37
37
|
const math_1 = require("@js-draw/math");
|
@@ -49,7 +49,7 @@ var EditorImageEventType;
|
|
49
49
|
EditorImageEventType[EditorImageEventType["ExportViewportChanged"] = 0] = "ExportViewportChanged";
|
50
50
|
EditorImageEventType[EditorImageEventType["AutoresizeModeChanged"] = 1] = "AutoresizeModeChanged";
|
51
51
|
})(EditorImageEventType || (exports.EditorImageEventType = EditorImageEventType = {}));
|
52
|
-
|
52
|
+
let debugMode = false;
|
53
53
|
// Handles lookup/storage of elements in the image
|
54
54
|
class EditorImage {
|
55
55
|
// @internal
|
@@ -301,6 +301,18 @@ class EditorImage {
|
|
301
301
|
});
|
302
302
|
}
|
303
303
|
}
|
304
|
+
/**
|
305
|
+
* @internal
|
306
|
+
*
|
307
|
+
* Enables debug mode for **all** `EditorImage`s.
|
308
|
+
*
|
309
|
+
* **Only use for debugging**.
|
310
|
+
*
|
311
|
+
* @internal
|
312
|
+
*/
|
313
|
+
setDebugMode(newDebugMode) {
|
314
|
+
debugMode = newDebugMode;
|
315
|
+
}
|
304
316
|
}
|
305
317
|
_a = EditorImage;
|
306
318
|
// A Command that can access private [EditorImage] functionality
|
@@ -440,6 +452,31 @@ EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand
|
|
440
452
|
})(),
|
441
453
|
_c);
|
442
454
|
exports.default = EditorImage;
|
455
|
+
/**
|
456
|
+
* Determines the first index in `sortedLeaves` that needs to be rendered
|
457
|
+
* (based on occlusion -- everything before that index can be skipped and
|
458
|
+
* produce a visually-equivalent image).
|
459
|
+
*
|
460
|
+
* Does nothing if visibleRect is not provided
|
461
|
+
*
|
462
|
+
* @internal
|
463
|
+
*/
|
464
|
+
const computeFirstIndexToRender = (sortedLeaves, visibleRect) => {
|
465
|
+
let startIndex = 0;
|
466
|
+
if (visibleRect) {
|
467
|
+
for (let i = sortedLeaves.length - 1; i >= 1; i--) {
|
468
|
+
if (
|
469
|
+
// Check for occlusion
|
470
|
+
sortedLeaves[i].getBBox().containsRect(visibleRect)
|
471
|
+
&& sortedLeaves[i].getContent()?.occludesEverythingBelowWhenRenderedInRect(visibleRect)) {
|
472
|
+
startIndex = i;
|
473
|
+
break;
|
474
|
+
}
|
475
|
+
}
|
476
|
+
}
|
477
|
+
return startIndex;
|
478
|
+
};
|
479
|
+
exports.computeFirstIndexToRender = computeFirstIndexToRender;
|
443
480
|
/**
|
444
481
|
* Part of the Editor's image. Does not handle fullscreen/invisible components.
|
445
482
|
* @internal
|
@@ -696,15 +733,7 @@ class ImageNode {
|
|
696
733
|
// If some components hide others (and we're permitted to simplify,
|
697
734
|
// which is true in the case of visibleRect being defined), then only
|
698
735
|
// draw the non-hidden components:
|
699
|
-
|
700
|
-
if (visibleRect) {
|
701
|
-
for (let i = leaves.length - 1; i >= 1; i--) {
|
702
|
-
if (leaves[i].getContent()?.occludesEverythingBelowWhenRenderedInRect(visibleRect)) {
|
703
|
-
startIndex = i;
|
704
|
-
break;
|
705
|
-
}
|
706
|
-
}
|
707
|
-
}
|
736
|
+
const startIndex = (0, exports.computeFirstIndexToRender)(leaves);
|
708
737
|
for (let i = startIndex; i < leaves.length; i++) {
|
709
738
|
const leaf = leaves[i];
|
710
739
|
// Leaves by definition have content
|
@@ -26,6 +26,7 @@ export default class Display {
|
|
26
26
|
private textRenderer;
|
27
27
|
private textRerenderOutput;
|
28
28
|
private cache;
|
29
|
+
private devicePixelRatio;
|
29
30
|
private resizeSurfacesCallback?;
|
30
31
|
private flattenCallback?;
|
31
32
|
/** @internal */
|
@@ -46,6 +47,14 @@ export default class Display {
|
|
46
47
|
getColorAt: (_screenPos: Point2) => Color4 | null;
|
47
48
|
private initializeCanvasRendering;
|
48
49
|
private initializeTextRendering;
|
50
|
+
/**
|
51
|
+
* Sets the device-pixel-ratio.
|
52
|
+
*
|
53
|
+
* Intended for debugging. Users do not need to call this manually.
|
54
|
+
*
|
55
|
+
* @internal
|
56
|
+
*/
|
57
|
+
setDevicePixelRatio(dpr: number): Promise<void> | undefined;
|
49
58
|
/**
|
50
59
|
* Rerenders the text-based display.
|
51
60
|
* The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
|
@@ -34,6 +34,7 @@ class Display {
|
|
34
34
|
this.editor = editor;
|
35
35
|
this.parent = parent;
|
36
36
|
this.textRerenderOutput = null;
|
37
|
+
this.devicePixelRatio = window.devicePixelRatio ?? 1;
|
37
38
|
/**
|
38
39
|
* @returns the color at the given point on the dry ink renderer, or `null` if `screenPos`
|
39
40
|
* is not on the display.
|
@@ -118,16 +119,32 @@ class Display {
|
|
118
119
|
this.parent.appendChild(wetInkCanvas);
|
119
120
|
}
|
120
121
|
this.resizeSurfacesCallback = () => {
|
122
|
+
const expectedWidth = (canvas) => {
|
123
|
+
return Math.ceil(canvas.clientWidth * this.devicePixelRatio);
|
124
|
+
};
|
125
|
+
const expectedHeight = (canvas) => {
|
126
|
+
return Math.ceil(canvas.clientHeight * this.devicePixelRatio);
|
127
|
+
};
|
121
128
|
const hasSizeMismatch = (canvas) => {
|
122
|
-
return canvas
|
129
|
+
return expectedHeight(canvas) !== canvas.height || expectedWidth(canvas) !== canvas.width;
|
123
130
|
};
|
124
131
|
// Ensure that the drawing surfaces sizes match the
|
125
132
|
// canvas' sizes to prevent stretching.
|
126
133
|
if (hasSizeMismatch(dryInkCanvas) || hasSizeMismatch(wetInkCanvas)) {
|
127
|
-
dryInkCanvas.width = dryInkCanvas
|
128
|
-
dryInkCanvas.height = dryInkCanvas
|
129
|
-
wetInkCanvas.width = wetInkCanvas
|
130
|
-
wetInkCanvas.height = wetInkCanvas
|
134
|
+
dryInkCanvas.width = expectedWidth(dryInkCanvas);
|
135
|
+
dryInkCanvas.height = expectedHeight(dryInkCanvas);
|
136
|
+
wetInkCanvas.width = expectedWidth(wetInkCanvas);
|
137
|
+
wetInkCanvas.height = expectedHeight(wetInkCanvas);
|
138
|
+
// Ensure correct drawing operations on high-resolution screens.
|
139
|
+
// See
|
140
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas#scaling_for_high_resolution_displays
|
141
|
+
//
|
142
|
+
// This scaling causes the rendering contexts to automatically convert
|
143
|
+
// between screen coordinates and pixel coordinates.
|
144
|
+
wetInkCtx.resetTransform();
|
145
|
+
dryInkCtx.resetTransform();
|
146
|
+
dryInkCtx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
147
|
+
wetInkCtx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
131
148
|
this.editor.notifier.dispatch(types_1.EditorEventType.DisplayResized, {
|
132
149
|
kind: types_1.EditorEventType.DisplayResized,
|
133
150
|
newSize: math_1.Vec2.of(this.width, this.height),
|
@@ -136,10 +153,17 @@ class Display {
|
|
136
153
|
};
|
137
154
|
this.resizeSurfacesCallback();
|
138
155
|
this.flattenCallback = () => {
|
156
|
+
dryInkCtx.save();
|
157
|
+
dryInkCtx.resetTransform();
|
139
158
|
dryInkCtx.drawImage(wetInkCanvas, 0, 0);
|
159
|
+
dryInkCtx.restore();
|
140
160
|
};
|
141
161
|
this.getColorAt = (screenPos) => {
|
142
|
-
|
162
|
+
// getImageData isn't affected by a transformation matrix -- we need to
|
163
|
+
// pre-transform screenPos to convert it from screen coordinates into pixel
|
164
|
+
// coordinates.
|
165
|
+
const adjustedScreenPos = screenPos.times(this.devicePixelRatio);
|
166
|
+
const pixel = dryInkCtx.getImageData(adjustedScreenPos.x, adjustedScreenPos.y, 1, 1);
|
143
167
|
const data = pixel?.data;
|
144
168
|
if (data) {
|
145
169
|
const color = math_1.Color4.ofRGBA(data[0] / 255, data[1] / 255, data[2] / 255, data[3] / 255);
|
@@ -162,6 +186,23 @@ class Display {
|
|
162
186
|
textRendererOutputContainer.replaceChildren(rerenderButton, this.textRerenderOutput);
|
163
187
|
this.editor.createHTMLOverlay(textRendererOutputContainer);
|
164
188
|
}
|
189
|
+
/**
|
190
|
+
* Sets the device-pixel-ratio.
|
191
|
+
*
|
192
|
+
* Intended for debugging. Users do not need to call this manually.
|
193
|
+
*
|
194
|
+
* @internal
|
195
|
+
*/
|
196
|
+
setDevicePixelRatio(dpr) {
|
197
|
+
const minDpr = 0.001;
|
198
|
+
const maxDpr = 10;
|
199
|
+
if (isFinite(dpr) && dpr >= minDpr && dpr <= maxDpr && dpr !== this.devicePixelRatio) {
|
200
|
+
this.devicePixelRatio = dpr;
|
201
|
+
this.resizeSurfacesCallback?.();
|
202
|
+
return this.editor.queueRerender();
|
203
|
+
}
|
204
|
+
return undefined;
|
205
|
+
}
|
165
206
|
/**
|
166
207
|
* Rerenders the text-based display.
|
167
208
|
* The text-based display is intended for screen readers and can be navigated to by pressing `tab`.
|
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
6
|
exports.CacheRecordManager = void 0;
|
7
7
|
const CacheRecord_1 = __importDefault(require("./CacheRecord"));
|
8
|
-
const debugMode = false;
|
9
8
|
class CacheRecordManager {
|
10
9
|
constructor(cacheProps) {
|
11
10
|
// Fixed-size array: Cache blocks are assigned indicies into [cachedCanvases].
|
@@ -22,19 +21,19 @@ class CacheRecordManager {
|
|
22
21
|
const record = new CacheRecord_1.default(onDealloc, this.cacheState);
|
23
22
|
record.setRenderingRegion(drawTo);
|
24
23
|
this.cacheRecords.push(record);
|
25
|
-
if (debugMode) {
|
24
|
+
if (this.cacheState.debugMode) {
|
26
25
|
console.log('[Cache] Cache spaces used: ', this.cacheRecords.length, ' of ', this.maxCanvases);
|
27
26
|
}
|
28
27
|
return record;
|
29
28
|
}
|
30
29
|
else {
|
31
30
|
const lru = this.getLeastRecentlyUsedRecord();
|
32
|
-
if (debugMode) {
|
31
|
+
if (this.cacheState.debugMode) {
|
33
32
|
console.log('[Cache] Re-alloc. Times allocated: ', lru.allocCount, '\nLast used cycle: ', lru.getLastUsedCycle(), '\nCurrent cycle: ', this.cacheState.currentRenderingCycle);
|
34
33
|
}
|
35
34
|
lru.realloc(onDealloc);
|
36
35
|
lru.setRenderingRegion(drawTo);
|
37
|
-
if (debugMode) {
|
36
|
+
if (this.cacheState.debugMode) {
|
38
37
|
console.log('[Cache] Now re-alloc\'d. Last used cycle: ', lru.getLastUsedCycle());
|
39
38
|
console.assert(lru['cacheState'] === this.cacheState, '[Cache] Unequal cache states! cacheState should be a shared object!');
|
40
39
|
}
|
@@ -13,6 +13,7 @@ class RenderingCache {
|
|
13
13
|
props: cacheProps,
|
14
14
|
currentRenderingCycle: 0,
|
15
15
|
recordManager: this.recordManager,
|
16
|
+
debugMode: false,
|
16
17
|
};
|
17
18
|
this.recordManager.setSharedState(this.sharedState);
|
18
19
|
}
|
@@ -49,5 +50,8 @@ class RenderingCache {
|
|
49
50
|
getDebugInfo() {
|
50
51
|
return this.recordManager.getDebugInfo();
|
51
52
|
}
|
53
|
+
setIsDebugMode(debugMode) {
|
54
|
+
this.sharedState.debugMode = debugMode;
|
55
|
+
}
|
52
56
|
}
|
53
57
|
exports.default = RenderingCache;
|
@@ -5,8 +5,6 @@ const EditorImage_1 = require("../../image/EditorImage");
|
|
5
5
|
const math_1 = require("@js-draw/math");
|
6
6
|
// 3x3 divisions for each node.
|
7
7
|
const cacheDivisionSize = 3;
|
8
|
-
// True: Show rendering updates.
|
9
|
-
const debugMode = false;
|
10
8
|
class RenderingCacheNode {
|
11
9
|
constructor(region, cacheState) {
|
12
10
|
this.region = region;
|
@@ -163,8 +161,8 @@ class RenderingCacheNode {
|
|
163
161
|
items.forEach(item => item.render(screenRenderer, viewport.visibleRect));
|
164
162
|
return;
|
165
163
|
}
|
166
|
-
if (debugMode) {
|
167
|
-
screenRenderer.drawRect(this.region,
|
164
|
+
if (this.cacheState.debugMode) {
|
165
|
+
screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.yellow });
|
168
166
|
}
|
169
167
|
// Could we render direclty from [this] or do we need to recurse?
|
170
168
|
const couldRender = this.renderingWouldBeHighEnoughResolution(viewport);
|
@@ -199,7 +197,9 @@ class RenderingCacheNode {
|
|
199
197
|
}
|
200
198
|
let leafApproxRenderTime = 0;
|
201
199
|
for (const leaf of leavesByIds) {
|
202
|
-
|
200
|
+
if (!tooSmallToRender(leaf.getBBox())) {
|
201
|
+
leafApproxRenderTime += leaf.getContent().getProportionalRenderingTime();
|
202
|
+
}
|
203
203
|
}
|
204
204
|
// Is it worth it to render the items?
|
205
205
|
if (leafApproxRenderTime > this.cacheState.props.minProportionalRenderTimePerCache) {
|
@@ -235,26 +235,30 @@ class RenderingCacheNode {
|
|
235
235
|
this.renderedMaxZIndex = zIndex;
|
236
236
|
}
|
237
237
|
}
|
238
|
-
if (debugMode) {
|
239
|
-
|
238
|
+
if (this.cacheState.debugMode) {
|
239
|
+
// Clay for adding new elements
|
240
|
+
screenRenderer.drawRect(this.region, 2 * viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.clay });
|
240
241
|
}
|
241
242
|
}
|
242
243
|
}
|
243
|
-
else if (debugMode) {
|
244
|
+
else if (this.cacheState.debugMode) {
|
244
245
|
console.log('Decided on a full re-render. Reason: At least one of the following is false:', '\n leafIds.length > this.renderedIds.length: ', leafIds.length > this.renderedIds.length, '\n this.allRenderedIdsIn(leafIds): ', this.allRenderedIdsIn(leafIds), '\n this.renderedMaxZIndex !== null: ', this.renderedMaxZIndex !== null, '\n\nthis.rerenderedIds: ', this.renderedIds, ', leafIds: ', leafIds);
|
245
246
|
}
|
246
247
|
if (fullRerenderNeeded) {
|
247
248
|
thisRenderer = this.cachedRenderer.startRender();
|
248
249
|
thisRenderer.clear();
|
249
250
|
this.renderedMaxZIndex = null;
|
250
|
-
|
251
|
+
const startIndex = (0, EditorImage_1.computeFirstIndexToRender)(leaves, this.region);
|
252
|
+
for (let i = startIndex; i < leaves.length; i++) {
|
253
|
+
const leaf = leaves[i];
|
251
254
|
const content = leaf.getContent();
|
252
255
|
this.renderedMaxZIndex ??= content.getZIndex();
|
253
256
|
this.renderedMaxZIndex = Math.max(this.renderedMaxZIndex, content.getZIndex());
|
254
257
|
leaf.render(thisRenderer, this.region);
|
255
258
|
}
|
256
|
-
if (debugMode) {
|
257
|
-
|
259
|
+
if (this.cacheState.debugMode) {
|
260
|
+
// Red for full rerender
|
261
|
+
screenRenderer.drawRect(this.region, 3 * viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.red });
|
258
262
|
}
|
259
263
|
}
|
260
264
|
this.renderedIds = leafIds;
|
@@ -271,6 +275,10 @@ class RenderingCacheNode {
|
|
271
275
|
leaf.render(screenRenderer, this.region.intersection(viewport.visibleRect));
|
272
276
|
}
|
273
277
|
screenRenderer.endObject();
|
278
|
+
if (this.cacheState.debugMode) {
|
279
|
+
// Green for no cache needed render
|
280
|
+
screenRenderer.drawRect(this.region, 2 * viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.green });
|
281
|
+
}
|
274
282
|
}
|
275
283
|
}
|
276
284
|
else {
|
@@ -82,7 +82,10 @@ class CanvasRenderer extends AbstractRenderer_1.default {
|
|
82
82
|
return math_1.Vec2.of(this.ctx.canvas.clientWidth, this.ctx.canvas.clientHeight);
|
83
83
|
}
|
84
84
|
clear() {
|
85
|
+
this.ctx.save();
|
86
|
+
this.ctx.resetTransform();
|
85
87
|
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
88
|
+
this.ctx.restore();
|
86
89
|
}
|
87
90
|
beginPath(startPoint) {
|
88
91
|
startPoint = this.canvasToScreen(startPoint);
|
package/dist/cjs/version.js
CHANGED
package/dist/mjs/Editor.mjs
CHANGED
@@ -160,19 +160,30 @@ export class Editor {
|
|
160
160
|
this.hideLoadingWarning();
|
161
161
|
// Enforce zoom limits.
|
162
162
|
this.notifier.on(EditorEventType.ViewportChanged, evt => {
|
163
|
-
if (evt.kind
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
this.
|
163
|
+
if (evt.kind !== EditorEventType.ViewportChanged)
|
164
|
+
return;
|
165
|
+
const getZoom = (mat) => mat.transformVec3(Vec2.unitX).length();
|
166
|
+
const zoom = getZoom(evt.newTransform);
|
167
|
+
if (zoom > this.settings.maxZoom || zoom < this.settings.minZoom) {
|
168
|
+
const oldZoom = getZoom(evt.oldTransform);
|
169
|
+
let resetTransform = Mat33.identity;
|
170
|
+
if (oldZoom <= this.settings.maxZoom && oldZoom >= this.settings.minZoom) {
|
171
|
+
resetTransform = evt.oldTransform;
|
172
|
+
}
|
173
|
+
else {
|
174
|
+
// If 1x zoom isn't acceptable, try a zoom between the minimum and maximum.
|
175
|
+
resetTransform = Mat33.scaling2D((this.settings.minZoom + this.settings.maxZoom) / 2);
|
176
|
+
}
|
177
|
+
this.viewport.resetTransform(resetTransform);
|
178
|
+
}
|
179
|
+
else if (!isFinite(zoom)) {
|
180
|
+
// Recover from possible division-by-zero
|
181
|
+
console.warn(`Non-finite zoom (${zoom}) detected. Resetting the viewport. This was likely caused by division by zero.`);
|
182
|
+
if (isFinite(getZoom(evt.oldTransform))) {
|
183
|
+
this.viewport.resetTransform(evt.oldTransform);
|
184
|
+
}
|
185
|
+
else {
|
186
|
+
this.viewport.resetTransform();
|
176
187
|
}
|
177
188
|
}
|
178
189
|
});
|
package/dist/mjs/SVGLoader.d.ts
CHANGED
@@ -18,7 +18,7 @@ export declare enum SVGLoaderLoadMethod {
|
|
18
18
|
export interface SVGLoaderOptions {
|
19
19
|
sanitize?: boolean;
|
20
20
|
disableUnknownObjectWarnings?: boolean;
|
21
|
-
loadMethod?:
|
21
|
+
loadMethod?: SVGLoaderLoadMethod;
|
22
22
|
}
|
23
23
|
export default class SVGLoader implements ImageLoader {
|
24
24
|
private source;
|
package/dist/mjs/SVGLoader.mjs
CHANGED
@@ -6,6 +6,7 @@ import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject.
|
|
6
6
|
import TextComponent, { TextTransformMode } from './components/TextComponent.mjs';
|
7
7
|
import UnknownSVGObject from './components/UnknownSVGObject.mjs';
|
8
8
|
import { pathToRenderable } from './rendering/RenderablePathSpec.mjs';
|
9
|
+
import { renderedStylesheetId } from './rendering/renderers/SVGRenderer.mjs';
|
9
10
|
// Size of a loaded image if no size is specified.
|
10
11
|
export const defaultSVGViewRect = new Rect2(0, 0, 500, 500);
|
11
12
|
// Key to retrieve unrecognised attributes from an AbstractComponent
|
@@ -475,7 +476,13 @@ export default class SVGLoader {
|
|
475
476
|
this.updateSVGAttrs(node);
|
476
477
|
break;
|
477
478
|
case 'style':
|
478
|
-
|
479
|
+
// Keeping unnecessary style sheets can cause the browser to keep all
|
480
|
+
// SVG elements *referenced* by the style sheet in some browsers.
|
481
|
+
//
|
482
|
+
// Only keep the style sheet if it won't be discarded on save.
|
483
|
+
if (node.getAttribute('id') !== renderedStylesheetId) {
|
484
|
+
await this.addUnknownNode(node);
|
485
|
+
}
|
479
486
|
break;
|
480
487
|
default:
|
481
488
|
if (!this.disableUnknownObjectWarnings) {
|
@@ -518,6 +525,7 @@ export default class SVGLoader {
|
|
518
525
|
this.onDetermineExportRect?.(defaultSVGViewRect);
|
519
526
|
}
|
520
527
|
this.onFinish?.();
|
528
|
+
this.onFinish = null;
|
521
529
|
}
|
522
530
|
/**
|
523
531
|
* Create an `SVGLoader` from the content of an SVG image. SVGs are loaded within a sandboxed
|
@@ -529,7 +537,7 @@ export default class SVGLoader {
|
|
529
537
|
* @param options - if `true` or `false`, treated as the `sanitize` option -- don't store unknown attributes.
|
530
538
|
*/
|
531
539
|
static fromString(text, options = false) {
|
532
|
-
const domParserLoad = typeof options !== 'boolean' && options?.loadMethod ===
|
540
|
+
const domParserLoad = typeof options !== 'boolean' && options?.loadMethod === SVGLoaderLoadMethod.DOMParser;
|
533
541
|
const { svgElem, cleanUp } = (() => {
|
534
542
|
// If the user requested an iframe load (the default) try to load with an iframe.
|
535
543
|
// There are some cases (e.g. in a sandboxed iframe) where this doesn't work.
|
@@ -577,6 +585,7 @@ export default class SVGLoader {
|
|
577
585
|
const cleanUp = () => {
|
578
586
|
svgElem.remove();
|
579
587
|
sandbox.remove();
|
588
|
+
sandbox.src = '';
|
580
589
|
};
|
581
590
|
return { svgElem, cleanUp };
|
582
591
|
}
|