diff --git a/AUTHORS.md b/AUTHORS.md
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -113,6 +113,7 @@
 - Erik Rask
 - Ethan Riley
 - Étienne Bersac
+- Evan Silberman
 - Even Brenden
 - Ezwal
 - Fabián Heredia Montiel
@@ -203,6 +204,7 @@
 - John Purnell
 - Jonas Scholl
 - Jonas Smedegaard
+- Jonathan
 - Jonathan Daugherty
 - Jonathan Dönszelmann
 - Jose Luis Duran
@@ -305,6 +307,7 @@
 - Owen McGrath
 - Pablo Rodríguez
 - Pascal Wagler
+- Pau RE
 - Paul Rivier
 - Paul Tilley
 - Paulo Tanimoto
@@ -340,6 +343,7 @@
 - Sascha Wilde
 - Scott Morrison
 - Sebastian Talmon
+- Sebbones
 - Sergei Trofimovich
 - Sergey Astanin
 - Seth Speaks
@@ -382,6 +386,7 @@
 - Toni Dietze
 - Tristan Stenner
 - Tristan de Cacqueray
+- Tristano Ajmone
 - Tuong Nguyen Manh
 - Uli Köhler
 - Urs Liska
@@ -419,6 +424,7 @@
 - bumper314
 - chinapedia
 - cholonam
+- closeobserve
 - csforste
 - d-dorazio
 - damon-sava-stanley
@@ -468,6 +474,7 @@
 - tecosaur
 - tgkokk
 - the-solipsist
+- thomjur
 - thsutton
 - thron7
 - timo-a
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -422,6 +422,29 @@
   - `Text.Pandoc.UUID` contains functions for generating UUIDs.
   - `Text.Pandoc.XML` contains functions for formatting XML.
 
+Adding a new command-line option
+--------------------------------
+
+To add a new command-line option, you'll need to make changes in several
+places:
+
+- `MANUAL.txt` -- documentation for new option, both in the list
+  of options and in the section on defaults files.
+- `Text.Pandoc.App.Opt` -- new constructor for Opt and default
+  value
+- `Text.Pandoc.App.CommandLineOptions` -- the option parser
+- `Text.Pandoc.App` or `Text.Pandoc.App.OutputSettings` --
+  handle the new option
+- possibly in pandoc-server: `Text.Pandoc.Server` -- handle the
+  new option
+
+If your change requires a new field for ReaderOptions or
+WriterOptions, you'll also need to
+
+- `Text.Pandoc.Options` -- type change and default value
+- in pandoc-lua-engine: Text.Pandoc.Lua.Marshal.WriterOptions
+  and/or Text.Pandoc.Lua.Marshal.ReaderOptions
+
 Lua filters
 -----------
 
diff --git a/INSTALL.md b/INSTALL.md
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -7,9 +7,15 @@
   Download the latest installer
 </a>
 
-For alternative ways to install pandoc, see below
-under the heading for your operating system.
+For alternative ways to install pandoc, see below under the heading for
+your operating system. 
 
+**Note**: the statically linked Pandoc binaries provided by us (or those
+available on Conda Forge) have a limitation. They are unable to utilise Lua
+filters that rely on Lua modules written in C. If you require the
+functionality offered by these filters, please consider an alternative
+method of installation. 
+
 ## Windows
 
 There is a package installer at pandoc's [download page].
@@ -33,12 +39,14 @@
 [MiKTeX] (to typeset PDFs with [LaTeX]):
 
     choco install rsvg-convert python miktex
-    
+
 Or, you can install pandoc using
 [winget](https://github.com/microsoft/winget-pkgs):
 
     winget install --source winget --exact --id JohnMacFarlane.Pandoc
 
+Or, you can install Pandoc using [Conda forge].
+
 Using multiple installation methods can result in two separate
 installations of pandoc; it is recommended to properly uninstall
 pandoc before switching to an alternative installation method.
@@ -70,6 +78,8 @@
 Homebrew installs from source, which takes additional time and disk space
 for the `ghc` compiler and dependent Haskell libraries.
 
+Or, you can install Pandoc using [Conda forge].
+
 We also provide a zip file containing the binaries and man
 pages, for those who prefer not to use the installer.  Simply
 unzip the file and move the binaries and man pages to
@@ -97,9 +107,7 @@
 
 The executable is statically linked and
 has no dynamic dependencies or dependencies on external
-data files.  Note:  because of the static
-linking, the pandoc binary from this package cannot use lua
-filters that require external lua modules written in C.
+data files.
 
 Both a tarball and a deb installer are provided.  To install the deb:
 
@@ -122,6 +130,8 @@
 
     ar p $DEB data.tar.gz | tar xvz --strip-components 2 -C $DEST
 
+Or, you can install Pandoc using [Conda forge].
+
 You can also install from source, using the
 instructions below under [Compiling from source].
 Note that most distros have the Haskell platform in their
@@ -149,6 +159,21 @@
 ## BSD
 
 Pandoc is in the [NetBSD], [FreeBSD], and [OpenBSD ports] repositories.
+
+## Conda Forge
+
+You can install Pandoc using a [Conda
+Forge](https://anaconda.org/conda-forge/pandoc) tool, like
+[Conda](https://conda.pydata.org/docs/intro.html),
+[[Micro]Mamba](https://mamba.readthedocs.io/en/latest/index.html) or
+[Pixi](https://prefix.dev). Conda forge also includes multiple LaTeX and
+other relevant packages for Pandoc (including `pandoc-citeproc`,
+`pandoc-plot`, `rsvg-convert` via `librsvg` etc.). **Note:** conda forge
+installs a statically-linked executable.
+
+    conda install -c conda-forge pandoc
+    pixi global install pandoc
+    micromamba install pandoc
 
 ## Docker
 
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: July 28, 2024
+date: September 9, 2024
 ---
 
 # Synopsis
@@ -144,7 +144,8 @@
 [`soul`], [`geometry`] (with the `geometry` variable set),
 [`setspace`] (with `linestretch`), and
 [`babel`] (with `lang`).  If `CJKmainfont` is set, [`xeCJK`]
-is needed. [`framed`] is required if code is highlighted in a
+is needed if `xelatex` is used, else [`luatexja`] is needed if
+`lualatex` is used. [`framed`] is required if code is highlighted in a
 scheme that use a colored background. The use of `xelatex` or
 `lualatex` as the PDF engine requires [`fontspec`]. `lualatex`
 uses [`selnolig`] and [`lua-ul`]. `xelatex` uses [`bidi`] (with
@@ -186,6 +187,7 @@
 [`listings`]: https://ctan.org/pkg/listings
 [`lm`]: https://ctan.org/pkg/lm
 [`lua-ul`]: https://ctan.org/pkg/lua-ul
+[`luatexja`]: https://ctan.org/pkg/luatexja
 [`longtable`]: https://ctan.org/pkg/longtable
 [`mathspec`]: https://ctan.org/pkg/mathspec
 [`microtype`]: https://ctan.org/pkg/microtype
@@ -289,6 +291,7 @@
 :   Specify output format.  *FORMAT* can be:
 
     ::: {#output-formats}
+    - `ansi` (text with [ANSI escape codes], for terminal viewing)
     - `asciidoc` (modern [AsciiDoc] as interpreted by [AsciiDoctor])
     - `asciidoc_legacy` ([AsciiDoc] as interpreted by [`asciidoc-py`]).
     - `asciidoctor` (deprecated synonym for `asciidoc`)
@@ -466,6 +469,7 @@
 
 :   Show usage message.
 
+[ANSI escape codes]: https://en.wikipedia.org/wiki/ANSI_escape_code
 [Markdown]: https://daringfireball.net/projects/markdown/
 [CommonMark]: https://commonmark.org
 [PHP Markdown Extra]: https://michelf.ca/projects/php-markdown/extra/
@@ -511,7 +515,7 @@
 [GNU Texinfo]: https://www.gnu.org/software/texinfo/
 [RIS]: https://en.wikipedia.org/wiki/RIS_(file_format)
 [Emacs Org mode]: https://orgmode.org
-[AsciiDoc]: https://www.methods.co.nz/asciidoc/
+[AsciiDoc]: https://asciidoc.org/
 [AsciiDoctor]: https://asciidoctor.org/
 [`asciidoc-py`]: https://github.com/asciidoc-py/asciidoc-py
 [DZSlides]: https://paulrouget.com/dzslides/
@@ -1054,6 +1058,18 @@
     specifying `--reference-location=section` will cause notes
     to be rendered at the bottom of a slide.
 
+`--figure-caption-position=above`|`below`
+
+:   Specify whether figure captions go above or below figures
+    (default is `below`).  This option only affects HTML,
+    LaTeX, Docx, ODT, and Typst output.
+
+`--table-caption-position=above`|`below`
+
+:   Specify whether table captions go above or below tables
+    (default is `above`).  This option only affects HTML,
+    LaTeX, Docx, ODT, and Typst output.
+
 `--markdown-headings=setext`|`atx`
 
 :   Specify whether to use ATX-style (`#`-prefixed) or
@@ -1091,14 +1107,17 @@
 
 `--number-offset=`*NUMBER*[`,`*NUMBER*`,`*...*]
 
-:   Offset for section headings in HTML output (ignored in other
-    output formats).  The first number is added to the section number for
-    top-level headings, the second for second-level headings, and so on.
-    So, for example, if you want the first top-level heading in your
-    document to be numbered "6", specify `--number-offset=5`.
-    If your document starts with a level-2 heading which you want to
-    be numbered "1.5", specify `--number-offset=1,4`.
-    Offsets are 0 by default.  Implies `--number-sections`.
+:   Offsets for section heading numbers. The first number is added
+    to the section number for level-1 headings, the second for
+    level-2 headings, and so on. So, for example, if you
+    want the first level-1 heading in your document to be
+    numbered "6" instead of "1", specify `--number-offset=5`.
+    If your document starts with a level-2 heading which you want
+    to be numbered "1.5", specify `--number-offset=1,4`.
+    `--number-offset` only directly affects the number of the
+    first section heading in a document; subsequent numbers
+    increment in the normal way. Implies `--number-sections`.
+    Currently this feature only affects HTML and Docx output.
 
 `--listings[=true|false]`
 
@@ -1417,7 +1436,7 @@
     - `-t latex` or none: `pdflatex` (other options: `xelatex`, `lualatex`,
         `tectonic`, `latexmk`)
     - `-t context`: `context`
-    - `-t html`:  `wkhtmltopdf` (other options: `prince`, `weasyprint`,
+    - `-t html`:  `weasyprint` (other options: `prince`, `wkhtmltopdf`,
         `pagedjs-cli`;
         see [print-css.rocks](https://print-css.rocks) for a good
         introduction to PDF generation from HTML/CSS)
@@ -1457,6 +1476,12 @@
     order they appear on the command line.  For more
     information, see the section on [Citations].
 
+    Note: if your target format is `markdown`, `org`, or `typst`,
+    you will need to disable the `citations` extension (e.g., `-t
+    markdown-citations`) to see the rendered citations and
+    bibliography. Otherwise the format's own citation syntax will
+    be used.
+
 `--bibliography=`*FILE*
 
 :   Set the `bibliography` field in the document's metadata to *FILE*,
@@ -1949,6 +1974,10 @@
 | ```                              | ```                               |
 +----------------------------------+-----------------------------------+
 | ```                              | ``` yaml                          |
+| --link-images                    | link-images: true                 |
+| ```                              | ```                               |
++----------------------------------+-----------------------------------+
+| ```                              | ``` yaml                          |
 | --html-q-tags                    | html-q-tags: true                 |
 | ```                              | ```                               |
 +----------------------------------+-----------------------------------+
@@ -1965,6 +1994,14 @@
 | ```                              | ```                               |
 +----------------------------------+-----------------------------------+
 | ```                              | ``` yaml                          |
+| --figure-caption-position=above  | figure-caption-position: above    |
+| ```                              | ```                               |
++----------------------------------+-----------------------------------+
+| ```                              | ``` yaml                          |
+| --table-caption-position=below   | table-caption-position: below     |
+| ```                              | ```                               |
++----------------------------------+-----------------------------------+
+| ```                              | ``` yaml                          |
 | --markdown-headings atx          | markdown-headings: atx            |
 | ```                              | ```                               |
 +----------------------------------+-----------------------------------+
@@ -2974,9 +3011,10 @@
 `mainfont`, `sansfont`, `monofont`, `mathfont`, `CJKmainfont`, `CJKsansfont`, `CJKmonofont`
 :   font families for use with `xelatex` or
     `lualatex`: take the name of any system font, using the
-    [`fontspec`] package.  `CJKmainfont` uses the [`xecjk`] package.
+    [`fontspec`] package.  `CJKmainfont` uses the [`xecjk`] package if `xelatex` is used,
+    or the [`luatexja`] package if `lualatex` is used.
 
-`mainfontoptions`, `sansfontoptions`, `monofontoptions`, `mathfontoptions`, `CJKoptions`
+`mainfontoptions`, `sansfontoptions`, `monofontoptions`, `mathfontoptions`, `CJKoptions`, `luatexjapresetoptions`
 :   options to use with `mainfont`, `sansfont`, `monofont`, `mathfont`,
     `CJKmainfont` in `xelatex` and `lualatex`.  Allow for any
     choices available through [`fontspec`]; repeat for multiple
@@ -3680,11 +3718,19 @@
 when you are targeting `ipynb` or a Markdown-based
 output format.
 
+### Extension: `citations` (typst) {#typst-citations}
+
+When the `citations` extension is enabled in `typst`
+(as it is by default), `typst` citations will
+be parsed as native pandoc citations, and native
+pandoc citations will be rendered as `typst` citations.
+
 ### Extension: `citations` (org) {#org-citations}
 
 When the `citations` extension is enabled in `org`,
 org-cite and org-ref style citations will be parsed as
-native pandoc citations.
+native pandoc citations, and org-cite citations will
+be used to render native pandoc citations.
 
   [org-cite]: https://orgmode.org/manual/Citations.html
   [org-ref]:  https://github.com/jkitchin/org-ref
@@ -5723,7 +5769,7 @@
 
 [CSL user documentation]: https://citationstyles.org/authors/
 [CSL]: https://docs.citationstyles.org/en/stable/specification.html
-[CSL markup specs]: https://docs.citationstyles.org/en/1.0/release-notes.html#rich-text-markup-within-fields
+[CSL markup specs]: https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#html-like-formatting-tags
 [Chicago Manual of Style]: https://chicagomanualofstyle.org
 [Citation Style Language]: https://citationstyles.org
 [Zotero Style Repository]: https://www.zotero.org/styles
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -111,10 +111,12 @@
 
 <div id="output-formats">
 
-- `asciidoc` (modern [AsciiDoc](https://www.methods.co.nz/asciidoc/) as
-  interpreted by [AsciiDoctor](https://asciidoctor.org/))
-- `asciidoc_legacy` ([AsciiDoc](https://www.methods.co.nz/asciidoc/) as
-  interpreted by
+- `ansi` (text with [ANSI escape
+  codes](https://en.wikipedia.org/wiki/ANSI_escape_code), for terminal
+  viewing)
+- `asciidoc` (modern [AsciiDoc](https://asciidoc.org/) as interpreted by
+  [AsciiDoctor](https://asciidoctor.org/))
+- `asciidoc_legacy` ([AsciiDoc](https://asciidoc.org/) as interpreted by
   [`asciidoc-py`](https://github.com/asciidoc-py/asciidoc-py)).
 - `asciidoctor` (deprecated synonym for `asciidoc`)
 - `beamer` ([LaTeX beamer](https://ctan.org/pkg/beamer) slide show)
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,212 @@
 # Revision history for pandoc
 
+## pandoc 3.4 (2024-09-09)
+
+  * New output format: `ansi` (for formatted console output) (Evan Silberman).
+    Most Pandoc elements are supported and printed in a reasonable way, if
+    not always ideally. This version does no detection of terminal
+    capabilities, nor does it fall back to different output styles for
+    less-capable terminals.
+
+  * Add command line options `--table-caption-position` and
+    `--figure-caption-position`. These allow the user to specify whether
+    to put captions above or below tables and figures, respectively.
+    The following output formats are supported: HTML (and related such
+    as EPUB), LaTeX (and Beamer), Docx, ODT/OpenDocument, Typst.
+
+  * Change default `--pdf-engine` via HTML to WeasyPrint (#10142).
+    `wkhtmltopdf` is deprecated.  `weasyprint` is the easiest-to-install,
+    maintained alternative.  For better results, one might prefer
+    `pagedjs-cli`.
+
+  * Org reader:
+
+    + Fix parsing of src blocks with an `-i` flag (#10071, Albert Krewinkel).
+      Tabs are now preserved in the contents of *src* blocks if the the block
+      has the `-i` flag.
+
+  * RTF reader:
+
+    + Handle images inside `shp` contexts (#10145).
+
+  * RST reader:
+
+   + Improve simple table support (#10093). Multiline rows occur
+     only when the *first* cell is empty; we were previously treating
+    lines with *any* empty cell as row continuations. In addition, we
+    no longer wrap multiline cells in Para if they can be represented
+    as Plain. This is consistent with docutils behavior.
+
+  * LaTeX reader:
+
+    + Math environments don't have bracketed options (#10160).
+    + Parse nested tabular environments (#4746).
+
+  * Typst reader:
+
+    + Change how "block" elements are handled. Previously they were
+      always parsed as divs.  But actually they can occur in some "inline"
+      contexts.  Now we first try to parse them as inlines, and only as
+      blocks if that fails.  A surrounding Div or Span element is added
+      only if there is an identifier.
+
+  * HTML reader:
+
+    + Only parse main element's contents (if present) (#10140).
+      If main has an id or class, we include a div with that id or class;
+      otherwise just the contents.
+    + Read TeX annotation in MathML content if present (#9971).
+    + Better handle KaTeX-generated math (#9971). KaTeX emits the mathml
+      followed by a span with an HTML fallback. Previously pandoc was
+      converting both.  We now ignore the HTML fallback span, marked
+      with class `katex-html`.
+
+  * New module: Text.Pandoc.Writers.ANSI [API change] (Evan Silberman).
+
+  * Docx writer:
+
+    + Add "SuppressAuthor" and "AuthorOnly" to citationMode
+      when `+citations` is used (thomjur).
+    + Support `custom-style` attribute for docx table (Sebbones).
+    + Support `--number-offsets`.
+    + Make table/figure rendering sensitive to caption position settings.
+
+  * OpenDocument writer:
+
+    + Make table/figure rendering sensitive to caption position settings.
+
+  * Typst writer/template:
+
+    + Implement figure caption positions by triggering a show rule in
+      the default template, which determines caption positions for
+      figures and tables globally.
+    + Don't include trailing semicolon after `@` style citations with suffixes
+      (#10148).
+    + Template: move header-includes before show doc (#9996, Gordon Woodhull).
+
+  * LaTeX writer:
+
+    + Make table/figure rendering sensitive to caption position settings (#5116).
+    + Preserve locator labels with `--natbib` (#10057).
+
+  * HTML writer/template:
+
+    + Make `<figcaption>` placement sensitive to caption position
+      settings. For tables, `<caption>` must be the first element, and
+      positioning is determined by CSS, for here we set a variable
+      which the default template is sensitive to.
+    + Use `makeSectionsWithOffsets` for `writerNumberOffsets`,
+      instead of the old, inefficient code.
+    + Don't add doc-biblioref role to every link in a citation;
+      only to links to the bibliography (#10156).
+    + Add `data-` when rendering `label` attribute (#10048).
+
+  * Markdown writer:
+
+    + Avoid emitting markdown caption if table has fallen back to raw HTML,
+      which will then contain a `<caption>` tag (#10094).
+    + Make math sensitive to `tex_math_gfm` extension (#9121).
+      This means that in GFM output, the "new style" math will be used
+      by default, e.g.
+
+          $`x=y`$
+
+          ```math
+          x = y
+          ```
+
+      To defeat this and get the older behavior, namely
+
+          $x=y$
+
+          $$x=y$$
+
+      one could use `-t gfm-tex_math_gfm`.
+
+  * AsciiDoc writer:
+
+    + Add `link:` prefix when needed (#10105). AsciiDoc requires it
+      except for `http`, `https`, `irc`, `mailto`, `ftp` schemes (#10105).
+    + Preserve original base level (#10062). We used to normalize so
+      that the base level is always 1, but asciidoc no longer seems
+      to care about that, and the behavior creates difficulties when
+      we are converting fragments.
+    + Don't emit empty figure caption (#10047).
+
+  * ODT writer:
+
+    + Add TableCaption to styles.xml (#10058, Ian Max Andolina).
+
+  * LaTeX template:
+
+    + Fix wrong beamer color in (sub)section page (Jonathan).
+
+  * Text.Pandoc.Options:
+
+    + Add `CaptionPosition` and new `WriterOptions` fields
+      `writerFigureCaptionPosition` and `writerTableCaptionPosition`
+      [API change].
+
+  * Text.Pandoc.Opt:
+
+    + Change default for optNumberOffset to `[]`. This behaves the same as
+      `[0,0,0,0,0]`.
+    + Add `Opt` fields `optFigureCaptionPosition` and
+      `optTableCaptionPosition` [API change].
+
+  * Text.Pandoc.Format: change `formatFromFilePaths` so that it is
+    smarter about URLs.  URLs are parsed, and we take the format from
+    the path component, if present (#10141). This means that
+    `https://emacs.org/` will be treated as HTML, while
+    `https://emacs.org/sample.org` will be treated as Org.
+
+  * Text.Pandoc.URI:
+
+    + Add unofficial `gemini:` to list of URI schemes (Pau RE).
+
+  * Text.Pandoc.Shared:
+
+    + Add `makeSectionsWithOffsets` [API change].
+    + Remove `stripEmptyParagraphs [API change] (Albert Krewinkel).
+      This function is no longer used.
+
+  * Text.Pandoc.Highlighting: Expose `formatANSI` [API change]
+    (Evan Silberman).
+
+  * Text.Pandoc.Writers.Shared: export `to{Sub,Super}scriptInline`
+    [API change] (Evan Silberman).
+
+  * Remove use of partial functions (e.g. `head`) in code.
+
+  * Use latest skylighting-core, skylighting, doclayout, texmath, typst.
+
+  * pandoc-lua-engine: Add accessors for several writer options, including
+    some that were added in previous releases.
+
+  * pandoc-server: Initialize some missing fields in WriterOptions:
+    `writerEpubTitlePage`, `writerChunkTemplate`, `writerListTables`,
+    `writerFigureCaptionPosition`, `writerTableCaptionPosition`.
+
+  * CONTRIBUTING.md: Summarize steps for adding a new cli option.
+
+  * MANUAL.txt:
+
+    + Clarify that the `--number-offset` option should only
+      directly affect numbering of the first section heading in
+      a document; subsequent headings will increment normally.
+    + Fix asciidoc link (#10039).
+    + Fix CSL Docs broken link (#10100, Tristano Ajmone).
+    + Document the use of `luatexja` when CJKmainfont is used with lualatex
+      (#3873, Kolen Cheung).
+    + Add a `citations` (typst) section to the manual (#9127).
+    + Clarify that `citations` affects both input and output for `org`.
+    + Add note on `--citeproc` that you may need to disable `citations`
+      extension on the output format (e.g., `-t markdown-citations`) to
+      see the rendered citation (#9127, #10012).
+
+  * INSTALL.md — reorganise info on static binaries and add conda-forge
+    install options (#10098, #10069, Ian Max Andolina).
+
 ## pandoc 3.3 (2024-07-28)
 
   * New cli option: `--link-images`.  This causes images to be linked
diff --git a/data/odt/styles.xml b/data/odt/styles.xml
--- a/data/odt/styles.xml
+++ b/data/odt/styles.xml
@@ -148,6 +148,9 @@
       style:font-size-complex="12pt"
       style:font-style-complex="italic" />
     </style:style>
+    <style:style style:name="TableCaption" style:family="paragraph"
+    style:parent-style-name="Caption" style:class="extra">
+    </style:style>
     <style:style style:name="Table" style:family="paragraph"
     style:parent-style-name="Caption" style:class="extra">
     </style:style>
diff --git a/data/templates/default.ansi b/data/templates/default.ansi
new file mode 100644
--- /dev/null
+++ b/data/templates/default.ansi
@@ -0,0 +1,21 @@
+$if(titleblock)$
+$titleblock$
+
+$endif$
+$for(header-includes)$
+$header-includes$
+
+$endfor$
+$for(include-before)$
+$include-before$
+
+$endfor$
+$if(toc)$
+$table-of-contents$
+
+$endif$
+$body$
+$for(include-after)$
+
+$include-after$
+$endfor$
diff --git a/data/templates/default.latex b/data/templates/default.latex
--- a/data/templates/default.latex
+++ b/data/templates/default.latex
@@ -60,13 +60,13 @@
 }
 \setbeamertemplate{section page}{
   \centering
-  \begin{beamercolorbox}[sep=12pt,center]{part title}
+  \begin{beamercolorbox}[sep=12pt,center]{section title}
     \usebeamerfont{section title}\insertsection\par
   \end{beamercolorbox}
 }
 \setbeamertemplate{subsection page}{
   \centering
-  \begin{beamercolorbox}[sep=8pt,center]{part title}
+  \begin{beamercolorbox}[sep=8pt,center]{subsection title}
     \usebeamerfont{subsection title}\insertsubsection\par
   \end{beamercolorbox}
 }
diff --git a/data/templates/default.typst b/data/templates/default.typst
--- a/data/templates/default.typst
+++ b/data/templates/default.typst
@@ -14,12 +14,24 @@
   stroke: none
 )
 
+#show figure.where(
+  kind: table
+): set figure.caption(position: $if(table-caption-position)$$table-caption-position$$else$top$endif$)
+
+#show figure.where(
+  kind: image
+): set figure.caption(position: $if(figure-caption-position)$$figure-caption-position$$else$bottom$endif$)
+
 $if(template)$
 #import "$template$": conf
 $else$
 $template.typst()$
 $endif$
 
+$for(header-includes)$
+$header-includes$
+
+$endfor$
 #show: doc => conf(
 $if(title)$
   title: [$title$],
@@ -76,10 +88,6 @@
   doc,
 )
 
-$for(header-includes)$
-$header-includes$
-
-$endfor$
 $for(include-before)$
 $include-before$
 
diff --git a/data/templates/styles.html b/data/templates/styles.html
--- a/data/templates/styles.html
+++ b/data/templates/styles.html
@@ -144,7 +144,12 @@
   font-variant-numeric: lining-nums tabular-nums;
 }
 table caption {
+$if(table-caption-below)$
+  caption-side: bottom;
+  margin-top: 0.75em;
+$else$
   margin-bottom: 0.75em;
+$endif$
 }
 tbody {
   margin-top: 0.5em;
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.3
+version:         3.4
 build-type:      Simple
 license:         GPL-2.0-or-later
 license-file:    COPYING.md
@@ -96,6 +96,7 @@
                  data/templates/default.org
                  data/templates/default.epub2
                  data/templates/default.epub3
+                 data/templates/default.ansi
                  data/templates/article.jats_publishing
                  data/templates/affiliations.jats
                  data/templates/default.markua
@@ -334,6 +335,7 @@
                  test/tables/*.mediawiki
                  test/tables/*.jats_archiving
                  test/testsuite.txt
+                 test/ansi-test.txt
                  test/writer.latex
                  test/writer.context
                  test/writer.djot
@@ -369,6 +371,7 @@
                  test/writer.zimwiki
                  test/writer.xwiki
                  test/writer.muse
+                 test/ansi-test.ansi
                  test/writers-lang-and-dir.latex
                  test/writers-lang-and-dir.context
                  test/dokuwiki_inline_formatting.dokuwiki
@@ -489,7 +492,7 @@
                  data-default          >= 0.4      && < 0.8,
                  deepseq               >= 1.3      && < 1.6,
                  directory             >= 1.2.3    && < 1.4,
-                 doclayout             >= 0.4.0.1  && < 0.5,
+                 doclayout             >= 0.5      && < 0.6,
                  doctemplates          >= 0.11     && < 0.12,
                  emojis                >= 0.1.4.1  && < 0.2,
                  exceptions            >= 0.8      && < 0.11,
@@ -514,13 +517,13 @@
                  random                >= 1        && < 1.3,
                  safe                  >= 0.3.18   && < 0.4,
                  scientific            >= 0.3      && < 0.4,
-                 skylighting           >= 0.14.2   && < 0.15,
-                 skylighting-core      >= 0.14.2   && < 0.15,
+                 skylighting           >= 0.14.3   && < 0.15,
+                 skylighting-core      >= 0.14.3   && < 0.15,
                  split                 >= 0.2      && < 0.3,
                  syb                   >= 0.1      && < 0.8,
                  tagsoup               >= 0.14.6   && < 0.15,
                  temporary             >= 1.1      && < 1.4,
-                 texmath               >= 0.12.8.9 && < 0.13,
+                 texmath               >= 0.12.8.10 && < 0.13,
                  text                  >= 1.1.1.0  && < 2.2,
                  text-conversions      >= 0.3      && < 0.4,
                  time                  >= 1.5      && < 1.15,
@@ -530,7 +533,7 @@
                  zip-archive           >= 0.4.3.1  && < 0.5,
                  zlib                  >= 0.5      && < 0.8,
                  xml                   >= 1.3.12   && < 1.4,
-                 typst                 >= 0.5.0.5  && < 0.5.1,
+                 typst                 >= 0.6      && < 0.6.1,
                  vector                >= 0.12     && < 0.14,
                  djot                  >= 0.1.2.1  && < 0.2,
                  tls                   >= 2.0.1    && < 2.2,
@@ -635,6 +638,7 @@
                    Text.Pandoc.Writers.OOXML,
                    Text.Pandoc.Writers.AnnotatedTable,
                    Text.Pandoc.Writers.BibTeX,
+                   Text.Pandoc.Writers.ANSI,
                    Text.Pandoc.PDF,
                    Text.Pandoc.UTF8,
                    Text.Pandoc.Scripting,
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
@@ -67,7 +67,6 @@
 import Text.Pandoc.Scripting (ScriptingEngine (..), CustomComponents(..))
 import Text.Pandoc.SelfContained (makeSelfContained)
 import Text.Pandoc.Shared (tshow)
-import Text.Pandoc.URI (isURI)
 import Text.Pandoc.Writers.Shared (lookupMetaString)
 import Text.Pandoc.Readers.Markdown (yamlToMeta)
 import qualified Text.Pandoc.UTF8 as UTF8
@@ -150,7 +149,6 @@
       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"
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
@@ -214,7 +214,7 @@
 -- | Supported HTML PDF engines; the first item is used as default
 -- engine when going through HTML.
 htmlEngines :: [String]
-htmlEngines  = ["wkhtmltopdf", "weasyprint", "pagedjs-cli", "prince"]
+htmlEngines  = ["weasyprint", "wkhtmltopdf", "pagedjs-cli", "prince"]
 
 engines :: [(Text, String)]
 engines = map ("html",) htmlEngines ++
@@ -728,7 +728,31 @@
                                "Argument of --reference-location must be block, section, or document"
                      return opt { optReferenceLocation = action })
                   "block|section|document")
-                 "" -- "Accepting or reject MS Word track-changes.""
+                 "" -- "Specify where reference links and footnotes go"
+
+    , Option "" ["figure-caption-position"]
+                 (ReqArg
+                  (\arg opt -> do
+                     pos <- case arg of
+                            "above"  -> return CaptionAbove
+                            "below"  -> return CaptionBelow
+                            _        -> optError $ PandocOptionError $ T.pack
+                               "Argument of --figure-caption-position must be above or below"
+                     return opt { optFigureCaptionPosition = pos })
+                  "above|below")
+                 "" -- "Specify where figure captions go"
+
+    , Option "" ["table-caption-position"]
+                 (ReqArg
+                  (\arg opt -> do
+                     pos <- case arg of
+                            "above"  -> return CaptionAbove
+                            "below"  -> return CaptionBelow
+                            _        -> optError $ PandocOptionError $ T.pack
+                               "Argument of --table-caption-position must be above or below"
+                     return opt { optTableCaptionPosition = pos })
+                  "above|below")
+                 "" -- "Specify where table captions go"
 
     , Option "" ["markdown-headings"]
                   (ReqArg
diff --git a/src/Text/Pandoc/App/Opt.hs b/src/Text/Pandoc/App/Opt.hs
--- a/src/Text/Pandoc/App/Opt.hs
+++ b/src/Text/Pandoc/App/Opt.hs
@@ -41,6 +41,7 @@
                             TrackChanges (AcceptChanges),
                             WrapOption (WrapAuto), HTMLMathMethod (PlainMath),
                             ReferenceLocation (EndOfDocument),
+                            CaptionPosition (..),
                             ObfuscationMethod (NoObfuscation),
                             CiteMethod (Citeproc))
 import Text.Pandoc.Class (readFileStrict, fileExists, setVerbosity, report,
@@ -143,6 +144,8 @@
     , optFailIfWarnings        :: Bool    -- ^ Fail on warnings
     , optReferenceLinks        :: Bool    -- ^ Use reference links in writing markdown, rst
     , optReferenceLocation     :: ReferenceLocation -- ^ location for footnotes and link references in markdown output
+    , optFigureCaptionPosition :: CaptionPosition -- ^ position for figure caption
+    , optTableCaptionPosition  :: CaptionPosition -- ^ position for table caption
     , optDpi                   :: Int     -- ^ Dpi
     , optWrap                  :: WrapOption  -- ^ Options for wrapping text
     , optColumns               :: Int     -- ^ Line length in characters
@@ -227,6 +230,8 @@
        <*> o .:? "fail-if-warnings" .!= optFailIfWarnings defaultOpts
        <*> o .:? "reference-links" .!= optReferenceLinks defaultOpts
        <*> o .:? "reference-location" .!= optReferenceLocation defaultOpts
+       <*> o .:? "figure-caption-position" .!= optFigureCaptionPosition defaultOpts
+       <*> o .:? "table-caption-position" .!= optTableCaptionPosition defaultOpts
        <*> o .:? "dpi" .!= optDpi defaultOpts
        <*> o .:? "wrap" .!= optWrap defaultOpts
        <*> o .:? "columns" .!= optColumns defaultOpts
@@ -594,6 +599,10 @@
       parseJSON v >>= \x -> return (\o -> o{ optReferenceLinks = x })
     "reference-location" ->
       parseJSON v >>= \x -> return (\o -> o{ optReferenceLocation = x })
+    "figure-caption-position" ->
+      parseJSON v >>= \x -> return (\o -> o{ optFigureCaptionPosition = x })
+    "table-caption-position" ->
+      parseJSON v >>= \x -> return (\o -> o{ optTableCaptionPosition = x })
     "dpi" ->
       parseJSON v >>= \x -> return (\o -> o{ optDpi = x })
     "wrap" ->
@@ -737,7 +746,7 @@
     , optOutputFile            = Nothing
     , optInputFiles            = Nothing
     , optNumberSections        = False
-    , optNumberOffset          = [0,0,0,0,0,0]
+    , optNumberOffset          = []
     , optSectionDivs           = False
     , optIncremental           = False
     , optSelfContained         = False
@@ -766,6 +775,8 @@
     , optFailIfWarnings        = False
     , optReferenceLinks        = False
     , optReferenceLocation     = EndOfDocument
+    , optFigureCaptionPosition = CaptionBelow
+    , optTableCaptionPosition  = CaptionAbove
     , optDpi                   = 96
     , optWrap                  = WrapAuto
     , optColumns               = 72
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
@@ -224,8 +224,8 @@
                       setVariableM "dzslides-core" dzcore vars
                   else return vars)
 
-  let writerOpts = def {
-          writerTemplate         = mtemplate
+  let writerOpts = WriterOptions
+        { writerTemplate         = mtemplate
         , writerVariables        = variables
         , writerTabStop          = optTabStop opts
         , writerTableOfContents  = optTableOfContents opts
@@ -238,6 +238,8 @@
         , writerExtensions       = writerExts
         , writerReferenceLinks   = optReferenceLinks opts
         , writerReferenceLocation = optReferenceLocation opts
+        , writerFigureCaptionPosition = optFigureCaptionPosition opts
+        , writerTableCaptionPosition = optTableCaptionPosition opts
         , writerDpi              = optDpi opts
         , writerWrapText         = optWrap opts
         , writerColumns          = optColumns opts
diff --git a/src/Text/Pandoc/Chunks.hs b/src/Text/Pandoc/Chunks.hs
--- a/src/Text/Pandoc/Chunks.hs
+++ b/src/Text/Pandoc/Chunks.hs
@@ -99,7 +99,7 @@
 addUp [] = []
 
 addNext :: [Chunk] -> [Chunk]
-addNext cs = zipWith go cs (map Just (tail cs) ++ [Nothing])
+addNext cs = zipWith go cs (map Just (drop 1 cs) ++ [Nothing])
  where
   go c nxt = c{ chunkNext = nxt }
 
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
@@ -38,6 +38,7 @@
   , showExtension
   , readExtension
   )
+import Network.URI (URI (..), parseURI)
 import Text.Pandoc.Parsing
 import qualified Data.Text as T
 
@@ -170,7 +171,7 @@
 -- | Determines format based on file extension.
 formatFromFilePath :: FilePath -> Maybe FlavoredFormat
 formatFromFilePath x =
-  case takeExtension (map toLower x) of
+  case takeExtension (map toLower fpath) of
     ".Rmd"      -> defFlavor "markdown"
     ".adoc"     -> defFlavor "asciidoc"
     ".asciidoc" -> defFlavor "asciidoc"
@@ -233,3 +234,8 @@
   withExtension Nothing _ = Nothing
   withExtension (Just (FlavoredFormat f ed)) ext = Just $
     FlavoredFormat f (ed <> ExtensionsDiff (extensionsFromList [ext]) mempty)
+  fpath = case parseURI x of
+            Nothing -> x
+            Just URI{ uriPath = "" } -> "index.html"
+            Just URI{ uriPath = "/" } -> "index.html"
+            Just URI{ uriPath = up } -> up
diff --git a/src/Text/Pandoc/Highlighting.hs b/src/Text/Pandoc/Highlighting.hs
--- a/src/Text/Pandoc/Highlighting.hs
+++ b/src/Text/Pandoc/Highlighting.hs
@@ -29,6 +29,7 @@
                                 , formatConTeXtInline
                                 , formatConTeXtBlock
                                 , styleToConTeXt
+                                , formatANSI
                                 -- * Styles
                                 , pygments
                                 , espresso
diff --git a/src/Text/Pandoc/Options.hs b/src/Text/Pandoc/Options.hs
--- a/src/Text/Pandoc/Options.hs
+++ b/src/Text/Pandoc/Options.hs
@@ -28,6 +28,7 @@
                            , WriterOptions (..)
                            , TrackChanges (..)
                            , ReferenceLocation (..)
+                           , CaptionPosition (..)
                            , def
                            , isEnabled
                            , defaultMathJaxURL
@@ -286,6 +287,22 @@
    toJSON EndOfSection = "end-of-section"
    toJSON EndOfDocument = "end-of-document"
 
+-- | Positions for figure and table captions
+data CaptionPosition = CaptionAbove -- ^ above figure or table
+                     | CaptionBelow -- ^ below figure or table
+                     deriving (Show, Read, Eq, Data, Typeable, Generic)
+
+instance FromJSON CaptionPosition where
+  parseJSON v =
+    case v of
+      String "above" -> return CaptionAbove
+      String "below" -> return CaptionBelow
+      _ -> fail $ "Unknown caption position " <> toStringLazy (encode v)
+
+instance ToJSON CaptionPosition where
+   toJSON CaptionAbove = "above"
+   toJSON CaptionBelow = "below"
+
 -- | Options for writers
 data WriterOptions = WriterOptions
   { writerTemplate          :: Maybe (Template Text) -- ^ Template to use
@@ -323,6 +340,8 @@
   , writerTOCDepth          :: Int            -- ^ Number of levels to include in TOC
   , writerReferenceDoc      :: Maybe FilePath -- ^ Path to reference document if specified
   , writerReferenceLocation :: ReferenceLocation    -- ^ Location of footnotes and references for writing markdown
+  , writerFigureCaptionPosition :: CaptionPosition -- ^ Position of figure caption
+  , writerTableCaptionPosition :: CaptionPosition -- ^ Position of table caption
   , writerSyntaxMap         :: SyntaxMap
   , writerPreferAscii       :: Bool           -- ^ Prefer ASCII representations of characters when possible
   , writerLinkImages        :: Bool           -- ^ Use links rather than embedding ODT images
@@ -362,6 +381,8 @@
                       , writerTOCDepth         = 3
                       , writerReferenceDoc     = Nothing
                       , writerReferenceLocation = EndOfDocument
+                      , writerFigureCaptionPosition = CaptionBelow
+                      , writerTableCaptionPosition  = CaptionAbove
                       , writerSyntaxMap        = defaultSyntaxMap
                       , writerPreferAscii      = False
                       , writerLinkImages       = False
diff --git a/src/Text/Pandoc/Parsing/GridTable.hs b/src/Text/Pandoc/Parsing/GridTable.hs
--- a/src/Text/Pandoc/Parsing/GridTable.hs
+++ b/src/Text/Pandoc/Parsing/GridTable.hs
@@ -248,4 +248,4 @@
                    then fromIntegral totLength
                    else fromIntegral numColumns
       fracs = map (\l -> fromIntegral l / quotient) lengths in
-  tail fracs
+  drop 1 fracs
diff --git a/src/Text/Pandoc/Readers/Docx.hs b/src/Text/Pandoc/Readers/Docx.hs
--- a/src/Text/Pandoc/Readers/Docx.hs
+++ b/src/Text/Pandoc/Readers/Docx.hs
@@ -510,6 +510,13 @@
          else smushInlines <$> mapM parPartToInlines' children
     _ -> smushInlines <$> mapM parPartToInlines' children
 
+-- Helper function to convert CitationItemType to CitationMode
+convertCitationMode :: Citeproc.CitationItemType -> CitationMode
+convertCitationMode itemType = case itemType of
+                                 Citeproc.NormalCite -> NormalCitation
+                                 Citeproc.SuppressAuthor -> SuppressAuthor
+                                 Citeproc.AuthorOnly -> AuthorInText
+
 -- Turn a 'Citeproc.Citation' into a list of 'Text.Pandoc.Definition.Citation',
 -- and store the embedded bibliographic data in state.
 handleCitation :: PandocMonad m
@@ -526,7 +533,7 @@
                          <> x <> " ")
                      (Citeproc.citationItemLocator item)
                     <> fromMaybe mempty (Citeproc.citationItemSuffix item)
-                , citationMode = NormalCitation -- TODO for now
+                , citationMode = convertCitationMode (Citeproc.citationItemType item)
                 , citationNoteNum = 0
                 , citationHash = 0 }
   let items = Citeproc.citationItems citation
@@ -539,7 +546,6 @@
           (docxReferences st)
           refs }
   return cs
-
 
 isAnchorSpan :: Inline -> Bool
 isAnchorSpan (Span (_, ["anchor"], []) _) = True
diff --git a/src/Text/Pandoc/Readers/Docx/Parse.hs b/src/Text/Pandoc/Readers/Docx/Parse.hs
--- a/src/Text/Pandoc/Readers/Docx/Parse.hs
+++ b/src/Text/Pandoc/Readers/Docx/Parse.hs
@@ -1170,7 +1170,7 @@
   | isElem ns "w" "r" element
   , Just altCont <- findChildByName ns "mc" "AlternateContent" element =
     do let choices = findChildrenByName ns "mc" "Choice" altCont
-           choiceChildren = map head $ filter (not . null) $ map elChildren choices
+           choiceChildren = concatMap (take 1 . elChildren) choices
        outputs <- mapD (childElemToRun ns) choiceChildren
        case outputs of
          r : _ -> return r
diff --git a/src/Text/Pandoc/Readers/DokuWiki.hs b/src/Text/Pandoc/Readers/DokuWiki.hs
--- a/src/Text/Pandoc/Readers/DokuWiki.hs
+++ b/src/Text/Pandoc/Readers/DokuWiki.hs
@@ -483,7 +483,7 @@
   rows <- tableRows
   let firstRow = fromMaybe [] . Safe.headMay $ rows
   let (headerRow, body) = if firstSeparator == '^'
-                            then (firstRow, tail rows)
+                            then (firstRow, drop 1 rows)
                             else ([], rows)
   -- Since Pandoc only has column level alignment, we have to make an arbitrary
   -- choice of how to reconcile potentially different alignments in the row.
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
@@ -85,7 +85,7 @@
         meta <- stateMeta . parserState <$> getState
         bs' <- replaceNotes (B.toList blocks)
         reportLogMessages
-        return $ Pandoc meta bs'
+        return $ Pandoc meta $ extractMain bs'
       getError (errorMessages -> ms) = case ms of
                                          []    -> ""
                                          (m:_) -> messageString m
@@ -98,6 +98,20 @@
     Right doc -> return doc
     Left  err -> throwError $ PandocParseError $ T.pack $ getError err
 
+-- Extract contents of main element if exactly one is present; otherwise
+-- return all blocks.
+extractMain :: [Block] -> [Block]
+extractMain bs =
+  case query getMain bs of
+    [] -> bs
+    [Div ("",[],_) bs'] -> bs'
+    bs' -> bs'
+ where
+   getMain :: Block -> [Block]
+   getMain b@(Div (_,_,kvs) _)
+     | Just "main" <- lookup "role" kvs = [b]
+   getMain _ = []
+
 -- Strip namespace prefixes on tags (not attributes)
 stripPrefixes :: [Tag Text] -> [Tag Text]
 stripPrefixes = map stripPrefix
@@ -512,7 +526,7 @@
                | "image/" `T.isPrefixOf` mt -> do
                     return $ B.divWith ("",["iframe"],[]) $
                       B.plain $ B.image url "" mempty
-             _ -> return $ B.divWith ("",["iframe"],[("src", url)]) $ mempty)
+             _ -> return $ B.divWith ("",["iframe"],[("src", url)]) mempty)
        (\e -> do
          logMessage $ CouldNotFetchResource url (renderError e)
          ignore $ renderTags' [tag, TagClose "iframe"])
@@ -852,23 +866,25 @@
 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
+  let attr = toAttr attr'
+  case attr of
+     (_,["katex-html"],_) -> mempty <$ pInTags "span" inline
+       -- skip HTML generated by KaTeX, since we get
+       -- the math by parsing mathml (#9971)
+     _ | extensionEnabled Ext_native_spans exts -> do
+           contents <- pInTags "span" inline
+           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
+       | extensionEnabled Ext_raw_html exts -> do
+            tag <- pSatisfy $ tagOpen (=="span") (const True)
+            return $ B.rawInline "html" $ renderTags' [tag]
+       | otherwise  -> pInTags "span" inline -- just contents
 
 pRawHtmlInline :: PandocMonad m => TagParser m Inlines
 pRawHtmlInline = do
@@ -904,15 +920,33 @@
   let attr = toStringAttr attr'
   unless inCase $
     guard (maybe True (== mathMLNamespace) (lookup "xmlns" attr))
+  let isDisplay = case lookup "display" attr of
+                     Just "block" -> True
+                     _ -> False
   contents <- manyTill pAny (pSatisfy (matchTagClose "math"))
-  case mathMLToTeXMath (renderTags $
-          [open] <> contents <> [TagClose "math"]) of
-       Left _   -> return $ B.spanWith ("",["math"],attr) $ B.text $
-                             innerText contents
-       Right "" -> return mempty
-       Right x  -> return $ case lookup "display" attr of
-                                 Just "block" -> B.displayMath x
-                                 _            -> B.math x
+  -- KaTeX and others include original TeX in annotation tag;
+  -- just use this if present rather than parsing MathML:
+  case extractTeXAnnotation contents of
+    Just x -> return $ if isDisplay
+                          then B.displayMath x
+                          else B.math x
+    Nothing ->
+      case mathMLToTeXMath (renderTags $
+              [open] <> contents <> [TagClose "math"]) of
+           Left _   -> return $ B.spanWith ("",["math"],attr) $ B.text $
+                                 innerText contents
+           Right "" -> return mempty
+           Right x  -> return $ if isDisplay
+                                   then B.displayMath x
+                                   else B.math x
+
+extractTeXAnnotation :: [Tag Text] -> Maybe Text
+extractTeXAnnotation [] = Nothing
+extractTeXAnnotation (TagOpen "annotation" [("encoding","application/x-tex")]
+                       : ts) =
+  Just $ innerText $ takeWhile (~/= TagClose ("annotation" :: Text)) ts
+extractTeXAnnotation (_:ts) = extractTeXAnnotation ts
+
 
 pInlinesInTags :: PandocMonad m => Text -> (Inlines -> Inlines)
                -> TagParser m Inlines
diff --git a/src/Text/Pandoc/Readers/LaTeX.hs b/src/Text/Pandoc/Readers/LaTeX.hs
--- a/src/Text/Pandoc/Readers/LaTeX.hs
+++ b/src/Text/Pandoc/Readers/LaTeX.hs
@@ -1007,7 +1007,7 @@
     skipMany $ infile (sourceName pos)
 
 environments :: PandocMonad m => M.Map Text (LP m Blocks)
-environments = M.union (tableEnvironments blocks inline) $
+environments = M.union (tableEnvironments block inline) $
    M.fromList
    [ ("document", env "document" blocks <* skipMany anyTok)
    , ("abstract", mempty <$ (env "abstract" blocks >>= addMeta "abstract"))
diff --git a/src/Text/Pandoc/Readers/LaTeX/Math.hs b/src/Text/Pandoc/Readers/LaTeX/Math.hs
--- a/src/Text/Pandoc/Readers/LaTeX/Math.hs
+++ b/src/Text/Pandoc/Readers/LaTeX/Math.hs
@@ -70,7 +70,6 @@
 
 mathEnv :: PandocMonad m => Text -> LP m Text
 mathEnv name = do
-  skipopts
   optional blankline
   res <- manyTill anyTok (end_ name)
   return $ stripTrailingNewlines $ untokenize res
diff --git a/src/Text/Pandoc/Readers/LaTeX/Table.hs b/src/Text/Pandoc/Readers/LaTeX/Table.hs
--- a/src/Text/Pandoc/Readers/LaTeX/Table.hs
+++ b/src/Text/Pandoc/Readers/LaTeX/Table.hs
@@ -25,17 +25,19 @@
                   => LP m Blocks
                   -> LP m Inlines
                   -> M.Map Text (LP m Blocks)
-tableEnvironments blocks inline =
+tableEnvironments block inline =
   M.fromList
   [ ("longtable",  env "longtable" $
           resetCaption *>
-            simpTable blocks inline "longtable" False >>= addTableCaption)
+            simpTable block inline "longtable" False >>= addTableCaption)
   , ("table",  env "table" $
           skipopts *> resetCaption *> blocks >>= addTableCaption)
-  , ("tabular*", env "tabular*" $ simpTable blocks inline "tabular*" True)
-  , ("tabularx", env "tabularx" $ simpTable blocks inline "tabularx" True)
-  , ("tabular", env "tabular"  $ simpTable blocks inline "tabular" False)
+  , ("tabular*", env "tabular*" $ simpTable block inline "tabular*" True)
+  , ("tabularx", env "tabularx" $ simpTable block inline "tabularx" True)
+  , ("tabular", env "tabular"  $ simpTable block inline "tabular" False)
   ]
+ where
+   blocks = mconcat <$> many block
 
 hline :: PandocMonad m => LP m ()
 hline = try $ do
@@ -137,7 +139,7 @@
               -> Text   -- ^ table environment name
               -> [([Tok], [Tok])] -- ^ pref/suffixes
               -> LP m Row
-parseTableRow blocks inline envname prefsufs = do
+parseTableRow block inline envname prefsufs = do
   notFollowedBy (spaces *> end_ envname)
   -- contexts that can contain & that is not colsep:
   let canContainAmp (Tok _ (CtrlSeq "begin") _) = True
@@ -150,10 +152,13 @@
         contents <- mconcat <$>
             many ( snd <$> withRaw
                      ((lookAhead (controlSeq "parbox") >>
-                       void blocks) -- #5711
+                       void block) -- #5711
                       <|>
                       (lookAhead (satisfyTok canContainAmp) >> void inline)
                       <|>
+                      (lookAhead (controlSeq "begin") >>
+                       void block) -- #4746
+                      <|>
                       (lookAhead (symbol '$') >> void inline))
                   <|>
                    (do notFollowedBy
@@ -164,16 +169,16 @@
         option [] (count 1 amp)
         return $ map (setpos prefpos) pref ++ contents ++ map (setpos suffpos) suff
   rawcells <- mapM celltoks prefsufs
-  cells <- mapM (parseFromToks (parseTableCell blocks)) rawcells
+  cells <- mapM (parseFromToks (parseTableCell block)) rawcells
   spaces
   return $ Row nullAttr cells
 
 parseTableCell :: PandocMonad m => LP m Blocks -> LP m Cell
-parseTableCell blocks = do
+parseTableCell block = do
   spaces
   updateState $ \st -> st{ sInTableCell = True }
-  cell' <-   multicolumnCell blocks
-         <|> multirowCell blocks
+  cell' <-   multicolumnCell block
+         <|> multirowCell block
          <|> parseSimpleCell
          <|> parseEmptyCell
   updateState $ \st -> st{ sInTableCell = False }
@@ -183,7 +188,7 @@
     -- The parsing of empty cells is important in LaTeX, especially when dealing
     -- with multirow/multicolumn. See #6603.
     parseEmptyCell = spaces $> emptyCell
-    parseSimpleCell = simpleCell <$> (plainify <$> blocks)
+    parseSimpleCell = simpleCell <$> (plainify . mconcat <$> many block)
 
 
 cellAlignment :: PandocMonad m => LP m Alignment
@@ -204,7 +209,7 @@
                 _          -> bs
 
 multirowCell :: PandocMonad m => LP m Blocks -> LP m Cell
-multirowCell blocks = controlSeq "multirow" >> do
+multirowCell block = controlSeq "multirow" >> do
   -- Full prototype for \multirow macro is:
   --     \multirow[vpos]{nrows}[bigstruts]{width}[vmove]{text}
   -- However, everything except `nrows` and `text` make
@@ -214,16 +219,16 @@
   _ <- optional $ symbol '[' *> manyTill anyTok (symbol ']')  -- bigstrut-related
   _ <- symbol '{' *> manyTill anyTok (symbol '}')             -- Cell width
   _ <- optional $ symbol '[' *> manyTill anyTok (symbol ']')  -- Length used for fine-tuning
-  content <- symbol '{' *> (plainify <$> blocks) <* symbol '}'
+  content <- symbol '{' *> (plainify . mconcat <$> many block) <* symbol '}'
   return $ cell AlignDefault (RowSpan nrows) (ColSpan 1) content
 
 multicolumnCell :: PandocMonad m => LP m Blocks -> LP m Cell
-multicolumnCell blocks = controlSeq "multicolumn" >> do
+multicolumnCell block = controlSeq "multicolumn" >> do
   span' <- fmap (fromMaybe 1 . safeRead . untokenize) braced
   alignment <- symbol '{' *> cellAlignment <* symbol '}'
 
   let singleCell = do
-        content <- plainify <$> blocks
+        content <- plainify . mconcat <$> many block
         return $ cell alignment (RowSpan 1) (ColSpan span') content
 
   -- Two possible contents: either a \multirow cell, or content.
@@ -231,7 +236,7 @@
   -- Note that a \multirow cell can be nested in a \multicolumn,
   -- but not the other way around. See #6603
   let nestedCell = do
-        (Cell _ _ (RowSpan rs) _ bs) <- multirowCell blocks
+        (Cell _ _ (RowSpan rs) _ bs) <- multirowCell block
         return $ cell
                   alignment
                   (RowSpan rs)
@@ -320,7 +325,7 @@
           -> Text
           -> Bool
           -> LP m Blocks
-simpTable blocks inline envname hasWidthParameter = try $ do
+simpTable block inline envname hasWidthParameter = try $ do
   when hasWidthParameter $ () <$ tokWith inline
   skipopts
   colspecs <- parseAligns
@@ -334,10 +339,10 @@
   skipMany hline
   spaces
   header' <- option [] . try . fmap (:[]) $
-             parseTableRow blocks inline envname prefsufs <*
+             parseTableRow block inline envname prefsufs <*
                lbreak <* many1 hline
   spaces
-  rows <- sepEndBy (parseTableRow blocks inline envname prefsufs)
+  rows <- sepEndBy (parseTableRow block inline envname prefsufs)
                     (lbreak <* optional (skipMany hline))
   spaces
   optional $ controlSeq "caption" *> setCaption inline
diff --git a/src/Text/Pandoc/Readers/Markdown.hs b/src/Text/Pandoc/Readers/Markdown.hs
--- a/src/Text/Pandoc/Readers/Markdown.hs
+++ b/src/Text/Pandoc/Readers/Markdown.hs
@@ -1248,7 +1248,7 @@
   let (lengths, lines') = unzip dashes
   let indices  = scanl (+) (T.length initSp) lines'
   -- If no header, calculate alignment on basis of first row of text
-  rawHeads <- fmap (tail . splitTextByIndices (init indices)) $
+  rawHeads <- fmap (drop 1 . splitTextByIndices (init indices)) $
               if headless
                  then lookAhead anyLine
                  else return rawContent
@@ -1295,8 +1295,7 @@
 rawTableLine indices = do
   notFollowedBy' (blanklines' <|> tableFooter)
   line <- anyLine
-  return $ map trim $ tail $
-           splitTextByIndices (init indices) line
+  return $ map trim $ drop 1 $ splitTextByIndices (init indices) line
 
 -- Parse a table line and return a list of lists of blocks (columns).
 tableLine :: PandocMonad m
@@ -1372,10 +1371,10 @@
                       []     -> []
                       (x:xs) -> reverse (x+1:xs)
   rawHeadsList <- if headless
-                     then map (:[]) . tail . splitTextByIndices (init indices')
+                     then map (:[]) . drop 1 . splitTextByIndices (init indices')
                           <$> lookAhead anyLine
                      else return $ transpose $ map
-                           (tail . splitTextByIndices (init indices'))
+                           (drop 1 . splitTextByIndices (init indices'))
                            rawContent
   let aligns   = zipWith alignType rawHeadsList lengths
   let rawHeads = if headless
diff --git a/src/Text/Pandoc/Readers/ODT/Generic/XMLConverter.hs b/src/Text/Pandoc/Readers/ODT/Generic/XMLConverter.hs
--- a/src/Text/Pandoc/Readers/ODT/Generic/XMLConverter.hs
+++ b/src/Text/Pandoc/Readers/ODT/Generic/XMLConverter.hs
@@ -65,6 +65,8 @@
 import           Data.Default
 import           Data.Maybe
 import           Data.List (foldl')
+import qualified Data.List.NonEmpty as NonEmpty
+import           Data.List.NonEmpty (NonEmpty(..))
 
 import qualified Text.Pandoc.XML.Light as XML
 
@@ -101,7 +103,7 @@
          -- Arguably, a real Zipper would be better. But that is an
          -- optimization that can be made at a later time, e.g. when
          -- replacing Text.XML.Light.
-         parentElements    :: [XML.Element]
+         parentElements    :: NonEmpty XML.Element
          -- | A map from internal namespace IDs to the namespace prefixes
          -- used in XML elements
        , namespacePrefixes :: NameSpacePrefixes nsID
@@ -126,7 +128,7 @@
                     -> XMLConverterState nsID extraState
 createStartState element extraState =
   XMLConverterState
-       { parentElements    = [element]
+       { parentElements    = element :| []
        , namespacePrefixes = M.empty
        , namespaceIRIs     = getInitialIRImap
        , moreState         = extraState
@@ -147,13 +149,14 @@
 --
 currentElement      :: XMLConverterState nsID extraState
                     -> XML.Element
-currentElement state = head (parentElements state)
+currentElement state = NonEmpty.head (parentElements state)
 
 -- | Replace the current position by another, modifying the extra state
 -- in the process
 swapStack'          :: XMLConverterState nsID extraState
-                    -> [XML.Element]
-                    -> ( XMLConverterState nsID extraState , [XML.Element] )
+                    -> NonEmpty XML.Element
+                    -> ( XMLConverterState nsID extraState
+                       , NonEmpty XML.Element )
 swapStack' state stack
                      = ( state { parentElements = stack }
                        , parentElements state
@@ -163,14 +166,16 @@
 pushElement         :: XML.Element
                     -> XMLConverterState nsID extraState
                     -> XMLConverterState nsID extraState
-pushElement e state  = state { parentElements = e:parentElements state }
+pushElement e state  = state { parentElements =
+                                 NonEmpty.cons e (parentElements state) }
 
 -- | Pop the top element from the call stack, unless it is the last one.
 popElement          :: XMLConverterState nsID extraState
                     -> Maybe (XMLConverterState nsID extraState)
 popElement state
-  | _:es@(_:_) <- parentElements state = Just $ state { parentElements = es }
-  | otherwise                          = Nothing
+  | _ :| (e:es) <- parentElements state
+                = Just $ state { parentElements = e :| es }
+  | otherwise   = Nothing
 
 --------------------------------------------------------------------------------
 -- Main type
@@ -544,7 +549,8 @@
                                    )
 
 --
-swapStack             :: XMLConverter nsID extraState [XML.Element] [XML.Element]
+swapStack             :: XMLConverter nsID extraState (NonEmpty XML.Element)
+                                                      (NonEmpty XML.Element)
 swapStack             = withState swapStack'
 
 --
@@ -559,7 +565,7 @@
 -- accessible to the converter.
 switchingTheStack      :: XMLConverter nsID moreState a b
                        -> XMLConverter nsID moreState (a, XML.Element) b
-switchingTheStack a    =     second ( (:[]) ^>> swapStack )
+switchingTheStack a    =     second ( (:| []) ^>> swapStack )
                          >>> first  a
                          >>> second swapStack
                          >>^ fst
diff --git a/src/Text/Pandoc/Readers/Org/Blocks.hs b/src/Text/Pandoc/Readers/Org/Blocks.hs
--- a/src/Text/Pandoc/Readers/Org/Blocks.hs
+++ b/src/Text/Pandoc/Readers/Org/Blocks.hs
@@ -33,6 +33,7 @@
 import Text.Pandoc.Shared (compactify, compactifyDL, safeRead)
 
 import Control.Monad (foldM, guard, mzero, void)
+import Data.Bifunctor (bimap)
 import Data.Char (isSpace)
 import Data.Default (Default)
 import Data.Functor (($>))
@@ -243,13 +244,20 @@
 rawBlockContent :: Monad m => Text -> OrgParser m Text
 rawBlockContent blockType = try $ do
   blkLines <- manyTill rawLine blockEnder
-  tabLen <- getOption readerTabStop
+  tabStop <- getOption readerTabStop
   trimP <- orgStateTrimLeadBlkIndent <$> getState
-  let stripIndent strs = if trimP then map (T.drop (shortestIndent strs)) strs else strs
-  (T.unlines
-   . stripIndent
-   . map (tabsToSpaces tabLen . commaEscaped)
-   $ blkLines)
+  -- split lines into indentation/contents tuples
+  let splitLines = map (T.span (\c -> c == ' ' || c == '\t')) blkLines
+  let countSpaces = T.foldr (\case {'\t' -> (tabStop +); _ -> (1 +)}) 0
+  let shortestIndent = foldr (min . countSpaces . fst) maxBound
+                     . filter (not . T.null . snd) -- ignore empty lines
+                     $ splitLines
+  let tabsToSpaces = T.replace "\t" (T.replicate tabStop " ")
+  let reIndent = if trimP
+                 then (T.drop shortestIndent . tabsToSpaces)
+                 else id
+
+  T.unlines (map (uncurry T.append . bimap reIndent commaEscaped) splitLines)
    <$ updateState (\s -> s { orgStateTrimLeadBlkIndent = True })
  where
    rawLine :: Monad m => OrgParser m Text
@@ -258,24 +266,11 @@
    blockEnder :: Monad m => OrgParser m ()
    blockEnder = try $ skipSpaces <* stringAnyCase ("#+end_" <> blockType)
 
-   shortestIndent :: [Text] -> Int
-   shortestIndent = foldr (min . T.length . T.takeWhile isSpace) maxBound
-                    . filter (not . T.null)
-
-   tabsToSpaces :: Int -> Text -> Text
-   tabsToSpaces tabStop t =
-     let (ind, suff) = T.span (\c -> c == ' ' || c == '\t') t
-         tabNum      = T.length $ T.filter (== '\n') ind
-         spaceNum    = T.length ind - tabNum
-     in  T.replicate (spaceNum + tabStop * tabNum) " " <> suff
-
-   commaEscaped t =
-     let (ind, suff) = T.span (\c -> c == ' ' || c == '\t') t
-     in  case T.uncons suff of
-           Just (',', cs)
-             | "*"  <- T.take 1 cs -> ind <> cs
-             | "#+" <- T.take 2 cs -> ind <> cs
-           _                       -> t
+   commaEscaped suff = case T.uncons suff of
+     Just (',', cs)
+       | "*"  <- T.take 1 cs -> cs
+       | "#+" <- T.take 2 cs -> cs
+     _                       -> suff
 
 -- | Read but ignore all remaining block headers.
 ignHeaders :: Monad m => OrgParser m ()
diff --git a/src/Text/Pandoc/Readers/RST.hs b/src/Text/Pandoc/Readers/RST.hs
--- a/src/Text/Pandoc/Readers/RST.hs
+++ b/src/Text/Pandoc/Readers/RST.hs
@@ -884,12 +884,6 @@
        Left e  ->
          throwError $ fromParsecError (toSources rawcsv') e
        Right rawrows -> do
-         let singleParaToPlain bs =
-               case B.toList bs of
-                 [Para ils] -> B.fromList [Plain ils]
-                 _          -> bs
-         let parseCell t = singleParaToPlain
-                <$> parseFromString' parseBlocks (t <> "\n\n")
          let parseRow = mapM parseCell
          rows <- mapM parseRow rawrows
          let (headerRow,bodyRows,numOfCols) =
@@ -918,6 +912,17 @@
                           [TableBody nullAttr 0 [] $ map toRow bodyRows]
                           (TableFoot nullAttr [])
 
+singleParaToPlain :: Blocks -> Blocks
+singleParaToPlain bs =
+  case B.toList bs of
+    [Para ils] -> B.fromList [Plain ils]
+    _          -> bs
+
+parseCell :: PandocMonad m => Text -> RSTParser m Blocks
+parseCell t = singleParaToPlain
+   <$> parseFromString' parseBlocks (t <> "\n\n")
+
+
 -- TODO:
 --  - Only supports :format: fields with a single format for :raw: roles,
 --    change Text.Pandoc.Definition.Format to fix
@@ -1285,23 +1290,24 @@
 simpleTableRawLine :: Monad m => [Int] -> RSTParser m [Text]
 simpleTableRawLine indices = simpleTableSplitLine indices <$> anyLine
 
-simpleTableRawLineWithEmptyCell :: Monad m => [Int] -> RSTParser m [Text]
-simpleTableRawLineWithEmptyCell indices = try $ do
+simpleTableRawLineWithInitialEmptyCell :: Monad m => [Int] -> RSTParser m [Text]
+simpleTableRawLineWithInitialEmptyCell indices = try $ do
   cs <- simpleTableRawLine indices
   let isEmptyCell = T.all (\c -> c == ' ' || c == '\t')
-  guard $ any isEmptyCell cs
-  return cs
+  case cs of
+    c:_ | isEmptyCell c -> return cs
+    _ -> mzero
 
 -- Parse a table row and return a list of blocks (columns).
 simpleTableRow :: PandocMonad m => [Int] -> RSTParser m [Blocks]
 simpleTableRow indices = do
   notFollowedBy' simpleTableFooter
   firstLine <- simpleTableRawLine indices
-  conLines  <- many $ simpleTableRawLineWithEmptyCell indices
+  conLines  <- many $ simpleTableRawLineWithInitialEmptyCell indices
   let cols = map T.unlines . transpose $ firstLine : conLines ++
                                   [replicate (length indices) ""
                                     | not (null conLines)]
-  mapM (parseFromString' parseBlocks) cols
+  mapM parseCell cols
 
 simpleTableSplitLine :: [Int] -> Text -> [Text]
 simpleTableSplitLine indices line =
diff --git a/src/Text/Pandoc/Readers/RTF.hs b/src/Text/Pandoc/Readers/RTF.hs
--- a/src/Text/Pandoc/Readers/RTF.hs
+++ b/src/Text/Pandoc/Readers/RTF.hs
@@ -431,9 +431,20 @@
     Grouped (Tok _ (ControlSymbol '*') : toks@(firsttok:_)) ->
       case firsttok of
         Tok _ (ControlWord "shppict" _) -> inGroup (foldM processTok bs toks)
+        Tok _ (ControlWord "shpinst" _) -> inGroup (foldM processTok bs toks)
         _ -> bs <$ (do oldTextContent <- sTextContent <$> getState
                        processTok mempty (Tok pos (Grouped toks))
                        updateState $ \st -> st{ sTextContent = oldTextContent })
+    Grouped (Tok _ (ControlWord "shp" _) : toks) ->
+      inGroup (foldM processTok bs toks)
+    Grouped [ Tok _ (ControlWord "sp" _)
+            , Tok _ (Grouped [Tok _ (ControlWord "sn" _),
+                      Tok _ (UnformattedText sn)])
+            , Tok _ (Grouped (Tok _ (ControlWord "sv" _) : svtoks))
+            ] ->
+      case sn of
+        "pib" -> inGroup (foldM processTok bs svtoks)
+        _ -> pure bs
     Grouped (Tok _ (ControlWord "fonttbl" _) : toks) -> inGroup $ do
       updateState $ \s -> s{ sFontTable = processFontTable toks }
       pure bs
diff --git a/src/Text/Pandoc/Readers/TikiWiki.hs b/src/Text/Pandoc/Readers/TikiWiki.hs
--- a/src/Text/Pandoc/Readers/TikiWiki.hs
+++ b/src/Text/Pandoc/Readers/TikiWiki.hs
@@ -157,8 +157,9 @@
   return $ B.simpleTable (headers rows) rows
   where
     -- The headers are as many empty strings as the number of columns
-    -- in the first row
-    headers rows = replicate (length $ head rows) ((B.plain . B.str) "")
+-- in the first row
+    headers [] = []
+    headers (r:_) = replicate (length r) ((B.plain . B.str) "")
 
 para :: PandocMonad m => TikiWikiParser m B.Blocks
 para =  fmap (result . mconcat) ( many1Till inline endOfParaElement)
@@ -226,10 +227,9 @@
 -- fixListNesting nestall | trace ("\n\nfixListNesting: " ++ (show nestall)) False = undefined
 -- fixListNesting nestall@(first:second:rest) =
 fixListNesting (first:second:rest) =
-  let secondBlock = head $ B.toList second in
-    case secondBlock of
-      BulletList _ -> fixListNesting $ mappend (recurseOnList first) (recurseOnList second) : rest
-      OrderedList _ _ -> fixListNesting $ mappend (recurseOnList first) (recurseOnList second) : rest
+    case B.toList second of
+      (BulletList _ : _) -> fixListNesting $ mappend (recurseOnList first) (recurseOnList second) : rest
+      (OrderedList _ _ : _) -> fixListNesting $ mappend (recurseOnList first) (recurseOnList second) : rest
       _ -> recurseOnList first : fixListNesting (second:rest)
 
 -- This function walks the Block structure for fixListNesting,
@@ -239,10 +239,9 @@
 -- recurseOnList item | trace ("rOL: " ++ (show $ length $ B.toList item) ++ ", " ++ (show $ B.toList item)) False = undefined
 recurseOnList items
   | length (B.toList items) == 1 =
-    let itemBlock = head $ B.toList items in
-      case itemBlock of
-        BulletList listItems -> B.bulletList $ fixListNesting $ map B.fromList listItems
-        OrderedList _ listItems -> B.orderedList $ fixListNesting $ map B.fromList listItems
+      case B.toList items of
+        (BulletList listItems : _) -> B.bulletList $ fixListNesting $ map B.fromList listItems
+        (OrderedList _ listItems : _) -> B.orderedList $ fixListNesting $ map B.fromList listItems
         _ -> items
 
   -- The otherwise works because we constructed the blocks, and we
diff --git a/src/Text/Pandoc/Readers/Typst.hs b/src/Text/Pandoc/Readers/Typst.hs
--- a/src/Text/Pandoc/Readers/Typst.hs
+++ b/src/Text/Pandoc/Readers/Typst.hs
@@ -294,7 +294,7 @@
       pure $ B.codeBlockWith attr txt)
   ,("parbreak", \_ _ -> pure mempty)
   ,("block", \mbident fields ->
-      B.divWith (fromMaybe "" mbident, [], [])
+      maybe id (\ident -> B.divWith (ident, [], [])) mbident
         <$> (getField "body" fields >>= pWithContents pBlocks))
   ,("place", \_ fields -> do
       ignored "parameters of place"
@@ -503,6 +503,9 @@
       (if display then B.displayMath else B.math) . writeTeX <$> pMathMany body)
   ,("pad", \_ fields ->  -- ignore paddingy
       getField "body" fields >>= pWithContents pInlines)
+  ,("block", \mbident fields ->
+      maybe id (\ident -> B.spanWith (ident, [], [])) mbident
+        <$> (getField "body" fields >>= pWithContents pInlines))
   ]
 
 getInlineBody :: PandocMonad m => M.Map Identifier Val -> P m (Seq Content)
diff --git a/src/Text/Pandoc/Readers/Vimwiki.hs b/src/Text/Pandoc/Readers/Vimwiki.hs
--- a/src/Text/Pandoc/Readers/Vimwiki.hs
+++ b/src/Text/Pandoc/Readers/Vimwiki.hs
@@ -249,14 +249,13 @@
 
 nameValue :: Text -> Maybe (Text, Text)
 nameValue s =
-  let t = splitTextBy (== '=') s in
-    if length t /= 2
-      then Nothing
-      else let (a, b) = (head t, last t) in
-             if (T.length b < 2) || ((T.head b, T.last b) /= ('"', '"'))
-               then Nothing
-               else Just (a, stripFirstAndLast b)
-
+  case splitTextBy (== '=') s of
+    [a,b]
+      | T.length b >= 2
+      , "\"" `T.isPrefixOf` b
+      , "\"" `T.isSuffixOf` b
+      -> Just (a, stripFirstAndLast b)
+    _ -> Nothing
 
 displayMath :: PandocMonad m => VwParser m Blocks
 displayMath = try $ do
@@ -291,8 +290,8 @@
 
 mixedList :: PandocMonad m => VwParser m Blocks
 mixedList = try $ do
-  (bl, _) <- mixedList' (-1)
-  return $ head bl
+  ((bl:_), _) <- mixedList' (-1)
+  return bl
 
 mixedList' :: PandocMonad m => Int -> VwParser m ([Blocks], Int)
 mixedList' prevInd = do
@@ -407,8 +406,8 @@
 -- headerless table
 table2 :: PandocMonad m => VwParser m ([Blocks], [[Blocks]])
 table2 = try $ do
-  trs <- many1 tableRow
-  return (replicate (length $ head trs) mempty, trs)
+  trs@(firstrow:_) <- many1 tableRow
+  return (replicate (length firstrow) mempty, trs)
 
 tableHeaderSeparator :: PandocMonad m => VwParser m ()
 tableHeaderSeparator = try $ do
@@ -507,26 +506,27 @@
 
 strong :: PandocMonad m => VwParser m Inlines
 strong = try $ do
-  s <- lookAhead $ char '*' *> many1 (noneOf "*") <* char '*'
-  guard $ (head s `notElem` spaceChars)
-             && (last s `notElem` spaceChars)
   char '*'
-  contents <- mconcat <$>manyTill inline' (char '*'
-    >> notFollowedBy alphaNum)
-  return $ B.spanWith (makeId contents, [], []) mempty
-    <> B.strong contents
+  notFollowedBy (oneOf spaceChars)
+  contents <- mconcat <$> many1Till inline'
+                           (try (char '*' *> notFollowedBy alphaNum))
+  guard $ case reverse (toList contents) of
+             Space:_ -> False
+             _ -> True
+  return $ B.spanWith (makeId contents, [], []) mempty <> B.strong contents
 
 makeId :: Inlines -> Text
 makeId i = T.concat (stringify <$> toList i)
 
 emph :: PandocMonad m => VwParser m Inlines
 emph = try $ do
-  s <- lookAhead $ char '_' *> many1 (noneOf "_") <* char '_'
-  guard $ (head s `notElem` spaceChars)
-          && (last s `notElem` spaceChars)
   char '_'
-  contents <- mconcat <$>manyTill inline' (char '_'
-    >> notFollowedBy alphaNum)
+  notFollowedBy (oneOf spaceChars)
+  contents <- mconcat <$> many1Till inline'
+                           (try (char '_' *> notFollowedBy alphaNum))
+  guard $ case reverse (toList contents) of
+             Space:_ -> False
+             _ -> True
   return $ B.emph contents
 
 strikeout :: PandocMonad m => VwParser m Inlines
@@ -601,8 +601,8 @@
 procLink' s
   | T.take 6 s == "local:" = "file" <> T.drop 5 s
   | T.take 6 s == "diary:" = "diary/" <> T.drop 6 s
-  | or ((`T.isPrefixOf` s) <$> [ "http:", "https:", "ftp:", "file:", "mailto:",
-                              "news:", "telnet:" ])
+  | any (`T.isPrefixOf` s) [ "http:", "https:", "ftp:", "file:", "mailto:",
+                             "news:", "telnet:" ]
                              = s
   | s == ""                  = ""
   | T.last s == '/'          = s
@@ -624,8 +624,9 @@
   char ':'
   s <- manyTillChar (noneOf spaceChars) (try (char ':' >> lookAhead space))
   guard $ not $ "::" `T.isInfixOf` (":" <> s <> ":")
-  let ss = splitTextBy (==':') s
-  return $ mconcat $ makeTagSpan' (head ss):(makeTagSpan <$> tail ss)
+  case splitTextBy (==':') s of
+    [] -> fail "tag doesn't contain :"
+    (x:xs) -> return $ mconcat $ makeTagSpan' x : (makeTagSpan <$> xs)
 
 todoMark :: PandocMonad m => VwParser m Inlines
 todoMark = try $ do
diff --git a/src/Text/Pandoc/Shared.hs b/src/Text/Pandoc/Shared.hs
--- a/src/Text/Pandoc/Shared.hs
+++ b/src/Text/Pandoc/Shared.hs
@@ -50,12 +50,12 @@
                      linesToPara,
                      figureDiv,
                      makeSections,
+                     makeSectionsWithOffsets,
                      combineAttr,
                      uniqueIdent,
                      inlineListToIdentifier,
                      textToIdentifier,
                      isHeaderBlock,
-                     stripEmptyParagraphs,
                      onlySimpleTableCells,
                      isTightList,
                      taskListItemFromAscii,
@@ -512,12 +512,20 @@
 -- adjusted so that the lowest header level is n.
 -- (There may still be gaps in header level if the author leaves them.)
 makeSections :: Bool -> Maybe Int -> [Block] -> [Block]
-makeSections numbering mbBaseLevel bs =
-  S.evalState (go bs) []
+makeSections = makeSectionsWithOffsets []
+
+-- | Like 'makeSections', but with a parameter for number offsets
+-- (a list of 'Int's, the first of which is added to the level 1
+-- section number, the second to the level 2, and so on).
+makeSectionsWithOffsets :: [Int] -> Bool -> Maybe Int -> [Block] -> [Block]
+makeSectionsWithOffsets numoffsets numbering mbBaseLevel bs =
+  S.evalState (go bs) numoffsets
  where
   getLevel (Header level _ _) = Min level
   getLevel _ = Min 99
-  minLevel = getMin $ query getLevel bs
+  minLevel = if null numoffsets || all (== 0) numoffsets
+                then getMin $ query getLevel bs
+                else 1 -- see #5071, for backwards compatibility
   go :: [Block] -> S.State [Int] [Block]
   go (Header level (ident,classes,kvs) title':xs) = do
     lastnum <- S.get
@@ -598,14 +606,6 @@
 isHeaderBlock :: Block -> Bool
 isHeaderBlock Header{} = True
 isHeaderBlock _        = False
-
--- | Remove empty paragraphs.
-stripEmptyParagraphs :: Pandoc -> Pandoc
-stripEmptyParagraphs = walk go
-  where go :: [Block] -> [Block]
-        go = filter (not . isEmptyParagraph)
-        isEmptyParagraph (Para []) = True
-        isEmptyParagraph _         = False
 
 -- | Detect if table rows contain only cells consisting of a single
 -- paragraph that has no @LineBreak@.
diff --git a/src/Text/Pandoc/URI.hs b/src/Text/Pandoc/URI.hs
--- a/src/Text/Pandoc/URI.hs
+++ b/src/Text/Pandoc/URI.hs
@@ -84,7 +84,7 @@
   , "xmlrpc.beep", "xmlrpc.beeps", "xmpp", "xri", "ymsgr", "z39.50", "z39.50r"
   , "z39.50s"
   -- Unofficial schemes
-  , "doi", "isbn", "javascript", "pmid"
+  , "doi", "gemini", "isbn", "javascript", "pmid"
   ]
 
 -- | Check if the string is a valid URL with a IANA or frequently used but
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
@@ -19,6 +19,7 @@
     -- * Writers: converting /from/ Pandoc format
       Writer(..)
     , writers
+    , writeANSI
     , writeAsciiDoc
     , writeAsciiDocLegacy
     , writeAsciiDoctor
@@ -90,6 +91,7 @@
 import Text.Pandoc.Options
 import qualified Text.Pandoc.UTF8 as UTF8
 import Text.Pandoc.Error
+import Text.Pandoc.Writers.ANSI
 import Text.Pandoc.Writers.AsciiDoc
 import Text.Pandoc.Writers.BibTeX
 import Text.Pandoc.Writers.ChunkedHTML
@@ -200,6 +202,7 @@
   ,("markua"       , TextWriter writeMarkua)
   ,("chunkedhtml"  , ByteStringWriter writeChunkedHTML)
   ,("djot"         , TextWriter writeDjot)
+  ,("ansi"         , TextWriter writeANSI)
   ]
 
 -- | Retrieve writer, extensions based on formatSpec (format+extensions).
diff --git a/src/Text/Pandoc/Writers/ANSI.hs b/src/Text/Pandoc/Writers/ANSI.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Pandoc/Writers/ANSI.hs
@@ -0,0 +1,354 @@
+{-# LANGUAGE OverloadedStrings #-}
+{- |
+   Module      : Text.Pandoc.Writers.ANSI
+   Copyright   : Copyright (C) 2024 Evan Silberman
+   License     : GNU GPL, version 2 or above
+
+   Maintainer  : John MacFarlane <jgm@berkeley.edu>
+   Stability   : alpha
+   Portability : portable
+
+Conversion of 'Pandoc' documents to Ansi terminal output.
+-}
+module Text.Pandoc.Writers.ANSI ( writeANSI ) where
+import Control.Monad.State.Strict ( StateT, gets, modify, evalStateT )
+import Control.Monad (foldM)
+import Data.List (intersperse)
+import Data.Maybe (fromMaybe)
+import Data.Text (Text)
+import Text.DocLayout ((<+>), ($$), ($+$))
+import Text.DocTemplates (Context(..))
+import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
+import Text.Pandoc.Definition
+import Text.Pandoc.Highlighting (highlight, formatANSI)
+import Text.Pandoc.Logging
+import Text.Pandoc.Options
+import Text.Pandoc.Shared
+import Text.Pandoc.Templates (renderTemplate)
+import Text.Pandoc.Writers.Math(texMathToInlines)
+import Text.Pandoc.Writers.Shared
+import qualified Data.Text as T
+import Data.Text.Lazy (toStrict)
+import qualified Text.DocLayout as D
+
+hr :: D.HasChars a => D.Doc a
+hr = rule 20
+
+rule :: D.HasChars a => Int -> D.Doc a
+rule n = D.literal $ D.replicateChar n '─'
+
+data WriterState = WriterState {
+    stNotes     :: [D.Doc Text]        -- Footnotes
+  , stColumns   :: Int         -- Width of the rendered text block
+  , stInner     :: Bool    -- Are we at the document's top-level or in a nested construct?
+  , stNextFigureNum :: Int
+  , stInFigure :: Bool
+  , stInTable :: Bool
+  }
+
+type TW = StateT WriterState
+
+withFewerColumns :: PandocMonad m => Int -> TW m a -> TW m a
+withFewerColumns n a = do
+  cols <- gets stColumns
+  inner <- gets stInner
+  modify $ \s -> s{stColumns = max (cols - n) 4, stInner = True}
+  result <- a
+  modify $ \s -> s{stColumns = cols, stInner = inner}
+  return result
+
+-- | Convert Pandoc to ANSI
+writeANSI :: PandocMonad m => WriterOptions -> Pandoc -> m Text
+writeANSI opts document =
+  evalStateT (pandocToANSI opts document)
+            WriterState { stNotes = [],
+                          stColumns = (writerColumns opts),
+                          stInner = False,
+                          stNextFigureNum = 1,
+                          stInFigure = False,
+                          stInTable = False
+                        }
+
+-- | Return ANSI-styled verison of document
+pandocToANSI :: PandocMonad m
+                => WriterOptions -> Pandoc -> TW m Text
+pandocToANSI opts (Pandoc meta blocks) = do
+  metadata <- metaToContext opts
+                 (blockListToANSI opts)
+                 (inlineListToANSI opts) meta
+  width <- gets stColumns
+  let title = titleBlock width metadata
+  let blocks' = makeSections (writerNumberSections opts) Nothing blocks
+  body <- blockListToANSI opts blocks'
+  notes <- gets $ reverse . stNotes
+  let notemark x = D.literal (tshow (x :: Int) <> ".") <+> D.space
+  let marks = take (length notes) $ map notemark [1..]
+  let hangWidth = foldr (max . D.offset) 0 marks
+  let notepretty | not (null notes) = D.cblock width hr $+$ hangMarks hangWidth marks notes
+                 | otherwise = D.empty
+  let main = body $+$ notepretty
+  let context = defField "body" main
+              $ defField "titleblock" title metadata
+  return $
+    case writerTemplate opts of
+         Nothing  -> toStrict $ D.renderANSI (Just width) main
+         Just tpl -> toStrict $ D.renderANSI (Just width) $ renderTemplate tpl context
+
+titleBlock :: Int -> Context Text -> D.Doc Text
+titleBlock width meta = if null most then D.empty else D.cblock width $ most $+$ hr
+  where
+    title = D.bold (fromMaybe D.empty $ getField "title" meta)
+    subtitle = fromMaybe D.empty $ getField "subtitle" meta
+    author =  D.vcat $ fromMaybe [] $ getField "author" meta
+    date = D.italic (fromMaybe D.empty $ getField "date" meta)
+    most = (title $$ subtitle) $+$ author $+$ date
+
+hangMarks :: Int -> [D.Doc Text] -> [D.Doc Text] -> D.Doc Text
+hangMarks width markers contents =
+  D.vsep (zipWith hangMark markers contents) where
+    hangMark m d = D.rblock width m <+> D.nest (width + 1) d
+
+stackMarks :: [D.Doc Text] -> [D.Doc Text] -> D.Doc Text
+stackMarks markers contents = D.vsep (zipWith stack markers contents)
+  where stack m d = m $$ D.nest 4 d
+
+-- | Convert Pandoc block element to ANSI
+blockToANSI :: PandocMonad m
+               => WriterOptions -- ^ Options
+               -> Block         -- ^ Block element
+               -> TW m (D.Doc Text)
+
+blockToANSI opts (Div _ bs) = blockListToANSI opts bs
+
+blockToANSI opts (Plain inlines) = inlineListToANSI opts inlines
+
+blockToANSI opts (Para inlines) = inlineListToANSI opts inlines
+
+blockToANSI opts (LineBlock lns) = blockToANSI opts $ linesToPara lns
+
+blockToANSI _ b@(RawBlock _ _) = do
+    report $ BlockNotRendered b
+    return D.empty
+
+blockToANSI _ HorizontalRule = return $ D.blankline $$ hr $$ D.blankline
+
+blockToANSI opts (Header level (_, classes, kvs) inlines) = do
+  contents <- inlineListToANSI opts inlines
+  let secnum = fromMaybe mempty $ lookup "number" kvs
+  let doNumber = writerNumberSections opts && not (T.null secnum) && "unnumbered" `notElem` classes
+  let number | doNumber = D.hang (D.realLength secnum + 1) (header level (D.literal secnum) <> D.space)
+             | otherwise = id
+  return $ number (header level contents) $$ D.blankline where
+    header 1 = (fmap T.toUpper) . D.bold
+    header 2 = D.bold
+    header _ = D.italic
+
+-- The approach to code blocks and highlighting here is a best-effort with
+-- existing tools. The Skylighting formatANSI function produces fully-rendered
+-- results, and its line numbers are followed by a tab character, which can
+-- produce less-than-ideal results depending on your terminal's tab stops. (See
+-- tabs(1)). A more ambitious approach here could process SourceLines into a
+-- Doc Text.
+blockToANSI opts (CodeBlock attr str) = do
+  table <- gets stInTable
+  inner <- case (table, writerHighlightStyle opts) of
+    (_, Nothing) -> return $ defaultStyle str
+    (True, _) -> return $ defaultStyle str
+    (False, Just s) -> do
+      let fmt o = formatANSI o s
+          result = highlight (writerSyntaxMap opts) fmt attr str
+      return $ case result of
+        Left _ -> defaultStyle str
+        Right f -> D.literal f
+  return $ nest table inner
+  where defaultStyle = (D.fg D.red) . D.literal
+        nest False = D.nest 4
+        nest True = id
+
+blockToANSI opts (BlockQuote blocks) = do
+  contents <- withFewerColumns 2 $ blockListToANSI opts blocks
+  return ( D.prefixed "│ " contents $$ D.blankline)
+
+-- TODO: Row spans don't work
+blockToANSI opts (Table _ (Caption _ caption) colSpecs (TableHead _ thead) tbody (TableFoot _ tfoot)) = do
+  let captionInlines = blocksToInlines caption
+  captionMarkup <-
+    if null captionInlines
+       then return mempty
+       else D.nest 2 <$> inlineListToANSI opts (blocksToInlines caption)
+  wasTable <- gets stInTable
+  modify $ \s -> s{stInTable = True}
+  let tw = writerColumns opts
+  let ncol = length colSpecs
+  let inWidths = map snd colSpecs
+  let spaceForColumns = tw - ncol + 1  -- reserve a 1-char gutter between tcols
+  let claimWidth ColWidthDefault = 0
+      claimWidth (ColWidth n) = floor (n * fromIntegral spaceForColumns)
+  let usedSpace = sum (map claimWidth inWidths)
+  let remaining = spaceForColumns - usedSpace
+  let defWidth = remaining `div` length (filter (== ColWidthDefault) inWidths)
+  let maxWidth ColWidthDefault = defWidth
+      maxWidth k = claimWidth k
+  let widths = map maxWidth inWidths
+  let decor = [D.hsep $ map rule widths]
+  head' <- mapM (goRow widths . unRow) thead
+  body' <- mapM (goRow widths . unRow) (unBodies tbody)
+  foot' <- mapM (goRow widths . unRow) tfoot
+  modify $ \s -> s{stInTable = wasTable}
+  return $ D.vcat (head' <> decor <> body' <> decor <> foot') $+$ captionMarkup
+  where
+    unRow (Row _ cs) = cs
+    unBody (TableBody _ _ hd bd) = hd <> bd
+    unBodies = concatMap unBody
+    goRow ws cs = do
+      (d, _) <- foldM goCell ([], ws) cs
+      return $ D.hcat $ intersperse (D.vfill " ") $ reverse d
+    goCell (r, ws) (Cell _ aln _ (ColSpan cspan) inner) = do
+      let (ws', render) = next ws aln cspan
+      innerDoc <- blockListToANSI opts inner
+      return ((render innerDoc):r, ws')
+    tcell AlignLeft    = D.lblock
+    tcell AlignRight   = D.rblock
+    tcell AlignCenter  = D.cblock
+    tcell AlignDefault = D.lblock
+    next ws aln cspan =
+      let (this, ws') = splitAt cspan ws
+          w = sum this + cspan - 1
+          cell = (tcell aln) w
+       in (ws', cell)
+
+blockToANSI opts (BulletList items) = do
+  contents <- withFewerColumns 2 $ mapM (blockListToANSI opts) items
+  return $ D.vsep (fmap hangMark contents) where
+    hangMark d = D.hang 2 (D.literal "• ") d
+
+blockToANSI opts (OrderedList attribs items) = do
+  let markers = fmap D.literal $ take (length items) $ orderedListMarkers attribs
+  let hangWidth = foldr (max . D.offset) 0 markers
+  contents <- withFewerColumns hangWidth $ mapM (blockListToANSI opts) items
+  return $ hangMarks hangWidth markers contents <> D.cr
+
+blockToANSI opts (DefinitionList items) = do
+  labels <- mapM (inlineListToANSI opts . fst) items
+  columns <- gets stColumns
+  let hangWidth = foldr (max . D.offset) 0 labels
+  if hangWidth > floor (toRational columns / 10 * 3)
+     then do
+       contents <- withFewerColumns 4 $ mapM ((mapM (blockListToANSI opts)) . snd) items
+       return $ stackMarks (D.bold <$> labels) (D.vsep <$> contents) <> D.cr
+     else do
+       contents <- withFewerColumns hangWidth $ mapM ((mapM (blockListToANSI opts)) . snd) items
+       return $ hangMarks hangWidth (D.bold <$> labels) (D.vsep <$> contents) <> D.cr
+
+blockToANSI opts (Figure _ (Caption _ caption)  body) = do
+  let captionInlines = blocksToInlines caption
+  figState <- gets stInFigure
+  captionMarkup <-
+    if null captionInlines
+       then return mempty
+       else D.nest 2 <$> inlineListToANSI opts (blocksToInlines caption)
+  modify $ \s -> s{stInFigure = True}
+  contents <- blockListToANSI opts body
+  modify $ \s -> s{stInFigure = figState}
+  return $ contents $+$ captionMarkup
+
+-- Auxiliary functions for lists:
+
+-- | Convert list of Pandoc block elements to ANSI
+blockListToANSI :: PandocMonad m
+                   => WriterOptions -- ^ Options
+                   -> [Block]       -- ^ List of block elements
+                   -> TW m (D.Doc Text)
+blockListToANSI opts blocks =
+  D.vsep <$> mapM (blockToANSI opts) blocks
+
+-- | Convert list of Pandoc inline elements to ANSI
+inlineListToANSI :: PandocMonad m
+                    => WriterOptions -> [Inline] -> TW m (D.Doc Text)
+inlineListToANSI opts lst =
+  D.hcat <$> mapM (inlineToANSI opts) lst
+
+-- | Convert Pandoc inline element to ANSI
+inlineToANSI :: PandocMonad m => WriterOptions -> Inline -> TW m (D.Doc Text)
+
+inlineToANSI opts (Span _ lst) =
+  inlineListToANSI opts lst
+
+inlineToANSI opts (Emph lst) = do
+  contents <- inlineListToANSI opts lst
+  return $ D.italic contents
+
+inlineToANSI opts (Underline lst) = do
+  contents <- inlineListToANSI opts lst
+  return $ D.underlined contents
+
+inlineToANSI opts (Strong lst) = do
+  contents <- inlineListToANSI opts lst
+  return $ D.bold contents
+
+inlineToANSI opts (Strikeout lst) = do
+  contents <- inlineListToANSI opts lst
+  return $ D.strikeout contents
+
+inlineToANSI opts (Superscript lst) = do
+  case traverse toSuperscriptInline lst of
+    Just xs -> inlineListToANSI opts xs
+    Nothing -> inlineListToANSI opts lst >>= return . D.parens
+
+inlineToANSI opts (Subscript lst) = do
+  case traverse toSuperscriptInline lst of
+    Just xs -> inlineListToANSI opts xs
+    Nothing -> inlineListToANSI opts lst >>= return . D.parens
+
+inlineToANSI opts (SmallCaps lst) = inlineListToANSI opts lst
+
+inlineToANSI opts (Quoted SingleQuote lst) = do
+  contents <- inlineListToANSI opts lst
+  return $ "‘" <> contents <> "’"
+
+inlineToANSI opts (Quoted DoubleQuote lst) = do
+  contents <- inlineListToANSI opts lst
+  return $ "“" <> contents <> "”"
+
+inlineToANSI opts (Cite _  lst) = inlineListToANSI opts lst
+
+-- Making a judgment call here that for ANSI-formatted output
+-- intended for reading, we want to reflow inline Code on spaces
+inlineToANSI _ (Code _ str) =
+  return $ D.bg D.white $ D.fg D.red $ " " <> D.hcat flow <> " "
+    where flow = intersperse D.space (D.literal <$> T.words str)
+
+inlineToANSI _ (Str str) = return $ D.literal str
+
+inlineToANSI opts (Math t str) = texMathToInlines t str >>= inlineListToANSI opts
+
+inlineToANSI _ il@RawInline{} = do
+  report $ InlineNotRendered il
+  return ""
+
+inlineToANSI _ LineBreak = return D.cr
+
+inlineToANSI _ SoftBreak = return D.space
+
+inlineToANSI _ Space = return D.space
+
+inlineToANSI opts (Link (_, _, _) txt (src, _)) = do
+  label <- inlineListToANSI opts txt
+  return $ D.underlined $ D.fg D.cyan $ D.link src label
+
+inlineToANSI opts (Image _ alt _) = do
+  infig <- gets stInFigure
+  if not infig then do
+    alt' <- inlineListToANSI opts alt
+    return $ D.brackets $ "image: " <> alt'
+  else return $ D.brackets "image"
+
+-- by construction, we should never be lacking in superscript characters
+-- for the footnote number, but we'll fall back to square brackets anyway
+inlineToANSI opts (Note contents) = do
+  curNotes <- gets stNotes
+  let newnum = tshow $ length curNotes + 1
+  contents' <- blockListToANSI opts contents
+  modify $ \s -> s { stNotes = contents' : curNotes }
+  let super = T.pack <$> (traverse toSuperscript (T.unpack newnum))
+  return $ D.literal $ fromMaybe ("[" <> newnum <> "]") super
diff --git a/src/Text/Pandoc/Writers/AsciiDoc.hs b/src/Text/Pandoc/Writers/AsciiDoc.hs
--- a/src/Text/Pandoc/Writers/AsciiDoc.hs
+++ b/src/Text/Pandoc/Writers/AsciiDoc.hs
@@ -34,6 +34,7 @@
 import qualified Data.Set as Set
 import qualified Data.Text as T
 import Data.Text (Text)
+import Network.URI (parseURI, URI(uriScheme))
 import System.FilePath (dropExtension)
 import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
 import Text.Pandoc.Definition
@@ -104,7 +105,7 @@
               (blockListToAsciiDoc opts)
               (fmap chomp . inlineListToAsciiDoc opts)
               meta
-  main <- blockListToAsciiDoc opts $ makeSections False (Just 1) blocks
+  main <- blockListToAsciiDoc opts $ makeSections False Nothing blocks
   st <- get
   let context  = defField "body" main
                $ defField "toc"
@@ -204,7 +205,10 @@
 blockToAsciiDoc opts (Figure attr (Caption _ longcapt) body) = do
   -- Images in figures all get rendered as individual block-level images
   -- with the given caption. Non-image elements are rendered unchanged.
-  capt <- inlineListToAsciiDoc opts (blocksToInlines longcapt)
+  capt <- if null longcapt
+             then pure mempty
+             else ("." <>) . nowrap <$>
+                   inlineListToAsciiDoc opts (blocksToInlines longcapt)
   let renderFigElement = \case
         Plain [Image imgAttr alternate (src, tit)] -> do
           args <- imageArguments opts imgAttr alternate src tit
@@ -213,7 +217,8 @@
                 (ident, _, _) -> literal $ "[#" <> ident <> "]"
           -- .Figure caption
           -- image::images/logo.png[Company logo, title="blah"]
-          return $ "." <> nowrap capt $$
+          return $
+            capt $$
             figAttributes $$
             "image::" <> args <> blankline
         blk -> blockToAsciiDoc opts blk
@@ -599,9 +604,13 @@
       fixCommas x = [x]
 
   linktext <- inlineListToAsciiDoc opts $ walk (concatMap fixCommas) txt
-  let isRelative = T.all (/= ':') src
+  let needsLinkPrefix = case parseURI (T.unpack src) of
+                          Just u -> uriScheme u `notElem` ["http:","https:",
+                                                           "ftp:", "irc:",
+                                                            "mailto:"]
+                          _ -> True
   let needsPassthrough = "--" `T.isInfixOf` src
-  let prefix = if isRelative
+  let prefix = if needsLinkPrefix
                   then text "link:"
                   else empty
   let srcSuffix = fromMaybe src (T.stripPrefix "mailto:" src)
diff --git a/src/Text/Pandoc/Writers/ConTeXt.hs b/src/Text/Pandoc/Writers/ConTeXt.hs
--- a/src/Text/Pandoc/Writers/ConTeXt.hs
+++ b/src/Text/Pandoc/Writers/ConTeXt.hs
@@ -696,9 +696,9 @@
       dims = if null dimList
                 then empty
                 else brackets $ mconcat (intersperse "," dimList)
-      clas = if null cls
-                then empty
-                else brackets $ literal $ toLabel $ head cls
+      clas = case cls of
+               [] -> empty
+               (cl:_) -> brackets $ literal $ toLabel cl
       -- Use / for path separators on Windows; see #4918
       fixPathSeparators = T.map $ \c -> case c of
                                           '\\' -> '/'
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
@@ -94,7 +94,8 @@
 writeDocx opts doc = do
   let Pandoc meta blocks = walk fixDisplayMath doc
   setupTranslations meta
-  let blocks' = makeSections True Nothing blocks
+  let blocks' = makeSectionsWithOffsets (writerNumberOffset opts)
+                   True Nothing blocks
   let doc' = Pandoc meta blocks'
 
   username <- P.lookupEnv "USERNAME"
diff --git a/src/Text/Pandoc/Writers/Docx/OpenXML.hs b/src/Text/Pandoc/Writers/Docx/OpenXML.hs
--- a/src/Text/Pandoc/Writers/Docx/OpenXML.hs
+++ b/src/Text/Pandoc/Writers/Docx/OpenXML.hs
@@ -455,7 +455,10 @@
     (Para xs  : bs) -> imageCaption (fstCaptionPara xs : bs)
     (Plain xs : bs) -> imageCaption (fstCaptionPara xs : bs)
     _               -> imageCaption longcapt
-  wrapBookmark ident $ contentsNode : captionNode
+  wrapBookmark ident $
+    case writerFigureCaptionPosition opts of
+      CaptionBelow -> contentsNode : captionNode
+      CaptionAbove -> captionNode ++ [contentsNode]
 
 toFigureTable :: PandocMonad m
               => WriterOptions -> [Block] -> WS m Content
diff --git a/src/Text/Pandoc/Writers/Docx/Table.hs b/src/Text/Pandoc/Writers/Docx/Table.hs
--- a/src/Text/Pandoc/Writers/Docx/Table.hs
+++ b/src/Text/Pandoc/Writers/Docx/Table.hs
@@ -22,7 +22,7 @@
 import Control.Monad.Except ( throwError )
 import Data.Array ( elems, (!), assocs, indices )
 import Data.Text (Text)
-import Data.Maybe (catMaybes)
+import Data.Maybe (catMaybes, fromMaybe)
 import Text.Pandoc.Definition
     ( ColSpec,
       Caption(Caption),
@@ -46,7 +46,7 @@
       withParaPropM )
 import Control.Monad.Reader (asks)
 import Text.Pandoc.Shared ( tshow, stringify )
-import Text.Pandoc.Options (WriterOptions, isEnabled)
+import Text.Pandoc.Options (WriterOptions(..), isEnabled, CaptionPosition(..))
 import Text.Pandoc.Extensions (Extension(Ext_native_numbering))
 import Text.Pandoc.Error (PandocError(PandocSomeError))
 import Text.Printf (printf)
@@ -71,7 +71,7 @@
                -> WS m [Content]
 tableToOpenXML opts blocksToOpenXML gridTable = do
   setFirstPara
-  let (Grid.Table (ident,_,_) caption colspecs _rowheads thead tbodies tfoot) =
+  let (Grid.Table (ident,_,tableAttr) caption colspecs _rowheads thead tbodies tfoot) =
         gridTable
   let (Caption _maybeShortCaption captionBlocks) = caption
   tablenum <- gets stNextTableNum
@@ -109,11 +109,12 @@
   let tblLookVal = if hasHeader then (0x20 :: Int) else 0
   let (gridCols, tblWattr) = tableLayout (elems colspecs)
   listLevel <- asks envListLevel
+  let tblStyle =  fromMaybe "Table" (lookup "custom-style" tableAttr)
   let indent = (listLevel + 1) * 720
   let hasWidths = not $ all ((== ColWidthDefault) . snd) colspecs
   let tbl = mknode "w:tbl" []
         ( mknode "w:tblPr" []
-          ( [ mknode "w:tblStyle" [("w:val","Table")] (),
+          ( [ mknode "w:tblStyle" [("w:val",tblStyle)] (),
               mknode "w:tblW" tblWattr () ] ++
             [ mknode "w:jc" [("w:val","left")] () | indent > 0 ] ++
             [ mknode "w:tblInd" [("w:w", tshow indent),("w:type","dxa")] ()
@@ -134,7 +135,10 @@
           : head' ++ mconcat bodies ++ foot'
         )
   modify $ \s -> s { stInTable = False }
-  return $ captionXml ++ [Elem tbl]
+  return $
+    case writerTableCaptionPosition opts of
+      CaptionAbove -> captionXml ++ [Elem tbl]
+      CaptionBelow -> Elem tbl : captionXml
 
 addLabel :: Text -> Text -> Int -> [Block] -> [Block]
 addLabel tableid tablename tablenum bs =
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
@@ -289,8 +289,8 @@
                           lookupMetaString "description" meta
   slideVariant <- gets stSlideVariant
   abstractTitle <- translateTerm Abstract
-  let sects = adjustNumbers opts $
-              makeSections (writerNumberSections opts) Nothing $
+  let sects = makeSectionsWithOffsets
+                (writerNumberOffset opts) (writerNumberSections opts) Nothing $
               if slideVariant == NoSlides
                  then blocks
                  else prepSlides slideLevel blocks
@@ -446,6 +446,8 @@
                   defField "slideous-url" ("slideous" :: Doc Text) .
                   defField "revealjs-url" ("https://unpkg.com/reveal.js@^4/" :: Doc Text) $
                   defField "s5-url" ("s5/default" :: Doc Text) .
+                  defField "table-caption-below"
+                     (writerTableCaptionPosition opts == CaptionBelow) .
                   defField "html5" (stHtml5 st) $
                   metadata
   return (thebody, context)
@@ -678,7 +680,8 @@
   addAttr html5 mbEpubVersion x y
     | T.null x = id  -- see #7546
     | html5
-      = if x `Set.member` (html5Attributes <> rdfaAttributes)
+      = if (x `Set.member` (html5Attributes <> rdfaAttributes)
+            && x /= "label") -- #10048
              || T.any (== ':') x -- e.g. epub: namespace
              || "data-" `T.isPrefixOf` x
              || "aria-" `T.isPrefixOf` x
@@ -726,30 +729,6 @@
                (Just x)         -> [("style", tshow dir <> ":" <> tshow x)]
                Nothing          -> []
 
-adjustNumbers :: WriterOptions -> [Block] -> [Block]
-adjustNumbers opts doc =
-  if all (==0) (writerNumberOffset opts)
-     then doc
-     else walk go doc
-  where
-   go (Div (ident,"section":classes,kvs) lst@(Header level _ _ : _)) =
-     Div (ident,"section":classes,map (fixnum level) kvs) lst
-   go (Header level (ident,classes,kvs) lst) =
-     Header level (ident,classes,map (fixnum level) kvs) lst
-   go x = x
-   fixnum level ("number",num) = ("number",
-                               showSecNum $ zipWith (+)
-                               (writerNumberOffset opts ++ repeat 0)
-                               (padTo level $
-                                map (fromMaybe 0 . safeRead) $
-                                T.split (=='.') num))
-   fixnum _ x = x
-   padTo n xs =
-     case n - length xs of
-       x | x > 0 -> replicate x 0 ++ xs
-         | otherwise -> xs
-   showSecNum = T.intercalate "." . map tshow
-
 blockToHtmlInner :: PandocMonad m => WriterOptions -> Block -> StateT WriterState m Html
 blockToHtmlInner opts (Plain lst) = inlineListToHtml opts lst
 blockToHtmlInner opts (Para lst) = do
@@ -1055,24 +1034,26 @@
 
   figAttrs <- attrsToHtml opts attrs
   contents <- blockListToHtml opts body
-  figCaption <- if null captBody
-                then return mempty
-                else do
-                  captCont <- blockListToHtml opts captBody
-                  return . mconcat $
+  captCont <- blockListToHtml opts captBody
+  let figCaption = mconcat $
                     if html5
                     then let fcattr = if captionIsAlt captBody body
                                       then H5.customAttribute
                                            (textTag "aria-hidden")
                                            (toValue @Text "true")
                                       else mempty
-                         in [ H5.figcaption ! fcattr $ captCont, nl ]
-                    else [ (H.div ! A.class_ "figcaption") captCont, nl ]
+                         in [ H5.figcaption ! fcattr $ captCont ]
+                    else [ (H.div ! A.class_ "figcaption") captCont ]
+  let innards = mconcat $
+                if null captBody
+                   then [nl, contents, nl]
+                   else case writerFigureCaptionPosition opts of
+                         CaptionAbove -> [nl, figCaption, nl, contents, nl]
+                         CaptionBelow -> [nl, contents, nl, figCaption, nl]
   return $
     if html5
-    then foldl (!) H5.figure figAttrs $ mconcat [nl, contents, nl, figCaption]
-    else foldl (!) H.div (A.class_ "float" : figAttrs) $ mconcat
-           [nl, contents, nl, figCaption]
+    then foldl (!) H5.figure figAttrs innards
+    else foldl (!) H.div (A.class_ "float" : figAttrs) innards
  where
   captionIsAlt capt [Plain [Image (_, _, kv) desc _]] =
     let alt = fromMaybe (stringify desc) $ lookup "alt" kv
@@ -1620,7 +1601,7 @@
                                       _          -> link
     (Cite cits il)-> do contents <- inlineListToHtml opts
                                       (if html5
-                                          then walk addRoleToLink il
+                                          then walk addBibliorefRole il
                                           else il)
                         let citationIds = T.unwords $ map citationId cits
                         let result = H.span ! A.class_ "citation" $ contents
@@ -1628,10 +1609,11 @@
                                     then result ! customAttribute "data-cites" (toValue citationIds)
                                     else result
 
-addRoleToLink :: Inline -> Inline
-addRoleToLink (Link (id',classes,kvs) ils (src,tit)) =
+addBibliorefRole :: Inline -> Inline
+addBibliorefRole (Link (id',classes,kvs) ils (src,tit))
+   | "#ref-" `T.isPrefixOf` src =
   Link (id',classes,("role","doc-biblioref"):kvs) ils (src,tit)
-addRoleToLink x = x
+addBibliorefRole x = x
 
 blockListToNote :: PandocMonad m
                 => WriterOptions -> Text -> [Block]
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
@@ -30,7 +30,7 @@
 import Text.Pandoc.ImageSize
 import Text.Pandoc.Logging
 import Text.Pandoc.Options
-import Text.DocLayout
+import Text.DocLayout hiding (link)
 import Text.Pandoc.Shared
 import Text.Pandoc.URI (isURI)
 import Text.Pandoc.Templates (renderTemplate)
@@ -345,9 +345,9 @@
                      then "0"
                      else "1"
       nrRows = length rows
-      nrCols = if null rows
-                  then 0
-                  else length $ head rows
+      nrCols = case rows of
+                   [] -> 0
+                   (r:_) -> length r
       rowsToICML [] _ = return empty
       rowsToICML (col:rest) rowNr =
         liftM2 ($$) (colsToICML col aligns rowNr (0::Int)) $ rowsToICML rest (rowNr+1)
@@ -430,14 +430,15 @@
                then firstListItemName:style
                else style
       stl' = makeNumbStart attribs ++ stl
-  in  if length item > 1
-         then do
-           let insertTab (Para lst) = blockToICML opts (subListParName:style) $ Para $ Str "\t":lst
-               insertTab block      = blockToICML opts style block
-           f <- blockToICML opts stl' $ head item
-           r <- mapM insertTab $ tail item
-           return $ intersperseBrs (f : r)
-         else blocksToICML opts stl' item
+  in  case item of
+          (i:rest@(_:_)) -> do
+             let insertTab (Para lst) = blockToICML opts (subListParName:style)
+                                         (Para (Str "\t":lst))
+                 insertTab block      = blockToICML opts style block
+             f <- blockToICML opts stl' i
+             r <- mapM insertTab rest
+             return $ intersperseBrs (f : r)
+          _ -> blocksToICML opts stl' item
 
 definitionListItemToICML :: PandocMonad m => WriterOptions -> Style -> ([Inline],[[Block]]) -> WS m (Doc Text)
 definitionListItemToICML opts style (term,defs) = do
@@ -484,9 +485,9 @@
 inlineToICML opts style ident (Link _ lst (url, title)) = do
   content <- inlinesToICML opts (linkName:style) ident lst
   state $ \st ->
-            let link_id = if null $ links st
-                            then 1::Int
-                            else 1 + fst (head $ links st)
+            let link_id = case links st of
+                              [] -> 1 :: Int
+                              ((n,_):_) -> 1 + n
                 newst = st{ links = (link_id, url):links st }
                 cont  = inTags True "HyperlinkTextSource"
                          [("Self","htss-"<>tshow link_id), ("Name",title), ("Hidden","false")] content
@@ -545,11 +546,12 @@
       attrs' =  if firstListItemName `elem` style
                    then let ats = attrs : [("NumberingContinue", "false")]
                             begins = filter (Text.isPrefixOf beginsWithName) style
-                        in  if null begins
-                               then ats
-                               else let i = fromMaybe "" $ Text.stripPrefix beginsWithName
-                                                         $ head begins
-                                    in  ("NumberingStartAt", i) : ats
+                        in  case begins of
+                                [] -> ats
+                                (b:_) ->
+                                   let i = fromMaybe ""
+                                             (Text.stripPrefix beginsWithName b)
+                                   in  ("NumberingStartAt", i) : ats
                    else [attrs]
   in  do
       content <- inlinesToICML opts [] ident lst
diff --git a/src/Text/Pandoc/Writers/LaTeX.hs b/src/Text/Pandoc/Writers/LaTeX.hs
--- a/src/Text/Pandoc/Writers/LaTeX.hs
+++ b/src/Text/Pandoc/Writers/LaTeX.hs
@@ -605,6 +605,7 @@
   tableToLaTeX inlineListToLaTeX blockListToLaTeX
                (Ann.toTable attr blkCapt specs thead tbodies tfoot)
 blockToLaTeX (Figure (ident, _, _) captnode body) = do
+  opts <- gets stOptions
   (capt, captForLof, footnotes) <- getCaption inlineListToLaTeX True captnode
   lab <- labelFor ident
   let caption = "\\caption" <> captForLof <> braces capt <> lab
@@ -615,7 +616,10 @@
     [b] -> blockToLaTeX b
     bs  -> mconcat . intersperse (cr <> "\\hfill") <$>
            mapM (toSubfigure (length bs)) bs
-  let innards = "\\centering" $$ contents $$ caption <> cr
+  let innards = "\\centering" $$
+                (case writerFigureCaptionPosition opts of
+                  CaptionBelow -> contents $$ caption
+                  CaptionAbove -> caption $$ contents) <> cr
   modify $ \st ->
     st{ stInFigure = isSubfigure
       , stSubfigure = stSubfigure st || isSubfigure
diff --git a/src/Text/Pandoc/Writers/LaTeX/Citation.hs b/src/Text/Pandoc/Writers/LaTeX/Citation.hs
--- a/src/Text/Pandoc/Writers/LaTeX/Citation.hs
+++ b/src/Text/Pandoc/Writers/LaTeX/Citation.hs
@@ -18,17 +18,19 @@
 import Control.Monad.State (gets)
 import Data.Maybe (fromMaybe)
 import qualified Data.Text as T
+import Text.Pandoc.Options
 import Text.Pandoc.Class.PandocMonad (PandocMonad)
 import Text.Pandoc.Definition
 import Data.List (foldl')
 import Text.DocLayout (Doc, brackets, empty, (<+>), text, isEmpty, literal,
                        braces)
 import Text.Pandoc.Walk
-import Text.Pandoc.Writers.LaTeX.Types ( LW, WriterState(stLang) )
+import Text.Pandoc.Writers.LaTeX.Types ( LW, WriterState(stLang, stOptions) )
 import Text.Pandoc.Citeproc.Locator (parseLocator, LocatorInfo(..),
                                      toLocatorMap)
 import Citeproc.Types (Lang(..))
 import Citeproc.Locale (getLocale)
+import Safe (headMay, lastMay)
 
 citationsToNatbib :: PandocMonad m
                   => ([Inline] -> LW m (Doc Text))
@@ -49,16 +51,17 @@
              NormalCitation -> "citep"
 
 citationsToNatbib inlineListToLaTeX cits
-  | noPrefix (tail cits) && noSuffix (init cits) && ismode NormalCitation cits
+  | noInnerPrefix cits && noInnerSuffix cits && ismode NormalCitation cits
   = citeCommand inlineListToLaTeX "citep" p s ks
   where
-     noPrefix  = all (null . citationPrefix)
-     noSuffix  = all (null . citationSuffix)
+     noInnerPrefix []     = True
+     noInnerPrefix (_:xs) = all (null . citationPrefix) xs
+     noInnerSuffix []     = True
+     noInnerSuffix [_]    = True
+     noInnerSuffix (x:xs) = null (citationSuffix x) && noInnerSuffix xs
      ismode m  = all ((==) m  . citationMode)
-     p         = citationPrefix  $
-                 head cits
-     s         = citationSuffix  $
-                 last cits
+     p         = maybe mempty citationPrefix $ headMay cits
+     s         = maybe mempty citationSuffix $ lastMay cits
      ks        = T.intercalate ", " $ map citationId cits
 
 citationsToNatbib inlineListToLaTeX (c:cs)
@@ -107,8 +110,13 @@
               -> LW m (Doc Text)
 citeArgumentsList _inlineListToLaTeX (CiteGroup _ _ []) = return empty
 citeArgumentsList inlineListToLaTeX (CiteGroup pfxs sfxs ids) = do
+      opts <- gets stOptions
       mblang <- gets stLang
-      let sfxs' = removePageLabel mblang $
+      let sfxs' = (case writerCiteMethod opts of
+                     -- In biblatex, the label p. or pp. can be omitted;
+                     -- ranges are treated as page ranges by default. See #9275.
+                     Biblatex -> removePageLabel mblang
+                     _ -> id) $
               stripLocatorBraces $ case sfxs of
                 (Str t : r) -> case T.uncons t of
                   Just (x, xs)
@@ -189,9 +197,6 @@
 
 citationsToBiblatex _ _ = return empty
 
--- | In natbib and biblatex, the label p. or pp. can be
--- omitted; ranges will be treated as page ranges by default.
--- See #9275.
 removePageLabel :: Maybe Lang -> [Inline] -> [Inline]
 removePageLabel mblang ils =
   case mbLocinfo of
diff --git a/src/Text/Pandoc/Writers/LaTeX/Table.hs b/src/Text/Pandoc/Writers/LaTeX/Table.hs
--- a/src/Text/Pandoc/Writers/LaTeX/Table.hs
+++ b/src/Text/Pandoc/Writers/LaTeX/Table.hs
@@ -33,11 +33,12 @@
 import Text.Pandoc.Writers.LaTeX.Notes (notesToLaTeX)
 import Text.Pandoc.Writers.LaTeX.Types
   ( LW, WriterState (stBeamer, stExternalNotes, stInMinipage, stMultiRow
-                    , stNotes, stTable) )
+                    , stNotes, stTable, stOptions) )
 import Text.Pandoc.Writers.LaTeX.Util (labelFor)
 import Text.Printf (printf)
 import qualified Text.Pandoc.Builder as B
 import qualified Text.Pandoc.Writers.AnnotatedTable as Ann
+import Text.Pandoc.Options (CaptionPosition(..), WriterOptions(..))
 
 tableToLaTeX :: PandocMonad m
              => ([Inline] -> LW m (Doc Text))
@@ -45,8 +46,13 @@
              -> Ann.Table
              -> LW m (Doc Text)
 tableToLaTeX inlnsToLaTeX blksToLaTeX tbl = do
+  opts <- gets stOptions
   let (Ann.Table (ident, _, _) caption specs thead tbodies tfoot) = tbl
   CaptionDocs capt captNotes <- captionToLaTeX inlnsToLaTeX caption ident
+  let hasTopCaption = not (isEmpty capt) &&
+                        writerTableCaptionPosition opts == CaptionAbove
+  let hasBottomCaption = not (isEmpty capt) &&
+                          writerTableCaptionPosition opts == CaptionBelow
   let isSimpleTable =
         all ((== ColWidthDefault) . snd) specs &&
         all (all isSimpleCell)
@@ -64,24 +70,31 @@
   -- duplicate the header rows for this.
   head' <- do
     let mkHead = headToLaTeX blksToLaTeX isSimpleTable colCount
-    case (not $ isEmpty capt, not $ isEmptyHead thead) of
-      (False, False) -> return "\\toprule\\noalign{}"
-      (False, True)  -> mkHead thead
-      (True, False)  -> return (capt $$ "\\toprule\\noalign{}" $$ "\\endfirsthead")
-      (True, True)   -> do
+    case (hasTopCaption, isEmptyHead thead) of
+      (False, True) -> return "\\toprule\\noalign{}"
+      (False, False)  -> mkHead thead
+      (True, True)  -> return (capt <> "\\tabularnewline"
+                                $$ "\\toprule\\noalign{}"
+                                $$ "\\endfirsthead")
+      (True, False)   -> do
         -- avoid duplicate notes in head and firsthead:
         firsthead <- mkHead thead
         repeated  <- mkHead (walk removeNote thead)
-        return $ capt $$ firsthead $$ "\\endfirsthead" $$ repeated
+        return $ capt <> "\\tabularnewline"
+                 $$ firsthead
+                 $$ "\\endfirsthead"
+                 $$ repeated
   rows' <- mapM (rowToLaTeX blksToLaTeX isSimpleTable colCount BodyCell) $
                 mconcat (map bodyRows tbodies)
-  foot' <- if isEmptyFoot tfoot
-           then pure empty
-           else do
-             lastfoot <- mapM
-                (rowToLaTeX blksToLaTeX isSimpleTable colCount BodyCell) $
-                footRows tfoot
-             pure $ "\\midrule\\noalign{}" $$ vcat lastfoot
+  lastfoot <- mapM (rowToLaTeX blksToLaTeX isSimpleTable colCount BodyCell) $
+                    footRows tfoot
+  let foot' = (if isEmptyFoot tfoot
+                  then mempty
+                  else "\\midrule\\noalign{}" $$ vcat lastfoot)
+              $$ "\\bottomrule\\noalign{}"
+              $$ (if hasBottomCaption
+                     then "\\tabularnewline" $$ capt
+                     else mempty)
   modify $ \s -> s{ stTable = True }
   notes <- notesToLaTeX <$> gets stNotes
   beamer <- gets stBeamer
@@ -99,10 +112,8 @@
        (if beamer
              then [ vcat rows'
                   , foot'
-                  , "\\bottomrule\\noalign{}"
                   ]
              else [ foot'
-                  , "\\bottomrule\\noalign{}"
                   , "\\endlastfoot"
                   ,  vcat rows'
                   ])
@@ -192,7 +203,6 @@
                        else "\\caption" <> captForLot <>
                             braces captionText
                             <> label
-                            <> "\\tabularnewline"
     }
 
 type BlocksWriter m = [Block] -> LW m (Doc Text)
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
@@ -610,7 +610,7 @@
   let isSimple = hasSimpleCells && all (==0) widths
   let isPlainBlock (Plain _) = True
       isPlainBlock _         = False
-  let hasBlocks = not (all isPlainBlock $ concat . concat $ headers:rows)
+  let hasBlocks = not (all (all (all isPlainBlock)) $ headers:rows)
   let padRow r = case numcols - length r of
                        x | x > 0 -> r ++ replicate x empty
                          | otherwise -> r
@@ -620,45 +620,51 @@
   let widths' = case numcols - length widths of
                      x | x > 0 -> widths ++ replicate x 0.0
                        | otherwise -> widths
-  (nst,tbl) <-
-     case True of
-          _ | isSimple &&
-              isEnabled Ext_simple_tables opts -> do
-                rawHeaders <- padRow <$> mapM (blockListToMarkdown opts) headers
-                rawRows <- mapM (fmap padRow . mapM (blockListToMarkdown opts))
-                           rows
-                (nest 2,) <$> pandocTable opts False (all null headers)
-                                aligns' widths' rawHeaders rawRows
-            | isSimple &&
-              isEnabled Ext_pipe_tables opts -> do
-                rawHeaders <- padRow <$> mapM (blockListToMarkdown opts) headers
-                rawRows <- mapM (fmap padRow . mapM (blockListToMarkdown opts))
-                           rows
-                (id,) <$> pipeTable opts (all null headers) aligns' widths'
-                            rawHeaders rawRows
-            | not hasBlocks &&
-              isEnabled Ext_multiline_tables opts -> do
-                rawHeaders <- padRow <$> mapM (blockListToMarkdown opts) headers
-                rawRows <- mapM (fmap padRow . mapM (blockListToMarkdown opts))
-                           rows
-                (nest 2,) <$> pandocTable opts True (all null headers)
-                                aligns' widths' rawHeaders rawRows
-            | isEnabled Ext_grid_tables opts &&
-               writerColumns opts >= 8 * numcols -> (id,) <$>
-                gridTable opts blockListToMarkdown
-                  (all null headers) aligns' widths' headers rows
-            | hasSimpleCells &&
-              isEnabled Ext_pipe_tables opts -> do
-                rawHeaders <- padRow <$> mapM (blockListToMarkdown opts) headers
-                rawRows <- mapM (fmap padRow . mapM (blockListToMarkdown opts))
-                           rows
-                (id,) <$> pipeTable opts (all null headers) aligns' widths'
-                           rawHeaders rawRows
-            | isEnabled Ext_raw_html opts -> fmap (id,) $
-                   literal . removeBlankLinesInHTML <$>
-                   writeHtml5String opts{ writerTemplate = Nothing } (Pandoc nullMeta [t])
-            | otherwise -> return (id, literal "[TABLE]")
-  return $ nst (tbl $$ caption''') $$ blankline
+  case True of
+     _ | isSimple &&
+         isEnabled Ext_simple_tables opts -> do
+           rawHeaders <- padRow <$> mapM (blockListToMarkdown opts) headers
+           rawRows <- mapM (fmap padRow . mapM (blockListToMarkdown opts))
+                      rows
+           tbl <- pandocTable opts False (all null headers)
+                      aligns' widths' rawHeaders rawRows
+           return $ nest 2 (tbl $$ caption''') $$ blankline
+       | isSimple &&
+         isEnabled Ext_pipe_tables opts -> do
+           rawHeaders <- padRow <$> mapM (blockListToMarkdown opts) headers
+           rawRows <- mapM (fmap padRow . mapM (blockListToMarkdown opts))
+                      rows
+           tbl <- pipeTable opts (all null headers) aligns' widths'
+                     rawHeaders rawRows
+           return $ (tbl $$ caption''') $$ blankline
+       | not hasBlocks &&
+         isEnabled Ext_multiline_tables opts -> do
+           rawHeaders <- padRow <$> mapM (blockListToMarkdown opts) headers
+           rawRows <- mapM (fmap padRow . mapM (blockListToMarkdown opts))
+                      rows
+           tbl <- pandocTable opts True (all null headers)
+                     aligns' widths' rawHeaders rawRows
+           return $ nest 2 (tbl $$ caption''') $$ blankline
+       | isEnabled Ext_grid_tables opts &&
+          writerColumns opts >= 8 * numcols -> do
+           tbl <- gridTable opts blockListToMarkdown
+             (all null headers) aligns' widths' headers rows
+           return $ (tbl $$ caption''') $$ blankline
+       | hasSimpleCells &&
+         isEnabled Ext_pipe_tables opts -> do
+           rawHeaders <- padRow <$> mapM (blockListToMarkdown opts) headers
+           rawRows <- mapM (fmap padRow . mapM (blockListToMarkdown opts))
+                      rows
+           tbl <- pipeTable opts (all null headers) aligns' widths'
+                    rawHeaders rawRows
+           return $ (tbl $$ caption''') $$ blankline
+       | isEnabled Ext_raw_html opts -> do -- HTML fallback
+           tbl <- literal . removeBlankLinesInHTML <$>
+                     writeHtml5String opts{ writerTemplate = Nothing }
+                     (Pandoc nullMeta [t])
+           return $ tbl $$ blankline  -- caption is in the HTML table
+       | otherwise
+         -> return $ (literal "[TABLE]" $$ caption''') $$ blankline
 blockToMarkdown' opts (BulletList items) = do
   contents <- inList $ mapM (bulletListItemToMarkdown opts) items
   return $ (if isTightList items then vcat else vsep)
diff --git a/src/Text/Pandoc/Writers/Markdown/Inline.hs b/src/Text/Pandoc/Writers/Markdown/Inline.hs
--- a/src/Text/Pandoc/Writers/Markdown/Inline.hs
+++ b/src/Text/Pandoc/Writers/Markdown/Inline.hs
@@ -506,7 +506,9 @@
             let str' = T.strip str
              in inlineToMarkdown opts
                   (Image nullAttr [Str str'] (url <> urlEncode str', str'))
-          _ | isEnabled Ext_tex_math_dollars opts ->
+          _ | isEnabled Ext_tex_math_gfm opts ->
+                return $ "$`" <> literal str <> "`$"
+            | isEnabled Ext_tex_math_dollars opts ->
                 return $ "$" <> literal str <> "$"
             | isEnabled Ext_tex_math_single_backslash opts ->
                 return $ "\\(" <> literal str <> "\\)"
@@ -531,7 +533,11 @@
              in (\x -> blankline <> x <> blankline) `fmap`
                  inlineToMarkdown opts (Image nullAttr [Str str']
                         (url <> urlEncode str', str'))
-          _ | isEnabled Ext_tex_math_dollars opts ->
+          _ | isEnabled Ext_tex_math_gfm opts ->
+                return $ cr <> (literal "``` math"
+                             $$ literal str
+                             $$ literal "```") <> cr
+            | isEnabled Ext_tex_math_dollars opts ->
                 return $ "$$" <> literal str <> "$$"
             | isEnabled Ext_tex_math_single_backslash opts ->
                 return $ "\\[" <> literal str <> "\\]"
@@ -724,19 +730,3 @@
   where
   go (Emph xs) = Span nullAttr xs
   go x         = x
-
-toSubscriptInline :: Inline -> Maybe Inline
-toSubscriptInline Space = Just Space
-toSubscriptInline (Span attr ils) = Span attr <$> traverse toSubscriptInline ils
-toSubscriptInline (Str s) = Str . T.pack <$> traverse toSubscript (T.unpack s)
-toSubscriptInline LineBreak = Just LineBreak
-toSubscriptInline SoftBreak = Just SoftBreak
-toSubscriptInline _ = Nothing
-
-toSuperscriptInline :: Inline -> Maybe Inline
-toSuperscriptInline Space = Just Space
-toSuperscriptInline (Span attr ils) = Span attr <$> traverse toSuperscriptInline ils
-toSuperscriptInline (Str s) = Str . T.pack <$> traverse toSuperscript (T.unpack s)
-toSuperscriptInline LineBreak = Just LineBreak
-toSuperscriptInline SoftBreak = Just SoftBreak
-toSuperscriptInline _ = Nothing
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
@@ -41,7 +41,7 @@
 import Text.Pandoc.ImageSize
 import Text.Pandoc.Logging
 import Text.Pandoc.Options
-import Text.DocLayout
+import Text.DocLayout hiding (Color)
 import Text.Pandoc.Shared
 import Text.Pandoc.Templates (renderTemplate)
 import Text.Pandoc.Writers.Math
diff --git a/src/Text/Pandoc/Writers/Muse.hs b/src/Text/Pandoc/Writers/Muse.hs
--- a/src/Text/Pandoc/Writers/Muse.hs
+++ b/src/Text/Pandoc/Writers/Muse.hs
@@ -719,7 +719,11 @@
                      then mempty
                      else literal ("#" <> anchor) <> space
   modify $ \st -> st { stUseTags = False }
-  return $ anchorDoc <> (if null inlines && not (T.null anchor)
-                         then mempty
-                         else (if null names then (if hasDir then contents' else "<class>" <> contents' <> "</class>")
-                               else "<class name=\"" <> literal (head names) <> "\">" <> contents' <> "</class>"))
+  return $ anchorDoc <>
+   if null inlines && not (T.null anchor)
+      then mempty
+      else case names of
+             [] | hasDir -> contents'
+                | otherwise -> "<class>" <> contents' <> "</class>"
+             (n:_) -> "<class name=\"" <> literal n <> "\">" <>
+                         contents' <> "</class>"
diff --git a/src/Text/Pandoc/Writers/OpenDocument.hs b/src/Text/Pandoc/Writers/OpenDocument.hs
--- a/src/Text/Pandoc/Writers/OpenDocument.hs
+++ b/src/Text/Pandoc/Writers/OpenDocument.hs
@@ -33,7 +33,7 @@
 import qualified Text.Pandoc.Builder as B
 import Text.Pandoc.Logging
 import Text.Pandoc.Options
-import Text.DocLayout
+import Text.DocLayout hiding (link)
 import Text.Pandoc.Shared (linesToPara, tshow, blocksToInlines)
 import Text.Pandoc.Templates (renderTemplate)
 import qualified Text.Pandoc.Translations as Term (Term(Figure, Table))
@@ -305,7 +305,10 @@
        go b                 = blockToOpenDocument o b
        newLevel a l = do
          nn <- length <$> gets stParaStyles
-         ls <- head   <$> gets stListStyles
+         liststyles <- gets stListStyles
+         let ls = case liststyles of
+                    [] -> (1,[])  -- should never happen
+                    (s:_) -> s
          modify $ \s -> s { stListStyles = orderedListLevelStyle a ls :
                                  drop 1 (stListStyles s) }
          inTagsIndented "text:list" <$> orderedListToOpenDocument o nn l
@@ -396,13 +399,14 @@
                 Left msg -> do
                   unless (T.null msg) $ report $ CouldNotHighlight msg
                   unhighlighted s
-    Table a bc s th tb tf -> setFirstPara >> table (Ann.toTable a bc s th tb tf)
+    Table a bc s th tb tf -> setFirstPara >>
+                              table o (Ann.toTable a bc s th tb tf)
     HorizontalRule   -> setFirstPara >> return (selfClosingTag "text:p"
                          [ ("text:style-name", "Horizontal_20_Line") ])
     b@(RawBlock f s) -> if f == Format "opendocument"
                         then return $ text $ T.unpack s
                         else empty <$ report (BlockNotRendered b)
-    Figure a capt b  -> figure a capt b
+    Figure a capt b  -> figure o a capt b
     where
       defList       b = do setInDefinitionList True
                            r <- vcat  <$> mapM (deflistItemToOpenDocument o) b
@@ -427,8 +431,9 @@
       orderedList a b = do (ln,pn) <- newOrderedListStyle (isTightList b) a
                            inTags True "text:list" [ ("text:style-name", "L" <> tshow ln)]
                                       <$> orderedListToOpenDocument o pn b
-      table :: PandocMonad m => Ann.Table -> OD m (Doc Text)
-      table (Ann.Table (ident, _, _) (Caption _ c) colspecs thead tbodies _) = do
+      table :: PandocMonad m => WriterOptions -> Ann.Table -> OD m (Doc Text)
+      table opts
+          (Ann.Table (ident, _, _) (Caption _ c) colspecs thead tbodies _) = do
         tn <- length <$> gets stTableStyles
         pn <- length <$> gets stParaStyles
         let  genIds      = map chr [65..]
@@ -458,8 +463,11 @@
                             ("table:name"      , name)
                           , ("table:style-name", name)
                           ] (vcat columns $$ th $$ vcat tr)
-        return $ captionDoc $$ tableDoc
-      figure (ident, _, _) (Caption _ longcapt) body =
+        return $
+          case writerTableCaptionPosition opts of
+            CaptionAbove -> captionDoc $$ tableDoc
+            CaptionBelow -> tableDoc $$ captionDoc
+      figure opts (ident, _, _) (Caption _ longcapt) body =
         case blocksToInlines longcapt of
           [] ->
             withParagraphStyle o "Figure" body
@@ -470,7 +478,10 @@
                           if isEnabled Ext_native_numbering o
                           then numberedFigureCaption ident
                           else unNumberedCaption "FigureCaption"
-            return $ imageDoc $$ captionDoc
+            return $
+              case writerFigureCaptionPosition opts of
+                CaptionAbove -> captionDoc $$ imageDoc
+                CaptionBelow -> imageDoc $$ captionDoc
 
 
 numberedTableCaption :: PandocMonad m => Text -> Doc Text -> OD m (Doc Text)
diff --git a/src/Text/Pandoc/Writers/RTF.hs b/src/Text/Pandoc/Writers/RTF.hs
--- a/src/Text/Pandoc/Writers/RTF.hs
+++ b/src/Text/Pandoc/Writers/RTF.hs
@@ -281,8 +281,9 @@
                  else sizes'
   columns <- T.concat <$>
      zipWithM (tableItemToRTF indent) aligns cols
-  let rightEdges = tail $ scanl (\sofar new -> sofar + floor (new * totalTwips))
-                                (0 :: Integer) sizes
+  let rightEdges = drop 1 $
+                    scanl (\sofar new -> sofar + floor (new * totalTwips))
+                          (0 :: Integer) sizes
   let cellDefs = map (\edge -> (if header
                                    then "\\clbrdrb\\brdrs"
                                    else "") <> "\\cellx" <> tshow edge)
diff --git a/src/Text/Pandoc/Writers/Shared.hs b/src/Text/Pandoc/Writers/Shared.hs
--- a/src/Text/Pandoc/Writers/Shared.hs
+++ b/src/Text/Pandoc/Writers/Shared.hs
@@ -38,6 +38,8 @@
                      , stripLeadingTrailingSpace
                      , toSubscript
                      , toSuperscript
+                     , toSubscriptInline
+                     , toSuperscriptInline
                      , toTableOfContents
                      , endsWithPlain
                      , toLegacyTable
@@ -469,6 +471,22 @@
                  Just $ chr (0x2080 + (ord c - 48))
   | isSpace c = Just c
   | otherwise = Nothing
+
+toSubscriptInline :: Inline -> Maybe Inline
+toSubscriptInline Space = Just Space
+toSubscriptInline (Span attr ils) = Span attr <$> traverse toSubscriptInline ils
+toSubscriptInline (Str s) = Str . T.pack <$> traverse toSubscript (T.unpack s)
+toSubscriptInline LineBreak = Just LineBreak
+toSubscriptInline SoftBreak = Just SoftBreak
+toSubscriptInline _ = Nothing
+
+toSuperscriptInline :: Inline -> Maybe Inline
+toSuperscriptInline Space = Just Space
+toSuperscriptInline (Span attr ils) = Span attr <$> traverse toSuperscriptInline ils
+toSuperscriptInline (Str s) = Str . T.pack <$> traverse toSuperscript (T.unpack s)
+toSuperscriptInline LineBreak = Just LineBreak
+toSuperscriptInline SoftBreak = Just SoftBreak
+toSuperscriptInline _ = Nothing
 
 -- | Construct table of contents (as a bullet list) from document body.
 toTableOfContents :: WriterOptions
diff --git a/src/Text/Pandoc/Writers/TEI.hs b/src/Text/Pandoc/Writers/TEI.hs
--- a/src/Text/Pandoc/Writers/TEI.hs
+++ b/src/Text/Pandoc/Writers/TEI.hs
@@ -138,9 +138,9 @@
 blockToTEI opts (CodeBlock (_,classes,_) str) =
   return $ literal ("<ab type='codeblock " <> lang <> "'>") <> cr <>
      flush (literal (escapeStringForXML str) <> cr <> text "</ab>")
-    where lang  = if null langs
-                     then ""
-                     else escapeStringForXML (head langs)
+    where lang  = case langs of
+                    [] -> ""
+                    (l:_) -> escapeStringForXML l
           syntaxMap = writerSyntaxMap opts
           isLang l    = T.toLower l `elem` map T.toLower (languages syntaxMap)
           langsFrom s = if isLang s
diff --git a/src/Text/Pandoc/Writers/Typst.hs b/src/Text/Pandoc/Writers/Typst.hs
--- a/src/Text/Pandoc/Writers/Typst.hs
+++ b/src/Text/Pandoc/Writers/Typst.hs
@@ -18,7 +18,8 @@
   ) where
 import Text.Pandoc.Definition
 import Text.Pandoc.Class ( PandocMonad)
-import Text.Pandoc.Options ( WriterOptions(..), WrapOption(..), isEnabled )
+import Text.Pandoc.Options ( WriterOptions(..), WrapOption(..), isEnabled,
+                             CaptionPosition(..) )
 import Data.Text (Text)
 import Data.List (intercalate, intersperse)
 import Data.Bifunctor (first, second)
@@ -67,6 +68,9 @@
               (fmap chomp . inlinesToTypst)
               meta
   main <- blocksToTypst blocks
+  let toPosition :: CaptionPosition -> Text
+      toPosition CaptionAbove = "top"
+      toPosition CaptionBelow = "bottom"
   let context = defField "body" main
               $ defField "toc" (writerTableOfContents options)
               $ (if isEnabled Ext_citations options
@@ -81,6 +85,10 @@
                           resetField "lang" (langLanguage l) .
                           maybe id (resetField "region") (langRegion l))
               $ defField "toc-depth" (tshow $ writerTOCDepth options)
+              $ defField "figure-caption-position"
+                   (toPosition $ writerFigureCaptionPosition options)
+              $ defField "table-caption-position"
+                   (toPosition $ writerTableCaptionPosition options)
               $ (if writerNumberSections options
                     then defField "numbering" ("1.1.1.1.1" :: Text)
                     else id)
@@ -177,8 +185,9 @@
                                   parens (
                                     "numbering: " <>
                                     doubleQuoted
-                                      (head (orderedListMarkers
-                                             (1, sty, delim))) <>
+                                     (case orderedListMarkers (1, sty, delim) of
+                                          (m:_) -> m
+                                          [] -> "1.") <>
                                     ", start: " <>
                                       text (show start) )) $$
                                x $$
@@ -517,8 +526,7 @@
      then do
        suppl <- case citationSuffix cite of
                   [] -> pure mempty
-                  suff -> (<> endCode) . brackets
-                            <$> inlinesToTypst (eatComma suff)
+                  suff -> brackets <$> inlinesToTypst (eatComma suff)
        pure $ "@" <> literal ident' <> suppl
      else do
        let label = if T.all isIdentChar ident'
diff --git a/src/Text/Pandoc/Writers/ZimWiki.hs b/src/Text/Pandoc/Writers/ZimWiki.hs
--- a/src/Text/Pandoc/Writers/ZimWiki.hs
+++ b/src/Text/Pandoc/Writers/ZimWiki.hs
@@ -128,7 +128,9 @@
                       c <- inlineListToZimWiki opts capt
                       return $ "" <> c <> "\n"
   headers' <- if all null headers
-                 then zipWithM (tableItemToZimWiki opts) aligns (head rows)
+                 then case rows of
+                        [] -> pure mempty
+                        (r:_) -> zipWithM (tableItemToZimWiki opts) aligns r
                  else mapM (inlineListToZimWiki opts . removeFormatting)headers  -- emphasis, links etc. are not allowed in table headers
   rows' <- mapM (zipWithM (tableItemToZimWiki opts) aligns) rows
   let widths = map (maybe 0 maximum . nonEmpty . map T.length) $
diff --git a/test/Tests/Old.hs b/test/Tests/Old.hs
--- a/test/Tests/Old.hs
+++ b/test/Tests/Old.hs
@@ -242,6 +242,10 @@
       "ipynb/rank.ipynb" "ipynb/rank.out.html"
     ]
   , testGroup "markua" [ testGroup "writer" $ writerTests' "markua"]
+  , testGroup "ansi"
+      [ test' "ansi" ["-f", "markdown", "-t", "ansi"]
+        "ansi-test.txt" "ansi-test.ansi"
+      ]
   ]
  where
     test'           = test pandocPath
diff --git a/test/Tests/Readers/HTML.hs b/test/Tests/Readers/HTML.hs
--- a/test/Tests/Readers/HTML.hs
+++ b/test/Tests/Readers/HTML.hs
@@ -87,16 +87,16 @@
             setMeta "lang" (text "es") (doc (plain (text "hola")))
           ]
         , testGroup "main"
-          [ test htmlNativeDivs "<main> becomes <div role=main>" $ "<main>hello</main>" =?>
-            doc (divWith ("", [], [("role", "main")]) (plain (text "hello")))
+          [ test htmlNativeDivs "<main> contents are parsed" $ "<header>ignore me</header><nav><p>ignore me</p><main>hello</main><footer>ignore me</footer>" =?>
+            doc (plain (text "hello"))
           , test htmlNativeDivs "<main role=X> becomes <div role=X>" $ "<main role=foobar>hello</main>" =?>
             doc (divWith ("", [], [("role", "foobar")]) (plain (text "hello")))
           , test htmlNativeDivs "<main> has attributes preserved" $ "<main id=foo class=bar data-baz=qux>hello</main>" =?>
             doc (divWith ("foo", ["bar"], [("role", "main"), ("baz", "qux")]) (plain (text "hello")))
           , test htmlNativeDivs "<main> closes <p>" $ "<p>hello<main>main content</main>" =?>
-            doc (para (text "hello") <> divWith ("", [], [("role", "main")]) (plain (text "main content")))
+            doc (plain (text "main content"))
           , test htmlNativeDivs "<main> followed by text" $ "<main>main content</main>non-main content" =?>
-            doc (divWith ("", [], [("role", "main")]) (plain (text "main content")) <> plain (text "non-main content"))
+            doc (plain (text "main content"))
           ]
         , testGroup "code"
           [
diff --git a/test/Tests/Readers/Markdown.hs b/test/Tests/Readers/Markdown.hs
--- a/test/Tests/Readers/Markdown.hs
+++ b/test/Tests/Readers/Markdown.hs
@@ -208,17 +208,17 @@
              | lsts <- [ [i, j, k] | i <- lists, j <- lists, k <- lists]
              ]
           <> [ "lists with newlines and indent in backticks" =:
-               T.intercalate ("\n" <> T.replicate 4 " ") (zipWith (\i (_, lt, _) -> lt <> i) lis lsts)
-               =?> let (_, _, f) = head lsts
-                   in f [plain $ code $ T.intercalate (T.replicate 5 " ") $ head lis' : zipWith (\i (_, lt, _) -> lt <> i) (tail lis') (tail lsts)]
-             | lsts <- [ [i, j, k] | i <- lists, j <- lists, k <- lists]
+               T.intercalate ("\n" <> T.replicate 4 " ") (zipWith (\i (_, lt, _) -> lt <> i) lis (l:ls))
+               =?> let (_, _, f) = l
+                   in f [plain $ code $ T.intercalate (T.replicate 5 " ") $ "text" : zipWith (\i (_, lt, _) -> lt <> i) (drop 1 lis') ls]
+             | (l:ls) <- [ [i, j, k] | i <- lists, j <- lists, k <- lists]
              ]
           <> [ "lists with blank lines and indent in backticks" =:
-               T.intercalate ("\n\n" <> T.replicate 4 " ") (zipWith (\i (_, lt, _) -> lt <> i) lis lsts)
+               T.intercalate ("\n\n" <> T.replicate 4 " ") (zipWith (\i (_, lt, _) -> lt <> i) lis (l:ls))
                <> "\n"
-               =?> let (_, _, f) = head lsts
-                   in f . pure $ (para . text $ head lis) <> bldLsts para (tail lsts) (tail lis)
-             | lsts <- [ [i, j, k] | i <- lists, j <- lists, k <- lists]
+               =?> let (_, _, f) = l
+                   in f . pure $ (para . text $ "`text") <> bldLsts para ls (drop 1 lis)
+             | (l:ls) <- [ [i, j, k] | i <- lists, j <- lists, k <- lists]
              ]
         , testGroup "emph and strong"
           [ "two strongs in emph" =:
diff --git a/test/Tests/Shared.hs b/test/Tests/Shared.hs
--- a/test/Tests/Shared.hs
+++ b/test/Tests/Shared.hs
@@ -51,7 +51,8 @@
    in d' == makeSections False Nothing d'
 
 givesTOC :: String -> (Blocks, Blocks) -> TestTree
-givesTOC desc (blocks, toc) = test (toTableOfContents def) desc (toList blocks, head . toList $ toc)
+givesTOC desc (blocks, toc) =
+  test (singleton . toTableOfContents def) desc (toList blocks, toc)
 
 linkId :: T.Text -> T.Text -> T.Text -> Inlines -> Inlines
 linkId lId = linkWith (lId,[],[])
diff --git a/test/ansi-test.ansi b/test/ansi-test.ansi
new file mode 100644
--- /dev/null
+++ b/test/ansi-test.ansi
@@ -0,0 +1,116 @@
+[1m[23m[39m[49m[24m[29mLEVEL 1 HEADING[0m
+
+Text
+
+[1m[23m[39m[49m[24m[29mLevel 2 heading[0m
+
+Text
+
+[22m[3m[39m[49m[24m[29mLevel 3 heading[0m
+
+Text
+
+[22m[3m[39m[49m[24m[29mLevel 4 heading[0m
+
+Text
+
+[22m[3m[39m[49m[24m[29mLevel 5 heading[0m
+
+Text
+
+• list
+
+  • nested
+
+    1) numbered
+
+       continuation paragraph
+
+[22m[23m[36m[49m[4m[29m]8;;http://example.com\link[0m]8;;\
+
+[22m[23m[36m[49m[4m[29m]8;;mailto:mail@example.com\mail@example.com[0m]8;;\
+
+│ Block quote
+│
+│ │ Nested block quote
+
+Multiline table with caption:
+
+Centered    Left       Right        Default aligned
+Header      Aligned    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.
+─────────── ────────── ──────────── ──────────────────────────
+
+  Here’s the caption. It may span multiple lines.
+
+Multiline table without caption:
+
+Centered    Left       Right        Default aligned
+Header      Aligned    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:
+
+───────────────── ───────────────── ───────────────── ─────────────────
+12                12                12                12
+123               123               123               123
+1                 1                 1                 1
+───────────────── ───────────────── ───────────────── ─────────────────
+
+Footnote¹
+
+Multiple blocks in a cell
+
+────────────────── ─────────── ────────────
+[1m[23m[39m[49m[24m[29mCOL 1[0m              [1m[23m[39m[49m[24m[29mCOL 2[0m       [1m[23m[39m[49m[24m[29mCOL 3[0m
+                               
+col 1              col 2       col 3
+r1 a               • b         c c 2 c 2
+                               
+r1 bis             • b 2       
+                               
+                   • b 2       
+────────────────── ─────────── ────────────
+
+East Asian characters have double width
+
+── ────
+魚 fish
+── ────
+
+This is inline code: [22m[23m[31m[47m[24m[29m x >>= y ++ 3 [0m.
+
+    [22m[23m[31m[49m[24m[29mcode block[0m
+    [22m[23m[31m[49m[24m[29m   >>> ## ``[0m
+
+Highlighted:
+
+    [0m[0m[22;23;24m[31;22;23;24m<[0m[22;23;24m[32;1;23;24mp[0m[22;23;24m[31;22;23;24m><[0m[22;23;24m[32;1;23;24ma[0m[22;23;24m[32;22;23;24m href[0m[22;23;24m[90;22;23;24m=[0m[22;23;24m[36;22;23;24m"foobar"[0m[22;23;24m[32;22;23;24m title[0m[22;23;24m[90;22;23;24m=[0m[22;23;24m[36;22;23;24m"title"[0m[22;23;24m[31;22;23;24m>[0m[22;23;24m[0m[22;23;24mThis is a link[0m[22;23;24m[31;22;23;24m</[0m[22;23;24m[32;1;23;24ma[0m[22;23;24m[31;22;23;24m></[0m[22;23;24m[32;1;23;24mp[0m[22;23;24m[31;22;23;24m>[0m[22;23;24m
+    [90;22;3;24m<!-- comment -->[0m[22;23;24m[0m
+
+Math: $\pi^2 = \frac{1}{2}$
+
+[22m[3m[39m[49m[24m[29me[0m = [22m[3m[39m[49m[24m[29mmc[0m²
+
+────────────────────
+
+More text.
+
+[1m[23m[39m[49m[24m[29mboldface text[0m, [22m[23m[39m[49m[4m[29munderlined text[0m, [22m[3m[39m[49m[24m[29memphasized text[0m.
+
+small caps
+
+                          ────────────────────
+
+  1. Here’s the note.[0m]8;;\
diff --git a/test/ansi-test.txt b/test/ansi-test.txt
new file mode 100644
--- /dev/null
+++ b/test/ansi-test.txt
@@ -0,0 +1,122 @@
+---
+title: My Document
+author: John Doe
+date: July 25, 1933
+...
+
+# Level 1 heading
+
+Text
+
+## Level 2 heading
+
+Text
+
+### Level 3 heading
+
+Text
+
+#### Level 4 heading
+
+Text
+
+##### Level 5 heading
+
+Text
+
+- list
+  - nested
+    1) numbered
+
+       continuation paragraph
+
+[link](http://example.com)
+
+<mail@example.com>
+
+> Block quote
+>
+> > Nested block quote
+
+Multiline table with caption:
+
+:  Here's the caption.
+It may span multiple lines.
+
+---------------------------------------------------------------
+ Centered   Left             Right
+  Header    Aligned        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.
+---------------------------------------------------------------
+
+Multiline table without caption:
+
+---------------------------------------------------------------
+ Centered   Left             Right
+  Header    Aligned        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:
+
+-------     ------ ----------   -------
+     12     12        12             12
+    123     123       123           123
+      1     1          1              1
+-------     ------ ----------   -------
+
+Footnote^[Here's the note.]
+
+Multiple blocks in a cell
+
++------------------+-----------+------------+
+| # col 1          | # col 2   | # col 3    |
+| col 1            | col 2     | col 3      |
++------------------+-----------+------------+
+| r1 a             | - b       | c          |
+|                  | - b 2     | c 2        |
+| r1 bis           | - b 2     | c 2        |
++------------------+-----------+------------+
+
+East Asian characters have double width
+
++--+----+
+|魚|fish|
++--+----+
+
+This is inline code: `x >>= y ++ 3`.
+
+```
+code block
+   >>> ## ``
+```
+
+Highlighted:
+
+```html
+<p><a href="foobar" title="title">This is a link</a></p>
+<!-- comment -->
+```
+
+Math: $\pi^2 = \frac{1}{2}$
+
+$$e = mc^2$$
+
+----
+
+More text.
+
+**boldface text**, [underlined text]{.underline}, *emphasized text*.
+
+[small caps]{.smallcaps}
+
diff --git a/test/command/10057.md b/test/command/10057.md
new file mode 100644
--- /dev/null
+++ b/test/command/10057.md
@@ -0,0 +1,6 @@
+```
+% pandoc --natbib -t latex
+We all love the famous proof by @BenjaminHornigold [p. 42].
+^D
+We all love the famous proof by \citet[p.~42]{BenjaminHornigold}.
+```
diff --git a/test/command/10062.md b/test/command/10062.md
new file mode 100644
--- /dev/null
+++ b/test/command/10062.md
@@ -0,0 +1,10 @@
+```
+% pandoc -t asciidoc
+### section
+
+##### five
+^D
+==== section
+
+====== five
+```
diff --git a/test/command/10071.md b/test/command/10071.md
new file mode 100644
--- /dev/null
+++ b/test/command/10071.md
@@ -0,0 +1,15 @@
+Leading tabs must be preserved in org mode *src* blocks with the `-i`
+flag when pandoc is called with `-p`/`--preserve-tabs`.
+
+```
+% pandoc -f org -t native --preserve-tabs
+#+begin_src makefile -i
+%.o: %.cpp
+	$(CXX)	-o	$@	$<
+#+end_src
+^D
+[ CodeBlock
+    ( "" , [ "makefile" ] , [] )
+    "%.o: %.cpp\n\t$(CXX)\t-o\t$@\t$<\n"
+]
+```
diff --git a/test/command/10093.md b/test/command/10093.md
new file mode 100644
--- /dev/null
+++ b/test/command/10093.md
@@ -0,0 +1,53 @@
+```
+% pandoc -f rst
+========== ====
+Function   1   
+========== ====
+a          yes 
+b              
+========== ====
+^D
+<table>
+<thead>
+<tr>
+<th>Function</th>
+<th>1</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>a</td>
+<td>yes</td>
+</tr>
+<tr>
+<td>b</td>
+<td></td>
+</tr>
+</tbody>
+</table>
+```
+
+```
+% pandoc -f rst
+========== ====
+Function   1   
+========== ====
+a          yes 
+           & no
+========== ====
+^D
+<table>
+<thead>
+<tr>
+<th>Function</th>
+<th>1</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>a</td>
+<td>yes &amp; no</td>
+</tr>
+</tbody>
+</table>
+```
diff --git a/test/command/10094.md b/test/command/10094.md
new file mode 100644
--- /dev/null
+++ b/test/command/10094.md
@@ -0,0 +1,47 @@
+```
+% pandoc -f rst -t markdown_strict
+.. table:: Test Table
+
+   ================================== ====
+   Functions for tests                1   
+   ================================== ====
+   a                                  yes 
+   b                                      
+   c                                      
+   d                                  yes
+   e                                      
+   ================================== ====
+^D
+<table>
+<caption>Test Table</caption>
+<thead>
+<tr>
+<th>Functions for tests</th>
+<th>1</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>a</td>
+<td>yes</td>
+</tr>
+<tr>
+<td>b</td>
+<td></td>
+</tr>
+<tr>
+<td>c</td>
+<td></td>
+</tr>
+<tr>
+<td>d</td>
+<td>yes</td>
+</tr>
+<tr>
+<td>e</td>
+<td></td>
+</tr>
+</tbody>
+</table>
+
+```
diff --git a/test/command/10105.md b/test/command/10105.md
new file mode 100644
--- /dev/null
+++ b/test/command/10105.md
@@ -0,0 +1,8 @@
+```
+% pandoc -t asciidoc --wrap=preserve
+[link](https://example.com)
+[link](ftps://example.com)
+^D
+https://example.com[link]
+link:ftps://example.com[link]
+```
diff --git a/test/command/10145.md b/test/command/10145.md
new file mode 100644
--- /dev/null
+++ b/test/command/10145.md
@@ -0,0 +1,24 @@
+```
+% pandoc -f rtf -t native
+{\rtf1\ansi\deff0{\fonttbl{\f0 \fswiss Helvetica;}{\f1 \fmodern Courier;}}
+{\colortbl;\red255\green0\blue0;\red0\green0\blue255;}
+\widowctrl\hyphauto
+
+{\pard \ql \f0 \sa0 \li0 \fi0 {\shp{\*\shpinst\shpwr2\shpwrk3\shpbypara\shpbyignore\shptop1320\shpbottom7745\shpbxcolumn\shpbxignore\shpleft0\shpright9638{\sp{\sn shapeType}{\sv 75}}{\sp{\sn wzDescription}{\sv }}{\sp{\sn wzName}{\sv }}{\sp{\sn pib}{\sv {\pict\jpegblip\picw20\pich22\picwgoal400\pichgoal440 ffd8ffe000104a46494600010101004800480000fffe0050546869732061727420697320696e20746865207075626c696320646f6d61696e2e204b6576696e204875676865732c206b6576696e68406569742e636f6d2c2053657074656d6265722031393935ffdb00430001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101ffdb00430101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101ffc00011080016001403012200021101031101ffc4001a000100020301000000000000000000000000080905060a07ffc400231000010501000300010500000000000000060304050708020001090a11153976b7ffc400160101010100000000000000000000000000060800ffc400261101000102050109000000000000000000010200030405061121b33134365154717475b4ffda000c03010002110311003f00a90cf388f366a62aa720ed6ae07f96901f3831d973452b8cf36fe3570fc908e46d466433e5dd954f2e96992d9e498c7753faa44916e016ca91cc7d88b38fe60a5b97737defcbcc539c98d336a57f4fc2ca9a486bf07ab575ad9a3af4df221d8215e36df86c4504ff0024574551b3d687ee0575757b3ad64e311ee62bd94158d37e24198c43973099f1fc0c41614d950246513a081abf76cfe7061f6863281e6352fd1670949c148dd6dfb0d25f5b3689b1d5c965b0eacbf4e0932ad28e22ab9ae945633f4744bd3c8cee0a7fdf085b9000f449c5f7afa30b83e0b6fd7b0c8429c9467ff9715347c891e25fa24a205861aa715e6a09bd0488237dc2723414d9891381524e8ca7c0894664f835653631ab55ee7e3de433e4ff001b30949124e4c10c8b6ad0a479b3f9c937b2cf5bc0095ad600a0a41a0e9faee174a1c605e161c6c7a313539650b0113190f1a8368e60d5b24f30ff008ea7f0bf867fa6595feeb6978f1fe0f9c26177f4d63a51a9235184750e7d18811339cd000000c75f000e00380380ae390c350def826ed42ad051fa6f501c50f9b699c3b69cbeb76476d202bf3ac985b6e0e968be66572893e6a744540bd9722e5c87956848629bc2559306bd113e8653d3b6aff651dfad7a3ac8b02958cba02a93ccf525757039bae6cff090e1d90688e8aa233ee86a4c4a3e0586d6b2340522e47dcb7d0046d8a5acb05a123ee25d2b230b2ada6e2e2f9ede3c05202520ec2487b0d56562529d8b3393bca76adca4ec1bca508abb001babc007915d84fe3dd14e207e3c62f8379da2a3b861fb6629d28dba53b6ea388ebfed866bf6dfb553455e91ed547ae92e9445253a4fdf3efb4f8ebdfbe7d3c78f1ee0bb9e13e358e942a4ed49e22cff00eeb35fdd7ebfffd9}}}}}\par}
+{\pard \ql \f0 \sa0 \li0 \fi0 moon\par}
+}
+^D
+[ Para
+    [ Image
+        ( ""
+        , []
+        , [ ( "width" , "0.2777777777777778in" )
+          , ( "height" , "0.3055555555555556in" )
+          ]
+        )
+        [ Str "image" ]
+        ( "9ea9761885249909bcd8a610706eef51b54dc351.jpg" , "" )
+    ]
+, Para [ Str "moon" ]
+]
+```
diff --git a/test/command/10148.md b/test/command/10148.md
new file mode 100644
--- /dev/null
+++ b/test/command/10148.md
@@ -0,0 +1,18 @@
+```
+% pandoc -t typst
+See [@MyPaper].
+
+See [@MyPaper, Equation 1] for details.
+
+See [@MyPaper, Equation 1].
+
+See [@MyPaper, Equation 1; @Other, Figure 1].
+^D
+See @MyPaper.
+
+See @MyPaper[Equation 1] for details.
+
+See @MyPaper[Equation 1].
+
+See @MyPaper[Equation 1]@Other[Figure 1].
+```
diff --git a/test/command/10160.md b/test/command/10160.md
new file mode 100644
--- /dev/null
+++ b/test/command/10160.md
@@ -0,0 +1,11 @@
+```
+% pandoc -f latex -t native
+\begin{equation}
+  [0,1)
+\end{equation}
+
+\begin{table}[h]
+\end{table}
+^D
+[ Para [ Math DisplayMath "[0,1)" ] ]
+```
diff --git a/test/command/1166.md b/test/command/1166.md
--- a/test/command/1166.md
+++ b/test/command/1166.md
@@ -28,11 +28,11 @@
 <td>Second column of row 1.</td>
 </tr>
 <tr>
-<td><p>2</p></td>
-<td><p>Second column of row 2. Second line of paragraph.</p></td>
+<td>2</td>
+<td>Second column of row 2. Second line of paragraph.</td>
 </tr>
 <tr>
-<td><p>3</p></td>
+<td>3</td>
 <td><ul>
 <li>Second column of row 3.</li>
 <li>Second item in bullet list (row 3, column 2).</li>
diff --git a/test/command/4746.md b/test/command/4746.md
new file mode 100644
--- /dev/null
+++ b/test/command/4746.md
@@ -0,0 +1,56 @@
+```
+% pandoc -f latex -t native
+\begin{tabular}{c c}
+  \begin{tabular}{c} a \\ \end{tabular}
+  &
+  \\
+\end{tabular}
+^D
+[ Table
+    ( "" , [] , [] )
+    (Caption Nothing [])
+    [ ( AlignCenter , ColWidthDefault )
+    , ( AlignCenter , ColWidthDefault )
+    ]
+    (TableHead ( "" , [] , [] ) [])
+    [ TableBody
+        ( "" , [] , [] )
+        (RowHeadColumns 0)
+        []
+        [ Row
+            ( "" , [] , [] )
+            [ Cell
+                ( "" , [] , [] )
+                AlignDefault
+                (RowSpan 1)
+                (ColSpan 1)
+                [ Table
+                    ( "" , [] , [] )
+                    (Caption Nothing [])
+                    [ ( AlignCenter , ColWidthDefault ) ]
+                    (TableHead ( "" , [] , [] ) [])
+                    [ TableBody
+                        ( "" , [] , [] )
+                        (RowHeadColumns 0)
+                        []
+                        [ Row
+                            ( "" , [] , [] )
+                            [ Cell
+                                ( "" , [] , [] )
+                                AlignDefault
+                                (RowSpan 1)
+                                (ColSpan 1)
+                                [ Plain [ Str "a" ] ]
+                            ]
+                        ]
+                    ]
+                    (TableFoot ( "" , [] , [] ) [])
+                ]
+            , Cell
+                ( "" , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 1) []
+            ]
+        ]
+    ]
+    (TableFoot ( "" , [] , [] ) [])
+]
+```
diff --git a/test/command/5071.md b/test/command/5071.md
--- a/test/command/5071.md
+++ b/test/command/5071.md
@@ -49,8 +49,8 @@
 ^D
 <h2 data-number="2.3" id="first-section"><span
 class="header-section-number">2.3</span> First section</h2>
-<h3 data-number="2.3.3" id="subhead"><span
-class="header-section-number">2.3.3</span> Subhead</h3>
+<h3 data-number="2.3.1" id="subhead"><span
+class="header-section-number">2.3.1</span> Subhead</h3>
 
 ```
 
@@ -62,7 +62,7 @@
 ^D
 <h2 data-number="0.3" id="first-section"><span
 class="header-section-number">0.3</span> First section</h2>
-<h3 data-number="0.3.3" id="subhead"><span
-class="header-section-number">0.3.3</span> Subhead</h3>
+<h3 data-number="0.3.1" id="subhead"><span
+class="header-section-number">0.3.1</span> Subhead</h3>
 
 ```
diff --git a/test/command/5116.md b/test/command/5116.md
new file mode 100644
--- /dev/null
+++ b/test/command/5116.md
@@ -0,0 +1,107 @@
+```
+% pandoc -t latex
+![This is a figure.](img.jpg)
+
+  Right     Left     Center     Default
+-------     ------ ----------   -------
+     12     12        12            12
+    123     123       123          123
+      1     1          1             1
+
+  :  Demonstration of simple table syntax.
+^D
+\begin{figure}
+\centering
+\pandocbounded{\includegraphics[keepaspectratio]{img.jpg}}
+\caption{This is a figure.}
+\end{figure}
+
+\begin{longtable}[]{@{}rlcl@{}}
+\caption{Demonstration of simple table syntax.}\tabularnewline
+\toprule\noalign{}
+Right & Left & Center & Default \\
+\midrule\noalign{}
+\endfirsthead
+\toprule\noalign{}
+Right & Left & Center & Default \\
+\midrule\noalign{}
+\endhead
+\bottomrule\noalign{}
+\endlastfoot
+12 & 12 & 12 & 12 \\
+123 & 123 & 123 & 123 \\
+1 & 1 & 1 & 1 \\
+\end{longtable}
+
+```
+
+```
+% pandoc -t latex --figure-caption-position=above --table-caption-position=above
+![This is a figure.](img.jpg)
+
+  Right     Left     Center     Default
+-------     ------ ----------   -------
+     12     12        12            12
+    123     123       123          123
+      1     1          1             1
+
+  :  Demonstration of simple table syntax.
+^D
+\begin{figure}
+\centering
+\caption{This is a figure.}
+\pandocbounded{\includegraphics[keepaspectratio]{img.jpg}}
+\end{figure}
+
+\begin{longtable}[]{@{}rlcl@{}}
+\caption{Demonstration of simple table syntax.}\tabularnewline
+\toprule\noalign{}
+Right & Left & Center & Default \\
+\midrule\noalign{}
+\endfirsthead
+\toprule\noalign{}
+Right & Left & Center & Default \\
+\midrule\noalign{}
+\endhead
+\bottomrule\noalign{}
+\endlastfoot
+12 & 12 & 12 & 12 \\
+123 & 123 & 123 & 123 \\
+1 & 1 & 1 & 1 \\
+\end{longtable}
+
+```
+
+```
+% pandoc -t latex --figure-caption-position=below --table-caption-position=below
+![This is a figure.](img.jpg)
+
+  Right     Left     Center     Default
+-------     ------ ----------   -------
+     12     12        12            12
+    123     123       123          123
+      1     1          1             1
+
+  :  Demonstration of simple table syntax.
+^D
+\begin{figure}
+\centering
+\pandocbounded{\includegraphics[keepaspectratio]{img.jpg}}
+\caption{This is a figure.}
+\end{figure}
+
+\begin{longtable}[]{@{}rlcl@{}}
+\toprule\noalign{}
+Right & Left & Center & Default \\
+\midrule\noalign{}
+\endhead
+\bottomrule\noalign{}
+\tabularnewline
+\caption{Demonstration of simple table syntax.}
+\endlastfoot
+12 & 12 & 12 & 12 \\
+123 & 123 & 123 & 123 \\
+1 & 1 & 1 & 1 \\
+\end{longtable}
+
+```
diff --git a/test/command/9121.md b/test/command/9121.md
new file mode 100644
--- /dev/null
+++ b/test/command/9121.md
@@ -0,0 +1,17 @@
+````
+% pandoc -t gfm
+a $$x=y$$ b
+^D
+a
+``` math
+x=y
+```
+b
+````
+
+````
+% pandoc -t gfm
+a $x=y$ b
+^D
+a $`x=y`$ b
+````
diff --git a/test/command/9452.md b/test/command/9452.md
--- a/test/command/9452.md
+++ b/test/command/9452.md
@@ -12,7 +12,7 @@
 
 Here is a sentence @something2024.
 
-With supplement @something2024[p.~3];.
+With supplement @something2024[p.~3].
 
 And just the year #cite(<something2024>, form: "year");.
 
diff --git a/test/typst-reader.native b/test/typst-reader.native
--- a/test/typst-reader.native
+++ b/test/typst-reader.native
@@ -4980,13 +4980,10 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Div
-                              ( "" , [] , [] )
-                              [ Para
-                                  [ Math
-                                      InlineMath
-                                      "f:{\\mathbb{R}} \\rightarrow {\\mathbb{R}}"
-                                  ]
+                          [ Para
+                              [ Math
+                                  InlineMath
+                                  "f:{\\mathbb{R}} \\rightarrow {\\mathbb{R}}"
                               ]
                           ]
                       , Cell
@@ -5004,13 +5001,8 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Div
-                              ( "" , [] , [] )
-                              [ Para
-                                  [ Math
-                                      InlineMath
-                                      "9.8\\ \\text{ m/s}^{2}"
-                                  ]
+                          [ Para
+                              [ Math InlineMath "9.8\\ \\text{ m/s}^{2}"
                               ]
                           ]
                       , Cell
@@ -5018,18 +5010,14 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Div
-                              ( "" , [] , [] )
-                              [ Para
-                                  [ Code
-                                      ( "" , [] , [] )
-                                      "\"9.8\" \"m/s\"^2"
-                                  , Space
-                                  , Link
-                                      ( "" , [ "ref" ] , [] )
-                                      [ Str "[tricky]" ]
-                                      ( "#tricky" , "" )
-                                  ]
+                          [ Para
+                              [ Code
+                                  ( "" , [] , [] ) "\"9.8\" \"m/s\"^2"
+                              , Space
+                              , Link
+                                  ( "" , [ "ref" ] , [] )
+                                  [ Str "[tricky]" ]
+                                  ( "#tricky" , "" )
                               ]
                           ]
                       ]
@@ -5238,13 +5226,10 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Div
-                              ( "" , [] , [] )
-                              [ Para
-                                  [ Math
-                                      InlineMath
-                                      "\\sigma^{2} = \\sqrt{{\\sum(x_{i} - \\mu)}^{2}/N}"
-                                  ]
+                          [ Para
+                              [ Math
+                                  InlineMath
+                                  "\\sigma^{2} = \\sqrt{{\\sum(x_{i} - \\mu)}^{2}/N}"
                               ]
                           ]
                       , Cell
@@ -5264,13 +5249,10 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Div
-                              ( "" , [] , [] )
-                              [ Para
-                                  [ Math
-                                      InlineMath
-                                      "E(X) = \\mu_{X} = \\sum(x_{i} - P\\left( x_{i} \\right))"
-                                  ]
+                          [ Para
+                              [ Math
+                                  InlineMath
+                                  "E(X) = \\mu_{X} = \\sum(x_{i} - P\\left( x_{i} \\right))"
                               ]
                           ]
                       , Cell
@@ -5366,29 +5348,26 @@
           , Str "."
           ]
       , Para []
-      , Div
-          ( "" , [] , [] )
-          [ HorizontalRule
-          , Para
-              [ Str "johanvx"
-              , Space
-              , Str "("
-              , Underline
+      , HorizontalRule
+      , Para
+          [ Str "johanvx"
+          , Space
+          , Str "("
+          , Underline
+              [ Underline
                   [ Underline
-                      [ Underline
-                          [ Link
-                              ( "" , [] , [] )
-                              [ Str "https://github.com/johanvx" ]
-                              ( "https://github.com/johanvx" , "" )
-                          ]
+                      [ Link
+                          ( "" , [] , [] )
+                          [ Str "https://github.com/johanvx" ]
+                          ( "https://github.com/johanvx" , "" )
                       ]
                   ]
-              , Str ")"
-              , Space
-              , Str "\8202\8193"
-              , Space
-              , Str "2023-05-22"
               ]
+          , Str ")"
+          , Space
+          , Str "\8202\8193"
+          , Space
+          , Str "2023-05-22"
           ]
       ]
   ]
diff --git a/test/writer.djot b/test/writer.djot
--- a/test/writer.djot
+++ b/test/writer.djot
@@ -533,7 +533,7 @@
 {#latex}
 # LaTeX
 
-- 
+-
 - $`2+2=4`
 - $`x \in y`
 - $`\alpha \wedge \omega`
diff --git a/test/writer.typst b/test/writer.typst
--- a/test/writer.typst
+++ b/test/writer.typst
@@ -20,6 +20,14 @@
   stroke: none
 )
 
+#show figure.where(
+  kind: table
+): set figure.caption(position: top)
+
+#show figure.where(
+  kind: image
+): set figure.caption(position: bottom)
+
 #let content-to-string(content) = {
   if content.has("text") {
     content.text
diff --git a/xml-light/Text/Pandoc/XML/Light/Output.hs b/xml-light/Text/Pandoc/XML/Light/Output.hs
--- a/xml-light/Text/Pandoc/XML/Light/Output.hs
+++ b/xml-light/Text/Pandoc/XML/Light/Output.hs
@@ -35,7 +35,7 @@
 import Text.Pandoc.XML.Light.Types
 
 --
--- duplicates functinos from Text.XML.Output
+-- duplicates functions from Text.XML.Output
 --
 
 -- | The XML 1.0 header
