@solidtv/renderer 1.5.5 → 1.6.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.
Files changed (106) hide show
  1. package/dist/src/core/CoreNode.js +2 -2
  2. package/dist/src/core/CoreNode.js.map +1 -1
  3. package/dist/src/core/Stage.js +9 -1
  4. package/dist/src/core/Stage.js.map +1 -1
  5. package/dist/src/core/lib/WebGlContextWrapper.d.ts +17 -1
  6. package/dist/src/core/lib/WebGlContextWrapper.js +21 -0
  7. package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
  8. package/dist/src/core/lib/colorParser.d.ts +4 -1
  9. package/dist/src/core/lib/colorParser.js +16 -6
  10. package/dist/src/core/lib/colorParser.js.map +1 -1
  11. package/dist/src/core/lib/validateImageBitmap.js +7 -0
  12. package/dist/src/core/lib/validateImageBitmap.js.map +1 -1
  13. package/dist/src/core/platforms/web/WebPlatform.js +39 -2
  14. package/dist/src/core/platforms/web/WebPlatform.js.map +1 -1
  15. package/dist/src/core/renderers/canvas/CanvasRenderer.d.ts +4 -0
  16. package/dist/src/core/renderers/canvas/CanvasRenderer.js +28 -13
  17. package/dist/src/core/renderers/canvas/CanvasRenderer.js.map +1 -1
  18. package/dist/src/core/renderers/canvas/CanvasShaderNode.js +1 -0
  19. package/dist/src/core/renderers/canvas/CanvasShaderNode.js.map +1 -1
  20. package/dist/src/core/renderers/webgl/SdfRenderOp.js +2 -2
  21. package/dist/src/core/renderers/webgl/SdfRenderOp.js.map +1 -1
  22. package/dist/src/core/renderers/webgl/WebGlRenderer.d.ts +43 -0
  23. package/dist/src/core/renderers/webgl/WebGlRenderer.js +109 -10
  24. package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
  25. package/dist/src/core/renderers/webgl/WebGlShaderProgram.js +21 -13
  26. package/dist/src/core/renderers/webgl/WebGlShaderProgram.js.map +1 -1
  27. package/dist/src/core/renderers/webgl/internal/ShaderUtils.d.ts +8 -3
  28. package/dist/src/core/renderers/webgl/internal/ShaderUtils.js +12 -11
  29. package/dist/src/core/renderers/webgl/internal/ShaderUtils.js.map +1 -1
  30. package/dist/src/core/shaders/canvas/Rounded.js +3 -3
  31. package/dist/src/core/shaders/canvas/Rounded.js.map +1 -1
  32. package/dist/src/core/shaders/canvas/RoundedWithShadow.js +3 -3
  33. package/dist/src/core/shaders/canvas/RoundedWithShadow.js.map +1 -1
  34. package/dist/src/core/shaders/canvas/utils/render.js +3 -3
  35. package/dist/src/core/shaders/canvas/utils/render.js.map +1 -1
  36. package/dist/src/core/shaders/webgl/Border.js +12 -11
  37. package/dist/src/core/shaders/webgl/Border.js.map +1 -1
  38. package/dist/src/core/shaders/webgl/HolePunch.js +12 -5
  39. package/dist/src/core/shaders/webgl/HolePunch.js.map +1 -1
  40. package/dist/src/core/shaders/webgl/LinearGradient.js +3 -19
  41. package/dist/src/core/shaders/webgl/LinearGradient.js.map +1 -1
  42. package/dist/src/core/shaders/webgl/RadialGradient.js +3 -20
  43. package/dist/src/core/shaders/webgl/RadialGradient.js.map +1 -1
  44. package/dist/src/core/shaders/webgl/RadialProgress.js +15 -23
  45. package/dist/src/core/shaders/webgl/RadialProgress.js.map +1 -1
  46. package/dist/src/core/shaders/webgl/RoundedWithBorder.js +35 -32
  47. package/dist/src/core/shaders/webgl/RoundedWithBorder.js.map +1 -1
  48. package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js +58 -45
  49. package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js.map +1 -1
  50. package/dist/src/core/shaders/webgl/RoundedWithShadow.js +15 -13
  51. package/dist/src/core/shaders/webgl/RoundedWithShadow.js.map +1 -1
  52. package/dist/src/core/shaders/webgl/Shadow.js +4 -5
  53. package/dist/src/core/shaders/webgl/Shadow.js.map +1 -1
  54. package/dist/src/core/text-rendering/SdfTextRenderer.js +23 -10
  55. package/dist/src/core/text-rendering/SdfTextRenderer.js.map +1 -1
  56. package/dist/src/core/text-rendering/TextLayoutEngine.js +14 -2
  57. package/dist/src/core/text-rendering/TextLayoutEngine.js.map +1 -1
  58. package/dist/src/main-api/Renderer.d.ts +20 -0
  59. package/dist/src/main-api/Renderer.js +6 -0
  60. package/dist/src/main-api/Renderer.js.map +1 -1
  61. package/dist/src/utils.js +1 -1
  62. package/dist/src/utils.js.map +1 -1
  63. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  64. package/package.json +1 -1
  65. package/src/core/CoreNode.ts +2 -2
  66. package/src/core/Stage.renderListUpdate.test.ts +73 -0
  67. package/src/core/Stage.ts +11 -1
  68. package/src/core/lib/WebGlContextWrapper.test.ts +23 -1
  69. package/src/core/lib/WebGlContextWrapper.ts +23 -1
  70. package/src/core/lib/colorParser.test.ts +46 -0
  71. package/src/core/lib/colorParser.ts +17 -6
  72. package/src/core/lib/validateImageBitmap.test.ts +15 -0
  73. package/src/core/lib/validateImageBitmap.ts +8 -0
  74. package/src/core/platforms/web/WebPlatform.loopError.test.ts +145 -0
  75. package/src/core/platforms/web/WebPlatform.ts +39 -2
  76. package/src/core/renderers/canvas/CanvasRenderer.test.ts +230 -0
  77. package/src/core/renderers/canvas/CanvasRenderer.ts +29 -15
  78. package/src/core/renderers/canvas/CanvasShaderNode.test.ts +73 -0
  79. package/src/core/renderers/canvas/CanvasShaderNode.ts +1 -0
  80. package/src/core/renderers/webgl/SdfRenderOp.ts +2 -2
  81. package/src/core/renderers/webgl/WebGlRenderer.reuseRenderOp.test.ts +122 -0
  82. package/src/core/renderers/webgl/WebGlRenderer.sdfBuffer.test.ts +302 -0
  83. package/src/core/renderers/webgl/WebGlRenderer.ts +151 -11
  84. package/src/core/renderers/webgl/WebGlShaderProgram.reuseRenderOp.test.ts +150 -0
  85. package/src/core/renderers/webgl/WebGlShaderProgram.ts +27 -15
  86. package/src/core/renderers/webgl/WebGlShaderProgram.uniformDedup.test.ts +4 -2
  87. package/src/core/renderers/webgl/internal/ShaderUtils.test.ts +124 -0
  88. package/src/core/renderers/webgl/internal/ShaderUtils.ts +14 -13
  89. package/src/core/shaders/canvas/Rounded.ts +3 -3
  90. package/src/core/shaders/canvas/RoundedWithShadow.ts +3 -3
  91. package/src/core/shaders/canvas/utils/render.ts +3 -3
  92. package/src/core/shaders/webgl/Border.ts +12 -11
  93. package/src/core/shaders/webgl/HolePunch.ts +12 -5
  94. package/src/core/shaders/webgl/LinearGradient.ts +3 -19
  95. package/src/core/shaders/webgl/RadialGradient.ts +3 -20
  96. package/src/core/shaders/webgl/RadialProgress.ts +15 -23
  97. package/src/core/shaders/webgl/RoundedWithBorder.ts +35 -32
  98. package/src/core/shaders/webgl/RoundedWithBorderAndShadow.ts +58 -45
  99. package/src/core/shaders/webgl/RoundedWithShadow.ts +15 -13
  100. package/src/core/shaders/webgl/Shadow.ts +4 -5
  101. package/src/core/text-rendering/SdfTextRenderer.test.ts +177 -0
  102. package/src/core/text-rendering/SdfTextRenderer.ts +38 -10
  103. package/src/core/text-rendering/TextLayoutEngine.ts +15 -2
  104. package/src/core/text-rendering/tests/TextLayoutEngine.test.ts +99 -0
  105. package/src/main-api/Renderer.ts +28 -0
  106. package/src/utils.ts +1 -1
@@ -368,6 +368,13 @@ export const wrapLine = (
368
368
  ): WrappedLinesStruct => {
369
369
  const words = line.split(spaceRegex);
370
370
  const spaces = line.match(spaceRegex) || [];
371
+ // Leading whitespace splits into an empty token; fold it into the next
372
+ // word so it isn't dropped and re-applied after the first word instead.
373
+ if (words[0] === '' && words.length > 1) {
374
+ const leadingSpace = spaces.shift() ?? '';
375
+ words.shift();
376
+ words[0] = leadingSpace + (words[0] ?? '');
377
+ }
371
378
  const wrappedLines: TextLineStruct[] = [];
372
379
  let currentLine = '';
373
380
  let currentLineWidth = 0;
@@ -454,8 +461,14 @@ export const wrapLine = (
454
461
  continue;
455
462
  }
456
463
  const space = spaces[spaceIdx++] || '';
457
- // For width calculation, treat ZWSP as having 0 width but regular space functionality
458
- const effectiveSpaceWidth = space === '\u200B' ? 0 : spaceWidth;
464
+ // Single ' ' is the dominant separator \u2014 reuse the precomputed width.
465
+ // Multi-space/ZWSP runs collapse into one match; measure those for real.
466
+ const effectiveSpaceWidth =
467
+ space === ' '
468
+ ? spaceWidth
469
+ : space.length > 0
470
+ ? measureText(space, fontFamily, letterSpacing)
471
+ : 0;
459
472
  const totalWidth = currentLineWidth + effectiveSpaceWidth + wordWidth;
460
473
 
461
474
  if (totalWidth < maxWidth) {
@@ -248,6 +248,105 @@ describe('SDF Text Utils', () => {
248
248
  expect(result[0][0]?.[2]).toBe(true);
249
249
  expect(result[1]).toBe(0);
250
250
  });
251
+
252
+ it('should keep leading whitespace attached to the first word, not the word after it', () => {
253
+ // Regression test: a line starting with padding spaces (used e.g. to
254
+ // reserve room for an inline badge before the first word) used to have
255
+ // those spaces silently reassigned as the gap AFTER the first word.
256
+ const result = wrapLine(
257
+ testMeasureText,
258
+ ' hello world',
259
+ 'Arial',
260
+ 1000,
261
+ 0,
262
+ 10, // spaceWidth
263
+ '',
264
+ 0,
265
+ 'break-word',
266
+ 10,
267
+ );
268
+
269
+ const [lines] = result;
270
+ expect(lines).toHaveLength(1);
271
+ expect(lines[0]?.[0]).toBe(' hello world');
272
+ });
273
+
274
+ it('should count the full width of a multi-space separator when wrapping', () => {
275
+ // 'aa bb' really measures 60 (6 chars * 10). Counting the separator
276
+ // as a single spaceWidth (50) used to keep it on one line past maxWidth.
277
+ const result = wrapLine(
278
+ testMeasureText,
279
+ 'aa bb',
280
+ 'Arial',
281
+ 55, // maxWidth — below the real 60, above the buggy 50
282
+ 0,
283
+ 10, // spaceWidth
284
+ '',
285
+ 0,
286
+ 'break-word',
287
+ 10,
288
+ );
289
+
290
+ const [lines] = result;
291
+ expect(lines).toHaveLength(2);
292
+ expect(lines[0]).toEqual(['aa', 20, false, 0, 0]);
293
+ expect(lines[1]).toEqual(['bb', 20, false, 0, 0]);
294
+ });
295
+
296
+ it('should preserve a multi-space separator and its width when the line fits', () => {
297
+ const result = wrapLine(
298
+ testMeasureText,
299
+ 'aa bb',
300
+ 'Arial',
301
+ 1000,
302
+ 0,
303
+ 10, // spaceWidth
304
+ '',
305
+ 0,
306
+ 'break-word',
307
+ 10,
308
+ );
309
+
310
+ expect(result[0][0]).toEqual(['aa bb', 60, false, 0, 0]);
311
+ });
312
+
313
+ it('should treat a run of multiple ZWSPs as a zero-width separator', () => {
314
+ const result = wrapLine(
315
+ testMeasureText,
316
+ 'aa\u200B\u200Bbb',
317
+ 'Arial',
318
+ 1000,
319
+ 0,
320
+ 10, // spaceWidth
321
+ '',
322
+ 0,
323
+ 'break-word',
324
+ 10,
325
+ );
326
+
327
+ // Zero-width separator: no visible gap added, width is just the glyphs
328
+ expect(result[0][0]).toEqual(['aabb', 40, false, 0, 0]);
329
+ });
330
+
331
+ it('should use the precomputed spaceWidth for a single-space separator', () => {
332
+ // spaceWidth (4) deliberately differs from testMeasureText(' ') (10):
333
+ // the single-space fast path must reuse the precomputed value instead
334
+ // of re-measuring the separator.
335
+ const result = wrapLine(
336
+ testMeasureText,
337
+ 'aa bb',
338
+ 'Arial',
339
+ 1000,
340
+ 0,
341
+ 4, // spaceWidth
342
+ '',
343
+ 0,
344
+ 'break-word',
345
+ 10,
346
+ );
347
+
348
+ expect(result[0][0]).toEqual(['aa bb', 44, false, 0, 0]);
349
+ });
251
350
  });
252
351
 
253
352
  describe('wrapText', () => {
@@ -23,6 +23,9 @@ import type {
23
23
  import { WebPlatform } from '../core/platforms/web/WebPlatform.js';
24
24
  import { Platform } from '../core/platforms/Platform.js';
25
25
 
26
+ /** Shared default `handleLoopError` — swallows the error, keeping the loop alive. */
27
+ const noop = (): void => {};
28
+
26
29
  /**
27
30
  * FPS Update Event Data
28
31
  *
@@ -661,6 +664,27 @@ export type RendererMainSettings = RendererRuntimeSettings & {
661
664
  *
662
665
  */
663
666
  maxRetryCount?: number;
667
+
668
+ /**
669
+ * Render loop error handler — an escape hatch for `requestAnimationFrame` crashes
670
+ *
671
+ * @remarks
672
+ * The render loop runs client-supplied code every frame: synchronous event
673
+ * subscribers (`frameTick`, `idle`, `fpsUpdate`, etc.) and animation steps. If
674
+ * any of these throw, the exception would otherwise propagate out of the
675
+ * `requestAnimationFrame` callback and permanently stop the loop — freezing
676
+ * the entire app until reload.
677
+ *
678
+ * The loop catches such errors and never lets them stop it. By default the
679
+ * error is swallowed (the default handler is a no-op), so a single bad frame
680
+ * can't freeze the app. Provide your own handler to log/report the crash (and
681
+ * optionally recover); the loop still keeps running after it returns.
682
+ *
683
+ * @param error The error thrown during the frame
684
+ *
685
+ * @defaultValue a no-op (errors are swallowed; the loop keeps running)
686
+ */
687
+ handleLoopError?: (error: unknown) => void;
664
688
  };
665
689
 
666
690
  /**
@@ -777,6 +801,9 @@ export class RendererMain extends EventEmitter {
777
801
  : settings.premultiplyAlphaHonored,
778
802
  platform: settings.platform || null,
779
803
  maxRetryCount: settings.maxRetryCount ?? 5,
804
+ // Default to a no-op so a thrown frame is swallowed and the render loop
805
+ // never crashes; apps can override to log/report.
806
+ handleLoopError: settings.handleLoopError ?? noop,
780
807
  };
781
808
 
782
809
  const {
@@ -841,6 +868,7 @@ export class RendererMain extends EventEmitter {
841
868
  premultiplyAlphaHonored: settings.premultiplyAlphaHonored,
842
869
  platform,
843
870
  maxRetryCount: settings.maxRetryCount ?? 5,
871
+ handleLoopError: settings.handleLoopError,
844
872
  });
845
873
 
846
874
  // Extract the root node
package/src/utils.ts CHANGED
@@ -9,7 +9,7 @@ export function createWebGLContext(
9
9
  alpha: true,
10
10
  antialias: false,
11
11
  depth: false,
12
- stencil: true,
12
+ stencil: false,
13
13
  desynchronized: false,
14
14
  // Disabled because it prevents Visual Regression Tests from working
15
15
  // failIfMajorPerformanceCaveat: true,