What is gridmicrotex?
gridmicrotex renders LaTeX math as native R
grid graphics objects. It embeds the MicroTeX C++ engine:
MicroTeX parses the LaTeX, builds the TeX box model and computes exact
glyph coordinates, and the package maps that layout onto grid primitives
(pathGrob, segmentsGrob,
rectGrob, textGrob), returning a
gTree.
No LaTeX installation is required, and the result is resolution independent on every R device.
- Full math: fractions, roots, integrals, matrices, Greek, accents,
delimiters, and colour via
\textcolor{} - Two bundled math fonts, plus any OpenType math font through
load_math_font() - CJK, RTL and multilingual text inside
\text{} - ggplot2 integration —
geom_latex()andelement_latex(), seevignette("ggplot2-integration") - Markdown with inline math — see
vignette("markdown")
Quick start
latex_grob() returns a grob; grid.latex()
builds and draws one.
library(gridmicrotex)
library(grid)
grid.newpage()
grid.latex(r"($\frac{\textcolor{red}{-b} \pm \sqrt{b^2 - 4ac}}{2a}$)",
gp = gpar(fontsize = 24))
Write LaTeX in a raw string. r"(...)"
passes backslashes through untouched, so what you paste is what renders.
Every example here uses one. In an ordinary "..." string
each \ has to be doubled — and LaTeX’s own row separator
\\ becomes a bewildering \\\\.
Mixing text and math
The default input_mode = "mixed" reads the string as
prose and typesets only what sits inside $…$ or
\(…\) as math, which is why Famous: below
needs no markup. It saves typing, but the split is heuristic.
input_mode = "math" treats the whole string as math, so
prose must be wrapped in \text{}; that suits heavy math and
pasted LaTeX. These two render identically:
grid.newpage()
grid.latex(r"(Famous: $E = mc^2$)",
x = 0.05, y = 0.7, hjust = 0, gp = gpar(fontsize = 22))
grid.latex(r"(\text{Famous: } E = mc^2)", input_mode = "math",
x = 0.05, y = 0.3, hjust = 0, gp = gpar(fontsize = 22))
Set the mode for a whole session with
latex_options(input_mode = ).
What it can render
Two figures before the details, because the range is easier to see
than to describe. 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}
)", gp = gpar(fontsize = 22))
And a page of assorted notation — split alignment,
fraktur, stacked delimiters, \sideset, extensible arrows,
\rotatebox, \reflectbox and a boxed
result:
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}
)", gp = gpar(fontsize = 22), render_mode = "path")
Both are ordinary LaTeX, pasted unchanged. The rest of this vignette is about placing such things, choosing fonts, and knowing where the supported set ends.
Aligning to the math baseline
hjust and vjust also take names. The useful
one is vjust = "baseline", which puts the formula’s
math baseline — not the bounding-box centre — on the anchor, so
a formula sits beside running text the way it would in a typeset
document.
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 = 16))
grid.latex(r"($x \geq \sqrt{2\pi}$)",
x = 0.22, y = y, hjust = "left", vjust = "baseline",
gp = gpar(fontsize = 16))
grid.text(", then proceed.", x = 0.62, y = y, just = c(0, 0.5),
gp = gpar(fontsize = 16))
hjust accepts "left"/"bbleft",
"center"/"centre"/"middle"/
"bbcentre", and
"right"/"bbright". vjust accepts
"bottom",
"center"/"centre"/"middle",
"top", and "baseline".
Named anchors with \mark{}
\mark{name} records a named anchor inside the formula,
and grobMark() resolves it to a pair of grid units ready to
drive an arrow or a callout. Marks work at any nesting level — even
inside a superscript or a fraction — and inherit the surrounding
transform (font shrink, scaling, rotation), so the anchor lands on the
rendered glyph.
g <- latex_grob(r"($a^2 + b\mark{term}^2 \mark{equals}= c^2$)",
x = 0.5, y = 0.4, gp = gpar(fontsize = 28))
grid.newpage()
grid.draw(g)
# The "=" sign, pointed at from above.
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", fontsize = 11))
# The b^2 term, from below -- the mark sits at the end of the term,
# including the superscript's smaller scale.
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", fontsize = 11))
The returned units carry the grob’s viewport position and
hjust/vjust, so they go straight into any grid
drawing function with no offset arithmetic. A mark is a single point,
not a span: to centre a callout over a multi-glyph term, use a pair
(\mark{l}…\mark{r}) and take the midpoint.
Display and inline style
TeX sets the same expression two ways. In display style a
\sum is drawn at full size with its limits above and below;
in text (inline) style it shrinks and the limits become scripts
beside it. This is a choice about spacing and layout, not about size —
size is always gp$fontsize.
By default the delimiters decide, exactly as in a LaTeX document:
$…$ gives text style and $$…$$ gives display
style. A label written without delimiters — a plot title, an axis title,
an element_latex() — has nothing for the parser to go on,
so it gets text style. tex_style overrides that for the
whole expression, and takes all four of TeX’s styles:
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()
}
Every panel is gp = gpar(fontsize = 20). The display
version is taller and narrower because the limits moved above and below
the operator instead of sitting beside it — worth knowing when you are
budgeting space for a label, and latex_dims() reports the
difference if you need the numbers. "script" and
"scriptscript" are the sizes TeX uses for first- and
second-level scripts; they are occasionally handy for a compact
sub-label, and rarely needed otherwise.
To change style for part of an expression rather than all of it, use
the inline commands \displaystyle, \textstyle,
\scriptstyle and \scriptscriptstyle instead.
latex_options(tex_style = ) sets a session default.
Wrapping and justification
max_width, in big points, wraps a label over as many
lines as it needs.
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(fontsize = 8, 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(fontsize = 8, 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)
Both refinements are off by default. justify = TRUE
fills every line but the last; line_break = "optimal"
chooses the breaks by total fit rather than one line at a time.
Justifying a narrow column opens wide word gaps, since nothing may break
inside a word — mark the words that may with \-, as in
in\-ter\-na\-tion\-al.
Including a figure
\includegraphics draws an image inside the formula. PNG,
JPEG and SVG are supported, sized with width,
height or scale in any LaTeX length; give one
of width/height and the aspect ratio is kept, or both plus
keepaspectratio to fit inside them. As in LaTeX the
extension may be omitted ({plots/fig} finds
plots/fig.svg or plots/fig.png), and
\graphicspath{{figs/}} adds a directory to search.
angle rotates the figure, like \rotatebox;
trim and clip are recognised but not applied,
and say so.
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()
#> agg_png
#> 2
grid.newpage()
grid.latex(sprintf(r"(\text{before }\includegraphics[width=1in]{%s}\text{ after})", fig),
input_mode = "math", gp = gpar(fontsize = 16))
Inline, the image sits on the baseline, as in LaTeX
— \raisebox moves it. A paragraph holding only an image is
a block, flush left unless halign or a
text-align rule on img says otherwise.
icon <- sprintf(r"(\includegraphics[width=14bp]{%s})", fig)
grid.newpage()
grid.latex(sprintf(r"(\text{baseline }%s\text{, raised }\raisebox{4bp}{%s})",
icon, icon),
input_mode = "math", gp = gpar(fontsize = 18))
A caption
\caption renders where it is written. A caption after
\includegraphics therefore sits below the figure, and one
written before a tabular sits above the table — which is
what makes raw xtable and kable output come
out the right way round without any float logic.
\centering is dropped, because a grob has no page to
centre against. A one-column array does the job instead: it
centres the figure and its caption on each other.
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))
Remove the array and the same two lines come out flush
left. LaTeX numbers captions from a counter; here the number is yours to
write.
Prefer SVG. An SVG is drawn as real grid primitives,
so it stays sharp at any output resolution — there is no dpi to choose.
A PNG or JPEG is a fixed grid of pixels, so if you display one much
larger than it was saved you get a blurry result; the package warns when
the effective resolution falls below 150 dpi and tells you what pixel
size would reach 300. The rule is
pixels = display_inches * output_dpi.
PDF and EPS are not supported. Save the figure as SVG instead: it needs no external tool and, unlike a rasterised PDF, its text stays text.
Fonts
Two layers are in play. MicroTeX chooses the math
glyphs and their metrics; grid draws everything inside
\text{} using gp$fontfamily, so prose follows
R’s ordinary font handling — Latin, CJK, Cyrillic and anything else the
device supports.
Two math fonts ship with the package and load automatically:
| Alias | Font | Style | Pairs with |
|---|---|---|---|
"lete" (default) |
Lete Sans Math | Sans-serif | fontfamily = "sans" |
"stix" |
STIX Two Math | Serif | fontfamily = "serif" |
available_math_fonts()
#> [1] "DejaVu Sans" "Lete Sans Math" "STIX Two Math"Set one per call with math_font, or for the session with
latex_options(math_font = ). Both rows below are the same
formula — only the math font and its paired text family differ:
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(fontsize = 15, fontfamily = "sans"))
upViewport()
pushViewport(viewport(layout.pos.row = 2))
grid.latex(formula, math_font = "stix",
gp = gpar(fontsize = 15, fontfamily = "serif"))
upViewport(2)
check_math_fonts() gives a diagnostic report. Any font
available to R works for the text half — base families like
"sans", "serif" and "mono", or
anything registered through systemfonts:
grid.newpage()
grid.latex(r"(如果 $x > 0$ 则 $y = x^2$)",
gp = gpar(fontsize = 24, fontfamily = "sans"))
Right-to-left scripts work the same way, mixed with Latin included.
Two things do not: wrapping a right-to-left paragraph across lines needs
the optional FriBidi
library, and a math atom between two right-to-left runs —
\text{…}$x^2$\text{…} — falls back to left-to-right
order.
Naming a font for one run
gp$fontfamily applies to the whole grob. For a
single run, use \gmfontfamily{family}{content} — a
gridmicrotex extension, not standard LaTeX. \textrm{…} goes
the other way, returning content to gp$fontfamily even
inside a \textsf{…}, \texttt{…} or
\gmfontfamily{…}{…} group, and without disturbing bold or
italic:
grid.newpage()
grid.latex(
r"(\textsf{sans \textrm{body} sans} \quad \gmfontfamily{mono}{mono})",
gp = gpar(fontsize = 16, fontfamily = "serif")
)
family is anything gp$fontfamily accepts: a
generic ("sans", "serif", "mono")
or a specific name such as "Georgia", which
\textsf{…} / \texttt{…} cannot express.
Unresolvable names fall back silently. The content is typeset as text,
so this styles prose, not math — math glyphs follow
math_font. It composes with emphasis in either nesting
order, but a nested \gmfontfamily replaces the
family it sits inside.
The name is deliberately not \fontfamily: LaTeX’s takes
one argument, does nothing until \selectfont, and wants an
NFSS code (ptm) rather than a font name. Since gridmicrotex
accepts pasted LaTeX, claiming that name would silently misparse real
input. This is also what markdown’s font-family CSS
compiles to — see vignette("markdown").
Loading a custom math font
load_math_font() adds any OpenType math font. The
OpenType MATH table is parsed directly in C++, so no companion metrics
file and no external toolchain are needed:
load_math_font("path/to/MyFont.otf")This is only for math fonts. Text fonts need no
loading at all — set gp$fontfamily, or name one for a run
with \gmfontfamily{}{}.
Devices and render modes
-
"typeface"(default) draws glyphs as native text, so PDF and SVG output stays selectable and searchable: a line of prose is emitted as a single element, so a viewer finds a phrase and not merely a word. Text given amax_widthis the exception — it is emitted one word per element, because the spaces are where the lines break. Fonts are read straight from their OTF files, with no system-wide install — but this needs a device with the R 4.3 glyph engine (ragg,svglite,cairo_pdf). On others, such as the basepdf()device, it falls back to path mode with a warning. -
"path"draws each glyph as a filled vector path. Works on every device; the text is not selectable, and files are larger.
grid.latex(r"($E = mc^2$)", gp = gpar(fontsize = 24)) # typeface
grid.latex(r"($E = mc^2$)", gp = gpar(fontsize = 24), render_mode = "path") # pathPrefer ragg::agg_png(), svglite::svglite()
or grDevices::cairo_pdf(). The default devices on Windows
and macOS may not find the bundled math fonts and will warn
font family not found in Windows font database; the README
covers the setup, and compares the package with tikzDevice,
xdvir, latex2exp and
plotmath.
Do not use
showtext::showtext_auto()with typeface mode. showtext intercepts all text rendering and converts it to paths, silently defeating typeface mode even onsvgliteandragg. Callshowtext::showtext_auto(FALSE)before drawing formulas.
Utilities
Measuring
latex_dims() returns the bounding box of an expression,
for layout arithmetic and for checking that a label fits:
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] FALSESession defaults
latex_options() sets math_font,
render_mode and input_mode for calls that
don’t supply them; explicit arguments always win. Size stays at the grob
level, via gp$fontsize / gp$lineheight.
latex_options(math_font = "stix", render_mode = "typeface")
latex_options() # query
reset_latex_options() # back to built-in defaultsUser-defined macros
define_macro() registers zero-argument shorthands,
expanded by text substitution before the expression reaches
MicroTeX:
define_macro("RR", r"(\mathbb{R})")
define_macro("eps", r"(\varepsilon)")
grid.newpage()
grid.latex(r"(\forall \eps > 0, \eps \in \RR)", gp = gpar(fontsize = 24))
Names must be ASCII letters, and expansion iterates to a fixed point
so macros can reference each other. list_macros() shows
what is registered; clear_macros() drops everything.
For parameterised macros (0–9 arguments) MicroTeX also accepts
plain-TeX \def. These live only for the expression they
appear in, so use them for an abbreviation local to one label and
define_macro() for one that should persist:
grid.newpage()
grid.latex(
r"(\def\norm#1{\left\lVert #1 \right\rVert}
\norm{\vec{v}} = \sqrt{\langle \vec{v}, \vec{v} \rangle})",
gp = gpar(fontsize = 24)
)
Caching and introspection
Parsed layouts are memoised by
(tex, fontsize, math_font, render_mode, …), so a repeated
axis label is laid out once:
latex_cache_info() # size / max_size / hits / misses
latex_cache_limit(1024) # LRU capacity; 0 disables caching
latex_cache_clear() # wipe (e.g. after re-loading fonts)latex_tree() returns the raw draw records plus bbox
metadata, and debug = TRUE overlays the bounding box,
baseline and record origins — both useful when checking alignment:
grid.newpage()
grid.latex(r"($x^{2} + y_{i}$)", gp = gpar(fontsize = 30), debug = TRUE)
LaTeX reference
MicroTeX is a math formula renderer, not a document typesetter. It covers the vast majority of notation used in plots and figures, but does not replace a LaTeX installation. This section is the boundary.
Lists
itemize and enumerate lay their items out
as a left-aligned column, one per row — itemize prefixes a
bullet, enumerate numbers them:
grid.newpage()
grid.latex(r"(\begin{enumerate}
\item e^{i\pi} + 1 = 0
\item \begin{itemize}
\item \alpha \item \beta
\end{itemize}
\end{enumerate})", gp = gpar(fontsize = 20))
An optional […] argument customises the marker. For
itemize it is the literal marker
(\begin{itemize}[\star]); for enumerate it is
a counter template containing one of \arabic*,
\alph*, \Alph*, \roman* or
\Roman* (e.g. \begin{enumerate}[\Roman*.]).
Lists nest, and an item may contain any math, including a
\begin{array} table.
Because MicroTeX is a math engine, each item is a math-mode,
single-line expression: no paragraph flow, no line wrapping,
and prose inside an item needs \text{}
(\item \text{First point}). The description
environment is not supported.
Pasting LaTeX from other sources
Input generated by other tools — ready-to-compile
tabular snippets, fragments copied out of a
.tex file — usually arrives wrapped in document-level
constructs MicroTeX does not implement. Rather than refusing it,
gridmicrotex rewrites or removes a small set of well-known wrappers
before parsing, so knitr::kable(format = "latex") and
xtable::print.xtable() output can be pasted in
unedited:
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, input_mode = "mixed", gp = gpar(fontsize = 11))
Six things were handled without any editing: the %
comment and the table float were dropped,
\centering removed, \caption set as a line
above the table, \toprule/\bottomrule became
thick rules, \midrule a plain one, and \emph
became italic.
Note that the cell text is set in math italics. A
tabular is a math environment, so its contents are math
whichever input_mode you choose — the two modes give an
identical layout for the snippet above. What "mixed" buys
you here is the prose outside the environment, such as the
caption. For upright cell text, wrap the cells in
\text{}.
Removed silently (no visual effect):
| Construct | Why |
|---|---|
%-to-end-of-line comments (\% is
preserved) |
comments are non-visual in LaTeX too |
\documentclass[…]{…},
\usepackage[…]{…}
|
preamble metadata |
\begin{document} / \end{document}
|
document boundary, structural only |
\maketitle, \title{…},
\author{…}
|
title-page metadata, no body output |
\label{…} |
cross-reference target, never rendered in LaTeX either |
\begin{table}[…] / \end{table},
\begin{figure}[…] / \end{figure} (and starred
variants) |
float wrappers; the contents stay |
\centering, \raggedright,
\raggedleft, \flushleft,
\flushright
|
alignment scope declarations |
\noindent, \relax
|
content-free declarations |
Rewritten to a MicroTeX equivalent:
| Construct | Becomes |
|---|---|
\emph{X} |
\textit{X} |
\textnormal{X} |
\text{X} |
\par, \newline
|
\\ (line break) |
\toprule, \bottomrule
|
\thickhline (rendered ~2× thickness) |
\midrule |
\hline |
\cmidrule[trim]?(parenarg)?{a-b} |
\cline{a-b} — partial-column rule |
\caption[short]{X} |
\text{X} plus a line break, at its source position — so
a caption written above the table stays above it |
\smallskip, \medskip,
\bigskip
|
\vspace{0.25em} / \vspace{0.5em} /
\vspace{1em} — em-relative so they scale with
gp$fontsize
|
\hfill, \vfill
|
\quad / \vspace{1em} — static proxies for
rubber lengths |
\url{X} |
coloured monospace text — a grob cannot be a hyperlink, so only the appearance survives |
\href{U}{X} |
X, coloured. Matches hyperref with
colorlinks=true; markdown links use HTML’s
blue-and-underlined convention instead |
Three of those are approximations. \caption renders
where it appears in the source, not where LaTeX’s float machinery would
move it, so caption-above or caption-below follows whatever your tool
emits. The skips are em-relative rather than LaTeX’s absolute 3/6/12 pt,
so they stay visible at any gp$fontsize; use
\vspace{Xpt} for an exact amount. And \hfill /
\vfill are rubber lengths with nothing to fill in
a fixed-size grob, so they become a static 1 em gap — right position, no
elasticity.
Not honored — rendered as literal text, which is intentional: it makes unsupported markup easy to spot.
- Declarative font scopes:
\bfseries,\itshape,\ttfamily,\sffamily,\rmfamily. These affect text within their group in LaTeX, which needs scope tracking we do not implement. Use the argument-bearing forms instead —\textbf{…},\textit{…},\texttt{…},\textsf{…},\textrm{…}— all of which nest. To choose the text font itself, setgp$fontfamilyor use\gmfontfamily{…}{…}. - References:
\ref{…},\cite{…}— there is nothing to resolve against. - Footnotes:
\footnote{…}— the positioning machinery is page-bound. - Small caps:
\textsc{…}— MicroTeX has no small-caps glyphs.
What is not supported
These need a real document compiler and are outside a formula
renderer’s scope: document structure (\section, page
layout, \tableofcontents); automatic hyphenation
(\- marks a break point yourself, and
max_width / justify do handle line breaking);
TikZ/PGF; cross-references and bibliographies; theorem environments; the
description list environment (itemize and
enumerate are supported); and \tag /
equation numbering. \usepackage{…} is accepted but loads
nothing — every supported command is built into MicroTeX.
For axis labels, annotations, legends and in-plot formulas, the supported set is more than sufficient.