Storing Annotations in an Xps Document

Learn how to store your Annotations directly in an Xps document file, oh, and I’ll show you some tricks for creating Xps documents from arbitrary clipboard content too…

In my first post I gave a high level overview of how to enable Annotations on any arbitrary document viewer.  In that example we wrote our annotations out to a static file on disk called “annotations.xml.”  This approach is going to get old really fast since we are quickly going to loose track of which annotation file goes with which document.  If you are familiar with the Xps document format, you might know that it is very closely related to ZIP format.  In fact, if you rename an .xps file to .zip you can go through and browse its contents with a normal zip browser!  So since an Xps document is actually a zip container we can actually store the annotations for an Xps document directly in the document container.  First I’ll show you how you can create an Xps document from arbitrary clipboard content and then how you can add annotations to this document and store them directly in the Xps file, at the end of this entry I’ll attach code for a simple app that brings these ideas together.

 

Creating an Xps Document from the clipboard contents

Here is a neat and quick way to create an Xps document for pretty much any content you can copy and paste. 

 

The first step is to load the Clipboad contents into a generic text container.  This example shows how we would support loading XamlPackage, Rrf, or PlainText content into a FlowDocument:

 

FlowDocument flowDocument = new FlowDocument();

// Make single column

flowDocument.ColumnWidth = 1000;

// Make left justified.

flowDocument.TextAlignment = TextAlignment.Left;

// Load clipboard content into FlowDocument.

TextRange textRange = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd);

if (Clipboard.ContainsData(DataFormats.XamlPackage))

{

using (Stream stream = (Stream)Clipboard.GetData(DataFormats.XamlPackage))

{

textRange.Load(stream, DataFormats.XamlPackage);

}

}

else if (Clipboard.ContainsData(DataFormats.Rtf))

{

using (Stream stream = new MemoryStream(Encoding.Default.GetBytes((string)Clipboard.GetData(DataFormats.Rtf))))

{

textRange.Load(stream, DataFormats.Rtf);

}

}

else

{

textRange.Text = Clipboard.GetText();

}

 

Now that we have an IDocumentPaginatorSource which contains the clipboard content, we can use an XpsDocumentWriter to create our Xps formatted document.

 

// Create an empty XpsDocument (this will add a Package to the PackageStore).

_xpsDocument = new XpsDocument(dialog.FileName, FileAccess.ReadWrite);

// Open a writer on the document.

XpsDocumentWriter xpsWriter = XpsDocument.CreateXpsDocumentWriter(_xpsDocument);

// Write the FlowDocument out in XPS format.

xpsWriter.Write(((IDocumentPaginatorSource)flowDocument).DocumentPaginator);

 

Storing Annotations in an Xps Document

Now that we have an XpsDocument, we’ll display it in the DocumentViewer control and create an AnnotationService instance for it:

 

Viewer.Document = _xpsDocument.GetFixedDocumentSequence();

AnnotationService service = AnnotationService.GetService(Viewer);

if (service == null)

service = new AnnotationService(Viewer);

 

In order to store the Annotations within the XpsDocument we’ll have to use the Packaging apis (System.IO.Packaging).  Getting a hold of our XpsDocument as a package is easy, we just get it from the PackageStore:

 

Package xpsPackage = PackageStore.GetPackage(_xpsDocument.Uri);

 

Now that we have the Package, we want to query it to see if an Annotation Part already exists.  For this example I’ve invented some path that I want to use the store my annotations, you can store the annotations wherever you would like within the package.  Try and get an existing annotation Part from the package:

 

Uri annotPartUri = PackUriHelper.CreatePartUri(new Uri("AnnotationStream", UriKind.Relative));

PackagePart annotPart = xpsPackage.GetPart(annotPartUri);

 

If no Part exists then we’ll create a new one:

annotPart = xpsPackage.CreatePart(annotPartUri, "Annotations/Stream");

 

Now just enable the AnnotationService on the Part stream we just retrieved/created:

 

AnnotationStore store = new XmlStreamStore(annotPart.GetStream());

service.Enable(store);

 

Ta-da, any annotations that we create will be written directly into the .xps container!

 

I've attached a Visual Studio project which combines the concepts above to allow you to create arbitrary Xps documents that you can annotate.  Once you get it running, just copy some content (like this blog for example) and then click “New”.  It will create an Xps document that you can create Annotations on, and your annotations will be stored directly in the document file.  Here's what it will look like:

 

Enjoy!

 

Fixed missing file in attachment - 4/24/06 3pm

AnnotatingXpsDocuments.zip