ScreenshotNeo

BlogHTML to image & PDF

How to Generate Right-to-Left PDFs With iTextSharp XmlWorker

Learn what XMLWorker handles for RTL tables, where it falls short, and how to build and migrate a robust Arabic or Hebrew PDF pipeline.

By the ScreenshotNeo team30 September 20267 min read

How to Generate Right-to-Left PDFs With iTextSharp XmlWorker

Direct answer: iTextSharp XMLWorker can derive a run direction for generated HTML table cells, but that source-level behavior is not an end-to-end guarantee for every paragraph, list, nested span, or mixed-direction run. XMLWorker is deprecated, and iTextSharp is end of life, so new .NET projects should evaluate iText pdfHTML instead. Whichever route you choose, validate representative Arabic, Hebrew, punctuation, numerals, mixed English and RTL text, tables, and lists with the fonts used in production.

What XMLWorker actually guarantees

The XMLWorker TableData implementation calls GetRunDirection(tag) and assigns the derived value to the generated HtmlCell when it is not RUN_DIRECTION_NO_BIDI. This is direct evidence for table-cell handling. It does not prove that one HTML attribute configures bidirectional layout correctly for every element in an XHTML document.

The official XMLWorker package metadata describes the component as deprecated and recommends iText for new projects. The iTextSharp repository describes iTextSharp as end of life, with only security fixes planned. See the XMLWorker source and iTextSharp repository.

Choose XMLWorker or pdfHTML

Situation Practical choice Reason
Existing application already depends on XMLWorker Keep it temporarily while you add regression coverage Changing converters can require API, CSS, font, and layout changes.
New .NET application Evaluate iText pdfHTML It is the current vendor-documented HTML-to-PDF route and includes an Arabic/Hebrew example.
Mixed-direction documents are business-critical Prototype with production fonts and content before committing Direction, shaping, line breaking, and fallback depend on the actual document.

Do not assume pdfHTML is a drop-in replacement. Treat migration behavior and exact font configuration as implementation-specific.

RTL direction, font shaping, and layout all participate in the HTML-to-PDF pipeline.
RTL direction, font shaping, and layout all participate in the HTML-to-PDF pipeline.

XMLWorker implementation in C#

The following is a starting point for a legacy XMLWorker project. Pin the package versions used by your application and verify the output visually and through text extraction. This example intentionally keeps direction declarations in the HTML so you can test the behavior of your own converter and content.

using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.tool.xml;
using iTextSharp.tool.xml.css;
using iTextSharp.tool.xml.html;
using iTextSharp.tool.xml.pipeline;
using iTextSharp.tool.xml.pipeline.css;
using iTextSharp.tool.xml.pipeline.end;
using iTextSharp.tool.xml.pipeline.html;

class Program
{
    static void Main()
    {
        var html = @"<html>
<head>
  <meta charset='utf-8' />
  <style>
    body { font-family: Arial; }
    .rtl { direction: rtl; text-align: right; }
    table { width: 100%; border-collapse: collapse; }
    td { border: 1px solid #999; padding: 6px; }
  </style>
</head>
<body>
  <h1 class='rtl'>مرحبا بالعالم</h1>
  <p class='rtl'>هذا نص عربي مع English 123.</p>
  <table dir='rtl'>
    <tr><td>العنصر</td><td>القيمة 42</td></tr>
  </table>
</body>
</html>";

        using (var output = File.Create("rtl-xmlworker.pdf"))
        using (var document = new Document(PageSize.A4, 36, 36, 36, 36))
        {
            var writer = PdfWriter.GetInstance(document, output);
            document.Open();

            var cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(true);
            var htmlContext = new HtmlPipelineContext(null);
            htmlContext.SetTagFactory(Tags.GetHtmlTagProcessorFactory());
            var pipeline = new CssResolverPipeline(
                cssResolver,
                new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer)));

            using (var reader = new StringReader(html))
            {
                XMLWorkerHelper.GetInstance().ParseXHtml(pipeline, reader);
            }
            document.Close();
        }
    }
}

This sample demonstrates HTML direction declarations and a table that XMLWorker can process through its table-cell run-direction path. It is not proof that all elements, nested bidirectional runs, or your selected font will render correctly.

Fonts are part of RTL correctness

  • Use a font that contains every Arabic, Hebrew, Latin, punctuation, and numeral character your documents require.
  • Register and embed the font in the way required by your iText version and license.
  • Test shaping, joining forms, diacritics, and fallback characters. A missing glyph can look like a direction bug.
  • Keep font files and registration code under source control so deployments do not silently use a different system font.

The research sources do not establish a universal XMLWorker font recipe. Select a font deliberately and verify the generated PDF.

How to test mixed-direction content

  1. Create fixtures for Arabic, Hebrew, and a paragraph containing both RTL and English words.
  2. Add Arabic-Indic and Western numerals, dates, currency, parentheses, slashes, and punctuation.
  3. Include headings, paragraphs, nested spans, ordered and unordered lists, links, and tables.
  4. Test long lines, wrapping, empty cells, and content containing HTML entities.
  5. Open the PDF in more than one viewer and extract text with your normal search or indexing tool.
  6. Compare the result with a reference rendering and keep the fixture PDFs in regression tests.

Moving to iText pdfHTML

For new or migrating .NET work, evaluate pdfHTML’s HtmlConverter workflow. The official .NET repository lists a specific example for converting HTML containing Arabic and Hebrew. The dossier does not verify that example’s exact font setup or output, so adapt it to your own fonts and content.

using System.IO;
using iText.Html2pdf;

class Program
{
    static void Main()
    {
        var html = File.ReadAllText("rtl.html");
        using var input = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(html));
        using var output = File.Create("rtl-pdfhtml.pdf");
        HtmlConverter.ConvertToPdf(input, output);
    }
}

Start from the current pdfHTML documentation and Arabic/Hebrew example in the official .NET repository. Confirm package versions, font registration, and licensing before production deployment.

Direction techniques and their limits

Technique Use Limit
dir='rtl' Expresses the base direction of an HTML element Does not guarantee correct shaping or every nested mixed-direction run.
CSS direction: rtl Sets direction in stylesheets Converter CSS support and element handling must be verified.
Right text alignment Places lines against the right edge Alignment is not the same as bidirectional ordering.
Table-cell run direction XMLWorker applies a derived direction to generated cells Evidence is limited to table-cell handling.
Table-cell direction handling does not establish identical behavior for every HTML element.
Table-cell direction handling does not establish identical behavior for every HTML element.

Troubleshooting

Arabic or Hebrew appears as boxes

Cause: the selected font lacks glyphs or was not loaded. Fix: choose a font covering the script, register it explicitly, and verify the deployed font path.

Letters are disconnected

Cause: shaping support or font configuration is incomplete. Fix: reproduce with a minimal paragraph, confirm the converter and font versions, and compare with the vendor’s current pdfHTML example.

Text is on the right but reads in the wrong order

Cause: alignment was changed without setting a base direction, or mixed-direction runs need more specific markup. Fix: add direction declarations at the correct element boundaries and test punctuation, numbers, and embedded English separately.

Tables look correct but paragraphs do not

Cause: XMLWorker’s inspected run-direction code is for generated table cells. Fix: treat table success as narrow evidence; add paragraph, list, and nested-span fixtures or move the pipeline to pdfHTML.

Some characters are replaced or missing

Cause: fallback fonts or entities do not cover the content. Fix: inspect extracted text, add the required font coverage, and normalize input encoding to UTF-8.

Migration to pdfHTML changes layout

Cause: the APIs, CSS support, font handling, and layout engines differ. Fix: migrate one template at a time, compare reference PDFs, and document intentional differences.

Production deployment raises a licensing question

Cause: iText uses AGPL licensing, with commercial licensing needed for particular deployments that cannot meet AGPL conditions, including serving PDFs in a web application or shipping a closed-source product. Fix: review the actual AGPL and commercial terms for your distribution and deployment facts before release. See the official repository licensing information.

Performance, reliability, and cost considerations

  • No benchmark or throughput figure is established by the supplied sources. Measure conversion time and memory with your templates, fonts, and document sizes.
  • Cache registered fonts and avoid repeatedly loading large font files in a long-running process.
  • Set application-level timeouts and record converter exceptions with the input template identifier.
  • Keep deterministic HTML, CSS, package versions, and font files so a deployment can be reproduced.
  • For high-volume jobs, isolate conversion workers and monitor memory, output size, and failure rates.
  • License cost depends on whether your deployment meets AGPL conditions or requires a commercial iText license; it is not determined by RTL support alone.

Or skip the browser setup

If your actual requirement is to capture a rendered web page as an image or PDF, ScreenshotNeo provides a single HTTP request instead of maintaining a browser pipeline. Cookie and consent banners are accepted and removed before the shot, along with 60+ known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API documentation for the available options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

There is a free tier of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Is XMLWorker suitable for a new RTL project?

It is legacy software. Evaluate pdfHTML for new work and confirm licensing, fonts, and output with representative documents.

Does dir='rtl' solve every XMLWorker issue?

No. The inspected source proves run-direction handling for generated table cells, not universal behavior for all HTML elements.

Can I migrate without changing templates?

Do not assume that. Compare rendering, CSS behavior, fonts, and extracted text template by template.

What should a regression test assert?

Assert that required glyphs exist, text extraction is sensible, mixed-direction punctuation is ordered correctly, and tables and lists match a visual reference.