All notable changes to md2any will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
A math-rendering and robustness release. The native math layout engine gained font-aware metrics, real bold, and properly-centred accents; custom OTF/CFF fonts now embed correctly in PDF; and a broad rendering sweep fixed several overflow / silent-drop bugs.
- Font-aware math metrics. The math layout engine now measures glyph
advances against the face that will actually render them (the embedded PDF
font, or bundled DejaVu for SVG/PNG) via a
GlyphMetricsprovider, so reserved width matches drawn glyphs — equations stay aligned under--pdf-font, not just with the default font. - Math bold.
\mathbf,\textbf,\boldsymbol,\bm,\pmb,\mathbfitnow render in real bold weight (PDF/SVG/PNG); previously a no-op, and\boldsymbolleaked literal source. \dfrac/\tfrac/\cfracand\mid/\vert/\Vertare now recognised (previously leaked literal source).- GFM table column alignment (
:---,:---:,---:) is honoured in PDF, SVG, PNG, and HTML. - Inline
<svg>…</svg>blocks are now rasterised and embedded instead of being silently dropped.
- OTF/CFF font embedding. PDF now emits
CIDFontType0+FontFile3for CFF/OpenType faces (e.g. STIX Two Math) instead of mislabelling them as TrueType, which strict readers rejected. - Math accents (
\bar,\hat,\vec,\tilde,\dot,\ddot) are drawn as centred geometry rather than zero-advance combining marks, which had landed at the glyph's right edge (and differently per font — e.g.\bar{d}rendered as "đ"). - Long titles no longer overflow. Title slides (all layouts) and section dividers auto-shrink to fit; wrapped content-slide headings push the body and underline down instead of overprinting them.
- Nested blockquotes (
>/>>/>>>) keep every level instead of rendering only the deepest. - Long unbreakable tokens (URLs, hashes, CamelCase) hard-break to wrap
instead of overflowing the slide edge (PDF/SVG); HTML uses
overflow-wrap. - Tabs in code expand to 4-column tab stops instead of rendering as missing-glyph boxes.
{width=N%}> 100 clamps instead of leaking the literal attribute text.:::columns handle leading/trailing/duplicate dividers (the fence form::: … ::: … :::) instead of dumping all content into the right column.- Task-list items show only their checkbox, not a checkbox and a bullet (PDF/SVG/HTML/PPTX).
- Display-math pagination weights generated equation images by their real aspect ratio, so a one-line equation no longer splits onto a near-empty continuation slide.
- Right-to-left text uses
direction: rtlfor right-alignment but does not perform full Unicode bidi reordering of mixed LTR/RTL runs. - CJK and colour-emoji glyphs require
--cjk <font>for PDF (the bundled DejaVu face does not cover them). - Table column alignment is not yet applied to the editable Office/ODF outputs (pptx/odp/odt/docx).
A feature-and-fix-heavy follow-up to the initial release. New CLI surface for non-Latin scripts, presenter workflows, debugging; richer per-slide control over layout and images; and a remote-image cache with retry, placeholders, and stress testing.
--cjk <PATH>— embed a CJK font (TTF / TTC / OTF) in PDF output as a per-character fallback for codepoints DejaVu doesn't cover. Subsets to just the glyphs the deck uses so a 20 MB Noto CJK source typically contributes only kilobytes to the output PDF.--with-notes— produce a presenter-notes PDF: one A4 page per slide with the slide thumbnail on top and the speaker notes below. Mutually exclusive with--handout.--outline— parse + paginate, then dump a one-line-per-slide outline (page, kind, block summary, notes/bg flags) and exit. Useful for debugging pagination and pasting into bug reports.md2any doctor— probe the environment: optional CLIs (dot,mmdc,plantuml,libreoffice), build feature flags, bundled font count, resolved remote-image cache directory.md2any licenses— print the embedded font licence notice. The DejaVu / Bitstream Vera / Arev licences require the notice to travel with the font programs; this command surfaces it for binary-only distributions.- Per-slide layout directives —
<!-- layout: image-left -->and<!-- layout: image-right -->split a slide with one image + text into a two-column layout, reusing the existing:::column renderer. - Image sizing — Pandoc-style
{width=N%}attribute resizes the image to N% of the column. Honoured by PDF, PPTX, and ODP slide renderers. - Remote image cache — http(s) image URLs fetched at build time are
cached under the platform cache directory and reused on subsequent
renders. Controls:
--remote-image-cache <PATH>,--no-remote-image-cache,--remote-image-user-agent <STRING>. - Retry on transient HTTP failures — 408 / 429 / 500 / 502 / 503 /
504 and network errors are retried up to three times with capped
exponential backoff (max 30 s), honouring
Retry-Afterin both delta-seconds and HTTP-date forms. - Placeholder substitution on image failure — any image load failure
(network, 404, garbage, empty body, payload over the 20 MB cap, broken
SVG, missing local file) substitutes a red "Image failed to load"
placeholder containing the URL and error, prints a one-line stderr
warning, and the render completes. CI pipelines can grep stderr for
warning: image failedto surface the issue without failing the build. - PDF font subsetting — every PDF embeds only the glyphs the deck actually references, not the full 3 MB face. A typical talk-sized deck lands under 200 KB even with code, tables, and the full Greek / math toolbox.
- SVG image support —
rasterises viaresvgandtiny-skiaat 192 DPI (2× retina) using the bundled DejaVu fonts so text renders identically regardless of the build machine's installed fonts. Gated on thesvgfeature (default on). - Three new example decks under
examples/:special-relativity.md— math, history, tables, quotes, three hand-authored SVG diagrams (CC0)sorting-algorithms.md— code-highlighting torture test across Python, Rust, COBOL, JCL, PL/I; complexity tablesperiodic-table.md— 14 tables, scientific notation, Unicode superscripts / subscripts / element symbols
- Demo bundle —
examples/demo.{pptx,odp,pdf,docx,odt}regenerated and committed so new users can inspect output without installing. - Integration stress test —
tests/cache_stress.rs(gated behindcargo test -- --ignored) drives 24 assertions against a local HTTP server covering cold fetch, warm cache, URL normalisation, cache disable, retry on 503, oversize cap, garbage / empty / concurrent scenarios. Skips cleanly when Python 3 / curl / the compiled binary are missing. - 10 image unit tests + 4 parser regression tests for
fnv1a64, URL normalisation,Retry-Afterparsing (both forms), retry-delay cap, andextract_image_attrsUTF-8 handling.
HELP.mdrewritten and expanded to 136 slides. New sections: per-construct math reference (12 slides), full--theme-filereference with colour / font / syntax tables, compatibility matrix across viewers, known limitations with "not Pandoc / not Quarto" positioning, bundled font licence pointer, showcase section exercising every feature.- Pagination algorithm overhaul:
- Width-aware line-wrap estimates — portrait aspects no longer overestimate body line capacity.
- Chrome-aware budget — title bar + footer subtracted before scaling so A4 portrait and 9:16 don't inflate the per-line allowance.
- Code-block per-line weight (0.7) — long flag listings no longer split across near-empty pages.
- Table chunking — long reference tables auto-split with the header row repeated on each continuation slide.
- Orphan-lead carry — single lead-in paragraphs that would otherwise sit alone get pulled forward to the next page.
- CI —
cargo fmt --checkis now a required job (was advisory); clippy stays advisory for v0.2.0. - Theme overlay validation —
--theme-filerejects non-hex colour values with a clear error rather than corrupting downstream XML.
- UTF-8 mojibake in body paragraphs. A byte-cast in
extract_image_attrsturned every multi-byte UTF-8 character (Greek letters, em-dashes, math operator output) into Latin-1 garbage. The manual's Math reference tables andRenders as:paragraphs were affected. Regression test added. - Title-slide subtitle overlap. When the title wrapped to a second line, the subtitle / author block was positioned assuming a one-line title and rendered through the wrapped text. Fixed in all four layouts (Clean / Studio / Frame / Bold).
- Ordered-list bullet overlap. Items numbered 10 and above overflowed the fixed 0.33" bullet gutter, so the period rendered beneath the first letter of the body text. Gutter now measured per item.
- Image weight underestimate.
Block::Imageweighted at 6.0 was letting text-after-image slides overflow the page footer; bumped to 13.0 to match the renderer's 65% slide-height cap. - Background propagation through
(cont.)slides — when pagination split a slide with<!-- bg: ... -->, the continuation pages lost the background. - PlantUML diagrams silently producing missing-file references —
plantuml -pipewrites to stdout (the-oflag is ignored in pipe mode); we were discarding stdout. Now captured to the output path. - DOCX + ODT mixed-list rendering. A list mixing ordered and
unordered items was coerced to one style for the whole block. DOCX now
picks
numIdper item; ODT closes and re-opens the<text:list>wrapper when the ordered flag flips, since ODF carries the style on the wrapper. (cont.)title suffix on the first emitted page — orphan-carry could swallow the initial split attempt; the page that finally shipped was still page 1 of the slide and shouldn't carry the suffix.- Section-slide
kicker_yunderflow —h - 2_600_000for slides taller than 2.7 in is fine; for shorter custom aspects it would underflowu32and panic. Now usessaturating_sub. - Remote-image cache hygiene — garbage, empty, or oversize responses
no longer get written to the cache.
sniff()runs before the cache write, so a 200 OK with HTML body or zero bytes is rejected up front. - Remote-image truncation undetected — the 20 MB cap was a silent truncation; oversize payloads now error loudly (or fall through to the placeholder).
--watchdoc string — claimed it watched referenced images, which it doesn't. Trimmed to match reality.
- Constants extracted:
theme::IMAGE_MAX_HEIGHT_FRACTION_*(shared by PDF / PPTX / ODP renderers + pagination weight),theme::LONG_LIST_THRESHOLD(shared across 6 sites). - Dead code removed:
pdf::rewrite_content_glyphs,pdf::build_tounicode_cmap_remapped,odt::_injection_anchor. ~98 lines deleted across the cleanup pass. - Module docs updated:
image.rs,paginate.rs,layout.rs,main.rsall have module-level//!headers;font.rsandimage.rsheaders rewritten to reflect their current breadth. - All test totals: 14 image unit + 14 renderer + 4 snapshot + 1 doctest
- 1 ignored integration test, all pass.
0.1.0 — 2026-05-23
Initial release.
- PowerPoint (
.pptx) — native OOXML, editable in PowerPoint, Keynote, LibreOffice Impress, Google Slides. - OpenDocument Impress (
.odp) — native ODF, smaller than PPTX, editable in LibreOffice Impress, Keynote, Slides. - PDF (
.pdf) — PDF 1.7, pure Rust, no external library. Embeds the bundled DejaVu Sans family (regular / bold / oblique / bold-oblique / mono) for broad Unicode coverage including Greek, math operators, sub/superscripts. - Microsoft Word (
.docx) — native OOXML document, flowing text with proper heading / list / table / image / hyperlink markup. - LibreOffice Writer (
.odt) — native ODF document.
- Two themes:
lightanddark. - Four layouts:
clean,studio,frame,bold. - Custom theme files via
--theme-file <path>.yaml— override colours, fonts, and sizes on top of the base theme.
- Presets:
16:9,4:3,9:16,a3,a4,a4-landscape,a5,letter,letter-landscape,legal,tabloid. - Custom:
WxH[unit]withpx(default at 96 DPI),mm,cm,in,pt,emu.
- Paragraphs, headings (H1–H6), bulleted + numbered nested lists, tables,
block quotes, fenced code blocks, inline
code/ bold / italic /strikethrough/ [links]. - Side-by-side columns via
:::separator. - GitHub-flavoured tables.
- Syntax highlighting for 20 languages including Rust, Python, JavaScript, TypeScript, Go, C/C++, Java, Ruby, Bash, SQL, JSON, YAML, TOML, HTML, XML, CSS, and mainframe languages (COBOL, JCL, REXX, PL/I, HLASM, DB2).
- LaTeX-flavoured math (
$inline$,$$display$$) → Unicode at parse time. - Diagrams:
```dot/```mermaid/```plantumlshell out to the matching CLI tool if installed. - Footnotes (
[^id]) with definitions collected at slide bottom. - Speaker notes via
<!-- notes: -->HTML comments. - Per-slide background image via
<!-- bg: path -->. - Auto Table-of-Contents slide via
toc: truein front-matter.
--watch: rebuild on file change.--serve [--port N]: localhost HTTP preview with hot reload (no Chromium).--check: lint mode — exit code 2 on warnings, no file written.md2any new <path>: scaffold a starter deck.- Multi-file input: concatenate several markdown files; first one's front-matter wins.
- Stdin input via
-.
--handout 2|4|6: N-up A4 portrait PDF for printing.--logo <path>: render a footer logo on every content slide.- Slide transitions:
fade,push,wipe,cover,split(PPTX, ODP, PDF). - RTL support via
direction: rtl(PPTX, ODP). - Clickable hyperlinks in PDF, PPTX, ODP, DOCX, ODT.
- Self-documenting:
--help-md,--help-pptx,--help-odp,--help-pdf,--help-docx,--help-odt.
- Public Rust crate. See
docs.rs/md2anyfor the API. - All renderers callable independently of the CLI.
- Snapshot tests over the parser → paginator pipeline.
- 30-slide deck → PPTX in ~1 ms, PDF in ~5 ms on commodity x86-64.
- 100-slide deck → PPTX in ~3 ms, PDF in ~12 ms.