laravel-unicode-pdf maintained by imrandevbd
Laravel Unicode PDF
Universal, multilingual, Unicode-first PDF generation for Laravel. Reliable RTL, complex-script shaping, intelligent font fallback, and a modern Laravel API (enums, events, queues, fakes, mail, cache).
use ImranDev\UnicodePdf\Facades\UnicodePdf;
return UnicodePdf::preset('bengali')
->loadView('invoices.show', [
'customer' => 'মোহাম্মদ ইমরান হোসেন',
'total' => '৳৯০,০০০',
])
->a4()
->download('চালানপত্র.pdf');
Table of contents
- Requirements
- Why this package
- Feature overview
- Installation
- Quick start
- Fluent document API
- Typography presets
- Locales
- Fonts
- PDF engines
- Unicode processing
- Native numerals
- HTTP responses
- Storage, queue, cache, mail
- Events
- Named profiles
- Helpers, Blade, and components
- Models (
Pdfable/GeneratesPdf) - Custom engines
- Enums
- Artisan commands
- Configuration
- Security
- Exceptions
- Testing
- Sample views
- Requirements matrix
- Documentation
- License
Requirements
| PHP | 8.1 – 8.5 |
| Laravel | 10 – 13 |
| Extensions | ext-mbstring, ext-json (required) · ext-intl, ext-gd (recommended) |
| Engine packages | Optional — install the driver you use (see PDF engines) |
Auto-discovery registers UnicodePdfServiceProvider and the UnicodePdf facade. No extra setup is required in Laravel 10+.
Why this package
Generating PDFs in non-Latin scripts often produces ???, tofu boxes (□), broken Arabic ligatures, or disconnected Bengali conjuncts (কৃষ্ণ, শিক্ষার্থী).
This package sits in front of Dompdf, mPDF, TCPDF, or Chromium (Browsershot) and always:
- Validates UTF-8
- Detects Unicode scripts and text direction
- Builds a font-fallback stack
- Injects
@font-face/font-familyCSS anddir/langon the HTML - Hands a prepared document to the engine you choose
You keep writing Blade. The package handles Unicode.
Feature overview
| Area | What you get |
|---|---|
| Scripts | Bengali, Arabic, Urdu, Persian, Hebrew, Devanagari, Tamil, Telugu, Malayalam, Gujarati, Gurmukhi, Kannada, Sinhala, Thai, Lao, Khmer, Myanmar, Tibetan, Ethiopic, CJK (SC/TC/JP/KR), Greek, Cyrillic, Armenian, Georgian, Thaana, NKo, Adlam, Syriac, Javanese, Balinese, Sundanese, Yi, Cherokee, emoji, Latin |
| RTL / BiDi | Auto direction, mixed LTR+RTL, Hebrew / Arabic / Urdu / Persian / Thaana / NKo / Adlam / Syriac |
| Fonts | Register TTF/OTF, auto-discover directories, CSS @font-face, glyph diagnostics, Noto install guide |
| Presets | 20 typography presets + locale aliases (bn, ar, ja, …) |
| Engines | Native (bundled, default) — plus optional Dompdf, mPDF, TCPDF, Browsershot, Null, and extend() |
| Laravel DX | Facade, helper, enums, Conditionable / Macroable / Tappable, Responsable, Stringable |
| Output | download, stream / inline, save, store / storeAs, output, base64, dataUri, toMailAttachment, queue, cache, defer |
| Document extras | Watermark, encryption, metadata, headers/footers, page numbers, extra CSS, engine options |
| Unicode tools | UTF-8 validator, NFC/NFD/NFKC/NFKD normalizer, script detector, direction detector, BiDi processor, numeral converter |
| Ops | Artisan diagnose / fonts / generate / cache, php artisan about, events, testing fake |
Installation
composer require imrandevbd/laravel-unicode-pdf
The native engine is built in. No Dompdf, mPDF, or other PDF library is required.
# Download Noto fonts for the scripts you use (SIL OFL)
php artisan unicode-pdf:font:install --font=bengali
php artisan unicode-pdf:font:install --font=arabic
php artisan unicode-pdf:font:install --font=universal
Optional third-party engines if you already use them: dompdf/dompdf, mpdf/mpdf, tecnickcom/tcpdf, spatie/browsershot.
Publish assets:
php artisan vendor:publish --tag=unicode-pdf-config
php artisan vendor:publish --tag=unicode-pdf-views
php artisan vendor:publish --tag=unicode-pdf-fonts
Optional Composer suggests:
# Mail attachments → illuminate/mail (already in a full Laravel app)
# Queued PDFs → illuminate/queue
# Output cache → illuminate/cache
Set the default engine in .env:
UNICODE_PDF_ENGINE=native
UNICODE_PDF_DEFAULT_FONT="Noto Sans"
Quick start
Download from a Blade view
use ImranDev\UnicodePdf\Facades\UnicodePdf;
return UnicodePdf::loadView('invoices.show', [
'customer' => 'মোহাম্মদ ইমরান হোসেন',
'items' => [
['name' => 'ল্যাপটপ কম্পিউটার', 'price' => '৳৮৫,০০০'],
['name' => '4K مونيتر شاشة', 'price' => '١٬٥٠٠ ر.س'],
],
'total' => '৳৯০,০০০',
])
->setPaper('a4')
->download('চালানপত্র.pdf');
HTML, CSS, and font size
Developers style PDFs the same way as a Blade page: <style>, classes, inline styles, plus ->css() and ->fontSize().
return UnicodePdf::locale('bn')
->loadView('invoices.show', $data)
->fontSize(12)
->css('
h1 { font-size: 22pt; color: #1a365d; text-align: center; }
.total { font-size: 16pt; color: #c53030; font-weight: bold; }
table th { background-color: #ebf8ff; }
')
->download('চালান.pdf');
<h1 style="font-size: 20pt; text-align: center;">বিক্রয় চালানপত্র</h1>
<p class="total" style="color: #2b6cb0;">মোট: ৳৯০,০০০</p>
Native layout & CSS engine supports:
- Typography & Styling:
font-size,font-family,font-weight(bold),font-style(italic),color,background-color,text-align(left,center,right,justify),text-decoration(underline),line-height,margin,padding,direction. - Text Elements:
<h1>–<h6>,<p>,<div>,<span>,<strong>,<b>,<em>,<i>,<u>,<a>,<br>,<hr>(customizable width and color),<blockquote>. - Lists:
<ul>,<ol>,<li>(automatically rendered with bullet points•). - Tables:
<table>,<thead>,<tbody>,<tr>,<th>,<td>with automatic column width distribution,colspan, borders, cell padding, background color, cell-level font size/color/alignment, and auto-pagination across pages when table rows exceed page height. - Images:
<img>tags (local file paths, public directory assets, ordata:image/...;base64URIs), auto-scaled with max-width or explicitwidth/height. - Page Breaks: Force manual page breaks anywhere using
page-break-before: always,page-break-after: always,break-before: page,break-after: page, or classpage-break.
Mixed-language HTML
$html = '
<h1>Universal Multilingual Document</h1>
<p>English: Hello World</p>
<p>বাংলা: শুভ সকাল, বাংলাদেশ (৳৮০,০০০)</p>
<p>Arabic: مرحباً بالعالم (١٠٬٠٠٠ ر.س)</p>
<p>Hindi: दुनिया में आपका स्वागत है</p>
<p>Chinese: 世界你好</p>
';
return UnicodePdf::preset('universal')
->loadHtml($html)
->download('multilingual.pdf');
Preview in the browser
return UnicodePdf::loadView('reports.monthly', $data)
->stream('preview.pdf');
Save locally or to S3
UnicodePdf::loadView('invoices.show', $data)
->save(storage_path('app/invoices/inv-101.pdf'));
UnicodePdf::loadView('invoices.show', $data)
->store('invoices/inv-101.pdf', 's3');
Return the document from a controller
UnicodePdfDocument implements Laravel’s Responsable interface:
public function show(Invoice $invoice)
{
return UnicodePdf::loadView('invoices.pdf', compact('invoice'))
->preset('bengali')
->name('চালান-'.$invoice->id.'.pdf');
}
Helper
return unicode_pdf('<h1>বাংলাদেশ</h1>')->preset('bengali')->download();
$manager = unicode_pdf(); // UnicodePdfManager
Fluent document API
Every call returns the same UnicodePdfDocument instance (except terminal methods like output() / download()).
Load content
| Method | Description |
|---|---|
loadHtml($html) / html($html) |
Raw HTML string |
loadView($view, $data = [], $mergeData = []) / view(...) |
Blade view |
loadFile($path) |
HTML file from disk |
Typography & locale
| Method | Description |
|---|---|
font($family) |
Primary font family |
fontSize(14) / fontSize('16px') |
Default body size (appends CSS) |
fallback(array $fonts) |
Ordered fallback stack |
preset($name) |
Built-in preset or Preset enum (throws if unknown) |
locale($locale) |
Apply font + direction from locale map (bn, ar, zh-TW, …) |
direction('auto'|'ltr'|'rtl') |
Or Direction enum |
bidi(true) |
Enable bidirectional processing |
lang('bn') |
HTML lang attribute |
css($css) |
Extra CSS injected into the prepared HTML |
Paper & layout
| Method | Description |
|---|---|
setPaper($size, $orientation) / paper(...) |
Size string, [w, h], or PaperSize enum |
a4() / letter() / legal() |
Shortcuts |
orientation('portrait'|'landscape') |
Or Orientation enum |
landscape() / portrait() |
Shortcuts |
setMargins($t, $r, $b, $l, $unit = 'mm') / margin(...) |
Page margins |
header($htmlOrView, $data = []) |
Header HTML or Blade view name |
footer($htmlOrView, $data = []) |
Footer HTML or Blade view name |
pageNumbers('{PAGE_NUM} / {PAGE_COUNT}') |
Page numbering format |
Metadata & protection
| Method | Description |
|---|---|
metadata(['title' => '...', 'author' => '...']) |
PDF info dictionary |
title() / author() / subject() / keywords() / creator() |
Shortcuts |
watermark($text, $opacity = 0.2) |
Text watermark |
protect($options) |
Engine password / permissions array |
encrypt($userPassword, $ownerPassword = null, $permissions = []) |
Fluent wrapper around protect() |
options(array) / option($key, $value) |
Engine-specific options |
debug(true) |
Debug flag |
name('invoice.pdf') |
Default filename for download / toResponse() |
Conditionable / macros / tap
use ImranDev\UnicodePdf\Enums\Engine;
use ImranDev\UnicodePdf\Enums\PaperSize;
use ImranDev\UnicodePdf\Enums\Preset;
UnicodePdfDocument::macro('invoice', function () {
return $this->preset(Preset::Bengali)->a4()->margin(15, 15, 15, 15);
});
return UnicodePdf::engine(Engine::Mpdf)
->invoice()
->paper(PaperSize::A4)
->when($isDraft, fn ($pdf) => $pdf->watermark('DRAFT'))
->unless($isPublic, fn ($pdf) => $pdf->encrypt('secret'))
->tap(fn ($pdf) => logger()->info('pdf', $pdf->snapshot()))
->loadView('invoices.show', $data)
->download();
Typography presets
preset() sets default font, fallbacks, and direction in one call.
UnicodePdf::preset('bengali');
UnicodePdf::preset(\ImranDev\UnicodePdf\Enums\Preset::Arabic);
Unknown names throw PresetNotFoundException.
Built-in presets
| Preset | Direction | Default font | Typical use |
|---|---|---|---|
bengali |
LTR | Noto Sans Bengali | Bangla (কার, যুক্তাক্ষর, ৳) |
arabic |
RTL | Noto Sans Arabic | Arabic, RTL + BiDi |
indian |
LTR | Noto Sans Devanagari | Hindi, Marathi, Nepali + other Indic fallbacks |
cjk |
LTR | Noto Sans CJK SC | Chinese / Japanese / Korean stack |
universal |
auto | Noto Sans | Mixed-script documents |
thai |
LTR | Noto Sans Thai | Thai |
hebrew |
RTL | Noto Sans Hebrew | Hebrew |
persian |
RTL | Noto Sans Arabic | Farsi |
urdu |
RTL | Noto Nastaliq Urdu | Urdu |
tamil |
LTR | Noto Sans Tamil | Tamil |
korean |
LTR | Noto Sans CJK KR | Hangul |
japanese |
LTR | Noto Sans CJK JP | Kanji / Kana |
vietnamese |
LTR | Noto Sans | Vietnamese Latin |
greek |
LTR | Noto Sans | Greek |
cyrillic |
LTR | Noto Sans | Russian, Ukrainian, … |
ethiopic |
LTR | Noto Sans Ethiopic | Amharic, Tigrinya |
khmer |
LTR | Noto Sans Khmer | Khmer |
myanmar |
LTR | Noto Sans Myanmar | Burmese |
sinhala |
LTR | Noto Sans Sinhala | Sinhala |
latin |
LTR | Noto Sans | European Latin |
Locale aliases for presets
These resolve to the same presets:
bn, hi, zh, fa, ur, ja, ko, he, th, el, ru, am, km, my, si, ta, vi, en
UnicodePdf::preset('bn'); // same as 'bengali'
UnicodePdf::preset('ja'); // same as 'japanese'
Register your own:
use ImranDev\UnicodePdf\Fonts\Presets\ScriptPreset;
use ImranDev\UnicodePdf\Facades\UnicodePdf;
UnicodePdf::getFontManager()->registerPreset(new ScriptPreset(
name: 'odia',
defaultFont: 'Noto Sans Oriya',
fallbackFonts: ['Noto Sans Oriya', 'Noto Sans'],
direction: 'ltr',
options: ['script' => 'Odia', 'complex_shaping' => true],
));
Locales
locale('ar') sets font and direction from the built-in map.
UnicodePdf::locale('bn')->loadView('invoices.show', $data)->download();
UnicodePdf::locale('zh-TW'); // Traditional Chinese font
UnicodePdf::locale('fa'); // RTL + Noto Sans Arabic
Built-in locale codes
| Locale | Script | Dir | Font |
|---|---|---|---|
bn, as |
Bengali | LTR | Noto Sans Bengali |
ar, ps, sd |
Arabic | RTL | Noto Sans Arabic |
ur |
Arabic | RTL | Noto Nastaliq Urdu |
fa |
Arabic | RTL | Noto Sans Arabic |
he, yi |
Hebrew | RTL | Noto Sans Hebrew |
dv |
Thaana | RTL | Noto Sans Thaana |
hi, mr, ne |
Devanagari | LTR | Noto Sans Devanagari |
ta te ml gu pa kn or si |
Indic | LTR | matching Noto Sans * |
th lo km my bo |
SEA / Tibetan | LTR | matching Noto Sans * |
am, ti |
Ethiopic | LTR | Noto Sans Ethiopic |
zh, zh-CN |
CJK | LTR | Noto Sans CJK SC |
zh-TW, zh-HK |
CJK | LTR | Noto Sans CJK TC |
ja |
Japanese | LTR | Noto Sans CJK JP |
ko |
Korean | LTR | Noto Sans CJK KR |
ru uk bg sr |
Cyrillic | LTR | Noto Sans |
el |
Greek | LTR | Noto Sans |
hy |
Armenian | LTR | Noto Sans Armenian |
ka |
Georgian | LTR | Noto Sans Georgian |
jv |
Javanese | LTR | Noto Sans Javanese |
en es fr de pt tr id ms vi |
Latin | LTR | Noto Sans |
Override or add mappings:
use ImranDev\UnicodePdf\Unicode\LocaleMapper;
LocaleMapper::register('ckb', 'Arabic', 'rtl', 'Noto Sans Arabic');
Fonts
Register a family
UnicodePdf::registerFont([
'family' => 'Kalpurush',
'regular' => storage_path('app/unicode-pdf/fonts/kalpurush.ttf'),
'bold' => storage_path('app/unicode-pdf/fonts/kalpurush-bold.ttf'),
'italic' => storage_path('app/unicode-pdf/fonts/kalpurush-italic.ttf'),
'bold_italic' => storage_path('app/unicode-pdf/fonts/kalpurush-bold-italic.ttf'),
]);
return UnicodePdf::font('Kalpurush')
->loadView('invoice', $data)
->download('invoice.pdf');
Same structure is accepted in config/unicode-pdf.php → fonts.
Supported files: TTF, OTF (all engines) · WOFF / WOFF2 (Browsershot).
Discover a directory
$discovered = UnicodePdf::getFontManager()->discoverDirectory(
storage_path('app/unicode-pdf/fonts')
);
Automatically scans the directory for .ttf and .otf fonts, parses their font headers, extracts font family names, styles (regular, bold, italic, bold_italic), and supported Unicode scripts, then registers them into the active font manager. Returns an associative array of discovered families.
Inspect font metadata
use ImranDev\UnicodePdf\Fonts\FontMetadata;
$metadata = FontMetadata::parse(storage_path('app/unicode-pdf/fonts/NotoSansBengali-Regular.ttf'));
// [
// 'family' => 'Noto Sans Bengali',
// 'subfamily' => 'Regular',
// 'full_name' => 'Noto Sans Bengali Regular',
// 'postscript_name' => 'NotoSansBengali-Regular',
// 'format' => 'TTF', // 'TTF' | 'OTF' | 'WOFF' | 'WOFF2'
// 'num_glyphs' => 745,
// 'unicode_ranges' => [...],
// 'supported_scripts' => ['Bengali', 'Latin'],
// ]
Glyph & script coverage diagnostics
Diagnose text against any font family to identify missing scripts and get suggested fonts:
$report = UnicodePdf::checkGlyphs('বাংলা ও English ও مرحبا', 'Noto Sans');
// $report returns:
// [
// 'detected_scripts' => ['Bengali' => 6, 'Latin' => 7, 'Arabic' => 5],
// 'dominant_script' => 'Bengali',
// 'primary_font' => 'Noto Sans',
// 'missing_scripts' => ['Bengali', 'Arabic'],
// 'suggested_fonts' => ['AI-Borno', 'Noto Sans Arabic'],
// ]
Install Noto fonts (guide)
Install pre-configured Google Noto fonts directly via Artisan:
# Interactive font installer menu:
php artisan unicode-pdf:font:install
# Direct installation:
php artisan unicode-pdf:font:install --font=bengali
# Force re-download even if already downloaded:
php artisan unicode-pdf:font:install --font=bengali --force
Available --font values: bengali, arabic, devanagari, tamil, thai, hebrew, khmer, myanmar, ethiopic, universal.
PDF engines
UnicodePdf::engine('mpdf')->preset('bengali')->loadView(...)->download();
UnicodePdf::engine(\ImranDev\UnicodePdf\Enums\Engine::Browsershot);
native is the default, zero-dependency engine (TTF embedding, RTL, Arabic joining, Indic GSUB). null writes a minimal PDF 1.4 binary for tests.
Capability matrix
| Package | Unicode | RTL | Complex shaping | SVG | JS | Encryption | Attachments | Best for | |
|---|---|---|---|---|---|---|---|---|---|
| Native | (bundled) | ✓ | ✓ | ✓ | — | — | — | — | Default — no extra packages |
| Dompdf | dompdf/dompdf |
✓ | — | — | ✓ | — | ✓ | — | Optional |
| mPDF | mpdf/mpdf |
✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ | Optional |
| TCPDF | tecnickcom/tcpdf |
✓ | ✓ | — | ✓ | — | ✓ | ✓ | Optional |
| Browsershot | spatie/browsershot |
✓ | ✓ | ✓ (HarfBuzz) | ✓ | ✓ | — | — | Optional |
| Null | (bundled) | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ | Tests / CI |
if (UnicodePdf::supports('font-shaping')) {
// default engine
}
if (UnicodePdf::engine('mpdf')->supports('rtl')) {
// ...
}
Capability names: unicode, rtl, font-shaping, svg, javascript, encryption, attachments.
Unicode processing
Used automatically when HTML is prepared; also available on the manager and container contracts.
// UTF-8 Validation
UnicodePdf::validateUtf8($text); // bool (throws InvalidUtf8Exception if invalid)
// Unicode Normalization (NFC, NFD, NFKC, NFKD)
UnicodePdf::normalize($text, 'NFC');
// Script Detection
UnicodePdf::detectScripts('বাংলা Hello مرحبا'); // ['Bengali' => 5, 'Latin' => 5, 'Arabic' => 5]
// Text Direction Detection ('ltr' or 'rtl')
UnicodePdf::detectDirection($html); // 'ltr' | 'rtl'
// Glyph & Script Diagnostics
$report = UnicodePdf::checkGlyphs($html, 'Noto Sans');
Script & direction utilities
use ImranDev\UnicodePdf\Contracts\ScriptDetectorInterface;
use ImranDev\UnicodePdf\Contracts\DirectionDetectorInterface;
$scriptDetector = app(ScriptDetectorInterface::class);
$scriptDetector->getDominantScript($text); // 'Bengali'
$scriptDetector->containsScript($text, 'Arabic'); // bool
$scriptDetector->containsComplexScript($text); // bool (true if font shaping is required)
$dirDetector = app(DirectionDetectorInterface::class);
$dirDetector->detect($text); // 'ltr' | 'rtl'
$dirDetector->isRtl($text); // bool
$dirDetector->isMixed($text); // bool (both RTL and LTR detected)
Detected scripts
Bengali, Arabic, Devanagari, Tamil, Telugu, Malayalam, Gujarati, Gurmukhi, Kannada, Sinhala, Thai, Hebrew, Cyrillic, Greek, Japanese, Korean, CJK, Armenian, Georgian, Ethiopic, Khmer, Myanmar, Lao, Tibetan, Mongolian, Thaana, Nko, Adlam, Syriac, Javanese, Balinese, Sundanese, Yi, Cherokee, Emoji, Latin.
Complex-script detection (needs shaping): Bengali, Arabic, Devanagari, Tamil, Telugu, Malayalam, Gujarati, Gurmukhi, Kannada, Sinhala, Thai, Khmer, Myanmar, Lao, Tibetan, Javanese, Balinese, Sundanese, Thaana, Nko, Adlam, Syriac.
RTL detection includes Hebrew, Arabic, Syriac, Thaana, NKo, Samaritan, Mandaic, Adlam, and presentation forms. Mixed LTR+RTL is detected for BiDi.
Register extra script regexes:
app(\ImranDev\UnicodePdf\Contracts\ScriptDetectorInterface::class)
->registerScript('Odia', '/[\x{0B00}-\x{0B7F}]/u');
Native numerals
Convert Western digits to native digits (and back).
use ImranDev\UnicodePdf\Support\NumeralConverter;
NumeralConverter::toBengali('85000'); // ৮৫০০০
NumeralConverter::toArabicIndic('12345'); // ١٢٣٤٥
NumeralConverter::toPersian('12345'); // ۱۲۳۴۵ (۴/۵/۶ differ from Arabic-Indic)
NumeralConverter::toDevanagari('123'); // १२३
NumeralConverter::convert('123', 'th'); // ๑๒๓
NumeralConverter::format(85000, 'bn'); // ৮৫,০০০
NumeralConverter::toLatin('৮৫০০০', 'bn'); // 85000
UnicodePdf::numerals('85000', 'bn'); // ৮৫০০০
Blade:
৳ @unicodeNumerals('85000', 'bn')
| Locale / alias | Digits |
|---|---|
bn / bengali / bangla |
০১২৩৪৫৬৭৮৯ |
ar / arabic / ur / urdu |
٠١٢٣٤٥٦٧٨٩ |
fa / persian / farsi |
۰۱۲۳۴۵۶۷۸۹ |
hi / devanagari / hindi / mr / ne |
०१२३४५६७८९ |
ta te ml gu pa kn si |
Tamil, Telugu, Malayalam, Gujarati, Gurmukhi, Kannada, Sinhala |
th lo my km |
Thai, Lao, Myanmar, Khmer |
en / latin / western |
0123456789 (no change) |
HTTP responses
Unicode filenames use RFC 5987 / RFC 6266 (filename="ascii.pdf"; filename*=UTF-8''...). CR/LF/NUL are stripped.
| Method | Content-Disposition |
|---|---|
download('চালান.pdf') |
attachment |
stream('preview.pdf') |
inline |
inline('preview.pdf') |
inline (also used if you later toResponse()) |
toResponse($request) |
download unless inline() was called |
(string) $document |
raw PDF binary (Stringable) |
$response = UnicodePdf::loadHtml('<p>Hi</p>')->download('বাংলা.pdf');
// Content-Type: application/pdf
// Content-Disposition: attachment; filename="______.pdf"; filename*=UTF-8''%E0%A6%AC%E0%A6%BE%E0%A6%82%E0%A6%B2%E0%A6%BE.pdf
Response macro:
return response()->unicodePdf($document, 'invoice.pdf');
Storage, queue, cache, mail
Filesystem
$pdf->save(storage_path('app/invoices/101.pdf')); // local path (SSRF/path-safe)
$pdf->store('invoices/101.pdf', 's3'); // Laravel disk
$pdf->storeAs('invoices', '101.pdf', 's3');
Binary helpers
$binary = $pdf->output();
$b64 = $pdf->base64();
$uri = $pdf->dataUri(); // data:application/pdf;base64,...
Queue
Requires illuminate/queue. Dispatches GeneratePdfJob:
UnicodePdf::loadView('invoices.show', $data)
->preset('bengali')
->queue('invoices/101.pdf', 's3');
Config: unicode-pdf.queue.connection, unicode-pdf.queue.queue.
Cache rendered binaries
UnicodePdf::loadView('invoices.show', $data)
->cache(3600) // seconds; store from config if omitted
->cache(3600, 'redis')
->withoutCache()
->download('invoice.pdf');
Config: unicode-pdf.cache.ttl, unicode-pdf.cache.store.
Defer after the HTTP response (Laravel 11+)
UnicodePdf::loadView('invoices.show', $data)
->defer('invoices/101.pdf', 'local');
Falls back to synchronous store() when defer() is not available.
Mail attachments
Requires illuminate/mail:
Mail::to($user)->send(new InvoiceMailable(
UnicodePdf::loadView('invoices.show', $data)
->preset('bengali')
->toMailAttachment('চালান.pdf')
));
Events
Dispatched around output() (and therefore around download / stream / save / store):
| Event | Payload |
|---|---|
ImranDev\UnicodePdf\Events\PdfGenerating |
document |
ImranDev\UnicodePdf\Events\PdfGenerated |
document, binary, durationMs, size() |
ImranDev\UnicodePdf\Events\PdfFailed |
document, exception |
use ImranDev\UnicodePdf\Events\PdfGenerated;
Event::listen(PdfGenerated::class, function (PdfGenerated $event) {
logger()->info('pdf.generated', [
'bytes' => $event->size(),
'ms' => $event->durationMs,
]);
});
Named profiles
Define reusable setups in config/unicode-pdf.php:
'profiles' => [
'invoice' => [
'engine' => 'mpdf',
'preset' => 'bengali',
'paper' => 'a4',
'orientation' => 'portrait',
'locale' => 'bn',
'font' => 'Noto Sans Bengali',
'fallback' => ['Noto Sans Bengali', 'Noto Sans'],
'direction' => 'ltr',
'metadata' => ['creator' => 'Acme ERP'],
],
],
return UnicodePdf::profile('invoice')
->loadView('invoices.show', $data)
->download('invoice.pdf');
Bundled examples: invoice, receipt, rtl-report. Missing names throw PresetNotFoundException::profile().
Helpers, Blade, and components
unicode_pdf()
unicode_pdf(); // manager
unicode_pdf('<h1>Hello</h1>'); // document with HTML loaded
@unicodeNumerals
@unicodeNumerals('85000', 'bn')
<x-unicode-pdf::document>
UTF-8 HTML shell with dir / lang:
<x-unicode-pdf::document lang="bn" dir="ltr" title="চালান">
<h1>{{ $customer }}</h1>
<p>মোট: ৳{{ $total }}</p>
</x-unicode-pdf::document>
Published views live under resources/views/vendor/unicode-pdf/.
Package views (namespace unicode-pdf::):
unicode-pdf::sample-multilingualunicode-pdf::story-all-languagesunicode-pdf::invoice-bengaliunicode-pdf::invoice-arabicunicode-pdf::invoice-hindiunicode-pdf::components.document
php artisan about includes Unicode PDF (engine, default font, direction).
Models (Pdfable / GeneratesPdf)
use ImranDev\UnicodePdf\Concerns\GeneratesPdf;
use ImranDev\UnicodePdf\Concerns\Pdfable;
class Invoice extends Model implements Pdfable
{
use GeneratesPdf;
protected function pdfView(): string
{
return 'invoices.pdf';
}
protected function pdfData(): array
{
return ['invoice' => $this];
}
protected function pdfPreset(): string
{
return 'bengali';
}
protected function pdfFilename(): string
{
return 'invoice-'.$this->id.'.pdf';
}
}
return $invoice->toPdf()->download();
Custom engines
use ImranDev\UnicodePdf\Engines\AbstractPdfEngine;
UnicodePdf::extend('gotenberg', function ($app, $fontManager, $securityManager) {
return new class($app['view'] ?? null, $fontManager, $securityManager) extends AbstractPdfEngine {
public function getName(): string
{
return 'gotenberg';
}
public function supports(string $capability): bool
{
return true;
}
public function output(): string
{
// Call your renderer with $this->getPreparedContent()
return '%PDF-1.4 ...';
}
};
});
return UnicodePdf::engine('gotenberg')->loadHtml($html)->download();
Implement ImranDev\UnicodePdf\Contracts\PdfEngine if you do not extend AbstractPdfEngine.
Enums
use ImranDev\UnicodePdf\Enums\Direction;
use ImranDev\UnicodePdf\Enums\Engine;
use ImranDev\UnicodePdf\Enums\Orientation;
use ImranDev\UnicodePdf\Enums\PaperSize;
use ImranDev\UnicodePdf\Enums\Preset;
UnicodePdf::engine(Engine::Mpdf)
->preset(Preset::Bengali)
->paper(PaperSize::A4, Orientation::Portrait)
->direction(Direction::Ltr);
| Enum | Cases |
|---|---|
Engine |
Native, Dompdf, Mpdf, Tcpdf, Browsershot, Null — label(), composerPackage(), values() |
Direction |
Auto, Ltr, Rtl |
Orientation |
Portrait, Landscape — short() → P / L |
PaperSize |
A3 A4 A5 A6 Letter Legal Tabloid Executive Folio B4 B5 — dimensions() in points |
Preset |
All preset names listed above — values() |
String names still work everywhere enums are accepted.
Artisan commands
| Command | Purpose |
|---|---|
php artisan unicode-pdf:diagnose |
Comprehensive system check: PHP version, extensions, engine adapters, font search paths, capabilities |
php artisan unicode-pdf:fonts |
Displays default, registered, and fallback font coverage across scripts |
php artisan unicode-pdf:font:list |
Formatted table of all registered font families and their variant file paths |
php artisan unicode-pdf:font:install |
Download SIL OFL Google Noto fonts (interactive or via --font flag) |
php artisan unicode-pdf:clear-cache |
Clears cached font metadata, glyph tables, and temporary PDF files |
php artisan unicode-pdf:generate |
CLI PDF compiler from a Blade view or raw HTML |
Font installation
# Interactive menu prompt:
php artisan unicode-pdf:font:install
# Targeted font install:
php artisan unicode-pdf:font:install --font=bengali
# Force overwrite if already downloaded:
php artisan unicode-pdf:font:install --font=arabic --force
CLI generation
# From a Blade view:
php artisan unicode-pdf:generate invoices.show \
--output=storage/app/invoice.pdf \
--preset=bengali \
--engine=native \
--paper=a4 \
--orientation=portrait
# From a raw HTML string:
php artisan unicode-pdf:generate \
--html="<h1>বাংলাদেশ</h1>" \
--output=/tmp/out.pdf \
--preset=bengali \
--filename=bangladesh.pdf
Options for unicode-pdf:generate:
view: Blade view template name--html: Raw HTML content string--output: Destination path on disk (defaults tostorage/app/unicode-pdf/generated/{filename})--preset: Preset name (bengali,arabic,devanagari,cjk,universal, etc.)--engine: PDF engine (native,dompdf,mpdf,tcpdf,browsershot,null)--paper: Paper size (default:a4)--orientation:portrait(default) orlandscape--filename: Filename for downloaded / saved artifact (default:document.pdf)
Configuration
Published file: config/unicode-pdf.php.
| Key | Default | Meaning |
|---|---|---|
engine |
env('UNICODE_PDF_ENGINE', 'native') |
Default driver (built-in) |
default_font |
Noto Sans |
Primary family |
fallback_fonts |
Noto multi-script list | Fallback chain |
fonts |
[] |
Registered TTF/OTF paths |
font_path |
storage/app/unicode-pdf/fonts |
Font storage |
font_cache |
storage/app/unicode-pdf/cache/fonts |
Parsed font cache |
font_detection.enabled |
true |
Script analysis |
font_detection.auto_inject_css |
true |
Inject @font-face + font-family |
font_detection.dominant_script_only |
false |
Limit detection to dominant script |
direction |
auto |
auto / ltr / rtl |
bidi |
true |
Bidirectional assistance |
normalization.enabled |
false |
Unicode normalize HTML |
normalization.form |
NFC |
NFC NFD NFKC NFKD |
paper |
a4 |
Default paper |
orientation |
portrait |
Default orientation |
margins |
10mm all sides |
Default margins |
emoji.enabled |
true |
Emoji handling |
emoji.fallback_font |
Noto Color Emoji |
Emoji fallback |
security.allow_remote_images |
false |
Remote <img> |
security.allow_remote_fonts |
false |
Remote fonts |
security.allowed_remote_hosts |
[] |
Host allowlist when remote is on |
security.allowed_local_paths |
base, storage, public |
Path allowlist |
performance.cache_enabled |
true |
Font/script cache files |
performance.cache_path |
storage/app/unicode-pdf/cache |
Cache dir |
performance.lazy_font_loading |
true |
Load fonts on demand |
logging.enabled |
false |
Diagnostic logs |
logging.channel |
stack |
Log channel |
cache.store / cache.ttl |
null / 3600 |
Laravel cache for PDF binaries |
queue.connection / queue.queue |
null / default |
GeneratePdfJob |
profiles |
invoice, receipt, rtl-report |
Named document profiles |
Environment variables: UNICODE_PDF_ENGINE, UNICODE_PDF_DEFAULT_FONT, UNICODE_PDF_LOGGING, UNICODE_PDF_LOG_CHANNEL, UNICODE_PDF_CACHE_STORE, UNICODE_PDF_CACHE_TTL, UNICODE_PDF_QUEUE_CONNECTION, UNICODE_PDF_QUEUE.
Security
Enabled by default:
| Control | Behavior |
|---|---|
| SSRF | Remote images/fonts off unless you opt in. Private/reserved IPs and 169.254.169.254 (cloud metadata) are blocked. Optional host allowlist. |
| Path traversal | realpath + allowlisted roots; NUL-byte rejected. Temp dir is always allowed for generated files. |
| Header injection | Filenames cannot contain CR/LF/NUL. UTF-8 names encoded as filename*=UTF-8''.... |
Enable remote assets only with a tight allowlist:
'security' => [
'allow_remote_images' => true,
'allowed_remote_hosts' => ['cdn.example.com'],
],
ResourceResolver::toDataUri() converts local/remote images to base64 data URIs after the same checks.
Exceptions
All extend ImranDev\UnicodePdf\Exceptions\UnicodePdfException:
| Exception | When |
|---|---|
InvalidUtf8Exception |
Input is not valid UTF-8 (offset + snippet) |
UnsupportedEngineException |
Unknown engine or missing Composer package |
PresetNotFoundException |
Unknown preset or profile |
FontRegistrationException |
Font definition missing family |
FontNotFoundException |
Requested family not registered |
MissingGlyphException |
Glyph coverage failure |
UnsupportedScriptException |
Script not supported by the current setup |
UnsafeResourceException |
SSRF or path traversal |
PdfGenerationException |
Engine failed to render / view factory missing |
Testing
composer test
composer test:unit
composer test:feature
composer test:coverage
composer lint
composer analyse
composer check # pint --test + phpstan + pest
The test suite uses the null engine (UNICODE_PDF_ENGINE=null). Dompdf / mPDF integration tests skip when those packages are not installed.
Fake (like Mail::fake())
use ImranDev\UnicodePdf\Facades\UnicodePdf;
UnicodePdf::fake();
$this->get('/invoices/101/pdf')->assertOk();
UnicodePdf::assertGenerated();
UnicodePdf::assertGenerated(2);
UnicodePdf::assertNothingGenerated();
UnicodePdf::assertDownloaded('invoice.pdf');
UnicodePdf::assertStreamed('preview.pdf');
UnicodePdf::assertGeneratedUsing(function ($document, $binary) {
return $document->getPresetName() === 'bengali';
});
$all = UnicodePdf::generated(); // Illuminate\Support\Collection
Event::fake() and Queue::fake() work with PdfGenerated / GeneratePdfJob as usual.
Sample views
After publishing --tag=unicode-pdf-views:
return UnicodePdf::preset('bengali')
->loadView('unicode-pdf::invoice-bengali', $data)
->download('invoice.pdf');
| View | Language |
|---|---|
unicode-pdf::invoice-bengali |
Bangla invoice |
unicode-pdf::invoice-arabic |
Arabic RTL invoice |
unicode-pdf::invoice-hindi |
Hindi / Devanagari |
unicode-pdf::sample-multilingual |
Mixed English + Bengali + Arabic + Hindi + CJK |
unicode-pdf::story-all-languages |
Same full-page story in every mapped language |
Requirements matrix
CI runs on PHP 8.1–8.5 and Laravel 10–13 (incompatible pairs excluded, e.g. PHP 8.1 × Laravel 11+).
| Laravel | PHP | Testbench |
|---|---|---|
| 10 | 8.1 – 8.5 | 8 |
| 11 | 8.2 – 8.5 | 9 |
| 12 | 8.2 – 8.5 | 10 |
| 13 | 8.3 – 8.5 | 11 |
Manager reference
Resolved as unicode-pdf / UnicodePdfManager. The facade proxies here; unknown methods are forwarded to a new document.
| Method | Returns |
|---|---|
createDocument($engine = null) |
UnicodePdfDocument |
engine($name) |
document using that engine |
driver($name) |
raw PdfEngine |
profile($name) |
document from config profile |
extend($driver, Closure) |
register custom engine |
getDefaultEngine() |
string |
getFontManager() |
FontManager |
registerFont(array) |
void |
fake() |
UnicodePdfFake |
supports($capability) |
bool |
validateUtf8 / normalize / numerals |
Unicode helpers |
detectScripts / detectDirection / checkGlyphs |
analysis |
clearCache() |
bool |
when / unless / tap / macros |
Laravel traits |
Documentation
| Guide | |
|---|---|
| Installation | Requirements, engines, publish tags |
| Quickstart | Copy-paste examples |
| Configuration | config/unicode-pdf.php |
| Fonts | Registration, presets, discovery |
| Unicode | Validation, normalization, scripts |
| Bengali | কার, যুক্তাক্ষর, ৳ |
| Arabic / RTL | Direction, BiDi, shaping |
| Engines | Adapter matrix |
| Fallback | Font stacks |
| Security | SSRF, paths, headers |
| Performance | Cache, lazy fonts |
| Artisan | CLI |
| Testing | Pest, fakes |
| Troubleshooting | Common failures |
| Contributing | Dev workflow |
License
The MIT License (MIT). See LICENSE for details.
Report security issues privately — see SECURITY.md.