December 2013

Volume 28 Number 12

Windows 8.1 - Rendering PDF Content in Windows Store Apps

By Sridhar Poduri

PDF as a document storage and archiving format is well established in today’s world. Documents such as books, technical manuals, user guides, reports and more are stored in PDF format. It lets a document be consumed from multiple platforms as long as a supported PDF viewer is available. While viewing PDF documents is largely a nonissue, supporting the rendering of PDF content remains a challenge, especially for Windows Store app developers. With Windows 8.1, Microsoft introduced new APIs that ease the process of rendering PDF content in Windows Store apps.

In this article, I’ll look at the different ways to do this rendering. First, I’ll focus on the APIs that are part of the Windows Runtime (WinRT) and are accessible to you via JavaScript, C#, Visual Basic .NET and C++. Then I’ll focus on the native APIs that let C++ developers render PDF content directly on a DirectX-based drawing surface.

The Windows Runtime APIs for PDF Rendering

The Windows Runtime for Windows 8.1 includes a new namespace, Windows.Data.Pdf, which contains the new runtime classes and structures that support PDF rendering in Windows Store apps. In this section, I’ll discuss the various classes that make up the Windows.Data.Pdf namespace, used for opening PDF documents, handling password protection, rendering documents, customizing the rendering process and more.

Opening PDF Documents Opening a PDF document programmatically is as easy as calling the static method LoadFromFileAsync from the PdfDocument runtime class. This class is the initial entry point for working with PDF documents. The LoadFromFileAsync method accepts a StorageFile object and begins the process of loading the PdfDocument. Loading a PDF document can sometimes take a long time, so the API returns an asynchronous operation. When the asynchronous operation has completed, you have a valid instance of the PdfDocument object, as shown here:

// Obtain a StorageFile object by prompting the user to select a .pdf file
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.ViewMode = PickerViewMode.List;
openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
openPicker.FileTypeFilter.Add(".pdf");
StorageFile pdfFile = await openPicker.PickSingleFileAsync();
// Load a PdfDocument from the selected file
create_task(PdfDocument::LoadFromFileAsync(pdfFile)).then(
  [this](PdfDocument^ pdfDoc)
{
  // Handle opened Pdf document here.
});

In addition to the LoadFromFileAsync method, the PdfDocument class also contains a helper static method to create a PdfDocument instance from a stream object. If you already hold a reference to a PDF document as a RandomAccessStream instance, you can simply pass the stream object to the LoadFromStreamAsync method. Depending on your scenario, you can choose to use either the LoadFromFileAsync or LoadFromStreamAsync methods to create a PdfDocument object instance. Once you have a valid PdfDocument instance, you can access individual pages in the document.

Handling Password-Protected PDF Documents PDF docu­ments are used to store a wide variety of information, such as credit-­card statements or other confidential data. Some publishers don’t want users to have unrestricted access to these types of documents and protect them with passwords. Access is granted  only to applications whose binaries contain the password. The LoadFromFileAsync and LoadFromStreamAsync methods of the PdfDocument runtime class contain overloaded versions of the methods that accept a password via a String parameter:

// Load a PdfDocument that's protected by a password
// Load a PdfDocument from the selected file
create_task(PdfDocument::LoadFromFileAsync(
  pdfFile, "password")).then([this](PdfDocument^ pdfDoc){
  Handle opened Pdf document here.
});

If you attempt to load a password-protected document without specifying a password, the LoadFromFileAsync and LoadFromStreamAsync methods will throw an exception.

Accessing the Pages in a PDF Document After you create an instance of a PdfDocument object, the Count property will return the number of pages in the PDF document. You can simply iterate from 0 to the “Count – 1” range to get access to individual PDF pages. Each page is of type PdfPage runtime class. The PdfPage runtime class has a method named PreparePageAsync that begins the process of preparing a PDF page for rendering. Page preparation involves parsing and loading the page, initializing Direct2D resources for proper handling of graphic paths and shapes, initializing DirectWrite for handling the correct set of fonts for rendering text and so on. If you don’t call PreparePageAsync before beginning to render PDF pages, the render process calls PreparePageAsync for you implicitly. However, you should call PreparePageAsync and have the pages ready for rendering rather than let the rendering process prepare the page. Preparing the page ahead of the actual rendering saves time in the actual rendering process and is a nice optimization technique.

Rendering the PDF Pages Once the PdfPage objects are prepared, they can be rendered. The Rendering API encodes the PdfPage as an image and writes the image data to a stream supplied by the developer. The stream can then either be set as the source for an Image control in the application UI or be used to write the data to disk for use later on.

The rendering happens once you call the RenderToStreamAsync method of the PdfPage runtime class. The RenderToStreamAsync method accepts an instance of an IRandomAccessStream or one of its derived types and writes the encoded data to the stream.

Customizing the Page Rendering The default rendering process involves encoding the PdfPage as a PNG image at the actual dimensions of the PDF page in the document. You can change the default encoding from PNG to either BMP or JPEG, although I strongly recommended that you use PNG encoding for rendering content on-screen because it’s lossless and also doesn’t generate large bitmaps. However, if you want to generate images from the PDF pages and store them to disk for access later, you should consider using JPEG encoding because it generates smaller image files with an acceptable resolution. You can also specify the SourceRect and DestinationWidth to render only a portion of a PDF page—for example, in response to a zoom-in operation. You can also check for rendering in high-contrast mode by using the Boolean flag IsHighContrastEnabled and setting this flag to true. You can create an instance of the PdfPageRenderOptions structure and pass it to the overloaded version of the RenderToStreamAsync method of the PdfPage class.

The Client Code A simple app demonstrates how easy it is to use these APIs to render PDF content. My sample app (PdfAPISample in the accompanying code download) contains a MainPage with two Button controls, as shown in Figure 1.

The PDF App UI
Figure 1 The PDF App UI

The click event handlers for both buttons will prompt the user to select a PDF document and render the first page. The event handler for the “Render PDF w/Options” button will use the overloaded RenderToStreamAsync method and change the page background color.

The Button_Click event handler is listed in Figure 2.

Figure 2 The Button_Click Event Handler to Open and Render a PDF Document

void MainPage::Button_Click(Platform::Object^ sender,
  Windows::UI::Xaml::RoutedEventArgs^ args)
{
  m_streamVec->Clear();
  FileOpenPicker^ openPicker = ref new FileOpenPicker();
  openPicker->SuggestedStartLocation = PickerLocationId::DocumentsLibrary;
  openPicker->ViewMode = PickerViewMode::List;
  openPicker->FileTypeFilter->Clear();
  openPicker->FileTypeFilter->Append(L".pdf");
  create_task(openPicker->PickSingleFileAsync())
  .then([this](StorageFile^ pdfFile)
  {
    m_ImagefileName = pdfFile->Name;
    create_task(PdfDocument::LoadFromFileAsync(pdfFile))
    .then([this](PdfDocument^ pdfDoc)
    {
      auto page = pdfDoc->GetPage(0);
      auto stream = ref new InMemoryRandomAccessStream();
      IAsyncAction^ action = page->RenderToStreamAsync(stream);
      auto actionTask = create_task(action);
      actionTask.then([this, stream, page]()
      {
        String^ img_name = m_ImagefileName + ".png";
        task<StorageFolder^> writeFolder(
          KnownFolders::PicturesLibrary->GetFolderAsync("Output"));
          writeFolder
          .then([this, img_name, stream, page](StorageFolder^ outputFolder)
          {
            task<StorageFile^> file(
            outputFolder->CreateFileAsync(img_name, 
            CreationCollisionOption::ReplaceExisting));
        file.then([this, stream, page](StorageFile^ file1) {
          task<IRandomAccessStream^> writeStream(
            file1->OpenAsync(FileAccessMode::ReadWrite));
          writeStream.then(
          [this, stream, page](IRandomAccessStream^ fileStream) {
            IAsyncOperationWithProgress<unsigned long long
,             unsigned long long>^ progress
              = RandomAccessStream::CopyAndCloseAsync(
                stream->GetInputStreamAt(0),
                fileStream->GetOutputStreamAt(0));
                auto copyTask = create_task(progress);
                copyTask.then(
                   [this, stream, page, fileStream](
                   unsigned long long bytesWritten) {
                  stream->Seek(0);
                  auto bmp = ref new BitmapImage();
                  bmp->SetSource(fileStream);
                  auto page1 = ref new PdfPageAsImage();
                  page1->PdfPageImage = bmp;
                  m_streamVec->Append(page1);
                  pageView->ItemsSource = ImageCollection;
                  delete stream;
                  delete page;
                  });
                });
            });
          });
        });
    });
  });
}

The Native APIs for PDF Rendering

The WinRT APIs allow easy integration of PDF content in Windows Store apps and are meant to be accessible from all WinRT-supported languages by creating an image file or stream from each page in a PDF document. However, certain classes of Windows Store apps have a requirement to render PDF content directly on-screen. Such apps are usually written using native C++ and utilize XAML or DirectX. For these apps, Windows 8.1 also includes native APIs for rendering PDF content on a DirectX surface.

The native APIs for PDF rendering are present in the win­dows.data.pdf.interop.h header file and require linking with the windows.data.pdf.lib static library. These APIs are available exclusively for C++ developers who want to render PDF content directly on-screen.

PdfCreateRenderer The PdfCreateRenderer function is the entry point for the native API. It accepts an instance of an IDXGIDevice as input and returns an instance of an IPdfRendererNative interface. The IDXGIDevice input parameter can be obtained from either a XAML SurfaceImageSource, a VirtualSurfaceImageSource or a XAML SwapChainBackgroundPanel. You might know these are the interop types that XAML supports for mixing DirectX content in a XAML framework. Calling the PdfCreateRenderer function is easy. Make sure to include windows.data.pdf.interop.h and link against the windows.data.pdf.lib static library. Obtain an instance of an IDXGIDevice from the underlying D3DDevice instance. Pass the IDXGIDevice instance to the PdfCreateRenderer function. If the function succeeds, it returns a valid instance of an IPdfRendererNative interface:

ComPtr<IDXGIDevice> dxgiDevice;
d3dDevice.As(&dxgiDevice)
ComPtr<IPdfRendererNative> pdfRenderer;
PdfCreateRenderer(dxgiDevice.Get(), &pdfRenderer)

The IPdfRendererNative Interface The IPdfRendererNative interface is the only interface supported in the native API. The interface contains two helper methods: RenderPageToSurface and RenderPageToDeviceContext.

RenderPageToSurface is the correct method to use when rendering PDF content to either a XAML SurfaceImageSource or VirtualSurfaceImageSource. The RenderPageToSurface method takes a PdfPage as an input parameter along with an instance of DXGISurface to which to draw the content, an offset on the device to draw and an optional PDF_RENDER_PARAMS structure. As you examine the RenderPageToSurface method, you might be surprised to see the PdfPage input being of type IUnknown. How do you get a PdfPage of type IUnknown? It turns out that with the WinRT API, once you get a valid PdfPage instance from a PdfDocument object, you can use safe_cast to cast a PdfPage to IUnknown.

When you use either the SurfaceImageSource or VirtualSurface­ImageSource interop types, calling BeginDraw returns an offset into the atlas that the XAML framework provides your application to draw content. You should pass that offset to RenderPageToSurface to ensure that drawing happens at the correct position.

The RenderPageToDeviceContext is the correct method to use when rendering PDF content to a XAML SwapChainBackgroundPanel. The RenderPageToDeviceContext takes a PdfPage as an input parameter along with an instance of ID2D1DeviceContext to which to draw the content and an optional PDF_RENDER_PARAMS structure.

In addition to drawing directly on-screen, the rendering can also be customized by using the PDF_RENDER_PARAMS structure. Both the RenderPageToSurface and RenderPageToDeviceContext accept an instance of PDF_RENDER_PARAMS. The options provided in PDF_RENDER_PARAMS are similar to the WinRT PDFPageRenderOptions structure. Note that neither of the native APIs are asynchronous methods. The rendering happens directly on-screen without incurring the cost of encoding and decoding.

Again, a simple app demonstrates how to use these APIs to render PDF content. This sample app (DxPdfApp in the accompanying code download) contains a MainPage with one button control, as shown in Figure 3.

Native PDF API App UI
Figure 3 Native PDF API App UI

The code to open a document and access a PDF page remains the same between the WinRT API and the native API. The major change is in the rendering process. While the WinRT APIs encode the PDF page as an image and write the image file to disk, the native APIs use a DirectX-based renderer to draw the PDF content on-screen, as shown in Figure 4.

Figure 4 The RenderPageRect Method Drawing PDF Content On-Screen

void PageImageSource::RenderPageRect(RECT rect)
{
  m_spRenderTask = m_spRenderTask.then([this, rect]() -> VSISData {
    VSISData vsisData;
    if (!is_task_cancellation_requested())
    {
      HRESULT hr = m_vsisNative->BeginDraw(
        rect,
        &(vsisData.dxgiSurface),
        &(vsisData.offset));
      if (SUCCEEDED(hr))
      {
        vsisData.fContinue = true;
      }
      else
      {
        vsisData.fContinue = false;
      }
    }
    else
    {
      cancel_current_task();
    }
    return vsisData;
  }, m_cts.get_token(), task_continuation_context::use_current())
  .then([this, rect](task<VSISData> beginDrawTask) -> VSISData {
    VSISData vsisData;
    try
    {
      vsisData = beginDrawTask.get();
      if ((m_pdfPage != nullptr) && vsisData.fContinue)
      {
        ComPtr<IPdfRendererNative> pdfRendererNative;
        m_renderer->GetPdfNativeRenderer(&pdfRendererNative);
        Windows::Foundation::Size pageSize = m_pdfPage->Size;
        float scale = min(static_cast<float>(
          m_width) / pageSize.Width,
          static_cast<float>(m_height) / pageSize.Height);
          IUnknown* pdfPageUnknown = (IUnknown*)
          reinterpret_cast<IUnknown*>(m_pdfPage);
          auto params = PdfRenderParams(D2D1::RectF((rect.left / scale),
            (rect.top / scale),
            (rect.right / scale),
            (rect.bottom / scale)),
            rect.right - rect.left,
            rect.bottom - rect.top,
            D2D1::ColorF(D2D1::ColorF::White),
            FALSE
            );
          pdfRendererNative->RenderPageToSurface(
            pdfPageUnknown,
            vsisData.dxgiSurface.Get(),
            vsisData.offset, &params);
      }
    }
    catch (task_canceled&)
    {
    }
    return vsisData;
  }, cancellation_token::none(), task_continuation_context::use_arbitrary())
  .then([this](task<VSISData> drawTask) {
    VSISData vsisData;
    try
    {
      vsisData = drawTask.get();
      if (vsisData.fContinue)
        m_vsisNative->EndDraw();
    }
    catch (task_canceled&)
    {
    }
  }, cancellation_token::none(), task_continuation_context::use_current());
}
}

Learn More

I discussed the WinRT PDF API in Windows 8.1 that lets you incorporate PDF content in Windows Store apps. I also discussed the differences between using the WinRT API or the C++/DirectX API, and the benefits of each approach. Finally, I provided a set of recommendations on which API should be used under certain scenarios. For more information on the PDF APIs in Windows 8.1, check out the documentation and PDF viewer showcase sample on MSDN at bit.ly/1bD72TO.

 


Sridhar Poduri is a program manager at Microsoft. A C++ aficionado and author of the book, “Modern C++ and Windows Store Apps” (Sridhar Poduri, 2013), he blogs regularly about C++ and the Windows Runtime at sridharpoduri.com.

Thanks to the following technical expert for reviewing this article: Subramanian Iyer (Microsoft)
Subramanian Iyer is a developer on the Windows team at Microsoft and has been involved with Windows development over the last three releases. He has been a part of the Reader team, which developed one of the first C++/XAML apps for Windows 8. A programmer and a new parent, he had found some time to publish a couple of apps on the Windows Store, under the name LSubs.