v0.13.0 - bookmark outline fixes + invoice discount column
Three defects in bookmark handling — one of which shipped real PDF corruption (duplicate outline entries) — plus a per-line discount column in the invoice template. Also the release where the VS Code extension joins the shared version line.
Fixed
Engine — three bookmark defects, one shared root cause.
- Duplicate PDF outline entries. A bookmarked container that both overflows a page and carries visual styling wrote its outline entry twice — two identical
/Outlinesentries and/Count 2for a singlebookmarkprop. A genuine document defect, not a reporting one. The overflow path emits a zero-height marker so unstyled views can't lose their bookmark; styled views also built a wrapper carrying the samebookmark, andcollect_bookmarksrecurses — so it found both. Wrappers no longer carrybookmark; the marker is the sole carrier on every path. - Bookmarks on content that fits its page now appear in
LayoutInfo. The marker was only emitted on the overflow path, so consumers walking layout output forBookmarknodes silently missed most bookmarks. The PDF was never wrong here — the outline was always complete — this was aLayoutInfoblind spot. PDF output is byte-identical after the fix (verified by hash:8f41f7e9fb58b3cbbefore and after). - The marker reports
nodeType: "Bookmark"instead of"None". The unset node type fell back tokind.to_string()and leaked theDrawCommand::Nonevariant name — a value never present inElementNodeType.
All three came from the same divergence: two hand-maintained copies of the marker-building code. Both paths now share one bookmark_marker() helper.
⚠️ Behavior notes
- Outline destination moves for one narrow case. Markers now sit at the view's outer top edge, so every container path resolves a bookmark to the same coordinate. Only an unstyled, overflowing, breakable view with top padding or border shifts (Letter, 36pt margin, padding 20: destination Y 736.00 → 756.00). Styled views are unchanged.
- Consumers matching
nodeType === 'None'(which the types never permitted) must switch to'Bookmark'."None"was an accident of the fallback, not a semantic role.
Added
@formepdf/templates — per-line discount on the invoice template. A decimal fraction off the line total, matching taxRate's units rather than introducing a second convention. Applies before tax. The items table grows a fifth column; discounted lines render -15% in green, undiscounted ones an em dash. invoiceExample expanded from 5 to 19 line items (three pages), so the fixture actually exercises header repetition and page breaks — the template's main job.
VS Code extension — version jump 0.10.5 → 0.13.0
The extension now tracks the monorepo version. It bundles the engine and @formepdf/renderer wholesale, so its independently-drifting number told you nothing about what was in the VSIX. No features were skipped; 0.11.x and 0.12.x simply never shipped as extension releases. Also picks up the sidebar fixes: bookmarked containers no longer show None in the component tree, and bookmarks on fitting content appear at all.
How these were found
Dogfooding, continued from v0.12.1: structural regression baselines (via pdf-testkit) extended to the shipped templates. The catalog template's bookmarks tripped all three engine defects — the duplicate-entry bug had survived because every pre-existing bookmark test asserted contains, which two identical entries satisfy perfectly well. The new test asserts counts.
Full changelog
engine · @formepdf/core · @formepdf/templates · vscode
All other @formepdf/* packages got version-alignment bumps to 0.13.0. No functional changes.