Enable chrome with aura for tizen
[platform/framework/web/chromium-efl.git] / printing / printing_context_chromeos.cc
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.
4
5 #include "printing/printing_context_chromeos.h"
6
7 #include <cups/cups.h>
8 #include <stdint.h>
9 #include <unicode/ulocdata.h>
10
11 #include <map>
12 #include <memory>
13 #include <utility>
14 #include <vector>
15
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"
32
33 namespace printing {
34
35 namespace {
36
37 // We only support sending username for secure printers.
38 const char kUsernamePlaceholder[] = "chronos";
39
40 // We only support sending document name for secure printers.
41 const char kDocumentNamePlaceholder[] = "-";
42
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:");
46 }
47
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;
51   int num_options = 0;
52   // Use cupsAddOption so that the pair of malloc and free are used.
53   num_options =
54       cupsAddOption(name.c_str(), value.c_str(), num_options, &cups_option);
55   DCHECK(cups_option);
56   DCHECK_EQ(num_options, 1);
57   return ScopedCupsOption(cups_option);
58 }
59
60 std::string GetCollateString(bool collate) {
61   return collate ? kCollated : kUncollated;
62 }
63
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);
68 }
69
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`.
78   int left_bound =
79       PwgUnitsToDeviceUnits(margins.left, micrometers_per_device_unit);
80   int bottom_bound =
81       PwgUnitsToDeviceUnits(margins.bottom, micrometers_per_device_unit);
82   int right_bound =
83       PwgUnitsToDeviceUnits(margins.right, micrometers_per_device_unit);
84   int top_bound =
85       PwgUnitsToDeviceUnits(margins.top, micrometers_per_device_unit);
86
87   // These values express the bounding box of the printable area on the
88   // page.
89   int printable_width = media_size.width() - (left_bound + right_bound);
90   int printable_height = media_size.height() - (top_bound + bottom_bound);
91
92   if (printable_width > 0 && printable_height > 0) {
93     return {left_bound, bottom_bound, printable_width, printable_height};
94   }
95
96   return {0, 0, media_size.width(), media_size.height()};
97 }
98
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);
108
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);
113   }
114 }
115
116 }  // namespace
117
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;
124       break;
125     case mojom::DuplexMode::kLongEdge:
126       sides = CUPS_SIDES_TWO_SIDED_PORTRAIT;
127       break;
128     case mojom::DuplexMode::kShortEdge:
129       sides = CUPS_SIDES_TWO_SIDED_LANDSCAPE;
130       break;
131     default:
132       NOTREACHED();
133   }
134
135   std::vector<ScopedCupsOption> options;
136   options.push_back(
137       ConstructOption(kIppColor,
138                       GetIppColorModelForModel(settings.color())));  // color
139   options.push_back(ConstructOption(kIppDuplex, sides));  // duplexing
140   options.push_back(
141       ConstructOption(kIppMedia,
142                       settings.requested_media().vendor_id));  // paper size
143   options.push_back(
144       ConstructOption(kIppCopies,
145                       base::NumberToString(settings.copies())));  // copies
146   options.push_back(
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));
152   }
153
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"));
159   }
160
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();
165     if (value.empty())
166       continue;
167
168     // Check for multivalue enum ("attribute/value").
169     size_t pos = key.find('/');
170     if (pos == std::string::npos) {
171       // Regular value.
172       options.push_back(ConstructOption(key, value));
173       continue;
174     }
175     // Store selected enum values.
176     if (value == kOptionTrue)
177       multival[key.substr(0, pos)].push_back(key.substr(pos + 1));
178   }
179
180   // Pass multivalue enums as comma-separated lists.
181   for (const auto& it : multival) {
182     options.push_back(
183         ConstructOption(it.first, base::JoinString(it.second, ",")));
184   }
185
186   return options;
187 }
188
189 // static
190 std::unique_ptr<PrintingContext> PrintingContext::CreateImpl(
191     Delegate* delegate,
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();
197 #endif
198   return context;
199 }
200
201 // static
202 std::unique_ptr<PrintingContextChromeos>
203 PrintingContextChromeos::CreateForTesting(
204     Delegate* delegate,
205     std::unique_ptr<CupsConnection> connection) {
206   // Private ctor.
207   return base::WrapUnique(
208       new PrintingContextChromeos(delegate, std::move(connection)));
209 }
210
211 PrintingContextChromeos::PrintingContextChromeos(Delegate* delegate)
212     : PrintingContext(delegate),
213       connection_(CupsConnection::Create(GURL(), HTTP_ENCRYPT_NEVER, true)) {}
214
215 PrintingContextChromeos::PrintingContextChromeos(
216     Delegate* delegate,
217     std::unique_ptr<CupsConnection> connection)
218     : PrintingContext(delegate), connection_(std::move(connection)) {}
219
220 PrintingContextChromeos::~PrintingContextChromeos() {
221   ReleaseContext();
222 }
223
224 void PrintingContextChromeos::AskUserForSettings(
225     int max_pages,
226     bool has_selection,
227     bool is_scripted,
228     PrintSettingsCallback callback) {
229   // We don't want to bring up a dialog here.  Ever.  This should not be called.
230   NOTREACHED();
231 }
232
233 mojom::ResultCode PrintingContextChromeos::UseDefaultSettings() {
234   DCHECK(!in_print_job_);
235
236   ResetSettings();
237
238   std::string device_name = base::UTF16ToUTF8(settings_->device_name());
239   if (device_name.empty())
240     return OnError();
241
242   // TODO(skau): https://crbug.com/613779. See UpdatePrinterSettings for more
243   // info.
244   if (settings_->dpi() == 0) {
245     DVLOG(1) << "Using Default DPI";
246     settings_->set_dpi(kDefaultPdfDpi);
247   }
248
249   // Retrieve device information and set it
250   if (InitializeDevice(device_name) != mojom::ResultCode::kSuccess) {
251     LOG(ERROR) << "Could not initialize printer";
252     return OnError();
253   }
254
255   // Set printable area
256   DCHECK(printer_);
257   PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_);
258
259   PrintSettings::RequestedMedia media;
260   media.vendor_id = paper.vendor_id;
261   media.size_microns = paper.size_um;
262   settings_->set_requested_media(media);
263
264   CupsPrinter::CupsMediaMargins margins =
265       printer_->GetMediaMarginsByName(paper.vendor_id);
266   SetPrintableArea(settings_.get(), media, margins);
267
268   return mojom::ResultCode::kSuccess;
269 }
270
271 gfx::Size PrintingContextChromeos::GetPdfPaperSizeDeviceUnits() {
272   int32_t width = 0;
273   int32_t height = 0;
274   UErrorCode error = U_ZERO_ERROR;
275   ulocdata_getPaperSize(delegate_->GetAppLocale().c_str(), &height, &width,
276                         &error);
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: "
280                  << error;
281     width =
282         static_cast<int>(kLetterWidthInch * settings_->device_units_per_inch());
283     height = static_cast<int>(kLetterHeightInch *
284                               settings_->device_units_per_inch());
285   } else {
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;
289     width *= multiplier;
290     height *= multiplier;
291   }
292   return gfx::Size(width, height);
293 }
294
295 mojom::ResultCode PrintingContextChromeos::UpdatePrinterSettings(
296     const PrinterSettings& printer_settings) {
297   DCHECK(!printer_settings.show_system_dialog);
298
299   if (InitializeDevice(base::UTF16ToUTF8(settings_->device_name())) !=
300       mojom::ResultCode::kSuccess) {
301     return OnError();
302   }
303
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);
311   }
312
313   // compute paper size
314   PrintSettings::RequestedMedia media = settings_->requested_media();
315
316   DCHECK(printer_);
317   if (media.IsDefault()) {
318     PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_);
319
320     media.vendor_id = paper.vendor_id;
321     media.size_microns = paper.size_um;
322     settings_->set_requested_media(media);
323   }
324
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_) {
331     DCHECK(printer_);
332     username_ = IsUriSecure(printer_->GetUri()) ? settings_->username()
333                                                 : kUsernamePlaceholder;
334   }
335
336   return mojom::ResultCode::kSuccess;
337 }
338
339 mojom::ResultCode PrintingContextChromeos::InitializeDevice(
340     const std::string& device) {
341   DCHECK(!in_print_job_);
342
343   std::unique_ptr<CupsPrinter> printer = connection_->GetPrinter(device);
344   if (!printer) {
345     LOG(WARNING) << "Could not initialize device";
346     return OnError();
347   }
348
349   printer_ = std::move(printer);
350
351   return mojom::ResultCode::kSuccess;
352 }
353
354 mojom::ResultCode PrintingContextChromeos::NewDocument(
355     const std::u16string& document_name) {
356   DCHECK(!in_print_job_);
357   in_print_job_ = true;
358
359   if (skip_system_calls())
360     return mojom::ResultCode::kSuccess;
361
362   std::string converted_name;
363   if (send_user_info_) {
364     DCHECK(printer_);
365     converted_name = IsUriSecure(printer_->GetUri())
366                          ? base::UTF16ToUTF8(document_name)
367                          : kDocumentNamePlaceholder;
368   }
369
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()));
374     } else {
375       DVLOG(1) << "Unsupported option skipped " << option->name << ", "
376                << option->value;
377     }
378   }
379
380   ipp_status_t create_status =
381       printer_->CreateJob(&job_id_, converted_name, username_, options);
382
383   if (job_id_ == 0) {
384     DLOG(WARNING) << "Creating cups job failed"
385                   << ippErrorString(create_status);
386     return OnError();
387   }
388
389   // we only send one document, so it's always the last one
390   if (!printer_->StartDocument(job_id_, converted_name, true, username_,
391                                options)) {
392     LOG(ERROR) << "Starting document failed";
393     return OnError();
394   }
395
396   return mojom::ResultCode::kSuccess;
397 }
398
399 mojom::ResultCode PrintingContextChromeos::PrintDocument(
400     const MetafilePlayer& metafile,
401     const PrintSettings& settings,
402     uint32_t num_pages) {
403   if (abort_printing_)
404     return mojom::ResultCode::kCanceled;
405   DCHECK(in_print_job_);
406
407 #if defined(USE_CUPS)
408   std::vector<char> buffer;
409   if (!metafile.GetDataAsVector(&buffer))
410     return mojom::ResultCode::kFailed;
411
412   return StreamData(buffer);
413 #else
414   NOTREACHED();
415   return mojom::ResultCode::kFailed;
416 #endif  // defined(USE_CUPS)
417 }
418
419 mojom::ResultCode PrintingContextChromeos::DocumentDone() {
420   if (abort_printing_)
421     return mojom::ResultCode::kCanceled;
422
423   DCHECK(in_print_job_);
424
425   if (!printer_->FinishDocument()) {
426     LOG(WARNING) << "Finishing document failed";
427     return OnError();
428   }
429
430   ipp_status_t job_status = printer_->CloseJob(job_id_, username_);
431   job_id_ = 0;
432
433   if (job_status != IPP_STATUS_OK) {
434     LOG(WARNING) << "Closing job failed";
435     return OnError();
436   }
437
438   ResetSettings();
439   return mojom::ResultCode::kSuccess;
440 }
441
442 void PrintingContextChromeos::Cancel() {
443   abort_printing_ = true;
444   in_print_job_ = false;
445 }
446
447 void PrintingContextChromeos::ReleaseContext() {
448   printer_.reset();
449 }
450
451 printing::NativeDrawingContext PrintingContextChromeos::context() const {
452   // Intentional No-op.
453   return nullptr;
454 }
455
456 mojom::ResultCode PrintingContextChromeos::StreamData(
457     const std::vector<char>& buffer) {
458   if (abort_printing_)
459     return mojom::ResultCode::kCanceled;
460
461   DCHECK(in_print_job_);
462   DCHECK(printer_);
463
464   if (!printer_->StreamData(buffer))
465     return OnError();
466
467   return mojom::ResultCode::kSuccess;
468 }
469
470 }  // namespace printing