@solidtv/renderer 1.6.2 → 1.6.3

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.
@@ -13,6 +13,7 @@ import { hasZeroWidthSpace } from './Utils.js';
13
13
  import { normalizeFontMetrics } from './TextLayoutEngine.js';
14
14
  import { isProductionEnvironment } from '../../utils.js';
15
15
  import type { TextureError } from '../TextureError.js';
16
+ import { takeSdfPrefetch } from './FontPrefetch.js';
16
17
 
17
18
  /**
18
19
  * SDF Font Data structure matching msdf-bmfont-xml output
@@ -315,6 +316,11 @@ export const loadFont = (
315
316
  const names = Array.isArray(fontFamily) ? fontFamily : [fontFamily];
316
317
  const primary = names[0]!;
317
318
 
319
+ // A prefetch started before the renderer existed, if any. Claimed here —
320
+ // ahead of the early returns — so that a font which turns out to need no
321
+ // load still releases the prefetched atlas blob rather than pinning it.
322
+ const prefetched = takeSdfPrefetch(primary);
323
+
318
324
  // Early return if already loaded
319
325
  if (fontCache.get(primary) !== undefined) {
320
326
  return Promise.resolve();
@@ -380,7 +386,15 @@ export const loadFont = (
380
386
  // success it processes + caches the font and wakes parked nodes. On failure
381
387
  // it drops the dead atlas texture (createTexture caches by `src`, so the
382
388
  // next attempt must evict it to build a fresh one) and rejects.
383
- const loadAtlas = (fontData: SdfFontData): Promise<void> => {
389
+ //
390
+ // `atlasBlob` is the prefetched atlas image when one was claimed. Passing it
391
+ // as `src` skips a second download; `key` is set to the atlas URL so the
392
+ // texture cache key is identical to the one the URL path produces — the two
393
+ // paths must not produce two cache entries for the same atlas.
394
+ const loadAtlas = (
395
+ fontData: SdfFontData,
396
+ atlasBlob: Blob | null,
397
+ ): Promise<void> => {
384
398
  if (!fontData || !fontData.chars) {
385
399
  return Promise.reject(new Error('Invalid SDF font data format'));
386
400
  }
@@ -395,7 +409,8 @@ export const loadFont = (
395
409
  return new Promise<void>((resolve, reject) => {
396
410
  // create new atlas texture using ImageTexture
397
411
  const atlasTexture = stage.txManager.createTexture('ImageTexture', {
398
- src: atlasUrl,
412
+ src: atlasBlob !== null ? atlasBlob : atlasUrl,
413
+ key: atlasUrl,
399
414
  premultiplyAlpha: false,
400
415
  });
401
416
 
@@ -454,15 +469,31 @@ export const loadFont = (
454
469
  });
455
470
  };
456
471
 
457
- // Initial attempt plus up to MAX_FONT_LOAD_RETRIES automatic reloads.
472
+ // Initial attempt plus up to MAX_FONT_LOAD_RETRIES automatic reloads. Only
473
+ // the first attempt uses the prefetch; a retry goes back to the network
474
+ // rather than replaying a payload that may be exactly what failed.
458
475
  const loadPromise = (async (): Promise<void> => {
459
476
  let lastError: unknown;
460
477
  for (let attempt = 0; attempt <= MAX_FONT_LOAD_RETRIES; attempt++) {
461
478
  try {
462
- await loadAtlas(await fetchFontData());
463
- // Success: clear the in-flight marker (the font now lives in fontCache)
464
- // parked nodes were already woken inside loadAtlas.
465
- fontLoadPromises.delete(primary);
479
+ // Both halves of a prefetch are already in flight in parallel; the
480
+ // fallbacks below are the original sequential path.
481
+ const fontData =
482
+ (attempt === 0 && prefetched !== undefined
483
+ ? await prefetched.data
484
+ : null) ?? (await fetchFontData());
485
+
486
+ const atlasBlob =
487
+ attempt === 0 && prefetched !== undefined
488
+ ? await prefetched.atlas
489
+ : null;
490
+
491
+ await loadAtlas(fontData, atlasBlob);
492
+ // Success: clear the in-flight markers (the font now lives in
493
+ // fontCache) — parked nodes were already woken inside loadAtlas.
494
+ for (let i = 0; i < names.length; i++) {
495
+ fontLoadPromises.delete(names[i]!);
496
+ }
466
497
  return;
467
498
  } catch (error) {
468
499
  lastError = error;
@@ -477,21 +508,30 @@ export const loadFont = (
477
508
  }
478
509
  }
479
510
 
480
- // Every attempt failed. Clear the in-flight marker so the font can be
511
+ // Every attempt failed. Clear the in-flight markers so the font can be
481
512
  // requested again and drop any partial cache entry. nodesWaitingForFont
482
513
  // is deliberately kept: nodes parked here must survive so a later
483
514
  // loadFont() (which reuses the list) can still wake them if the font
484
515
  // eventually loads. The list shrinks as nodes self-remove via
485
516
  // stopWaitingForFont on destroy.
486
- fontLoadPromises.delete(primary);
487
517
  for (let i = 0; i < names.length; i++) {
518
+ fontLoadPromises.delete(names[i]!);
488
519
  fontCache.delete(names[i]!);
489
520
  }
490
521
  console.error(`Failed to load SDF font: ${primary}`, lastError);
491
522
  throw lastError;
492
523
  })();
493
524
 
494
- fontLoadPromises.set(primary, loadPromise);
525
+ // Mark the load in flight under *every* name, not just the primary.
526
+ // `canRenderFont` treats a name with no cache entry and no in-flight promise
527
+ // as unrenderable, so keying only the primary made an alias unresolvable for
528
+ // the whole load: the stage would either fall through to the Canvas engine
529
+ // (permanently, with a substitute face) or, on an SDF-only engine list,
530
+ // throw "No compatible text renderer found". Registering every name also
531
+ // makes a later loadFont() for an alias dedupe onto this same load.
532
+ for (let i = 0; i < names.length; i++) {
533
+ fontLoadPromises.set(names[i]!, loadPromise);
534
+ }
495
535
  return loadPromise;
496
536
  };
497
537
 
@@ -0,0 +1,341 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import {
3
+ prefetchFont,
4
+ clearFontPrefetch,
5
+ takeSdfPrefetch,
6
+ takeCanvasPrefetch,
7
+ } from '../FontPrefetch.js';
8
+ import { loadFont as loadSdfFont, isFontLoaded } from '../SdfFontHandler.js';
9
+ import {
10
+ loadFont as loadCanvasFont,
11
+ isFontLoaded as isCanvasFontLoaded,
12
+ } from '../CanvasFontHandler.js';
13
+ import { EventEmitter } from '../../../common/EventEmitter.js';
14
+ import type { Stage } from '../../Stage.js';
15
+ import type { ImageTextureProps } from '../../textures/ImageTexture.js';
16
+
17
+ const VALID_FONT_DATA = {
18
+ chars: [{}],
19
+ kernings: [],
20
+ lightningMetrics: {
21
+ ascender: 800,
22
+ descender: -200,
23
+ lineGap: 200,
24
+ unitsPerEm: 1000,
25
+ },
26
+ };
27
+
28
+ // Records every request so tests can assert what was fetched, when, and in
29
+ // what order. Responses are keyed by URL and delivered on demand, letting a
30
+ // test hold a request open to observe concurrency.
31
+ class RecordingXHR {
32
+ static requests: RecordingXHR[] = [];
33
+ static responses: Record<string, unknown> = {};
34
+
35
+ status = 200;
36
+ response: unknown = null;
37
+ responseType = '';
38
+ url = '';
39
+ onload: (() => void) | null = null;
40
+ onerror: (() => void) | null = null;
41
+ onreadystatechange: (() => void) | null = null;
42
+ readyState = 0;
43
+
44
+ open(_method: string, url: string): void {
45
+ this.url = url;
46
+ }
47
+
48
+ send(): void {
49
+ RecordingXHR.requests.push(this);
50
+ }
51
+
52
+ /** Deliver the configured response for this request's URL. */
53
+ respond(status = 200): void {
54
+ this.status = status;
55
+ this.response = RecordingXHR.responses[this.url] ?? null;
56
+ this.readyState = 4;
57
+ if (this.onreadystatechange !== null) this.onreadystatechange();
58
+ if (this.onload !== null) this.onload();
59
+ }
60
+
61
+ /** Deliver every outstanding request. */
62
+ static respondAll(): void {
63
+ RecordingXHR.requests.forEach((r) => r.respond());
64
+ }
65
+
66
+ static reset(): void {
67
+ RecordingXHR.requests = [];
68
+ RecordingXHR.responses = {};
69
+ }
70
+ }
71
+
72
+ // `XMLHttpRequest.DONE` is read by fetchJson; the class stand-in must carry it.
73
+ (RecordingXHR as unknown as { DONE: number }).DONE = 4;
74
+
75
+ function makeFakeTexture() {
76
+ const tex = new EventEmitter() as unknown as EventEmitter & {
77
+ state: string;
78
+ preventCleanup: boolean;
79
+ setRenderableOwner: (owner: string, val: boolean) => void;
80
+ };
81
+ tex.state = 'loading';
82
+ tex.preventCleanup = false;
83
+ tex.setRenderableOwner = () => {};
84
+ return tex;
85
+ }
86
+
87
+ type FakeTexture = ReturnType<typeof makeFakeTexture>;
88
+
89
+ // Stage stub that records the props each atlas texture was created with, so
90
+ // tests can assert whether the prefetched blob (or the URL) was used.
91
+ function makeStage(): {
92
+ stage: Stage;
93
+ textures: FakeTexture[];
94
+ textureProps: ImageTextureProps[];
95
+ } {
96
+ const textures: FakeTexture[] = [];
97
+ const textureProps: ImageTextureProps[] = [];
98
+ const stage = {
99
+ txManager: {
100
+ createTexture: (_type: string, props: ImageTextureProps) => {
101
+ textureProps.push(props);
102
+ const t = makeFakeTexture();
103
+ textures.push(t);
104
+ return t;
105
+ },
106
+ removeTextureFromCache: () => {},
107
+ },
108
+ } as unknown as Stage;
109
+ return { stage, textures, textureProps };
110
+ }
111
+
112
+ const flush = (): Promise<void> => new Promise((r) => setTimeout(r, 0));
113
+
114
+ const sdfOpts = (fontFamily: string) => ({
115
+ fontFamily,
116
+ atlasUrl: `${fontFamily}.png`,
117
+ atlasDataUrl: `${fontFamily}.json`,
118
+ });
119
+
120
+ describe('FontPrefetch — SDF', () => {
121
+ let originalXHR: unknown;
122
+ let originalBlob: unknown;
123
+
124
+ beforeEach(() => {
125
+ originalXHR = (globalThis as unknown as { XMLHttpRequest: unknown })
126
+ .XMLHttpRequest;
127
+ (globalThis as unknown as { XMLHttpRequest: unknown }).XMLHttpRequest =
128
+ RecordingXHR;
129
+ // jsdom provides Blob; guard for environments that do not.
130
+ originalBlob = (globalThis as unknown as { Blob: unknown }).Blob;
131
+ RecordingXHR.reset();
132
+ clearFontPrefetch();
133
+ });
134
+
135
+ afterEach(() => {
136
+ (globalThis as unknown as { XMLHttpRequest: unknown }).XMLHttpRequest =
137
+ originalXHR;
138
+ (globalThis as unknown as { Blob: unknown }).Blob = originalBlob;
139
+ clearFontPrefetch();
140
+ });
141
+
142
+ it('requests the atlas description and the atlas image concurrently', () => {
143
+ prefetchFont(sdfOpts('ConcurrentFont'));
144
+
145
+ // Both are in flight before either has responded — the handler's own path
146
+ // only starts the image fetch after the JSON resolves.
147
+ expect(RecordingXHR.requests.map((r) => r.url).sort()).toEqual([
148
+ 'ConcurrentFont.json',
149
+ 'ConcurrentFont.png',
150
+ ]);
151
+ });
152
+
153
+ it('starts no request for a font it has already prefetched', () => {
154
+ prefetchFont(sdfOpts('DedupeFont'));
155
+ expect(RecordingXHR.requests.length).toBe(2);
156
+
157
+ prefetchFont(sdfOpts('DedupeFont'));
158
+ expect(RecordingXHR.requests.length).toBe(2);
159
+ });
160
+
161
+ it('hands the prefetched payload to loadFont instead of refetching', async () => {
162
+ const font = sdfOpts('PrefetchHit');
163
+ const blob = new Blob(['atlas-bytes']);
164
+ RecordingXHR.responses['PrefetchHit.json'] = VALID_FONT_DATA;
165
+ RecordingXHR.responses['PrefetchHit.png'] = blob;
166
+
167
+ prefetchFont(font);
168
+ RecordingXHR.respondAll();
169
+ const prefetchRequestCount = RecordingXHR.requests.length;
170
+
171
+ const { stage, textures, textureProps } = makeStage();
172
+ const promise = loadSdfFont(
173
+ stage,
174
+ font as Parameters<typeof loadSdfFont>[1],
175
+ );
176
+ await flush();
177
+ textures[0]!.emit('loaded');
178
+ await promise;
179
+
180
+ // No second trip to the network for either resource.
181
+ expect(RecordingXHR.requests.length).toBe(prefetchRequestCount);
182
+ // The atlas texture was built from the prefetched bytes...
183
+ expect(textureProps[0]!.src).toBe(blob);
184
+ // ...while still keying the texture cache by URL, so the blob path and the
185
+ // URL path resolve to the same cache entry.
186
+ expect(textureProps[0]!.key).toBe('PrefetchHit.png');
187
+ expect(isFontLoaded('PrefetchHit')).toBe(true);
188
+ });
189
+
190
+ it('falls back to fetching when the prefetch failed', async () => {
191
+ const font = sdfOpts('PrefetchFail');
192
+ // No responses registered -> fetchJson resolves null -> unusable prefetch.
193
+ prefetchFont(font);
194
+ RecordingXHR.respondAll();
195
+ RecordingXHR.reset();
196
+
197
+ RecordingXHR.responses['PrefetchFail.json'] = VALID_FONT_DATA;
198
+
199
+ const { stage, textures, textureProps } = makeStage();
200
+ const promise = loadSdfFont(
201
+ stage,
202
+ font as Parameters<typeof loadSdfFont>[1],
203
+ );
204
+ // The handler runs its own XHR for the atlas description.
205
+ await flush();
206
+ RecordingXHR.respondAll();
207
+ await flush();
208
+ textures[0]!.emit('loaded');
209
+ await promise;
210
+
211
+ expect(RecordingXHR.requests.map((r) => r.url)).toEqual([
212
+ 'PrefetchFail.json',
213
+ ]);
214
+ // Atlas falls back to the URL rather than a blob.
215
+ expect(textureProps[0]!.src).toBe('PrefetchFail.png');
216
+ expect(isFontLoaded('PrefetchFail')).toBe(true);
217
+ });
218
+
219
+ it('rejects a malformed atlas description rather than loading it', async () => {
220
+ RecordingXHR.responses['Malformed.json'] = { not: 'font data' };
221
+ prefetchFont(sdfOpts('Malformed'));
222
+ RecordingXHR.respondAll();
223
+
224
+ await expect(takeSdfPrefetch('Malformed')!.data).resolves.toBe(null);
225
+ });
226
+
227
+ it('parses an atlas description that arrives as a string', async () => {
228
+ RecordingXHR.responses['StringJson.json'] = JSON.stringify(VALID_FONT_DATA);
229
+ prefetchFont(sdfOpts('StringJson'));
230
+ RecordingXHR.respondAll();
231
+
232
+ await expect(takeSdfPrefetch('StringJson')!.data).resolves.toMatchObject({
233
+ chars: [{}],
234
+ });
235
+ });
236
+
237
+ it('claims a prefetch only once', () => {
238
+ prefetchFont(sdfOpts('OnceOnly'));
239
+
240
+ expect(takeSdfPrefetch('OnceOnly')).not.toBe(undefined);
241
+ expect(takeSdfPrefetch('OnceOnly')).toBe(undefined);
242
+ });
243
+
244
+ it('ignores a canvas-typed descriptor even when it carries atlas urls', () => {
245
+ prefetchFont({ ...sdfOpts('CanvasTyped'), type: 'canvas' });
246
+
247
+ expect(takeSdfPrefetch('CanvasTyped')).toBe(undefined);
248
+ });
249
+ });
250
+
251
+ describe('FontPrefetch — web fonts', () => {
252
+ class FakeFontFace {
253
+ static created: FakeFontFace[] = [];
254
+ constructor(public family: string, public source: string) {
255
+ FakeFontFace.created.push(this);
256
+ }
257
+ load(): Promise<FakeFontFace> {
258
+ return Promise.resolve(this);
259
+ }
260
+ }
261
+
262
+ let originalFontFace: unknown;
263
+
264
+ beforeEach(() => {
265
+ originalFontFace = (globalThis as unknown as { FontFace: unknown })
266
+ .FontFace;
267
+ (globalThis as unknown as { FontFace: unknown }).FontFace = FakeFontFace;
268
+ FakeFontFace.created = [];
269
+ clearFontPrefetch();
270
+ });
271
+
272
+ afterEach(() => {
273
+ (globalThis as unknown as { FontFace: unknown }).FontFace =
274
+ originalFontFace;
275
+ clearFontPrefetch();
276
+ });
277
+
278
+ it('starts the download for every registered name', async () => {
279
+ prefetchFont({
280
+ fontFamily: ['WebPrimary', 'WebAlias'],
281
+ fontUrl: 'web.woff2',
282
+ });
283
+
284
+ expect(FakeFontFace.created.map((f) => f.family)).toEqual([
285
+ 'WebPrimary',
286
+ 'WebAlias',
287
+ ]);
288
+ await expect(takeCanvasPrefetch('WebPrimary')).resolves.toHaveLength(2);
289
+ });
290
+
291
+ it('resolves to null when the download fails, so the handler can retry', async () => {
292
+ vi.spyOn(FakeFontFace.prototype, 'load').mockRejectedValueOnce(
293
+ new Error('network'),
294
+ );
295
+ prefetchFont({ fontFamily: 'WebFail', fontUrl: 'web.woff2' });
296
+
297
+ await expect(takeCanvasPrefetch('WebFail')).resolves.toBe(null);
298
+ });
299
+
300
+ it('does nothing when the engine has no FontFace', () => {
301
+ (globalThis as unknown as { FontFace: unknown }).FontFace = undefined;
302
+ prefetchFont({ fontFamily: 'NoFontFace', fontUrl: 'web.woff2' });
303
+
304
+ expect(takeCanvasPrefetch('NoFontFace')).toBe(undefined);
305
+ });
306
+
307
+ it('registers prefetched faces with the platform without reloading them', async () => {
308
+ prefetchFont({ fontFamily: 'CanvasHit', fontUrl: 'web.woff2' });
309
+ const createdByPrefetch = FakeFontFace.created.length;
310
+
311
+ const added: FakeFontFace[] = [];
312
+ const stage = {
313
+ platform: { addFont: (f: FakeFontFace) => added.push(f) },
314
+ } as unknown as Stage;
315
+
316
+ await loadCanvasFont(stage, {
317
+ fontFamily: 'CanvasHit',
318
+ fontUrl: 'web.woff2',
319
+ });
320
+
321
+ // No extra FontFace was constructed — the prefetched one was used.
322
+ expect(FakeFontFace.created.length).toBe(createdByPrefetch);
323
+ expect(added).toEqual(FakeFontFace.created);
324
+ expect(isCanvasFontLoaded('CanvasHit')).toBe(true);
325
+ });
326
+
327
+ it('loads normally when there is no prefetch to claim', async () => {
328
+ const added: FakeFontFace[] = [];
329
+ const stage = {
330
+ platform: { addFont: (f: FakeFontFace) => added.push(f) },
331
+ } as unknown as Stage;
332
+
333
+ await loadCanvasFont(stage, {
334
+ fontFamily: 'CanvasMiss',
335
+ fontUrl: 'web.woff2',
336
+ });
337
+
338
+ expect(FakeFontFace.created.map((f) => f.family)).toEqual(['CanvasMiss']);
339
+ expect(isCanvasFontLoaded('CanvasMiss')).toBe(true);
340
+ });
341
+ });
@@ -4,11 +4,13 @@ import {
4
4
  waitingForFont,
5
5
  isFontLoaded,
6
6
  getFontData,
7
+ canRenderFont,
7
8
  MAX_FONT_LOAD_RETRIES,
8
9
  } from '../SdfFontHandler.js';
9
10
  import { EventEmitter } from '../../../common/EventEmitter.js';
10
11
  import { TextureError, TextureErrorCode } from '../../TextureError.js';
11
12
  import type { Stage } from '../../Stage.js';
13
+ import type { TrProps } from '../TextRenderer.js';
12
14
 
13
15
  // Minimal XHR stand-in: synchronously delivers valid SDF font JSON so loadFont
14
16
  // proceeds to create the atlas texture and attach its event listeners.
@@ -294,6 +296,47 @@ describe('SdfFontHandler loadFont — multiple names for one font', () => {
294
296
  }
295
297
  });
296
298
 
299
+ it('reports every name as renderable while the font is still loading', async () => {
300
+ const { stage, textures } = makeStage();
301
+ const names = ['MidLoadPrimary', 'MidLoadAlias'];
302
+
303
+ const promise = loadFont(stage, opts(names));
304
+
305
+ // Mid-load: nothing is in fontCache yet, so this is answered purely by the
306
+ // in-flight markers. An alias that answers `false` here makes the stage
307
+ // resolve a *different* text renderer for the node — silently substituting
308
+ // a Canvas face, or throwing "No compatible text renderer found" when SDF
309
+ // is the only engine. Both names must answer the same.
310
+ for (let i = 0; i < names.length; i++) {
311
+ expect(canRenderFont({ fontFamily: names[i]! } as TrProps)).toBe(true);
312
+ }
313
+ expect(isFontLoaded(names[1]!)).toBe(false);
314
+
315
+ await flush();
316
+ textures[0]!.emit('loaded');
317
+ await promise;
318
+
319
+ for (let i = 0; i < names.length; i++) {
320
+ expect(canRenderFont({ fontFamily: names[i]! } as TrProps)).toBe(true);
321
+ expect(isFontLoaded(names[i]!)).toBe(true);
322
+ }
323
+ });
324
+
325
+ it('dedupes a load requested under an alias onto the in-flight load', async () => {
326
+ const { stage, textures } = makeStage();
327
+
328
+ const first = loadFont(stage, opts(['DedupePrimary', 'DedupeAlias']));
329
+ const second = loadFont(stage, opts('DedupeAlias'));
330
+
331
+ expect(second).toBe(first);
332
+
333
+ await flush();
334
+ // A single atlas fetch served both calls.
335
+ expect(textures.length).toBe(1);
336
+ textures[0]!.emit('loaded');
337
+ await first;
338
+ });
339
+
297
340
  it('wakes nodes parked under an alias, not just the primary name', async () => {
298
341
  const { stage, textures } = makeStage();
299
342
  const names = ['MultiWakePrimary', 'MultiWakeAlias'];
@@ -654,16 +654,16 @@ export type RendererMainSettings = RendererRuntimeSettings & {
654
654
  * ignore it, returning straight (non-premultiplied) alpha. This causes edge
655
655
  * "ghosting" on images with transparency.
656
656
  *
657
- * `'auto'` (the default) detects via a cheap startup probe (one 1×1 PNG
658
- * decode + texture upload + framebuffer readback). On devices that honor the
659
- * option — the vast majority — the probe returns `true` and behavior is
660
- * identical to assuming it honored; only devices that actually ignore it
661
- * (e.g. the Movistar STB) switch to the WebGL-side premultiply fallback, so
662
- * the change is invisible everywhere it isn't needed. Set to a boolean to
663
- * force the value and skip the probe (`false` forces the GL-premultiply
664
- * fallback; `true` skips it).
665
- *
666
- * @defaultValue `'auto'` (probe once at startup)
657
+ * Set to `'auto'` to detect via a cheap startup probe (one 1×1 PNG decode +
658
+ * texture upload + framebuffer readback). On devices that honor the option —
659
+ * the vast majority — the probe returns `true` and behavior is identical to
660
+ * assuming it honored; only devices that actually ignore it (e.g. the
661
+ * Movistar STB) switch to the WebGL-side premultiply fallback. Set to a
662
+ * boolean to force the value (`false` forces the GL-premultiply fallback;
663
+ * `true` skips it). Leave unset to assume the option is honored the
664
+ * default, which preserves existing behavior with no probe overhead.
665
+ *
666
+ * @defaultValue `true` (assume honored; no probe)
667
667
  */
668
668
  premultiplyAlphaHonored?: boolean | 'auto';
669
669
 
@@ -825,11 +825,11 @@ export class RendererMain extends EventEmitter {
825
825
  textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 10,
826
826
  canvas: settings.canvas,
827
827
  createImageBitmapSupport: settings.createImageBitmapSupport || 'auto',
828
- // undefined -> 'auto' (probe once at startup); 'auto' -> probe;
829
- // explicit boolean -> force the value, skip the probe.
828
+ // undefined -> true (assume honored, no probe); 'auto' -> probe;
829
+ // explicit boolean -> force the value.
830
830
  premultiplyAlphaHonored:
831
831
  settings.premultiplyAlphaHonored === undefined
832
- ? 'auto'
832
+ ? true
833
833
  : settings.premultiplyAlphaHonored,
834
834
  platform: settings.platform || null,
835
835
  maxRetryCount: settings.maxRetryCount ?? 5,