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.
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.
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.
%% 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:
#showstrong:it=>ifis-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]
#showstrong:it=>ifis-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]
#showstrong:it=>ifis-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]
#showstrong:it=>ifis-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
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
Or define a state that shares between the template and the processor script:
#letis-typlite=state("is-typlite",false)
// processor.typ
#letarticle(body)={
is-typlite.update(_=>true)
body
}
// template.typ
#letis-typlite=...
#letabstract(body)=contextifis-typlite{
abstract-state.update(_=>body)
}else{
// fallback to regular abstract
}
#letis-typlite=state("is-typlite",false)
// processor.typ
#letarticle(body)={
is-typlite.update(_=>true)
body
}
// template.typ
#letis-typlite=...
#letabstract(body)=contextifis-typlite{
abstract-state.update(_=>body)
}else{
// fallback to regular abstract
}
#letis-typlite=state("is-typlite",false)
// processor.typ
#letarticle(body)={
is-typlite.update(_=>true)
body
}
// template.typ
#letis-typlite=...
#letabstract(body)=contextifis-typlite{
abstract-state.update(_=>body)
}else{
// fallback to regular abstract
}
#letis-typlite=state("is-typlite",false)
// processor.typ
#letarticle(body)={
is-typlite.update(_=>true)
body
}
// template.typ
#letis-typlite=...
#letabstract(body)=contextifis-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:
#letabstract=context{
letabstract-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}
```)
}
#letabstract=context{
letabstract-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}
```)
}
#letabstract=context{
letabstract-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}
```)
}
#letabstract=context{
letabstract-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.