How to format content for Direct2D printing (DirectX)

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

Learn here how to format content for Direct2D printing in your Windows Store app using DirectX with C++.

Formatting content for printing in your apps is like formatting it for display, because in both cases there is a limited region where you can format your content and draw. But if your users expect to format printed content differently from content that appears on the screen—For example, adjusting margins, pagination, and orientation—have your app meet these expectations when formatting its content for printing.

What you need to know

Technologies

Prerequisites

You must:

  • Be familiar with C++, Direct2D APIs, Windows events, and event handling.
  • Have Microsoft Visual Studio installed.
  • Have a printer installed (or use the Microsoft XPS Document Writer).
  • Have an app in which you want to format content for printing. If you don't have your own app, you can download the Direct2D printing for Windows Store apps sample and use that. Note  You can find all of the code examples mentioned here in the Direct2D printing for Windows Store apps sample.  

Instructions

Step 1: Open your app's source code for editing

This step shows you how to open the Direct2D printing for Windows Store apps sample. If you are using your own app, just open that in Visual Studio and skip to the next step.

Note  The code examples shown in this tutorial refer to the Direct2D printing for Windows Store apps sample. You might need to add more code from the sample app to use these examples in your own app.

 

  1. Go to the Direct2D printing for Windows Store apps sample and download the C++ example to your computer.
  2. In Visual Studio, click File > Open Project and go to the folder that contains the solution file of the sample app that you just downloaded.
  3. Select the D2DPrinting solution file and click Open.

Step 2: Build and test the app

  1. Click Build > Build Solution to build the app you're working on. Make sure that there are no error messages in the Output pane at the bottom of the screen.
  2. Click Debug > Start Without Debugging.
  3. Verify that, after a few seconds, the D2DPrinting app starts.
  4. If the app runs without error, return to Visual Studio and click Debug > Stop Debugging.

Step 3: Format your app's content for printing

  1. In your app's implementation of IPrintDocumentPageSource.MakeDocument, create a PrintTaskOptions object and a PrintPageDescription object to get the print settings.

    
    IFACEMETHODIMP
    CDocumentSource::MakeDocument(
        _In_ IInspectable*                docOptions,
        _In_ IPrintDocumentPackageTarget* docPackageTarget
        )
    {
            // ...
            // Get print settings from PrintTaskOptions for printing, 
            // such as page description, which contains page size, printable area, and DPI.
            PrintTaskOptions^ option = reinterpret_cast<PrintTaskOptions^>(docOptions);
            PrintPageDescription pageDesc = option->GetPageDescription(1); // Get the description of the first page.
    
            // ...
    
    }
    
  2. Calculate the printable area and page size from PrintPageDescription, like this.

    
    
    D2D1_RECT_F imageableRect = D2D1::RectF(
        pageDesc.ImageableRect.X,
        pageDesc.ImageableRect.Y,
        pageDesc.ImageableRect.X + pageDesc.ImageableRect.Width,
        pageDesc.ImageableRect.Y + pageDesc.ImageableRect.Height
        );
    
    D2D1_SIZE_F pageSize = D2D1::SizeF(
        pageDesc.PageSize.Width, pageDesc.PageSize.Height);
    

    Note  You can also apply margins to the printable area by using D2D1::RectF, like this.

     

    
    D2D1_RECT_F textBox = D2D1::RectF(
        m_targetBox.left + m_margin,
        m_targetBox.top + m_margin,
        m_targetBox.right - m_margin,
        m_targetBox.bottom - m_margin
        );
    
  3. For each page to print, render that page's content to a command list and pass that command list to the print control using ID2D1PrintControl::AddPage

    Note  PageRenderer::PrintPage makes the call to ID2D1PrintControl::AddPage.

     

    
        for (uint32 pageNum = 1; pageNum <= m_totalPages; ++pageNum)
        {
            // If a page-level print ticket is not specified here, 
            // the package print ticket is applied for each page.
            m_renderer->PrintPage(
                pageNum,
                imageableRect,
                pageSize,
                nullptr 
                );
        }
    
    

    PageRenderer::PrintPage is defined here.

    // Print out one page, with the given print ticket.
    // This sample has only one page and we ignore pageNumber below.
    void PageRenderer::PrintPage(
        _In_ uint32                 /*pageNumber*/,
        _In_ D2D1_RECT_F            imageableArea,
        _In_ D2D1_SIZE_F            pageSize,
        _In_opt_ IStream*           pagePrintTicketStream
        )
    {
        // Create a new D2D device context for generating the print command list.
        // D2D device contexts are stateful, and hence a unique device context must
        // be used on each thread.
        ComPtr<ID2D1DeviceContext> d2dContext;
        DX::ThrowIfFailed(
            m_d2dDevice->CreateDeviceContext(
                D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                &d2dContext
                )
            );
    
        ComPtr<ID2D1CommandList> printCommandList;
        DX::ThrowIfFailed(
            d2dContext->CreateCommandList(&printCommandList)
            );
    
        d2dContext->SetTarget(printCommandList.Get());
    
        // Create and initialize the page renderer context for print.
        // In this case, we want to use the bitmap source that already has
        // the color context embedded in it. Thus, we pass NULL for the
        // color context parameter.
        PageRendererContext^ printPageRendererContext =
            ref new PageRendererContext(
                imageableArea,
                d2dContext.Get(),
                DrawTypes::Printing,
                this
                );
    
        d2dContext->BeginDraw();
    
        // Draw page content on a command list.
        printPageRendererContext->Draw();
    
        // The document source handles D2DERR_RECREATETARGET, so it is okay to throw this error
        // here.
        DX::ThrowIfFailed(
            d2dContext->EndDraw()
            );
    
        DX::ThrowIfFailed(
            printCommandList->Close()
            );
    
        DX::ThrowIfFailed(
            m_d2dPrintControl->AddPage(printCommandList.Get(), pageSize, pagePrintTicketStream)
            );
    }
    

    The rendering happens in the PageRendererContext::Draw method, like this.

    
    // Draws the scene to a rendering device context or a printing device context.
    void PageRendererContext::Draw(_In_ float scale)
    {
        // Clear rendering background with CornflowerBlue and clear preview
        // background with white color. For the printing case (command list), it
        // is recommended not to clear because the surface is clean when created.
        if (m_type == DrawTypes::Rendering)
        {
            m_d2dContext->Clear(D2D1::ColorF(D2D1::ColorF::CornflowerBlue));
        }
        else if (m_type == DrawTypes::Preview)
        {
            m_d2dContext->Clear(D2D1::ColorF(D2D1::ColorF::White));
        }
    
        // We use scale matrix to shrink the text size and scale is only available
        // for preview.  For on-screen rendering or printing, scale is 1.f, that
        // is, the Identity is the transform matrix.
        m_d2dContext->SetTransform(D2D1::Matrix3x2F(1/scale, 0, 0, 1/scale, 0, 0));
    
        D2D1_RECT_F textBox =
            D2D1::RectF(
                m_targetBox.left + m_margin,
                m_targetBox.top + m_margin,
                m_targetBox.right - m_margin,
                m_targetBox.bottom - m_margin
                );
    
        const char16 textString[] = L"\
            Lorem ipsum dolor sit amet, consectetur adipisicing elit, \
    sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \
    Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris \
    nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in \
    reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla\
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in \
    culpa qui officia deserunt mollit anim id est laborum.";
    
        m_d2dContext->DrawText(
            textString,
            ARRAYSIZE(textString) - 1,
            m_textFormat.Get(),
            textBox,
            m_blackBrush.Get()
            );
    }
    
  4. Finally, close the print control even if the AddPage() method from within PrintPage() fails, like this.

           HRESULT hrClose = m_renderer->ClosePrintControl();
    

Remarks

You can format content for printing in your apps as if you were formatting content for display. However, there are a few differences:

  • When formatting content for printing, apps retrieve the virtual canvas size (that is, the page size) from PrintTaskOptions. When formatting content for display, apps retrieve the virtual canvas size (that is, the display size) from Windows::Graphics::Display::DisplayProperties.
  • Many printers can print only on a printable area (PrintPageDescription) on a page, leaving margins on the page boundaries. Your app must draw only inside that printable area when formatting content for printing. When formatting content for display, however, your app does not have this constraint.
  • Due to paper-size limits, apps must paginate content to separate and fit it nicely onto each page. Pagination is a concern only if your app can have multiple pages of content to print. If it has only a single canvas for drawing to the display, pagination is not an issue when formatting content for printing.

To learn how to improve the performance of Direct2D printing in your app, see Improving the performance of Direct2D apps (Windows). To learn how your app can retrieve print settings or handle errors, see How to retrieve and change Direct2D print settings and the Direct2D app printing sample, respectively.

Direct2D printing from Windows Store apps

Printing and Command Lists

Printing

Improving the performance of Direct2D apps (Windows)

Quickstart: Add Direct2D printing to your app