PDF for Silverlight:
Leveraging the power of Silverlight to view PDF
documents and forms
By: Dany Amiouny
|

|
Using
Microsoft Silverlight, developers can provide their
users with content-rich web applications that are not limited to text and
images, and which can now include complex graphics and better interaction with
the users. While reading some MSDN articles about the power of Silverlight and the types of applications that can be built
with it, I came across Jeff Prosise’s article in the
May 2008 MSDN issue. It struck me that the author had converted his sample
documents into poor quality Jpeg images in order to demonstrate the “Page-Turn” Silverlight application. A more natural choice for me
would be to use high quality PDF documents that would have been easily readable
by the users. Now that PDF has become the standard format for document storage
and forms processing in most corporations, I thought that Silverlight applications would benefit from the ability to natively serve PDF documents and
forms.
In this
article, I will show how to use the Amyuni PDF components to dynamically view
PDF documents within a Silverlight control. I will
adapt the page turn framework to viewing PDF, XPS or any type of document
rather than Jpeg images. The documents are located on the server and viewed by the
client within a Silverlight control without having to
download any files or components to the client. The framework that is presented can be easily expanded to add inter-activity when the PDF
files contain interactive form fields.
The Amyuni PDF Creator and Converter versions
3.5 components are needed to run the sample and can be found at http://www.amyuni.com/silverlight.
The sample can also be run directly from the same location.
Requirements (Server Side)
The sample
that is given is based on ASP but any programming environment that supports
either ActiveX or .NET can be used.
Requirements (Client Side)
Any web
browser running on any operating system with the Silverlight control version 1.0 or higher. No additional components need to be downloaded
or run on the client PC.
Implementation
At first
glance, viewing PDF documents within a Silverlight control looked like a 15-minute job. It would be sufficient to convert the PDF
document into an XPS, which is a derivative of XAML, feed the XPS to the Silverlight control, add some bells and whistles and be
done. Converting a PDF, or any document for that
matter, into an XPS can easily be achieved with the Amyuni PDF Creator; all that
was needed is a way to feed the XPS into the Silverlight control. I was, however, faced with a number of challenges:
1)
Silverlight supports reading only a single XAML file. There is no mechanism by which one can feed multiple pages separately, nor is there a way to
feed all the resources such as images and fonts used by that page. Silverlight provides no neat way of packaging a document
the same way PDF and XPS do but requires all bits and pieces of a document to
be created separately on the server and downloaded programmatically using the
calling application. This precludes the ability to dynamically stream a
document from a server to a client, which is a basic requirement of most
applications.
2)
Inconsistencies
in the Silverlight object model made trial and error
the most time-consuming part of building the sample. Examples of
inconsistencies:
a.
Accessing
the width of a canvas object is done using “elem.Width”,
assuming that “elem” is an object of type Canvas.
Accessing the left position using “elem.Left” returns
an error and should be replaced with “elem.GetValue(“Canvas.Left”).”
b.
With
complex objects such as the Path object, it is difficult to figure out how to
access a specific attribute. In the following object:
<Path
Opacity=“1.00”><Path.Fill><ImageBrush ImageSource=“image1.jpeg”/>
To
access the Opacity, one can simply use “elem.Opacity”.
To access the ImageSource, the right syntax is “elem.Fill.ImageSource”; the ImageBrush element should be skipped altogether which did not make sense to me.
3)
The
image object provided by Silverlight is limited in that
it does not allow for inline image data. The only way to specify the source for
an image is by using a URL. To extract an image from a PDF and set it as the
source for a Silverlight image object would require
extracting the image into a temporary file on the server and setting the source
of the image as the temporary URL - a solution that is not feasible in any
real-life situation.
The basic
requirements for building a document viewer using Silverlight would be:
- The ability to feed each page separately to account
for large documents and to prevent having to download the whole document
to the client PC before viewing it.
- The ability to package page resources such as images
and fonts within the document without having to store and retrieve the
resources from the server.
- The ability to use fonts that are not installed on
the client PC but are embedded within the source document.
- The ability to send additional information known as document metadata to the client.
All these
requirements are accounted for by the XPS specifications, and it might have
made sense for Microsoft to support XPS directly into Silverlight.
As this is not the case, I had to build a modified XPS package with XAML
instructions that are supported by Silverlight and
write all the code that is needed to feed the modified XPS into the Silverlight component.
For the complete PDF for Silverlight sample, including a test web page, source-code
and the Amyuni PDF Suite product needed to run the sample, please visit: http://www.amyuni.com/silverlight.
Using the Downloader Object
The
Downloader object provided me with the solution to a number of the
requirements. This Downloader allows for packaging multiple objects in a zip
package and extracting each object separately on the client PC. Within the ZIP
package, each page description can be stored as a separate XAML file and all
resources used by that page stored in a virtual folder. Downloading the objects
is also asynchronous, meaning that the user does not have to wait for a large
document to be fully downloaded. What I found most useful about the Downloader object
is its ability to refer to dynamic pages containing scripts, and not only
static files. This means that the ZIP package can be dynamically created on the
server and streamed back to the client without resorting to temporary files,
which is exactly what I needed. The basic syntax for using the Downloader is as
follows:
function onPageLoad(control, context,
root)
{
//
Instantiate the downloader object and store a reference for later use
_downloader =
_pdfView.createObject('downloader');
// Add
an event listener to detect when data is available
_token2 =
_downloader.addEventListener('completed',
downloadCompleted);
//
Request a PDF document from the Server
// The
Amyuni PDF Creator will be used on the server to load the PDF
// and
return it in a ZIP package similar in format to XPS
_downloader.open('GET', '/PageTurnPdf/pdf.asp?PDFFile=doc.pdf');
//
Begin asynchronous download of the zip package
_downloader.send();
}
function downloadCompleted(sender, args)
{
//
Retrieve the XAML description of the document from the downloaded package file
var Document =
sender.getResponseText('Document/document_1.fdoc');
// Doanload the XAML
description of a specific page
var Xaml =
_downloader.getResponseText('Pages/page_' +
page + '.fpage');
...
Extracting and Rendering Images
Downloading
the XAML description of the page to the client is not sufficient to display the
images. An additional step is needed to loop through all the image objects and
set the source for every image to one of the images in the ZIP package. Each
time a new page is loaded, “ProcessElements” is called. This function searches
for all images on a page and sets their “ImageSource” attribute:
function ProcessElements(elems)
{
// process all page elements to set:
// - the source of all
image elements
// - the font of all
text elements
//
for ( i = 0; i < elems.Count; i++ )
{
var elem = elems.getItem(i);
var src = elem.Fill.ImageSource;
if ( src != null && src != "" )
{
elem.Fill.setSource(_downloader, src.substring(1));
}
// loop
recursively through all children of the current element
ProcessElements(elem.children);
...
}
}
“Try/Catch” blocks are not shown here but are
needed as we do not know in advance if an object has an “ImageSource”
or a “Children” attribute.
Rendering Text
Accurately
positioning and rendering of text elements proved to be trickier than graphics
and images. PDF provides a number of ways for defining text encodings and font
types. Both XPS and XAML are limited to Unicode text and XAML provides much
less flexibility than its counterparts for rendering text. To alleviate text
issues, Amyuni PDF components provide the developers with methods to optimize
the text content of a page and convert all text to Unicode. The “OptimizeDocument” method is used on the server to optimize
text content before attempting to render it in a Silverlight control:
' Optimize the document to line level in order to improve the
XAML export
objPdf.OptimizeDocument 1
When the font
used in the source document is not available on the client PC, Silverlight would substitute with a different font which
usually gives results that are not satisfactory. Silverlight provides a mechanism for specifying the font used to display a specific
element, as long as the font is in OpenType or
TrueType format. The Downloader object can be used in this case to package all
the fonts used by the document. The fonts are embedded in the ZIP package as
partially embedded fonts that are not usable by the end-user in order to avoid
font licensing issues. Some font manufacturers might still limit the use of
licensed fonts in which case it is recommended that the documents do not use
any licensed fonts.
In parallel
to the image processing described above, the sample viewer loops through all
text objects and sets the font source for each object to be one of the fonts in
the ZIP package:
// set the font source for all text elements to fonts
retrieved from the ZIP package
if ( elem.toString() == "TextBlock" )
{
elem.setFontSource( _downloader
);
}
The text
element’s “FontFamily” attribute maps the text’s font
to one of the fonts packaged in the ZIP file. This is done automatically by the Silverlight component when the requested font is not
located on the end-user’s system.
Server Side Scripts
The
server-side code that converts the document into XAML and builds the ZIP
package is very straight-forward and uses the Amyuni PDF Creator ActiveX:
<%
' Set the returned content type to be a ZIP package
Response.ContentType = “application/x-zip-compressed”
' Get the PDF filename requested by the user
strFilePath = Request.QueryString( “PDFFile” )
' Create the PDF Creator ActiveX object and open the document
' This will throw an exception if the control is not
installed
' or the document not found
Set objPdf = Server.CreateObject(“PDFCreactiveX.PDFCreactiveX”)
objPdf.Open Server.MapPath( strFilePath ), ““
' Optimize the document to line level in order to improve the
XAML export
objPdf.OptimizeDocument 1
' return the XAML attribute of the document object which is a
ZIP package
Response.BinaryWrite objPdf.ObjectAttribute(
“Document”, “XAML” )
Set objPdf = Nothing
%>
Expanding the Sample to View Documents
other than PDF
The
server-side script can easily be expanded in order to render other types of
documents. To render XPS documents, the document should be loaded by the PDF
Creator control and saved back into XAML to make it compatible with Silverlight. This can be done by replacing the PDF file
with an XPS in the server-side script, i.e. no code changes are needed.
To render
other types of documents, they should be converted first into PDF using the
Amyuni PDF Converter, then streamed back to the client as a ZIP package. The
PDF Converter product is available for download from: http://www.amyuni.com/silverlight.
A sample for converting documents into PDF on a server can be found at:
http://www.amyuni.com/en/support/technotes.html.
About the author:
Dany Amiouny
started the development of the Amyuni PDF tools back in 1998. These tools are
now embedded into thousands of applications and millions of desktops worldwide,
and maintained by a team of experienced software developers. An expert in
document conversion and processing, Dany is the CTO of Amyuni Technologies and
provides consulting services to corporations worldwide. Dany holds a “Bachelor
of Engineering” degree from the American University of Beirut and a “Masters in
Business Administration” degree from McGill University. He can be reached by
email at dany.amiouny@amyuni.com.