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 <AppKit/AppKit.h>
12 #include "base/logging.h"
13 #include "base/mac/scoped_cftyperef.h"
14 #include "base/mac/scoped_nsautorelease_pool.h"
15 #include "base/mac/scoped_nsexception_enabler.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/values.h"
19 #include "printing/print_settings_initializer_mac.h"
20 #include "printing/units.h"
26 const int kMaxPaperSizeDiffereceInPoints = 2;
28 // Return true if PPD name of paper is equal.
29 bool IsPaperNameEqual(CFStringRef name1, const PMPaper& paper2) {
30 CFStringRef name2 = NULL;
31 return (name1 && PMPaperGetPPDPaperName(paper2, &name2) == noErr) &&
32 (CFStringCompare(name1, name2, kCFCompareCaseInsensitive) ==
36 PMPaper MatchPaper(CFArrayRef paper_list,
40 double best_match = std::numeric_limits<double>::max();
41 PMPaper best_matching_paper = NULL;
42 int num_papers = CFArrayGetCount(paper_list);
43 for (int i = 0; i < num_papers; ++i) {
44 PMPaper paper = (PMPaper)[(NSArray*)paper_list objectAtIndex : i];
45 double paper_width = 0.0;
46 double paper_height = 0.0;
47 PMPaperGetWidth(paper, &paper_width);
48 PMPaperGetHeight(paper, &paper_height);
50 std::max(fabs(width - paper_width), fabs(height - paper_height));
52 // Ignore papers with size too different from expected.
53 if (difference > kMaxPaperSizeDiffereceInPoints)
56 if (name && IsPaperNameEqual(name, paper))
59 if (difference < best_match) {
60 best_matching_paper = paper;
61 best_match = difference;
64 return best_matching_paper;
70 scoped_ptr<PrintingContext> PrintingContext::Create(Delegate* delegate) {
71 return make_scoped_ptr<PrintingContext>(new PrintingContextMac(delegate));
74 PrintingContextMac::PrintingContextMac(Delegate* delegate)
75 : PrintingContext(delegate),
76 print_info_([[NSPrintInfo sharedPrintInfo] copy]),
80 PrintingContextMac::~PrintingContextMac() {
84 void PrintingContextMac::AskUserForSettings(
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;
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;
98 DCHECK([NSThread isMainThread]);
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
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();
110 NSPrintPanelOptions options = [panel options];
111 options |= NSPrintPanelShowsPaperSize;
112 options |= NSPrintPanelShowsOrientation;
113 options |= NSPrintPanelShowsScaling;
114 [panel setOptions:options];
116 // Set the print job title text.
117 gfx::NativeView parent_view = delegate_->GetParentView();
119 NSString* job_title = [[parent_view window] title];
121 PMPrintSettings printSettings =
122 (PMPrintSettings)[printInfo PMPrintSettings];
123 PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
124 [printInfo updateFromPMPrintSettings];
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();
137 callback.Run(CANCEL);
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();
147 PMPageFormat page_format =
148 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
150 PMGetAdjustedPaperRect(page_format, &paper_rect);
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;
160 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
161 DCHECK(!in_print_job_);
163 print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
164 settings_.set_ranges(GetPageRangesFromPrintInfo());
165 InitPrintSettingsFromPrintInfo();
170 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
171 bool external_preview,
172 bool show_system_dialog,
174 DCHECK(!show_system_dialog);
175 DCHECK(!in_print_job_);
177 // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
178 // with a clean slate.
179 print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
181 if (external_preview) {
182 if (!SetPrintPreviewJob())
185 // Don't need this for preview.
186 if (!SetPrinter(base::UTF16ToUTF8(settings_.device_name())) ||
187 !SetCopiesInPrintSettings(settings_.copies()) ||
188 !SetCollateInPrintSettings(settings_.collate()) ||
189 !SetDuplexModeInPrintSettings(settings_.duplex_mode()) ||
190 !SetOutputColor(settings_.color())) {
195 if (!UpdatePageFormatWithPaperInfo() ||
196 !SetOrientationIsLandscape(settings_.landscape())) {
200 [print_info_.get() updateFromPMPrintSettings];
202 InitPrintSettingsFromPrintInfo();
206 bool PrintingContextMac::SetPrintPreviewJob() {
207 PMPrintSession print_session =
208 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
209 PMPrintSettings print_settings =
210 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
211 return PMSessionSetDestination(
212 print_session, print_settings, kPMDestinationPreview,
213 NULL, NULL) == noErr;
216 void PrintingContextMac::InitPrintSettingsFromPrintInfo() {
217 PMPrintSession print_session =
218 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
219 PMPageFormat page_format =
220 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
222 PMSessionGetCurrentPrinter(print_session, &printer);
223 PrintSettingsInitializerMac::InitPrintSettings(
224 printer, page_format, &settings_);
227 bool PrintingContextMac::SetPrinter(const std::string& device_name) {
228 DCHECK(print_info_.get());
229 PMPrintSession print_session =
230 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
232 PMPrinter current_printer;
233 if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr)
236 CFStringRef current_printer_id = PMPrinterGetID(current_printer);
237 if (!current_printer_id)
240 base::ScopedCFTypeRef<CFStringRef> new_printer_id(
241 base::SysUTF8ToCFStringRef(device_name));
242 if (!new_printer_id.get())
245 if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
250 PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
254 OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
255 PMRelease(new_printer);
256 return status == noErr;
259 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
260 PMPrintSession print_session =
261 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
263 PMPageFormat default_page_format =
264 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
266 PMPrinter current_printer = NULL;
267 if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr)
270 double page_width = 0.0;
271 double page_height = 0.0;
272 base::ScopedCFTypeRef<CFStringRef> paper_name;
273 PMPaperMargins margins = {0};
275 const PrintSettings::RequestedMedia& media = settings_.requested_media();
276 if (media.IsDefault()) {
277 PMPaper default_paper;
278 if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr ||
279 PMPaperGetWidth(default_paper, &page_width) != noErr ||
280 PMPaperGetHeight(default_paper, &page_height) != noErr) {
284 // Ignore result, because we can continue without following.
285 CFStringRef tmp_paper_name = NULL;
286 PMPaperGetPPDPaperName(default_paper, &tmp_paper_name);
287 PMPaperGetMargins(default_paper, &margins);
288 paper_name.reset(tmp_paper_name, base::scoped_policy::RETAIN);
290 const double kMutiplier = kPointsPerInch / (10.0f * kHundrethsMMPerInch);
291 page_width = media.size_microns.width() * kMutiplier;
292 page_height = media.size_microns.height() * kMutiplier;
293 paper_name.reset(base::SysUTF8ToCFStringRef(media.vendor_id));
296 CFArrayRef paper_list = NULL;
297 if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
300 PMPaper best_matching_paper =
301 MatchPaper(paper_list, paper_name, page_width, page_height);
303 if (best_matching_paper)
304 return UpdatePageFormatWithPaper(best_matching_paper, default_page_format);
306 // Do nothing if unmatched paper was default system paper.
307 if (media.IsDefault())
310 PMPaper paper = NULL;
311 if (PMPaperCreateCustom(current_printer,
312 CFSTR("Custom paper ID"),
313 CFSTR("Custom paper"),
320 bool result = UpdatePageFormatWithPaper(paper, default_page_format);
325 bool PrintingContextMac::UpdatePageFormatWithPaper(PMPaper paper,
326 PMPageFormat page_format) {
327 PMPageFormat new_format = NULL;
328 if (PMCreatePageFormatWithPMPaper(&new_format, paper) != noErr)
330 // Copy over the original format with the new page format.
331 bool result = (PMCopyPageFormat(new_format, page_format) == noErr);
332 [print_info_.get() updateFromPMPageFormat];
333 PMRelease(new_format);
337 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
341 PMPrintSettings pmPrintSettings =
342 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
343 return PMSetCopies(pmPrintSettings, copies, false) == noErr;
346 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
347 PMPrintSettings pmPrintSettings =
348 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
349 return PMSetCollate(pmPrintSettings, collate) == noErr;
352 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
353 PMPageFormat page_format =
354 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
356 PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
358 if (PMSetOrientation(page_format, orientation, false) != noErr)
361 PMPrintSession print_session =
362 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
364 PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
366 [print_info_.get() updateFromPMPageFormat];
370 bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
371 PMDuplexMode duplexSetting;
374 duplexSetting = kPMDuplexNoTumble;
377 duplexSetting = kPMDuplexTumble;
380 duplexSetting = kPMDuplexNone;
382 default: // UNKNOWN_DUPLEX_MODE
386 PMPrintSettings pmPrintSettings =
387 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
388 return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
391 bool PrintingContextMac::SetOutputColor(int color_mode) {
392 PMPrintSettings pmPrintSettings =
393 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
394 std::string color_setting_name;
395 std::string color_value;
396 GetColorModelForMode(color_mode, &color_setting_name, &color_value);
397 base::ScopedCFTypeRef<CFStringRef> color_setting(
398 base::SysUTF8ToCFStringRef(color_setting_name));
399 base::ScopedCFTypeRef<CFStringRef> output_color(
400 base::SysUTF8ToCFStringRef(color_value));
402 return PMPrintSettingsSetValue(pmPrintSettings,
408 PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
409 PageRanges page_ranges;
410 NSDictionary* print_info_dict = [print_info_.get() dictionary];
411 if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) {
413 range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1;
414 range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1;
415 page_ranges.push_back(range);
420 PrintingContext::Result PrintingContextMac::InitWithSettings(
421 const PrintSettings& settings) {
422 DCHECK(!in_print_job_);
424 settings_ = settings;
431 PrintingContext::Result PrintingContextMac::NewDocument(
432 const base::string16& document_name) {
433 DCHECK(!in_print_job_);
435 in_print_job_ = true;
437 PMPrintSession print_session =
438 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
439 PMPrintSettings print_settings =
440 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
441 PMPageFormat page_format =
442 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
444 base::ScopedCFTypeRef<CFStringRef> job_title(
445 base::SysUTF16ToCFStringRef(document_name));
446 PMPrintSettingsSetJobName(print_settings, job_title.get());
448 OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
457 PrintingContext::Result PrintingContextMac::NewPage() {
460 DCHECK(in_print_job_);
463 PMPrintSession print_session =
464 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
465 PMPageFormat page_format =
466 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
468 status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
471 status = PMSessionGetCGGraphicsContext(print_session, &context_);
478 PrintingContext::Result PrintingContextMac::PageDone() {
481 DCHECK(in_print_job_);
484 PMPrintSession print_session =
485 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
486 OSStatus status = PMSessionEndPageNoDialog(print_session);
494 PrintingContext::Result PrintingContextMac::DocumentDone() {
497 DCHECK(in_print_job_);
499 PMPrintSession print_session =
500 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
501 OSStatus status = PMSessionEndDocumentNoDialog(print_session);
509 void PrintingContextMac::Cancel() {
510 abort_printing_ = true;
511 in_print_job_ = false;
514 PMPrintSession print_session =
515 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
516 PMSessionEndPageNoDialog(print_session);
519 void PrintingContextMac::ReleaseContext() {
524 gfx::NativeDrawingContext PrintingContextMac::context() const {
528 } // namespace printing