Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

PDF for Silverlight

30 Jun 2008 1  
Leveraging the power of Silverlight to view PDF documents and forms

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

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:

  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 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.
  2. 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.
  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 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)
{
    // 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 != &quot;&quot; )
        {
            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

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:

' 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() == &quot;TextBlock&quot; )
{
    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:

<%
' 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 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here