- Javascript в PDF
- Hello World
- От простого – к сложному
- Устанавливаем дату по умолчанию
- Валидация вводимых значений
- Синхронизация значений полей
- Подытожим
- Pdf with javascript example
- Document
- Page
- Rendering the Page
- Interactive examples
- Hello World with document load error handling
- Hello World using base64 encoded PDF
- Previous/Next example
Javascript в PDF
Недалек тот час, когда PDF документы можно будет полноценно отображать средствами Javascript. При этом обратная возможность, а именно использование Javascript в PDF документах, существует уже очень давно. Об этом и пойдет речь в данной статье.
Любое ПО содержит некоторый активно используемый функционал и значительную долю редко используемого функционала. Можно прикинуть, все ли возможности операционной системы (или Microsoft Word, или своей IDE) мы используем в повседневной работе или вообще использовали хоть раз в жизни. Как правило, далеко не все.
Формат PDF не является исключением. Все мы привыкли к тексту и изображениям в PDF документах, однако это лишь малая часть того, что можно использовать. В частности, формат PDF включает в себя разнообразные возможности для создания документов с динамическим контентом, зависящим от читателя и его действий. Одной из таких возможностей является использование Javascript.
- Для изменения содержимого документа в зависимости от некоторых событий. Например, скрыть часть документа при отправке на печать. Или при открытии документа автоматически заполнить часть полей формы.
- Для ограничения действий читателя. Например, для валидации вводимых значений при заполнении форм.
Рассмотрим ряд практических примеров использования Javascript в PDF документах.
Hello World
Начнем рассмотрение темы с традиционного Hello World примера. В данном и последующих примерах используется язык C# и библиотека Docotic.Pdf для работы с PDF документами. Ссылка на исходники с кодом всех примеров приводится в конце статьи.
using BitMiracle.Docotic.Pdf; namespace JavascriptInPdf < public static class Demo < public static void Main(string[] args) < PdfDocument pdf = new PdfDocument(); pdf.OnOpenDocument = pdf.CreateJavaScriptAction("app.alert(\"Привет, Хабр!\", 3);"); pdf.Save("Hello world.pdf"); >> >
Если открыть созданный этим кодом документ в Adobe Reader, то увидим примерно следующее:
Что же происходит в примере? Суть заключена в строке
pdf.OnOpenDocument = pdf.CreateJavaScriptAction("app.alert(\"Привет, Хабр!\", 3);");
Формат PDF включает поддержку actions – это действия, происходящие по тому или иному событию. Например, когда в оглавлении в некотором PDF документе кликаем на ссылку с номером страницы – срабатывает определенный action для перехода на соответствующую страницу:
Для Javascript также есть соответствующий action. Мы создаем его с помощью метода PdfDocument.CreateJavaScriptAction, которому передаем в качестве параметра JS код. Созданный action мы привязываем к событию OnOpenDocument, происходящему при открытии документа просмотрщиком.
Непосредственно Javascript код выглядит так:
Статический класс app является частью Javascript API и предоставляет набор методов для взаимодействия с приложением-просмотрщиком. В частности, он содержит несколько перегрузок метода alert для показа модального диалога с сообщением. В данном примере используется перегрузка со вторым необязательным параметром – индексом иконки диалога, значение 3 соответствует Status Icon.
От простого – к сложному
Рассмотрим более реалистичный пример.
Многие PDF документы описывают некоторые формы для заполнения – это может быть договор открытия банковского вклада, анкета на получение визы или загран. паспорта, заявление на отпуск и т.п. Довольно удобно, поскольку такую форму можно заполнить прямо в просмотрщике и сохранить или распечатать. Авторы PDF документов, содержащих формы, могут облегчить пользователю их заполнение с помощью Javascript.
Реальные документы часто содержат поля для ввода даты заполнения. Например, это может выглядеть так:
В принципе, создавая документ, можно на этом и остановиться. Однако, можно пойти чуть дальше и немного упростить задачу заполняющему – например, устанавливать дату по умолчанию в текущую – в 99% случаев именно это и необходимо.
Устанавливаем дату по умолчанию
C# код по созданию полей для даты, как на скриншоте, в данном случае не так интересен. Рассмотрим лишь часть, касающуюся Javascript. Необходимо при открытии документа устанавливать в полях текущую дату, делается это так:
function setDay(date) < var dayField = this.getField("day"); if (dayField.value.length == 0) < dayField.value = util.printd("dd", date); >> function setMonth(date) < var monthField = this.getField("month"); if (monthField.value.length == 0) < monthField.value = util.printd("date(ru)", date, true); > > function setYear(date) < var yearField = this.getField("year"); if (yearField.value.length == 0) < yearField.value = util.printd("yyyy", date); >> function setCurrentDate() < var now = new Date(); setDay(now); setMonth(now); setYear(now); >setCurrentDate();
По сравнению с предыдущим примером JS код увеличился в объемах, поэтому использовать его напрямую в C# строке стало неудобно из-за необходимости экранировать кавычки и вставлять переносы строк. Поэтому поместим данный код в ресурсы, тогда использоваться скрипт будет так:
pdf.OnOpenDocument = pdf.CreateJavaScriptAction(Resources.SetCurrentDate);
В результате при открытии документа увидим примерно следующую картину:
Обратите внимание на код для установки месяца – мы используем специфическую перегрузку метода util.printd для вывода локализованного месяца.
monthField.value = util.printd("date(ru)", date, true);
Это дает отличные результаты в Adobe Reader, но, к сожалению, не гарантируется, что другие просмотрщики будут корректно поддерживать столь специфические конструкции. При проектировании документа это нужно учитывать. Возможно, стоит заменить этот код на более многословный (самостоятельное получение названия месяца в нужном падеже), но зато поддерживаемый большим количеством просмотрщиков.
В данном примере также важно то, что значения устанавливаются только в пустые поля. Без этих проверок может возникнуть ситуация, когда пользователь сохранит заполненную форму, а при открытии такой сохраненной формы дата будет изменена на текущую.
Валидация вводимых значений
Если при заполнении формы все-таки необходимо поменять дату, то имеет смысл разрешать ввод только цифр в поля для дня и года. Используем для этого следующий Javascript код:
function validateNumeric(event) < var validCharacters = "0123456789"; for (var i = 0; i < event.change.length; i++) < if (validCharacters.indexOf(event.change.charAt(i)) == -1) < app.beep(0); event.rc = false; break; >> > validateNumeric(event);
В C# коде используем событие OnKeyPress у контролов для проверки вводимого символа:
PdfJavaScriptAction validateNumericAction = m_document.CreateJavaScriptAction(Resources.ValidateNumeric); dayTextBox.OnKeyPress = validateNumericAction; yearTextBox.OnKeyPress = validateNumericAction;
После этого в поля для дня и года будет невозможно ввести любой символ, отличный от цифры. Вставить строку из буфера обмена, содержащую некорректный символ, также не удастся.
Синхронизация значений полей
Часто бывает, что одну и ту же информацию в документе нужно указывать несколько раз. И в случае PDF документов с помощью Javascript можно избавить пользователя от повторения одних и тех же действий.
Предположим, имеется документ следующего вида:
Модифицируем его так, чтобы при изменении одного из полей с ФИО обновлялось и другое.
Используем простую Javascript функцию:
function synchronizeFields(sourceFieldName, destinationFieldName) < var source = this.getField(sourceFieldName); var destination = this.getField(destinationFieldName); if (source != null && destination != null) < destination.value = source.value; >>
PdfDocument pdf = new PdfDocument(“Names.pdf”); pdf.SharedScripts.Add( pdf.CreateJavaScriptAction(Resources.SynchronizeFields) ); pdf.GetControl("name0").OnLostFocus = pdf.CreateJavaScriptAction("synchronizeFields(\"name0\", \"name1\");"); pdf.GetControl("name1").OnLostFocus = pdf.CreateJavaScriptAction("synchronizeFields(\"name1\", \"name0\");"); pdf.Save("NamesModified.pdf");
Теперь при потере фокуса любым из текстбоксов будет обновлено значение другого. Обратите внимание на прием, не встречавшийся ранее, — общий Javascript код помещается в коллекцию PdfDocument.SharedScripts, и далее мы получаем возможность из конкретных action’ов вызывать функцию, определенную в общем коде.
Подытожим
Использовать Javascript можно не только в web-разработке, но и в такой области, как оформление PDF документов. Немного дополнительных усилий, и создаваемые PDF документы порадуют читателя не меньше, чем программа с удобным и продуманным интерфейсом – искушенного пользователя.
- Сложнее писать Javascript код, чем в случае обычной web-разработки. Нужно создать и открыть документ, чтобы проверить корректность написанного кода.
- Javascript полноценно поддерживается лишь просмотрщиками от Adobe. В альтернативных просмотрщиках поддержка существенно ограничена либо отсутствует вообще.
- Javascript может быть отключен в просмотрщике PDF.
- Теоретически исполнение Javascript скриптов в документе небезопасно, и периодически обнаруживаются различные уязвимости.
Скачать код примеров из статьи можно здесь.
Pdf with javascript example
PDF .js heavily relies on the use of Promises. If promises are new to you, it’s recommended you become familiar with them before continuing on.
This tutorial shows how PDF .js can be used as a library in a web browser. examples/ provides more examples, including usage in Node.js (at examples/node/).
Document
The object structure of PDF .js loosely follows the structure of an actual PDF . At the top level there is a document object. From the document, more information and individual pages can be fetched. To get the document:
pdfjsLib.getDocument('helloworld.pdf')
Remember though that PDF .js uses promises, and the above will return a PDFDocumentLoadingTask instance that has a promise property which is resolved with the document object.
var loadingTask = pdfjsLib.getDocument('helloworld.pdf'); loadingTask.promise.then(function(pdf) < // you can now use *pdf* here >);
Page
Now that we have the document, we can get a page. Again, this uses promises.
pdf.getPage(1).then(function(page) < // you can now use *page* here >);
Rendering the Page
Each PDF page has its own viewport which defines the size in pixels( 72DPI ) and initial rotation. By default the viewport is scaled to the original size of the PDF , but this can be changed by modifying the viewport. When the viewport is created, an initial transformation matrix will also be created that takes into account the desired scale, rotation, and it transforms the coordinate system (the 0,0 point in PDF documents the bottom-left whereas canvas 0,0 is top-left).
var scale = 1.5; var viewport = page.getViewport(< scale: scale, >); // Support HiDPI-screens. var outputScale = window.devicePixelRatio || 1; var canvas = document.getElementById('the-canvas'); var context = canvas.getContext('2d'); canvas.width = Math.floor(viewport.width * outputScale); canvas.height = Math.floor(viewport.height * outputScale); canvas.style.width = Math.floor(viewport.width) + "px"; canvas.style.height = Math.floor(viewport.height) + "px"; var transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null; var renderContext = < canvasContext: context, transform: transform, viewport: viewport >; page.render(renderContext);
Alternatively, if you want the canvas to render to a certain pixel size you could do the following:
var desiredWidth = 100; var viewport = page.getViewport(< scale: 1, >); var scale = desiredWidth / viewport.width; var scaledViewport = page.getViewport(< scale: scale, >);
Interactive examples
Hello World with document load error handling
The example demonstrates how promises can be used to handle errors during loading. It also demonstrates how to wait until a page is loaded and rendered.
Hello World using base64 encoded PDF
The PDF .js can accept any decoded base64 data as an array.
Previous/Next example
The same canvas cannot be used to perform to draw two pages at the same time – the example demonstrates how to wait on previous operation to be complete.
©Mozilla and individual contributors
PDF.js is licensed under Apache, documentation is licensed under CC BY-SA 2.5