Roll PDFium to bring in:
[chromium-blink-merge.git] / printing / printing_context_mac.mm
blobbe3df6dc524918e5b3c5fd7248f92d6ad64533ea
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     bool is_scripted,
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   gfx::NativeView parent_view = delegate_->GetParentView();
119   if (parent_view) {
120     NSString* job_title = [[parent_view window] title];
121     if (job_title) {
122       PMPrintSettings printSettings =
123           (PMPrintSettings)[printInfo PMPrintSettings];
124       PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
125       [printInfo updateFromPMPrintSettings];
126     }
127   }
129   // TODO(stuartmorgan): We really want a tab sheet here, not a modal window.
130   // Will require restructuring the PrintingContext API to use a callback.
131   NSInteger selection = [panel runModalWithPrintInfo:printInfo];
132   if (selection == NSOKButton) {
133     print_info_.reset([[panel printInfo] retain]);
134     settings_.set_ranges(GetPageRangesFromPrintInfo());
135     InitPrintSettingsFromPrintInfo();
136     callback.Run(OK);
137   } else {
138     callback.Run(CANCEL);
139   }
142 gfx::Size PrintingContextMac::GetPdfPaperSizeDeviceUnits() {
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]);
146   UpdatePageFormatWithPaperInfo();
148   PMPageFormat page_format =
149       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
150   PMRect paper_rect;
151   PMGetAdjustedPaperRect(page_format, &paper_rect);
153   // Device units are in points. Units per inch is 72.
154   gfx::Size physical_size_device_units(
155       (paper_rect.right - paper_rect.left),
156       (paper_rect.bottom - paper_rect.top));
157   DCHECK(settings_.device_units_per_inch() == kPointsPerInch);
158   return physical_size_device_units;
161 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
162   DCHECK(!in_print_job_);
164   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
165   settings_.set_ranges(GetPageRangesFromPrintInfo());
166   InitPrintSettingsFromPrintInfo();
168   return OK;
171 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
172     bool external_preview,
173     bool show_system_dialog) {
174   DCHECK(!show_system_dialog);
175   DCHECK(!in_print_job_);
177   // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
178   // with a clean slate.
179   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
181   if (external_preview) {
182     if (!SetPrintPreviewJob())
183       return OnError();
184   } else {
185     // Don't need this for preview.
186     if (!SetPrinter(base::UTF16ToUTF8(settings_.device_name())) ||
187         !SetCopiesInPrintSettings(settings_.copies()) ||
188         !SetCollateInPrintSettings(settings_.collate()) ||
189         !SetDuplexModeInPrintSettings(settings_.duplex_mode()) ||
190         !SetOutputColor(settings_.color())) {
191       return OnError();
192     }
193   }
195   if (!UpdatePageFormatWithPaperInfo() ||
196       !SetOrientationIsLandscape(settings_.landscape())) {
197     return OnError();
198   }
200   [print_info_.get() updateFromPMPrintSettings];
202   InitPrintSettingsFromPrintInfo();
203   return OK;
206 bool PrintingContextMac::SetPrintPreviewJob() {
207   PMPrintSession print_session =
208       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
209   PMPrintSettings print_settings =
210       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
211   return PMSessionSetDestination(
212       print_session, print_settings, kPMDestinationPreview,
213       NULL, NULL) == noErr;
216 void PrintingContextMac::InitPrintSettingsFromPrintInfo() {
217   PMPrintSession print_session =
218       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
219   PMPageFormat page_format =
220       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
221   PMPrinter printer;
222   PMSessionGetCurrentPrinter(print_session, &printer);
223   PrintSettingsInitializerMac::InitPrintSettings(
224       printer, page_format, &settings_);
227 bool PrintingContextMac::SetPrinter(const std::string& device_name) {
228   DCHECK(print_info_.get());
229   PMPrintSession print_session =
230       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
232   PMPrinter current_printer;
233   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
234     return false;
236   CFStringRef current_printer_id = PMPrinterGetID(current_printer);
237   if (!current_printer_id)
238     return false;
240   base::ScopedCFTypeRef<CFStringRef> new_printer_id(
241       base::SysUTF8ToCFStringRef(device_name));
242   if (!new_printer_id.get())
243     return false;
245   if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
246           kCFCompareEqualTo) {
247     return true;
248   }
250   PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
251   if (!new_printer)
252     return false;
254   OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
255   PMRelease(new_printer);
256   return status == noErr;
259 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
260   PMPrintSession print_session =
261       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
263   PMPageFormat default_page_format =
264       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
266   PMPrinter current_printer = NULL;
267   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
268     return false;
270   double page_width = 0.0;
271   double page_height = 0.0;
272   base::ScopedCFTypeRef<CFStringRef> paper_name;
273   PMPaperMargins margins = {0};
275   const PrintSettings::RequestedMedia& media = settings_.requested_media();
276   if (media.IsDefault()) {
277     PMPaper default_paper;
278     if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr ||
279         PMPaperGetWidth(default_paper, &page_width) != noErr ||
280         PMPaperGetHeight(default_paper, &page_height) != noErr) {
281       return false;
282     }
284     // Ignore result, because we can continue without following.
285     CFStringRef tmp_paper_name = NULL;
286     PMPaperGetPPDPaperName(default_paper, &tmp_paper_name);
287     PMPaperGetMargins(default_paper, &margins);
288     paper_name.reset(tmp_paper_name, base::scoped_policy::RETAIN);
289   } else {
290     const double kMutiplier = kPointsPerInch / (10.0f * kHundrethsMMPerInch);
291     page_width = media.size_microns.width() * kMutiplier;
292     page_height = media.size_microns.height() * kMutiplier;
293     paper_name.reset(base::SysUTF8ToCFStringRef(media.vendor_id));
294   }
296   CFArrayRef paper_list = NULL;
297   if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
298     return false;
300   PMPaper best_matching_paper =
301       MatchPaper(paper_list, paper_name, page_width, page_height);
303   if (best_matching_paper)
304     return UpdatePageFormatWithPaper(best_matching_paper, default_page_format);
306   // Do nothing if unmatched paper was default system paper.
307   if (media.IsDefault())
308     return true;
310   PMPaper paper = NULL;
311   if (PMPaperCreateCustom(current_printer,
312                           CFSTR("Custom paper ID"),
313                           CFSTR("Custom paper"),
314                           page_width,
315                           page_height,
316                           &margins,
317                           &paper) != noErr) {
318     return false;
319   }
320   bool result = UpdatePageFormatWithPaper(paper, default_page_format);
321   PMRelease(paper);
322   return result;
325 bool PrintingContextMac::UpdatePageFormatWithPaper(PMPaper paper,
326                                                    PMPageFormat page_format) {
327   PMPageFormat new_format = NULL;
328   if (PMCreatePageFormatWithPMPaper(&new_format, paper) != noErr)
329     return false;
330   // Copy over the original format with the new page format.
331   bool result = (PMCopyPageFormat(new_format, page_format) == noErr);
332   [print_info_.get() updateFromPMPageFormat];
333   PMRelease(new_format);
334   return result;
337 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
338   if (copies < 1)
339     return false;
341   PMPrintSettings pmPrintSettings =
342       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
343   return PMSetCopies(pmPrintSettings, copies, false) == noErr;
346 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
347   PMPrintSettings pmPrintSettings =
348       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
349   return PMSetCollate(pmPrintSettings, collate) == noErr;
352 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
353   PMPageFormat page_format =
354       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
356   PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
358   if (PMSetOrientation(page_format, orientation, false) != noErr)
359     return false;
361   PMPrintSession print_session =
362       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
364   PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
366   [print_info_.get() updateFromPMPageFormat];
367   return true;
370 bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
371   PMDuplexMode duplexSetting;
372   switch (mode) {
373     case LONG_EDGE:
374       duplexSetting = kPMDuplexNoTumble;
375       break;
376     case SHORT_EDGE:
377       duplexSetting = kPMDuplexTumble;
378       break;
379     case SIMPLEX:
380       duplexSetting = kPMDuplexNone;
381       break;
382     default:  // UNKNOWN_DUPLEX_MODE
383       return true;
384   }
386   PMPrintSettings pmPrintSettings =
387       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
388   return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
391 bool PrintingContextMac::SetOutputColor(int color_mode) {
392   PMPrintSettings pmPrintSettings =
393       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
394   std::string color_setting_name;
395   std::string color_value;
396   GetColorModelForMode(color_mode, &color_setting_name, &color_value);
397   base::ScopedCFTypeRef<CFStringRef> color_setting(
398       base::SysUTF8ToCFStringRef(color_setting_name));
399   base::ScopedCFTypeRef<CFStringRef> output_color(
400       base::SysUTF8ToCFStringRef(color_value));
402   return PMPrintSettingsSetValue(pmPrintSettings,
403                                  color_setting.get(),
404                                  output_color.get(),
405                                  false) == noErr;
408 PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
409   PageRanges page_ranges;
410   NSDictionary* print_info_dict = [print_info_.get() dictionary];
411   if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) {
412     PageRange range;
413     range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1;
414     range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1;
415     page_ranges.push_back(range);
416   }
417   return page_ranges;
420 PrintingContext::Result PrintingContextMac::InitWithSettings(
421     const PrintSettings& settings) {
422   DCHECK(!in_print_job_);
424   settings_ = settings;
426   NOTIMPLEMENTED();
428   return FAILED;
431 PrintingContext::Result PrintingContextMac::NewDocument(
432     const base::string16& document_name) {
433   DCHECK(!in_print_job_);
435   in_print_job_ = true;
437   PMPrintSession print_session =
438       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
439   PMPrintSettings print_settings =
440       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
441   PMPageFormat page_format =
442       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
444   base::ScopedCFTypeRef<CFStringRef> job_title(
445       base::SysUTF16ToCFStringRef(document_name));
446   PMPrintSettingsSetJobName(print_settings, job_title.get());
448   OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
449                                                      print_settings,
450                                                      page_format);
451   if (status != noErr)
452     return OnError();
454   return OK;
457 PrintingContext::Result PrintingContextMac::NewPage() {
458   if (abort_printing_)
459     return CANCEL;
460   DCHECK(in_print_job_);
461   DCHECK(!context_);
463   PMPrintSession print_session =
464       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
465   PMPageFormat page_format =
466       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
467   OSStatus status;
468   status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
469   if (status != noErr)
470     return OnError();
471   status = PMSessionGetCGGraphicsContext(print_session, &context_);
472   if (status != noErr)
473     return OnError();
475   return OK;
478 PrintingContext::Result PrintingContextMac::PageDone() {
479   if (abort_printing_)
480     return CANCEL;
481   DCHECK(in_print_job_);
482   DCHECK(context_);
484   PMPrintSession print_session =
485       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
486   OSStatus status = PMSessionEndPageNoDialog(print_session);
487   if (status != noErr)
488     OnError();
489   context_ = NULL;
491   return OK;
494 PrintingContext::Result PrintingContextMac::DocumentDone() {
495   if (abort_printing_)
496     return CANCEL;
497   DCHECK(in_print_job_);
499   PMPrintSession print_session =
500       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
501   OSStatus status = PMSessionEndDocumentNoDialog(print_session);
502   if (status != noErr)
503     OnError();
505   ResetSettings();
506   return OK;
509 void PrintingContextMac::Cancel() {
510   abort_printing_ = true;
511   in_print_job_ = false;
512   context_ = NULL;
514   PMPrintSession print_session =
515       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
516   PMSessionEndPageNoDialog(print_session);
519 void PrintingContextMac::ReleaseContext() {
520   print_info_.reset();
521   context_ = NULL;
524 gfx::NativeDrawingContext PrintingContextMac::context() const {
525   return context_;
528 }  // namespace printing