litedown: R Markdown Reimagined

Yihui Xie

2024-11-25

Edit this chapter on GitHub

Preface

The litedown package is still new and experimental. The documentation is very incomplete. Besides, litedown was designed for minimalists with a limited scope. Average users should perhaps consider using rmarkdown or Quarto instead.

You may say I’m a dreamer
But I’m not the only one
I hope someday you’ll join us
And the world will live as one

—John Lennon, Imagine

Imagine there’s no PDF. It’s easy if you try. No Word below us. Above us, only HTML.

I do not mean PDF and Word are bad. I only lament the time that human beings spend on various document formats, given how much a static web page can do.

The past journey

Having worked on the development of R Markdown for nearly 12 years, I have to confess that I have become a little perplexed about my work, although I clearly remember the exciting moments. For example:

I think the work was largely meaningful. The only problem is I do not see an end. The list of exciting things to do goes on and on.

A question

“At forty, I had no more doubts.” Confucius said. Apparently, I’m not Confucius. On the contrary, as I’m turning forty, I’m having more doubts. I have been reflecting on the things that have kept me busy.

If I were to summarize the 700+ episodes of Naruto in one sentence, I would use the question that Gaara (the Fifth Kazekage) asked Onoki (the Third Tsuchikage):

When did you all forsake yourselves?

In my opinion, this question was the most critical turning point in the 700+ episodes, reminding the 79-year-old Onoki and the rest of people of their original dreams. The shinobi world existed to put an end to wars, but it turned out to bring even more and larger-scale wars.

When did we forsake simplicity? We create software to simplify things, but software often ends up in feature wars.

Markdown was originally invented for simplicity. That is, to make it easier to write HTML. I used to take out my wallet and tell people that if they are unable to learn the basics of Markdown in 5 minutes, I’d award them 20 dollars. I also used to say “In HTML I Trust”, which sounds like a joke, but I really love HTML and web technologies. Again, I do not mean other document formats are bad at all. It is just that I feel HTML has the greatest potential, and I hope to take full advantage of its power.1

In other words, I do not expect that “the (software) world will live as one”. I just want to make the HTML world a little bit better.

Overview

The litedown package is an attempt to reimagine R Markdown with one primary goal—do HTML, and do it well, with the minimalism principle. Before LaTeX fans walk away in disappointment, let me quickly clarify that LaTeX output is also supported, but please do not expect anything fancy before you learn to customize LaTeX templates. Most other output formats are not supported. No Word, RTF, PowerPoint, or EPUB.

R Markdown is rendered via litedown::fuse(), which is similar to rmarkdown::render() and knitr::knit(). Markdown is rendered via litedown::mark(), which uses commonmark instead of Pandoc.

The commonmark package follows the GFM (GitHub Flavored Markdown) spec, which can be seen as a subset of Pandoc’s Markdown. Therefore the litedown package can be viewed as a small subset of the existing R Markdown ecosystem (the latter is based on Pandoc). It aims at simplicity, lightweight, and speed, at the cost of giving up some advanced features. This package is intended for minimalists. Most users may prefer using tools based on Pandoc instead, such as rmarkdown or Quarto, which offer richer features.

Is litedown really simple? From the developer’s perspective, yes, it is, largely due to the limited scope of the package. From the user’s perspective, some features are definitely not that simple. However, the point is that the core is simple and small, and you can enable or disable most features. What’s more, you can implement features by yourself if you know CSS/JS.

Scope

You can view litedown as a minimal (re-)implementation of some core packages in the existing R Markdown ecosystem, such as rmarkdown for reports, xaringan for slides, bookdown for books, blogdown for websites, pkgdown for R package sites, and pagedown for paged HTML documents.

$$\mathrm{litedown} = \min{\{R\}} + \{D_i\} - \{D_e\} + \{J\}$$

It is absolutely not the goal for litedown to become a substitute of tools based on knitr and Pandoc, such as rmarkdown and Quarto. If you are not sure if you should choose litedown or rmarkdown/Quarto, you may want to choose the latter (especially Quarto).

Features out of scope

Output formats besides HTML and LaTeX are unlikely to be supported.2 If other output formats are desired, you may use Pandoc to do the conversion.

For tables, only pipe tables are supported. Other table formats are not recognized.

At the moment, litedown mainly supports R as the computing language. Other languages might be added in the future, but please keep your expectation low, because the support is unlikely to be as good as R. Back in 2013, I experimented with a simple idea to run program code in other languages, and the outcome was the runr package. However, I forsook simplicity after other sophisticated packages emerged, such as reticulate and JuliaCall. I may revisit the idea in the future.

HTML widgets are not supported yet, but may be reimagined in the future with some minimal support. Meanwhile, you are free to add arbitrary JS libraries to your documents (4.9), so it is entirely possible that you can create HTML widgets on your own.

Highlights

Small footprint

Almost everything in litedown was written from scratch. The package is very lightweight, with only two R package dependencies: commonmark and xfun.

It is a deliberate design choice to keep this package lightweight, to make it relatively easy to use and simple to maintain. The functions mark() and fuse() can be viewed as significantly trimmed-down versions of Pandoc and knitr, respectively.

Just to give you a better idea about the “lightweight” (the numbers below refer to uncompressed software and source code as of today):

Admittedly, pursuing lightweight requires sacrifice in features and quality (e.g., my pages.js is far less sophisticated than paged.js), but overall I feel the trade-off should be reasonable to those who prefer minimalism. With a 2Mb footprint (the total file size of litedown, commonmark, and xfun), you can:

If you load additional JS/CSS assets, you can create more types of elements, such as folded code blocks, tabsets, side elements, sticky TOC, and callout blocks (4).

Live preview everything

To get started, run litedown::roam() and it will launch a file browser to let you preview everything that can be rendered to HTML, such as .md, .Rmd, and .R files (5.2).

The rendering takes place in memory, which means it will not render .html files on your disk, unless you request so. The page will be automatically updated as you edit and save a file. This update-on-save feature can be turned off, and then you can manually refresh the page to re-render the file whenever you want.

You can open any file in your editor or the default system application by clicking a button in the browser. You can also render a file or a project in a new R session by clicking a button in the browser.

Precise parser

The R Markdown parser stores the precise line and column numbers of code elements. The location information is used in various places. For example, when an error occurs, you will get a message that tells you the precise location in the source. In editors that support ANSI links (such as the RStudio IDE), you can even click on the message to go to a specific line/column in the source document, so you can quickly and easily know where the error occurred exactly. When previewing .Rmd documents with roam(), you will see line numbers on the left side of code blocks. Clicking on these numbers will bring you to the lines in the source.

Due to the fact that the parser is based on commonmark (instead of solely on regular expressions like knitr’s parser), it can precisely recognize code chunks and inline code, which means code within other verbatim code blocks or comments will be untouched. For example:

````md
Below is not a code chunk but verbatim content
inside a fenced code block (with four backticks).

```{r}
1 + 1
```

Inline code expressions like `{r} 1+1` are not
parsed, either.
````
<!--
Feel free to comment out anything, such as a code chunk:

```{r}
1 + 1
```
-->

Versioned CSS/JS assets

By default, litedown produces bare minimal HTML output, but many features can be enabled by adding CSS/JS assets (4). You can freely choose whatever versions that work for you to avoid potential future breakage when the assets are upgraded. The assets are not bundled in the litedown package, but hosted on CDN, so updating litedown will not update your assets.

The CSS and JS code for commonly used features do not depend on any frameworks such as Bootstrap or jQuery. They are simply vanilla code written from scratch. No generator was used (such as SCSS). The code is often relatively short, so you could just fork the code, modify it, publish your own version, and use it if you are not satisfied with the original version.

Chunk options management

Chunk options are managed by an environment, i.e., litedown::reactor() (1.2.2). Using an environment (as compared to a list like knitr’s opts_chunk) means you can access the up-to-date chunk options anywhere, because environments in R are mutable. I cannot explain how awkward knitr’s opts_current has been. It is basically a lie—chunk options that you get from opts_current are not necessarily “current”.

Non-linear order of execution

A document does not have to be compiled in the linear order. With the chunk option order, you can specify code chunks and inline code expressions to be executed in an arbitrary order (1.2.5.16). For example, if you have an abstract in the beginning of a document, and the abstract needs to use a number calculated at the end of the document, you can let the abstract be compiled in the end, although it appears earlier in the document.

I guess some users may want to kill me upon knowing this feature, and some may send me flowers (although I’m not sure if they want to thank me or prepare the flowers for my funeral). For those who want to kill me, please note that this feature does not mean litedown is as awful as Jupyter notebooks. It means you can specify a fixed order to execute code in the document. The order does not have to be from beginning to end, but it is deterministic. In other words, it does not mean you run code chunks in an arbitrary or random order that can detriment reproducibility.

Time code chunks

If you want to figure out which code chunks are time-consuming, simply set the chunk option litedown::reactor(time = TRUE) (1.2.5.23) in the beginning, and put litedown::timing_data() in the last code chunk. It will tell you detailed information. In the roam() preview, the data will also contain links to specific lines of code chunks, so you click to jump to a specific code chunk.

Table output for data frames by default

Rectangular objects such as data frames (including tibbles) and matrices are printed as tables by default (1.2.5.22), and the number of rows is limited to 10 by default to avoid generating huge tables by accident.

Relieved pain of paths

File paths (such as image paths) have been a mess in knitr. My deepest apologies for that. I have worked much harder in litedown in this regard.

Output in memory or to disk

Functions such as mark(), fuse(), and fuse_book() can operate in memory without writing to disk. By default, if you pass a file input, you get a file output; if you pass text input, you get text output.

A new cache system

You can feel more confident with using the chunk option cache = TRUE in litedown than in knitr. The new cache system is more robust and intelligent (1.2.5.2).

A book is a single HTML file

Unlike bookdown and Quarto, litedown::fuse_book() renders multiple input files to a single output file. Yes, your whole precious book is in a single (HTML or PDF) file. Grab and go.

The assumption of single-file output for books has made several things a lot easier. For example, if you want to search within the book, just press Ctrl + F or Command + F in your browser as you usually do on any other web page. You do not need a client-side search library. It is also quicker to jump between chapters because they are on the same page. If you want to print the HTML version of the book to PDF, just press Ctrl + P or Command + P.

I know you have a concern: wouldn’t the single HTML file be too heavy for the browser? The answer is: you should be fine if you do not have too many images. If you do, do not base64 encode them (which is the default), and you can also lazy-load images to make the book load faster.

R scripts as first-class citizens

An R script has the same status as an R Markdown document (1.4). You can write Markdown in #' comments, and chunk options in #| comments (both are optional). These are the only two rules to remember. Compared to knitr::spin(), the rules are much simpler. Besides, R scripts are also carefully parsed into “code chunks”, so their line numbers work as well as R Markdown’s line numbers.

Any application that you can create with R Markdown can be created with R scripts, such as books and websites.

Clean HTML output

The HTML output generated from litedown is very clean. For example, code blocks in HTML output contain plain code, instead of full of <span> tags with random attributes. Clean HTML output means the output file size is smaller, and more importantly, it is easier to inspect the differences between two versions of output (e.g., in Git). Every time when you update the source document, you can know more clearly what has changed in the output, which can help you avoid unexpected changes before publishing the output.

Edit this chapter on GitHub

1 Computing

If you give someone a program, you will frustrate them for a day; if you teach them how to program, you will frustrate them for a lifetime.

—David Leinweber

flowchart LR
  A@{ shape: doc, label: "*.Rmd" }-. <code>fuse()</code> .-o F@{ shape: bolt }
  F o--- B@{ shape: doc, label: "*.md" }
  B-. <code>mark()</code> .-o G@{ shape: framed-circle }
  G --> C@{ shape: doc, label: "*.html" }
  G --> D@{ shape: doc, label: "*.tex" }
  G --> E@{ shape: doc, label: "..." }
  A-. <code>fiss()</code> .-o H@{ shape: cross-circ } --> I@{ shape: lin-doc, label: "*.R" }

1 A diagram illustrating how litedown converts R Markdown documents to target output formats.

R Markdown documents need to be knitted to Markdown before being rendered to a target output format. For those who are familiar with R Markdown, you can think of the function litedown::fuse() as the new knitr::knit(), litedown::fiss() as the new knitr::purl(), and litedown::mark() as the new Pandoc. 1 shows the conversion process from R Markdown to a target format.

We will focus on fuse() in this chapter, and leave mark() to 2 and 3.

Unless you use litedown programmatically (i.e., via R code), you should rarely need to call fuse() or mark() directly. Instead, please consider using litedown::roam() to preview or render your files (5.2), which will call fuse() and mark() behind the scenes.

The function fuse() “fuses” program code with narratives, i.e., it executes all code in the source document and interweaves results with narratives in the output document; fiss() splits the document and extracts the code. Similar to knitr, litedown supports code chunks (1.2) and inline code (1.3).

1.1 A minimal example

To understand what fuse() does, we look at a minimal example:

---
title: The area of a circle
---

Define the radius of a circle as `x`:

```{r}
x = 1 + 1
x
```

The area of the circle with a radius of `x` is `{r} pi * x^2`.

When we fuse() the document, the program code will be executed, giving us the results below:

---
title: The area of a circle
---

Define the radius of a circle as `x`:


``` {.r}
x = 1 + 1
x
```

```
#> [1] 2
```

The area of the circle with a radius of `x` is 12.6.

You can see that the value 2 was assigned to the object x in a code chunk, and the inline R expression pi * x^2 was also evaluated to a number.

The advantage of interweaving computing code with text is that you can freely update the source code according to the purpose of computing, and you will get the up-to-date results after rebuilding the document. For example, you can change x to 3, and the area of the circle will be automatically updated after you re-run fuse(). You do not need to copy and paste computational outcome manually from one place to another.

1.2 Code chunks

A code chunk is a fenced code block that consists of: 1) the language (or engine) name, 2) chunk options that control the behavior of computing and output, and 3) the program code. It is of the following form:

```{lang}
#| chunk options

code
```

Whitespaces are allowed between the opening ``` and {. The language name should begin with only alphanumeric characters (a-z, A-Z, 0-9) and underscores (_). If any other character is used or there are no curly braces in the chunk header, the chunk will be treated as a normal fenced code block in Markdown, instead of a code chunk for computing. Below are some examples of normal code blocks:

```r
# lack of {} in the header
```

``` {.r, echo = FALSE}
# the language name should not start with "."
```

In this book, we use the term “code block” to refer to plain Markdown code blocks, and “code chunk” to refer to executable code chunks.

1.2.1 Syntax for chunk options

The chunk options can be provided in the chunk header after the language name, or in the chunk body as pipe comments (#|).

For the comma-separated syntax, the option value can be provided via arbitrary R code as long as it is syntactically valid. The code will be evaluated when the value is needed (i.e., the evaluation is lazy).

Below are examples showing different ways to write chunk options:

You can use #| for any language, although not all languages use # as the comment character. If you prefer, you can use language-specific comments and add pipes after the comment characters to write chunk options, e.g., //| for JavaScript. You can find the supported comment characters for different languages in xfun:::comment_chars. Here are a few of them:

str(xfun:::comment_chars[c('css', 'fortran', 'js', 'r')])
#> List of 4
#>  $ css    : chr [1:2] "/*" "*/"
#>  $ fortran: chr "!"
#>  $ js     : chr "//"
#>  $ r      : chr "#"

For languages that use opening and closing comment delimiters such as C and CSS, you do not need to repeat the comment pipe on every line, e.g.,

```{css}
/*| label = 'foo',
    echo = FALSE   */

p { color: red; }
```

In this book, I will write options in the chunk header when they are relatively short, and write them in pipe comments when they are too long.

See 1.2.5 for the full list of possible chunk options.

1.2.2 The chunk option manager

All chunk options are managed internally via litedown::reactor(). To get an option value, call reactor("NAME"), where NAME is the option name (e.g., fig.width). To set an option globally for all code chunks, call reactor(NAME = VALUE).

If an option is set in both reactor() and a code chunk (1.2.1), the value in the chunk wins. For example, if you have set litedown::reactor(echo = FALSE) previously, and echo = TRUE in a chunk header, the chunk option echo will be TRUE for this chunk.

Alternatively, you can manipulate the value returned by reactor() directly, which is essentially an environment:

opts = litedown::reactor()
opts$fig.width  # get an option
opts$echo = FALSE  # set an option via assignment

You can call reactor() anywhere to get or set chunk options. For example, you can call it inside a code chunk. Please note that when you call it to change global chunk options outside an R Markdown document, we recommend that you restore the original options afterwards to limit the scope of the changes, otherwise the changes may affect the behavior of other documents unexpectedly. To restore options, you need to save the old option values, e.g.,

# reactor() returns old values after setting new values
old_opts = litedown::reactor(echo = FALSE, fig.height = 6)

# fuse a document with new global chunk options
litedown::fuse("foo.Rmd")

# restore old option values
litedown::reactor(old_opts)

This is unnecessary if you call reactor() inside a document, since global chunk options are always restored when fuse() finishes compiling the document, e.g.,

```{r, setup}
litedown::reactor(echo = FALSE)
```

```{r}
# echo = FALSE will be applied to this chunk
1 + 1
```

The chunk option echo will be restored to TRUE after fuse() finishes this document.

1.2.3 Skipped code chunks

Code chunks inside other code blocks are not parsed or evaluated, which provides a way to write verbatim code chunks, e.g.,

````md
Some verbatim content.

```{r}
1 + 1
```
````

Similarly, code in HTML comments (<!-- -->) will not be recognized, either, e.g.,

<!--
Do not run this chunk:

```{r}
1 + 1
```

or the inline code `{r} pi`.
-->

1.2.4 Show chunk fences in output

If N + 1 pairs of curly braces are used in the opening fence, the chunk fences (with N pairs of curly braces) and chunk options will be shown in the output, which can be useful for telling readers the full source of a chunk, e.g.,

```{{r}}
#| echo = TRUE, eval = FALSE

1 + 1
```
```{r}
#| echo = TRUE, eval = FALSE

1 + 1
```

1.2.5 All chunk options

All supported chunk options are listed alphabetically below.

1.2.5.1 attr.*: attributes for output elements

The attr.* options can be used to customize different types of output elements, such as source code blocks, messages, and plots, etc. To understand these options, you need to learn the Markdown syntax in 2.2.5 and 2.2.7.

The Markdown output from a code chunk follows a structure like this (illustrated in 2):

:::: {.chunk}
``` {.source}
1:2 + 1:3
```

``` {.output}
#> [1] 2 4 4
```

``` {.warning}
#> longer object length is not a multiple of ...
```

``` {.source}
plot(cars)
```

![alt](*__files/*.png){.plot}
::::
kanban
  _chunk_
    ["1:2 + 1:3"]@{ assigned: '_source_', priority: 'High' }
    ["#> [1] 2 4 4"]@{ assigned: '_output_' }
    ["#> longer object length is not a multiple of ..."]@{ assigned: '_warning_' }
    ["plot(cars)"]@{ assigned: '_source_', priority: 'High' }
    ["!\[alt](*__files/*.png)"]@{ assigned: '_plot_' }

2 An illustration of the output structure of a code chunk.

The source code, text output, and messages (including warnings and errors) are formatted as fenced code blocks. Plots are written in ![](). The whole chunk can be wrapped in a fenced Div (2.2.7). The attr.* options will add attributes to these fenced code blocks, plots, and fenced Divs.

Below is an example of using options attr.chunk, attr.source, and attr.plot:

---
title: The `attr.*` chunk options
---

1. Add an ID `#example-a` to the whole chunk.

2. Add line numbers to source blocks via the `.line-numbers` class.

3. Add the class `.round` to the first plot and set its width to 400px.

4. Add two classes `.dark` and `.img-center` to the second plot.

```{r}
#| example-a,
#| attr.chunk = '#example-a',
#| attr.source = '.line-numbers',
#| attr.plot = c('.round width="400"', '.dark .img-center'),
#| fig.alt = c('A scatterplot of rnorm(100) numbers.',
#|   'A sunflower plot of iris.')

plot(rnorm(100), rnorm(100))
i34 = iris[, 3:4]
smoothScatter(i34)
sunflowerplot(i34, add = TRUE)
```

Define CSS rules for the classes in the `#example-a` chunk:

```{css}
#example-a {
  .round { border: solid 1px; border-radius: 50%; }
  .dark  { filter: invert(1); }
  .img-center { display: block; margin: auto; }
}
```

Here is an example of creating a callout (4.5) from a code chunk:

---
title: Create a callout via the option `attr.chunk`
output:
  litedown::html_format:
    meta:
      css: ["@default", "@callout"]
      js: ["@callout"]
---

If you use the class name `.callout-*` on a chunk, you can turn it into a callout, e.g.,

```{r}
#| attr.chunk = '.callout-example'

1 + 1
```

Remember to load the `callout` CSS/JS assets in YAML.

1.2.5.2 cache: speed up the computing

There are only two hard things in Computer Science: cache invalidation and naming things.

—Phil Karlton

You can cache the computing of code chunks via the chunk option cache = TRUE. When a code chunk is cached, the computing will be skipped when the document is compiled on the next time if the source code in the chunk has not changed3 and the external dependencies used by the chunk have not changed, either, otherwise the cache will be invalidated and the results will be re-computed.

The key to understand cache (in)validation is understanding the “external dependencies” of a code chunk. A common dependency is external variables. For example, x is an external (or global) variable and y is an internal (or local) variable in the following chunk:

```{r}
y = x + 1
```

That is because y is defined inside the code chunk, and x must come from elsewhere, otherwise the code chunk will throw an error (“object x not found”).

When cache is enabled on a code chunk, the global and local variables will be automatically inferred from the code. If the value of any global variable has changed, the cache will be invalidated. Local variables will be saved in a current run, and (lazy-)loaded4 in the next run, so they can be available to later code chunks in the document.

Another common dependency is external data sources. For example, if the data file foo.csv has been updated in the following chunk, we may want to invalidate the cache and read the file again:

```{r, cache = TRUE}
z = read.csv('foo.csv')
```

3 illustrates how the cache system works. The code expression, global variables, and other dependencies will be summarized into a hash (a character string), which will be used to check if the cache exists. Any change in the input of the hash will lead to a change in the hash value, which in turn invalidates the old cache and creates new cache.

flowchart TD
  A[[Code chunk]]--"parse()"--> B1>Expression]
  A--"xfun::find_globals()"--> B2>Global variables]
  A--"cache.extra"--> B3>Other dependencies]
  B1--"deparse()"--> C((Hash))
  B2 --> C
  B3 --> C
  C --> D(Find cache)
  D--"No (re-run code)"--> E@{ shape: lin-cyl, label: "New cache" }
  D--"Yes (skip running)"--> F[(Load cache)]--import--> G@{ shape: bow-rect, label: "Local variables" }
  E--"export"--> G

3 The cache (in)validation process.

Besides the option cache, below are other chunk options that allow you to customize the cache system:

A special application of the cache.hash option is to freeze the computation of a code chunk, meaning that the cache will not be affected by any variable, even if the chunk uses global variables. To do this, you can set cache.hash to any non-character constant, e.g., cache.hash = FALSE. Since FALSE is a constant that cannot be changed by any variable, the cache will not be invalidated by changes in any variable.

With great power comes great responsibility. Freezing the cache can make it faster to compile the document, but you may get outdated and/or inconsistent computational results when the old cache should have been invalidated. Please make sure you understand what you are doing before freezing the cache.

1.2.5.3 cap.pos: caption position

Possible values are 'top' and 'bottom'. By default, figure captions are placed below figures, and table captions are placed above tables. 1 shows an example of a table caption at the bottom.

```{r}
#| tab-bottom, cap.pos = 'bottom', tab.env = '.table .box',
#| tab.cap = 'A table caption at the bottom via `cap.pos = "bottom"`.'
head(cars, 4)
speed dist
4 2
4 10
7 4
7 22

1 A table caption at the bottom via cap.pos = "bottom".

```

You can test different positions for both figures and tables in the following example.

---
title: Caption position
---

## Default caption positions

```{r, fig-bottom, fig.cap = 'Bottom figure caption.'}
plot(cars)
```

```{r, tab-top, tab.cap = 'Top table caption.'}
cars
```

## Change the positions

```{r, fig-top, fig.cap = 'Top figure caption.', cap.pos = 'top'}
plot(cars)
```

```{r, tab-bottom, tab.cap = 'Bottom table caption.', cap.pos = 'bottom'}
cars
```

1.2.5.4 child: child documents

The child option can take a vector of file paths to other .Rmd files, which will be compiled and included as the output the current chunk. This offers a way to organize a large R Markdown report as smaller child documents.

1.2.5.5 code and file: alternative ways to provide code

While you can write source code directly in a code chunk, there are two more ways to provide the code through the chunk options:

These options will be ignored (with a warning) if the code chunk is not empty. They can be used in any code chunks (not necessarily R code chunks).

---
title: The `code` option
---

Define a code template `tpl`:

```{r}
tpl    = 'lm(mpg ~ %s, data = mtcars) |> summary() |> coef()'
x_vars = names(mtcars)[2:4]
```

We run regressions on three variables one by one:

```{r, code = sprintf(tpl, x_vars)}
```
.
.
.

``` {.r}
lm(mpg ~ cyl, data = mtcars) |> summary() |> coef()
```
| |Estimate|Std. Error|t value|Pr(>\|t\|)|
|---|--:|--:|--:|--:|
|(Intercept)|37.885| 2.074|18.268| 0.000|
|cyl|-2.876| 0.322|-8.920| 0.000|


``` {.r}
lm(mpg ~ disp, data = mtcars) |> summary() |> coef()
```
| |Estimate|Std. Error|t value|Pr(>\|t\|)|
|---|--:|--:|--:|--:|
|(Intercept)|29.600| 1.230|24.070| 0.000|
|disp|-0.041| 0.005|-8.747| 0.000|


``` {.r}
lm(mpg ~ hp, data = mtcars) |> summary() |> coef()
```
| |Estimate|Std. Error|t value|Pr(>\|t\|)|
|---|--:|--:|--:|--:|
|(Intercept)|30.099| 1.634|18.421| 0.000|
|hp|-0.068| 0.010|-6.742| 0.000|

1.2.5.6 collapse: collapse source code and text output

When the option collapse = TRUE (it is FALSE by default), adjacent source code blocks and verbatim text output blocks will be merged, which can make the output a little more compact, e.g.,

```{r, collapse = TRUE}
x = 1:5
x
#> [1] 1 2 3 4 5
x + 100
#> [1] 101 102 103 104 105
```

Note that if a source block and an output block are not adjacent to each other, they will not be merged, e.g., when there is a warning block between the source and text output.

1.2.5.7 comment: comment out text output

By default, verbatim text output is commented out with a prefix #>. This comment prefix can be set via the chunk option comment, e.g.,

```{r, comment = '#~~> ', print = NA}
1:9
#~~> [1] 1 2 3 4 5 6 7 8 9
matrix(1:9, 3)
#~~>      [,1] [,2] [,3]
#~~> [1,]    1    4    7
#~~> [2,]    2    5    8
#~~> [3,]    3    6    9
```

If you do not want the comment prefix, you may set comment = ''.

```{r, comment = '', print = NA}
matrix(1:9, 3)
     [,1] [,2] [,3]
[1,]    1    4    7
[2,]    2    5    8
[3,]    3    6    9
```

The reason to use comments is that readers will be able to copy multiple code blocks from the chunk output in one go and directly run the copied text as code elsewhere when desired (all text output will be ignored as comments).

1.2.5.8 dev: graphics device

For a code chunk to generate graphics output, it will need a graphics device to record the plots. The device can be set via the dev option. The value can be a function (e.g., svg), a function name as a string (e.g., "svg"), or a string that can be evaluated to a function (e.g., "grDevices::svg").

The default device is cairo_pdf for LaTeX output, and png for other types of output (such as HTML).

The plot file path (specified via the chunk option fig.path) will be passed to the first argument of the device function. The chunk option dev.args can be used to pass a list of additional arguments to the device, and the default list is:

list(units = "in", onefile = FALSE, width = 8, height = 8, res = 84)

Note that the default unit for width and height is inches instead of pixels.

Any argument in dev.args that is not available in a device function will be ignored. For example, the png() device does not have the onefile argument, so it will not be passed to png(), whereas you can use dev.args = list(bg = 'yellow') to pass a custom bg value (yellow background) to png(). Please read the help page of the device function (e.g., ?png) to learn the possible arguments that you can use.

---
title: The graphics device
---

The default (png) device with a higher resolution:

```{r}
#| chunk-a, dev.args = list(res = 96),
#| fig.alt = 'png with a resolution of 96 ppi'

plot(cars)
```

The `svg` device with a background color:

```{r}
#| chunk-b, dev = 'svg', dev.args = list(bg = 'lightyellow'),
#| fig.alt = 'svg with a lightyellow background'

plot(cars)
```

1.2.5.9 echo: visibility of source code

By default, the source code blocks are displayed in the output with the chunk option echo = TRUE. To suppress source code blocks, you can use echo = FALSE.

1.2.5.10 error: error behavior

You can specify how errors in a code chunk should be handled with the error option. Its possible values are:

If a certain error cannot be captured by tryCatch(), the chunk option error = TRUE or FALSE will not work.

Note that in addition to errors in executing the code, syntax errors in the source code can also be captured, e.g.,

```{r, error = TRUE}
x = 1 + 2 +
#> <text>:2:0: unexpected end of input
#> 1: x = 1 + 2 +
#>    ^
```

1.2.5.11 eval: code evaluation

If you do not want to evaluate a certain code chunk, you can use the chunk option eval = FALSE, which is TRUE by default.

1.2.5.12 fig.*: figure options

The fig.* options fall into two categories: one for decorating the images in the output, and the other for customizing the plot files. Chunk options for decoration include:

To avoid omitting the alt text inadvertently, you can set options(litedown.fig.alt = TRUE) in your .Rprofile. When this option is set and the chunk option fig.alt is unset, fuse() will emit reminders about the missing alt text for code chunks containing plots. You can also set this option inside a particular R Markdown document to receive reminders only for that document.

When all these options are provided, the Markdown output of figures will be of this form:

:::: {fig.env}
![fig.alt](fig.path){attr.plot}

:::
fig.cap
:::
::::

When a code chunk generates multiple plots, the options fig.alt and attr.plot (1.2.5.1) are vectorized, i.e., they will be recycled to a length equal to the number of plots, and each value in the vectors will be applied to each plot. For example, for fig.alt = c('aaa', 'bbb'), the first value will be the alt text for the first plot, and the second value is for the second plot.

When fig.cap is provided and a chunk generates multiple plots, all plots will be moved into the same figure environment at the end of the chunk output. As a result, one code chunk can produce at most one figure environment, which may contain one or more plots. If you need multiple figure environments, you have to write separate code chunks.

---
title: Decorating figures
output:
  litedown::html_format:
    meta:
      css: ["@default", "@article"]
      js: ["@sidenotes"]
---

Place two plots side by side via the `width` attribute:

```{r}
#| chunk-a, attr.plot = 'width="45%"',
#| fig.alt = c('a histogram', 'a sunflower plot'),
#| fig.cap = 'Exploring the faithful dataset.',
#| fig.env = '.figure .box'

hist(faithful$eruptions, main = '', border = 'white')
sunflowerplot(faithful)
```

A full-width figure (requires the `@article` CSS):

```{r}
#| chunk-b, fig.dim = c(14, 4), fig.env = '.figure .fullwidth',
#| fig.cap = 'Monthly mean relative sunspot numbers from 1749 to 1983.'
par(mar = c(4, 4, .1, .1), bg = 'lightyellow', fg = 'red', las = 1)
plot(sunspots, col = 'red')
grid()
```

Feel free to experiment with other class names provided by the `@article` CSS, such as `.side .side-right` or `.embed-right` in addition to `.fullwidth`.

Chunk options for plot files include:

---
title: Plot files
---

The default extension for the `jpeg()` device is `jpeg`, and you can change it to `.jpg` if desired:

```{r, chunk-a, dev = 'jpeg', fig.ext = '.jpg'}
plot(cars)
```

Set the plot size via `fig.dim`:

```{r, chunk-b, fig.dim = c(5, 4)}
plot(cars)
```

Write plot files to a different folder:

```{r, chunk-c, fig.path = 'figures/'}
plot(cars)
```

1.2.5.13 include: visibility of whole code chunk

When you want to hide the full output of a code chunk, you can use the option include = FALSE, instead of trying to hide elements individually like echo = FALSE (source), results = 'hide' (text output), message = FALSE, and warning = FALSE, etc.

Note that even with include = FALSE, the code is still executed, unless you also set eval = FALSE.

1.2.5.14 label: chunk label

The chunk label is an identifier of a code chunk. It is used in plot/cache filenames and figure/table cross-references. If two code chunks have the same label, their plots and cache will overwrite each other, which may lead to unexpected output. If one of these chunks does not produce plots/tables or use cache, it is fine for them to use the same label.

If two code chunks share the same label, and one of the chunks is empty, the empty chunk will copy code from the non-empty one.

---
title: Shared chunk labels
---

```{r, chunk-a}
message("This chunk's label is chunk-a")
```

Repeat `chunk-a` but suppress the message:

```{r, chunk-a, message=FALSE}
```
---
title: Shared chunk labels
---


``` {.r}
message("This chunk's label is chunk-a")
```

``` {.plain .message}
#> This chunk's label is chunk-a
```

Repeat `chunk-a` but suppress the message:


``` {.r}
message("This chunk's label is chunk-a")
```

If the label is not provided in a chunk, a label of the form chunk-i will be assigned to the chunk, where i is the chunk number in the document. For example, the labels for the following code chunks will be chunk-a, chunk-2, chunk-js, and project-flowchart:

```{r, chunk-a}
```

```{css}
```

```{js}
//| label: chunk-js
```

```{mermaid, label = 'project-flowchart'}
```

1.2.5.15 message: message behavior

The message option is similar to the error option (1.2.5.10) but is for handling message(). Possible values are:

1.2.5.16 order: order of execution

Code chunks and inline code expressions do not have to be executed sequentially. The chunk option order can be used to customize the order of execution. It takes a numeric value and defaults to the chunk number (e.g., 3 for the 3rd chunk), therefore all chunks are executed in the natural linear order by default.

A lower order value indicates earlier execution of the chunk, and vice versa. If you want to delay the execution of a chunk, assign a higher order value to it. If you want to prioritize the execution, you may assign a lower value.

It is the order of these values that matters, instead of the specific values. For example, if the input contains three chunks in total, order values 1, 1000, and 888 for these chunks will be equivalent to 1, 3, and 2, since the order is calculated via the order() function:

order(c(1, 1000, 888))
#> [1] 1 3 2
order(c(1, 3, 2))  # same order
#> [1] 1 3 2

You may use two variables, i (the chunk number) and N (the total number of chunks), in the order value, which can make it easier to specify the relative order. For example, if a chunk has order = i + 1.5, its next chunk will be executed before it, because the order of the next chunk is i + 1 (unless its order has also been changed), which is smaller than i + 1.5. Without the variable i, you would have to figure out the chunk number by yourself and assign a fixed value like 8.5 in this case.

If you want an earlier chunk to be executed last, you may use order = N + 1. Similarly, to execute a later chunk first, you may use order = 0.

Note that the order option also works for text chunks that contain inline code expressions. To specify the order of a text chunk, set the order option in any inline code expression in the chunk.

In the following example, we execute the first text chunk in the end by setting order = N + 1, so that the variables x and n_cyl will be available (calculated from later chunks), and we move the last chunk one step earlier via order = i - 1.5, so the variable m will be ready for the text chunk before. Without the custom order, this example will either throw errors (objects not found) or use wrong values of these variables (from elsewhere in the session).

---
title: Custom execution order
abstract: "We analyzed `{r, order = N + 1} nrow(x)` `{r} n_cyl`-cylinder cars, with an average MPG of `{r} m`."
---

Subset the data:

```{r}
n_cyl = 8
x = subset(mtcars, cyl == n_cyl)
```

The average MPG `{r} m` is calculated from:

```{r, order = i - 1.5}
m = mean(x$mpg)
```
---
title: Custom execution order
abstract: "We analyzed 14 8-cylinder cars, with an average MPG of 15.1."
---

Subset the data:


``` {.r}
n_cyl = 8
x = subset(mtcars, cyl == n_cyl)
```

The average MPG 15.1 is calculated from:


``` {.r}
m = mean(x$mpg)
```

You can change the value of n_cyl to 4 or 6, re-run the example, and get a new report of cars with 4 or 6 cylinders.

1.2.5.17 print: printing function

In a code chunk, if the value of an expression is visible, it will be printed. You may read the help pages ?invisible and ?withVisible to learn more about the visibility of values.

To print a value, a print function needs to be called. The function can be provided via the chunk option print, which defaults to the S3 generic function xfun::record_print(), with the following methods:

methods(xfun::record_print)
#> [1] record_print.data.frame*  record_print.default*    
#> [3] record_print.knitr_kable* record_print.matrix*     
#> [5] record_print.record_asis* record_print.tbl_df*     
#> see '?methods' for accessing help and source code

These methods are mainly for generating tables from rectangular data objects, such as matrices and data frames.

If a non-function value (such as NA) is passed to the print option, base::print() (or methods::show() for S4 objects) will be called, which will generate text output that you would normally see in the R console. Therefore if you wish to avoid printing data objects to tables, you may use the chunk option print = NA.

The chunk option print.args can be used to pass additional arguments to the print function. It should be of the form list(class_a = args_a, class_b = args_b, ...), where class_x is a class name, and args_x is a list of arguments. If the (first) class name of a visible value in a code chunk is class_x, args_x will be passed to the print function.

In the following example, objects are printed through base::print() (by setting the chunk option print = NA). The argument zero.print = '.' (see ?print.table) is used for table objects, and arguments quote / max.levels (see ?print.factor) are used for factor objects.

---
title: Print objects
---

Print objects with `base::print()`, and use different arguments for different objects.

```{r}
#| print = NA,
#| print.args = list(table = list(zero.print = '.'),
#|   factor = list(quote = TRUE, max.levels = 3))

X = c('a', 'b', 'c', 'c', 'c', 'a')
Y = factor(c('A', 'B', 'C', 'C', 'D', 'E'))
Y  # factor

table(X, Y)  # table
```
.
.
.

```
#> [1] "A" "B" "C" "C" "D" "E"
#> 5 Levels: "A" "B" ... "E"
```

``` {.r}
table(X, Y)  # table
```

```
#>    Y
#> X   A B C D E
#>   a 1 . . . 1
#>   b . 1 . . .
#>   c . . 2 1 .
```

If you do not want to pass arguments by class names but pass a list of arguments to the print function regardless of the object classes, you can wrap the list in I(). For example, print.args = I(list(zero.print = '.')) means that print(..., zero.print = '.') is called to print any objects in the chunk in the above example. However, please note that this “universal” argument list may not work for all print functions or methods—some arguments may be ignored, and some may cause errors if the print function does not have certain arguments. It is often better to define print.args by class names.

1.2.5.18 purl: code extraction

When using litedown::fiss() to extract code from a document, all code chunks are extracted by default. To skip a code chunk, set purl = FALSE for that chunk.

This code chunk is not important for the `fiss()` output.

```{r, setup, purl = FALSE}
litedown::reactor(fig.height = 5)
```

This code chunk will be included in the script.

```{r}
if (TRUE) {
  x = 1 + 1
}
```
if (TRUE) {
  x = 1 + 1
}

You can see that the setup chunk with the option purl = FALSE was omitted in the R script output (generated by fiss()).

1.2.5.19 ref.label: chunk references

A code chunk can reuse code from other code chunks via the option ref.label. It takes a vector of chunk labels, and will copy code from these chunks.

Using chunk references makes it possible to avoid copying code from one chunk to another manually. You can also compose code freely from other chunks.

---
title: Reference chunks by labels
---

We define a function `abs2()`:

```{r, chunk-a, eval = FALSE}
abs2 = function(x)
```

Return `x` if `x >= 0`, otherwise return `-x`:

```{r, chunk-b, eval=FALSE}
  ifelse(x >= 0, x, -x)
```

The full function (combining `chunk-a` and `chunk-b`):

```{r, ref.label = c('chunk-a', 'chunk-b')}
```

See if it works:

```{r}
abs2(c(1, -2, 0, -100))
```
.
.
.
The full function (combining `chunk-a` and `chunk-b`):


``` {.r}
abs2 = function(x)
  ifelse(x >= 0, x, -x)
```

See if it works:


``` {.r}
abs2(c(1, -2, 0, -100))
```

```
#> [1]   1   2   0 100
```

1.2.5.20 results: text output behavior

Text output from code chunks can be shown verbatim, hidden, or just written out as is. The behavior is controlled by the option results, with possible values:

---
title: Text output
---

Default verbatim output:

```{r, test-out}
cat('Hello _world_!\n')
```

Hide output:

```{r, test-out, results = FALSE}
```

Output as is:

```{r, test-out, results = 'asis'}
```
---
title: Text output
---

Default verbatim output:


``` {.r}
cat('Hello _world_!\n')
```

```
#> Hello _world_!
```

Hide output:


``` {.r}
cat('Hello _world_!\n')
```

Output as is:


``` {.r}
cat('Hello _world_!\n')
```
Hello _world_!

1.2.5.21 strip.white: leading / trailing blank lines in code

By default, blank lines at the beginning and end of a source code block are removed. To keep them, you can use the chunk option strip.white = FALSE.

---
title: Blank lines in source code
---

Keep blank lines at the beginning/end:

```{r, strip.white = FALSE}

# a blank line above
1 + 1
# and a blank line below

```
.
.
.

``` {.r}

# a blank line above
1 + 1
```

```
#> [1] 2
```

``` {.r}
# and a blank line below

```

1.2.5.22 tab.*: table options

Unless the default print option (1.2.5.17) is changed, common rectangular data objects are printed to Markdown tables through xfun::record_print(), and tables are generated by a simple function xfun::md_table(), which is even much simpler than knitr::kable().

The chunk option tab.cap can be used to provide the table caption. The option tab.env can be used to customize the attributes (2.2.7) of the table environment, and it defaults to .table, i.e., a class name table. Below is a quick example showing where tab.cap and tab.env are used in the Markdown output:

---
title: A simple table
---

See @tab:simple.

```{r}
#| simple, tab.env = '.table .box',
#| tab.cap = 'First 3 rows of the `cars` data.',
head(cars, 3)
```
.
.
.

``` {.r}
head(cars, 3)
```

:::: {.table .box #tab:simple}

::: {.tab-caption}
[](#@tab:simple)
First 3 rows of the `cars` data.
:::

|speed|dist|
|--:|--:|
|4| 2|
|4|10|
|7| 4|
::::

The position of the caption can be changed via the chunk option cap.pos (1.2.5.3).

To customize the table content, you can pass arguments to xfun::md_table() via the chunk option print.args. Below is an example that limits data frames to at most 4 rows and 6 columns:

```{r}
#| print.args = list(data.frame = list(limit = c(4, 6)))
mtcars
mpg cyl gear am vs
Mazda RX4 21.0 6 4 1 0
Mazda RX4 Wag 21.0 6 4 1 0
Maserati Bora 15.0 8 5 1 0
Volvo 142E 21.4 4 4 1 1
```

By default, tables are limited to 10 rows. If you want to get rid of this limit, you may set options(xfun.md_table.limit = Inf) or limit = Inf in print.args or wrap the data object in I().

Please read the help page ?xfun::md_table to learn the possible arguments, and also see 1.2.5.17 to learn how the chunk option print.args works.

1.2.5.23 time: code timing

When the chunk option time is set to TRUE, the execution time of the chunk will be recorded. You can print out litedown::timing_data() at the end of a document to check the timing data. The data is sorted by default, so you can quickly know which code chunks are slow.

You can time specific code chunks by applying time = TRUE to them, or time all chunks in the document by setting litedown::reactor(time = TRUE) in the first code chunk.

1.2.5.24 verbose: printing verbosity

By default, invisible values are not printed (1.2.5.17). However, you can use the chunk option verbose to change this behavior. Its possible values are:

Below are examples for different values of verbose:

```{r, test-verbose, collapse = TRUE}
1:5
#> [1] 1 2 3 4 5
x = 1 + 1
y = x^2
```
```{r, test-verbose, verbose = 1, collapse = TRUE}
1:5
#> [1] 1 2 3 4 5
x = 1 + 1
y = x^2
#> [1] 4
```
```{r, test-verbose, verbose = 2, collapse = TRUE}
1:5
#> [1] 1 2 3 4 5
x = 1 + 1
#> [1] 2
y = x^2
#> [1] 4
```

In case you do not know, an assignment (e.g., x = 1 + 1) in R returns the value being assigned invisibly, so the value will not be printed by default. You can set verbose = 2 to reveal the value. Normally you would have to explicitly print the variable or use the () trick to make it visible:

x = 1 + 1
x  # print x explicitly
(x = 1 + 1)  # or the () trick

1.2.5.25 warning: warning behavior

The warning option is similar to the error option (1.2.5.10) but is for handling warning(). Possible values are:

1.2.5.26 wd: working directory

The working directory when evaluating code chunks and inline code will be temporarily changed to the directory of the input file to fuse(). If the input is not a file path, the working directory will not be changed.

When using relative paths to read/write files in code chunks, these paths are relative to the directory of the input file by default. I know some people hate this default. It is a matter of thinking inside or outside the (R Markdown) box. I tend to think inside—everything is relative to the document that I’m working inside. It is just a habit (following the conventions of HTML and CSS), not necessarily right or wrong.

You are free to specify any directory as the working directory for the code via the chunk option wd, e.g., wd = '../' (up one level from the directory of the input file) or wd = 'C:/Documents/Project/'. Please note that using a hard-coded absolute directory may affect others if they need to re-run your document on their computers, since the absolute directory may not exist there.

If you have to change the working directory to an absolute directory, you may consider using functions like xfun::proj_root() to dynamically find the absolute directory.

1.3 Inline code

1.3.1 The syntax

The syntax for inline code expressions is `{lang} code`, where lang is the language name, e.g., r. Spaces are allowed after the opening backtick and before the closing backtick. If the code happens to contain N backticks, you will need to use at least N + 1 backticks outside, e.g., ``{r} paste("`", rnorm(1), "`")``. An inline code expression inside another piece of inline code is not parsed or evaluated, which provides a way to write verbatim inline code expressions, e.g., `` `{lang} code` ``.

Comma-separated chunk options can also be provided to inline code expressions after the language name, e.g., `{r, eval = FALSE} code`.

1.3.2 Compatibility with knitr

For knitr users, please note that the syntax `r code` is not supported by default. You have to wrap the language name r in curly braces. As a temporary workaround, you may set options(litedown.enable.knitr_inline = TRUE) in your .Rprofile to make litedown recognize `r code`, but we recommend that you convert the document via litedown:::convert_knitr() instead if you decide to stay with litedown in the long run.

1.3.3 Numeric output

If the inline expression returns a single number, the number will be formatted. To bypass the formatting, wrap the inline expression in I(). We denote the number by \(x\) for now.

1.4 R scripts

Besides R Markdown, you can also pass an R script to fuse(). You can write Markdown content in #' comments, and group lines of code into chunks by #| comments, e.g.,

#' ---
#' title: An R script
#' output:
#'   litedown::latex_format: null
#' ---
#' 
#' A _paragraph_.

#| eval = FALSE
1:10
1 + 1

#| fig.width = 10, dev = 'pdf'
plot(cars)

Both #' and #| comments are optional.

1.5 Language engines

Currently, the main computing language supported by litedown is R. You can check all supported languages via:

sort(names(litedown::engines()))
#> [1] "css"     "embed"   "js"      "md"      "mermaid" "r"      

You can get a language engine definition via litedown::engines('LANG'), where LANG is the language name, e.g.,

litedown::engines('md')

You can also set a new language engine via:

litedown::engines(LANG = function(x, inline = FALSE, ...) {

})

The function’s argument x is an element in the list returned by litedown::crack().

1.5.1 The Markdown engine

The md engine will output Markdown text both verbatim and as-is, which can be useful for showing Markdown examples, e.g.,

```{md}
You can see both the _source_ and _output_ of
this `md` chunk.
```

You can also use `{md} the engine **inline**`.

1.5.2 The CSS/JS engines

You can insert CSS/JS to the output via the css/js engines, e.g.,

```{css}
a {
  color: red;
}
```

```{js}
document.body.classList.add('dark');
```

1.5.3 The Mermaid engine

The mermaid engine can be used to draw diagrams with mermaid.js, e.g.,

```{mermaid, flow-abcd, echo = FALSE, fig.cap = 'A flowchart.'}
flowchart TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;
```

The JS library mermaid.js has to be loaded for the diagrams to be rendered. By default, the latest version of mermaid.js will be added to the js variable (3.3.2.2) in the YAML metadata automatically. If you prefer using a specific version, you can add it manually to the YAML metadata, e.g.,

---
title: "A mermaid diagram"
output:
  litedown::html_format:
    meta:
      js: ["@npm/mermaid@11.3.0/dist/mermaid.min.js"]
---

```{mermaid}

```

1.5.4 The embed engine

The embed engine can be used to embed text files verbatim in the output. You can pass the file paths to the chunk option file or write the paths in the chunk body (with optional quotes), e.g.,

```{embed, file = "foo.txt"}
```

```{embed}
"foo.txt"
```

You can use multiple paths in either case, e.g.,

```{embed, file = c("foo.txt", "bar.R")}
```

```{embed}
"foo.txt"
bar.R
```

The content of the file(s) will be included in a fenced code block in the output. For example, if foo.txt contains a single line “hello world”, the output will be:

``` {.txt}
hello world
```

By default, the language name of the code block is from the file extension. You can use the chunk option attr.output to customize the name, e.g.,

```{embed, file = "foo.txt", attr.output = ".md"}
```

Then the output will be in a Markdown (.md) block:

``` {.md}
hello world
```

You can use the chunk option results = "hide" to hide the output, or results = "asis" to output the file content as is (i.e., not wrapping it in a code block).

1.6 Comparison to knitr

Major differences between knitr and litedown include:

knitr litedown
Supports multiple graphical devices for a chunk. Only supports one device for a chunk (but there are multiple choices for this device).
Depending on certain chunk options, figure output could be both Markdown (![]()) and raw HTML (<img>) / LaTeX (\includegraphics{}). Always use Markdown syntax for figures.
The document parser is based on regular expressions and not robust. Code chunks and inline expressions are not aware of their contexts (e.g., code blocks or comments). The parser is based on commonmark, which is more robust and makes it straightforward to write verbatim code (in a parent code block) or comment out code (in <!-- --> comments).
Supports a large number of chunk options and language engines. Supports a limited number of chunk options and engines.
Inline code does not support options or languages other than R. Inline code supports options and other languages.
All code is executed in the linear order. Code chunks and inline code can be executed in a custom non-linear order defined by the chunk option order.
Supports chunk hooks and output hooks. No hooks at the moment.
The package is more than 12 years old and quite mature. The package is new and still experimental.

If you feel any indispensable features are missing in litedown, please feel free to suggest them in Github issues. However, please remember that the goal of litedown is not to fully re-implement rmarkdown, knitr or Pandoc. Some features may never be re-implemented, especially when the implementation is not simple enough.

Edit this chapter on GitHub

2 Markdown Syntax

All the tired horses in a run; how’m I gonna get any writing done!

Michael Friendly (and Bob Dylan)

2.1 Basic syntax

For the full list of supported document elements, please read the GFM spec. Below is a quick summary:

2.2 Add-on features

In addition to GFM features, the litedown package also supports the following features.

2.2.1 Raw LaTeX/HTML blocks

Raw LaTeX and HTML blocks can be written as fenced code blocks with language names =latex (or =tex) and =html, e.g.,

```{=tex}
This only appears in \LaTeX{} output.
```

Raw LaTeX blocks will only appear in LaTeX output, and will be ignored in other output formats. Similarly, raw HTML blocks will only appear in HTML output. One exception is raw LaTeX blocks that are LaTeX math environments, which also work for HTML output (see the next section).

2.2.2 LaTeX math

You can write both $inline$ and $$display$$ LaTeX math, e.g., \(\sin^{2}(\theta)+\cos^{2}(\theta) = 1\).

$$\bar{X} = \frac{1}{n} \sum_{i=1}^n X_i$$

$$|x| = \begin{cases} x &\text{if } x \geq 0 \\ -x &\text{if } x < 0 \end{cases}$$

For expressions in pairs of single or double dollar signs to be recognized as LaTeX math, there must be no spaces after the opening dollar sign, or before the closing dollar sign. The math expression should either start from the very beginning of a line, or have a space or ( before the opening dollar sign.

Valid examples:

$x + y$
($x + y$)
  $x + y$
text $x + y$ text

$$x + y$$
  $$x + y$$
text $$x + y$$ text
$$x +
  y$$

Invalid examples:

$ x + y$  <- space after the opening `$`
text$x + y$  <- lack of space before the opening `$`
text $x + y$10 text  <- number after closing `$`
$x +
y$  <- multi-line `$ $` expressions
$`x + y`$  <- expression wrapped in backticks

$$x +
  y
$$
^- lack of non-space character before closing `$$`

LaTeX math environments are also supported, e.g., below are an align environment and an equation environment:

\begin{align}
a^{2}+b^{2} & =  c^{2}\\
\sin^{2}(\theta)+\cos^{2}(\theta) & =  1
\label{eq:pyth-identity}
\end{align}

\begin{equation}
  \begin{split}
  (a+b)^2 &=(a+b)(a+b)\\
    &=a^2+2ab+b^2
  \end{split}
\end{equation}

\begin{align} a^{2}+b^{2} & = c^{2}\\ \sin^{2}(\theta)+\cos^{2}(\theta) & = 1 \label{eq:pyth-identity} \end{align}

\begin{equation} \begin{split} (a+b)^2 &=(a+b)(a+b)\\ &=a^2+2ab+b^2 \end{split} \end{equation}

These math environments can be written as either nake LaTeX code or raw LaTeX blocks (```{=latex}), but we recommend that you use raw LaTeX blocks because they are more robust. LaTeX math environments work for both LaTeX and HTML output.

For HTML output, it is up to the JavaScript library (MathJax or KaTeX) whether a math environment can be rendered (3.1.6).

2.2.3 Superscripts and subscripts

Write superscripts in ^text^ and subscripts in ~text~ (same syntax as Pandoc’s Markdown), e.g., 210 and H2O. Currently only alphanumeric characters, *, (, and ) are allowed in the scripts. For example, a^b c^ will not be recognized as a superscript (because the space is not allowed). Note that GFM supports striking out text via ~text~, but this feature has been disabled and replaced by the feature of subscripts in litedown. To strike out text, you must use a pair of double tildes.

2.2.4 Footnotes

Insert footnotes via [^n], where n is a footnote number (a unique identifier). The footnote content should be defined in a separate block starting with [^n]:. For example:

Insert a footnote here.[^1]

[^1]: This is the footnote.

The support is limited for LaTeX output at the moment,6 and there are two caveats if the document is intended to be converted to LaTeX:

The two limitations do not apply to HTML output, e.g., you can write arbitrary elements in footnotes and not necessarily one paragraph.

2.2.5 Attributes

Attributes on images, links, fenced code blocks, and section headings can be written in {}. ID and class are two common attributes. An ID can be written after the # character, and a class can be written after . . Attributes are typically written in the form name="value", and separated by spaces (in fact, you can also write IDs and classes explicitly like other attributes, e.g., id="foo" class="bar"). Certain attributes do not require values, and you can provide the attribute name only, e.g., disabled or contenteditable, although it is harmless to write disabled="true".

Only lowercase letters (a-z), digits (0-9), hyphens (-), and colons (:) are allowed in ID and class strings. For example, sec:intro and fig-cars are valid IDs, but sec_intro and tab cars are not.

For example, ![text](path){.foo #bar width="50%"} will generate an <img> tag with attributes in HTML output:

<img src="path" alt="text" id="bar" class="foo" width="50%" />

and ## Heading {#baz} will generate:

<h2 id="baz">Heading</h2>

Links of the form [text](url){...} will generate:

<a href="url" ...></a>

When the url is empty, <a> will be converted to <span>, e.g., [text](){.foo .bar} will generate:

<span class="foo bar">text</span>

This provides a way to create <span> elements, which is similar to bracketed Spans (i.e., [text]{...}) in Pandoc’s Markdown.

For fenced code blocks, a special rule is that the first class name will be treated as the language name for a block, and the class attribute of the result <code> tag will have a language- prefix. For example, the following code block

```{.foo .bar #my-code style="color: red;"}
```

will generate the HTML output below:

<pre>
  <code class="language-foo bar" id="my-code" style="color: red;">
  </code>
</pre>

Most attributes in {} are ignored for LaTeX output except for:

2.2.6 Appendices

When a top-level heading has the attribute .appendix, the rest of the document will be treated as the appendix. If section numbering is enabled (3.1.9), the appendix section headings will be numbered differently.

2.2.7 Fenced Divs

A fenced Div can be written in ::: fences. Note that the opening fence must have at least one attribute, such as the class name. For example:

::: foo
This is a fenced Div.
:::

::: {.foo}
The syntax `::: foo` is equivalent to `::: {.foo}`.
:::

::: {.foo #bar style="color: red;"}
This div has more attributes.

It will be red in HTML output.
:::

A fenced Div will be converted to <div> with attributes in HTML output, e.g.,

<div class="foo" id="bar" style="color: red;">
</div>

For LaTeX output, it can be converted to a LaTeX environment if both the class name and an attribute data-latex are present. For example,

::: {.tiny data-latex=""}
This is _tiny_ text.
:::

will be converted to:

\begin{tiny}
This is \emph{tiny} text.
\end{tiny}

The data-latex attribute can be used to specify arguments to the environment (which can be an empty string if the environment doesn’t need an argument). For example,

::: {.minipage data-latex="{.5\linewidth}"}

will be converted to:

\begin{minipage}{.5\linewidth}

The data-latex attribute is optional for fenced Divs with class names figure or table. They will be converted to figure or table environments. For example,

:::: {.figure}
![](foo.png)

::: {.fig-caption}
This is a caption.
:::
::::

will be converted to:

\begin{figure}
  \includegraphics{foo.png}
  \caption{This is a caption.}
\end{figure}

Other fenced Divs will be ignored if they don’t have the data-latex attribute, and their inner content will be written out normally without a surrounding environment.

If a fenced Div has multiple class names (e.g., {.a .b .c}), only the first class name will be used as the LaTeX environment name. However, all class names will be used if the output format is HTML (e.g., <div class="a b c">).

2.2.8 Cross-references

To cross-reference an element, it must be numbered first. Then we can refer to it by its ID.

2.2.8.1 Sections, figures, and tables

Section heading IDs can be either manually assigned or automatically generated (3.1.1). Section numbers are automatically generated if the number_sections option is true (3.1.9).

Figures and tables are automatically numbered if their captions are provided (via the chunk options fig.cap / tab.cap), e.g.,

```{r}
#| nice-plot, fig.cap = "A nice caption"

plot(cars)
```

To refer to an element in the text, use the syntax @ID, where ID is the ID of the element to be referenced, which typically consists of a prefix (e.g., sec:, fig:, tab:, or eq:) and a label. For example:

Please see @fig:nice-plot for an overview of the `cars` data.

Hyphens (-) are also allowed in place of colons in the ID prefix, e.g., @fig-nice-plot.

2.2.8.2 LaTeX equations

LaTeX math environments such as align and equation are numbered by default. To refer to an expression (e.g., an equation) in a math environment, a label needs to be assigned to the expression first via \label{}, and it must start with the prefix eq: or eq-, e.g.,

```{=latex}
\begin{equation}
\sin^2(x) + \cos^2(x) = 1 \label{eq:pyth-identity}
\end{equation}
```

Then we can use either @eq:* or @eqn:* to cross-reference the equation, e.g., @eqn:pyth-identity. Under the hood, the prefix @eq will be resolved to \ref{}, and @eqn will be resolved to \eqref{}. If you are not familiar with LaTeX commands \ref{} and \eqref{}, the main difference is that \eqref{} will render the equation number in parentheses, e.g., \(\eqref{eq:pyth-identity}\), whereas \ref{} will only generate the equation number, e.g., \(\ref{eq:pyth-identity}\).

In HTML output, \eqref{} will also add a label “Equation” before the number by default. If you prefer writing the label manually and having control over the parentheses, you can use @eq: instead of @eqn:, e.g., Eq. [@eq:*] (using the label “Eq.” and square brackets).

In addition to equation numbers, you can also specify a tag for an equation via \tag{} and refer to the equations by its tag, e.g.,

For a right-angled triangle, we are all familiar with @eqn:pyth-theorem.

\begin{equation}
a^2 + b^2 = c^2 \label{eq:pyth-theorem} \tag{PT}
\end{equation}

For a right-angled triangle, we are all familiar with \(\eqref{eq:pyth-theorem}\).

\begin{equation} a^2 + b^2 = c^2 \label{eq:pyth-theorem} \tag{PT} \end{equation}

2.2.8.3 Arbitrary elements

You can cross-reference any other type of elements by adding empty anchors of the form [](#@ID) into them, e.g.,

::: {style="background: ghostwhite; padding: 1px 1em;"}
[](#@blk:example) This is a numbered block.
:::

We can cross-reference @blk:example.

1 This is a numbered block.

We can cross-reference 1.

For HTML output, we can style the numbers and references with CSS. Element numbers are wrapped in <span class="ref-number-*"></span>, and references are wrapped in <span class="cross-ref-*"></span>, where * is the ID prefix (e.g., fig, tab, and eqn). For example, we can add the label “Block” to the number and reference of the block above:

.ref-number-blk::before, .cross-ref-blk::before {
  content: "Block ";
}
.ref-number-blk {
  font-weight: bold;
  font-style: italic;
}
.ref-number-blk::after {
  content: ". "
}

2.2.9 Citations

This feature requires the R package rbibutils. Please make sure it is installed before using citations.

xfun::pkg_load2("rbibutils")

To insert citations, you have to first declare one or multiple bibliography databases in the YAML metadata, e.g.,

bibliography: ["papers.bib", "books.bib"]

Each .bib file contains entries that start with keywords. For example, R-base is the keyword for the following entry:

@Manual{R-base,
  title = {R: A Language and Environment for Statistical Computing},
  author = {{R Core Team}},
  organization = {R Foundation for Statistical Computing},
  address = {Vienna, Austria},
  year = {2024},
  url = {https://www.R-project.org/},
}

Then you can use [@R-base] or @R-base to cite this item. The keywords must consist of only alphanumeric characters (a-z, A-Z, 0-9) and -. You can include multiple keywords in [ ] separated by semicolons.

For HTML output, the citation uses the author-year style. The syntax [@keyword] generates the citation in parentheses (e.g., (Author, 2024)), and @keyword only puts the year in parentheses (e.g., Author (2024)).

For LaTeX output, the citation style depends on the LaTeX package, which you can set via the citation_package option of the output format in YAML metadata, e.g.,

output:
  litedown::latex_format:
    citation_package: biblatex

The default is natbib. The table below shows the LaTeX commands corresponding to the Markdown citation syntax:

citation package [@key-1; @key-2] @key
none \cite{key-1, key-2} \cite{key}
natbib \citep{key-1, key-2} \citet{key}
biblatex \parencite{key-1, key-2} \cite{key}

Note that litedown actually generates \citep and \citet regardless of the citation package, but will redefine them to \cite or \parencite according to the citation package. For example, it will insert \let\citep\parencite in the LaTeX preamble when citation_package is biblatex.

2.2.10 Smart HTML entities

“Smart” HTML entities can be represented by ASCII characters, e.g., you can write fractions in the form n/m. Below are some example entities:

1/2 1/3 2/3 7/8 1/7 1/9 1/10 (c) (r) (tm)
½ © ®

2.3 Comparison to Pandoc

As mentioned earlier, a lot of features in Pandoc’s Markdown are not supported in the litedown package. Any feature that you find missing in previous sections is likely to be unavailable. In addition, a lot of R Markdown and Quarto (both are based on Pandoc) features are not supported, either. Some HTML features have been implemented via JavaScript and CSS.

Pandoc can convert Markdown to many output formats, such as Word, PowerPoint, LaTeX beamer, and EPUB. The litedown package is unlikely to support output formats beyond HTML and LaTeX.

Edit this chapter on GitHub

3 Markdown Rendering

The main function to convert Markdown to other formats is litedown::mark().

You can either call litedown::mark() to render a Markdown document programmatically, or click the Knit button in RStudio to render a (Markdown or R Markdown) document interactively. The latter requires you to specify the output format in the output field in YAML metadata (3.3), e.g.,

---
output:
  litedown::html_format:
    options:
      js_math:
        package: "katex"
        version: "0.16.4"
      number_sections: true
      embed_resources: ["local", "https"]
    meta:
      css: "custom.css"
---

3.1 Markdown options

The options argument of mark() can be used to enable/disable/set options to control Markdown rendering. This argument can take either a list, e.g., list(toc = TRUE, smart = FALSE), or a character vector, e.g., c("+toc", "-smart"), or equivalently, +toc-smart, where + means to enable an option, and - means to disable an option. The options can also be set in YAML metadata in 3.3 (recommended). Available options are listed below.

3.1.1 auto_identifiers

Add automatic IDs to headings, e.g.,

# Hello world!

## Introduction

will be converted to

<h1 id="chp:hello-world">Hello world!</h1>

<h2 id="sec:introduction">Introduction</h2>

The prefix chp: (chapter) will be added to the automatic IDs of level-one headings, and sec: (section) will be added for other levels of headings.

You can override the automatic ID by providing an ID manually via the ID attribute, e.g.,

# Hello world! {#hello}

An automatic ID is generated by substituting non-alphanumeric characters in the heading text with hyphens. If the result is empty, the ID will be section. If any ID is duplicated, a numeric suffix will be added to the ID, e.g., example_1 and example_2.

3.1.2 cleveref

Whether to use the LaTeX package cleveref for “clever” cross-references (2.2.8). This option is for LaTeX output only. If enabled, cleveref will be loaded and references will use the command \cref{} instead of \ref{}, which will automatically add the type of reference before the reference number, e.g., \cref{sec:intro} may generate Section 1, so you do not have to write Section \ref{sec:intro}.

3.1.3 embed_cleanup

Whether to clean up plot files after they have been embedded in HTML output (see 3.1.4).

3.1.4 embed_resources

Embed resources (images, CSS, and JS) in the HTML output using their base64-encoded data (images) or raw content (CSS/JS). Possible values are:

The default is "local", i.e., local resources are embedded, whereas https resources are not. This means the output document may not work offline. If you have to view the output offline, you need to use the option value "https" (or "all") and render the document at least once before you go offline.

3.1.5 js_highlight

Specify the JavaScript library to syntax highlight code blocks. Possible values are highlight (highlight.js) and prism (Prism.js). The default is prism. This option can also take a list of the form list(package, version, style, languages), which specifies the package name (highlight or prism), version, CSS style/theme name, and names of languages to be highlighted.

By default, languages are automatically detected and the required JS files are automatically loaded. Normally you need to specify the languages array only if the automatic detection fails.

Technically this option is a shorthand for setting the metadata variables css and js in 3.3. If you want full control, you may disable this option (set it to false or null) and use metadata variables directly, which requires more familiarity with the JS libraries and the jsdelivr CDN.

3.1.6 js_math

Specify the JavaScript library for rendering math expressions in HTML output. Possible values are "mathjax" and "katex" (the default). Like the js_highlight option, this option is also essentially a shorthand for setting the metadata variables css and js.

If you want finer control, you can provide a list of the form list(package, version, css, js). This will allow you to specify the package name, version, and css/js files. For example, if you want to use MathJax’s tex-chtml.js instead, you may set:

js_math:
  package: mathjax
  version: 3
  js: es5/tex-chtml.js

By default, MathJax version 3 is used. If you want to use the older v2, you may set:

js_math:
  package: mathjax
  version: 2
  js: MathJax.js?config=TeX-AMS-MML_CHTML

Please visit the MathJax CDN to know which versions and JS files are available.

For KaTeX, the version is not specified by default, which means the latest version from the CDN. Below is an example of specifying the version 0.16.4 and using the mhchem extension:

js_math:
  package: katex
  version: 0.16.4
  js: [dist/katex.min.js, dist/contrib/mhchem.min.js]

Note that if you want the HTML output to be self-contained via the embed_resources option, KaTeX can be embedded and used offline, but MathJax cannot be fully embedded due to its complexity. MathJax v3 can be partially embedded and used offline, but currently only its fonts can be embedded, and extensions cannot. If you must view HTML output offline, we recommend using KaTeX, but please also note that KaTeX and MathJax do not fully cover each other’s features.

3.1.7 keep_yaml

Whether to keep the YAML metadata in the output. When TRUE, the original YAML in the Markdown input (if exists) will be written to the output. Note that when this option is enabled, templates (3.2) will be disabled and mark() will only generate a document fragment. This option was introduced mainly for Hugo websites to use litedown instead of Hugo’s Markdown engines to render pages.

3.1.8 latex_math

Whether to identify LaTeX math expressions in pairs of single ($ $) or double dollar signs ($$ $$), and transform them so that they could be correctly rendered by MathJax (HTML output) or LaTeX.

3.1.9 number_sections

Whether to number section headings. To skip numbering a specific heading, add a class attribute .unnumbered (or use the shorthand -) to it. For example:

## Preface {.unnumbered}

## About the author {-}

3.1.10 smartypants

Whether to translate certain ASCII strings into smart typographic characters (see ?litedown::smartypants).

3.1.11 superscript

Whether to translate strings between two carets into superscripts, e.g., text^foo^ to text<sup>foo</sup>.

3.1.12 subscript

Whether to translate strings between two tildes into subscripts, e.g., text~foo~ to text<sub>foo</sub>.

3.1.13 toc

Whether to generate a table of contents (TOC) from section headings. If a heading has an id attribute, the corresponding TOC item will be a link to this heading. You can also set a sub-option:

To exclude a certain heading from the TOC, assign a class name unlisted to it. For example:

## Acknowledgments {.unlisted}

3.1.14 top_level

The desired type of the top-level headings in LaTeX output. Possible values are 'chapter' and 'part'. For example, if top_level = 'chapter', # heading will be rendered to \chapter{heading} instead of the default \section{heading}.

Options not described above can be found on the help pages of commonmark, e.g., the hardbreaks option is for the hardbreaks argument of commonmark::markdown_*() functions, and the table option is for the table extension in commonmark’s extensions.

litedown::markdown_options()
#>  [1] "-cleveref"         "-hardbreaks"       "-number_sections" 
#>  [4] "-smartypants"      "-tagfilter"        "-toc"             
#>  [7] "+auto_identifiers" "+autolink"         "+cross_refs"      
#> [10] "+embed_cleanup"    "+embed_resources"  "+js_highlight"    
#> [13] "+js_math"          "+latex_math"       "+smart"           
#> [16] "+strikethrough"    "+subscript"        "+superscript"     
#> [19] "+table"            "+tasklist"        
# commonmark's arguments
opts = formals(commonmark::markdown_html)
opts = opts[setdiff(names(opts), c('text', 'extensions'))]
unlist(opts)
#> hardbreaks      smart  normalize  sourcepos  footnotes 
#>      FALSE      FALSE      FALSE      FALSE      FALSE 
# commonmark's extensions
commonmark::list_extensions()
#> [1] "table"         "strikethrough" "autolink"      "tagfilter"    
#> [5] "tasklist"     

3.2 Templates

By default, mark() generates a document fragment (i.e., the body) if the input does not contain YAML metadata at the beginning. To generate a full document, you need to specify YAML metadata. A full document is generated with a template. Below is a simple HTML template example:

<html>
  <head>
    <title>$title$</title>
  </head>

  <body>
  $body$
  </body>
</html>

It contains two variables, $title$ and $body$. All variables will be substituted by metadata values, except for $body$, which is from the body of the input document (after conversion to a target output format).

The litedown has provided default templates for HTML and LaTeX output. To pass metadata to templates, use the meta argument, e.g.,

litedown::mark(..., meta = list(title = "My Title"))

If you want to use a custom template file, you can set the path in the global option litedown.FORMAT.template (where FORMAT is the output format name (html or latex), e.g., in .Rprofile:

options(litedown.html.template = 'path/to/my/template.html')

The global option will be applied to all documents to be converted by mark(). Alternatively, you can pass a template path to the template argument of the output format litedown::html_format or litedown::latex_format in an individual document, e.g.,

---
output:
  litedown::html_format:
    template: "path/to/my/template.html"
---

The template path can also take a logical value: TRUE means to use the default template, and FALSE means to generate only a fragment document without using any template.

3.3 YAML metadata

Alternatively, the meta argument can read YAML metadata in the Markdown document.

3.3.1 Top-level variables

The following variables can be set in the top-level fields in YAML:

For example:

---
title: "My Title"
author: "[Frida Gomam](https://example.com)"
date: "2023-01-09"
---

The values are treated as Markdown text, and will be converted to the target output format before being passed to the template. For the above example, the variable $author$ will be <a href="https://example.com">Frida Gomam</a> in the HTML template.

For top-level variables, mark() will also create the “underscore” versions for templates, which contain HTML and LaTeX markups. For example, for the $title$ variable, $title_$ will also be available to the template. The following table shows the values of the underscore variables, assuming the original value of a variable is TEXT:

Variables HTML LaTeX
$title_$ <div class="title"> <h1>TEXT</h1> </div> \title{TEXT}
$subtitle_$ <div class="subtitle"> <h2>TEXT</h2> </div> \subtitle{TEXT}
$author_$ <div class="author"> <h2>TEXT</h2> </div> \author{TEXT}
$date_$ <div class="date"> <h3>TEXT</h3> </div> \date{TEXT}
$abstract_$ <div class="abstract"> <p>TEXT</p> </div> \begin{abstract} TEXT \end{abstract}

If an original variable is empty or missing, its underscore version will also be empty. For the $author_$ variable, if the $author$ variable contains multiple author names as an array, each name will be in a separate <h2> in HTML output, and all names will be concatenated by \and in LaTeX output, e.g., for

author: ["Jane X", "John Y"]

in YAML, the HTML output will be:

<div class="author">
  <h2>Jane X</h2>
  <h2>John Y</h2>
</div>

and the LaTeX output will be:

\author{Jane X \and John Y}

If you design your own template, you are free to use either the original or the underscore versions of these variables. For example, you could put the title in an <h1> without the <div> wrapper via <h1 class="title">$title$</h1> instead of using $title_$.

When these top-level variables are also provided as meta variables for an output format, the latter will override the former, e.g.,

title: "Global Title"
output:
  litedown::html_format:
    meta:
      title: "Title for HTML output"
  litedown::latex_format:
    meta:
      title: "Title for LaTeX output"

3.3.2 Format-specific variables

Other variables need to be specified under output -> litedown::*_format -> meta, where * can be html or latex, e.g.,

---
title: "My Title"
output:
  litedown::html_format:
    meta:
      css: "style.css"
      js: "script.js"
  litedown::latex_format:
    meta:
      documentclass: "book"
      header_includes: "\\usepackage{microtype}"
---

The following metadata variables are supported for both HTML and LaTeX templates:


The following variables are for HTML templates:

3.3.2.1 The css variable

A vector of CSS files to be included in the output. The default value is litedown:::pkg_file('resources', 'default.css').

If you want to use built-in CSS files in this package, you can only specify the base name, e.g., default means default.css in this package.

You can also use web resources, e.g., https://example.org/style.css. One special case is jsdelivr resources: if a css value starts with @, it will be recognized as a jsdelivr.com resource. if you are not familiar with jsdelivr, you may read its documentation to understand the following example URLs. The shorthand syntax is as follows (CDN stands for https://cdn.jsdelivr.net):

This provides a way to reduce the output HTML file size by loading CSS from the web instead of embedding inside HTML, at the cost of requiring Internet connection when viewing the HTML file. If you need the external web resources to work after you go offline, you can enable "https" in the Markdown option embed_resources in advance to embed the resources.

3.3.2.2 The js variable

A vector of JavaScript files to be included in the output. The syntax is the same as the css variable, e.g., snap means snap.js in this package, @snap means a “jsdelivr” resource, and you can use arbitrary paths or URLs to other JS files.

3.3.2.3 The body-class variable

A class name for the main body (the default value is body).


The following variables are for LaTeX templates:

3.3.2.4 The classoption variable

A string containing options for the document class.

3.3.2.5 The documentclass variable

The document class (by default, article).

3.3.3 Naming convention of variables

A variable name must consist of alphanumeric characters and hyphens only. You could use underscores in variable names in the metadata, but please note that underscores will be normalized to hyphens internally, e.g., header_includes will be converted to header-includes. This means if you use a custom template, you must use hyphens instead of underscores as separators in variable names in the template.

3.3.4 Using custom variables

The above are variables supported in the default templates. If you use a custom template, you can use arbitrary variable names following the naming convention (except for body, which is a reserved name and cannot be used in metadata), and the values of variables in the metadata will be passed to your template.

3.3.5 Setting options in YAML

Besides metadata variables, the aforementioned Markdown options (3.1) can also be set in YAML under output -> litedown::*_format -> options, e.g.,

output:
  litedown::html_format:
    options:
      toc: true
      js_highlight:
        package: highlight
        theme: github
        languages: [diff, latex]

3.3.6 Other fields in YAML

See the help page ?litedown::html_format for possible fields in addiction to meta and options that can be specified under the format name, e.g.,

output:
  litedown::latex_format:
    latex_engine: xelatex
    keep_md: true
    template: custom-template.tex

Edit this chapter on GitHub

4 CSS/JS assets

The litedown package aims at lightweight with a minimal number of features at its core, but you can add more features by yourself. In this chapter, we introduce some CSS/JS assets from the GitHub repository https://github.com/yihui/misc.js. You can load arbitrary external JS and CSS files via the js and css meta variables. There are numerous JS libraries and CSS frameworks on the web that you can use, and you do not have to use the ones mentioned in this chapter. You can also write CSS/JS by yourself to enhance your HTML applications.

Remember that the CSS and JS are introduced under the output format litedown::html_format in YAML metadata, e.g.,

---
output:
  litedown::html_format:
    meta:
      css: ["one.css", "two.css"]
      js: ["three.js", "four.js"]
---

For the sake of brevity, we will omit the full YAML fields in examples throughout this chapter but only use the css and js fields. A file name foo.js denotes the file under the js/ directory of the aforementioned yihui/misc.js repository. Similarly, foo.css is under the css/ directory.

All these CSS/JS resources can be used offline, and there are two ways to do it. One way is to clone the GitHub repo to your working directory, and use the files you need, e.g.,

js: ["repo/path/js/callout.js"]

Another way is to enable embedding resources:

output:
  litedown::html_format:
    meta:
      js: ["@callout"]
    options:
      embed_resources: ["https"]

After you have embedded CSS/JS resources once when you have Internet access, these resources will be cached locally and will not require an Internet connection again. This solution works for any online resources, not limited to the yihui/misc.js repository.

4.1 HTML slides

With snap.css and snap.js, you can create lightweight HTML slides:

css: ["@default", "@snap"]
js: ["@snap"]

You can learn more in vignette('slides', package = 'litedown').

4.2 HTML articles

You can style an HTML page in an article format via the following CSS and JS:

css: ["@default", "@article"]
js: ["@sidenotes", "@appendix"]

The article.css is mainly for styling the article frontmatter, body, and side content.

The web version of this book is also based on the article format, so you know what an article format looks like when you read the HTML version of the book.

4.2.1 The overall style

The maximum width of the article body is 800px. For larger screens, this means there will be extra space in the left/right margin, where we can place auxiliary information, such as the TOC and footnotes. On smaller screens, the side content will be collapsed into the body.

The article frontmatter, body, and optionally the appendix are placed in separate boxes.

The default typeface is sans-serif, and you can customize it by supplying an external CSS file (via the css meta variable) or just embedding CSS in the document body, e.g.,

```{css, echo = FALSE}
body {
  font-family: Palatino, "Book Antiqua", Georgia, serif;
  font-size: 1em;
}
```

4.2.2 Side elements

The TOC and footnotes are automatically placed in the margin if space permits. You can also write arbitrary content in the margin via a fenced Div.

4.2.2.1 The TOC

The TOC is sticky on the left side as you scroll down the article. If you do not like this behavior, you may cancel it via CSS:

#TOC {
  top: unset;
}

4.2.2.2 Footnotes

Footnotes are moved to the right side. When you move your cursor over a footnote number in the body, the footnote will be moved next to your cursor. This can be convenient when you have multiple footnotes on a line, since you do not need to look for a specific footnote in the margin.

4.2.2.3 Arbitrary sidenotes

You can write anything in the margin by using a fenced Div with the classes .side and .side-left or .side-right.

Notice

Here is a note on the left side. Anything permitted by law is permitted here. Math? No problem!

$$e^{i\theta}=\sin{\theta}+i\cos{\theta}$$

When you have this sidenote “hammer”, I’m sure you will hit a lot of nails into the margin, even if you do not have to.

::: {.side .side-left}
**Anything** on the left.
:::
::: {.side .side-right}
_Anything_ on the right.
:::

4.2.3 Body elements

Inside the article body, you can write a few special elements.

4.2.3.1 Full-width elements

When an element is wider than the article body, you can show it in its full width by enclosing the element in a fenced Div with the class .fullwidth, e.g.,

::: {.fullwidth}
![text](path/to/image)
:::

Sunspots

If you use R Markdown, you can generate a wide plot or table from an R code chunk, e.g.,

::: {.fullwidth}
```{r}
#| sunspots, echo = FALSE, fig.dim = c(14, 4),
#| fig.cap = 'Monthly mean relative sunspot numbers from 1749 to 1983.'
par(mar = c(4, 4, .1, .1), bg = 'lightyellow', fg = 'red', las = 1)
plot(sunspots, col = 'red')
grid()
```
:::

If you want to show code (echo = TRUE) but do not want the code to be in the full-width container, you can apply the .fullwidth class to the plot only, e.g.,

```{r}
#| sunspots, fig.dim = c(14, 4), fig.env = '.fullwidth .figure .box',
#| fig.cap = 'Monthly mean relative sunspot numbers from 1749 to 1983.'
par(mar = c(4, 4, .1, .1), bg = 'lightyellow', fg = 'red', las = 1)
plot(sunspots, col = 'red')
grid()
```

4.2.3.2 Left/right quotes

Whenever you find that you are on the side of the majority, it is time to pause and reflect.

Mark Twain

Sometimes you may want to add a quote but do not want it to take the full width in the body. You may use a fenced Div with the class .quote-left or .quote-right.

Despite the class names, the content does not have to be a quote. If you do want a quote, just use the blockquote syntax >, e.g.,

::: {.quote-right}
> This is a boring quote.
>
> ---Someone
:::

4.2.3.3 Margin embedding

You can embed elements on the left or right margin using a fenced Div with the class .embed-left or .embed-right. These elements will float to the left or right and exceed the margin by about 200px, which can save some space in the article body. You can use the extra space to explain the embedded element with text.

mpg cyl disp hp drat wt qsec vs
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1

We have embedded a table of the first 4 rows of the mtcars data on the right margin, which you can see if the browser window is wide enough.

4.3 Tabbed sections

You can load the script tabsets.js and CSS tabsets.css to create tabsets from sections (see documentation here).

css: ["@tabsets"]
js: ["@tabsets"]

4.4 Code folding

Code folding is supported by fold-details.js (see documentation here).

js: ["@fold-details"]

4.5 Callout blocks

A callout block is a fenced Div with the class name callout-*. Callouts require callout.css and callout.js:

css: ["@callout"]
js: ["@callout"]

For example:

::: callout-tip
This is a tip.

> You can write arbitrary content, such as a blockquote.

You can even _nest another callout_ into this one!

Is that cool?
:::

This is a tip.

You can write arbitrary content, such as a blockquote.

You can even nest another callout into this one!

Is that cool?

4.5.1 Built-in callouts

The stylesheet callout.css supports styling tip, note, caution, warning, important, and example callouts. For example:

Thank you for your notice! Your notice has been noted.

Be careful when testing for strict equality of floating point numbers.

seq(0, 1, .2) == c(0, .2, .4, .6, .8, 1)
#> [1]  TRUE  TRUE  TRUE FALSE  TRUE  TRUE

Never try to out-stubborn a cat!

For the sake of reproducibility, please remember to render an R Markdown document in a new R session before publishing or submitting the output document.

::: {.callout-example data-legend="Demo"}
Change the title of this example to "Demo".
:::

Change the title of this example to “Demo”.

4.5.2 Customizing callouts

You do not have to use callout.css but can define your own CSS rules, e.g.,

.callout-important {
  background: red;
  color: yellow;
}

Under the hood, callout.js turns the fenced Div into a <fieldset> for the form:

<fieldset class="callout-*">
  <legend>Title</legend>

  Content.
</fieldset>

The content comes from the original fenced Div. The title comes from the class name (converted to uppercase) by default. You can provide a custom title via the data-legend attribute of the Div, e.g.,

::: {.callout-tip data-legend="Information"}
:::

The icons before the callout titles can be defined via CSS, e.g., you can add two exclamation marks before the title of important callouts:

.callout-important legend::before {
  content: "!! ";
}

The default icons defined in callout.css are essentially UTF-8 characters. In theory, there are hundreds of thousands of characters that you can choose from. Each character is 1 to 4 bytes. For example, you can define a music callout with the music note symbol ♫ in the CSS:

.callout-music {
  background: springgreen;
  border-color: yellow;
}
.callout-music legend::before {
  content: "♫ ";
}

Then you can insert a music callout in your document:

::: callout-music
Please listen to this lovely song.
:::

Alternatively, if you are using callout.css, you can also use CSS variables to define the border color, background, and icon of a callout, e.g.,

.callout-music {
  --callout-background: springgreen;
  --callout-border: yellow;
  --callout-icon: "♫ ";
}

You can use the script right-quote.js to right-align a blockquote footer if it starts with an em-dash (---).

js: ["@right-quote"]

The CSS is necessary only if you want to hide the anchors by default and reveal them on hover.

css: ["@heading-anchor"]
js: ["@heading-anchor"]

4.8 Style keyboard shortcuts

The script key-button.js identifies keys and the CSS styles them, which can be useful for showing keyboard shortcuts.

css: ["@key-buttons"]
js: ["@key-buttons"]

Of course, you can combine any number of JS scripts and CSS files if you want multiple features.

4.9 Include arbitrary CSS/JS

You can include arbitrary CSS frameworks and JS libraries in your documents, not limited to aforementioned ones in the GitHub repository yihui/misc.js. Below is a quick example of using the DataTables library to create interactive tables:

---
title: Display Tables with the JS Library DataTables
output:
  litedown::html_format:
    meta:
      css: ["@default", "@npm/datatables/media/css/jquery.dataTables.min.css"]
      js: ["@npm/jquery/dist/jquery", "@npm/datatables/media/js/jquery.dataTables.min.js"]
---

::: {#mtcars-table}
```{r}
I(mtcars)
```
:::

```{js}
window.addEventListener('load', () => {
  $('#mtcars-table table').DataTable();
});
```

Edit this chapter on GitHub

5 Authoring

5.1 The Knit button

If you use the RStudio IDE, the Knit button can render R Markdown to a litedown output format specified in YAML (e.g., litedown::html_format or litedown::latex_format). Please also remember to add a top-level setting knit: litedown:::knit in YAML, otherwise litedown will throw an error as a reminder.

---
output:
  litedown::html_format: null
  litedown::latex_format: null
knit: litedown:::knit
---

5.2 Live preview

Unless it has become your muscle memory to click on the Knit button in RStudio, you may try to switch to litedown::roam() to preview your HTML output by clicking on the run buttons in the user interface. Note that the preview takes place in memory only. Although you see an HTML page rendered from a file in the preview, the page is not rendered to disk, unless you click on the render button (5.2.3), or call litedown::fuse() (1) or mark() (3) on the file.

5.2.1 Live reload

By default, the preview will automatically refresh the content after you edit and save a file. If you prefer building the document only when you want to, you can turn off the live preview via litedown::roam(live = FALSE). In this case, the document will be rebuilt only when you refresh the page by yourself.

5.2.2 The file listing

After launching the preview via litedown::roam(), a file listing will be displayed, which shows the first few lines of *.R, *.Rmd, and *.md files in boxes, followed by the list of other files.

By clicking on plain-text files, you will see their full content. For binary files, they may be opened in your browser; if they cannot be opened, the browser may prompt to download the file.

Each file will have its size displayed after the filename, with a link attached to the file size. The link points to the raw file (the behavior of the link is up to the browser—the file may be opened or downloaded), and litedown will not process it at all.

The full path of the file or directory being previewed is displayed at the top left.

For .R, .Rmd, and .md files, you can click on the “Run” button () to render them to HTML in memory and preview the output. This button is displayed at the top right of each file box on the listing page, and at the top right of the preview page of an individual file.

Rendering .R and .Rmd files via the Run button means the full R code in them will be executed. If the R code involves intensive computing, it may not be a good idea to run the whole file, unless you have taken measures to speed up the computing (e.g., via caching).

5.2.3 The buttons

There is a button group at the top right of the preview page.

Code blocks in the preview mode will have line numbers automatically added to their left. If you click on a line number, it will bring you to that line in the source document.

Note that the keyboard shortcuts require the page to be currently on focus before they can take effect. This is important when you are viewing a page inside RStudio or other IDEs, because the viewer may not gain focus automatically, and you will have to explicitly click on it.

5.2.4 Cleaning up

Previewing .Rmd and .R files that generate plots will leave *__files/ directories (containing plot files) on disk by default. If you want to clean up such directories when closing or navigating away from the preview page, you may set the option

options(litedown.roam.cleanup = TRUE)

before you run litedown::roam() or in your .Rprofile. Note that it will not clean up a *__files/ directory if it has existed before you preview a file. This is to make sure roam() will not delete existing files by accident. If you are certain that a *__files/ directory can be safely deleted, you can always delete it by hand. After that, roam() will automatically clean it up when you preview the file again.

Please also note that when caching is enabled (via the chunk option cache = TRUE) in the file being previewed, the *__files/ directory will not be cleaned up, because when a code chunk is cached, it will not be re-evaluated or re-generate plots next time (unless the cache is invalidated).

5.3 Visual editor

Since the Markdown syntax of litedown can be viewed as a subset of Pandoc’s Markdown, you can use RStudio’s visual Markdown editor to author documents. Please bear in mind that most common, but not all, Markdown features are supported.

Edit this chapter on GitHub

6 Books and Websites

Books and websites are usually based on multiple input files under a directory. For a directory to be recognized as a book or website project, it needs to contain a configuration file named _litedown.yml.

If you want to customize the output formats html_format or latex_format for books or websites, you should do it in _litedown.yml, e.g.,

output:
  litedown::html_format:
    options:
      toc:
        depth: 4
  litedown::latex_format:
    meta:
      documentclass: "book"

6.1 Books

The _litedown.yml file should contain a top-level field named book, which currently supports these options:

book:
  new_session: false
  subdir: false
  pattern: "[.]R?md$"
  chapter_before: "Information before a chapter."
  chapter_after: "This chapter was generated from `$input$`."

You can choose whether to render each input file in a new R session, whether to search subdirectories for input files, the types of input files (e.g., you can use .md or .R files if you want), and additional information to be included before/after each chapter, in which you can use some variables such as $input$, which is the path of each input file.

6.1.1 Previewing a single chapter

Rendering a whole book may be time-consuming8 and unnecessary when you work on a book. It may be easier to only preview the single chapter that you currently work on. The preview can be done with litedown::roam():

When a chapter contains dependencies on certain elements in other chapters (e.g., a chapter includes cross-references to other chapters), we recommend that you run the index file to preview the whole book at least once before you preview individual chapters, to make litedown know the book elements fully.

Similarly, if you use the configuration new_session: false to render all chapters in the same R session, and a later chapter uses computed results from a previous chapter, you will need to preview the full book at least once to make sure the results are computed and exist in the R session.

You may also choose to run the index file to preview the whole book but then work on an individual chapter. In this case, roam() will try to detect changes in chapter files. When a certain chapter file has been updated, its output on the full book page will be updated. That is, the full book page is partially updated without being reloaded. This method lets you preview changes in one chapter while presenting the whole book. However, please note that certain JS libraries may not work well in this preview mode. When in doubt, refresh the page.

6.1.2 R package documentation

R package developers can build the full package documentation as a book. A series of helper functions have been provided in litedown to get various information about the package, such as the package description (pkg_desc()), news (pkg_news()), citation (pkg_citation()), source code (pkg_code()), and all manual pages (pkg_manual()). You can call these functions in code chunks to print out the desired information. For example, you may put them in the appendix (2.2.6):

# Appendix {.appendix}

# Package Metadata

```{r, echo = FALSE}
litedown::pkg_desc()
```

To cite the package:

```{r, echo = FALSE}
litedown::pkg_citation()
```

# News

```{r, echo = FALSE}
litedown::pkg_news(recent = 0)  # show full news
```

# Manual pages

```{r, echo = FALSE}
litedown::pkg_manual()
```

# Source code

```{r, echo = FALSE}
litedown::pkg_code()
```

6.2 Websites

The _litedown.yml file should contain a top-level field named site, and you are likely to customize the meta variables css, include_before, and include_after for the html_format, e.g.,

site:
  rebuild: "outdated"
  pattern: "[.]R?md$"

output:
  litedown::html_format:
    meta:
      css: ["@default"]
      include_before: "[Home](/) [About](/about.html)"
      include_after: "&copy; 2024 | [Edit]($input$)"

Basically, include_before can take a file or text input that will be used as the header of each web page, and include_after will be the footer.

Edit this chapter on GitHub

Appendix

A For rmarkdown Users

The litedown package has also provided two internal output formats for compatibility with rmarkdown: litedown:::html_document and litedown:::pdf_document.9 The purpose is to make it a little easier to switch from rmarkdown to litedown by mapping some rmarkdown output format options to litedown.

For example, for an R Markdown document with the following output format:

output:
  html_document:
    toc: true
    number_sections: true
    anchor_sections: true
    self_contained: false

You can switch to litedown simply by changing the output format name from html_document to litedown:::html_document. Internally, the above output format is transformed to:

output:
  litedown::html_format:
    options:
      toc: true
      number_sections: true
      embed_resources: false
    meta:
      css: ["default", "@heading-anchor"]
      js: ["@heading-anchor"]

Note that not all rmarkdown options are supported, and not even all supported options have exactly the same effects in litedown. The supported options include: toc, toc_depth, number_sections, anchor_sections, code_folding, self_contained, math_method, css, and includes.

Edit this chapter on GitHub

B Package vignettes

To build package vignettes with litedown, first add this to the package DESCRIPTION file:

VignetteBuilder: litedown

Then use the vignette engine litedown::vignette in the YAML metadata of a .Rmd or .md vignette file:

vignette: >
  %\VignetteEngine{litedown::vignette}
  %\VignetteIndexEntry{Your vignette title}
  %\VignetteEncoding{UTF-8}

The output format of a vignette can be specified in the output field of the YAML metadata, e.g., litedown::html_format (for HTML vignettes) or litedown::latex_format (for PDF vignettes). If no output format is specified, the default is HTML.

The vignette file can be either .Rmd or .md. The former is processed by litedown::fuse(), and the latter is converted by litedown::mark(). Please avoid using the same base filename for two .Rmd and .md files, otherwise their output files will overwrite each other.

Edit this chapter on GitHub

C Technical Notes

C.1 Embedding resources

When https resources needs to be embedded (via the embed_resources option), only these elements are considered:

<img src="..." />
<link rel="stylesheet" href="...">
<script src="..."></script>

Background images set in the attribute style="background-image: url(...)" are also considered. If an external CSS file contains url() resources, these resources will also be downloaded and embedded.

C.2 CSS for margin content

It’s quite simple to move an element into the margin using CSS. For example, the .side-right class in this article is roughly defined as:

.side-right {
  width: 200px;
  float: right;
  margin-right: -200px
}

That basically means the width of the element is 200px and it floats to the right. Now its right side will touch the right margin of its parent element (the article body). What we need to do next is move it further to the right by 200px (i.e., its width), which is done by the -200px right margin. Remember, a positive right margin in CSS moves an element to the left, and a negative right margin moves it to the right.

  1. At one night, as I was thinking about Pandoc Lua filters, an obvious fact suddenly came to my mind: suppose all I care about is HTML output, then the good old JavaScript can actually play a perfect role of Lua filters, because you can manipulate the DOM arbitrarily with JavaScript.

  2. In fact, xml, man, text, and commonmark output formats are supported (thanks to the commonmark package), but perhaps they are not very useful to average users.

  3. Cosmetic changes such as adding/deleting spaces and blank lines or modifying comments do not matter. The code is considered unchanged if it still parses to the same expressions via parse(). For example, parse(text = '1+1') and parse(text = '1 + 1') generate the same result.

  4. Lazy-loading means that the value of a variable will not be read from the cache until the variable is actually used somewhere. This can save the read time, especially if a large variable is not actually used later in the document.

  5. Please note that for links and images, their URLs should not contain spaces. If they do, the URLs must be enclosed in <>, e.g., ![alt](<some dir/a subdir/foo.png>).

  6. If you know C, I’ll truly appreciate it if you could help with the LaTeX implementation in GFM: https://github.com/github/cmark-gfm/issues/314

  7. The specific number doesn’t matter, as long as it’s a unique footnote number in the document. For example, the first footnote can be [^100] and the second can be [^64]. Eventually they will appear as [1] and [2]. If you use the RStudio visual editor to edit Markdown documents, the footnote numbers will be automatically generated and updated when new footnotes are inserted before existing footnotes.

  8. Remember that you can cache time-consuming code chunks to make the rendering faster.

  9. The triple-colon ::: means these functions are not exported, which is to avoid name conflicts between the two packages.