Статьи

Пользовательский рендеринг PDF в JavaScript с помощью Mozilla’s PDF.Js

Эта статья была рецензирована Яни Хартикайнен , Флориан Раппл , Джезен Томас и Джефф Смит . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!

Когда дело доходит до Интернета, почти каждый современный браузер поддерживает просмотр PDF-документов. Но этот нативный компонент находится вне контроля разработчика. Представьте, что из-за какого-то бизнес-правила в вашем веб-приложении вы хотели отключить кнопку « Print или отобразить только несколько страниц, в то время как другие требуют платного членства. Вы можете использовать встроенную в браузер функцию рендеринга PDF, используя тег embed , но, поскольку у вас нет программного доступа, вы не можете управлять фазой рендеринга в соответствии с вашими потребностями.

К счастью, теперь существует такой инструмент, PDF.js , созданный Mozilla Labs, который может отображать документы PDF в вашем браузере. Самое главное, вы, как разработчик, имеете полный контроль над отображением страниц документа PDF в соответствии с вашими требованиями. Разве это не круто? Да, это так!

Давайте посмотрим, что на самом деле PDF.js.

Что такое PDF.js

PDF.js — это Portable Document Format (PDF), созданный на основе технологий на основе HTML5, что означает, что его можно использовать в современных браузерах без установки сторонних плагинов.

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

PDF.js, без сомнения, является замечательным и необходимым инструментом в вашем веб-приложении, но его интеграция не так проста, как может показаться. Нет практически никакой документации о том, как интегрировать определенные функции, такие как рендеринг текстовых слоев или аннотаций (внешние / внутренние ссылки), и поддержка файлов, защищенных паролем.

В этой статье мы рассмотрим PDF.js и посмотрим, как мы можем интегрировать различные функции. Вот некоторые темы, которые мы рассмотрим:

  • Базовая интеграция
  • Рендеринг с использованием SVG
  • Рендеринг текстовых слоев
  • Увеличение / уменьшение

Базовая интеграция

Скачивание необходимых файлов

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

  • pdf.js
  • pdf.worker.js

Чтобы получить вышеупомянутые файлы, если вы являетесь пользователем Node.js, вы можете выполнить следующие действия, как указано в репозитории GitHub. После того, как вы закончите с gulp generic командой gulp generic , у вас будут эти необходимые файлы.

Если, как и я, вы не чувствуете себя комфортно с Node.js, есть более простой способ. Вы можете использовать следующие URL для загрузки необходимых файлов:

Вышеупомянутые URL-адреса указывают на живую демонстрацию Mozilla PDF.js. Загружая файлы таким образом, вы всегда будете иметь последнюю версию библиотеки.

Веб-работники и PDF.js

Два загруженных вами файла содержат методы для извлечения, анализа и визуализации документа PDF. pdf.js — это основная библиотека, в которой есть методы для извлечения PDF-документа с какого-либо URL. Но анализ и рендеринг PDF не простая задача. Фактически, в зависимости от характера PDF, этапы анализа и рендеринга могут занять немного больше времени, что может привести к блокировке других функций JavaScript.

HTML5 представил веб-работников , которые используются для запуска кода в отдельном потоке от потока JavaScript в браузере. PDF.js в значительной степени полагается на веб-работников для обеспечения повышения производительности за счет удаления из основного потока операций, интенсивно работающих с процессором, таких как анализ и рендеринг. Выполнение обработки дорогостоящего кода в Web Workers по умолчанию в PDF.js, но при необходимости его можно отключить.

Обещания в PDF.js

JavaScript API в PDF.js довольно элегантен и прост в использовании и в значительной степени основан на Promises . Каждый вызов API возвращает Promise, что позволяет аккуратно обрабатывать асинхронные операции.

Привет, мир!

Давайте интегрируем простой «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 на локальном веб-сервере. После создания папки pdfjs_learning на локальном веб-сервере поместите в pdf.js файлы ( 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 содержит весь код, необходимый для анализа и рендеринга PDF-документов без использования Web Workers, но в зависимости от ваших 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> с идентификатором the-canvas в body тега.

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

После создания элемента <canvas> обновите браузер и, если вы разместили все на своем месте, вы должны увидеть Hello, world! напечатано в вашем браузере. Но это не обычный Привет, мир! , Привет, мир! Вы видите, что в основном весь документ PDF отображается в вашем браузере с использованием кода JavaScript. Охватите удивительность!

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

PDFJS — это глобальный объект, который вы получаете, когда включаете файл pdf.js в браузер. Этот объект является базовым объектом и содержит различные методы.

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

PDFJS.getDocument() возвращает Обещание, которое можно использовать для размещения кода, который будет выполнен, когда PDF.js завершит извлечение документа. Обратному вызову об успешном выполнении Promise передается объект, который содержит информацию о загруженном документе PDF. В нашем примере этот аргумент называется pdf .

Вы могли бы задаться вопросом, если, поскольку документ PDF извлекается порциями, для документов, которые имеют большой размер, обратный вызов успеха будет вызываться только с задержкой в ​​несколько секунд (или даже минут). Фактически, обратный вызов сработает, как только будут получены необходимые байты для первой страницы.

pdf.getPage() используется для получения отдельных страниц в документе PDF. Когда вы предоставляете действительный номер страницы, getPage() возвращает обещание, которое после разрешения дает нам объект page который представляет запрошенную страницу. У объекта pdf также есть свойство numPages , которое можно использовать для получения общего количества страниц в документе PDF.

scale — это уровень масштабирования, который мы хотим отображать на страницах документа PDF.

page.getViewport() возвращает размеры страницы документа PDF для предоставленного уровня масштабирования.

page.render() требует, чтобы объект с различными парами ключ / значение отображал страницу PDF на холсте. В нашем примере мы передали 2d контекст элемента Canvas и объект viewport которые мы получаем из метода page.getViewport .

Рендеринг с использованием SVG

PDF.js поддерживает два режима рендеринга. По умолчанию это популярный режим рендеринга на основе Canvas. Но это также позволяет вам отображать PDF документы с использованием SVG. Давайте представим Hello World! PDF документ из предыдущего примера в SVG.

Обновите успешный обратный вызов pdf.getPage() следующим кодом, чтобы увидеть SVG-рендеринг PDF.js в действии.

 .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.

Рендеринг текстовых слоев

PDF.js дает вам возможность визуализировать текстовые слои поверх страниц PDF, которые были отрисованы с использованием Canvas. Для этого нам нужно получить дополнительный файл JavaScript из репозитория PDF.js GitHub. Загрузите плагин text_layer_builder.js . Нам также нужно получить соответствующий CSS-файл text_layer_builder.css . Загрузите оба файла и поместите их в папку pdfjs_learning на локальном сервере.

Прежде чем мы перейдем к фактическому рендерингу текстового слоя, давайте получим PDF-документ с большим содержанием, чем «Hello World!» пример. Документ, который мы собираемся сделать, снова взят из живой демонстрации Mozilla, здесь .

Поскольку этот документ содержит несколько страниц, нам нужно немного изменить наш код. Сначала удалите <div> мы создали в последнем примере, и замените его следующим:

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

Этот контейнер будет использоваться для хранения нескольких страниц документа PDF. Структура размещения страниц, отображаемых как элементы Canvas довольно проста. В div#container каждая страница PDF будет иметь свой собственный <div> . Атрибут id для <div> будет иметь формат page-#{pdf_page_number} . Например, первая страница в документе PDF будет иметь атрибут <div> с атрибутом id установленным как page-1 а 12-ая страница будет иметь page-12 . Внутри каждой из этих page-#{pdf_page_number} , будет элемент Canvas .

Давайте заменим обратный вызов getDocument() на следующий код. Не забудьте обновить переменную url с помощью http://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf (или другого другого 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, возвращает обещание, которое разрешается, когда страница PDF успешно отображается на экране. Мы можем использовать обратный вызов успеха для визуализации текстовых слоев.

page.getTextContent() — это метод, который возвращает фрагменты текста для этой конкретной страницы. Это также возвращает обещание, и в случае успеха возвращается обратный вызов этого представления фрагментов текста обещания.

TextLayerBuilder — это класс, который требует некоторые параметры, которые у нас уже есть в pdf.getPage() для каждой страницы. Параметр textLayerDiv представляет textLayerDiv <div> который будет использоваться в качестве контейнера для размещения нескольких textLayerDiv <div> каждый из которых представляет определенный фрагмент текста.

Вновь созданный экземпляр TextLayerBuilder имеет два важных метода: setTextContent() , который используется для установки фрагментов текста, возвращаемых page.getTextContent() , и render() , который используется для визуализации текстового слоя.

Как вы можете видеть, мы назначаем CSS-класс textLayer для textLayerDiv . У этого класса есть стили, которые гарантируют, что фрагменты текста хорошо вписываются в элементы Canvas, так что пользователь может выбирать / копировать текст естественным образом.

Увеличение / уменьшение

С PDF.js вы также можете управлять масштабированием документа PDF. На самом деле, масштабирование довольно простое, и нам просто нужно обновить значение scale . Увеличьте или уменьшите scale с вашим желаемым коэффициентом, чтобы изменить уровень масштабирования. Это оставлено в качестве упражнения для читателя, но попробуйте это и дайте нам знать, как вы продвигаетесь в комментариях.

Вывод

PDF.js — это замечательный инструмент, который предоставляет нам гибкую альтернативу родным PDF-компонентам браузеров, использующим JavaScript. API прост, точен и элегантен и может использоваться по вашему усмотрению. Дайте мне знать в комментариях о том, как вы собираетесь использовать PDF.js в своем следующем проекте!