Skip Navigation LinksHome > View Post

Cracking an XPS in WPF

UPDATE: Checkout Merging XPS Documents for more insight on the XPS API and WPF.

I recently pondered on the best way to convert a page into word document into a high-res image (don't ask). Some colleagues suggested a bunch of print based solutions that act as printers and write to an image (FinePrint is one such example). However, I tried the trial of a few and I couldn't get results I was happy with, so - being a developer at heart - I couldn't help but try and roll my own. Naughty boy.

I thought I'd document it here because I know I'll come looking for this code again some time in the future. Who knows, maybe somebody else will find it useful too.

My first leap was to save the word document as an XPS. There are two ways I know of doing this: 1) Install the 'Save as XPS' add-on for Word 2007 or 2) Print to XPS feature via the Microsoft XPS Document Writer.

I chose the former because I suspect the printing gubbins is a layer of indirection that probably creates some crazy looking markup inside the XPS. If our primary concern is rendering to an image then this almost certainly doesn't matter very much but the Save as XPS options seemed more direct and was readily available.

Firstly, lets crack open the XPS document and get it into the WPF world:

string actualPath = @"C:\location\of\your\xps\file.xps";
Uri uri = new Uri(string.Format("memorystream://{0}", actualPath));
FixedDocumentSequence seq;

using (Package pack = Package.Open(actualPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (StorePackage(uri, pack))
using (XpsDocument xps = new XpsDocument(pack, CompressionOption.Normal, uri.ToString()))
{
    seq = xps.GetFixedDocumentSequence();
}


DocumentPaginator paginator = seq.DocumentPaginator;
// I only want the first page for this example
Visual visual = paginator.GetPage(0).Visual;

You'll need to add a reference to the ReachFramework dll for the XpsDocument type. The StorePackage method (called above) is a static method I created to allow me to use a using block to add and remove the Package from the application's PackageStore.

public static IDisposable StorePackage(Uri uri, Package package)
{
    PackageStore.AddPackage(uri, package);
    return new Disposer(() => PackageStore.RemovePackage(uri));
}

Because I use this pattern a lot I have a general Disposer class I use to help me:

public class Disposer : IDisposable
{
    private bool _disposed = false;
    private Action _onDisposal;

    public Disposer(Action onDisposal)
    {
        _onDisposal = onDisposal;
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _disposed = true;
            _onDisposal();
        }
    }
}

Maybe that should have been in a post on its own, hey-ho (Note, the Disposer class isn't thread safe and should be used by a single thread, otherwise the onDisposal delegate could potentially be called twice. On no!).

Back to XPS stuff... Now we've grabbed the visual - we want to save it to a file. I'm going to go PNG for my format.

FrameworkElement fe = (FrameworkElement)visual;

int multiplyFactor = 4;
string outputPath = @"C:\location\of\your\png\file.png";

RenderTargetBitmap bmp = new RenderTargetBitmap(
    (int)fe.ActualWidth * multiplyFactor,
    (int)fe.ActualHeight * multiplyFactor,
    96d * multiplyFactor,
    96d * multiplyFactor,
    PixelFormats.Default);
bmp.Render(fe);

PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bmp));

using (Stream stream = File.Create(outputPath))
{
    png.Save(stream);
}

And we're done. Easy peasy. Notice I decided to render the image 4x larger than 'actual' size by multiplying all the values passed to RenderTargetBitmap. I'm starting to like XPS, very much.

Tags: WPF XPS

 
Josh Post By Josh Twist
6:12 AM
17 Apr 2009

» Next Post: WPF Quick Tip: Converters as MarkupExtensions
« Previous Post: Animating when data changes

Comments are closed for this post.

Posted by David Cuccia @ 17 Apr 2009 3:10 PM
Cool. Seems silly that they didn't include the image writer in the box w/ WPF ala WinForms' System.Windows.Controls.ToBitmap()

Posted by David Cuccia @ 17 Apr 2009 3:29 PM
I meant System.Windows.Controls.Control.ToBitmap()

Posted by josh @ 18 Apr 2009 11:49 PM
Hi David, at first I agreed with you but over time it makes sense to me that this now belongs elsewhere. It might not be as discoverable but it reduces the amount of noise in Control's members.

Posted by L. @ 11 May 2009 2:46 AM
The shame is, XPS supports high dynamic range colors, WPF knows about high dynamic range pixel formats, but RenderTargetBitmap supports only 32 bits ARGB, so this method breaks high dynamic range colors.

Posted by Balaji @ 08 Dec 2010 10:29 AM
Hi,

Thanks for sharing a nice program. Is it possible to convert pdf to xps programmatically.

Posted by Josh @ 08 Dec 2010 4:12 PM
Hi Balaji,

You can, but it isn't easy! Much easier to buy a component - there are lots out there on the internet if you search.

Posted by gemon @ 15 Feb 2011 12:34 PM
Hi,
Please give some solution how to convert xps to ms word.
converting xps to ms word.please send me the result asap.
Thanks
gemon

Posted by josh @ 15 Feb 2011 3:13 PM
Its not easy, you'll have to buy a component to help you. Search the internet and you'll find lots of options.

© 2005 - 2014 Josh Twist - All Rights Reserved.