fix(functional): centre text vertically in buttons and form fields (S14)
Python CI / test (3.10) (push) Has been cancelled
Python CI / test (3.12) (push) Has been cancelled
Python CI / test (3.13) (push) Has been cancelled

Both renderers placed the baseline at box_top + height/2 + descent/2. Centring
glyphs of visual height ascent+descent in a box of height H puts the baseline at
box_top + H/2 + (ascent-descent)/2; the two agree only when ascent is exactly
twice descent. DejaVu is nearer 4:1, so labels sat high against the top edge -
measured at 5px above and 11px below for a 14px button.

ButtonText also sized itself from the nominal font size, which is smaller than
the text's visual height (17px of ink for a 14px DejaVu font), leaving the
button too short to centre its label in. It now measures ascent+descent, with a
fallback for font objects that cannot report metrics.

docs/images/example_07_pressed_state.png was stale - no example writes it, the
demo emits demo_07_pressed.png at the repo root and the docs copy had been
placed by hand in November. Refreshed here; the demo should write straight to
docs/images/ so it cannot drift again.
This commit is contained in:
2026-08-06 22:59:27 +02:00
parent 202dacf350
commit c5c61a3503
6 changed files with 210 additions and 10 deletions
+53
View File
@@ -28,6 +28,7 @@ It is independent of every other spec here.
| [S11](#s11--partial-block-progress-is-discarded) | Partial-block progress is discarded | 0 |
| [S12](#s12--background-rendering) | Background rendering | 4 |
| [S13](#s13--word-spacing-and-alignment) | Word spacing and alignment | 0 |
| [S14](#s14--vertical-centring-in-buttons-and-fields) | Vertical centring in buttons and fields | 0 |
## Design invariants
@@ -1158,6 +1159,58 @@ Three defects, all visible as a right edge that wobbles from line to line.
---
## S14 — Vertical centring in buttons and fields
### Problem
`ButtonText.render` and `FormFieldText.render` both placed the text baseline at
`box_top + box_height / 2 + descent / 2`. Centring glyphs whose visual height is
`ascent + descent` inside a box of height `H` puts the baseline at
`box_top + H/2 + (ascent - descent)/2`. The two agree only when
`ascent == 2 * descent`; DejaVu is nearer 4:1, so labels rode high against the
top edge of the control.
`ButtonText` also sized itself as `font_size + padding`, but the text's visual
height exceeds the nominal size — DejaVu at 14px measures 17 — so the button was
too short to centre its own label in.
### Evidence
A 14px "Save Document" button with 6px vertical padding, measuring the label's
ink against the button rectangle:
```
gap above text: 5px
gap below text: 11px
```
### Design
- `baseline = area_top + (area_height - (ascent + descent)) / 2 + ascent` in both
renderers.
- `ButtonText._padded_height` derives from `ascent + descent`, guarded so a mock
or unusual font object falls back to the nominal size.
### Acceptance criteria
- Label ink is centred within ±2px at font sizes 10, 14 and 20.
- Label ink stays inside the button rectangle.
- Button height is at least `ascent + descent + vertical padding`.
- A form field's value is centred within its input box (±3px).
### Files
`pyWebLayout/concrete/functional.py`
### Note
`docs/images/example_07_pressed_state.png` was stale — no example regenerates it;
`07_pressed_state_demo.py` writes `demo_07_pressed.png` at the repository root
and the docs copy had been placed by hand. It has been refreshed. Worth wiring
the demo to write straight to `docs/images/` so it cannot drift again.
---
## Test plan
Findings were reproduced with four probe scripts; each becomes a regression test