Skip to content

v0.9.8

Latest

Choose a tag to compare

@github-actions github-actions released this 02 Aug 07:05
· 69 commits to main since this release
6f37a50

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), and font-variant-east-asian (9 keywords) — previously unrecognized entirely
  • font-variant-ligatures: discretionary-ligatures/historical-ligatures now actually apply a font's dlig/hlig features, and font-feature-settings now actually activates the OpenType features it names — both previously parsed but had no effect
  • font-variant: small-caps/all-small-caps now renders via a font's real smcp/c2sc substitution when available, instead of always synthesizing (upper-casing and shrinking); font-variant is 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 rowspan cell 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 a string() picked up (#596)
  • Fixed a flex-column item anchored with margin-top: auto spuriously triggering a real page break during an internal measurement pass, splitting content across two pages when it fit on one (#570)

Spec compliance (#577, #589)

  • vertical-align no longer inherits from an ancestor — CSS 2.1 §10.8.1 defines it as Inherited: no; vertical-align: unset now correctly resets to baseline instead of copying the parent's value (#589)
  • The dir global 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.AddPdfPages no 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-align no 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 use vertical-align: inherit to opt in.
  • dir/bdo[dir] now isolate instead of embed (see Spec compliance above) — a dir-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-caps now 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, rowspan own-content overflow, inline string-set corruption, flex-column margin-top: auto measurement) 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