Как рендерить R Markdown в PDF на кириллице

Так получилось, что за всё время, что я использую в работе R, мне не доводилось рендерить markdown-файлы в формат PDF. Иногда я хранил наработки просто в фалах .R, а исследования оформлял в Google Docs, накидывая туда скринов из viewer-а. Иногда это был рендер в .html, с интерактивной графикой, или проекты Shiny в облаке Posit. Да мало ли вариантов.

Но в связи с некоторыми изменениями в форматах хранения аналитических артефактов, судьба привела меня к PDF.

«Дело-то не хитрое, просто меняем формат вывода в markdown» — подумал я. Однако всё оказалось не так просто: и тексты и ggplot не видят кириллицу, движок xelatex не находит кастомные шрифты, LaTeX вообще бесится на всё.

Каждая проблема отдельно худо-бедно гуглится или решается через chatGPT, но в интернетах намного больше информации как посадить markdown-PDF на китайский, чем на кириллицу.

Рассмотрим на примере классического markdown, а в конце реализация для Quarto.

В общем, по порядку.

В качестве IDE я использую RStudio.

Для рендера в PDF, в первую очередь нужно поставить LaTeX, я не изучал там нюансы движков, а выбрал наиболее цитируемый TinyTex. Устанавливается через CRAN или через Git, кому как нравится:

install.packages('tinytex')

tinytex::install_tinytex() 
#после этой команды нужно полностью перезапустить ваш R-клиент

За основу возьмём вот такой файл .Rmd, с базовым набором элементов: заголовок, текст, код и график.

---
title: "Cyrillic markdown"
date: "`r Sys.Date()`"
output: pdf_document
---

```{r include = F}
library(tidyverse)
```

```{r include = F}
# Создаём датасет
dates <- rep(seq(as.Date("2023-01-01"), length.out = 7, by = "days"), each = 2)
values <- sample(100:130, 14, replace = TRUE)
categories <- rep(c("Cat_A", "Cat_B"), 7)

df <- data.frame(date = dates, 
                 value = values, 
                 category = categories)
```

## Заголовок

Тут у нас кириллический текст с некоторыми вставками english words.

```{r}
head(df)
```

Ну и график прилагается:

```{r}
# plot 1
ggplot(df, aes(x = date,
               y = value,
               col = category)) + 
  geom_line() +
  labs(title = "График",
       x = "Дата",
       y = "Метрика") +
  theme_minimal()
```

При попытке рендера, если у вас локаль RU, консоль выкатит первую ошибку (или отрендерит без кириллического текста и с кучей warning-ов):

! LaTeX Error: Unicode character З (U+0417)
               not set up for use with LaTeX.

Решается она, например, таким способом:

  1. В YAML-заголовке указываем движок и включаем дополнения в виде файла header.tex:

---
title: "Cyrillic markdown"
date: "`r Sys.Date()`"
output:
  pdf_document:
    latex_engine: xelatex
    includes:
      in_header: header.tex
---

2. Сам файл header.tex лежит в папке с нашим markdown-файлом и выглядит примерно так:

\usepackage{fontspec}
\setmainfont{Segoe UI}
\setsansfont{Segoe UI}
\setmonofont{Segoe UI}
\usepackage{polyglossia}
\setmainlanguage{russian}
\setotherlanguage{english}

Теперь LaTeX перестанет ругаться и кое-как, но PDF-ку соберёт.

Ну, пока хоть так

Ну, пока хоть так

Кириллица в тексте обрабатывается, а вот ggplot форматирует даты в текст и ломается.

Если вы не любитель описывать labs в графиках, а уж тем более кириллицей, то тут можно ограничиться сменой локали прям в markdown-файле, добавив куда-нибудь в начало такую строку:

Sys.setlocale("LC_TIME", "en_US.UTF-8")

Второй вариант — использовать библиотеку showtext:

install.packages("showtext")
library(showtext)

# после загрузки библиотеки, отдаём распознавание на откуп движку
showtext_auto()

Предупреждения ушли, график видит кириллицу как в лабсах, так и в датах:

0780a813fddfd1efc58578f8db9bbbc0.png

В целом готово. Но мне вообще не нравится как выглядят блоки с кодом. Я люблю для этого использовать шрифты Cascadia Mono PL Light или Cascadia Code Light. Но если шрифт кастомный, просто переписать header.tex не поможет. Я перепробовал разные варианты, и переустановить шрифты, и прописать полное название, и скормить ему весь путь к файлу шрифта — безрезультатно.

В итоге помогла смена движка на lualatex, который тоже поддерживает fontscpec.

---
title: "Cyrillic markdown"
date: "`r Sys.Date()`"
output:
  pdf_document:
    latex_engine: lualatex
    includes:
      in_header: header.tex
---

Ну другое дело же)

012950f05e3c42d4f8912ccbb4ec7838.png

На этом, пожалуй, и все нюансы рендера markdown в PDF.

Я потратил на это пару вечеров (а мог пить пиво), надеюсь кому-нибудь поможет сэкономить время.

Quarto

Тут намного всё проще, никаких костылей с header.tex не нужно, шрифты прописываются прям в YAML, кириллица на графике лечится тем же showtext:

---
title: "Cyrillic markdown"
format: pdf
pdf-engine: lualatex
mainfont: Segoe UI
sansfont: Segoe UI
monofont: Cascadia Mono PL Light
editor: visual
---

```{r include = F}
library(showtext)
showtext_auto()
```

По традиции, приглашаю вас в свой ТГ-канал. Там я пишу о продуктовой аналитке, в основном для ребят, которые уже работают, но только начинают свой карьерный путь.

© Habrahabr.ru