Upstream version 5.34.98.0
[platform/framework/web/crosswalk.git] / src / chrome / service / cloud_print / print_system_xps_win.cc
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(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);
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", base::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         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;
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               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;
369
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;
378
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;
389
390       job_progress_watcher_.StartWatching(job_progress_event_.Get(), this);
391       job_canceler.reset();
392       return true;
393     }
394
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;
401
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_;
411
412     DISALLOW_COPY_AND_ASSIGN(Core);
413   };
414   scoped_refptr<Core> core_;
415
416   DISALLOW_COPY_AND_ASSIGN(JobSpoolerWin);
417 };
418
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) {
427   }
428
429   // ServiceUtilityProcessHost::Client implementation.
430   virtual void OnChildDied() OVERRIDE {
431     OnGetPrinterCapsAndDefaultsFailed(printer_name_);
432   }
433
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();
440   }
441
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();
448   }
449
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()));
455   }
456
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_));
474     }
475   }
476
477   std::string printer_name_;
478   PrintSystem::PrinterCapsAndDefaultsCallback callback_;
479 };
480
481 class PrintSystemWinXPS : public PrintSystemWin {
482  public:
483   PrintSystemWinXPS();
484   virtual ~PrintSystemWinXPS();
485
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;
494
495   virtual PrintSystem::JobSpooler* CreateJobSpooler() OVERRIDE;
496   virtual std::string GetSupportedMimeTypes() OVERRIDE;
497
498  private:
499   DISALLOW_COPY_AND_ASSIGN(PrintSystemWinXPS);
500 };
501
502 PrintSystemWinXPS::PrintSystemWinXPS() {
503 }
504
505 PrintSystemWinXPS::~PrintSystemWinXPS() {
506 }
507
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);
514   }
515   return PrintSystemResult(true, std::string());
516 }
517
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();
528 }
529
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;
538   }
539   bool ret = false;
540   HPTPROVIDER provider = NULL;
541   printing::XPSModule::OpenProvider(base::UTF8ToWide(printer_name.c_str()),
542                                     1,
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);
566   }
567   return ret;
568 }
569
570 PrintSystem::JobSpooler* PrintSystemWinXPS::CreateJobSpooler() {
571   return new JobSpoolerWin();
572 }
573
574 std::string PrintSystemWinXPS::GetSupportedMimeTypes() {
575   if (printing::XPSPrintModule::Init())
576     return "application/vnd.ms-xpsdocument,application/pdf";
577   return "application/pdf";
578 }
579
580 }  // namespace
581
582 scoped_refptr<PrintSystem> PrintSystem::CreateInstance(
583     const base::DictionaryValue* print_system_settings) {
584   return new PrintSystemWinXPS;
585 }
586
587 }  // namespace cloud_print