Home | History | Annotate | Download | only in cloud_print
      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