D&DでIStreamを得てPDF描画する
コメントなし
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(), ¶ms); } } } 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の処理を削ったものにしました。
スポンサード リンク |