1. Introduction
  2. Editor Integration
  3. Common Configurations
  4. 1. Editor Frontends
    1. 1.1. VS Cod(e,ium)
    2. 1.2. Neovim
    3. 1.3. Emacs
    4. 1.4. Sublime Text
    5. 1.5. Helix
    6. 1.6. Zed
  5. Features
  6. 2. Command line interface
  7. 3. Code Documentation
  8. 4. Code Completion
  9. 5. Exporting Documents
  10. 6. Exporting to Other Markup Formats
  11. 7. Document Preview
  12. 8. Testing
  13. 9. Linting
  14. 10. Other Features
  15. Service Overview
  16. Overview of Service
  17. 11. Principles
  18. 12. Commands System
  19. 13. LSP Inputs
  20. 14. Type System
  21. Service Development
  22. 15. Crate Docs
  23. 16. Language Server
  24. 17. Language Queries
  25. 18. Document Preview

Tinymist Docs

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:

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

#

Assets Path

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

#

Implementing target-aware functions

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

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

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

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

3
#let github-link(path, body, kind: none) = {
4
  let dest = if is-md-target {
5
    path
6
  } else {
7
    if kind == none {
8
      kind = if path.ends-with("/") { "tree" } else { "blob" }
9
    }
10
    (remote, kind, current-revision, path).join("/")
11
  }
12

13
  link(dest, body)
14
}
15

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

3
#let github-link(path, body, kind: none) = {
4
  let dest = if is-md-target {
5
    path
6
  } else {
7
    if kind == none {
8
      kind = if path.ends-with("/") { "tree" } else { "blob" }
9
    }
10
    (remote, kind, current-revision, path).join("/")
11
  }
12

13
  link(dest, body)
14
}
15

#

Example: Styling a Typst Document by IEEE LaTeX Template

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

  • Run the command:

    1
    typlite main.typ main.tex --processor "/ieee-tex.typ"
    1
    typlite main.typ main.tex --processor "/ieee-tex.typ"
  • Create a project on Overleaf, using the IEEE LaTeX Template.
  • Upload the main.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" option, which is not a flag of the official typst-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 article function obtained from the processor file.

1
#let verbatim(body) = {
2
  show raw.where(lang: "tex"): it => html.elem("m1verbatim", attrs: (src: it.text))
3
  body
4
}
5
#let article(body) = {
6
  verbatim(```tex
7
  %% Generated by typlite
8
  %% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.
9

10
  \begin{document}
11
  Hey,
12
  ```)
13
  body
14
  verbatim(```tex
15
  \end{document}
16
  ```)
17
}
1
#let verbatim(body) = {
2
  show raw.where(lang: "tex"): it => html.elem("m1verbatim", attrs: (src: it.text))
3
  body
4
}
5
#let article(body) = {
6
  verbatim(```tex
7
  %% Generated by typlite
8
  %% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.
9

10
  \begin{document}
11
  Hey,
12
  ```)
13
  body
14
  verbatim(```tex
15
  \end{document}
16
  ```)
17
}
Currently, html.elem("m1verbatim") is the only xml element can be used by processor scripts. When seeing a <m1verbatim/> element, typlite writes the inner content to output directly.

It will output like that:

1
%% Generated by typlite
2
%% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.
3

4
\begin{document}
5
Hey, [CONTENT generated according to body]
6
\end{document}
1
%% Generated by typlite
2
%% Please report to https://github.com/Myriad-Dreamin/tinymist if you find any bugs.
3

4
\begin{document}
5
Hey, [CONTENT generated according to body]
6
\end{document}

You can implement the article 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" is the file relative to the current workspace root.
  • From a package: "@local/ieee-tex:0.1.0" or "@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> v.s. <b>. Written in typst,

1
#strong[Good Content]
2
#text(weight: 700)[Bad Content]
1
#strong[Good Content]
2
#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:

1
#show strong: it => if is-md-target {
2
  // style for Markdown target, for example:
3
  html.span(class: "my-strong", it.body)
4
} else { // style for builtin Typst targets:
5
  text(weight: 700, it.body)
6
}
7
#strong[Bad Content]
1
#show strong: it => if is-md-target {
2
  // style for Markdown target, for example:
3
  html.span(class: "my-strong", it.body)
4
} else { // style for builtin Typst targets:
5
  text(weight: 700, it.body)
6
}
7
#strong[Bad Content]

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

#

Implementing abstract for IEEE LaTeX Template

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

1
#let abstract-state = state("tex:abstract", "")
2
#let abstract(body) = if is-md-target {
3
  abstract-state.update(_ => body)
4
} else {
5
    // fallback to regular abstract
6
}
1
#let abstract-state = state("tex:abstract", "")
2
#let abstract(body) = if is-md-target {
3
  abstract-state.update(_ => body)
4
} else {
5
    // fallback to regular abstract
6
}

is-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.input mechanism so you can distinguish it by yourself:

1
// typst compile or typlite main.typ --input exporter=typlite-tex
2
#let exporter = sys.inputs.at("exporter", default: "typst")
3
#exporter // "typst" or "typlite-tex"
1
// typst compile or typlite main.typ --input exporter=typlite-tex
2
#let exporter = sys.inputs.at("exporter", default: "typst")
3
#exporter // "typst" or "typlite-tex"

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

1
#let is-typlite = state("is-typlite", false)
2
// processor.typ
3
#let article(body) = {
4
  is-typlite.update(_ => true)
5
  body
6
}
7
// template.typ
8
#let is-typlite = ...
9
#let abstract(body) = context if is-typlite {
10
  abstract-state.update(_ => body)
11
} else {
12
  // fallback to regular abstract
13
}
1
#let is-typlite = state("is-typlite", false)
2
// processor.typ
3
#let article(body) = {
4
  is-typlite.update(_ => true)
5
  body
6
}
7
// template.typ
8
#let is-typlite = ...
9
#let abstract(body) = context if is-typlite {
10
  abstract-state.update(_ => body)
11
} else {
12
  // fallback to regular abstract
13
}

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

1
#let abstract = context {
2
  let abstract-body = state("tex:abstract", "").final()
3
  verbatim(```tex
4
  % As a general rule, do not put math, special symbols or citations
5
  % in the abstract
6
  \begin{abstract}
7
  ```)
8
  abstract-body
9
  verbatim(```tex
10
  \end{abstract}
11
  ```)
12
}
1
#let abstract = context {
2
  let abstract-body = state("tex:abstract", "").final()
3
  verbatim(```tex
4
  % As a general rule, do not put math, special symbols or citations
5
  % in the abstract
6
  \begin{abstract}
7
  ```)
8
  abstract-body
9
  verbatim(```tex
10
  \end{abstract}
11
  ```)
12
}
We don't extracts content from abstract-body. Inteadly, we put the body directly in the document, to let typlite process the equations in abstract-body and convert them to LaTeX.