fix(layout): real-only forced-overflow reports; stop dropping flex and abspos content - #384
Conversation
…x/abspos content - PAGINATION-FORCED-OVERFLOW-001 fired on every page of any document whose content sits in one wrapper taller than a page: the wrapper is committed at the page top only to be entered, and its children paginate normally. The top-level report is now deferred for an enterable block-flow / flex wrapper and emitted only if its content really did not paginate (using the emitted extent on a resumed page). A nested box whose OWN border box is taller than a page is reported where it is placed, once. - Chasing the remaining reports found real content loss: a `flex: 1` list in a stretched column card was flexed to ~0, laid out into that budget, paginated, and everything after the first item was discarded (02-travel-quote). Column items with auto height/min-height now honor the 4.5 automatic minimum (grown to their content height, shared with the pre-measure), and item content measured against the item's own size never paginates (NestedContentMeasurer suppressPagination). - Absolutely positioned content is laid out with pagination suppressed, like fixed content, so content taller than the box overflows it instead of being cut after the first break. Corpus: 02-travel-quote now shows every card feature (was 1 of 5-6); no other page changes. Forced-overflow reports drop from ~160 to 1 across the 28 docs (the remaining one is the measure-width over-estimate, next PR). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d51af3fc3b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| var contentMainBorderBox = buffer.ContainsDecorationOwnerFragment | ||
| ? buffer.ContentBlockExtent |
There was a problem hiding this comment.
Preserve max-height when applying the automatic minimum
When a column item has an automatic height/min-height but an explicit max-height (for example, .item { flex: 1; max-height: 20px }), ResolveFlexibleMainSizes first clamps it to that maximum, but this post-measure assignment grows it back to the full content height without reapplying the maximum. The item can therefore exceed its authored max-height, altering subsequent placement and pagination; the content-based automatic minimum must be capped by the resolved maximum before updating the used size.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 8c66c0e. The column content floor (content-sized and flex: 1 items) is now capped by the resolved max-height, in the FlexLayouter emission and in the BlockLayouter column pre-measure. Tests: FlexColumnAutomaticMinimumTests.Content_floor_of_a_flex_one_item_is_capped_by_max_height and Content_sized_item_is_capped_by_max_height.
| placement.BlockSize, | ||
| noPaginate: false, | ||
| noPaginate: true, |
There was a problem hiding this comment.
Respect non-visible overflow on absolute boxes
For an absolutely positioned box with overflow: hidden, clip, auto, or scroll and content taller than its definite height, setting noPaginate: true unconditionally emits every descendant beyond the box. Ordinary layout fragments currently receive no overflow clip rectangle, so this content visibly spills instead of being clipped; only visible overflow should take this path unless an appropriate clip is attached.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not changed, by design. Overflow clipping (overflow: hidden/clip/scroll/auto) is not implemented for any ordinary box yet (see the BoxFragment remarks: overflow clip stays deferred; only margin boxes have an opt-in ClipRect). An absolutely positioned box now behaves like every other box. The old behavior was not a clip: it paginated the content into the box-sized fragmentainer and silently DROPPED whole blocks after the first break (a rule-7 violation). When overflow clipping lands, it will apply to abspos boxes the same way.
| var deferForcedOverflowDiagnostic = borderBoxBlockSize <= fragmentainer.BlockSize | ||
| && pendingTableLayouter is null | ||
| && !IsGridContainer(child) | ||
| && (IsFlexContainer(child) || IsBlockFlowContainerOwnedByBlockLayouter(child)); |
There was a problem hiding this comment.
Retain diagnostics for oversized break-inside regions
When a block-flow wrapper has break-inside: avoid and its children collectively exceed a page, this condition defers the warning just like an ordinary splittable wrapper. The recursive layout then returns a continuation at an avoided child boundary on every non-final page, so the deferred check never emits there, while the final page's emitted extent fits and emits nothing either. The avoid constraint is therefore violated without the promised PAGINATION-FORCED-OVERFLOW-001; wrappers that avoid internal breaks should not use this clean-pagination suppression path.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Checked against the code. A break-inside: avoid block taller than a page does not reach the forced-overflow commit: the break resolver splits it between its children (counting BreakInsideAvoidViolation), which is what docs-site/page-breaks.md promises ("If it is taller than a whole page, it has to split anyway"). The reports seen before this PR for that case came from the enclosing wrapper on every page, not from the avoid region. Nothing is forced past a page edge, so no forced-overflow diagnostic is correct. I fixed the diagnostics doc instead (8c66c0e) and added Break_inside_avoid_region_taller_than_a_page_splits_between_its_children (splits, keeps every paragraph, no diagnostic).
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Column flex sizing still drops long content and incorrectly handles definite and maximum heights.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Refines forced-overflow diagnostics and prevents nested flex and absolute-positioned content from being discarded.
Changes:
- Defers wrapper overflow diagnostics until actual overflow is confirmed.
- Suppresses inappropriate nested pagination for flex and absolute-positioned content.
- Adds regression tests and updates documentation.
| File | Description |
|---|---|
CHANGELOG.md |
Documents the fixes. |
docs/deferrals.md |
Updates flex sizing limitations. |
docs/diagnostics-codes.md |
Revises diagnostic semantics. |
src/NetPdf.Layout/Layouters/BlockLayouter.cs |
Refines overflow reporting and abspos layout. |
src/NetPdf.Layout/Layouters/FlexLayouter.cs |
Implements column automatic minimum sizing. |
src/NetPdf.Layout/Layouters/NestedContentMeasurer.cs |
Adds pagination suppression. |
tests/NetPdf.UnitTests/Phase3/FlexLayouterProductionTests.cs |
Tests column flex sizing. |
tests/NetPdf.UnitTests/Rendering/ForcedOverflowDiagnosticTests.cs |
Tests diagnostics and content preservation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| internal static bool HasColumnContentAutomaticMinimum(Box item) | ||
| { | ||
| var st = item.Style; | ||
| if (st.Get(PropertyId.Height).Tag is ComputedSlotTag.LengthPx or ComputedSlotTag.Percentage) return false; |
There was a problem hiding this comment.
Fixed in 8c66c0e. A definite height no longer switches the floor off: for block-child content the column floor is now min(content, height) (the specified size suggestion), capped by max-height. Test: Definite_height_item_with_taller_content_is_not_shrunk_below_its_height. Note: while testing I found that column items with a definite height do not shrink at all in a fixed-height column today (same on main), so the shrink case can't occur yet. That is a separate, older gap, tracked as a follow-up.
| |---|---|---| | ||
| | `PAGINATION-OPTIMIZER-FALLBACK-001` | Info | The bounded DP optimizer exceeded its time / candidate-set budget on a long document; greedy pagination (no lookahead) used. PDF still emits cleanly; layout quality is the same as a non-optimizing renderer. | | ||
| | `PAGINATION-FORCED-OVERFLOW-001` | Warning | A region marked `break-inside: avoid` (or otherwise un-splittable per the cost model) was taller than a single fragmentainer; forced to split anyway. The first piece occupies the remainder of the current page; the rest cascades onto subsequent pages. PDF renders correctly but the author's break constraint was violated. Per CSS Fragmentation L3 §3.2 last-resort fallback. | | ||
| | `PAGINATION-FORCED-OVERFLOW-001` | Warning | Content was forced past a page edge because it cannot fit any page: a box whose own height is taller than a page (an explicit `height`, a tall image), or an un-splittable region (e.g. `break-inside: avoid`) taller than a page. It is committed at the top of a page and overflows it (see also `PDF-CONTENT-OVERFLOW-TRUNCATED-001`). Reported once per such box — a wrapper element that merely CONTAINS paginating content (`<div class="page">…</div>`) does not report it (1.1.2+; earlier versions reported it on every page of every wrapped document). Per CSS Fragmentation L3 §4.4 forward progress. | |
There was a problem hiding this comment.
Agreed. Fixed the doc in 8c66c0e: the break-inside: avoid example is removed from the forced-overflow contract, and the entry now says such an element splits between its children and does not report. See the reply on the Codex thread for the code path.
…eight caps the automatic minimum PR #384 review: - The column content-height floor (content-sized and flex:1 items) is now capped by max-height, in the emission and the BlockLayouter pre-measure. - A definite height no longer switches the automatic minimum off: the floor is min(content, height) (CSS Flexbox 4.5 specified size suggestion). - diagnostics-codes: a break-inside:avoid element taller than a page splits between its children (as the page-break guide says); it is not a forced overflow. Test pins that it splits, keeps every paragraph, and does not report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(layout): report forced overflow only when real; stop dropping flex/abspos content - PAGINATION-FORCED-OVERFLOW-001 fired on every page of any document whose content sits in one wrapper taller than a page: the wrapper is committed at the page top only to be entered, and its children paginate normally. The top-level report is now deferred for an enterable block-flow / flex wrapper and emitted only if its content really did not paginate (using the emitted extent on a resumed page). A nested box whose OWN border box is taller than a page is reported where it is placed, once. - Chasing the remaining reports found real content loss: a `flex: 1` list in a stretched column card was flexed to ~0, laid out into that budget, paginated, and everything after the first item was discarded (02-travel-quote). Column items with auto height/min-height now honor the 4.5 automatic minimum (grown to their content height, shared with the pre-measure), and item content measured against the item's own size never paginates (NestedContentMeasurer suppressPagination). - Absolutely positioned content is laid out with pagination suppressed, like fixed content, so content taller than the box overflows it instead of being cut after the first break. Corpus: 02-travel-quote now shows every card feature (was 1 of 5-6); no other page changes. Forced-overflow reports drop from ~160 to 1 across the 28 docs (the remaining one is the measure-width over-estimate, next PR). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): measure nested content at its real containing width The block measure pass (MeasureSubtreeVisualBlockExtentRecursive) laid every nested text block, flex row, table and multicol out at the BFC (page) content width. Text that wraps inside a narrow box was measured as one line, so: - a narrow box's painted border was too short (lost its bottom padding); - spacing after such content was squeezed (index.pdf payment block: 4px gap instead of the browser's 42px); - the mid-split entry under-measured a list's first item, entered the list at the page bottom, and line-split it leaving a single line. The containing content width is now threaded through the recursion (MeasureInlineGeometry mirrors the emit path's ResolveInFlowBorderBoxInlineSize: explicit / % width, box-sizing, min/max, else fill minus margins), and used by the inline-only, flex, table-wrapper and multicol branches, the first-child estimate, and the keep-with-next lookahead. Also makes HtmlPdfFacadeTests' slow-loader timeout test deterministic: it waits for the deadline to pass instead of a fixed 600ms delay (a busy test host could fire the 100ms timer late and the render finished first). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): cap column flex content floor by max-height; specified height caps the automatic minimum PR #384 review: - The column content-height floor (content-sized and flex:1 items) is now capped by max-height, in the emission and the BlockLayouter pre-measure. - A definite height no longer switches the automatic minimum off: the floor is min(content, height) (CSS Flexbox 4.5 specified size suggestion). - diagnostics-codes: a break-inside:avoid element taller than a page splits between its children (as the page-break guide says); it is not a forced overflow. Test pins that it splits, keeps every paragraph, and does not report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): keep a short box's bottom padding; measure % insets and float-adjusted widths PR #385 review: - The measure pass resolves nested boxes' block-axis % padding and % margins against the containing inline size (they are not rewritten to used px until emission, so px reads treated them as 0). - Nested table and multicol measures use the content width resolved by MeasureInlineGeometry (with % padding) instead of re-reading px insets. - In an intrinsic probe, an explicit width's padding is subtracted at the same base the border box was built with. - The outer dispatch passes its float-adjusted available range, so an auto-width block beside a float is measured at its emitted width. Found while testing: the descendant-dominant check compared the deepest child against the parent's whole border box, so a box whose bottom padding + border exceeded its content (one short line in a padding:24px card) lost its bottom padding and the next box overlapped it. It now compares against the content-area bottom, using the deepest child bottom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
) * fix(layout): report forced overflow only when real; stop dropping flex/abspos content - PAGINATION-FORCED-OVERFLOW-001 fired on every page of any document whose content sits in one wrapper taller than a page: the wrapper is committed at the page top only to be entered, and its children paginate normally. The top-level report is now deferred for an enterable block-flow / flex wrapper and emitted only if its content really did not paginate (using the emitted extent on a resumed page). A nested box whose OWN border box is taller than a page is reported where it is placed, once. - Chasing the remaining reports found real content loss: a `flex: 1` list in a stretched column card was flexed to ~0, laid out into that budget, paginated, and everything after the first item was discarded (02-travel-quote). Column items with auto height/min-height now honor the 4.5 automatic minimum (grown to their content height, shared with the pre-measure), and item content measured against the item's own size never paginates (NestedContentMeasurer suppressPagination). - Absolutely positioned content is laid out with pagination suppressed, like fixed content, so content taller than the box overflows it instead of being cut after the first break. Corpus: 02-travel-quote now shows every card feature (was 1 of 5-6); no other page changes. Forced-overflow reports drop from ~160 to 1 across the 28 docs (the remaining one is the measure-width over-estimate, next PR). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): measure nested content at its real containing width The block measure pass (MeasureSubtreeVisualBlockExtentRecursive) laid every nested text block, flex row, table and multicol out at the BFC (page) content width. Text that wraps inside a narrow box was measured as one line, so: - a narrow box's painted border was too short (lost its bottom padding); - spacing after such content was squeezed (index.pdf payment block: 4px gap instead of the browser's 42px); - the mid-split entry under-measured a list's first item, entered the list at the page bottom, and line-split it leaving a single line. The containing content width is now threaded through the recursion (MeasureInlineGeometry mirrors the emit path's ResolveInFlowBorderBoxInlineSize: explicit / % width, box-sizing, min/max, else fill minus margins), and used by the inline-only, flex, table-wrapper and multicol branches, the first-child estimate, and the keep-with-next lookahead. Also makes HtmlPdfFacadeTests' slow-loader timeout test deterministic: it waits for the deadline to pass instead of a fixed 600ms delay (a busy test host could fire the 100ms timer late and the render finished first). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(fonts): index and use every face of system font collections (.ttc/.otc) The system-font indexer skipped collection files, so families that ship only as a collection (macOS Helvetica, Helvetica Neue, Avenir, Menlo, Optima; Windows/Linux CJK families) fell back to another, often wider, font. - FontCollection: bounded random-access reader for the ttcf header and per-face directories, and ExtractFace, which copies one face into a standalone sfnt (tag-sorted directory, 4-byte aligned tables, recomputed head.checkSumAdjustment) so the validator, parser, HarfBuzz and the PDF subsetter need no collection awareness. - The enumerator detects collections by content and indexes each face from its name/OS/2/head tables only, skipping faces with rejected tables, missing required tables, or over the byte cap. - SystemFontResolver extracts and validates the chosen face (one cache slot per face); a face whose full parse fails resolves to nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): cap column flex content floor by max-height; specified height caps the automatic minimum PR #384 review: - The column content-height floor (content-sized and flex:1 items) is now capped by max-height, in the emission and the BlockLayouter pre-measure. - A definite height no longer switches the automatic minimum off: the floor is min(content, height) (CSS Flexbox 4.5 specified size suggestion). - diagnostics-codes: a break-inside:avoid element taller than a page splits between its children (as the page-break guide says); it is not a forced overflow. Test pins that it splits, keeps every paragraph, and does not report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): keep a short box's bottom padding; measure % insets and float-adjusted widths PR #385 review: - The measure pass resolves nested boxes' block-axis % padding and % margins against the containing inline size (they are not rewritten to used px until emission, so px reads treated them as 0). - Nested table and multicol measures use the content width resolved by MeasureInlineGeometry (with % padding) instead of re-reading px insets. - In an intrinsic probe, an explicit width's padding is subtracted at the same base the border box was built with. - The outer dispatch passes its float-adjusted available range, so an auto-width block beside a float is measured at its emitted width. Found while testing: the descendant-dominant check compared the deepest child against the parent's whole border box, so a box whose bottom padding + border exceeded its content (one short line in a padding:24px card) lost its bottom padding and the next box overlapped it. It now compares against the content-area bottom, using the deepest child bottom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(fonts): PR #386 review — Apple 'true' faces, generic fallback past broken faces, docs - ExtractFace writes Apple's 'true' sfnt signature as 0x00010000, so the safety validator accepts the extracted face (it was indexed but never resolved). - SystemFontResolver walks every candidate of a CSS-generic chain and skips a collection face whose full parse fails, instead of returning null. - The collection sniff loops until 4 bytes are read (partial reads). - Docs: one summary per IsDangerousTableTag overload; the enumerator's class docs describe per-face collection indexing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(layout): report forced overflow only when real; stop dropping flex/abspos content - PAGINATION-FORCED-OVERFLOW-001 fired on every page of any document whose content sits in one wrapper taller than a page: the wrapper is committed at the page top only to be entered, and its children paginate normally. The top-level report is now deferred for an enterable block-flow / flex wrapper and emitted only if its content really did not paginate (using the emitted extent on a resumed page). A nested box whose OWN border box is taller than a page is reported where it is placed, once. - Chasing the remaining reports found real content loss: a `flex: 1` list in a stretched column card was flexed to ~0, laid out into that budget, paginated, and everything after the first item was discarded (02-travel-quote). Column items with auto height/min-height now honor the 4.5 automatic minimum (grown to their content height, shared with the pre-measure), and item content measured against the item's own size never paginates (NestedContentMeasurer suppressPagination). - Absolutely positioned content is laid out with pagination suppressed, like fixed content, so content taller than the box overflows it instead of being cut after the first break. Corpus: 02-travel-quote now shows every card feature (was 1 of 5-6); no other page changes. Forced-overflow reports drop from ~160 to 1 across the 28 docs (the remaining one is the measure-width over-estimate, next PR). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): measure nested content at its real containing width The block measure pass (MeasureSubtreeVisualBlockExtentRecursive) laid every nested text block, flex row, table and multicol out at the BFC (page) content width. Text that wraps inside a narrow box was measured as one line, so: - a narrow box's painted border was too short (lost its bottom padding); - spacing after such content was squeezed (index.pdf payment block: 4px gap instead of the browser's 42px); - the mid-split entry under-measured a list's first item, entered the list at the page bottom, and line-split it leaving a single line. The containing content width is now threaded through the recursion (MeasureInlineGeometry mirrors the emit path's ResolveInFlowBorderBoxInlineSize: explicit / % width, box-sizing, min/max, else fill minus margins), and used by the inline-only, flex, table-wrapper and multicol branches, the first-child estimate, and the keep-with-next lookahead. Also makes HtmlPdfFacadeTests' slow-loader timeout test deterministic: it waits for the deadline to pass instead of a fixed 600ms delay (a busy test host could fire the 100ms timer late and the render finished first). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(fonts): index and use every face of system font collections (.ttc/.otc) The system-font indexer skipped collection files, so families that ship only as a collection (macOS Helvetica, Helvetica Neue, Avenir, Menlo, Optima; Windows/Linux CJK families) fell back to another, often wider, font. - FontCollection: bounded random-access reader for the ttcf header and per-face directories, and ExtractFace, which copies one face into a standalone sfnt (tag-sorted directory, 4-byte aligned tables, recomputed head.checkSumAdjustment) so the validator, parser, HarfBuzz and the PDF subsetter need no collection awareness. - The enumerator detects collections by content and indexes each face from its name/OS/2/head tables only, skipping faces with rejected tables, missing required tables, or over the byte cap. - SystemFontResolver extracts and validates the chosen face (one cache slot per face); a face whose full parse fails resolves to nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(text): per-character font fallback Each text run used one font, so characters it lacked (✔, ▲, →, other scripts) were drawn as .notdef boxes even when a later font-family entry or an installed symbol font had them. - LineBuilder.Shape: a run that shaped to visible .notdef is split by font coverage; each piece is shaped with the first font of the style's fallback chain that covers it (ItemizedRun.FontIndex). Marks, variation selectors and format characters follow their neighbour's font. - IShaperResolver gains Resolve(style, fontIndex) and FindFallbackFont (default: no fallback). HarfBuzzShaperResolver builds the chain once per query: rest of the author stack, default family, then a fixed list of symbol / broad-coverage system families; each validated, de-duped by content, unusable candidates skipped. - TextPainter embeds glyphs from the run's own font and keeps the primary font's metrics for the baseline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): cap column flex content floor by max-height; specified height caps the automatic minimum PR #384 review: - The column content-height floor (content-sized and flex:1 items) is now capped by max-height, in the emission and the BlockLayouter pre-measure. - A definite height no longer switches the automatic minimum off: the floor is min(content, height) (CSS Flexbox 4.5 specified size suggestion). - diagnostics-codes: a break-inside:avoid element taller than a page splits between its children (as the page-break guide says); it is not a forced overflow. Test pins that it splits, keeps every paragraph, and does not report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): keep a short box's bottom padding; measure % insets and float-adjusted widths PR #385 review: - The measure pass resolves nested boxes' block-axis % padding and % margins against the containing inline size (they are not rewritten to used px until emission, so px reads treated them as 0). - Nested table and multicol measures use the content width resolved by MeasureInlineGeometry (with % padding) instead of re-reading px insets. - In an intrinsic probe, an explicit width's padding is subtracted at the same base the border box was built with. - The outer dispatch passes its float-adjusted available range, so an auto-width block beside a float is measured at its emitted width. Found while testing: the descendant-dominant check compared the deepest child against the parent's whole border box, so a box whose bottom padding + border exceeded its content (one short line in a padding:24px card) lost its bottom padding and the next box overlapped it. It now compares against the content-area bottom, using the deepest child bottom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(fonts): PR #386 review — Apple 'true' faces, generic fallback past broken faces, docs - ExtractFace writes Apple's 'true' sfnt signature as 0x00010000, so the safety validator accepts the extracted face (it was indexed but never resolved). - SystemFontResolver walks every candidate of a CSS-generic chain and skips a collection face whose full parse fails, instead of returning null. - The collection sniff loops until 4 bytes are read (partial reads). - Docs: one summary per IsDangerousTableTag overload; the enumerator's class docs describe per-face collection indexing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(text): PR #387 review — fallback per grapheme cluster, lazy chain, skip unparsable fonts - LineBuilder picks one font per extended grapheme cluster (UAX #29): the first fallback covering ALL of it, else the font covering its base, so a base and its marks (or an emoji ZWJ sequence) are never split. Variation selectors / format characters are optional for coverage. Malformed UTF-16 (a lone surrogate) stays on the primary instead of throwing. - IShaperResolver.FindFallbackFont takes the cluster's codepoints. - HarfBuzzShaperResolver resolves fallback candidates lazily, in fixed order, only until one covers the cluster (stable indexes), and skips a candidate that HarfBuzz can't load or the OpenType parser can't parse. - SyntheticFont.Build(char, char) maps two independent characters (tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>


Task 1 of 4 follow-ups from the 1.1.1 corpus review.
Problem
PAGINATION-FORCED-OVERFLOW-001fired on every page of any document whose content sits in one wrapper element taller than a page (<div class="page">…</div>, or just<body>): the wrapper is committed at the page top only to be entered, and its children then paginate normally. The NetPdf-tester corpus reported ~160 of them across 28 documents, hiding real problems.Fix
height, tall image) is reported where it is placed — exactly once (before: once per page, by the wrapper). No double report with its wrapper.Real content loss found while tracing the remaining reports
<ul>isflex: 1in a stretched column card; it was flexed to ~0, its content laid out into that ~1px budget, paginated, and the continuation discarded. Now:height+min-height(visible overflow) honor the §4.5 automatic minimum: grown to their content height (HasColumnContentAutomaticMinimum, shared withPreMeasureFlexMainExtent);NestedContentMeasurer(suppressPagination:)) — it overflows instead of dropping.Results
tfoot).ForcedOverflowDiagnosticTests(6, facade) +Column_flex_zero_basis_item_is_not_shorter_than_its_content(2). All 8 fail on the old code. Full suite green; no golden changed.diagnostics-codes.md(what the code now means),deferrals.md(column automatic minimum), CHANGELOG[Unreleased].🤖 Generated with Claude Code