Changing line heights in Android using LineHeightSpan has this ugly side-effect where the cursor ends up being very tall, extending till the full line height instead of just the text height.
I've decided to go to extreme lengths to fix this over the next few days. For science. pic.twitter.com/2tWLvDq1rz
— Saket Narayan (@Saketme) November 24, 2019
A few hours after posting the above tweet, I noticed something was off in the screenshot. The italic “Late Show” text wasn’t really italic — it was fake italic!
If you’re unaware of what that means, here’s a primer: When Android fails to find a true bold or italic font, it compensates by stretching the glyphs of the upright font for fake bold and slanting them for fake italic. This behavior is consistent with most other platforms and web browsers. Here’s a comparison of my screenshot vs what it was supposed to look like:
Notice how the true italic glyphs of Work Sans look optically correct. The difference in bold is glaring:
Type designers create italic and bold fonts to introduce differentiation in text. Fake glyphs reduce that differentiation, and in doing that they defeat the purpose of their designers. Worse, they also hinder legibility by slanting and smearing letters in a way they weren’t designed to be. If you have friends who are designers, they will tell you that fake fonts are a typographical sin.
The fake glyphs for Work Sans used in my screenshots aren’t that bad, but here are some examples that are worse:
As Nyla Smith explains, each character in a true italic or bold is designed meticulously, normally with calligraphic or cursive flourishes, with optical corrections, and even changing the letterform altogether.
So how do you avoid fake glyphs?
The screenshots in my tweet were taken from my markdown editor app Press that highlights markdown using spans. Emphasized text is highlighted using StyleSpan(Typeface.ITALIC)
. The code can be imagined somewhat along the lines of,
textView.typeface = resources.getFont(R.font.work_sans_regular)
textView.text = SpannableStringBuilder("Appearing as a guest on David Letterman's")
.italic { append(" *Late Show* ") } // from android-ktx
.append("in 2010")
...
At the time of rendering, StyleSpan
tries to find an italic font from the font family applied on the TextView. If the font is unavailable, it fakes italic by skewing the glyphs using textPaint.textSkewX = -0.25f
. In the above example, Android doesn’t find an italic font because the TextView
is only given the regular font. The fix is to apply the full font family instead of just one font:
// Incorrect
textView.typeface = resources.getFont(R.font.work_sans_regular)
// Correct
val fontFamily = ResourcesCompat.getFont(context, R.font.work_sans)
textView.typeface = if (Build.VERSION.SDK_INT >= 28) {
Typeface.create(fontFamily, weight = 400, isItalic = true)
} else {
// Star this: https://issuetracker.google.com/issues/145311058
Typeface.create(fontFamily, styleInt = Typeface.BOLD_ITALIC)
}
res/font/work_sans.xml
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
<font
app:font="@font/work_sans_regular"
app:fontStyle="normal"
app:fontWeight="400" />
<font
app:font="@font/work_sans_italic"
app:fontStyle="italic"
app:fontWeight="400" />
<font
app:font="@font/work_sans_bold"
app:fontStyle="normal"
app:fontWeight="700" />
</font-family>
When using Xml,
// Incorrect
<TextView
...
android:fontFamily="@font/work_sans_bold_italic"
/>
// Incorrect
<TextView
...
android:fontFamily="@font/work_sans_regular"
android:textStyle="bold|italic"
/>
// Correct
<TextView
...
android:fontFamily="@font/work_sans"
android:textStyle="bold|italic"
/>
// If you're reading this in 2030 and minSdkVersion=28 has arrived,
// then textFontWeight can be used if your font has multiple strong
// variants like Medium (500), Bold (700), Black (900), etc.
<TextView
android:fontFamily="@font/work_sans"
android:textFontWeight="700"
android:textStyle="italic"
/>
If you’re using a library that uses custom spans for formatting text, make sure that they’re not taking a shortcut by rendering fake styling.
Here’s an exercise for you: if you’re using a custom font in your IDE, does it have a true italic variant?