Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / service / cloud_print / print_system_xps_win.cc
blob5fc722d21443da880c06ae5a5b2bb140a66499eb
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.
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"
22 namespace cloud_print {
24 namespace {
26 class DevMode {
27 public:
28 DevMode() : dm_(NULL) {}
29 ~DevMode() { Free(); }
31 void Allocate(int size) {
32 Free();
33 dm_ = reinterpret_cast<DEVMODE*>(new char[size]);
36 void Free() {
37 if (dm_)
38 delete [] dm_;
39 dm_ = NULL;
42 DEVMODE* dm_;
44 private:
45 DISALLOW_COPY_AND_ASSIGN(DevMode);
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;
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;
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;
74 base::win::ScopedComPtr<IStream> pt_stream;
75 HRESULT hr = StreamFromPrintTicket(print_ticket, pt_stream.Receive());
76 if (FAILED(hr))
77 return hr;
79 HPTPROVIDER provider = NULL;
80 hr = printing::XPSModule::OpenProvider(base::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);
98 printing::XPSModule::CloseProvider(provider);
100 return hr;
103 class JobSpoolerWin : public PrintSystem::JobSpooler {
104 public:
105 JobSpoolerWin() : core_(new Core) {}
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);
125 protected:
126 virtual ~JobSpoolerWin() {}
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) {
141 ~Core() {}
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;
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;
169 HDC dc = CreateDC(L"WINSPOOL", base::UTF8ToWide(printer_name).c_str(),
170 NULL, pt_dev_mode.dm_);
171 if (!dc) {
172 NOTREACHED();
173 return false;
175 hr = E_FAIL;
176 DOCINFO di = {0};
177 di.cbSize = sizeof(DOCINFO);
178 base::string16 doc_name = base::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;
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;
202 return true;
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);
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();
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);
260 virtual void OnRenderPDFPagesToMetafileFailed() OVERRIDE {
261 PrintJobDone();
264 virtual void OnChildDied() OVERRIDE {
265 PrintJobDone();
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) {
276 ~PrintJobCanceler() {
277 if (job_ptr_ && *job_ptr_) {
278 (*job_ptr_)->Cancel();
279 job_ptr_->Release();
283 void reset() { job_ptr_ = NULL; }
285 private:
286 base::win::ScopedComPtr<IXpsPrintJob>* job_ptr_;
288 DISALLOW_COPY_AND_ASSIGN(PrintJobCanceler);
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_);
302 delegate_ = NULL;
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);
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()));
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();
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;
355 job_progress_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL));
356 if (!job_progress_event_.Get())
357 return false;
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 base::UTF8ToWide(printer_name).c_str(),
364 base::UTF8ToWide(job_title).c_str(),
365 NULL, job_progress_event_.Get(), NULL, NULL, NULL,
366 xps_print_job_.Receive(), doc_stream.Receive(),
367 print_ticket_stream.Receive())))
368 return false;
370 ULONG print_bytes_written = 0;
371 if (FAILED(print_ticket_stream->Write(print_ticket.c_str(),
372 print_ticket.length(),
373 &print_bytes_written)))
374 return false;
375 DCHECK_EQ(print_ticket.length(), print_bytes_written);
376 if (FAILED(print_ticket_stream->Close()))
377 return false;
379 std::string document_data;
380 base::ReadFileToString(print_data_file_path, &document_data);
381 ULONG doc_bytes_written = 0;
382 if (FAILED(doc_stream->Write(document_data.c_str(),
383 document_data.length(),
384 &doc_bytes_written)))
385 return false;
386 DCHECK_EQ(document_data.length(), doc_bytes_written);
387 if (FAILED(doc_stream->Close()))
388 return false;
390 job_progress_watcher_.StartWatching(job_progress_event_.Get(), this);
391 job_canceler.reset();
392 return true;
395 // Some Cairo-generated PDFs from Chrome OS result in huge metafiles.
396 // So the PageCountPerBatch is set to 1 for now.
397 // TODO(sanjeevr): Figure out a smarter way to determine the pages per
398 // batch. Filed a bug to track this at
399 // http://code.google.com/p/chromium/issues/detail?id=57350.
400 static const int kPageCountPerBatch = 1;
402 int last_page_printed_;
403 PlatformJobId job_id_;
404 PrintSystem::JobSpooler::Delegate* delegate_;
405 int saved_dc_;
406 base::win::ScopedCreateDC printer_dc_;
407 base::FilePath print_data_file_path_;
408 base::win::ScopedHandle job_progress_event_;
409 base::win::ObjectWatcher job_progress_watcher_;
410 base::win::ScopedComPtr<IXpsPrintJob> xps_print_job_;
412 DISALLOW_COPY_AND_ASSIGN(Core);
414 scoped_refptr<Core> core_;
416 DISALLOW_COPY_AND_ASSIGN(JobSpoolerWin);
419 // A helper class to handle the response from the utility process to the
420 // request to fetch printer capabilities and defaults.
421 class PrinterCapsHandler : public ServiceUtilityProcessHost::Client {
422 public:
423 PrinterCapsHandler(
424 const std::string& printer_name,
425 const PrintSystem::PrinterCapsAndDefaultsCallback& callback)
426 : printer_name_(printer_name), callback_(callback) {
429 // ServiceUtilityProcessHost::Client implementation.
430 virtual void OnChildDied() OVERRIDE {
431 OnGetPrinterCapsAndDefaultsFailed(printer_name_);
434 virtual void OnGetPrinterCapsAndDefaultsSucceeded(
435 const std::string& printer_name,
436 const printing::PrinterCapsAndDefaults& caps_and_defaults) OVERRIDE {
437 callback_.Run(true, printer_name, caps_and_defaults);
438 callback_.Reset();
439 Release();
442 virtual void OnGetPrinterCapsAndDefaultsFailed(
443 const std::string& printer_name) OVERRIDE {
444 printing::PrinterCapsAndDefaults caps_and_defaults;
445 callback_.Run(false, printer_name, caps_and_defaults);
446 callback_.Reset();
447 Release();
450 void Start() {
451 g_service_process->io_thread()->message_loop_proxy()->PostTask(
452 FROM_HERE,
453 base::Bind(&PrinterCapsHandler::GetPrinterCapsAndDefaultsImpl, this,
454 base::MessageLoopProxy::current()));
457 private:
458 // Called on the service process IO thread.
459 void GetPrinterCapsAndDefaultsImpl(
460 const scoped_refptr<base::MessageLoopProxy>&
461 client_message_loop_proxy) {
462 DCHECK(g_service_process->io_thread()->message_loop_proxy()->
463 BelongsToCurrentThread());
464 scoped_ptr<ServiceUtilityProcessHost> utility_host(
465 new ServiceUtilityProcessHost(this, client_message_loop_proxy));
466 if (utility_host->StartGetPrinterCapsAndDefaults(printer_name_)) {
467 // The object will self-destruct when the child process dies.
468 utility_host.release();
469 } else {
470 client_message_loop_proxy->PostTask(
471 FROM_HERE,
472 base::Bind(&PrinterCapsHandler::OnGetPrinterCapsAndDefaultsFailed,
473 this, printer_name_));
477 std::string printer_name_;
478 PrintSystem::PrinterCapsAndDefaultsCallback callback_;
481 class PrintSystemWinXPS : public PrintSystemWin {
482 public:
483 PrintSystemWinXPS();
484 virtual ~PrintSystemWinXPS();
486 // PrintSystem implementation.
487 virtual PrintSystemResult Init() OVERRIDE;
488 virtual void GetPrinterCapsAndDefaults(
489 const std::string& printer_name,
490 const PrinterCapsAndDefaultsCallback& callback) OVERRIDE;
491 virtual bool PrintSystemWinXPS::ValidatePrintTicket(
492 const std::string& printer_name,
493 const std::string& print_ticket_data) OVERRIDE;
495 virtual PrintSystem::JobSpooler* CreateJobSpooler() OVERRIDE;
496 virtual std::string GetSupportedMimeTypes() OVERRIDE;
498 private:
499 DISALLOW_COPY_AND_ASSIGN(PrintSystemWinXPS);
502 PrintSystemWinXPS::PrintSystemWinXPS() {
505 PrintSystemWinXPS::~PrintSystemWinXPS() {
508 PrintSystem::PrintSystemResult PrintSystemWinXPS::Init() {
509 if (!printing::XPSModule::Init()) {
510 std::string message = l10n_util::GetStringFUTF8(
511 IDS_CLOUD_PRINT_XPS_UNAVAILABLE,
512 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT));
513 return PrintSystemResult(false, message);
515 return PrintSystemResult(true, std::string());
518 void PrintSystemWinXPS::GetPrinterCapsAndDefaults(
519 const std::string& printer_name,
520 const PrinterCapsAndDefaultsCallback& callback) {
521 // Launch as child process to retrieve the capabilities and defaults because
522 // this involves invoking a printer driver DLL and crashes have been known to
523 // occur.
524 PrinterCapsHandler* handler =
525 new PrinterCapsHandler(printer_name, callback);
526 handler->AddRef();
527 handler->Start();
530 bool PrintSystemWinXPS::ValidatePrintTicket(
531 const std::string& printer_name,
532 const std::string& print_ticket_data) {
533 crash_keys::ScopedPrinterInfo crash_key(GetPrinterDriverInfo(printer_name));
534 printing::ScopedXPSInitializer xps_initializer;
535 if (!xps_initializer.initialized()) {
536 // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll)
537 return false;
539 bool ret = false;
540 HPTPROVIDER provider = NULL;
541 printing::XPSModule::OpenProvider(base::UTF8ToWide(printer_name.c_str()),
543 &provider);
544 if (provider) {
545 base::win::ScopedComPtr<IStream> print_ticket_stream;
546 CreateStreamOnHGlobal(NULL, TRUE, print_ticket_stream.Receive());
547 ULONG bytes_written = 0;
548 print_ticket_stream->Write(print_ticket_data.c_str(),
549 print_ticket_data.length(),
550 &bytes_written);
551 DCHECK(bytes_written == print_ticket_data.length());
552 LARGE_INTEGER pos = {0};
553 ULARGE_INTEGER new_pos = {0};
554 print_ticket_stream->Seek(pos, STREAM_SEEK_SET, &new_pos);
555 base::win::ScopedBstr error;
556 base::win::ScopedComPtr<IStream> result_ticket_stream;
557 CreateStreamOnHGlobal(NULL, TRUE, result_ticket_stream.Receive());
558 ret = SUCCEEDED(printing::XPSModule::MergeAndValidatePrintTicket(
559 provider,
560 print_ticket_stream.get(),
561 NULL,
562 kPTJobScope,
563 result_ticket_stream.get(),
564 error.Receive()));
565 printing::XPSModule::CloseProvider(provider);
567 return ret;
570 PrintSystem::JobSpooler* PrintSystemWinXPS::CreateJobSpooler() {
571 return new JobSpoolerWin();
574 std::string PrintSystemWinXPS::GetSupportedMimeTypes() {
575 if (printing::XPSPrintModule::Init())
576 return "application/vnd.ms-xpsdocument,application/pdf";
577 return "application/pdf";
580 } // namespace
582 scoped_refptr<PrintSystem> PrintSystem::CreateInstance(
583 const base::DictionaryValue* print_system_settings) {
584 return new PrintSystemWinXPS;
587 } // namespace cloud_print