A rendering-correctness release across border-radius (#329), CSS paged media
(@page, position: fixed, margin boxes — #327/#328), and lists
(#330/#339/#342/#347/#358), plus a from-scratch fix of the QR barcode encoder
(#341). Adds native multi-level list numbering via CSS counters (#356), table
rowspan geometry (#357), Document.AddConvertResult, and
SetPageSize/PageSize (#338). No breaking changes.
Added
document.Document.AddConvertResult(*html.ConvertResult) error— the wiring half ofAddHTML, exported so callers who need the rawConvertResult(to inspect or transform elements before rendering) can add it back with a single correct call. It forwards everything the converter produces — normal-flow elements, absolutely/position:fixedelements,@pagesize and margins, all four margin-box sets, and<title>/<meta>metadata — and sets the page size from any@pagerule.AddHTMLis now exactlyConvertFull+AddConvertResult, so the two paths cannot diverge. This closes the discoverability trap where a hand-writtenfor _, e := range result.Elements { doc.Add(e) }loop silently dropped absolutes, margin boxes, and@pageconfig with no error (#338)document.Document.SetPageSize(PageSize)andPageSize() PageSize— read and override the page dimensions afterNewDocument. The size was previously fixed at construction even thoughAddHTMLmutates it internally from@pagerules; these expose the missing setter/getter.AddConvertResultderives the size from@pageautomatically, so the pre-NewDocumentgeometry-resolve dance is no longer required (#338)- Code 128 code sets A and C —
barcode.NewCode128now selects sets A, B, and C automatically instead of Code B only. Set C encodes digit runs as pairs (roughly halving the width of numeric payloads) and set A encodes the ASCII control characters (0–31); bytes above 127 still return an error. Round-trip and golden-pattern tests cover every code-set transition (#346) - Native multi-level list numbering via CSS counters and
::marker—li::marker { content: ... }now defines the marker string, socounter-reset/counter-incrementwithcounter()/counters()produce true nested numbering (e.g.1,1.1,1.1.1) instead of a per-level restart. Addslist-style-position(inside/outside):insiderenders the marker inline with the first content line and wraps subsequent lines under it;outside(default) places it in a gutter that auto-grows to fit a wide marker so multi-level ordinals no longer overlap the item text (short-marker lists stay byte-identical). Thelist-styleshorthand now scans all tokens (position / type / image).layout.ListgainsSetMarkerInside. Newexamples/legal-numberingrenders a nested Master Services Agreement with hanging indents. Phase 1 of #356 - Table
rowspanvertical geometry — arowspancell previously reserved grid occupancy (following rows skipped its columns) but was always drawn one row tall. The spanning cell now spans the full height of its rows:buildGridexcludes rowspanning cells from their starting row's natural height, then resolves span heights from the summed spanned-row heights plus inter-row gaps (growing the last spanned row when content needs more room), and vertical alignment centers across the full span. A rowspan straddling a page break is not yet handled (#357)
Fixed
border-radius (#329)
- Percentage
border-radiusrenders as a true rounded corner —border-radius: 50%was resolving against a zero reference and collapsing to 0 (square corners). It now resolves per-axis: a circle on a square box, an ellipse on a rectangle, per CSS Backgrounds §5.5 corner clamping (#329) border-radiussurvives on a text-bearing box — a box with a direct text run drew its rounded background, then the text's paragraph re-painted the same color as a square rectangle on top, squaring the corners. The redundant fill (and any matchingTextRun.BackgroundColorhighlight) is now cleared, so the common "number inside a colored circle" badge works as a single rounded box (#329)border-radiusapplied across the remaining container paths —flex: 0 0 autochips, grid items,display: blockspans,blockquote,figure, and table wrappers each now round likedisplay: inline-block. Previously only the inline-block path took the rounded-rect draw, so visually identical content-hugging boxes rounded or not depending on how they were sized (#329)
CSS paged media — @page, position: fixed, margin boxes (#327, #328)
position: fixedelements render on every page — a fixed element (page watermark, pinned footer) was emitted once on the last flowed page instead of repeated on each page. It now draws on every page (#327)@page :first/:left/:rightmargins inherit the base@pagecascade — a partial pseudo-page override (e.g.@page :first { margin: 20px }) now inherits the base@page {}margins for the sides it does not set, and left/right duplex margin boxes resolve correctly (#327)@pageparsing correctness bundle — a batch of fixes to@pagerule parsing across the converter, layout, and document layers (size/orientation resolution, margin percentage/calcresolution, and margin-box cascade), so every document-building entry point applies identical paged-media geometry (#327)@pagemargin boxes embed the default font for PDF/A — running headers/footers declared via@bottom-centeretc. now carry the embedded body font, so a PDF/A document with page numbering no longer fails conformance on an unembedded font in the margin box (#328)
Lists and inline-block (#330, #339, #342, #347, #358)
display: inline-blockshrinks to fit and flows inline — an inline-block box now sizes to its content (fit-content width) and participates in inline flow instead of forcing a block break (#330)- Styled
<li>lays out block children and honors its own box — a list item containing block-level children (cards, nested divs) now lays them out correctly, and the<li>'s own background / border /border-radiusis drawn via a per-item box that the list layout previously lacked (#339, #342) inline-block/ explicit-width<li>hugs its content and stops double-painting — follow-up to the per-item box: a badge-style<li>now shrinks to content and clears the square background overpaint that squared its rounded corners (#342)- Ordered lists continue numbering and keep every item across a page break — a long
<ol>spanning pages restarted at1.on the second page and silently dropped the item straddling the boundary. Numbering now continues the sequence (honoring<ol start="N">), and a boundary item is split or deferred so no content is lost. Addslayout.List.SetStart/Start(#347) - Nested list markers indent per level — a sub-list's marker was drawn at the container's left margin regardless of depth, so every nested ordinal sat in the same left column while only the body text indented. Each level's marker now steps right by the accumulated parent indent, landing under its parent's content edge (LTR and RTL;
list-style-position: insidealready drew relative to the content edge). Single-level lists are unaffected (#358) - Margin-box fidelity for
@page :left/:right— fixing the wiring aroundAddConvertResultsurfaced that the left/right margin-box reconstruction dropped theHasColorflag and the embedded font that base and:firstalready carried; all four sets are now consistent (#338) examples/html-to-pdfno longer drops absolutes — the example wired the rawConvertFullresult by hand and forwarded onlyresult.Elements, silently droppingposition: fixed/absolutecontent. It now usesdoc.AddConvertResult(result), which forwards elements, absolutes,@pageconfig, and margin boxes in one call (#338)
Barcodes (#341, #346)
- QR codes are now scannable —
barcode.NewQRproduced structurally valid but unscannable symbols due to four independent encoder defects: the Reed-Solomon generator polynomial was built constant-term-first (so every block's error-correction codewords were wrong), one of the two format-information copies was written bit-reversed, the version-information bits were written in reverse (breaking version 7+), and the level-H ECC-per-block table had wrong codeword counts for versions 21–40. All four are fixed and verified by an independent decoder (#341) - EAN-13 quiet zones — corrected the leading/trailing quiet-zone widths so EAN-13 symbols meet the spec and scan reliably (#346)
Tests
- Independent barcode decode tests —
qr_decode_test.goreads generated QR symbols back with an independently-built GF(256) field, cross-checks the codeword-capacity tables, and reads format/version info from the matrix; each check was confirmed to fail when its corresponding fix is reverted. Adds Code 128 (A/B/C) and EAN-13 decode round-trips, and anexamples/barcodesdemo rendering QR (all four ECC levels), Code 128, and EAN-13 into one PDF for visual/scanner verification (#341, #346) - List pagination regression tests — multi-page ordered-list numbering continuity,
<ol start>continuation across a break, unordered no-content-loss, a single item taller than a page, andSetStartclamping (#347) AddConvertResultequivalence + fidelity tests — assertAddHTMLandConvertFull+AddConvertResultleave identical document state (page size, all margins, all four margin-box sets, elements, absolutes, metadata), that absolutes and@pageconfig survive, and the:left/:rightfield-fidelity regression (#338)
Documentation
using-folioClaude Code skill — a thin, version-controlled skill (.claude/skills/using-folio) capturing the stable usage patterns: preferDocument.AddHTML(orConvertFull+AddConvertResult) over hand-wiring, the asset-loading options (BaseFS,FallbackFontPath,StrictAssets), PDF/A setup, and a verification cookbook. Thehtmlpackage docs gain matching guidance steering callers toAddHTML/AddConvertResult(#338)
What's Changed
- docs: cut CHANGELOG [0.8.0] and [0.9.0] sections from Unreleased by @carlos7ags in #325
- html/layout: display:inline-block shrinks to fit and flows inline (#330) by @carlos7ags in #331
- html/layout: percentage border-radius with elliptical corners (#329) by @carlos7ags in #332
- html/layout: keep border-radius on text-bearing boxes (#329) by @carlos7ags in #333
- layout/html: embed default font in @page margin boxes for PDF/A (#328) by @carlos7ags in #334
- layout/html: render position:fixed on every page (#327) by @carlos7ags in #335
- html/layout: @page :first/:left/:right margin cascade and duplex margin boxes (#327) by @carlos7ags in #336
- html/layout/document: fix a batch of @page parsing bugs by @carlos7ags in #337
- html/layout: apply border-radius across remaining container paths (#329) by @carlos7ags in #340
- html/layout: lay out
- block children and honor the
- box (#342, #339) by @carlos7ags in #343
- html/layout: inline-block/width
- hugs content; clear box overpaint (follow-up to #343) by @carlos7ags in #345
- barcode: fix QR Reed-Solomon, format/version info, and ECC tables by @carlos7ags in #344
- barcode: Code 128 code sets A/C, decode tests, and EAN-13 quiet zones by @carlos7ags in #348
- layout: fix ordered-list page-break numbering and content loss (#347) by @carlos7ags in #349
- docs: add using-folio skill; fix html-to-pdf example dropping absolutes by @carlos7ags in #350
- barcode: support Code 128 code sets A and C by @carlos7ags in #351
- document: add AddConvertResult and SetPageSize/PageSize by @carlos7ags in #352
- docs: use AddConvertResult in html-to-pdf example and skill by @carlos7ags in #354
- barcode: declare UTF-8 with an ECI segment in QR byte mode by @carlos7ags in #355
- html: native multi-level list numbering via CSS counters, ::marker content, and list-style-position (#356) by @carlos7ags in #359
- layout: implement table rowspan geometry (#357) by @carlos7ags in #361
- layout: indent nested list markers per level (#358) by @carlos7ags in #360
Full Changelog: v0.9.0...v0.9.1