Skip to contents

There are quite a few systems for font management in R. A key part of our work is trying to make graphs and tables cross platform reproducible, and consistent. This means dealing with fonts across different platforms. Some of the older parts of R are designed around the use of Adobe Type 1 fonts which are generally no longer in mainstream use. Packages like extrafont are attempting to keep this working, but the main problem is the outdated font support in the default graphics devices. Newer devices in the ragg and svglite packages provide better font support for bitmap and svg. The non default grDevices::cairo_pdf and grDevices::cairo_ps work better for pdf and postscript respectively. It is however, in our view, best to adopt an svg-first workflow and make us of the wide ecosystem of modern web delivered fonts. For pdf and postscript files exporting from the svg using rsvg or a headless chrome browser recovers a better output that the native graphics devices.

Transparently ensure a font is available

Instead of specifying a font family as a character in a call to ggplot, we can use a check_font() call which tries to find the font on your system and if not available downloads it from Google fonts or Brick and registers it with systemfonts, returning the family name. This will happen transparently.

We then recommend that ggplot rendering is done via SVG which uses systemfonts and we attempt to make the output portable for use by embedding web fonts into the svg outputs.

check_font("Helvetica")
##   Helvetica 
## "Helvetica"

Check what fonts are available

Find out what fonts ggrrr can find on this system.

# list all available fonts
tmp = fonts_available()

# Check if specific fonts are available. Returns
fonts_available(c("Roboto","Arial","Helvetica"))
## [1] "Roboto"    "Arial"     "Helvetica"

Download a font from webfont providers

Checks if a local font is available. If not downloads and installs the font and registers in the systemfonts packages. Currently supported are google and brick

# clears any registered fonts
reset_fonts()
## This wipes font databases created by `systemfonts` and `ggrrr`.
## These will be rebuilt on demand by `ggrrr`.
## N.b. This will NOT remove any custom fonts installed on your system by `ggrrr`.Are you sure? (yes/No/cancel)
# if the fonts is named the names are used for the family on this system
check_font(c("Roboto","Arial","Kings","EB Garamond"))
##        Roboto         Arial         Kings   EB Garamond 
##      "Roboto"       "Arial"       "Kings" "EB Garamond"
systemfonts::registry_fonts()
## # A tibble: 0 × 7
## # ℹ 7 variables: path <chr>, index <int>, family <chr>, style <chr>,
## #   weight <ord>, italic <lgl>, features <list>

Legacy (default graphics devices) support

If for some reason you really must use the legacy devices then check_font can try and install the fonts you want to use in grDevices. This is complex and requires converting modern fonts to Type 1 fonts, which is only possible for (locally) installed .ttf fonts. Your mileage may vary and UTF-8 support will be patchy. check_fonts will try and load the fonts but even if a font is listed in grDevices::pdfFonts() or grDevices::postscriptFonts() things are still not guaranteed to work. TLDR; don’t use the legacy devices.

check_font(c("Roboto","Arial","Kings","EB Garamond"), .legacy=TRUE)
##        Roboto         Arial         Kings   EB Garamond 
##      "Roboto"       "Arial"       "Kings" "EB Garamond"

Comparing devices

The following code can be used to test out the capabilites of the different graphics devices:

check_font(c("Roboto","Kings","EB Garamond"), .legacy=TRUE)
##        Roboto         Kings   EB Garamond 
##      "Roboto"       "Kings" "EB Garamond"
plot = ggplot2::ggplot()+
ggplot2::theme_void(base_family="Roboto")+
ggplot2::geom_point()+ggplot2::theme(margins = ggplot2::margin(14,0,14,0))+
ggplot2::annotate("label",x=0,y=0,label="Kings: Em dash: \u2014 hello world", family="Kings")+
ggplot2::annotate("text",x=0,y=1,label="Roboto: UTF-8 subscript 2: \u2082", family="Roboto")+
ggplot2::annotate("text",x=0,y=2,label="EB Garamond: UTF-8 gte: \u2265", family="EB Garamond")

if (FALSE) {

  # Does not work - "invalid font type"  & "font family 'Kings' not found in PostScript font database"
  # Native pdf device: font but no unicode support
  
  tmp = tempfile(fileext = ".pdf")
  grDevices::pdf(tmp, width=3, height = 1, units="in")
  plot
  grDevices::dev.off()
  utils::browseURL(tmp)

  # Cairo PDF: font and unicode support / Mac support limited
  tmp = tempfile(fileext = ".pdf")
  grDevices::cairo_pdf(tmp, width=3, height = 1, units="in")
  plot
  grDevices::dev.off()
  utils::browseURL(tmp)

  # Native png device: font and unicode support
  tmp = tempfile(fileext = ".png")
  grDevices::png(tmp, width=3, height = 1, units="in", res=300)
  plot
  grDevices::dev.off()
  utils::browseURL(tmp)

  # RAGG png: font and unicode support
  tmp = tempfile(fileext = ".png")
  ragg::agg_png(tmp, width=3, height = 1, units="in", res=300)
  plot
  grDevices::dev.off()
  utils::browseURL(tmp)

  # SVGLite: font and unicode support
  tmp = tempfile(fileext = ".svg")
  svglite::svglite(tmp, width=3, height = 1)
  plot
  grDevices::dev.off()
  utils::browseURL(tmp)

  
  # Native postscript:
  # Does not work - "family 'Roboto' not included in postscript() device"
  # however:  names(grDevices::postscriptFonts()) includes Roboto
  tmp = tempfile(fileext = ".eps")
  grDevices::postscript(tmp, width=3, height = 1)
  plot
  grDevices::dev.off()
  utils::browseURL(tmp)

  # font and unicode support
  # Cairo Postscript: font and unicode support / Mac support limited
  tmp = tempfile(fileext = ".eps")
  grDevices::cairo_ps(tmp, width=3, height = 1)
  plot
  grDevices::dev.off()
  utils::browseURL(tmp)

}