Technical Article

RtLTextOut in HotPDF: Right-to-Left PDF Text in Delphi

Send the Arabic sentence يوضح ملف PDF هذا to plain TextOut and the page that comes back is wrong in two ways at once. The words run left to right instead of right to left, and the letters sit apart in their isolated forms instead of joining into connected words. Nothing errors. The Delphi compiles, the file opens, and a reviewer who reads Arabic tells you the output is unusable. The fix is one call, not a library swap: HotPDF routes right-to-left text through a separate method, RtLTextOut, that handles the reordering plain TextOut will not. Four things about that method decide whether the output is usable: what it does to the string, how its charset argument selects the script, the document-level change it makes as a side effect, and the font work that has to come first.

Why right-to-left needs its own call

A PDF content stream does not store editable text. It stores glyphs at fixed positions, which means whatever emits the stream owns the job of deciding what order those glyphs go in. On screen the operating system did that for you: drop Arabic into a TEdit and the OS text stack reorders and joins it before you ever see a pixel. That is exactly why the string looks perfect in your form and breaks in the PDF. The desktop did the work silently, and the moment you write your own content stream, the work is back on your side.

TextOut takes you at your word. It draws the codepoints in the order you pass them, left to right, which is correct for Latin, Cyrillic, and CJK and wrong for Arabic and Hebrew. RtLTextOut is the call that reorders the line into visual right-to-left order first, then draws. HotPDF keeps the two methods deliberately separate rather than guessing direction from the characters, so the choice of which one to call is the choice of which script behavior you get. The deeper mechanics of bidirectional reordering and Arabic contextual joining are their own subject, covered in the article on Arabic and RTL text shaping with HotPDF; here the practical point is narrower. Use RtLTextOut for right-to-left runs, use TextOut for everything else, and never route one through the other.

Diagram of how RtLTextOut reorders a mixed Arabic and Latin line into visual right-to-left order before drawing it into a PDF
RtLTextOut reorders each line into visual order before drawing: right-to-left runs keep their sequence while embedded Latin words and digits read left to right inside the line.

The charset argument decides the script

What tells RtLTextOut whether it is laying out Arabic or Hebrew is not the method, it is the font. SetFont takes a Windows charset as its fourth argument, and that value carries the script rules into the right-to-left call: 178 selects Arabic, 177 selects Hebrew. Set the charset, then draw, and the two lines below come out in correct reading order without any further configuration.

// Arabic: charset 178 tells RtLTextOut to apply Arabic rules
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');

// Hebrew: charset 177 switches the rules to Hebrew
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');

Two details about those coordinates are easy to miss. The position you pass is still the start of the run in the page's own coordinate system, measured from the bottom-left corner with Y growing upward, the same origin every TextOut uses; RtLTextOut changes the glyph order, not where the page measures from. And as with any drawing call, the SetFont has to come first and has to be repeated after every AddPage, because the current font does not survive a page break. Forget the repeat and the second page falls back to whatever font was active, which for Arabic usually means empty boxes.

It does not reverse text you already reversed

The single mistake that swallows the most debugging time here is feeding RtLTextOut a string you already flipped by hand. People reach this method after a first attempt with plain TextOut came out backwards, and a common stopgap is to reverse the characters in code before drawing. RtLTextOut reverses internally on its own, so a pre-reversed string gets reversed a second time and lands right back where it started. Pass the text in logical order, the order you would type it and read it aloud, and let the call do the reordering.

The trap is nastier than a plain flip because a double-reversed string can look correct for one all-Arabic test phrase and then break the instant a line carries a Latin word or a number. Inside a right-to-left line those embedded runs are supposed to read left to right, and hand-reversal wrecks that nesting while the pure-Arabic case happens to survive it. So the bug sails through your first smoke test and surfaces later on a real invoice with an account number in it. Strip out every manual reversal the moment you switch to RtLTextOut.

The Direction side effect worth knowing

Calling RtLTextOut changes more than the line you are drawing. It also flips the document's reading-direction preference to right-to-left, the same thing you would otherwise set yourself through the Direction property. That setter adds vpDirection to the document's ViewerPreferences, which tells a viewer how to arrange two-up spreads and which side a facing-page layout starts from. When the whole document is Arabic or Hebrew this is exactly what you want, and you get it for free.

It is worth knowing about precisely because it is invisible on a single page. If the document is mostly left-to-right with one right-to-left block, the first RtLTextOut call will still tip the whole file's preference, and nothing in your one-page proof will show it. The symptom appears weeks later when someone prints a duplex booklet and the spreads come out mirrored. If that is not what you want, set Direction back explicitly after the right-to-left run:

// RtLTextOut already set the document direction to RightToLeft;
// restore left-to-right if the document is predominantly LTR
Pdf.Direction := LeftToRight;

For a document that genuinely reads right-to-left, leave it alone. The point is to know the call has a document-wide effect so the booklet surprise never happens.

Register the font you ship, not the one you hope is installed

None of the reordering matters if the font has no glyphs to draw. The classic failure is a report that renders flawlessly on the developer's machine, where Arial Unicode MS happens to be present, and comes out as rows of empty boxes on a customer's server where Windows quietly substituted a font with no Arabic coverage at all. The cure is to stop trusting installed system fonts and register one you ship with the application.

// Ship a known Arabic font and register it before drawing
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');

Two boundaries ride along with registration. A font brought in through RegisterUnicodeTTF gets embedded, and HotPDF's embedded Unicode handling needs the document at PDF 1.5 or later; that only bites if something downstream insists on PDF 1.4, but when it does the failure is silent. The other is legal rather than technical: TrueType files carry embedding-permission bits, and a face that looks fine on screen can be licensed in a way that forbids shipping it inside customer documents. Confirm the license before you embed, not after a complaint.

A complete console example

Putting the pieces together, here is a self-contained program that writes one page with an Arabic line, a Hebrew line, and a mixed line carrying a Latin product name. Each block sets its charset, then draws in logical order.

program RtLTextOutDemo;

{$APPTYPE CONSOLE}

uses
  HPDFDoc;   // HotPDF main unit

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'RtLTextOut.pdf';
    Pdf.BeginDoc;

    // A Latin heading goes through the ordinary TextOut path
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');

    // Arabic: charset 178, logical order, RtLTextOut does the reordering
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
    Pdf.CurrentPage.RtLTextOut(400, 720, 0,
      'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');

    // Hebrew: charset 177
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
    Pdf.CurrentPage.RtLTextOut(400, 680, 0,
      'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');

    // Mixed line: the embedded Latin word still reads left to right
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
    Pdf.CurrentPage.RtLTextOut(400, 640, 0,
      'مرحبا بالعالم! تم إنشاؤه بواسطة HotPDF');

    Pdf.EndDoc;
    Writeln('Wrote RtLTextOut.pdf');
  finally
    Pdf.Free;
  end;
end.

Run it and open the result. The Arabic and Hebrew lines read right to left, the letters join where the script joins them, and in the last line the token HotPDF sits left-to-right inside the Arabic run, which is the spec-correct result even though it surprises anyone seeing bidirectional layout for the first time. That last point is worth writing into your acceptance criteria before a native reader reviews the output, because the embedded run reading the "wrong" way relative to the surrounding script is the single thing most often filed as a bug when it is not one.

Verifying the output

A page that looks right is not the same as a page that is right, so check it the way a downstream system will. Copy the text back out of the viewer and compare the codepoints against your source string; correct visual order with scrambled logical order is a real failure mode. Run the viewer's in-document search for a word you can see on the page. Then open the file on a machine that does not have your development fonts, the one most likely to expose a silent substitution. None of that replaces a native speaker reading one genuine document, which catches problems no synthetic test string will, so put that review on the calendar before the format ships.

RtLTextOut handles bidirectional reordering and Arabic contextual joining, which covers the great majority of right-to-left report and document work. Where it stops, scripts that need more than reordering and joining such as the Indic families, and the optional OpenType features that go through single-glyph substitution, is mapped out alongside the glyph-coverage and shaping details in the companion article on Arabic and RTL text shaping with HotPDF.

The RtLTextOut, SetFont, and RegisterUnicodeTTF calls shown here are part of the HotPDF Component for Delphi and C++Builder.