HTMLをPDFに変換する4つの方法

青灯夜游
青灯夜游転載
2020-11-11 17:53:492736ブラウズ

HTML を PDF に変換するにはどうすればよいですか?次の記事では、Node.js、Puppeteer、ヘッドレス Chrome、Docker を使用して、複雑なスタイルの React ページから PDF ドキュメントを生成する方法を示します。 一定の参考価値がありますので、困っている友人の参考にしていただければ幸いです。

背景: 数か月前、クライアントから、ユーザーが React ページのコンテンツを PDF 形式で取得できる機能の開発を依頼されました。このページは基本的に患者症例のレポートとデータ視覚化であり、多くの SVG が含まれています。レイアウトを操作したり、HTML 要素の再配置を実行したりするための特別なリクエストもあります。したがって、元の React ページと比較して、PDF には異なるスタイルと追加コンテンツが存在するはずです。

このタスクは単純な CSS ルールで解決するよりもはるかに複雑であるため、最初にそれを実現する可能な方法を検討します。私たちは 3 つの主な解決策を見つけました。このブログ投稿では、その可能性と最終的な実装について説明します。

ディレクトリ:

  • クライアント側で生成されるのか、サーバー側で生成されるのか?
  • オプション 1: DOM からスクリーンショットを作成する
  • オプション 2: PDF ライブラリを使用するだけ
  • 最後のオプション 3: Node.js、Puppeteer、ヘッドレス Chrome

    • スタイル コントロール
    • ファイルをクライアントに送信して保存します
  • Docker で Puppeteer を使用する
  • オプション 3 1 : CSS 印刷ルール
  • 概要

それらはクライアント側で生成されるのでしょうか、それともサーバー側で生成されるのでしょうか?

PDF ファイルはクライアント側とサーバー側の両方で生成できます。ただし、ユーザーのブラウザが提供できるすべてのリソースを使い果たしたくないため、バックエンドに処理させる方がおそらく合理的です。

それでも、両方の方法の解決策を示します。

オプション 1: DOM からスクリーンショットを作成する

一見すると、この解決策が最も単純であるように見えます。実際、実際はそうなのですが、これには独自の制限があります。これは、PDF 内のテキストを選択したり、テキストの検索を実行したりするなど、特別なニーズがない場合に使いやすい方法です。

この方法はシンプルで簡単です。ページからスクリーンショットを作成し、それを PDF ファイルに配置します。とても簡単です。これを実現するには 2 つのパッケージを使用できます:

  • 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')
})

以上です!

html2canvasonclone メソッドに注意してください。スクリーンショットを撮る前に 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 ドキュメントから引用しています。ただし、既存の (そして常に変化する) HTML ページを変換するのではなく、PDF ファイルを直接生成することが目標の場合には、依然として便利です。

最終ソリューション 3: Node.js ベースの Puppeteer とヘッドレス Chrome

Puppeteer とは何ですか?そのドキュメントには次のように書かれています:

Puppeteer は、DevTools プロトコルで Chrome または Chromium を制御するための高レベル API を提供するノード ライブラリです。 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' オプションは、ネットワーク接続が少なくとも 500 ミリ秒ない場合に、Puppeteer がナビゲーションが完了したとみなすことを意味します。 (詳細については、API ドキュメントから入手できます。)

その後、PDF を変数として保存し、ブラウザを閉じて PDF に戻ります。

注: page.pdf メソッドは options オブジェクトを受け入れます。「パス」オプションを使用してファイルをディスクに保存できます。パスが指定されていない場合、PDF はディスクに保存されず、代わりにバッファリングされます。 (対処方法は後ほど説明します。)

如果需要先登录才能从受保护的页面生成 PDF,首先你要导航到登录页面,检查表单元素的 ID 或名称,填写它们,然后提交表单:

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。

在 Docker 中使用 Puppeteer

我认为这是实施中最棘手的部分 —— 所以让我帮你节省几个小时的百度时间。

官方文档指出“在 Docker 中使用 headless Chrome 并使其运行起来可能会非常棘手”。官方文档有疑难解答部分,你可以找到有关用 Docker 安装 puppeteer 的所有必要信息。

如果你在 Alpine 镜像上安装 Puppeteer,请确保在看到页面的这一部分时再向下滚动一点。否则你可能会忽略一个事实:你无法运行最新的 Puppeteer 版本,并且你还需要用一个标记禁用 shm :

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

否则,Puppeteer 子进程可能会在正常启动之前耗尽内存。

方案 3 + 1:CSS 打印规则

可能有人认为从开发人员的角度来看,简单地使用 CSS 打印规则很容易。没有 NPM 模块,只有纯 CSS。但是在跨浏览器兼容性方面,它的表现如何呢?

在选择 CSS 打印规则时,你必须在每个浏览器中测试结果,以确保它提供的布局是相同的,并且它不是100%能做到这一点。

例如,在给定元素后面插入一个 break-after 并不是一个多么高深的技术,但是你可能会惊讶的发现要在 Firefox 中使用它需要使用变通方法。

除非你是一位经验丰富的 CSS 大师,在创建可打印页面方面有很多的经验,否则这可能会非常耗时。

如果你可以使打印样式表保持简单,打印规则是很好用的。

让我们来看一个例子吧。

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

上面的 CSS 隐藏了打印按钮,并在每个 p 之后插入一个分页符,其中包含content 类。有一篇很棒的文章总结了你可以用打印规则做什么,以及它们有什么问题,包括浏览器兼容性。

考虑到所有因素,如果你想从不那么复杂的页面生成 PDF,CSS打印规则非常有效。

总结

让我们快速回顾前面介绍的方案,以便从 HTML 页面生成 PDF 文件:

  • 从 DOM 产生截图:当你需要从页面创建快照时(例如创建缩略图)可能很有用,但是当你需要处理大量数据时就会有些捉襟见肘。
  • 只用 PDF 库:如果你打算从头开始以编程方式创建 PDF 文件,这是一个完美的解决方案。否则,你需要同时维护 HTML 和 PDF 模板,这绝对是一个禁忌。
  • Puppeteer:尽管在 Docker 上工作相对困难,但它为我们的实现提供了最好的结果,而且编写代码也是最简单的。
  • CSS打印规则:如果你的用户受过足够的教育,知道如何把页面内容打印到文件,并且你的页面相对简单,那么它可能是最轻松的解决方案。正如你在我们的案例中所看到的,事实并非如此。

打印快乐!

原文:https://blog.risingstack.com/pdf-from-html-node-js-puppeteer/

更多编程相关知识,请访问:编程教学!!

以上がHTMLをPDFに変換する4つの方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。