Генерация счета на оплату PDF PHP

С помощью расширения dompdf можно легко сформировать PDF файл. По сути, dompdf – это конвертер HTML в PDF который поддерживает CSS, атрибуты style и другие.

  • Поддерживает большинство свойств CSS 2.1.
  • Поддерживает сложные таблицы, включая индивидуальный стили ячеек.
  • Поддержка изображений gif, png с прозрачностью, bmp и jpeg.
  • PHP => 5.3 с установленными расширениями DOM, GD, MBString, php-font-lib, php-svg-lib.

Проект на GitHub.

1

Для примера сделаем счет на оплату:

Для удобства сформируем массив товаров по которому скрипт выведет таблицу и посчитает итоговую сумму и НДС.

$prods = array(
	array(
		'name'  => 'Плита CERAMAGUARD FINE FISSURED (100 RH) 600*600*15',
		'count' => 25.3,
		'unit'  => 'м2',
		'price' => 1210,
		'nds'   => 18,
	),
	array(
		'name'  => 'Европодвес (0.5м)',
		'count' => 100,
		'unit'  => 'шт.',
		'price' => 5.50,
		'nds'   => 0,
	),  
	array(
		'name'  => 'Профиль 20*20',
		'count' => 10,
		'unit'  => 'м',
		'price' => 550,
		'nds'   => 10,
	),
);
PHP

Также понадобятся функции для форматирования цен и вывода суммы прописью.

// Форматирование цен.
function format_price($value)
{
	return number_format($value, 2, ',', ' ');
}

// Сумма прописью.
function str_price($value)
{
	$value = explode('.', number_format($value, 2, '.', ''));

	$f = new NumberFormatter('ru', NumberFormatter::SPELLOUT);
	$str = $f->format($value[0]);

	// Первую букву в верхний регистр.
	$str = mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1, mb_strlen($str));

	// Склонение слова "рубль".
	$num = $value[0] % 100;
	if ($num > 19) { 
		$num = $num % 10; 
	}	
	switch ($num) {
		case 1: $rub = 'рубль'; break;
		case 2: 
		case 3: 
		case 4: $rub = 'рубля'; break;
		default: $rub = 'рублей';
	}	
	
	return $str . ' ' . $rub . ' ' . $value[1] . ' копеек.';
}
PHP

Далее нужно сверстать счёт в обычном HTML с CSS.

Некоторые замечания:

  • В адреса картинок в <img src=""> лучше указывать полные URL.
  • У таблиц не получится задать ширину колонок тегом <col>.
$html = '
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
	
	<style type="text/css">
	* {font-family: arial;font-size: 14px;line-height: 14px;}
	table {margin: 0 0 15px 0;width: 100%;border-collapse: collapse;border-spacing: 0;}		
	table th {padding: 5px;font-weight: bold;}        
	table td {padding: 5px;}	
	.header {margin: 0 0 0 0;padding: 0 0 15px 0;font-size: 12px;line-height: 12px;text-align: center;}
	h1 {margin: 0 0 10px 0;padding: 10px 0;border-bottom: 2px solid #000;font-weight: bold;font-size: 20px;}
		
	/* Реквизиты банка */
	.details td {padding: 3px 2px;border: 1px solid #000000;font-size: 12px;line-height: 12px;vertical-align: top;}

	/* Поставщик/Покупатель */
	.contract th {padding: 3px 0;vertical-align: top;text-align: left;font-size: 13px;line-height: 15px;}	
	.contract td {padding: 3px 0;}		

	/* Наименование товара, работ, услуг */
	.list thead, .list tbody  {border: 2px solid #000;}
	.list thead th {padding: 4px 0;border: 1px solid #000;vertical-align: middle;text-align: center;}	
	.list tbody td {padding: 0 2px;border: 1px solid #000;vertical-align: middle;font-size: 11px;line-height: 13px;}	
	.list tfoot th {padding: 3px 2px;border: none;text-align: right;}	

	/* Сумма */
	.total {margin: 0 0 20px 0;padding: 0 0 10px 0;border-bottom: 2px solid #000;}	
	.total p {margin: 0;padding: 0;}
		
	/* Руководитель, бухгалтер */
	.sign {position: relative;}
	.sign table {width: 60%;}
	.sign th {padding: 40px 0 0 0;text-align: left;}
	.sign td {padding: 40px 0 0 0;border-bottom: 1px solid #000;text-align: right;font-size: 12px;}
	.sign-1 {position: absolute;left: 149px;top: -44px;}	
	.sign-2 {position: absolute;left: 149px;top: 0;}	
	.printing {position: absolute;left: 271px;top: -15px;}
	</style>
</head>
<body>
	<p class="header">
		Внимание! Оплата данного счета означает согласие с условиями поставки товара.
		Уведомление об оплате обязательно, в противном случае не гарантируется наличие
		товара на складе. Товар отпускается по факту прихода денег на р/с Поставщика,
		самовывозом, при наличии доверенности и паспорта.
	</p>

	<table class="details">
		<tbody>
			<tr>
				<td colspan="2" style="border-bottom: none;">ЗАО "БАНК", г.Москва</td>
				<td>БИК</td>
				<td style="border-bottom: none;">000000000</td>
			</tr>
			<tr>
				<td colspan="2" style="border-top: none; font-size: 10px;">Банк получателя</td>
				<td>Сч. №</td>
				<td style="border-top: none;">00000000000000000000</td>
			</tr>
			<tr>
				<td width="25%">ИНН 0000000000</td>
				<td width="30%">КПП 000000000</td>
				<td width="10%" rowspan="3">Сч. №</td>
				<td width="35%" rowspan="3">00000000000000000000</td>
			</tr>
			<tr>
				<td colspan="2" style="border-bottom: none;">ООО "Компания"</td>
			</tr>
			<tr>
				<td colspan="2" style="border-top: none; font-size: 10px;">Получатель</td>
			</tr>
		</tbody>
	</table>

	<h1>Счет на оплату № 10 от 01 февраля 2018 г.</h1>

	<table class="contract">
		<tbody>
			<tr>
				<td width="15%">Поставщик:</td>
				<th width="85%">ООО "Компания", ИНН 0000000000, КПП 000000000, 125009, Москва г, Тверская, дом № 9</th>
			</tr>
			<tr>
				<td>Покупатель:</td>
				<th>ООО "Покупатель", ИНН 0000000000, КПП 000000000, 119019, Москва г, Новый Арбат, дом № 10
				</th>
			</tr>
		</tbody>
	</table>

	<table class="list">
		<thead>
			<tr>
				<th width="5%">№</th>
				<th width="54%">Наименование товара, работ, услуг</th>
				<th width="8%">Коли-<br>чество</th>
				<th width="5%">Ед.<br>изм.</th>
				<th width="14%">Цена</th>
				<th width="14%">Сумма</th>
			</tr>
		</thead>
		<tbody>';
		
		$total = $nds = 0;
		foreach ($prods as $i => $row) {
			$total += $row['price'] * $row['count'];
			$nds += ($row['price'] * $row['nds'] / 100) * $row['count'];

			$html .= '
			<tr>
				<td align="center">' . (++$i) . '</td>
				<td align="left">' . $row['name'] . '</td>
				<td align="right">' . $row['count'] . '</td>
				<td align="left">' . $row['unit'] . '</td>
				<td align="right">' . format_price($row['price']) . '</td>
				<td align="right">' . format_price($row['price'] * $row['count']) . '</td>
			</tr>';
		}

		$html .= '
		</tbody>
		<tfoot>
			<tr>
				<th colspan="5">Итого:</th>
				<th>' . format_price($total) . '</th>
			</tr>
			<tr>
				<th colspan="5">В том числе НДС:</th>
				<th>' . ((empty($nds)) ? '-' : format_price($nds)) . '</th>
			</tr>
			<tr>
				<th colspan="5">Всего к оплате:</th>
				<th>' . format_price($total) . '</th>
			</tr>
		</tfoot>
	</table>
	
	<div class="total">
		<p>Всего наименований ' . count($prods) . ', на сумму ' . format_price($total) . ' руб.</p>
		<p><strong>' . str_price($total) . '</strong></p>
	</div>
	
	<div class="sign">
		<img class="sign-1" src="https://example.com/demo/sign-1.png">
		<img class="sign-2" src="https://example.com/demo/sign-2.png">
		<img class="printing" src="https://example.com/demo/printing.png">
		<table>
			<tbody>
				<tr>
					<th width="30%">Руководитель</th>
					<td width="70%">Иванов А.А.</td>
				</tr>
				<tr>
					<th>Бухгалтер</th>
					<td>Сидоров Б.Б.</td>
				</tr>
			</tbody>
		</table>
	</div>
</body>
</html>';
PHP
2

Передаём переменную $html в dompdf. Библиотеку можно скачать по ссылке https://github.com/dompdf/dompdf/releases.

// Если на сайте используется автозагрузка классов
//spl_autoload_unregister('autoload');

include_once __DIR__ . '/dompdf/autoload.inc.php';
$dompdf = new Dompdf\Dompdf();
$dompdf->set_option('isRemoteEnabled', TRUE);
$dompdf->setPaper('A4', 'portrait');
$dompdf->loadHtml($html, 'UTF-8');
$dompdf->render();

// Вывод файла в браузер:
$dompdf->stream('schet'); 

// Или сохранение на сервере:
$pdf = $dompdf->output(); 
file_put_contents(__DIR__ . '/schet.pdf', $pdf); 
PHP

В результате получится pdf файл.

3

Если используется не стандартный шрифт то нужно его установить по следующиму алгоритму:

1. Скачать шрифты в формате ttf для обычного и жирного начертания (например open-sans.ttf и open-sans-bold.ttf).

2. Скачать программу ttf2ufm (ttf2ufm.zip).

3. Запустить консоль (cmd), лучше от имени администратора.

4. Выполнить команды для всех файлов шрифтов по отдельности, подставив полные пути до файлов.

C:\ttf2ufm.exe -A -F -l russian C:\open-sans.ttf
C:\ttf2ufm.exe -A -F -l russian C:\open-sans-bold.ttf

5. На выходе получатся файлы:

open-sans.ttf
open-sans.ufm
open-sans.afm
open-sans-bold.ttf
open-sans-bold.ufm
​open-sans-bold.afm

6. Копируем их на сайт, в директорию библиотеки /dompdf/lib/fonts

7. В файле dompdf_font_family_cache.dist.php нужно прописать новый шрифт, добавив элемент в массив:

'open-sans' =>
	[
		'normal' => $distFontDir . '/open-sans',
		'bold' => $distFontDir . '/open-sans-bold'
	],
PHP
4

При использовании стандарных шрифтов (times-roman, helvetica и т.д.) если вместо кирилицы выводятся знаки вороса (???), то нужно сделать те же действия как описаны ваше, заменив только файлы (.ufm и .afm) в /dompdf/lib/fonts.

В файле dompdf_font_family_cache.dist.php они уже прописаны.

16.02.2018, обновлено 17.12.2022
65859
Предыдущая запись PHP массив в файл CSV
Следующая запись Отключить кэширование PHP

Комментарии 5

Alexander Kravchenko Alexander Kravchenko
28 апреля 2022 в 16:28
Спасибо за полезную статью с примером!
У меня не получилось использовать Arial. Файлы сгенерил, положил в папку, но всё равно были ??? вместо кириллицы. В итоге использую font-family: dejavu serif
Внимание, НЮАНС (сам долго провозился, разбираясь в чём проблема): строка таблицы не должна быть больше высоты страницы pdf, иначе в пдф эта строка начинается с новой страницы и больше чем высота страницы содержимое этой строки не показывается.
PS:
задонатил 100 руб.
Алексей Дементьев Алексей Дементьев
20 сентября 2022 в 06:27
В версии Dompdf 2.0.0 отсутстует файл dompdf_font_family_cache.dist.php
Stepan Nikatin Stepan Nikatin
29 мая 2023 в 09:13
dompdf\lib\fonts\installed-fonts.dist.json
Алексей Хоршев Алексей Хоршев
16 декабря 2023 в 15:17
Что за класс NumberFormatter? где его взять?
Алексей Хоршев Алексей Хоршев
16 декабря 2023 в 15:29
А еще, вот этот путь не корректен /dompdf/autoload.inc.php
В моем dompdf нет такого файла вообще

, чтобы добавить комментарий.

Другие публикации

Сумма прописью PHP
В PHP 5.3 появился класс NumberFormatter для форматирования чисел и денежных единиц в нужной локали. На его основе была написана функция со склонением слова «рубль».
17066
+5
Преобразование CSV в XLSX на PHP
В статье приведены два примера конвертации фалов csv в xlsx, алгоритм следующий...
8992
+2
Чтение Google таблиц в PHP
Как получить данные из Google spreadsheets в виде массива PHP? Очень просто, Google docs позволяет экспортировать лист в формате CSV, главное чтобы файл был в общем доступе.
22733
+6
PHP массив в файл CSV
Пример как преобразовать массив в CSV и сохранить его диске или отдать на скачивание.
15158
+1
Редактирование файла в PHPExcel
Иногда бывают случаи, когда нужно заполнить некий уже сформатированный xls-файл в PHP. Библиотека PHPExcel тоже это...
3728
-1
Excel: Как умножить все выделенные ячейки на число
Задача: В прайс-листе необходимо увеличить цены на 20%, при этом чтобы в листе не было формул.
12976
0