Upload upstream chromium 73.0.3683.0
[platform/framework/web/chromium-efl.git] / printing / printing_context_mac.mm
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 <AppKit/AppKit.h>
8 #import <QuartzCore/QuartzCore.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/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"
22
23 namespace printing {
24
25 namespace {
26
27 const int kMaxPaperSizeDiffereceInPoints = 2;
28
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) ==
34           kCFCompareEqualTo);
35 }
36
37 PMPaper MatchPaper(CFArrayRef paper_list,
38                    CFStringRef name,
39                    double width,
40                    double height) {
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);
50     double difference =
51         std::max(fabs(width - paper_width), fabs(height - paper_height));
52
53     // Ignore papers with size too different from expected.
54     if (difference > kMaxPaperSizeDiffereceInPoints)
55       continue;
56
57     if (name && IsPaperNameEqual(name, paper))
58       return paper;
59
60     if (difference < best_match) {
61       best_matching_paper = paper;
62       best_match = difference;
63     }
64   }
65   return best_matching_paper;
66 }
67
68 }  // namespace
69
70 // static
71 std::unique_ptr<PrintingContext> PrintingContext::Create(Delegate* delegate) {
72   return base::WrapUnique(new PrintingContextMac(delegate));
73 }
74
75 PrintingContextMac::PrintingContextMac(Delegate* delegate)
76     : PrintingContext(delegate),
77       print_info_([[NSPrintInfo sharedPrintInfo] copy]),
78       context_(NULL) {
79 }
80
81 PrintingContextMac::~PrintingContextMac() {
82   ReleaseContext();
83 }
84
85 void PrintingContextMac::AskUserForSettings(int max_pages,
86                                             bool has_selection,
87                                             bool is_scripted,
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;
92
93   DCHECK([NSThread isMainThread]);
94
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
97   // a range.
98
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();
104
105   NSPrintPanelOptions options = [panel options];
106   options |= NSPrintPanelShowsPaperSize;
107   options |= NSPrintPanelShowsOrientation;
108   options |= NSPrintPanelShowsScaling;
109   [panel setOptions:options];
110
111   // Set the print job title text.
112   gfx::NativeView parent_view = delegate_->GetParentView();
113   if (parent_view) {
114     NSString* job_title = [[parent_view.GetNativeNSView() window] title];
115     if (job_title) {
116       PMPrintSettings printSettings =
117           (PMPrintSettings)[printInfo PMPrintSettings];
118       PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
119       [printInfo updateFromPMPrintSettings];
120     }
121   }
122
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.
125
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);
138     } else {
139       std::move(block_callback).Run(CANCEL);
140     }
141   }];
142 }
143
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();
149
150   PMPageFormat page_format =
151       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
152   PMRect paper_rect;
153   PMGetAdjustedPaperRect(page_format, &paper_rect);
154
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;
161 }
162
163 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
164   DCHECK(!in_print_job_);
165
166   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
167   settings_.set_ranges(GetPageRangesFromPrintInfo());
168   InitPrintSettingsFromPrintInfo();
169
170   return OK;
171 }
172
173 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
174     bool external_preview,
175     bool show_system_dialog,
176     int page_count) {
177   DCHECK(!show_system_dialog);
178   DCHECK(!in_print_job_);
179
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]);
183
184   if (external_preview) {
185     if (!SetPrintPreviewJob())
186       return OnError();
187   } else {
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())) {
194       return OnError();
195     }
196   }
197
198   if (!UpdatePageFormatWithPaperInfo() ||
199       !SetOrientationIsLandscape(settings_.landscape())) {
200     return OnError();
201   }
202
203   [print_info_.get() updateFromPMPrintSettings];
204
205   InitPrintSettingsFromPrintInfo();
206   return OK;
207 }
208
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;
217 }
218
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]);
224   PMPrinter printer;
225   PMSessionGetCurrentPrinter(print_session, &printer);
226   PrintSettingsInitializerMac::InitPrintSettings(
227       printer, page_format, &settings_);
228 }
229
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]);
234
235   PMPrinter current_printer;
236   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
237     return false;
238
239   CFStringRef current_printer_id = PMPrinterGetID(current_printer);
240   if (!current_printer_id)
241     return false;
242
243   base::ScopedCFTypeRef<CFStringRef> new_printer_id(
244       base::SysUTF8ToCFStringRef(device_name));
245   if (!new_printer_id.get())
246     return false;
247
248   if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
249           kCFCompareEqualTo) {
250     return true;
251   }
252
253   PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
254   if (!new_printer)
255     return false;
256
257   OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
258   PMRelease(new_printer);
259   return status == noErr;
260 }
261
262 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
263   PMPrintSession print_session =
264       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
265
266   PMPageFormat default_page_format =
267       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
268
269   PMPrinter current_printer = NULL;
270   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
271     return false;
272
273   double page_width = 0.0;
274   double page_height = 0.0;
275   base::ScopedCFTypeRef<CFStringRef> paper_name;
276   PMPaperMargins margins = {0};
277
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) {
284       return false;
285     }
286
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);
292   } else {
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));
298   }
299
300   CFArrayRef paper_list = NULL;
301   if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
302     return false;
303
304   PMPaper best_matching_paper =
305       MatchPaper(paper_list, paper_name, page_width, page_height);
306
307   if (best_matching_paper)
308     return UpdatePageFormatWithPaper(best_matching_paper, default_page_format);
309
310   // Do nothing if unmatched paper was default system paper.
311   if (media.IsDefault())
312     return true;
313
314   PMPaper paper = NULL;
315   if (PMPaperCreateCustom(current_printer,
316                           CFSTR("Custom paper ID"),
317                           CFSTR("Custom paper"),
318                           page_width,
319                           page_height,
320                           &margins,
321                           &paper) != noErr) {
322     return false;
323   }
324   bool result = UpdatePageFormatWithPaper(paper, default_page_format);
325   PMRelease(paper);
326   return result;
327 }
328
329 bool PrintingContextMac::UpdatePageFormatWithPaper(PMPaper paper,
330                                                    PMPageFormat page_format) {
331   PMPageFormat new_format = NULL;
332   if (PMCreatePageFormatWithPMPaper(&new_format, paper) != noErr)
333     return false;
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);
338   return result;
339 }
340
341 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
342   if (copies < 1)
343     return false;
344
345   PMPrintSettings pmPrintSettings =
346       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
347   return PMSetCopies(pmPrintSettings, copies, false) == noErr;
348 }
349
350 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
351   PMPrintSettings pmPrintSettings =
352       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
353   return PMSetCollate(pmPrintSettings, collate) == noErr;
354 }
355
356 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
357   PMPageFormat page_format =
358       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
359
360   PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
361
362   if (PMSetOrientation(page_format, orientation, false) != noErr)
363     return false;
364
365   PMPrintSession print_session =
366       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
367
368   PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
369
370   [print_info_.get() updateFromPMPageFormat];
371   return true;
372 }
373
374 bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
375   PMDuplexMode duplexSetting;
376   switch (mode) {
377     case LONG_EDGE:
378       duplexSetting = kPMDuplexNoTumble;
379       break;
380     case SHORT_EDGE:
381       duplexSetting = kPMDuplexTumble;
382       break;
383     case SIMPLEX:
384       duplexSetting = kPMDuplexNone;
385       break;
386     default:  // UNKNOWN_DUPLEX_MODE
387       return true;
388   }
389
390   PMPrintSettings pmPrintSettings =
391       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
392   return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
393 }
394
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));
405
406   return PMPrintSettingsSetValue(pmPrintSettings,
407                                  color_setting.get(),
408                                  output_color.get(),
409                                  false) == noErr;
410 }
411
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]) {
416     PageRange range;
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);
420   }
421   return page_ranges;
422 }
423
424 PrintingContext::Result PrintingContextMac::NewDocument(
425     const base::string16& document_name) {
426   DCHECK(!in_print_job_);
427
428   in_print_job_ = true;
429
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]);
436
437   base::ScopedCFTypeRef<CFStringRef> job_title(
438       base::SysUTF16ToCFStringRef(document_name));
439   PMPrintSettingsSetJobName(print_settings, job_title.get());
440
441   OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
442                                                      print_settings,
443                                                      page_format);
444   if (status != noErr)
445     return OnError();
446
447   return OK;
448 }
449
450 PrintingContext::Result PrintingContextMac::NewPage() {
451   if (abort_printing_)
452     return CANCEL;
453   DCHECK(in_print_job_);
454   DCHECK(!context_);
455
456   PMPrintSession print_session =
457       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
458   PMPageFormat page_format =
459       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
460   OSStatus status;
461   status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
462   if (status != noErr)
463     return OnError();
464   status = PMSessionGetCGGraphicsContext(print_session, &context_);
465   if (status != noErr)
466     return OnError();
467
468   return OK;
469 }
470
471 PrintingContext::Result PrintingContextMac::PageDone() {
472   if (abort_printing_)
473     return CANCEL;
474   DCHECK(in_print_job_);
475   DCHECK(context_);
476
477   PMPrintSession print_session =
478       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
479   OSStatus status = PMSessionEndPageNoDialog(print_session);
480   if (status != noErr)
481     OnError();
482   context_ = NULL;
483
484   return OK;
485 }
486
487 PrintingContext::Result PrintingContextMac::DocumentDone() {
488   if (abort_printing_)
489     return CANCEL;
490   DCHECK(in_print_job_);
491
492   PMPrintSession print_session =
493       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
494   OSStatus status = PMSessionEndDocumentNoDialog(print_session);
495   if (status != noErr)
496     OnError();
497
498   ResetSettings();
499   return OK;
500 }
501
502 void PrintingContextMac::Cancel() {
503   abort_printing_ = true;
504   in_print_job_ = false;
505   context_ = NULL;
506
507   PMPrintSession print_session =
508       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
509   PMSessionEndPageNoDialog(print_session);
510 }
511
512 void PrintingContextMac::ReleaseContext() {
513   print_info_.reset();
514   context_ = NULL;
515 }
516
517 printing::NativeDrawingContext PrintingContextMac::context() const {
518   return context_;
519 }
520
521 }  // namespace printing