PeachPDF 0.9.8 Release Notes
Release date: August 2, 2026
Full changelog: v0.9.7...v0.9.8
Release tag: v0.9.8
PeachPDF 0.9.8 is a focused follow-up to 0.9.7's fragment-tree rewrite: three new font-variant-* properties backed by real OpenType GSUB substitution, a spec-compliance fix for vertical-align inheritance and bidi isolation, and a batch of pagination-correctness fixes — including one that could corrupt a document's content entirely at certain non-standard page sizes — found by continuing to exercise the fragment-tree architecture against real-world documents. It also closes out several performance regressions the rewrite introduced, cutting wall-clock and memory on large, deeply-paginated documents.
Highlights
Font variants — real GSUB substitution (#591)
- Three new CSS properties:
font-variant-caps(all 7 CSS Fonts Level 3 keywords),font-variant-numeric(8 keywords), andfont-variant-east-asian(9 keywords) — previously unrecognized entirely font-variant-ligatures: discretionary-ligatures/historical-ligaturesnow actually apply a font'sdlig/hligfeatures, andfont-feature-settingsnow actually activates the OpenType features it names — both previously parsed but had no effectfont-variant: small-caps/all-small-capsnow renders via a font's realsmcp/c2scsubstitution when available, instead of always synthesizing (upper-casing and shrinking);font-variantis now a real combinable shorthand over all five variant properties, resetting every longhand it doesn't mention (#591)
Pagination correctness (#570, #571, #580, #592, #595, #596)
- Fixed severe content corruption at non-round page heights (e.g. millimeter page sizes): a resumed multi-column/table restart could resolve its own new page's top edge to the page it was leaving rather than entering, freezing the page count and piling content on top of itself (#580)
- A table cell (or flex/grid item) whose own content genuinely spans multiple pages no longer repaints its border/background on every page it reaches — only the first fragment draws the top edge and only the last draws the bottom, matching an ordinary multi-page block (#595)
- A
rowspancell whose own content (not just its span) overflows its band is now fragmented instead of stretched across the boundary, and a flex/grid item's decoration now extends to cover content that outgrows its originally-pinned bounds (#571, #592) - Fixed an inline
string-set/used-page-name target accumulating stale, orphaned entries across multi-column reflow and page continuations, which could shift which value astring()picked up (#596) - Fixed a flex-column item anchored with
margin-top: autospuriously triggering a real page break during an internal measurement pass, splitting content across two pages when it fit on one (#570)
vertical-alignno longer inherits from an ancestor — CSS 2.1 §10.8.1 defines it asInherited: no;vertical-align: unsetnow correctly resets tobaselineinstead of copying the parent's value (#589)- The
dirglobal attribute and<bdo dir>now isolate their bidi resolution (unicode-bidi: isolate/isolate-override) instead of embedding it, matching the HTML Standard and browsers —<bdi>already did this (#577)
Performance (#578, #579, #584, #585, #586, #587)
- On a real 255,000-box document, fragment emission dropped from 84.7s to a small fraction of that by no longer re-walking the whole box tree for every page emitted (#584)
- On a real
widows/orphans-heavy document (~14,600 entries), wall-clock dropped from ~2m40s to 54.9s and allocations from 271GB to 118GB by scoping fragment-emission invalidation instead of retiring it globally (#587) PdfGenerator.AddPdfPagesno longer performs a full second parse+cascade+layout pass just to resolve a document's own@page { size }against the caller-configured size (#585)- Smaller wins: memoized CSS length validation (#578), skipped redundant multi-column rollback resets on already-settled children (#579), and skipped fragment-emission invalidation calls that had nothing to clear (#586)
None of the performance work in this release changes rendered output — each is verified against the existing suite (and, for the two fragment-emission changes, an additional differential-pruning oracle) with identical results before and after.
Breaking changes & migration notes
vertical-alignno longer inherits from an ancestor (see Spec compliance above) — a document that declared it once on a container and relied on it applying to every descendant inline box now needs to declare it explicitly on each element that should be affected, or usevertical-align: inheritto opt in.dir/bdo[dir]now isolate instead of embed (see Spec compliance above) — adir-bearing inline element embedded in an opposite-direction paragraph is now opaque to that paragraph's own neutral-character/number resolution around it, the same way<bdi>already was; a document relying on the old (non-isolating) placement of digits/neutral characters around such an element will see that placement change.font-variant: small-caps/all-small-capsnow prefers a font's real substituted glyphs over the previous always-synthesized appearance where the resolved font supports it — a document that depended on the exact synthesized look (e.g. pixel-comparing it against another renderer that always synthesizes) will see a different result wherever a font actually implements the feature.- The pagination-correctness fixes above (non-round page heights, table-cell/flex-item decoration repainting,
rowspanown-content overflow, inlinestring-setcorruption, flex-columnmargin-top: automeasurement) each describe a case that previously rendered incorrectly or corrupted content — a document that happened to trigger one of them will render differently (and, in the non-round-page-height case, correctly for the first time) after upgrading.
See Forward compatibility in the HTML/CSS support docs for how PeachPDF treats this class of change going forward.
All Merged Pull Requests
- Refuse a fragmentation question during a detached measurement pass — #570
- Extend a flex/grid item's decoration past its own pinned bounds — #571
- Make the UA stylesheet's dir/bdo rules isolate instead of embed — #577
- Memoize CssValueParser.IsValidLength by input string — #578
- Skip redundant multi-column rollback resets on untouched children — #579
- Use the boundary-aware slot lookup for a resumed column/table restart top — #580
- Stop re-walking the whole box tree for every page emitted — #584
- Resolve @page size in place instead of re-parsing the document — #585
- Skip fragment-emission invalidation when nothing was recorded to clear — #586
- Scope fragment-emission pruning invalidation instead of retiring it globally — #587
- Delete dead OffsetRectangle geometry-notification landmine (closes #583) — #588
- Stop cascading vertical-align as inherited — #589
- Add real GSUB substitution for font-variant-caps/-numeric/-east-asian, complete ligatures — #591
- Fragment a rowspan cell's own overflowing content instead of stretching it — #592
- Add regression tests that actually discriminate the rowspan dispatch fix — #594
- Walk a fan-out break token's per-child continuations in RecordChain — #595
- Fix inline string-set/page corruption across multicol reflow and page continuations — #596
- Bump package version from 0.9.7 to 0.9.8 — #597