fix(layout): respect section and table metrics (fixes #319)#396
Open
fix(layout): respect section and table metrics (fixes #319)#396
Conversation
Use each DOCX section's page geometry during layout and preserve table/cell metrics so paged rendering better matches Word. Made-with: Cursor
Builds on the community fix in afaaf66 to address regressions and edge cases surfaced by an audit against more complex documents: - forcePageBreak is idempotent when the current page is empty; eliminates phantom empty pages between a section break and a following pageBreakBefore=true paragraph (the failing case in the issue fixture). - inferImplicitSingleCellRowSpans skips cells that are vMerge continuations or already declared their gridSpan; preserves vertically merged columns and sparse single-cell rows. - Section margin override honors any single side that's set; previously a section overriding only `marginRight` or `marginBottom` was silently dropped because the gate required `marginTop` or `marginLeft`. - renderedPageBreakBefore round-trips through fromProseDoc and the paragraph serializer (`<w:lastRenderedPageBreak/>` is re-emitted), so save+reload no longer loses the break Word recorded. Tests: - packages/core/src/layout-engine/__tests__/force-page-break-empty.test.ts - packages/core/src/docx/__tests__/table-vmerge-implicit-rowspan.test.ts - packages/core/src/prosemirror/conversion/__tests__/rendered-page-break-roundtrip.test.ts - e2e/tests/issue-319-section-pagination.spec.ts Co-Authored-By: Juan Diego Mendez <juan.mendez@biorce.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Continues the audit-driven cleanup on top of the earlier edge-case fixes. Parser: - paragraphStartsWithRenderedPageBreak now scans past leading empty rPr-only runs and recurses through hyperlink / sdt / smartTag / fldSimple / customXml / ins / del / moveFrom / moveTo wrappers, so Word's `<w:lastRenderedPageBreak/>` placement inside any of those containers is honored. fldChar / instrText / pgNum and the rest of the visible run-content set are recognized. - Header/footer parser strips renderedPageBreakBefore — HF reflows on every page and a leading rendered break in a header would force a body-level page break for every header repetition. - paragraphHasPageBreak (ProseMirror conversion) now considers fieldChar / instrText / footnoteRef / endnoteRef and recurses through hyperlink, insertion, deletion and field wrappers. Layout engine: - continuous section breaks now call updatePageLayout so the next natural overflow page adopts the new section's geometry (ECMA-376 §17.6.22). - Unified SectionMeasureMetrics and SectionLayoutConfig into a single exported type used by both the engine and the React paged editor. - Cell content layout reverted to block flow; the brief flex-column switch broke margin collapse, baseline alignment and floating-image interaction in nested tables. Layout bridge: - New `tableWidthUtils` helpers (resolveTableWidthPx, resolveCellWidthPx, countTableColumns, normalizeTableColumnWidths) extracted into a shared module. PagedEditor now imports them so the React side stops carrying hand-rolled twip math. - pct width math is always /5000 per ECMA-376 §17.18.111. The previous "treat values <= 100 as plain percentage" heuristic mis-rendered cells whose authoring tool wrote small valid pct values. Tests added (12 new): - packages/core/src/docx/__tests__/rendered-page-break-edge-cases.test.ts - packages/core/src/docx/__tests__/header-footer-rendered-page-break.test.ts - packages/core/src/layout-bridge/__tests__/tableWidthUtils.test.ts - packages/core/src/layout-engine/__tests__/continuous-section-geometry.test.ts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Builds on @juanmendez-git's fix from #320 to address regressions and edge cases surfaced by an audit against more complex documents. Fixes #319.
The community PR's commit (
afaaf66) is preserved as the first commit; a follow-up commit hardens the changes against documents the original PR didn't exercise.What the original PR does
<w:lastRenderedPageBreak/>markers and leading hard page breaks are preserved through parser → ProseMirror → flow layout.Edge cases hardened in the follow-up commit
forcePageBreakis idempotent when the current page is empty. A section break followed by apageBreakBefore=trueparagraph (e.g. an "Attachment" heading after a section change) used to produce a blank page between the body and the attachment.inferImplicitSingleCellRowSpansno longer expands cells that are vMerge continuations or that already declared their gridSpan. The previous version corrupted any DOCX with vertically merged columns or sparse single-cell rows.marginRightormarginBottomnow honors the override. Previously the gate requiredmarginTopormarginLeft, so right/bottom-only overrides were silently dropped.<w:lastRenderedPageBreak/>— the marker now survivesfromProseDocand is re-emitted by the paragraph serializer, so save+reload doesn't silently lose Word's recorded break.Test plan
bun run typecheckbun test packages/core— 404 / 404 passingforce-page-break-empty.test.ts— paginator no-ops on empty pagestable-vmerge-implicit-rowspan.test.ts— vMerge protectionrendered-page-break-roundtrip.test.ts—<w:lastRenderedPageBreak/>survives save+reloadissue-319-section-pagination.spec.tsasserts the issue's fixture renders without phantom empty pages, with both portrait and landscape sections.🤖 Generated with Claude Code