Программный рендеринг PDF-файлов из HTML с использованием Chrome и Puppeteer

Я давно работаю веб-разработчиком, и мне постоянно приходит в голову идея превратить HTML в PDF. Это довольно естественная просьба; HTML — это хороший формат, ориентированный на отображение, и он имеет отличные возможности макета и стиля. Кроме того, для веб-приложения у меня, вероятно, уже есть код, написанный для создания именно того контента, который мне нужен. Мне просто нужно превратить этот контент в переносимый PDF-файл только для чтения для загрузки или прикрепления к электронному письму, не заставляя пользователя печатать в PDF-файл вручную.

Для этого преобразования я использовал множество технологий, в том числе ABCpdf.net WebSuperGoo и бесплатную библиотеку Rotativa. (У нас даже есть статья о том, как его использовать.) Последний все еще используется в некоторых моих проектах, но он — и его более новая версия .Net Core — основан на wkhtmltopdf.exe, который является проблема. Подробнее об этом позже.

Большинство этих инструментов работают с использованием браузера для отображения HTML и создания документа PDF. Движок браузера уже знает, как отображать HTML (и использовать CSS и JavaScript), так зачем изобретать велосипед?

Проблема с wkhtmltopdf — и любым инструментом, который его использует — заключается в том, что он основан на механизме рендеринга WebKit, используемом Safari. Если вы еще не слышали, WebKit — это новый Internet Explorer с точки зрения сдерживания принятия стандартов и предотвращения хороших вещей. Хорошо, да, это напыщенная гипербола, но я, честно говоря, столкнулся с большим количеством проблем со стилем и макетом, в которых wkhtmltopdf просто не будет отображаться, как все другие браузеры. Возможно, это функция wkthtmltopdf, а не самого WebKit, но все же моя проблема остается в поисках решения.

Последнее решение, которое я нашел, — Кукловод. Это пакет Node.Js, который манипулирует безголовой (то есть без окна браузера) версией Chrome. Chrome часто обновляется, поддерживает хорошие стандарты и хорошо отображается. Вывод PDF из Puppeteer почти точно совпадает с выводом, который вы получите, используя Chrome для печати в PDF вручную.

Puppeteer поддерживает отличные параметры, такие как верхние и нижние колонтитулы (с содержимым шаблона для «Страницы N из X»), управление полями печати, печать фоновых изображений, различные размеры страниц и многое другое. Поскольку вы выполняете рендеринг в реальном браузере, вы даже можете использовать JavaScript для создания динамического контента по мере необходимости.

Детали реализации

Шаг первый — сгенерировать HTML. Есть два способа сделать это; вы можете указать Puppeteer на URL-адрес или на локальный (временный) файл с HTML. Первое может быть удобным, но только в том случае, если ваше веб-приложение разрешает анонимный доступ к URL-адресу, который вы хотите распечатать. Обычно это не так, поэтому я часто использую локальный файловый маршрут. Единственная загвоздка заключается в том, чтобы убедиться, что любые ресурсы, такие как файлы CSS или изображения, на которые вы ссылаетесь, общедоступны, и что ваш HTML содержит полные URL-адреса для них (или элемент ‹base/› в заголовке, чтобы указать базовый URL-адрес для относительных запросов). ).

Второй шаг — вызвать Puppeteer и сообщить ему, что печатать, какие параметры и куда поместить выходной PDF-файл.

Третий шаг — просто дождаться, пока Puppeteer закончит рендеринг, получить байты файла и отправить их в конвейер ответов. Очистите временные файлы и вуаля!

Детали Gory ASP.Net

Второй шаг выше оставил много недосказанным! Как мне на самом деле вызвать пакет Node.Js из моего веб-приложения Asp.Net? Вот шаги, которые я предпринял, они примерно одинаковы для Asp.Net или Asp.Net Core (хотя в последнем немного проще).

  1. Установите Node.JS на веб-сервер.
    Сейчас вы будете запускать там «приложение» узла, так что оно вам понадобится. Он также понадобится вам на вашем компьютере для разработки, но, возможно, он у вас уже есть, поскольку сейчас он является частью многих цепочек инструментов сборки.
  2. Создайте папку в своем проекте и на сервере, где будут размещаться новые сценарии узлов.
    Эта папка должна находиться за пределами веб-приложения; он не должен обслуживаться вашим веб-сервером, поэтому он не должен быть доступен в Интернете.
  3. Используйте npm для установки Puppeteer и любых других пакетов, которые вам нужны в этой папке (отдельно от любых пакетов npm, которые вы используете где-либо еще в своем проекте).
    Я делаю это на своей машине разработки. Затем я копирую package.json (и package-lock.json) в папку сервера и «npm install» туда. Я не развертываю папку node_modules, хотя, думаю, вы могли бы это сделать вместо развертывания package.json. Имейте в виду, что модули, необходимые для Chrome, имеют размер 200+ МБ, поэтому вам не нужны они в исходном репозитории!
  4. Напишите простой файл JavaScript, который экспортирует функцию, запускающую Puppeteer, с любыми входными данными, которые вы хотите.
    В проекте Puppeteer есть несколько примеров, которые могут служить отправной точкой для этого кода.
  5. Теперь самое сложное — вызвать этот скрипт из ASP.Net!
    Я использовал AspNetCore.NodeServices для управления исполняемым экземпляром Node.js. Несмотря на название, старые версии этой библиотеки прекрасно работают под Asp.Net (неосновной), хотя и имеют смущающее количество зависимостей. (Раздражает, но в моем случае оно того стоит.) Если вы используете это из Asp.Net Core, это довольно просто. На старой платформе Asp.net вам нужно будет пройти через несколько небольших обручей, чтобы настроить внедрение зависимостей, но они не слишком сложны (кроме вашей кривой обучения, если вы не знакомы) и не должны влиять на остальная часть вашей кодовой базы.
  6. В этой библиотеке есть хороший простой метод для вызова вашего скрипта и ожидания его завершения.
    Один нюанс заключается в том, что ваша функция скрипта должна принимать функцию «обратного вызова» в качестве первого аргумента. Функция должна вызвать это либо с ошибкой, либо с успешным результатом:

//AddNumbers.js

module.exports = async (обратный вызов, a, b) => {

var rv = (parseInt(a,10) + parseInt(b,10)).toString();

callback(null /*ошибка*/, rv /*успех*/);

};

Краткое содержание

Я считаю необходимость установки node.js на сервер и концепцию развертывания и запуска отдельного «приложения» node.js на сервере, отдельно от моего веб-приложения, странной архитектурой.

Однако это не намного хуже, чем решения wkhtmltopdf, поскольку им также требуется большое отдельное приложение для развертывания на сервере. Эти решения просто лучше скрывают сложность с помощью библиотек-оболочек.

Даже если это будет сложнее, результаты этого решения Node.js/Puppeteer/Chrome того стоят! Я могу использовать современные макеты и CSS (например, flexbox) для создания PDF-файлов, которые выглядят так же хорошо, как браузер. И скорость генерации PDF высокая! Поскольку Chrome часто обновляется, я ожидаю, что это будет решение, которое я смогу поддерживать долгие годы.

Эта техническая/деловая статья предназначена для разработчиков, менеджеров по найму/проектов и других технических специалистов, стремящихся улучшить свои навыки. Зарегистрируйтесь, чтобы получать наши статьи на свой электронный ящик.

Если вы ищете работу в технологической отрасли, посетите нашу доску вакансий, чтобы узнать, подходите ли вы для некоторых из наших вакансий. Если вы ищете технических специалистов для своей компании, свяжитесь с нами.

Stout Systems – это консалтинговая и кадровая компания, работающая на основе самой мощной доступной технологии: человеческого интеллекта®. Мы были основаны в 1993 году и базируемся в Анн-Арборе, штат Мичиган. У нас есть клиенты в США в таких областях, как инженерия, наука, производство, образование, маркетинг, развлечения, малый бизнес и робототехника. Мы предоставляем экспертные услуги по разработке программного обеспечения, веб-сайтов и встроенных систем, а также услуги по подбору персонала, а также подбор и трудоустройство технических специалистов по прямому найму.