Skip to content
Devix Open Source
Laravel package v1.0.0 MIT Alpha

Invoice PDF

VAT-ready invoice PDFs with Arabic and Urdu support.

composer require devix-labs/invoice-pdf
PHP Laravel
Invoice PDF

Live demo coming soon

dompdf does 8.4 million installs a month and barryvdh/laravel-dompdf 5.7 million, and neither can join a single Arabic letter: hand either of them فاتورة ضريبية and you get six separate isolated glyphs running the wrong way, every character present and none of it readable. What exists to fix that on Packagist, in full, is omaralalwi/gpdf at 31,431 downloads in its lifetime. So this ships an Arabic shaper in pure PHP — every letter's four presentation forms, the six that never join forwards, the lam-alef ligature that لا always is, vowels that ride along without breaking a join, Urdu's extra letters — plus a reordering pass that turns right-to-left runs around while leaving numbers and Latin alone. The other half is the invoice itself: problems() returns reason codes for what a jurisdiction requires and this invoice has not got, including the exchange rate a UAE invoice not in dirhams must show and ZATCA's requirement that the seller be named in Arabic. Whole minor units throughout, tax in basis points, summed per line. No mPDF, no headless Chrome, no dependencies.

What you get

It makes dompdf speak Arabic

Arabic is cursive and Unicode leaves the choosing of shapes to the renderer. dompdf does not choose — it draws the isolated form of every glyph in the order given. This does the shaping first, so there is nothing left for the engine to get wrong.

لا is one glyph, never two

The lam-alef ligature is the single most visible failure in the field — every Arabic reader spots it instantly. Along with it: the six letters that never join forwards, so دار is three separate shapes, and vowels that ride along without breaking a join.

Numbers are not turned around

A reordering pass turns right-to-left runs while leaving numbers, invoice references and Latin words running the right way inside them, and mirrors brackets. An invoice where the total reads 65.432,1 is worse than one in English.

It tells you what an auditor would

problems() returns reason codes rather than a boolean — per jurisdiction, from the authority's own wording. The one everybody forgets: a UAE invoice not in dirhams must show the exchange rate used. Catch it in a test, not in an audit.

Never a float, anywhere

Whole minor units from the moment an amount arrives, tax rates in basis points, and tax summed per line — which is what both the FTA and ZATCA specify and which is not the same number as taxing the total. The same rules as Devix VAT Calculator.

Zero-rated is not exempt

Zero-rated, exempt, out-of-scope and reverse charge are all nought per cent and all different: one recovers input tax, one does not, and reverse charge moves the liability. The breakdown keeps them apart, because a VAT return does.

No mPDF, no Chrome, no dependencies

The usual answers to Arabic in PHP are a forty-megabyte dependency or installing a browser on the server. This needs ext-mbstring and the engine you already have. html() gives the finished document to mPDF or Browsershot if you prefer them.

It pairs with the rest

The ZATCA QR from Devix Laravel ZATCA, the amount in correct Arabic grammar from Devix Number Words, and totals that match Devix VAT Calculator to the fils — because they are all computed the same way.

Invoice PDF — overview

The gap, in one measurement

dompdf does 8.4 million installs a month and barryvdh/laravel-dompdf does 5.7 million. Neither can join a single Arabic letter.

Arabic is cursive: a letter's shape depends on its neighbours, and Unicode stores the letter once and leaves the choosing to the renderer. dompdf does not choose. It draws the isolated form of every glyph in the order given, left to right. The result contains every character of the invoice and cannot be read.

What exists to fix that, on Packagist, in full:

Downloads, lifetime
omaralalwi/gpdf 31,431
rezgui/laravel-mpdf-dz 3,242
joffyjose/magento2-invoicearabicpdf 6

Against 5.7 million a month for the engine that causes it. The usual answers are to take on mPDF — forty megabytes — or to install Chrome on the server.

What is actually in here

An Arabic shaper in pure PHP. Every letter's four presentation forms from the Unicode Arabic Presentation Forms-B block; the six letters that never join forwards, which is why دار is three separate shapes; the lam-alef ligature that لا always is and whose absence is the single most visible failure in the field; vowels and shadda carried along without breaking a join; and Urdu's پ چ ٹ ڈ ڑ گ ک ہ ے.

A reordering pass. Right-to-left runs turned around, numbers and Latin words left running the right way inside them, brackets mirrored, per line.

An invoice that knows what a tax authority asks for. problems() returns reason codes rather than a boolean, per jurisdiction. The UAE's Federal Decree-Law No. 8 of 2017, Article 59 — including the exchange rate that an invoice not in dirhams must show, which is forgotten constantly. ZATCA's requirement that the seller be named in Arabic. The distinction between a standard invoice, which must name the customer, and a simplified one, which need not.

Whole minor units. The same rules as Devix VAT Calculator, so the total in the browser and the total on the page are the same number. Tax rates in basis points, because 5% is 500 and not 0.05000000000000000277. Tax summed per line, which is what both authorities specify and which is not the same as taxing the total.

One thing found by looking

The first rendered page was correct Arabic in the wrong order: the table columns ran description-first from the left, because dompdf lays columns out in source order whatever dir="rtl" says. So an Arabic invoice is emitted with its columns already reversed. That is not in any specification; it came from looking at the page.

What we do not claim

A full UAX #9. The bidirectional handling here covers right-to-left runs with embedded numbers and Latin, mirrored brackets, and per-line direction. Not explicit embedding characters or nested overrides — an invoice has none, and a word processor would.

Copyable text. Shaping writes presentation forms into the PDF, so copying out of a reader gives shaped characters. Every PHP Arabic invoice makes that trade. Shaper::toPlain() reverses it exactly, and a test proves the round trip.

That mPDF is wrong. If forty megabytes is acceptable, mPDF shapes at the font layer and keeps the text copyable. This is for the far more common case where dompdf is already there and the invoice has to go out in Arabic tomorrow.

How it compares

Questions

Why can't dompdf do this already?

Because shaping is a font-layer job and dompdf does not do it. Arabic letters change shape according to their neighbours, and Unicode stores only the letter, leaving the choosing to the renderer. A browser chooses; dompdf draws the isolated form of each glyph in source order. The text is all there and unreadable. mPDF does shape, at the cost of being a forty-megabyte dependency.

Can I still copy the text out of the PDF?

Not as the original string — shaping writes presentation forms into the document, so copying gives shaped characters. That is the trade every PHP Arabic invoice makes. Shaper::toPlain() reverses it exactly, and there is a test proving the round trip, so the machine-readable copy of the invoice is still correct.

Is this a full implementation of the Unicode bidirectional algorithm?

No, and it says so. It handles right-to-left runs with embedded numbers and Latin, mirrored brackets, and per-line direction — which is everything an invoice contains. Explicit embedding characters and nested overrides are not implemented; a word processor would need them and an invoice does not.

Do I need a special font?

No. The font must contain Arabic Presentation Forms-B (U+FE70–U+FEFF), and DejaVu Sans — which dompdf ships with — does. That is why this works with nothing installed. Amiri or Cairo look better; register them with your engine and name them in the config. A font without that block renders boxes, which is immediately obvious.

Does it handle Saudi e-invoicing end to end?

It produces the invoice and checks what ZATCA requires be printed, and it takes the QR payload from Devix Laravel ZATCA. It does not do phase-two XML signing, which needs a certificate from ZATCA's CA and an onboarding process — that is deliberately out of scope rather than half-done.