Using fonts with ‘munch’ involves two independent steps:
Measurement, ‘munch’ calls
gdtools::strings_sizes() to compute text metrics (width,
ascent, descent). Under the hood, ‘systemfonts’ locates the font file,
then the metrics are read from this file with FreeType. Any font
registered via systemfonts::register_font(),
gdtools::register_gfont(),
gdtools::font_set(),
gdtools::font_set_liberation() or
gdtools::font_set_auto() is found at measurement
time.
Rendering, the graphic device draws the text. Each device has its own way of resolving font names. This is where mismatches happen: a font that measurement found may be invisible to the device, or vice versa.
When both steps find the same font, the output is correct. When they disagree, text is positioned using one font’s metrics but drawn with another, leading to overlapping, clipping or misaligned labels.
Even with the same font, measurement does not apply kerning while
devices do: see the section “Alignment accuracy” of
vignette("using-ggplot2").
| Device family | Font lookup | Agrees with measurement? |
|---|---|---|
| ragg, svglite, ggiraph | ‘systemfonts’ | Always, same lookup on both sides |
cairo_pdf(), png(type = "cairo") |
fontconfig | Only for system-installed fonts |
pdf() |
Own engine | No guarantee, needs ‘extrafont’ |
png(), jpeg() (default type) |
Depends on the system | Only for system-installed fonts |
Devices from ragg, svglite and ggiraph use ‘systemfonts’ for both measurement and rendering. Every registered font is visible on both sides.
With svglite and ggiraph, the SVG file names the fonts, and the text
is finally drawn by the browser or the viewer, which needs these fonts
too. ‘ggiraph’ can embed them with HTML dependencies (for example
liberationsansHtmlDependency()), ‘svglite’ with its
argument web_fonts.
The simplest approach is font_set_liberation(), which
registers Liberation Sans, Serif and Mono in a single call, no internet
needed, SIL Open Font License:
For system-aware detection (Arial, Helvetica, etc. with Liberation as
fallback), use font_set_auto():
For specific fonts (e.g. a Google Font), use
font_set():
With Cairo devices (cairo_pdf(),
png(type = "cairo")), font lookup differs between the two
steps: measurement goes through ‘systemfonts’, rendering goes through
fontconfig. Fontconfig only scans system-installed
fonts.
This means:
register_gfont(),
found by measurement but not necessarily by fontconfig, depending on
where the cache is.font_set_liberation() (or individual
register_liberation*()), found by measurement only.
Rendering falls back silently unless the fonts are also installed at the
OS level.Use font_family_exists(system_only = TRUE) to check
whether a font is installed at the system level:
library(gdtools)
# Found by measurement (systemfonts + registry)?
font_family_exists("sans")
#> [1] TRUE
# Installed at the system level?
font_family_exists("sans", system_only = TRUE)
#> [1] TRUE‘munch’ recognizes cairo_pdf(), but not
png(type = "cairo"): its device name, "png",
is the same as with other types. Set
options(munch.skip_device_check = TRUE) to avoid the
compatibility warning with this device.
png() and jpeg()With their default type, png() and jpeg()
use the graphics engine of the system: Cairo on Linux (see above),
Quartz on macOS, GDI on Windows. They find fonts installed at the system
level, not fonts registered with ‘systemfonts’ only. Prefer
ragg::agg_png() and ragg::agg_jpeg(), which
are drop-in replacements.
pdf() device and extrafontThe standard pdf() device uses its own font engine and
ignores ‘systemfonts’ entirely.
‘munch’ uses ‘gdtools’ (which reads the font files found by
‘systemfonts’) for text measurement. For the output to be correct, the
fonts used by ‘munch’ must also be known to pdf().
The ‘extrafont’ package bridges this gap: it imports TrueType fonts
into R’s PDF device by generating font metric files that
pdf() can use.
The key requirement: every font used in the plot
must be available to both ‘systemfonts’ (for measurement) and
pdf() (for rendering). If a font is registered with
‘systemfonts’ but not with pdf(), the text metrics will not
match the rendering and the output will have positioning errors.
library(gdtools)
library(munch)
library(ggplot2)
df <- data.frame(
x = 1:3,
y = 1:3,
label = c("**Bold**", "*Italic*", "`Code`")
)
p <- ggplot(df, aes(x, y, label = label)) +
# the theme font does not apply to geoms: set it in the geom too
geom_label_md(family = "Arial", code_font_family = "Courier New") +
theme_minimal(base_family = "Arial")
# Make Arial and Courier New available to pdf()
library(extrafont)
# font_import()
# Run font_import() once to scan system fonts,
# then loadfonts() in each session.
loadfonts()
# Suppress the device warning since we ensured font concordance
options(munch.skip_device_check = TRUE)
pdf(file = "munch-example.pdf", width = 7, height = 7)
print(p)
dev.off()The steps are:
extrafont::font_import(), this scans system fonts and
creates metric files for pdf(). Only needed once.extrafont::loadfonts(), this registers the imported fonts
with pdf() for the current session.options(munch.skip_device_check = TRUE), since you have
ensured font concordance manually, the warning is no longer needed.Note:
cairo_pdf()is a simpler alternative. It finds system-installed fonts with fontconfig, so they work without ‘extrafont’. Usepdf()+ ‘extrafont’ only whencairo_pdf()is not available.
| Font source | Measurement | ragg / svglite / ggiraph | Cairo devices, png(), jpeg() |
pdf() |
|---|---|---|---|---|
| System font | Yes | Yes | Yes | Via extrafont |
register_gfont() |
Yes | Yes | Depends on cache location | No |
font_set_liberation() |
Yes | Yes | No (unless OS-installed) | No |
font_set_auto() |
Yes | Yes | System fonts only | No |
font_set() |
Yes | Yes | System fonts only | No |
When text looks wrong (wrong font, misaligned labels), the cause is almost always a measurement/rendering disagreement. Here is a quick checklist:
Is the font found by measurement?
If FALSE: the font is not registered with ‘systemfonts’.
Register it with register_gfont(), font_set(),
or systemfonts::register_font().
Is the font found by the device?
For Cairo devices, png() and
jpeg():
If FALSE: the font is not system-installed. Either
install it at the OS level or switch to a ‘systemfonts’ device (ragg,
svglite).
For pdf(): check that
extrafont::fonts() includes the family.
For ragg/svglite/ggiraph: if step 1 passed, this always works. With svglite and ggiraph, the browser that displays the SVG also needs the font (see above).
Do both sides find the same font? Even when both find a font, it may not be the same file (e.g. a different weight or a substitution). Compare: