Summary
In a section that carries <w:bidi/>, column fill order is still left-to-right. Paragraph 1 lands in the left column and overflow spills into the right one. Word starts an RTL section in the right column and flows right-to-left.
Column widths, gap and the text direction inside each column are all correct — only the column ordering axis is wrong. Export is clean (<w:cols> and <w:bidi/> round-trip intact), so this is a render-only defect.
Measured on superdoc@2.10.0 / @superdoc/docx-engine@0.9.0, Chrome 141, Windows 10.
Reproduction
A minimal document is enough — 24 numbered paragraphs so the content overflows column 1, and an RTL section:
<w:body>
<!-- x24, numbered so the flow order is readable off the screen -->
<w:p>
<w:pPr><w:bidi/><w:jc w:val="both"/></w:pPr>
<w:r><w:rPr><w:rtl/></w:rPr><w:t xml:space="preserve">פסקה מספר 1. …</w:t></w:r>
</w:p>
…
<w:sectPr>
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
w:header="708" w:footer="708" w:gutter="0"/>
<w:cols w:num="2" w:space="720" w:equalWidth="1"/>
<w:bidi/>
<w:docGrid w:linePitch="360"/>
</w:sectPr>
</w:body>
Open it in SuperDoc and read the paragraph numbers.
Measured
Geometry is correct. A4 page at x=296 w=794, 1" margins, so a 602px content area:
|
x range |
width |
| column A |
392 … 669 |
277px |
| column B |
717 … 994 |
277px |
277 + 48 + 277 = 602, and the 48px gap is exactly w:space="720". Nothing wrong there.
The order is wrong. Reading each rendered line's x and text:
y=290 x=392 "פסקה מספר 1. …" ← left column holds paragraphs 1–19
y=290 x=717 "פסקה מספר 20. …" ← right column holds the overflow, 20–24
Expected in Word for this section: paragraphs 1–19 in the right column (x≈717), overflow into the left one (x≈392).
Control — the direction is not being read at all
Same document, same w:cols, but English text and no w:bidi anywhere:
y=290 x=392 "Paragraph number 1. …" ← left column holds paragraphs 1–14
y=290 x=717 "Paragraph number 15. …" ← right column holds the overflow
Identical geometry, identical fill order. So the fill order is a fixed left-to-right, and the section direction is not consulted for columns at all — this is not an RTL mis-resolution, it is an axis that was never wired up.
Scope
- Same result whether
w:cols comes from the file or is applied at runtime via sections.setColumns({ count: 2, gap: 0.5, equalWidth: true }).
- Text inside each column is correctly RTL — right-aligned, correct Hebrew shaping. Paragraph-level bidi works.
- Round-trip is clean. Exported
sectPr comes back as <w:cols w:num="2" w:space="720" w:equalWidth="1"/> <w:bidi/>, so no data is lost and the file still opens correctly in Word. The defect is display-only.
- Presumably the same axis governs column separators (
w:sep) and column-break targeting, though I did not measure those.
Where it looks like it lives
Your published types already describe the contract that is not being honoured.
layout-engine/contracts/src/direction-context.d.ts:
Direction context for a section. Used by section-level chrome only (page numbers, columns, gutters).
pageDirection: BaseDirection; — Section page direction; from w:sectPr/w:bidi.
But layout-engine/contracts/src/column-layout.d.ts resolves geometry on one axis only:
x and separatorX are CONTENT-RELATIVE (measured from the content-area left edge)
and no function in that API — getColumnGeometry, getColumnX, getColumnAtX, getColumnSeparatorPositions — takes a direction argument. So SectionDirectionContext.pageDirection appears to have no consumer on the column axis.
Note that getColumnAtX matters for more than paint: once ordering is direction-aware, hit-testing has to flip with it, or clicks will select the wrong column.
I would have opened a PR, but superdoc/dist/layout-engine ships 225 .d.ts files and zero .js — the implementation is in @superdoc/docx-engine, which is not in this repo. Hence an issue.
Context
Hebrew two-column layouts are common in the documents we handle (Torah literature — commentary set beside text), and today every one of them reads backwards on screen. Related: #3933 (Hebrew numbering), which you fixed.
Summary
In a section that carries
<w:bidi/>, column fill order is still left-to-right. Paragraph 1 lands in the left column and overflow spills into the right one. Word starts an RTL section in the right column and flows right-to-left.Column widths, gap and the text direction inside each column are all correct — only the column ordering axis is wrong. Export is clean (
<w:cols>and<w:bidi/>round-trip intact), so this is a render-only defect.Measured on
superdoc@2.10.0/@superdoc/docx-engine@0.9.0, Chrome 141, Windows 10.Reproduction
A minimal document is enough — 24 numbered paragraphs so the content overflows column 1, and an RTL section:
Open it in SuperDoc and read the paragraph numbers.
Measured
Geometry is correct. A4 page at
x=296 w=794, 1" margins, so a 602px content area:277 + 48 + 277 = 602, and the 48px gap is exactlyw:space="720". Nothing wrong there.The order is wrong. Reading each rendered line's
xand text:Expected in Word for this section: paragraphs 1–19 in the right column (x≈717), overflow into the left one (x≈392).
Control — the direction is not being read at all
Same document, same
w:cols, but English text and now:bidianywhere:Identical geometry, identical fill order. So the fill order is a fixed left-to-right, and the section direction is not consulted for columns at all — this is not an RTL mis-resolution, it is an axis that was never wired up.
Scope
w:colscomes from the file or is applied at runtime viasections.setColumns({ count: 2, gap: 0.5, equalWidth: true }).sectPrcomes back as<w:cols w:num="2" w:space="720" w:equalWidth="1"/> <w:bidi/>, so no data is lost and the file still opens correctly in Word. The defect is display-only.w:sep) and column-break targeting, though I did not measure those.Where it looks like it lives
Your published types already describe the contract that is not being honoured.
layout-engine/contracts/src/direction-context.d.ts:But
layout-engine/contracts/src/column-layout.d.tsresolves geometry on one axis only:and no function in that API —
getColumnGeometry,getColumnX,getColumnAtX,getColumnSeparatorPositions— takes a direction argument. SoSectionDirectionContext.pageDirectionappears to have no consumer on the column axis.Note that
getColumnAtXmatters for more than paint: once ordering is direction-aware, hit-testing has to flip with it, or clicks will select the wrong column.I would have opened a PR, but
superdoc/dist/layout-engineships 225.d.tsfiles and zero.js— the implementation is in@superdoc/docx-engine, which is not in this repo. Hence an issue.Context
Hebrew two-column layouts are common in the documents we handle (Torah literature — commentary set beside text), and today every one of them reads backwards on screen. Related: #3933 (Hebrew numbering), which you fixed.