Skip to content

fix(html): write page size, margins and tab advances in exact points (#1153) - #1161

Merged
JSv4 merged 2 commits into
mainfrom
fix/issue-1153
Oct 16, 2026
Merged

JSv4 merged 2 commits into
mainfrom
fix/issue-1153

Conversation

@JSv4

@JSv4 JSv4 commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Closes #1153

First of a three-PR stack: this one (#1153), then #1154 on top of it, then #1155 on top of that. All three touch the HTML converter, so they are stacked to keep them from conflicting.

Why

The converter wrote a few lengths as CSS inches rounded to hundredths (a 0.72pt step). A4 paper (11906 × 16838 twips) came out as 8.27in 11.69in, about a quarter point short, and a tab stop at 1417 twips came out as 0.98in instead of 70.85pt. #1128 and #1121 already removed this rounding from paragraph indents and row heights; these sites were left.

How it works

  • @page size and margin (the single rule and the per-section named rules) are written with the converter's existing PointsCss formatter from the exact point values: size: 595.3pt 841.9pt.
  • Tab spans: the tab width, the leader width, and a line break's leading margin are written in points.
  • Tab advances internally: the converter carries each advance in a pt:TabWidth annotation, in inches. That annotation was itself rounded to 0.001in, so even a points formatter could not be exact. It now keeps six decimals, and the CSS writer rounds back to the whole twip. That rounding matters for the tab "segment" boxes, which add several advances together: without it, A plus a tab to 1417 twips came out as 70.8499pt.
  • Tab segment widths (the inline-flex boxes that hold the text before a tab and the tab itself) were in thousandths of an inch; they move to points too, so a tab and its segment share one unit.
  • A pre-existing bug fixed on the way: when a tab ran past the last tab stop, the fallback advance was written as 720 in an attribute measured in inches (the comment says "put 1/2 inch in"). It is now half an inch.

Nothing in C#, npm, the export package, Python or MCP parses these CSS values or the inch strings; the PDF export writes its own @page from PageMap points (#1138). Out of scope and unchanged: the paginator's page-geometry data-* attributes still carry 0.1pt.

Validation

  • New PageAndTabLengthPrecisionTests: A4 size and margins in continuous and paginated @page rules, named per-section rules, a plain and a leader tab to a 1417-twip stop, a line break's margin, and a segment after one glyph. All fail on main. Reverting only the six-decimal annotation makes the three tab tests fail, and reverting only the twip rounding makes the segment test fail.
  • Existing tests changed (approved). 15 assertions pinned the inch strings. They now expect the same lengths in points: HtmlConversionOpsTests HCO058–060, HCO076–078, HCO088–090 and one more; HtmlConverterTests HC052, HC053 and HC055, plus one tab assertion; FeatureVerificationTests FV001 and FV099; TabAfterWrappingTextTests; and the PaginatedHeaderFooterContentTests segment-width helper. HCO058–060 now read the tab span's own declarations. In points the tab span and the segment that wraps it print the same width, so a search of the whole page could no longer tell them apart.
  • The full .NET suite ran: 6,871 passed, 1 failed, 2 skipped. The failure was TrackedRangeDeletion_ScalesWithTheRange, a timing-scaled session test, while three suites ran at once; it passes alone. The 1,007 converter, border and tab tests pass at the top of the stack. The Release library build has no warnings. The Playwright tab-geometry specs run in CI.

Adversarial review

One round of hostile review ran on this diff before opening. It found:

  • summed segment widths that were still not twip-exact (70.8499pt), now fixed;
  • three edited tests that had become weaker because the tab span and its segment now print the same width, now fixed;
  • the 720-inch fallback, now fixed;
  • a CHANGELOG line that claimed more than the change does (the data-* attributes), now narrowed.

It checked every other edited assertion by mutation, and it confirmed no consumer parses the changed values.

JSv4 added 2 commits October 16, 2026 00:58
…1153)

The @page size and margin, tab span widths, a line break's leading
margin and tab segment widths were CSS inches rounded to hundredths or
thousandths, so A4 came out a quarter point short and a 1417-twip stop
became 0.98in. They are now written in points from the exact twip value,
and tab advances are carried at six decimals of an inch internally.
Existing assertions that pinned the inch strings now expect points.
…'s own width (#1153)

Tab segment widths add several advances carried at six decimals of an
inch, which could leave 70.8499pt for a 1417-twip stop; they now round
back to the twip. The half-inch fallback for a tab past the last stop
wrote 720 inches. Three default-tab-stop tests now read the tab span's
own declarations, since the segment that wraps it prints the same width.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Page size, page margins and tab widths still round to hundredths of an inch

1 participant