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.
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
}
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
}
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.