4 Схемы и методы реализации преобразования HTML в PDF

Node.js

Перевод: сумасшедший технический ботаник оригинал:blog.rising stack.com/PDF-from-contract…

В этой статье я покажу, как создавать PDF-документы из страниц React со сложным стилем, используя Node.js, Puppeteer, безголовый Chrome и Docker.

Предыстория: несколько месяцев назад клиент попросил нас разработать функцию, позволяющую пользователям получать содержимое страницы React в формате PDF. Страница в основном представляет собой отчет и визуализацию данных о пациенте с множеством SVG. Есть также некоторые специальные запросы на изменение макета и некоторую перестановку элементов HTML. Таким образом, в PDF должны быть другие стили и дополнительный контент по сравнению с исходной страницей React.

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

содержание:

  • Генерируется на стороне клиента или сервера?
  • Сценарий 1: Скриншот DOM
  • Решение 2. Используйте только библиотеку PDF
  • Окончательное решение 3: Node.js, Puppeteer и Chrome без головы
    • контроль стиля
    • Отправьте файл клиенту и сохраните его
  • Использование Puppeteer с Docker
  • Сценарий 3 +1: правила печати CSS
  • Суммировать

Генерируется на стороне клиента или сервера?

PDF-файлы могут быть созданы как на стороне клиента, так и на стороне сервера. Но может быть более разумным позволить бэкенду справиться с этим, поскольку вы не хотите исчерпать все ресурсы, которые может предоставить браузер пользователя.

Тем не менее, я покажу решения для обоих подходов.

Сценарий 1. Сделайте снимок экрана из DOM.

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

Метод прост и понятен: создайте скриншот со страницы и поместите его в PDF-файл. Очень прямо вперед. Мы можем сделать это, используя два пакета:

  • Html2canvas, генерировать скриншоты на основе DOM
  • jsPdf, библиотека для создания PDF-файлов

Начать кодирование:

npm install html2canvas jspdf

import html2canvas from 'html2canvas'
import jsPdf from 'jspdf'
 
function printPDF () {
    const domElement = document.getElementById('your-id')
    html2canvas(domElement, { onclone: (document) => {
      document.getElementById('print-button').style.visibility = 'hidden'
}})
    .then((canvas) => {
        const img = canvas.toDataURL('image/png')
        const pdf = new jsPdf()
        pdf.addImage(imgData, 'JPEG', 0, 0, width, height)
        pdf.save('your-filename.pdf')
})

Это оно!

осторожностьhtml2canvasизoncloneметод. Это очень удобно, когда вам нужно манипулировать DOM перед тем, как сделать снимок экрана (например, скрыть кнопку печати). Я видел много проектов, использующих этот пакет. Но, к сожалению, это не то, что нам нужно, так как нам нужно сделать создание PDF на бэкенде.

Вариант 2. Используйте только библиотеку PDF

В NPM есть несколько библиотек, такие как JSPDF (как описано выше) илиPDFKit. Проблема с ними в том, что если я захочу использовать эти библиотеки, мне придется реструктурировать страницу. Это определенно ухудшает ремонтопригодность, поскольку мне нужно применить все последующие изменения к шаблону PDF и странице React.

Пожалуйста, смотрите код ниже. Вам необходимо вручную создать документ PDF самостоятельно. Вам нужно просмотреть DOM, найти каждый элемент и преобразовать его в PDF, что является утомительной работой. Должен быть более простой способ.

doc = new PDFDocument
doc.pipe fs.createWriteStream('output.pdf')
doc.font('fonts/PalatinoBold.ttf')
   .fontSize(25)
   .text('Some text with an embedded font!', 100, 100)
 
doc.image('path/to/image.png', {
   fit: [250, 300],
   align: 'center',
   valign: 'center'
});
 
doc.addPage()
   .fontSize(25)
   .text('Here is some vector graphics...', 100, 100)
 
doc.end()

Этот фрагмент кода взят из документации PDFKit. Но это все равно полезно, если вашей целью является непосредственное создание PDF-файла, а не преобразование существующей (и изменяемой) HTML-страницы.

Окончательное решение 3: Puppeteer на основе Node.js и Headless Chrome

чтоPuppeteer? В его документации говорится:

Puppeteer Node — это библиотека, предоставляющая высокоуровневый API для управления Chrome или Chromium по соглашению DevTools. Режим по умолчанию в безголовом Puppeteer Chrome или Chromium, но его также можно настроить для работы в полном (не безголовом) режиме.

По сути, это браузер, который можно запустить из Node.js. Если вы читали его документацию, первое упоминание в ней — это то, что вы можете использовать Puppeteer дляСоздание скриншотов и PDF-файлов страниц. превосходно! Это именно то, что мы хотим.

Сначала сnpmi i puppeteerУстановите Puppeteer и реализуйте наши возможности.

const puppeteer = require('puppeteer')
 
async function printPDF() {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto('https://blog.risingstack.com', {waitUntil: 'networkidle0'});
  const pdf = await page.pdf({ format: 'A4' });
 
  await browser.close();
  return pdf
})

Это простая функция, которая переходит по URL-адресу и создает PDF-файл сайта.

Сначала мы запускаем браузер (генерация PDF поддерживается только в автономном режиме), затем открываем новую страницу, устанавливаем область просмотра и переходим по указанному URL-адресу.

настраиватьwaitUntil:'networkidle0'Опция означает, что Puppeteer будет считать навигацию завершенной при отсутствии сетевого подключения в течение как минимум 500 мс. (Доступна сAPI docsПолучите больше информации. )

После этого сохраняем PDF как переменную, закрываем браузер и возвращаемся к PDF.

Уведомление:page.pdfметод полученияoptionsобъект, вы можете использовать опцию «путь», чтобы сохранить файл на диск. Если путь не указан, PDF-файл не будет сохранен на диск, а будет помещен в буфер. (Я расскажу, как с этим бороться позже.)

Если для создания PDF-файла с защищенной страницы требуется вход в систему, сначала вы перейдете на страницу входа, проверите идентификатор или имя элементов формы, заполните их и отправьте форму:

await page.type('#email', process.env.PDF_USER)
await page.type('#password', process.env.PDF_PASSWORD)
await page.click('#submit')

Чтобы всегда сохранять учетные данные для входа в переменные среды, не используйте жесткий код!

контроль стиля

У Puppeteer также есть решение для этой манипуляции со стилем. Вы можете вставить теги стилей перед созданием PDF-файла, и Puppeteer создаст файл с измененными стилями.

await page.addStyleTag({ content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}' })

Отправьте файл клиенту и сохраните его

Хорошо, теперь вы создали файл PDF в бэкэнде. Что дальше?

Как упоминалось выше, если вы не сохраните файл на диск, вы получите буфер. Вам просто нужно отправить буфер с соответствующим типом контента во внешний интерфейс.

printPDF.then(pdf => {
	res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdf.length })
	res.send(pdf)

Теперь вам нужно только отправить запрос на сервер в браузере, чтобы получить сгенерированный PDF.

function getPDF() {
 return axios.get(`${API_URL}/your-pdf-endpoint`, {
   responseType: 'arraybuffer',
   headers: {
     'Accept': 'application/pdf'
   }
 })

После отправки запроса содержимое буфера должно начать загружаться. Последним шагом является преобразование данных буфера в файл PDF.

savePDF = () => {
    this.openModal(‘Loading…’) // open modal
   return getPDF() // API call
     .then((response) => {
       const blob = new Blob([response.data], {type: 'application/pdf'})
       const link = document.createElement('a')
       link.href = window.URL.createObjectURL(blob)
       link.download = `your-file-name.pdf`
       link.click()
       this.closeModal() // close modal
     })
   .catch(err => /** error handling **/)
 }
<button onClick={this.savePDF}>Save as PDF</button>

Это оно! Если вы нажмете кнопку «Сохранить», браузер сохранит PDF-файл.

Использование Puppeteer с Docker

Я думаю, что это самая сложная часть реализации, так что позвольте мне сэкономить вам несколько часов времени Baidu.

Официальная документация гласит * «Использование безголового Chrome в Docker и его запуск могут быть сложными» *. В официальной документации естьПоиск проблемыраздел, вы можете найти всю необходимую информацию об установке puppeteer с помощью Docker.

Если вы устанавливаете Puppeteer на образ Alpine, обязательно посмотритеэта часть страницыПрокрутите вниз еще немного. В противном случае вы можете упустить из виду тот факт, что вы не можете запустить последнюю версию Puppeteer, а также вам необходимо отключить shm с помощью флага:

const browser = await puppeteer.launch({
  headless: true,
  args: ['--disable-dev-shm-usage']
});

В противном случае дочерним процессам Puppeteer может не хватить памяти до нормального запуска.

Сценарий 3 + 1: правила печати CSS

Можно подумать, что с точки зрения разработчика просто использовать CSS для печати правил — это просто. Никаких модулей NPM, только чистый CSS. Но как обстоят дела с кросс-браузерной совместимостью?

При выборе правила печати CSS вы должны проверить результаты в каждом браузере, чтобы убедиться, что макет, который он предоставляет, одинаков, и он не может сделать это на 100%.

Например, вставка разрыва после заданного элемента — не очень сложная техника, но вы можете быть удивлены, обнаружив, чтоИспользуйте его в FirefoxТребуется обходной путь.

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

Правила печати хороши, если вы можете сделать свои таблицы стилей печати простыми.

Давайте посмотрим на пример.

@media print {
    .print-button {
        display: none;
    }
    
    .content div {
        break-after: always;
    }
}

Приведенный выше CSS скрывает кнопку печати и печатает ее каждый раз.divВставьте разрыв страницы после этого, который содержитcontentДобрый. Есть одинотличная статьяОбобщает, что вы можете делать с правилами печати и что с ними не так, включая совместимость с браузером.

Учитывая все обстоятельства, правила печати CSS работают очень хорошо, если вы хотите создавать PDF-файлы из менее сложных страниц.

Суммировать

Давайте быстро рассмотрим ранее представленные сценарии создания PDF-файлов из HTML-страниц:

  • Создать скриншот из DOM: может быть полезно, когда вам нужно создать снимки страницы (например, для создания эскизов), но может быть немного растянутым, когда вам нужно обработать много данных.

  • Используйте только библиотеку PDF: это идеальное решение, если вы планируете программно создавать PDF-файлы с нуля. В противном случае вам необходимо поддерживать как HTML, так и PDF-шаблоны, что, безусловно, недопустимо.

  • Puppeteer: Несмотря на то, что работать с Docker было относительно сложно, он дал нашей реализации наилучшие результаты, а также его было легче всего кодировать.

  • Правила печати CSS: если ваши пользователи достаточно образованы, чтобы знать, как печатать содержимое страницы в файл, и ваша страница относительно проста, то это, вероятно, самое простое решение. Как видите, в нашем случае это не так.

Хотя сегодня День дурака, все вышесказанное верно!

С Первым Апреля!

Добро пожаловать на официальный аккаунт Jingcheng Yideng: Jingcheng Yideng, чтобы получить больше галантереи.