Search code examples
winapidirect2dxps

Direct2D to XPS file without print queue


Direct2D allows printing via ID2D1Device::CreatePrintControl, which presumably renders an XPS as an intermediate representation. With an XPS Document Writer or other printer, I can create an xps file via redirection from Direct2D. Can I create an ID2D1PrintControl or ID2D1DeviceContext against an XPS document without a print queue?

Is there some ID2D1DeviceMagic::CreatePrintControlOnStream(IStream* ...) or IPrintDocumentPackageTargetFactoryMagic::CreateOnXpsOM(IXpsOMPage* pPage, ...) I'm missing?


Solution

  • It's not magic, but almost. You can implement IPrintDocumentPackageTarget COM interface yourself and use it when calling the ID2D1Device::CreatePrintControl method.

    In your implementation you can delegate the work to the IXpsOMObjectFactory::CreatePackageWriterOnStream method, so, it can be something like this:

    CTarget *target = new CTarget();
    
    auto hr = d2dDevice->CreatePrintControl(
        wicFactory,
        target, // use my own target
        nullptr,
        &printControl
    );
    
    delete target;
    

    This is the CTarget sample class that implements IPrintDocumentPackageTarget, and IXpsDocumentPackageTarget:

    // needs <initguid.h>, <shlwapi.h> and shlwapi.lib
    class CTarget : public IPrintDocumentPackageTarget, IXpsDocumentPackageTarget
    {
        // make sure this get release *before* CoUninitialize call
        CComPtr<IXpsOMObjectFactory> _factory; // uses ATL needs <atlbase.h>
    
    public:
        CTarget() {}
    
        STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
        {
            static QITAB rgqit[] =
            {
                QITABENT(CTarget, IPrintDocumentPackageTarget),
                QITABENT(CTarget, IXpsDocumentPackageTarget),
                { 0},
            };
    
            return QISearch(this, rgqit, riid, ppv);
        }
    
        // for testing we just send something static
        STDMETHODIMP_(ULONG) AddRef() { return 1; }; STDMETHODIMP_(ULONG) Release() { return 1; };
    
        // IPrintDocumentPackageTarget
        STDMETHODIMP GetPackageTargetTypes(UINT32* targetCount, GUID** targetTypes)
        {
            if (!targetCount || !targetTypes) return E_INVALIDARG;
            *targetTypes = (GUID*)CoTaskMemAlloc(sizeof(GUID));
            if (!*targetTypes)
            {
                *targetCount = 0;
                return E_OUTOFMEMORY;
            }
    
            *targetCount = 1;
            **targetTypes = ID_DOCUMENTPACKAGETARGET_MSXPS;
            return S_OK;
        }
    
        STDMETHODIMP GetPackageTarget(REFGUID guidTargetType, REFIID riid, void** ppvTarget)
        {
            if (guidTargetType == ID_DOCUMENTPACKAGETARGET_MSXPS)
                return QueryInterface(riid, ppvTarget);
    
            return E_FAIL;
        }
    
        STDMETHODIMP Cancel()
        {
            return S_OK;
        }
    
        // IXpsDocumentPackageTarget
        STDMETHODIMP GetXpsOMPackageWriter(IOpcPartUri* documentSequencePartName, IOpcPartUri* discardControlPartName, IXpsOMPackageWriter** packageWriter)
        {
            // here you can create your own stream, file, memory, etc.
            CComPtr<IStream> stream;
            SHCreateStreamOnFile(L"c:\\temp\\d2d1.xps", STGM_CREATE | STGM_WRITE, &stream);
    
            // ... and delegate to factory w/o more effort
            // you can also configure custom properties, etc.
            return _factory->CreatePackageWriterOnStream(stream, FALSE, XPS_INTERLEAVING_OFF, documentSequencePartName, nullptr, nullptr, nullptr, discardControlPartName, packageWriter);
        }
    
        STDMETHODIMP GetXpsOMFactory(IXpsOMObjectFactory** xpsFactory)
        {
            HRESULT hr = S_OK;
            if (!_factory)
            {
                if (FAILED(hr = _factory.CoCreateInstance(CLSID_XpsOMObjectFactory)))
                    return hr;
            }
    
            return _factory.QueryInterface(xpsFactory);
        }
    
        STDMETHODIMP GetXpsType(XPS_DOCUMENT_TYPE* documentType)
        {
            if (!documentType) return E_INVALIDARG;
            *documentType = XPS_DOCUMENT_TYPE_XPS;
            return S_OK;
        }
    };