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/buildflags/buildflags.h"
26 #include "printing/metafile.h"
27 #include "printing/mojom/print.mojom.h"
28 #include "printing/print_job_constants.h"
29 #include "printing/print_settings.h"
30 #include "printing/printing_utils.h"
31 #include "printing/units.h"
37 // We only support sending username for secure printers.
38 const char kUsernamePlaceholder[] = "chronos";
40 // We only support sending document name for secure printers.
41 const char kDocumentNamePlaceholder[] = "-";
43 bool IsUriSecure(base::StringPiece uri) {
44 return base::StartsWith(uri, "ipps:") || base::StartsWith(uri, "https:") ||
45 base::StartsWith(uri, "usb:") || base::StartsWith(uri, "ippusb:");
48 ScopedCupsOption ConstructOption(std::string name, std::string value) {
49 // ScopedCupsOption frees the name and value buffers on deletion
50 cups_option_t* cups_option = nullptr;
52 // Use cupsAddOption so that the pair of malloc and free are used.
54 cupsAddOption(name.c_str(), value.c_str(), num_options, &cups_option);
56 DCHECK_EQ(num_options, 1);
57 return ScopedCupsOption(cups_option);
60 std::string GetCollateString(bool collate) {
61 return collate ? kCollated : kUncollated;
64 // Given an integral `value` expressed in PWG units (1/100 mm), returns
65 // the same value expressed in device units.
66 int PwgUnitsToDeviceUnits(int value, float micrometers_per_device_unit) {
67 return ConvertUnitFloat(value, micrometers_per_device_unit, 10);
70 // Given a `media_size`, the specification of the media's `margins`, and
71 // the number of micrometers per device unit, returns the rectangle
72 // bounding the apparent printable area of said media.
73 gfx::Rect RepresentPrintableArea(const gfx::Size& media_size,
74 const CupsPrinter::CupsMediaMargins& margins,
75 float micrometers_per_device_unit) {
76 // These values express inward encroachment by margins, away from the
77 // edges of the `media_size`.
79 PwgUnitsToDeviceUnits(margins.left, micrometers_per_device_unit);
81 PwgUnitsToDeviceUnits(margins.bottom, micrometers_per_device_unit);
83 PwgUnitsToDeviceUnits(margins.right, micrometers_per_device_unit);
85 PwgUnitsToDeviceUnits(margins.top, micrometers_per_device_unit);
87 // These values express the bounding box of the printable area on the
89 int printable_width = media_size.width() - (left_bound + right_bound);
90 int printable_height = media_size.height() - (top_bound + bottom_bound);
92 if (printable_width > 0 && printable_height > 0) {
93 return {left_bound, bottom_bound, printable_width, printable_height};
96 return {0, 0, media_size.width(), media_size.height()};
99 void SetPrintableArea(PrintSettings* settings,
100 const PrintSettings::RequestedMedia& media,
101 const CupsPrinter::CupsMediaMargins& margins) {
102 if (!media.size_microns.IsEmpty()) {
103 float device_microns_per_device_unit =
104 static_cast<float>(kMicronsPerInch) / settings->device_units_per_inch();
105 gfx::Size paper_size =
106 gfx::Size(media.size_microns.width() / device_microns_per_device_unit,
107 media.size_microns.height() / device_microns_per_device_unit);
109 gfx::Rect paper_rect = RepresentPrintableArea(
110 paper_size, margins, device_microns_per_device_unit);
111 settings->SetPrinterPrintableArea(paper_size, paper_rect,
112 /*landscape_needs_flip=*/true);
118 std::vector<ScopedCupsOption> SettingsToCupsOptions(
119 const PrintSettings& settings) {
120 const char* sides = nullptr;
121 switch (settings.duplex_mode()) {
122 case mojom::DuplexMode::kSimplex:
123 sides = CUPS_SIDES_ONE_SIDED;
125 case mojom::DuplexMode::kLongEdge:
126 sides = CUPS_SIDES_TWO_SIDED_PORTRAIT;
128 case mojom::DuplexMode::kShortEdge:
129 sides = CUPS_SIDES_TWO_SIDED_LANDSCAPE;
135 std::vector<ScopedCupsOption> options;
137 ConstructOption(kIppColor,
138 GetIppColorModelForModel(settings.color()))); // color
139 options.push_back(ConstructOption(kIppDuplex, sides)); // duplexing
141 ConstructOption(kIppMedia,
142 settings.requested_media().vendor_id)); // paper size
144 ConstructOption(kIppCopies,
145 base::NumberToString(settings.copies()))); // copies
147 ConstructOption(kIppCollate,
148 GetCollateString(settings.collate()))); // collate
149 if (!settings.pin_value().empty()) {
150 options.push_back(ConstructOption(kIppPin, settings.pin_value()));
151 options.push_back(ConstructOption(kIppPinEncryption, kPinEncryptionNone));
154 if (settings.dpi_horizontal() > 0 && settings.dpi_vertical() > 0) {
155 std::string dpi = base::NumberToString(settings.dpi_horizontal());
156 if (settings.dpi_horizontal() != settings.dpi_vertical())
157 dpi += "x" + base::NumberToString(settings.dpi_vertical());
158 options.push_back(ConstructOption(kIppResolution, dpi + "dpi"));
161 std::map<std::string, std::vector<std::string>> multival;
162 for (const auto& setting : settings.advanced_settings()) {
163 const std::string& key = setting.first;
164 const std::string& value = setting.second.GetString();
168 // Check for multivalue enum ("attribute/value").
169 size_t pos = key.find('/');
170 if (pos == std::string::npos) {
172 options.push_back(ConstructOption(key, value));
175 // Store selected enum values.
176 if (value == kOptionTrue)
177 multival[key.substr(0, pos)].push_back(key.substr(pos + 1));
180 // Pass multivalue enums as comma-separated lists.
181 for (const auto& it : multival) {
183 ConstructOption(it.first, base::JoinString(it.second, ",")));
190 std::unique_ptr<PrintingContext> PrintingContext::CreateImpl(
192 bool skip_system_calls) {
193 auto context = std::make_unique<PrintingContextChromeos>(delegate);
194 #if BUILDFLAG(ENABLE_OOP_PRINTING)
195 if (skip_system_calls)
196 context->set_skip_system_calls();
202 std::unique_ptr<PrintingContextChromeos>
203 PrintingContextChromeos::CreateForTesting(
205 std::unique_ptr<CupsConnection> connection) {
207 return base::WrapUnique(
208 new PrintingContextChromeos(delegate, std::move(connection)));
211 PrintingContextChromeos::PrintingContextChromeos(Delegate* delegate)
212 : PrintingContext(delegate),
213 connection_(CupsConnection::Create(GURL(), HTTP_ENCRYPT_NEVER, true)) {}
215 PrintingContextChromeos::PrintingContextChromeos(
217 std::unique_ptr<CupsConnection> connection)
218 : PrintingContext(delegate), connection_(std::move(connection)) {}
220 PrintingContextChromeos::~PrintingContextChromeos() {
224 void PrintingContextChromeos::AskUserForSettings(
228 PrintSettingsCallback callback) {
229 // We don't want to bring up a dialog here. Ever. This should not be called.
233 mojom::ResultCode PrintingContextChromeos::UseDefaultSettings() {
234 DCHECK(!in_print_job_);
238 std::string device_name = base::UTF16ToUTF8(settings_->device_name());
239 if (device_name.empty())
242 // TODO(skau): https://crbug.com/613779. See UpdatePrinterSettings for more
244 if (settings_->dpi() == 0) {
245 DVLOG(1) << "Using Default DPI";
246 settings_->set_dpi(kDefaultPdfDpi);
249 // Retrieve device information and set it
250 if (InitializeDevice(device_name) != mojom::ResultCode::kSuccess) {
251 LOG(ERROR) << "Could not initialize printer";
255 // Set printable area
257 PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_);
259 PrintSettings::RequestedMedia media;
260 media.vendor_id = paper.vendor_id;
261 media.size_microns = paper.size_um;
262 settings_->set_requested_media(media);
264 CupsPrinter::CupsMediaMargins margins =
265 printer_->GetMediaMarginsByName(paper.vendor_id);
266 SetPrintableArea(settings_.get(), media, margins);
268 return mojom::ResultCode::kSuccess;
271 gfx::Size PrintingContextChromeos::GetPdfPaperSizeDeviceUnits() {
274 UErrorCode error = U_ZERO_ERROR;
275 ulocdata_getPaperSize(delegate_->GetAppLocale().c_str(), &height, &width,
277 if (error > U_ZERO_ERROR) {
278 // If the call failed, assume a paper size of 8.5 x 11 inches.
279 LOG(WARNING) << "ulocdata_getPaperSize failed, using 8.5 x 11, error: "
282 static_cast<int>(kLetterWidthInch * settings_->device_units_per_inch());
283 height = static_cast<int>(kLetterHeightInch *
284 settings_->device_units_per_inch());
286 // ulocdata_getPaperSize returns the width and height in mm.
287 // Convert this to pixels based on the dpi.
288 float multiplier = settings_->device_units_per_inch() / kMicronsPerMil;
290 height *= multiplier;
292 return gfx::Size(width, height);
295 mojom::ResultCode PrintingContextChromeos::UpdatePrinterSettings(
296 const PrinterSettings& printer_settings) {
297 DCHECK(!printer_settings.show_system_dialog);
299 if (InitializeDevice(base::UTF16ToUTF8(settings_->device_name())) !=
300 mojom::ResultCode::kSuccess) {
304 // TODO(skau): Convert to DCHECK when https://crbug.com/613779 is resolved
305 // Print quality suffers when this is set to the resolution reported by the
306 // printer but print quality is fine at this resolution. UseDefaultSettings
307 // exhibits the same problem.
308 if (settings_->dpi() == 0) {
309 DVLOG(1) << "Using Default DPI";
310 settings_->set_dpi(kDefaultPdfDpi);
313 // compute paper size
314 PrintSettings::RequestedMedia media = settings_->requested_media();
317 if (media.IsDefault()) {
318 PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_);
320 media.vendor_id = paper.vendor_id;
321 media.size_microns = paper.size_um;
322 settings_->set_requested_media(media);
325 CupsPrinter::CupsMediaMargins margins =
326 printer_->GetMediaMarginsByName(media.vendor_id);
327 SetPrintableArea(settings_.get(), media, margins);
328 cups_options_ = SettingsToCupsOptions(*settings_);
329 send_user_info_ = settings_->send_user_info();
330 if (send_user_info_) {
332 username_ = IsUriSecure(printer_->GetUri()) ? settings_->username()
333 : kUsernamePlaceholder;
336 return mojom::ResultCode::kSuccess;
339 mojom::ResultCode PrintingContextChromeos::InitializeDevice(
340 const std::string& device) {
341 DCHECK(!in_print_job_);
343 std::unique_ptr<CupsPrinter> printer = connection_->GetPrinter(device);
345 LOG(WARNING) << "Could not initialize device";
349 printer_ = std::move(printer);
351 return mojom::ResultCode::kSuccess;
354 mojom::ResultCode PrintingContextChromeos::NewDocument(
355 const std::u16string& document_name) {
356 DCHECK(!in_print_job_);
357 in_print_job_ = true;
359 if (skip_system_calls())
360 return mojom::ResultCode::kSuccess;
362 std::string converted_name;
363 if (send_user_info_) {
365 converted_name = IsUriSecure(printer_->GetUri())
366 ? base::UTF16ToUTF8(document_name)
367 : kDocumentNamePlaceholder;
370 std::vector<cups_option_t> options;
371 for (const ScopedCupsOption& option : cups_options_) {
372 if (printer_->CheckOptionSupported(option->name, option->value)) {
373 options.push_back(*(option.get()));
375 DVLOG(1) << "Unsupported option skipped " << option->name << ", "
380 ipp_status_t create_status =
381 printer_->CreateJob(&job_id_, converted_name, username_, options);
384 DLOG(WARNING) << "Creating cups job failed"
385 << ippErrorString(create_status);
389 // we only send one document, so it's always the last one
390 if (!printer_->StartDocument(job_id_, converted_name, true, username_,
392 LOG(ERROR) << "Starting document failed";
396 return mojom::ResultCode::kSuccess;
399 mojom::ResultCode PrintingContextChromeos::PrintDocument(
400 const MetafilePlayer& metafile,
401 const PrintSettings& settings,
402 uint32_t num_pages) {
404 return mojom::ResultCode::kCanceled;
405 DCHECK(in_print_job_);
407 #if defined(USE_CUPS)
408 std::vector<char> buffer;
409 if (!metafile.GetDataAsVector(&buffer))
410 return mojom::ResultCode::kFailed;
412 return StreamData(buffer);
415 return mojom::ResultCode::kFailed;
416 #endif // defined(USE_CUPS)
419 mojom::ResultCode PrintingContextChromeos::DocumentDone() {
421 return mojom::ResultCode::kCanceled;
423 DCHECK(in_print_job_);
425 if (!printer_->FinishDocument()) {
426 LOG(WARNING) << "Finishing document failed";
430 ipp_status_t job_status = printer_->CloseJob(job_id_, username_);
433 if (job_status != IPP_STATUS_OK) {
434 LOG(WARNING) << "Closing job failed";
439 return mojom::ResultCode::kSuccess;
442 void PrintingContextChromeos::Cancel() {
443 abort_printing_ = true;
444 in_print_job_ = false;
447 void PrintingContextChromeos::ReleaseContext() {
451 printing::NativeDrawingContext PrintingContextChromeos::context() const {
452 // Intentional No-op.
456 mojom::ResultCode PrintingContextChromeos::StreamData(
457 const std::vector<char>& buffer) {
459 return mojom::ResultCode::kCanceled;
461 DCHECK(in_print_job_);
464 if (!printer_->StreamData(buffer))
467 return mojom::ResultCode::kSuccess;
470 } // namespace printing