PDF描画の試み、続きます。今回は以下の発想により、ドラッグ&ドロップ (D&D)でPDFファイルを受け取り、前回同様に画面に表示するというものになりました。

  • CreateRandomAccessStreamOverStreamという、IStreamからIRandomAccessStreamを作り出す関数を見つけた。
  • そうだ。D&Dで受け取ったデータは直接IStreamとして受け取れるはず (TYMED_ISTREAM)。

試してみたところ、CFSTR_FILECONTENTSを指定することで、うまくいきました。当該部分のコードはこうです。

IFACEMETHOD(Drop)(
  _In_opt_ IDataObject* pDataObj,
  DWORD grfKeyState,
  POINTL pt,
  _Inout_ DWORD* pdwEffect) override
{
  if (pDataObj == nullptr)
    return E_INVALIDARG;
  if (pdwEffect == nullptr)
    return E_POINTER;
 
  FORMATETC formatetc{};
  formatetc.cfFormat = CF_FILECONTENTS;
  formatetc.dwAspect = DVASPECT_CONTENT;
  formatetc.lindex = 0;
  formatetc.tymed = TYMED_ISTREAM;
  STGMEDIUM medium;
  auto hr = pDataObj->GetData(&formatetc, &medium);
  if (SUCCEEDED(hr))
  {
    LoadPdf(medium.pstm);
    ReleaseStgMedium(&medium);
    *pdwEffect = DROPEFFECT_COPY;
  }
  else
  {
    *pdwEffect = DROPEFFECT_NONE;
  }
  return S_OK;
}
 
HRESULT LoadPdf(_In_ IStream* src)
{
  if (src == nullptr) return E_INVALIDARG;
 
  ComPtr<IStream> tmp;
  auto hr = CopyStream(src, &tmp);
  if (FAILED(hr)) return hr;
 
  ComPtr<IRandomAccessStream> s;
  hr = CreateRandomAccessStreamOverStream(
    tmp.Get(), BSOS_DEFAULT, IID_PPV_ARGS(&s));
  if (FAILED(hr)) return hr;
 
  hr = LoadPdf(s.Get());
  if (FAILED(hr)) return hr;
 
  m_stream = std::move(tmp);
  return S_OK;
}
 
HRESULT CopyStream(_In_ IStream* src, _COM_Outptr_ IStream** dst)
{
  if (src == nullptr) return E_INVALIDARG;
  assert(dst != nullptr);
 
  ComPtr<IStream> tmp;
  tmp.Attach(SHCreateMemStream(nullptr, 0));
  if (tmp == nullptr) return E_OUTOFMEMORY;
 
  char buf[16 * 1024];
  for (;;)
  {
    ULONG read;
    auto hrRead = src->Read(buf, sizeof buf, &read);
    if (FAILED(hrRead)) return hrRead;
    if (read == 0) break;
    DWORD written;
    auto hrWrite= tmp->Write(buf, read, &written);
    if (FAILED(hrWrite)) return hrWrite;
    if (written != read) return E_FAIL;
  }
  auto hrReset = IStream_Reset(tmp.Get());
  if (FAILED(hrReset)) return hrReset;
  *dst = tmp.Detach();
  return S_OK;
}

ドラッグ元として、通常のフォルダのほか、エクスプローラでLHA/ZIP/CABファイルを開き、その中のPDFファイルも可能なことを確かめました。以下の問題に遭遇したので、その回避するコード(CopyStream関数)を作る羽目になりましたが。

  • ZIPファイルからだと、受信したIStreamがIDropTarget::Dropの間しか使えない。
  • CABファイルからだと、受信したIStreamがStatを実装していない(E_NOTIMPLが返る)ため、サイズが不明。当初のCopyStream関数の実装は、「IStream_Size関数で大きさを得て、ReadとWriteを1回で済ます」というものだったが、これを断念した。

以下、ソースコード全体です。前回までの「コマンドライン引数から読み込む処理」は#if 0(2ヶ所)で残してあります。

#define UNICODE
#define _UNICODE
 
#include <cassert>
#include <cstdlib>
#include <utility>
 
#include <windows.h>
#include <shlobj.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;
 
//static const auto CF_FILEDESCRIPTOR = static_cast<CLIPFORMAT>(
//  ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR));
static const auto CF_FILECONTENTS = static_cast<CLIPFORMAT>(
  ::RegisterClipboardFormat(CFSTR_FILECONTENTS));
 
class Module : public ATL::CAtlExeModuleT<Module> {};
Module module;
 
class TestWindow
  : public ATL::CWindowImpl<TestWindow>
  , public ATL::CComObjectRootEx<ATL::CComSingleThreadModel>
  , public IDropTarget
{
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()
 
  BEGIN_COM_MAP(TestWindow)
    COM_INTERFACE_ENTRY(IDropTarget)
  END_COM_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);
      }
    }
  }
 
  IFACEMETHOD(DragEnter)(
    _In_opt_ IDataObject* pDataObj,
    DWORD grfKeyState,
    POINTL pt,
    _Inout_ DWORD* pdwEffect) override
  {
    if (pDataObj == nullptr)
      return E_INVALIDARG;
    if (pdwEffect == nullptr)
      return E_POINTER;
    FORMATETC formatetc{};
    formatetc.cfFormat = CF_FILECONTENTS;
    formatetc.dwAspect = DVASPECT_CONTENT;
    formatetc.lindex = 0;
    formatetc.tymed = TYMED_ISTREAM;
    auto hr = pDataObj->QueryGetData(&formatetc);
    if (FAILED(hr))
    {
      *pdwEffect = DROPEFFECT_NONE;
      return DRAGDROP_S_CANCEL;
    }
    else
    {
      *pdwEffect = DROPEFFECT_COPY;
      return S_OK;
    }
  }
 
  IFACEMETHOD(DragOver)(
    DWORD grfKeyState,
    POINTL pt,
    _Inout_ DWORD* pdwEffect) override
  {
    if (pdwEffect == nullptr)
      return E_POINTER;
    *pdwEffect = DROPEFFECT_COPY;
    return S_OK;
  }
 
  IFACEMETHOD(DragLeave)() override
  {
    return S_OK;
  }
 
  IFACEMETHOD(Drop)(
    _In_opt_ IDataObject* pDataObj,
    DWORD grfKeyState,
    POINTL pt,
    _Inout_ DWORD* pdwEffect) override
  {
    if (pDataObj == nullptr)
      return E_INVALIDARG;
    if (pdwEffect == nullptr)
      return E_POINTER;
 
    FORMATETC formatetc{};
    formatetc.cfFormat = CF_FILECONTENTS;
    formatetc.dwAspect = DVASPECT_CONTENT;
    formatetc.lindex = 0;
    formatetc.tymed = TYMED_ISTREAM;
    STGMEDIUM medium;
    auto hr = pDataObj->GetData(&formatetc, &medium);
    if (SUCCEEDED(hr))
    {
      LoadPdf(medium.pstm);
      ReleaseStgMedium(&medium);
      *pdwEffect = DROPEFFECT_COPY;
    }
    else
    {
      *pdwEffect = DROPEFFECT_NONE;
    }
    return S_OK;
  }
 
  HRESULT LoadPdf(_In_ IStream* src)
  {
    if (src == nullptr) return E_INVALIDARG;
 
    ComPtr<IStream> tmp;
    auto hr = CopyStream(src, &tmp);
    if (FAILED(hr)) return hr;
 
    ComPtr<IRandomAccessStream> s;
    hr = CreateRandomAccessStreamOverStream(
      tmp.Get(), BSOS_DEFAULT, IID_PPV_ARGS(&s));
    if (FAILED(hr)) return hr;
 
    hr = LoadPdf(s.Get());
    if (FAILED(hr)) return hr;
 
    m_stream = std::move(tmp);
    return S_OK;
  }
 
  HRESULT CopyStream(_In_ IStream* src, _COM_Outptr_ IStream** dst)
  {
    if (src == nullptr) return E_INVALIDARG;
    assert(dst != nullptr);
 
    ComPtr<IStream> tmp;
    tmp.Attach(SHCreateMemStream(nullptr, 0));
    if (tmp == nullptr) return E_OUTOFMEMORY;
 
    char buf[16 * 1024];
    for (;;)
    {
      ULONG read;
      auto hrRead = src->Read(buf, sizeof buf, &read);
      if (FAILED(hrRead)) return hrRead;
      if (read == 0) break;
      DWORD written;
      auto hrWrite= tmp->Write(buf, read, &written);
      if (FAILED(hrWrite)) return hrWrite;
      if (written != read) return E_FAIL;
    }
    auto hrReset = IStream_Reset(tmp.Get());
    if (FAILED(hrReset)) return hrReset;
    *dst = tmp.Detach();
    return S_OK;
  }
 
  //LRESULT OnCreate(const CREATESTRUCT*)
  LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)
  {
    if (FAILED(CreateDeviceIndependentResources()))
      return -1;
    if (FAILED(CreateDeviceResources()))
      return -1;
#if 0
    LoadPdf();
#endif
    RegisterDragDrop(m_hWnd, this);
    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;
  }
 
#if 0
  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());
  }
#endif
 
  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<IStream> m_stream;
  ComPtr<IPdfDocument> m_document;
};
 
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int cmdShow)
{
  OleInitialize(nullptr);
 
  ATL::CComObjectStackEx<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));
}

なお、D&Dでデータを受け取るプログラムとしては、改良の余地があります。今回のCFSTR_FILECONTENTS (IStream)で受け取る方式だと、プロセス間通信でファイルの内容まるごとをやり取りするため、効率は決して良くないはずなのです。実際には、CFSTR_SHELLIDLISTやCF_HDROPをまず試して、その次にCFSTR_FILECONTENTSを試すほうが良いと思います。

実は、PdfCreateRenderer関数のサンプルとして当初作ったのがこのD&D対応版でした。それだと盛り込みすぎだと考え直し、当初公開したもの(Direct2DでPDFを描画するAPIを使ってみた)ではD&Dの処理を削ったものにしました。


スポンサード リンク

この記事のカテゴリ