51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

使用 Mozilla 的 PDF.Js 在 JavaScript 中自定义 PDF 渲染

500.jpg

对于 Web,几乎每个现代浏览器都支持本地查看 PDF 文档。但是,该原生组件不在开发人员的控制范围内。想象一下,由于您的 Web 应用程序中的某些业务规则,您想禁用该Print按钮,或者只显示几个页面,而其他页面则需要付费会员资格。您可以通过embed标签使用浏览器的本机 PDF 呈现功能,但由于您没有编程访问权限,因此无法控制呈现阶段以满足您的需要。

幸运的是,现在有这样一个工具,PDF.js,由 Mozilla Labs 创建,它可以在您的浏览器中呈现 PDF 文档。最重要的是,作为开发人员,您可以完全控制根据您的要求呈现 PDF 文档的页面。这不是很酷吗?是的!

让我们看看 PDF.js 实际上是什么。

什么是 PDF.js {#whatispdfjs}

PDF.js 是围绕基于 HTML5 的技术构建的可移植文档格式 (PDF),这意味着它可以在现代浏览器中使用而无需安装任何第三方插件。

PDF.js 已经在许多不同的地方使用,包括一些在线文件共享服务,如DropboxCloudUpJumpshare,使用户可以在线查看 PDF 文档,而无需依赖浏览器的本机 PDF 渲染功能。

PDF.js 毫无疑问是您的网络应用程序中的一个很棒且必不可少的工具,但集成它并不像看起来那么简单。关于如何集成某些功能(如呈现文本层或注释(外部/内部链接)以及支持密码保护文件)的文档很少甚至没有。

在本文中,我们将探索 PDF.js,并研究如何集成不同的功能。我们将涵盖的一些主题是:

  • 基本整合

  • 使用 SVG 渲染

  • 渲染文本层

  • 放大/缩小

基本整合 {#basicintegration}

下载必要的文件 {#downloadingthenecessaryfiles}

PDF.js,顾名思义,是一个 JavaScript 库,可用于在浏览器中呈现 PDF 文档。第一步是获取 PDF.js 正常工作所需的必要 JavaScript 文件。以下是 PDF.js 所需的两个主要文件:

  • pdf.js

  • pdf.worker.js

要获取上述文件,如果您是 Node.js 用户,则可以按照GitHub 存储库中提到的这些步骤进行操作。完成gulp generic命令后,您将拥有那些必要的文件。

如果像我一样,您对 Node.js 感到不适应,还有一种更简单的方法。您可以使用以下 URL 下载必要的文件:

  • https://mozilla.github.io/pdf.js/build/pdf.js

  • https://mozilla.github.io/pdf.js/build/pdf.worker.js

上面提到的 URL 指向 Mozilla 的PDF.js现场演示。通过这种方式下载文件,您将始终拥有最新版本的库。

Web Worker 和 PDF.js {#webworkersandpdfjs}

您下载的两个文件包含获取、解析和呈现 PDF 文档的方法。pdf.js是主库,它本质上具有从某些 URL 获取 PDF 文档的方法。但是解析和渲染 PDF 并不是一项简单的任务。事实上,根据 PDF 的性质,解析和呈现阶段可能需要更长的时间,这可能会导致其他 JavaScript 函数被阻塞。

HTML5 引入了Web Workers,用于在与浏览器的 JavaScript 线程不同的线程中运行代码。PDF.js 严重依赖 Web Workers 通过将 CPU 密集型操作(如解析和渲染)从主线程中移出来提供性能提升。在 Web Workers 中运行处理昂贵的代码是 PDF.js 中的默认设置,但如果需要可以关闭。

PDF.js 中的承诺 {#promisesinpdfjs}

PDF.js 的 JavaScript API 非常优雅且易于使用,并且在很大程度上基于Promises。每次调用 API 都会返回一个 Promise,它允许干净地处理异步操作。

你好世界! {#helloworld}

让我们整合一个简单的"Hello World!" PDF文件。我们在这个例子中使用的文档可以在 http://mozilla.github.io/pdf.js/examples/learning/helloworld.pdf 找到。

在您的本地网络服务器下创建一个项目,以便可以使用http://localhost/pdfjs_learning/index.html访问它。PDF.js 进行 Ajax 调用以分块获取文档,因此为了使 Ajax 调用在本地工作,我们需要将 PDF.js 文件放在本地 Web 服务器中。pdfjs_learning在本地网络服务器上创建文件夹后,将上面下载的文件 ( pdf.js, pdf.worker.js) 放入其中。将以下代码放入index.html

<!DOCTYPE html>
<html>
  <head>
    <title>PDF.js Learning</title>
  </head>
  <body>
    <script type="text/javascript" src="pdf.js"></script>
  </body>
</html>

如您所见,我们包含了一个指向主库文件的链接,pdf.js. PDF.js 会自动检测您的浏览器是否支持 Web Workers,如果支持,它将尝试pdf.worker.js从与pdf.js. 如果该文件位于其他位置,您可以PDFJS.workerSrc在包含主库后立即使用属性对其进行配置:

<script type="text/javascript" src="pdf.js"></script>
<script type="text/javascript">
    PDFJS.workerSrc = "/path/to/pdf.worker.js";
</script>

如果您的浏览器不支持 Web Workers,则无需担心,因为pdf.js它包含在不使用 Web Workers 的情况下解析和呈现 PDF 文档所需的所有代码,但根据您的 PDF 文档,它可能会暂停您的主要 JavaScript 执行线程。

让我们编写一些代码来呈现"Hello World!" PDF文件。将以下代码放在script标记中,位于标记下方pdf.js

// URL of PDF document
var url = "http://mozilla.github.io/pdf.js/examples/learning/helloworld.pdf";

// Asynchronous download PDF
PDFJS.getDocument(url)
  .then(function(pdf) {
    return pdf.getPage(1);
  })
  .then(function(page) {
    // Set scale (zoom) level
    var scale = 1.5;

    // Get viewport (dimensions)
    var viewport = page.getViewport(scale);

    // Get canvas#the-canvas
    var canvas = document.getElementById('the-canvas');

    // Fetch canvas' 2d context
    var context = canvas.getContext('2d');

    // Set dimensions to Canvas
    canvas.height = viewport.height;
    canvas.width = viewport.width;

    // Prepare object needed by render method
    var renderContext = {
      canvasContext: context,
      viewport: viewport
    };

    // Render PDF page
    page.render(renderContext);
  });

现在在标签内创建一个<canvas>带有 id 的元素。the-canvas``body

<canvas id="the-canvas"></canvas>

创建<canvas>元素后,刷新您的浏览器,如果您已将所有内容放置在适当的位置,您应该会看到Hello, world! 在您的浏览器中打印。但这不是普通的Hello, world! . 你好,世界!您看到的基本上是一个完整的 PDF 文档,使用 JavaScript 代码在您的浏览器中呈现。拥抱真棒!

让我们讨论上述代码的不同部分,这些代码使 PDF 文档呈现成为可能。

PDFJS是在浏览器中包含pdf.js文件时获得的全局对象。该对象是基础对象,包含各种方法。

PDFJS.getDocument()是主入口点,所有其他操作都在其中执行。它用于异步获取PDF文档,发送多个Ajax请求来分块下载文档,不仅速度快而且效率高。可以将不同的参数传递给此方法,但最重要的参数是指向 PDF 文档的 URL。

PDFJS.getDocument()返回一个 Promise,可用于放置将在 PDF.js 完成获取文档时执行的代码。Promise 的成功回调传递了一个对象,该对象包含有关获取的 PDF 文档的信息。在我们的例子中,这个参数被命名为pdf

您可能想知道,由于 PDF 文档是分块获取的,对于尺寸巨大的文档,成功回调是否只会在延迟几秒(甚至几分钟)后调用。事实上,回调将在获取第一页所需的字节后立即触发。

pdf.getPage()用于获取 PDF 文档中的单个页面。当您提供有效的页码时,getPage()返回一个承诺,该承诺在解决后会为我们提供一个page代表所请求页面的对象。该pdf对象还有一个属性 ,numPages可用于获取 PDF 文档中的总页数。

scale是我们希望 PDF 文档页面呈现的缩放级别。

page.getViewport()为提供的缩放级别返回 PDF 文档的页面尺寸。

page.render()需要具有不同键/值对的对象才能将 PDF 页面呈现到画布上。在我们的示例中,我们传递了从方法中获取的 Canvas 元素的2d上下文和viewport对象。page.getViewport

使用 SVG 渲染 {#renderingusingsvg}

PDF.js 支持两种渲染模式。它的默认和流行渲染模式是基于 Canvas 的。但它也允许您使用 SVG 呈现 PDF 文档。让我们渲染 Hello World!来自上一个 SVG 示例的 PDF 文档。

pdf.getPage()使用以下代码更新成功回调以查看 PDF.js 的 SVG 渲染效果。

.then(function(page) {

  // Set scale (zoom) level
  var scale = 1.5;

  // Get viewport (dimensions)
  var viewport = page.getViewport(scale);

  // Get div#the-svg
  var container = document.getElementById('the-svg');

  // Set dimensions
  container.style.width = viewport.width + 'px';
  container.style.height = viewport.height + 'px';

  // SVG rendering by PDF.js
  page.getOperatorList()
    .then(function (opList) {
      var svgGfx = new PDFJS.SVGGraphics(page.commonObjs, page.objs);
      return svgGfx.getSVG(opList, viewport);
    })
    .then(function (svg) {
      container.appendChild(svg);
    });

});

<canvas>将body 标签中的元素替换为<div id="the-svg"></div>并刷新浏览器。

如果您正确放置了代码,您将看到Hello, world! 正在渲染,但这次它使用 SVG 而不是 Canvas。继续检查页面的 HTML,您将看到整个渲染都是使用标准 SVG 组件完成的。

如您所见,PDF.js 并不限制您使用单一的呈现机制。您可以根据需要使用 Canvas 或 SVG 渲染。对于本文的其余部分,我们将使用基于 Canvas 的渲染。

渲染文本层 {#renderingtextlayers}

PDF.js 使您能够在使用 Canvas 呈现的 PDF 页面上呈现文本层。为此,我们需要从 PDF.js GitHub 的存储库中获取一个额外的 JavaScript 文件。继续下载text_layer_builder.js 插件。我们还需要获取其对应的 CSS 文件text_layer_builder.css。下载这两个文件并将它们pdfjs_learning放在本地服务器上的文件夹中。

在我们进入实际的文本层渲染之前,让我们先获取一个包含比"Hello World!"更多内容的 PDF 文档。例子。我们要呈现的文档再次取自 Mozilla 的现场演示,此处

由于该文档包含多个页面,我们需要稍微调整一下代码。首先,删除<div>我们在上一个示例中创建的标签,并将其替换为:

<div id="container"></div>

此容器将用于保存多页 PDF 文档。放置呈现为 Canvas元素的页面的结构非常简单。PDFdiv#container的每一页都有自己的<div>. 的id属性<div>将具有格式page-#{pdf_page_number}。例如,PDF 文档的第一页的<div>withid属性设置为 as page-1,第 12 页的属性设置为page-12. 在每个page-#{pdf_page_number}div 中,都会有一个Canvas元素。

getDocument()让我们用下面的代码替换成功回调。不要忘记使用(或您选择的其他在线 PDF 文档)更新url变量。http://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf

PDFJS.getDocument(url)
  .then(function(pdf) {

    // Get div#container and cache it for later use
    var container = document.getElementById("container");

    // Loop from 1 to total_number_of_pages in PDF document
    for (var i = 1; i <= pdf.numPages; i++) {

        // Get desired page
        pdf.getPage(i).then(function(page) {

          var scale = 1.5;
          var viewport = page.getViewport(scale);
          var div = document.createElement("div");

          // Set id attribute with page-#{pdf_page_number} format
          div.setAttribute("id", "page-" + (page.pageIndex + 1));

          // This will keep positions of child elements as per our needs
          div.setAttribute("style", "position: relative");

          // Append div within div#container
          container.appendChild(div);

          // Create a new Canvas element
          var canvas = document.createElement("canvas");

          // Append Canvas within div#page-#{pdf_page_number}
          div.appendChild(canvas);

          var context = canvas.getContext('2d');
          canvas.height = viewport.height;
          canvas.width = viewport.width;

          var renderContext = {
            canvasContext: context,
            viewport: viewport
          };

          // Render PDF page
          page.render(renderContext);
        });
    }
});

刷新浏览器并等待几秒钟(同时在后台获取新的 PDF 文档),文档加载完成后,您应该会在浏览器中看到呈现精美的 PDF 页面。现在我们已经了解了如何呈现多个页面,让我们讨论如何呈现文本层。

添加以下两行以index.html包含文本层渲染所需的必要文件:

<link type="text/css" href="text_layer_builder.css" rel="stylesheet">
<script type="text/javascript" src="text_layer_builder.js"></script>

PDF.js 在多个元素中呈现画布上方的文本层<div>,因此最好将所有这些<div>元素包装在一个容器元素中。将page.render(renderContext)行替换为以下代码以查看正在运行的文本层:

page.render(renderContext)
  .then(function() {
    // Get text-fragments
    return page.getTextContent();
  })
  .then(function(textContent) {
    // Create div which will hold text-fragments
    var textLayerDiv = document.createElement("div");
    // Set it's class to textLayer which have required CSS styles
    textLayerDiv.setAttribute("class", "textLayer");
    // Append newly created div in `div#page-#{pdf_page_number}`
    div.appendChild(textLayerDiv);
    // Create new instance of TextLayerBuilder class
    var textLayer = new TextLayerBuilder({
      textLayerDiv: textLayerDiv, 
      pageIndex: page.pageIndex,
      viewport: viewport    });
    // Set text-fragments
    textLayer.setTextContent(textContent);
    // Render text-fragments
    textLayer.render();
  });

刷新您的浏览器,这一次您不仅会看到正在呈现的 PDF 页面,而且您还可以从中选择和复制文本。PDF.js 太酷了!

让我们讨论上面代码片段的一些重要部分。

page.render(),与 PDF.js 中的任何其他方法一样,返回一个 promise,当 PDF 页面成功呈现到屏幕上时,该 promise 将被解析。我们可以使用成功回调来渲染文本层。

page.getTextContent()是一种返回特定页面的文本片段的方法。这也会返回一个承诺,并在成功回调时返回该承诺的文本片段表示。

TextLayerBuilder是一个类,它需要一些我们已经从pdf.getPage()每个页面获得的参数。该textLayerDiv参数表示<div>将用作承载多个<div>s 的容器,每个 s 代表一些特定的文本片段。

新创建的实例TextLayerBuilder有两个重要的方法:setTextContent(),用于设置 返回的文本片段page.getTextContent(), ,render(),用于渲染文本层。

如您所见,我们正在将 CSS 类分配textLayertextLayerDiv. 此类具有可确保文本片段很好地适合 Canvas 元素顶部的样式,以便用户可以自然地选择/复制文本。

放大/缩小 {#zoominginout}

使用 PDF.js,您还可以控制 PDF 文档的缩放。事实上,缩放非常简单,我们只需要更新scale值即可。增加或减少scale所需的系数以更改缩放级别。这是留给读者的练习,但请尝试一下,并在评论中告诉我们您的进展情况。

结论 {#conclusion}

PDF.js 是一个很棒的工具,它为我们提供了一个灵活的替代方案,可以替代浏览器使用 JavaScript 的本机 PDF 组件。API 简单、精确、优雅,您可以随意使用。在评论中让我知道您打算如何在下一个项目中使用 PDF.js!

赞(0)
未经允许不得转载:工具盒子 » 使用 Mozilla 的 PDF.Js 在 JavaScript 中自定义 PDF 渲染