w:bidi

Bidirectional Text (RTL)

Right-to-left paragraph layout, run-level text direction, and how bidi interacts with alignment, indentation, and tab stops.

Bidirectional (bidi) support in OOXML handles right-to-left scripts like Arabic and Hebrew. It operates at two levels: w:bidi sets the paragraph's base direction, and w:rtl controls individual run reading order. Getting these right is the difference between text that renders correctly and text that's backwards.

Structure

w:pPr (paragraph properties)
├── w:bidi          Paragraph base direction (RTL layout)
├── w:jc            Alignment — start/end are logical, flip with bidi
├── w:ind           Indentation — start/end flip with bidi
└── w:tabs
    └── w:tab       Tab stops — measured from leading edge (right for RTL)

w:rPr (run properties)
├── w:rtl           Run reading order (right-to-left)
├── w:cs            Treat run as complex script
├── w:rFonts        @w:cs — complex script font
└── w:lang          @w:bidi — bidi language (ar-SA, he-IL, etc.)

Paragraph Direction — w:bidi

The w:bidi element on w:pPr sets the paragraph's base direction to right-to-left. This flips four things: indentation (start/end swap sides), alignment (start/end resolve to opposite edges), tab stop measurement (from right edge instead of left), and text flow direction. It does NOT reorder characters within runs — that's w:rtl's job.

<w:p>
  <w:pPr>
    <w:bidi/>
  </w:pPr>
  <w:r>
    <w:rPr><w:rtl/></w:rPr>
    <w:t>مرحبا بالعالم</w:t>
  </w:r>
  <w:r>
    <w:t xml:space="preserve"> - Hello World</w:t>
  </w:r>
</w:p>

Alignment with Bidi

The w:jc element uses logical values start and end that flip based on paragraph direction. start means the leading edge: left for LTR, right for RTL. end means the trailing edge. The values left, right, and center are always physical and don't flip. Arabic justify variants (lowKashida, mediumKashida, highKashida) extend joiners between characters instead of adding word spacing.

<w:p>
  <w:pPr>
    <w:bidi/>
    <w:jc w:val="center"/>
  </w:pPr>
  <w:r>
    <w:rPr><w:rtl/></w:rPr>
    <w:t>نص عربي في الوسط</w:t>
  </w:r>
</w:p>
<w:p>
  <w:pPr>
    <w:bidi/>
    <w:jc w:val="start"/>
  </w:pPr>
  <w:r>
    <w:rPr><w:rtl/></w:rPr>
    <w:t>محاذاة البداية — تعني اليمين في الفقرات العربية</w:t>
  </w:r>
</w:p>
w:jc valueLTR resultRTL result
startLeftRight
endRightLeft
centerCenterCenter
bothJustify (word spacing)Justify (word spacing + lowKashida)
leftLeftLeft (physical, doesn't flip)
rightRightRight (physical, doesn't flip)
lowKashidaJustifyJustify (short kashida extension)
mediumKashidaJustifyJustify (medium kashida)
highKashidaJustifyJustify (widest kashida)
distributeJustify (char + word spacing)Justify (char + word spacing)

Indentation with Bidi

The w:ind element uses start/end attributes that are logical — they refer to the leading and trailing edges of the paragraph. For an RTL paragraph, start is the right margin and end is the left margin. The firstLine and hanging attributes also apply relative to the start edge.

<w:p>
  <w:pPr>
    <w:bidi/>
    <w:ind w:start="720"/>
  </w:pPr>
  <w:r>
    <w:rPr><w:rtl/></w:rPr>
    <w:t>فقرة مع مسافة بادئة من اليمين</w:t>
  </w:r>
</w:p>

Tab Stops with Bidi

Tab stop positions in RTL paragraphs are measured from the right edge of the text area, not the left. The w:tab alignment values start and end also flip. A start-aligned tab in an RTL paragraph aligns text to the right of the tab position.

<w:p>
  <w:pPr>
    <w:bidi/>
    <w:tabs>
      <w:tab w:val="left" w:pos="8640" w:leader="dot"/>
    </w:tabs>
  </w:pPr>
  <w:r>
    <w:rPr><w:rtl/></w:rPr>
    <w:t>عنوان الفصل</w:t>
  </w:r>
  <w:r><w:tab/></w:r>
  <w:r>
    <w:rPr><w:rtl/></w:rPr>
    <w:t>٤٢</w:t>
  </w:r>
</w:p>

Run-Level Direction — w:rtl

The w:rtl element on w:rPr sets the reading order of a single run to right-to-left. This is separate from w:bidi — you can have an LTR paragraph with RTL runs (inline Arabic in English text) or an RTL paragraph with LTR runs (English words in Arabic text). The spec warns: don't use w:rtl on strong LTR characters — behavior is undefined.

<w:p>
  <w:r>
    <w:t xml:space="preserve">English text then </w:t>
  </w:r>
  <w:r>
    <w:rPr><w:rtl/></w:rPr>
    <w:t>نص عربي</w:t>
  </w:r>
  <w:r>
    <w:t xml:space="preserve"> then English again</w:t>
  </w:r>
</w:p>

Mixed Document — RTL and LTR Paragraphs

<w:p>
  <w:pPr><w:bidi/></w:pPr>
  <w:r>
    <w:rPr><w:rtl/></w:rPr>
    <w:t>هذه فقرة كاملة باللغة العربية</w:t>
  </w:r>
</w:p>
<w:p>
  <w:r>
    <w:t>This is a complete English paragraph</w:t>
  </w:r>
</w:p>
<w:p>
  <w:pPr><w:bidi/></w:pPr>
  <w:r>
    <w:rPr><w:rtl/></w:rPr>
    <w:t>فقرة عربية أخرى بعد الإنجليزية</w:t>
  </w:r>
</w:p>

Implementation Notes

w:bidi and w:rtl are independent

w:bidi flips paragraph layout (indentation, alignment, tabs, text direction). w:rtl on w:rPr controls run reading order. You need both — without w:rtl on the runs, text lands in the right position but characters read backwards.

Tab positions are from the right edge in RTL

Tab pos is measured from the leading edge (§17.3.1.37). For RTL, that's the right margin. If your layout engine always measures from the left, every RTL tab stop lands on the wrong side.

start/end are logical, left/right are physical (Word)

jc="start" flips with direction; jc="left" does not. Don't resolve startleft during import — you'll lose the logical intent. Same applies to w:ind start/end attributes.

Section bidi is separate from paragraph bidi

w:sectPr > w:bidi controls page chrome (page numbers, column order). It doesn't affect text. You need paragraph-level w:bidi for text layout.

Schema

ElementParentDescription
w:bidiw:pPrParagraph base direction — sets RTL layout for indentation, alignment, tabs
w:rtlw:rPrRun reading order — right-to-left character ordering
w:csw:rPrComplex script flag — forces complex script font/size
w:bidiw:sectPrSection layout direction — page-level RTL (separate from paragraph bidi)
Attribute / ValueContextDescription
w:jc val="start"w:pPrLeading edge alignment — left for LTR, right for RTL
w:jc val="end"w:pPrTrailing edge alignment — right for LTR, left for RTL
w:ind startw:pPrLeading edge indent — right side for RTL
w:ind endw:pPrTrailing edge indent — left side for RTL
w:tab posw:pPr > w:tabsTab position from leading edge — from right for RTL
w:lang bidiw:rPrBidi language tag (ar-SA, he-IL) — affects neutral char resolution

Spec reference: ECMA-376 §17.3.1.6 (bidi), §17.3.2.30 (rtl), §17.3.1.13 (jc), §17.18.44 (ST_Jc), §17.3.1.37 (tab), §17.3.1.12 (ind), §I.2 (Bidi annex)