Laravel

Fixing Sinhala Unicode in Laravel PDFs (2026 Guide)

Why Sinhala text breaks in Dompdf PDFs even though the browser shows it correctly, and how mPDF or headless Chrome fix it in Laravel.

By Maneesh PrashanthPublished 7 min read

Sinhala text rendered correctly in a PDF generated by a Laravel application
On this page

In short

Dompdf has no text-shaping engine, so Sinhala letters that should join into conjuncts or move around the consonant come out broken. Embedding a better font does not fix that. Use mPDF with a Sinhala font that has OpenType layout tables if you want a PHP-only solution, or render the PDF with headless Chrome (Browsershot) if you want exactly what the browser shows.

In November 2020 I asked a question on Stack Overflow: Dompdf Laravel unicode character not rendering correctly (opens in a new tab). The Sinhala text looked perfect in the browser and came out wrong in the PDF. I found a practical workaround at the time (switch to mPDF and use the Kaputaunicode font). This post is the longer, corrected version: why it happens, what the options are in 2026, and how to pick one.

The problem: right in the browser, wrong in the PDF

A Blade view with Sinhala text renders correctly in Chrome, Firefox and Safari. Pass the same HTML to barryvdh/laravel-dompdf and the PDF has letters in the wrong order, vowel signs floating beside the wrong consonant, or conjuncts split into separate letters. Copying the text out of the PDF still gives the right Unicode, which is the clue: the data is fine, the drawing is not.

A common first guess is a missing font. Embedding a Sinhala TrueType file with @font-face does stop boxes and question marks, but it does not fix the ordering and joining problem. That needs something else.

Why it happens: text shaping

Sinhala is a Brahmic script. The characters you store are in logical (typing) order, but what is drawn is decided by text shaping: the engine reads the font's OpenType tables (GSUB for substitutions, GPOS for positioning) and applies script rules. Browsers do this with HarfBuzz (Chrome, Firefox) or the platform shaper (Safari). Microsoft's Sinhala OpenType specification (opens in a new tab) describes the rules fonts and shaping engines follow.

Three things the shaping step does that a plain "one code point, one glyph" renderer will not:

  • Conjuncts. ක්ෂ is three code points (ka, al-lakuna, ssa) drawn as one joined shape.
  • Reordering and split vowels. In කොළඹ the vowel sign is written around the consonant, not after it.
  • Joiners. In ශ්‍රී and ප්‍ර an invisible zero-width joiner (U+200D) asks for the special ra forms. Strip it and the word changes shape.

Dompdf has no complex-script shaping engine, so it draws glyphs in code-point order. Its own Unicode guide (opens in a new tab) covers fonts and encoding, not shaping. That is why the font is not the cause and cannot be the cure.

Your options in 2026

PHP PDF options and whether they shape Sinhala
EngineShapes Sinhala?Runs onVerdict
DompdfNoPHP onlyFine for Latin text. Not for Sinhala.
mPDF 6 and laterYes, through OpenType layout (OTL)PHP onlyBest PHP-only option. Needs a font with OTL tables.
TCPDFNot an OpenType shaperPHP onlyVerify with your own strings before using it.
Browsershot (headless Chrome)Yes, HarfBuzzNode.js and ChromeClosest to the browser. Heavier to host.
wkhtmltopdf (Snappy)Reported to work (old Qt WebKit)BinaryArchived since 2023. Avoid for new projects.
Hosted PDF APIsDepends on the providerHTTP callRun the test page below before you pay.

Fix 1: mPDF with a Sinhala OTL font

mPDF only shapes a script when the font has the right tables and OTL is switched on for that font. The mPDF OpenType layout page (opens in a new tab) says the font file needs GDEF, GSUB and GPOS tables. Noto Sans Sinhala has them. Download static TTF files (not the variable font) and put them in resources/fonts.

  1. Install: composer require mpdf/mpdf.
  2. Copy NotoSansSinhala-Regular.ttf and NotoSansSinhala-Bold.ttf to resources/fonts.
  3. Register the font with useOTL set, and make it the default font.
app/Http/Controllers/PdfController.phpphp
use Mpdf\Config\ConfigVariables;
use Mpdf\Config\FontVariables;
use Mpdf\Mpdf;

public function show()
{
    $fontDirs = (new ConfigVariables())->getDefaults()['fontDir'];
    $fontData = (new FontVariables())->getDefaults()['fontdata'];

    $mpdf = new Mpdf([
        'mode' => 'utf-8',
        'tempDir' => storage_path('app/mpdf'),
        'fontDir' => array_merge($fontDirs, [resource_path('fonts')]),
        'fontdata' => $fontData + [
            'notosanssinhala' => [
                'R' => 'NotoSansSinhala-Regular.ttf',
                'B' => 'NotoSansSinhala-Bold.ttf',
                'useOTL' => 0xFF, // turn on OpenType layout (shaping) for every script
            ],
        ],
        'default_font' => 'notosanssinhala',
    ]);

    $mpdf->WriteHTML(view('sinhala_document')->render());

    return response($mpdf->Output('document.pdf', 'S'), 200, [
        'Content-Type' => 'application/pdf',
    ]);
}

In the 2020 answer I used Kaputaunicode, which ships with mPDF. It still works for simple text, but it is an older font and its licence is unclear, so I now prefer Noto Sans Sinhala for new work.

Fix 2: Browsershot (headless Chrome)

If the PDF must look exactly like the web page, let Chrome make it. Browsershot (opens in a new tab) drives headless Chrome from PHP, so shaping, CSS grid, flexbox and modern CSS all behave as in the browser. The cost is hosting: you need Node.js and Chrome on the server.

app/Http/Controllers/PdfController.phpphp
use Spatie\Browsershot\Browsershot;

public function show()
{
    $html = view('sinhala_document')->render();

    $pdf = Browsershot::html($html)
        ->format('A4')
        ->margins(15, 15, 15, 15)
        ->showBackground()
        ->waitUntilNetworkIdle()
        ->pdf();

    return response($pdf, 200, ['Content-Type' => 'application/pdf']);
}

Chrome picks the font from the server, so install a Sinhala font there (a Noto font package on Linux) or load one with @font-face from a file or data URL. Without a font installed, you get boxes even though shaping works.

If you must stay on Dompdf

Embedding a Sinhala font is still worth doing, because it removes the empty boxes. Expect simple words to work and conjuncts and vowel signs to break. Treat it as a stopgap for non-critical documents and plan the move to mPDF.

resources/views/sinhala_document.blade.phphtml
<style>
    @font-face {
        font-family: 'Noto Sans Sinhala';
        font-weight: normal;
        src: url("{{ public_path('fonts/NotoSansSinhala-Regular.ttf') }}") format("truetype");
    }
    body { font-family: 'Noto Sans Sinhala', sans-serif; }
</style>

I would not move to wkhtmltopdf for a new project. Its repository (opens in a new tab) has been archived and it is built on an old Qt WebKit, so it gets no security or rendering fixes.

Choosing a Sinhala font

Sinhala fonts for PDF generation
FontLicenceUse it?
Noto Sans Sinhala (opens in a new tab)SIL Open Font LicenceYes. Free, complete, maintained. First choice.
Noto Serif SinhalaSIL Open Font LicenceYes, for a serif look.
Abhaya LibreSIL Open Font LicenceYes. Sinhala and Latin serif, good for headings.
KaputaunicodeUnclearWorks in mPDF, but check the licence before you redistribute it.
Iskoola PotaProprietary (Microsoft)Only where it is already installed. Do not bundle it.
FM Abhaya and other legacy fontsVariesNo. They use the old non-Unicode encoding and do not work with Unicode text.
DejaVu Sans (Dompdf default)FreeNo. It does not cover Sinhala.

Check the licence file for any font you embed in a PDF. Embedding redistributes a subset of the font, so "free to install" is not always "free to embed".

Test it with a known page

Do not judge a fix by one easy word. Use a page that contains conjuncts, joiners and split vowels, and compare the PDF with the browser side by side.

sinhala_document.blade.phphtml
<!doctype html>
<html lang="si">
<head>
    <meta charset="utf-8">
    <style>
        body { font-family: 'Noto Sans Sinhala', sans-serif; font-size: 16pt; }
    </style>
</head>
<body>
    <p>සිංහල</p>
    <p>ශ්‍රී ලංකා</p>
    <p>ක්ෂ / ප්‍ර / කොළඹ</p>
    <p>ආයුබෝවන්</p>
</body>
</html>

Then check each of these:

  • ක්ෂ and ප්‍ර are single joined shapes, not three separate letters.
  • In කොළඹ the vowel signs sit on the correct sides of the consonant.
  • ශ්‍රී has the special ra form. If it looks like a plain "sri", the joiner was lost somewhere.
  • Copy a word from the PDF into a text editor. You should get the same Unicode you started with.
  • Fonts are embedded as subsets, so the file size stays sensible.
  • Long paragraphs wrap at sensible places and page breaks do not split a word.

Common pitfalls

  • Variable fonts. The Google Fonts download of Noto may be a variable font. Use static instances with mPDF.
  • Forgetting `useOTL`. mPDF has the engine, but a font registered without useOTL is drawn unshaped.
  • Lost joiners. Some editors and database exports strip U+200D. Keep the column and file encoding UTF-8 (utf8mb4 in MySQL) and test with the sample page.
  • No font on the server. Headless Chrome uses installed fonts. A missing font gives boxes, not an error.
  • Cached fonts. After changing a font in Dompdf or mPDF, clear its font cache and temp directory.
  • Version drift. Behaviour changes between releases. Keep the test page in your repository and run it after upgrades.

Which one should you use?

  • Invoices, reports and certificates with simple layouts, and you want PHP only: mPDF with Noto Sans Sinhala.
  • Complex layouts, modern CSS, or it must match the web page exactly: Browsershot.
  • You are already on Dompdf with mostly Latin text: keep it, and move only the Sinhala documents.
  • You cannot host Node.js and Chrome: mPDF, or a hosted API after you test it with the sample page.

Sources

  1. Dompdf Laravel unicode character not rendering correctly (Stack Overflow) (opens in a new tab)The original question, November 2020.
  2. mPDF manual: OpenType layout (OTL) (opens in a new tab)Supported scripts including Sinhala, and the font table requirements.
  3. Microsoft: Developing OpenType fonts for Sinhala script (opens in a new tab)How Sinhala shaping works.
  4. Dompdf wiki: Unicode how-to (opens in a new tab)
  5. Noto Sans Sinhala (Google Fonts) (opens in a new tab)
  6. Spatie Browsershot documentation (opens in a new tab)
  7. wkhtmltopdf repository (archived) (opens in a new tab)
Maneesh Prashanth

Maneesh PrashanthFull Stack Software Engineer from Galle, Sri Lanka. I build web and mobile apps and fix the problems that show up when they meet real users. About me or get in touch.

Frequently Asked Questions

Why does Sinhala text break in Dompdf PDFs when the browser shows it correctly?

Browsers shape Sinhala text with an engine such as HarfBuzz, which joins conjuncts, reorders vowel signs and applies the font's OpenType rules. Dompdf has no complex-script shaping engine, so it draws glyphs in code-point order. Embedding a better font removes empty boxes but does not fix the ordering and joining.

Does mPDF support Sinhala in Laravel?

Yes. Since version 6, mPDF supports OpenType layout and its manual lists Sinhala among the supported scripts. You need a Sinhala font with GDEF, GSUB and GPOS tables, such as Noto Sans Sinhala as static TTF files, registered with 'useOTL' => 0xFF.

What is the best way to generate Sinhala PDFs from Laravel?

For a PHP-only setup, use mPDF with Noto Sans Sinhala. If the PDF must match the web page exactly or uses modern CSS, render it with headless Chrome through Spatie Browsershot, which needs Node.js and Chrome on the server. Test either option with a page that contains conjuncts, joiners and split vowels.

Which Sinhala font should I use in a PDF?

Noto Sans Sinhala (SIL Open Font Licence) is the safest first choice. Avoid legacy non-Unicode fonts such as FM Abhaya, and do not bundle Iskoola Pota because it is Microsoft's proprietary font.

Should I move to wkhtmltopdf for Sinhala PDFs?

Not for a new project. Its repository has been archived since 2023 and it is built on an old Qt WebKit, so it receives no security or rendering fixes. Choose mPDF or Browsershot instead.