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 23namespace printing { 24 25namespace { 26 27const int kMaxPaperSizeDiffereceInPoints = 2; 28 29// Return true if PPD name of paper is equal. 30bool 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 37PMPaper 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 71scoped_ptr<PrintingContext> PrintingContext::Create(Delegate* delegate) { 72 return make_scoped_ptr<PrintingContext>(new PrintingContextMac(delegate)); 73} 74 75PrintingContextMac::PrintingContextMac(Delegate* delegate) 76 : PrintingContext(delegate), 77 print_info_([[NSPrintInfo sharedPrintInfo] copy]), 78 context_(NULL) { 79} 80 81PrintingContextMac::~PrintingContextMac() { 82 ReleaseContext(); 83} 84 85void 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 141gfx::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 160PrintingContext::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 170PrintingContext::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 205bool 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 215void 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 226bool 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, ¤t_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 258bool 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, ¤t_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 324bool 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 336bool 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 345bool PrintingContextMac::SetCollateInPrintSettings(bool collate) { 346 PMPrintSettings pmPrintSettings = 347 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); 348 return PMSetCollate(pmPrintSettings, collate) == noErr; 349} 350 351bool 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 369bool 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 390bool 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 407PageRanges 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 419PrintingContext::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 430PrintingContext::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 456PrintingContext::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 477PrintingContext::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 493PrintingContext::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 508void 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 518void PrintingContextMac::ReleaseContext() { 519 print_info_.reset(); 520 context_ = NULL; 521} 522 523gfx::NativeDrawingContext PrintingContextMac::context() const { 524 return context_; 525} 526 527} // namespace printing 528