diff --git a/AUTHORS.md b/AUTHORS.md
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -17,9 +17,11 @@
 - Amar Al-Zubaidi
 - Amir Dekel
 - Amogh Rathore
+- Amy de Buitléir
 - Anabra
 - Anders Waldenborg
 - Andreas Lööw
+- Andres Freund
 - Andrew Dunning
 - Andy Morris
 - Aner Lucero
@@ -62,6 +64,7 @@
 - Charanjit Singh
 - Chris Black
 - Christian Conkle
+- Christian Christiansen
 - Christian Despres
 - Christoffer Ackelman
 - Christoffer Sawicki
@@ -219,6 +222,7 @@
 - Mark Wright
 - Martin Joerg
 - Martin Linn
+- Martín Pozo
 - Masataka Ogawa
 - Masayoshi Takahashi
 - Matej Kollar
diff --git a/MANUAL.txt b/MANUAL.txt
--- a/MANUAL.txt
+++ b/MANUAL.txt
@@ -1,7 +1,7 @@
 ---
 title: Pandoc User's Guide
 author: John MacFarlane
-date: March 5, 2023
+date: March 26, 2023
 ---
 
 # Synopsis
@@ -342,6 +342,7 @@
     - `revealjs` ([reveal.js] HTML5 + JavaScript slide show)
     - `s5` ([S5] HTML and JavaScript slide show)
     - `tei` ([TEI Simple])
+    - `typst` ([typst])
     - `xwiki` ([XWiki markup])
     - `zimwiki` ([ZimWiki markup])
     - the path of a custom Lua writer, see [Custom readers and writers] below
@@ -375,8 +376,7 @@
     `$HOME/.local/share`, overridable by setting the `XDG_DATA_HOME`
     environment variable).  If that directory does not exist and
     `$HOME/.pandoc` exists, it will be used (for backwards compatibility).
-    On Windows the default user data directory is
-    `C:\Users\USERNAME\AppData\Roaming\pandoc`.
+    On Windows the default user data directory is `%APPDATA%\pandoc`.
     You can find the default user data directory on your system by
     looking at the output of `pandoc --version`.
     Data files placed in this directory (for example, `reference.odt`,
@@ -481,7 +481,7 @@
 [OPML]: http://dev.opml.org/spec2.html
 [OpenDocument]: http://opendocument.xml.org
 [ODT]: https://en.wikipedia.org/wiki/OpenDocument
-[Textile]: https://www.promptworks.com/textile
+[Textile]: https://textile-lang.com
 [MediaWiki markup]: https://www.mediawiki.org/wiki/Help:Formatting
 [DokuWiki markup]: https://www.dokuwiki.org/dokuwiki
 [ZimWiki markup]: https://zim-wiki.org/manual/Help/Wiki_Syntax.html
@@ -516,6 +516,7 @@
 [BibLaTeX]: https://ctan.org/pkg/biblatex
 [Markua]: https://leanpub.com/markua/read
 [EndNote XML bibliography]: https://support.clarivate.com/Endnote/s/article/EndNote-XML-Document-Type-Definition
+[typst]: https://typst.app
 
 ## Reader options {.options}
 
@@ -1014,7 +1015,9 @@
     current section, or the document. The default is
     `document`. Currently this option only affects the
     `markdown`, `muse`, `html`, `epub`, `slidy`, `s5`, `slideous`,
-    `dzslides`, and `revealjs` writers.
+    `dzslides`, and `revealjs` writers.  In slide formats,
+    specifying `--reference-location=section` will cause notes
+    to be rendered at the bottom of a slide.
 
 `--markdown-headings=setext`|`atx`
 
@@ -1165,6 +1168,7 @@
         - Author
         - Date
         - Abstract
+        - AbstractTitle
         - Bibliography
         - Heading 1
         - Heading 2
@@ -1365,7 +1369,7 @@
 :   Use the specified engine when producing PDF output.
     Valid values are `pdflatex`, `lualatex`, `xelatex`, `latexmk`,
     `tectonic`, `wkhtmltopdf`, `weasyprint`, `pagedjs-cli`,
-    `prince`, `context`, and `pdfroff`. If the engine is not in
+    `prince`, `context`, `pdfroff`, and `typst`. If the engine is not in
     your PATH, the full path of the engine may be specified here.
     If this option is not specified, pandoc uses the following
     defaults depending on the output format specified using
@@ -1379,6 +1383,7 @@
         see [print-css.rocks](https://print-css.rocks) for a good
         introduction to PDF generation from HTML/CSS)
     - `-t ms`:  `pdfroff`
+    - `-t typst`: `typst`
 
 `--pdf-engine-opt=`*STRING*
 
@@ -2927,6 +2932,16 @@
         - Numbers=Proportional
         ...
 
+`babelfonts`
+:    a map of Babel language names (e.g. `chinese`) to the font
+     to be used with the language:
+
+     ---
+     babelfonts:
+       chinese-hant: "Noto Serif CJK TC"
+       russian: "Noto Serif"
+     ...
+
 `microtypeoptions`
 :    options to pass to the microtype package
 
@@ -3129,6 +3144,27 @@
 
 `section`
 :   section number in man pages
+
+### Variables for Typst
+
+`margin`
+:   A dictionary with the fields defined in the Typst documentation:
+    `x`, `y`, `top`, `bottom`, `left`, `right`.
+
+`papersize`
+:    Paper size: `a4`, `us-letter`, etc.
+
+`mainfont`
+:    Name of system font to use for the main font.
+
+`fontsize`
+:    Font size (e.g., `12pt`).
+
+`section-numbering`
+:    Schema to use for numbering sections, e.g. `1.A.1`.
+
+`columns`
+:    Number of columns for body text.
 
 ### Variables for ms
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -198,6 +198,7 @@
 - `s5` ([S5](https://meyerweb.com/eric/tools/s5/) HTML and JavaScript
   slide show)
 - `tei` ([TEI Simple](https://github.com/TEIC/TEI-Simple))
+- `typst` ([typst](https://typst.app))
 - `xwiki` ([XWiki
   markup](https://www.xwiki.org/xwiki/bin/view/Documentation/UserGuide/Features/XWikiSyntax/))
 - `zimwiki` ([ZimWiki
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,143 @@
 # Revision history for pandoc
 
+## pandoc 3.1.2 (2023-03-26)
+
+  * Add a Lua REPL (Albert Krewinkel). This can be started
+    with `pandoc lua -i`.  It is also possible to instruct a filter to
+    open the REPL at a certain point, for debugging (see `pandoc.cli.repl`).
+
+  * Support `typst` as a `--pdf-engine`.
+
+  * Add typst writer (#8713).  New module Text.Pandoc.Writers.Typst,
+    exporting `writeTypst` [API change].
+
+  * Org reader:
+
+    + Allow zero width space as an escape character (#8716,
+      Christian Christiansen). Allow the character U+200B to be used as
+      an escape character as described in the Org-mode documentation
+      (<https://orgmode.org/manual/Escape-Character.html>).
+
+  * DocBook reader:
+
+    + Handle "book" for xref references (#8712, Andres Freund)
+      This also adds a test xref to book and part.
+    + Handle `<part>` (#8712).
+
+  * HTML reader:
+
+    + Fix behavior with `-native_spans-raw_html` (#8711). Previously with
+      this configuration, `<span>`s were not treated as inline elements at all.
+
+  * HTML writer:
+
+    + Avoid duplicate classes (#8705).
+    + Use img element instead of embed for `.svg.gz` and `.png.gz` etc. (#8699).
+    + HTML writer footnotes changes (#8695): when `--reference-location=section`
+      or `=block`, use an `aside` element for the notes rather than a `section`.
+      When `--reference-location=section`, include the `aside` element inside
+      the section element, rather than outside. (In slide shows, this option
+      causes footnotes on a slide to be displayed at the bottom of the slide.)
+
+  * EPUB writer:
+
+    + Use different structure for epub footnotes (#8676, see #8672, #5583).
+      Many EPUB readers are thrown off by pandoc's current footnote
+      output.  Both the ol and the fact that the footnote backlink is
+      at the end of the note seem to pose problems.
+      With this commit, we now create a list of aside (or div) elements,
+      instead of an ordered list. Each element begins with a note number
+      that is linked back to the note reference.  (So, the backlink occurs
+      at the beginning rather than the end.) Thanks to @Porges and @lewer.
+
+  * Docx writer:
+
+    + Include abstract title (#8702). Uses localized term for abstract.
+
+  * Markdown writer:
+
+    + Use implicit figures if there's a caption but no alt (#8689,
+      Albert Krewinkel).
+
+  * Jira reader (Albert Krewinkel):
+
+    + Add panel title as nested div (#8681).
+    + Require jira-wiki-markup 1.5.1 (#8680). This fixes a bug in the parser
+      that caused text between two exclamation marks to be parsed as an
+      image. The first `!` of image markup must now be followed by a
+      non-space character; otherwise, the enclosed text is parsed as
+      normal content.
+    
+  * Ms writer:
+
+    + Fix handling of Figure (#8660).
+
+  * ICML writer:
+
+    + Fix images with data (#8675). The Contents element
+      should be inside Properties.
+
+  * LaTeX writer:
+
+    + Add Chinese to Babel languages.
+    + Fix background image in Beamer when there are figure environments (#8671,
+      Martín Pozo).
+
+  * LaTeX template:
+
+    + Add `babelfonts` variable to default LaTeX template.
+      This allows specifying certain fonts to be used with
+      certain babel languages. Thanks to Frederik Elwert.
+    + Fix highlight/underline with lualatex (#8707). We need the lua-ul package
+      instead of soul, which doesn't work with lualatex.
+
+  * Lua (Albert Krewinkel):
+
+    + Add `pandoc.cli.repl` function
+    + Fix `json.encode` for nested AST elements. Ensures that objects with
+      nested AST elements can be encoded as JSON.
+    + Auto-generate docs for pandoc modules.
+    + Load text module as `pandoc.text`. This only affects the name in the
+      Lua-internal documentation. It is still possible to load the modules
+      via `require 'text'`, although this is deprecated.
+    + Move docs from module `text` to `pandoc.text`
+      The latter is easier to use and more consistent with the other modules.
+    + Keep the Lua stack clean A metatable used during initialization was
+      not properly removed from the stack. Likewise, accessing the
+      CommonState from Lua previously led to the pollution of the
+      Lua stack with a left-over value.
+    * Add function `pandoc.format.from_path`.
+    + Allow to get the JSON encoding of log messages.
+
+  * Text.Pandoc.Format: Add new function `formatFromFilePaths` [API change]
+    (#8710, Albert Krewinkel).
+
+  * The old Text.Pandoc.App.FormatHeuristics module has been removed.
+
+  * In `--version`, use Windows `%APPDATA%` variable to describe
+    user data dir (#8686, Pablo Rodríguez).
+
+  * Text.Pandoc.App.CommandLineOptions: don't lowercase arg to `--from`/`--read`
+    (Albert Krewinkel). This prevented users to use custom writers with
+    uppercase characters in their filenames. Format-normalization,
+    including lower-casing of format identifiers, happens during
+    format parsing.
+
+  * Documentation:
+
+    + Add `doc/nix.md`.
+    + Add `doc/extras.md`. This was formally in the website repo.
+    + `doc/lua-filters.md`: improve docs for `pandoc.zip`.
+
+  * Factor out `make_macos_release.sh` from the release candidate workflow.
+    Use cabal instead of stack to build the macos binary.
+
+  * Modify linux/make_artifacts.sh so it will work on cirrus.
+
+  * Switch to hslua-2.3
+
+  * Depend on latest releases of texmath, doclayout.
+
 ## pandoc 3.1.1 (2023-03-05)
 
   * EPUB reader: Give additional information in error if the epub
diff --git a/data/docx/word/styles.xml b/data/docx/word/styles.xml
--- a/data/docx/word/styles.xml
+++ b/data/docx/word/styles.xml
@@ -99,6 +99,24 @@
       <w:jc w:val="center" />
     </w:pPr>
   </w:style>
+  <w:style w:type="paragraph" w:customStyle="1" w:styleId="AbstractTitle">
+    <w:name w:val="Abstract Title" />
+    <w:basedOn w:val="Normal" />
+    <w:next w:val="Abstract" />
+    <w:qFormat />
+    <w:pPr>
+      <w:keepNext />
+      <w:keepLines />
+      <w:jc w:val="center" />
+      <w:spacing w:before="300" w:after="0" />
+    </w:pPr>
+    <w:rPr>
+      <w:sz w:val="20" />
+      <w:szCs w:val="20" />
+      <w:b />
+      <w:color w:val="345A8A" />>
+    </w:rPr>
+  </w:style>
   <w:style w:type="paragraph" w:customStyle="1" w:styleId="Abstract">
     <w:name w:val="Abstract" />
     <w:basedOn w:val="Normal" />
@@ -107,7 +125,7 @@
     <w:pPr>
       <w:keepNext />
       <w:keepLines />
-      <w:spacing w:before="300" w:after="300" />
+      <w:spacing w:before="100" w:after="300" />
     </w:pPr>
     <w:rPr>
       <w:sz w:val="20" />
diff --git a/data/templates/default.latex b/data/templates/default.latex
--- a/data/templates/default.latex
+++ b/data/templates/default.latex
@@ -33,6 +33,9 @@
 \usebackgroundtemplate{%
   \includegraphics[width=\paperwidth]{$background-image$}%
 }
+% In beamer background-image does not work well when other images are used, so this is the workaround
+\pgfdeclareimage[width=\paperwidth,height=\paperheight]{background}{$background-image$}
+\usebackgroundtemplate{\pgfuseimage{background}}
 $endif$
 \usepackage{pgfpages}
 \setbeamertemplate{caption}[numbered]
@@ -289,7 +292,12 @@
 $endif$
 $if(strikeout)$
 $-- also used for underline
-\usepackage{soul}
+\ifLuaTeX
+  \usepackage{luacolor}
+  \usepackage[soul]{lua-ul}
+\else
+  \usepackage{soul}
+\fi
 $endif$
 \setlength{\emergencystretch}{3em} % prevent overfull lines
 \providecommand{\tightlist}{%
@@ -355,12 +363,15 @@
 $if(mainfont)$
 \ifPDFTeX
 \else
-\babelfont[$babel-lang$]{rm}{$mainfont$}
+\babelfont[$babel-lang$]{rm}[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$}
 \fi
 $endif$
 $endif$
 $for(babel-otherlangs)$
 \babelprovide[import]{$babel-otherlangs$}
+$endfor$
+$for(babelfonts/pairs)$
+\babelfont[$babelfonts.key$]{rm}{$babelfonts.value$}
 $endfor$
 % get rid of language-specific shorthands (see #6817):
 \let\LanguageShortHands\languageshorthands
diff --git a/data/templates/default.typst b/data/templates/default.typst
new file mode 100644
--- /dev/null
+++ b/data/templates/default.typst
@@ -0,0 +1,106 @@
+$definitions.typst()$
+
+#show terms: it => {
+  it.children
+    .map(child => [
+      #strong[#child.term]
+      #block(inset: (left: 1.5em, top: -0.4em))[#child.description]
+      ])
+    .join()
+}
+
+$if(template)$
+#import "$template$": conf
+$else$
+$template.typst()$
+$endif$
+
+#show: doc => conf(
+$if(title)$
+  title: [$title$],
+$endif$
+$if(author)$
+  authors: (
+$for(author)$
+$if(author.name)$
+    ( name: [$author.name$],
+      affiliation: [$author.affiliation$],
+      email: [$author.email$] ),
+$else$
+    ( name: [$author$],
+      affiliation: [],
+      email: [] ),
+$endif$
+$endfor$
+    ),
+$endif$
+$if(date)$
+  date: [$date$],
+$endif$
+$if(lang)$
+  lang: "$lang$",
+$endif$
+$if(region)$
+  region: "$region$",
+$endif$
+$if(abstract)$
+  abstract: [$abstract$],
+$endif$
+$if(margin)$
+  margin: ($for(margin/pairs)$$margin.key$: $margin.value$,$endfor$),
+$endif$
+$if(papersize)$
+  paper: "$papersize$",
+$endif$
+$if(mainfont)$
+  font: ("$mainfont$",),
+$endif$
+$if(fontsize)$
+  fontsize: $fontsize$,
+$endif$
+$if(section-numbering)$
+  sectionnumbering: "$section-numbering$",
+$endif$
+  cols: $if(columns)$$columns$$else$1$endif$,
+  doc,
+)
+
+$for(header-includes)$
+$header-includes$
+
+$endfor$
+$for(include-before)$
+$include-before$
+
+$endfor$
+$if(toc)$
+#outline(
+  title: auto,
+  depth: none
+);
+$endif$
+
+$body$
+
+$if(notes)$
+#v(1em)
+#block[
+#horizontalrule
+#set text(size: .88em)
+#v(3pt) // otherwise first note marker is swallowed, bug?
+
+$notes$
+]
+$endif$
+$if(bibliographystyle)$
+
+#set bibliography(style: "$bibliographystyle$")
+$endif$
+$for(bibliography)$
+
+#bibliography("$bibliography$")
+$endfor$
+$for(include-after)$
+
+$include-after$
+$endfor$
diff --git a/data/templates/definitions.typst b/data/templates/definitions.typst
new file mode 100644
--- /dev/null
+++ b/data/templates/definitions.typst
@@ -0,0 +1,13 @@
+// Some definitions presupposed by pandoc's typst output.
+#let blockquote(body) = [
+  #set text( size: 0.92em )
+  #block(inset: (left: 1.5em, top: 0.2em, bottom: 0.2em))[#body]
+]
+
+#let horizontalrule = [
+  #line(start: (25%,0%), end: (75%,0%))
+]
+
+#let endnote(num, contents) = [
+  #stack(dir: ltr, spacing: 3pt, super[#num], contents)
+]
diff --git a/data/templates/template.typst b/data/templates/template.typst
new file mode 100644
--- /dev/null
+++ b/data/templates/template.typst
@@ -0,0 +1,67 @@
+#let conf(
+  title: none,
+  authors: none,
+  date: none,
+  abstract: none,
+  cols: 1,
+  margin: (x: 1.25in, y: 1.25in),
+  paper: "us-letter",
+  lang: "en",
+  region: "US",
+  font: (),
+  fontsize: 11pt,
+  sectionnumbering: none,
+  doc,
+) = {
+  set page(
+    paper: paper,
+    margin: margin,
+    numbering: "1",
+  )
+  set par(justify: true)
+  set text(lang: lang,
+           region: region,
+           font: font,
+           size: fontsize)
+  set heading(numbering: sectionnumbering)
+
+  if title != none {
+    align(center)[#block(inset: 2em)[
+      #text(weight: "bold", size: 1.5em)[#title]
+    ]]
+  }
+
+  if authors != none {
+    let count = authors.len()
+    let ncols = calc.min(count, 3)
+    grid(
+      columns: (1fr,) * ncols,
+      row-gutter: 1.5em,
+      ..authors.map(author =>
+          align(center)[
+            #author.name \
+            #author.affiliation \
+            #author.email
+          ]
+      )
+    )
+  }
+
+  if date != none {
+    align(center)[#block(inset: 1em)[
+      #date
+    ]]
+  }
+
+  if abstract != none {
+    block(inset: 2em)[
+    #text(weight: "semibold")[Abstract] #h(1em) #abstract
+    ]
+  }
+
+  if cols == 1 {
+    doc
+  } else {
+    columns(cols, doc)
+  }
+}
diff --git a/man/pandoc.1 b/man/pandoc.1
--- a/man/pandoc.1
+++ b/man/pandoc.1
@@ -14,7 +14,7 @@
 . ftr VB CB
 . ftr VBI CBI
 .\}
-.TH "Pandoc User\[cq]s Guide" "" "March 5, 2023" "pandoc 3.1.1" ""
+.TH "Pandoc User\[cq]s Guide" "" "March 26, 2023" "pandoc 3.1.2" ""
 .hy
 .SH NAME
 pandoc - general markup converter
@@ -452,6 +452,8 @@
 .IP \[bu] 2
 \f[V]tei\f[R] (TEI Simple)
 .IP \[bu] 2
+\f[V]typst\f[R] (typst)
+.IP \[bu] 2
 \f[V]xwiki\f[R] (XWiki markup)
 .IP \[bu] 2
 \f[V]zimwiki\f[R] (ZimWiki markup)
@@ -491,7 +493,7 @@
 If that directory does not exist and \f[V]$HOME/.pandoc\f[R] exists, it
 will be used (for backwards compatibility).
 On Windows the default user data directory is
-\f[V]C:\[rs]Users\[rs]USERNAME\[rs]AppData\[rs]Roaming\[rs]pandoc\f[R].
+\f[V]%APPDATA%\[rs]pandoc\f[R].
 You can find the default user data directory on your system by looking
 at the output of \f[V]pandoc --version\f[R].
 Data files placed in this directory (for example,
@@ -1064,6 +1066,8 @@
 \f[V]muse\f[R], \f[V]html\f[R], \f[V]epub\f[R], \f[V]slidy\f[R],
 \f[V]s5\f[R], \f[V]slideous\f[R], \f[V]dzslides\f[R], and
 \f[V]revealjs\f[R] writers.
+In slide formats, specifying \f[V]--reference-location=section\f[R] will
+cause notes to be rendered at the bottom of a slide.
 .TP
 \f[V]--markdown-headings=setext\f[R]|\f[V]atx\f[R]
 Specify whether to use ATX-style (\f[V]#\f[R]-prefixed) or Setext-style
@@ -1228,6 +1232,8 @@
 .IP \[bu] 2
 Abstract
 .IP \[bu] 2
+AbstractTitle
+.IP \[bu] 2
 Bibliography
 .IP \[bu] 2
 Heading 1
@@ -1477,7 +1483,8 @@
 Valid values are \f[V]pdflatex\f[R], \f[V]lualatex\f[R],
 \f[V]xelatex\f[R], \f[V]latexmk\f[R], \f[V]tectonic\f[R],
 \f[V]wkhtmltopdf\f[R], \f[V]weasyprint\f[R], \f[V]pagedjs-cli\f[R],
-\f[V]prince\f[R], \f[V]context\f[R], and \f[V]pdfroff\f[R].
+\f[V]prince\f[R], \f[V]context\f[R], \f[V]pdfroff\f[R], and
+\f[V]typst\f[R].
 If the engine is not in your PATH, the full path of the engine may be
 specified here.
 If this option is not specified, pandoc uses the following defaults
@@ -1495,6 +1502,8 @@
 print-css.rocks for a good introduction to PDF generation from HTML/CSS)
 .IP \[bu] 2
 \f[V]-t ms\f[R]: \f[V]pdfroff\f[R]
+.IP \[bu] 2
+\f[V]-t typst\f[R]: \f[V]typst\f[R]
 .RE
 .TP
 \f[V]--pdf-engine-opt=\f[R]\f[I]STRING\f[R]
@@ -3029,6 +3038,17 @@
 .fi
 .RE
 .TP
+\f[V]babelfonts\f[R]
+a map of Babel language names (e.g.\ \f[V]chinese\f[R]) to the font to
+be used with the language:
+.RS
+.PP
+   *   *   *   *   *
+.PP
+babelfonts: chinese-hant: \[lq]Noto Serif CJK TC\[rq] russian: \[lq]Noto
+Serif\[rq] \&...
+.RE
+.TP
 \f[V]microtypeoptions\f[R]
 options to pass to the microtype package
 .SS Links
@@ -3215,6 +3235,27 @@
 .TP
 \f[V]section\f[R]
 section number in man pages
+.SS Variables for Typst
+.TP
+\f[V]margin\f[R]
+A dictionary with the fields defined in the Typst documentation:
+\f[V]x\f[R], \f[V]y\f[R], \f[V]top\f[R], \f[V]bottom\f[R],
+\f[V]left\f[R], \f[V]right\f[R].
+.TP
+\f[V]papersize\f[R]
+Paper size: \f[V]a4\f[R], \f[V]us-letter\f[R], etc.
+.TP
+\f[V]mainfont\f[R]
+Name of system font to use for the main font.
+.TP
+\f[V]fontsize\f[R]
+Font size (e.g., \f[V]12pt\f[R]).
+.TP
+\f[V]section-numbering\f[R]
+Schema to use for numbering sections, e.g.\ \f[V]1.A.1\f[R].
+.TP
+\f[V]columns\f[R]
+Number of columns for body text.
 .SS Variables for ms
 .TP
 \f[V]fontfamily\f[R]
diff --git a/pandoc.cabal b/pandoc.cabal
--- a/pandoc.cabal
+++ b/pandoc.cabal
@@ -1,6 +1,6 @@
 cabal-version:   2.4
 name:            pandoc
-version:         3.1.1
+version:         3.1.2
 build-type:      Simple
 license:         GPL-2.0-or-later
 license-file:    COPYING.md
@@ -25,6 +25,7 @@
                  - Documentation formats (GNU TexInfo, Haddock)
                  - Roff formats (man, ms)
                  - TeX formats (LaTeX, ConTeXt)
+                 - Typst
                  - XML formats (DocBook 4 and 5, JATS, TEI Simple, OpenDocument)
                  - Outline formats (OPML)
                  - Bibliography formats (BibTeX, BibLaTeX, CSL JSON, CSL YAML,
@@ -40,9 +41,9 @@
                  - PDF (via external programs such as pdflatex or wkhtmltopdf)
                  .
                  Pandoc can convert mathematical content in documents
-                 between TeX, MathML, Word equations, roff eqn, and plain text.
-                 It includes a powerful system for automatic citations
-                 and bibliographies, and it can be customized extensively
+                 between TeX, MathML, Word equations, roff eqn, typst,
+                 and plain text. It includes a powerful system for automatic
+                 citations and bibliographies, and it can be customized extensively
                  using templates, filters, and custom readers and writers
                  written in Lua.
 data-files:
@@ -94,6 +95,9 @@
                  data/templates/article.jats_publishing
                  data/templates/affiliations.jats
                  data/templates/default.markua
+                 data/templates/default.typst
+                 data/templates/definitions.typst
+                 data/templates/template.typst
                  -- translations
                  data/translations/*.yaml
                  -- entities
@@ -315,6 +319,7 @@
                  test/tables.asciidoctor
                  test/tables.haddock
                  test/tables.texinfo
+                 test/tables.typst
                  test/tables.rst
                  test/tables.rtf
                  test/tables.txt
@@ -345,6 +350,7 @@
                  test/writer.plain
                  test/writer.mediawiki
                  test/writer.textile
+                 test/writer.typst
                  test/writer.opendocument
                  test/writer.org
                  test/writer.asciidoc
@@ -480,7 +486,7 @@
                  data-default          >= 0.4      && < 0.8,
                  deepseq               >= 1.3      && < 1.5,
                  directory             >= 1.2.3    && < 1.4,
-                 doclayout             >= 0.4      && < 0.5,
+                 doclayout             >= 0.4.0.1  && < 0.5,
                  doctemplates          >= 0.11     && < 0.12,
                  base64                >= 0.4      && < 0.5,
                  emojis                >= 0.1      && < 0.2,
@@ -493,7 +499,7 @@
                  http-client-tls       >= 0.2.4    && < 0.4,
                  http-types            >= 0.8      && < 0.13,
                  ipynb                 >= 0.2      && < 0.3,
-                 jira-wiki-markup      >= 1.5      && < 1.6,
+                 jira-wiki-markup      >= 1.5.1    && < 1.6,
                  mime-types            >= 0.1.1    && < 0.2,
                  mtl                   >= 2.2      && < 2.4,
                  network               >= 2.6,
@@ -512,7 +518,7 @@
                  syb                   >= 0.1      && < 0.8,
                  tagsoup               >= 0.14.6   && < 0.15,
                  temporary             >= 1.1      && < 1.4,
-                 texmath               >= 0.12.6   && < 0.13,
+                 texmath               >= 0.12.7   && < 0.13,
                  text                  >= 1.1.1.0  && < 2.1,
                  text-conversions      >= 0.3      && < 0.4,
                  time                  >= 1.5      && < 1.14,
@@ -589,6 +595,7 @@
                    Text.Pandoc.Writers.Jira,
                    Text.Pandoc.Writers.LaTeX,
                    Text.Pandoc.Writers.ConTeXt,
+                   Text.Pandoc.Writers.Typst,
                    Text.Pandoc.Writers.OpenDocument,
                    Text.Pandoc.Writers.Texinfo,
                    Text.Pandoc.Writers.Man,
@@ -639,7 +646,6 @@
                    Text.Pandoc.Chunks,
                    Text.Pandoc.Version
   other-modules:   Text.Pandoc.App.CommandLineOptions,
-                   Text.Pandoc.App.FormatHeuristics,
                    Text.Pandoc.App.Input,
                    Text.Pandoc.App.Opt,
                    Text.Pandoc.App.OutputSettings,
diff --git a/src/Text/Pandoc/App.hs b/src/Text/Pandoc/App.hs
--- a/src/Text/Pandoc/App.hs
+++ b/src/Text/Pandoc/App.hs
@@ -51,7 +51,6 @@
 import Text.Pandoc.Builder (setMeta)
 import Text.Pandoc.MediaBag (mediaItems)
 import Text.Pandoc.Image (svgToPng)
-import Text.Pandoc.App.FormatHeuristics (formatFromFilePaths)
 import Text.Pandoc.App.Opt (Opt (..), LineEnding (..), defaultOpts,
                             IpynbOutput (..), OptInfo(..))
 import Text.Pandoc.App.CommandLineOptions (parseOptions, parseOptionsFromArgs,
@@ -142,20 +141,20 @@
                      Just xs | not (optIgnoreArgs opts) -> xs
                      _ -> ["-"]
 
+  let defFlavor fmt = Format.FlavoredFormat fmt mempty
   -- assign reader and writer based on options and filenames
-  readerName <- case optFrom opts of
-                     Just f  -> return f
-                     Nothing -> case formatFromFilePaths sources of
-                         Just f' -> return f'
-                         Nothing | sources == ["-"] -> return "markdown"
-                                 | any (isURI . T.pack) sources -> return "html"
-                                 | otherwise -> do
-                           report $ CouldNotDeduceFormat
-                               (map (T.pack . takeExtension) sources) "markdown"
-                           return "markdown"
-
   flvrd@(Format.FlavoredFormat readerNameBase _extsDiff) <-
-    Format.parseFlavoredFormat readerName
+    case optFrom opts of
+      Just f  -> Format.parseFlavoredFormat f
+      Nothing -> case Format.formatFromFilePaths sources of
+        Just f' -> return f'
+        Nothing | sources == ["-"] -> return $ defFlavor "markdown"
+                | any (isURI . T.pack) sources -> return $ defFlavor "html"
+                | otherwise -> do
+                    report $ CouldNotDeduceFormat
+                      (map (T.pack . takeExtension) sources) "markdown"
+                    return $ defFlavor "markdown"
+
   let makeSandboxed pureReader =
         let files = maybe id (:) (optReferenceDoc opts) .
                     maybe id (:) (optEpubMetadata opts) .
@@ -176,7 +175,7 @@
             components <- engineLoadCustom scriptingEngine scriptPath
             r <- case customReader components of
                    Nothing -> throwError $ PandocAppError $
-                               readerName <> " does not contain a custom reader"
+                               readerNameBase <> " does not contain a custom reader"
                    Just r -> return r
             let extsConf = fromMaybe mempty (customExtensions components)
             rexts <- Format.applyExtensionsDiff extsConf flvrd
diff --git a/src/Text/Pandoc/App/CommandLineOptions.hs b/src/Text/Pandoc/App/CommandLineOptions.hs
--- a/src/Text/Pandoc/App/CommandLineOptions.hs
+++ b/src/Text/Pandoc/App/CommandLineOptions.hs
@@ -223,6 +223,7 @@
           map ("latex",) latexEngines ++
           map ("beamer",) latexEngines ++
           [ ("ms", "pdfroff")
+          , ("typst", "typst")
           , ("context", "context")
           ]
 
@@ -235,8 +236,7 @@
 options =
     [ Option "fr" ["from","read"]
                  (ReqArg
-                  (\arg opt -> return opt { optFrom =
-                                              Just (T.toLower $ T.pack arg) })
+                  (\arg opt -> return opt { optFrom = Just $ T.pack arg })
                   "FORMAT")
                  ""
 
diff --git a/src/Text/Pandoc/App/FormatHeuristics.hs b/src/Text/Pandoc/App/FormatHeuristics.hs
deleted file mode 100644
--- a/src/Text/Pandoc/App/FormatHeuristics.hs
+++ /dev/null
@@ -1,92 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{- |
-   Module      : Text.Pandoc.App.FormatHeuristics
-   Copyright   : Copyright (C) 2006-2023 John MacFarlane
-   License     : GNU GPL, version 2 or above
-
-   Maintainer  : John MacFarlane <jgm@berkeley@edu>
-   Stability   : alpha
-   Portability : portable
-
-Guess the format of a file from its name.
--}
-module Text.Pandoc.App.FormatHeuristics
-  ( formatFromFilePaths
-  ) where
-
-import Data.Char (toLower)
-import Data.Foldable (asum)
-import Data.Text (Text)
-import System.FilePath (takeExtension)
-
--- | Determines default format based on file extensions; uses the format
--- of the first extension that's associated with a format.
---
--- Examples:
---
--- > formatFromFilePaths ["text.unknown", "no-extension"]
--- Nothing
---
--- > formatFromFilePaths ["my.md", "other.rst"]
--- Just "markdown"
-formatFromFilePaths :: [FilePath] -> Maybe Text
-formatFromFilePaths = asum . map formatFromFilePath
-
--- | Determines format based on file extension.
-formatFromFilePath :: FilePath -> Maybe Text
-formatFromFilePath x =
-  case takeExtension (map toLower x) of
-    ".adoc"     -> Just "asciidoc"
-    ".asciidoc" -> Just "asciidoc"
-    ".context"  -> Just "context"
-    ".ctx"      -> Just "context"
-    ".db"       -> Just "docbook"
-    ".doc"      -> Just "doc"  -- so we get an "unknown reader" error
-    ".docx"     -> Just "docx"
-    ".dokuwiki" -> Just "dokuwiki"
-    ".epub"     -> Just "epub"
-    ".fb2"      -> Just "fb2"
-    ".htm"      -> Just "html"
-    ".html"     -> Just "html"
-    ".icml"     -> Just "icml"
-    ".json"     -> Just "json"
-    ".latex"    -> Just "latex"
-    ".lhs"      -> Just "markdown+lhs"
-    ".ltx"      -> Just "latex"
-    ".markdown" -> Just "markdown"
-    ".markua"   -> Just "markua"
-    ".mkdn"     -> Just "markdown"
-    ".mkd"      -> Just "markdown"
-    ".mdwn"     -> Just "markdown"
-    ".mdown"    -> Just "markdown"
-    ".Rmd"      -> Just "markdown"
-    ".md"       -> Just "markdown"
-    ".ms"       -> Just "ms"
-    ".muse"     -> Just "muse"
-    ".native"   -> Just "native"
-    ".odt"      -> Just "odt"
-    ".opml"     -> Just "opml"
-    ".org"      -> Just "org"
-    ".pdf"      -> Just "pdf"  -- so we get an "unknown reader" error
-    ".pptx"     -> Just "pptx"
-    ".ris"      -> Just "ris"
-    ".roff"     -> Just "ms"
-    ".rst"      -> Just "rst"
-    ".rtf"      -> Just "rtf"
-    ".s5"       -> Just "s5"
-    ".t2t"      -> Just "t2t"
-    ".tei"      -> Just "tei"
-    ".tex"      -> Just "latex"
-    ".texi"     -> Just "texinfo"
-    ".texinfo"  -> Just "texinfo"
-    ".text"     -> Just "markdown"
-    ".textile"  -> Just "textile"
-    ".txt"      -> Just "markdown"
-    ".wiki"     -> Just "mediawiki"
-    ".xhtml"    -> Just "html"
-    ".ipynb"    -> Just "ipynb"
-    ".csv"      -> Just "csv"
-    ".tsv"      -> Just "tsv"
-    ".bib"      -> Just "biblatex"
-    ['.',y]     | y `elem` ['1'..'9'] -> Just "man"
-    _           -> Nothing
diff --git a/src/Text/Pandoc/App/OutputSettings.hs b/src/Text/Pandoc/App/OutputSettings.hs
--- a/src/Text/Pandoc/App/OutputSettings.hs
+++ b/src/Text/Pandoc/App/OutputSettings.hs
@@ -37,10 +37,10 @@
 import System.IO (stdout)
 import Text.Pandoc.Chunks (PathTemplate(..))
 import Text.Pandoc
-import Text.Pandoc.App.FormatHeuristics (formatFromFilePaths)
 import Text.Pandoc.App.Opt (Opt (..))
 import Text.Pandoc.App.CommandLineOptions (engines)
-import qualified Text.Pandoc.Format as Format
+import Text.Pandoc.Format (FlavoredFormat (..), applyExtensionsDiff,
+                           parseFlavoredFormat, formatFromFilePaths)
 import Text.Pandoc.Highlighting (lookupHighlightingStyle)
 import Text.Pandoc.Scripting (ScriptingEngine (engineLoadCustom),
                               CustomComponents(..))
@@ -72,24 +72,26 @@
 
   let pdfOutput = map toLower (takeExtension outputFile) == ".pdf" ||
                   optTo opts == Just "pdf"
-  (writerName, maybePdfProg) <-
+  let defaultOutput = "html"
+  defaultOutputFlavor <- parseFlavoredFormat defaultOutput
+  (flvrd@(FlavoredFormat format _extsDiff), maybePdfProg) <-
     if pdfOutput
-       then liftIO $ pdfWriterAndProg
-               (case optTo opts of
-                  Just "pdf" -> Nothing
-                  x          -> x)
-               (optPdfEngine opts)
+       then do
+         outflavor <- case optTo opts of
+                        Just x | x /= "pdf" -> Just <$> parseFlavoredFormat x
+                        _ -> pure Nothing
+         liftIO $ pdfWriterAndProg outflavor (optPdfEngine opts)
        else case optTo opts of
-              Just f -> return (f, Nothing)
+              Just f -> (, Nothing) <$> parseFlavoredFormat f
               Nothing
-               | outputFile == "-" -> return ("html", Nothing)
-               | otherwise ->
-                     case formatFromFilePaths [outputFile] of
-                           Nothing -> do
-                             report $ CouldNotDeduceFormat
-                                [T.pack $ takeExtension outputFile] "html"
-                             return ("html", Nothing)
-                           Just f  -> return (f, Nothing)
+               | outputFile == "-" ->
+                   return (defaultOutputFlavor, Nothing)
+               | otherwise -> case formatFromFilePaths [outputFile] of
+                   Nothing -> do
+                     report $ CouldNotDeduceFormat
+                       [T.pack $ takeExtension outputFile] defaultOutput
+                     return (defaultOutputFlavor,Nothing)
+                   Just f  -> return (f, Nothing)
 
   let makeSandboxed pureWriter =
           let files = maybe id (:) (optReferenceDoc opts) .
@@ -104,9 +106,6 @@
                  ByteStringWriter w ->
                    ByteStringWriter $ \o d -> sandbox files (w o d)
 
-  flvrd@(Format.FlavoredFormat format _extsDiff) <-
-    Format.parseFlavoredFormat writerName
-
   let standalone = optStandalone opts || isBinaryFormat format || pdfOutput
   let templateOrThrow = \case
         Left  e -> throwError $ PandocTemplateError (T.pack e)
@@ -134,7 +133,7 @@
                          format <> " does not contain a custom writer"
              Just w -> return w
       let extsConf = fromMaybe mempty $ customExtensions components
-      wexts <- Format.applyExtensionsDiff extsConf flvrd
+      wexts <- applyExtensionsDiff extsConf flvrd
       templ <- processCustomTemplate $
                case customTemplate components of
                  Nothing -> throwError $ PandocNoTemplateError format
@@ -272,18 +271,16 @@
   where go Nothing             = Just $ toVal val
         go (Just x)            = Just x
 
-baseWriterName :: T.Text -> T.Text
-baseWriterName = T.takeWhile (\c -> c /= '+' && c /= '-')
-
-pdfWriterAndProg :: Maybe T.Text              -- ^ user-specified writer name
+pdfWriterAndProg :: Maybe FlavoredFormat      -- ^ user-specified format
                  -> Maybe String              -- ^ user-specified pdf-engine
-                 -> IO (T.Text, Maybe String) -- ^ IO (writerName, maybePdfEngineProg)
+                 -> IO (FlavoredFormat, Maybe String) -- ^ format, pdf-engine
 pdfWriterAndProg mWriter mEngine =
   case go mWriter mEngine of
       Right (writ, prog) -> return (writ, Just prog)
       Left err           -> liftIO $ E.throwIO $ PandocAppError err
     where
-      go Nothing Nothing       = Right ("latex", "pdflatex")
+      go Nothing Nothing       = Right
+                                 (FlavoredFormat "latex" mempty, "pdflatex")
       go (Just writer) Nothing = (writer,) <$> engineForWriter writer
       go Nothing (Just engine) = (,engine) <$> writerForEngine (takeBaseName engine)
       go (Just writer) (Just engine) | isCustomWriter writer =
@@ -291,23 +288,25 @@
            -- what they are doing.
            Right (writer, engine)
       go (Just writer) (Just engine) =
-           case find (== (baseWriterName writer, takeBaseName engine)) engines of
+           case find (== (formatName writer, takeBaseName engine)) engines of
                 Just _  -> Right (writer, engine)
                 Nothing -> Left $ "pdf-engine " <> T.pack engine <>
-                           " is not compatible with output format " <> writer
+                           " is not compatible with output format " <>
+                           formatName writer
 
       writerForEngine eng = case [f | (f,e) <- engines, e == eng] of
-                                 fmt : _ -> Right fmt
+                                 fmt : _ -> Right (FlavoredFormat fmt mempty)
                                  []      -> Left $
                                    "pdf-engine " <> T.pack eng <> " not known"
 
-      engineForWriter "pdf" = Left "pdf writer"
-      engineForWriter w = case [e | (f,e) <- engines, f == baseWriterName w] of
+      engineForWriter (FlavoredFormat "pdf" _) = Left "pdf writer"
+      engineForWriter w = case [e | (f,e) <- engines, f == formatName w] of
                                 eng : _ -> Right eng
                                 []      -> Left $
-                                   "cannot produce pdf output from " <> w
+                                   "cannot produce pdf output from " <>
+                                   formatName w
 
-      isCustomWriter w = ".lua" `T.isSuffixOf` w
+      isCustomWriter w = ".lua" `T.isSuffixOf` formatName w
 
 isBinaryFormat :: T.Text -> Bool
 isBinaryFormat s =
diff --git a/src/Text/Pandoc/Extensions.hs b/src/Text/Pandoc/Extensions.hs
--- a/src/Text/Pandoc/Extensions.hs
+++ b/src/Text/Pandoc/Extensions.hs
@@ -466,6 +466,7 @@
 getDefaultExtensions "opml"            = pandocExtensions -- affects notes
 getDefaultExtensions "markua"          = extensionsFromList
                                           []
+getDefaultExtensions "typst"           = extensionsFromList [Ext_citations]
 getDefaultExtensions _                 = extensionsFromList
                                           [Ext_auto_identifiers]
 
@@ -644,4 +645,5 @@
   getAll "mediawiki"       = autoIdExtensions <>
     extensionsFromList
     [ Ext_smart ]
+  getAll "typst"           = extensionsFromList [Ext_citations]
   getAll _                 = mempty
diff --git a/src/Text/Pandoc/Format.hs b/src/Text/Pandoc/Format.hs
--- a/src/Text/Pandoc/Format.hs
+++ b/src/Text/Pandoc/Format.hs
@@ -16,17 +16,22 @@
   , parseFlavoredFormat
   , applyExtensionsDiff
   , getExtensionsConfig
+  , formatFromFilePaths
   ) where
 
 import Control.Monad.Except (throwError)
+import Data.Char (toLower)
+import Data.Foldable (asum)
 import Data.List (foldl')
-import System.FilePath (splitExtension)
+import System.FilePath (splitExtension, takeExtension)
 import Text.Pandoc.Class (PandocMonad)
 import Text.Pandoc.Error (PandocError (..))
 import Text.Pandoc.Extensions
-  ( Extensions
+  ( Extension (Ext_literate_haskell)
+  , Extensions
   , disableExtensions
   , enableExtension
+  , extensionsFromList
   , extensionsToList
   , getAllExtensions
   , getDefaultExtensions
@@ -148,3 +153,81 @@
                                           extsToEnable extsDiff}
           _   -> extsDiff{extsToDisable = enableExtension ext $
                                           extsToDisable extsDiff}
+
+-- | Determines default format based on file extensions; uses the format
+-- of the first extension that's associated with a format.
+--
+-- Examples:
+--
+-- > formatFromFilePaths ["text.unknown", "no-extension"]
+-- Nothing
+--
+-- > formatFromFilePaths ["my.md", "other.rst"]
+-- Just "markdown"
+formatFromFilePaths :: [FilePath] -> (Maybe FlavoredFormat)
+formatFromFilePaths = asum . map formatFromFilePath
+
+-- | Determines format based on file extension.
+formatFromFilePath :: FilePath -> Maybe FlavoredFormat
+formatFromFilePath x =
+  case takeExtension (map toLower x) of
+    ".Rmd"      -> defFlavor "markdown"
+    ".adoc"     -> defFlavor "asciidoc"
+    ".asciidoc" -> defFlavor "asciidoc"
+    ".bib"      -> defFlavor "biblatex"
+    ".context"  -> defFlavor "context"
+    ".csv"      -> defFlavor "csv"
+    ".ctx"      -> defFlavor "context"
+    ".db"       -> defFlavor "docbook"
+    ".doc"      -> defFlavor "doc"  -- so we get an "unknown reader" error
+    ".docx"     -> defFlavor "docx"
+    ".dokuwiki" -> defFlavor "dokuwiki"
+    ".epub"     -> defFlavor "epub"
+    ".fb2"      -> defFlavor "fb2"
+    ".htm"      -> defFlavor "html"
+    ".html"     -> defFlavor "html"
+    ".icml"     -> defFlavor "icml"
+    ".ipynb"    -> defFlavor "ipynb"
+    ".json"     -> defFlavor "json"
+    ".latex"    -> defFlavor "latex"
+    ".lhs"      -> defFlavor "markdown" `withExtension` Ext_literate_haskell
+    ".ltx"      -> defFlavor "latex"
+    ".markdown" -> defFlavor "markdown"
+    ".markua"   -> defFlavor "markua"
+    ".md"       -> defFlavor "markdown"
+    ".mdown"    -> defFlavor "markdown"
+    ".mdwn"     -> defFlavor "markdown"
+    ".mkd"      -> defFlavor "markdown"
+    ".mkdn"     -> defFlavor "markdown"
+    ".ms"       -> defFlavor "ms"
+    ".muse"     -> defFlavor "muse"
+    ".native"   -> defFlavor "native"
+    ".odt"      -> defFlavor "odt"
+    ".opml"     -> defFlavor "opml"
+    ".org"      -> defFlavor "org"
+    ".pdf"      -> defFlavor "pdf"  -- so we get an "unknown reader" error
+    ".pptx"     -> defFlavor "pptx"
+    ".ris"      -> defFlavor "ris"
+    ".roff"     -> defFlavor "ms"
+    ".rst"      -> defFlavor "rst"
+    ".rtf"      -> defFlavor "rtf"
+    ".s5"       -> defFlavor "s5"
+    ".t2t"      -> defFlavor "t2t"
+    ".tei"      -> defFlavor "tei"
+    ".tex"      -> defFlavor "latex"
+    ".texi"     -> defFlavor "texinfo"
+    ".texinfo"  -> defFlavor "texinfo"
+    ".text"     -> defFlavor "markdown"
+    ".textile"  -> defFlavor "textile"
+    ".tsv"      -> defFlavor "tsv"
+    ".txt"      -> defFlavor "markdown"
+    ".typ"      -> defFlavor "typst"
+    ".wiki"     -> defFlavor "mediawiki"
+    ".xhtml"    -> defFlavor "html"
+    ['.',y]     | y `elem` ['1'..'9'] -> defFlavor "man"
+    _           -> Nothing
+ where
+  defFlavor f = Just (FlavoredFormat f mempty)
+  withExtension Nothing _ = Nothing
+  withExtension (Just (FlavoredFormat f ed)) ext = Just $
+    FlavoredFormat f (ed <> ExtensionsDiff (extensionsFromList [ext]) mempty)
diff --git a/src/Text/Pandoc/PDF.hs b/src/Text/Pandoc/PDF.hs
--- a/src/Text/Pandoc/PDF.hs
+++ b/src/Text/Pandoc/PDF.hs
@@ -85,9 +85,17 @@
   case takeBaseName program of
     "wkhtmltopdf" -> makeWithWkhtmltopdf program pdfargs writer opts doc
     prog | prog `elem` ["pagedjs-cli" ,"weasyprint", "prince"] -> do
+      let mkOutArgs f =
+            if program `elem` ["pagedjs-cli", "prince"]
+               then ["-o", f]
+               else [f]
       source <- writer opts doc
       verbosity <- getVerbosity
-      liftIO $ html2pdf verbosity program pdfargs source
+      liftIO $ toPdfViaTempFile verbosity program pdfargs mkOutArgs source
+    "typst" -> do
+      source <- writer opts doc
+      verbosity <- getVerbosity
+      liftIO $ toPdfViaTempFile verbosity program pdfargs (:[]) source
     "pdfroff" -> do
       source <- writer opts doc
       let paperargs =
@@ -174,7 +182,7 @@
                  -- see #6474
   source <- writer opts doc
   verbosity <- getVerbosity
-  liftIO $ html2pdf verbosity program args source
+  liftIO $ toPdfViaTempFile verbosity program args (:[]) source
 
 handleImages :: (PandocMonad m, MonadIO m)
              => WriterOptions
@@ -432,24 +440,20 @@
              ExitFailure _ -> Left out
              ExitSuccess   -> Right out
 
-
-html2pdf  :: Verbosity    -- ^ Verbosity level
-          -> String       -- ^ Program (wkhtmltopdf, weasyprint, prince, or path)
+toPdfViaTempFile  ::
+             Verbosity    -- ^ Verbosity level
+          -> String       -- ^ Program (program name or path)
           -> [String]     -- ^ Args to program
-          -> Text         -- ^ HTML5 source
+          -> (String -> [String]) -- ^ Construct args for output file
+          -> Text         -- ^ Source
           -> IO (Either ByteString ByteString)
-html2pdf verbosity program args source =
-  -- write HTML to temp file so we don't have to rewrite
-  -- all links in `a`, `img`, `style`, `script`, etc. tags,
-  -- and piping to weasyprint didn't work on Windows either.
-  withTempFile "." "html2pdf.html" $ \file h1 ->
-    withTempFile "." "html2pdf.pdf" $ \pdfFile h2 -> do
+toPdfViaTempFile verbosity program args mkOutArgs source =
+  withTempFile "." "toPdfViaTempFile.html" $ \file h1 ->
+    withTempFile "." "toPdfViaTempFile.pdf" $ \pdfFile h2 -> do
       hClose h1
       hClose h2
       BS.writeFile file $ UTF8.fromText source
-      let pdfFileArgName = ["-o" | takeBaseName program `elem`
-                                   ["pagedjs-cli", "prince"]]
-      let programArgs = args ++ [file] ++ pdfFileArgName ++ [pdfFile]
+      let programArgs = args ++ [file] ++ mkOutArgs pdfFile
       env' <- getEnvironment
       when (verbosity >= INFO) $
         UTF8.readFile file >>=
diff --git a/src/Text/Pandoc/Readers/DocBook.hs b/src/Text/Pandoc/Readers/DocBook.hs
--- a/src/Text/Pandoc/Readers/DocBook.hs
+++ b/src/Text/Pandoc/Readers/DocBook.hs
@@ -44,11 +44,12 @@
 import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
 import Text.Pandoc.Options
 import Text.Pandoc.Logging (LogMessage(..))
-import Text.Pandoc.Shared (safeRead, extractSpaces)
+import Text.Pandoc.Shared (safeRead, extractSpaces, headerShift)
 import Text.Pandoc.Sources (ToSources(..), sourcesToText)
 import Text.TeXMath (readMathML, writeTeX)
 import qualified Data.Map as M
 import Text.Pandoc.XML.Light
+import Text.Pandoc.Walk (query)
 
 {-
 
@@ -320,7 +321,7 @@
 [x] para - A paragraph
 [ ] paramdef - Information about a function parameter in a programming language
 [x] parameter - A value or a symbolic reference to a value
-[ ] part - A division in a book
+[x] part - A division in a book
 [ ] partinfo - Meta-information for a Part
 [ ] partintro - An introduction to the contents of a part
 [ ] personblurb - A short description or note about a person
@@ -563,7 +564,14 @@
             docbookEntityMap
               (TL.fromStrict . handleInstructions . sourcesToText $ sources)
   (bs, st') <- flip runStateT (def{ dbContent = tree }) $ mapM parseBlock tree
-  return $ Pandoc (dbMeta st') (toList . mconcat $ bs)
+  let headerLevel (Header n _ _) = [n]
+      headerLevel _              = []
+  let bottomLevel = maybe 1 minimum $ nonEmpty $ query headerLevel bs
+  return $
+    -- handle the case where you have <part> or <chapter>
+    (if bottomLevel < 1
+        then headerShift (1 - bottomLevel)
+        else id) $ Pandoc (dbMeta st') $ toList $ mconcat bs
 
 -- We treat certain processing instructions by converting them to tags
 -- beginning "pi-".
@@ -746,6 +754,8 @@
   , "mediaobject"
   , "orderedlist"
   , "para"
+  , "part"
+  , "partinfo"
   , "preface"
   , "procedure"
   , "programlisting"
@@ -882,6 +892,7 @@
         "glosslist" -> definitionList <$>
                   mapM parseGlossEntry (filterChildren (named "glossentry") e)
         "chapter" -> modify (\st -> st{ dbBook = True}) >> sect 0
+        "part" -> modify (\st -> st{ dbBook = True}) >> sect (-1)
         "appendix" -> sect 0
         "preface" -> sect 0
         "bridgehead" -> para . strong <$> getInlines e
@@ -940,6 +951,7 @@
         "sect4info" -> skip  -- keywords & other metadata
         "sect5info" -> skip  -- keywords & other metadata
         "chapterinfo" -> skip -- keywords & other metadata
+        "partinfo" -> skip -- keywords & other metadata
         "glossaryinfo" -> skip  -- keywords & other metadata
         "appendixinfo" -> skip  -- keywords & other metadata
         "bookinfo" -> addMetadataFromElement e
@@ -1342,6 +1354,8 @@
          xrefTitleByElem el
              | not (T.null xrefLabel) = xrefLabel
              | otherwise              = case qName (elName el) of
+                  "book"         -> descendantContent "title" el
+                  "part"         -> descendantContent "title" el
                   "chapter"      -> descendantContent "title" el
                   "section"      -> descendantContent "title" el
                   "sect1"        -> descendantContent "title" el
diff --git a/src/Text/Pandoc/Readers/HTML.hs b/src/Text/Pandoc/Readers/HTML.hs
--- a/src/Text/Pandoc/Readers/HTML.hs
+++ b/src/Text/Pandoc/Readers/HTML.hs
@@ -813,18 +813,26 @@
     Nothing  -> contents
 
 pSpan :: PandocMonad m => TagParser m Inlines
-pSpan = try $ do
-  guardEnabled Ext_native_spans
-  TagOpen _ attr' <- lookAhead $ pSatisfy $ tagOpen (=="span") (const True)
-  let attr = toAttr attr'
-  contents <- pInTags "span" inline
-  let isSmallCaps = fontVariant == "small-caps" || "smallcaps" `elem` classes
-                    where styleAttr   = fromMaybe "" $ lookup "style" attr'
-                          fontVariant = fromMaybe "" $ pickStyleAttrProps ["font-variant"] styleAttr
-                          classes     = maybe []
-                                          T.words $ lookup "class" attr'
-  let tag = if isSmallCaps then B.smallcaps else B.spanWith attr
-  return $ tag contents
+pSpan = do
+  (TagOpen _ attr') <- lookAhead (pSatisfy $ tagOpen (=="span") (const True))
+  exts <- getOption readerExtensions
+  if extensionEnabled Ext_native_spans exts
+     then do
+       contents <- pInTags "span" inline
+       let attr = toAttr attr'
+       let classes = maybe [] T.words $ lookup "class" attr'
+       let styleAttr   = fromMaybe "" $ lookup "style" attr'
+       let fontVariant = fromMaybe "" $
+                          pickStyleAttrProps ["font-variant"] styleAttr
+       let isSmallCaps = fontVariant == "small-caps" ||
+                           "smallcaps" `elem` classes
+       let tag = if isSmallCaps then B.smallcaps else B.spanWith attr
+       return $ tag contents
+     else if extensionEnabled Ext_raw_html exts
+             then do
+               tag <- pSatisfy $ tagOpen (=="span") (const True)
+               return $ B.rawInline "html" $ renderTags' [tag]
+             else pInTags "span" inline -- just contents
 
 pRawHtmlInline :: PandocMonad m => TagParser m Inlines
 pRawHtmlInline = do
diff --git a/src/Text/Pandoc/Readers/Jira.hs b/src/Text/Pandoc/Readers/Jira.hs
--- a/src/Text/Pandoc/Readers/Jira.hs
+++ b/src/Text/Pandoc/Readers/Jira.hs
@@ -12,6 +12,7 @@
 module Text.Pandoc.Readers.Jira ( readJira ) where
 
 import Control.Monad.Except (throwError)
+import Data.List (partition)
 import Data.Text (Text, append, pack, singleton)
 import Text.Pandoc.XML (lookupEntity)
 import Text.Jira.Parser (parse)
@@ -76,7 +77,16 @@
 -- | Create a pandoc @'Div'@ from a panel.
 toPandocDiv :: [Jira.Parameter] -> [Jira.Block] -> Blocks
 toPandocDiv params =
-  divWith ("", ["panel"], map paramToPair params) . foldMap jiraToPandocBlocks
+  let (titles, params') = partition ((== "title") . Jira.parameterKey) params
+      addTitle = case titles of
+        [] ->
+          id
+        (title:_) -> \blks ->
+          (divWith ("", ["panelheader"], []) . plain . strong $
+           text (Jira.parameterValue title)) <> blks
+  in divWith ("", ["panel"], map paramToPair params')
+     . addTitle
+     . foldMap jiraToPandocBlocks
 
 paramToPair :: Jira.Parameter -> (Text, Text)
 paramToPair (Jira.Parameter key value) = (key, value)
diff --git a/src/Text/Pandoc/Readers/Org/Inlines.hs b/src/Text/Pandoc/Readers/Org/Inlines.hs
--- a/src/Text/Pandoc/Readers/Org/Inlines.hs
+++ b/src/Text/Pandoc/Readers/Org/Inlines.hs
@@ -758,7 +758,7 @@
 
 -- | Chars not allowed at the (inner) border of emphasis
 emphasisForbiddenBorderChars :: [Char]
-emphasisForbiddenBorderChars = "\t\n\r "
+emphasisForbiddenBorderChars = "\t\n\r \x200B"
 
 -- | The maximum number of newlines within
 emphasisAllowedNewlines :: Int
diff --git a/src/Text/Pandoc/Readers/Org/ParserState.hs b/src/Text/Pandoc/Readers/Org/ParserState.hs
--- a/src/Text/Pandoc/Readers/Org/ParserState.hs
+++ b/src/Text/Pandoc/Readers/Org/ParserState.hs
@@ -164,8 +164,8 @@
 defaultOrgParserState :: OrgParserState
 defaultOrgParserState = OrgParserState
   { orgStateAnchorIds = []
-  , orgStateEmphasisPreChars = "-\t ('\"{"
-  , orgStateEmphasisPostChars  = "-\t\n .,:!?;'\")}["
+  , orgStateEmphasisPreChars = "-\t ('\"{\x200B"
+  , orgStateEmphasisPostChars  = "-\t\n .,:!?;'\")}[\x200B"
   , orgStateEmphasisCharStack = []
   , orgStateEmphasisNewlines = Nothing
   , orgStateExportSettings = def
diff --git a/src/Text/Pandoc/Writers.hs b/src/Text/Pandoc/Writers.hs
--- a/src/Text/Pandoc/Writers.hs
+++ b/src/Text/Pandoc/Writers.hs
@@ -72,6 +72,7 @@
     , writeTEI
     , writeTexinfo
     , writeTextile
+    , writeTypst
     , writeXWiki
     , writeZimWiki
     , getWriter
@@ -121,6 +122,7 @@
 import Text.Pandoc.Writers.TEI
 import Text.Pandoc.Writers.Texinfo
 import Text.Pandoc.Writers.Textile
+import Text.Pandoc.Writers.Typst
 import Text.Pandoc.Writers.XWiki
 import Text.Pandoc.Writers.ZimWiki
 
@@ -177,6 +179,7 @@
   ,("xwiki"        , TextWriter writeXWiki)
   ,("zimwiki"      , TextWriter writeZimWiki)
   ,("textile"      , TextWriter writeTextile)
+  ,("typst"        , TextWriter writeTypst)
   ,("rtf"          , TextWriter writeRTF)
   ,("org"          , TextWriter writeOrg)
   ,("asciidoc"     , TextWriter writeAsciiDoc)
diff --git a/src/Text/Pandoc/Writers/Docx.hs b/src/Text/Pandoc/Writers/Docx.hs
--- a/src/Text/Pandoc/Writers/Docx.hs
+++ b/src/Text/Pandoc/Writers/Docx.hs
@@ -48,7 +48,7 @@
 import Data.Digest.Pure.SHA (sha1, showDigest)
 import Skylighting
 import Text.Pandoc.Class (PandocMonad, report, toLang, getMediaBag)
-import Text.Pandoc.Translations (translateTerm)
+import Text.Pandoc.Translations (Term(Abstract), translateTerm)
 import Text.Pandoc.MediaBag (lookupMedia, MediaItem(..))
 import qualified Text.Pandoc.Translations as Term
 import qualified Text.Pandoc.Class.PandocMonad as P
@@ -767,7 +767,15 @@
   date <- withParaPropM (pStyleM "Date") $ blocksToOpenXML opts [Para dat | not (null dat)]
   abstract <- if null abstract'
                  then return []
-                 else withParaPropM (pStyleM "Abstract") $ blocksToOpenXML opts abstract'
+                 else do
+                   abstractTitle <- translateTerm Abstract
+                   abstractTit <- withParaPropM (pStyleM "AbstractTitle") $
+                                   blocksToOpenXML opts
+                                     [Para [Str abstractTitle]]
+                   abstractContents <- withParaPropM (pStyleM "Abstract") $
+                                         blocksToOpenXML opts abstract'
+                   return $ abstractTit <> abstractContents
+
   let convertSpace (Str x : Space : Str y : xs) = Str (x <> " " <> y) : xs
       convertSpace (Str x : Str y : xs)         = Str (x <> y) : xs
       convertSpace xs                           = xs
diff --git a/src/Text/Pandoc/Writers/HTML.hs b/src/Text/Pandoc/Writers/HTML.hs
--- a/src/Text/Pandoc/Writers/HTML.hs
+++ b/src/Text/Pandoc/Writers/HTML.hs
@@ -543,10 +543,17 @@
                 = H5.section ! A.id "footnotes"
                              ! A.class_ className
                              ! customAttribute "epub:type" "footnotes" $ x
-        | html5 = H5.section ! A5.id "footnotes"
+        | html5
+        , refLocation == EndOfDocument
+        , slideVariant == RevealJsSlides -- need a section for a new slide:
+                = H5.section ! A5.id "footnotes"
                              ! A5.class_ className
                              ! A5.role "doc-endnotes"
                              $ x
+        | html5 = H5.aside   ! A5.id "footnotes"
+                             ! A5.class_ className
+                             ! A5.role "doc-endnotes"
+                             $ x
         | slideVariant /= NoSlides = H.div ! A.class_ "footnotes slide" $ x
         | otherwise = H.div ! A.class_ className $ x
   return $
@@ -559,11 +566,12 @@
            hrtag
            -- Keep the previous output exactly the same if we don't
            -- have multiple notes sections
-           if startCounter == 1
-             then H.ol $ mconcat notes >> nl
-             else H.ol ! A.start (fromString (show startCounter)) $
-                         mconcat notes >> nl
-           nl
+           case epubVersion of
+             Just _ -> mconcat notes
+             Nothing | startCounter == 1 ->
+               (H.ol (nl >> mconcat notes)) >> nl
+             Nothing -> (H.ol ! A.start (fromString (show startCounter)) $
+                         nl >> mconcat notes) >> nl
 
 -- | Parse a mailto link; return Just (name, domain) or Nothing.
 parseMailto :: Text -> Maybe (Text, Text)
@@ -677,7 +685,7 @@
             => WriterOptions -> Attr -> StateT WriterState m [Attribute]
 attrsToHtml opts (id',classes',keyvals) = do
   attrs <- toAttrs keyvals
-  let classes'' = filter (not . T.null) classes'
+  let classes'' = nubOrd $ filter (not . T.null) classes'
   return $
     [prefixedId opts id' | not (T.null id')] ++
     [A.class_ (toValue $ T.unwords classes'') | not (null classes'')] ++ attrs
@@ -794,9 +802,19 @@
     modify $ \st -> st{ stInSection = True }
     res <- blockListToHtml opts innerSecs
     modify $ \st -> st{ stInSection = inSection }
-    return res
-  let classes' = nubOrd $
-                  ["title-slide" | titleSlide] ++ ["slide" | slide] ++
+    notes <- gets stNotes
+    let emitNotes = writerReferenceLocation opts == EndOfSection &&
+                     not (null notes)
+    if emitNotes
+      then do
+        st <- get
+        renderedNotes <- footnoteSection (writerReferenceLocation opts)
+                           (stEmittedNotes st + 1) (reverse notes)
+        modify (\st' -> st'{ stNotes = mempty,
+                             stEmittedNotes = stEmittedNotes st' + length notes })
+        return (res <> renderedNotes)
+      else return res
+  let classes' = ["title-slide" | titleSlide] ++ ["slide" | slide] ++
                   ["section" | (slide || writerSectionDivs opts) &&
                                not html5 ] ++
                   ["level" <> tshow level | slide || writerSectionDivs opts ]
@@ -1055,8 +1073,6 @@
 -- the block if necessary.
 blockToHtml :: PandocMonad m => WriterOptions -> Block -> StateT WriterState m Html
 blockToHtml opts block = do
-  -- Ignore inserted section divs -- they are not blocks as they came from
-  -- the document itself (at least not when coming from markdown)
   let isSection = case block of
         Div (_, classes, _) _ | "section" `elem` classes -> True
         _ -> False
@@ -1066,8 +1082,7 @@
   doc <- blockToHtmlInner opts block
   st <- get
   let emitNotes =
-        (writerReferenceLocation opts == EndOfBlock && stBlockLevel st == 1) ||
-        (writerReferenceLocation opts == EndOfSection && isSection)
+        writerReferenceLocation opts == EndOfBlock && stBlockLevel st == 1
   res <- if emitNotes
     then do
       notes <- if null (stNotes st)
@@ -1560,7 +1575,8 @@
                                             else alternate
                               in (tg $ H.a ! A.href (toValue s) $ toHtml linkTxt
                                  , [A5.controls ""] )
-                            normSrc = maybe (T.unpack s) uriPath (parseURIReference $ T.unpack s)
+                            s' = fromMaybe s $ T.stripSuffix ".gz" s
+                            normSrc = maybe (T.unpack s) uriPath (parseURIReference $ T.unpack s')
                             (tag, specAttrs) = case mediaCategory normSrc of
                               Just "image" -> imageTag
                               Just "video" -> mediaTag H5.video "Video"
@@ -1612,36 +1628,51 @@
                 => WriterOptions -> Text -> [Block]
                 -> StateT WriterState m Html
 blockListToNote opts ref blocks = do
-  html5 <- gets stHtml5
-  -- If last block is Para or Plain, include the backlink at the end of
-  -- that block. Otherwise, insert a new Plain block with the backlink.
-  let kvs = [("role","doc-backlink") | html5]
-  let backlink = [Link ("",["footnote-back"],kvs)
-                    [Str "↩"] ("#" <> "fnref" <> ref,"")]
-  let blocks'  = if null blocks
-                    then []
-                    else let lastBlock   = last blocks
-                             otherBlocks = init blocks
-                         in  case lastBlock of
-                                  Para [Image (_,cls,_) _ (_,tit)]
-                                      | "fig:" `T.isPrefixOf` tit
-                                        || "r-stretch" `elem` cls
-                                            -> otherBlocks ++ [lastBlock,
-                                                  Plain backlink]
-                                  Para lst  -> otherBlocks ++
-                                                 [Para (lst ++ backlink)]
-                                  Plain lst -> otherBlocks ++
-                                                 [Plain (lst ++ backlink)]
-                                  _         -> otherBlocks ++ [lastBlock,
-                                                 Plain backlink]
-  contents <- blockListToHtml opts blocks'
-  let noteItem = H.li ! prefixedId opts ("fn" <> ref) $ contents
   epubVersion <- gets stEPUBVersion
-  let noteItem' = case epubVersion of
-                       Just EPUB3 -> noteItem !
-                                       customAttribute "epub:type" "footnote"
-                       _          -> noteItem
-  return $ nl >> noteItem'
+  html5 <- gets stHtml5
+  case epubVersion of
+    Nothing -> do -- web page
+      -- If last block is Para or Plain, include the backlink at the end of
+      -- that block. Otherwise, insert a new Plain block with the backlink.
+      let kvs = [("role","doc-backlink") | html5]
+      let backlink = [Link ("",["footnote-back"],kvs)
+                        [Str "↩"] ("#" <> "fnref" <> ref,"")]
+      let blocks'  = if null blocks
+                        then []
+                        else let lastBlock   = last blocks
+                                 otherBlocks = init blocks
+                             in  case lastBlock of
+                                      Para [Image (_,cls,_) _ (_,tit)]
+                                          | "fig:" `T.isPrefixOf` tit
+                                            || "r-stretch" `elem` cls
+                                                -> otherBlocks ++ [lastBlock,
+                                                      Plain backlink]
+                                      Para lst  -> otherBlocks ++
+                                                     [Para (lst ++ backlink)]
+                                      Plain lst -> otherBlocks ++
+                                                     [Plain (lst ++ backlink)]
+                                      _         -> otherBlocks ++ [lastBlock,
+                                                     Plain backlink]
+      contents <- blockListToHtml opts blocks'
+      let noteItem = H.li ! prefixedId opts ("fn" <> ref) $ contents
+      return $ noteItem >> nl
+    Just epubv -> do
+      let kvs = [("role","doc-backlink") | html5]
+      let backlink = Link ("",["footnote-back"],kvs)
+                        [Str ref] ("#" <> "fnref" <> ref,"")
+      let blocks' =
+           case blocks of
+             (Para ils : rest) ->
+                Para (backlink : Str "." : Space : ils) : rest
+             (Plain ils : rest) ->
+                Plain (backlink : Str "." : Space : ils) : rest
+             _ -> Para [backlink , Str "."] : blocks
+      contents <- blockListToHtml opts blocks'
+      let noteItem = (if epubv == EPUB3
+                         then H5.aside ! customAttribute "epub:type" "footnote"
+                         else H.div) ! prefixedId opts ("fn" <> ref)
+                      $ nl >> contents >> nl
+      return $ noteItem >> nl
 
 inDiv :: PandocMonad m=> Text -> Html -> StateT WriterState m Html
 inDiv cls x = do
diff --git a/src/Text/Pandoc/Writers/ICML.hs b/src/Text/Pandoc/Writers/ICML.hs
--- a/src/Text/Pandoc/Writers/ICML.hs
+++ b/src/Text/Pandoc/Writers/ICML.hs
@@ -628,13 +628,19 @@
                    , selfClosingTag "PathPointType" [("Anchor", hw<>" -"<>hh),
                        ("LeftDirection", hw<>" -"<>hh), ("RightDirection", hw<>" -"<>hh)]
                    ]
-      img = if "data:" `Text.isPrefixOf` src' && "base64," `Text.isInfixOf` src'
+
+      isdata = "data:" `Text.isPrefixOf` src' && "base64," `Text.isInfixOf` src'
+      contents =
+            if isdata
                then -- see #8398
                   inTags True "Contents" [] $
-                    literal ("<![CDATA[" <>
-                             Text.drop 1 (Text.dropWhile (/=',') src') <> "]]>")
-               else selfClosingTag "Link" [("Self", "ueb"), ("LinkResourceURI", src')]
-
+                    literal ("<![CDATA[" <> Text.replace "%20" ""
+                            (Text.drop 1 (Text.dropWhile (/=',') src')) <> "]]>")
+               else mempty
+      link = if isdata
+                then mempty
+                else  selfClosingTag "Link" [("Self", "ueb"),
+                                             ("LinkResourceURI", src')]
       image  = inTags True "Image"
                    [("Self","ue6"), ("ItemTransform", scale<>" -"<>hw<>" -"<>hh)]
                  $ vcat [
@@ -643,8 +649,9 @@
                        , selfClosingTag "GraphicBounds" [("Left","0"), ("Top","0")
                          , ("Right",  showFl $ ow*ow / imgWidth)
                          , ("Bottom", showFl $ oh*oh / imgHeight)]
+                       , contents
                        ]
-                   , img
+                   , link
                    ]
       doc    = inTags True "CharacterStyleRange" attrs
                  $ inTags True "Rectangle" [("Self","uec"), ("StrokeWeight", "0"),
diff --git a/src/Text/Pandoc/Writers/LaTeX/Lang.hs b/src/Text/Pandoc/Writers/LaTeX/Lang.hs
--- a/src/Text/Pandoc/Writers/LaTeX/Lang.hs
+++ b/src/Text/Pandoc/Writers/LaTeX/Lang.hs
@@ -49,7 +49,14 @@
   | "x-classic" `elem` vars             = Just "classiclatin"
 toBabel (Lang "pt" _ (Just "BR") _ _ _) = Just "brazilian"
 toBabel (Lang "sl" _ _ _ _ _)           = Just "slovene"
-toBabel x                               = commonFromBcp47 x
+toBabel (Lang "zh" (Just "Hant") (Just "HK") _ _ _) = Just "chinese-hant-hk"
+toBabel (Lang "zh" (Just "Hant") (Just "MO") _ _ _) = Just "chinese-hant-mo"
+toBabel (Lang "zh" (Just "Hans") (Just "HK") _ _ _) = Just "chinese-hans-hk"
+toBabel (Lang "zh" (Just "Hans") (Just "MO") _ _ _) = Just "chinese-hans-mo"
+toBabel (Lang "zh" (Just "Hans") _ _ _ _) = Just "chinese-hans"
+toBabel (Lang "zh" (Just "Hant") _ _ _ _) = Just "chinese-hant"
+toBabel (Lang "zh" _ _ _ _ _)             = Just "chinese"
+toBabel x                                 = commonFromBcp47 x
 
 -- Takes a list of the constituents of a BCP47 language code
 -- and converts it to a string shared by Babel and Polyglossia.
diff --git a/src/Text/Pandoc/Writers/Markdown.hs b/src/Text/Pandoc/Writers/Markdown.hs
--- a/src/Text/Pandoc/Writers/Markdown.hs
+++ b/src/Text/Pandoc/Writers/Markdown.hs
@@ -677,7 +677,7 @@
                               then Just [Str "image"]
                               else Just alt
         Caption Nothing [Plain captInlines]
-          | captInlines == alt -> Just captInlines
+          | captInlines == alt || null alt -> Just captInlines
         _ -> Nothing
   case body of
     [Plain [Image imgAttr alt (src, ttl)]]
diff --git a/src/Text/Pandoc/Writers/Ms.hs b/src/Text/Pandoc/Writers/Ms.hs
--- a/src/Text/Pandoc/Writers/Ms.hs
+++ b/src/Text/Pandoc/Writers/Ms.hs
@@ -162,25 +162,6 @@
        return $ anchor $$ res
 blockToMs opts (Plain inlines) =
   splitSentences <$> inlineListToMs' opts inlines
-blockToMs opts (Para [Image attr alt (src,_tit)])
-  | let ext = takeExtension (T.unpack src) in (ext == ".ps" || ext == ".eps") = do
-  let (mbW,mbH) = (inPoints opts <$> dimension Width attr,
-                   inPoints opts <$> dimension Height attr)
-  let sizeAttrs = case (mbW, mbH) of
-                       (Just wp, Nothing) -> space <> doubleQuotes
-                              (literal (tshow (floor wp :: Int) <> "p"))
-                       (Just wp, Just hp) -> space <> doubleQuotes
-                              (literal (tshow (floor wp :: Int) <> "p")) <>
-                              space <>
-                              doubleQuotes (literal (tshow (floor hp :: Int)))
-                       _ -> empty
-  capt <- splitSentences <$> inlineListToMs' opts alt
-  return $ nowrap (literal ".PSPIC " <>
-             doubleQuotes (literal (escapeStr opts src)) <>
-             sizeAttrs) $$
-           literal ".ce 1000" $$
-           capt $$
-           literal ".ce 0"
 blockToMs opts (Para inlines) = do
   firstPara <- gets stFirstPara
   resetFirstPara
@@ -317,7 +298,32 @@
   contents <- mapM (definitionListItemToMs opts) items
   setFirstPara
   return (vcat contents)
-blockToMs opts (Figure attr _ body) = blockToMs opts $ Div attr body
+blockToMs opts (Figure figattr (Caption _ caption) body) =
+  case body of
+    [Plain [ Image attr _alt (src, _tit) ]]
+     | let ext = takeExtension (T.unpack src)
+        in (ext == ".ps" || ext == ".eps")
+      -> do
+         let (mbW,mbH) = (inPoints opts <$> dimension Width attr,
+                          inPoints opts <$> dimension Height attr)
+         let sizeAttrs = case (mbW, mbH) of
+                              (Just wp, Nothing) -> space <> doubleQuotes
+                                     (literal (tshow (floor wp :: Int) <> "p"))
+                              (Just wp, Just hp) -> space <> doubleQuotes
+                                     (literal (tshow (floor wp :: Int) <> "p"))
+                                     <> space <>
+                                     doubleQuotes
+                                      (literal (tshow (floor hp :: Int)))
+                              _ -> empty
+         capt <- blockToMs opts (Div figattr caption)
+         let captlines = height capt
+         return $ nowrap (literal ".PSPIC " <>
+                    doubleQuotes (literal (escapeStr opts src)) <>
+                    sizeAttrs) $$
+                  literal (".ce " <> tshow captlines) $$
+                  capt $$
+                  literal ".sp 1"
+    _ -> blockToMs opts $ Div figattr body
 
 -- | Convert bullet list item (list of blocks) to ms.
 bulletListItemToMs :: PandocMonad m => WriterOptions -> [Block] -> MS m (Doc Text)
diff --git a/src/Text/Pandoc/Writers/Typst.hs b/src/Text/Pandoc/Writers/Typst.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Pandoc/Writers/Typst.hs
@@ -0,0 +1,335 @@
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE BangPatterns        #-}
+{-# LANGUAGE LambdaCase          #-}
+{-# LANGUAGE TupleSections       #-}
+{-# LANGUAGE PatternGuards       #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications    #-}
+{-# LANGUAGE ViewPatterns        #-}
+{- |
+   Module      : Text.Pandoc.Writers.Typst
+   Copyright   : Copyright (C) 2023 John MacFarlane
+   License     : GNU GPL, version 2 or above
+
+   Maintainer  : John MacFarlane <jgm@berkeley.edu>
+   Stability   : alpha
+   Portability : portable
+
+Conversion of 'Pandoc' format into Typst markup
+(<https://typst.app>).
+-}
+module Text.Pandoc.Writers.Typst (
+    writeTypst
+  ) where
+import Text.Pandoc.Definition
+import Text.Pandoc.Class.PandocMonad ( PandocMonad )
+import Text.Pandoc.Options ( WriterOptions(..), WrapOption(..), isEnabled )
+import Data.Text (Text)
+import Data.List (intercalate, intersperse)
+import qualified Data.Text as T
+import Control.Monad.State ( StateT, evalStateT, gets, modify )
+import Text.Pandoc.Writers.Shared ( metaToContext, defField, resetField,
+                                    toLegacyTable, lookupMetaString )
+import Text.Pandoc.Shared (isTightList, orderedListMarkers)
+import Text.Pandoc.Writers.Math (convertMath)
+import qualified Text.TeXMath as TM
+import Text.DocLayout
+import Text.DocTemplates (renderTemplate)
+import Text.Pandoc.Extensions (Extension(..))
+import Text.Collate.Lang (Lang(..), parseLang)
+
+-- | Convert Pandoc to Typst.
+writeTypst :: PandocMonad m => WriterOptions -> Pandoc -> m Text
+writeTypst options document =
+  evalStateT (pandocToTypst options document)
+    WriterState{ stOptions = options,
+                 stEscapeContext = NormalContext,
+                 stNotes = [] }
+
+data EscapeContext = NormalContext | TermContext
+  deriving (Show, Eq)
+
+data WriterState =
+  WriterState {
+    stOptions :: WriterOptions,
+    stEscapeContext :: EscapeContext,
+    stNotes   :: [Doc Text]
+  }
+
+type TW m = StateT WriterState m
+
+pandocToTypst :: PandocMonad m
+              => WriterOptions -> Pandoc -> TW m Text
+pandocToTypst options (Pandoc meta blocks) = do
+  let colwidth = if writerWrapText options == WrapAuto
+                    then Just $ writerColumns options
+                    else Nothing
+  metadata <- metaToContext options
+              blocksToTypst
+              (fmap chomp . inlinesToTypst)
+              meta
+  main <- blocksToTypst blocks
+  noteContents <- reverse <$> gets stNotes
+  let notes = vsep $ zipWith
+                      (\(num :: Int) cont ->
+                        "#endnote" <> parens (brackets (text (show num))
+                                   <> ", " <>  brackets (chomp cont <> cr)))
+                      [1..] noteContents
+  let context = defField "body" main
+              $ defField "notes" notes
+              $ defField "toc" (writerTableOfContents options)
+              $ (case lookupMetaString "lang" meta of
+                    "" -> id
+                    lang ->
+                      case parseLang lang of
+                        Left _ -> id
+                        Right l ->
+                          resetField "lang" (langLanguage l) .
+                          maybe id (resetField "region") (langRegion l))
+              $ (if writerNumberSections options
+                    then defField "numbering" ("1.1.1.1.1" :: Text)
+                    else id)
+              $ metadata
+  return $ render colwidth $
+    case writerTemplate options of
+       Nothing  -> main
+       Just tpl -> renderTemplate tpl context
+
+blocksToTypst :: PandocMonad m => [Block] -> TW m (Doc Text)
+blocksToTypst blocks = vcat <$> mapM blockToTypst blocks
+
+blockToTypst :: PandocMonad m => Block -> TW m (Doc Text)
+blockToTypst block =
+  case block of
+    Plain inlines -> inlinesToTypst inlines
+    Para inlines -> ($$ blankline) <$> inlinesToTypst inlines
+    Header level (ident,_,_) inlines -> do
+      contents <- inlinesToTypst inlines
+      let lab = toLabel ident
+      return $ literal (T.replicate level "=") <> space <> contents <> cr <> lab
+    RawBlock fmt str ->
+      case fmt of
+        Format "typst" -> return $ literal str
+        _ -> return mempty
+    CodeBlock (_,cls,_) code -> do
+      let go :: Char -> (Int, Int) -> (Int, Int)
+          go '`' (longest, current) =
+            let !new = current + 1 in (max longest new, new)
+          go _ (longest, _) = (longest, 0)
+      let (longestBacktickSequence, _) = T.foldr go (0,0) code
+      let fence = literal $ T.replicate (max 3 (longestBacktickSequence + 1)) "`"
+      let lang = case cls of
+                   (cl:_) -> literal cl
+                   _ -> mempty
+      return $ fence <> lang <> cr <> literal code <> cr <> fence <> blankline
+    LineBlock lns -> do
+      contents <- inlinesToTypst (intercalate [LineBreak] lns)
+      return $ contents <> blankline
+    BlockQuote blocks -> do
+      contents <- blocksToTypst blocks
+      return $ "#blockquote[" $$ chomp contents $$ "]" $$ blankline
+    HorizontalRule ->
+      return $ blankline <> "#horizontalrule" <> blankline
+    OrderedList attribs items -> do
+      let addBlock = case attribs of
+                       (1, DefaultStyle, DefaultDelim) -> id
+                       (1, Decimal, Period) -> id
+                       (start, sty, delim) -> \x ->
+                              "#block[" $$
+                               ("#set enum" <>
+                                  parens (
+                                    "numbering: " <>
+                                    doubleQuoted
+                                      (head (orderedListMarkers
+                                             (1, sty, delim))) <>
+                                    ", start: " <>
+                                      text (show start) )) $$
+                               x $$
+                               "]"
+      items' <- mapM (fmap chomp . listItemToTypst 2 ("+ ")) items
+      return $ addBlock
+               (if isTightList items
+                   then vcat items'
+                   else vsep items')
+              $$ blankline
+    BulletList items -> do
+      items' <- mapM (fmap chomp . listItemToTypst 2 "- ") items
+      return $ (if isTightList items
+                   then vcat items'
+                   else vsep items') $$ blankline
+    DefinitionList items ->
+      ($$ blankline) . vsep <$> mapM defListItemToTypst items
+    Table (ident,_,_) blkCapt colspecs thead tbodies tfoot -> do
+      let (caption, aligns, _, headers, rows) =
+            toLegacyTable blkCapt colspecs thead tbodies tfoot
+      let numcols = length aligns
+      headers' <- mapM blocksToTypst headers
+      rows' <- mapM (mapM blocksToTypst) rows
+      capt' <- if null caption
+                  then return mempty
+                  else do
+                    captcontents <- inlinesToTypst caption
+                    return $ "#align(center, " <> brackets captcontents <> ")"
+      let lab = toLabel ident
+      let formatalign AlignLeft = "left,"
+          formatalign AlignRight = "right,"
+          formatalign AlignCenter = "center,"
+          formatalign AlignDefault = "auto,"
+      let alignarray = parens $ mconcat $ map formatalign aligns
+      return $ "#align(center)[#table("
+        $$ nest 2
+           (  "columns: " <> text (show numcols) <> "," -- auto
+           $$ "align: (col, row) => " <> alignarray <> ".at(col),"
+           $$ "inset: 6pt" <> ","
+           $$ hsep (map ((<>",") . brackets) headers')
+           $$ vcat (map (\x -> brackets x <> ",") (concat rows'))
+           )
+        $$ ")"
+        $$ capt'
+        $$ lab
+        $$ "]"
+        $$ blankline
+    Figure (ident,_,_) (Caption _mbshort capt) blocks -> do
+      caption <- blocksToTypst capt
+      contents <- blocksToTypst blocks
+      let lab = toLabel ident
+      return $ "#figure(" <> nest 2 (brackets contents <> "," <> cr <>
+                                     ("caption: [" $$ nest 2 caption $$ "]"))
+                          $$ ")" $$ lab $$ blankline
+    Div (ident,_,_) blocks -> do
+      let lab = toLabel ident
+      contents <- blocksToTypst blocks
+      return $ lab $$ contents
+
+defListItemToTypst :: PandocMonad m => ([Inline], [[Block]]) -> TW m (Doc Text)
+defListItemToTypst (term, defns) = do
+  modify $ \st -> st{ stEscapeContext = TermContext }
+  term' <- inlinesToTypst term
+  modify $ \st -> st{ stEscapeContext = NormalContext }
+  defns' <- mapM blocksToTypst defns
+  return $ nowrap ("/ " <> term' <> ": " <> "#block[") $$
+            chomp (vcat defns') $$ "]"
+listItemToTypst :: PandocMonad m => Int -> Doc Text -> [Block] -> TW m (Doc Text)
+listItemToTypst ind marker blocks = do
+  contents <- blocksToTypst blocks
+  return $ hang ind (marker <> space) contents
+
+inlinesToTypst :: PandocMonad m => [Inline] -> TW m (Doc Text)
+inlinesToTypst ils = hcat <$> mapM inlineToTypst ils
+
+inlineToTypst :: PandocMonad m => Inline -> TW m (Doc Text)
+inlineToTypst inline =
+  case inline of
+    Str txt -> do
+      context <- gets stEscapeContext
+      return $ literal $ escapeTypst context txt
+    Space -> return space
+    SoftBreak -> do
+      wrapText <- gets $ writerWrapText . stOptions
+      case wrapText of
+        WrapPreserve -> return cr
+        WrapAuto     -> return space
+        WrapNone     -> return space
+    LineBreak -> return (space <> "\\" <> cr)
+    Math mathType str -> do
+      res <- convertMath TM.writeTypst mathType str
+      case res of
+          Left il -> inlineToTypst il
+          Right r ->
+            case mathType of
+              InlineMath -> return $ "$" <> literal r <> "$"
+              DisplayMath -> return $ "$ " <> literal r <> " $"
+    Code (_,cls,_) code -> return $
+      case cls of
+        (lang:_) -> "#raw(lang:" <> doubleQuoted lang <>
+                        ", " <> doubleQuoted code <> ")"
+        _ | T.any (=='`') code -> "#raw(" <> doubleQuoted code <> ")"
+          | otherwise -> "`" <> literal code <> "`"
+    RawInline fmt str ->
+      case fmt of
+        Format "typst" -> return $ literal str
+        _ -> return mempty
+    Strikeout inlines -> textstyle "#strike" inlines
+    Emph inlines -> textstyle "#emph" inlines
+    Underline inlines -> textstyle "#underline" inlines
+    Strong inlines -> textstyle "#strong" inlines
+    Superscript inlines -> textstyle "#super" inlines
+    Subscript inlines -> textstyle "#sub" inlines
+    SmallCaps inlines -> textstyle "#smallcaps" inlines
+    Span (ident,_,_) inlines -> do
+      let lab = toLabel ident
+      (lab $$) <$> inlinesToTypst inlines
+    Quoted quoteType inlines -> do
+      let q = case quoteType of
+                   DoubleQuote -> literal "\""
+                   SingleQuote -> literal "'"
+      contents <- inlinesToTypst inlines
+      return $ q <> contents <> q
+    Cite citations inlines -> do
+      opts <-  gets stOptions
+      if isEnabled Ext_citations opts
+         then return $ -- Note: this loses locators, prefix, suffix
+              "#cite" <> parens
+                (mconcat $ intersperse ", " $
+                  map (doubleQuoted . citationId) citations)
+         else inlinesToTypst inlines
+    Link _attrs inlines (src,_tit) -> do
+      contents <- inlinesToTypst inlines
+      return $ "#link" <> parens (doubleQuoted src) <>
+                if render Nothing contents == src
+                   then mempty
+                   else nowrap $ brackets contents
+    Image (_,_,kvs) _inlines (src,_tit) -> do
+      let width' = maybe mempty ((", width: " <>) . literal) $ lookup "width" kvs
+      let height' = maybe mempty ((", height: " <>) . literal) $
+                    lookup "height" kvs
+      return $ "#image(" <> doubleQuoted src <> width' <> height' <> ")"
+    Note blocks -> do -- currently typst has no footnotes!
+      -- TODO create endnotes with manual typesetting
+      contents <- blocksToTypst blocks
+      modify $ \st -> st{ stNotes = contents : stNotes st }
+      num <- text . show . length <$> gets stNotes
+      return $ "#super" <> brackets num
+
+textstyle :: PandocMonad m => Doc Text -> [Inline] -> TW m (Doc Text)
+textstyle s inlines = (s <>) . brackets <$> inlinesToTypst inlines
+
+escapeTypst :: EscapeContext -> Text -> Text
+escapeTypst context t =
+  if T.any needsEscape t
+     then T.concatMap escapeChar t
+     else t
+  where
+    escapeChar c
+      | needsEscape c = "\\" <> T.singleton c
+      | otherwise = T.singleton c
+    needsEscape '[' = True
+    needsEscape ']' = True
+    needsEscape '#' = True
+    needsEscape '<' = True
+    needsEscape '>' = True
+    needsEscape '@' = True
+    needsEscape '$' = True
+    needsEscape '\\' = True
+    needsEscape '\'' = True
+    needsEscape '"' = True
+    needsEscape '`' = True
+    needsEscape '=' = True
+    needsEscape '_' = True
+    needsEscape '*' = True
+    needsEscape ':' = context == TermContext
+    needsEscape _ = False
+
+toLabel :: Text -> Doc Text
+toLabel ident =
+  if T.null ident
+     then mempty
+     else "#label" <> parens (doubleQuotes (literal ident))
+
+doubleQuoted :: Text -> Doc Text
+doubleQuoted = doubleQuotes . literal . escape
+ where
+  escape = T.concatMap escapeChar
+  escapeChar '\\' = "\\\\"
+  escapeChar '"' = "\\\""
+  escapeChar c = T.singleton c
diff --git a/test/Tests/Old.hs b/test/Tests/Old.hs
--- a/test/Tests/Old.hs
+++ b/test/Tests/Old.hs
@@ -193,6 +193,9 @@
   , testGroup "ms"
     [ testGroup "writer" $ writerTests' "ms"
     ]
+  , testGroup "typst"
+    [ testGroup "writer" $ writerTests' "typst"
+    ]
   , testGroup "creole"
     [ test' "reader" ["-r", "creole", "-w", "native", "-s"]
       "creole-reader.txt" "creole-reader.native"
diff --git a/test/Tests/Readers/Org/Inline.hs b/test/Tests/Readers/Org/Inline.hs
--- a/test/Tests/Readers/Org/Inline.hs
+++ b/test/Tests/Readers/Org/Inline.hs
@@ -120,12 +120,13 @@
       para (spcSep [ "//", "**", "__", "<>", "==", "~~", "$$" ])
 
   , "Adherence to Org's rules for markup borders" =:
-      "/t/& a/ / ./r/ (*l*) /e/! /b/." =?>
+      "/t/& a/ / ./r/ (*l*) /e/! ze\x200b/r/\x200bo /b/." =?>
       para (spcSep [ emph $ "t/&" <> space <> "a"
                    , "/"
                    , "./r/"
                    , "(" <> strong "l" <> ")"
                    , emph "e" <> "!"
+                   , "ze\x200b" <> emph "r" <> "\x200bo"
                    , emph "b" <> "."
                    ])
 
@@ -136,6 +137,10 @@
   , "Spaces are forbidden border chars" =:
       "/nada /" =?>
       para "/nada /"
+
+  , "Zero width spaces are forbidden border chars" =:
+      "/emph\x200b/asis" =?>
+      para "/emph\x200b/asis"
 
   , "Markup should work properly after a blank line" =:
     T.unlines ["foo", "", "/bar/"] =?>
diff --git a/test/Tests/Writers/HTML.hs b/test/Tests/Writers/HTML.hs
--- a/test/Tests/Writers/HTML.hs
+++ b/test/Tests/Writers/HTML.hs
@@ -193,13 +193,13 @@
           , "<p>A note inside a block quote.<a href=\"#fn2\" class=\"footnote-ref\" id=\"fnref2\"><sup>2</sup></a></p>"
           , "<p>A second paragraph.</p>"
           , "</blockquote>"
-          , "</div>"
           , "<div class=\"footnotes footnotes-end-of-section\">"
           , "<hr />"
           , "<ol>"
           , "<li id=\"fn1\"><p>Down here.<a href=\"#fnref1\" class=\"footnote-back\">↩︎</a></p></li>"
           , "<li id=\"fn2\"><p>The second note.<a href=\"#fnref2\" class=\"footnote-back\">↩︎</a></p></li>"
           , "</ol>"
+          , "</div>"
           , "</div>"
           , "<div class=\"section level2\">"
           , "<h2>Second section</h2>"
diff --git a/test/command/4235.md b/test/command/4235.md
--- a/test/command/4235.md
+++ b/test/command/4235.md
@@ -4,12 +4,12 @@
 ^D
 <p>This.<a href="#foofn1" class="footnote-ref" id="foofnref1"
 role="doc-noteref"><sup>1</sup></a></p>
-<section id="footnotes" class="footnotes footnotes-end-of-document"
+<aside id="footnotes" class="footnotes footnotes-end-of-document"
 role="doc-endnotes">
 <hr />
 <ol>
 <li id="foofn1"><p>Has a footnote.<a href="#foofnref1"
 class="footnote-back" role="doc-backlink">↩︎</a></p></li>
 </ol>
-</section>
+</aside>
 ```
diff --git a/test/command/7006.md b/test/command/7006.md
--- a/test/command/7006.md
+++ b/test/command/7006.md
@@ -8,7 +8,7 @@
 ^D
 <p>Test.<a href="#fn1" class="footnote-ref" id="fnref1"
 role="doc-noteref"><sup>1</sup></a></p>
-<section id="footnotes" class="footnotes footnotes-end-of-document"
+<aside id="footnotes" class="footnotes footnotes-end-of-document"
 role="doc-endnotes">
 <hr />
 <ol>
@@ -19,5 +19,5 @@
 </figure>
 <a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></li>
 </ol>
-</section>
+</aside>
 ```
diff --git a/test/command/8681.md b/test/command/8681.md
new file mode 100644
--- /dev/null
+++ b/test/command/8681.md
@@ -0,0 +1,14 @@
+# Include Jira panel title in resulting div
+```
+% pandoc -f jira -t html
+{panel:title=This is the title|titleBGColor=#cccccc|bgColor=#eeeeee}
+Panel content
+{panel}
+^D
+<div class="panel" data-titleBGColor="#cccccc" data-bgColor="#eeeeee">
+<div class="panelheader">
+<strong>This is the title</strong>
+</div>
+<p>Panel content</p>
+</div>
+```
diff --git a/test/command/8689.md b/test/command/8689.md
new file mode 100644
--- /dev/null
+++ b/test/command/8689.md
@@ -0,0 +1,12 @@
+# Org figures should be rendered implicit figures
+
+```
+% pandoc -f org -t markdown+implicit_figures
+#+label: fig:bsdd-graphql-voyager-orig-detail
+#+caption: Original bSDD GraphQL Schema: Detail of Classification and ClassificationProperty
+[[./Classification-ClassificationProperty.png]]
+^D
+![Original bSDD GraphQL Schema: Detail of Classification and
+ClassificationProperty](./Classification-ClassificationProperty.png){#fig:bsdd-graphql-voyager-orig-detail}
+
+```
diff --git a/test/command/8711.md b/test/command/8711.md
new file mode 100644
--- /dev/null
+++ b/test/command/8711.md
@@ -0,0 +1,118 @@
+```
+% pandoc -f html-native_spans -t native
+<table>
+<tr>
+<td>
+  <p><span class="foo">a</span> <span class="bar">b</span></p>
+</td>
+</tr>
+</table>
+^D
+[ Table
+    ( "" , [] , [] )
+    (Caption Nothing [])
+    [ ( AlignDefault , ColWidthDefault ) ]
+    (TableHead ( "" , [] , [] ) [])
+    [ TableBody
+        ( "" , [] , [] )
+        (RowHeadColumns 0)
+        []
+        [ Row
+            ( "" , [] , [] )
+            [ Cell
+                ( "" , [] , [] )
+                AlignDefault
+                (RowSpan 1)
+                (ColSpan 1)
+                [ Para [ Str "a" , Space , Str "b" ] ]
+            ]
+        ]
+    ]
+    (TableFoot ( "" , [] , [] ) [])
+]
+
+```
+
+```
+% pandoc -f html-native_spans+raw_html -t native
+<table>
+<tr>
+<td>
+  <p><span class="foo">a</span> <span class="bar">b</span></p>
+</td>
+</tr>
+</table>
+^D
+[ Table
+    ( "" , [] , [] )
+    (Caption Nothing [])
+    [ ( AlignDefault , ColWidthDefault ) ]
+    (TableHead ( "" , [] , [] ) [])
+    [ TableBody
+        ( "" , [] , [] )
+        (RowHeadColumns 0)
+        []
+        [ Row
+            ( "" , [] , [] )
+            [ Cell
+                ( "" , [] , [] )
+                AlignDefault
+                (RowSpan 1)
+                (ColSpan 1)
+                [ Para
+                    [ RawInline (Format "html") "<span class=\"foo\">"
+                    , Str "a"
+                    , RawInline (Format "html") "</span>"
+                    , Space
+                    , RawInline (Format "html") "<span class=\"bar\">"
+                    , Str "b"
+                    , RawInline (Format "html") "</span>"
+                    ]
+                ]
+            ]
+        ]
+    ]
+    (TableFoot ( "" , [] , [] ) [])
+]
+
+```
+
+```
+% pandoc -f html -t native
+<table>
+<tr>
+<td>
+  <p><span class="foo">a</span> <span class="bar">b</span></p>
+</td>
+</tr>
+</table>
+^D
+[ Table
+    ( "" , [] , [] )
+    (Caption Nothing [])
+    [ ( AlignDefault , ColWidthDefault ) ]
+    (TableHead ( "" , [] , [] ) [])
+    [ TableBody
+        ( "" , [] , [] )
+        (RowHeadColumns 0)
+        []
+        [ Row
+            ( "" , [] , [] )
+            [ Cell
+                ( "" , [] , [] )
+                AlignDefault
+                (RowSpan 1)
+                (ColSpan 1)
+                [ Para
+                    [ Span ( "" , [ "foo" ] , [] ) [ Str "a" ]
+                    , Space
+                    , Span ( "" , [ "bar" ] , [] ) [ Str "b" ]
+                    ]
+                ]
+            ]
+        ]
+    ]
+    (TableFoot ( "" , [] , [] ) [])
+]
+
+```
diff --git a/test/docbook-xref.docbook b/test/docbook-xref.docbook
--- a/test/docbook-xref.docbook
+++ b/test/docbook-xref.docbook
@@ -1,6 +1,6 @@
 <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
           "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">
-<book><title>An Example Book</title>
+<book id='book01'><title>An Example Book</title>
 <chapter id="ch01"><title>XRef Samples</title>
 <para>
 This paragraph demonstrates several features of
@@ -30,6 +30,12 @@
 <listitem><para>A link to a
 <sgmltag>table</sgmltag> element: <xref linkend="table01"/>.
 </para></listitem>
+<listitem><para>A link to a
+<sgmltag>part</sgmltag> element: <xref linkend="part01"/>.
+</para></listitem>
+<listitem><para>A link to a
+<sgmltag>book</sgmltag> element: <xref linkend="book01"/>.
+</para></listitem>
 </itemizedlist>
 </chapter>
 
@@ -38,6 +44,9 @@
   <para>Some content here</para>
 </chapter>
 
+<part id="part01">
+  <title>Later Part</title>
+
 <chapter id="ch03" xreflabel="Chapter the Third">
   <title>The Third Chapter</title>
   <para>Some content here</para>
@@ -101,4 +110,5 @@
 </table>
 
 </chapter>
+</part>
 </book>
diff --git a/test/docbook-xref.native b/test/docbook-xref.native
--- a/test/docbook-xref.native
+++ b/test/docbook-xref.native
@@ -14,7 +14,7 @@
           ]
     }
   [ Header
-      1
+      2
       ( "ch01" , [] , [] )
       [ Str "XRef" , Space , Str "Samples" ]
   , Para
@@ -195,21 +195,70 @@
             , Str "."
             ]
         ]
+      , [ Para
+            [ Str "A"
+            , Space
+            , Str "link"
+            , Space
+            , Str "to"
+            , Space
+            , Str "a"
+            , SoftBreak
+            , Str "part"
+            , Space
+            , Str "element:"
+            , Space
+            , Link
+                ( "" , [] , [] )
+                [ Str "Later" , Space , Str "Part" ]
+                ( "#part01" , "" )
+            , Str "."
+            ]
+        ]
+      , [ Para
+            [ Str "A"
+            , Space
+            , Str "link"
+            , Space
+            , Str "to"
+            , Space
+            , Str "a"
+            , SoftBreak
+            , Str "book"
+            , Space
+            , Str "element:"
+            , Space
+            , Link
+                ( "" , [] , [] )
+                [ Str "An"
+                , Space
+                , Str "Example"
+                , Space
+                , Str "Book"
+                ]
+                ( "#book01" , "" )
+            , Str "."
+            ]
+        ]
       ]
   , Header
-      1
+      2
       ( "ch02" , [] , [] )
       [ Str "The" , Space , Str "Second" , Space , Str "Chapter" ]
   , Para
       [ Str "Some" , Space , Str "content" , Space , Str "here" ]
   , Header
       1
+      ( "part01" , [] , [] )
+      [ Str "Later" , Space , Str "Part" ]
+  , Header
+      2
       ( "ch03" , [] , [] )
       [ Str "The" , Space , Str "Third" , Space , Str "Chapter" ]
   , Para
       [ Str "Some" , Space , Str "content" , Space , Str "here" ]
   , Header
-      1
+      2
       ( "ch04" , [] , [ ( "titleabbrev" , "Chapter 4" ) ] )
       [ Str "The" , Space , Str "Fourth" , Space , Str "Chapter" ]
   , Para
diff --git a/test/docx/golden/block_quotes.docx b/test/docx/golden/block_quotes.docx
Binary files a/test/docx/golden/block_quotes.docx and b/test/docx/golden/block_quotes.docx differ
diff --git a/test/docx/golden/codeblock.docx b/test/docx/golden/codeblock.docx
Binary files a/test/docx/golden/codeblock.docx and b/test/docx/golden/codeblock.docx differ
diff --git a/test/docx/golden/comments.docx b/test/docx/golden/comments.docx
Binary files a/test/docx/golden/comments.docx and b/test/docx/golden/comments.docx differ
diff --git a/test/docx/golden/custom_style_no_reference.docx b/test/docx/golden/custom_style_no_reference.docx
Binary files a/test/docx/golden/custom_style_no_reference.docx and b/test/docx/golden/custom_style_no_reference.docx differ
diff --git a/test/docx/golden/custom_style_preserve.docx b/test/docx/golden/custom_style_preserve.docx
Binary files a/test/docx/golden/custom_style_preserve.docx and b/test/docx/golden/custom_style_preserve.docx differ
diff --git a/test/docx/golden/definition_list.docx b/test/docx/golden/definition_list.docx
Binary files a/test/docx/golden/definition_list.docx and b/test/docx/golden/definition_list.docx differ
diff --git a/test/docx/golden/document-properties-short-desc.docx b/test/docx/golden/document-properties-short-desc.docx
Binary files a/test/docx/golden/document-properties-short-desc.docx and b/test/docx/golden/document-properties-short-desc.docx differ
diff --git a/test/docx/golden/document-properties.docx b/test/docx/golden/document-properties.docx
Binary files a/test/docx/golden/document-properties.docx and b/test/docx/golden/document-properties.docx differ
diff --git a/test/docx/golden/headers.docx b/test/docx/golden/headers.docx
Binary files a/test/docx/golden/headers.docx and b/test/docx/golden/headers.docx differ
diff --git a/test/docx/golden/image.docx b/test/docx/golden/image.docx
Binary files a/test/docx/golden/image.docx and b/test/docx/golden/image.docx differ
diff --git a/test/docx/golden/inline_code.docx b/test/docx/golden/inline_code.docx
Binary files a/test/docx/golden/inline_code.docx and b/test/docx/golden/inline_code.docx differ
diff --git a/test/docx/golden/inline_formatting.docx b/test/docx/golden/inline_formatting.docx
Binary files a/test/docx/golden/inline_formatting.docx and b/test/docx/golden/inline_formatting.docx differ
diff --git a/test/docx/golden/inline_images.docx b/test/docx/golden/inline_images.docx
Binary files a/test/docx/golden/inline_images.docx and b/test/docx/golden/inline_images.docx differ
diff --git a/test/docx/golden/link_in_notes.docx b/test/docx/golden/link_in_notes.docx
Binary files a/test/docx/golden/link_in_notes.docx and b/test/docx/golden/link_in_notes.docx differ
diff --git a/test/docx/golden/links.docx b/test/docx/golden/links.docx
Binary files a/test/docx/golden/links.docx and b/test/docx/golden/links.docx differ
diff --git a/test/docx/golden/lists.docx b/test/docx/golden/lists.docx
Binary files a/test/docx/golden/lists.docx and b/test/docx/golden/lists.docx differ
diff --git a/test/docx/golden/lists_continuing.docx b/test/docx/golden/lists_continuing.docx
Binary files a/test/docx/golden/lists_continuing.docx and b/test/docx/golden/lists_continuing.docx differ
diff --git a/test/docx/golden/lists_div_bullets.docx b/test/docx/golden/lists_div_bullets.docx
Binary files a/test/docx/golden/lists_div_bullets.docx and b/test/docx/golden/lists_div_bullets.docx differ
diff --git a/test/docx/golden/lists_multiple_initial.docx b/test/docx/golden/lists_multiple_initial.docx
Binary files a/test/docx/golden/lists_multiple_initial.docx and b/test/docx/golden/lists_multiple_initial.docx differ
diff --git a/test/docx/golden/lists_restarting.docx b/test/docx/golden/lists_restarting.docx
Binary files a/test/docx/golden/lists_restarting.docx and b/test/docx/golden/lists_restarting.docx differ
diff --git a/test/docx/golden/nested_anchors_in_header.docx b/test/docx/golden/nested_anchors_in_header.docx
Binary files a/test/docx/golden/nested_anchors_in_header.docx and b/test/docx/golden/nested_anchors_in_header.docx differ
diff --git a/test/docx/golden/notes.docx b/test/docx/golden/notes.docx
Binary files a/test/docx/golden/notes.docx and b/test/docx/golden/notes.docx differ
diff --git a/test/docx/golden/raw-blocks.docx b/test/docx/golden/raw-blocks.docx
Binary files a/test/docx/golden/raw-blocks.docx and b/test/docx/golden/raw-blocks.docx differ
diff --git a/test/docx/golden/raw-bookmarks.docx b/test/docx/golden/raw-bookmarks.docx
Binary files a/test/docx/golden/raw-bookmarks.docx and b/test/docx/golden/raw-bookmarks.docx differ
diff --git a/test/docx/golden/table_one_row.docx b/test/docx/golden/table_one_row.docx
Binary files a/test/docx/golden/table_one_row.docx and b/test/docx/golden/table_one_row.docx differ
diff --git a/test/docx/golden/table_with_list_cell.docx b/test/docx/golden/table_with_list_cell.docx
Binary files a/test/docx/golden/table_with_list_cell.docx and b/test/docx/golden/table_with_list_cell.docx differ
diff --git a/test/docx/golden/tables-default-widths.docx b/test/docx/golden/tables-default-widths.docx
Binary files a/test/docx/golden/tables-default-widths.docx and b/test/docx/golden/tables-default-widths.docx differ
diff --git a/test/docx/golden/tables.docx b/test/docx/golden/tables.docx
Binary files a/test/docx/golden/tables.docx and b/test/docx/golden/tables.docx differ
diff --git a/test/docx/golden/tables_separated_with_rawblock.docx b/test/docx/golden/tables_separated_with_rawblock.docx
Binary files a/test/docx/golden/tables_separated_with_rawblock.docx and b/test/docx/golden/tables_separated_with_rawblock.docx differ
diff --git a/test/docx/golden/track_changes_deletion.docx b/test/docx/golden/track_changes_deletion.docx
Binary files a/test/docx/golden/track_changes_deletion.docx and b/test/docx/golden/track_changes_deletion.docx differ
diff --git a/test/docx/golden/track_changes_insertion.docx b/test/docx/golden/track_changes_insertion.docx
Binary files a/test/docx/golden/track_changes_insertion.docx and b/test/docx/golden/track_changes_insertion.docx differ
diff --git a/test/docx/golden/track_changes_move.docx b/test/docx/golden/track_changes_move.docx
Binary files a/test/docx/golden/track_changes_move.docx and b/test/docx/golden/track_changes_move.docx differ
diff --git a/test/docx/golden/track_changes_scrubbed_metadata.docx b/test/docx/golden/track_changes_scrubbed_metadata.docx
Binary files a/test/docx/golden/track_changes_scrubbed_metadata.docx and b/test/docx/golden/track_changes_scrubbed_metadata.docx differ
diff --git a/test/docx/golden/unicode.docx b/test/docx/golden/unicode.docx
Binary files a/test/docx/golden/unicode.docx and b/test/docx/golden/unicode.docx differ
diff --git a/test/docx/golden/verbatim_subsuper.docx b/test/docx/golden/verbatim_subsuper.docx
Binary files a/test/docx/golden/verbatim_subsuper.docx and b/test/docx/golden/verbatim_subsuper.docx differ
diff --git a/test/tables.typst b/test/tables.typst
new file mode 100644
--- /dev/null
+++ b/test/tables.typst
@@ -0,0 +1,142 @@
+Simple table with caption:
+
+#align(center)[#table(
+  columns: 4,
+  align: (col, row) => (right,left,center,auto,).at(col),
+  inset: 6pt,
+  [Right], [Left], [Center], [Default],
+  [12],
+  [12],
+  [12],
+  [12],
+  [123],
+  [123],
+  [123],
+  [123],
+  [1],
+  [1],
+  [1],
+  [1],
+)
+#align(center, [Demonstration of simple table syntax.])
+]
+
+Simple table without caption:
+
+#align(center)[#table(
+  columns: 4,
+  align: (col, row) => (right,left,center,auto,).at(col),
+  inset: 6pt,
+  [Right], [Left], [Center], [Default],
+  [12],
+  [12],
+  [12],
+  [12],
+  [123],
+  [123],
+  [123],
+  [123],
+  [1],
+  [1],
+  [1],
+  [1],
+)
+]
+
+Simple table indented two spaces:
+
+#align(center)[#table(
+  columns: 4,
+  align: (col, row) => (right,left,center,auto,).at(col),
+  inset: 6pt,
+  [Right], [Left], [Center], [Default],
+  [12],
+  [12],
+  [12],
+  [12],
+  [123],
+  [123],
+  [123],
+  [123],
+  [1],
+  [1],
+  [1],
+  [1],
+)
+#align(center, [Demonstration of simple table syntax.])
+]
+
+Multiline table with caption:
+
+#align(center)[#table(
+  columns: 4,
+  align: (col, row) => (center,left,right,left,).at(col),
+  inset: 6pt,
+  [Centered Header], [Left Aligned], [Right Aligned], [Default aligned],
+  [First],
+  [row],
+  [12.0],
+  [Example of a row that spans multiple lines.],
+  [Second],
+  [row],
+  [5.0],
+  [Here’s another one. Note the blank line between rows.],
+)
+#align(center, [Here’s the caption. It may span multiple lines.])
+]
+
+Multiline table without caption:
+
+#align(center)[#table(
+  columns: 4,
+  align: (col, row) => (center,left,right,left,).at(col),
+  inset: 6pt,
+  [Centered Header], [Left Aligned], [Right Aligned], [Default aligned],
+  [First],
+  [row],
+  [12.0],
+  [Example of a row that spans multiple lines.],
+  [Second],
+  [row],
+  [5.0],
+  [Here’s another one. Note the blank line between rows.],
+)
+]
+
+Table without column headers:
+
+#align(center)[#table(
+  columns: 4,
+  align: (col, row) => (right,left,center,right,).at(col),
+  inset: 6pt,
+  [12],
+  [12],
+  [12],
+  [12],
+  [123],
+  [123],
+  [123],
+  [123],
+  [1],
+  [1],
+  [1],
+  [1],
+)
+]
+
+Multiline table without column headers:
+
+#align(center)[#table(
+  columns: 4,
+  align: (col, row) => (center,left,right,auto,).at(col),
+  inset: 6pt,
+  [First],
+  [row],
+  [12.0],
+  [Example of a row that spans multiple lines.],
+  [Second],
+  [row],
+  [5.0],
+  [Here’s another one. Note the blank line between rows.],
+)
+]
diff --git a/test/writer.html5 b/test/writer.html5
--- a/test/writer.html5
+++ b/test/writer.html5
@@ -733,7 +733,7 @@
 role="doc-noteref"><sup>5</sup></a></li>
 </ol>
 <p>This paragraph should not be part of the note, as it is not indented.</p>
-<section id="footnotes" class="footnotes footnotes-end-of-document"
+<aside id="footnotes" class="footnotes footnotes-end-of-document"
 role="doc-endnotes">
 <hr />
 <ol>
@@ -756,6 +756,6 @@
 <li id="fn5"><p>In list.<a href="#fnref5" class="footnote-back"
 role="doc-backlink">↩︎</a></p></li>
 </ol>
-</section>
+</aside>
 </body>
 </html>
diff --git a/test/writer.latex b/test/writer.latex
--- a/test/writer.latex
+++ b/test/writer.latex
@@ -50,7 +50,12 @@
 \makeatletter
 \def\fps@figure{htbp}
 \makeatother
-\usepackage{soul}
+\ifLuaTeX
+  \usepackage{luacolor}
+  \usepackage[soul]{lua-ul}
+\else
+  \usepackage{soul}
+\fi
 \setlength{\emergencystretch}{3em} % prevent overfull lines
 \providecommand{\tightlist}{%
   \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
diff --git a/test/writer.typst b/test/writer.typst
new file mode 100644
--- /dev/null
+++ b/test/writer.typst
@@ -0,0 +1,822 @@
+// Some definitions presupposed by pandoc's typst output.
+#let blockquote(body) = [
+  #set text( size: 0.92em )
+  #block(inset: (left: 1.5em, top: 0.2em, bottom: 0.2em))[#body]
+]
+
+#let horizontalrule = [
+  #line(start: (25%,0%), end: (75%,0%))
+]
+
+#let endnote(num, contents) = [
+  #stack(dir: ltr, spacing: 3pt, super[#num], contents)
+]
+#show terms: it => {
+  it.children
+    .map(child => [
+      #strong[#child.term]
+      #block(inset: (left: 1.5em, top: -0.4em))[#child.description]
+      ])
+    .join()
+}
+
+#let conf(
+  title: none,
+  authors: none,
+  date: none,
+  abstract: none,
+  cols: 1,
+  margin: (x: 1.25in, y: 1.25in),
+  paper: "us-letter",
+  lang: "en",
+  region: "US",
+  font: (),
+  fontsize: 11pt,
+  sectionnumbering: none,
+  doc,
+) = {
+  set page(
+    paper: paper,
+    margin: margin,
+    numbering: "1",
+  )
+  set par(justify: true)
+  set text(lang: lang,
+           region: region,
+           font: font,
+           size: fontsize)
+  set heading(numbering: sectionnumbering)
+
+  if title != none {
+    align(center)[#block(inset: 2em)[
+      #text(weight: "bold", size: 1.5em)[#title]
+    ]]
+  }
+
+  if authors != none {
+    let count = authors.len()
+    let ncols = calc.min(count, 3)
+    grid(
+      columns: (1fr,) * ncols,
+      row-gutter: 1.5em,
+      ..authors.map(author =>
+          align(center)[
+            #author.name \
+            #author.affiliation \
+            #author.email
+          ]
+      )
+    )
+  }
+
+  if date != none {
+    align(center)[#block(inset: 1em)[
+      #date
+    ]]
+  }
+
+  if abstract != none {
+    block(inset: 2em)[
+    #text(weight: "semibold")[Abstract] #h(1em) #abstract
+    ]
+  }
+
+  if cols == 1 {
+    doc
+  } else {
+    columns(cols, doc)
+  }
+}
+#show: doc => conf(
+  title: [Pandoc Test Suite],
+  authors: (
+    ( name: [John MacFarlane],
+      affiliation: [],
+      email: [] ),
+    ( name: [Anonymous],
+      affiliation: [],
+      email: [] ),
+    ),
+  date: [July 17, 2006],
+  cols: 1,
+  doc,
+)
+
+
+This is a set of tests for pandoc. Most of them are adapted from John Gruber’s
+markdown test suite.
+
+#horizontalrule
+
+= Headers
+#label("headers")
+== Level 2 with an #link("/url")[embedded link]
+#label("level-2-with-an-embedded-link")
+=== Level 3 with #emph[emphasis]
+#label("level-3-with-emphasis")
+==== Level 4
+#label("level-4")
+===== Level 5
+#label("level-5")
+= Level 1
+#label("level-1")
+== Level 2 with #emph[emphasis]
+#label("level-2-with-emphasis")
+=== Level 3
+#label("level-3")
+with no blank line
+
+== Level 2
+#label("level-2")
+with no blank line
+
+#horizontalrule
+
+= Paragraphs
+#label("paragraphs")
+Here’s a regular paragraph.
+
+In Markdown 1.0.0 and earlier. Version 8. This line turns into a list item.
+Because a hard-wrapped line in the middle of a paragraph looked like a list
+item.
+
+Here’s one with a bullet. \* criminey.
+
+There should be a hard line break \
+here.
+
+#horizontalrule
+
+= Block Quotes
+#label("block-quotes")
+E-mail style:
+
+#blockquote[
+This is a block quote. It is pretty short.
+]
+
+#blockquote[
+Code in a block quote:
+
+```
+sub status {
+    print "working";
+}
+```
+
+A list:
+
++  item one
++  item two
+
+Nested block quotes:
+
+#blockquote[
+nested
+]
+
+#blockquote[
+nested
+]
+]
+
+This should not be a block quote: 2 \> 1.
+
+And a following paragraph.
+
+#horizontalrule
+
+= Code Blocks
+#label("code-blocks")
+Code:
+
+```
+---- (should be four hyphens)
+
+sub status {
+    print "working";
+}
+
+this code block is indented by one tab
+```
+
+And:
+
+```
+    this code block is indented by two tabs
+
+These should not be escaped:  \$ \\ \> \[ \{
+```
+
+#horizontalrule
+
+= Lists
+#label("lists")
+== Unordered
+#label("unordered")
+Asterisks tight:
+
+-  asterisk 1
+-  asterisk 2
+-  asterisk 3
+
+Asterisks loose:
+
+-  asterisk 1
+
+-  asterisk 2
+
+-  asterisk 3
+
+Pluses tight:
+
+-  Plus 1
+-  Plus 2
+-  Plus 3
+
+Pluses loose:
+
+-  Plus 1
+
+-  Plus 2
+
+-  Plus 3
+
+Minuses tight:
+
+-  Minus 1
+-  Minus 2
+-  Minus 3
+
+Minuses loose:
+
+-  Minus 1
+
+-  Minus 2
+
+-  Minus 3
+
+== Ordered
+#label("ordered")
+Tight:
+
++  First
++  Second
++  Third
+
+and:
+
++  One
++  Two
++  Three
+
+Loose using tabs:
+
++  First
+
++  Second
+
++  Third
+
+and using spaces:
+
++  One
+
++  Two
+
++  Three
+
+Multiple paragraphs:
+
++  Item 1, graf one.
+
+  Item 1. graf two. The quick brown fox jumped over the lazy dog’s back.
+
++  Item 2.
+
++  Item 3.
+
+== Nested
+#label("nested")
+-  Tab
+  -  Tab
+    -  Tab
+
+Here’s another:
+
++  First
++  Second:
+  -  Fee
+  -  Fie
+  -  Foe
++  Third
+
+Same thing but with paragraphs:
+
++  First
+
++  Second:
+
+  -  Fee
+  -  Fie
+  -  Foe
+
++  Third
+
+== Tabs and spaces
+#label("tabs-and-spaces")
+-  this is a list item indented with tabs
+
+-  this is a list item indented with spaces
+
+  -  this is an example list item indented with tabs
+
+  -  this is an example list item indented with spaces
+
+== Fancy list markers
+#label("fancy-list-markers")
+#block[
+#set enum(numbering: "(1)", start: 2)
++  begins with 2
+
++  and now 3
+
+  with a continuation
+
+  #block[
+  #set enum(numbering: "i.", start: 4)
+  +  sublist with roman numerals, starting with 4
+  +  more items
+    #block[
+    #set enum(numbering: "(A)", start: 1)
+    +  a subsublist
+    +  a subsublist
+    ]
+  ]
+]
+
+Nesting:
+
+#block[
+#set enum(numbering: "A.", start: 1)
++  Upper Alpha
+  #block[
+  #set enum(numbering: "I.", start: 1)
+  +  Upper Roman.
+    #block[
+    #set enum(numbering: "(1)", start: 6)
+    +  Decimal start with 6
+      #block[
+      #set enum(numbering: "a)", start: 3)
+      +  Lower alpha with paren
+      ]
+    ]
+  ]
+]
+
+Autonumbering:
+
++  Autonumber.
++  More.
+  +  Nested.
+
+Should not be a list item:
+
+M.A. 2007
+
+B. Williams
+
+#horizontalrule
+
+= Definition Lists
+#label("definition-lists")
+Tight using spaces:
+
+/ apple: #block[
+red fruit
+]
+
+/ orange: #block[
+orange fruit
+]
+
+/ banana: #block[
+yellow fruit
+]
+
+Tight using tabs:
+
+/ apple: #block[
+red fruit
+]
+
+/ orange: #block[
+orange fruit
+]
+
+/ banana: #block[
+yellow fruit
+]
+
+Loose:
+
+/ apple: #block[
+red fruit
+]
+
+/ orange: #block[
+orange fruit
+]
+
+/ banana: #block[
+yellow fruit
+]
+
+Multiple blocks with italics:
+
+/ #emph[apple]: #block[
+red fruit
+
+contains seeds, crisp, pleasant to taste
+]
+
+/ #emph[orange]: #block[
+orange fruit
+
+```
+{ orange code block }
+```
+
+#blockquote[
+orange block quote
+]
+]
+
+Multiple definitions, tight:
+
+/ apple: #block[
+red fruit
+computer
+]
+
+/ orange: #block[
+orange fruit
+bank
+]
+
+Multiple definitions, loose:
+
+/ apple: #block[
+red fruit
+
+computer
+]
+
+/ orange: #block[
+orange fruit
+
+bank
+]
+
+Blank line after term, indented marker, alternate markers:
+
+/ apple: #block[
+red fruit
+
+computer
+]
+
+/ orange: #block[
+orange fruit
+
++  sublist
++  sublist
+]
+
+= HTML Blocks
+#label("html-blocks")
+Simple block on one line:
+
+foo
+And nested without indentation:
+
+foo
+
+bar
+Interpreted markdown in a table:
+
+This is #emph[emphasized]
+And this is #strong[strong]
+Here’s a simple block:
+
+foo
+
+This should be a code block, though:
+
+```
+<div>
+    foo
+</div>
+```
+
+As should this:
+
+```
+<div>foo</div>
+```
+
+Now, nested:
+
+foo
+This should just be an HTML comment:
+
+Multiline:
+
+Code block:
+
+```
+<!-- Comment -->
+```
+
+Just plain comment, with trailing spaces on the line:
+
+Code:
+
+```
+<hr />
+```
+
+Hr’s:
+
+#horizontalrule
+
+= Inline Markup
+#label("inline-markup")
+This is #emph[emphasized], and so #emph[is this].
+
+This is #strong[strong], and so #strong[is this].
+
+An #emph[#link("/url")[emphasized link]].
+
+#strong[#emph[This is strong and em.]]
+
+So is #strong[#emph[this]] word.
+
+#strong[#emph[This is strong and em.]]
+
+So is #strong[#emph[this]] word.
+
+This is code: `>`, `$`, `\`, `\$`, `<html>`.
+
+#strike[This is #emph[strikeout].]
+
+Superscripts: a#super[bc]d a#super[#emph[hello]] a#super[hello there].
+
+Subscripts: H#sub[2]O, H#sub[23]O, H#sub[many of them]O.
+
+These should not be superscripts or subscripts, because of the unescaped spaces:
+a^b c^d, a~b c~d.
+
+#horizontalrule
+
+= Smart quotes, ellipses, dashes
+#label("smart-quotes-ellipses-dashes")
+"Hello," said the spider. "'Shelob' is my name."
+
+'A', 'B', and 'C' are letters.
+
+'Oak,' 'elm,' and 'beech' are names of trees. So is 'pine.'
+
+'He said, "I want to go."' Were you alive in the 70’s?
+
+Here is some quoted '`code`' and a
+"#link("http://example.com/?foo=1&bar=2")[quoted link]".
+
+Some dashes: one—two — three—four — five.
+
+Dashes between numbers: 5–7, 255–66, 1987–1999.
+
+Ellipses…and…and….
+
+#horizontalrule
+
+= LaTeX
+#label("latex")
+- 
+-  $2 plus 2 eq 4$
+-  $x in y$
+-  $alpha and omega$
+-  $223$
+-  $p$-Tree
+-  Here’s some display math:
+  $ frac(d, d x) f lr((x)) eq lim_(h arrow.r 0) frac(f lr((x plus h)) minus f lr((x)), h) $
+-  Here’s one that has a line break in it: $alpha plus omega times x^2$.
+
+These shouldn’t be math:
+
+-  To get the famous equation, write `$e = mc^2$`.
+-  \$22,000 is a #emph[lot] of money. So is \$34,000. (It worked if "lot" is
+  emphasized.)
+-  Shoes (\$20) and socks (\$5).
+-  Escaped `$`: \$73 #emph[this should be emphasized] 23\$.
+
+Here’s a LaTeX table:
+
+#horizontalrule
+
+= Special Characters
+#label("special-characters")
+Here is some unicode:
+
+-  I hat: Î
+-  o umlaut: ö
+-  section: §
+-  set membership: ∈
+-  copyright: ©
+
+AT&T has an ampersand in their name.
+
+AT&T is another way to write it.
+
+This & that.
+
+4 \< 5.
+
+6 \> 5.
+
+Backslash: \\
+
+Backtick: \`
+
+Asterisk: \*
+
+Underscore: \_
+
+Left brace: {
+
+Right brace: }
+
+Left bracket: \[
+
+Right bracket: \]
+
+Left paren: (
+
+Right paren: )
+
+Greater-than: \>
+
+Hash: \#
+
+Period: .
+
+Bang: !
+
+Plus: +
+
+Minus: -
+
+#horizontalrule
+
+= Links
+#label("links")
+== Explicit
+#label("explicit")
+Just a #link("/url/")[URL].
+
+#link("/url/")[URL and title].
+
+#link("/url/")[URL and title].
+
+#link("/url/")[URL and title].
+
+#link("/url/")[URL and title]
+
+#link("/url/")[URL and title]
+
+#link("/url/with_underscore")[with\_underscore]
+
+#link("mailto:nobody@nowhere.net")[Email link]
+
+#link("")[Empty].
+
+== Reference
+#label("reference")
+Foo #link("/url/")[bar].
+
+With #link("/url/")[embedded \[brackets\]].
+
+#link("/url/")[b] by itself should be a link.
+
+Indented #link("/url")[once].
+
+Indented #link("/url")[twice].
+
+Indented #link("/url")[thrice].
+
+This should \[not\]\[\] be a link.
+
+```
+[not]: /url
+```
+
+Foo #link("/url/")[bar].
+
+Foo #link("/url/")[biz].
+
+== With ampersands
+#label("with-ampersands")
+Here’s a
+#link("http://example.com/?foo=1&bar=2")[link with an ampersand in the URL].
+
+Here’s a link with an amersand in the link text: #link("http://att.com/")[AT&T].
+
+Here’s an #link("/script?foo=1&bar=2")[inline link].
+
+Here’s an #link("/script?foo=1&bar=2")[inline link in pointy braces].
+
+== Autolinks
+#label("autolinks")
+With an ampersand:
+#link("http://example.com/?foo=1&bar=2")[http://example.com/?foo\=1&bar\=2]
+
+-  In a list?
+-  #link("http://example.com/")
+-  It should.
+
+An e-mail address: #link("mailto:nobody@nowhere.net")[nobody\@nowhere.net]
+
+#blockquote[
+Blockquoted: #link("http://example.com/")
+]
+
+Auto-links should not occur here: `<http://example.com/>`
+
+```
+or here: <http://example.com/>
+```
+
+#horizontalrule
+
+= Images
+#label("images")
+From "Voyage dans la Lune" by Georges Melies (1902):
+
+#figure([#image("lalune.jpg")],
+  caption: [
+    lalune
+  ]
+)
+
+Here is a movie #image("movie.jpg") icon.
+
+#horizontalrule
+
+= Footnotes
+#label("footnotes")
+Here is a footnote reference,#super[1] and another.#super[2] This should
+#emph[not] be a footnote reference, because it contains a space.\[^my note\]
+Here is an inline note.#super[3]
+
+#blockquote[
+Notes can go in quotes.#super[4]
+]
+
++  And in list items.#super[5]
+
+This paragraph should not be part of the note, as it is not indented.
+
+#v(1em)
+#block[
+#horizontalrule
+#set text(size: .88em)
+#v(3pt) // otherwise first note marker is swallowed, bug?
+
+#endnote([1], [Here is the footnote. It can go anywhere after the footnote
+reference. It need not be placed at the end of the document.
+])
+
+#endnote([2], [Here’s the long note. This one contains multiple blocks.
+
+Subsequent blocks are indented to show that they belong to the footnote (as with
+list items).
+
+```
+  { <code> }
+```
+
+If you want, you can indent every line, but you can also be lazy and just indent
+the first line of each block.
+])
+
+#endnote([3], [This is #emph[easier] to type. Inline notes may contain
+#link("http://google.com")[links] and `]` verbatim characters, as well as
+\[bracketed text\].
+])
+
+#endnote([4], [In quote.
+])
+
+#endnote([5], [In list.
+])
+]
