Add support for running disabled Telemetry benchmarks
[chromium-blink-merge.git] / printing / printing_context_mac.mm
blob9bbe5ac1de9e67198c10f64284577d76dbfac223
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 const int kMaxPaperSizeDiffereceInPoints = 2;
29 // Return true if PPD name of paper is equal.
30 bool IsPaperNameEqual(CFStringRef name1, const PMPaper& paper2) {
31   CFStringRef name2 = NULL;
32   return (name1 && PMPaperGetPPDPaperName(paper2, &name2) == noErr) &&
33          (CFStringCompare(name1, name2, kCFCompareCaseInsensitive) ==
34           kCFCompareEqualTo);
37 PMPaper MatchPaper(CFArrayRef paper_list,
38                    CFStringRef name,
39                    double width,
40                    double height) {
41   double best_match = std::numeric_limits<double>::max();
42   PMPaper best_matching_paper = NULL;
43   int num_papers = CFArrayGetCount(paper_list);
44   for (int i = 0; i < num_papers; ++i) {
45     PMPaper paper = (PMPaper)[(NSArray*)paper_list objectAtIndex : i];
46     double paper_width = 0.0;
47     double paper_height = 0.0;
48     PMPaperGetWidth(paper, &paper_width);
49     PMPaperGetHeight(paper, &paper_height);
50     double difference =
51         std::max(fabs(width - paper_width), fabs(height - paper_height));
53     // Ignore papers with size too different from expected.
54     if (difference > kMaxPaperSizeDiffereceInPoints)
55       continue;
57     if (name && IsPaperNameEqual(name, paper))
58       return paper;
60     if (difference < best_match) {
61       best_matching_paper = paper;
62       best_match = difference;
63     }
64   }
65   return best_matching_paper;
68 }  // namespace
70 // static
71 PrintingContext* PrintingContext::Create(const std::string& app_locale) {
72   return static_cast<PrintingContext*>(new PrintingContextMac(app_locale));
75 PrintingContextMac::PrintingContextMac(const std::string& app_locale)
76     : PrintingContext(app_locale),
77       print_info_([[NSPrintInfo sharedPrintInfo] copy]),
78       context_(NULL) {
81 PrintingContextMac::~PrintingContextMac() {
82   ReleaseContext();
85 void PrintingContextMac::AskUserForSettings(
86     gfx::NativeView parent_view,
87     int max_pages,
88     bool has_selection,
89     const PrintSettingsCallback& callback) {
90   // Third-party print drivers seem to be an area prone to raising exceptions.
91   // This will allow exceptions to be raised, but does not handle them.  The
92   // NSPrintPanel appears to have appropriate NSException handlers.
93   base::mac::ScopedNSExceptionEnabler enabler;
95   // Exceptions can also happen when the NSPrintPanel is being
96   // deallocated, so it must be autoreleased within this scope.
97   base::mac::ScopedNSAutoreleasePool pool;
99   DCHECK([NSThread isMainThread]);
101   // We deliberately don't feed max_pages into the dialog, because setting
102   // NSPrintLastPage makes the print dialog pre-select the option to only print
103   // a range.
105   // TODO(stuartmorgan): implement 'print selection only' (probably requires
106   // adding a new custom view to the panel on 10.5; 10.6 has
107   // NSPrintPanelShowsPrintSelection).
108   NSPrintPanel* panel = [NSPrintPanel printPanel];
109   NSPrintInfo* printInfo = print_info_.get();
111   NSPrintPanelOptions options = [panel options];
112   options |= NSPrintPanelShowsPaperSize;
113   options |= NSPrintPanelShowsOrientation;
114   options |= NSPrintPanelShowsScaling;
115   [panel setOptions:options];
117   // Set the print job title text.
118   if (parent_view) {
119     NSString* job_title = [[parent_view window] title];
120     if (job_title) {
121       PMPrintSettings printSettings =
122           (PMPrintSettings)[printInfo PMPrintSettings];
123       PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
124       [printInfo updateFromPMPrintSettings];
125     }
126   }
128   // TODO(stuartmorgan): We really want a tab sheet here, not a modal window.
129   // Will require restructuring the PrintingContext API to use a callback.
130   NSInteger selection = [panel runModalWithPrintInfo:printInfo];
131   if (selection == NSOKButton) {
132     print_info_.reset([[panel printInfo] retain]);
133     settings_.set_ranges(GetPageRangesFromPrintInfo());
134     InitPrintSettingsFromPrintInfo();
135     callback.Run(OK);
136   } else {
137     callback.Run(CANCEL);
138   }
141 gfx::Size PrintingContextMac::GetPdfPaperSizeDeviceUnits() {
142   // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
143   // with a clean slate.
144   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
145   UpdatePageFormatWithPaperInfo();
147   PMPageFormat page_format =
148       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
149   PMRect paper_rect;
150   PMGetAdjustedPaperRect(page_format, &paper_rect);
152   // Device units are in points. Units per inch is 72.
153   gfx::Size physical_size_device_units(
154       (paper_rect.right - paper_rect.left),
155       (paper_rect.bottom - paper_rect.top));
156   DCHECK(settings_.device_units_per_inch() == kPointsPerInch);
157   return physical_size_device_units;
160 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
161   DCHECK(!in_print_job_);
163   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
164   settings_.set_ranges(GetPageRangesFromPrintInfo());
165   InitPrintSettingsFromPrintInfo();
167   return OK;
170 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
171     bool external_preview) {
172   DCHECK(!in_print_job_);
174   // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
175   // with a clean slate.
176   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
178   if (external_preview) {
179     if (!SetPrintPreviewJob())
180       return OnError();
181   } else {
182     // Don't need this for preview.
183     if (!SetPrinter(base::UTF16ToUTF8(settings_.device_name())) ||
184         !SetCopiesInPrintSettings(settings_.copies()) ||
185         !SetCollateInPrintSettings(settings_.collate()) ||
186         !SetDuplexModeInPrintSettings(settings_.duplex_mode()) ||
187         !SetOutputColor(settings_.color())) {
188       return OnError();
189     }
190   }
192   if (!UpdatePageFormatWithPaperInfo() ||
193       !SetOrientationIsLandscape(settings_.landscape())) {
194     return OnError();
195   }
197   [print_info_.get() updateFromPMPrintSettings];
199   InitPrintSettingsFromPrintInfo();
200   return OK;
203 bool PrintingContextMac::SetPrintPreviewJob() {
204   PMPrintSession print_session =
205       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
206   PMPrintSettings print_settings =
207       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
208   return PMSessionSetDestination(
209       print_session, print_settings, kPMDestinationPreview,
210       NULL, NULL) == noErr;
213 void PrintingContextMac::InitPrintSettingsFromPrintInfo() {
214   PMPrintSession print_session =
215       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
216   PMPageFormat page_format =
217       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
218   PMPrinter printer;
219   PMSessionGetCurrentPrinter(print_session, &printer);
220   PrintSettingsInitializerMac::InitPrintSettings(
221       printer, page_format, &settings_);
224 bool PrintingContextMac::SetPrinter(const std::string& device_name) {
225   DCHECK(print_info_.get());
226   PMPrintSession print_session =
227       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
229   PMPrinter current_printer;
230   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
231     return false;
233   CFStringRef current_printer_id = PMPrinterGetID(current_printer);
234   if (!current_printer_id)
235     return false;
237   base::ScopedCFTypeRef<CFStringRef> new_printer_id(
238       base::SysUTF8ToCFStringRef(device_name));
239   if (!new_printer_id.get())
240     return false;
242   if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
243           kCFCompareEqualTo) {
244     return true;
245   }
247   PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
248   if (!new_printer)
249     return false;
251   OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
252   PMRelease(new_printer);
253   return status == noErr;
256 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
257   PMPrintSession print_session =
258       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
260   PMPageFormat default_page_format =
261       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
263   PMPrinter current_printer = NULL;
264   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
265     return false;
267   double page_width = 0.0;
268   double page_height = 0.0;
269   base::ScopedCFTypeRef<CFStringRef> paper_name;
270   PMPaperMargins margins = {0};
272   const PrintSettings::RequestedMedia& media = settings_.requested_media();
273   if (media.IsDefault()) {
274     PMPaper default_paper;
275     if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr ||
276         PMPaperGetWidth(default_paper, &page_width) != noErr ||
277         PMPaperGetHeight(default_paper, &page_height) != noErr) {
278       return false;
279     }
281     // Ignore result, because we can continue without following.
282     CFStringRef tmp_paper_name = NULL;
283     PMPaperGetPPDPaperName(default_paper, &tmp_paper_name);
284     PMPaperGetMargins(default_paper, &margins);
285     paper_name.reset(tmp_paper_name, base::scoped_policy::RETAIN);
286   } else {
287     const double kMutiplier = kPointsPerInch / (10.0f * kHundrethsMMPerInch);
288     page_width = media.size_microns.width() * kMutiplier;
289     page_height = media.size_microns.height() * kMutiplier;
290     paper_name.reset(base::SysUTF8ToCFStringRef(media.vendor_id));
291   }
293   CFArrayRef paper_list = NULL;
294   if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
295     return false;
297   PMPaper best_matching_paper =
298       MatchPaper(paper_list, paper_name, page_width, page_height);
300   if (best_matching_paper)
301     return UpdatePageFormatWithPaper(best_matching_paper, default_page_format);
303   // Do nothing if unmatched paper was default system paper.
304   if (media.IsDefault())
305     return true;
307   PMPaper paper = NULL;
308   if (PMPaperCreateCustom(current_printer,
309                           CFSTR("Custom paper ID"),
310                           CFSTR("Custom paper"),
311                           page_width,
312                           page_height,
313                           &margins,
314                           &paper) != noErr) {
315     return false;
316   }
317   bool result = UpdatePageFormatWithPaper(paper, default_page_format);
318   PMRelease(paper);
319   return result;
322 bool PrintingContextMac::UpdatePageFormatWithPaper(PMPaper paper,
323                                                    PMPageFormat page_format) {
324   PMPageFormat new_format = NULL;
325   if (PMCreatePageFormatWithPMPaper(&new_format, paper) != noErr)
326     return false;
327   // Copy over the original format with the new page format.
328   bool result = (PMCopyPageFormat(new_format, page_format) == noErr);
329   [print_info_.get() updateFromPMPageFormat];
330   PMRelease(new_format);
331   return result;
334 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
335   if (copies < 1)
336     return false;
338   PMPrintSettings pmPrintSettings =
339       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
340   return PMSetCopies(pmPrintSettings, copies, false) == noErr;
343 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
344   PMPrintSettings pmPrintSettings =
345       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
346   return PMSetCollate(pmPrintSettings, collate) == noErr;
349 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
350   PMPageFormat page_format =
351       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
353   PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
355   if (PMSetOrientation(page_format, orientation, false) != noErr)
356     return false;
358   PMPrintSession print_session =
359       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
361   PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
363   [print_info_.get() updateFromPMPageFormat];
364   return true;
367 bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
368   PMDuplexMode duplexSetting;
369   switch (mode) {
370     case LONG_EDGE:
371       duplexSetting = kPMDuplexNoTumble;
372       break;
373     case SHORT_EDGE:
374       duplexSetting = kPMDuplexTumble;
375       break;
376     case SIMPLEX:
377       duplexSetting = kPMDuplexNone;
378       break;
379     default:  // UNKNOWN_DUPLEX_MODE
380       return true;
381   }
383   PMPrintSettings pmPrintSettings =
384       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
385   return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
388 bool PrintingContextMac::SetOutputColor(int color_mode) {
389   PMPrintSettings pmPrintSettings =
390       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
391   std::string color_setting_name;
392   std::string color_value;
393   GetColorModelForMode(color_mode, &color_setting_name, &color_value);
394   base::ScopedCFTypeRef<CFStringRef> color_setting(
395       base::SysUTF8ToCFStringRef(color_setting_name));
396   base::ScopedCFTypeRef<CFStringRef> output_color(
397       base::SysUTF8ToCFStringRef(color_value));
399   return PMPrintSettingsSetValue(pmPrintSettings,
400                                  color_setting.get(),
401                                  output_color.get(),
402                                  false) == noErr;
405 PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
406   PageRanges page_ranges;
407   NSDictionary* print_info_dict = [print_info_.get() dictionary];
408   if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) {
409     PageRange range;
410     range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1;
411     range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1;
412     page_ranges.push_back(range);
413   }
414   return page_ranges;
417 PrintingContext::Result PrintingContextMac::InitWithSettings(
418     const PrintSettings& settings) {
419   DCHECK(!in_print_job_);
421   settings_ = settings;
423   NOTIMPLEMENTED();
425   return FAILED;
428 PrintingContext::Result PrintingContextMac::NewDocument(
429     const base::string16& document_name) {
430   DCHECK(!in_print_job_);
432   in_print_job_ = true;
434   PMPrintSession print_session =
435       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
436   PMPrintSettings print_settings =
437       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
438   PMPageFormat page_format =
439       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
441   base::ScopedCFTypeRef<CFStringRef> job_title(
442       base::SysUTF16ToCFStringRef(document_name));
443   PMPrintSettingsSetJobName(print_settings, job_title.get());
445   OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
446                                                      print_settings,
447                                                      page_format);
448   if (status != noErr)
449     return OnError();
451   return OK;
454 PrintingContext::Result PrintingContextMac::NewPage() {
455   if (abort_printing_)
456     return CANCEL;
457   DCHECK(in_print_job_);
458   DCHECK(!context_);
460   PMPrintSession print_session =
461       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
462   PMPageFormat page_format =
463       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
464   OSStatus status;
465   status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
466   if (status != noErr)
467     return OnError();
468   status = PMSessionGetCGGraphicsContext(print_session, &context_);
469   if (status != noErr)
470     return OnError();
472   return OK;
475 PrintingContext::Result PrintingContextMac::PageDone() {
476   if (abort_printing_)
477     return CANCEL;
478   DCHECK(in_print_job_);
479   DCHECK(context_);
481   PMPrintSession print_session =
482       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
483   OSStatus status = PMSessionEndPageNoDialog(print_session);
484   if (status != noErr)
485     OnError();
486   context_ = NULL;
488   return OK;
491 PrintingContext::Result PrintingContextMac::DocumentDone() {
492   if (abort_printing_)
493     return CANCEL;
494   DCHECK(in_print_job_);
496   PMPrintSession print_session =
497       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
498   OSStatus status = PMSessionEndDocumentNoDialog(print_session);
499   if (status != noErr)
500     OnError();
502   ResetSettings();
503   return OK;
506 void PrintingContextMac::Cancel() {
507   abort_printing_ = true;
508   in_print_job_ = false;
509   context_ = NULL;
511   PMPrintSession print_session =
512       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
513   PMSessionEndPageNoDialog(print_session);
516 void PrintingContextMac::ReleaseContext() {
517   print_info_.reset();
518   context_ = NULL;
521 gfx::NativeDrawingContext PrintingContextMac::context() const {
522   return context_;
525 }  // namespace printing