Fixing Font Padding in Compose Text
TL;DR
Update June 2023: Compose ui 1.6.0-alpha01 is the first version with includeFontPadding
switched to false by default.
- Use
PlatformTextStyle
API fromCompose 1.2.0
to configureincludeFontPadding
. For few compose versions it was deprecated on purpose to indicate that it is a compatibility API, but has been undeprecated in compose 1.5 to encourage developers to use it. - Test your UI using
includeFontPadding
set tofalse
and make any necessary adjustments if required. - Use
LineHeightStyle
API to match your designs easier.
Creating Jetpack Compose, our new UI toolkit, from the ground up gave us the chance to re-evaluate choices made in the past. In this blog post, we’ll dive into the details of text alignment in both the View system and in Compose. You’ll learn more about the changes we’re making to font padding which will make text rendering more fault-proof and help you implement designs easier from common designer tools like Figma or Sketch.
While migrating to the new APIs has plenty of benefits it could also mean slight design inconsistencies, and/or breaking changes in your automated screenshot testing. But not to worry! We will show you how you can start using these APIs and begin testing your text today, migrating at the pace that’s most convenient for your app.
Font padding in the View system
includeFontPadding
is a TextView
parameter that effectively controls the line height of the first and last line in a text. Typically, turning it on will make the first and last line taller by adding vertical padding.
The default value is true
and you can override it as follows:
If we take a look at the following example:
When includeFontPadding
is false, the line height of the first and last line is equal to (descent — ascent).
When includeFontPadding
is true is where it gets interesting:
- Line height of the first line is equal to (descent — top).
- Line height of the last line is equal to (bottom — ascent).
- If there is only 1 line, the line height is equal to (bottom — top).
- For inner lines, their line height is always equal to (descent — ascent).
Top, ascent, and descent are examples of font metrics. These metrics provide information about a font that allows the system to evenly space and uniformly align lines of text. They are constant per font, and you can retrieve them by using the FontMetrics
API or a tool like ttx.
Setting includeFontPadding
to true or false can impact how text is aligned in its parent container, and lead to subtle differences in alignment.
It might sound like having includeFontPadding
configured as true by default might be the opposite of what you’d want. If you need to follow specific design guidelines, having extra padding in your text could make this harder to achieve. The reasoning behind including this padding by default is historical.
Background
Roboto is the standard typeface and the default font (or first font in the system font fallback) on the Android platform.
To calculate the maximum amount of space available to draw a font, the system, up until Android 27, made these calculations using Roboto’s font metrics. It decided on the amount of available space and considered this space the “safe space to draw” the text.
This solution did not work for all fonts, as fonts with designs or “scripts” that are typically taller than Latin text were “clipped”. Clipping means that part of the glyphs (or characters) would not be entirely visible on the available draw space. Throughout this article we’ll use Burmese as an example of a tall script. A sample text in Burmese is ၇ဤဩဦနိ.
This is where includeFontPadding
was introduced, to prevent tall scripts from getting clipped, allowing you to add a top and bottom padding to the text.
We can see the behavior in this example:
So, if you’ve ever set includeFontPadding
to false, and your app supports Burmese language, it might face font clipping issues depending on your Android version.
Setting includeFontPadding to false
There are some valid use cases for setting includeFontPadding
to false, for example, to solve vertical alignment issues or to comply with specific design specs.
Let’s see two examples:
When centering text in a parent widget, for example a button, text can be expected to be centered vertically.
The horizontal bar in H is expected to be vertically centered. However, when includeFontPadding
is true, this alignment would depend on the font and its font metrics. Let’s see what happens with a font that has more padding at the top than at the bottom:
In this example, includeFontPadding
is false on the left and true on the right. Notice the vertical alignment is slightly off on the right due to the uneven font padding. Depending on the font and the text used (for instance lowercase Latin text would naturally sit closer to the lower part of the draw space), this difference could be more obvious.
Another example is where text has to be aligned to the top or bottom of another view, for example an image. Because of the extra padding added by includeFontPadding
, the text and image are not aligned. Let’s take a look at the desired design:
Usually to achieve the above result you would use a combination of setting includeFontPadding
to false plus hardcoding some padding.
Fixes for the View system
The TextView
widget is built on top of two foundational Java classes, StaticLayout
and BoringLayout
, and delegates into one or the other depending on the nature of the text to display on the screen.
Starting with devices running Android 28 and above, we added useLineSpacingFromFallbacks
functionality to StaticLayout
, so the ascent/descent of the text will be adjusted based on the used font instead of Roboto’s font metrics. When set to true, this configuration fixes the font clipping issue even if includeFontPadding
is false. And it prevents consecutive lines running into each other which includeFontPadding
wasn’t solving.
However this feature wasn’t added to BoringLayout
(text on a single line, left-to-right characters) at that time. For this scenario, there could still be clipping. Support for this has been added recently, and will come out with Android T.
From API 33,
includeFontPadding
becomes redundant forTextView
for the original purpose it was designed, because clipping issues will be automatically handled. Until your minSdk is 33, you still need to setincludeFontPadding
true in the View system to prevent font clipping for tall scripts.
includeFontPadding
is used in combination with fallbackLineSpacing
but also elegantTextHeight
. The combination of these two parameters results in complex behaviors.
To add to the flexibility (but also to the confusion), TextView
also allows you to configure firstBaselineToTopHeight
and lastBaselineToBottomHeight
which helps define additional padding in the top and bottom text line respectively. These attributes are only applied in API level 28 and higher.
Multiple parameters allow you to modify font padding in a
TextView
, they can interact in complex ways, and behavior might vary between API levels. Always make sure to test your View-based UI against various API levels.
Implementation in Compose
When building the initial Text
implementation in Compose, the behavior was aligned with TextView
’s behavior, adding font padding by default in Compose’s TextLayout
.
However, it didn’t expose a parameter to turn this padding on and off.
The community was very quick to point this out, by creating a bug related to View parity which earned a lot of attention. The request was to allow turning includeFontPadding
on and off when using the Text
composable function, like in the View system.
While thinking how to implement this toggle feature in Compose, we analyzed how includeFontPadding
is used and the main use cases it was solving. We knew that text should just render as expected and that includeFontPadding
is a very Android specific configuration and should not be on the common API. It is a confusing parameter overall and very error prone (it exposes an API that is hard to understand, hard to explain, and works only for a subset of specific cases).
We had the following goals:
- Removing unnecessary paddings, giving the control back to you to implement the paddings you really need.
- Automatically preventing clipping issues for tall scripts, italic fonts, etc.
So we released changes that included:
- Creating a new API
PlatformTextStyle
to allow to switchincludeFontPadding
on and off (default value is true). - To fix all tall fonts clipping issues that could happen from turning
includeFontPadding
off in Compose, we apply additional padding when required for only the first and last line, and the max line height is calculated based on the font(s) that is used for all the text included in a given line (instead of theFontMetrics
of the first font in the font fallback). - We’ve applied a solution that works for all versions of Android until API 21 (the min SDK level required to use Jetpack Compose libraries).
If you’re interested in taking a closer look at the implementation of our solution, check out the following change lists:
first change, adding additional padding automatically
with this change we added temporary compatibility configuration forincludeFontPadding
in TextStyle/ParagraphStyle
follow up change, keepingincludeFontPadding
true by default in Compose
PlatformTextStyle API
As part of Compose 1.2.0-alpha07, we exposed an API in TextStyle
/PlatformTextStyle
that allows you to toggle includeFontPadding
on and off. The default value is true, meaning font padding is added.
The API is marked as experimental/deprecated and is only for compatibility purposes. As you configure your texts with includeFontPadding
switched to false and adjust your layouts if required, we intend to change includeFontPadding
default value to false in upcoming releases of Compose, and eventually remove the compatibility API.
You can configure this in each Text
in particular, so the migration is progressive:
Or more generically on your typography:
As these APIs are experimental, you’ll need to annotate the usages with ExperimentalTextApi
annotation.
Alternatively, if you’re building with Gradle
, you can add the following exception to the kotlinOptions
in your app’s build.gradle
file:
kotlinOptions {
...
freeCompilerArgs += ["-Xuse-experimental=androidx.compose.ui.text.ExperimentalTextApi"]
}
LineHeightStyle API
As we keep evaluating the impact of our changes, we listened to community’s feedback and developed an API to help you match your designs easier for text in your Compose app.
In 2019 Figma, a designer tool, announced that they made changes to how they measure and apply line height. In short, Figma distributed the line height just like the web in general does: 50/50 on top and bottom. Many designers and developers have been chatting to us about this blog post since.
In March 2022, a developer posted an article on how to have Figma behavior in Compose, as it doesn’t work out of the box.
LineHeight
is not a new concept for Compose Text
, it can be defined as the distance between baselines of text:
Which leaves the first and last line of a text in a special scenario because they don’t have a baseline above or below respectively. So for each line to have the same line height, some extra space needs to be added and distributed with a given criteria in each line. This criteria is configurable through the new API LineHeightStyle
.
LineHeightStyle
controls how to distribute the line height in each line of text and whether line height is applied to the top of the first line and to the bottom of the last line. It defines the alignment of lines in the space provided by TextStyle(lineHeight)
and offers several options to be able to modify your text’s behavior within the available draw space.
You can configure LineHeightStyle
as follows:
Note: We recommend defining TextStyle
by using the LocalTextStyle
merger, because failing to do will make you lose your Material theme’s styles, e.g. if you add this Text as the content for a Button. You can check the discussion around this in this feature request.
Alignment
LineHeightStyle.Alignment
defines how to align the text in the space provided by the line height.
LineHeightStyle.Alignment.Proportional
it’s the default behaviour, distributes the line height as specified by the font metrics.LineHeightStyle.Alignment.Center
adds the same amount of space at the top and bottom of each line of text, placing the text in the center.
LineHeightStyle.Alignment.Top
adds the space at the bottom of each line of text, pushing the text to the top.LineHeightStyle.Alignment.Bottom
adds the space at the top of each line of text, pushing the text to the bottom.
Trim
On the other hand, LineHeightStyle.Trim
options will allow you to remove or keep extra padding from the top of the first line and the bottom of the last line. These are the available trim configuration, configured together with alignment center:
LineHeightStyle.Trim.FirstLineTop
trims the space that would be added to the top of the first line as a result of the line height.LineHeightStyle.Trim.LastLineBottom
trims the space that would be added to the bottom of the last line as a result of the line height.
LineHeightStyle.Trim.None
doesn’t remove space.LineHeightStyle.Trim.Both
trims the space that would be added to the top of the first line and bottom of the last line as a result of the line height.
Note: The configuration is applied only when a line height is defined on the text.trim
feature is available only when PlatformParagraphStyle.includeFontPadding
is false.
includeFontPadding
together withlineHeight
andLineHeightStyle
(alignment and trim configurations) result in multiple combinations that allow you to better align and style yourText
in any given font. Make sure you check out the documentation for the relevant classes we introduced above, to explore the options that help you best match your designs.
Conclusion
Compose 1.6.0 alpha01 is the first compose version with includeFontPadding
being false by default. This change is coming to stable at the end of 1.6.0 dev cycle, when we release the stable version.
You can switch the value using PlatformTextStyle
. Start testing your Text
today on stable setting includeFontPadding
false!
PlatformTextStyle
compatibility API (which allows you to disable includeFontPadding
) will remain available in future releases. You can start using it to test your screens and make all necessary adjustments if they are required.
Additionally we’ve added the LineHeightStyle
API with multiple options, another highly requested feature, that will help you match your designs easier.
If you experience issues/bugs while turning includeFontPadding
off, please let us know and file a bug on our issue tracker.
Any further questions? Drop a comment below or reach out to me on Twitter, we’d love to hear from you!
Happy composing! 👋
This post was written with the collaboration of Seigo Nonaka, Sean McQuillan, Siyamed Sinir and Anastasia Soboleva on the Jetpack Compose Text team. Special thanks to Jolanda Verhoef and Florina Muntenescu on the DevRel team for their ideas and review.