Printing from .NET 3.5 in Windows 7

The printed output looks like the following image; various glyphs are substituted
with larger sans-serif versions of themselves, causing a ransom-note-like appearance.

We found that changing any one of the conditions above fixes the problem, but
unfortunately we need to print XPS using an embedded font, we’re still using .NET 3.5,
and we have to run on Windows 7.

Strangely enough, printing to the XPS Document Writer print driver, and then printing
that document with the XPS Viewer built into Windows 7 doesn’t reproduce the problem;
it only happens when our app prints directly to an actual printer.

Workaround

I noticed that the XPS Viewer is a native application; this led me to
discover the Windows 7 XPS Print API.
In conjunction with the (also new in Windows 7)
XPS Document API,
this lets you print XPS documents from native code.

We were able to solve the problem by automating the workaround described above:
our code writes its output to an XpsDocumentWriter
backed by a temporary file (instead of a PrintQueue);
we then use the native APIs to print the temporary XPS file to the currently-selected printer.

The first part is to define the native methods and COM interfaces we will need.
(And, as noted in this StackOverflow question,
the IXpsPrintJobStream interface is either declared or implemented incorrectly, so we
have to call the Close method as if it existed on ISequentialStream.)

With those declared, the printing code can be written. This method (which should be called on a
background thread), prints a XPS file to a specific printer, returning true if printing
succeeded. (If it fails, the application should fall back to the .NET printing APIs.)

internalstaticclassNativeMethods{[DllImport("XpsPrint.dll",ExactSpelling=true,CharSet=CharSet.Unicode)]publicstaticexternintStartXpsPrintJob(stringprinterName,stringjobName,stringoutputFileName,IntPtrprogressEvent,SafeWaitHandlecompletionEvent,[MarshalAs(UnmanagedType.LPArray)]byte[]printablePagesOn,intprintablePagesOnCount,outIXpsPrintJobxpsPrintJob,outIXpsPrintJobStreamdocumentStream,outIXpsPrintJobStreamprintTicketStream);}[ComImport, Guid("E974D26D-3D9B-4D47-88CC-3872F2DC3585"), ClassInterface(ClassInterfaceType.None)]internalclassXpsOMObjectFactory{}[ComImport, Guid("F9B2A685-A50D-4FC2-B764-B56E093EA0CA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]internalinterfaceIXpsOMObjectFactory{voidCreatePackage();[return:MarshalAs(UnmanagedType.Interface)]IXpsOMPackageCreatePackageFromFile([MarshalAs(UnmanagedType.LPWStr)]stringfilename,boolreuseObjects);voidCreatePackageFromStream();voidCreateStoryFragmentsResource();voidCreateDocumentStructureResource();voidCreateSignatureBlockResource();voidCreateRemoteDictionaryResource();voidCreateRemoteDictionaryResourceFromStream();voidCreatePartResources();voidCreateDocumentSequence();voidCreateDocument();voidCreatePageReference();voidCreatePage();voidCreatePageFromStream();voidCreateCanvas();voidCreateGlyphs();voidCreatePath();voidCreateGeometry();voidCreateGeometryFigure();voidCreateMatrixTransform();voidCreateSolidColorBrush();voidCreateColorProfileResource();voidCreateImageBrush();voidCreateVisualBrush();voidCreateImageResource();voidCreatePrintTicketResource();voidCreateFontResource();voidCreateGradientStop();voidCreateLinearGradientBrush();voidCreateRadialGradientBrush();voidCreateCoreProperties();voidCreateDictionary();voidCreatePartUriCollection();voidCreatePackageWriterOnFile();voidCreatePackageWriterOnStream();voidCreatePartUri();voidCreateReadOnlyStreamOnFile();}[ComImport, Guid("18C3DF65-81E1-4674-91DC-FC452F5A416F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]internalinterfaceIXpsOMPackage{voidGetDocumentSequence();voidSetDocumentSequence();voidGetCoreProperties();voidSetCoreProperties();voidGetDiscardControlPartName();voidSetDiscardControlPartName();voidGetThumbnailResource();voidSetThumbnailResource();voidWriteToFile();voidWriteToStream(IXpsPrintJobStreamstream,booloptimizeMarkupSize);};// NOTE: It appears that the IID for IXpsPrintJobStream specified in XpsPrint.h --
// MIDL_INTERFACE("7a77dc5f-45d6-4dff-9307-d8cb846347ca") -- is not correct, or the object
// doesn't implement QueryInterface correctly. However, we can QI for ISequentialStream and
// successfully (at least in Windows 7 SP1 x86) call the Close method as if it existed on that
// interface.
// That is, we obtain the ISequentialStream interface, but work with it as the IXpsPrintJobStream interface.
// Thanks to http://stackoverflow.com/questions/6123507/xps-printing-from-windows-service for this tip.
[ComImport, Guid("0C733A30-2A1C-11CE-ADE5-00AA0044773D"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]internalinterfaceIXpsPrintJobStream{// ISequentialStream methods
voidRead([MarshalAs(UnmanagedType.LPArray)]byte[]pv,uintcb,outuintpcbRead);voidWrite([MarshalAs(UnmanagedType.LPArray)]byte[]pv,uintcb,outuintpcbWritten);// IXpsPrintJobStream methods
voidClose();}[ComImport, Guid("5AB89B06-8194-425F-AB3B-D7A96E350161"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]internalinterfaceIXpsPrintJob{voidCancel();IntPtrGetJobStatus();};