Back to all insights

Article • Reading 3 mins

Let’s have some fun with Fonts: part 1

A font is a particular size, weight and style of a typeface. Each font is a matched set of type, with a piece (a “sort”) for each glyph.

Close-up of large orange letterforms with glowing teal outlines, evoking abstract typography.

This article is the first part of the series that will introduce the topic of fonts to you.

Do you remember Fun with Flags in The Big Bang Theory? Let’s have some Fun with Fonts.

Sitcom scene of two men holding up a flag in front of a whiteboard reading 'FUN WITH FLAGS.'

Jim Parsons and Will Wheaton in The Big Bang Theory || www.filmweb.pl

ABC

First of all, to avoid confusion, let’s define what is what. According to wiki:

  • A typeface is the design of lettering that can include variations in size, weight (e.g. bold), slope (e.g. italic), width (e.g. condensed), and so on. Each of these variations of the typeface is a font.
  • In metal typesetting, a font is a typeface’s particular size, weight, and style. Each font is a matched set of types, with a piece (a “sort”) for each glyph. A typeface consists of a range of such fonts that share an overall design.
    So, typeface describes how the letters should look and font is particular (exact size, weight, etc) implementation of a given typeface.
  • Font Family is a group of fonts that implements the same typeface with different variations.

Show me the code

A single font family can exponentially have many fonts and scales by adding more weights or slopes. This approach has a few flaws in development. When adding a typeface to our project, we need to include about 12 fonts, which also increases the app size.

private val fonts = listOf( 
    Font(R.font.roboto_thin, weight = FontWeight.Thin), 
    Font(R.font.roboto_thin_italic, weight = FontWeight.Thin, style = FontStyle.Italic), 
    Font(R.font.roboto_light, weight = FontWeight.Light), 
    Font(R.font.roboto_light_italic, weight = FontWeight.Light, style = FontStyle.Italic), 
    Font(R.font.roboto_regular), 
    Font(R.font.roboto_italic, style = FontStyle.Italic), 
    Font(R.font.roboto_medium, weight = FontWeight.Medium), 
    Font(R.font.roboto_medium_italic, weight = FontWeight.Medium, style = FontStyle.Italic), 
    Font(R.font.roboto_bold, weight = FontWeight.Bold), 
    Font(R.font.roboto_bold_italic, weight = FontWeight.Bold, style = FontStyle.Italic), 
    Font(R.font.roboto_black, weight = FontWeight.Black), 
    Font(R.font.roboto_black_italic, weight = FontWeight.Black, style = FontStyle.Italic), 
)

We need to assign each font the proper weight and style. Let’s render it.

@Composable 
fun AllFonts() = Column { 
    fonts.forEach { font -> 
        Text( 
            text = demoText, 
            fontFamily = FontFamily(font), 
        ) 
    } 
}
Grid of the same sentence rendered in every weight and style of a font family, from light to bold, upright and italic.

We have all fonts required for possible design, but this is not the way how you use Font Family is supposed to be used, let’s achieve this again.

private val robotoFontFamily = FontFamily(fonts) 
 

@Composable 
fun FontFamily() = Column { 
    val weights = listOf( 
        FontWeight.Thin, 
        FontWeight.Light, 
        FontWeight.Normal, 
        FontWeight.Medium, 
        FontWeight.Bold, 
        FontWeight.Black, 
    ) 
    weights.forEach { weight -> 
        Text( 
            text = demoText, 
            fontFamily = robotoFontFamily, 
            fontWeight = weight, 
        ) 
        Text( 
            text = demoText, 
            fontFamily = robotoFontFamily, 
            fontWeight = weight, 
            fontStyle = FontStyle.Italic, 
        ) 
    } 
}
Sample sentence repeated across a font family's full range of weights and italic styles.

To my eyes, it looks the same. Now we might start to think if we really need italic fonts. Let’s check what would happen if we use an italic style without having italic font.

@Composable 
fun NotEverythingItalicIsItalic() = Column { 
    val fontsNormal = fonts.filter { it.style == FontStyle.Normal } 
    val fontsItalic = fonts.filter { it.style == FontStyle.Italic } 
    fontsNormal.forEach { font -> 
        Text( 
            text = demoText, 
            fontFamily = FontFamily(font), 
            fontStyle = FontStyle.Italic, 
        ) 
    } 
    fontsItalic.forEach { font -> 
        Text( 
            text = demoText, 
            fontFamily = FontFamily(font), 
        ) 
    } 
}
Grid of the same sentence in increasing weights, split into upright and italic blocks, contrasting true italics with a slanted style.

Again we see that text is actually slanted. So, it worked, italic fonts are not needed? Well, kind of, it depends on how lenient your designer is. If we look closely, we will see that the angle is different in the font slanted by Android Framework (HarfBuzz library most likely) than the original font.

@Composable 
fun NotEverythingBoldIsBold() = Column { 
    val regularFont = fonts.first { it.weight == FontWeight.Normal } 
    val boldFont = fonts.first { it.weight == FontWeight.Bold } 
    Text( 
        text = "$demoText regular", 
        fontFamily = FontFamily(regularFont), 
    ) 
    Text( 
        text = "$demoText regular bolded", 
        fontFamily = FontFamily(regularFont), 
        fontWeight = FontWeight.Bold, 
    ) 
    Text( 
        text = "$demoText bold", 
        fontFamily = FontFamily(boldFont), 
    ) 
}
Three lines of the same sentence: regular weight, a synthetically bolded version, and a true bold weight, comparing fake and real bold.

The same thing happened for bolding text. We can see that framework tried to make the font bolded, but it is not exactly what we would get using the correct font.

That’s it

That was easy. Now you know how to correctly define fonts and use them in the app. Next, we will check how we can make this process less cumbersome.

Do you want to know more? Check the second part of the article.

You may also like