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, ¤t_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, ¤t_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