// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "printing/printing_context_mac.h" #import <ApplicationServices/ApplicationServices.h> #import <AppKit/AppKit.h> #import <iomanip> #import <numeric> #include "base/logging.h" #include "base/mac/scoped_cftyperef.h" #include "base/mac/scoped_nsautorelease_pool.h" #include "base/mac/scoped_nsexception_enabler.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "printing/print_settings_initializer_mac.h" #include "printing/units.h" namespace printing { namespace { // Return true if PPD name of paper is equal. bool IsPaperNameEqual(const PMPaper& paper1, const PMPaper& paper2) { CFStringRef name1 = NULL; CFStringRef name2 = NULL; return (PMPaperGetPPDPaperName(paper1, &name1) == noErr) && (PMPaperGetPPDPaperName(paper2, &name2) == noErr) && (CFStringCompare(name1, name2, kCFCompareCaseInsensitive) == kCFCompareEqualTo); } } // namespace // static PrintingContext* PrintingContext::Create(const std::string& app_locale) { return static_cast<PrintingContext*>(new PrintingContextMac(app_locale)); } PrintingContextMac::PrintingContextMac(const std::string& app_locale) : PrintingContext(app_locale), print_info_([[NSPrintInfo sharedPrintInfo] copy]), context_(NULL) { } PrintingContextMac::~PrintingContextMac() { ReleaseContext(); } void PrintingContextMac::AskUserForSettings( gfx::NativeView parent_view, int max_pages, bool has_selection, const PrintSettingsCallback& callback) { // Third-party print drivers seem to be an area prone to raising exceptions. // This will allow exceptions to be raised, but does not handle them. The // NSPrintPanel appears to have appropriate NSException handlers. base::mac::ScopedNSExceptionEnabler enabler; // Exceptions can also happen when the NSPrintPanel is being // deallocated, so it must be autoreleased within this scope. base::mac::ScopedNSAutoreleasePool pool; DCHECK([NSThread isMainThread]); // We deliberately don't feed max_pages into the dialog, because setting // NSPrintLastPage makes the print dialog pre-select the option to only print // a range. // TODO(stuartmorgan): implement 'print selection only' (probably requires // adding a new custom view to the panel on 10.5; 10.6 has // NSPrintPanelShowsPrintSelection). NSPrintPanel* panel = [NSPrintPanel printPanel]; NSPrintInfo* printInfo = print_info_.get(); NSPrintPanelOptions options = [panel options]; options |= NSPrintPanelShowsPaperSize; options |= NSPrintPanelShowsOrientation; options |= NSPrintPanelShowsScaling; [panel setOptions:options]; // Set the print job title text. if (parent_view) { NSString* job_title = [[parent_view window] title]; if (job_title) { PMPrintSettings printSettings = (PMPrintSettings)[printInfo PMPrintSettings]; PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title); [printInfo updateFromPMPrintSettings]; } } // TODO(stuartmorgan): We really want a tab sheet here, not a modal window. // Will require restructuring the PrintingContext API to use a callback. NSInteger selection = [panel runModalWithPrintInfo:printInfo]; if (selection == NSOKButton) { print_info_.reset([[panel printInfo] retain]); settings_.set_ranges(GetPageRangesFromPrintInfo()); InitPrintSettingsFromPrintInfo(); callback.Run(OK); } else { callback.Run(CANCEL); } } gfx::Size PrintingContextMac::GetPdfPaperSizeDeviceUnits() { // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start // with a clean slate. print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]); UpdatePageFormatWithPaperInfo(); PMPageFormat page_format = static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); PMRect paper_rect; PMGetAdjustedPaperRect(page_format, &paper_rect); // Device units are in points. Units per inch is 72. gfx::Size physical_size_device_units( (paper_rect.right - paper_rect.left), (paper_rect.bottom - paper_rect.top)); DCHECK(settings_.device_units_per_inch() == kPointsPerInch); return physical_size_device_units; } PrintingContext::Result PrintingContextMac::UseDefaultSettings() { DCHECK(!in_print_job_); print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]); settings_.set_ranges(GetPageRangesFromPrintInfo()); InitPrintSettingsFromPrintInfo(); return OK; } PrintingContext::Result PrintingContextMac::UpdatePrinterSettings( bool external_preview) { DCHECK(!in_print_job_); // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start // with a clean slate. print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]); if (external_preview) { if (!SetPrintPreviewJob()) return OnError(); } else { // Don't need this for preview. if (!SetPrinter(UTF16ToUTF8(settings_.device_name())) || !SetCopiesInPrintSettings(settings_.copies()) || !SetCollateInPrintSettings(settings_.collate()) || !SetDuplexModeInPrintSettings(settings_.duplex_mode()) || !SetOutputColor(settings_.color())) { return OnError(); } } if (!UpdatePageFormatWithPaperInfo() || !SetOrientationIsLandscape(settings_.landscape())) { return OnError(); } [print_info_.get() updateFromPMPrintSettings]; InitPrintSettingsFromPrintInfo(); return OK; } bool PrintingContextMac::SetPrintPreviewJob() { PMPrintSession print_session = static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); PMPrintSettings print_settings = static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); return PMSessionSetDestination( print_session, print_settings, kPMDestinationPreview, NULL, NULL) == noErr; } void PrintingContextMac::InitPrintSettingsFromPrintInfo() { PMPrintSession print_session = static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); PMPageFormat page_format = static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); PMPrinter printer; PMSessionGetCurrentPrinter(print_session, &printer); PrintSettingsInitializerMac::InitPrintSettings( printer, page_format, &settings_); } bool PrintingContextMac::SetPrinter(const std::string& device_name) { DCHECK(print_info_.get()); PMPrintSession print_session = static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); PMPrinter current_printer; if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr) return false; CFStringRef current_printer_id = PMPrinterGetID(current_printer); if (!current_printer_id) return false; base::ScopedCFTypeRef<CFStringRef> new_printer_id( base::SysUTF8ToCFStringRef(device_name)); if (!new_printer_id.get()) return false; if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) == kCFCompareEqualTo) { return true; } PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get()); if (new_printer == NULL) return false; OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer); PMRelease(new_printer); return status == noErr; } bool PrintingContextMac::UpdatePageFormatWithPaperInfo() { PMPrintSession print_session = static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); PMPageFormat default_page_format = static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); PMPaper default_paper; if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr) return false; double default_page_width = 0.0; double default_page_height = 0.0; if (PMPaperGetWidth(default_paper, &default_page_width) != noErr) return false; if (PMPaperGetHeight(default_paper, &default_page_height) != noErr) return false; PMPrinter current_printer = NULL; if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr) return false; if (current_printer == nil) return false; CFArrayRef paper_list = NULL; if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr) return false; double best_match = std::numeric_limits<double>::max(); PMPaper best_matching_paper = kPMNoData; int num_papers = CFArrayGetCount(paper_list); for (int i = 0; i < num_papers; ++i) { PMPaper paper = (PMPaper)[(NSArray*)paper_list objectAtIndex: i]; double paper_width = 0.0; double paper_height = 0.0; PMPaperGetWidth(paper, &paper_width); PMPaperGetHeight(paper, &paper_height); double current_match = std::max(fabs(default_page_width - paper_width), fabs(default_page_height - paper_height)); // Ignore paper sizes that are very different. if (current_match > 2) continue; current_match += IsPaperNameEqual(paper, default_paper) ? 0 : 1; if (current_match < best_match) { best_matching_paper = paper; best_match = current_match; } } if (best_matching_paper == kPMNoData) { PMPaper paper = kPMNoData; // Create a custom paper for the specified default page size. PMPaperMargins default_margins; if (PMPaperGetMargins(default_paper, &default_margins) != noErr) return false; const PMPaperMargins margins = {default_margins.top, default_margins.left, default_margins.bottom, default_margins.right}; CFStringRef paper_id = CFSTR("Custom paper ID"); CFStringRef paper_name = CFSTR("Custom paper"); if (PMPaperCreateCustom(current_printer, paper_id, paper_name, default_page_width, default_page_height, &margins, &paper) != noErr) { return false; } [print_info_.get() updateFromPMPageFormat]; PMRelease(paper); } else { PMPageFormat chosen_page_format = NULL; if (PMCreatePageFormat((PMPageFormat*) &chosen_page_format) != noErr) return false; // Create page format from that paper. if (PMCreatePageFormatWithPMPaper(&chosen_page_format, best_matching_paper) != noErr) { PMRelease(chosen_page_format); return false; } // Copy over the original format with the new page format. if (PMCopyPageFormat(chosen_page_format, default_page_format) != noErr) { PMRelease(chosen_page_format); return false; } [print_info_.get() updateFromPMPageFormat]; PMRelease(chosen_page_format); } return true; } bool PrintingContextMac::SetCopiesInPrintSettings(int copies) { if (copies < 1) return false; PMPrintSettings pmPrintSettings = static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); return PMSetCopies(pmPrintSettings, copies, false) == noErr; } bool PrintingContextMac::SetCollateInPrintSettings(bool collate) { PMPrintSettings pmPrintSettings = static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); return PMSetCollate(pmPrintSettings, collate) == noErr; } bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) { PMPageFormat page_format = static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait; if (PMSetOrientation(page_format, orientation, false) != noErr) return false; PMPrintSession print_session = static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean); [print_info_.get() updateFromPMPageFormat]; return true; } bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) { PMDuplexMode duplexSetting; switch (mode) { case LONG_EDGE: duplexSetting = kPMDuplexNoTumble; break; case SHORT_EDGE: duplexSetting = kPMDuplexTumble; break; case SIMPLEX: duplexSetting = kPMDuplexNone; break; default: // UNKNOWN_DUPLEX_MODE return true; } PMPrintSettings pmPrintSettings = static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr; } bool PrintingContextMac::SetOutputColor(int color_mode) { PMPrintSettings pmPrintSettings = static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); std::string color_setting_name; std::string color_value; GetColorModelForMode(color_mode, &color_setting_name, &color_value); base::ScopedCFTypeRef<CFStringRef> color_setting( base::SysUTF8ToCFStringRef(color_setting_name)); base::ScopedCFTypeRef<CFStringRef> output_color( base::SysUTF8ToCFStringRef(color_value)); return PMPrintSettingsSetValue(pmPrintSettings, color_setting.get(), output_color.get(), false) == noErr; } PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() { PageRanges page_ranges; NSDictionary* print_info_dict = [print_info_.get() dictionary]; if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) { PageRange range; range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1; range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1; page_ranges.push_back(range); } return page_ranges; } PrintingContext::Result PrintingContextMac::InitWithSettings( const PrintSettings& settings) { DCHECK(!in_print_job_); settings_ = settings; NOTIMPLEMENTED(); return FAILED; } PrintingContext::Result PrintingContextMac::NewDocument( const base::string16& document_name) { DCHECK(!in_print_job_); in_print_job_ = true; PMPrintSession print_session = static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); PMPrintSettings print_settings = static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); PMPageFormat page_format = static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); base::ScopedCFTypeRef<CFStringRef> job_title( base::SysUTF16ToCFStringRef(document_name)); PMPrintSettingsSetJobName(print_settings, job_title.get()); OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session, print_settings, page_format); if (status != noErr) return OnError(); return OK; } PrintingContext::Result PrintingContextMac::NewPage() { if (abort_printing_) return CANCEL; DCHECK(in_print_job_); DCHECK(!context_); PMPrintSession print_session = static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); PMPageFormat page_format = static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); OSStatus status; status = PMSessionBeginPageNoDialog(print_session, page_format, NULL); if (status != noErr) return OnError(); status = PMSessionGetCGGraphicsContext(print_session, &context_); if (status != noErr) return OnError(); return OK; } PrintingContext::Result PrintingContextMac::PageDone() { if (abort_printing_) return CANCEL; DCHECK(in_print_job_); DCHECK(context_); PMPrintSession print_session = static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); OSStatus status = PMSessionEndPageNoDialog(print_session); if (status != noErr) OnError(); context_ = NULL; return OK; } PrintingContext::Result PrintingContextMac::DocumentDone() { if (abort_printing_) return CANCEL; DCHECK(in_print_job_); PMPrintSession print_session = static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); OSStatus status = PMSessionEndDocumentNoDialog(print_session); if (status != noErr) OnError(); ResetSettings(); return OK; } void PrintingContextMac::Cancel() { abort_printing_ = true; in_print_job_ = false; context_ = NULL; PMPrintSession print_session = static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); PMSessionEndPageNoDialog(print_session); } void PrintingContextMac::ReleaseContext() { print_info_.reset(); context_ = NULL; } gfx::NativeDrawingContext PrintingContextMac::context() const { return context_; } } // namespace printing