Typst HTML Export with Math Support
This post itself is written in Typst and exported to HTML using the method described below.
Typst now offers experimental support for HTML export.
Use typst compile a.typ --format html --features=html to export a.typ to a.html.
Use typst watch a.typ --format html --features=html to watch and recompile on changes. This is useful during writing because the incremental compilation is very fast.
Math Formula
HTML export of math formulas is not perfect yet. See issue #721 in GitHub.
But you can render math equations as SVG images, using the html.frame function. You may also need to wrap the SVG images in <span> or <div> elements to set proper CSS styles.
The following code is my current solution to math formula rendering. I save it as lib.typ and import it in my Typst documents. To use it in a Typst document:
#import "lib.typ": html-export-template
#show: html-export-template
lib.typ:
#let inline-math-count = counter("inline-math-count")
#let shift-inline-math(body) = context {
// Allocate a new state for each call to this function
let y-shift = state("y-shift" + str(inline-math-count.get().first()), 0pt)
inline-math-count.step()
let begin-loc = here()
// The wrapper ensures that the viewbox of rendered SVG math matches its bounding box.
let wrapper = text.with(top-edge: "bounds", bottom-edge: "bounds")
// For debugging: draw red box around the wrapper
// let wrapper = it => box(wrapper(it), stroke: red)
html.elem(
"span",
html.frame(wrapper(
// Add invisible elements below the math body to measure its bottom position.
math.attach(math.limits(body.body), b: pad([#none<_math_bot>], -1em))
+ sym.wj
+ math.attach(math.limits([#none]), b: pad([#none<_math_ref_bot>], -1em)),
)),
attrs: (
// Rendered SVG defines its width & height in "em" units,
// so we also convert y-shift relative to text size in "em" units.
style: "vertical-align: -" + str(calc.round(y-shift.final() / text.size, digits: 2)) + "em;",
class: "typst-inline-math",
),
)
context {
let end-loc = here()
let math-bot = query(
selector(<_math_bot>).after(begin-loc).before(end-loc),
)
let math-ref-bot = query(
selector(<_math_ref_bot>).after(begin-loc).before(end-loc),
)
if math-ref-bot.len() >= 1 {
let y1 = math-bot.at(0).location().position().y
let y2 = math-ref-bot.at(0).location().position().y
let new-y-shift = y1 - y2
if new-y-shift > y-shift.get() + 0.1pt {
y-shift.update(old => new-y-shift)
}
}
}
}
#let html-export-template(doc) = context {
if target() != "html" {
return doc
}
show math.equation.where(block: false): it => {
// The target() function can be used to apply html.frame selectively only
// when the export target is HTML.
// When html.frame is applied to a figure, the target() for all the elements
// inside will be set to "paged" instead.
// https://github.com/typst/typst/issues/721#issuecomment-3064895139
if target() == "html" {
shift-inline-math(it)
} else {
it
}
}
show math.equation.where(block: true): it => {
html.elem(
"div",
html.frame(it),
attrs: (class: "typst-display-math"),
)
}
// Wrap code blocks in a div for styling
show raw.where(block: true): it => {
html.elem(
"div",
it,
attrs: (class: "typst-code-block"),
)
}
doc
}
Vertical Alignment of Inline Math
I did spend some time tweaking the vertical alignment of inline math formulas. Current solution is still a bit hacky, but it works reasonably. Here is a comparison of the results with and without the y-shift adjustment:
The post “How to align baselines of display equations in tables? - Questions - Typst Forum” helps a lot. Many thanks to @bluss and the mannot package. I adopted a similar approach to measure the y-shift.
CSS
Below is the minimal CSS I use.
/* Center the display math */
.typst-display-math {
margin: 1em auto;
text-align: center;
}
.typst-display-math>svg {
display: inline-block;
}
.typst-inline-math {
display: inline-block;
}
/* Ensure inline math SVGs are inline */
.typst-inline-math>svg {
display: inline-block;
overflow: visible;
}
.typst-code-block {
font-size: 14px;
overflow-x: auto;
}
Math Typesetting Samples
The following formulas are used to test the math typesetting quality.
Maxwell’s equations in differential form:
Definition (Set and Function). Let and be sets. A function is a rule that assigns to each a unique element .
The image of under is
Definition (Equivalence Relation). A relation on a set is called an equivalence relation if, for all ,
The equivalence class of is denoted by
Definition (Metric Space). A metric on a set is a function such that for all ,
- (non-negativity), and iff (identity of indiscernibles),
- (symmetry),
- (triangle inequality).
The pair is called a metric space.
Theorem (Cauchy–Schwarz Inequality). For all in an inner product space,
Definition (Continuity). Let , where is a metric space. The function is continuous at if
Theorem (Uniqueness of Limits). If a sequence converges to both and , then .
Proof. Assume and . For any , there exist such that
Hence , and thus .
Definition (Basis). Let be a vector space over a field . A family is called a basis of if it is linearly independent and spans .
Theorem. Any two bases of a finite-dimensional vector space have the same cardinality.
Theorem (Fundamental Theorem of Calculus). Let be continuous. Define
Then is differentiable and .
Theorem (Monotone Convergence Theorem). Let be a sequence of non-negative measurable functions such that and almost everywhere. Then
Theorem (Fatou’s Lemma). Let be a sequence of non-negative measurable functions. Then
Theorem (Dominated Convergence Theorem). Let be measurable functions such that almost everywhere and there exists an integrable function with for all . Then
Definition (Lebesgue Integral). For a non-negative measurable function , define
Definition (Group). A group is a pair consisting of a set and a binary operation such that
- (Associativity) For all , .
- (Identity Element) There exists an element such that for all , .
- (Inverse Element) For each , there exists an element such that .