State Street Gang
.NET, straight up

Creating an XPS document in memory via the DOM

March 14, 2008 12:09 by Will

Most of the examples out there covering the creation of XPS documents use the file system as a backing store.  This isn't optimal; in a server situation its downright stupid.  So, how do you create an XPS document in memory?

I've created a sample project (VS 2005) with the code in this article.  Jump down to the bottom of the post to download it and follow along.

A word (or many) about Packages

XPS documents use the Open Packaging Conventions, which are a shared description of how content can be organized within a package.  A package, in this case, is a collection of other content types that can be addressed as a single object.  Content can be accessed within a package via URIs, loaded on demand rather than all at once.  An advantage of this can be seen when accessing a document over a network. 

Office 2007 documents adhere to these conventions.  For example, a word document contains not only the text of the document but the fonts, images, edit history, etc.  All of these parts are different, but they are combined together within the package to create the whole document.

XPS documents are the same.  The in-memory representation of an XPS document works closely with its Package to manage its content.  Packages, as mentioned before, are collections of parts. These parts may be local, or they may be located on another server.  Because of this, loading of package parts can be slow.  To speed up loading of package parts, the PackageStore is used to manage references to these parts and perform local caching. 

So, when creating XPS documents in memory, what we are actually doing is saving parts of the XPS document to a package managed by the PackageStore.  It is due to the flexibility and the power of the design that our job is a bit more complex than just new-ing up an XPS document and adding children to it.

Enough, how about some code?

The first steps to creating an XPS document in memory are to create an XpsDocument and a MemoryStream.  The XpsDocument object controls how parts are added to the package, and the package stores these parts in the MemoryStream.  Remember, the MemoryStream must be disposed when you are done with it, and you are done with it when you are done with the XPS document; either because you are discarding it or because you have written it to whatever backing store you are using (disk, database, etc).

XpsDocument doc;
ms = new MemoryStream(); 

Next, we open a Package on the MemoryStream.  We must tell the Package that we are creating a new document on this stream, and that we wish to be able to read and write to the document.  I wish these'd be called StreamMode and StreamAccess, but since streams came from the file system originally, these enums bear the marks of their heritage.  In addition, we create a PackageStore to manage our Package.  This uses a URI to reference our XPS document, so we create one using the "pack:" URI scheme, since the document is located in a local Package.  The same URI marks our XPS document

Package p = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
Uri DocumentUri = new Uri("pack://document.xps");
PackageStore.AddPackage(DocumentUri, p);
doc = new XpsDocument(p, 
	CompressionOption.NotCompressed, 
	DocumentUri.AbsoluteUri);

Creating our DOM in memory is the next step.  Its pretty simple to understand, for the most part.

XPS documents contain one or more FixedDocuments within a "fixed document sequence," which is an ordered collection of individual FixedDocuments.  Each FixedDocument contains one or more pages, represented by PageContent objects.  Each PageContent can have one single child; since XPS documents are designed for fixed layouts, the most obvious type to use as a child is the FixedPage.  For some reason, you cannot add a FixedPage directly to a PageContent; you must first explicitly cast the content page as IAddChild.  I'm not sure exactly why this is.  If anybody has a clue, a comment would be nice!

FixedDocument fd = new FixedDocument();
PageContent pc = new PageContent();
fd.Pages.Add(pc);
FixedPage fp = new FixedPage();
((IAddChild)pc).AddChild(fp);
TextBlock tb = new TextBlock();
tb.Text = "Page one";
fp.Children.Add(tb);

Once the FixedDocument is constructed in memory it can be serialized to the package's stream via the XpsDocumentWriter.

XpsDocumentWriter dw =
	XpsDocument.CreateXpsDocumentWriter(doc);
dw.Write(fd);

And that's it.  The only thing to worry about is our MemoryStream object.  Once its disposed, our PackageStore cannot access the parts within the XPS document package.  This means that as long as you wish to manipulate the XPS document you've just created (including viewing it), you must keep the stream open.  Once you are done with the XPS document (e.g., its being discarded or it has been saved to disk) you can dispose of the stream.

The code for this post is included in a working sample project here:


Tags:
Categories: WPF | XAML | XPS
Actions: E-mail | Permalink | Comments (4) | Comment RSSRSS comment feed

Related posts

Comments

March 20. 2008 13:39

JC

This prints out 2 blank pages for me. D'oh! Thank you for the work though, it's certainly enough to get me started!

JC

March 29. 2008 13:06

Will

I may have messed up part of the project... You might have to place the textblock in a container prior to adding it to the fixeddocument. Try creating a Canvas that is the full width and height of the FixedDocument and then add the TextBlock to the Canvas.

Will

May 20. 2008 20:26

Collin

I just followed the example, but I have documentviewer control on my form. I works perfectly. But when I print the example, it was a blank page. It turns out that the example put the text output in the margin of the FixedPage.

Collin

May 21. 2008 08:30

Will

Thanks for the heads up. I'll fix the example soon.

Will

Comments are closed