Генерация pdf-документов является повседневной задачей в веб-разработке. В перечень таких документов входят счета, накладные, полисы и прочие. Существует множество готовых библиотек для решения этой задачи, в том числе и для php. Например, mpdf,tcpdf, и многие другие. Файл можно собрать с помощью api этих библиотек, но это довольно долгое занятие. А времени на реализацию задачи много не бывает, не так ли? Поэтому чаще всего pdf-файл создается из html-представления, что довольно удобно. Но, к сожалению, не все так просто. У такого подхода есть множество подводных камней, способных вывести из себя кого угодно.
Например:
- Стили нельзя подключить отдельно, следовательно они должны быть включены в html-документ отдельным блоком, либо инлайново для каждого элемента. В этом нет ничего страшного, небольшое неудобство.
- К сожалению, в таких библиотеках некоторые стили могут работать не так как этого от них ожидаешь, либо не работать в принципе. Это самый главный недостаток.
- Из предыдущего пункта следует, что создать документ максимально соответствующий требованиям очень трудоемко, а порой просто невозможно.
- А в случае разработки под Битрикс есть еще одна проблемка. Все знают, что для работы платформы требуется в php.ini установить параметр mbstring.func_overload в значение 2. А для создания pdf-файла, содержащего кириллицу потребуется значение 0. Обычно эта проблема решается с помощью настройки веб-сервера, но все равно неприятно.
Сталкиваясь в очередной раз с такой задачей, я в все чаще задумываюсь о том, что генерацией pdf-файлов должен заниматься отдельный микросервис, особенно если проект большой, но это уже совсем другая история.
Пришло время перейти к главной части этой статьи. Помимо вариантов предложенных выше, существует альтернативный - PhantomJS.
PhantomJS — это сборка движка WebKit без графического интерфейса, позволяющая в режиме консоли загружать веб-страницу, выполнять JavaScript, полноценно работать с DOM, Canvas и SVG.
Конечно, помимо перечисленных выше возможностей, он дает возможность создавать pdf-файлы.
Каким образом? Говоря простым языком, он загружает требуемую веб-страницу и дает возможность сохранить результат как pdf-файл.
Процесс установки PhantomJS достаточно подробно описан в документации, поэтому я не буду останавливаться на этом вопросе.
Важным моментом в работе PhantomJS является js-файл (далее config.js), который своим содержанием определяет то, что именно мы хотим сделать. На официальном сайте есть множество готовых примеров таких файлов, с помощью которых решаются самые разные задачи, например, unit-тестирование. В числе примеров есть сохранение веб-страницы в формате pdf. Это именно то, что нам нужно.
Пара небольших правок позволит использовать этот пример в наших целях.
var page = require('webpage').create(),
system = require('system'),
address, output, size;
//если аргументов мало или слишком много, выводится сообщение с помощью по использованию
if (system.args.length < 3 || system.args.length > 5) {
console.log('Usage: config.js URL filename [paperwidth*paperheight|paperformat] [zoom]');
console.log(' paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
console.log(' image (png/jpg output) examples: "1920px" entire page, window width 1920px');
console.log(' "800px*600px" window, clipped to 800x600');
phantom.exit(1);
} else {
// обработка аргументов
address = system.args[1];
output = system.args[2];
page.viewportSize = { width: 800, height: 800 };
if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") {
size = system.args[3].split('*');
page.paperSize = (size.length === 2)
? { width: size[0], height: size[1], margin: '0px' }
: { format: system.args[3], orientation: 'portrait', margin: '1cm' };
} else {
console.log('Invalid path to pdf!');
phantom.exit(1);
}
if (system.args.length > 4) {
page.zoomFactor = system.args[4];
}
// открытие страницы и сохранение результата
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit(1);
} else {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
}
Далее, запустив из консоли следующую команду (делаю допущение, что PhantomJS доступен глобально):
$ phantomjs path/to/config.js "url" path/to/pdf/file "A4"
Разберем аргументы по порядку:
- Путь до config.js.
- Адрес веб-страницы, которую надо преобразовать в pfd-файл.
- Путь до pdf-файла, в который произойдет сохранение результата.
- Формат pdf-файла.
Получаем искомый pdf-файл.
Возвращаясь к php, это решение довольно просто интегрировать в код.
В простейшем случае это выглядит вот так:
$command = sprintf(
"phantomjs %s %s %s %s",
$fullPathToConfigJS,
$url,
$fullPathToSave,
$format
);
exec($command);
Итак, подведу итоги. На мой взгляд, такое решение имеет следующие достоинства:
- Сверстать макет для такого документа намного проще. Это очень важно, т.к. не только упрощает разработку, но и не превращает в кошмар последующие правки документа.
- Избавляет от нужды извращаться с mbstring.func_overload. Но это, несомненно, проблема характерная в основном для Битрикс.
Конечно, есть и недостатки:
- Не 100% поддержка всех css-стилей. Но в сравнении с библиотеками, перечисленными в начале стати, все очень хорошо.
- Такое решение может отпугнуть начинающего разработчика.