JavaScript处理密码保护PDF的完整指南

本文详细介绍了如何使用PDF.js库在浏览器中处理密码保护的PDF文件,包括文件读取、密码验证、解密和页面渲染的全流程实现方案,提供了完整的代码示例和最佳实践。

JavaScript处理密码保护PDF的完整指南

PDF是最简单的文档共享格式之一。它们具有便携性,并能通过密码保护提供基本的访问控制。本文将讨论在JavaScript中解锁和打开密码保护PDF文档的多种方法之一。

本文使用PDF.js和现代浏览器内置的客户端JavaScript工具来实现:

  • 从用户设备读取PDF文件
  • 仅在PDF受密码保护时提示输入密码
  • 显示解锁PDF文件失败尝试的反馈
  • 使用浏览器的Canvas API渲染解密后的PDF页面

创建新的JavaScript项目

首先,让我们使用Vite设置一个原生JavaScript应用程序的脚手架。在终端中运行以下命令创建名为pdf-password的新项目并安装依赖:

1
npm create vite@latest -- --template vanilla pdf-password && cd pdf-password && npm install

接下来,安装PDF.js作为项目依赖:

1
npm install pdfjs-dist

创建处理用户输入和PDF渲染的HTML元素

将项目的index.html文件内容替换为以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Locked PDF Viewer</title>
  </head>
  <body>
    <form class="pdf-form">
      <label for="pdf-form__input" class="pdf-form__label"
        >View a PDF file in your browser</label
      >
      <input type="file" name="pdf-file" id="" class="pdf-form__input" />
    </form>

    <div class="password-form-backdrop">
      <form class="password-form">
        <label for="" class="password-form__label"
          >The PDF is password-protected. Please enter its password.</label
        >
        <p class="password-incorrect">Incorrect password. Please try again.</p>
        <input
          type="password"
          name="password"
          class="password-input"
          placeholder="PDF Password"
          autocomplete=""
          autofocus
        />
        <button type="submit" class="password-submit">Unlock</button>
      </form>
    </div>

    <canvas class="pdf-canvas"></canvas>

    <script type="module" src="/src/main.js"></script>
  </body>
</html>

第一个表单处理从用户设备上传文件,第二个表单收集PDF密码。canvas元素将用于渲染PDF页面。

导入项目依赖

删除项目src/main.js文件的内容,然后导入并配置PDF.js和项目的样式表:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import './style.css';

// Import pdfJs
import {
  GlobalWorkerOptions,
  getDocument,
  PasswordResponses,
} from 'pdfjs-dist';

// Setup pdfJs' worker from the package's node_modules folder
GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.mjs',
  import.meta.url
).toString();

显示PDF内容

将以下代码复制到src/main.js文件中。它为pdf-form元素的文件输入字段附加事件监听器,检测用户何时选择PDF并尝试在屏幕上显示文档内容:

1
2
3
// Display an uploaded PDF file
document.querySelector('.pdf-form__input')
.addEventListener('change', viewPDF);

定义viewPDF事件处理程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function viewPDF(e) {
  // Get the File object of the uploaded PDF.
  const file = e.target.files[0];

  // Continue only if the user has uploaded a PDF document
  if (file.type !== 'application/pdf') {
    alert(`Error: ${file.name} is not a PDF file`);
    return;
  }

  // Read the contents of the PDF file from the user's device
  const reader = new FileReader();
  reader.readAsArrayBuffer(file);

  // Handle error(s) encountered while reading the contents of the PDF file
  reader.onerror = () => {
    alert(`Unable to read ${file.name} to an ArrayBuffer`);
    console.error(reader.error);
  };

  // Wait till FileReader has read all contents of the PDF file before proceeding
  reader.onload = async () => {
    // Transform the contents of the PDF file to a generic byte array
    const bytes = new Uint8Array(reader.result);

    // Using PDF.js, start loading the PDF contents from the above byte array
    const loadingTask = getDocument(bytes);

    // Prompt for a password only if PDF.js detects password protection while loading the document
    loadingTask.onPassword = handlePDFPassword;

    // Complete the process of loading the PDF document
    const pdfDocument = await loadingTask.promise;

    // Hide the PDF upload form since we don't need it anymore
    document.querySelector('.pdf-form').style.display = 'none';

    renderPage(pdfDocument);
  };
}

提示输入密码并解锁受密码保护的PDF

定义handlePDFPassword函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function handlePDFPassword(setPassword, reason) {
  const passwordForm = document.querySelector('.password-form-backdrop').style;
  const passwordIncorrect = document.querySelector('.password-incorrect').style;
   
  // Prompt for a password if PDF.js needs the file’s password to proceed
  if (reason === PasswordResponses.NEED_PASSWORD) {
    passwordForm.display = 'flex';
    passwordIncorrect.display = 'none';

    document.querySelector('.password-form').addEventListener('submit', (e) => {
      e.preventDefault();
      setPassword(document.querySelector('.password-input').value);
       
      // Hide password prompt after the correct password is submitted
      passwordForm.display = 'none';
      passwordIncorrect.display = 'none';
    });
  }

  // Display incorrect password error message if the entered password doesn’t unlock the PDF file
  if (reason === PasswordResponses.INCORRECT_PASSWORD) {
    passwordForm.display = 'flex';
    passwordIncorrect.display = 'block';
  }
}

渲染解锁的PDF页面

定义renderPage函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
async function renderPage(pdfDocument) {
  // Load the first page of the document.
  const page = await pdfDocument.getPage(1);

  // Use the page's dimensions (in pixels) to set the
  // dimensions of the canvas on which the page will be rendered
  const viewport = page.getViewport({ scale: 1 });
  const canvas = document.querySelector('.pdf-canvas');
  const canvasContext = canvas.getContext('2d');

  canvas.style.display = 'block';
  canvas.height = viewport.height;
  canvas.width = viewport.width;

  // Render the PDF page on the site
  const renderTask = page.render({
    canvasContext,
    viewport,
  });
  await renderTask.promise;
}

进一步扩展

上面定义的renderPage函数仅渲染PDF文档的第一页。有关为应用程序添加分页和/或更好的错误处理的指导,请参阅https://mozilla.github.io/pdf.js/examples。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计