rawand201 / kurdish-escpos
Print correctly shaped Kurdish (Sorani and Badini), Arabic and English receipts and labels on ESC/POS thermal printers and Zebra (ZPL) and TSC (TSPL) label printers. Pure PHP, no binaries.
Requires
- php: ^8.2
- ext-gd: *
- ext-iconv: *
- ext-json: *
- bacon/bacon-qr-code: ^3.0
Requires (Dev)
- laravel/pint: ^1.25
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5|^12.0
Suggests
- illuminate/support: For the Laravel service provider and facade (^11.0|^12.0|^13.0)
Provides
None
Conflicts
None
Replaces
None
README
kurdish-escpos
Print receipts and labels in Kurdish, Arabic and English on thermal printers, with PHP. It works with Sorani, Badini, Hawrami and the other Kurdish dialects. Every letter joins the right way, and every line reads in the right direction.
You only need PHP and the GD extension. There is nothing else to install, so it also works on normal shared hosting.
kurdish-escpos پاکێجێکی PHPیە بۆ چاپکردنی وەسڵ بە کوردی (سۆرانی، بادینی، هەورامی و شێوەزارەکانی تر)، عەرەبی و ئینگلیزی لەسەر پرینتەری وەسڵ. پیتەکان بە دروستی پێکەوە دەلکێن و دەق بە ڕێکی لە ڕاستەوە بۆ چەپ دەنووسرێت. هیچ بەرنامەیەکی دەرەکی پێویست نییە؛ تەنها PHP و GD.
use RawanD201\KurdishEscpos\Laravel\Facades\KurdishEscpos; KurdishEscpos::print( KurdishEscpos::receipt() ->title('دەرمانخانەی ئازادی') ->text('سلێمانی، شەقامی سالم', align: 'center') ->divider() ->row('کوڵێرە', '1,250') ->row('ڤیتامین C', '12,500') ->divider() ->row('کۆی گشتی', '13,750 دینار', size: 32) ->qr('https://example.com/r/1042') );
Contents
- Why this package exists
- Features
- Showcase
- Installation
- Usage with Laravel
- Usage without Laravel
- Building receipts
- Supported printers
- Multiple printers
- Fonts
- Contributing
- Credits
- License
Why this package exists
Receipt printers do not know Kurdish:
- They do not have the Kurdish letters ڕ, ڵ, ێ and ە.
- They do not join letters together.
- They do not write from right to left.
So when you send Kurdish text to a receipt printer, it prints wrong or broken text.
Drawing the receipt as a picture with PHP does not help either. PHP's image functions draw every letter on its own, so the letters do not join. Some PHP libraries fix this for Arabic, but Kurdish letters like ڵ and ێ still come out broken.
This package solves the problem. It joins every letter the right way, puts the words in the right order, draws the receipt as a picture, and sends the picture to the printer. Every receipt printer can print a picture, so your Kurdish text always looks right.
Features
- Every Kurdish letter joins correctly, even ڵ and ێ.
- Works with Sorani, Badini, Hawrami and the other Kurdish dialects.
- Kurdish, Arabic and English can be on the same line, and numbers and prices stay in the right place.
- Use the included font, or your own font, such as NRT or Rudaw.
- Titles, text, item rows, lines, QR codes and empty space, for 80 mm and 58 mm paper and for labels.
- See a picture of the receipt before you print it.
- Works with receipt printers (ESC/POS) and with Zebra and TSC label printers.
- Print to network printers, USB printers and Windows shared printers.
- Ready for Laravel, and works in any PHP project.
- Only needs PHP and GD.
Showcase
Receipts in Sorani, Badini, Hawrami, Gorani, Laki, Luri and Zazaki, each printed by the package with a different font:
Installation
Requirements:
- PHP 8.2 or later
- The
gdandiconvextensions, which are enabled on almost every PHP installation - Laravel 11, 12 or 13, if you use the Laravel integration
Install the package with Composer:
composer require rawand201/kurdish-escpos
Usage with Laravel
The service provider and the KurdishEscpos facade are registered
automatically. Configure your printer in .env:
KURDISH_ESCPOS_PRINTER_HOST=192.168.1.50 # network printer # KURDISH_ESCPOS_PRINTER_PATH=/dev/usb/lp0 # or a printer exposed as a file KURDISH_ESCPOS_WIDTH=576 # 576 for 80 mm paper, 384 for 58 mm
To change other settings, publish the configuration file:
php artisan vendor:publish --tag=kurdish-escpos-config
Print a receipt:
use RawanD201\KurdishEscpos\Laravel\Facades\KurdishEscpos; $receipt = KurdishEscpos::receipt() ->title('دەرمانخانەی ئازادی') ->row('پاراسیتامۆڵ', '3,000') ->row('کۆی گشتی', '3,000 دینار', size: 32); KurdishEscpos::print($receipt);
To send a receipt to a printer other than the default one:
use RawanD201\KurdishEscpos\Printers\NetworkPrinter; KurdishEscpos::print($receipt, new NetworkPrinter('192.168.1.60'));
Show a preview before printing, for example in a Blade view:
$preview = KurdishEscpos::render($receipt)->pngDataUri();
<img src="{{ $preview }}" alt="Receipt preview">
Printing from a queue
Rendering a receipt takes about 130 ms. To keep requests fast, print from a queued job:
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Queue\Queueable; use RawanD201\KurdishEscpos\Laravel\Facades\KurdishEscpos; class PrintReceipt implements ShouldQueue { use Queueable; public function __construct(public Order $order) {} public function handle(): void { $receipt = KurdishEscpos::receipt()->title($this->order->shop_name); foreach ($this->order->items as $item) { $receipt->row($item->name, number_format($item->total)); } KurdishEscpos::print($receipt); } }
Usage without Laravel
use RawanD201\KurdishEscpos\Printers\NetworkPrinter; use RawanD201\KurdishEscpos\Receipt; use RawanD201\KurdishEscpos\Renderer; $receipt = Receipt::make() ->title('سوپاس') ->text('ڕۆژێکی خۆش', align: 'center') ->feed(); $rendered = (new Renderer)->render($receipt); file_put_contents('preview.png', $rendered->png); (new NetworkPrinter('192.168.1.50'))->send($rendered->commands());
Building receipts
| Method | Description |
|---|---|
text($text, $size = null, $align = 'start') |
A paragraph. Long lines wrap, and \n starts a new paragraph. start is the right edge for Kurdish and Arabic and the left edge for English; center and end are also available. |
title($text, $size = 40) |
Large, centered text, such as the shop name. |
row($name, $price, $size = null) |
An item name and its price on one line. For Kurdish and Arabic the name is on the right and the price on the left; for Latin text it is the other way around. Long names wrap. |
divider() |
A dashed horizontal rule. |
qr($data, $size = null) |
A centered QR code, about 240 dots wide by default. |
feed($lines = 1) |
Vertical space. |
openDrawer() |
Opens the cash drawer after printing. |
Sizes are measured in printer dots, at 203 dots per inch. The default text
size is 24. For 58 mm paper, create the receipt with
Receipt::make(Receipt::WIDTH_58MM).
A receipt can also be given as an array or as JSON, which is convenient when receipts are produced by another system:
{
"width": 576,
"items": [
{ "type": "text", "text": "دەرمانخانەی ئازادی", "size": 40, "align": "center" },
{ "type": "row", "name": "کوڵێرە", "price": "1,250" },
{ "type": "divider" },
{ "type": "qr", "data": "https://example.com/r/1042" },
{ "type": "feed", "lines": 1 }
]
}
Unknown fields and invalid values are reported with a clear error message rather than ignored.
Supported printers
This package works with almost every thermal receipt printer, and with Zebra and TSC label printers. Your printer only needs to:
- Speak one of these command languages:
- ESC/POS, which almost all receipt printers use. It was made by Epson, and most other brands copied it. This is the default.
- ZPL, used by Zebra label printers.
- TSPL, used by TSC label printers and many other label printers. The printer's manual tells you if it supports TSPL.
- Print pictures. The package sends each receipt as one picture, so the printer does not need to know Kurdish at all.
This includes most receipt printers sold in Kurdistan, such as Xprinter, Epson TM, Rongta and HPRT models, and label printers such as the Zebra ZD220, ZD421 and GK420 and the TSC TE200 and TTP-244.
These printers do not work:
- Very old Zebra printers that only speak EPL, such as the LP 2844.
- Star Micronics printers in Star mode. Many Star models can be switched to ESC/POS mode in their settings.
- Normal office printers (A4 laser or inkjet).
Connecting the printer
| Connection | Class | Example |
|---|---|---|
| Ethernet or Wi-Fi | NetworkPrinter |
new NetworkPrinter('192.168.1.50') |
| USB on Linux | FilePrinter |
new FilePrinter('/dev/usb/lp0') |
| Shared printer on Windows | FilePrinter |
new FilePrinter('\\\\localhost\\XPrinter') |
| Serial or Bluetooth on Linux | FilePrinter |
new FilePrinter('/dev/ttyUSB0') |
Most network printers use port 9100. If yours uses another port, pass it as the second argument.
FilePrinter only works when the printer is connected to the same computer
that runs PHP, and PHP must be allowed to write to it. On Linux, add the web
server user to the lp group. For a serial printer, set the speed first, for
example stty -F /dev/ttyUSB0 9600. A Bluetooth printer that is paired as a
serial port appears as /dev/rfcomm0.
Paper width
Set the width to the number of dots your printer prints on one line:
| Paper | Width |
|---|---|
| 80 mm | 576 dots (some models, such as the Epson TM-T88, print 512) |
| 58 mm | 384 dots |
If you are not sure, print the printer's self-test page: turn the printer off, hold the FEED button, and turn it on. The page shows the print width.
Older printers
Some older printers do not understand the normal picture command. If your printer prints nothing, or prints strange symbols, switch to the older command:
use RawanD201\KurdishEscpos\EscPos\PrinterOptions; $renderer = new Renderer(options: new PrinterOptions(image: 'column'));
In Laravel, set KURDISH_ESCPOS_IMAGE=column in .env.
Cutting and the cash drawer
| Option | Values |
|---|---|
cut |
partial (default), full, or none for printers without a cutter |
feed |
Lines of paper fed before the cut (default 4) |
drawerPin |
The cash drawer pin: 2 (default) or 5 |
$renderer = new Renderer(options: new PrinterOptions(cut: 'full'));
In Laravel, set KURDISH_ESCPOS_CUT=full in .env.
To open the cash drawer that is connected to the printer, call
openDrawer() on the receipt:
KurdishEscpos::print($receipt->openDrawer());
Label printers
Zebra and TSC printers print labels, such as price tags and pharmacy labels. Tell the package which language your printer speaks:
$renderer = new Renderer(options: new PrinterOptions(language: 'zpl')); // Zebra $renderer = new Renderer(options: new PrinterOptions(language: 'tspl')); // TSC
In Laravel, set KURDISH_ESCPOS_LANGUAGE=zpl (or tspl) in .env.
Set the width to the width of your labels, in dots:
| Label width | 203 dpi printer | 300 dpi printer |
|---|---|---|
| 40 mm | 320 dots | 472 dots |
| 50 mm | 400 dots | 590 dots |
| 100 mm | 800 dots | 1181 dots |
| 4 inch | 812 dots | 1200 dots |
Most label printers are 203 dpi. On a 300 dpi printer, the same text size looks smaller, so use sizes about one and a half times larger.
| Option | Values |
|---|---|
labelHeight |
The length of one label, in dots. Leave it out for continuous paper; the label is then as long as the receipt. Anything longer than the label does not print. |
labelGap |
TSC only: the gap between two labels, in mm (default 2). |
darkness |
0 to 30 for Zebra, 0 to 15 for TSC. Leave it out to keep the printer's own setting. |
dpi |
203 (default), 300 or 600. TSC printers need it to work out the label size. |
// 50 x 30 mm labels on a TSC printer $renderer = new Renderer(options: new PrinterOptions(language: 'tspl', labelHeight: 240)); $rendered = $renderer->render(Receipt::make(400)->title('ڤیتامین C')->row('نرخ', '12,500'));
In the Laravel config, the same settings are called language,
label_height, label_gap, darkness and dpi. The cut, feed and
cash drawer settings are only for receipt printers.
Testing your printer
Print a test page to check that everything works:
php artisan kurdish-escpos:test-print 192.168.1.50 php artisan kurdish-escpos:test-print /dev/usb/lp0 --width=384 php artisan kurdish-escpos:test-print 192.168.1.60 --language=zpl --width=812
The test page has the words «ڕاست» (right) and «چەپ» (left) at the two
edges of the paper. If one of them is cut off, your width is too large. If
nothing prints, try --image=column.
You can also give the name of a configured printer, such as
test-print kitchen, or test every printer at once with --all. Without an
address or a name, the command uses the default printer.
Multiple printers
A shop often has more than one printer: one at the cashier, one in the
kitchen, one at the bar. Give each printer a name in
config/kurdish-escpos.php:
'default' => 'cashier', 'printers' => [ 'cashier' => ['host' => '192.168.1.50'], 'kitchen' => ['host' => '192.168.1.51', 'width' => 384], 'bar' => ['path' => '/dev/usb/lp0', 'image' => 'column', 'cut' => 'none'], 'labels' => ['host' => '192.168.1.60', 'language' => 'zpl', 'width' => 812], ],
Each printer can have its own width, font, image, cut, feed,
drawer_pin, language and label settings. The receipt is laid out again for each printer, so the same
receipt fits both 80 mm and 58 mm paper.
Print on one printer:
KurdishEscpos::print($receipt); // the default printer KurdishEscpos::print($receipt, 'kitchen'); // a named printer
Print on several printers at the same time:
$result = KurdishEscpos::print($receipt, ['cashier', 'kitchen', 'bar']);
Network printers get the receipt at the same moment, so they all print together instead of waiting for each other. If one printer is off, the others still print. Check the result to see what happened:
if (! $result->ok()) { foreach ($result->failures() as $printer => $error) { Log::warning("Printer {$printer} failed: {$error->getMessage()}"); } } $result->throw(); // or stop with an error if any printer failed
When you print on a single printer, a problem stops with a
PrinterException, as before.
To print different receipts at the same time, for example the customer
receipt at the cashier and the order ticket in the kitchen, use
printEach():
KurdishEscpos::printEach([ 'cashier' => $receipt, 'kitchen' => $kitchenTicket, ]);
KurdishEscpos::receipt('kitchen') starts a receipt with the width of the
kitchen printer, and php artisan kurdish-escpos:test-print --all prints a
test page on every printer at once.
Without Laravel, pass the printers by name:
use RawanD201\KurdishEscpos\KurdishEscpos; use RawanD201\KurdishEscpos\Printers\NetworkPrinter; use RawanD201\KurdishEscpos\Printers\PrinterSetup; $escpos = new KurdishEscpos(new Renderer, [ 'cashier' => new NetworkPrinter('192.168.1.50'), 'kitchen' => new PrinterSetup(new NetworkPrinter('192.168.1.51'), width: 384), ], default: 'cashier'); $escpos->print($receipt, ['cashier', 'kitchen']);
Fonts
The package comes with the Vazirmatn font. It has all Kurdish, Arabic and English letters, including the Kurmanji letters ê, î, û, ç and ş.
Using another font
You can use any Kurdish .ttf font, such as NRT or Rudaw:
$renderer = new Renderer('/path/to/NRT-Reg.ttf');
In Laravel, set the path in .env:
KURDISH_ESCPOS_FONT=/var/www/fonts/NRT-Reg.ttf
If your font is missing some letters (many Kurdish fonts have no English letters), those letters are drawn with Vazirmatn, so nothing is left blank.
Checking a font
To check if a font works, run the command below. It tells you if any Kurdish letters are missing, and it saves a sample receipt so you can see how the font looks:
php artisan kurdish-escpos:check-font /path/to/font.ttf --preview=sample.png
# without Laravel
vendor/bin/kurdish-escpos-check-font /path/to/font.ttf sample.png
Contributing
Contributions are welcome. Please read CONTRIBUTING.md for the development setup and guidelines, and run the checks before opening a pull request:
composer test
composer format
composer analyse
To report a security vulnerability, follow SECURITY.md instead of opening a public issue. All changes are listed in CHANGELOG.md.
Credits
License
The package is released under the MIT License. The bundled Vazirmatn font is licensed under the SIL Open Font License 1.1.


