| // 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 <AppKit/AppKit.h> |
| #import <QuartzCore/QuartzCore.h> |
| #include <cups/cups.h> |
| |
| #import <iomanip> |
| #import <numeric> |
| |
| #include "base/check.h" |
| #include "base/mac/scoped_cftyperef.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/printing_features.h" |
| #include "printing/units.h" |
| |
| namespace printing { |
| |
| namespace { |
| |
| const int kMaxPaperSizeDiffereceInPoints = 2; |
| |
| // Return true if PPD name of paper is equal. |
| bool IsPaperNameEqual(CFStringRef name1, const PMPaper& paper2) { |
| CFStringRef name2 = nullptr; |
| return (name1 && PMPaperGetPPDPaperName(paper2, &name2) == noErr) && |
| (CFStringCompare(name1, name2, kCFCompareCaseInsensitive) == |
| kCFCompareEqualTo); |
| } |
| |
| PMPaper MatchPaper(CFArrayRef paper_list, |
| CFStringRef name, |
| double width, |
| double height) { |
| double best_match = std::numeric_limits<double>::max(); |
| PMPaper best_matching_paper = nullptr; |
| 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 difference = |
| std::max(fabs(width - paper_width), fabs(height - paper_height)); |
| |
| // Ignore papers with size too different from expected. |
| if (difference > kMaxPaperSizeDiffereceInPoints) |
| continue; |
| |
| if (name && IsPaperNameEqual(name, paper)) |
| return paper; |
| |
| if (difference < best_match) { |
| best_matching_paper = paper; |
| best_match = difference; |
| } |
| } |
| return best_matching_paper; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<PrintingContext> PrintingContext::Create(Delegate* delegate) { |
| return std::make_unique<PrintingContextMac>(delegate); |
| } |
| |
| PrintingContextMac::PrintingContextMac(Delegate* delegate) |
| : PrintingContext(delegate), |
| print_info_([[NSPrintInfo sharedPrintInfo] copy]), |
| context_(nullptr) {} |
| |
| PrintingContextMac::~PrintingContextMac() { |
| ReleaseContext(); |
| } |
| |
| void PrintingContextMac::AskUserForSettings(int max_pages, |
| bool has_selection, |
| bool is_scripted, |
| PrintSettingsCallback callback) { |
| // Exceptions can also happen when the NSPrintPanel is being |
| // deallocated, so it must be autoreleased within this scope. |
| @autoreleasepool { |
| 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* print_info = print_info_.get(); |
| |
| NSPrintPanelOptions options = [panel options]; |
| options |= NSPrintPanelShowsPaperSize; |
| options |= NSPrintPanelShowsOrientation; |
| options |= NSPrintPanelShowsScaling; |
| [panel setOptions:options]; |
| |
| // Set the print job title text. |
| gfx::NativeView parent_view = delegate_->GetParentView(); |
| if (parent_view) { |
| NSString* job_title = [[parent_view.GetNativeNSView() window] title]; |
| if (job_title) { |
| PMPrintSettings print_settings = |
| (PMPrintSettings)[print_info PMPrintSettings]; |
| PMPrintSettingsSetJobName(print_settings, (CFStringRef)job_title); |
| [print_info updateFromPMPrintSettings]; |
| } |
| } |
| |
| // TODO(stuartmorgan): We really want a tab sheet here, not a modal window. |
| // Will require restructuring the PrintingContext API to use a callback. |
| |
| // This function may be called in the middle of a CATransaction, where |
| // running a modal panel is forbidden. That situation isn't ideal, but from |
| // this code's POV the right answer is to defer running the panel until |
| // after the current transaction. See https://crbug.com/849538. |
| __block auto block_callback = std::move(callback); |
| [CATransaction setCompletionBlock:^{ |
| NSInteger selection = [panel runModalWithPrintInfo:print_info]; |
| if (selection == NSOKButton) { |
| print_info_.reset([[panel printInfo] retain]); |
| settings_->set_ranges(GetPageRangesFromPrintInfo()); |
| InitPrintSettingsFromPrintInfo(); |
| std::move(block_callback).Run(OK); |
| } else { |
| std::move(block_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, |
| bool show_system_dialog, |
| int page_count) { |
| DCHECK(!show_system_dialog); |
| 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(base::UTF16ToUTF8(settings_->device_name())) || |
| !SetCopiesInPrintSettings(settings_->copies()) || |
| !SetCollateInPrintSettings(settings_->collate()) || |
| !SetDuplexModeInPrintSettings(settings_->duplex_mode()) || |
| !SetOutputColor(static_cast<int>(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, nullptr, |
| nullptr) == 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_.get()); |
| } |
| |
| 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) |
| 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]); |
| |
| PMPrinter current_printer = nullptr; |
| if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr) |
| return false; |
| |
| double page_width = 0.0; |
| double page_height = 0.0; |
| base::ScopedCFTypeRef<CFStringRef> paper_name; |
| PMPaperMargins margins = {0}; |
| |
| const PrintSettings::RequestedMedia& media = settings_->requested_media(); |
| if (media.IsDefault()) { |
| PMPaper default_paper; |
| if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr || |
| PMPaperGetWidth(default_paper, &page_width) != noErr || |
| PMPaperGetHeight(default_paper, &page_height) != noErr) { |
| return false; |
| } |
| |
| // Ignore result, because we can continue without following. |
| CFStringRef tmp_paper_name = nullptr; |
| PMPaperGetPPDPaperName(default_paper, &tmp_paper_name); |
| PMPaperGetMargins(default_paper, &margins); |
| paper_name.reset(tmp_paper_name, base::scoped_policy::RETAIN); |
| } else { |
| const double kMutiplier = |
| kPointsPerInch / static_cast<float>(kMicronsPerInch); |
| page_width = media.size_microns.width() * kMutiplier; |
| page_height = media.size_microns.height() * kMutiplier; |
| paper_name.reset(base::SysUTF8ToCFStringRef(media.vendor_id)); |
| } |
| |
| CFArrayRef paper_list = nullptr; |
| if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr) |
| return false; |
| |
| PMPaper best_matching_paper = |
| MatchPaper(paper_list, paper_name, page_width, page_height); |
| |
| if (best_matching_paper) |
| return UpdatePageFormatWithPaper(best_matching_paper, default_page_format); |
| |
| // Do nothing if unmatched paper was default system paper. |
| if (media.IsDefault()) |
| return true; |
| |
| PMPaper paper = nullptr; |
| if (PMPaperCreateCustom(current_printer, CFSTR("Custom paper ID"), |
| CFSTR("Custom paper"), page_width, page_height, |
| &margins, &paper) != noErr) { |
| return false; |
| } |
| bool result = UpdatePageFormatWithPaper(paper, default_page_format); |
| PMRelease(paper); |
| return result; |
| } |
| |
| bool PrintingContextMac::UpdatePageFormatWithPaper(PMPaper paper, |
| PMPageFormat page_format) { |
| PMPageFormat new_format = nullptr; |
| if (PMCreatePageFormatWithPMPaper(&new_format, paper) != noErr) |
| return false; |
| // Copy over the original format with the new page format. |
| bool result = (PMCopyPageFormat(new_format, page_format) == noErr); |
| [print_info_.get() updateFromPMPageFormat]; |
| PMRelease(new_format); |
| return result; |
| } |
| |
| bool PrintingContextMac::SetCopiesInPrintSettings(int copies) { |
| if (copies < 1) |
| return false; |
| |
| PMPrintSettings print_settings = |
| static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); |
| return PMSetCopies(print_settings, copies, false) == noErr; |
| } |
| |
| bool PrintingContextMac::SetCollateInPrintSettings(bool collate) { |
| PMPrintSettings print_settings = |
| static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); |
| return PMSetCollate(print_settings, 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(mojom::DuplexMode mode) { |
| PMDuplexMode duplexSetting; |
| switch (mode) { |
| case mojom::DuplexMode::kLongEdge: |
| duplexSetting = kPMDuplexNoTumble; |
| break; |
| case mojom::DuplexMode::kShortEdge: |
| duplexSetting = kPMDuplexTumble; |
| break; |
| case mojom::DuplexMode::kSimplex: |
| duplexSetting = kPMDuplexNone; |
| break; |
| default: // kUnknownDuplexMode |
| return true; |
| } |
| |
| PMPrintSettings print_settings = |
| static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); |
| return PMSetDuplex(print_settings, duplexSetting) == noErr; |
| } |
| |
| bool PrintingContextMac::SetOutputColor(int color_mode) { |
| PMPrintSettings print_settings = |
| static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); |
| std::string color_setting_name; |
| std::string color_value; |
| if (base::FeatureList::IsEnabled(features::kCupsIppPrintingBackend)) { |
| color_setting_name = CUPS_PRINT_COLOR_MODE; |
| color_value = GetIppColorModelForMode(color_mode); |
| } else { |
| 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(print_settings, 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::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, nullptr); |
| 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_ = nullptr; |
| |
| 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_ = nullptr; |
| |
| PMPrintSession print_session = |
| static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); |
| PMSessionEndPageNoDialog(print_session); |
| } |
| |
| void PrintingContextMac::ReleaseContext() { |
| print_info_.reset(); |
| context_ = nullptr; |
| } |
| |
| printing::NativeDrawingContext PrintingContextMac::context() const { |
| return context_; |
| } |
| |
| } // namespace printing |