Transpile CommonMark Markdown to Typst, from within Typst!
MIT License
This package enables you to write CommonMark Markdown, and import it directly into Typst.
#import "@preview/cmarker:0.1.1"
#cmarker.render(read("simple.md"))
# We can write Markdown!
*Using* __lots__ ~of~ `fancy` [features](https://example.org/).
This document is available in Markdown and rendered PDF formats.
We offer a single function:
render(
markdown,
smart-punctuation: true,
blockquote: none,
math: none,
h1-level: 1,
raw-typst: true,
scope: (:),
show-source: false,
) -> content
The parameters are as follows:
markdown
:
The CommonMark Markdown string to be processed.
Parsed with the pulldown-cmark Rust library.
You can set this to read("somefile.md")
to import an external markdown file;
see the
documentation for the read function.
smart-punctuation
:
Automatically convert ASCII punctuation to Unicode equivalents:
Note that although Typst also offers this functionality, this conversion is done through the Markdown parser rather than Typst.
true
.blockquote
:
A callback to be used when a blockquote is encountered in the Markdown,
or none
if blockquotes should be treated as normal text.
Because Typst does not support blockquotes natively,
the user must configure this.
none
.none
.For example, to display a black border to the left of the text one can use:
box.with(stroke: (left: 1pt + black), inset: (left: 5pt, y: 6pt))
math
:
A callback to be used when equations are encountered in the Markdown,
or none
if it should be treated as normal text.
Because Typst does not support LaTeX equations natively,
the user must configure this.
block
and a positional string argumentmitex
function fromnone
.none
.For example, to render math equation as a Typst math block, one can use:
#import "@preview/mitex:0.2.4": mitex
#cmarker.render(`$\int_1^2 x \mathrm{d} x$`, math: mitex)
h1-level
:
The level that top-level headings in Markdown should get in Typst.
When set to zero,
top-level headings are treated as text,
##
headings become =
headings,
###
headings become ==
headings,
et cetera;
when set to 2
,
#
headings become ==
headings,
##
headings become ===
headings,
et cetera.
raw-typst
:
Whether to allow raw Typst code to be injected into the document via HTML comments.
If disabled, the comments will act as regular HTML comments.
true
.For example, when this is enabled, <!--raw-typst #circle(radius: 10pt) -->
will result in a circle in the document
(but only when rendered through Typst).
See also <!--typst-begin-exclude-->
and <!--typst-end-exclude-->
,
which is the inverse of this.
scope
:
When raw-typst
is enabled,
this is a dictionary
providing the context in which the evaluated Typst code runs.
It is useful to pass values in to code inside <!--raw-typst-->
blocks.
(:)
.show-source
:
A debugging tool.
When set to true
, the Typst code that would otherwise have been displayed
will be instead rendered in a code block.
false
.This function returns the rendered content
.
We support CommonMark with a couple extensions.
*emphasis*
or _emphasis_
: emphasis
**strong**
or __strong__
: strong
~strikethrough~
: [links](https://example.org)
: links
### Headings
, where #
is a top-level heading,##
a subheading, ###
a sub-subheading, etc`inline code blocks`
: inline code blocks
```
out of line code blocks
```
Syntax highlighting can be achieved by specifying a language after the opening backticks:
```rust
let x = 5;
```
giving:
let x = 5;
---
, making a horizontal rule: - Unordered
- lists
1. Ordered
1. Lists
$x + y$
or $$x + y$$
: math equations, if the math
parameter is set.> blockquotes
, if the blockquote
parameter is set.![Some tiled hexagons](examples/hexagons.png)
, givingSometimes,
you might want to render a certain section of the document
only when viewed as Markdown,
or only when viewed through Typst.
To achieve the former,
you can simply wrap the section in
<!--typst-begin-exclude-->
and <!--typst-end-exclude-->
:
<!--typst-begin-exclude-->
Hello from not Typst!
<!--typst-end-exclude-->
Most Markdown parsers support HTML comments,
so from their perspective this is no different
to just writing out the Markdown directly;
but cmarker.typ
knows to search for those comments
and avoid rendering the content in between.
Note that when the opening comment is followed by the end of an element,
cmarker.typ
will close the block for you.
For example:
> <!--typst-begin-exclude-->
> One
Two
In this code, “Two” will be given no matter where the document is rendered. This is done to prevent us from generating invalid Typst code.
Conversely, one can put Typst code inside
a HTML comment of the form
<!--raw-typst […]-->
to have it evaluated directly as Typst code
(but only if the raw-typst
option to render
is set to true
,
otherwise it will just be seen as a regular comment and removed):
<!--raw-typst Hello from #text(fill:blue)[Typst]!-->
This project has a manual as a PDF and a README as a Markdown document,
but by the power of this library they are in fact the same thing!
Furthermore, one can read the README.md
in a markdown viewer and it will display correctly,
but one can also run typst compile README.md
to generate the Typst-typeset README.pdf
.
How does this work? We just have to be clever about how we write the README:
<picture>
(Typst preamble content)
#"</picture>
<!--".slice(0,0)
#import "@preview/cmarker:0.1.1"
#let markdown = read("README.md")
#markdown = markdown.slice(markdown.position("</picture>") + "</picture>".len())
#cmarker.render(markdown, h1-level: 0)
/*-->
Regular Markdown goes here…
<!--*///-->
The same code but syntax-highlighted as Typst code helps to illuminate it:
<picture>
(Typst preamble content)
#"</picture>
<!--".slice(0,0)
#import "@preview/cmarker:0.1.1"
#let markdown = read("README.md")
#markdown = markdown.slice(markdown.position("</picture>") + "</picture>".len())
#cmarker.render(markdown, h1-level: 0)
/*-->
Regular Markdown goes here…
<!--*///-->
<sub>text</sub>
to get subscript text,raw-typst: false
../build.sh
,plugin.wasm
necessary to use this.typst compile examples/{name}.typ --root .
.typst compile README.md
.