js-draw 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/bundle.js +2 -2
- package/dist/cjs/SVGLoader.d.ts +1 -1
- package/dist/cjs/SVGLoader.js +11 -2
- 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 +39 -5
- 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/SVGLoader.d.ts +1 -1
- package/dist/mjs/SVGLoader.mjs +11 -2
- 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 +39 -5
- 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/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
|
}
|
@@ -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,29 @@ 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
|
+
wetInkCtx.resetTransform();
|
142
|
+
dryInkCtx.resetTransform();
|
143
|
+
dryInkCtx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
144
|
+
wetInkCtx.scale(this.devicePixelRatio, this.devicePixelRatio);
|
131
145
|
this.editor.notifier.dispatch(types_1.EditorEventType.DisplayResized, {
|
132
146
|
kind: types_1.EditorEventType.DisplayResized,
|
133
147
|
newSize: math_1.Vec2.of(this.width, this.height),
|
@@ -136,7 +150,10 @@ class Display {
|
|
136
150
|
};
|
137
151
|
this.resizeSurfacesCallback();
|
138
152
|
this.flattenCallback = () => {
|
153
|
+
dryInkCtx.save();
|
154
|
+
dryInkCtx.resetTransform();
|
139
155
|
dryInkCtx.drawImage(wetInkCanvas, 0, 0);
|
156
|
+
dryInkCtx.restore();
|
140
157
|
};
|
141
158
|
this.getColorAt = (screenPos) => {
|
142
159
|
const pixel = dryInkCtx.getImageData(screenPos.x, screenPos.y, 1, 1);
|
@@ -162,6 +179,23 @@ class Display {
|
|
162
179
|
textRendererOutputContainer.replaceChildren(rerenderButton, this.textRerenderOutput);
|
163
180
|
this.editor.createHTMLOverlay(textRendererOutputContainer);
|
164
181
|
}
|
182
|
+
/**
|
183
|
+
* Sets the device-pixel-ratio.
|
184
|
+
*
|
185
|
+
* Intended for debugging. Users do not need to call this manually.
|
186
|
+
*
|
187
|
+
* @internal
|
188
|
+
*/
|
189
|
+
setDevicePixelRatio(dpr) {
|
190
|
+
const minDpr = 0.001;
|
191
|
+
const maxDpr = 10;
|
192
|
+
if (isFinite(dpr) && dpr >= minDpr && dpr <= maxDpr && dpr !== this.devicePixelRatio) {
|
193
|
+
this.devicePixelRatio = dpr;
|
194
|
+
this.resizeSurfacesCallback?.();
|
195
|
+
return this.editor.queueRerender();
|
196
|
+
}
|
197
|
+
return undefined;
|
198
|
+
}
|
165
199
|
/**
|
166
200
|
* Rerenders the text-based display.
|
167
201
|
* 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/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
|
}
|
@@ -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.
|
@@ -19,7 +19,7 @@ export var EditorImageEventType;
|
|
19
19
|
EditorImageEventType[EditorImageEventType["ExportViewportChanged"] = 0] = "ExportViewportChanged";
|
20
20
|
EditorImageEventType[EditorImageEventType["AutoresizeModeChanged"] = 1] = "AutoresizeModeChanged";
|
21
21
|
})(EditorImageEventType || (EditorImageEventType = {}));
|
22
|
-
|
22
|
+
let debugMode = false;
|
23
23
|
// Handles lookup/storage of elements in the image
|
24
24
|
class EditorImage {
|
25
25
|
// @internal
|
@@ -271,6 +271,18 @@ class EditorImage {
|
|
271
271
|
});
|
272
272
|
}
|
273
273
|
}
|
274
|
+
/**
|
275
|
+
* @internal
|
276
|
+
*
|
277
|
+
* Enables debug mode for **all** `EditorImage`s.
|
278
|
+
*
|
279
|
+
* **Only use for debugging**.
|
280
|
+
*
|
281
|
+
* @internal
|
282
|
+
*/
|
283
|
+
setDebugMode(newDebugMode) {
|
284
|
+
debugMode = newDebugMode;
|
285
|
+
}
|
274
286
|
}
|
275
287
|
_a = EditorImage;
|
276
288
|
// A Command that can access private [EditorImage] functionality
|
@@ -410,6 +422,30 @@ EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand
|
|
410
422
|
})(),
|
411
423
|
_c);
|
412
424
|
export default EditorImage;
|
425
|
+
/**
|
426
|
+
* Determines the first index in `sortedLeaves` that needs to be rendered
|
427
|
+
* (based on occlusion -- everything before that index can be skipped and
|
428
|
+
* produce a visually-equivalent image).
|
429
|
+
*
|
430
|
+
* Does nothing if visibleRect is not provided
|
431
|
+
*
|
432
|
+
* @internal
|
433
|
+
*/
|
434
|
+
export const computeFirstIndexToRender = (sortedLeaves, visibleRect) => {
|
435
|
+
let startIndex = 0;
|
436
|
+
if (visibleRect) {
|
437
|
+
for (let i = sortedLeaves.length - 1; i >= 1; i--) {
|
438
|
+
if (
|
439
|
+
// Check for occlusion
|
440
|
+
sortedLeaves[i].getBBox().containsRect(visibleRect)
|
441
|
+
&& sortedLeaves[i].getContent()?.occludesEverythingBelowWhenRenderedInRect(visibleRect)) {
|
442
|
+
startIndex = i;
|
443
|
+
break;
|
444
|
+
}
|
445
|
+
}
|
446
|
+
}
|
447
|
+
return startIndex;
|
448
|
+
};
|
413
449
|
/**
|
414
450
|
* Part of the Editor's image. Does not handle fullscreen/invisible components.
|
415
451
|
* @internal
|
@@ -666,15 +702,7 @@ export class ImageNode {
|
|
666
702
|
// If some components hide others (and we're permitted to simplify,
|
667
703
|
// which is true in the case of visibleRect being defined), then only
|
668
704
|
// draw the non-hidden components:
|
669
|
-
|
670
|
-
if (visibleRect) {
|
671
|
-
for (let i = leaves.length - 1; i >= 1; i--) {
|
672
|
-
if (leaves[i].getContent()?.occludesEverythingBelowWhenRenderedInRect(visibleRect)) {
|
673
|
-
startIndex = i;
|
674
|
-
break;
|
675
|
-
}
|
676
|
-
}
|
677
|
-
}
|
705
|
+
const startIndex = computeFirstIndexToRender(leaves);
|
678
706
|
for (let i = startIndex; i < leaves.length; i++) {
|
679
707
|
const leaf = leaves[i];
|
680
708
|
// 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`.
|