この記事は、Windows & Microsoft技術 基礎 Advent Calendar 2015の24日目の記事です。Windows APIプログラミングのあんまり基礎っぽくない内容です。


Windows 8.1には、「リーダー」というPDFビューアー(ストアアプリ)があります。ということは、当然PDFを描画するAPIも追加されています:WinRT/Metro TIPS:Windows 8.1の新機能、PDFを表示するには?[Windows 8.1ストア・アプリ開発] – @IT。これをネイティブC++のデスクトップアプリから使ってみました。

上記記事ではPdfPage.RenderToStreamAsyncで描画してますが、このほかにもIPdfRendererNativeというインタフェースがあります。これには、ID2D1DeviceContextやIDXGISurfaceに描画するメソッドがあるんです。今回、ID2D1DeviceContextに描画するRenderPageToDeviceContextメソッドを使いました。以下、サンプルコードです。

#define UNICODE
#define _UNICODE
 
#include <cstdlib>
#include <utility>
 
#include <windows.h>
#include <shcore.h>
#include <d3d11_1.h>
#include <dxgi1_2.h>
#include <d2d1_2.h>
 
#include <windows.storage.h>
#include <windows.storage.streams.h>
#include <windows.data.pdf.h>
#include <windows.data.pdf.interop.h>
 
#include <wrl/client.h>
#include <wrl/event.h>
 
#include <atlbase.h>
#include <atlwin.h>
#include <atltypes.h>
 
#pragma comment(lib, "shcore.lib")
#pragma comment(lib, "runtimeobject.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "windows.data.pdf.lib")
 
using namespace ABI::Windows::Data::Pdf;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Storage;
using namespace ABI::Windows::Storage::Streams;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
 
class TestWindow
  : public ATL::CWindowImpl<TestWindow>
{
public:
  DECLARE_WND_CLASS_EX(L"Test Window Class", 0, -1);
 
  BEGIN_MSG_MAP(TestWindow)
    MESSAGE_HANDLER(WM_SIZE, OnSize) //MSG_WM_SIZE(OnSize)
    MESSAGE_HANDLER(WM_PAINT, OnPaint) //MSG_WM_PAINT(OnPaint)
    MESSAGE_HANDLER(WM_CREATE, OnCreate) //MSG_WM_CREATE(OnCreate)
    MESSAGE_HANDLER(WM_DESTROY, OnDestroy) //MSG_WM_DESTROY(OnDestroy)
  END_MSG_MAP()
 
private:
  //void OnSize(UINT type, SIZE size)
  LRESULT OnSize(UINT, WPARAM type, LPARAM lp, BOOL&)
  {
    SIZE size{ GET_X_LPARAM(lp), GET_Y_LPARAM(lp) };
    if (type == SIZE_MINIMIZED || type == SIZE_MAXHIDE)
      return 0;
 
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
    auto hr = resource.m_dxgiSwapChain->GetDesc1(&swapChainDesc);
    if (SUCCEEDED(hr)
      && swapChainDesc.Width == size.cx && swapChainDesc.Height == size.cy)
    {
      return 0;
    }
    resource.m_d2dDeviceContext->SetTarget(nullptr);
 
    hr = resource.m_dxgiSwapChain->ResizeBuffers(
      swapChainDesc.BufferCount, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
    if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
    {
      HandleDeviceLost();
    }
    InitializeRenderTarget();
 
    BOOL b{};
    OnPaint(0, 0, 0, b); // WTL使用時はOnPaint(nullptr);
    return 0;
  }
 
  //void OnPaint(HDC)
  LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&)
  {
    OnRender();
    ValidateRect(nullptr);
    return 0;
  }
 
  void OnRender()
  {
    resource.m_d2dDeviceContext->BeginDraw();
    OnRenderCore();
    auto hr = resource.m_d2dDeviceContext->EndDraw();
    if (hr == D2DERR_RECREATE_TARGET)
    {
      HandleDeviceLost();
    }
    else if (SUCCEEDED(hr))
    {
      hr = resource.m_dxgiSwapChain->Present(1, 0);
      if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
      {
        HandleDeviceLost();
      }
    }
  }
 
  void OnRenderCore()
  {
    resource.m_d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Black));
    if (m_document != nullptr)
    {
      Microsoft::WRL::ComPtr<ABI::Windows::Data::Pdf::IPdfPage> page;
      auto hr = m_document->GetPage(0, &page);
      if (SUCCEEDED(hr))
      {
        auto params = PdfRenderParams();
        resource.m_pdfRenderer->RenderPageToDeviceContext(
          page.Get(), resource.m_d2dDeviceContext.Get(), &params);
      }
    }
  }
 
  //LRESULT OnCreate(const CREATESTRUCT*)
  LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)
  {
    if (FAILED(CreateDeviceIndependentResources()))
      return -1;
    if (FAILED(CreateDeviceResources()))
      return -1;
    LoadPdf();
    return 0;
  }
 
  void HandleDeviceLost()
  {
    //MessageBox(L"デバイスロストが発生しました。");
    OutputDebugStringW(L"デバイスロストが発生しました。\r\n");
 
    ReleaseDeviceResources();
    CreateDeviceResources();
    // いったんメッセージループに戻したくて、OnRenderを直接呼ばず、こうしている。
    Invalidate();
  }
 
  //void OnDestroy()
  LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL&)
  {
    PostQuitMessage(0);
    RevokeDragDrop(m_hWnd);
    ReleaseDeviceResources();
    return 0;
  }
 
  void ReleaseDeviceResources()
  {
    resource = {};
  }
 
  HRESULT Direct3DCreateDevice(
    D3D_DRIVER_TYPE driverType, _COM_Outptr_ ID3D11Device** ppDevice)
  {
    UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#ifdef _DEBUG
    //flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
 
    const D3D_FEATURE_LEVEL featureLevels[] =
    {
      D3D_FEATURE_LEVEL_11_1,
      D3D_FEATURE_LEVEL_11_0,
      D3D_FEATURE_LEVEL_10_1,
      D3D_FEATURE_LEVEL_10_0,
      D3D_FEATURE_LEVEL_9_3,
      D3D_FEATURE_LEVEL_9_2,
      D3D_FEATURE_LEVEL_9_1,
    };
    return D3D11CreateDevice(nullptr,
      driverType,
      nullptr,
      flags,
      featureLevels,
      ARRAYSIZE(featureLevels),
      D3D11_SDK_VERSION,
      ppDevice,
      nullptr,
      nullptr);
  }
 
  HRESULT Direct3DCreateDevice(_COM_Outptr_ ID3D11Device** ppDevice)
  {
    auto hr = Direct3DCreateDevice(D3D_DRIVER_TYPE_HARDWARE, ppDevice);
    if (SUCCEEDED(hr)) return hr;
    return Direct3DCreateDevice(D3D_DRIVER_TYPE_WARP, ppDevice);
  }
 
  HRESULT CreateDeviceResources()
  {
    ComPtr<ID3D11Device> device;
 
    auto hr = Direct3DCreateDevice(&device);
    if (FAILED(hr)) return hr;
    ComPtr<IDXGIDevice2> dxgiDevice;
    hr = device.As(&dxgiDevice);
    if (FAILED(hr)) return hr;
 
    ComPtr<ID2D1Device> d2d1Device;
    hr = m_d2dFactory->CreateDevice(dxgiDevice.Get(), &d2d1Device);
    if (FAILED(hr)) return hr;
 
    hr = d2d1Device->CreateDeviceContext(
      D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
      &resource.m_d2dDeviceContext);
    if (FAILED(hr)) return hr;
    ComPtr<IDXGIAdapter> dxgiAdapter;
    hr = dxgiDevice->GetAdapter(&dxgiAdapter);
    if (FAILED(hr)) return hr;
    ComPtr<IDXGIFactory2> dxgiFactory;
    hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
    if (FAILED(hr)) return hr;
 
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
    swapChainDesc.Width = 0;
    swapChainDesc.Height = 0;
    swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    swapChainDesc.Stereo = FALSE;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.BufferCount = 2;
    swapChainDesc.Scaling = DXGI_SCALING_NONE;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
    swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
    hr = dxgiFactory->CreateSwapChainForHwnd(
      device.Get(),
      m_hWnd,
      &swapChainDesc,
      nullptr,
      nullptr,
      &resource.m_dxgiSwapChain);
    if (FAILED(hr)) return hr;
    (void)dxgiDevice->SetMaximumFrameLatency(1);
    hr = InitializeRenderTarget();
    if (FAILED(hr)) return hr;
    hr = PdfCreateRenderer(dxgiDevice.Get(), &resource.m_pdfRenderer);
    if (FAILED(hr)) return hr;
    return S_OK;
  }
 
  HRESULT InitializeRenderTarget()
  {
    ComPtr<ID2D1Bitmap1> d2d1RenderTarget;
    ComPtr<IDXGISurface2> dxgiSurface;
    auto hr = resource.m_dxgiSwapChain->GetBuffer(
      0, IID_PPV_ARGS(&dxgiSurface));
    if (FAILED(hr)) return hr;
 
    float dpiX, dpiY;
    m_d2dFactory->GetDesktopDpi(&dpiX, &dpiY);
    hr = resource.m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
      dxgiSurface.Get(),
      D2D1::BitmapProperties1(
        D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
        D2D1::PixelFormat(
          DXGI_FORMAT_B8G8R8A8_UNORM,
          D2D1_ALPHA_MODE_IGNORE)),
      &d2d1RenderTarget);
    if (FAILED(hr)) return hr;
    resource.m_d2dDeviceContext->SetTarget(d2d1RenderTarget.Get());
    return S_OK;
  }
 
  HRESULT LoadPdf()
  {
    ComPtr<IRandomAccessStream> s;
    auto hr = CreateRandomAccessStreamOnFile(
      __wargv[1], FileAccessMode_Read, IID_PPV_ARGS(&s));
    if (FAILED(hr))
      return hr;
    return LoadPdf(s.Get());
  }
 
  HRESULT LoadPdf(_In_ IRandomAccessStream* s)
  {
    ComPtr<IPdfDocumentStatics> pdfDocumentsStatics;
    HStringReference className(RuntimeClass_Windows_Data_Pdf_PdfDocument);
    // 以下はRoGetActivationFactoryのラッパー
    auto hr = Windows::Foundation::GetActivationFactory(
      className.Get(), &pdfDocumentsStatics);
    if (FAILED(hr))
      return hr;
    ComPtr<IAsyncOperation<PdfDocument*>> async;
    hr = pdfDocumentsStatics->LoadFromStreamAsync(s, &async);
    if (FAILED(hr))
      return hr;
    auto callback = Callback<IAsyncOperationCompletedHandler<PdfDocument*>>(
      [this](_In_ IAsyncOperation<PdfDocument*>* async, AsyncStatus status)
    {
      if (status != AsyncStatus::Completed)
        return S_FALSE;
      ComPtr<IPdfDocument> doc;
      auto hr = async->GetResults(&doc);
      if (SUCCEEDED(hr))
      {
        m_document = std::move(doc);
        Invalidate();
      }
      return hr;
    });
    hr = async->put_Completed(callback.Get());
    if (FAILED(hr))
      return hr;
    return S_OK;
  }
 
  HRESULT CreateDeviceIndependentResources()
  {
    D2D1_FACTORY_OPTIONS options = {};
#ifdef _DEBUG
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
    auto hr = D2D1CreateFactory(
      D2D1_FACTORY_TYPE_MULTI_THREADED,
      options,
      m_d2dFactory.GetAddressOf());
    if (FAILED(hr)) return hr;
    return S_OK;
  }
 
  // デバイス非依存リソース
  ComPtr<ID2D1Factory1> m_d2dFactory;
  // デバイス依存リソース
  struct DeviceResources
  {
    ComPtr<IDXGISwapChain1> m_dxgiSwapChain;
    ComPtr<ID2D1DeviceContext> m_d2dDeviceContext;
    ComPtr<IPdfRendererNative> m_pdfRenderer;
  } resource;
  // その他
  ComPtr<IPdfDocument> m_document;
};
 
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int cmdShow)
{
  CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
 
  TestWindow wnd;
  if (!wnd.Create(nullptr, ATL::CWindow::rcDefault,
    TEXT("Hello, world"), WS_OVERLAPPEDWINDOW))
  {
    std::quick_exit(-1);
  }
 
  wnd.ShowWindow(cmdShow);
  wnd.UpdateWindow();
 
  MSG msg;
  while (GetMessage(&msg, nullptr, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  std::quick_exit(static_cast<int>(msg.wParam));
}

このプログラムは、コマンドライン引数に与えられたPDFファイルを読み込み、その1ページ目(固定)を描画する内容になっています。

このサンプルではまるで何も指定していませんが、描画座標や拡大・縮小などはRenderPageToDeviceContextメソッドで指定可能です。

Direct2Dアプリとしてのコードは、前日の記事D2Dデバイスコンテキストを使う最小サンプルを元にしています。ただし、以下の点を変更しました。

  • DXGI_SWAP_EFFECT_FLIP_SEQUENTIALおよびDXGI_SCALING_NONEを採用。Windows 8.1以上を前提として良いため。
  • Direct3DとDirect2Dをマルチスレッドで使用する指定に変更。MSDNライブラリのどこにも書いていないけど、こうしないと不意にクラッシュする。
    • D3D11_CREATE_DEVICE_SINGLETHREADEDを外す。
    • D2D1_FACTORY_TYPE_SINGLE_THREADEDをD2D1_FACTORY_TYPE_MULTI_THREADEDに変える。

PdfDocument.LoadFromStreamAsyncメソッドは非同期なので、やや苦労しました。ネイティブC++では、WinRT APIの非同期の処理にMicrosoft::WRL::Callback関数を使います。ソースコード上、PdfDocumentと書くべきところとIPdfDocumentと書くべきところが混在しており、コンパイルが通るコードが出来上がるまで時間がかかりました。次はこれをC++/CXで書き直したものも作ってみたいと思います。

以上、C++製デスクトップアプリからPDF描画のAPIを叩いてみる話でした。PdfCreateRendererとCreateRandomAccessStreamOnFileというマイナーな関数2つの紹介となりました。


スポンサード リンク

この記事のカテゴリ

  • ⇒ Direct2DでPDFを描画するAPIを使ってみた
  • ⇒ Direct2DでPDFを描画するAPIを使ってみた
  • ⇒ Direct2DでPDFを描画するAPIを使ってみた
  • ⇒ Direct2DでPDFを描画するAPIを使ってみた