Bruno Lowagie se dévoile dans iText in Action
Conférence iText in action par son créateur
Jeudi 10 mars, Bruno Lowagie, l’auteur de la librairie Java PDF iText, sera exceptionnellement en France pour vous présenter les nouveautés de la deuxième édition de son livre “iText in Action”.
A cette occasion, Bruno vous guidera à travers la table de contenu à l’aide d’une série d’exemples inédits. 5 exemples seront examinés plus en détail:
- Comment utiliser des templates pour générer des factures,
- La difference entre les technologies ‘AcroForm’ et ‘XFA’ pour les formulaires,
- Utiliser du contenu optionnel dans un document PDF,
- Les avantages de créer un fichier “portable collection”, et
- Comment (et pourquoi) intégrer une application Flash dans un fichier PDF.
Pour finir, Bruno parlera de ses futurs projets, entre autres en faisant la démonstration d’une application ‘Hello World’ sur Android.
Ce sera l’occasion unique d’échanger sur ces sujets avec Bruno Lowagie.
Inscrivez-vous dès maintenant à la conférence!
Exclusif : un extrait de la seconde édition d’iText in Action!
Nous sommes très heureux de vous offrir en avant-première un article tiré d’iText in Action, Seconde Edition. Cet article présente l’intégration d’un contenu Flash dans un document PDF, et les intérêts que cela peut avoir.
Nous espérons que vous apprécierez ce texte autant que nous, un grand merci à Bruno et à Manning d’avoir bien voulu diffuser un extrait. Si vous appréciez ce texte, n’hésitez pas à acheter le livre, qui à Zenika est l’un des plus empruntés à la bibliothèque. Merci Bruno encore.
This article is taken from the book iText in Action, Second Edition. The author discusses a rich media annotation that embeds a Flash application into a PDF document.
You can embed a Flash application (a .swf file) into a PDF document using a movie annotation. This works well for a Flash movie, but if you want to embed a Flash application, you’ll discover that the interactive features are rather limited. If you want to take advantage of all the functionality of a Flash application, you’ll need to embed the .swf file as a rich media annotation. This was the case for the PDF shown in figure 1.
Figure 1 Integrating a Flash application in a PDF document
The combo box with dates, the button for selecting a day, and the table listing screenings on a particular day are all part of a Flash application written in Flex.
Writing a Flex application
This listing shows the source code of the Flex application.
Listing 1 FestivalCalendar1.mxml
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" applicationComplete="stage.scaleMode = StageScaleMode.EXACT_FIT; #1 initList(Application.application.parameters.day);"> #2 <mx:Script> <![CDATA[ private function initList(day:Object):void { #A days.selectedItem = day; #A getDateInfo(days.selectedItem); #A } #A private function getDateInfo(day:Object):void { #B screeningsService.url= #B 'http://flex.itextpdf.org/fff/day_' #B + day + '.xml'; #B screeningsService.send(); #B screeningsDataGrid.invalidateList(); #B } #B ]]> </mx:Script> <mx:HTTPService #C id="screeningsService" resultFormat="e4x" /> #C <mx:Grid id="formgrid"> <mx:GridRow id="row1"> <mx:GridItem> <mx:ComboBox id="days" dataProvider="{[ #D '2011-10-12', '2011-10-13', '2011-10-14', #D '2011-10-15', '2011-10-16', '2011-10-17', #D '2011-10-18', '2011-10-19' ]}" /> #D </mx:GridItem> <mx:GridItem> <mx:Button id="date" label="Select day" #E click="getDateInfo(days.selectedItem);" /> #E </mx:GridItem> </mx:GridRow> <mx:GridRow id="row2"> <mx:GridItem colSpan="2"> <mx:DataGrid id="screeningsDataGrid" #F dataProvider= #F "{screeningsService.lastResult.screening}"> #F <mx:columns> #F <mx:DataGridColumn #F headerText="Time" dataField="time"/> #F <mx:DataGridColumn headerText="Location" #F dataField="location"/> #F <mx:DataGridColumn headerText="Duration" #F dataField="duration"/> #F <mx:DataGridColumn #F headerText="Title" dataField="title"/> <mx:DataGridColumn headerText="Year" dataField="year"/> </mx:columns> #F </mx:DataGrid> #F </mx:GridItem> </mx:GridRow> </mx:Grid> </mx:Application> #A Initializes date #B Gets data for specific day #C Fetches XML file #D Displays combo box with dates #E Displays button to trigger getDateInfo method #F Displays grid containing screening info
This listing is easy to understand even if you’ve never written a Flex application. There are two methods written in ActionScript inside the Script tag. The object defined with the HTTPService tag will be responsible for making a connection to a site and retrieving data about screenings in the form of an XML file (resultFormat=”e4x”).
The layout of the UI is defined using a Grid containing two GridRows. The first row has two GridItems: a ComboBox with days ranging from 2011-10-12 to 2011-10-19, and a Button with the label “Select day”. The item in the second row has colspan 2, and contains a DataGrid with five DataGridColumns: Time, Location, Duration, Title, and Year. The data provider for this data grid is the last result of the HTTPService with id screeningsService.
This .mxml file was compiled into an .swf file using Flex Builder. This .swf file can be embedded into an HTML file, but you’re going to integrate it into a PDF document.
Fetching XML data from a server
This example shows the XML file that is fetched by this service for October 12.
Listing 2 http://flex.itextpdf.org/fff/day_2011-10-12.xml
<day date="2011-10-12"> <screening> <location>GP.3</location> <time>09:30:00</time> <duration>98</duration> <title>The Counterfeiters</title> <year>2007</year> </screening> <screening> <location>GP.3</location> <time>11:30:00</time> <duration>120</duration> <title>Give It All</title> <year>1998</year> </screening> ... </day>
You’ve indicated that you’re looking for screening nodes in the dataProvider of the DataGrid. As a result, the data grid will have a line for every screening tag in the XML, containing the contents of the dataField defined in the DataGridColumn.
To make this work, you need to put XML files for every date in the combo box in the appropriate place on our web server, but this may not be sufficient. This will work for HTML and .swf files that are hosted on the same domain as the data files, but it won’t work in a PDF that is opened on somebody’s local machine. The Flash player that runs the Flash application—in a browser, or in a PDF viewer—operates in a secure sandbox. This sandbox will prevent the application from accessing the user’s filesystem, and from fetching data from a remote website.
In this case, the Flex application won’t be allowed to access the XML files outside the domain to which the application is deployed, unless the owner of the site where the XML files reside allows it. If you open a PDF containing this Flex application locally, you are not on the http://flex.itextpdf.org/ domain, and Adobe Reader will open a dialog box with the following security warning:
The document is trying to connect to http://flex.itextpdf.org/crossdomain.xml. If you trust the site, choose Allow. If you do not trust the site, choose Block.
The next bit of code shows the contents of the crossdomain.xml file that I had to put at the root of the flex.itextpdf.org domain in order to grant access to any Flex application from any domain.
Listing 3 crossdomain.xml
<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="all"/> <allow-access-from domain="*" /> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy>
If such a file isn’t there, or if it doesn’t allow everyone access, the Flex application won’t be able to retrieve the data.
Even with the crossdomain.xml file in place, Adobe Reader will show a security warning every time an XML file (for instance,
http://flex.itextpdf.org/fff/day_2011-10-12.xml) is fetched, unless you check the Remember My Action for This Site check box.
NOTE Most SWF files that can be found on the market are written to be embedded in HTML files. In theory, you can embed all these files in a PDF document. However, if the SWF files were created using Flex Builder, you may experience problems when zooming in and out, or when printing a page that has a rich media annotation. Thes
e problems are caused by the default scale mode. To avoid them, you need to change the scale mode as is done in line #1 in listing 1: stage.scaleMode = StageScaleMode.EXACT_FIT. This is important if you buy a Flash component that was written using Flex Builder and that was intended for use in HTML. You need to make sure the vendor has taken this into account if you want to use the .swf in a PDF document.
Now that you know how the Flex application was written, let’s look at how you can integrate it into a PDF document.
Rich media annotations
Rich media annotations aren’t part of ISO-32000-1. Support for these annotations was added by Adobe in PDF 1.7 extension level 3. In this case, it isn’t sufficient to change the version number to 1.7 with setPdfVersion(); you also have to set the extension level with the addDeveloperExtension() method. You can do this more than once if you’re using extensions from different companies.
The method expects an instance of the PdfDeveloperExtension class. In listing 4, you use the static final object ADOBE_1_7_EXTENSIONLEVEL3. This value was created like this:
new PdfDeveloperExtension(PdfName.ADBE, PdfWriter.PDF_VERSION_1_7, 3)
The first parameter refers to the developing company. The second parameter indicates for which PDF version the extension was written. Finally, you pass in the number of the extension level as an int.
Listing 4 FestivalCalendar1.java
Document document = new Document(); PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(RESULT)); writer.setPdfVersion(PdfWriter.PDF_VERSION_1_7); #A writer.addDeveloperExtension( #A PdfDeveloperExtension.ADOBE_1_7_EXTENSIONLEVEL3); #A document.open(); RichMediaAnnotation richMedia #B = new RichMediaAnnotation(writer, #B new Rectangle(36, 400, 559,806)); #B PdfFileSpecification fs = #C PdfFileSpecification.fileEmbedded( #C writer, RESOURCE, "FestivalCalendar1.swf", null); #C PdfIndirectReference asset #C = richMedia.addAsset("FestivalCalendar1.swf", fs); #C RichMediaConfiguration configuration #D = new RichMediaConfiguration(PdfName.FLASH); #D RichMediaInstance instance #D = new RichMediaInstance(PdfName.FLASH); #D RichMediaParams flashVars = new RichMediaParams(); #D String vars = new String("&day=2011-10-13"); #D flashVars.setFlashVars(vars); #D instance.setParams(flashVars); #D instance.setAsset(asset); #D configuration.addInstance(instance); #D PdfIndirectReference configurationRef #D = richMedia.addConfiguration(configuration); #D RichMediaActivation activation = new RichMediaActivation(); #E activation.setConfiguration(configurationRef); #E richMedia.setActivation(activation); #E PdfAnnotation richMediaAnnotation = richMedia.createAnnotation(); richMediaAnnotation.setFlags(PdfAnnotation.FLAGS_PRINT); writer.addAnnotation(richMediaAnnotation); document.close(); #A Sets version and extension level #B Creates annotation object #C Adds .swf file as asset #D Configures RichMedia annotation as Flash app #E Sets activation dictionary
The RichMediaAnnotation class isn’t a subclass of PdfAnnotation, but it can create such an object using the method createAnnotation(). The rich media annotation dictionary contains two important entries: a /RichMediaContent dictionary and a /RichMedia-Settings dictionary. These dictionaries are created internally by iText.
The RichMediaContent dictionary consists of the assets, the configuration, and the views:
- Assets – These are stored as a name tree with embedded file specifications. You can use the addAsset() method to add entries to this name tree.
- Configuration – This is an array of RichMediaConfiguration objects. Such an object contains an array of RichMediaInstance objects.
- Views – This is an array of 3D view dictionaries, in case the rich media annotation contains a 3D stream.
The RichMediaConfiguration dictionary describes a set of instances that are loaded for a given scene configuration. In this example, you use a rich media annotation to embed a Flash application, but you can also use such an annotation for 3D, sound, or video objects.
The constructors of the RichMediaConfiguration and RichMediaInstance classes accept the following parameters:
- PdfName._3D—For 3D objects
- PdfName.FLASH—For Flash objects
- PdfName.SOUND—For sound objects
- PdfName.VIDEO—For video objects
A RichMediaInstance dictionary describes a single instance of an asset with settings to populate the artwork of an annotation. In this example, you only have one Flash instance, for which you define /FlashVars: &day=2011-10-13. The day variable is retrieved in line #2 of listing 1: Application.application.parameters.day.
NOTE If you want to reuse the RichMediaContent dictionary in more than one rich media annotation, you have to create the first RichMediaAnnotation as is done in listing 4. You can then get a reference to the RichMediaContent dictionary with the getRichMediaContentReference() method, and use this reference as an extra parameter for the RichMediaAnnotation constructor.
Rich media annotations can be active or inactive. The RichMediaSettings dictionary stores conditions and responses that determine when the annotation should be activated and deactivated. iText creates this dictionary automatically, just like the RichMediaContent dictionary. It can contain a RichMediaActivation dictionary that is set with the method setActivation(), and a RichMediaDeactivation dictionary set with set-Deactivation(). Listing 4 uses the default activation and deactivation conditions.
The possible conditions for activation—set with setCondition()—are:
- PdfName.XA—The annotation is explicitly activated by a user action or script; this is the default.
- PdfName.PO—The annotation is activated as soon as the page that contains the annotation receives focus as the current page.
- PdfName.PV—The annotation is activated as soon as any part of the page that contains the annotation becomes visible.
These are the possible conditions for deactivation—also set with setCondition():
- PdfName.XD—The annotation is explicitly deactivated by a user action or script; this is the default.
- PdfName.PC—The annotation is deactivated as soon as the page that contains the annotation loses focus as the current page.
- PdfName.PI—The annotation is deactivated as soon as the entire page that contains the annotation is no longer visible.
In the RichMediaActivation dictionary, you can also add keys to specify the animation, the view, presentation, and scripts.
This first “Flash in PDF” example is cool because you have a PDF document that presents data to the end user that isn’t part of the PDF document. I know from experience that the schedule of screenings at a film festival can change at any moment, because the film stock didn’t arrive on time or some other reason. By using this Flash application written in Flex, the document can always show the most recent information fetched from the official film festival website.
You could use the same technique to get the most recent items and prices to complete an order form in PDF. To achieve this, you’d need to establish communication between the embedded Flex application and the PDF document.
Figure 2 shows a series of widget annotations (PDF), and one rich media annotation (Flash). The sentence, “This is the festival program for 2011-10-14”, is shown using a read-only text field. The text is updated using a JavaScript method that is triggered by the rich media annotation. The buttons with the different dates call a Rich-MediaExecuteAction that executes an ActionScript method in the Flash application.
Figure 2 Communication between PDF and Flash
The Flex application, in the next listing, is different from the previous example.
Listing 5 FestivalCalendar2.mxml
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="525" height="200" applicationComplete= "stage.scaleMode = StageScaleMode.EXACT_FIT;init();"> <mx:Script> <![CDATA[ import flash.external.*; #1 private function init():void { ExternalInterface.addCallback("getDateInfo", getDateInfo); #2 } private function getDateInfo(day:Object):void { screeningsService.url= 'http://flex.itextpdf.org/fff/day_' + day + '.xml'; screeningsService.send(); screeningsDataGrid.invalidateList(); ExternalInterface.call("showDate", day); #3 } ]]> </mx:Script> <mx:HTTPService #A id="screeningsService" resultFormat="e4x" /> #A <mx:DataGrid id="screeningsDataGrid" #B dataProvider= #B "{screeningsService.lastResult.screening}" #B width="100%" height="100%"> #B <mx:columns> #B <mx:DataGridColumn #B headerText="Time" dataField="time"/> #B <mx:DataGridColumn #B headerText="Location" dataField="location"/> #B <mx:DataGridColumn #B headerText="Duration" dataField="duration"/> #B <mx:DataGridColumn #B headerText="Title" dataField="title"/> #B <mx:DataGridColumn #B headerText="Year" dataField="year"/> #B </mx:columns> #B </mx:DataGrid> #B </mx:Application> #B #1 Imports package #2 Registers AS method #3 Calls JS method #A HTTP service to fetch XML file #B Grid containing screening info
You’ll recognize the HTTP service that will get the XML files from http://flex.itextpdf.org/ and the data grid that visualizes the XML data. You no longer need the combo box and the button, because you’re going to change the data from outside the Flex application.
You import the flash.external.* package (#1), because you’re going to use the ExternalInterface object. With the addCallback() method (#2), you make the ActionScript method getDateInfo() in the Flex application available for external applications. In this method, you call the JavaScript showDate() method that is supposed to be present in the PDF document by using the call() method (#3).
The showDate() JavaScript method in the PDF is very simple: function showDate(txt) { this.getField("date").value = "This is the festival program for " + txt; }
It gets the text field with the name date, and it changes the value of this field so that it corresponds with the date for which the screenings are shown.
The first part of this next listing should look familiar.
Listing 6 FestivalCalendar2.java
Document document = new Document(); PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(RESULT)); writer.setPdfVersion(PdfWriter.PDF_VERSION_1_7); writer.addDeveloperExtension( PdfDeveloperExtension.ADOBE_1_7_EXTENSIONLEVEL3); document.open(); writer.addJavaScript(Utilities.readFileToString(JS)); #A RichMediaAnnotation richMedia #B = new RichMediaAnnotation( #B writer, new Rectangle(36, 560, 561, 760)); #B PdfFileSpecification fs = PdfFileSpecification #B .fileEmbedded(writer, RESOURCE, #B "FestivalCalendar2.swf", null); #B PdfIndirectReference asset #B = richMedia.addAsset("FestivalCalendar2.swf", fs); #B RichMediaConfiguration configuration #B = new RichMediaConfiguration(PdfName.FLASH); #B RichMediaInstance instance #B = new RichMediaInstance(PdfName.FLASH); #B instance.setAsset(asset); #B configuration.addInstance(instance); #B PdfIndirectReference configurationRef #B = richMedia.addConfiguration(configuration); #B RichMediaActivation activation #B = new RichMediaActivation(); #B activation.setConfiguration(configurationRef); #B richMedia.setActivation(activation); #B PdfAnnotation richMediaAnnotation #B = richMedia.createAnnotation(); richMediaAnnotation.setFlags(PdfAnnotation.FLAGS_PRINT); writer.addAnnotation(richMediaAnnotation); String days = new String{"2011-10-12", #C "2011-10-13", "2011-10-14", "2011-10-15", #C "2011-10-16", "2011-10-17", "2011-10-18", #C "2011-10-19"}; #C for (int i = 0; i < days.length; i++) { #C Rectangle rect = new Rectangle( #C 36 + (65 * i), 765, 100 + (65 * i), 780); #C PushbuttonField button #C = new PushbuttonField(writer, rect, "button" + i); #C button.setBackgroundColor(new GrayColor(0.75f)); #C button.setBorderStyle( #C PdfBorderDictionary.STYLE_BEVELED); #C button.setTextColor(GrayColor.GRAYBLACK); #C button.setFontSize(12); #C button.setText(daysi); #C button.setLayout( #C PushbuttonField.LAYOUT_ICON_LEFT_LABEL_RIGHT); #C button.setScaleIcon( #C PushbuttonField.SCALE_ICON_ALWAYS); #C button.setProportionalIcon(true); #C button.setIconHorizontalAdjustment(0); #C PdfFormField field = button.getField(); #C RichMediaCommand command = new RichMediaCommand( #D new PdfString("getDateInfo")); #D command.setArguments(new PdfString(daysi)); #D RichMediaExecuteAction action #E = new RichMediaExecuteAction( #E richMediaAnnotation.getIndirectReference(), #E command); #E field.setAction(action); writer.addAnnotation(field); } TextField text = new TextField(writer, #F new Rectangle(36, 785, 559, 806), "date"); #F text.setOptions(TextField.READ_ONLY); #F writer.addAnnotation(text.getTextField()); #F document.close(); #A Adds JavaScript #B Adds rich media annotation #C Adds buttons #D Creates rich media command #E Creates rich media action #F Adds text field
In the second half of the previous listing, you add a series of buttons to the document, creating a RichMediaExecuteAction for each button. The action will be triggered on the rich media annotation for which you pass the indirect reference. You also pass a RichMediaCommand.
The name of the action is a PDF string that corresponds to the string you used as the parameter of the ExternalInterface.addCallback() method in the Flex application. The argument can be a PdfString, PdfNumber, PdfBoolean, or PdfArray containing those objects.
You also add a text field named date. When you click one of the PDF buttons, the getDateInfo() method will be called, an XML file containing screenings will be fetched from the internet, filling the data grid, and the Flex application will trigger the showDate() JavaScript method to change the value of the date field.
Although this is a very simple example, the techniques that are used can apply to many different types of applications. You could use these techniques to integrate fancy Flash buttons that trigger functions in a PDF file, or you could embed a Flex application to establish client-server communication to retrieve the most recent data. But don’t forget that this functionality is very new: it only works with the most recent versions of Adobe Reader!
Summary
We discussed rich media annotation. There’s a difference between file attachments that are added as annotations, and file attachments that are stored at the document level as embedded files. This difference matters if you want to extract files from a PDF document. Files that are embedded at the document level can be organized into a portable collection, aka a portfolio.