1 // Copyright 2016 The Chromium Authors
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_chromeos.h"
9 #include <unicode/ulocdata.h>
16 #include "base/logging.h"
17 #include "base/memory/ptr_util.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "printing/backend/cups_connection.h"
22 #include "printing/backend/cups_ipp_constants.h"
23 #include "printing/backend/cups_ipp_helper.h"
24 #include "printing/backend/cups_printer.h"
25 #include "printing/backend/print_backend_utils.h"
26 #include "printing/buildflags/buildflags.h"
27 #include "printing/client_info_helpers.h"
28 #include "printing/metafile.h"
29 #include "printing/mojom/print.mojom.h"
30 #include "printing/print_job_constants.h"
31 #include "printing/print_settings.h"
32 #include "printing/printing_utils.h"
33 #include "printing/units.h"
39 // We only support sending username for secure printers.
40 const char kUsernamePlaceholder[] = "chronos";
42 // We only support sending document name for secure printers.
43 const char kDocumentNamePlaceholder[] = "-";
45 bool IsUriSecure(base::StringPiece uri) {
46 return base::StartsWith(uri, "ipps:") || base::StartsWith(uri, "https:") ||
47 base::StartsWith(uri, "usb:") || base::StartsWith(uri, "ippusb:");
50 // Populates the 'client-info' attribute of the IPP collection `options`. Each
51 // item in `client_infos` represents one collection in 'client-info'.
52 // Invalid 'client-info' items will be dropped.
53 void EncodeClientInfo(const std::vector<mojom::IppClientInfo>& client_infos,
55 std::vector<ScopedIppPtr> option_values;
56 std::vector<const ipp_t*> raw_option_values;
57 option_values.reserve(client_infos.size());
58 raw_option_values.reserve(client_infos.size());
60 for (const mojom::IppClientInfo& client_info : client_infos) {
61 if (!ValidateClientInfoItem(client_info)) {
62 LOG(WARNING) << "Invalid client-info item skipped";
66 // Create a temporary collection object owned by this function.
67 ipp_t* collection = ippNew();
68 option_values.emplace_back(WrapIpp(collection));
69 raw_option_values.emplace_back(collection);
71 ippAddString(collection, IPP_TAG_ZERO, IPP_TAG_NAME, kIppClientName,
72 nullptr, client_info.client_name.c_str());
73 ippAddInteger(collection, IPP_TAG_ZERO, IPP_TAG_ENUM, kIppClientType,
74 static_cast<int>(client_info.client_type));
75 ippAddString(collection, IPP_TAG_ZERO, IPP_TAG_TEXT,
76 kIppClientStringVersion, nullptr,
77 client_info.client_string_version.c_str());
79 if (client_info.client_version.has_value()) {
80 ippAddOctetString(collection, IPP_TAG_ZERO, kIppClientVersion,
81 client_info.client_version.value().data(),
82 client_info.client_version.value().size());
85 if (client_info.client_patches.has_value()) {
86 ippAddString(collection, IPP_TAG_ZERO, IPP_TAG_TEXT, kIppClientPatches,
87 nullptr, client_info.client_patches.value().c_str());
91 if (raw_option_values.empty()) {
95 // Now add the client-info list to the options.
96 ippAddCollections(options, IPP_TAG_OPERATION, kIppClientInfo,
97 raw_option_values.size(), raw_option_values.data());
100 // Construct the IPP media-col attribute specifying media size, margins, source,
101 // etc., and add it to 'options'.
102 void EncodeMediaCol(ipp_t* options,
103 const gfx::Size& size_um,
104 const gfx::Rect& printable_area_um,
106 const std::string& source,
107 const std::string& type) {
108 // The size and printable area in microns were calculated from the size and
109 // margins in PWG units, so we can losslessly convert them back. If
110 // borderless printing was requested, though, set all margins to zero.
111 DCHECK_EQ(size_um.width() % kMicronsPerPwgUnit, 0);
112 DCHECK_EQ(size_um.height() % kMicronsPerPwgUnit, 0);
113 int width = size_um.width() / kMicronsPerPwgUnit;
114 int height = size_um.height() / kMicronsPerPwgUnit;
115 int bottom_margin = 0, left_margin = 0, right_margin = 0, top_margin = 0;
117 PwgMarginsFromSizeAndPrintableArea(size_um, printable_area_um,
118 &bottom_margin, &left_margin,
119 &right_margin, &top_margin);
122 ScopedIppPtr media_col = WrapIpp(ippNew());
123 ScopedIppPtr media_size = WrapIpp(ippNew());
124 ippAddInteger(media_size.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER, kIppXDimension,
126 ippAddInteger(media_size.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER, kIppYDimension,
128 ippAddCollection(media_col.get(), IPP_TAG_ZERO, kIppMediaSize,
130 ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
131 kIppMediaBottomMargin, bottom_margin);
132 ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
133 kIppMediaLeftMargin, left_margin);
134 ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
135 kIppMediaRightMargin, right_margin);
136 ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
137 kIppMediaTopMargin, top_margin);
138 if (!source.empty()) {
139 ippAddString(media_col.get(), IPP_TAG_ZERO, IPP_TAG_KEYWORD,
140 kIppMediaSource, nullptr, source.c_str());
143 ippAddString(media_col.get(), IPP_TAG_ZERO, IPP_TAG_KEYWORD, kIppMediaType,
144 nullptr, type.c_str());
147 ippAddCollection(options, IPP_TAG_JOB, kIppMediaCol, media_col.get());
150 std::string GetCollateString(bool collate) {
151 return collate ? kCollated : kUncollated;
154 void SetPrintableArea(PrintSettings* settings,
155 const PrintSettings::RequestedMedia& media,
156 const gfx::Rect& printable_area_um) {
157 if (!media.size_microns.IsEmpty()) {
158 float device_microns_per_device_unit =
159 static_cast<float>(kMicronsPerInch) / settings->device_units_per_inch();
160 gfx::Size paper_size =
161 gfx::Size(media.size_microns.width() / device_microns_per_device_unit,
162 media.size_microns.height() / device_microns_per_device_unit);
164 gfx::Rect paper_rect =
165 gfx::Rect(printable_area_um.x() / device_microns_per_device_unit,
166 printable_area_um.y() / device_microns_per_device_unit,
167 printable_area_um.width() / device_microns_per_device_unit,
168 printable_area_um.height() / device_microns_per_device_unit);
169 settings->SetPrinterPrintableArea(paper_size, paper_rect,
170 /*landscape_needs_flip=*/true);
176 ScopedIppPtr SettingsToIPPOptions(const PrintSettings& settings,
177 const gfx::Rect& printable_area_um) {
178 ScopedIppPtr scoped_options = WrapIpp(ippNew());
179 ipp_t* options = scoped_options.get();
181 const char* sides = nullptr;
182 switch (settings.duplex_mode()) {
183 case mojom::DuplexMode::kSimplex:
184 sides = CUPS_SIDES_ONE_SIDED;
186 case mojom::DuplexMode::kLongEdge:
187 sides = CUPS_SIDES_TWO_SIDED_PORTRAIT;
189 case mojom::DuplexMode::kShortEdge:
190 sides = CUPS_SIDES_TWO_SIDED_LANDSCAPE;
197 ippAddString(options, IPP_TAG_JOB, IPP_TAG_KEYWORD, kIppDuplex, nullptr,
200 ippAddString(options, IPP_TAG_JOB, IPP_TAG_KEYWORD, kIppColor, nullptr,
201 GetIppColorModelForModel(settings.color()).c_str());
203 ippAddInteger(options, IPP_TAG_JOB, IPP_TAG_INTEGER, kIppCopies,
206 ippAddString(options, IPP_TAG_JOB, IPP_TAG_KEYWORD, kIppCollate, nullptr,
207 GetCollateString(settings.collate()).c_str());
209 if (!settings.pin_value().empty()) {
210 ippAddOctetString(options, IPP_TAG_OPERATION, kIppPin,
211 settings.pin_value().data(), settings.pin_value().size());
212 ippAddString(options, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, kIppPinEncryption,
213 nullptr, kPinEncryptionNone);
217 if (settings.dpi_horizontal() > 0 && settings.dpi_vertical() > 0) {
218 ippAddResolution(options, IPP_TAG_JOB, kIppResolution, IPP_RES_PER_INCH,
219 settings.dpi_horizontal(), settings.dpi_vertical());
222 std::map<std::string, std::vector<int>> multival;
223 std::string media_source;
224 for (const auto& setting : settings.advanced_settings()) {
225 const std::string& key = setting.first;
226 const std::string& value = setting.second.GetString();
230 if (key == kIppMediaSource) {
231 media_source = value;
235 // Check for multivalue enum ("attribute/value").
236 size_t pos = key.find('/');
237 if (pos == std::string::npos) {
239 ippAddString(options, IPP_TAG_JOB, IPP_TAG_KEYWORD, key.c_str(), nullptr,
243 // Store selected enum values.
244 if (value == kOptionTrue) {
245 std::string option_name = key.substr(0, pos);
246 std::string enum_string = key.substr(pos + 1);
247 int enum_value = ippEnumValue(option_name.c_str(), enum_string.c_str());
248 DCHECK_NE(enum_value, -1);
249 multival[option_name].push_back(enum_value);
253 // Construct the IPP media-col attribute specifying media size, margins,
255 EncodeMediaCol(options, settings.requested_media().size_microns,
256 printable_area_um, settings.borderless(), media_source,
257 settings.media_type());
259 // Add multivalue enum options.
260 for (const auto& it : multival) {
261 ippAddIntegers(options, IPP_TAG_JOB, IPP_TAG_ENUM, it.first.c_str(),
262 it.second.size(), it.second.data());
265 // OAuth access token
266 if (!settings.oauth_token().empty()) {
267 ippAddString(options, IPP_TAG_JOB, IPP_TAG_NAME,
268 kSettingChromeOSAccessOAuthToken, nullptr,
269 settings.oauth_token().c_str());
272 // IPP client-info attribute.
273 if (!settings.client_infos().empty()) {
274 EncodeClientInfo(settings.client_infos(), options);
277 return scoped_options;
281 std::unique_ptr<PrintingContext> PrintingContext::CreateImpl(
283 bool skip_system_calls) {
284 auto context = std::make_unique<PrintingContextChromeos>(delegate);
285 #if BUILDFLAG(ENABLE_OOP_PRINTING)
286 if (skip_system_calls)
287 context->set_skip_system_calls();
293 std::unique_ptr<PrintingContextChromeos>
294 PrintingContextChromeos::CreateForTesting(
296 std::unique_ptr<CupsConnection> connection) {
298 return base::WrapUnique(
299 new PrintingContextChromeos(delegate, std::move(connection)));
302 PrintingContextChromeos::PrintingContextChromeos(Delegate* delegate)
303 : PrintingContext(delegate),
304 connection_(CupsConnection::Create()),
305 ipp_options_(WrapIpp(nullptr)) {}
307 PrintingContextChromeos::PrintingContextChromeos(
309 std::unique_ptr<CupsConnection> connection)
310 : PrintingContext(delegate),
311 connection_(std::move(connection)),
312 ipp_options_(WrapIpp(nullptr)) {}
314 PrintingContextChromeos::~PrintingContextChromeos() {
318 void PrintingContextChromeos::AskUserForSettings(
322 PrintSettingsCallback callback) {
323 // We don't want to bring up a dialog here. Ever. This should not be called.
327 mojom::ResultCode PrintingContextChromeos::UseDefaultSettings() {
328 DCHECK(!in_print_job_);
332 std::string device_name = base::UTF16ToUTF8(settings_->device_name());
333 if (device_name.empty())
336 // TODO(skau): https://crbug.com/613779. See UpdatePrinterSettings for more
338 if (settings_->dpi() == 0) {
339 DVLOG(1) << "Using Default DPI";
340 settings_->set_dpi(kDefaultPdfDpi);
343 // Retrieve device information and set it
344 if (InitializeDevice(device_name) != mojom::ResultCode::kSuccess) {
345 LOG(ERROR) << "Could not initialize printer";
349 // Set printable area
351 PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_);
353 PrintSettings::RequestedMedia media;
354 media.vendor_id = paper.vendor_id();
355 media.size_microns = paper.size_um();
356 settings_->set_requested_media(media);
357 SetPrintableArea(settings_.get(), media, paper.printable_area_um());
359 return mojom::ResultCode::kSuccess;
362 gfx::Size PrintingContextChromeos::GetPdfPaperSizeDeviceUnits() {
365 UErrorCode error = U_ZERO_ERROR;
366 ulocdata_getPaperSize(delegate_->GetAppLocale().c_str(), &height, &width,
368 if (error > U_ZERO_ERROR) {
369 // If the call failed, assume a paper size of 8.5 x 11 inches.
370 LOG(WARNING) << "ulocdata_getPaperSize failed, using 8.5 x 11, error: "
373 static_cast<int>(kLetterWidthInch * settings_->device_units_per_inch());
374 height = static_cast<int>(kLetterHeightInch *
375 settings_->device_units_per_inch());
377 // ulocdata_getPaperSize returns the width and height in mm.
378 // Convert this to pixels based on the dpi.
379 float multiplier = settings_->device_units_per_inch() / kMicronsPerMil;
381 height *= multiplier;
383 return gfx::Size(width, height);
386 mojom::ResultCode PrintingContextChromeos::UpdatePrinterSettings(
387 const PrinterSettings& printer_settings) {
388 DCHECK(!printer_settings.show_system_dialog);
390 if (InitializeDevice(base::UTF16ToUTF8(settings_->device_name())) !=
391 mojom::ResultCode::kSuccess) {
395 // TODO(skau): Convert to DCHECK when https://crbug.com/613779 is resolved
396 // Print quality suffers when this is set to the resolution reported by the
397 // printer but print quality is fine at this resolution. UseDefaultSettings
398 // exhibits the same problem.
399 if (settings_->dpi() == 0) {
400 DVLOG(1) << "Using Default DPI";
401 settings_->set_dpi(kDefaultPdfDpi);
404 // compute paper size
405 PrintSettings::RequestedMedia media = settings_->requested_media();
408 if (media.IsDefault()) {
409 PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_);
411 media.vendor_id = paper.vendor_id();
412 media.size_microns = paper.size_um();
413 settings_->set_requested_media(media);
416 gfx::Rect printable_area_um =
417 GetPrintableAreaForSize(*printer_, media.size_microns);
418 SetPrintableArea(settings_.get(), media, printable_area_um);
419 ipp_options_ = SettingsToIPPOptions(*settings_, printable_area_um);
420 send_user_info_ = settings_->send_user_info();
421 if (send_user_info_) {
423 username_ = IsUriSecure(printer_->GetUri()) ? settings_->username()
424 : kUsernamePlaceholder;
427 return mojom::ResultCode::kSuccess;
430 mojom::ResultCode PrintingContextChromeos::InitializeDevice(
431 const std::string& device) {
432 DCHECK(!in_print_job_);
434 std::unique_ptr<CupsPrinter> printer = connection_->GetPrinter(device);
436 LOG(WARNING) << "Could not initialize device";
440 printer_ = std::move(printer);
442 return mojom::ResultCode::kSuccess;
445 mojom::ResultCode PrintingContextChromeos::NewDocument(
446 const std::u16string& document_name) {
447 DCHECK(!in_print_job_);
448 in_print_job_ = true;
450 if (skip_system_calls())
451 return mojom::ResultCode::kSuccess;
453 std::string converted_name;
454 if (send_user_info_) {
456 converted_name = IsUriSecure(printer_->GetUri())
457 ? base::UTF16ToUTF8(document_name)
458 : kDocumentNamePlaceholder;
461 ipp_status_t create_status = printer_->CreateJob(
462 &job_id_, converted_name, username_, ipp_options_.get());
465 DLOG(WARNING) << "Creating cups job failed"
466 << ippErrorString(create_status);
470 // we only send one document, so it's always the last one
471 if (!printer_->StartDocument(job_id_, converted_name, true, username_,
472 ipp_options_.get())) {
473 LOG(ERROR) << "Starting document failed";
477 return mojom::ResultCode::kSuccess;
480 mojom::ResultCode PrintingContextChromeos::PrintDocument(
481 const MetafilePlayer& metafile,
482 const PrintSettings& settings,
483 uint32_t num_pages) {
485 return mojom::ResultCode::kCanceled;
486 DCHECK(in_print_job_);
488 #if BUILDFLAG(USE_CUPS)
489 std::vector<char> buffer;
490 if (!metafile.GetDataAsVector(&buffer))
491 return mojom::ResultCode::kFailed;
493 return StreamData(buffer);
496 return mojom::ResultCode::kFailed;
497 #endif // BUILDFLAG(USE_CUPS)
500 mojom::ResultCode PrintingContextChromeos::DocumentDone() {
502 return mojom::ResultCode::kCanceled;
504 DCHECK(in_print_job_);
506 if (!printer_->FinishDocument()) {
507 LOG(WARNING) << "Finishing document failed";
511 ipp_status_t job_status = printer_->CloseJob(job_id_, username_);
514 if (job_status != IPP_STATUS_OK) {
515 LOG(WARNING) << "Closing job failed";
520 return mojom::ResultCode::kSuccess;
523 void PrintingContextChromeos::Cancel() {
524 abort_printing_ = true;
525 in_print_job_ = false;
528 void PrintingContextChromeos::ReleaseContext() {
532 printing::NativeDrawingContext PrintingContextChromeos::context() const {
533 // Intentional No-op.
537 mojom::ResultCode PrintingContextChromeos::StreamData(
538 const std::vector<char>& buffer) {
540 return mojom::ResultCode::kCanceled;
542 DCHECK(in_print_job_);
545 if (!printer_->StreamData(buffer))
548 return mojom::ResultCode::kSuccess;
551 } // namespace printing