@solidtv/renderer 1.2.0 → 1.2.2

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 (30) hide show
  1. package/dist/src/common/CommonTypes.d.ts +5 -0
  2. package/dist/src/core/CoreTextNode.d.ts +17 -0
  3. package/dist/src/core/CoreTextNode.js +27 -3
  4. package/dist/src/core/CoreTextNode.js.map +1 -1
  5. package/dist/src/core/shaders/templates/BorderTemplate.d.ts +1 -1
  6. package/dist/src/core/shaders/templates/BorderTemplate.js +1 -1
  7. package/dist/src/core/shaders/templates/BorderTemplate.js.map +1 -1
  8. package/dist/src/core/text-rendering/CanvasTextRenderer.js +7 -0
  9. package/dist/src/core/text-rendering/CanvasTextRenderer.js.map +1 -1
  10. package/dist/src/core/text-rendering/SdfFontHandler.js +4 -3
  11. package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
  12. package/dist/src/core/text-rendering/SdfTextRenderer.js +11 -0
  13. package/dist/src/core/text-rendering/SdfTextRenderer.js.map +1 -1
  14. package/dist/src/core/text-rendering/TextLayoutEngine.js +32 -24
  15. package/dist/src/core/text-rendering/TextLayoutEngine.js.map +1 -1
  16. package/dist/src/core/text-rendering/TextRenderer.d.ts +34 -8
  17. package/dist/src/main-api/Renderer.d.ts +8 -5
  18. package/dist/src/main-api/Renderer.js +1 -1
  19. package/dist/src/main-api/Renderer.js.map +1 -1
  20. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  21. package/package.json +1 -1
  22. package/src/common/CommonTypes.ts +5 -0
  23. package/src/core/CoreTextNode.ts +28 -3
  24. package/src/core/shaders/templates/BorderTemplate.ts +2 -2
  25. package/src/core/text-rendering/CanvasTextRenderer.ts +9 -0
  26. package/src/core/text-rendering/SdfFontHandler.ts +4 -3
  27. package/src/core/text-rendering/SdfTextRenderer.ts +13 -0
  28. package/src/core/text-rendering/TextLayoutEngine.ts +31 -24
  29. package/src/core/text-rendering/TextRenderer.ts +34 -8
  30. package/src/main-api/Renderer.ts +9 -6
@@ -69,10 +69,10 @@ export const normalizeFontMetrics = (
69
69
  /**
70
70
  * Engine-wide per-line baseline anchor. Configured once at renderer creation
71
71
  * via {@link RendererMainSettings.textBaselineMode}, not exposed per node so
72
- * a single app can't mix anchor models across its text. Defaults to `'cap'`
73
- * — see {@link TextBaselineMode} for the rationale.
72
+ * a single app can't mix anchor models across its text. Defaults to
73
+ * `'optical'` — see {@link TextBaselineMode} for the rationale.
74
74
  */
75
- let baselineMode: TextBaselineMode = 'cap';
75
+ let baselineMode: TextBaselineMode = 'optical';
76
76
 
77
77
  /**
78
78
  * Sets the engine-wide baseline anchor. Called by `Stage` during construction;
@@ -185,44 +185,51 @@ export const mapTextLayout = (
185
185
 
186
186
  // line[4] stores the alphabetic baseline Y of each line in screen px.
187
187
  //
188
- // ── Per-line anchor: cap-height centering ─────────────────────────────
188
+ // ── Per-line anchor: optical centering (default) ─────────────────────
189
189
  //
190
- // baselineY(i) = lineHeight/2 + capHeight/2 + i × lineHeight
190
+ // anchor = (capHeight + xHeight) / 2
191
+ // baselineY = lineHeight/2 + anchor/2 + i × lineHeight
191
192
  //
192
- // The baseline sits below the line's geometric mid-line by exactly
193
- // `capHeight / 2`, so the top of an uppercase letter lands the same
194
- // distance *above* the mid-line i.e. capital letters bracket the
195
- // center symmetrically. Cap-height centering matches what designers
196
- // expect for UI text (button labels, headings, badges): TXYZ and 1234
197
- // sit centered; descenders like 'gjpq' hang slightly below, mirroring
198
- // CSS button behavior in browsers.
193
+ // Pure cap-height centering reads slightly low for lowercase-heavy
194
+ // mixed-case strings like "Button" because the visual mass sits in
195
+ // the x-height band, below cap-center. Pure x-height centering goes
196
+ // the other way — caps appear high. Splitting the difference (mean of
197
+ // cap and x heights) lands at the optical center for the broadest mix
198
+ // of UI text. This is the same heuristic macOS/iOS controls use.
199
199
  //
200
200
  // Alternative anchors considered (kept here for the record):
201
201
  //
202
+ // ── cap-height centering ────────────────────────────────────────────
203
+ // baselineY = lineHeight/2 + capHeight/2 + i × lineHeight
204
+ // Caps bracket the mid-line symmetrically. Right for all-caps or
205
+ // numeric content (timers, badges); reads low for "Button".
206
+ //
202
207
  // ── x-height centering ──────────────────────────────────────────────
203
- // baselineY(i) = lineHeight/2 + xHeight/2 + i × lineHeight
204
- // Centers lowercase letters on the mid-line. Matches CSS inline
205
- // `vertical-align: middle`. Reads well for running body text but
206
- // capitals appear high in headings/labels — wrong default for TV UI.
208
+ // baselineY = lineHeight/2 + xHeight/2 + i × lineHeight
209
+ // Centers lowercase. Matches CSS inline `vertical-align: middle`.
210
+ // Reads well for body text; capitals appear high in headings.
207
211
  //
208
212
  // ── line-box centering (pre-cap-height behavior) ───────────────────
209
213
  // const halfLeading = (lineHeightPx − bareLineHeight) / 2
210
- // baselineY(i) = halfLeading + ascender + i × lineHeight
211
- // Centers the abstract asc-to-desc-plus-leading rectangle. The visible
212
- // ink lands noticeably high because asc/(asc−desc) is asymmetric for
213
- // most Latin fonts (~4.2:1 for Ubuntu). Mathematically tidy, visually
214
- // wrong.
214
+ // baselineY = halfLeading + ascender + i × lineHeight
215
+ // Centers the abstract asc-to-desc-plus-leading rectangle. Ink lands
216
+ // noticeably high because asc/(asc−desc) is asymmetric for most
217
+ // Latin fonts (~4.2:1 for Ubuntu). Mathematically tidy, visually wrong.
215
218
  //
216
219
  // The active anchor is configured at renderer creation via
217
- // `RendererMainSettings.textBaselineMode`. Defaults to `'cap'`.
220
+ // `RendererMainSettings.textBaselineMode`. Defaults to `'optical'`.
218
221
  let firstBaselineY: number;
219
- if (baselineMode === 'x') {
222
+ if (baselineMode === 'cap') {
223
+ firstBaselineY = (lineHeightPx + metrics.capHeight) * 0.5;
224
+ } else if (baselineMode === 'x') {
220
225
  firstBaselineY = (lineHeightPx + metrics.xHeight) * 0.5;
221
226
  } else if (baselineMode === 'linebox') {
222
227
  const halfLeading = (lineHeightPx - bareLineHeight) * 0.5;
223
228
  firstBaselineY = halfLeading + metrics.ascender;
224
229
  } else {
225
- firstBaselineY = (lineHeightPx + metrics.capHeight) * 0.5;
230
+ // 'optical' mean of cap-height and x-height
231
+ const opticalAnchor = (metrics.capHeight + metrics.xHeight) * 0.5;
232
+ firstBaselineY = (lineHeightPx + opticalAnchor) * 0.5;
226
233
  }
227
234
  for (let i = 0; i < effectiveLineAmount; i++) {
228
235
  const line = lines[i] as TextLineStruct;
@@ -21,17 +21,20 @@ export type TextRenderers = 'canvas' | 'sdf';
21
21
  * cannot be overridden per node — see the engine-wide reasoning in
22
22
  * `TextLayoutEngine.mapTextLayout`.
23
23
  *
24
- * - `'cap'` (default): capital letters centered. Best for UI text — button
25
- * labels, headings, badges. Capitals and digits bracket the center
26
- * symmetrically; descenders hang slightly below, matching CSS button
27
- * behavior in browsers.
28
- * - `'x'`: lowercase x-height centered. Better for running body text;
24
+ * - `'optical'` (default): the midpoint of cap-height and x-height is
25
+ * centered. Looks visually centered for mixed-case text the
26
+ * sweet spot between `'cap'` (which can read low for lowercase-heavy
27
+ * strings) and `'x'` (which can read high for headings).
28
+ * - `'cap'`: capital letters centered. Use when content is mostly
29
+ * uppercase or numeric (badges, timers). Mixed-case strings like
30
+ * "Button" may read slightly low.
31
+ * - `'x'`: lowercase x-height centered. Best for running body text;
29
32
  * capitals appear slightly high in headings.
30
33
  * - `'linebox'`: legacy. Centers the abstract asc-to-desc-plus-leading
31
34
  * rectangle. Mathematically tidy but visually unbalanced because most
32
35
  * Latin fonts have asymmetric asc/desc ratios.
33
36
  */
34
- export type TextBaselineMode = 'cap' | 'x' | 'linebox';
37
+ export type TextBaselineMode = 'optical' | 'cap' | 'x' | 'linebox';
35
38
  /**
36
39
  * Structure mapping font family names to a set of font faces.
37
40
  */
@@ -231,10 +234,13 @@ export interface TrProps extends TrFontProps {
231
234
  */
232
235
  maxLines: number;
233
236
  /**
234
- * Vertical alignment of the text block within `maxHeight`.
237
+ * Vertical alignment of the text block within its containing box.
235
238
  *
236
239
  * @remarks
237
- * Activates when `maxHeight > 0`. Composes with `textBaselineMode`
240
+ * The containing box is `maxHeight` if set, otherwise the node's
241
+ * own `h` (which a flex parent or the user may have grown beyond
242
+ * the intrinsic text height). Activates whenever the box is taller
243
+ * than the intrinsic text height. Composes with `textBaselineMode`
238
244
  * (per-line anchor). CSS line-box semantics — `'top'` leaves
239
245
  * half-leading above the first line's cap-top; `'bottom'` leaves
240
246
  * half-leading below the last line's descender.
@@ -314,6 +320,11 @@ export interface TextLayout {
314
320
  * Total text height
315
321
  */
316
322
  height: number;
323
+ /**
324
+ * Trimmed text height — cap-top of the first line to descender bottom
325
+ * of the last line. See `TextRenderInfo.trimmedHeight`.
326
+ */
327
+ trimmedHeight: number;
317
328
  /**
318
329
  * Font scale factor
319
330
  */
@@ -410,6 +421,21 @@ export interface TextRenderProps {
410
421
  export interface TextRenderInfo {
411
422
  width: number;
412
423
  height: number;
424
+ /**
425
+ * Height of the visible glyph extent — from the first line's cap-top to
426
+ * the last line's descender bottom. Excludes half-leading and the slack
427
+ * between the font's ascender and cap-top.
428
+ *
429
+ * @remarks
430
+ * Formula: `capHeight − descender + (lines − 1) × lineHeightPx`
431
+ * (descender is negative in font metrics, so subtracting it adds the
432
+ * descender depth). For empty text, this is 0.
433
+ *
434
+ * Use this when you want flex `alignItems: 'center'` (or any layout
435
+ * that aligns by node `h`) to optically center the visible glyphs.
436
+ * Set `node.h = node.trimmedHeight` after the `loaded` event.
437
+ */
438
+ trimmedHeight: number;
413
439
  hasRemainingText?: boolean;
414
440
  remainingLines?: number;
415
441
  imageData?: ImageData | null; // Image data for Canvas Text Renderer
@@ -374,17 +374,20 @@ export type RendererMainSettings = RendererRuntimeSettings & {
374
374
  * mid-line. This is engine-wide and intentionally not exposed per node —
375
375
  * mixing anchor models within one app produces visually inconsistent text.
376
376
  *
377
- * - `'cap'` (default): capital letters and digits sit centered on the line.
378
- * Best fit for UI text (button labels, headings, badges); descenders on
379
- * words like 'gjpq' hang slightly below center, matching CSS button
380
- * behavior in browsers.
377
+ * - `'optical'` (default): the mean of cap-height and x-height is centered.
378
+ * Reads visually centered for mixed-case UI text like "Button"; the
379
+ * sweet spot between `'cap'` (low for lowercase-heavy) and `'x'` (high
380
+ * for headings). Matches macOS/iOS control behavior.
381
+ * - `'cap'`: capital letters and digits sit centered on the line. Use
382
+ * when content is mostly uppercase or numeric (timers, badges); mixed-
383
+ * case "Button" can read slightly low.
381
384
  * - `'x'`: lowercase x-height is centered. Better for running body text;
382
385
  * capitals appear slightly high in headings.
383
386
  * - `'linebox'`: legacy mode. Centers the asc/lineGap/desc rectangle.
384
387
  * Mathematically tidy but visually unbalanced because most Latin fonts
385
388
  * have asymmetric asc/desc ratios.
386
389
  *
387
- * @defaultValue `'cap'`
390
+ * @defaultValue `'optical'`
388
391
  */
389
392
  textBaselineMode: TextBaselineMode;
390
393
 
@@ -555,7 +558,7 @@ export class RendererMain extends EventEmitter {
555
558
  renderEngine: settings.renderEngine,
556
559
  quadBufferSize: settings.quadBufferSize ?? 4 * 1024 * 1024,
557
560
  fontEngines: settings.fontEngines ?? [],
558
- textBaselineMode: settings.textBaselineMode ?? 'cap',
561
+ textBaselineMode: settings.textBaselineMode ?? 'optical',
559
562
  textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 10,
560
563
  canvas: settings.canvas,
561
564
  createImageBitmapSupport: settings.createImageBitmapSupport || 'full',