- add sources.
[platform/framework/web/crosswalk.git] / src / win8 / metro_driver / print_document_source.cc
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