Why Hebrew looks right on screen and reverses in the PDF, what bidi reordering actually is, why fonts are the real cause of missing glyphs, and the mixed Hebrew-English number problem that no CSS fixes.
Key takeaways
- The browser runs the bidirectional algorithm for you. Most PDF and image libraries do not - which is exactly why Hebrew that looks correct on screen comes out reversed in the generated file.
- Missing or boxed characters are a font problem, not an encoding problem. The text is fine; the font you asked the renderer to use has no Hebrew glyphs. Bundle a font that does.
- Use logical CSS properties - start/end rather than left/right. A layout built with physical directions has to be rewritten, not flipped, when the locale changes.
- Mixed Hebrew text with numbers, IDs and Latin terms is where bidi actually bites. Invoice numbers, phone numbers and SKUs inside a Hebrew sentence need explicit isolation, not hope.
Hebrew in business software looks like a problem solved twenty years ago, right up until the invoice your system generates shows the customer name reversed, or the invoice number jumps to the wrong side of the sentence. These bugs recur on every project, and they are almost always the same four.
1. The most common bug: correct on screen, reversed in the PDF
This is complaint number one, and the explanation is simple once you see it.
The browser runs the bidirectional algorithm for you. You hand it a Hebrew string and it decides how to order it for display - where the sentence starts, where numbers go, where punctuation lands.
Most PDF libraries do not. They draw glyphs in the order received, left to right, with no awareness of directionality. The result is Hebrew that appears reversed.
There are two solutions, and choosing between them is the real architectural decision:
- Render through a browser. Build HTML and let a browser engine produce the PDF. Upside: you get bidi, CSS layout and font loading for free, exactly as it looks on screen. Downside: you need a browser on the server, which is genuine operational weight.
- Draw directly with a PDF library. Faster and easier to deploy, but then you own the bidirectional reordering - every Hebrew string must pass through a bidi library that reorders it before drawing.
Both work. What does not work is drawing directly without reordering and hoping.
2. Boxes and missing characters - that is a font problem
If text renders as boxes, question marks or nothing, it is almost never encoding. The text is fine; the font the renderer is using simply has no Hebrew glyphs.
PDF libraries' default fonts are typically classic Latin-1 families with no Hebrew coverage at all.
The fix is to bundle a font with real Hebrew coverage and point at it explicitly. Two things that are easy to miss:
- Ship the font file with the project rather than relying on what is installed on the server. What works on your development machine does not exist on a clean Linux box, and you find out in production.
- Check the licence. A font embedded in a file sent to a customer is distribution, and not every font licence permits it.
3. Layout: logical, not physical
The structural mistake is building layout in physical directions - margin-left, padding-right, text-align: left - then trying to "flip" everything when Hebrew arrives.
That does not work, because not everything should flip. A logo meant to stay on one side, a play icon, a timeline - a blanket flip breaks them.
The right approach is logical properties: margin-inline-start instead of margin-left, text-align: start instead of left. They flip themselves according to dir, and whatever should not flip simply stays physical - deliberately and explicitly.
A practical point: decide up front which components do not flip, and document it. A main navigation kept in a fixed direction is a legitimate design decision, but it should be a decision rather than the result of a bug.
4. The hardest bug to find: mixed text
Pure Hebrew works. Hebrew with numbers, identifiers and Latin terms is where bidirectionality actually bites.
Real examples: Tax invoice 2026-00123 for ACME Ltd written in Hebrew, a phone number inside a Hebrew sentence, an alphanumeric SKU, a URL in a WhatsApp message.
What happens: the bidi algorithm classifies each character by its inherent direction, and numbers and punctuation are "neutral" - their direction is decided by what surrounds them. So a hyphen between digits, a bracket, or a full stop at the end of a sentence can jump to the wrong place, and the meaning changes. An invoice number rendered backwards is not a cosmetic issue.
The fix is explicit isolation. Any non-Hebrew run inside a Hebrew sentence - an identifier, a number, an English name - should be wrapped in directional isolation so it is ordered as a self-contained unit rather than being influenced by its neighbours. In HTML that is an element or attribute declaring isolation; in raw text, the corresponding control characters.
The riskiest place is text that never passes through HTML at all - an SMS, a WhatsApp message, a filename, an email subject line. There is no CSS and no dir there, and only the control characters work.
Places people forget
- Filenames. A PDF with a Hebrew filename can break in the download header and, in some systems, be stored corrupted. An English filename with a numeric identifier is the safe choice.
- Excel export. A Hebrew column looks fine, but a mixed column of text and numbers suffers the same neutrality problem - and the sheet's own direction is a separate setting.
- Email subject lines. A Hebrew subject needs the right encoding; without it some clients show gibberish. The body can look perfect while the subject is broken.
- Logs. Hebrew in a terminal log is displayed according to the terminal's directionality, which is usually wrong. That is not a bug in your system - but it will confuse you while debugging if you do not know it.
How to actually test this
Screen testing is not enough, because the screen gets bidi for free. What to test:
- A PDF generated in production, not in development - that is where the fonts are missing.
- A record with mixed text - a Hebrew customer name, a document number and an amount. Not just pure Hebrew.
- An email actually sent, including the subject line, in two different mail clients.
- A message going out over a text channel - SMS or WhatsApp - with an identifier inside a Hebrew sentence.
- The empty case. A missing Hebrew field should not break the layout or leave punctuation dangling.
Those four bugs cover almost everything that shows up in practice. They are not hard to fix - they are hard to identify, because all four look like "a Hebrew problem" while being four entirely different things.
Frequently asked questions
Why does Hebrew look correct on screen but reversed in a generated PDF?
Because the browser runs the Unicode bidirectional algorithm for you and most PDF libraries do not - they draw glyphs in the order received, left to right. Either render the PDF through a browser engine, which gives you bidi and CSS layout for free, or pass every Hebrew string through a bidi reordering library before drawing it.
Why does Hebrew show as boxes or question marks in my PDF?
That is a font problem, not encoding. The text is correct but the font the renderer uses has no Hebrew glyphs - PDF library defaults are usually Latin-only families. Bundle a font with real Hebrew coverage into the project rather than relying on what happens to be installed on the server, and confirm its licence permits embedding.
Why does an invoice number appear in the wrong place inside a Hebrew sentence?
Because numbers and punctuation are directionally neutral - the bidi algorithm decides their direction from the surrounding characters. Inside a Hebrew sentence, a hyphen between digits or a trailing full stop can land on the wrong side and change what the identifier reads as. Wrap any non-Hebrew run - identifiers, numbers, Latin names - in explicit directional isolation.
Should I flip the whole layout for Hebrew?
No - use logical CSS properties instead. margin-inline-start rather than margin-left, text-align: start rather than left, and so on. These flip themselves based on dir, while anything that genuinely should not flip - a logo position, a play icon, a timeline - stays physical deliberately. A blanket flip breaks those.
Where does RTL break outside the web page?
Anywhere there is no CSS and no dir attribute: SMS and WhatsApp messages, email subject lines, filenames, Excel exports and terminal logs. In those channels only Unicode control characters provide isolation, so mixed Hebrew-and-identifier text is the case most likely to render wrongly - and it is also the case least likely to be tested.
Keep reading
Related service
Web Development
Fast, custom websites and web apps you fully own.
About the author
Yehonatan Saadia
Freelance automation, web & MVP engineer
I'm Yehonatan Saadia, a senior engineer who builds business automation, custom websites, and MVPs for small and mid-sized companies across the US, Europe, and Israel. These guides come from real client work, not theory.
Work with meHave a project like this?
Tell me what you're trying to automate or build and I'll tell you the fastest reliable way to ship it.
