A Simple XPS Decoder in C++


If you write programs in C#, Windows Presentation Foundation in .Net 3.0 provides quite nice API to write, generate and manipulate XPS documents. You can get the same feature if you work with managed C++ in .Net 3.0. Even if you work with .Net 2.0, you can get the basic ZIP stream decoding/encoding support.


But if you want to write code in unmamanged world, especially outside of the XPS filter pipeline, there is not enough sample code there. I’m going to write a simple XPS decoder in C++, based on the zlib compression library (http://www.zlib.net/zlib123-dll.zip).



#include <windows.h>


#include <stdio.h>


#include <tchar.h>


 


#include “MemFile.h”


#include “zip.h”


#include “zlib.h”


 


#pragma comment(lib, “zdll.lib”)


 


HANDLE  CreateDirFile(char * pName, DWORD len, HRESULT & hr);


 


class CXps : CMemoryMappedFile


{


    const CEndCentralDir     * m_pEndCentralDir;


    const CCentralFileHeader * m_pFileHeader;


 


public:


    HRESULT LoadXps(const wchar_t * pFileName);


    HRESULT DecodePiece(const BYTE * pCur, DWORD compressedSize);


    HRESULT ExtractAllStreams();


};


The code uses zlib.h and zdll.lib for decompression. It opens up an XPS container as a memory-mapped file, wrapped in the CXps class. The CXps class has a pointer to end central directory, and a pointer to the first central file header.



HRESULT CXps::LoadXps(const wchar_t * pFileName)


{


    m_pEndCentralDir = NULL;


    m_pFileHeader;


 


    if (! Open(pFileName))


    {


        return E_INVALIDARG;


    }


 


    const BYTE * pCur = m_View + m_nFileSize – (sizeof(CEndCentralDir) – 1);


 


    while (pCur > m_View)


    {


        DWORD signature = * (DWORD *) pCur;


 


        if (signature == sig_EndCentralDir)


        {


            m_pEndCentralDir = (const CEndCentralDir *) pCur;


            m_pFileHeader    = (const CCentralFileHeader *) (m_View + m_pEndCentralDir->m_offsetCentralDic);


 


            return S_OK;


        }


       


        pCur –;


    }


 


    return E_FAIL;


}


The LoadXps method opens up an XPS file and locates its end central directory by reading backward from the end of the ZIP file.



HRESULT CXps::ExtractAllStreams()


{


    HRESULT hr = S_OK;


 


    const CCentralFileHeader * pFile = m_pFileHeader;


 


    for (int i = 0; SUCCEEDED(hr) && (i < m_pEndCentralDir->m_entryCentralDic); i ++)


    {


        hr = DecodePiece(m_View + pFile->m_offsetLocalHeader, pFile->m_compressedSize);


 


        pFile = (const CCentralFileHeader *) (pFile->m_Data + pFile->m_nFileName + pFile->m_nExtraField + pFile->m_nFileComment);


    }


  


    return hr;


}


 


 


int XpsExtract(const wchar_t * pFileName)


{


    CXps xpsFile;


 


    HRESULT hr = xpsFile.LoadXps(pFileName);


 


    if (SUCCEEDED(hr))


    {


        hr = xpsFile.ExtractAllStreams();


    }


 


    printf(“\nhr = %x\n”, hr);


 


    return 0;


}


The ExtractAllStreams method tries to extract every pieces in the XPS container to an file on the disk by calling DecodePiece method. The main routine XpsExtract is responsible for calling the loading routine and the main extracting routine.


The most complicated routine here is DecodePiece:



HRESULT CXps::DecodePiece(const BYTE * pCur, DWORD compressedSize)


{


    HRESULT hr = S_OK;


 


    const CLocalFileHeader * pHeader = (const CLocalFileHeader *) pCur;


 


    char * pName = new char[pHeader->m_nFileName + 1];


 


    if (pName == NULL)


    {


        return E_OUTOFMEMORY;


    }


 


    memcpy(pName, pHeader->m_Data, pHeader->m_nFileName);


    pName[pHeader->m_nFileName] = 0;


 


    printf(“\n%8d %8d %-60s”, pHeader->m_uncompressedSize, compressedSize, pName);


 


    HANDLE hFile = CreateDirFile(pName, pHeader->m_nFileName, hr);


 


    delete pName;


 


    if (FAILED(hr))


    {


        printf(“\nUnable to open file\n”);


 


        return hr;


    }


 


    pCur = pHeader->m_Data + pHeader->m_nFileName + pHeader->m_nExtraField;


   


    if (pHeader->m_compression != 0)


    {


        BYTE * pOutput = new BYTE[pHeader->m_uncompressedSize];


 


        if (pOutput == NULL)


        {


            return E_OUTOFMEMORY;


        }


 


        z_stream stream;


 


        memset(&stream, 0, sizeof(stream));


 


        stream.avail_in  = compressedSize;


        stream.avail_out = pHeader->m_uncompressedSize;


        stream.next_in   = (Bytef *) pCur;


        stream.next_out  = pOutput;


 


        int result = inflateInit2(& stream, -MAX_WBITS);


 


        if (result == Z_OK)


        {


            result = inflate(& stream, Z_SYNC_FLUSH);


        }


 


        DWORD dwWritten;


 


        WriteFile(hFile, pOutput, pHeader->m_uncompressedSize, & dwWritten, NULL);


 


        delete [] pOutput;


    }


    else


    {


        DWORD dwWritten;


 


        WriteFile(hFile, pCur, pHeader->m_uncompressedSize, & dwWritten, NULL);


    }


 


    CloseHandle(hFile);


 


    return hr;


}


 


DecocePiece has two parameters: a pointer to local file header structure, and compressed size. Note the compressed size may not be stored in the local file header, so it’s better to pass it in from the end of the container. From the header structure, we can get its name and call CreateDirFile to create a corresponding file on the disk, making sure the proper directory path is created. The ZLIB decompression routine inflate, together with its preparation routine inflateInit2 are called when the piece is actually compressed. The result uncompressed streams are then written to disk.


The structures not shown here can be easily created by following ZIP specification at http://www.pkware.com/documents/casestudies/APPNOTE.TXT. The CreateDirFile routine is a simple wrapper around CreateFile and CreateDirectory.


The program is implicitly linked with ZLIB1 ZDLL.

Comments (10)

  1. Brad Hards says:

    I’ve also implemented quite a lot of XPS decoding in okular (KDE file viewer). The code relies on KDE and Qt classes for parsing.

    http://websvn.kde.org/trunk/KDE/kdegraphics/okular/generators/xps/

    GPL’d.

  2. FredCox says:

    Thanks for the sample code.  That’s very helpful.  

    Do you know of any sample code for working with XPS using .NET 3.0 APIs?  

  3. Nauman says:

    Where are these defined:

    1. CEndCentralDir

    2. CCentralFileHeader

    Or do have to define them ourself.

  4. Adrian Ford says:

    I was involved in a conversation this week where someone mentioned "it’s just 2 lines of code", So I

  5. I was involved in a conversation this week where someone mentioned &quot;it&#39;s just 2 lines of code&quot;

  6. achellies says:

    hi, i can not compile the source code, i just can not find the CEndCentralDir CCentralFileHeader class,

    would you do me a favor to send me the source code?

    thanks for your invaluable time and effect

    my email: achellies@qq.com

  7. # re: A Simple XPS Decoder in C++

    Hello,

    Sorry, I am not able compile the source code.

    Please could you send me the source code compilable with VS 2005?

    thanks in advance!

    my email: privat@hsm.ch

  8. wajahat says:

    hi..can you please send me the complete project for parsing an xps file in MFC or c/c++. thanks

    email:wajahatraza@gmail.com

  9. Raman says:

    hi..can you please send me the complete project for parsing an xps file in MFC or c/c++.Thanks a lots