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.
6 #include "win8/metro_driver/print_document_source.h"
8 #include <windows.graphics.display.h>
10 #include "base/logging.h"
11 #include "base/numerics/safe_conversions.h"
16 class D2DFactoryAutoLock
{
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();
23 NOTREACHED() << "Failed to QI for ID2D1Multithread " << std::hex
<< hr
;
26 ~D2DFactoryAutoLock() {
27 if (d2d_multithread_
.Get())
28 d2d_multithread_
->Leave();
32 mswr::ComPtr
<ID2D1Multithread
> d2d_multithread_
;
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}};
46 namespace metro_driver
{
48 PrintDocumentSource::PrintDocumentSource()
49 : page_count_ready_(true, false),
55 using_old_preview_interface_(false) {
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
;
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
;
78 void PrintDocumentSource::Abort() {
79 base::AutoLock
lock(*parent_lock_
);
81 if (page_count_ready_
.IsSignaled()) {
83 for (size_t i
= 0; i
< pages_ready_state_
.size(); ++i
)
84 pages_ready_state_
[i
]->Broadcast();
86 DCHECK(pages_
.empty() && pages_ready_state_
.empty());
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
);
97 HRESULT hr
= package_target
->GetPackageTarget(
98 __uuidof(IPrintPreviewDxgiPackageTarget
),
99 IID_PPV_ARGS(&dxgi_preview_target_
));
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;
110 hr
= package_target
->GetPackageTarget(package_target_uuid
,
112 &dxgi_preview_target_
);
114 LOG(ERROR
) << "Failed to get IPrintPreviewDXGIPackageTarget " << std::hex
119 using_old_preview_interface_
= (__uuidof(IPrintPreviewDxgiPackageTarget
) ==
120 kOldPackageTargetGuid
);
123 mswr::ComPtr
<IPrintPreviewPageCollection
> preview_page_collection
;
124 mswr::ComPtr
<PrintDocumentSource
> print_document_source(this);
125 hr
= print_document_source
.As(&preview_page_collection
);
127 LOG(ERROR
) << "Failed to get preview_page_collection " << std::hex
<< hr
;
131 hr
= preview_page_collection
.CopyTo(page_collection
);
133 LOG(ERROR
) << "Failed to copy preview_page_collection " << std::hex
<< hr
;
139 STDMETHODIMP
PrintDocumentSource::MakeDocument(
140 IInspectable
* options
,
141 IPrintDocumentPackageTarget
* package_target
) {
142 DVLOG(1) << __FUNCTION__
;
143 DCHECK(options
!= NULL
);
144 DCHECK(package_target
!= NULL
);
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()));
151 LOG(ERROR
) << "Failed to QI for IPrintTaskOptionsCore " << std::hex
<< hr
;
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
);
161 LOG(ERROR
) << "Failed to GetPageDescription " << std::hex
<< hr
;
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
);
169 print_control_properties
.rasterDPI
= static_cast<float>(page_desc
.DpiX
);
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
;
175 mswr::ComPtr
<ID2D1PrintControl
> print_control
;
176 hr
= directx_context_
.d2d_device
->CreatePrintControl(
177 directx_context_
.wic_factory
.Get(),
179 print_control_properties
,
180 print_control
.GetAddressOf());
182 LOG(ERROR
) << "Failed to CreatePrintControl " << std::hex
<< hr
;
186 D2D1_SIZE_F page_size
= D2D1::SizeF(page_desc
.PageSize
.Width
,
187 page_desc
.PageSize
.Height
);
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();
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
199 // S_FALSE means we got aborted.
200 if (hr
== S_FALSE
|| FAILED(hr
))
202 hr
= PrintPage(print_control
.Get(), gdi_metafile
.Get(), page_size
);
207 HRESULT close_hr
= print_control
->Close();
208 if (FAILED(close_hr
) && SUCCEEDED(hr
))
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
);
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()));
230 LOG(ERROR
) << "Failed to QI for IPrintTaskOptionsCore " << std::hex
<< hr
;
234 wingfx::Printing::PrintPageDescription page_desc
= {};
235 hr
= print_options
->GetPageDescription(1 /* page */, &page_desc
);
237 LOG(ERROR
) << "Failed to GetPageDescription " << std::hex
<< hr
;
241 width_
= page_desc
.PageSize
.Width
;
242 height_
= page_desc
.PageSize
.Height
;
244 hr
= dxgi_preview_target_
->InvalidatePreview();
246 LOG(ERROR
) << "Failed to InvalidatePreview " << std::hex
<< hr
;
250 size_t page_count
= WaitAndGetPageCount();
251 // A page_count of 0 means abort...
254 hr
= dxgi_preview_target_
->SetJobPageCount(
255 PageCountType::FinalPageCount
,
256 base::checked_cast
<UINT32
>(page_count
));
258 LOG(ERROR
) << "Failed to SetJobPageCount " << std::hex
<< hr
;
264 STDMETHODIMP
PrintDocumentSource::MakePage(uint32 job_page
,
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)
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
)
279 winfoundtn::Size preview_size
;
280 preview_size
.Width
= width
;
281 preview_size
.Height
= height
;
282 float scale
= width_
/ width
;
284 mswr::ComPtr
<ID2D1Factory
> factory
;
285 directx_context_
.d2d_device
->GetFactory(&factory
);
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
291 // Again, S_FALSE means we got aborted.
292 if (hr
== S_FALSE
|| FAILED(hr
))
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());
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)),
305 D3D11_BIND_RENDER_TARGET
| D3D11_BIND_SHADER_RESOURCE
307 mswr::ComPtr
<ID3D11Texture2D
> texture
;
308 hr
= directx_context_
.d3d_device
->CreateTexture2D(
309 &texture_desc
, NULL
, &texture
);
311 LOG(ERROR
) << "Failed to create a 2D texture " << std::hex
<< hr
;
315 mswr::ComPtr
<IDXGISurface
> dxgi_surface
;
316 hr
= texture
.As
<IDXGISurface
>(&dxgi_surface
);
318 LOG(ERROR
) << "Failed to QI for IDXGISurface " << std::hex
<< hr
;
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
);
328 d2d_context
->SetDpi(dpi_
, dpi_
);
330 mswr::ComPtr
<ID2D1Bitmap1
> d2dSurfaceBitmap
;
331 hr
= d2d_context
->CreateBitmapFromDxgiSurface(dxgi_surface
.Get(),
332 NULL
, // default properties.
335 LOG(ERROR
) << "Failed to CreateBitmapFromDxgiSurface " << std::hex
<< hr
;
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());
345 hr
= d2d_context
->EndDraw();
347 LOG(ERROR
) << "Failed to EndDraw " << std::hex
<< hr
;
351 // TODO(mad): remove once we don't run mixed SDK/OS anymore.
352 #ifdef __IPrintPreviewDxgiPackageTarget_FWD_DEFINED__
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_
);
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_
;
366 #endif // __IPrintPreviewDxgiPackageTarget_FWD_DEFINED__
367 hr
= dxgi_preview_target_
->DrawPage(job_page
, dxgi_surface
.Get(), dpi
, dpi
);
369 LOG(ERROR
) << "Failed to DrawPage " << std::hex
<< hr
;
375 void PrintDocumentSource::ResetDpi(float dpi
) {
377 base::AutoLock
lock(*parent_lock_
);
382 directx_context_
.d2d_context
->SetDpi(dpi
, dpi
);
385 void PrintDocumentSource::SetPageCount(size_t page_count
) {
386 DCHECK(page_count
> 0);
388 base::AutoLock
lock(*parent_lock_
);
389 DCHECK(!page_count_ready_
.IsSignaled());
390 DCHECK(pages_
.empty() && pages_ready_state_
.empty());
392 pages_
.resize(page_count
);
393 pages_ready_state_
.resize(page_count
);
395 for (size_t i
= 0; i
< page_count
; ++i
)
396 pages_ready_state_
[i
] = new base::ConditionVariable(parent_lock_
);
398 page_count_ready_
.Signal();
401 void PrintDocumentSource::AddPage(size_t page_number
,
402 IStream
* metafile_stream
) {
403 DCHECK(metafile_stream
!= NULL
);
404 base::AutoLock
lock(*parent_lock_
);
406 DCHECK(page_count_ready_
.IsSignaled());
407 DCHECK(page_number
< pages_
.size());
409 pages_
[page_number
] = metafile_stream
;
410 pages_ready_state_
[page_number
]->Signal();
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
);
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
);
427 LOG(ERROR
) << "Failed to CreateDeviceContext " << std::hex
<< hr
;
431 mswr::ComPtr
<ID2D1CommandList
> print_command_list
;
432 hr
= d2d_context
->CreateCommandList(&print_command_list
);
434 LOG(ERROR
) << "Failed to CreateCommandList " << std::hex
<< hr
;
438 d2d_context
->SetTarget(print_command_list
.Get());
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
;
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
449 if (SUCCEEDED(hr
) && SUCCEEDED(close_hr
))
450 hr
= print_control
->AddPage(print_command_list
.Get(), page_size
, NULL
);
457 size_t PrintDocumentSource::WaitAndGetPageCount() {
458 // Properly protect the wait/access to the page count.
460 base::AutoLock
lock(*parent_lock_
);
463 DCHECK(pages_
.size() == pages_ready_state_
.size());
465 return pages_
.size();
467 page_count_ready_
.Wait();
469 base::AutoLock
lock(*parent_lock_
);
471 DCHECK(pages_
.size() == pages_ready_state_
.size());
472 return pages_
.size();
475 // A page count of 0 means abort.
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.
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();
495 // Make sure we weren't aborted while we waited unlocked.
498 DCHECK(page_number
< pages_
.size());
500 mswr::ComPtr
<ID2D1Factory
> factory
;
501 directx_context_
.d2d_device
->GetFactory(&factory
);
503 mswr::ComPtr
<ID2D1Factory1
> factory1
;
504 HRESULT hr
= factory
.As(&factory1
);
506 LOG(ERROR
) << "Failed to QI for ID2D1Factory1 " << std::hex
<< hr
;
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
);
515 LOG(ERROR
) << "Failed to Seek page stream " << std::hex
<< hr
;
519 hr
= factory1
->CreateGdiMetafile(pages_
[page_number
].Get(), gdi_metafile
);
521 LOG(ERROR
) << "Failed to CreateGdiMetafile " << std::hex
<< hr
;
527 } // namespace metro_driver