123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- // 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/string_piece.h"
- #include "base/strings/sys_string_conversions.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/values.h"
- #include "build/build_config.h"
- #include "printing/buildflags/buildflags.h"
- #include "printing/metafile.h"
- #include "printing/mojom/print.mojom.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)[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::CreateImpl(
- Delegate* delegate,
- bool skip_system_calls) {
- auto context = std::make_unique<PrintingContextMac>(delegate);
- #if BUILDFLAG(ENABLE_OOP_PRINTING)
- if (skip_system_calls)
- context->set_skip_system_calls();
- #endif
- return context;
- }
- 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 == NSModalResponseOK) {
- print_info_.reset([[panel printInfo] retain]);
- settings_->set_ranges(GetPageRangesFromPrintInfo());
- InitPrintSettingsFromPrintInfo();
- std::move(block_callback).Run(mojom::ResultCode::kSuccess);
- } else {
- std::move(block_callback).Run(mojom::ResultCode::kCanceled);
- }
- }];
- }
- }
- 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;
- }
- mojom::ResultCode PrintingContextMac::UseDefaultSettings() {
- DCHECK(!in_print_job_);
- print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
- settings_->set_ranges(GetPageRangesFromPrintInfo());
- InitPrintSettingsFromPrintInfo();
- return mojom::ResultCode::kSuccess;
- }
- mojom::ResultCode PrintingContextMac::UpdatePrinterSettings(
- const PrinterSettings& printer_settings) {
- DCHECK(!printer_settings.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 (printer_settings.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())) ||
- !SetResolution(settings_->dpi_size())) {
- return OnError();
- }
- }
- if (!UpdatePageFormatWithPaperInfo() ||
- !SetOrientationIsLandscape(settings_->landscape())) {
- return OnError();
- }
- [print_info_.get() updateFromPMPrintSettings];
- InitPrintSettingsFromPrintInfo();
- return mojom::ResultCode::kSuccess;
- }
- 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) {
- const mojom::ColorModel color_model = ColorModeToColorModel(color_mode);
- if (!base::FeatureList::IsEnabled(features::kCupsIppPrintingBackend)) {
- std::string color_setting_name;
- std::string color_value;
- GetColorModelForModel(color_model, &color_setting_name, &color_value);
- return SetKeyValue(color_setting_name, color_value);
- }
- // First, set the default CUPS IPP output color.
- if (!SetKeyValue(CUPS_PRINT_COLOR_MODE,
- GetIppColorModelForModel(color_model))) {
- return false;
- }
- struct PpdColorSetting {
- constexpr PpdColorSetting(base::StringPiece name,
- base::StringPiece bw,
- base::StringPiece color)
- : name(name), bw(bw), color(color) {}
- base::StringPiece name;
- base::StringPiece bw;
- base::StringPiece color;
- };
- // TODO(crbug.com/1210992): Move `kKnownPpdColorSettings` elsewhere so it can
- // be used for general CUPS printing code (e.g., for parsing PPDs).
- static constexpr PpdColorSetting kKnownPpdColorSettings[] = {
- {"ARCMode", "CMBW", "CMColor"}, // Sharp
- {"BLW", "TrueM", "FalseM"}, // Lexmark
- {"BRMonoColor", "Mono", "FullColor"}, // Brother
- {"BRPrintQuality", "Black", "Color"}, // Brother
- {"CNIJGrayScale", "1", "0"}, // Canon
- {"ColorMode", "Monochrome", "Color"}, // Samsung
- {"ColorModel", "Gray", "Color"}, // Generic
- {"HPColorMode", "GrayscalePrint", "ColorPrint"}, // HP
- {"Ink", "MONO", "COLOR"}, // Epson
- {"OKControl", "Gray", "Auto"}, // Oki
- {"PrintoutMode", "Normal.Gray", "Normal"}, // Foomatic
- {"SelectColor", "Grayscale", "Color"}, // Konica Minolta
- {"XRXColor", "BW", "Automatic"}, // Xerox
- {"XROutputColor", "PrintAsGrayscale", "PrintAsColor"}, // Xerox
- };
- // Even when interfacing with printer settings using CUPS IPP, the print job
- // may still expect PPD color values if the printer was added to the system
- // with a PPD. To avoid parsing PPDs (which is the point of using CUPS IPP),
- // set every single known PPD color setting and hope that one of them sticks.
- const bool is_color = IsColorModelSelected(color_model).value_or(false);
- for (const auto& setting : kKnownPpdColorSettings) {
- const base::StringPiece& color_setting_name = setting.name;
- const base::StringPiece& color_value =
- is_color ? setting.color : setting.bw;
- if (!SetKeyValue(color_setting_name, color_value))
- return false;
- }
- return true;
- }
- bool PrintingContextMac::SetResolution(const gfx::Size& dpi_size) {
- if (dpi_size.IsEmpty())
- return true;
- PMPrintSession print_session =
- static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
- PMPrinter current_printer;
- if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr)
- return false;
- PMResolution resolution;
- resolution.hRes = dpi_size.width();
- resolution.vRes = dpi_size.height();
- PMPrintSettings print_settings =
- static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
- return PMPrinterSetOutputResolution(current_printer, print_settings,
- &resolution) == noErr;
- }
- bool PrintingContextMac::SetKeyValue(base::StringPiece key,
- base::StringPiece value) {
- PMPrintSettings print_settings =
- static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
- base::ScopedCFTypeRef<CFStringRef> cf_key(base::SysUTF8ToCFStringRef(key));
- base::ScopedCFTypeRef<CFStringRef> cf_value(
- base::SysUTF8ToCFStringRef(value));
- return PMPrintSettingsSetValue(print_settings, cf_key.get(), cf_value.get(),
- /*locked=*/false) == noErr;
- }
- PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
- PageRanges page_ranges;
- NSDictionary* print_info_dict = [print_info_.get() dictionary];
- if (![print_info_dict[NSPrintAllPages] boolValue]) {
- PageRange range;
- range.from = [print_info_dict[NSPrintFirstPage] intValue] - 1;
- range.to = [print_info_dict[NSPrintLastPage] intValue] - 1;
- page_ranges.push_back(range);
- }
- return page_ranges;
- }
- mojom::ResultCode PrintingContextMac::NewDocument(
- const std::u16string& document_name) {
- DCHECK(!in_print_job_);
- in_print_job_ = true;
- if (skip_system_calls())
- return mojom::ResultCode::kSuccess;
- 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 mojom::ResultCode::kSuccess;
- }
- mojom::ResultCode PrintingContextMac::NewPage() {
- if (abort_printing_)
- return mojom::ResultCode::kCanceled;
- 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 mojom::ResultCode::kSuccess;
- }
- mojom::ResultCode PrintingContextMac::PageDone() {
- if (abort_printing_)
- return mojom::ResultCode::kCanceled;
- 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 mojom::ResultCode::kSuccess;
- }
- mojom::ResultCode PrintingContextMac::PrintDocument(
- const MetafilePlayer& metafile,
- const PrintSettings& settings,
- uint32_t num_pages) {
- const PageSetup& page_setup = settings.page_setup_device_units();
- const CGRect paper_rect = gfx::Rect(page_setup.physical_size()).ToCGRect();
- for (size_t metafile_page_number = 1; metafile_page_number <= num_pages;
- metafile_page_number++) {
- mojom::ResultCode result = NewPage();
- if (result != mojom::ResultCode::kSuccess)
- return result;
- if (!metafile.RenderPage(metafile_page_number, context_, paper_rect,
- /*autorotate=*/true, /*fit_to_page=*/false)) {
- return mojom::ResultCode::kFailed;
- }
- result = PageDone();
- if (result != mojom::ResultCode::kSuccess)
- return result;
- }
- return mojom::ResultCode::kSuccess;
- }
- mojom::ResultCode PrintingContextMac::DocumentDone() {
- if (abort_printing_)
- return mojom::ResultCode::kCanceled;
- 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 mojom::ResultCode::kSuccess;
- }
- 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
|