DOMPDF – экспорт данных из PHP в PDF

  • Tutorial
С помощью библиотеки DOMPDF можно создавать PDF файлы из HTML кода. Нужно только сверстать некий HTML шаблон и передать его DOMPDF и уже на выходе получить сгенерированный PDF файл. Но не надо забывать и то, что эта библиотека не движок для обработки веб-страниц, а значит и шаблоны должны быть сверстаны с использованием самых базовых средств: HTML тегов и CSS стилей. Хотя разработчики заявляют, что их библиотека поддерживает стандарты CSS 2.1 и даже некоторые свойства CSS 3. С полным их перечнем можно ознакомиться здесь. Есть, кстати, один минус – она является довольно “прожорливой” к оперативной памяти. Поэтому возможно придется задуматься об аренде виртуального выделенного сервера. Но это зависит от содержания и объема планируемых PDF документов. С другой стороны она вам может помочь сэкономить много времени на разработку различных отчетов в формате PDF.
Читая различную документацию по DOMPDF, а ее немного и то в основном больше информации в виде ответов на заданные вопросы. Я заметил, что ее используют для более тривиальных задач. Ну, к примеру, ответа на вопрос: как задать номер стартовой страницы для документа? Мной получено не было.
Сейчас я вам хочу рассказать о тех “подводных камнях”, с которыми мне пришлось столкнуться. И поверьте, я потерял много времени. Потерял бы еще больше, если бы мне не дал пару советов один добрый человек. Уже имевший опыт по работе с данной библиотекой.
Представляю перечень проблем, с которыми мне пришлось столкнуться:

  • отсутствие необходимых каталогов с файлами (не полная сборка);
  • частично рабочая версия библиотеки;
  • наличие бага при попытке установки номера страницы документа (номер стартовой страницы);
  • наличие в конце документа пустой страницы;
  • не полное заполнение страницы текстом (иногда оставалось много пустого места).

Как видно из всего выше сказанного, в этой библиотеке имеется достаточно неприятных багов, но если постараться, то с ней можно “договориться”.

Установка библиотеки


Саму библиотеку берем вот здесь. Я, к сожалению, не могу сказать (как обычно говорят в таких случаях), что необходимо скачать самую новую версию. Далее я объясню почему. Но я все-таки надеюсь, что со временем существующие в последней версии баги будут устранены. После получения архива с библиотекой, распакуйте его в нужную вам на сервере директорию.
В корневом каталоге библиотеки DOMPDF находиться файл конфигурации – dompdf_config.inc.php. В нем присутствуют базовые настройки библиотеки. Нас здесь интересуют следующие три:

  • значением директивы DOMPDF_DEFAULT_PAPER_SIZE содержит значение размера листа бумаги (a4, letter и т.д.);
  • в директиве DOMPDF_DPI в численном выражении указывается качество детализации документа. По умолчанию оно равно 96;
  • значение директивы DOMPDF_TEMP_DIR следует изменить на содержащееся значение в PHP директиве upload_tmp_dir, но только в том случае, если значение, возвращенное PHP функцией sys_get_temp_dir, будет отличным от значения этой PHP директивы. С полным перечнем возможных настроек можно ознакомиться здесь.

Минимальная конфигурация сервера должна быть следующей:

  • PHP Version 5.0;
  • DOMDocument extension;
  • PCRE;
  • Zlib;
  • MBString extension;
  • GD.

Так же рекомендуется установить программу GMagick или IMagick ну и соответственно подключить ее через расширение к PHP. Здесь прослеживается такая вот особенность: что якобы установка одной из рекомендуемых программ позволит лучше работать с прозрачностью изображений в формате PNG. Но на практике выяснилось следующее: если на сервере не установлена ни одна из программ, то прозрачность изображений в формате PNG вообще не обрабатывается. Исключение было для Denwera 3. Для PHP 5.3 входящим в пакет Denwera 3 необходимости в установке ни в одной из двух данных программ не возникло.

Установка шрифтов


Изначально в библиотеке присутствует набор базовых шрифтов, однако среди них нет тех, которые поддерживали бы кириллицу. Поэтому если нужны кириллические шрифты или есть необходимость в расширении имеющегося множества уже готовых шрифтов, то придется заняться их установкой. И в этом нет ничего сложного. Устанавливать можно шрифты как TrueType (*.ttf) так и OpenType (*.otf). Для работы нам понадобится PHP скрипт load_font.php, который располагается в корне каталога библиотеки DOMPDF. Запуск данного скрипта необходимо производить из командной строки и на вход он принимает следующие параметры:

  • font_family – имя шрифта;
  • n_file – файл *.ttf или *.otf;
  • {b|i|bi}_file – файлы соответствующие стилям шрифта (bold, italic, bold-italic).

Примеры:

./load_font.php slkscr /usr/share/fonts/truetype/slkscr.ttf
./load_font.php Arial /mnt/c_drive/WINDOWS/Fonts/arial.ttf

Для демонстрации произведем установку шрифта “roboto”. Предположим что каталог со шрифтом “roboto” и его стилями находится в директории “D:\font”. Соответственно если нам нужен только шрифт со стилем “normal”, то необходимо запустить скрипт со следующими параметрами:

./load_font.php Roboto D:\font\Roboto-Regular.ttf.

Но если требуется установить шрифт “roboto” со всеми четырьмя его стилями (normal, bold, italic и bold-italic), то вызов скрипта уже будет иметь другой вид:

./load_font.php Roboto D:\font\Roboto-Regular.ttf D:\font\Roboto-Bold.ttf 
D:\font\Roboto-Italic.ttf D:\font\Roboto-BoldItalic.ttf.

Обратите внимание на порядок передачи параметров скрипту для одновременного создания шрифта “roboto” с его четырьмя стилями. Первым передается путь к файлу шрифта со стилем “normal”, затем “bold” и т.д. Этот порядок нарушать ни в коем случае нельзя.

Создание шаблона для DOMPDF


Шаблон представляет обычную HTML страницу с добавлением некоторых новых свойств CSS, которые необходимы для работы самой библиотеки. Весь исходный код шаблона я здесь приводить не стану, так как в этом нет необходимости. Тем более что к статье прилагаются все необходимые файлы. Кстати в шаблоны можно встраивать код на PHP и JavaScript. В нашем случае в шаблоне будет располагаться шесть меток: метка для устанавливаемого номера страницы {start-page}, метка с кодом цвета {color} для подвала страницы, метка {page-1} для текста на первой странице, метка {page-2} для текста на второй странице и две метки {label-1} и {label-2} для вывода информационного текста в подвал. Также будет присутствовать встроенный скрипт на PHP для примитивного рисования и вывода нумерации страниц. Данные, которые могут занимать более одной страницы необходимо заключать в блок. Можно конечно этого не делать, но тогда не будет возможности вставлять разрывы страниц, да и текст может не равномерно заполнять страницу. Для вставки разрыва страницы, следует в стилях этого блока прописать одно из следующих свойств:

  • page-break-after: always – вставляет разрыв после страницы;
  • page-break-before: always – вставляет разрыв перед страницей;
  • page-break-inside: auto – здесь имеется ввиду, что DOMPDF сама “примет” решение;
  • page-break-inside: avoid – запрещает разрыв внутри элемента.

Теперь осталось сделать нумерацию страниц. Для этого требуется написать небольшой PHP скрипт и вставить его в шаблон.

if ( isset($pdf) ) {
	/*
	* Открытие объекта для сохранения всех
	* операций рисования
	*/
	$footer = $pdf->open_object();
	// Получаем ширину страницы
	$p_width = $pdf->get_width();
	// Получаем высоту страницы
    $p_height = $pdf->get_height();
	// Устанавливаем номер страницы
	$pdf->set_page_number({start-page});
	$font = Font_Metrics::get_font("Roboto", "normal");
	//$text_w = $pdf->get_text_width($PAGE_NUM, "Roboto", 14);
	/*
	* Сами (для примера) рассчитываем ширину текста (номера страницы)
	* иначе есть видимо неустранимый пока баг
	*/
	$text_w = 8.036;
	// Получаем высоту текста (номера страницы)
	$text_h = $pdf->get_font_height("Roboto", 14);
	$x = $p_width - $text_w - 15;
	$y = $p_height - $text_h / 2 - 20;
	// Выводим текст на страницу
	$pdf->page_text($x, $y, "{PAGE_NUM}", $font, 14, array(255, 255, 255));
	// Рисуем окружность
	$radius = min($text_w * 1.5, $text_h * 1.5);
	$color = array(0, 0.607, 0.901);
	$pdf->circle($x + $text_w / 2, $y + $text_h / 2, $radius, $color);

	// закрытие текущего объекта
	$pdf->close_object();
    /*
	* Добавляет объект на каждую страницу
    * также можно добавлять на четную или нечетную: "even" или "odd")
	*/
    $pdf->add_object($footer, "all");
}

Нумерацию можно сделать и другим способом. Создаете CSS стиль:
footer .page:after { 
    content: counter(page); 
}

А затем в необходимое вам в HTML коде место вставить конструкцию:
<span><?php $PAGE_NUM ?></span>


Но только во втором варианте, нельзя задать номер стартовой страницы. Возможно, нам понадобиться не начинать нумерацию с единицы, а к примеру с двойки и т.д. В таком случае подойдет только использование первого метода.

Примечание: При работе с графикой во встроенных скриптах, значение цвета каждой из RGB составляющих должно находиться в диапазоне 0 — 1. Т.е. каждую составляющую цвета в формате RGB требуется разделить на 255.

Создание модуля для экспорта данных


Осталось самое простое – это сделать экспорт данных. Но есть в библиотеке и еще один небольшой баг. Он заключается в том, что если нам понадобиться сгенерировать несколько файлов, то нужно для каждой итерации создавать экземпляр объекта класса DOMPDF заново, иначе вы получите сообщение об ошибке. Ниже приведен исходный код модуля:
define ( 'ROOT_DIR', dirname ( __FILE__ ) );

define ( 'BASE_PATH', ROOT_DIR . '/template/icon');

define ( 'TEMPLATE', ROOT_DIR . '/template/main.html' );

// Подключаем файл с конфигурацией DOMPDF
require_once (ROOT_DIR . '/dompdf/dompdf_config.inc.php');

// Загрузка шаблона
if (@file_exists(TEMPLATE) )
	$template = file_get_contents(TEMPLATE);

/*
* Номер текущей страницы
* если требуется сгенерировать несколько документов
*/
$current_page = 1;

// Массив с метками и их значениями для шаблона
$signs = array (
	'color' => '#0080C0',
	'page-1' => 'Страница 1',
	'page-2' => 'Страница 2',
	'label-1' => 'site.com',
	'label-2' => 'info@site.com',
	'start-page' => $current_page);

// Замена меток в шаблоне их значениями
foreach ($signs as $key => $value)
	$template = str_replace('{' . $key . '}', $value, $template);

$pdf = new DOMPDF();
// Устанавливаем путь к директории с изображениями и CSS стилями
$pdf->set_base_path(BASE_PATH);
// Загружаем шаблон
$pdf->load_html($template);
// Генерируем PDF файл
$pdf->render();
// Получаем данные в формате PDF
$data = $pdf->output();

/*
* Увеличили счетчик числа сгенерированных страниц
* на число страниц текущего документа
*/
$current_page = $current_page + $pdf->get_canvas()->get_page_count();
// Сохраняем PDF файл
file_put_contents(ROOT_DIR . "/example.pdf", $data);


В результате должен получиться PDF файл следующего образца:


Выводы


Хотя в данной библиотеке и достаточно различных недоработок, но она того стоит. Во-первых, она бесплатная и это уже должно закрыть все претензии к разработчикам. Во-вторых, я думаю, что в ближайшее время если не все, то многие баги будут устранены. Единственная проблема в том, что как я уже писал выше, что данная библиотека используется больше для тривиальных задач, поэтому мало есть описания для решения различных возникающих проблем.
Все используемые файлы: PDF, шаблон и модуль экспорта можно скачать отсюда.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 23

    +6
    А я сейчас использую wkhtmltopdf. Отличная библиотека — генерирует PDF сразу из HTML-страницы, при этом автоматически подгружая шрифты, изображения и прочее. При этом правильно читает весь CSS и @media-квери, умеет делать колонтитулы, разрывы страниц и т.п.

    Кстати, моё CV использует как раз эту библиотеку: cv.andrewdunai.com, так что всегда можно скачать PDF-версию.

    Единственный минус данной библиотеки — каждая буква на выходе получается отдельным блоком (по какой-то причине). В итоге, файл довольно немалый на выходе получается.

    П.с. солидарен с автором в симпатии к шрифту Roboto ;)
      0
      Я читал про нее статью. Успокаивает то, что уже есть возможность на основе структуры сайта формировать PDF.
        0
        С формулами в mathml что-то она до сих пор не дружит.
          0
          Открою маленький секрет. Это не библиотека. Это программа. Внутри довольно старый по нынешним меркам webkit а генерация PDF идет через виртуальный принтер. То есть камни нужно кидать в сторону виртуального принтера и не системных шрифтов.

          Еще есть вариант через PhantomJS это добро делать. Там чуть поновее webkit (точнее новее Qt, а следовательно и webkit)
            0
            Кстати по поводу принтера это очень интересно. Никогда бы сам не подумал.
              0
              Через PhantomJS? А он это может? Точнее даже так, на сколько адекватно он это может?

              Использовал на практике несколько библиотек, во всех имеются те или иные проблемы при попытке использовать html как шаблон для верстки. Вариант использовать для этого безголовый браузер конечно крайне любопытен.
                0
                Да, там в документации даже говорится что и как. Единственное что — стоит попробовать собрать себе 2-ую версию. Ибо в 1.* дико старый webkit.
                  0
                  Пробовал уже… и даже дев версию использовал. У меня так и не взлетело. Ибо да, тоже столкнулся со старостью webkit и багов в нем которых нет в новом.
                  0
                  Ну и да, wkhtmltopdf по сути тот же «безголовый» браузер. Он глупее это да, но внутри принцип работы тот же.
              +1
              Чем вам www.mpdf1.com/mpdf/index.php не подошел?)
                0
                Порекомендовали попробовать эту.
                  0
                  Спасибо за подсказку! Провоевал с dompdf больше часа, так и не победил соблюдение границ документа. У меня вывод большого табличного документа (декларация) и в PDF процентов 10-15 документа не помещалось. С mpdf проблемы не возникло.
                    0
                    И все-же везде свои плюсы и минусы… dompdf поддерживает селекторы first-child, last-child и т.п. (за это у него отвечает stylesheet.cls.php), а mpdf нет.
                      0
                      Не понимает mpdf и селекторов типа tr > td.
                    +1
                    Я долгое время использовал mpdf, но потом открыл для себя wkhtmltopdf — на типовых документах вроде счетов и актов состоящих из 2-5 страниц получил улучшение производительности в 2 раза! Также из mpdf в лог постоянно сыпались различные notice undefined index.
                      0
                      Тоже использую wkhtmltopdf так как рендер точный, а вот с библиотеками на php очень много гемора с версткой чего либо если это не таблица, а про флоаты вообще можно забыть. тестил на mpdf, dompdf и tcpdf никто не смог нормально сделать рендер
                        0
                        Но wkhtmltopdf есть отдельный процесс и консольная тулза.
                          0
                          Ну если у вас не шаред хостинг то особой проблемы не возникает
                            0
                            Да и на шаредах нормальных все хорошо.
                        0
                        C dompdf есть проблема, он иногда уходит в бесконечный цикл. Для продакшена такое неприемлемо
                          0
                          Ага есть такое. При не значительных отклонениях в верстке шаблона, можно «словить» зацикливание.
                          Пока знаю, что на двух крупных проектах используется для отчетов DOMPDF.
                          0
                          Пара комментариев.
                          GD, на сколько я понимаю, сама по себе с прозрачностью работать умеет, но в софте, который ее использует, вечно проблемы с тем, чтобы заставить ее делать это правильно. Решения на основе ImageMagick в общем случае надежнее. Так что не сюрприз.

                          Что касается wkhtmltopdf из комментариев — если выводить через нее счета и таблицы, проблемы нет. Если начинается что-то более сложное, можно над затачиванием простейшего макета под вывод через нее сидеть два дня.
                            0
                            Данная библиотека использовалась для генерации простых документов.
                            К сожалению для генерации простых html-документов wkhtml2df показал себя гораздо производительнее. Правда это была сторонняя утилита — по описанию это именно тот бинарник который в вебките отвечает за отрисовку pdf-ок. По итогам — dompdf требовалось до 60Мб памяти на генерацию и 1-2с, вебкитовскому бинернику — 0,1Мб памяти и 100-200мс на те же фреймы.
                            Была также задача показывать html и pdf версию одно и того же документа, очевидно это средство в какой-то мере реализует данные возможности. В итоге поимел много головной боли не только с шаблонизатором а-ля Smarty, но и те что все эти чудесные page-break ломали все html/print-стили.
                            Тем не менее библиотека лучше чем ничего.
                            Затем возникла потребность генерировать интеллектуальные документы со сложной разметкой и был использован MPDF.
                            MPDF является надстройкой над KPDF(совсем базовый разметчик а-ля нарисовать линию А-Б), на простые документы MPDF кушал 250-300МБ памяти, и был написан настолько монолитно что для того чтобы нарисовать что-то стоящее(например ТОРГ-12) потребовалось переопределять целые блоки кода по 100-120 строчек. Спасением стал ASP.NET Crystall Reports.

                            Only users with full accounts can post comments. Log in, please.