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