Xps and Silverlight

I’ve recently been asked to investigate displaying an Xps file using Silverlight.

What I initially found was this sample posted by Koen Zwikstra.  It turns out that the format of the Xps file is pretty similar to the Xaml use by Silverlight.  What this sample does is replace or remove the items in the Xps file which Silverlight doesn’t understand.

The main item which needs translating is the Glyphs element, because of the way that Xps uses obfuscated fonts.  For the Glyph element to use an obfuscated font in Silverlight it must be a resource of the Silverlight application.  The problem with this is unless you have a predetermined set of Xps file to show you have no idea which fonts you need to embed as resources.

To get around this Koen converts the Glyphs to TextBlock to allow the use of the FontSource class which must use a stream which contains the unobfuscated font. 

There was a small problem with the way Koen’s example deobfuscated the font which might have been because I’m using Silverlight 3 and the example was written for Silverlight 2.  What I had to do was replace code in the ObfuscatedFontStream constructor.

 1: public ObfuscatedFontStream(Stream source, string fontFileName)
 2: {
 3:     if (source == null) {
 4:         throw new ArgumentNullException("source");
 5:     }
 6:     if (fontFileName == null) {
 7:         throw new ArgumentNullException("fontFileName");
 8:     }
 9:     this.source = source;
 10:  
 11:     string name = Path.GetFileNameWithoutExtension(fontFileName);
 12:     Guid guid;
 13:     try {
 14:         guid = new Guid(name);
 15:     }
 16:     catch (Exception e) {
 17:         throw new ArgumentException(string.Format("File name {0} does not contain a valid GUID", fontFileName), e);
 18:     }
 19:  
 20:     byte[] guidBytes = new byte[16];
 21:     for (int i = 0; i < guidBytes.Length; i++)
 22:     {
 23:         guidBytes[i] = Convert.ToByte(guid.ToString("N").Substring(i * 2, 2), 16);
 24:     }
 25:  
 26:     long position = source.Position;
 27:  
 28:     if (source.CanSeek)
 29:     {
 30:         source.Seek(0, SeekOrigin.Begin);
 31:     }
 32:  
 33:     this.header = new byte[32];
 34:     source.Read(this.header, 0, 32);
 35:     byte[] key = guid.ToByteArray();
 36:  
 37:     for (int i = 0; i < 32; i++) {
 38:         int gi = guidBytes.Length - (i % guidBytes.Length) - 1;
 39:         header[i] ^= guidBytes[gi];
 40:     }
 41:     // reset stream position
 42:     if (source.CanSeek)
 43:     {
 44:         source.Seek(position, SeekOrigin.Begin);
 45:     }
 46: }

There was still one problem with the sample and that was that it was not able to display Xps files generated from the Xps printer driver.  For some reason the normal method of getting the resouces out of the Xps file using Application.GetResourceStream doesn’t work.

I found that I could use SilverlightContribs Zip methods to open the Xps file.  What I did was change Koen’s Load method in XpsReader to use ZipFile if Application.GetResourceStream fails.

 1: private StreamResourceInfo Load(Uri uri)
 2:         {
 3:             StreamResourceInfo streamResourceInfo = Application.GetResourceStream(_xps, uri);
 4:             if (streamResourceInfo == null)
 5:             {
 6:                 // Zipfile closes the underlying stream when it is disposed, which we don't really want.
 7:                 // so we copy the original stream and let ZipFile close that.
 8:                 Stream zipStream = CopyStream(_xps.Stream);
 9:                 using (var zipFile = new ZipFile(zipStream))
 10:                 {
 11:                     if (zipFile.FindEntry(uri.ToString(), true) > 0)
 12:                     {
 13:                         ZipEntry entry = zipFile.GetEntry(uri.ToString());
 14:                         Stream stream = zipFile.GetInputStream(entry.ZipFileIndex);
 15:                         MemoryStream depressed = CopyStream(stream);
 16:                         streamResourceInfo = new StreamResourceInfo(depressed, string.Empty);
 17:                     }
 18:                 }
 19:             }
 20:             return streamResourceInfo;
 21:         }
 22:  
 23:         private MemoryStream CopyStream(Stream stream)
 24:         {
 25:             var ms = new MemoryStream();
 26:             if (stream.CanSeek)
 27:             {
 28:                 stream.Seek(0, SeekOrigin.Begin);
 29:             }
 30:             var buffer = new byte[32768];
 31:             int read;
 32:             do
 33:             {
 34:                 read = stream.Read(buffer, 0, buffer.Length);
 35:                 if (read > 0)
 36:                 {
 37:                     ms.Write(buffer, 0, read);
 38:                 }
 39:             } while (read > 0);
 40:             ms.Seek(0, SeekOrigin.Begin);
 41:             return ms;
 42:         }

I found that I had to return an uncompressed stream due to the implementation of the InflaterInputStream not working with the rest of the sample.

Technorati Tags: [.Net](http://technorati.com/tags/.Net),[C#](http://technorati.com/tags/C%23),[Silverlight](http://technorati.com/tags/Silverlight)