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
{
28 DevMode() : dm_(NULL
) {}
29 ~DevMode() { Free(); }
31 void Allocate(int size
) {
33 dm_
= reinterpret_cast<DEVMODE
*>(new char[size
]);
45 DISALLOW_COPY_AND_ASSIGN(DevMode
);
48 HRESULT
StreamFromPrintTicket(const std::string
& print_ticket
,
51 HRESULT hr
= CreateStreamOnHGlobal(NULL
, TRUE
, stream
);
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
);
64 HRESULT
PrintTicketToDevMode(const std::string
& printer_name
,
65 const std::string
& print_ticket
,
68 printing::ScopedXPSInitializer xps_initializer
;
69 if (!xps_initializer
.initialized()) {
70 // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll)
74 base::win::ScopedComPtr
<IStream
> pt_stream
;
75 HRESULT hr
= StreamFromPrintTicket(print_ticket
, pt_stream
.Receive());
79 HPTPROVIDER provider
= NULL
;
80 hr
= printing::XPSModule::OpenProvider(base::UTF8ToWide(printer_name
), 1,
85 // Use kPTJobScope, because kPTDocumentScope breaks duplex.
86 hr
= printing::XPSModule::ConvertPrintTicketToDevMode(provider
,
94 dev_mode
->Allocate(size
);
95 memcpy(dev_mode
->dm_
, dm
, size
);
96 printing::XPSModule::ReleaseMemory(dm
);
98 printing::XPSModule::CloseProvider(provider
);
103 class JobSpoolerWin
: public PrintSystem::JobSpooler
{
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
,
126 virtual ~JobSpoolerWin() {}
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
{
135 : last_page_printed_(-1),
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
));
154 // We are already in the process of printing.
158 last_page_printed_
= -1;
159 // We only support PDF and XPS documents for now.
160 if (print_data_mime_type
== "application/pdf") {
162 HRESULT hr
= PrintTicketToDevMode(printer_name
, print_ticket
,
169 HDC dc
= CreateDC(L
"WINSPOOL", base::UTF8ToWide(printer_name
).c_str(),
170 NULL
, pt_dev_mode
.dm_
);
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
);
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
,
193 print_data_file_path
,
196 delegate_
= delegate
;
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
);
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
;
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());
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
);
255 job_progress_watcher_
.StopWatching();
256 job_progress_watcher_
.StartWatching(job_progress_event_
.Get(), this);
260 virtual void OnRenderPDFPagesToMetafileFailed() OVERRIDE
{
264 virtual void OnChildDied() OVERRIDE
{
269 // Helper class to allow PrintXPSDocument() to have multiple exits.
270 class PrintJobCanceler
{
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();
283 void reset() { job_ptr_
= NULL
; }
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.
295 RestoreDC(printer_dc_
.Get(), saved_dc_
);
296 EndDoc(printer_dc_
.Get());
297 if (-1 == last_page_printed_
) {
298 delegate_
->OnJobSpoolFailed();
300 delegate_
->OnJobSpoolSucceeded(job_id_
);
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(
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(
341 printing::PdfRenderSettings(render_area
, render_dpi
, false),
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())
355 job_progress_event_
.Set(CreateEvent(NULL
, TRUE
, FALSE
, NULL
));
356 if (!job_progress_event_
.Get())
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())))
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
)))
375 DCHECK_EQ(print_ticket
.length(), print_bytes_written
);
376 if (FAILED(print_ticket_stream
->Close()))
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
)))
386 DCHECK_EQ(document_data
.length(), doc_bytes_written
);
387 if (FAILED(doc_stream
->Close()))
390 job_progress_watcher_
.StartWatching(job_progress_event_
.Get(), this);
391 job_canceler
.reset();
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_
;
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
{
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
);
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
);
451 g_service_process
->io_thread()->message_loop_proxy()->PostTask(
453 base::Bind(&PrinterCapsHandler::GetPrinterCapsAndDefaultsImpl
, this,
454 base::MessageLoopProxy::current()));
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();
470 client_message_loop_proxy
->PostTask(
472 base::Bind(&PrinterCapsHandler::OnGetPrinterCapsAndDefaultsFailed
,
473 this, printer_name_
));
477 std::string printer_name_
;
478 PrintSystem::PrinterCapsAndDefaultsCallback callback_
;
481 class PrintSystemWinXPS
: public PrintSystemWin
{
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
;
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
524 PrinterCapsHandler
* handler
=
525 new PrinterCapsHandler(printer_name
, callback
);
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)
540 HPTPROVIDER provider
= NULL
;
541 printing::XPSModule::OpenProvider(base::UTF8ToWide(printer_name
.c_str()),
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(),
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(
560 print_ticket_stream
.get(),
563 result_ticket_stream
.get(),
565 printing::XPSModule::CloseProvider(provider
);
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";
582 scoped_refptr
<PrintSystem
> PrintSystem::CreateInstance(
583 const base::DictionaryValue
* print_system_settings
) {
584 return new PrintSystemWinXPS
;
587 } // namespace cloud_print