Table of contents
Introduction
It shouldn't take long for Javascript to be able to render PDF documents. Meanwhile, the somewhat opposite functionality - executing Javascript in PDF documents - is available for quite a long time already. This article is about this functionality.
Any software contains not only an actively used set of features but also a share of rarely used features. Sometimes latter set can be significant in size. You can think of features of Microsoft Word or your favorite IDE, for example. Most probably, there are enough features in them that you never used.
Portable Document Format is also has many rarely used features. We are all accustomed to the text and images in PDF documents, but this is only a subset of what the format offers. PDF has set of features for creating documents that could change their contents in response to reader actions. One of such features is the ability to use Javascript in PDF documents.
Javascript in PDF is most often used for the following tasks:
- To change document contents in response to some events. For example, to hide part of document before printing or pre-fill some form fields when document is opened.
- To restrict actions of the reader. For example, to validate entered form field values.
- To introduce malicious code in documents.
A Javascript API was developed for PDF viewers to be able to interpret Javascript code. The API was primarily developed for Adobe Acrobat family of products but alternative viewers usually also support a subset of the API.
Let's take a look at some samples.
Hello World
Here is a traditional "Hello World" sample for a start. Please note that I'll use C# and Docotic.Pdf library for the samples. You can download source code of all the samples at the end of the article.
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(\"Hello CodeProject!\", 3);");
pdf.Save("Hello world.pdf");
}
}
}
You should see something like on the following screenshot if you open the PDF created by the sample in Adobe Reader:
So, what's done by the sample? Here is the essential line:
pdf.OnOpenDocument = pdf.CreateJavaScriptAction("app.alert(\"Hello CodeProject!\", 3);");
PDF supports actions. It is something that is done when an event occurs. For example, clicking on a link in a PDF document outline might trigger an action that cause viewer to open a page in the document:
There are different types of actions defined. One of the types is Javascript actions. An action of this type can be created using PdfDocument.CreateJavaScriptAction
method. The method accepts Javascript code. Then the action can be attached to the OnOpenDocument
event. Obviously, the event occurs when document is opened in a viewer.
Here is the Javascript code:
app.alert("Hello CodeProject!", 3);
Static class app
is part of the Javascript API. This class provides methods for communicating with a PDF viewer. In particular, this class contains alert
method with a number of overloads. The alert method can be used to present a modal window with a message. The code above uses the overload with optional second parameter. This parameter specifies an icon to be displayed (3
is a code for Status Icon).
More complex scenarios
Let's try something more pragmatic.
There are many PDF documents with fillable forms. A document with a form can be a deposit account agreement, a visa application form, a questionnaire etc. Such documents can be conveniently filled right in a viewer and printed or saved for later use. Creators of such documents can employ Javascript to farther improve user experience.
Fillable forms often contain date fields. These fields might look like this:
Sure, a creator of a document can just add date field to the document and stop there. However, it would be nice to assist someone who will fill document later by putting current date to the date field. In most cases this will be just what is expected.
Default value for a date field
I won't go into details of how to create a date field. Let's focus on the Javascript part only. So, we need to put current date into a date field (there is three fields actually, one for day, month and year). Here is the script:
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(en){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();
As you can see, there is much more code than in “Hello World” sample. Using a string for all this code is probably not very convenient because of need to escape quotes. And editing such code later might be painful because of lack of formatting. Let's put a text file with code into application resources and use another overload of CreateJavaScriptAction
method.
pdf.OnOpenDocument = pdf.CreateJavaScriptAction(Resources.SetCurrentDate);
When opened, a document with the script should look like the following:
Take a closer look at following part of the code:
monthField.value = util.printd("date(en){MMMM}", date, true);
Please notice that I am using an overload of util.printd
method to get localized name of the month. This works as expected in Adobe Reader but, unfortunately, other PDF viewers might not support all built-in methods of Javascript API. This should be taken into account if you are going to support anything other than Adobe Reader. Another approach is to implement custom code that does the same as util.printd
.
Please also notice that code populates a field only if it's empty. Without this additional check a following scenario might occur: a person fills the document, saves it but next time the document is opened all date fields are reset with current date.
Validation of data
Suppose you want to ensure that day and year are numbers. It's easy. Let's use following code for this:
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);
PDF fields fire OnKeyPress
event whenever something is typed in a field. You might want to create an action with the validation code and attach it to OnKeyPress
event of fields.
PdfJavaScriptAction validateNumericAction = m_document.CreateJavaScriptAction(Resources.ValidateNumeric);
dayTextBox.OnKeyPress = validateNumericAction;
yearTextBox.OnKeyPress = validateNumericAction;
After that a person filling the form won't be able to put anything other than numbers in day and year fields. Anything getting pasted from clipboard will also be validated.
Synchronization of data
Quite often same data should be put several times in different parts of a form. In such cases some Javascript code can help to eliminate the need for this repetitive efforts.
Assume that there is a document like this:
It would be nice to have both fields synchronized.
It's possible with following Javascript method:
function synchronizeFields(sourceFieldName, destinationFieldName) {
var source = this.getField(sourceFieldName);
var destination = this.getField(destinationFieldName);
if (source != null && destination != null) {
destination.value = source.value;
}
}
The Javascript method could be used like this:
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");
This will ensure that data in both fields is the same at all times. The data is synchronized whenever any field looses focus. Please notice that synchronizeFields
method is put is shared scripts collection (PdfDocument.SharedScripts
). This is done to use the same code from several actions.
Summary
As you can see, Javascript can be used not only in web development. With some additional efforts a PDF form with Javascript code can be created. And such form could please those who will fill it almost like an application with a well thought-out UI.
Don't get me wrong, most important part of a PDF file is its content. Javascript is just a nice addition. And there are some imitations:
- Writing Javascript code for PDF files is not as convenient as for web pages
- There is incomplete support for Javascript API in PDF viewers other than Adobe Reader
- Execution of scripts can be disabled in a viewer
Also, Javascript in PDF files is a security threat. From time to time different vulnerabilities are found (and fixed) in viewers. Knowing all that don't be surprised if an opened PDF file will offer you a chess game to distract you from malicious acts getting performed in background. " />
Download sample code