杰瑞科技汇

JavaScript如何生成PDF?

JavaScript 操作 PDF 全景指南

在 Web 开发中,我们经常需要在前端或后端处理 PDF 文件,

JavaScript如何生成PDF?-图1
(图片来源网络,侵删)
  • 生成 PDF:将订单、报告、发票等数据导出为 PDF 供用户下载。
  • 读取/解析 PDF:从用户上传的 PDF 文件中提取文本、图片或表单数据。
  • 编辑 PDF:在现有 PDF 上添加水印、合并多个 PDF、填写表单等。

JavaScript 生态为此提供了强大的库,我们将主要围绕以下几个方面展开:

  1. 客户端生成 PDF (浏览器端)
  2. 客户端解析 PDF (浏览器端)
  3. 服务器端生成/操作 PDF (Node.js)
  4. 最佳实践与注意事项

第一部分:客户端生成 PDF (浏览器端)

在浏览器中生成 PDF,通常是将 HTML 页面或特定 DOM 元素转换为 PDF 文件并触发下载。

推荐库:html2canvas + jspdf (经典组合)

这是一个非常流行且灵活的组合,适合将复杂的 HTML 布局(如报表、海报)转换为 PDF。

安装

JavaScript如何生成PDF?-图2
(图片来源网络,侵删)

通过 CDN 或 npm 安装。

# npm
npm install html2canvas jspdf
<!-- CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

基本用法

将某个 div 元素转换为 PDF。

<!-- 你想要转换的 HTML 内容 -->
<div id="content-to-print" style="padding: 20px; border: 1px solid #ccc;">
  <h1>我的 PDF 报告</h1>
  <p>这是由 JavaScript 生成的 PDF 文件。</p>
  <table>
    <tr><th>产品</th><th>价格</th></tr>
    <tr><td>苹果</td><td>¥5</td></tr>
    <tr><td>香蕉</td><td>¥3</td></tr>
  </table>
</div>
<!-- 一个按钮来触发下载 -->
<button id="download-btn">下载 PDF</button>
<script>
  document.getElementById('download-btn').addEventListener('click', function() {
    const input = document.getElementById('content-to-print');
    // 1. 使用 html2canvas 将 DOM 元素转换为 canvas
    html2canvas(input).then(canvas => {
      // 2. 获取 canvas 的图像数据
      const imgData = canvas.toDataURL('image/png');
      // 3. 创建 jsPDF 实例
      const pdf = new jspdf.jsPDF('p', 'mm', 'a4'); // 'p' (portrait), 'mm' (millimeters), 'a4' (page size)
      // 4. 计算图片在 PDF 中的尺寸,使其适应页面
      const imgWidth = 210; // A4 宽度
      const pageHeight = 295; // A4 高度
      const imgHeight = canvas.height * imgWidth / canvas.width;
      let heightLeft = imgHeight;
      let position = 0;
      // 5. 将图片添加到 PDF
      pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
      heightLeft -= pageHeight;
      // 6. 如果内容超过一页,添加新页
      while (heightLeft >= 0) {
        position = heightLeft - imgHeight;
        pdf.addPage();
        pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
        heightLeft -= pageHeight;
      }
      // 7. 保存 PDF 文件
      pdf.save('my-report.pdf');
    });
  });
</script>

优点

JavaScript如何生成PDF?-图3
(图片来源网络,侵删)
  • 灵活性高,可以转换任何 HTML 元素。
  • 保留了 CSS 样式和布局。

缺点

  • 依赖浏览器,对于非常复杂的 CSS(如 box-shadow, transform)支持可能不完美。
  • 性能较差,对于大量内容或复杂页面,转换可能很慢。
  • 生成的 PDF 是基于图片的,无法复制文本或搜索。

推荐库:pdf-lib (功能强大的客户端 PDF 编辑)

pdf-lib 是一个更现代、功能更强大的库,它不仅可以创建 PDF,还可以读取、修改和合并现有的 PDF 文件,并且操作的是 PDF 的矢量对象,而不是图片。

安装

npm install pdf-lib

基本用法

示例:在现有 PDF 上添加文字

import { PDFDocument, rgb } from 'pdf-lib';
// 1. 加载一个现有的 PDF 文件 (可以是 ArrayBuffer, Blob 或 URL)
const existingPdfBytes = await fetch('path/to/your/document.pdf').then(res => res.arrayBuffer());
// 2. 创建 PDFDocument 实例
const pdfDoc = await PDFDocument.load(existingPdfBytes);
// 3. 获取第一页
const pages = pdfDoc.getPages();
const firstPage = pages[0];
const { width, height } = firstPage.getSize();
// 4. 在页面上绘制文字
firstPage.drawText('你好,世界!', {
  x: 50,
  y: height - 50,
  size: 30,
  color: rgb(0, 0.5, 0), // 绿色
});
// 5. 保存修改后的 PDF
const pdfBytes = await pdfDoc.save();
// 6. 创建一个下载链接并触发下载
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'modified-document.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);

优点

  • 功能极其强大,可以进行 PDF 的深度编辑。
  • 操作的是矢量对象,生成的 PDF 质量高,可复制、可搜索。
  • 可以合并、拆分、添加表单、签名等。

缺点

  • API 相对复杂,学习曲线比 html2canvas + jspdf 陡峭。
  • 对于简单的“HTML 转 PDF”需求,有些“杀鸡用牛刀”。

第二部分:客户端解析 PDF (浏览器端)

有时我们需要在浏览器中读取 PDF 文件,例如提取文本或预览。

推荐库:pdfjs-dist (官方推荐)

PDF.js 是 Mozilla 官方维护的 PDF 渲染库,功能非常强大,是浏览器中处理 PDF 的事实标准。

安装

npm install pdfjs-dist

基本用法:提取文本

import { getDocument } from 'pdfjs-dist/build/pdf';
import 'pdfjs-dist/build/pdf.worker.entry'; // 必须引入 worker
async function extractTextFromPdf(file) {
  const fileReader = new FileReader();
  await new Promise((resolve, reject) => {
    fileReader.onload = resolve;
    fileReader.onerror = reject;
    fileReader.readAsArrayBuffer(file);
  });
  const typedArray = new Uint8Array(fileReader.result);
  // 加载 PDF 文档
  const pdf = await getDocument(typedArray).promise;
  let fullText = '';
  // 遍历每一页
  for (let i = 1; i <= pdf.numPages; i++) {
    const page = await pdf.getPage(i);
    // 获取文本内容
    const textContent = await page.getTextContent();
    // 将文本块拼接成字符串
    const pageText = textContent.items.map(item => item.str).join(' ');
    fullText += `--- 第 ${i} 页 ---\n${pageText}\n\n`;
  }
  console.log(fullText);
  return fullText;
}
// 使用示例
const fileInput = document.getElementById('pdf-file-input');
fileInput.addEventListener('change', (event) => {
  const file = event.target.files[0];
  if (file) {
    extractTextFromPdf(file);
  }
});

优点

  • 官方维护,稳定可靠。
  • 功能全面,可以渲染 PDF、提取文本、获取元数据等。
  • 支持高级 PDF 特性。

缺点

  • 库较大,可能会影响页面加载性能。
  • 配置稍显复杂(如需要 worker 文件)。

第三部分:服务器端生成/操作 PDF (Node.js)

在服务器端生成 PDF 可以避免客户端的性能问题,并且可以处理更复杂的逻辑,如模板渲染、数据库查询等。

推荐库:puppeteer (基于 Chrome 的自动化)

puppeteer 是一个由 Google 开发的库,可以通过编程控制一个无头 Chrome 浏览器,这是目前生成高质量、可复制的 PDF 的最佳方式之一。

安装

npm install puppeteer

基本用法:将 URL 转为 PDF

const puppeteer = require('puppeteer');
async function generatePdfFromUrl(url, outputPath) {
  // 1. 启动浏览器
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  // 2. 访问 URL
  await page.goto(url, { waitUntil: 'networkidle2' });
  // 3. 生成 PDF
  // viewport 可以控制 PDF 的尺寸
  await page.pdf({
    path: outputPath,
    format: 'A4',
    printBackground: true, // 打印背景
    margin: {
      top: '20px',
      bottom: '20px',
      left: '20px',
      right: '20px'
    }
  });
  // 4. 关闭浏览器
  await browser.close();
  console.log(`PDF 已生成并保存到: ${outputPath}`);
}
// 使用示例
generatePdfFromUrl('https://example.com', 'example.pdf');

从 HTML 字符串生成 PDF

async function generatePdfFromHtml(htmlContent, outputPath) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  // 直接设置页面的 HTML 内容
  await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
  await page.pdf({
    path: outputPath,
    format: 'A4',
  });
  await browser.close();
}
// 使用示例
const html = `
  <html>
    <head><title>My PDF</title></head>
    <body style="font-family: sans-serif;">
      <h1>服务器生成的 PDF</h1>
      <p>这是由 Puppeteer 在 Node.js 中生成的。</p>
    </body>
  </html>
`;
generatePdfFromHtml(html, 'from-html.pdf');

优点

  • 质量极高:生成的是真正的、可复制的、可搜索的 PDF。
  • 功能强大:可以执行 JavaScript,渲染任何现代 Web 技术(React, Vue, WebGL 等)。
  • 高度可控:可以模拟用户交互、截图、处理表单等。

缺点

  • 依赖系统中的 Chrome/Chromium,部署环境需要额外配置。
  • 启动浏览器有性能开销,不适合高并发的简单任务。

推荐库:pdf-lib (Node.js 环境)

pdf-lib 不仅可以在浏览器中使用,也可以在 Node.js 中使用,非常适合进行 PDF 的修改和合并。

// Node.js 示例
const { PDFDocument, rgb } = require('pdf-lib');
const fs = require('fs');
async function modifyPdf() {
  // 读取 PDF 文件
  const existingPdfBytes = fs.readFileSync('path/to/document.pdf');
  const pdfDoc = await PDFDocument.load(existingPdfBytes);
  const pages = pdfDoc.getPages();
  const firstPage = pages[0];
  const { width } = firstPage.getSize();
  firstPage.drawText('添加于服务器', {
    x: width / 2 - 50,
    y: 50,
    size: 20,
    color: rgb(1, 0, 0), // 红色
  });
  const pdfBytes = await pdfDoc.save();
  fs.writeFileSync('modified-document.pdf', pdfBytes);
}
modifyPdf();

第四部分:最佳实践与注意事项

场景 推荐方案 原因
简单 HTML 转 PDF html2canvas + jspdf 快速实现,无需服务器,适合简单布局。
高质量、可复制 PDF puppeteer (服务器端) 生成 PDF 质量最高,支持所有现代 Web 技术。
编辑现有 PDF (添加文字/水印/合并) pdf-lib (客户端或服务器端) 功能强大,专门为 PDF 操作设计,矢量编辑。
从网页 URL 生成 PDF puppeteer (服务器端) 最可靠的方式,能处理复杂的动态内容。
在浏览器中预览/提取 PDF pdfjs-dist 官方标准,功能全面,兼容性好。
服务器端生成报表/发票 puppeteerpdf-lib puppeteer 适合模板化页面,pdf-lib 适合基于现有文档修改。

重要注意事项

  1. CORS (跨域资源共享)

    • 在浏览器中使用 pdfjs-distpuppeteer (如果页面有 CORS 限制) 时,PDF 文件来自不同的域名,服务器必须设置正确的 Access-Control-Allow-Origin 头,否则浏览器会阻止请求。
  2. 字体问题

    • 当使用 html2canvaspuppeteer 时,PDF 中使用了特殊的 Web 字体,需要确保这些字体可以被嵌入或正确加载。puppeteer 通常能更好地处理这个问题。
    • 使用 pdf-lib 时,可以嵌入字体,确保 PDF 在任何设备上都能正确显示。
  3. 性能

    • 客户端生成 PDF 会消耗大量 CPU 和内存,可能导致页面卡顿或崩溃,对于复杂或大型 PDF,强烈建议使用服务器端方案(如 puppeteer)。
  4. 安全性

    • 如果用户输入用于生成 PDF 的 HTML 内容,务必进行XSS 防护,防止恶意脚本注入,对 HTML 进行净化处理。
  5. puppeteer 的部署

    • 在生产环境中使用 puppeteer,通常需要配合 docker 或使用预装的 Chromium 镜像(如 puppeteer/chromium)来简化部署。

希望这份详细的教程能帮助你选择并掌握 JavaScript 操作 PDF 的最佳方法!

分享:
扫描分享到社交APP
上一篇
下一篇