---
title: "Font recipes"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Font recipes}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

## Measurement vs rendering

Using fonts with 'munch' involves two independent steps:

1. **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.

2. **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")`.

## Which devices agree with measurement?

| 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 |

### systemfonts devices (recommended)

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:

```{r eval=FALSE}
library(gdtools)
font_set_liberation()
```

For system-aware detection (Arial, Helvetica, etc. with Liberation as
fallback), use `font_set_auto()`:

```{r eval=FALSE}
font_set_auto()
```

For specific fonts (e.g. a Google Font), use `font_set()`:

```{r eval=FALSE}
library(munch)
library(ggplot2)

fonts <- font_set(sans = font_google("Open Sans"))

ggplot(mtcars, aes(mpg, wt)) +
  geom_point() +
  labs(title = "**Open Sans** via *font_set()*") +
  theme_minimal(base_family = fonts$sans) +
  theme(plot.title = element_md())
```

### Cairo devices

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:

- **System fonts** (e.g. Arial, Helvetica), measurement and rendering
  agree. Everything works.
- **Google Fonts** via `register_gfont()`, found by measurement but
  not necessarily by fontconfig, depending on where the cache is.
- **Liberation fonts** via `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:

```{r}
library(gdtools)

# Found by measurement (systemfonts + registry)?
font_family_exists("sans")

# Installed at the system level?
font_family_exists("sans", system_only = 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.

### Bitmap devices `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.

### The `pdf()` device and extrafont

The 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.

#### Example: exporting a munch plot to PDF

```r
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:

1. **Install fonts at the system level**, both "Arial" and "Courier New"
   must be installed as system fonts.
2. **Import fonts once** with `extrafont::font_import()`, this scans
   system fonts and creates metric files for `pdf()`. Only needed once.
3. **Load fonts each session** with `extrafont::loadfonts()`, this
   registers the imported fonts with `pdf()` for the current session.
4. **Disable the device check** with
   `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'. Use `pdf()` +
> 'extrafont' only when `cairo_pdf()` is not available.

## Font sources at a glance

| 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 |

## Diagnosing font problems

When text looks wrong (wrong font, misaligned labels), the cause is
almost always a measurement/rendering disagreement. Here is a quick
checklist:

1. **Is the font found by measurement?**
   ```r
   gdtools::font_family_exists("My Font")
   ```
   If `FALSE`: the font is not registered with 'systemfonts'. Register it
   with `register_gfont()`, `font_set()`, or `systemfonts::register_font()`.

2. **Is the font found by the device?**
   - For Cairo devices, `png()` and `jpeg()`:
     ```r
     gdtools::font_family_exists("My Font", system_only = TRUE)
     ```
     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).

3. **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:
   ```r
   # What systemfonts resolves
   systemfonts::match_fonts("My Font")

   # What's installed at the system level
   subset(systemfonts::system_fonts(), family == "My Font")
   ```
