--- title: "Introduction to gridmicrotex" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction to gridmicrotex} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", dpi = 300, dev = "png" ) ``` ## Quick start `grid.latex()` draws LaTeX on the current page; `latex_grob()` returns it as a grob. ```{r basic, fig.height=1, fig.width=2.4, out.width="45%"} 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: ```{r modes, fig.height=1.2, fig.width=3.2, out.width="45%"} 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: ```{r malformed, fig.height=0.7, fig.width=5, out.width="55%"} grid.newpage() grid.latex(r"(Area $\pi r^2$, see \nosuchcommand{here})") ``` ## Base graphics Set `latex_options(device_math = TRUE)` and write `$...$` in any base plot label: ```{r base-quick, fig.height=3.6, fig.width=6, out.width="70%"} 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") ``` ```{r base-quick-off, include = FALSE} latex_options(device_math = FALSE) ``` See `vignette("base-graphics")` for details. ## Examples A coloured `array` with `\multicolumn`, `\rowcolor`, `\cellcolor`, a custom column type and a nested matrix: ```{r showcase-table, fig.height = 3, fig.width = 8, out.width="70%"} 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`: ```{r showcase-equation, fig.height = 9, fig.width = 9, out.width="70%"} 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: ```{r baseline-align, fig.height=1.1, fig.width=6, out.width="70%"} 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: ```{r mark, fig.height=2.4, fig.width=5, out.width="70%"} 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: ```{r style-options, fig.height=1.9, fig.width=6.5, out.width="100%"} 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: ```{r wrap, fig.height = 2, fig.width = 4, out.width = "65%"} 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. ```{r img-basic, fig.height = 1.6, fig.width = 4, out.width = "60%"} 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() 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`: ```{r img-caption, fig.height = 1.4, fig.width = 3, out.width = "45%"} 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`: ```{r fonts, fig.height=1.5, fig.width=3.4, out.width="60%"} 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: ```{r cjk, fig.height=1, fig.width=5, out.width="55%", dev = "ragg_png", dev.args = list(), purl = FALSE} 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`: ```{r textrm, fig.height=0.8, fig.width=4.5, out.width="85%", dev = "ragg_png", dev.args = list()} 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. ```{r load-font, fig.height=0.8, fig.width=4.5, out.width="85%"} # 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: ```{r load-installed-font, eval=FALSE} 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: ```{r dims} latex_dims(r"(\frac{a}{b})", gp = gpar(fontsize = 20)) ``` `latex_options()` sets defaults for the session; arguments given in a call always win: ```{r options, eval=FALSE} 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: ```{r macros, fig.height=0.7, fig.width=3, out.width="50%"} 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: ```{r def-inline, fig.height=0.7, fig.width=4, out.width="60%"} 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: ```{r debug, fig.height=1, fig.width=3, out.width="60%"} 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: ```{r tikzcd, fig.height = 1.6, fig.width = 2.6, out.width = "40%"} 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: ```{r cd, fig.height = 1.6, fig.width = 5.2, out.width = "70%"} 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: ```{r pasted, fig.height = 1.1, fig.width = 3.2, out.width = "55%"} 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: ```{r kable, fig.height = 1.5, fig.width = 4.5, out.width = "65%"} 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: ```{r tinytable, fig.height = 1.5, fig.width = 4.5, out.width = "65%"} 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 - Pages: `\pageref` draws `??` and `\cite` draws `[?]`. - Automatic hyphenation, and TikZ beyond `tikz-cd` diagrams. - `\usepackage` loads nothing; every supported command is built in. - A formula between two right-to-left words is not reordered.