Skip to content

Releases: JavaPerformance/md2any

v0.2.0

24 May 18:56

Choose a tag to compare

[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 ![alt](src){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-After in 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 failed to 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![alt](path.svg) rasterises via resvg and
    tiny-skia at 192 DPI (2× retina) using the bundled DejaVu fonts so
    text renders identically regardless of the build machine's installed
    fonts. Gated on the svg feature (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 tables
    • periodic-table.md — 14 tables, scientific notation, Unicode
      superscripts / subscripts / element symbols
  • Demo bundleexamples/demo.{pptx,odp,pdf,docx,odt} regenerated
    and committed so new users can inspect output without installing.
  • Integration stress testtests/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-After parsing (both forms), retry-delay cap,
    and extract_image_attrs UTF-8 handling.

Changed

  • HELP.md rewritten and expanded to 136 slides. New sections:
    per-construct math reference (12 slides), full --theme-file reference
    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.
  • CIcargo fmt --check is now a required job (was advisory);
    clippy stays advisory for v0.2.0.
  • Theme overlay validation--theme-file rejects 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_attrs turned every multi-byte UTF-8 character (Greek
    letters, em-dashes, math operator output) into Latin-1 garbage. The
    manual's Math reference tables and Renders 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::Image weighted 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 -pipe writes to stdout (the -o flag 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 numId per 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_y underflowh - 2_600_000 for slides
    taller than 2.7 in is fine; for shorter custom aspects it would
    underflow u32 and panic. Now uses saturating_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).
  • --watch doc 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.rs and image.rs headers
    rewritten to reflect their current breadth.
  • All test totals: 14 image unit + 14 renderer + 4 snapshot + 1 doctest
    • 1 ignored integration test, all pass.