Tinymist Docs

Exporting to Other Markup Formats

This feature is currently in early development.

typlite is a pure Rust library for converting Typst documents to other markup formats.

typlite's goal is to convert docstrings in typst packages to LSP docs (Markdown format). To achieve this, it runs HTML export and extracts semantic information from the HTML document for markup conversion.

Figure 1: The conversion path from typst source code to other markup formats, by typlite.

TodoList

  • [ ] Renders figures into PDF instead of SVG.
  • [ ] Converts typst equations, might use texmath or mathyml plus pmml2tex.

Example: Writing README in typst

To export to Markdown, run the command:


                                
typlite README.typ README.md --assets-path assets

                                
typlite README.typ README.md --assets-path assets

                                
typlite README.typ README.md --assets-path assets

                                
typlite README.typ README.md --assets-path assets

Assets Path

You might love to use html.framehtml.frame to render typst examples in README.mdREADME.md. By default, the examples are embedded in the output by data url. To externalize them, please specify the --assets-path--assets-path option.

Implementing target-aware functions

typlite will set sys.inputs.x-targetsys.inputs.x-target to mdmd if it is exporting to Markdown. You can use this variable to implement target-aware functions in your typst documents.


                                
#let x-target = sys.inputs.at("x-target", default: "pdf")

                                
#let is-md-target = x-target == "md"

                                
#let x-target = sys.inputs.at("x-target", default: "pdf")

                                
#let is-md-target = x-target == "md"

                                
#let x-target = sys.inputs.at("x-target", default: "pdf")

                                
#let is-md-target = x-target == "md"

                                
#let x-target = sys.inputs.at("x-target", default: "pdf")

                                
#let is-md-target = x-target == "md"

For example, you can implement a GitHub link function, which determines the link based on the target:


                                
#let current-revision = read("/.git/" + read("/.git/HEAD").trim().slice(5)).trim()

                                


                                
#let github-link(path, body, kind: none) = {

                                
  let dest = if is-md-target {

                                
    path

                                
  } else {

                                
    if kind == none {

                                
      kind = if path.ends-with("/") { "tree" } else { "blob" }

                                
    }

                                
    (remote, kind, current-revision, path).join("/")

                                
  }

                                
  

                                
  link(dest, body)

                                
}

                                


                                
#let current-revision = read("/.git/" + read("/.git/HEAD").trim().slice(5)).trim()

                                


                                
#let github-link(path, body, kind: none) = {

                                
  let dest = if is-md-target {

                                
    path

                                
  } else {

                                
    if kind == none {

                                
      kind = if path.ends-with("/") { "tree" } else { "blob" }

                                
    }

                                
    (remote, kind, current-revision, path).join("/")

                                
  }

                                
  

                                
  link(dest, body)

                                
}

                                


                                
#let current-revision = read("/.git/" + read("/.git/HEAD").trim().slice(5)).trim()

                                


                                
#let github-link(path, body, kind: none) = {

                                
  let dest = if is-md-target {

                                
    path

                                
  } else {

                                
    if kind == none {

                                
      kind = if path.ends-with("/") { "tree" } else { "blob" }

                                
    }

                                
    (remote, kind, current-revision, path).join("/")

                                
  }

                                
  

                                
  link(dest, body)

                                
}

                                


                                
#let current-revision = read("/.git/" + read("/.git/HEAD").trim().slice(5)).trim()

                                


                                
#let github-link(path, body, kind: none) = {

                                
  let dest = if is-md-target {

                                
    path

                                
  } else {

                                
    if kind == none {

                                
      kind = if path.ends-with("/") { "tree" } else { "blob" }

                                
    }

                                
    (remote, kind, current-revision, path).join("/")

                                
  }

                                
  

                                
  link(dest, body)

                                
}

                                

Example: Styling a Typst Document by IEEE LaTeX Template

The main.typmain.typ in the Sample Workspace: IEEE Paper can be converted perfectly.

  • Run the command:

    
                                        
    typlite main.typ main.tex --processor "/ieee-tex.typ"
    
                                        
    typlite main.typ main.tex --processor "/ieee-tex.typ"
    
                                        
    typlite main.typ main.tex --processor "/ieee-tex.typ"
    
                                        
    typlite main.typ main.tex --processor "/ieee-tex.typ"
  • Create a project on Overleaf, using the IEEE LaTeX Template.
  • Upload the main.texmain.tex file and exported PDF assets and it will get rendered and ready to submit.

Processor Scripts

The CLI command in the previous example uses the --processor "/ieee-tex.typ"--processor "/ieee-tex.typ" option, which is not a flag of the official typst-clitypst-cli. The option tells typlite to use a processor script to process the HTML export result for LaTeX export.

typlite will show your main documents with the articlearticle function obtained from the processor file.


                                
#let verbatim(body) = {

                                
  show raw.where(lang: "tex"): it => html.elem("m1verbatim", attrs: (src: it.text))

                                
  body

                                
}

                                
#let article(body) = {

                                
  verbatim(```tex

                                
  %% Generated by typlite

                                
  %% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.

                                
  

                                
  \begin{document}

                                
  Hey,

                                
  ```)

                                
  body

                                
  verbatim(```tex

                                
  \end{document}

                                
  ```)

                                
}

                                
#let verbatim(body) = {

                                
  show raw.where(lang: "tex"): it => html.elem("m1verbatim", attrs: (src: it.text))

                                
  body

                                
}

                                
#let article(body) = {

                                
  verbatim(```tex

                                
  %% Generated by typlite

                                
  %% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.

                                
  

                                
  \begin{document}

                                
  Hey,

                                
  ```)

                                
  body

                                
  verbatim(```tex

                                
  \end{document}

                                
  ```)

                                
}

                                
#let verbatim(body) = {

                                
  show raw.where(lang: "tex"): it => html.elem("m1verbatim", attrs: (src: it.text))

                                
  body

                                
}

                                
#let article(body) = {

                                
  verbatim(```tex

                                
    %% Generated by typlite

                                
    %% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.

                                
  

                                
    \begin{document}

                                
    Hey,

                                
    ```)

                                
  body

                                
  verbatim(```tex

                                
    \end{document}

                                
    ```)

                                
}

                                
#let verbatim(body) = {

                                
  show raw.where(lang: "tex"): it => html.elem("m1verbatim", attrs: (src: it.text))

                                
  body

                                
}

                                
#let article(body) = {

                                
  verbatim(```tex

                                
    %% Generated by typlite

                                
    %% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.

                                
  

                                
    \begin{document}

                                
    Hey,

                                
    ```)

                                
  body

                                
  verbatim(```tex

                                
    \end{document}

                                
    ```)

                                
}
Currently, html.elem("m1verbatim")html.elem("m1verbatim") is the only xmlxml element can be used by processor scripts. When seeing a <m1verbatim/><m1verbatim/> element, typlite writes the inner content to output directly.

It will output like that:


                                
%% Generated by typlite

                                
%% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.

                                


                                
\begin{document}

                                
Hey, [CONTENT generated according to body]

                                
\end{document}

                                
%% Generated by typlite

                                
%% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.

                                


                                
\begin{document}

                                
Hey, [CONTENT generated according to body]

                                
\end{document}

                                
%% Generated by typlite

                                
%% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.

                                


                                
\begin{document}

                                
Hey, [CONTENT generated according to body]

                                
\end{document}

                                
%% Generated by typlite

                                
%% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.

                                


                                
\begin{document}

                                
Hey, [CONTENT generated according to body]

                                
\end{document}

You can implement the articlearticle function for different markup formats, such as LaTeX, Markdown, DocX, and Plain Text.

Using Processor Packages

The processor script can be not only a file, but also a package:

  • From current workspace: "/ieee-tex.typ""/ieee-tex.typ" is the file relative to the current workspace root.
  • From a package: "@local/ieee-tex:0.1.0""@local/ieee-tex:0.1.0" or "@preview/ieee-tex:0.1.0""@preview/ieee-tex:0.1.0" can be used to get functions from local packages or typst universe.

Perfect Conversion

typlite is called "-ite" because it only ensures that nice docstrings are converted perfectly. Similarly, if your document looks nice, typlite can also convert it to other markup formats perfectly.

This introduces concept of Semantic Typst. To help conversion, you should separate styling scripts and semantic content in your typst documents.

A good example in HTML is <strong><strong> v.s. <b><b>. Written in typst,


                                
#strong[Good Content]

                                
#text(weight: 700)[Bad Content]

                                
#strong[Good Content]

                                
#text(weight: 700)[Bad Content]

                                
#strong[Good Content]

                                
#text(weight: 700)[Bad Content]

                                
#strong[Good Content]

                                
#text(weight: 700)[Bad Content]

typlite can convert "Good Content" perfectly, but not "Bad Content". This is because we can attach markup-specific styles to "Good Content" then, but "Bad Content" may be broken by some reasons, such as failing to find font weight when rendering the content.

To style your typst documents, rewriting the bad content by show rule is suggested:


                                
#show strong: it => if is-md-target {

                                
  // style for Markdown target, for example:

                                
  html.span(class: "my-strong", it.body)

                                
} else { // style for builtin Typst targets:

                                
  text(weight: 700, it.body)

                                
}

                                
#strong[Bad Content]

                                
#show strong: it => if is-md-target {

                                
  // style for Markdown target, for example:

                                
  html.span(class: "my-strong", it.body)

                                
} else { // style for builtin Typst targets:

                                
  text(weight: 700, it.body)

                                
}

                                
#strong[Bad Content]

                                
#show strong: it => if is-md-target {

                                
  // style for Markdown target, for example:

                                
  html.span(class: "my-strong", it.body)

                                
} else { // style for builtin Typst targets:

                                
  text(weight: 700, it.body)

                                
}

                                
#strong[Bad Content]

                                
#show strong: it => if is-md-target {

                                
  // style for Markdown target, for example:

                                
  html.span(class: "my-strong", it.body)

                                
} else { // style for builtin Typst targets:

                                
  text(weight: 700, it.body)

                                
}

                                
#strong[Bad Content]

typlite will feel happy and make perfect conversion if you keep aware of keep pure semantics of main.typmain.typ documents in the above ways.

Implementing abstractabstract for IEEE LaTeX Template

Let's explain how /ieee-tex.typ/ieee-tex.typ works by the abstractabstract example. First, we edit /ieee-template.typ/ieee-template.typ to store the abstract in state:


                                
#let abstract-state = state("tex:abstract", "")

                                
#let abstract(body) = if is-md-target {

                                
  abstract-state.update(_ => body)

                                
} else {

                                
    // fallback to regular abstract

                                
}

                                
#let abstract-state = state("tex:abstract", "")

                                
#let abstract(body) = if is-md-target {

                                
  abstract-state.update(_ => body)

                                
} else {

                                
    // fallback to regular abstract

                                
}

                                
#let abstract-state = state("tex:abstract", "")

                                
#let abstract(body) = if is-md-target {

                                
  abstract-state.update(_ => body)

                                
} else {

                                
    // fallback to regular abstract

                                
}

                                
#let abstract-state = state("tex:abstract", "")

                                
#let abstract(body) = if is-md-target {

                                
  abstract-state.update(_ => body)

                                
} else {

                                
    // fallback to regular abstract

                                
}

is-md-targetis-md-target already distinguishes regular typst PDF export and typlite export (which uses HTML export). We haven't decide a way to let your template aware of LaTeX export.

Luckily, typst has sys.inputsys.input mechanism so you can distinguish it by yourself:


                                  
// typst compile or typlite main.typ --input exporter=typlite-tex

                                  
#let exporter = sys.inputs.at("exporter", default: "typst")

                                  
#exporter // "typst" or "typlite-tex"

                                  
// typst compile or typlite main.typ --input exporter=typlite-tex

                                  
#let exporter = sys.inputs.at("exporter", default: "typst")

                                  
#exporter // "typst" or "typlite-tex"

                                  
// typst compile or typlite main.typ --input exporter=typlite-tex

                                  
#let exporter = sys.inputs.at("exporter", default: "typst")

                                  
#exporter // "typst" or "typlite-tex"

                                  
// typst compile or typlite main.typ --input exporter=typlite-tex

                                  
#let exporter = sys.inputs.at("exporter", default: "typst")

                                  
#exporter // "typst" or "typlite-tex"

Or define a state that shares between the template and the processor script:


                                  
#let is-typlite = state("is-typlite", false)

                                  
// processor.typ

                                  
#let article(body) = {

                                  
  is-typlite.update(_ => true)

                                  
  body

                                  
}

                                  
// template.typ

                                  
#let is-typlite = ...

                                  
#let abstract(body) = context if is-typlite {

                                  
  abstract-state.update(_ => body)

                                  
} else {

                                  
  // fallback to regular abstract

                                  
}

                                  
#let is-typlite = state("is-typlite", false)

                                  
// processor.typ

                                  
#let article(body) = {

                                  
  is-typlite.update(_ => true)

                                  
  body

                                  
}

                                  
// template.typ

                                  
#let is-typlite = ...

                                  
#let abstract(body) = context if is-typlite {

                                  
  abstract-state.update(_ => body)

                                  
} else {

                                  
  // fallback to regular abstract

                                  
}

                                  
#let is-typlite = state("is-typlite", false)

                                  
// processor.typ

                                  
#let article(body) = {

                                  
  is-typlite.update(_ => true)

                                  
  body

                                  
}

                                  
// template.typ

                                  
#let is-typlite = ...

                                  
#let abstract(body) = context if is-typlite {

                                  
  abstract-state.update(_ => body)

                                  
} else {

                                  
  // fallback to regular abstract

                                  
}

                                  
#let is-typlite = state("is-typlite", false)

                                  
// processor.typ

                                  
#let article(body) = {

                                  
  is-typlite.update(_ => true)

                                  
  body

                                  
}

                                  
// template.typ

                                  
#let is-typlite = ...

                                  
#let abstract(body) = context if is-typlite {

                                  
  abstract-state.update(_ => body)

                                  
} else {

                                  
  // fallback to regular abstract

                                  
}

Next, in the ieee-tex.typieee-tex.typ, we can get the abstractabstract material from the state and render it in the LaTeX template:


                                
#let abstract = context {

                                
  let abstract-body = state("tex:abstract", "").final()

                                
  verbatim(```tex

                                
  % As a general rule, do not put math, special symbols or citations

                                
  % in the abstract

                                
  \begin{abstract}

                                
  ```)

                                
  abstract-body

                                
  verbatim(```tex

                                
  \end{abstract}

                                
  ```)

                                
}

                                
#let abstract = context {

                                
  let abstract-body = state("tex:abstract", "").final()

                                
  verbatim(```tex

                                
  % As a general rule, do not put math, special symbols or citations

                                
  % in the abstract

                                
  \begin{abstract}

                                
  ```)

                                
  abstract-body

                                
  verbatim(```tex

                                
  \end{abstract}

                                
  ```)

                                
}

                                
#let abstract = context {

                                
  let abstract-body = state("tex:abstract", "").final()

                                
  verbatim(```tex

                                
    % As a general rule, do not put math, special symbols or citations

                                
    % in the abstract

                                
    \begin{abstract}

                                
    ```)

                                
  abstract-body

                                
  verbatim(```tex

                                
    \end{abstract}

                                
    ```)

                                
}

                                
#let abstract = context {

                                
  let abstract-body = state("tex:abstract", "").final()

                                
  verbatim(```tex

                                
    % As a general rule, do not put math, special symbols or citations

                                
    % in the abstract

                                
    \begin{abstract}

                                
    ```)

                                
  abstract-body

                                
  verbatim(```tex

                                
    \end{abstract}

                                
    ```)

                                
}
We don't extracts content from abstract-bodyabstract-body. Inteadly, we put the body directly in the document, to let typlite process the equations in abstract-bodyabstract-body and convert them to LaTeX.