Editing Arabic text inside a PDF, in the browser

· 9 min read

Read this post in Arabic

BlendPDF’s editor lets you open a PDF, click a line of Arabic, type it again and save. The letters join, the line still starts at the right margin, it keeps the file’s own font when that font has the letters you typed, and the saved line can be searched and copied. It runs in the browser, so the file isn’t uploaded.

Most online editors don’t do this yet. Sejda’s editor warns that right-to-left scripts “are not fully supported”, and iLovePDF says its Content Editor doesn’t support right-to-left languages. When we tested free editors on 12 November 2026, PDF24 and PdfBayt saved an added Arabic line as a picture, and PDF Candy saved real text with its words in reverse order. The comparison pages have the files.

This post is about why it’s hard, what we did, and where it still fails. The code is on GitHub, open source under the AGPL-3.0.

Adding a line, changing a line, then searching the saved file.

A PDF has glyphs, not letters

Text in a PDF is a list of glyph codes drawn at positions in a font. Nothing in the page says which letters those glyphs are. To search or copy, a viewer looks each code up in the font’s ToUnicode map, a small table the producer writes next to the font.

For Latin text, glyph and letter are mostly one to one. For Arabic they aren’t. A letter has up to four shapes depending on whether it joins the letter before it, the one after, both or neither. Lam followed by alef is drawn as one glyph. Dots and vowel marks can be glyphs of their own. The ToUnicode map has to undo all of that, with one entry per glyph.

So editing Arabic in a PDF is two problems at once: drawing the right glyphs in the right places, and leaving a map behind that turns them back into the right letters. A page can look perfect and still be unsearchable.

Writing new Arabic text

The Text tool writes new lines with three libraries. bidi-js runs the Unicode bidi algorithm for each paragraph. Lines are broken on the logical text first, then each line is reordered for display, so a date inside an Arabic sentence still reads left to right. fontkit shapes each run with the font’s own GSUB and GPOS tables, which picks the joining forms and the lam-alef ligature and places the marks. Then every glyph goes into the page with its own text matrix through pdf-lib.

pdf-lib’s own drawText can’t be used for Arabic. It has no bidi, and it drops every GPOS offset. Placing glyphs one by one needs four of pdf-lib’s internals, so it is pinned to exactly 1.17.1, and a test fails if any of them moves. The font is Cairo, served from our own domain, and only the glyphs a document uses are embedded.

Two bugs from this part took a while to find.

The lam-alef that came back as alef-lam. لا is one glyph for two characters. Viewers extract a line in display order, then flip right-to-left text back to logical order, and they flip the inside of a two-character ToUnicode entry along with it. Write the entry as لا and a search finds ال. So when one glyph stands for several right-to-left characters, we store them reversed, and the round trip comes out right. Chrome stores them in logical order, which is why pdftotext reads الأطلس in our Chrome-printed test page as األطلس.

Letters that lost their text on the second save. fontkit caches one glyph object per glyph id for the life of a font. When pdf-lib’s subsetter embeds a composite glyph, it pulls in the glyph’s parts, and fontkit caches those parts with no code points. In Cairo many letters are such parts: ا ح د ر س ع ه و, most lowercase Latin, several digits. The first export was fine. A second export in the same session got those letters back from the cache with no code points, so they had no ToUnicode entry and dropped out of search. The fix was to lay text out with one fontkit instance and give the subsetter its own.

Changing a line that’s already there

Adding text is the easy half. Changing an existing line means reading it, taking it out of the page without disturbing anything else, and drawing the new words so they match the rest of the line.

Reading it. pdf.js gives you a page’s text with getTextContent, but it reverses right-to-left items character by character, which turns Chrome’s لا glyph into ال. So the edit box is filled glyph by glyph instead. We collect the glyphs in the order they are drawn and reverse whole glyphs, not characters, for right-to-left runs.

Taking it out. The old line is removed with PDFium compiled to WebAssembly. It goes by whole text objects, picked by their baseline, not by a rectangle. Every page we’re about to rewrite is rendered first, then rendered again after the change and compared in 10 px tiles. If the colour of any tile outside the edited lines moves past a small threshold, the page is refused. After saving, the file is opened again, and if one old glyph is left on a replaced line, the save fails.

Drawing it back in its own font. Most PDFs embed a subset of each font, only the glyphs the document used, so you can’t just shape new text with the file’s font: the glyphs you need may not be in it. Instead we read every line the document draws in that font and record which glyph it uses for each letter in each joining form, plus kerning pairs, where marks sit on each base letter, and ligatures. A changed word is shaped into those units. Words you didn’t touch are redrawn from their original glyphs at their original positions, and only the changed part of the line re-flows, from its right edge for Arabic.

Fonts make this messier than it sounds. Some draw two forms of a letter with one glyph: Arial’s beh uses the same glyph for its initial and medial forms, and a right-joining letter like reh can use its isolated glyph as the final one. A reverse cmap only names the first form, so the harvest accepts exactly those two pairings and nothing looser.

The Persian letters in Chrome’s Arabic PDFs

We found this one on 12 November 2026, while testing our editor next to Sejda’s, and fixed it the same day.

Chrome writes a font’s reverse cmap into ToUnicode: for each glyph, a character the font maps to it. Times New Roman draws heh and heh doachashmee (a Persian and Urdu letter) with one glyph, and yeh and Farsi yeh with one glyph, and the reverse cmap lands on the Persian ones. Chrome also wraps each glyph in an ActualText span with the right Arabic letter. So the same glyph says two things:

% page content: the glyph, inside a span that says it is Arabic yeh
/Span <</ActualText <FEFF064A>>> BDC
<03F3> Tj
EMC

% the font’s ToUnicode map: the same glyph is Farsi yeh, initial form
<03F3> <03F4> <FBFE>
Glyph on the pageToUnicode saysAfter NFKCAn Arabic keyboard types
yeh, initial formU+FBFE Farsi yeh, initialی U+06CCي U+064A
heh, medial formU+FBAD heh doachashmee, medialھ U+06BEه U+0647

Tools that read ActualText, like pdftotext, get Arabic letters. pdf.js reads the ToUnicode map, so once the presentation forms are folded, لديها comes out as لدیھا. On screen the two are the same word. To a search box they are different strings.

Our editor reads with pdf.js, so the edit box showed the Persian letters. If the line was then saved in a look-alike font, we wrote what was in the box, Persian code points and all, with no ActualText to correct them. The words of that line you hadn’t touched stopped matching an Arabic search, even in pdftotext, which had found them in the original.

The fix reads the letters back as Arabic when the line says it’s Arabic. If the line has letters only Arabic uses (ة ك ي ى) and none only Persian or Urdu use (پ چ ژ گ ے and the like), the look-alikes are mapped back form for form: a joined Farsi yeh becomes yeh, a dotless final one becomes alef maksura. Persian and Urdu documents are left alone. On the keep-the-font side, heh and heh doachashmee now match in every form but the isolated one, so a line kept in its own font still finds its glyphs.

One part is still open. A line kept in its own font keeps the file’s own ToUnicode map, which still says Persian, exactly like the lines around it. Fixing that means rewriting the font’s map for the whole page, and we haven’t done it yet.

When the font can’t draw what you typed

Because fonts are subset, a changed word often needs a glyph the file doesn’t have: a name with a letter the document never used, or a digit. Then the line is redrawn in the closest font we ship, picked from the font’s name and its descriptor flags (serif, fixed pitch, bold, italic):

  • Liberation Sans, Serif and Mono for Arial, Times New Roman and Courier. They share the metrics, so the line keeps its length.
  • Carlito for Calibri, and Caladea for Cambria.
  • Noto Sans Arabic for Arabic, or Cairo when the file already used Cairo.

Arabic is the weak spot. Amiri would be the natural match for Times-style Arabic, but pdf-lib 1.17.1’s subsetter breaks it, and only one or two of its glyphs render. Noto Naskh Arabic draws the dots of most letters as separate glyphs and shares one dotless glyph between ب ت ث ن ي. With one ToUnicode entry per glyph, a line written that way can’t be mapped back to text, so it couldn’t be searched. That leaves a sans font for serif documents, and it shows. The editor tells you when it happens.

A changed word with vowel marks also goes to the look-alike, because we can’t place marks on a new letter combination reliably from what the file shows.

An Arabic work certificate printed from Chrome in Times New Roman: the title and the first line, right-aligned.
Before: our Arabic test page, printed from Chrome in Times New Roman.
The same line after the name was changed in BlendPDF: right to left, inside the margins, letters joined, in a plainer sans-serif font.
After changing the name: the letters join and the line still starts at the right margin, but it is now in Noto Sans Arabic, plainer than the original.

What still fails

  • Pages with shadows or soft masks are refused. PDFium 2.15.1 drops soft masks when it rewrites a page, so a CSS text shadow or a Word shape effect would disappear. The before-and-after render catches it, and the editor won’t change text on that page. The workaround is a white box over the old words and a new line with Text.
  • One line at a time. Words don’t flow onto the next line, so rewriting a paragraph is still a job for a word processor.
  • The font can change, as above, and for serif Arabic it’s visible.
  • Lines kept in their own font keep the file’s Persian letters in their text layer, as above.
  • Lines drawn twice. Some producers fake bold or a shadow by drawing every glyph twice. We can’t tell which copy is the text, so the edit box starts empty and you type the line yourself.

Try it, or read the code

The editor is at blendpdf.com/edit-pdf. Choose Edit existing text (still in beta), click a line and type. If you want a file to try, here is the Arabic test page we use for the comparisons.

The code is on GitHub at ogcalyps/blendpdf. New text is in lib/pdf-text/, keeping the font is in lib/edit-pdf/keep-font/, and the Persian fold is in lib/edit-pdf/prefill.ts. If you have a PDF the editor gets wrong, an issue with the file attached is the most useful thing you can send.