Home | History | Annotate | Download | only in printing
      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.
      4 
      5 #include "printing/printing_context_mac.h"
      6 
      7 #import <ApplicationServices/ApplicationServices.h>
      8 #import <AppKit/AppKit.h>
      9 
     10 #import <iomanip>
     11 #import <numeric>
     12 
     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/values.h"
     19 #include "printing/print_settings_initializer_mac.h"
     20 
     21 namespace printing {
     22 
     23 namespace {
     24 
     25 // Return true if PPD name of paper is equal.
     26 bool IsPaperNameEqual(const PMPaper& paper1, const PMPaper& paper2) {
     27   CFStringRef name1 = NULL;
     28   CFStringRef name2 = NULL;
     29   return (PMPaperGetPPDPaperName(paper1, &name1) == noErr) &&
     30          (PMPaperGetPPDPaperName(paper2, &name2) == noErr) &&
     31          (CFStringCompare(name1, name2,
     32                           kCFCompareCaseInsensitive) == kCFCompareEqualTo);
     33 }
     34 
     35 }  // namespace
     36 
     37 // static
     38 PrintingContext* PrintingContext::Create(const std::string& app_locale) {
     39   return static_cast<PrintingContext*>(new PrintingContextMac(app_locale));
     40 }
     41 
     42 PrintingContextMac::PrintingContextMac(const std::string& app_locale)
     43     : PrintingContext(app_locale),
     44       print_info_([[NSPrintInfo sharedPrintInfo] copy]),
     45       context_(NULL) {
     46 }
     47 
     48 PrintingContextMac::~PrintingContextMac() {
     49   ReleaseContext();
     50 }
     51 
     52 void PrintingContextMac::AskUserForSettings(
     53     gfx::NativeView parent_view,
     54     int max_pages,
     55     bool has_selection,
     56     const PrintSettingsCallback& callback) {
     57   // Third-party print drivers seem to be an area prone to raising exceptions.
     58   // This will allow exceptions to be raised, but does not handle them.  The
     59   // NSPrintPanel appears to have appropriate NSException handlers.
     60   base::mac::ScopedNSExceptionEnabler enabler;
     61 
     62   // Exceptions can also happen when the NSPrintPanel is being
     63   // deallocated, so it must be autoreleased within this scope.
     64   base::mac::ScopedNSAutoreleasePool pool;
     65 
     66   DCHECK([NSThread isMainThread]);
     67 
     68   // We deliberately don't feed max_pages into the dialog, because setting
     69   // NSPrintLastPage makes the print dialog pre-select the option to only print
     70   // a range.
     71 
     72   // TODO(stuartmorgan): implement 'print selection only' (probably requires
     73   // adding a new custom view to the panel on 10.5; 10.6 has
     74   // NSPrintPanelShowsPrintSelection).
     75   NSPrintPanel* panel = [NSPrintPanel printPanel];
     76   NSPrintInfo* printInfo = print_info_.get();
     77 
     78   NSPrintPanelOptions options = [panel options];
     79   options |= NSPrintPanelShowsPaperSize;
     80   options |= NSPrintPanelShowsOrientation;
     81   options |= NSPrintPanelShowsScaling;
     82   [panel setOptions:options];
     83 
     84   // Set the print job title text.
     85   if (parent_view) {
     86     NSString* job_title = [[parent_view window] title];
     87     if (job_title) {
     88       PMPrintSettings printSettings =
     89           (PMPrintSettings)[printInfo PMPrintSettings];
     90       PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
     91       [printInfo updateFromPMPrintSettings];
     92     }
     93   }
     94 
     95   // TODO(stuartmorgan): We really want a tab sheet here, not a modal window.
     96   // Will require restructuring the PrintingContext API to use a callback.
     97   NSInteger selection = [panel runModalWithPrintInfo:printInfo];
     98   if (selection == NSOKButton) {
     99     print_info_.reset([[panel printInfo] retain]);
    100     InitPrintSettingsFromPrintInfo(GetPageRangesFromPrintInfo());
    101     callback.Run(OK);
    102   } else {
    103     callback.Run(CANCEL);
    104   }
    105 }
    106 
    107 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
    108   DCHECK(!in_print_job_);
    109 
    110   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
    111   InitPrintSettingsFromPrintInfo(GetPageRangesFromPrintInfo());
    112 
    113   return OK;
    114 }
    115 
    116 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
    117     const DictionaryValue& job_settings, const PageRanges& ranges) {
    118   DCHECK(!in_print_job_);
    119 
    120   // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
    121   // with a clean slate.
    122   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
    123 
    124   bool collate;
    125   int color;
    126   bool landscape;
    127   bool print_to_pdf;
    128   bool is_cloud_dialog;
    129   int copies;
    130   int duplex_mode;
    131   std::string device_name;
    132 
    133   if (!job_settings.GetBoolean(kSettingLandscape, &landscape) ||
    134       !job_settings.GetBoolean(kSettingCollate, &collate) ||
    135       !job_settings.GetInteger(kSettingColor, &color) ||
    136       !job_settings.GetBoolean(kSettingPrintToPDF, &print_to_pdf) ||
    137       !job_settings.GetInteger(kSettingDuplexMode, &duplex_mode) ||
    138       !job_settings.GetInteger(kSettingCopies, &copies) ||
    139       !job_settings.GetString(kSettingDeviceName, &device_name) ||
    140       !job_settings.GetBoolean(kSettingCloudPrintDialog, &is_cloud_dialog)) {
    141     return OnError();
    142   }
    143 
    144   bool print_to_cloud = job_settings.HasKey(kSettingCloudPrintId);
    145   bool open_pdf_in_preview = job_settings.HasKey(kSettingOpenPDFInPreview);
    146 
    147   if (!print_to_pdf && !print_to_cloud && !is_cloud_dialog) {
    148     if (!SetPrinter(device_name))
    149       return OnError();
    150 
    151     if (!SetCopiesInPrintSettings(copies))
    152       return OnError();
    153 
    154     if (!SetCollateInPrintSettings(collate))
    155       return OnError();
    156 
    157     if (!SetDuplexModeInPrintSettings(
    158             static_cast<DuplexMode>(duplex_mode))) {
    159       return OnError();
    160     }
    161 
    162     if (!SetOutputColor(color))
    163       return OnError();
    164   }
    165   if (open_pdf_in_preview) {
    166     if (!SetPrintPreviewJob())
    167       return OnError();
    168   }
    169 
    170   if (!UpdatePageFormatWithPaperInfo())
    171     return OnError();
    172 
    173   if (!SetOrientationIsLandscape(landscape))
    174     return OnError();
    175 
    176   [print_info_.get() updateFromPMPrintSettings];
    177 
    178   InitPrintSettingsFromPrintInfo(ranges);
    179   return OK;
    180 }
    181 
    182 bool PrintingContextMac::SetPrintPreviewJob() {
    183   PMPrintSession print_session =
    184       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    185   PMPrintSettings print_settings =
    186       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
    187   return PMSessionSetDestination(
    188       print_session, print_settings, kPMDestinationPreview,
    189       NULL, NULL) == noErr;
    190 }
    191 
    192 void PrintingContextMac::InitPrintSettingsFromPrintInfo(
    193     const PageRanges& ranges) {
    194   PMPrintSession print_session =
    195       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    196   PMPageFormat page_format =
    197       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
    198   PMPrinter printer;
    199   PMSessionGetCurrentPrinter(print_session, &printer);
    200   PrintSettingsInitializerMac::InitPrintSettings(
    201       printer, page_format, ranges, false, &settings_);
    202 }
    203 
    204 bool PrintingContextMac::SetPrinter(const std::string& device_name) {
    205   DCHECK(print_info_.get());
    206   PMPrintSession print_session =
    207       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    208 
    209   PMPrinter current_printer;
    210   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
    211     return false;
    212 
    213   CFStringRef current_printer_id = PMPrinterGetID(current_printer);
    214   if (!current_printer_id)
    215     return false;
    216 
    217   base::ScopedCFTypeRef<CFStringRef> new_printer_id(
    218       base::SysUTF8ToCFStringRef(device_name));
    219   if (!new_printer_id.get())
    220     return false;
    221 
    222   if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
    223           kCFCompareEqualTo) {
    224     return true;
    225   }
    226 
    227   PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
    228   if (new_printer == NULL)
    229     return false;
    230 
    231   OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
    232   PMRelease(new_printer);
    233   return status == noErr;
    234 }
    235 
    236 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
    237   PMPrintSession print_session =
    238       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    239 
    240   PMPageFormat default_page_format =
    241       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
    242 
    243   PMPaper default_paper;
    244   if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr)
    245     return false;
    246 
    247   double default_page_width = 0.0;
    248   double default_page_height = 0.0;
    249   if (PMPaperGetWidth(default_paper, &default_page_width) != noErr)
    250     return false;
    251 
    252   if (PMPaperGetHeight(default_paper, &default_page_height) != noErr)
    253     return false;
    254 
    255   PMPrinter current_printer = NULL;
    256   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
    257     return false;
    258 
    259   if (current_printer == nil)
    260     return false;
    261 
    262   CFArrayRef paper_list = NULL;
    263   if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
    264     return false;
    265 
    266   double best_match = std::numeric_limits<double>::max();
    267   PMPaper best_matching_paper = kPMNoData;
    268   int num_papers = CFArrayGetCount(paper_list);
    269   for (int i = 0; i < num_papers; ++i) {
    270     PMPaper paper = (PMPaper)[(NSArray*)paper_list objectAtIndex: i];
    271     double paper_width = 0.0;
    272     double paper_height = 0.0;
    273     PMPaperGetWidth(paper, &paper_width);
    274     PMPaperGetHeight(paper, &paper_height);
    275     double current_match = std::max(fabs(default_page_width - paper_width),
    276                                     fabs(default_page_height - paper_height));
    277     // Ignore paper sizes that are very different.
    278     if (current_match > 2)
    279       continue;
    280     current_match += IsPaperNameEqual(paper, default_paper) ? 0 : 1;
    281     if (current_match < best_match) {
    282       best_matching_paper = paper;
    283       best_match = current_match;
    284     }
    285   }
    286 
    287   if (best_matching_paper == kPMNoData) {
    288     PMPaper paper = kPMNoData;
    289     // Create a custom paper for the specified default page size.
    290     PMPaperMargins default_margins;
    291     if (PMPaperGetMargins(default_paper, &default_margins) != noErr)
    292       return false;
    293 
    294     const PMPaperMargins margins =
    295         {default_margins.top, default_margins.left, default_margins.bottom,
    296          default_margins.right};
    297     CFStringRef paper_id = CFSTR("Custom paper ID");
    298     CFStringRef paper_name = CFSTR("Custom paper");
    299     if (PMPaperCreateCustom(current_printer, paper_id, paper_name,
    300             default_page_width, default_page_height, &margins, &paper) !=
    301             noErr) {
    302       return false;
    303     }
    304     [print_info_.get() updateFromPMPageFormat];
    305     PMRelease(paper);
    306   } else {
    307     PMPageFormat chosen_page_format = NULL;
    308     if (PMCreatePageFormat((PMPageFormat*) &chosen_page_format) != noErr)
    309       return false;
    310 
    311     // Create page format from that paper.
    312     if (PMCreatePageFormatWithPMPaper(&chosen_page_format,
    313             best_matching_paper) != noErr) {
    314       PMRelease(chosen_page_format);
    315       return false;
    316     }
    317     // Copy over the original format with the new page format.
    318     if (PMCopyPageFormat(chosen_page_format, default_page_format) != noErr) {
    319       PMRelease(chosen_page_format);
    320       return false;
    321     }
    322     [print_info_.get() updateFromPMPageFormat];
    323     PMRelease(chosen_page_format);
    324   }
    325   return true;
    326 }
    327 
    328 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
    329   if (copies < 1)
    330     return false;
    331 
    332   PMPrintSettings pmPrintSettings =
    333       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
    334   return PMSetCopies(pmPrintSettings, copies, false) == noErr;
    335 }
    336 
    337 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
    338   PMPrintSettings pmPrintSettings =
    339       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
    340   return PMSetCollate(pmPrintSettings, collate) == noErr;
    341 }
    342 
    343 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
    344   PMPageFormat page_format =
    345       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
    346 
    347   PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
    348 
    349   if (PMSetOrientation(page_format, orientation, false) != noErr)
    350     return false;
    351 
    352   PMPrintSession print_session =
    353       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    354 
    355   PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
    356 
    357   [print_info_.get() updateFromPMPageFormat];
    358   return true;
    359 }
    360 
    361 bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
    362   PMDuplexMode duplexSetting;
    363   switch (mode) {
    364     case LONG_EDGE:
    365       duplexSetting = kPMDuplexNoTumble;
    366       break;
    367     case SHORT_EDGE:
    368       duplexSetting = kPMDuplexTumble;
    369       break;
    370     case SIMPLEX:
    371       duplexSetting = kPMDuplexNone;
    372       break;
    373     default:  // UNKNOWN_DUPLEX_MODE
    374       return true;
    375   }
    376 
    377   PMPrintSettings pmPrintSettings =
    378       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
    379   return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
    380 }
    381 
    382 bool PrintingContextMac::SetOutputColor(int color_mode) {
    383   PMPrintSettings pmPrintSettings =
    384       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
    385   std::string color_setting_name;
    386   std::string color_value;
    387   GetColorModelForMode(color_mode, &color_setting_name, &color_value);
    388   base::ScopedCFTypeRef<CFStringRef> color_setting(
    389       base::SysUTF8ToCFStringRef(color_setting_name));
    390   base::ScopedCFTypeRef<CFStringRef> output_color(
    391       base::SysUTF8ToCFStringRef(color_value));
    392 
    393   return PMPrintSettingsSetValue(pmPrintSettings,
    394                                  color_setting.get(),
    395                                  output_color.get(),
    396                                  false) == noErr;
    397 }
    398 
    399 PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
    400   PageRanges page_ranges;
    401   NSDictionary* print_info_dict = [print_info_.get() dictionary];
    402   if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) {
    403     PageRange range;
    404     range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1;
    405     range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1;
    406     page_ranges.push_back(range);
    407   }
    408   return page_ranges;
    409 }
    410 
    411 PrintingContext::Result PrintingContextMac::InitWithSettings(
    412     const PrintSettings& settings) {
    413   DCHECK(!in_print_job_);
    414 
    415   settings_ = settings;
    416 
    417   NOTIMPLEMENTED();
    418 
    419   return FAILED;
    420 }
    421 
    422 PrintingContext::Result PrintingContextMac::NewDocument(
    423     const string16& document_name) {
    424   DCHECK(!in_print_job_);
    425 
    426   in_print_job_ = true;
    427 
    428   PMPrintSession print_session =
    429       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    430   PMPrintSettings print_settings =
    431       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
    432   PMPageFormat page_format =
    433       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
    434 
    435   base::ScopedCFTypeRef<CFStringRef> job_title(
    436       base::SysUTF16ToCFStringRef(document_name));
    437   PMPrintSettingsSetJobName(print_settings, job_title.get());
    438 
    439   OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
    440                                                      print_settings,
    441                                                      page_format);
    442   if (status != noErr)
    443     return OnError();
    444 
    445   return OK;
    446 }
    447 
    448 PrintingContext::Result PrintingContextMac::NewPage() {
    449   if (abort_printing_)
    450     return CANCEL;
    451   DCHECK(in_print_job_);
    452   DCHECK(!context_);
    453 
    454   PMPrintSession print_session =
    455       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    456   PMPageFormat page_format =
    457       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
    458   OSStatus status;
    459   status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
    460   if (status != noErr)
    461     return OnError();
    462   status = PMSessionGetCGGraphicsContext(print_session, &context_);
    463   if (status != noErr)
    464     return OnError();
    465 
    466   return OK;
    467 }
    468 
    469 PrintingContext::Result PrintingContextMac::PageDone() {
    470   if (abort_printing_)
    471     return CANCEL;
    472   DCHECK(in_print_job_);
    473   DCHECK(context_);
    474 
    475   PMPrintSession print_session =
    476       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    477   OSStatus status = PMSessionEndPageNoDialog(print_session);
    478   if (status != noErr)
    479     OnError();
    480   context_ = NULL;
    481 
    482   return OK;
    483 }
    484 
    485 PrintingContext::Result PrintingContextMac::DocumentDone() {
    486   if (abort_printing_)
    487     return CANCEL;
    488   DCHECK(in_print_job_);
    489 
    490   PMPrintSession print_session =
    491       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    492   OSStatus status = PMSessionEndDocumentNoDialog(print_session);
    493   if (status != noErr)
    494     OnError();
    495 
    496   ResetSettings();
    497   return OK;
    498 }
    499 
    500 void PrintingContextMac::Cancel() {
    501   abort_printing_ = true;
    502   in_print_job_ = false;
    503   context_ = NULL;
    504 
    505   PMPrintSession print_session =
    506       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    507   PMSessionEndPageNoDialog(print_session);
    508 }
    509 
    510 void PrintingContextMac::ReleaseContext() {
    511   print_info_.reset();
    512   context_ = NULL;
    513 }
    514 
    515 gfx::NativeDrawingContext PrintingContextMac::context() const {
    516   return context_;
    517 }
    518 
    519 }  // namespace printing
    520