Releases: JavaPerformance/md2any
Releases · JavaPerformance/md2any
v0.2.0
[0.2.0] — 2026-05-24
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.
Added
--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), honouringRetry-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 viaresvgand
tiny-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 behind
cargo 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.
Changed
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.
- Width-aware line-wrap estimates — portrait aspects no longer
- 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.
Fixed
- 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
picksnumIdper 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.
Internal
- 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.rs
all 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.