mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
6ea4119a3c
This commit ensures that any given paragraph of text shaped by Gio will use a single internal line height. This line height is determined (by default) by the text size, rather than the fonts involved. This is a breaking change, as previously we would blindly use the largest line height of any font in a line for that line, leading to lines within the same paragraph with extremely uneven spacing. This commit also updates some test expectations in package widget. I thought pretty hard about how to implement line spacing, and consulted a few sources: [0] https://www.figma.com/blog/line-height-changes/ [1] https://practicaltypography.com/line-spacing.html [2] https://developer.mozilla.org/en-US/docs/Web/CSS/line-height There is no single, universal way to think about line spacing. Fonts internally specify a line height as the sum of their ascent, descent, and gap, but the line height of two fonts at the same pixel size (say 20 Sp) can vary wildy (especially across writing systems). There are two strategies we could pursue to establish the line height of a paragraph of text: - derive the line height from the fonts involved (our old behavior, and the behavior of many word processors) - derive the line height from the requested text size provided by the user (the behavior of the web). The challenge with the first option is that for a given piece of text in the UI, there can be a silly number of fonts involved. If a label dispays user-generated content, the user can put an emoji in it, and emoji fonts have different line heights from latin ones. This can cause unexpected and nasty layout shift. Gio would previously do exactly this, on a line-by-line basis, resulting in unevenly spaced lines within a paragraph depending on which fonts were used on which lines. Choosing one of the fonts and enforcing its line height would make things consistent, but it isn't clear how to choose that canonical font. There is no 1:1 mapping between the input text.Font provided in the shaping parameters and a single font.Face. Instead, that mapping depends upon the runes being shaped. I think the only sane way to implement the first option would be to synthesize some text in the provided system.Locale (mapping the language to a script and then generating a rune from that script), shape that single rune, and then enforce the line height of the resulting face on the entire paragraph. This would require doing a fair bit more work per paragraph than Gio does today, so I've opted not to do it. Instead, the second option allows us to choose a line height based on the size of the text that the user wants to display. While this can potentially interact poorly with unusually tall fonts, it means that text will always have a consistent line height. I've provided two knobs to control line height: - text.Parameters.LineHeight lets you set a specific height in pixels with a default value of text.Parameters.PxPerEm. - text.Parameters.LineHeightScale applies a scaling factor to the LineHeight, allowing you to easily space out text without hard-coding a specific pixel size. The default value here (drawn from the recommendations of [1]) is 1.2, which looks pretty good across many fonts. I've chosen this two-value API because many users will want to set one or the other value. I considered instead a single value field and a "mode" that would specify how it was used, but that felt uglier. Also, you *can* set both of these two fields and get predictable results. I'd like to revisit using the line height of the chosen fonts in the future, but it seems a little too complex to be worthwhile right now. An interesting option would be making the select-a-face-using-locale strategy described above an opt-in feature, though some users might instead want to just use the tallest line height among fonts in use. Something like this Android API might be appropriate: [3] https://learn.microsoft.com/en-us/dotnet/api/android.widget.textview.fallbacklinespacing?view=xamarin-android-sdk-13 I'd like to thank Dominik Honnef for some good discussion around this feature, and for pointing me to some good sources on the subject. Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>