Introduction
Read the latest advertorial from Amyuni here
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 4.0 components are needed to run the sample and can be found here. 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:
- 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 that PDF and XPS do, but requires all the 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.
- Inconsistencies in the Silverlight object model made trial and error the most time-consuming part of building the sample. Examples of inconsistencies:
- 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”)
.
- 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.
- 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 this page.
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 file 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)
{
_downloader = _pdfView.createObject('downloader');
_token2 = _downloader.addEventListener('completed', downloadCompleted);
_downloader.open('GET', '/PageTurnPdf/pdf.asp?PDFFile=doc.pdf');
_downloader.send();
}
function downloadCompleted(sender, args)
{
var Document = sender.getResponseText('Document/document_1.fdoc');
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)
{
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));
}
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
Accurate 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:
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:
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 straightforward and uses the Amyuni PDF Creator ActiveX:
<%
Response.ContentType = “application/x-zip-compressed”
strFilePath = Request.QueryString( “PDFFile” )
Set objPdf = Server.CreateObject(“PDFCreactiveX.PDFCreactiveX”)
objPdf.Open Server.MapPath( strFilePath ), ““
objPdf.OptimizeDocument 1
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 this page. A sample for converting documents into PDF on a server can be found here.
- For complete source code and an updated version of the article and the sample, please visit this page.
More from Amyuni