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/strings/utf_string_conversions.h"
     19 #include "base/values.h"
     20 #include "printing/print_settings_initializer_mac.h"
     21 #include "printing/units.h"
     22 
     23 namespace printing {
     24 
     25 namespace {
     26 
     27 const int kMaxPaperSizeDiffereceInPoints = 2;
     28 
     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);
     35 }
     36 
     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));
     52 
     53     // Ignore papers with size too different from expected.
     54     if (difference > kMaxPaperSizeDiffereceInPoints)
     55       continue;
     56 
     57     if (name && IsPaperNameEqual(name, paper))
     58       return paper;
     59 
     60     if (difference < best_match) {
     61       best_matching_paper = paper;
     62       best_match = difference;
     63     }
     64   }
     65   return best_matching_paper;
     66 }
     67 
     68 }  // namespace
     69 
     70 // static
     71 scoped_ptr<PrintingContext> PrintingContext::Create(Delegate* delegate) {
     72   return make_scoped_ptr<PrintingContext>(new PrintingContextMac(delegate));
     73 }
     74 
     75 PrintingContextMac::PrintingContextMac(Delegate* delegate)
     76     : PrintingContext(delegate),
     77       print_info_([[NSPrintInfo sharedPrintInfo] copy]),
     78       context_(NULL) {
     79 }
     80 
     81 PrintingContextMac::~PrintingContextMac() {
     82   ReleaseContext();
     83 }
     84 
     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;
     93 
     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;
     97 
     98   DCHECK([NSThread isMainThread]);
     99 
    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.
    103 
    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();
    109 
    110   NSPrintPanelOptions options = [panel options];
    111   options |= NSPrintPanelShowsPaperSize;
    112   options |= NSPrintPanelShowsOrientation;
    113   options |= NSPrintPanelShowsScaling;
    114   [panel setOptions:options];
    115 
    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   }
    127 
    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   }
    139 }
    140 
    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();
    146 
    147   PMPageFormat page_format =
    148       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
    149   PMRect paper_rect;
    150   PMGetAdjustedPaperRect(page_format, &paper_rect);
    151 
    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;
    158 }
    159 
    160 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
    161   DCHECK(!in_print_job_);
    162 
    163   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
    164   settings_.set_ranges(GetPageRangesFromPrintInfo());
    165   InitPrintSettingsFromPrintInfo();
    166 
    167   return OK;
    168 }
    169 
    170 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
    171     bool external_preview,
    172     bool show_system_dialog) {
    173   DCHECK(!show_system_dialog);
    174   DCHECK(!in_print_job_);
    175 
    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]);
    179 
    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   }
    193 
    194   if (!UpdatePageFormatWithPaperInfo() ||
    195       !SetOrientationIsLandscape(settings_.landscape())) {
    196     return OnError();
    197   }
    198 
    199   [print_info_.get() updateFromPMPrintSettings];
    200 
    201   InitPrintSettingsFromPrintInfo();
    202   return OK;
    203 }
    204 
    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;
    213 }
    214 
    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_);
    224 }
    225 
    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]);
    230 
    231   PMPrinter current_printer;
    232   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
    233     return false;
    234 
    235   CFStringRef current_printer_id = PMPrinterGetID(current_printer);
    236   if (!current_printer_id)
    237     return false;
    238 
    239   base::ScopedCFTypeRef<CFStringRef> new_printer_id(
    240       base::SysUTF8ToCFStringRef(device_name));
    241   if (!new_printer_id.get())
    242     return false;
    243 
    244   if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
    245           kCFCompareEqualTo) {
    246     return true;
    247   }
    248 
    249   PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
    250   if (!new_printer)
    251     return false;
    252 
    253   OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
    254   PMRelease(new_printer);
    255   return status == noErr;
    256 }
    257 
    258 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
    259   PMPrintSession print_session =
    260       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    261 
    262   PMPageFormat default_page_format =
    263       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
    264 
    265   PMPrinter current_printer = NULL;
    266   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
    267     return false;
    268 
    269   double page_width = 0.0;
    270   double page_height = 0.0;
    271   base::ScopedCFTypeRef<CFStringRef> paper_name;
    272   PMPaperMargins margins = {0};
    273 
    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     }
    282 
    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   }
    294 
    295   CFArrayRef paper_list = NULL;
    296   if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
    297     return false;
    298 
    299   PMPaper best_matching_paper =
    300       MatchPaper(paper_list, paper_name, page_width, page_height);
    301 
    302   if (best_matching_paper)
    303     return UpdatePageFormatWithPaper(best_matching_paper, default_page_format);
    304 
    305   // Do nothing if unmatched paper was default system paper.
    306   if (media.IsDefault())
    307     return true;
    308 
    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;
    322 }
    323 
    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;
    334 }
    335 
    336 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
    337   if (copies < 1)
    338     return false;
    339 
    340   PMPrintSettings pmPrintSettings =
    341       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
    342   return PMSetCopies(pmPrintSettings, copies, false) == noErr;
    343 }
    344 
    345 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
    346   PMPrintSettings pmPrintSettings =
    347       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
    348   return PMSetCollate(pmPrintSettings, collate) == noErr;
    349 }
    350 
    351 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
    352   PMPageFormat page_format =
    353       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
    354 
    355   PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
    356 
    357   if (PMSetOrientation(page_format, orientation, false) != noErr)
    358     return false;
    359 
    360   PMPrintSession print_session =
    361       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    362 
    363   PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
    364 
    365   [print_info_.get() updateFromPMPageFormat];
    366   return true;
    367 }
    368 
    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   }
    384 
    385   PMPrintSettings pmPrintSettings =
    386       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
    387   return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
    388 }
    389 
    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));
    400 
    401   return PMPrintSettingsSetValue(pmPrintSettings,
    402                                  color_setting.get(),
    403                                  output_color.get(),
    404                                  false) == noErr;
    405 }
    406 
    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;
    417 }
    418 
    419 PrintingContext::Result PrintingContextMac::InitWithSettings(
    420     const PrintSettings& settings) {
    421   DCHECK(!in_print_job_);
    422 
    423   settings_ = settings;
    424 
    425   NOTIMPLEMENTED();
    426 
    427   return FAILED;
    428 }
    429 
    430 PrintingContext::Result PrintingContextMac::NewDocument(
    431     const base::string16& document_name) {
    432   DCHECK(!in_print_job_);
    433 
    434   in_print_job_ = true;
    435 
    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]);
    442 
    443   base::ScopedCFTypeRef<CFStringRef> job_title(
    444       base::SysUTF16ToCFStringRef(document_name));
    445   PMPrintSettingsSetJobName(print_settings, job_title.get());
    446 
    447   OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
    448                                                      print_settings,
    449                                                      page_format);
    450   if (status != noErr)
    451     return OnError();
    452 
    453   return OK;
    454 }
    455 
    456 PrintingContext::Result PrintingContextMac::NewPage() {
    457   if (abort_printing_)
    458     return CANCEL;
    459   DCHECK(in_print_job_);
    460   DCHECK(!context_);
    461 
    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();
    473 
    474   return OK;
    475 }
    476 
    477 PrintingContext::Result PrintingContextMac::PageDone() {
    478   if (abort_printing_)
    479     return CANCEL;
    480   DCHECK(in_print_job_);
    481   DCHECK(context_);
    482 
    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;
    489 
    490   return OK;
    491 }
    492 
    493 PrintingContext::Result PrintingContextMac::DocumentDone() {
    494   if (abort_printing_)
    495     return CANCEL;
    496   DCHECK(in_print_job_);
    497 
    498   PMPrintSession print_session =
    499       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    500   OSStatus status = PMSessionEndDocumentNoDialog(print_session);
    501   if (status != noErr)
    502     OnError();
    503 
    504   ResetSettings();
    505   return OK;
    506 }
    507 
    508 void PrintingContextMac::Cancel() {
    509   abort_printing_ = true;
    510   in_print_job_ = false;
    511   context_ = NULL;
    512 
    513   PMPrintSession print_session =
    514       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
    515   PMSessionEndPageNoDialog(print_session);
    516 }
    517 
    518 void PrintingContextMac::ReleaseContext() {
    519   print_info_.reset();
    520   context_ = NULL;
    521 }
    522 
    523 gfx::NativeDrawingContext PrintingContextMac::context() const {
    524   return context_;
    525 }
    526 
    527 }  // namespace printing
    528