Skip to content

text: Use font metrics to determine line heights#31291

Merged
QuLogic merged 3 commits intomatplotlib:text-overhaulfrom
QuLogic:font-heights
Mar 20, 2026
Merged

text: Use font metrics to determine line heights#31291
QuLogic merged 3 commits intomatplotlib:text-overhaulfrom
QuLogic:font-heights

Conversation

@QuLogic
Copy link
Copy Markdown
Member

@QuLogic QuLogic commented Mar 12, 2026

PR summary

As outlined in #31220, we follow the process from the CSS Inline Layout module, specifically:

  1. The default ascent and descent come from the OS/2 font table, or failing that, the hhea table, with final fallback to the measurement we used to do.
  2. If linespacing (cf line height in CSS) is normal, then we do as before and size each line based on the maximum ascent/descent of its contents. Additionally, apply the line gap from the font metrics as half-leading around each line.
  3. If linespacing is a float, then scale it by font size of the first available font, and keep it fixed for each line.

Additionally, the first commit changes mathtext to use the x-height from the default fonts. We can read that from font metrics, but I thought I'd leave the general case to something like #31048.

Note, this is based on the text-overhaul-figures branch, so more test images than necessary are changed here. Please look at the last commit for the test images. Most test image changes are minor, maybe half a pixel or so with the text. The main ones you probably want to look at are those with multiple lines, such as 'basictext_wrap.png`.

One thing to decide is whether to apply the line gap for a single line. Previously, I don't believe we ended up doing that, because the line spacing was added before the second and subsequent lines. This PR currently always adds it, which means the text bounding box is taller in all cases. This causes such changes as the fancy bbox patches in boxarrow_test_image.png to all be larger, or e.g., every multi-artist legend to be larger. To minimize such changes, we can decide to drop the line gap for single-line text entries, if we want?

AI Disclosure

None

PR checklist

@QuLogic QuLogic added this to the v3.11.0 milestone Mar 12, 2026
@github-project-automation github-project-automation Bot moved this to Waiting for other PR in Font and text overhaul Mar 12, 2026
@QuLogic QuLogic moved this from Waiting for other PR to Ready for Review in Font and text overhaul Mar 12, 2026
@QuLogic
Copy link
Copy Markdown
Member Author

QuLogic commented Mar 12, 2026

One thing to decide is whether to apply the line gap for a single line. Previously, I don't believe we ended up doing that, because the line spacing was added before the second and subsequent lines. This PR currently always adds it, which means the text bounding box is taller in all cases. This causes such changes as the fancy bbox patches in boxarrow_test_image.png to all be larger, or e.g., every multi-artist legend to be larger. To minimize such changes, we can decide to drop the line gap for single-line text entries, if we want?

Discussed on the call today and decided not to add line gap for a single-line text. This should be closer to what we used to do, and will minimize the number of changes that occur due to this.

@QuLogic QuLogic force-pushed the font-heights branch 2 times, most recently from 0264fde to 05c7199 Compare March 14, 2026 04:13
@QuLogic
Copy link
Copy Markdown
Member Author

QuLogic commented Mar 14, 2026

I've now pushed fixes to the text_placeholders fixture, which should reduce the number of test image changes. Also fixed some tests that explicitly check values of text locations/sizes/etc.

@QuLogic
Copy link
Copy Markdown
Member Author

QuLogic commented Mar 16, 2026

There is one (set of) regression for which I have not pushed the image updates (and so why tests are failing). All the vector multi-font tests now have a reduced line height:
multi_font_type42_pdf
vs before:
multi_font_type42-expected_pdf

This is because the test uses Computer Modern as its primary font, with fallback to DejaVu Sans for the other characters (which is on purpose to test fallbacks). While the ascent/descent metrics for Computer Modern are larger than the "lp" measurement, that font does not have a nonzero line gap. Additionally, since we don't do the fully-correct thing of looking at all font metrics for a used line, while we do see the ascent/descent of DejaVu Sans, we don't know that DejaVu Sans has a larger line gap. Since we aren't applying any other line spacing, all the lines now look a bit cramped. This last thing cannot be fixed until we move to processing all text in the backend where we can inspect all glyphs at a time.

I see three options for handling these:

  1. The easiest fix here would be to set a fixed line spacing in the tests themselves.
  2. If we think this might be something that users might complain about, then we can hard code a line gap for Computer Modern only to match the !~120% line spacing that we used to do. (But keep in mind that Computer Modern is the old default font, not the current one, and this would only affect multi-line text.)
  3. Just ignore it entirely.

Separately, I did mention that I would run the constrained/tight layout tests without placeholders so that we could compare the results. I did that, and posted the results to a gist here: https://gist.github.com/QuLogic/254ee85ecaa778dd44635924d706acea

@tacaswell
Copy link
Copy Markdown
Member

Consensus on call is to ignore for now and let it be cramped for now. If we fix this later a few tests would be acceptable.

QuLogic added 3 commits March 20, 2026 15:03
This is minimally different from the `x` measurement, but technically
more correct. We still do the measurement for fonts we don't ship, but
that may change with Unicode Math fonts in the future.
This follows from CSS' default for line height. At the moment, the
behaviour has not been changed, and still just falls back to 1.2 for
'normal'.
We follow the process from [the CSS Inline Layout
module](https://www.w3.org/TR/css-inline-3/), specifically:

1. The default ascent and descent come from the `OS/2` font table, or
   failing that, the `hhea` table, with final fallback to the
   measurement we used to do.
2. If `linespacing` (cf line height in CSS) is normal, then we do as
   before and size each line based on the maximum ascent/descent of its
   contents. Additionally, apply the line gap from the font metrics as
   half-leading around each line.
3. If `linespacing` is a float, then scale it by font size of the first
   available font, and keep it fixed for each line.

However, if we are drawing a single line, then we do not add the line
gap around the line, to keep them a similar height as before.
@QuLogic
Copy link
Copy Markdown
Member Author

QuLogic commented Mar 20, 2026

OK, pushed the images to the text-overhaul-figures branch, and dropped them from here. Will merge after CI is okay.

@QuLogic
Copy link
Copy Markdown
Member Author

QuLogic commented Mar 20, 2026

So it looks like some of these tests are failing on platform/arch-specific ways. What I'll do for now is merge this one since it's holding up everything, and put in a smaller PR upping the tolerances on the ones that are broken.

@QuLogic QuLogic merged commit 1f064dc into matplotlib:text-overhaul Mar 20, 2026
23 of 33 checks passed
@github-project-automation github-project-automation Bot moved this from Ready for Review to Done in Font and text overhaul Mar 20, 2026
@QuLogic QuLogic deleted the font-heights branch March 20, 2026 19:45
@QuLogic QuLogic linked an issue Mar 27, 2026 that may be closed by this pull request
QuLogic added a commit to QuLogic/matplotlib that referenced this pull request Apr 10, 2026
This includes images changes for the following pull requests / commits:

* [Fix center of rotation with
  rotation_mode='anchor'](matplotlib#29199)
  (c44db77)
* [Remove ttconv backwards-compatibility
  code](matplotlib#30145)
  (8caff88)
* [Remove kerning_factor from
  tests](matplotlib#29816)
  (7b4d725)
* [Set text hinting to
  defaults](matplotlib#29816)
  (8255ae2)
* [Update FreeType to
  2.13.3](matplotlib#29816)
  (89c054d)
* [Implement text shaping with
  libraqm](matplotlib#30000)
  (b0ded3a,
  9813523)
* [Add language parameter to Text
  objects](matplotlib#29794)
  (7ce8eae)
* [Fix auto-sized glyphs with BaKoMa
  fonts](matplotlib#29936)
  (3ba2c13)
* [pdf: Improve text with characters outside embedded font
  limits](matplotlib#30512)
  (b70fb88,
  6cedcf7)
* [Prepare `CharacterTracker` for advanced font
  features](matplotlib#30608)
  (8274e17,
  70dc388,
  df670cf,
  ed5e074)
* [Add font feature API to
  Text](matplotlib#29695)
  (972a688)
* [Fix spacing in r"$\max
  f$"](matplotlib#30715)
  (4a99a83)
* [Implement libraqm for vector
  outputs](matplotlib#30607)
  (bd17cd4)
* [Drop the FT2Font intermediate
  buffer](matplotlib#30059)
  (9d7d7b4)
* [Rasterize dvi files without
  dvipng](matplotlib#30039)
  (7627118)
* [Update bundled FreeType and HarfBuzz
  libraries](matplotlib#30938)
  (a161658,
  9619bcc)
* [Fix positioning of wide mathtext
  accents](matplotlib#31069)
  (c2fa7ba)
* [Refactor RendererAgg.draw_{mathtext,text,tex} to use same base
  algorithm](matplotlib#31085)
  (931bcf3)
* [Implement TeX's fraction and script
  alignment](matplotlib#31046)
  (94ff452,
  4bfa0f9,
  1cd8510)
* [Fix confusion between text height and ascent in metrics
  calculations](matplotlib#31107)
  (60f2310)
* [mathtext: Fetch quad width & axis height from font
  metrics](matplotlib#31110)
  (692df3f,
  383028b)
* [mathtext: add mathnormal and distinguish between normal and italic
  family](matplotlib#31121)
  (a6913f3)
* [ENH: Ignore empty text for
  tightbbox](matplotlib#31285)
  (d772043)
* [Drop axis_artist tickdir image compat, due to text-overhaul
  merge](matplotlib#31281)
  (2057583)
* [text: Use font metrics to determine line
  heights](matplotlib#31291)
  (3ab6a27,
  d961462,
  97f4943)
* [ps/pdf: Override font height metrics to support AFM
  files](matplotlib#31371)
  (e0913d4)
* [TST: Cleanup back-compat code in tests touched by text
  overhaul](matplotlib#31295)
  (7c33379)
* [TST: Set tests touched by text overhaul to mpl20
  style](matplotlib#31300)
  (41c4d8d)
@QuLogic QuLogic mentioned this pull request Apr 10, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Should we use font metrics for line height instead of "lp"?

3 participants