Home | History | Annotate | Download | only in metro_driver
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "stdafx.h"
      6 #include "win8/metro_driver/print_document_source.h"
      7 
      8 #include <windows.graphics.display.h>
      9 
     10 #include "base/logging.h"
     11 #include "base/safe_numerics.h"
     12 
     13 
     14 namespace {
     15 
     16 class D2DFactoryAutoLock {
     17  public:
     18   explicit D2DFactoryAutoLock(ID2D1Factory* d2d_factory) {
     19     HRESULT hr = d2d_factory->QueryInterface(IID_PPV_ARGS(&d2d_multithread_));
     20     if (d2d_multithread_.Get())
     21       d2d_multithread_->Enter();
     22     else
     23       NOTREACHED() << "Failed to QI for ID2D1Multithread " << std::hex << hr;
     24   }
     25 
     26   ~D2DFactoryAutoLock() {
     27     if (d2d_multithread_.Get())
     28       d2d_multithread_->Leave();
     29   }
     30 
     31  private:
     32   mswr::ComPtr<ID2D1Multithread> d2d_multithread_;
     33 };
     34 
     35 // TODO(mad): remove once we don't run mixed SDK/OS anymore.
     36 const GUID kOldPackageTargetGuid =
     37     {0xfb2a33c0, 0x8c35, 0x465f,
     38       {0xbe, 0xd5, 0x9f, 0x36, 0x89, 0x51, 0x77, 0x52}};
     39 const GUID kNewPackageTargetGuid =
     40     {0x1a6dd0ad, 0x1e2a, 0x4e99,
     41       {0xa5, 0xba, 0x91, 0xf1, 0x78, 0x18, 0x29, 0x0e}};
     42 
     43 
     44 }  // namespace
     45 
     46 namespace metro_driver {
     47 
     48 PrintDocumentSource::PrintDocumentSource()
     49     : page_count_ready_(true, false),
     50       parent_lock_(NULL),
     51       height_(0),
     52       width_(0),
     53       dpi_(96),
     54       aborted_(false),
     55       using_old_preview_interface_(false) {
     56 }
     57 
     58 HRESULT PrintDocumentSource::RuntimeClassInitialize(
     59     const DirectXContext& directx_context,
     60     base::Lock* parent_lock) {
     61   DCHECK(parent_lock != NULL);
     62   DCHECK(directx_context.d2d_context.Get() != NULL);
     63   DCHECK(directx_context.d2d_device.Get() != NULL);
     64   DCHECK(directx_context.d2d_factory.Get() != NULL);
     65   DCHECK(directx_context.d3d_device.Get() != NULL);
     66   DCHECK(directx_context.wic_factory.Get() != NULL);
     67   directx_context_ = directx_context;
     68 
     69   // No other method can be called before RuntimeClassInitialize which is called
     70   // during the construction via mswr::MakeAndInitialize(), so it's safe for all
     71   // other methods to use the parent_lock_ without checking if it's NULL.
     72   DCHECK(parent_lock_ == NULL);
     73   parent_lock_ = parent_lock;
     74 
     75   return S_OK;
     76 }
     77 
     78 void PrintDocumentSource::Abort() {
     79   base::AutoLock lock(*parent_lock_);
     80   aborted_ = true;
     81   if (page_count_ready_.IsSignaled()) {
     82     pages_.clear();
     83     for (size_t i = 0; i < pages_ready_state_.size(); ++i)
     84       pages_ready_state_[i]->Broadcast();
     85   } else {
     86     DCHECK(pages_.empty() && pages_ready_state_.empty());
     87   }
     88 }
     89 
     90 STDMETHODIMP PrintDocumentSource::GetPreviewPageCollection(
     91     IPrintDocumentPackageTarget* package_target,
     92     IPrintPreviewPageCollection** page_collection) {
     93   DVLOG(1) << __FUNCTION__;
     94   DCHECK(package_target != NULL);
     95   DCHECK(page_collection != NULL);
     96 
     97   HRESULT hr = package_target->GetPackageTarget(
     98       __uuidof(IPrintPreviewDxgiPackageTarget),
     99       IID_PPV_ARGS(&dxgi_preview_target_));
    100   if (FAILED(hr)) {
    101     // TODO(mad): remove once we don't run mixed SDK/OS anymore.
    102     // The IID changed from one version of the SDK to another, so try the other
    103     // one in case we are running a build from a different SDK than the one
    104     // related to the OS version we are running.
    105     GUID package_target_uuid = kNewPackageTargetGuid;
    106     if (package_target_uuid == __uuidof(IPrintPreviewDxgiPackageTarget)) {
    107       package_target_uuid = kOldPackageTargetGuid;
    108       using_old_preview_interface_ = true;
    109     }
    110     hr = package_target->GetPackageTarget(package_target_uuid,
    111                                           package_target_uuid,
    112                                           &dxgi_preview_target_);
    113     if (FAILED(hr)) {
    114       LOG(ERROR) << "Failed to get IPrintPreviewDXGIPackageTarget " << std::hex
    115                  << hr;
    116       return hr;
    117     }
    118   } else {
    119     using_old_preview_interface_ = (__uuidof(IPrintPreviewDxgiPackageTarget) ==
    120                                     kOldPackageTargetGuid);
    121   }
    122 
    123   mswr::ComPtr<IPrintPreviewPageCollection> preview_page_collection;
    124   mswr::ComPtr<PrintDocumentSource> print_document_source(this);
    125   hr = print_document_source.As(&preview_page_collection);
    126   if (FAILED(hr)) {
    127     LOG(ERROR) << "Failed to get preview_page_collection " << std::hex << hr;
    128     return hr;
    129   }
    130 
    131   hr = preview_page_collection.CopyTo(page_collection);
    132   if (FAILED(hr)) {
    133     LOG(ERROR) << "Failed to copy preview_page_collection " << std::hex << hr;
    134     return hr;
    135   }
    136   return hr;
    137 }
    138 
    139 STDMETHODIMP PrintDocumentSource::MakeDocument(
    140     IInspectable* options,
    141     IPrintDocumentPackageTarget* package_target) {
    142   DVLOG(1) << __FUNCTION__;
    143   DCHECK(options != NULL);
    144   DCHECK(package_target != NULL);
    145 
    146   mswr::ComPtr<wingfx::Printing::IPrintTaskOptionsCore> print_task_options;
    147   HRESULT hr = options->QueryInterface(
    148       wingfx::Printing::IID_IPrintTaskOptionsCore,
    149       reinterpret_cast<void**>(print_task_options.GetAddressOf()));
    150   if (FAILED(hr)) {
    151     LOG(ERROR) << "Failed to QI for IPrintTaskOptionsCore " << std::hex << hr;
    152     return hr;
    153   }
    154 
    155   // Use the first page's description for the whole document. Page numbers
    156   // are 1-based in this context.
    157   // TODO(mad): Check if it would be useful to use per page descriptions.
    158   wingfx::Printing::PrintPageDescription page_desc = {};
    159   hr = print_task_options->GetPageDescription(1 /* page */, &page_desc);
    160   if (FAILED(hr)) {
    161     LOG(ERROR) << "Failed to GetPageDescription " << std::hex << hr;
    162     return hr;
    163   }
    164 
    165   D2D1_PRINT_CONTROL_PROPERTIES print_control_properties;
    166   if (page_desc.DpiX > page_desc.DpiY)
    167     print_control_properties.rasterDPI = static_cast<float>(page_desc.DpiY);
    168   else
    169     print_control_properties.rasterDPI = static_cast<float>(page_desc.DpiX);
    170 
    171   // Color space for vector graphics in D2D print control.
    172   print_control_properties.colorSpace = D2D1_COLOR_SPACE_SRGB;
    173   print_control_properties.fontSubset = D2D1_PRINT_FONT_SUBSET_MODE_DEFAULT;
    174 
    175   mswr::ComPtr<ID2D1PrintControl> print_control;
    176   hr = directx_context_.d2d_device->CreatePrintControl(
    177       directx_context_.wic_factory.Get(),
    178       package_target,
    179       print_control_properties,
    180       print_control.GetAddressOf());
    181   if (FAILED(hr)) {
    182     LOG(ERROR) << "Failed to CreatePrintControl " << std::hex << hr;
    183     return hr;
    184   }
    185 
    186   D2D1_SIZE_F page_size = D2D1::SizeF(page_desc.PageSize.Width,
    187                                       page_desc.PageSize.Height);
    188 
    189   // Wait for the number of pages to be available.
    190   // If an abort occured, we'll get 0 and won't enter the loop below.
    191   size_t page_count = WaitAndGetPageCount();
    192 
    193   mswr::ComPtr<ID2D1GdiMetafile> gdi_metafile;
    194   for (size_t page = 0; page < page_count; ++page) {
    195     gdi_metafile.Reset();
    196     hr = WaitAndGetPage(page, gdi_metafile.GetAddressOf());
    197     LOG_IF(ERROR, FAILED(hr)) << "Failed to get page's metafile " << std::hex
    198                               << hr;
    199     // S_FALSE means we got aborted.
    200     if (hr == S_FALSE || FAILED(hr))
    201       break;
    202     hr = PrintPage(print_control.Get(), gdi_metafile.Get(), page_size);
    203     if (FAILED(hr))
    204       break;
    205   }
    206 
    207   HRESULT close_hr = print_control->Close();
    208   if (FAILED(close_hr) && SUCCEEDED(hr))
    209     return close_hr;
    210   else
    211     return hr;
    212 }
    213 
    214 STDMETHODIMP PrintDocumentSource::Paginate(uint32 page,
    215                                            IInspectable* options) {
    216   DVLOG(1) << __FUNCTION__ << ", page = " << page;
    217   DCHECK(options != NULL);
    218   // GetPreviewPageCollection must have been successfuly called.
    219   DCHECK(dxgi_preview_target_.Get() != NULL);
    220 
    221   // Get print settings from PrintTaskOptions for preview, such as page
    222   // description, which contains page size, imageable area, DPI.
    223   // TODO(mad): obtain other print settings in the same way, such as ColorMode,
    224   // NumberOfCopies, etc...
    225   mswr::ComPtr<wingfx::Printing::IPrintTaskOptionsCore> print_options;
    226   HRESULT hr = options->QueryInterface(
    227       wingfx::Printing::IID_IPrintTaskOptionsCore,
    228       reinterpret_cast<void**>(print_options.GetAddressOf()));
    229   if (FAILED(hr)) {
    230     LOG(ERROR) << "Failed to QI for IPrintTaskOptionsCore " << std::hex << hr;
    231     return hr;
    232   }
    233 
    234   wingfx::Printing::PrintPageDescription page_desc = {};
    235   hr = print_options->GetPageDescription(1 /* page */, &page_desc);
    236   if (FAILED(hr)) {
    237     LOG(ERROR) << "Failed to GetPageDescription " << std::hex << hr;
    238     return hr;
    239   }
    240 
    241   width_ = page_desc.PageSize.Width;
    242   height_ = page_desc.PageSize.Height;
    243 
    244   hr = dxgi_preview_target_->InvalidatePreview();
    245   if (FAILED(hr)) {
    246     LOG(ERROR) << "Failed to InvalidatePreview " << std::hex << hr;
    247     return hr;
    248   }
    249 
    250   size_t page_count = WaitAndGetPageCount();
    251   // A page_count of 0 means abort...
    252   if (page_count == 0)
    253     return S_FALSE;
    254   hr = dxgi_preview_target_->SetJobPageCount(
    255            PageCountType::FinalPageCount,
    256            base::checked_numeric_cast<UINT32>(page_count));
    257   if (FAILED(hr)) {
    258     LOG(ERROR) << "Failed to SetJobPageCount " << std::hex << hr;
    259     return hr;
    260   }
    261   return hr;
    262 }
    263 
    264 STDMETHODIMP PrintDocumentSource::MakePage(uint32 job_page,
    265                                            float width,
    266                                            float height) {
    267   DVLOG(1) << __FUNCTION__ << ", width: " << width << ", height: " << height
    268           << ", job_page: " << job_page;
    269   DCHECK(width > 0 && height > 0);
    270   // Paginate must have been called before this.
    271   if (width_ <= 0.0 || height_ <= 0.0)
    272     return S_FALSE;
    273 
    274   // When job_page is JOB_PAGE_APPLICATION_DEFINED, it means a new preview
    275   // begins. TODO(mad): Double check if we need to cancel pending resources.
    276   if (job_page == JOB_PAGE_APPLICATION_DEFINED)
    277     job_page = 1;
    278 
    279   winfoundtn::Size preview_size;
    280   preview_size.Width  = width;
    281   preview_size.Height = height;
    282   float scale = width_ / width;
    283 
    284   mswr::ComPtr<ID2D1Factory> factory;
    285   directx_context_.d2d_device->GetFactory(&factory);
    286 
    287   mswr::ComPtr<ID2D1GdiMetafile> gdi_metafile;
    288   HRESULT hr = WaitAndGetPage(job_page - 1, gdi_metafile.GetAddressOf());
    289   LOG_IF(ERROR, FAILED(hr)) << "Failed to get page's metafile " << std::hex
    290                             << hr;
    291   // Again, S_FALSE means we got aborted.
    292   if (hr == S_FALSE || FAILED(hr))
    293     return hr;
    294 
    295   // We are accessing D3D resources directly without D2D's knowledge, so we
    296   // must manually acquire the D2D factory lock.
    297   D2DFactoryAutoLock factory_lock(directx_context_.d2d_factory.Get());
    298 
    299   CD3D11_TEXTURE2D_DESC texture_desc(
    300       DXGI_FORMAT_B8G8R8A8_UNORM,
    301       static_cast<UINT32>(ceil(width  * dpi_ / 96)),
    302       static_cast<UINT32>(ceil(height * dpi_ / 96)),
    303       1,
    304       1,
    305       D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE
    306       );
    307   mswr::ComPtr<ID3D11Texture2D> texture;
    308   hr = directx_context_.d3d_device->CreateTexture2D(
    309       &texture_desc, NULL, &texture);
    310   if (FAILED(hr)) {
    311     LOG(ERROR) << "Failed to create a 2D texture " << std::hex << hr;
    312     return hr;
    313   }
    314 
    315   mswr::ComPtr<IDXGISurface> dxgi_surface;
    316   hr = texture.As<IDXGISurface>(&dxgi_surface);
    317   if (FAILED(hr)) {
    318     LOG(ERROR) << "Failed to QI for IDXGISurface " << std::hex << hr;
    319     return hr;
    320   }
    321 
    322   // D2D device contexts are stateful, and hence a unique device context must
    323   // be used on each call.
    324   mswr::ComPtr<ID2D1DeviceContext> d2d_context;
    325   hr = directx_context_.d2d_device->CreateDeviceContext(
    326       D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d_context);
    327 
    328   d2d_context->SetDpi(dpi_, dpi_);
    329 
    330   mswr::ComPtr<ID2D1Bitmap1> d2dSurfaceBitmap;
    331   hr = d2d_context->CreateBitmapFromDxgiSurface(dxgi_surface.Get(),
    332                                                 NULL,  // default properties.
    333                                                 &d2dSurfaceBitmap);
    334   if (FAILED(hr)) {
    335     LOG(ERROR) << "Failed to CreateBitmapFromDxgiSurface " << std::hex << hr;
    336     return hr;
    337   }
    338 
    339   d2d_context->SetTarget(d2dSurfaceBitmap.Get());
    340   d2d_context->BeginDraw();
    341   d2d_context->Clear();
    342   d2d_context->SetTransform(D2D1::Matrix3x2F(1/scale, 0, 0, 1/scale, 0, 0));
    343   d2d_context->DrawGdiMetafile(gdi_metafile.Get());
    344 
    345   hr = d2d_context->EndDraw();
    346   if (FAILED(hr)) {
    347     LOG(ERROR) << "Failed to EndDraw " << std::hex << hr;
    348     return hr;
    349   }
    350 
    351 // TODO(mad): remove once we don't run mixed SDK/OS anymore.
    352 #ifdef __IPrintPreviewDxgiPackageTarget_FWD_DEFINED__
    353   FLOAT dpi = dpi_;
    354   if (using_old_preview_interface_) {
    355     // We compiled with the new API but run on the old OS, so we must cheat
    356     // and send something that looks like a float but has a UINT32 value.
    357     *reinterpret_cast<UINT32*>(&dpi) = static_cast<UINT32>(dpi_);
    358   }
    359 #else
    360   UINT32 dpi = static_cast<UINT32>(dpi_);
    361   if (!using_old_preview_interface_) {
    362     // We compiled with the old API but run on the new OS, so we must cheat
    363     // and send something that looks like a UINT32 but has a float value.
    364     *reinterpret_cast<FLOAT*>(&dpi) = dpi_;
    365   }
    366 #endif  // __IPrintPreviewDxgiPackageTarget_FWD_DEFINED__
    367   hr = dxgi_preview_target_->DrawPage(job_page, dxgi_surface.Get(), dpi, dpi);
    368   if (FAILED(hr)) {
    369     LOG(ERROR) << "Failed to DrawPage " << std::hex << hr;
    370     return hr;
    371   }
    372   return hr;
    373 }
    374 
    375 void PrintDocumentSource::ResetDpi(float dpi) {
    376   {
    377     base::AutoLock lock(*parent_lock_);
    378     if (dpi == dpi_)
    379       return;
    380     dpi_ = dpi;
    381   }
    382   directx_context_.d2d_context->SetDpi(dpi, dpi);
    383 }
    384 
    385 void PrintDocumentSource::SetPageCount(size_t page_count) {
    386   DCHECK(page_count > 0);
    387   {
    388     base::AutoLock lock(*parent_lock_);
    389     DCHECK(!page_count_ready_.IsSignaled());
    390     DCHECK(pages_.empty() && pages_ready_state_.empty());
    391 
    392     pages_.resize(page_count);
    393     pages_ready_state_.resize(page_count);
    394 
    395     for (size_t i = 0; i < page_count; ++i)
    396       pages_ready_state_[i].reset(new base::ConditionVariable(parent_lock_));
    397   }
    398   page_count_ready_.Signal();
    399 }
    400 
    401 void PrintDocumentSource::AddPage(size_t page_number,
    402                                   IStream* metafile_stream) {
    403   DCHECK(metafile_stream != NULL);
    404   base::AutoLock lock(*parent_lock_);
    405 
    406   DCHECK(page_count_ready_.IsSignaled());
    407   DCHECK(page_number < pages_.size());
    408 
    409   pages_[page_number] = metafile_stream;
    410   pages_ready_state_[page_number]->Signal();
    411 }
    412 
    413 HRESULT PrintDocumentSource::PrintPage(ID2D1PrintControl* print_control,
    414                                        ID2D1GdiMetafile* gdi_metafile,
    415                                        D2D1_SIZE_F page_size) {
    416   DVLOG(1) << __FUNCTION__ << ", page_size: (" << page_size.width << ", "
    417           << page_size.height << ")";
    418   DCHECK(print_control != NULL);
    419   DCHECK(gdi_metafile != NULL);
    420 
    421   // D2D device contexts are stateful, and hence a unique device context must
    422   // be used on each call.
    423   mswr::ComPtr<ID2D1DeviceContext> d2d_context;
    424   HRESULT hr = directx_context_.d2d_device->CreateDeviceContext(
    425       D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d_context);
    426   if (FAILED(hr)) {
    427     LOG(ERROR) << "Failed to CreateDeviceContext " << std::hex << hr;
    428     return hr;
    429   }
    430 
    431   mswr::ComPtr<ID2D1CommandList> print_command_list;
    432   hr = d2d_context->CreateCommandList(&print_command_list);
    433   if (FAILED(hr)) {
    434     LOG(ERROR) << "Failed to CreateCommandList " << std::hex << hr;
    435     return hr;
    436   }
    437 
    438   d2d_context->SetTarget(print_command_list.Get());
    439 
    440   d2d_context->BeginDraw();
    441   d2d_context->DrawGdiMetafile(gdi_metafile);
    442   hr = d2d_context->EndDraw();
    443   LOG_IF(ERROR, FAILED(hr)) << "Failed to EndDraw " << std::hex << hr;
    444 
    445   // Make sure to always close the command list.
    446   HRESULT close_hr = print_command_list->Close();
    447   LOG_IF(ERROR, FAILED(close_hr)) << "Failed to close command list " << std::hex
    448                                   << hr;
    449   if (SUCCEEDED(hr) && SUCCEEDED(close_hr))
    450     hr = print_control->AddPage(print_command_list.Get(), page_size, NULL);
    451   if (FAILED(hr))
    452     return hr;
    453   else
    454     return close_hr;
    455 }
    456 
    457 size_t PrintDocumentSource::WaitAndGetPageCount() {
    458   // Properly protect the wait/access to the page count.
    459   {
    460     base::AutoLock lock(*parent_lock_);
    461     if (aborted_)
    462       return 0;
    463     DCHECK(pages_.size() == pages_ready_state_.size());
    464     if (!pages_.empty())
    465       return pages_.size();
    466   }
    467   page_count_ready_.Wait();
    468   {
    469     base::AutoLock lock(*parent_lock_);
    470     if (!aborted_) {
    471       DCHECK(pages_.size() == pages_ready_state_.size());
    472       return pages_.size();
    473     }
    474   }
    475   // A page count of 0 means abort.
    476   return 0;
    477 }
    478 
    479 HRESULT PrintDocumentSource::WaitAndGetPage(size_t page_number,
    480                                             ID2D1GdiMetafile** gdi_metafile) {
    481   // Properly protect the wait/access to the page data.
    482   base::AutoLock lock(*parent_lock_);
    483   // Make sure we weren't canceled before getting here.
    484   // And the page count should have been received before we get here too.
    485   if (aborted_)
    486     return S_FALSE;
    487 
    488   // We shouldn't be asked for a page until we got the page count.
    489   DCHECK(page_count_ready_.IsSignaled());
    490   DCHECK(page_number <= pages_ready_state_.size());
    491   DCHECK(pages_.size() == pages_ready_state_.size());
    492   while (!aborted_ && pages_[page_number].Get() == NULL)
    493     pages_ready_state_[page_number]->Wait();
    494 
    495   // Make sure we weren't aborted while we waited unlocked.
    496   if (aborted_)
    497     return S_FALSE;
    498   DCHECK(page_number < pages_.size());
    499 
    500   mswr::ComPtr<ID2D1Factory> factory;
    501   directx_context_.d2d_device->GetFactory(&factory);
    502 
    503   mswr::ComPtr<ID2D1Factory1> factory1;
    504   HRESULT hr = factory.As(&factory1);
    505   if (FAILED(hr)) {
    506     LOG(ERROR) << "Failed to QI for ID2D1Factory1 " << std::hex << hr;
    507     return hr;
    508   }
    509 
    510   ULARGE_INTEGER result;
    511   LARGE_INTEGER seek_pos;
    512   seek_pos.QuadPart = 0;
    513   hr = pages_[page_number]->Seek(seek_pos, STREAM_SEEK_SET, &result);
    514   if (FAILED(hr)) {
    515     LOG(ERROR) << "Failed to Seek page stream " << std::hex << hr;
    516     return hr;
    517   }
    518 
    519   hr = factory1->CreateGdiMetafile(pages_[page_number].Get(), gdi_metafile);
    520   if (FAILED(hr)) {
    521     LOG(ERROR) << "Failed to CreateGdiMetafile " << std::hex << hr;
    522     return hr;
    523   }
    524   return hr;
    525 }
    526 
    527 }  // namespace metro_driver
    528