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: XPS WPF

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:

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.

Post a comment:

Name  

E-mail (never shared)

URL

Comments  

Captcha ImageRefresh Image
What's this?
Enter code above