Document that chrome.accessibilityFeatures only works on ChromeOS
[chromium-blink-merge.git] / printing / printing_context_mac.mm
blob23d2d84c1e6989c52fe36835aec62349868f8a32
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 scoped_ptr<PrintingContext> PrintingContext::Create(Delegate* delegate) {
72   return make_scoped_ptr<PrintingContext>(new PrintingContextMac(delegate));
75 PrintingContextMac::PrintingContextMac(Delegate* delegate)
76     : PrintingContext(delegate),
77       print_info_([[NSPrintInfo sharedPrintInfo] copy]),
78       context_(NULL) {
81 PrintingContextMac::~PrintingContextMac() {
82   ReleaseContext();
85 void PrintingContextMac::AskUserForSettings(
86     int max_pages,
87     bool has_selection,
88     const PrintSettingsCallback& callback) {
89   // Third-party print drivers seem to be an area prone to raising exceptions.
90   // This will allow exceptions to be raised, but does not handle them.  The
91   // NSPrintPanel appears to have appropriate NSException handlers.
92   base::mac::ScopedNSExceptionEnabler enabler;
94   // Exceptions can also happen when the NSPrintPanel is being
95   // deallocated, so it must be autoreleased within this scope.
96   base::mac::ScopedNSAutoreleasePool pool;
98   DCHECK([NSThread isMainThread]);
100   // We deliberately don't feed max_pages into the dialog, because setting
101   // NSPrintLastPage makes the print dialog pre-select the option to only print
102   // a range.
104   // TODO(stuartmorgan): implement 'print selection only' (probably requires
105   // adding a new custom view to the panel on 10.5; 10.6 has
106   // NSPrintPanelShowsPrintSelection).
107   NSPrintPanel* panel = [NSPrintPanel printPanel];
108   NSPrintInfo* printInfo = print_info_.get();
110   NSPrintPanelOptions options = [panel options];
111   options |= NSPrintPanelShowsPaperSize;
112   options |= NSPrintPanelShowsOrientation;
113   options |= NSPrintPanelShowsScaling;
114   [panel setOptions:options];
116   // Set the print job title text.
117   gfx::NativeView parent_view = delegate_->GetParentView();
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     bool show_system_dialog) {
173   DCHECK(!show_system_dialog);
174   DCHECK(!in_print_job_);
176   // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
177   // with a clean slate.
178   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
180   if (external_preview) {
181     if (!SetPrintPreviewJob())
182       return OnError();
183   } else {
184     // Don't need this for preview.
185     if (!SetPrinter(base::UTF16ToUTF8(settings_.device_name())) ||
186         !SetCopiesInPrintSettings(settings_.copies()) ||
187         !SetCollateInPrintSettings(settings_.collate()) ||
188         !SetDuplexModeInPrintSettings(settings_.duplex_mode()) ||
189         !SetOutputColor(settings_.color())) {
190       return OnError();
191     }
192   }
194   if (!UpdatePageFormatWithPaperInfo() ||
195       !SetOrientationIsLandscape(settings_.landscape())) {
196     return OnError();
197   }
199   [print_info_.get() updateFromPMPrintSettings];
201   InitPrintSettingsFromPrintInfo();
202   return OK;
205 bool PrintingContextMac::SetPrintPreviewJob() {
206   PMPrintSession print_session =
207       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
208   PMPrintSettings print_settings =
209       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
210   return PMSessionSetDestination(
211       print_session, print_settings, kPMDestinationPreview,
212       NULL, NULL) == noErr;
215 void PrintingContextMac::InitPrintSettingsFromPrintInfo() {
216   PMPrintSession print_session =
217       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
218   PMPageFormat page_format =
219       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
220   PMPrinter printer;
221   PMSessionGetCurrentPrinter(print_session, &printer);
222   PrintSettingsInitializerMac::InitPrintSettings(
223       printer, page_format, &settings_);
226 bool PrintingContextMac::SetPrinter(const std::string& device_name) {
227   DCHECK(print_info_.get());
228   PMPrintSession print_session =
229       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
231   PMPrinter current_printer;
232   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
233     return false;
235   CFStringRef current_printer_id = PMPrinterGetID(current_printer);
236   if (!current_printer_id)
237     return false;
239   base::ScopedCFTypeRef<CFStringRef> new_printer_id(
240       base::SysUTF8ToCFStringRef(device_name));
241   if (!new_printer_id.get())
242     return false;
244   if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
245           kCFCompareEqualTo) {
246     return true;
247   }
249   PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
250   if (!new_printer)
251     return false;
253   OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
254   PMRelease(new_printer);
255   return status == noErr;
258 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
259   PMPrintSession print_session =
260       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
262   PMPageFormat default_page_format =
263       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
265   PMPrinter current_printer = NULL;
266   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
267     return false;
269   double page_width = 0.0;
270   double page_height = 0.0;
271   base::ScopedCFTypeRef<CFStringRef> paper_name;
272   PMPaperMargins margins = {0};
274   const PrintSettings::RequestedMedia& media = settings_.requested_media();
275   if (media.IsDefault()) {
276     PMPaper default_paper;
277     if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr ||
278         PMPaperGetWidth(default_paper, &page_width) != noErr ||
279         PMPaperGetHeight(default_paper, &page_height) != noErr) {
280       return false;
281     }
283     // Ignore result, because we can continue without following.
284     CFStringRef tmp_paper_name = NULL;
285     PMPaperGetPPDPaperName(default_paper, &tmp_paper_name);
286     PMPaperGetMargins(default_paper, &margins);
287     paper_name.reset(tmp_paper_name, base::scoped_policy::RETAIN);
288   } else {
289     const double kMutiplier = kPointsPerInch / (10.0f * kHundrethsMMPerInch);
290     page_width = media.size_microns.width() * kMutiplier;
291     page_height = media.size_microns.height() * kMutiplier;
292     paper_name.reset(base::SysUTF8ToCFStringRef(media.vendor_id));
293   }
295   CFArrayRef paper_list = NULL;
296   if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
297     return false;
299   PMPaper best_matching_paper =
300       MatchPaper(paper_list, paper_name, page_width, page_height);
302   if (best_matching_paper)
303     return UpdatePageFormatWithPaper(best_matching_paper, default_page_format);
305   // Do nothing if unmatched paper was default system paper.
306   if (media.IsDefault())
307     return true;
309   PMPaper paper = NULL;
310   if (PMPaperCreateCustom(current_printer,
311                           CFSTR("Custom paper ID"),
312                           CFSTR("Custom paper"),
313                           page_width,
314                           page_height,
315                           &margins,
316                           &paper) != noErr) {
317     return false;
318   }
319   bool result = UpdatePageFormatWithPaper(paper, default_page_format);
320   PMRelease(paper);
321   return result;
324 bool PrintingContextMac::UpdatePageFormatWithPaper(PMPaper paper,
325                                                    PMPageFormat page_format) {
326   PMPageFormat new_format = NULL;
327   if (PMCreatePageFormatWithPMPaper(&new_format, paper) != noErr)
328     return false;
329   // Copy over the original format with the new page format.
330   bool result = (PMCopyPageFormat(new_format, page_format) == noErr);
331   [print_info_.get() updateFromPMPageFormat];
332   PMRelease(new_format);
333   return result;
336 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
337   if (copies < 1)
338     return false;
340   PMPrintSettings pmPrintSettings =
341       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
342   return PMSetCopies(pmPrintSettings, copies, false) == noErr;
345 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
346   PMPrintSettings pmPrintSettings =
347       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
348   return PMSetCollate(pmPrintSettings, collate) == noErr;
351 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
352   PMPageFormat page_format =
353       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
355   PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
357   if (PMSetOrientation(page_format, orientation, false) != noErr)
358     return false;
360   PMPrintSession print_session =
361       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
363   PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
365   [print_info_.get() updateFromPMPageFormat];
366   return true;
369 bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
370   PMDuplexMode duplexSetting;
371   switch (mode) {
372     case LONG_EDGE:
373       duplexSetting = kPMDuplexNoTumble;
374       break;
375     case SHORT_EDGE:
376       duplexSetting = kPMDuplexTumble;
377       break;
378     case SIMPLEX:
379       duplexSetting = kPMDuplexNone;
380       break;
381     default:  // UNKNOWN_DUPLEX_MODE
382       return true;
383   }
385   PMPrintSettings pmPrintSettings =
386       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
387   return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
390 bool PrintingContextMac::SetOutputColor(int color_mode) {
391   PMPrintSettings pmPrintSettings =
392       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
393   std::string color_setting_name;
394   std::string color_value;
395   GetColorModelForMode(color_mode, &color_setting_name, &color_value);
396   base::ScopedCFTypeRef<CFStringRef> color_setting(
397       base::SysUTF8ToCFStringRef(color_setting_name));
398   base::ScopedCFTypeRef<CFStringRef> output_color(
399       base::SysUTF8ToCFStringRef(color_value));
401   return PMPrintSettingsSetValue(pmPrintSettings,
402                                  color_setting.get(),
403                                  output_color.get(),
404                                  false) == noErr;
407 PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
408   PageRanges page_ranges;
409   NSDictionary* print_info_dict = [print_info_.get() dictionary];
410   if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) {
411     PageRange range;
412     range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1;
413     range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1;
414     page_ranges.push_back(range);
415   }
416   return page_ranges;
419 PrintingContext::Result PrintingContextMac::InitWithSettings(
420     const PrintSettings& settings) {
421   DCHECK(!in_print_job_);
423   settings_ = settings;
425   NOTIMPLEMENTED();
427   return FAILED;
430 PrintingContext::Result PrintingContextMac::NewDocument(
431     const base::string16& document_name) {
432   DCHECK(!in_print_job_);
434   in_print_job_ = true;
436   PMPrintSession print_session =
437       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
438   PMPrintSettings print_settings =
439       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
440   PMPageFormat page_format =
441       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
443   base::ScopedCFTypeRef<CFStringRef> job_title(
444       base::SysUTF16ToCFStringRef(document_name));
445   PMPrintSettingsSetJobName(print_settings, job_title.get());
447   OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
448                                                      print_settings,
449                                                      page_format);
450   if (status != noErr)
451     return OnError();
453   return OK;
456 PrintingContext::Result PrintingContextMac::NewPage() {
457   if (abort_printing_)
458     return CANCEL;
459   DCHECK(in_print_job_);
460   DCHECK(!context_);
462   PMPrintSession print_session =
463       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
464   PMPageFormat page_format =
465       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
466   OSStatus status;
467   status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
468   if (status != noErr)
469     return OnError();
470   status = PMSessionGetCGGraphicsContext(print_session, &context_);
471   if (status != noErr)
472     return OnError();
474   return OK;
477 PrintingContext::Result PrintingContextMac::PageDone() {
478   if (abort_printing_)
479     return CANCEL;
480   DCHECK(in_print_job_);
481   DCHECK(context_);
483   PMPrintSession print_session =
484       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
485   OSStatus status = PMSessionEndPageNoDialog(print_session);
486   if (status != noErr)
487     OnError();
488   context_ = NULL;
490   return OK;
493 PrintingContext::Result PrintingContextMac::DocumentDone() {
494   if (abort_printing_)
495     return CANCEL;
496   DCHECK(in_print_job_);
498   PMPrintSession print_session =
499       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
500   OSStatus status = PMSessionEndDocumentNoDialog(print_session);
501   if (status != noErr)
502     OnError();
504   ResetSettings();
505   return OK;
508 void PrintingContextMac::Cancel() {
509   abort_printing_ = true;
510   in_print_job_ = false;
511   context_ = NULL;
513   PMPrintSession print_session =
514       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
515   PMSessionEndPageNoDialog(print_session);
518 void PrintingContextMac::ReleaseContext() {
519   print_info_.reset();
520   context_ = NULL;
523 gfx::NativeDrawingContext PrintingContextMac::context() const {
524   return context_;
527 }  // namespace printing