TikZ & system fonts with Quarto html

2024-10-23

knitr has support for executing tikz blocks in a Rmarkdown/quarto document and can even output an svg for html using the following block syntax.

```{R, engine = "tikz"}
#| fig-ext: svg
\begin{tikzpicture}[scale=2]
\node (A) at (0,0) {Hello};
\node (B) at (1,0.5) {Entire};
\node (C) at (1,-0.5) {World};
\draw[->] (A) -- (B);
\draw[->] (A) -- (C);
\end{tikzpicture}
```

The only issue when using svg output is that we cannot access system fonts using fontspec. From knitr/R/engine.R in the knitr::eng_tikz function, knitr calls tinytex::latexmk with the latex backend since neither xelatex or lualatex support dvi output, and then feeds the dvi output to dvisvgm to convert to the final svg product.

  ext = dev2ext(options)

  to_svg = ext == 'svg'
  outf = if (to_svg) tinytex::latexmk(texf, 'latex') else tinytex::latexmk(texf)

  fig = fig_path(if (to_svg) '.dvi' else '.pdf', options)
  dir.create(dirname(fig), recursive = TRUE, showWarnings = FALSE)
  file.rename(outf, fig)

  fig2 = with_ext(fig, ext)
  if (to_svg) {
    # dvisvgm needs to be on the path
    # dvisvgm for windows needs ghostscript bin dir on the path also
    if (Sys.which('dvisvgm') == '') tinytex::tlmgr_install('dvisvgm')
    if (system2('dvisvgm', c(
      options$engine.opts$dvisvgm.opts, '-o', shQuote(fig2), fig
    )) != 0) stop('Failed to compile ', fig, ' to ', fig2)
  } else {
    # convert to the desired output-format using magick
    if (ext != 'pdf') magick::image_write(do.call(magick::image_convert, c(
      list(magick::image_read_pdf(fig), ext), options$engine.opts$convert.opts
    )), fig2)
  }

Unfortunately, fontspec doesn’t support latex. But, dvisvgm does accept xdv (extended dvi) input, which we can create using xelatex. This isn’t currently supported by knitr so either patch the eng_tikz function or create a standalone TeX document for each diagram. For example, we could have a file diagram.tex.

\documentclass[12pt,tikz,dvisvgm]{standalone}

\usepackage{fontspec}
\usepackage{tikz}

\setmainfont{Jost-400-Book.otf}

\begin{document}
\begin{tikzpicture}[scale=2]
\node (A) at (0,0) {Hello};
\node (B) at (1,0.5) {Entire};
\node (C) at (1,-0.5) {World};
\draw[->] (A) -- (B);
\draw[->] (A) -- (C);
\end{tikzpicture}
\end{document}

Compile to xdv by disabling pdf output with xelatex and then convert using dvisvgm with the bonus that we can embed our font into the svg.

$ xelatex -no-pdf diagram.tex
$ dvisvgm -c 3 --font-format=woff2 diagram.xdv

Finally, we can embed this in a markdown document.

![](diagram.svg)
Hello Entire World