print_document_source.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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
14namespace {
15
16class 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.
36const GUID kOldPackageTargetGuid =
37    {0xfb2a33c0, 0x8c35, 0x465f,
38      {0xbe, 0xd5, 0x9f, 0x36, 0x89, 0x51, 0x77, 0x52}};
39const GUID kNewPackageTargetGuid =
40    {0x1a6dd0ad, 0x1e2a, 0x4e99,
41      {0xa5, 0xba, 0x91, 0xf1, 0x78, 0x18, 0x29, 0x0e}};
42
43
44}  // namespace
45
46namespace metro_driver {
47
48PrintDocumentSource::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
58HRESULT 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
78void 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
90STDMETHODIMP 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
139STDMETHODIMP 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
214STDMETHODIMP 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
264STDMETHODIMP 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
375void 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
385void 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
401void 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
413HRESULT 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
457size_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
479HRESULT 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