The Engineering Challenge of Arabic PDF Generation
In enterprise resource planning (ERP) platforms, point-of-sale (POS) systems, and regional e-commerce stores, automated invoice and receipt generation is mission-critical. Many transactions require printable documents; exact legal requirements depend on the jurisdiction and transaction.
Yet, generating PDF documents containing Arabic text remains one of the most frustrating hurdles for backend and full-stack software engineers. Developers migrating desktop systems or building web platforms frequently encounter classic Arabic rendering defects:
- Disconnected Glyphs: Arabic is an inherently cursive script where letters alter their shape based on position (isolated, initial, medial, or final). Many standard PDF libraries render Arabic as isolated, detached letters (
ف ا ت و ر ةinstead ofفاتورة). - Reversed Character Ordering: Naive engines lacking a compliant Unicode Bidirectional Algorithm (UAX #9) implementation reverse strings entirely, rendering right-to-left text backward.
- BiDi Punctuation and Mixed-Token Collisions: When Arabic descriptions mingle with Latin product SKUs, phone numbers, serial codes, or currency symbols (such as
SYP,USD, or%), punctuation marks jump to the wrong side of sentences, and numbers display in erratic order. - Table Pagination Breakage: Multi-item invoices spanning several pages often split table rows right through text baselines or lose the table header on subsequent pages.
For Syrian startups, manufacturers, and logistics operators deploying on cost-effective cloud virtual machines (VMs) or on-premise servers with limited RAM, the system must also generate high-resolution, vector-crisp documents without exhausting server memory.
This guide outlines a practical starting point for an Arabic PDF invoicing pipeline using HTML/CSS Paged Media and headless browser automation; validate it against your own security and compliance requirements.
---
Architecture: Why Direct PDF Libraries Fall Short vs Headless Engines
Historically, backend teams chose between low-level PDF generation libraries or headless rendering engines:
| Dimension | Native Vector Libraries (e.g. PDFKit, TCPDF, ReportLab) | Legacy HTML-to-PDF Tools (e.g. wkhtmltopdf) | Modern Headless Chromium (Playwright / Puppeteer) | | :--- | :--- | :--- | :--- | | Arabic Text Shaping (HarfBuzz) | Requires manual OpenType glyph shaping passes or external C-extensions. | Flaky; uses outdated WebKit forks with broken modern CSS. | Native, automated font shaping powered by Chromium’s built-in HarfBuzz and Skia engines. | | RTL & Layout Flexibility | Imperative coordinate calculation (doc.text(x, y)). Extremely brittle. | Basic HTML4 table layouts; flexbox and CSS grid fail. | Full CSS Flexbox, CSS Grid, and CSS Logical Properties support. | | Multi-Page Pagination | Manual cursor management and page overflow calculation. | Unpredictable page breaks; frequently splits text rows. | Native CSS Paged Media (@page, break-inside: avoid, repeating <thead>). | | Memory Footprint | Usually lower, but depends on the library and document. | Varies with renderer and workload. | Usually higher; measure peak memory and bound concurrent jobs. |
While native libraries offer lower RAM consumption, the developer overhead of hand-crafting RTL coordinate systems and OpenType glyph tables is prohibitive.
Modern Headless Chromium via Puppeteer or Playwright provides the most reliable architecture for Arabic document generation because it evaluates text with the exact same industrial-grade font shaping, kerning, and CSS rendering engine used by modern desktop browsers.
---
Step 1: Crafting an RTL Print-Ready HTML & CSS Template
The key to predictable PDF output is treating your invoice template as a print-first document using the CSS Paged Media Module. Replace the font-data placeholders below with licensed WOFF2 files encoded as data URLs; relative font paths will not resolve from a page populated with setContent alone.
Here is an architectural template structure optimized for Arabic invoices:
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>فاتورة ضريبية</title>
<style>
/* 1. Embed Fonts Locally as Base64 or Local WOFF2 */
@font-face {
font-family: 'Cairo';
font-style: normal;
font-weight: 400;
src: url('data:font/woff2;base64,REPLACE_WITH_FONT_DATA') format('woff2');
}
@font-face {
font-family: 'Cairo';
font-style: normal;
font-weight: 700;
src: url('data:font/woff2;base64,REPLACE_WITH_FONT_DATA') format('woff2');
}
/* 2. Configure Print Page Geometry */
@page {
size: A4 portrait;
margin: 15mm 12mm 20mm 12mm; /* Top, Right, Bottom, Left */
@bottom-left {
content: "صفحة " counter(page) " من " counter(pages);
font-family: 'Cairo', sans-serif;
font-size: 9pt;
color: #64748b;
}
@bottom-right {
content: "شركة دراجون سوفت - نظام الفوترة الرقمي";
font-family: 'Cairo', sans-serif;
font-size: 9pt;
color: #64748b;
}
}
/* 3. Base Document Typography */
body {
font-family: 'Cairo', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 11pt;
line-height: 1.5;
color: #0f172a;
direction: rtl;
text-align: right;
margin: 0;
padding: 0;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* 4. Table Pagination Rules */
table {
width: 100%;
border-collapse: collapse;
page-break-inside: auto;
}
/* Repeat header on every page break */
thead {
display: table-header-group;
}
/* Never break a table row across pages */
tr {
page-break-inside: avoid;
break-inside: avoid;
}
/* Avoid orphans in totals and signature blocks */
.invoice-summary, .signature-block {
page-break-inside: avoid;
break-inside: avoid;
}
/* Header styling with brand accent */
.header-bar {
border-top: 4px solid #ff003c;
padding-top: 10px;
margin-bottom: 20px;
}
/* Isolate mixed English SKUs and numeric values */
.sku-code, .unit-num {
direction: ltr;
display: inline-block;
unicode-bidi: isolate;
}
</style>
</head>
<body>
<div class="header-bar">
<table style="border: none;">
<tr>
<td style="text-align: right;">
<h1 style="margin: 0; font-size: 20pt; color: #0f172a;">فاتورة مبيعات ضريبية</h1>
<p style="margin: 4px 0; color: #64748b;">رقم الفاتورة: <bdi>INV-2026-0842</bdi></p>
</td>
<td style="text-align: left; vertical-align: top;">
<span style="background: #fef2f2; color: #ff003c; padding: 4px 12px; border-radius: 4px; font-weight: bold; font-size: 9pt;">
معتمدة إلكترونياً
</span>
</td>
</tr>
</table>
</div>
<!-- Line items table -->
<table class="items-table">
<thead>
<tr style="background: #f1f5f9;">
<th style="padding: 8px; text-align: right;">البند والوصف</th>
<th style="padding: 8px; text-align: center;">الرمز (SKU)</th>
<th style="padding: 8px; text-align: center;">الكمية</th>
<th style="padding: 8px; text-align: left;">سعر الوحدة</th>
<th style="padding: 8px; text-align: left;">الإجمالي</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px 8px; border-bottom: 1px solid #e2e8f0;">
لوحة تحكم إلكترونية صناعية
</td>
<td style="padding: 10px 8px; text-align: center; border-bottom: 1px solid #e2e8f0;">
<span class="sku-code">CTRL-992-SY</span>
</td>
<td style="padding: 10px 8px; text-align: center; border-bottom: 1px solid #e2e8f0;">04</td>
<td style="padding: 10px 8px; text-align: left; border-bottom: 1px solid #e2e8f0;">
<span class="unit-num">150,000 SYP</span>
</td>
<td style="padding: 10px 8px; text-align: left; border-bottom: 1px solid #e2e8f0; font-weight: bold;">
<span class="unit-num">600,000 SYP</span>
</td>
</tr>
</tbody>
</table>
</body>
</html>
Essential BiDi Tactics in the Template
- The
<bdi>Tag (Bidirectional Isolation): Wrap dynamic tokens that mix letters and punctuation (e.g. invoice codes#INV-2026, serial numbers, or phone numbers+963 991 039 132) inside<bdi>elements. This prevents the surrounding Arabic directional context from dragging adjacent hyphens or plus signs to the wrong side. - Local Font Subsetting: Never load fonts from external CDNs during a server-side PDF render. Network latency will delay page completion, and missing web connectivity will cause fallback to un-shaped system fonts. Bundle local TrueType (
.ttf) or WOFF2 files directly on the server filesystem. - Exact Color Adjustment: Modern Chromium engines suppress background colors by default to save ink. Setting
-webkit-print-color-adjust: exactandprint-color-adjust: exactforces the engine to preserve your brand colors, table row shading, and status badges.
---
Step 2: The Production Node.js Rendering Pipeline
With our template defined, let's write a robust Node.js service that compiles the HTML and triggers Chromium's vector PDF export.
// pdf-generator.ts
import puppeteer, { Browser } from 'puppeteer';
import fs from 'node:fs/promises';
import path from 'node:path';
export interface InvoiceItem {
description: string;
sku: string;
quantity: number;
unitPrice: number;
total: number;
}
export interface InvoiceData {
invoiceNumber: string;
issueDate: string;
clientName: string;
items: InvoiceItem[];
subtotal: number;
tax: number;
grandTotal: number;
qrCodeSvg: string; // Dynamic inline verification QR
}
export async function generateInvoicePdf(
htmlContent: string,
outputPath?: string
): Promise<Buffer> {
// Keep Chromium's sandbox enabled and run the service as a non-root user.
const browser: Browser = await puppeteer.launch({
headless: true,
});
try {
const page = await browser.newPage();
// 1. Set media type to 'print' to evaluate @page rules
await page.emulateMediaType('print');
// 2. Embed fonts/assets in the HTML and wait for font loading.
await page.setContent(htmlContent, {
waitUntil: 'networkidle0',
timeout: 15000,
});
await page.evaluate(() => document.fonts.ready);
// 3. Render PDF binary with strict print dimensions
const pdfBuffer = await page.pdf({
format: 'A4',
printBackground: true,
preferCSSPageSize: true, // Respects @page margins and dimensions defined in CSS
margin: {
top: '15mm',
right: '12mm',
bottom: '20mm',
left: '12mm',
},
});
if (outputPath) {
await fs.writeFile(outputPath, pdfBuffer);
}
return Buffer.from(pdfBuffer);
} finally {
await browser.close();
}
}
---
Step 3: Preventing VPS Out-of-Memory Crashes via Browser Pooling
Launching a new Chromium browser for every invoice is costly. Under concurrent requests, memory use can rise enough to exhaust a small server; measure it under your actual workload rather than assuming a fixed per-process figure.
Reuse a browser and enforce a measured concurrency limit with a job queue. The example below illustrates browser reuse and isolated contexts; it is not a complete queue or a memory ceiling. A production implementation must serialize browser startup, put context creation inside cleanup handling, and enforce a concurrency limit before accepting jobs:
// browser-pool.ts
import puppeteer, { Browser, BrowserContext } from 'puppeteer';
class BrowserPool {
private browser: Browser | null = null;
private activeJobs = 0;
private jobCount = 0;
private readonly MAX_JOBS_BEFORE_RESTART = 150; // Periodic recycle to eliminate memory bloat
private async getBrowser(): Promise<Browser> {
if (!this.browser || !this.browser.connected) {
this.browser = await puppeteer.launch({
headless: true,
});
this.jobCount = 0;
}
return this.browser;
}
public async renderPdf(html: string): Promise<Buffer> {
const browser = await this.getBrowser();
this.activeJobs++;
this.jobCount++;
// Use an isolated Incognito Context rather than a separate browser instance
const context: BrowserContext = await browser.createBrowserContext();
const page = await context.newPage();
try {
await page.emulateMediaType('print');
await page.setContent(html, { waitUntil: 'networkidle0', timeout: 10000 });
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
});
return Buffer.from(pdf);
} finally {
await page.close();
await context.close();
this.activeJobs--;
// Recycle periodically; monitor memory separately and limit concurrency.
if (this.jobCount >= this.MAX_JOBS_BEFORE_RESTART && this.activeJobs === 0) {
await this.browser?.close();
this.browser = null;
}
}
}
}
export const pdfPool = new BrowserPool();
Key Architectural Benefits of This Worker Pool:
- Browser reuse: Isolated contexts avoid launching a whole browser for every document; benchmark the difference on your infrastructure.
- Resource control: A bounded job queue and memory monitoring are still required. Periodic recycling alone does not prevent out-of-memory failures.
- Context isolation: Separate contexts reduce cross-job state sharing, but sensitive data still needs careful handling in logs, templates, and storage.
---
Step 4: Verification and Quality Assurance Checklist
Before pushing your PDF invoicing engine to production, audit your output against this operational checklist:
| Quality Dimension | Test Scenario | Expected Outcome | | :--- | :--- | :--- | | Punctuation Alignment | Text ending with a colon or parenthesis: (المجموع الإجمالي): | Colon and parenthesis remain on the far left side of the sentence in RTL mode. | | Mixed-Language SKUs | Arabic item name followed by Latin SKU: محرك سيرفو AC-220V | Latin model string reads left-to-right without reversing digits or breaking adjacent Arabic words. | | Page Boundary Split | Invoice with 45 line items spanning 3 pages. | Table rows never cut horizontally across text lines; the table header repeats cleanly at the top of pages 2 and 3. | | Vector Sharpness | Zooming into the generated PDF at 400% on desktop or mobile. | Fonts, borders, and QR codes remain crystal-clear vector paths rather than pixelated raster images. | | Print Spool Size | Typical 2-page invoice file size. | Measure output on representative documents and optimize embedded fonts and images where needed. |
---
Conclusion: Engineering Modern Business Infrastructure
Automating Arabic business documentation need not compromise design or stability. CSS Paged Media and a carefully managed Headless Chromium service can support clear billing documents; confirm legal and tax requirements separately for each jurisdiction.
Whether you are scaling an enterprise ERP, modernizing wholesale supply chain workflows, or building an omnichannel e-commerce platform, robust software architecture is the foundation of dependable operations.
Are you planning to build or modernize your enterprise software, automate complex document workflows, or deploy custom web and mobile applications tailored to the Syrian market? Contact Dragonfly Soft today to consult with our engineering team in Aleppo and transform your business systems.