Writing direction
Last updated 2026-11-02
- writing direction
- vertical text
- RTL
- Arabic
- joining
- bidi
- writing-mode
- Mongolian
- shaping
- text engine
On this page
The same text can come out right in one application and wrong in the next. Direction problems are the most common cause, and they are really three separate things.
Three different things
- Character order: left to right (LTR) or right to left (RTL);
- Line direction: horizontal or vertical;
- Joining and shaping: whether adjacent characters connect, and whether a character swaps form depending on its position.
When a tool says "no Arabic support", it usually means the third; "no vertical text" means the second. To the person looking at the screen the symptom is the same: the text looks wrong.
Right to left
Arabic, Hebrew, Persian and Urdu run right to left. The key point: text is stored in logical order — the characters you typed first come first — and only the display is reversed.
That means copying, searching and character counting are all unaffected. Only rendering flips. Which also means that manually reversing a string in code to "simulate RTL" is always wrong: the moment someone copies it, the order is broken.
When RTL and LTR appear together (an English brand name or a number inside Arabic), the side each run lands on is decided by the Unicode bidirectional algorithm. Three counter-intuitive consequences:
- digits inside RTL text still run LTR, only their whole block moves;
- brackets and a trailing full stop are mirrored in an RTL run;
- when a pure LTR run (a URL, a code snippet) sits inside RTL text, which side the punctuation next to it belongs to is often not what you expect.
All of this is handled by the shaping engine; CSS only exposes direction and unicode-bidi.
dir changed: the two runs swapped order and the full stop moved to the far left. But the Arabic reads right to left in both boxes — dir only sets the base direction.
This is easy to misread: the Arabic is not being “reversed” in either box. What changed is the order of the runs and which side the punctuation belongs to. There are two layers of direction:
- base direction (
dir/ CSSdirection): which side a line aligns to, which run comes first when directions differ, which edge the trailing punctuation sticks to; - the direction of each character: decided by its Unicode property. Arabic letters are class R, so a run of them reads right to left inside any paragraph, LTR or RTL.
So dir=ltr does not reverse Arabic — it treats the Arabic as an RTL run embedded at the left. What actually breaks is everything at the paragraph level: a trailing full stop, where mixed-run punctuation lands, and line alignment.
One thing in the figure that looks like nothing changed: brackets are mirrored in an RTL run (( is drawn with the ) glyph). The point of mirroring is exactly to keep (2026) looking like “open bracket on the left” in both directions — it looks unchanged because it was handled deliberately.
Joining
Arabic letters look different at the start, middle and end of a word, and different again in isolation. The text always stores the same code point; changing form is something the renderer does. The font's GSUB table carries the features for it (isol / init / medi / fina) and the shaping engine picks forms from context.
So "the letters do not connect" is rarely a character problem — usually nothing is choosing forms: either the application is not shaping at all, or the font has none of those features. More in Shaping and fallback and GSUB / GPOS.
| Position | Sample | Meaning |
|---|---|---|
| Isolated | ح | no neighbours |
| Initial | حـ | joins to the right only |
| Medial | ـحـ | joins on both sides |
| Final | ـح | joins to the left only |
Those four cells are themselves a test: if they render as four visibly different shapes, the font has joining features; if they all look like the same letter plus a few strokes, the font probably only has isolated forms.
Why letters stop connecting
In rough order of frequency:
- The application lays out text itself instead of using the system shaper. Each letter is drawn on its own, so everything is isolated;
- The font has no such features. Render Arabic with a Latin-only font and the fallback may know only isolated forms;
- It is no longer text. After converting to outlines, exporting as graphics, or tracing an image, there are no forms to choose from (see Text to outlines);
- It has been split into separate objects. One text frame per letter, or a per-character SVG
<text>— nothing is adjacent to anything; - It went through a screenshot or OCR round trip. Joining information is gone by the time pixels come back.
The quickest test: can this be selected, copied, searched? If not, it is not text, and none of the direction or joining discussion applies.
Vertical text
Vertical Chinese, Japanese and Korean is not horizontal text rotated 90 degrees:
- punctuation needs different glyphs (the full stop, comma, brackets and corner quotes all have vertical forms), supplied by the font's
vert/vrt2features; - two-digit numbers usually lie on their side, occupying one full width —
text-combine-uprightin CSS; - ruby annotations move to the right of the character;
- Latin runs can be rotated as a block or set upright per letter, a design decision exposed as
text-orientation.
If the font has no vert, punctuation stays in its horizontal position — it looks "crooked", but what actually failed is one GSUB substitution. Chinese has other direction-related rules; see Chinese typography.
Horizontal
Vertical
vert, not a rotation.
Why support varies so much
Because this is not in the font, it is in the text engine:
- browsers (HarfBuzz) handle shaping, bidi and vertical text completely;
- Word uses the system's DirectWrite and is, oddly, one of the most reliable for complex scripts;
- InDesign needs the Middle Eastern edition for full RTL support;
- in vector software, point text, area text and type on a path are often different engines, each with its own bugs.
That explains a workaround common in studios: typeset in Word, take a screenshot, trace it into vectors. What it really works around is a missing text engine — at the price of turning text into a picture.
Fallback workflow
If the output really has to come from a tool that cannot shape:
- typeset where shaping works (Word, a browser, InDesign with RTL support);
- export PDF and check the text is still selectable — selectable means it is still text;
- if you do need curves, convert then, and record the font name and its licence (see Text to outlines);
- keep screenshot-plus-trace as the last resort: it is the only path whose information loss cannot be undone.
Checklist
- Can this be selected, copied, searched? (decide first whether it is text at all)
- Does the font have
init/medi/fina(joining) andvert/vrt2(vertical)? dirsets the base direction of a paragraph, it does not “reverse the text” — do not conflate the two.- Is the RTL text stored in logical order, or was it reversed by hand in code?
- In vertical mode, is the punctuation orientation and the posture of digits correct?
- Is this application using the system shaper, or its own engine?