1 // Copyright 2013 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 "base/file_util.h" 6 #include "base/strings/utf_string_conversions.h" 7 #include "base/win/object_watcher.h" 8 #include "base/win/scoped_bstr.h" 9 #include "base/win/scoped_comptr.h" 10 #include "base/win/scoped_hdc.h" 11 #include "chrome/common/crash_keys.h" 12 #include "chrome/service/cloud_print/print_system_win.h" 13 #include "chrome/service/service_process.h" 14 #include "chrome/service/service_utility_process_host.h" 15 #include "grit/generated_resources.h" 16 #include "printing/backend/win_helper.h" 17 #include "printing/emf_win.h" 18 #include "printing/page_range.h" 19 #include "printing/printing_utils.h" 20 #include "ui/base/l10n/l10n_util.h" 21 22 namespace cloud_print { 23 24 namespace { 25 26 class DevMode { 27 public: 28 DevMode() : dm_(NULL) {} 29 ~DevMode() { Free(); } 30 31 void Allocate(int size) { 32 Free(); 33 dm_ = reinterpret_cast<DEVMODE*>(new char[size]); 34 } 35 36 void Free() { 37 if (dm_) 38 delete [] dm_; 39 dm_ = NULL; 40 } 41 42 DEVMODE* dm_; 43 44 private: 45 DISALLOW_COPY_AND_ASSIGN(DevMode); 46 }; 47 48 HRESULT StreamFromPrintTicket(const std::string& print_ticket, 49 IStream** stream) { 50 DCHECK(stream); 51 HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, stream); 52 if (FAILED(hr)) { 53 return hr; 54 } 55 ULONG bytes_written = 0; 56 (*stream)->Write(print_ticket.c_str(), print_ticket.length(), &bytes_written); 57 DCHECK(bytes_written == print_ticket.length()); 58 LARGE_INTEGER pos = {0}; 59 ULARGE_INTEGER new_pos = {0}; 60 (*stream)->Seek(pos, STREAM_SEEK_SET, &new_pos); 61 return S_OK; 62 } 63 64 HRESULT PrintTicketToDevMode(const std::string& printer_name, 65 const std::string& print_ticket, 66 DevMode* dev_mode) { 67 DCHECK(dev_mode); 68 printing::ScopedXPSInitializer xps_initializer; 69 if (!xps_initializer.initialized()) { 70 // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll) 71 return E_FAIL; 72 } 73 74 base::win::ScopedComPtr<IStream> pt_stream; 75 HRESULT hr = StreamFromPrintTicket(print_ticket, pt_stream.Receive()); 76 if (FAILED(hr)) 77 return hr; 78 79 HPTPROVIDER provider = NULL; 80 hr = printing::XPSModule::OpenProvider(UTF8ToWide(printer_name), 1, 81 &provider); 82 if (SUCCEEDED(hr)) { 83 ULONG size = 0; 84 DEVMODE* dm = NULL; 85 // Use kPTJobScope, because kPTDocumentScope breaks duplex. 86 hr = printing::XPSModule::ConvertPrintTicketToDevMode(provider, 87 pt_stream, 88 kUserDefaultDevmode, 89 kPTJobScope, 90 &size, 91 &dm, 92 NULL); 93 if (SUCCEEDED(hr)) { 94 dev_mode->Allocate(size); 95 memcpy(dev_mode->dm_, dm, size); 96 printing::XPSModule::ReleaseMemory(dm); 97 } 98 printing::XPSModule::CloseProvider(provider); 99 } 100 return hr; 101 } 102 103 class JobSpoolerWin : public PrintSystem::JobSpooler { 104 public: 105 JobSpoolerWin() : core_(new Core) {} 106 107 // PrintSystem::JobSpooler implementation. 108 virtual bool Spool(const std::string& print_ticket, 109 const base::FilePath& print_data_file_path, 110 const std::string& print_data_mime_type, 111 const std::string& printer_name, 112 const std::string& job_title, 113 const std::vector<std::string>& tags, 114 JobSpooler::Delegate* delegate) OVERRIDE { 115 // TODO(gene): add tags handling. 116 scoped_refptr<printing::PrintBackend> print_backend( 117 printing::PrintBackend::CreateInstance(NULL)); 118 crash_keys::ScopedPrinterInfo crash_key( 119 print_backend->GetPrinterDriverInfo(printer_name)); 120 return core_->Spool(print_ticket, print_data_file_path, 121 print_data_mime_type, printer_name, job_title, 122 delegate); 123 } 124 125 protected: 126 virtual ~JobSpoolerWin() {} 127 128 private: 129 // We use a Core class because we want a separate RefCountedThreadSafe 130 // implementation for ServiceUtilityProcessHost::Client. 131 class Core : public ServiceUtilityProcessHost::Client, 132 public base::win::ObjectWatcher::Delegate { 133 public: 134 Core() 135 : last_page_printed_(-1), 136 job_id_(-1), 137 delegate_(NULL), 138 saved_dc_(0) { 139 } 140 141 ~Core() {} 142 143 bool Spool(const std::string& print_ticket, 144 const base::FilePath& print_data_file_path, 145 const std::string& print_data_mime_type, 146 const std::string& printer_name, 147 const std::string& job_title, 148 JobSpooler::Delegate* delegate) { 149 scoped_refptr<printing::PrintBackend> print_backend( 150 printing::PrintBackend::CreateInstance(NULL)); 151 crash_keys::ScopedPrinterInfo crash_key( 152 print_backend->GetPrinterDriverInfo(printer_name)); 153 if (delegate_) { 154 // We are already in the process of printing. 155 NOTREACHED(); 156 return false; 157 } 158 last_page_printed_ = -1; 159 // We only support PDF and XPS documents for now. 160 if (print_data_mime_type == "application/pdf") { 161 DevMode pt_dev_mode; 162 HRESULT hr = PrintTicketToDevMode(printer_name, print_ticket, 163 &pt_dev_mode); 164 if (FAILED(hr)) { 165 NOTREACHED(); 166 return false; 167 } 168 169 HDC dc = CreateDC(L"WINSPOOL", UTF8ToWide(printer_name).c_str(), 170 NULL, pt_dev_mode.dm_); 171 if (!dc) { 172 NOTREACHED(); 173 return false; 174 } 175 hr = E_FAIL; 176 DOCINFO di = {0}; 177 di.cbSize = sizeof(DOCINFO); 178 string16 doc_name = UTF8ToUTF16(job_title); 179 DCHECK(printing::SimplifyDocumentTitle(doc_name) == doc_name); 180 di.lpszDocName = doc_name.c_str(); 181 job_id_ = StartDoc(dc, &di); 182 if (job_id_ <= 0) 183 return false; 184 185 printer_dc_.Set(dc); 186 saved_dc_ = SaveDC(printer_dc_.Get()); 187 print_data_file_path_ = print_data_file_path; 188 delegate_ = delegate; 189 RenderNextPDFPages(); 190 } else if (print_data_mime_type == "application/vnd.ms-xpsdocument") { 191 bool ret = PrintXPSDocument(printer_name, 192 job_title, 193 print_data_file_path, 194 print_ticket); 195 if (ret) 196 delegate_ = delegate; 197 return ret; 198 } else { 199 NOTREACHED(); 200 return false; 201 } 202 return true; 203 } 204 205 void PreparePageDCForPrinting(HDC, double scale_factor) { 206 SetGraphicsMode(printer_dc_.Get(), GM_ADVANCED); 207 // Setup the matrix to translate and scale to the right place. Take in 208 // account the scale factor. 209 // Note that the printing output is relative to printable area of 210 // the page. That is 0,0 is offset by PHYSICALOFFSETX/Y from the page. 211 int offset_x = ::GetDeviceCaps(printer_dc_.Get(), PHYSICALOFFSETX); 212 int offset_y = ::GetDeviceCaps(printer_dc_.Get(), PHYSICALOFFSETY); 213 XFORM xform = {0}; 214 xform.eDx = static_cast<float>(-offset_x); 215 xform.eDy = static_cast<float>(-offset_y); 216 xform.eM11 = xform.eM22 = 1.0 / scale_factor; 217 SetWorldTransform(printer_dc_.Get(), &xform); 218 } 219 220 // ServiceUtilityProcessHost::Client implementation. 221 virtual void OnRenderPDFPagesToMetafileSucceeded( 222 const printing::Emf& metafile, 223 int highest_rendered_page_number, 224 double scale_factor) OVERRIDE { 225 PreparePageDCForPrinting(printer_dc_.Get(), scale_factor); 226 metafile.SafePlayback(printer_dc_.Get()); 227 bool done_printing = (highest_rendered_page_number != 228 last_page_printed_ + kPageCountPerBatch); 229 last_page_printed_ = highest_rendered_page_number; 230 if (done_printing) 231 PrintJobDone(); 232 else 233 RenderNextPDFPages(); 234 } 235 236 // base::win::ObjectWatcher::Delegate implementation. 237 virtual void OnObjectSignaled(HANDLE object) OVERRIDE { 238 DCHECK(xps_print_job_); 239 DCHECK(object == job_progress_event_.Get()); 240 ResetEvent(job_progress_event_.Get()); 241 if (!delegate_) 242 return; 243 XPS_JOB_STATUS job_status = {0}; 244 xps_print_job_->GetJobStatus(&job_status); 245 if ((job_status.completion == XPS_JOB_CANCELLED) || 246 (job_status.completion == XPS_JOB_FAILED)) { 247 delegate_->OnJobSpoolFailed(); 248 } else if (job_status.jobId || 249 (job_status.completion == XPS_JOB_COMPLETED)) { 250 // Note: In the case of the XPS document being printed to the 251 // Microsoft XPS Document Writer, it seems to skip spooling the job 252 // and goes to the completed state without ever assigning a job id. 253 delegate_->OnJobSpoolSucceeded(job_status.jobId); 254 } else { 255 job_progress_watcher_.StopWatching(); 256 job_progress_watcher_.StartWatching(job_progress_event_.Get(), this); 257 } 258 } 259 260 virtual void OnRenderPDFPagesToMetafileFailed() OVERRIDE { 261 PrintJobDone(); 262 } 263 264 virtual void OnChildDied() OVERRIDE { 265 PrintJobDone(); 266 } 267 268 private: 269 // Helper class to allow PrintXPSDocument() to have multiple exits. 270 class PrintJobCanceler { 271 public: 272 explicit PrintJobCanceler( 273 base::win::ScopedComPtr<IXpsPrintJob>* job_ptr) 274 : job_ptr_(job_ptr) { 275 } 276 ~PrintJobCanceler() { 277 if (job_ptr_ && *job_ptr_) { 278 (*job_ptr_)->Cancel(); 279 job_ptr_->Release(); 280 } 281 } 282 283 void reset() { job_ptr_ = NULL; } 284 285 private: 286 base::win::ScopedComPtr<IXpsPrintJob>* job_ptr_; 287 288 DISALLOW_COPY_AND_ASSIGN(PrintJobCanceler); 289 }; 290 291 void PrintJobDone() { 292 // If there is no delegate, then there is nothing pending to process. 293 if (!delegate_) 294 return; 295 RestoreDC(printer_dc_.Get(), saved_dc_); 296 EndDoc(printer_dc_.Get()); 297 if (-1 == last_page_printed_) { 298 delegate_->OnJobSpoolFailed(); 299 } else { 300 delegate_->OnJobSpoolSucceeded(job_id_); 301 } 302 delegate_ = NULL; 303 } 304 305 void RenderNextPDFPages() { 306 printing::PageRange range; 307 // Render 10 pages at a time. 308 range.from = last_page_printed_ + 1; 309 range.to = last_page_printed_ + kPageCountPerBatch; 310 std::vector<printing::PageRange> page_ranges; 311 page_ranges.push_back(range); 312 313 int printer_dpi = ::GetDeviceCaps(printer_dc_.Get(), LOGPIXELSX); 314 int dc_width = GetDeviceCaps(printer_dc_.Get(), PHYSICALWIDTH); 315 int dc_height = GetDeviceCaps(printer_dc_.Get(), PHYSICALHEIGHT); 316 gfx::Rect render_area(0, 0, dc_width, dc_height); 317 g_service_process->io_thread()->message_loop_proxy()->PostTask( 318 FROM_HERE, 319 base::Bind(&JobSpoolerWin::Core::RenderPDFPagesInSandbox, this, 320 print_data_file_path_, render_area, printer_dpi, 321 page_ranges, base::MessageLoopProxy::current())); 322 } 323 324 // Called on the service process IO thread. 325 void RenderPDFPagesInSandbox( 326 const base::FilePath& pdf_path, const gfx::Rect& render_area, 327 int render_dpi, const std::vector<printing::PageRange>& page_ranges, 328 const scoped_refptr<base::MessageLoopProxy>& 329 client_message_loop_proxy) { 330 DCHECK(g_service_process->io_thread()->message_loop_proxy()-> 331 BelongsToCurrentThread()); 332 scoped_ptr<ServiceUtilityProcessHost> utility_host( 333 new ServiceUtilityProcessHost(this, client_message_loop_proxy)); 334 // TODO(gene): For now we disabling autorotation for CloudPrinting. 335 // Landscape/Portrait setting is passed in the print ticket and 336 // server is generating portrait PDF always. 337 // We should enable autorotation once server will be able to generate 338 // PDF that matches paper size and orientation. 339 if (utility_host->StartRenderPDFPagesToMetafile( 340 pdf_path, 341 printing::PdfRenderSettings(render_area, render_dpi, false), 342 page_ranges)) { 343 // The object will self-destruct when the child process dies. 344 utility_host.release(); 345 } 346 } 347 348 bool PrintXPSDocument(const std::string& printer_name, 349 const std::string& job_title, 350 const base::FilePath& print_data_file_path, 351 const std::string& print_ticket) { 352 if (!printing::XPSPrintModule::Init()) 353 return false; 354 355 job_progress_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL)); 356 if (!job_progress_event_.Get()) 357 return false; 358 359 PrintJobCanceler job_canceler(&xps_print_job_); 360 base::win::ScopedComPtr<IXpsPrintJobStream> doc_stream; 361 base::win::ScopedComPtr<IXpsPrintJobStream> print_ticket_stream; 362 if (FAILED(printing::XPSPrintModule::StartXpsPrintJob( 363 UTF8ToWide(printer_name).c_str(), UTF8ToWide(job_title).c_str(), 364 NULL, job_progress_event_.Get(), NULL, NULL, NULL, 365 xps_print_job_.Receive(), doc_stream.Receive(), 366 print_ticket_stream.Receive()))) 367 return false; 368 369 ULONG print_bytes_written = 0; 370 if (FAILED(print_ticket_stream->Write(print_ticket.c_str(), 371 print_ticket.length(), 372 &print_bytes_written))) 373 return false; 374 DCHECK_EQ(print_ticket.length(), print_bytes_written); 375 if (FAILED(print_ticket_stream->Close())) 376 return false; 377 378 std::string document_data; 379 base::ReadFileToString(print_data_file_path, &document_data); 380 ULONG doc_bytes_written = 0; 381 if (FAILED(doc_stream->Write(document_data.c_str(), 382 document_data.length(), 383 &doc_bytes_written))) 384 return false; 385 DCHECK_EQ(document_data.length(), doc_bytes_written); 386 if (FAILED(doc_stream->Close())) 387 return false; 388 389 job_progress_watcher_.StartWatching(job_progress_event_.Get(), this); 390 job_canceler.reset(); 391 return true; 392 } 393 394 // Some Cairo-generated PDFs from Chrome OS result in huge metafiles. 395 // So the PageCountPerBatch is set to 1 for now. 396 // TODO(sanjeevr): Figure out a smarter way to determine the pages per 397 // batch. Filed a bug to track this at 398 // http://code.google.com/p/chromium/issues/detail?id=57350. 399 static const int kPageCountPerBatch = 1; 400 401 int last_page_printed_; 402 PlatformJobId job_id_; 403 PrintSystem::JobSpooler::Delegate* delegate_; 404 int saved_dc_; 405 base::win::ScopedCreateDC printer_dc_; 406 base::FilePath print_data_file_path_; 407 base::win::ScopedHandle job_progress_event_; 408 base::win::ObjectWatcher job_progress_watcher_; 409 base::win::ScopedComPtr<IXpsPrintJob> xps_print_job_; 410 411 DISALLOW_COPY_AND_ASSIGN(Core); 412 }; 413 scoped_refptr<Core> core_; 414 415 DISALLOW_COPY_AND_ASSIGN(JobSpoolerWin); 416 }; 417 418 // A helper class to handle the response from the utility process to the 419 // request to fetch printer capabilities and defaults. 420 class PrinterCapsHandler : public ServiceUtilityProcessHost::Client { 421 public: 422 PrinterCapsHandler( 423 const std::string& printer_name, 424 const PrintSystem::PrinterCapsAndDefaultsCallback& callback) 425 : printer_name_(printer_name), callback_(callback) { 426 } 427 428 // ServiceUtilityProcessHost::Client implementation. 429 virtual void OnChildDied() OVERRIDE { 430 OnGetPrinterCapsAndDefaultsFailed(printer_name_); 431 } 432 433 virtual void OnGetPrinterCapsAndDefaultsSucceeded( 434 const std::string& printer_name, 435 const printing::PrinterCapsAndDefaults& caps_and_defaults) OVERRIDE { 436 callback_.Run(true, printer_name, caps_and_defaults); 437 callback_.Reset(); 438 Release(); 439 } 440 441 virtual void OnGetPrinterCapsAndDefaultsFailed( 442 const std::string& printer_name) OVERRIDE { 443 printing::PrinterCapsAndDefaults caps_and_defaults; 444 callback_.Run(false, printer_name, caps_and_defaults); 445 callback_.Reset(); 446 Release(); 447 } 448 449 void Start() { 450 g_service_process->io_thread()->message_loop_proxy()->PostTask( 451 FROM_HERE, 452 base::Bind(&PrinterCapsHandler::GetPrinterCapsAndDefaultsImpl, this, 453 base::MessageLoopProxy::current())); 454 } 455 456 private: 457 // Called on the service process IO thread. 458 void GetPrinterCapsAndDefaultsImpl( 459 const scoped_refptr<base::MessageLoopProxy>& 460 client_message_loop_proxy) { 461 DCHECK(g_service_process->io_thread()->message_loop_proxy()-> 462 BelongsToCurrentThread()); 463 scoped_ptr<ServiceUtilityProcessHost> utility_host( 464 new ServiceUtilityProcessHost(this, client_message_loop_proxy)); 465 if (utility_host->StartGetPrinterCapsAndDefaults(printer_name_)) { 466 // The object will self-destruct when the child process dies. 467 utility_host.release(); 468 } else { 469 client_message_loop_proxy->PostTask( 470 FROM_HERE, 471 base::Bind(&PrinterCapsHandler::OnGetPrinterCapsAndDefaultsFailed, 472 this, printer_name_)); 473 } 474 } 475 476 std::string printer_name_; 477 PrintSystem::PrinterCapsAndDefaultsCallback callback_; 478 }; 479 480 class PrintSystemWinXPS : public PrintSystemWin { 481 public: 482 PrintSystemWinXPS(); 483 virtual ~PrintSystemWinXPS(); 484 485 // PrintSystem implementation. 486 virtual PrintSystemResult Init() OVERRIDE; 487 virtual void GetPrinterCapsAndDefaults( 488 const std::string& printer_name, 489 const PrinterCapsAndDefaultsCallback& callback) OVERRIDE; 490 virtual bool PrintSystemWinXPS::ValidatePrintTicket( 491 const std::string& printer_name, 492 const std::string& print_ticket_data) OVERRIDE; 493 494 virtual PrintSystem::JobSpooler* CreateJobSpooler() OVERRIDE; 495 virtual std::string GetSupportedMimeTypes() OVERRIDE; 496 497 private: 498 DISALLOW_COPY_AND_ASSIGN(PrintSystemWinXPS); 499 }; 500 501 PrintSystemWinXPS::PrintSystemWinXPS() { 502 } 503 504 PrintSystemWinXPS::~PrintSystemWinXPS() { 505 } 506 507 PrintSystem::PrintSystemResult PrintSystemWinXPS::Init() { 508 if (!printing::XPSModule::Init()) { 509 std::string message = l10n_util::GetStringFUTF8( 510 IDS_CLOUD_PRINT_XPS_UNAVAILABLE, 511 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT)); 512 return PrintSystemResult(false, message); 513 } 514 return PrintSystemResult(true, std::string()); 515 } 516 517 void PrintSystemWinXPS::GetPrinterCapsAndDefaults( 518 const std::string& printer_name, 519 const PrinterCapsAndDefaultsCallback& callback) { 520 // Launch as child process to retrieve the capabilities and defaults because 521 // this involves invoking a printer driver DLL and crashes have been known to 522 // occur. 523 PrinterCapsHandler* handler = 524 new PrinterCapsHandler(printer_name, callback); 525 handler->AddRef(); 526 handler->Start(); 527 } 528 529 bool PrintSystemWinXPS::ValidatePrintTicket( 530 const std::string& printer_name, 531 const std::string& print_ticket_data) { 532 crash_keys::ScopedPrinterInfo crash_key(GetPrinterDriverInfo(printer_name)); 533 printing::ScopedXPSInitializer xps_initializer; 534 if (!xps_initializer.initialized()) { 535 // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll) 536 return false; 537 } 538 bool ret = false; 539 HPTPROVIDER provider = NULL; 540 printing::XPSModule::OpenProvider(UTF8ToWide(printer_name.c_str()), 541 1, 542 &provider); 543 if (provider) { 544 base::win::ScopedComPtr<IStream> print_ticket_stream; 545 CreateStreamOnHGlobal(NULL, TRUE, print_ticket_stream.Receive()); 546 ULONG bytes_written = 0; 547 print_ticket_stream->Write(print_ticket_data.c_str(), 548 print_ticket_data.length(), 549 &bytes_written); 550 DCHECK(bytes_written == print_ticket_data.length()); 551 LARGE_INTEGER pos = {0}; 552 ULARGE_INTEGER new_pos = {0}; 553 print_ticket_stream->Seek(pos, STREAM_SEEK_SET, &new_pos); 554 base::win::ScopedBstr error; 555 base::win::ScopedComPtr<IStream> result_ticket_stream; 556 CreateStreamOnHGlobal(NULL, TRUE, result_ticket_stream.Receive()); 557 ret = SUCCEEDED(printing::XPSModule::MergeAndValidatePrintTicket( 558 provider, 559 print_ticket_stream.get(), 560 NULL, 561 kPTJobScope, 562 result_ticket_stream.get(), 563 error.Receive())); 564 printing::XPSModule::CloseProvider(provider); 565 } 566 return ret; 567 } 568 569 PrintSystem::JobSpooler* PrintSystemWinXPS::CreateJobSpooler() { 570 return new JobSpoolerWin(); 571 } 572 573 std::string PrintSystemWinXPS::GetSupportedMimeTypes() { 574 if (printing::XPSPrintModule::Init()) 575 return "application/vnd.ms-xpsdocument,application/pdf"; 576 return "application/pdf"; 577 } 578 579 } // namespace 580 581 scoped_refptr<PrintSystem> PrintSystem::CreateInstance( 582 const base::DictionaryValue* print_system_settings) { 583 return new PrintSystemWinXPS; 584 } 585 586 } // namespace cloud_print 587