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>
8 #import <QuartzCore/QuartzCore.h>
13 #include "base/logging.h"
14 #include "base/mac/scoped_cftyperef.h"
15 #include "base/mac/scoped_nsautorelease_pool.h"
16 #include "base/memory/ptr_util.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 const int kMaxPaperSizeDiffereceInPoints = 2;
29 // Return true if PPD name of paper is equal.
30 bool IsPaperNameEqual(CFStringRef name1, const PMPaper& paper2) {
31 CFStringRef name2 = NULL;
32 return (name1 && PMPaperGetPPDPaperName(paper2, &name2) == noErr) &&
33 (CFStringCompare(name1, name2, kCFCompareCaseInsensitive) ==
37 PMPaper MatchPaper(CFArrayRef paper_list,
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);
51 std::max(fabs(width - paper_width), fabs(height - paper_height));
53 // Ignore papers with size too different from expected.
54 if (difference > kMaxPaperSizeDiffereceInPoints)
57 if (name && IsPaperNameEqual(name, paper))
60 if (difference < best_match) {
61 best_matching_paper = paper;
62 best_match = difference;
65 return best_matching_paper;
71 std::unique_ptr<PrintingContext> PrintingContext::Create(Delegate* delegate) {
72 return base::WrapUnique(new PrintingContextMac(delegate));
75 PrintingContextMac::PrintingContextMac(Delegate* delegate)
76 : PrintingContext(delegate),
77 print_info_([[NSPrintInfo sharedPrintInfo] copy]),
81 PrintingContextMac::~PrintingContextMac() {
85 void PrintingContextMac::AskUserForSettings(int max_pages,
88 PrintSettingsCallback callback) {
89 // Exceptions can also happen when the NSPrintPanel is being
90 // deallocated, so it must be autoreleased within this scope.
91 base::mac::ScopedNSAutoreleasePool pool;
93 DCHECK([NSThread isMainThread]);
95 // We deliberately don't feed max_pages into the dialog, because setting
96 // NSPrintLastPage makes the print dialog pre-select the option to only print
99 // TODO(stuartmorgan): implement 'print selection only' (probably requires
100 // adding a new custom view to the panel on 10.5; 10.6 has
101 // NSPrintPanelShowsPrintSelection).
102 NSPrintPanel* panel = [NSPrintPanel printPanel];
103 NSPrintInfo* printInfo = print_info_.get();
105 NSPrintPanelOptions options = [panel options];
106 options |= NSPrintPanelShowsPaperSize;
107 options |= NSPrintPanelShowsOrientation;
108 options |= NSPrintPanelShowsScaling;
109 [panel setOptions:options];
111 // Set the print job title text.
112 gfx::NativeView parent_view = delegate_->GetParentView();
114 NSString* job_title = [[parent_view.GetNativeNSView() window] title];
116 PMPrintSettings printSettings =
117 (PMPrintSettings)[printInfo PMPrintSettings];
118 PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
119 [printInfo updateFromPMPrintSettings];
123 // TODO(stuartmorgan): We really want a tab sheet here, not a modal window.
124 // Will require restructuring the PrintingContext API to use a callback.
126 // This function may be called in the middle of a CATransaction, where
127 // running a modal panel is forbidden. That situation isn't ideal, but from
128 // this code's POV the right answer is to defer running the panel until after
129 // the current transaction. See https://crbug.com/849538.
130 __block auto block_callback = std::move(callback);
131 [CATransaction setCompletionBlock:^{
132 NSInteger selection = [panel runModalWithPrintInfo:printInfo];
133 if (selection == NSOKButton) {
134 print_info_.reset([[panel printInfo] retain]);
135 settings_.set_ranges(GetPageRangesFromPrintInfo());
136 InitPrintSettingsFromPrintInfo();
137 std::move(block_callback).Run(OK);
139 std::move(block_callback).Run(CANCEL);
144 gfx::Size PrintingContextMac::GetPdfPaperSizeDeviceUnits() {
145 // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
146 // with a clean slate.
147 print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
148 UpdatePageFormatWithPaperInfo();
150 PMPageFormat page_format =
151 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
153 PMGetAdjustedPaperRect(page_format, &paper_rect);
155 // Device units are in points. Units per inch is 72.
156 gfx::Size physical_size_device_units(
157 (paper_rect.right - paper_rect.left),
158 (paper_rect.bottom - paper_rect.top));
159 DCHECK(settings_.device_units_per_inch() == kPointsPerInch);
160 return physical_size_device_units;
163 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
164 DCHECK(!in_print_job_);
166 print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
167 settings_.set_ranges(GetPageRangesFromPrintInfo());
168 InitPrintSettingsFromPrintInfo();
173 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
174 bool external_preview,
175 bool show_system_dialog,
177 DCHECK(!show_system_dialog);
178 DCHECK(!in_print_job_);
180 // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
181 // with a clean slate.
182 print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
184 if (external_preview) {
185 if (!SetPrintPreviewJob())
188 // Don't need this for preview.
189 if (!SetPrinter(base::UTF16ToUTF8(settings_.device_name())) ||
190 !SetCopiesInPrintSettings(settings_.copies()) ||
191 !SetCollateInPrintSettings(settings_.collate()) ||
192 !SetDuplexModeInPrintSettings(settings_.duplex_mode()) ||
193 !SetOutputColor(settings_.color())) {
198 if (!UpdatePageFormatWithPaperInfo() ||
199 !SetOrientationIsLandscape(settings_.landscape())) {
203 [print_info_.get() updateFromPMPrintSettings];
205 InitPrintSettingsFromPrintInfo();
209 bool PrintingContextMac::SetPrintPreviewJob() {
210 PMPrintSession print_session =
211 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
212 PMPrintSettings print_settings =
213 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
214 return PMSessionSetDestination(
215 print_session, print_settings, kPMDestinationPreview,
216 NULL, NULL) == noErr;
219 void PrintingContextMac::InitPrintSettingsFromPrintInfo() {
220 PMPrintSession print_session =
221 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
222 PMPageFormat page_format =
223 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
225 PMSessionGetCurrentPrinter(print_session, &printer);
226 PrintSettingsInitializerMac::InitPrintSettings(
227 printer, page_format, &settings_);
230 bool PrintingContextMac::SetPrinter(const std::string& device_name) {
231 DCHECK(print_info_.get());
232 PMPrintSession print_session =
233 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
235 PMPrinter current_printer;
236 if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr)
239 CFStringRef current_printer_id = PMPrinterGetID(current_printer);
240 if (!current_printer_id)
243 base::ScopedCFTypeRef<CFStringRef> new_printer_id(
244 base::SysUTF8ToCFStringRef(device_name));
245 if (!new_printer_id.get())
248 if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
253 PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
257 OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
258 PMRelease(new_printer);
259 return status == noErr;
262 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
263 PMPrintSession print_session =
264 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
266 PMPageFormat default_page_format =
267 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
269 PMPrinter current_printer = NULL;
270 if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr)
273 double page_width = 0.0;
274 double page_height = 0.0;
275 base::ScopedCFTypeRef<CFStringRef> paper_name;
276 PMPaperMargins margins = {0};
278 const PrintSettings::RequestedMedia& media = settings_.requested_media();
279 if (media.IsDefault()) {
280 PMPaper default_paper;
281 if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr ||
282 PMPaperGetWidth(default_paper, &page_width) != noErr ||
283 PMPaperGetHeight(default_paper, &page_height) != noErr) {
287 // Ignore result, because we can continue without following.
288 CFStringRef tmp_paper_name = NULL;
289 PMPaperGetPPDPaperName(default_paper, &tmp_paper_name);
290 PMPaperGetMargins(default_paper, &margins);
291 paper_name.reset(tmp_paper_name, base::scoped_policy::RETAIN);
293 const double kMutiplier =
294 kPointsPerInch / static_cast<float>(kMicronsPerInch);
295 page_width = media.size_microns.width() * kMutiplier;
296 page_height = media.size_microns.height() * kMutiplier;
297 paper_name.reset(base::SysUTF8ToCFStringRef(media.vendor_id));
300 CFArrayRef paper_list = NULL;
301 if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
304 PMPaper best_matching_paper =
305 MatchPaper(paper_list, paper_name, page_width, page_height);
307 if (best_matching_paper)
308 return UpdatePageFormatWithPaper(best_matching_paper, default_page_format);
310 // Do nothing if unmatched paper was default system paper.
311 if (media.IsDefault())
314 PMPaper paper = NULL;
315 if (PMPaperCreateCustom(current_printer,
316 CFSTR("Custom paper ID"),
317 CFSTR("Custom paper"),
324 bool result = UpdatePageFormatWithPaper(paper, default_page_format);
329 bool PrintingContextMac::UpdatePageFormatWithPaper(PMPaper paper,
330 PMPageFormat page_format) {
331 PMPageFormat new_format = NULL;
332 if (PMCreatePageFormatWithPMPaper(&new_format, paper) != noErr)
334 // Copy over the original format with the new page format.
335 bool result = (PMCopyPageFormat(new_format, page_format) == noErr);
336 [print_info_.get() updateFromPMPageFormat];
337 PMRelease(new_format);
341 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
345 PMPrintSettings pmPrintSettings =
346 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
347 return PMSetCopies(pmPrintSettings, copies, false) == noErr;
350 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
351 PMPrintSettings pmPrintSettings =
352 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
353 return PMSetCollate(pmPrintSettings, collate) == noErr;
356 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
357 PMPageFormat page_format =
358 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
360 PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
362 if (PMSetOrientation(page_format, orientation, false) != noErr)
365 PMPrintSession print_session =
366 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
368 PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
370 [print_info_.get() updateFromPMPageFormat];
374 bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
375 PMDuplexMode duplexSetting;
378 duplexSetting = kPMDuplexNoTumble;
381 duplexSetting = kPMDuplexTumble;
384 duplexSetting = kPMDuplexNone;
386 default: // UNKNOWN_DUPLEX_MODE
390 PMPrintSettings pmPrintSettings =
391 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
392 return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
395 bool PrintingContextMac::SetOutputColor(int color_mode) {
396 PMPrintSettings pmPrintSettings =
397 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
398 std::string color_setting_name;
399 std::string color_value;
400 GetColorModelForMode(color_mode, &color_setting_name, &color_value);
401 base::ScopedCFTypeRef<CFStringRef> color_setting(
402 base::SysUTF8ToCFStringRef(color_setting_name));
403 base::ScopedCFTypeRef<CFStringRef> output_color(
404 base::SysUTF8ToCFStringRef(color_value));
406 return PMPrintSettingsSetValue(pmPrintSettings,
412 PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
413 PageRanges page_ranges;
414 NSDictionary* print_info_dict = [print_info_.get() dictionary];
415 if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) {
417 range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1;
418 range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1;
419 page_ranges.push_back(range);
424 PrintingContext::Result PrintingContextMac::NewDocument(
425 const base::string16& document_name) {
426 DCHECK(!in_print_job_);
428 in_print_job_ = true;
430 PMPrintSession print_session =
431 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
432 PMPrintSettings print_settings =
433 static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
434 PMPageFormat page_format =
435 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
437 base::ScopedCFTypeRef<CFStringRef> job_title(
438 base::SysUTF16ToCFStringRef(document_name));
439 PMPrintSettingsSetJobName(print_settings, job_title.get());
441 OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
450 PrintingContext::Result PrintingContextMac::NewPage() {
453 DCHECK(in_print_job_);
456 PMPrintSession print_session =
457 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
458 PMPageFormat page_format =
459 static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
461 status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
464 status = PMSessionGetCGGraphicsContext(print_session, &context_);
471 PrintingContext::Result PrintingContextMac::PageDone() {
474 DCHECK(in_print_job_);
477 PMPrintSession print_session =
478 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
479 OSStatus status = PMSessionEndPageNoDialog(print_session);
487 PrintingContext::Result PrintingContextMac::DocumentDone() {
490 DCHECK(in_print_job_);
492 PMPrintSession print_session =
493 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
494 OSStatus status = PMSessionEndDocumentNoDialog(print_session);
502 void PrintingContextMac::Cancel() {
503 abort_printing_ = true;
504 in_print_job_ = false;
507 PMPrintSession print_session =
508 static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
509 PMSessionEndPageNoDialog(print_session);
512 void PrintingContextMac::ReleaseContext() {
517 printing::NativeDrawingContext PrintingContextMac::context() const {
521 } // namespace printing