• FPDF
• MPDF — основанная на FPDF библиотека, позволяющая генерировать pdf-файл из любого html-кода
• DOMPDF
• TCPDF
и многие разные другие библиотеки. Наиболее мощной и подходящей, к тому же, изначально корректно работающей с кириллицей, оказалась библиотека MPDF, если бы не критичный в нашем случае недостаток: большие таблицы и вообще большие файлы крайне медленно генерировались. Более того, часто генерация не происходила совсем, а скрипт останавливался с ошибкой 504.
Дальнейший поиск помог найти программу wkhtmltopdf. Сайт программы: http://wkhtmltopdf.org.
В отличие от php-библиотек, это серверная программа, распространяемая в том числе в виде пакетов и исполняемых файлов для linux, windows и других операционных систем. Программа принимает html-код (в виде веб-адреса, пути к файлу либо строки кода) и генерирует на его основе pdf-файл на сервере.
Предварительный опыт показал, что на локальном сервере XAMPP под Windows огромная html-таблица на 300-500 страниц преобразуется в pdf-файл за 1-2 секунды!
Установка wkhtmltopdf на CentOs 6
Для работы программе необходим webkit и qt.
Итак, установим требуемое окружение и программу на сервер. На нашем сервере установлена CentOs 6. Зайдем на сервер с правами root и выполним следующие команды.
Получим rpm-пакет программы wkhtmltopdf по ссылке с сайта разработчика и установим ее на рабочем сервере:
wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2.1/wkhtmltox-0.12.2.1_linux-centos6-i386.rpm
yum --nogpgcheck localinstall wkhtmltox-0.12.2.1_linux-centos6-i386.rpm
Все зависимости пакетов должны автоматически провериться и удовлетвориться. Если окружение по каким-то причинам не установилось, используйте команды:
yum install urw-fonts libXext openssl-devel libXrender
yum install xorg-x11-fonts-cyrillic.noarch xorg-x11-fonts-misc.noarch xorg-x11-fonts-truetype.noarch xorg-x11-fonts-100dpi.noarch xorg-x11-fonts-75dpi.noarch fonts-ISO8859-2.noarch fonts-ISO8859-2-100dpi.noarch fonts-ISO8859-2-75dpi.noarch freefont.noarch
До недавнего времени программа не предоставлялась в виде rpm-пакета, и приходилось копировать бинарный файл и вручную устанавливать все необходимые пакеты.
Использование wkhtmltopdf на CentOs 6
Общий формат запуска программы такой:
wkhtmltopdf <путь, имя исходного файла.html> <путь, имя выходного файла.pdf>
Кроме того, программа позволяет автоматически встраивать шапку и подвал документа из отдельных html-файлов. Для этого синтаксис такой:
wkhtmltopdf --header-html <путь,имя шапки.html> --footer-html <путь,имя подвала.html> <путь,имя исходного файла.html> <путь, имя выходного файла.pdf>
Также среди опций запуска программы — настраиваемый размер полей получаемого pdf-файла. В верхнее и нижнее поле программа подставляет шапку и подвал:
wkhtmltopdf --margin-top 35mm --margin-bottom 27mm --margin-left 10mm --margin-right 10mm --header-html <путь,имя шапки.html> --footer-html <путь,имя подвала.html> <путь,имя исходного файла.html> <путь, имя выходного файла.pdf>
В этом примере:
• верхнее поле: 35 мм
• нижнее поле: 27 мм
• левое, правое поля: по 10мм
Приведу также пример кода подвала. В нашем случае автоматически формируются и подставляются в подвал номера страниц. Таким образом, в нашем документе автоматически пронумеруются страницы:
<html><head><script>
function subst() {
var vars={};
var x=document.location.search.substring(1).split('&');
for (var i in x) {var z=x[i].split('=',2);vars[z[0]] = unescape(z[1]);}
var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];
for (var i in x) {
var y = document.getElementsByClassName(x[i]);
for (var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];
}
}
</script></head><body style="border:0; margin: 0;" onLoad="subst()">
<div align="right" style="font-family:'Times New Roman', Times, serif; font-size: 14px;">
/<span class="page"></span>/
</div>
</body></html>
Также среди полезных опций запуска программы:
— encoding – указание кодировки исходного html-файла, например:
--encoding windows-1251
— page-size – указание формата страницы, например:
--page-size A4
— orientation – ориентация страницы, например:
--orientation Landscape
В нашем веб-проекте на php-странице, формирующей pdf-файл, используется такой php-код:
$tmp=time();
$f=fopen(ABSPATH.'/tmp/'.$tmp.'.html','w');
fputs($f, $llg);
fclose($f);
$cd = "cd ".ABSPATH.'/tmp';
exec($cd);
$command = "wkhtmltopdf-i386 --margin-top 35mm --margin-bottom 27mm --margin-left 10mm --margin-right 10mm --footer-html ".ABSPATH."/tpl-sm/pl_pdf/pdf_footer.html --header-html ".ABSPATH."/tpl-sm/pl_pdf/pdf_header.html ".ABSPATH.'/tmp/'.$tmp.'.html'." ".ABSPATH.'/tmp/'."$tmp.pdf";
exec($command);
if (file_exists(ABSPATH.'/tmp/'.$tmp.'.pdf')) {
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="pricelist.pdf"');
readfile(ABSPATH.'/tmp/'.$tmp.'.pdf');
}
unlink(ABSPATH.'/tmp/'.$tmp.'.pdf');
unlink(ABSPATH.'/tmp/'.$tmp.'.html');
В этом коде:
• переменная $llg — содержит html-код прайс-листа
• константа ABSPATH — абсолютный путь к папке веб-проекта на сервере.
Код делает следующее:
• Записывает во временный файл html-код прайс-листа;
• Переходит во временный каталог;
• Запускает wkhtmltopdf с требуемыми опциями;
• Если pdf-файл был успешно создан — то возвращает его пользователю в браузер, предлагая скачать файл под именем pricelist.pdf;
• Удаляет временные html- и pdf-файлы из временного каталога.
Комментарии (24)
Obramko
10.09.2015 08:57изначально корректно работающей с кириллицей
Не думал, что в мире UTF-8 для кого-то это до сих пор проблема.Diden05
10.09.2015 11:21+2Вы удивитесь, но проблем с кодировками по прежнему много, тяжкое наследие так сказать.
gydex
15.09.2015 18:34При работе с FPDF и подобными библиотеками приходилось конвертировать русские ttf-шрифты в удобный ей формат, чтобы кириллица заработала в принципе. В MPDF это уже было сделано изначально.
eugenyh
10.09.2015 11:22+2Программа принимает html-код (в виде веб-адреса, пути к файлу либо строки кода) и генерирует на его основе php-файл на сервере.
может PDF?
schors
10.09.2015 12:081. Эмуляцией браузера по прайслисту… А пробовали не на пустой машине, а на VDS запустить? Вы же понимаете, что практически браузер запускаете?
2. Держать прайс на несколько тысяч позиций в переменной — это план победы. Когда он станет под миллион позиций — что делать будете?
3. Имея прайс текстом в одной переменной были проблемы с переводом его в UTF-8? Т.е. функция iconv на переменную не?
4. Вы вот так прямо «запрос-ответ» генерируете нечто большое? А если связь отвалится — будете перегенерировать? Кормите девопсов, которые потом будут предлагать вам облака для решения несуществующей проблемы? :) Не, я тоже генерю иногда на лету, но на 5-6 страничек, которые мне не жалко перегенерить.
5. Кстати, а как прайс становится HTML? Т.е. Вы ещё готовите специальный HTML, который можно превратить в читаемый прайс, коий HTML явно надо делать отдельно от того, что людям показывается. Ну т.е. в каких-то частных случаях этим действительно можно пренебречь, но… Изучаете конвертилку из HTML в PDF, включая шаблоны и вот всякие ключи… Чую бесовщину, обосновать не могу.
6. А LaTeX не? Пакет texmf сейчас вполне развит, даже TTF туда впихивается за милу душу. Генерация текста/таблиц может быть автоматизирована по самое небалуйся. Причем, если без изысков, то и изучать ничего изыскового не надо. Там сразу Вам и разбиение таблиц по страницам, и игра с текстом, и нумерация без явапрограмминга, и в общем чёрт в ступе. Два дня покурить над самим LaTeX, и потом всё делается быстрее, чем HTML-шаблоны.gydex
15.09.2015 18:40Мы решали задачи конвертации прайса с тысячами позиций. Миллиона там не предполагалось. Будет миллион — будем генерировать в фоне, задействуем LaTeX. На VDS кстати еще быстрее, чем на пустой машине, работает.
Есть два варианта печатной формы прайс-листа: HTML и PDF. Обе показываем людям, кому в чем удобнее, в том и смотрят. PDF-версия фактически формируется из шаблона для HTML-версии. Соответственно, быстрее тиражируются изменения шаблона, если они требуются.
PQR
10.09.2015 12:37+1Я длительное время использовал MPDF, но он медленный, поэтому изучал альтернативы, тестировал разные варианты. Ещё один интересный сопособ: рендрить с помощью Node и PhantomJS: www.npmjs.com/package/phantomjs-pdf
Но я в итоге остановился на wkhtmltopdf. Расскажу свой опыт.
Во-первых, есть готовая обёртка на PHP: github.com/mikehaertl/phpwkhtmltopdf (сам ей не пользуюсь, у меня простая задача, все аргументы зафиксированы, нет смысла в этой обёртке)
Во-вторых, генерировать временный html файл на диске не обязательно, wkhtmltopdf умеет принимать исходный html через stdin, если указать параметр "-" (прочерк). На PHP получается как-то так:
$descriptorspec = [ 0 => ['pipe', 'r']; //stdin 1 => ['pipe', 'w']; //stdout 2 => ['pipe', 'w']; //stderr ]; //Обратите внимание на аргументы, которые я передаю при запуске wkthmltopdf //--disable-smart-shrinking без этого аргумента, всё становится каких-то не правильных пропорций //--dpi 96 если принудительно не поставить dpi, то размеры указанные в css в милиметрах на печати будут совсем не такими! //- последний аргумент это прочерк, чтобы передать html через stdin //bypass_shell (только для Windows): при установке в TRUE процесс будет запущен в обход оболочки cmd.exe //это нужно для обхода бага https://bugs.php.net/bug.php?id=60181 "proc_open fails to read quoted whitespaced directories in Windows" $process = proc_open("/path/to/wkthmltopdf --disable-smart-shrinking --margin-left 20mm --dpi 96 -", $descriptorspec, $pipes, null, null, ['bypass_shell' => true]); if (is_resource($process)) { //Пишем в stdin html контейнт fwrite($pipes[0], $html); fclose($pipes[0]); //Читаем результаты и ошибки, если они вам нужны в приложении $stdOut = stream_get_contents($pipes[1]); $stdErr = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]); $exitCode= proc_close($process); }
Более подробно про разные параметры принимаемые wkthmltopdf рекомендую почитать здесь: wkhtmltopdf.org/usage/wkhtmltopdf.txt
Да, если нужен header-html и footer-html, то пара временных html файлов на диске всё-таки понадобится.
В третьих, на Linux машинах есть неприятный баг, что шрифты выглядят меньше, чем при печати в PDF этого же html файла с помощью браузера: github.com/wkhtmltopdf/wkhtmltopdf/issues/2171
По этому багу, в том числе, есть много обсуждений на StackOverflow, но фикса в ближайшее время не предвидится, предлагается workaround: github.com/wkhtmltopdf/wkhtmltopdf/issues/2171#issuecomment-139164039
Но я использую другой подход: есть отдельный windows сервер, на котором запускается wkthmltopdf, а обращение идёт к нему через простенький самописный веб-сервис: принимает html контент в POST и возвращает готовый PDF — так и живём.
Вообще по wkhtmltopdf активность на github почти нулевая, баги не фиксятся, но ничего лучше пока не нашел. Кто знает C++ — присоединяйтесь к проекту, внесите свой вклад в opensource!
Есть ещё www.princexml.com — но он платный и жутко дорогой!Fesor
10.09.2015 13:41Главная проблема wkhtmltopdf — старый webkit, большая часть багов связана именно с этим. ветка 0.12 основана на 4-ом QT и соответственно довольно древней версии webkit. Ветка 0.13 же основана на Qt 5.4 и, если удалось собрать и оно не крэшнулось, то там все получше.
Веселье в ном что альтернативы нет, можно пробовать запустить хром скажем и распечатать страницу через селениум какой, но это извращение то еще. Штуки же типа phantomjs (сейчас вышла вторая версия, использующая Qt 5.4) используют для генерации скриншетов и рендринга PDF код wkhtmltopdf, так что всеравно придется ждать.
принимает html контент в POST и возвращает готовый PDF — так и живём.
Вариант с windows сервер худо-бедно живой, если у вас он есть. Вот только кастыли с RPC можно было бы заменить на какой-нибудь MQ, например RabbitMQ
schors
10.09.2015 14:55А смысл в этих телодвижениях, если есть вполне живой LaTeX? Там тоже есть какие-то баги и всё такое, но вот туда бы как раз влить усилия.
Alexufo
10.09.2015 17:26смысл в html с css3 с поддержкой svg — так генерируем бейджи для печати со штрихкодами.
schors
10.09.2015 17:31А зачем? Он не предназначен для этого ) Сколько я не пытался шаблонизировать для PDF, какими только библиотеками — один фиг изучаешь особенности конкретного рендера. А смысл? Для того же LaTeX есть просто шаблон для штрихкодов. И делается это проще. И хорошо когда одна страница и не надо программировать в HTML многостраничность таблицы например. И опять же — HTML это немного не типографская штука…
Alexufo
10.09.2015 17:42Что то не понял) Вы про вынесение временных файлов на диск перед сборкой? В нашем случае мы выносим. Проблем с бейджами, делениями на листы для двухсторонней печати (бейдж на другой скан паспорта) на принтере нет. php ->html->wkhtmltopdf->pdf все гладко и относительно удобно. Штрихи генерятся на php в svg. Уж зачем Latex нужен вот не пойму :-) Помоему это шаг назад.
Удобнее wkhtmltopdf ничего и нет, увы.schors
10.09.2015 17:55Зачем как шаблон использовать HTML? В чём смысл? Опять же. Если это одноразовая задача — не вопрос, смысл ясен. Но если нет, то зачем? LaTeX шаг назад? :) Тогда UTF-8 тоже. Его придумали два стрика для своего недоюникса в самом начале 90-ых, который так и не выстрелил. И /procfs кстати тоже. Про HTML, который ножницами вырезали из IBM SGML 70-ых я и вообще молчу :) И про PDF, который есть расширенная версия PS, который где-то в районе 80-ых застрял, ктсати тоже :) LaTeX вполне жив в различных видах, развивается и до сих пор вполне используется для различной верстки. Имеет кучу всяких шаблонов и расширений, вполне вменяемых. Изначально служил и служит для автоматизации создания книг, брошюр, листовок и всё такое. Прямо вот для него работа. Там сложно начать, но при этом он прост. Для половины задач есть готовые решения. Усложнения задач проходят мягко и хорошо. Рендеринг однозначный, в отличии от. Не требует подпорок для большинства задач. Иногда надо поднапрячься, чтобы придумать как верно на нем что-то сделать, но это делается, как я уже говорил — без подпорок.
Alexufo
10.09.2015 18:03+2потому html везде в вебраработке и бейджики на нем сделать не проблема. Latex шаг назад в вебразработке в скорости изменения макета. Конечно, на LaTeX много кто верстает без проблем, но для бейджиков прикручивать его ну уж совсем лищнее коли на wkhtmltopdf все устраивает. Тут все просто — если все устраивает то оно отлично: -)
Если задачи будут куда сложнее, то LaTeX можно рассматривать.schors
11.09.2015 10:27Ну я ранее вроде сказал, что если только бэджик, или там бэджик, счет и акт — то фиг с ним. Но попробуйте просто для себя (из любопытства) заморочиться — я Вас уверяю, там изменение макета будет намного быстрее, чем типогравская подгонка XML-подобного языка :) Не обязательно кстати даже ставить ПО — есть какое-то количество онлайн-сервисов редактирования. Там принцип в два окна — в левом пишешь, в правом показывается итоговый pdf. Понравится — поставите ПО уже себе для автоматизации.
schors
11.09.2015 10:28Однако, у топикастера немного другая проблема и он там бегает вокруг листа с подпорками и свистелками. И вот ему бы как раз и заморочиться.
gydex
15.09.2015 18:45Пример интересный.
Из всего, что пробовали, wkhtmltopdf самая быстрая и корректная утилита. А header-html и footer-html нужны, там элементы фирменного бланка с графикой.
sHinE
10.09.2015 21:36Пару лет уже не генерировал длинные таблицы через wkhtmltopdf, но раньше был баг с разрезанием ячеек и текста в них, если таблица занимала несколько страниц. Насколько я помню, css для переноса на другой лист wkhtmltopdf не понимал.
Наверно это. как уже выше сказали, из-за того, что движок внутри старый используется.Londeren
11.09.2015 10:51Может кто-то знает исправлено это сейчас или нет? Столкнулся с такой же проблемой в dompdf (таблицы с colspan rospan), все воркараунды некрасивые
gydex
15.09.2015 18:26Пока не исправлено, ячейки режутся неровно. Где это критично и размер ячейки предсказуем, вынуждены предварительно разбивать таблицу на несколько таблиц, по числу страниц. Где некритично — оставляем как есть.
DmitryKoterov
11.09.2015 02:46+1exec($cd); — это пять. Можете смело удалять данную строку, она ничего не делает.
Blumfontein
По-моему, wkhtmltopdf можно из композера устанавливать. И для использования тоже врапперы есть, консоль вызывать неудобно.
hudson
Это враппер к утилите wkhtmlto*. Сам инструмент — это консольные приложения.