Skip to content

0.7.5-rc5

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Aug 13:48
· 106 commits to main since this release
3b17da7

Installation

latexml-oxide ships as a single self-contained executable. It also calls a few external tools when they are on your PATH:

  • a TeX distribution (TeX Live, MacTeX, or MiKTeX) — for the packages, classes, and fonts your documents load.
  • graphics tools (ImageMagick, Ghostscript, MuPDF, Poppler, dvipng, dvisvgm) — to convert figures.

Per-platform install instructions are in the README Installation guide.

Download the asset for your platform (each has a matching .sha256 to verify file integrity):

  • Linux (x86-64) — latexml-oxide_0.7.5-rc5-1_amd64.deb, or the portable latexml-oxide-0.7.5-rc5-x86_64-unknown-linux-gnu.tar.gz
  • Linux (aarch64 / arm64) — latexml-oxide_0.7.5-rc5-1_arm64.deb, or the portable latexml-oxide-0.7.5-rc5-aarch64-unknown-linux-gnu.tar.gz
  • macOS (Apple Silicon) — latexml-oxide-0.7.5-rc5-aarch64-apple-darwin.tar.gz
  • macOS (Intel) — latexml-oxide-0.7.5-rc5-x86_64-apple-darwin.tar.gz
  • Windows (x86-64) — latexml-oxide-0.7.5-rc5-x86_64-pc-windows-msvc.zip (unzip; latexml_oxide.exe is self-contained, no installer)

Changelog

[0.7.5] (Rhai binding API; bibliography content recovery; wider math coverage; large-document memory; default-CSS sync; ar5iv corpus fixes)

  • A conversion always writes <jobname>.latexml.log — Perl latexmlc
    parity: with --log unset the log lands in the working directory
    (latexml.log for literal input), replacing any stale copy, and still
    ends with the canonical Status:conversion:N verdict line. rc5's first
    artifacts wrote no log at all unless --log was passed.

  • A memory Fatal now says what to do — the cooperative-fuse message
    names the 75% fuse and the --max-memory ceiling it derives from, and
    advises raising it: hitting the fuse is a known need of large documents
    (peak scales with macro expansion and math density, not source bytes),
    not an anomaly. The run then ends with Info:memory:peak — the
    kernel-tracked peak RSS, the honest lower bound on what THIS document
    needs — in stderr and just above the log's status tail. Clean runs stay
    quiet.

  • Large-document logs got humane — disk staging is by design, so the
    alarming "spilled" wording is gone ("459,579 segment(s) staged to disk");
    the [N] math-progress markers count conversion-wide instead of
    restarting at [1] in every streamed fragment; and Scan: DBStatus:
    backs off exponentially (logs when the object count passes a power of
    two), ~20 lines where a book-scale split wrote 115k+.

  • Page rendering runs in parallel worker processes — after the
    document-wide scan, N workers (LATEXML_RENDER_JOBS, self-clamped to the
    machine's actual memory headroom) render page ranges concurrently,
    byte-identical to the serial path. Measured on the 131 MB witness:
    post-processing 37:31 → 12:17 (3.05×) at 8 workers on a fresh parent;
    the full single-invocation .tex → .html run 1:24 → 1:00:34 with the
    self-sized fleet on a 31 GB laptop, exit 0, all 115,519 pages. The
    ObjectDB behind it gained a SQLite store (Perl --dbfile heritage,
    WAL-concurrent, sqlite3-CLI-inspectable).

  • A book-scale document converts .tex → .html in ONE invocation on a
    31 GB laptop
    — the 131 MB witness end-to-end in 1:17 at a 22.95 GiB peak:
    115,519 pages, exit 0, zero errors. The render loop is now memory-flat
    (~3 KB/page, was ~150 KB/page): Node::get_namespaces in the libxml
    binding leaked its xmlGetNsList array on every call since the crate's
    beginning — fixed upstream in libxml 0.3.21, which this build requires.

  • The run's LAST line is the combined core+post verdict — a core Fatal
    stays the final word even after thousands of per-page post lines
    (Conversion failed: …, exit 1), --log files and archive status
    members end with the canonical Status:conversion:N (the max of the core
    and post phases), so downstream frameworks derive severity from one line.

  • The final tally counts every printed diagnostic, losslessly — the
    same 131 MB run used to log 12,105 Warning: lines and report
    "2 warnings": raw log-crate emissions (chief among them the math
    parser's) bypassed the counters entirely. All diagnostics now flow
    through ONE Perl-shaped vehicle (count + emit + caps + taxonomy), raw
    log-crate diagnostics are lint-banned in the workspace, Fatal: lines
    are never suppressed at any verbosity, and what a reader greps from the
    log is what the verdict reports.

  • --max-memory unset now follows actual machine headroom — 90% of
    AVAILABLE RAM at startup (cgroup-capped, 64 GiB max) instead of half of
    total, so an idle machine converts large documents by default while a busy
    one still self-limits; hand-tuning the ceiling is no longer needed.

  • Book-scale split documents render on commodity RAM — post-processing no
    longer parses the whole document as one DOM before splitting: past 1 GiB of
    core XML, a streaming front-end spills each page as the file streams by and
    scans them one at a time, byte-identical to the whole-DOM split. First
    witness across the line: a 131 MB book's 2.68 GB core XML → 115,519 pages /
    11 GB of HTML in 37½ minutes at 17.4 GB peak on a 31 GB laptop — previously
    an out-of-memory kill with zero pages written.

  • Multi-gigabyte post-processing inputs parse trustworthily — libxml2's
    hard limits (absent XML_PARSE_HUGE) silently corrupted any parse past
    ~1.4 GB (hundreds of thousands of phantom ID already defined errors for
    ids that occur exactly once) and killed it outright at ~1.7 GB. All
    post-processing parses now lift those limits.

  • A 2 GiB-plus core→post handoff no longer dies on libxml2's i32 buffer
    ceiling
    — the single-invocation .tex → .html flow spills the handoff to
    disk beside the destination and streams it.

  • Split pages inherit xml:lang from their ancestors, as Perl does — the
    copy had been silently skipped (namespaced-attribute read).

  • The runtime (Rhai) binding API reaches feature-parity with the compile-time
    macros
    — definition lookup and digest/construct hooks, the same flexary
    option bags, definitions registered from a running body, external commands,
    and the full diagnostics surface.

  • A .rhai binding can parse and manipulate XML/(X)HTML
    document.insertXML splices a parsed subtree, ParseXML exposes the parser
    on its own, namespaces resolve by URI, and malformed markup is rejected
    outright rather than silently salvaged.

  • .rhai bindings reach the same document XML surface the compile-time
    bindings do
    — XPath query, element insertion, and structural editing, each
    under its Perl name.

  • A failing .rhai binding no longer costs the whole document — each
    binding kind degrades to a neutral result and reports a clean Error:.

  • Default HTML styling re-synced to vanilla LaTeXML.css — justified text,
    \underline/\overline and verbatim no-wrap are back; .htm infers HTML5.

  • Adjacent display equations are vertically separated — the bundled CSS
    now carries TeX's display skips (1em, collapsing with paragraph margins),
    so back-to-back \[…\] displays no longer render touching (issue 473).

  • Verbatim renders true to the source — fancyvrb Verbatim lines each
    keep their own row, indentation and spacing (the binding's per-line
    ltx_verbatim class had been dropped in porting), and the bundled CSS
    replaces vanilla's nowrap — which collapsed a plain {verbatim} block
    to a single line — with true pre whitespace (issue 431).

  • A stale or empty stylesheet in the output directory is overwritten instead
    of leaving the page unstyled.

  • Split output (--splitat) styles every page and carries the document date
    — a shared post-processing XPath defect had been dropping relative-path
    lookups (also repairing split navigation and cross-reference/glossary
    resolution).

  • Split-page navigation links reach Perl parity — the full <link rel=…>
    head set with relation types and full-breadcrumb titles.

  • The generator identifier spells out the product name and version, matching
    Perl.

  • A standalone document class's options are no longer mis-loaded as packages.

  • A package loaded in a subfile's preamble keeps its definitions — the group
    around the child's preamble used to discard them.

  • Named scopes are tracked correctly again — a deactivated scope could never
    be re-activated, and a second deactivation popped the same bindings twice.

  • \includefrom/\subincludefrom no longer drop their file in silence.

  • The cortex worker builds without the runtime-bindings feature — a
    Rhai-free conversion binary for the fleet.

  • File resolution can no longer die silently — a conversion could run with no
    kpathsea backend at all and say nothing, reporting only Can't find TeX file X
    (fixed in kpathsea 0.3.4
    plus a subprocess fallback here).

  • Every conversion log records the file-resolution backendin-process,
    subprocess kpsewhich, or unavailable.

  • Dingbat and symbol fonts selected by family render their glyphs instead
    of the OT1 slot's text character — bbding's \XSolidBrush was silently
    coming out as % and \Checkmark as !, inverting whole comparison-table
    columns at zero reported errors. An unrecognized font family, series or
    shape is also now announced once per document rather than once per font
    switch.

  • ar5iv corpus fixes (2026-07 issue sprint) — xcolor dvipsnames as a
    global class option, \sidecaptionvpos, verbatim \newtcblisting bodies,
    agujournal2019 end-matter, blkarray (recovering papers that hit
    OOM/timeout), scrartcl \titlehead, and frontmatter bindings for
    fairmeta, selfevolagent and openmoss.

  • silence and the bundled arXiv preprint styles no longer leave their
    commands undefined
    \WarningFilter and friends stopped spilling their
    filter text into the page, and \keywords renders again. The silence
    binding also restores diagnostics the real package's \ErrorsOff was
    swallowing.

  • ICASSP/Interspeech papers keep their Index Terms and author blocks — the
    spconf style's keywords environment and \twoauthors now become real
    keyword and creator frontmatter instead of an undefined-environment error
    (the largest such cluster in the arXiv sandbox corpora).

  • \usepackage{xparse} no longer destroys the \c cedilla accent — any
    document loading xparse or expl3 rendered Fran\c cois as "Fran0cois",
    silently and with no error reported.

  • A deferred package-load miss no longer poisons a later raw load.

  • Perl .ltxml bindings are never read as TeX — file resolution used to hand
    back the .ltxml (Perl source) and tokenize it, so raw-loading a package that
    ships one (e.g. sTeX) emitted spurious errors; a missing binding now falls
    through to the raw .sty, matching pdflatex.

  • \usepackage{X} finds an X.sty.rhai runtime binding on $TEXINPUTS — a
    .rhai beside your document still overrides a compiled binding; one in a
    texmf tree only fills a gap.

  • cprotect is now supported\cprotect\section{\verb|…|} and
    \cprotect\footnote{\begin{verbatim}…\end{verbatim}} produce the verbatim
    title/footnote, with \cMakeRobust and \cprotEnv. Perl LaTeXML has no
    binding for this package.

  • The .rhai binding interface has a reference, generated from the live
    engine into cargo doc so it cannot drift from what is registered.

  • Bibliographies stop losing reference content.bib field markup
    (\url, \href, \emph) survived only as dead literal text, and eleven
    field kinds (howpublished, institution, address, edition, series,
    type, …) were emitted by no branch at all. Recovering that content means
    the fields are now interpreted, so they must also be given what a
    .bst-generated .bbl provides — the \providecommand{\url}… block, and
    a percent that stays literal (BibTeX has no comments, and % is routine in
    an encoded URL). Measured over a 30,079-document arXiv sample: 62 more
    documents convert cleanly, 44 fewer carry errors.

  • A reference's & % # _ are the characters the author typed — "Taylor &
    Francis" in a publisher used to report an error and print "Taylor Francis",
    a gene id AT1G01010_v2 came out as a subscript error, and a percent in an
    encoded URL commented out the rest of the entry. A .bib field's content is
    data, so all four are kept, while \emph{…}, $x_1+x_2$ and accents in the
    same field keep working.

  • A reference exported through HTML no longer shows &amp; for & — a
    doubly escaped ampersand (\&amp;) in a .bib title, journal or booktitle
    is decoded back to the one character the author wrote.

  • amsrefs \bib values digest as live TeX\MR{…} came out as literal
    characters and pages rendered empty.

  • MathReview / ZentralBlatt links are synthesizedmrnumber/zblno
    produced no link at all — and an entry carrying both date and year no
    longer emits a duplicate date.

  • An accent in a MathSciNet / Zentralblatt reviewer name survives
    MRREVIEWER = {Fran\c cois Digne} came back as \ccois, an undefined
    macro, because flattening a token list drops the space that terminates a
    control word. That flattening had already cost author names and two column
    types, so the tokenizing entry points now take a dedicated TeX-string type
    and it no longer compiles anywhere in the tree.

  • A biblatex .bbl carrying two \datalist blocks no longer hangs the
    conversion
    on a self-referential \let.

  • A biber \missing{key} is a named warning, not an undefined-command error.

  • A font-encoding text symbol inside a \cite key or package option no
    longer hangs the conversion
    — an accented character reached the encoding
    dispatch while the font encoding was the stay-ASCII one, and the fallback
    could not terminate under pure expansion.

  • The author-year citation label uses the short author form — a
    collaboration paper's label ran to 5104 characters and displaced the entry.

  • More math parses — fences split by TeX's null delimiter, bare operators
    used as operands (f(\cdot), (+)), and mixed relation/term comma lists.

  • $50,000$ reads as one number rather than list(50, 000) — US default;
    the European $1.234,56$ reading is unchanged.

  • Math in a section title survives into the table of contents and into any
    \ref to that section.

  • latexmlmath no longer empties a single-structure formula
    (\frac{1}{2}, \sqrt{2}).

  • siunitx complex numbers are faithful to Perl — the port had flattened away
    the imaginary-unit enrichment and mantissa brackets — plus the v3 command
    surface and the full \sisetup default set.

  • \lstinputlisting converts the requested snippetlastline=N used to
    unbalance the listing and swallow the rest of the document, and CRLF sources
    bled comment styling down the file.

  • Very large documents use far less memory — peak RSS 9.05 → 5.99 GB on a
    232K-line book, wall time unchanged.

  • A document far larger than RAM converts — the core stage builds and
    releases the document in fragments, spilling completed parts to disk, so
    peak memory follows fragment size rather than document size. A 131 MB
    5-million-line book that could not be converted at all now completes within
    a 48 GB budget (28.1 GB peak, 2.66 GB of XML). Output is byte-identical to
    the normal path.

  • Post-processing no longer holds the whole site in memory — pages are
    scanned once, then rendered and written one at a time, so peak memory
    follows a single page instead of the page count. A 40,201-page document
    that grew to 80 GB and wrote nothing now completes flat at 16 GB, and the
    stage is 2.5x faster (27:04 -> 10:48) with byte-identical output.

  • A query that cannot be answered is an error, not an empty result
    whole-document XPath silently returned "no matches" when libxml2 refused
    it, so on a large document nothing was cross-referenced, no MathML was
    generated, and a 0-byte page was written with a success exit code. Those
    queries are now answered by traversal, and a genuine failure says so.

  • Math parsing no longer frees nodes it is still using — discarded
    subtrees are released at a formula boundary rather than at the discard
    site. The use-after-free behind this crashed 22 papers in a
    30,000-document corpus run, and its formulas were real content, not
    garbage. Releasing per formula also uses less memory than before the
    fix: a 19.8 MB book peaks 7% lower streamed and 9% lower on the plain
    path.

  • Very large documents convert 2.1x faster — the 131 MB / 5-million-line
    book drops from 70 to 33 minutes, byte-identical output. A memory-pressure
    trigger had been fragmenting the work into 459,000 tiny segments (and the
    intermediate spill text was half indentation, written only to be deleted);
    the work now flows in ~6,000 sensible pieces, serialized flat.

  • Conversion logs shrink ~100x on such runs (3.1 million lines → 26,000):
    every message carried a spurious blank line, and per-segment progress now
    reports at milestones instead of three lines per segment.

  • Streamed conversions report where the time went — per-phase wall time
    (digest, build, math parse, …) lands in telemetry and two summary lines;
    previously the streamed path reported no phase timing at all.

  • A document whose XML exceeds 2 GiB post-processes instead of failing
    libxml2's in-memory parser takes its length as a 32-bit int, so the 131 MB
    book's 2.68 GB core XML died at the core→post handoff (Document too large for i32) and echoed raw XML into the .htm. Oversized handoffs now spill
    to a temp file beside the destination and parse through the streaming file
    reader.

  • A \Description on a table is expected, not a defect — acmart asks for
    one on every float and a table has no image, so attaching the description
    to the table is reported as information rather than a warning that demoted
    otherwise-clean papers.

  • A memory limit set by a container is honoured — the ceiling was derived
    from the host's RAM, so a memory-limited container chose a budget it could
    never reach and was killed by the kernel instead of stopping gracefully.

  • Post-processing stops gracefully rather than being killed — it had no
    cooperative memory check at all, so an oversized run died at the hard
    ceiling with nothing written; pages that finish are now kept.

  • --max-memory is the budget, and everything follows from it — the
    graceful-Fatal fuse, the point where spilling begins, and the fragment size
    are all derived, so no two memory settings can contradict each other. The
    default is now half of physical RAM (2 GiB floor, 64 GiB cap) rather than
    90 %, which on a 16 GB laptop had let one conversion reach 10.8 GiB before
    complaining. --max-memory=0 lifts the ceiling but still spills.
    --streaming forces fragmentation, --streaming=false forces the plain
    path, and large multi-file documents are recognised by their whole source
    tree instead of the main file's size alone.

  • --max-memory is the single memory knob, 0 disables limiting entirely,
    and no environment variable can countermand it. The stomach's box-list
    ceilings now ride it too — they were fixed constants, so --max-memory=0
    still Fatal'd on a 3.2 GB budget no flag could raise, and a very large
    document could not be converted at all.

  • A Fatal is reported as a Fatal — the stomach's runaway guards raise
    outside the Fatal! macro, so their diagnostic never reached the status
    tally: a run could print Fatal: and still sign off as Conversion complete: No obvious problems with a success exit code. Recovery of the
    already-digested content is unchanged (and still runs through
    post-processing, so a partial document is written) — only the verdict is
    corrected.

  • Environments report their true source extent — the locator spans through
    the matching \end instead of collapsing to the \begin.

What's Changed

  • fix(bibliography): one runaway .bib field no longer empties the whole bibliography by @dginev in #395
  • feat(bibliography): convert a raw .bib through the engine, not a string parser by @dginev in #396
  • fix #386: build XML through libxml, not string concatenation by @dginev in #397
  • fix(bibliography): revert author names with UnTeX — space-form accents were becoming undefined macros by @dginev in #399
  • fix(bibliography): an abstract no longer takes the whole bibliography with it by @dginev in #400
  • docs(release): an RC is published but stays a prerelease by @dginev in #402
  • docs(bibliography): record the two-regime design tension by @dginev in #407
  • fix(bibliography): a blank line in a .bib field no longer starts a new bibliography item by @dginev in #406
  • fix(contrib): silence.sty and the bundled arXiv styles no longer leave commands undefined by @dginev in #404
  • docs(bibliography): the data-vs-TeX seam is chosen by blast radius by @dginev in #408
  • fix(bibliography): a .bib field's specials are data — the two-treatment design by @dginev in #403
  • fix(bibliography): a reviewer's accented name is a name, not an undefined macro by @dginev in #411
  • docs(sync-status): rationalize .bst support as R9-BST by @dginev in #412
  • docs(sync-status): retract the .bst-vendoring premise behind R9-BST by @dginev in #414
  • feat(core): a TeXString type, so a flattened Tokens cannot reach the tokenizer by @dginev in #413
  • A .bib field's ^ is data; mathscinet.sty gets a real binding by @dginev in #415
  • A .bib @Preamble already executes; guard it, and keep the \cprime stub by @dginev in #417
  • fix(bibliography): digest the cited entries, not the whole .bib library by @dginev in #416
  • fix(bibliography): an unmatched $ in a .bib field is currency, not math by @dginev in #418
  • fix(bibliography): \cprime belongs to mathscinet.sty, not to every document by @dginev in #419
  • docs: reconcile the bibliography-campaign docs with what actually landed by @dginev in #420
  • fix(post): a graphics test asserting on subprocesses must bypass the cache by @dginev in #422
  • fix(hyperref): \href in a Semiverbatim argument no longer expands forever by @dginev in #423
  • An \ext@arrow mkern amount may be braced; the rest of the unclosed-math cluster is Perl's too by @dginev in #424
  • fix(expl3): loading xparse/expl3 silently destroyed the \c cedilla accent by @dginev in #425
  • A LaTeX kernel command before \documentclass no longer derails the document by @dginev in #426
  • spconf papers keep their Index Terms and author blocks by @dginev in #427
  • fix(engine): \meaning of a \chardef token reports the character, not "Register" by @dginev in #428
  • docs: make docs/README.md the authoritative doc index; trim CLAUDE.md resident context by @dginev in #429
  • feat(acmart): \Description becomes the figure's ARIA text alternative by @dginev in #430
  • perf(memory): share the font handle instead of deep-cloning it per box by @dginev in #432
  • fix(memory): guard the Build phase cooperatively, and keep what it built by @dginev in #434
  • feat(memory): derive the default ceiling from the machine, portably by @dginev in #435
  • docs: design for fragmented core-stage conversion (streaming XML) by @dginev in #433
  • docs: refresh the test baseline, and flag the coming fatal-rate discontinuity by @dginev in #437
  • docs(acmart): correct \Description comments left stale by the ARIA redesign by @dginev in #436
  • chore(audit): re-verify clang-sys at 1.9.1 and move the pin by @dginev in #439
  • perf(stomach): share the constructor definition instead of cloning it per whatsit by @dginev in #438
  • perf(core): one-borrow token read, duty-cycled cycle guard, pinned hot keys by @dginev in #441
  • fix(acmart): \Description describes the image, not the float by @dginev in #440
  • fix(preload): strip the bracket and .cls/.sty off the preload PI, as Perl does by @dginev in #442
  • docs: prune obvious/misleading standing instructions by @dginev in #443
  • fix(captions): give \fnum@ an empty group so an arg-taking hook cannot eat the caption's brace by @dginev in #446
  • fix(bibliography): AY is the abbreviated label, and four more MakeBibliography gaps by @dginev in #445
  • feat: streaming XML core — bounded-memory conversion for book-scale documents by @dginev in #448
  • Bibliography absence: 16 fixes, 291 known articles recovered and re-verified by @dginev in #444
  • fix(mathml): close the F17 pMML gaps, target MathML Core, chain post-scripts to any depth by @dginev in #447
  • feat: --max-memory is the budget, and everything follows from it by @dginev in #449
  • fix(fonts): a font selected by family must decode through that family's fontmap by @dginev in #450
  • fix(fonts): \char decodes with FontDecode semantics, in math as in text by @dginev in #452
  • fix(fonts): \DeclareMathAlphabet maps NFSS codes to abstract font properties by @dginev in #453
  • fix(fonts): sized delimiters scale to NOMINAL_FONT_SIZE, not a baked-in 10 by @dginev in #454
  • fix(fonts): AMSb's six negated relations were decoding to the empty string by @dginev in #455
  • fix(fonts): a multi-character fontmap slot survives \DeclareTextSymbol by @dginev in #456
  • docs: font-chain audit status, two pinned bugs, and the rule that prevents them by @dginev in #457
  • fix(post): bounded-memory page rendering, no query fails in silence, and a 2.5x faster post stage by @dginev in #451
  • ci: run tests with nextest, sharded 4 ways — the suite was serialized by @dginev in #458
  • fix(math): stop freeing mid-parse — a use-after-free behind 22 corpus fatals by @dginev in #459
  • fix(memory): the default ceiling respects a cgroup limit by @dginev in #461
  • fix(post): cooperative memory checks at the phase and page seams by @dginev in #462
  • perf(math): drain discards per formula — below the pre-fix memory baseline on both paths by @dginev in #460
  • release: 0.7.5-rc4 by @dginev in #463
  • fix(windows): MEMORYSTATUSEX — this arm had never compiled by @dginev in #464
  • ci(windows): compile the Windows arms when they change, and weekly by @dginev in #465
  • fix(windows): --max-memory was never enforced on Windows (+ 3 Windows-only lint/test gaps) by @dginev in #466
  • perf(streaming): 131 MB witness 70:29 → 32:56 — yield floor, flat spill, shared labels, phase telemetry by @dginev in #467
  • release: recut 0.7.5-rc4 with the large-document performance work by @dginev in #468
  • fix(streaming): shared conversion tail — ASF stats and source-map reach streamed runs by @dginev in #469
  • docs(perf): two closed investigations from the witness campaign by @dginev in #470
  • fix(post): 2 GiB-plus core→post handoff spills to disk instead of dying on libxml2's i32 limit by @dginev in #471
  • release: recut 0.7.5-rc4 with the 2 GiB post-handoff fix by @dginev in #472
  • feat(post): two-pass streaming split — book-scale documents render on commodity RAM by @dginev in #477
  • release: recut 0.7.5-rc4 with the streaming post-split by @dginev in #478
  • fix #473: adjacent display equations no longer render touching by @dginev in #479
  • fix(status): the combined verdict is the LAST word of every multi-phase run by @dginev in #480
  • feat(memory): default --max-memory follows actual machine headroom by @dginev in #482
  • fix #431: fancyvrb Verbatim renders true to the source by @dginev in #481
  • docs(audit): record libxml 0.3.21 re-verification; fix declared-license phrase by @dginev in #483
  • fix(diag): every printed diagnostic counts — lossless tally across all phases by @dginev in #484
  • release: recut 0.7.5-rc4 with the single-invocation witness milestone by @dginev in #485
  • docs(perf): witness phase shares; tally residual closed by @dginev in #486
  • feat(post): ObjectDB persists to SQLite — Perl --dbfile parity by @dginev in #487
  • fix(diag): robust cap latch in emit_error; post-cap divergence documented by @dginev in #488
  • docs(sync): rc4-recut 2605+2606 rerun triage round by @dginev in #489
  • feat(post): process-parallel page rendering — N workers over the spilled pages by @dginev in #490
  • fix(math): stale deferred-discard handles no longer straddle pooled-worker threads by @dginev in #491
  • fix(post): the render fleet sizes itself to actual headroom by @dginev in #492
  • release: cut 0.7.5-rc5 — parallel rendering, complete diagnostics, headroom defaults by @dginev in #493
  • fix(cli): a conversion always writes .latexml.log — Perl latexmlc parity by @dginev in #495
  • feat(diag): memory Fatals report what the document needs; disk staging loses the scary wording by @dginev in #496
  • release: recut 0.7.5-rc5 with the UAT log/diagnostics round by @dginev in #497

Full Changelog: 0.7.5-rc3...0.7.5-rc5