Revert 233039 "Blink roll 161254:161319"
[chromium-blink-merge.git] / printing / printing_context_mac.mm
blobd91ce6f09d0466ecb023f7e74809f27b2867abfa
1 // Copyright (c) 2011 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 "printing/printing_context_mac.h"
7 #import <ApplicationServices/ApplicationServices.h>
8 #import <AppKit/AppKit.h>
10 #import <iomanip>
11 #import <numeric>
13 #include "base/logging.h"
14 #include "base/mac/scoped_cftyperef.h"
15 #include "base/mac/scoped_nsautorelease_pool.h"
16 #include "base/mac/scoped_nsexception_enabler.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/values.h"
20 #include "printing/print_settings_initializer_mac.h"
21 #include "printing/units.h"
23 namespace printing {
25 namespace {
27 // Return true if PPD name of paper is equal.
28 bool IsPaperNameEqual(const PMPaper& paper1, const PMPaper& paper2) {
29   CFStringRef name1 = NULL;
30   CFStringRef name2 = NULL;
31   return (PMPaperGetPPDPaperName(paper1, &name1) == noErr) &&
32          (PMPaperGetPPDPaperName(paper2, &name2) == noErr) &&
33          (CFStringCompare(name1, name2,
34                           kCFCompareCaseInsensitive) == kCFCompareEqualTo);
37 }  // namespace
39 // static
40 PrintingContext* PrintingContext::Create(const std::string& app_locale) {
41   return static_cast<PrintingContext*>(new PrintingContextMac(app_locale));
44 PrintingContextMac::PrintingContextMac(const std::string& app_locale)
45     : PrintingContext(app_locale),
46       print_info_([[NSPrintInfo sharedPrintInfo] copy]),
47       context_(NULL) {
50 PrintingContextMac::~PrintingContextMac() {
51   ReleaseContext();
54 void PrintingContextMac::AskUserForSettings(
55     gfx::NativeView parent_view,
56     int max_pages,
57     bool has_selection,
58     const PrintSettingsCallback& callback) {
59   // Third-party print drivers seem to be an area prone to raising exceptions.
60   // This will allow exceptions to be raised, but does not handle them.  The
61   // NSPrintPanel appears to have appropriate NSException handlers.
62   base::mac::ScopedNSExceptionEnabler enabler;
64   // Exceptions can also happen when the NSPrintPanel is being
65   // deallocated, so it must be autoreleased within this scope.
66   base::mac::ScopedNSAutoreleasePool pool;
68   DCHECK([NSThread isMainThread]);
70   // We deliberately don't feed max_pages into the dialog, because setting
71   // NSPrintLastPage makes the print dialog pre-select the option to only print
72   // a range.
74   // TODO(stuartmorgan): implement 'print selection only' (probably requires
75   // adding a new custom view to the panel on 10.5; 10.6 has
76   // NSPrintPanelShowsPrintSelection).
77   NSPrintPanel* panel = [NSPrintPanel printPanel];
78   NSPrintInfo* printInfo = print_info_.get();
80   NSPrintPanelOptions options = [panel options];
81   options |= NSPrintPanelShowsPaperSize;
82   options |= NSPrintPanelShowsOrientation;
83   options |= NSPrintPanelShowsScaling;
84   [panel setOptions:options];
86   // Set the print job title text.
87   if (parent_view) {
88     NSString* job_title = [[parent_view window] title];
89     if (job_title) {
90       PMPrintSettings printSettings =
91           (PMPrintSettings)[printInfo PMPrintSettings];
92       PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
93       [printInfo updateFromPMPrintSettings];
94     }
95   }
97   // TODO(stuartmorgan): We really want a tab sheet here, not a modal window.
98   // Will require restructuring the PrintingContext API to use a callback.
99   NSInteger selection = [panel runModalWithPrintInfo:printInfo];
100   if (selection == NSOKButton) {
101     print_info_.reset([[panel printInfo] retain]);
102     settings_.set_ranges(GetPageRangesFromPrintInfo());
103     InitPrintSettingsFromPrintInfo();
104     callback.Run(OK);
105   } else {
106     callback.Run(CANCEL);
107   }
110 gfx::Size PrintingContextMac::GetPdfPaperSizeDeviceUnits() {
111   // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
112   // with a clean slate.
113   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
114   UpdatePageFormatWithPaperInfo();
116   PMPageFormat page_format =
117       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
118   PMRect paper_rect;
119   PMGetAdjustedPaperRect(page_format, &paper_rect);
121   // Device units are in points. Units per inch is 72.
122   gfx::Size physical_size_device_units(
123       (paper_rect.right - paper_rect.left),
124       (paper_rect.bottom - paper_rect.top));
125   DCHECK(settings_.device_units_per_inch() == kPointsPerInch);
126   return physical_size_device_units;
129 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
130   DCHECK(!in_print_job_);
132   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
133   settings_.set_ranges(GetPageRangesFromPrintInfo());
134   InitPrintSettingsFromPrintInfo();
136   return OK;
139 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
140     bool external_preview) {
141   DCHECK(!in_print_job_);
143   // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
144   // with a clean slate.
145   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
147   if (external_preview) {
148     if (!SetPrintPreviewJob())
149       return OnError();
150   } else {
151     // Don't need this for preview.
152     if (!SetPrinter(UTF16ToUTF8(settings_.device_name())) ||
153         !SetCopiesInPrintSettings(settings_.copies()) ||
154         !SetCollateInPrintSettings(settings_.collate()) ||
155         !SetDuplexModeInPrintSettings(settings_.duplex_mode()) ||
156         !SetOutputColor(settings_.color())) {
157       return OnError();
158     }
159   }
161   if (!UpdatePageFormatWithPaperInfo() ||
162       !SetOrientationIsLandscape(settings_.landscape())) {
163     return OnError();
164   }
166   [print_info_.get() updateFromPMPrintSettings];
168   InitPrintSettingsFromPrintInfo();
169   return OK;
172 bool PrintingContextMac::SetPrintPreviewJob() {
173   PMPrintSession print_session =
174       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
175   PMPrintSettings print_settings =
176       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
177   return PMSessionSetDestination(
178       print_session, print_settings, kPMDestinationPreview,
179       NULL, NULL) == noErr;
182 void PrintingContextMac::InitPrintSettingsFromPrintInfo() {
183   PMPrintSession print_session =
184       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
185   PMPageFormat page_format =
186       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
187   PMPrinter printer;
188   PMSessionGetCurrentPrinter(print_session, &printer);
189   settings_.set_selection_only(false);
190   PrintSettingsInitializerMac::InitPrintSettings(
191       printer, page_format, &settings_);
194 bool PrintingContextMac::SetPrinter(const std::string& device_name) {
195   DCHECK(print_info_.get());
196   PMPrintSession print_session =
197       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
199   PMPrinter current_printer;
200   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
201     return false;
203   CFStringRef current_printer_id = PMPrinterGetID(current_printer);
204   if (!current_printer_id)
205     return false;
207   base::ScopedCFTypeRef<CFStringRef> new_printer_id(
208       base::SysUTF8ToCFStringRef(device_name));
209   if (!new_printer_id.get())
210     return false;
212   if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
213           kCFCompareEqualTo) {
214     return true;
215   }
217   PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
218   if (new_printer == NULL)
219     return false;
221   OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
222   PMRelease(new_printer);
223   return status == noErr;
226 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
227   PMPrintSession print_session =
228       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
230   PMPageFormat default_page_format =
231       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
233   PMPaper default_paper;
234   if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr)
235     return false;
237   double default_page_width = 0.0;
238   double default_page_height = 0.0;
239   if (PMPaperGetWidth(default_paper, &default_page_width) != noErr)
240     return false;
242   if (PMPaperGetHeight(default_paper, &default_page_height) != noErr)
243     return false;
245   PMPrinter current_printer = NULL;
246   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
247     return false;
249   if (current_printer == nil)
250     return false;
252   CFArrayRef paper_list = NULL;
253   if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
254     return false;
256   double best_match = std::numeric_limits<double>::max();
257   PMPaper best_matching_paper = kPMNoData;
258   int num_papers = CFArrayGetCount(paper_list);
259   for (int i = 0; i < num_papers; ++i) {
260     PMPaper paper = (PMPaper)[(NSArray*)paper_list objectAtIndex: i];
261     double paper_width = 0.0;
262     double paper_height = 0.0;
263     PMPaperGetWidth(paper, &paper_width);
264     PMPaperGetHeight(paper, &paper_height);
265     double current_match = std::max(fabs(default_page_width - paper_width),
266                                     fabs(default_page_height - paper_height));
267     // Ignore paper sizes that are very different.
268     if (current_match > 2)
269       continue;
270     current_match += IsPaperNameEqual(paper, default_paper) ? 0 : 1;
271     if (current_match < best_match) {
272       best_matching_paper = paper;
273       best_match = current_match;
274     }
275   }
277   if (best_matching_paper == kPMNoData) {
278     PMPaper paper = kPMNoData;
279     // Create a custom paper for the specified default page size.
280     PMPaperMargins default_margins;
281     if (PMPaperGetMargins(default_paper, &default_margins) != noErr)
282       return false;
284     const PMPaperMargins margins =
285         {default_margins.top, default_margins.left, default_margins.bottom,
286          default_margins.right};
287     CFStringRef paper_id = CFSTR("Custom paper ID");
288     CFStringRef paper_name = CFSTR("Custom paper");
289     if (PMPaperCreateCustom(current_printer, paper_id, paper_name,
290             default_page_width, default_page_height, &margins, &paper) !=
291             noErr) {
292       return false;
293     }
294     [print_info_.get() updateFromPMPageFormat];
295     PMRelease(paper);
296   } else {
297     PMPageFormat chosen_page_format = NULL;
298     if (PMCreatePageFormat((PMPageFormat*) &chosen_page_format) != noErr)
299       return false;
301     // Create page format from that paper.
302     if (PMCreatePageFormatWithPMPaper(&chosen_page_format,
303             best_matching_paper) != noErr) {
304       PMRelease(chosen_page_format);
305       return false;
306     }
307     // Copy over the original format with the new page format.
308     if (PMCopyPageFormat(chosen_page_format, default_page_format) != noErr) {
309       PMRelease(chosen_page_format);
310       return false;
311     }
312     [print_info_.get() updateFromPMPageFormat];
313     PMRelease(chosen_page_format);
314   }
315   return true;
318 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
319   if (copies < 1)
320     return false;
322   PMPrintSettings pmPrintSettings =
323       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
324   return PMSetCopies(pmPrintSettings, copies, false) == noErr;
327 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
328   PMPrintSettings pmPrintSettings =
329       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
330   return PMSetCollate(pmPrintSettings, collate) == noErr;
333 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
334   PMPageFormat page_format =
335       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
337   PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
339   if (PMSetOrientation(page_format, orientation, false) != noErr)
340     return false;
342   PMPrintSession print_session =
343       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
345   PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
347   [print_info_.get() updateFromPMPageFormat];
348   return true;
351 bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
352   PMDuplexMode duplexSetting;
353   switch (mode) {
354     case LONG_EDGE:
355       duplexSetting = kPMDuplexNoTumble;
356       break;
357     case SHORT_EDGE:
358       duplexSetting = kPMDuplexTumble;
359       break;
360     case SIMPLEX:
361       duplexSetting = kPMDuplexNone;
362       break;
363     default:  // UNKNOWN_DUPLEX_MODE
364       return true;
365   }
367   PMPrintSettings pmPrintSettings =
368       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
369   return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
372 bool PrintingContextMac::SetOutputColor(int color_mode) {
373   PMPrintSettings pmPrintSettings =
374       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
375   std::string color_setting_name;
376   std::string color_value;
377   GetColorModelForMode(color_mode, &color_setting_name, &color_value);
378   base::ScopedCFTypeRef<CFStringRef> color_setting(
379       base::SysUTF8ToCFStringRef(color_setting_name));
380   base::ScopedCFTypeRef<CFStringRef> output_color(
381       base::SysUTF8ToCFStringRef(color_value));
383   return PMPrintSettingsSetValue(pmPrintSettings,
384                                  color_setting.get(),
385                                  output_color.get(),
386                                  false) == noErr;
389 PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
390   PageRanges page_ranges;
391   NSDictionary* print_info_dict = [print_info_.get() dictionary];
392   if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) {
393     PageRange range;
394     range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1;
395     range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1;
396     page_ranges.push_back(range);
397   }
398   return page_ranges;
401 PrintingContext::Result PrintingContextMac::InitWithSettings(
402     const PrintSettings& settings) {
403   DCHECK(!in_print_job_);
405   settings_ = settings;
407   NOTIMPLEMENTED();
409   return FAILED;
412 PrintingContext::Result PrintingContextMac::NewDocument(
413     const base::string16& document_name) {
414   DCHECK(!in_print_job_);
416   in_print_job_ = true;
418   PMPrintSession print_session =
419       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
420   PMPrintSettings print_settings =
421       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
422   PMPageFormat page_format =
423       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
425   base::ScopedCFTypeRef<CFStringRef> job_title(
426       base::SysUTF16ToCFStringRef(document_name));
427   PMPrintSettingsSetJobName(print_settings, job_title.get());
429   OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
430                                                      print_settings,
431                                                      page_format);
432   if (status != noErr)
433     return OnError();
435   return OK;
438 PrintingContext::Result PrintingContextMac::NewPage() {
439   if (abort_printing_)
440     return CANCEL;
441   DCHECK(in_print_job_);
442   DCHECK(!context_);
444   PMPrintSession print_session =
445       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
446   PMPageFormat page_format =
447       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
448   OSStatus status;
449   status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
450   if (status != noErr)
451     return OnError();
452   status = PMSessionGetCGGraphicsContext(print_session, &context_);
453   if (status != noErr)
454     return OnError();
456   return OK;
459 PrintingContext::Result PrintingContextMac::PageDone() {
460   if (abort_printing_)
461     return CANCEL;
462   DCHECK(in_print_job_);
463   DCHECK(context_);
465   PMPrintSession print_session =
466       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
467   OSStatus status = PMSessionEndPageNoDialog(print_session);
468   if (status != noErr)
469     OnError();
470   context_ = NULL;
472   return OK;
475 PrintingContext::Result PrintingContextMac::DocumentDone() {
476   if (abort_printing_)
477     return CANCEL;
478   DCHECK(in_print_job_);
480   PMPrintSession print_session =
481       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
482   OSStatus status = PMSessionEndDocumentNoDialog(print_session);
483   if (status != noErr)
484     OnError();
486   ResetSettings();
487   return OK;
490 void PrintingContextMac::Cancel() {
491   abort_printing_ = true;
492   in_print_job_ = false;
493   context_ = NULL;
495   PMPrintSession print_session =
496       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
497   PMSessionEndPageNoDialog(print_session);
500 void PrintingContextMac::ReleaseContext() {
501   print_info_.reset();
502   context_ = NULL;
505 gfx::NativeDrawingContext PrintingContextMac::context() const {
506   return context_;
509 }  // namespace printing