litedown: Lightweight R Markdown Implementation

repository·main·Indexed 42 days ago

https://github.com/yihui/litedown

A high-performance, reimagined implementation of R Markdown that removes dependencies on knitr and Pandoc. It features functions like litedown::mark() for rendering Markdown to HTML and LaTeX, litedown::fuse() for executing code and interweaving results, and litedown::roam() for document previews. Built with a vendored cmark-gfm C library, it supports LaTeX math, Pandoc-style attributes, and multibyte characters while replacing heavy external dependencies with vanilla JavaScript and CSS.

Tokens
6.5K
Snippets
35
Records
40
Agent score
54%

What's inside litedown

  1. Overview of litedown scope and dependencies

    main

    litedown is a lightweight reimagining of R Markdown designed to be independent of the knitr R package and the system-level Pandoc package.

    It aims to provide the core functionality of several major R ecosystem packages (like rmarkdown, bookdown, blogdown, pkgdown, etc.) while removing heavy external dependencies like Bootstrap, jQuery, and Hugo, replacing them with lightweight vanilla JavaScript and CSS.

  2. Understand litedown's C extensions and bindings

    main

    Litedown uses a vendored version of cmark-gfm (GitHub's CommonMark implementation) located in src/cmark/ and src/extensions/. Instead of relying on external R packages, litedown calls this C library directly to allow for custom features and bug fixes at the C level.

    Key custom extensions and bindings provided by litedown include:

    • Math: LaTeX math support ($...$, $$...$$, and \begin{...}\end{...} environments).
    • Subscript/Superscript/Strikethrough: Supports ^x^ (superscript), ~x~ (subscript), and ~~x~~ (strikethrough). Note that the subscript extension owns the ~ delimiter.
    • Pandoc-style Attributes: Supports code block attributes like ```{.class #id key="val"}. In HTML, this renders as <pre><code class="class" id="id" key="val">.
    • Raw Content Blocks: Supports ```{=html}, ```{=latex}, and ```{=tex} to emit raw content into specific output formats.
    • LaTeX Footnotes: A postprocess pass that moves footnote definitions inline for LaTeX output, rendering them as \footnote{body} or \footnotemark[N] for repeats.
    • R Bindings:
      • R_render_markdown(): Renders markdown to a string.
      • R_parse_markdown(): Returns a parse tree as a nested list.
      • R_code_tokens(): Returns a flat table of code blocks and inline code.
      • R_prose_lines(): Returns indices of lines not inside a code block.
  3. Use LaTeX math and footnotes

    main

    Litedown handles LaTeX math syntax (inline $x$ and display $$x$$) and footnotes ([^foo]).

    Footnotes: To create a footnote, use the [^label] syntax in the text and define the content at the bottom using [^label]: content.

    Math: By default, math is enabled. You can disable it using options = '-latex_math' if you want the raw symbols to be treated as plain text.

    # LaTeX Math
    mark('`$x$` is inline math $x$! $$x + y$$')
    
    # Footnotes
    mark('Hello[^foo] world!\n\n[^foo]: A footnote.')
  4. Handle code blocks and raw blocks

    main

    Litedown renders fenced code blocks using verbatim environments in LaTeX. It supports language identifiers and attribute syntax (e.g., {.r .js #foo}).

    Raw Blocks: You can embed raw content using curly brace syntax:

    • {=html} ... for raw HTML.
    • {=latex} ... for raw LaTeX.

    Fenced Divs: Litedown supports fenced Divs using the ::: syntax, which can include classes, IDs, and styles (e.g., ::: {.class #id style="..."}).

    # Fenced code block with attributes
    mark('```{.r .js #foo style="background:lime;"}\n1 + 1;\n```')
    
    # Raw LaTeX block
    mark('```{=latex}\n\textbf{raw LaTeX}\n```')
    
    # Fenced Div
    mark('::: {.foo .bar #baz style="color: red;"}\nasdf\n:::')
  5. Sync litedown with a newer upstream cmark-gfm

    main

    To update the vendored cmark-gfm source code with a newer version from upstream, run the sync.sh script from the package root. You can sync to the latest release tag or a specific reference (tag, branch, or commit).

    Steps to sync:

    1. Run the sync script:

      src/patches/sync.sh          # Syncs to the latest release tag
      src/patches/sync.sh REF      # Syncs to a specific tag, branch, or commit
    2. Verify the build: After syncing, the script clones upstream, copies sources, regenerates headers, and re-applies local patches. You must then build the package and run tests to ensure compatibility:

      R CMD INSTALL .
      Rscript -e 'testit::test_pkg("litedown","test-cran")'
    3. Check examples: Re-render the examples to ensure the new upstream version hasn't changed the output unexpectedly:

      cd examples && CI=true Rscript _run.R
    4. Resolve patch conflicts: If any local patches fail to apply, you must manually fix them or drop them if they have been merged into the upstream version.

    src/patches/sync.sh          # latest release tag
    src/patches/sync.sh REF      # a specific tag/branch/commit
  6. Use the 'snap-clean' presentation theme

    main

    To create minimalist and elegant presentations using the snap-clean theme, add the @snap-clean CSS asset and the @snap JS asset to your YAML header under the html output configuration. This theme provides a lightweight alternative to Quarto reveal.js themes without requiring Pandoc or knitr dependencies.

    To render the resulting presentation, use litedown::fuse().

    output:
      html:
        meta:
          css: ["@default", "@snap", "@snap-clean"]
          js: ["@snap"]
        options:
          embed_resources: null
  7. Position figure and table captions

    main

    In Litedown, you control the position of captions (for figures and tables) by the order in which you place the {.caption} div relative to the content within a container div (like {.figure} or {.table}).

    • Bottom Caption (Default for Figures): Place the {.caption} div after the image/content inside the container.
    • Top Caption (Default for Tables): Place the {.caption} div before the table content inside the container.
    • Custom Positioning: To override defaults, simply move the {.caption} block. For example, to put a figure caption at the top, place the {.caption} div before the image within the {.figure} container.
    ## Default Figure (Bottom Caption)
    :::: {.figure #fig:id}
    ![Alt text](image.png)
    
    ::: {.caption}
    [](#@fig:id)
    Caption text.
    :::
    ::::
    
    ## Custom Figure (Top Caption)
    :::: {.figure #fig:id}
    ::: {.caption}
    [](#@fig:id)
    Caption text.
    :::
    
    ![Alt text](image.png)
    ::::
    
    ## Default Table (Top Caption)
    :::: {.table #tab:id}
    ::: {.caption}
    [](#@tab:id)
    Caption text.
    :::
    
    | col1 | col2 |
    |--:|--:|
    | val1 | val2 |
    ::::
    
    ## Custom Table (Bottom Caption)
    :::: {.table #tab:id}
    | col1 | col2 |
    |--:|--:|
    | val1 | val2 |
    
    ::: {.caption}
    [](#@tab:id)
    Caption text.
    :::
    ::::
  8. Install the litedown development version

    main

    To use the latest features, install the development version of litedown from r-universe using the following command:

    install.packages('litedown', repos = c('https://yihui.r-universe.dev', 'https://cloud.r-project.org'))
  9. Configure Autolinks

    main

    Litedown can automatically convert URLs into clickable hyperlinks. Use +autolink to enable this or -autolink to disable it.

    # Enable autolinks (default)
    mark('https://www.r-project.org/')
    
    # Disable autolinks
    mark('https://www.r-project.org/', options = '-autolink')
  10. Create multi-column layouts

    main

    Use Div syntax with column classes to create side-by-side layouts. The .columns5050 class creates two columns of equal width. Each column is defined by a nested Div ::: {} inside the parent container.

    Example structure:

    :::: {.columns5050} ::: {}

    Column 1

    Content... ::: ::: {}

    Column 2

    Content... ::: ::::

    :::: {.columns5050}
    ::: {}
    
    #### Column 1
    
    Here is some text for the first column.
    
    :::
    ::: {}
    
    #### Column 2
    
    Here is some text for the second column.
    
    :::
    ::::