Skip to content

v0.9.5

Latest

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:34
569ff45

PeachPDF 0.9.5 Release Notes

Release date: July 21, 2026
Full changelog: v0.9.4...v0.9.5
Release tag: v0.9.5

PeachPDF 0.9.5 centers on CSS Paged Media correctness and font capability: per-page @page geometry with true full-bleed pages, spec-correct px resolution (1px = 0.75pt), relative units in @page rules, @font-face unicode-range with per-character font matching, and emoji/astral codepoint rendering — plus a batch of pagination, table, and Acid2 paint fixes.

Highlights

CSS Paged Media: per-page geometry and full-bleed pages (#142)

  • @page { margin: 0 } was silently ignored (treated as unset), making full-bleed pages impossible — fixed (closes #125), and the shared length parser now delegates to the CSS-OM Length struct instead of a duplicate hand-rolled one
  • Implemented the CSS Paged Media 3 page-box model for per-page margin overrides: when @page :first, :left/:right, or named-page rules override top/bottom margins, each pagination slot gets its own content-band height and cumulative top — enabling true four-edge full-bleed pages and variable-height content bands that genuinely affect where text breaks
  • New PageGeometryTable (per-slot band geometry) and PageRuleResolver (unified @page cascade shared by layout and paint) make paint unable to disagree with layout about a page's geometry
  • Exact-boundary forced breaks per CSS Fragmentation 3 §4.4: a sibling ending flush on a page boundary satisfies the break, so an exact-fit cover page no longer manufactures a blank page
  • Link annotations now use the shifted page grid and map to materialized pages (they were silently dropped after any skipped blank page)

Spec-correct CSS px resolution (#160)

  • px now resolves at 1px = 1/96in = 0.75pt engine-wide per CSS Values & Units §6.2, replacing the old dual convention (1px = 1pt for layout but 0.75pt for fonts) — output now matches browser print behavior (closes #150)
  • The original symptom: @page { margin: 96px } and @page :first { margin: 96px } resolved through different paths and produced different band heights for textually identical margins
  • A single Length.PointsPerPx constant is now the only place the ratio lives, covering body layout, fonts, borders, backgrounds, image/SVG intrinsic sizes, @page geometry, and calc()
  • See the new "Length units" section in docs

Relative units in @page margins, size, and margin boxes (#164)

  • %, em, ex, and rem in @page margins, @page { size }, and margin-box dimensions now resolve against the correct context per CSS Paged Media 3 §7–8 (root font for em/rem, margin-area dimensions for %), instead of being silently ignored or mis-resolved (closes #154, #155, #156)
  • Units invalid in a page context (viewport units, ch) are dropped as invalid declarations per CSS error handling, leaving the configured value in place rather than resolving to zero

Named-page @page styles no longer leak onto later default pages (#167)

  • After a page: <name> element ended, content reverting to the default page kept the named page's margins and margin-box suppression — the used value of page was resolved flow-based and forward-sticky
  • Now computed tree-based per CSS Paged Media 3 §3 (own page unless auto, else the parent's used value), with reversion entries registered so geometry and margin boxes revert correctly (closes #126); flex/table/multicol reversion is tracked separately in #166

@font-face unicode-range and per-character font matching (#174)

  • The unicode-range descriptor was parsed but never honored — font matching is now per-character (codepoint-aware) following the CSS Fonts 4 matching algorithm, and the same mechanism gives glyph-coverage fallback across the font-family stack (a codepoint one family lacks falls through to the next, including system fonts via their actual cmap coverage) (closes #158)
  • Emoji and other supplementary-plane (astral) codepoints now render: the CID text pipeline moved from UTF-16 char to System.Text.Rune, cmap format-12 subtables are read, and a word-break bug that split every astral character's surrogate pair into two U+FFFD words is fixed
  • Custom fonts are now content-addressed, so two different files sharing one internal name (a common webfont-subset pattern) no longer collide
  • New public API: AddFontFromStream(stream, RuneRange[]) restricts a registered font to codepoint ranges — the programmatic unicode-range
  • Known limits, documented: color emoji renders as monochrome outlines (tracked in #175), and there is no text-shaping stage (no GSUB/GPOS, Bidi, or Arabic joining)

Table pagination fixes (#153, #161)

  • Headings with page-break-after: avoid are no longer stranded when the following table is relocated to the next page — keep-with-next run pulling now applies in the margin-crossing relocation path, and repeating-header tables where the header fits but the first body row doesn't are now relocated too
  • A repeating <tfoot> row-group had a zero-width bounding box (ActualRight was never assigned, unlike <thead>'s), breaking its per-page repeat rendering (closes #124)

Replaced elements in flex containers vs. page breaks (#170)

  • An <svg>/<img> that was the sole child of a display: flex container could paint its content well below its own box after an ancestor table relocated the row to the next page — flex's throwaway measurement layout passes ran at provisional positions and could trigger word-level page-break jumps that stuck (closes #171)
  • Fixed by suppressing word page-break decisions during measurement-only layout passes; the documented trade-off is that word-level page-break avoidance is unavailable inside display: flex containers
  • Two pre-existing SVG clipPath spec gaps found during the investigation are filed as #168 and #169

Acid2 paint visibility and background tiling fixes (#177)

  • CssBox.Paint()'s visibility pre-check used the containing block's client rectangle instead of the box's own line rectangles, so an inline replaced element inside a deliberately zero-height container (Acid2's "eyes") was wrongly skipped entirely — background and all
  • Independently, background-attachment: fixed repeat loops tiled across the full page viewport instead of the visible clip area, emitting ~200k+ draw operators for one small clipped element — now bounded by the actual visible intersection

counter(name, style) rendering (#165)

  • The two-argument counter() form incremented the counter but emitted no text at all; the style argument was never read (closes #128)
  • A shared counter-style formatter now serves both list markers and content: counter(), with decimal-leading-zero support and spec-correct fallback to decimal for unknown styles (CSS Counter Styles 3 §2)

Documentation and support policy (#173, #179)

  • Documented the .NET support policy — PeachPDF supports .NET 8 and every newer currently-supported .NET version, aligned with Microsoft's lifecycle — in the README and getting-started guide; CI now skips heavy test steps for doc-only changes
  • Major documentation expansion: architecture.md now covers per-page content bands, blank-page skipping/margin truncation/keep-with-next, CSS2.1 Appendix E stacking order, and the full font subsystem (codepoint-aware resolution, Rune-based CID pipeline, content-addressed font identity); a new testing.md documents the test suite, CI pipeline, diff-coverage gate, and the two-renderer (PDFium + MuPDF) rasterization verification practice

All Merged Pull Requests

  • CSS Paged Media: per-page geometry and full-bleed pages — #142
  • Fix keep-with-next when tables page-break — #153
  • Fix CSS px unit to resolve spec-correctly at 0.75pt — #160
  • Fix repeating tfoot row-group having a zero-width bounding box — #161
  • Resolve relative units in @page margins, size, and margin boxes — #164
  • Fix counter(name, style) rendering nothing — #165
  • Fix named-page @page styles leaking onto later default pages — #167
  • Fix SVG content painting outside its box after page-break relocation — #170
  • Document .NET support policy and compare it across libraries — #173
  • Honor @font-face unicode-range (per-character matching) and render astral/emoji codepoints — #174
  • Fix Acid2 eyes disappearing behind an un-interlocked checkerboard — #177
  • Bump package version from 0.9.4 to 0.9.5 — #178
  • Document architecture, testing, and font subsystem; add testing.md — #179