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.
5 #include "printing/printing_context_mac.h"
7 #import <ApplicationServices/ApplicationServices.h>
8 #import <AppKit/AppKit.h>
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"
27 // Return true if PPD name of paper is equal.
28 bool IsPaperNameEqual(const PMPaper& paper1, const PMPaper& paper2) {
29 CFStringRef name1 = NULL;
30 CFStringRef name2 = NULL;
31 return (PMPaperGetPPDPaperName(paper1, &name1) == noErr) &&
32 (PMPaperGetPPDPaperName(paper2, &name2) == noErr) &&
33 (CFStringCompare(name1, name2,
34 kCFCompareCaseInsensitive) == kCFCompareEqualTo);
40 PrintingContext* PrintingContext::Create(const std::string& app_locale) {
41 return static_cast<PrintingContext*>(new PrintingContextMac(app_locale));
44 PrintingContextMac::PrintingContextMac(const std::string& app_locale)
45 : PrintingContext(app_locale),
46 print_info_([[NSPrintInfo sharedPrintInfo] copy]),
50 PrintingContextMac::~PrintingContextMac() {
54 void PrintingContextMac::AskUserForSettings(
55 gfx::NativeView parent_view,
58 const PrintSettingsCallback& callback) {
59 // Third-party print drivers seem to be an area prone to raising exceptions.
60 // This will allow exceptions to be raised, but does not handle them. The
61 // NSPrintPanel appears to have appropriate NSException handlers.
62 base::mac::ScopedNSExceptionEnabler enabler;
64 // Exceptions can also happen when the NSPrintPanel is being
65 // deallocated, so it must be autoreleased within this scope.
66 base::mac::ScopedNSAutoreleasePool pool;
68 DCHECK([NSThread isMainThread]);
70 // We deliberately don't feed max_pages into the dialog, because setting
71 // NSPrintLastPage makes the print dialog pre-select the option to only print
74 // TODO(stuartmorgan): implement 'print selection only' (probably requires
75 // adding a new custom view to the panel on 10.5; 10.6 has
76 // NSPrintPanelShowsPrintSelection).
77 NSPrintPanel* panel = [NSPrintPanel printPanel];
78 NSPrintInfo* printInfo = print_info_.get();
80 NSPrintPanelOptions options = [panel options];
81 options |= NSPrintPanelShowsPaperSize;
82 options |= NSPrintPanelShowsOrientation;
83 options |= NSPrintPanelShowsScaling;
84 [panel setOptions:options];
86 // Set the print job title text.
88 NSString* job_title = [[parent_view window] title];
90 PMPrintSettings printSettings =
91 (PMPrintSettings)[printInfo PMPrintSettings];
92 PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
93 [printInfo updateFromPMPrintSettings];
97 // TODO(stuartmorgan): We really want a tab sheet here, not a modal window.
98 // Will require restructuring the PrintingContext API to use a callback.
99 NSInteger selection = [panel runModalWithPrintInfo:printInfo];
100 if (selection == NSOKButton) {
101 print_info_.reset([[panel printInfo] retain]);
102 settings_.set_ranges(GetPageRangesFromPrintInfo());
103 InitPrintSettingsFromPrintInfo();
106 callback.Run(CANCEL);
110 gfx::Size PrintingContextMac::GetPdfPaperSizeDeviceUnits() {
111 // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
112 // with a clean slate.
113 print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
114 UpdatePageFormatWithPaperInfo();
116 PMPageFormat page_format =
117 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
119 PMGetAdjustedPaperRect(page_format, &paper_rect);
121 // Device units are in points. Units per inch is 72.
122 gfx::Size physical_size_device_units(
123 (paper_rect.right - paper_rect.left),
124 (paper_rect.bottom - paper_rect.top));
125 DCHECK(settings_.device_units_per_inch() == kPointsPerInch);
126 return physical_size_device_units;
129 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
130 DCHECK(!in_print_job_);
132 print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
133 settings_.set_ranges(GetPageRangesFromPrintInfo());
134 InitPrintSettingsFromPrintInfo();
139 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
140 bool external_preview) {
141 DCHECK(!in_print_job_);
143 // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
144 // with a clean slate.
145 print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
147 if (external_preview) {
148 if (!SetPrintPreviewJob())
151 // Don't need this for preview.
152 if (!SetPrinter(UTF16ToUTF8(settings_.device_name())) ||
153 !SetCopiesInPrintSettings(settings_.copies()) ||
154 !SetCollateInPrintSettings(settings_.collate()) ||
155 !SetDuplexModeInPrintSettings(settings_.duplex_mode()) ||
156 !SetOutputColor(settings_.color())) {
161 if (!UpdatePageFormatWithPaperInfo() ||
162 !SetOrientationIsLandscape(settings_.landscape())) {
166 [print_info_.get() updateFromPMPrintSettings];
168 InitPrintSettingsFromPrintInfo();
172 bool PrintingContextMac::SetPrintPreviewJob() {
173 PMPrintSession print_session =
174 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
175 PMPrintSettings print_settings =
176 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
177 return PMSessionSetDestination(
178 print_session, print_settings, kPMDestinationPreview,
179 NULL, NULL) == noErr;
182 void PrintingContextMac::InitPrintSettingsFromPrintInfo() {
183 PMPrintSession print_session =
184 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
185 PMPageFormat page_format =
186 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
188 PMSessionGetCurrentPrinter(print_session, &printer);
189 settings_.set_selection_only(false);
190 PrintSettingsInitializerMac::InitPrintSettings(
191 printer, page_format, &settings_);
194 bool PrintingContextMac::SetPrinter(const std::string& device_name) {
195 DCHECK(print_info_.get());
196 PMPrintSession print_session =
197 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
199 PMPrinter current_printer;
200 if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr)
203 CFStringRef current_printer_id = PMPrinterGetID(current_printer);
204 if (!current_printer_id)
207 base::ScopedCFTypeRef<CFStringRef> new_printer_id(
208 base::SysUTF8ToCFStringRef(device_name));
209 if (!new_printer_id.get())
212 if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
217 PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
218 if (new_printer == NULL)
221 OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
222 PMRelease(new_printer);
223 return status == noErr;
226 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
227 PMPrintSession print_session =
228 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
230 PMPageFormat default_page_format =
231 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
233 PMPaper default_paper;
234 if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr)
237 double default_page_width = 0.0;
238 double default_page_height = 0.0;
239 if (PMPaperGetWidth(default_paper, &default_page_width) != noErr)
242 if (PMPaperGetHeight(default_paper, &default_page_height) != noErr)
245 PMPrinter current_printer = NULL;
246 if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr)
249 if (current_printer == nil)
252 CFArrayRef paper_list = NULL;
253 if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
256 double best_match = std::numeric_limits<double>::max();
257 PMPaper best_matching_paper = kPMNoData;
258 int num_papers = CFArrayGetCount(paper_list);
259 for (int i = 0; i < num_papers; ++i) {
260 PMPaper paper = (PMPaper)[(NSArray*)paper_list objectAtIndex: i];
261 double paper_width = 0.0;
262 double paper_height = 0.0;
263 PMPaperGetWidth(paper, &paper_width);
264 PMPaperGetHeight(paper, &paper_height);
265 double current_match = std::max(fabs(default_page_width - paper_width),
266 fabs(default_page_height - paper_height));
267 // Ignore paper sizes that are very different.
268 if (current_match > 2)
270 current_match += IsPaperNameEqual(paper, default_paper) ? 0 : 1;
271 if (current_match < best_match) {
272 best_matching_paper = paper;
273 best_match = current_match;
277 if (best_matching_paper == kPMNoData) {
278 PMPaper paper = kPMNoData;
279 // Create a custom paper for the specified default page size.
280 PMPaperMargins default_margins;
281 if (PMPaperGetMargins(default_paper, &default_margins) != noErr)
284 const PMPaperMargins margins =
285 {default_margins.top, default_margins.left, default_margins.bottom,
286 default_margins.right};
287 CFStringRef paper_id = CFSTR("Custom paper ID");
288 CFStringRef paper_name = CFSTR("Custom paper");
289 if (PMPaperCreateCustom(current_printer, paper_id, paper_name,
290 default_page_width, default_page_height, &margins, &paper) !=
294 [print_info_.get() updateFromPMPageFormat];
297 PMPageFormat chosen_page_format = NULL;
298 if (PMCreatePageFormat((PMPageFormat*) &chosen_page_format) != noErr)
301 // Create page format from that paper.
302 if (PMCreatePageFormatWithPMPaper(&chosen_page_format,
303 best_matching_paper) != noErr) {
304 PMRelease(chosen_page_format);
307 // Copy over the original format with the new page format.
308 if (PMCopyPageFormat(chosen_page_format, default_page_format) != noErr) {
309 PMRelease(chosen_page_format);
312 [print_info_.get() updateFromPMPageFormat];
313 PMRelease(chosen_page_format);
318 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
322 PMPrintSettings pmPrintSettings =
323 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
324 return PMSetCopies(pmPrintSettings, copies, false) == noErr;
327 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
328 PMPrintSettings pmPrintSettings =
329 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
330 return PMSetCollate(pmPrintSettings, collate) == noErr;
333 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
334 PMPageFormat page_format =
335 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
337 PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
339 if (PMSetOrientation(page_format, orientation, false) != noErr)
342 PMPrintSession print_session =
343 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
345 PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
347 [print_info_.get() updateFromPMPageFormat];
351 bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
352 PMDuplexMode duplexSetting;
355 duplexSetting = kPMDuplexNoTumble;
358 duplexSetting = kPMDuplexTumble;
361 duplexSetting = kPMDuplexNone;
363 default: // UNKNOWN_DUPLEX_MODE
367 PMPrintSettings pmPrintSettings =
368 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
369 return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
372 bool PrintingContextMac::SetOutputColor(int color_mode) {
373 PMPrintSettings pmPrintSettings =
374 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
375 std::string color_setting_name;
376 std::string color_value;
377 GetColorModelForMode(color_mode, &color_setting_name, &color_value);
378 base::ScopedCFTypeRef<CFStringRef> color_setting(
379 base::SysUTF8ToCFStringRef(color_setting_name));
380 base::ScopedCFTypeRef<CFStringRef> output_color(
381 base::SysUTF8ToCFStringRef(color_value));
383 return PMPrintSettingsSetValue(pmPrintSettings,
389 PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
390 PageRanges page_ranges;
391 NSDictionary* print_info_dict = [print_info_.get() dictionary];
392 if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) {
394 range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1;
395 range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1;
396 page_ranges.push_back(range);
401 PrintingContext::Result PrintingContextMac::InitWithSettings(
402 const PrintSettings& settings) {
403 DCHECK(!in_print_job_);
405 settings_ = settings;
412 PrintingContext::Result PrintingContextMac::NewDocument(
413 const base::string16& document_name) {
414 DCHECK(!in_print_job_);
416 in_print_job_ = true;
418 PMPrintSession print_session =
419 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
420 PMPrintSettings print_settings =
421 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
422 PMPageFormat page_format =
423 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
425 base::ScopedCFTypeRef<CFStringRef> job_title(
426 base::SysUTF16ToCFStringRef(document_name));
427 PMPrintSettingsSetJobName(print_settings, job_title.get());
429 OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
438 PrintingContext::Result PrintingContextMac::NewPage() {
441 DCHECK(in_print_job_);
444 PMPrintSession print_session =
445 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
446 PMPageFormat page_format =
447 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
449 status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
452 status = PMSessionGetCGGraphicsContext(print_session, &context_);
459 PrintingContext::Result PrintingContextMac::PageDone() {
462 DCHECK(in_print_job_);
465 PMPrintSession print_session =
466 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
467 OSStatus status = PMSessionEndPageNoDialog(print_session);
475 PrintingContext::Result PrintingContextMac::DocumentDone() {
478 DCHECK(in_print_job_);
480 PMPrintSession print_session =
481 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
482 OSStatus status = PMSessionEndDocumentNoDialog(print_session);
490 void PrintingContextMac::Cancel() {
491 abort_printing_ = true;
492 in_print_job_ = false;
495 PMPrintSession print_session =
496 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
497 PMSessionEndPageNoDialog(print_session);
500 void PrintingContextMac::ReleaseContext() {
505 gfx::NativeDrawingContext PrintingContextMac::context() const {
509 } // namespace printing