Introduction to gridmicrotex

Quick start

grid.latex() draws LaTeX on the current page; latex_grob() returns it as a grob.

library(gridmicrotex)
library(grid)

grid.newpage()
grid.latex(r"($\frac{\textcolor{red}{-b} \pm \sqrt{b^2 - 4ac}}{2a}$)")

Write LaTeX in a raw string, r"(...)", so backslashes need no doubling.

Text and math

A string is read like a line of LaTeX text: math goes between $...$ or \(...\), and a newline starts a new line. With input_mode = "math" the whole string is math and text goes in \text{}. These two are the same:

grid.newpage()
grid.latex(r"(Famous: $E = mc^2$)",
           x = 0.05, y = 0.7, hjust = 0)
grid.latex(r"(\text{Famous: } E = mc^2)", input_mode = "math",
           x = 0.05, y = 0.3, hjust = 0)

input_mode = "document" reads a document body with paragraphs, headings and displayed equations (see vignette("documents")). latex_options(input_mode = ) sets the default for the session.

Mistakes do not stop the drawing. One warning lists each problem with its line and column, and an unknown command is drawn in red:

grid.newpage()
grid.latex(r"(Area $\pi r^2$, see \nosuchcommand{here})")
#> Warning: LaTeX input: 1:21: unknown command \nosuchcommand: drawn as its name

Base graphics

Set latex_options(device_math = TRUE) and write $...$ in any base plot label:

latex_options(device_math = TRUE)

plot(1:10, (1:10)^2,
     main = r"(Slope $\hat{\beta}_1 = \sum_{i=1}^{n} x_i^2$)",
     xlab = r"($x$)", ylab = r"($\frac{y}{2}$)")
text(3, 80, r"($\int_0^\infty e^{-x^2}\,dx$)", col = "steelblue")

See vignette("base-graphics") for details.

Examples

A coloured array with \multicolumn, \rowcolor, \cellcolor, a custom column type and a nested matrix:

grid.newpage()
grid.latex(r"(
\newcolumntype{s}{>{\color{#1234B6}}c}
\begin{array}{|c|c|c|s|}
  \hline
  \rowcolor{Tan}\multicolumn{4}{|c|}{\textcolor{white}{\bold{\text{Table Head}}}}\\
  \hline
  \text{Matrix}&\multicolumn{2}{|c|}{\text{Multicolumns}}&\text{Font size commands}\\
  \hline
  \begin{pmatrix}
      \alpha_{11}&\cdots&\alpha_{1n}\\
      \hdotsfor{3}\\
      \alpha_{n1}&\cdots&\alpha_{nn}
  \end{pmatrix}
  &\large \text{Left}&\cellcolor{#00bde5}\small \textcolor{white}{\text{\bold{Right}}}
  &\small \text{small Small}\\
  \hline
  \multicolumn{4}{|c|}{\text{Table Foot}}\\
  \hline
\end{array}
)")

Assorted notation: split alignment, fraktur, stacked delimiters, \sideset, extensible arrows, \rotatebox and \reflectbox:

grid.newpage()
grid.latex(r"(
\definecolor{gris}{gray}{0.9}
\definecolor{noir}{rgb}{0,0,0}
\fatalIfCmdConflict{false}
\newcommand{\pa}{\left|}
\begin{array}{c}
  \LaTeX\\
  \begin{split}
      |I_2| &= \pa\int_0^T\psi(t)\left\{ u(a,t)-\int_{\gamma(t)}^a \frac{d\theta}{k} (\theta,t) \int_a^\theta c(\xi)
          u_t (\xi,t)\,d\xi\right\}dt\right|\\
      &\le C_6 \Bigg|\pa f \int_\Omega \pa\widetilde{S}^{-1,0}_{a,-}
          W_2(\Omega, \Gamma_1)\right|\ \right|\left| |u|\overset{\circ}{\to} W_2^{\widetilde{A}}(\Omega\Gamma_r,T)\right|\Bigg|\\
      &\\
      &\begin{pmatrix}
          \alpha&\beta&\gamma&\delta\\
          \aleph&\beth&\gimel&\daleth\\
          \mathfrak{A}&\mathfrak{B}&\mathfrak{C}&\mathfrak{D}\\
          \boldsymbol{\mathfrak{a}}&\boldsymbol{\mathfrak{b}}&\boldsymbol{\mathfrak{c}}&\boldsymbol{\mathfrak{d}}
      \end{pmatrix}
      \quad{(a+b)}^{\frac{n}{2}}=\sqrt{\sum_{k=0}^n\tbinom{n}{k}a^kb^{n-k}}\quad
          \Biggl(\biggl(\Bigl(\bigl(()\bigr)\Bigr)\biggr)\Biggr)\\
      &\forall\varepsilon\in\mathbb{R}_+^*\ \exists\eta>0\ |x-x_0|\leq\eta\Longrightarrow|f(x)-f(x_0)|\leq\varepsilon\\
      &\det
      \begin{bmatrix}
          a_{11}&a_{12}&\cdots&a_{1n}\\
          a_{21}&\ddots&&\vdots\\
          \vdots&&\ddots&\vdots\\
          a_{n1}&\cdots&\cdots&a_{nn}
      \end{bmatrix}
      \overset{\mathrm{def}}{=}\sum_{\sigma\in\mathfrak{S}_n}\varepsilon(\sigma)\prod_{k=1}^n a_{k\sigma(k)}\\
      &\Delta f(x,y)=\frac{\partial^2f}{\partial x^2}+\frac{\partial^2f}{\partial y^2}\qquad\qquad \fcolorbox{noir}{gris}
          {n!\underset{n\rightarrow+\infty}{\sim} {\left(\frac{n}{e}\right)}^n\sqrt{2\pi n}}\\
      &\sideset{_\alpha^\beta}{_\gamma^\delta}{
      \begin{pmatrix}
          a&b\\
          c&d
      \end{pmatrix}}
      \xrightarrow[T]{n\pm i-j}\sideset{^t}{}A\xleftarrow{\overrightarrow{u}\wedge\overrightarrow{v}}
          \underleftrightarrow{\iint_{\mathds{R}^2}e^{-\left(x^2+y^2\right)}\,\mathrm{d}x\mathrm{d}y}
  \end{split}\\
  \rotatebox{30}{\sum_{n=1}^{+\infty}}\quad\mbox{Mirror rorriM}\reflectbox{\mbox{Mirror rorriM}}
\end{array}
)", render_mode = "path")

Placing a formula

hjust and vjust take numbers or names. vjust = "baseline" puts the formula’s baseline on y, so it lines up with text beside it:

grid.newpage()
y <- 0.5
grid.segments(unit(0, "npc"), unit(y, "npc"),
              unit(1, "npc"), unit(y, "npc"), gp = gpar(col = "grey80"))
grid.text("if ", x = 0.10, y = y, just = c(0, 0.5), gp = gpar(fontsize = 20))
grid.latex(r"($x \geq \sqrt{2\pi}$)",
           x = 0.22, y = y, hjust = "left", vjust = "baseline",
           gp = gpar(fontsize = 20))
grid.text(", then proceed.", x = 0.62, y = y, just = c(0, 0.5),
          gp = gpar(fontsize = 20))

Named anchors

\mark{name} records a point inside a formula, and grobMark() returns it as grid units, ready for an arrow or a callout:

g <- latex_grob(r"($a^2 + b\mark{term}^2 \mark{equals}= c^2$)",
                x = 0.5, y = 0.4)
grid.newpage()
grid.draw(g)

mk_eq <- grobMark(g, "equals")
grid.segments(mk_eq$x, mk_eq$y + unit(15, "mm"),
              mk_eq$x, mk_eq$y + unit(3, "mm"),
              arrow = arrow(length = unit(2, "mm"), type = "closed"),
              gp = gpar(col = "red"))
grid.text("equals", x = mk_eq$x, y = mk_eq$y + unit(18, "mm"),
          gp = gpar(col = "red"))

mk_bsq <- grobMark(g, "term")
grid.segments(mk_bsq$x - unit(6, "mm"), mk_bsq$y - unit(15, "mm"),
              mk_bsq$x - unit(2, "mm"), mk_bsq$y - unit(3, "mm"),
              arrow = arrow(length = unit(2, "mm"), type = "closed"),
              gp = gpar(col = "blue"))
grid.text("b² term", x = mk_bsq$x - unit(7, "mm"),
          y = mk_bsq$y - unit(18, "mm"), just = "right",
          gp = gpar(col = "blue"))

Display and text style

$...$ sets a formula in text style, as in a paragraph; $$...$$ sets it in display style, with limits above and below. A label without delimiters is set in text style. tex_style overrides this:

sum_expr <- r"(\sum_{i=1}^{n} \frac{x_i}{n})"
styles <- c("display", "text", "script", "scriptscript")
labels <- c('"display"  ($$...$$)', '"text"  ($...$)',
            '"script"', '"scriptscript"')

grid.newpage()
for (i in seq_along(styles)) {
  pushViewport(viewport(x = (i - 0.5) / 4, width = 1 / 4))
  grid.text(labels[i], y = 0.88, gp = gpar(cex = 0.75, fontface = "bold"))
  grid.latex(sum_expr, y = 0.42, input_mode = "math",
             tex_style = styles[i], gp = gpar(fontsize = 20))
  grid.rect(gp = gpar(col = "grey85", fill = NA))
  popViewport()
}

All four use the same font size. To change the style of part of a formula, use \displaystyle, \textstyle, \scriptstyle or \scriptscriptstyle.

Line wrapping

max_width, in big points, wraps a label over several lines. justify = TRUE fills every line but the last, and line_break = "optimal" balances the breaks across the paragraph:

prose <- paste(rep(
  r"(The quick brown fox jumps over the lazy dog, and $x^2$ too.)", 3),
  collapse = " ")

grid.newpage()
pushViewport(viewport(layout = grid.layout(2, 1)))
pushViewport(viewport(layout.pos.row = 1))
grid.text("ragged (default)", x = 0.02, y = 0.98, hjust = 0, vjust = 1,
          gp = gpar(col = "grey40"))
grid.latex(prose, x = 0.02, y = 0.78, hjust = 0, vjust = 1,
           max_width = 3.6 * 72, gp = gpar(fontsize = 11))
popViewport()
pushViewport(viewport(layout.pos.row = 2))
grid.text("justified + optimal", x = 0.02, y = 0.98, hjust = 0, vjust = 1,
          gp = gpar(col = "grey40"))
grid.latex(prose, x = 0.02, y = 0.78, hjust = 0, vjust = 1,
           max_width = 3.6 * 72, justify = TRUE, line_break = "optimal",
           gp = gpar(fontsize = 11))
popViewport(2)

Words are not hyphenated. Mark where one may break with \-, as in in\-ter\-na\-tion\-al.

Images

\includegraphics draws a PNG, JPEG or SVG file. Size it with width, height or scale, and rotate it with angle. The extension may be left off, and \graphicspath{{figs/}} adds a folder to search.

fig <- tempfile(fileext = ".svg")
svglite::svglite(fig, width = 2, height = 1.2)
grid.newpage()
grid.circle(r = 0.35, gp = gpar(fill = "steelblue", col = NA))
dev.off()
#> png 
#>   2

grid.newpage()
grid.latex(sprintf(r"(before \includegraphics[width=1in]{%s} after)", fig),
           gp = gpar(fontsize = 20))

An inline image sits on the baseline; \raisebox moves it. A \caption is drawn where it is written. To centre a figure and its caption, put them in a one-column array:

logo <- system.file("img", "Rlogo.png", package = "png")
grid.newpage()
grid.latex(sprintf(r"(\begin{array}{c}
  \includegraphics[width=0.6in]{%s}\\
  \caption{Figure 1: the R logo}
\end{array})", logo), gp = gpar(fontsize = 11))

Prefer SVG, which stays sharp at any size. A PNG or JPEG warns when it is shown below 150 dpi. PDF and EPS files are not supported.

Fonts

Two math fonts are included:

Alias Font Style Pairs with
"lete" (default) Lete Sans Math Sans-serif fontfamily = "sans"
"stix" STIX Two Math Serif fontfamily = "serif"

Choose one with math_font, or for the session with latex_options(math_font = ). Text follows gp$fontfamily:

formula <- r"(Theorem: $\int_0^1 f(x)\,dx \geq 0$)"

grid.newpage()
pushViewport(viewport(layout = grid.layout(2, 1)))
pushViewport(viewport(layout.pos.row = 1))
grid.latex(formula, gp = gpar(fontfamily = "sans"))
upViewport()
pushViewport(viewport(layout.pos.row = 2))
grid.latex(formula, math_font = "stix",
           gp = gpar(fontfamily = "serif"))
upViewport(2)

Any font R can use works for text, including CJK and right-to-left scripts:

grid.newpage()
grid.latex(r"(如果 $x > 0$ 则 $y = x^2$)",
           gp = gpar(fontfamily = "sans"))

\textsf{} and \texttt{} set text in the sans and mono fonts, and \textrm{} returns to gp$fontfamily:

grid.newpage()
grid.latex(
  r"(\textsf{sans \textrm{body} sans} \quad \texttt{mono})",
  gp = gpar(fontfamily = "serif")
)

Your own fonts

load_font() names a font file, or an installed family, once, and loads an OpenType math font for math_font the same way. The name then works wherever a font is named: in gp, in latex_options() (main_font, sans_font, mono_font) and in the LaTeX itself, with the commands of fontspec and unicode-math (\setmainfont, \setsansfont, \setmonofont, \setmathfont, \fontspec, \newfontfamily) and \fontfamily{...}\selectfont, which also reads LaTeX’s family codes such as ppl for Palatino.

# A font file of your own, here the bundled Lete Sans Math
otf <- system.file("fonts", "LeteSansMath.otf", package = "gridmicrotex")
load_font(otf, name = "My Font", bold = otf)

grid.newpage()
grid.latex(r"(\setmainfont{My Font} body, \textbf{bold} and
          {\fontspec{STIX Two Math} another font})")

Text in a loaded font is drawn from its file on every device, pdf() included. bold and italic are the files of those faces, so \textbf and \textit use the real design. available_fonts() lists what is loaded.

An installed family is loaded by its name; its bold and italic faces are found on their own. The families installed differ from one system to the next, so this chunk is not run:

unique(systemfonts::system_fonts()$family)   # what is installed

load_font("Georgia")                          # registered as "Georgia"
latex_options(main_font = "Georgia")
grid.latex(r"(Text in \textbf{Georgia} and $x^2$)")

Devices

By default glyphs are drawn as text, so PDF and SVG output can be selected and searched. This needs ragg, svglite or cairo_pdf(); on other devices, such as pdf(), glyphs are drawn as outlines with a warning. render_mode = "path" always draws outlines: it works on every device, but the text cannot be selected.

If the default device on Windows or macOS warns font family not found, use ragg::agg_png() instead.

showtext::showtext_auto() turns all text into outlines, formulas included. Turn it off with showtext::showtext_auto(FALSE).

Utilities

latex_dims() measures a formula:

latex_dims(r"(\frac{a}{b})", gp = gpar(fontsize = 20))
#> $width
#> [1] 7bigpts
#> 
#> $height
#> [1] 25bigpts
#> 
#> $depth
#> [1] 9bigpts
#> 
#> $baseline
#> [1] 9.36317294836044bigpts
#> 
#> $is_split
#> [1] FALSE

latex_options() sets defaults for the session; arguments given in a call always win:

latex_options(math_font = "stix")
latex_options()        # show the current settings
reset_latex_options()  # back to the defaults

define_macro() adds a shorthand for every later label:

define_macro("RR", r"(\mathbb{R})")
define_macro("eps", r"(\varepsilon)")

grid.newpage()
grid.latex(r"($\forall \eps > 0, \eps \in \RR$)")


clear_macros()

A label can also define its own, with \newcommand or \def. These last for that label only:

grid.newpage()
grid.latex(
  r"(\def\norm#1{\left\lVert #1 \right\rVert}
      $\norm{\vec{v}} = \sqrt{\langle \vec{v}, \vec{v} \rangle}$)"
)

debug = TRUE draws the bounding box and baseline:

grid.newpage()
grid.latex(r"($x^{2} + y_{i}$)", debug = TRUE)

Diagrams

tikz-cd’s tikzcd and amscd’s CD draw commutative diagrams: objects in a grid joined by arrows. An arrow is written in the cell it leaves, with the direction it goes (r, d, dr, rr, …), a label in quotes (a prime puts it on the other side), and a style:

sq <- r"(\begin{tikzcd}
A \arrow[r, "f"] \arrow[d, "g"'] \arrow[dr, dashed] & B \arrow[d, "h"] \\
C \arrow[r, hook, "k"'] & D
\end{tikzcd})"
grid.newpage()
grid.latex(sq, x = 0.5, y = 0.5, gp = gpar(fontsize = 14))

Styles include hook, tail, two heads, mapsto, dashed, dotted, Rightarrow, equal, harpoon, squiggly, bend left, shift right, crossing over, phantom and a colour name; labels take description, near start and near end; and row sep and column sep set the spacing. CD writes its arrows between the objects instead:

cd <- r"(\begin{CD}
A @>f>> B \\
@VgVV @VVhV \\
C @>>k> D
\end{CD})"
bent <- r"(\begin{tikzcd}[column sep=large]
A \arrow[r, "f", bend left] \arrow[r, "g"', bend right]
  & B \arrow[r, two heads, mapsto] & C
\end{tikzcd})"
grid.newpage()
grid.latex(cd, x = 0.2, y = 0.5, gp = gpar(fontsize = 14))
grid.latex(bent, x = 0.68, y = 0.5, gp = gpar(fontsize = 14))

Pasting LaTeX

Tables from knitr::kable(format = "latex"), kableExtra, xtable, gt and tinytable can be pasted unchanged:

snippet <- r"(
% latex table generated by kable()
\begin{table}[ht]
\centering
\caption{Model coefficients}
\begin{tabular}{lrr}
\toprule
Term & Estimate & \emph{p} \\
\midrule
Intercept & 2.14 & 0.003 \\
Slope & 0.42 & 0.001 \\
\bottomrule
\end{tabular}
\end{table}
)"

grid.newpage()
grid.latex(snippet, gp = gpar(fontsize = 11))

A table is set to the max_width it is given, so kable(booktabs = TRUE) and a full-width kableExtra or gt table can be drawn as they are:

tab <- knitr::kable(head(mtcars[, 1:4], 3), format = "latex",
                    booktabs = TRUE, digits = 1)
grid.newpage()
grid.latex(as.character(tab), input_mode = "document", max_width = 4 * 72,
           x = 0.05, y = 0.95, hjust = 0, vjust = 1, gp = gpar(fontsize = 11))

tinytable’s output is read as well:

tt <- r"(\begin{table}
\centering
\begin{tblr}{
colspec={Q[]Q[r]Q[r]},
hline{1,3}={1-3}{solid, black, 0.08em},
hline{2}={1-3}{solid, black, 0.05em},
row{2}={}{font=\bfseries, bg=yellow},
}
Term & Estimate & p \\
Intercept & 2.14 & 0.003 \\
Slope & 0.42 & 0.001 \\
\end{tblr}
\end{table})"
grid.newpage()
grid.latex(tt, input_mode = "document", max_width = 4 * 72,
           x = 0.05, y = 0.95, hjust = 0, vjust = 1, gp = gpar(fontsize = 11))

What is supported

Every function on KaTeX’s lists of supported functions is drawn, and more. A list of them, in KaTeX’s order, with how each is drawn, is on the package website as supported-functions.pdf. source(system.file( "supported/build.R", package = "gridmicrotex")) and build_supported() write it, and its LaTeX file, wherever you ask.

Not supported