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.
@@ -18,7 +18,7 @@ export declare enum SVGLoaderLoadMethod {
18
18
  export interface SVGLoaderOptions {
19
19
  sanitize?: boolean;
20
20
  disableUnknownObjectWarnings?: boolean;
21
- loadMethod?: 'iframe' | 'domparser';
21
+ loadMethod?: SVGLoaderLoadMethod;
22
22
  }
23
23
  export default class SVGLoader implements ImageLoader {
24
24
  private source;
@@ -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
- await this.addUnknownNode(node);
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 === 'domparser';
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
- const debugMode = false;
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
- let startIndex = 0;
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.clientHeight !== canvas.height || canvas.clientWidth !== canvas.width;
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.clientWidth;
128
- dryInkCanvas.height = dryInkCanvas.clientHeight;
129
- wetInkCanvas.width = wetInkCanvas.clientWidth;
130
- wetInkCanvas.height = wetInkCanvas.clientHeight;
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
  }
@@ -9,4 +9,5 @@ export default class RenderingCache {
9
9
  constructor(cacheProps: CacheProps);
10
10
  render(screenRenderer: AbstractRenderer, image: ImageNode, viewport: Viewport): void;
11
11
  getDebugInfo(): string;
12
+ setIsDebugMode(debugMode: boolean): void;
12
13
  }
@@ -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, 0.5 * viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.yellow });
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
- leafApproxRenderTime += leaf.getContent().getProportionalRenderingTime();
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
- screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.clay });
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
- for (const leaf of leaves) {
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
- screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: math_1.Color4.red });
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 {
@@ -16,4 +16,5 @@ export interface CacheState {
16
16
  currentRenderingCycle: number;
17
17
  props: CacheProps;
18
18
  recordManager: CacheRecordManager;
19
+ debugMode: boolean;
19
20
  }
@@ -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);
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = {
4
- number: '1.8.0',
4
+ number: '1.9.0',
5
5
  };
@@ -18,7 +18,7 @@ export declare enum SVGLoaderLoadMethod {
18
18
  export interface SVGLoaderOptions {
19
19
  sanitize?: boolean;
20
20
  disableUnknownObjectWarnings?: boolean;
21
- loadMethod?: 'iframe' | 'domparser';
21
+ loadMethod?: SVGLoaderLoadMethod;
22
22
  }
23
23
  export default class SVGLoader implements ImageLoader {
24
24
  private source;
@@ -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
- await this.addUnknownNode(node);
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 === 'domparser';
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
- const debugMode = false;
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
- let startIndex = 0;
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`.