Pull to refresh

Javascript в PDF

Reading time 6 min
Views 58K
Недалек тот час, когда PDF документы можно будет полноценно отображать средствами Javascript. При этом обратная возможность, а именно использование Javascript в PDF документах, существует уже очень давно. Об этом и пойдет речь в данной статье.

Любое ПО содержит некоторый активно используемый функционал и значительную долю редко используемого функционала. Можно прикинуть, все ли возможности операционной системы (или Microsoft Word, или своей IDE) мы используем в повседневной работе или вообще использовали хоть раз в жизни. Как правило, далеко не все.

Формат PDF не является исключением. Все мы привыкли к тексту и изображениям в PDF документах, однако это лишь малая часть того, что можно использовать. В частности, формат PDF включает в себя разнообразные возможности для создания документов с динамическим контентом, зависящим от читателя и его действий. Одной из таких возможностей является использование Javascript.

Javascript в PDF чаще всего применяется для следующих задач:
  • Для изменения содержимого документа в зависимости от некоторых событий. Например, скрыть часть документа при отправке на печать. Или при открытии документа автоматически заполнить часть полей формы.
  • Для ограничения действий читателя. Например, для валидации вводимых значений при заполнении форм.
Чтобы просмотрщики PDF могли интерпретировать Javascript код, определен специальный Javascript API. Изначально API был разработан только для семейства продуктов Adobe Acrobat, но альтернативные просмотрщики, как правило, также поддерживают некоторое базовое подмножество данного API.

Рассмотрим ряд практических примеров использования 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.alert("Привет, Хабр!", 3);

Статический класс 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){MMMM}", 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){MMMM}", 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;
	}
}

А также следующий C# код:

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 в PDF не столь высока. Кроме того, с этим связан еще ряд проблем:
  • Сложнее писать Javascript код, чем в случае обычной web-разработки. Нужно создать и открыть документ, чтобы проверить корректность написанного кода.
  • Javascript полноценно поддерживается лишь просмотрщиками от Adobe. В альтернативных просмотрщиках поддержка существенно ограничена либо отсутствует вообще.
  • Javascript может быть отключен в просмотрщике PDF.
  • Теоретически исполнение Javascript скриптов в документе небезопасно, и периодически обнаруживаются различные уязвимости.
Вооруженные описанной информацией, не удивляйтесь, если однажды открывшийся PDF документ предложит сыграть в шахматы, а сам в это время по-тихому удалит Foxit Reader из системы ;)

Скачать код примеров из статьи можно здесь.
Tags:
Hubs:
+41
Comments 21
Comments Comments 21

Articles