DIY generation of XPS Documents


Microsoft provides two ways of generating XPS Documents. If you’re a Win32 application or a .NET 1.0 application, you can generate XPS Documents using the Microsoft XPS Document Writer through Win32 GDI printing API. If you’re a WPF (Windows Presentation Foundation) application, generation of XPS Document is supported through our new API. Both cases have been covered in this blog.


But we’re still hearing people asking about how to generate XPS Documents outside of WPF and without using XPS Document Writer. The short answer is that XPS is designed to be a simple document format, so it should be quite easy to generate XPS Document on your own.


The longer answer is check the code below. This is what I’ve got today while working full time as a PDC hands-on-lab proctor:


#include “stdafx.h”


#include <stdio.h>


 


class CXpsGenerator


{


    HDC     m_hDC;


    int     m_dpi;


    HGDIOBJ m_pen;


    HGDIOBJ m_brush;


   


public:


    CStringA m_xps;


 


    int     m_fillMode;


    int     m_penAlpha;


    int     m_brushAlpha;


 


    CXpsGenerator()


    {


        m_pen        = GetStockObject(BLACK_PEN);


        m_brush      = GetStockObject(WHITE_BRUSH);


       


        m_fillMode   = 0;


        m_penAlpha   = 255;


        m_brushAlpha = 255;


    }


 


    void SelectPen(HGDIOBJ pen)


    {


        m_pen = pen;


    }


 


    void SelectBrush(HGDIOBJ brush)


    {


        m_brush = brush;


    }


 


    void WritePen()


    {


        if (m_pen == NULL)


        {


            return;


        }


 


        LOGPEN lp;


 


        GetObject(m_pen, sizeof(lp), & lp);


 


        int width = lp.lopnWidth.x;


       


        if (width <= 0)


        {


            width = 1;


        }


 


        m_xps.AppendFormat(“StrokeThickness=\”%d\” “, width);


       


        m_xps.AppendFormat(“Stroke=\”#%02X%02X%02X%02X\” “, m_penAlpha,


             GetRValue(lp.lopnColor), GetGValue(lp.lopnColor), GetBValue(lp.lopnColor));


 


        // pen style and EXTPEN missing


    }


 


    void WriteBrush()


    {


        if (m_brush == NULL)


        {


            return;


        }


 


        LOGBRUSH lb;


 


        GetObject(m_brush, sizeof(lb), & lb);


 


        switch (lb.lbStyle)


        {


            case PS_SOLID:


                m_xps.AppendFormat(“Fill=\”#%02X%02X%02X%02X\” “, m_brushAlpha,


                     GetRValue(lb.lbColor), GetGValue(lb.lbColor), GetBValue(lb.lbColor));


                break;


 


            // other brush style missing


        }


    }


 


    void WritePath()


    {


        ::EndPath(m_hDC);


 


        int n = ::GetPath(m_hDC, NULL, NULL, 0);


 


        if (n <= 0)


        {


            return;


        }


 


        int m = (n + 3 ) / 4 * 4;


 


        BYTE * pType = new BYTE[m * (1 + sizeof(POINT))];


        POINT * pPoint = (POINT *) (pType + m);


       


        GetPath(m_hDC, pPoint, pType, n);


 


        m_xps.Append(“<Path “);


 


        WritePen();


        WriteBrush();


 


        m_xps.AppendFormat(“Data=\”F%d”, m_fillMode);


 


        for (int i = 0; i < n; i ++)


        {


            switch (pType[i] & ~ PT_CLOSEFIGURE)


            {


            case PT_MOVETO:


                m_xps.Append(” M”);


                break;


 


            case PT_LINETO:


                m_xps.Append(” L”);


                break;


 


            case PT_BEZIERTO:


                m_xps.Append(” C”);


                pType[i + 1] |= 32; // avoid generating C to two subsequent points


                pType[i + 2] |= 32;


                break;


            }


 


            m_xps.AppendFormat(” %d,%d”, pPoint[i].x, pPoint[i].y);


 


            if (pType[i] & PT_CLOSEFIGURE)


            {


                m_xps.Append(“z”);


            }


        }


 


        m_xps.Append(“\” />\r\n”);


 


        delete [] pType;


    }


 


    void Rectangle(int left, int top, int right, int bottom)


    {


        BeginPath(m_hDC);


 


        ::Rectangle(m_hDC, left, top, right, bottom);


 


        WritePath();


    }


 


    void Ellipse(int left, int top, int right, int bottom)


    {


        BeginPath(m_hDC);


 


        ::Ellipse(m_hDC, left, top, right, bottom);


 


        WritePath();


    }


  


    void StartPage(double width, double height, HDC hDC)


    {


        m_hDC = hDC;


        m_dpi = GetDeviceCaps(hDC, LOGPIXELSX);


        m_dpi = 300;


 


        m_xps.Append(“<FixedPage xmlns=\”http://schemas.microsoft.com/xps/2005/06\” “);


        m_xps.Append(“xmlns:x=\”http://schemas.microsoft.com/xps/2005/06/resourcedictionary-key\” xml:lang=\”en-us\” “);


        m_xps.AppendFormat(“Width=\”%f\” Height=\”%f\” >\r\n”, width * 96, height * 96);


 


        if (m_dpi != 96) // scale to 96 dpi


        {


            m_xps.AppendFormat(“<Canvas RenderTransform=\”matrix(%f, 0, 0, %f, 0, 0)\” >\r\n”,


            96.0 / m_dpi, 96.0 / m_dpi);


        }


    }


 


    void EndPage()


    {


        if (m_dpi != 96)


        {


            m_xps.Append(“</Canvas>\r\n”);


        }


 


        m_xps += “</FixedPage>\r\n”;


    }


};


 


 


int _tmain(int argc, _TCHAR* argv[])


{


    CString s;


 


    CXpsGenerator xps;


 


    HDC hDC = GetDC(NULL);


 


    xps.StartPage(8.5, 11, hDC);


 


    HBRUSH yellowBrush = CreateSolidBrush(RGB(0xFF, 0xFF, 0));


    HPEN   bluePen     = CreatePen(PS_SOLID, 1, RGB(0, 0, 0xFF));


 


    xps.SelectBrush(yellowBrush);


    xps.SelectPen(bluePen);


 


    xps.Rectangle(48, 48, 192, 192);


 


    xps.SelectPen(GetStockObject(BLACK_PEN));


   


    HBRUSH cyanBrush = CreateSolidBrush(RGB(0xFF, 0, 0xFF));


 


    xps.SelectBrush(cyanBrush);


 


    xps.m_brushAlpha = 128;


    xps.Ellipse(120, 120, 120 + 192, 120 + 128);


    xps.m_brushAlpha = 255;


   


    xps.EndPage();


 


    DeleteObject(yellowBrush);


    DeleteObject(bluePen);


    DeleteObject(cyanBrush);


 


    ReleaseDC(NULL, hDC);


 


    puts(xps.m_xps);


}


 


The code should be quite easy to read. The CXpsGenerator class supports generating XML markup for simple pages with vector graphics using simple brushes and pens. It’s interface is similar to GDI. More methods can be added to support more brushes, more pens, more vector primitives, images and texts. To generate the full XPS container, you also need a way to generate .zip container and create other streams which make a XPS document complete.


 


Here is the markup generated:


 


<FixedPage xmlns=http://schemas.microsoft.com/xps/2005/06


      xmlns:x=http://schemas.microsoft.com/xps/2005/06/resourcedictionary-key


      xml:lang=en-us Width=816.000000 Height=1056.000000 >


<Path StrokeThickness=1 Stroke=#FF0000FF Fill=#FFFFFF00


      Data=F0 M 191,48 L 48,48 L 48,191 L 191,191z />


<Path StrokeThickness=1 Stroke=#FF000000 Fill=#80FF00FF


      Data=F0 M 311,184 C 311,148 268,120 216,120 C 163,120 120,148 120,184 C 120,219 163,247 216,247 C 268,247 311,219 311,184 />


</FixedPage>


 


It’s definitely more costly to generate XPS Documents on your own, comparing with using the XPS Document Writter. But there are a few possible benefits:




  1. Less dependency.


  2. More efficient, because you’re bypassing GDI, DDI and spooler.


  3. Making use of more XPS features, which may not be accessible through GDI:



    • Transparency: XPS Document Writer can understand a few transparency simulation pattens.


    • Brushes: more complicated linear gradient brushes, radial gradient brushes, visual brushes, more tiling modes, viewbox.


    • Pen: more pen styles.


    • Opacity mask.


    • More compressed image file format support.


    • Resource dictionary.


    • Extra meta data.

Comments (0)