SkillAgentSearch skills...

Cmarker.typ

Transpile CommonMark Markdown to Typst, from within Typst!

Install / Use

/learn @SabrinaJewson/Cmarker.typ
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

cmarker.typ

<!--raw-typst #align(center)[https://github.com/SabrinaJewson/cmarker.typ] -->

This package enables you to write CommonMark Markdown, and import it directly into Typst.

<table> <tr> <th><code>simple.typ</code></th> <th><code>simple.md</code></th> </tr> <tr> <td>
#import "@preview/cmarker:0.1.8"

#cmarker.render(read("simple.md"))
</td> <td>
# We can write Markdown!

*Using* __lots__ ~of~ `fancy` [features](https://example.org/).
</td> </tr> </table> <table> <tr><th><code>simple.pdf</code></th></tr> <tr><td><img src="./examples/simple.png"></td></tr> </table>

This document is available in Markdown and rendered PDF formats.

API

We offer a two functions:

  1. render
  2. render-with-metadata

render

render(
  markdown,
  smart-punctuation: true,
  math: none,
  h1-level: 1,
  raw-typst: true,
  html: (:),
  label-prefix: "",
  prefix-label-uses: true,
  heading-labels: "github",
  scope: (:),
  show-source: false,
  blockquote: none,
) -> 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.

    • Accepted values: Strings and raw text code blocks.
    • Required.
  • smart-punctuation: Automatically convert ASCII punctuation to Unicode equivalents:

    • nondirectional quotations (" and ') become directional (“” and ‘’);
    • three consecutive full stops (...) become ellipses (…);
    • two and three consecutive hypen-minus signs (-- and ---) become en and em dashes (– and —).

    Note that although Typst also offers this functionality, this conversion is done through the Markdown parser rather than Typst.

    • Accepted values: Booleans.
    • Default value: true.
  • 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.

    • Accepted values: Functions that take a boolean argument named block and a positional string argument (often, the mitex function from the mitex package), or none.
    • Default value: none.

    For example, to render math equation as a Typst math block, one can use:

    #import "@preview/mitex:0.2.6": mitex
    #cmarker.render(`$\int_1^2 x \mathrm{d} x$`, math: mitex)
    
    <!--raw-typst which renders as: $integral_1^2 x dif x$ -->
  • h1-level: The level that top-level headings in Markdown should get in Typst. When set to zero, top-level headings are treated as titles, ## headings become = headings, ### headings become == headings, et cetera; when set to 2, # headings become == headings, ## headings become === headings, et cetera.

    • Accepted values: Integers in the range [-128, 127].
    • Default value: 1.
  • 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.

    • Accepted values: Booleans.
    • Default value: 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.

  • html: The dictionary of HTML elements that cmarker will support.

    • Accepted values: Dictionaries whose keys are the tag name (without the surrounding <>) and whose values can be:
      • ("normal", (attrs, body) => [/* … */]): Defines a normal element, where attrs is a dictionary of strings, body is content, and the function returns content.
      • ("void", (attrs) => [/* … */]): Defines a void element (e.g. <br>, <img>, <hr>).
      • ("raw-text", (attrs, body) => [/* … */]): Defines a raw text element (e.g. <script>, <style>), where body is a string.
      • ("escapable-raw-text", (attrs, body) => [/* … */]): Defines an escapable raw text element (e.g. <textarea>), where body is a string.
      • (attrs, body) => [/* … */]: Shorthand for ("normal", (attrs, body) => [/* … */]).
    • Default value: (:).
    • Overridable keys: The following HTML elements are provided by default, but you are free to override them: <sub>, <sup>, <mark>, <h1><h6>, <ul>, <ol>, <li>, <dl>, <dt>, <dd>, <table>, <thead>, <tfoot>, <tr>, <th>, <td>, <hr>, <a>, <em>, <strong>, <s>, <code>, <br>, <blockquote>, <figure>, <figcaption>, <svg>, <img>.

    For example, the following code would allow you to write <circle radius="25"> to render a 25pt circle:

    #cmarker.render(read("input.md"), html: (
      circle: ("void", attrs => circle(radius: int(attrs.radius) * 1pt))
    ))
    
  • label-prefix: If present, any labels autogenerated by footnotes and headings will be prefixed by this string. This is useful to avoid collisions.

    • Accepted values: A valid label or the empty string.
    • Default value: The empty string.
  • prefix-label-uses: Whether references to labels such as [@label] and [link](#label) should be prefixed by label-prefix.

    • Accepted values: Booleans.
    • Default value: true.
  • heading-labels: How the cases of labels autogenerated by headings should be transformed. The label prefix (given in label-prefix) will not be transformed.

    • Accepted values:
      • "github": Match GitHub’s slugification algorithm: strip out invalid characters, lowercase everything, convert ASCII spaces to hyphens, and number duplicate headings to avoid collisions. For example, # My heading! will become <my-heading>.
      • "jupyter": Match Jupyter’s slugification algorithm: just convert ASCII spaces to hyphens. For example, # My heading! will become <My-heading!>. Unlike the real Jupyter, we also number duplicate headings; but this shouldn’t have any difference in practice since Jupyter doesn’t allow referring to duplicate headings anyway.
    • Default value: "github".
  • scope: 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, but can also be used to override element functions generated by cmarker itself.

    • Accepted values: Any dictionary.
    • Default value: (:).
    • Overridable keys:
      • All built-in Typst functions.
      • rule: Expected to be a function returning content. Will be used when thematic breaks (--- in Markdown) are encountered. Defaults to line.with(length: 100%).
  • 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.

    • Accepted values: Booleans.
    • Default value: false.
  • blockquote: Deprecated! This used to control how blockquotes were rendered, but we now default to quote(block: true). If you want to override how blockquotes look, either use #show quote.where(block: true) or use scope: (quote: …).

    • Accepted values: Functions accepting content and returning content, or none.
    • Default value: none.

This function returns the rendered content.

render-with-metadata

render-with-metadata(
  markdown,
  smart-punctuation: true,
  math: none,
  h1-level: 1,
  raw-typst: true,
  html: (:),
  label-prefix: "",
  prefix-label-uses: true,
  heading-labels: "github",
  scope: (:),
  show-source: false,
  blockquote: none,
  metadata-block: none,
) -> (metadata, content)

The render-with-metadata functions works the same as render with two exceptions:

  1. This function returns (meta, body). This allows the user to freely manipulate the metadata.
    #let (meta, body) = render-with-metadata(input)
    // `body` will be the same as:
    #let body = render(input)
    
  2. This function has an extra argument: metadata-block. The other arguments are the same as render.
  • metadata-block: How to parse the metadata block. Only a single metadata block at the beginning of the document is supported; metadata blocks not at the start will be ignored. If none, the metadata block will not be parsed. The behaviour is the same as using render.
    • Accepted values:
      • "frontmatter-raw": Parse the metadata block and return it as a string.
      • "frontmatter-yaml": Parsed the metadata block as YAML and return it as a dictionary.
      • none.
    • Default value: none.

Resolving Paths Correctly

Because of how Typst handles paths, elements like images will by default resolve relative to the project root of cmarker itself and not your project.

To fix this, one can override the image function in the scope the Typst code is evaluated.

#import "@preview/cmarker:0.1.8"

#cmarker.render(
  read("yourfile.md"),
  scope: (image: (source, alt: none, format: auto) => image(source, alt: alt, format: format))
)

References, Labels, Figures and Citations

cmarker.typ integrates well with Typst’s native references and labels. Where in Typst you would write @foo, in Markdown you write [@foo]; where in Typst you would write @foo[Chapter], in Markdown you’d write [Chapter][@foo]. You can also write [some text](#foo) to have “some text” be a link that points a

View on GitHub
GitHub Stars141
CategoryDevelopment
Updated1d ago
Forks6

Languages

Rust

Security Score

95/100

Audited on Mar 30, 2026

No findings