skau | b793195 | 2016-07-27 18:04:51 | [diff] [blame] | 1 | // Copyright 2016 The Chromium Authors. All rights reserved. |
| 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 <memory> |
| 12 | #include <utility> |
| 13 | #include <vector> |
| 14 | |
| 15 | #include "base/logging.h" |
| 16 | #include "base/memory/ptr_util.h" |
| 17 | #include "base/strings/string_number_conversions.h" |
| 18 | #include "base/strings/string_util.h" |
| 19 | #include "base/strings/stringprintf.h" |
| 20 | #include "base/strings/utf_string_conversions.h" |
| 21 | #include "printing/backend/cups_connection.h" |
| 22 | #include "printing/backend/cups_ipp_util.h" |
| 23 | #include "printing/backend/cups_printer.h" |
| 24 | #include "printing/metafile.h" |
| 25 | #include "printing/print_job_constants.h" |
| 26 | #include "printing/print_settings.h" |
| 27 | #include "printing/printing_context_no_system_dialog.h" |
| 28 | #include "printing/units.h" |
| 29 | |
| 30 | namespace printing { |
| 31 | |
| 32 | namespace { |
| 33 | |
| 34 | using ScopedCupsOption = std::unique_ptr<cups_option_t, OptionDeleter>; |
| 35 | |
| 36 | // convert from a ColorMode setting to a print-color-mode value from PWG 5100.13 |
| 37 | const char* GetColorModelForMode(int color_mode) { |
| 38 | const char* mode_string; |
| 39 | switch (color_mode) { |
| 40 | case COLOR: |
| 41 | case CMYK: |
| 42 | case CMY: |
| 43 | case KCMY: |
| 44 | case CMY_K: |
| 45 | case RGB: |
| 46 | case RGB16: |
| 47 | case RGBA: |
| 48 | case COLORMODE_COLOR: |
thestig | ca4af0b | 2017-03-15 18:28:48 | [diff] [blame] | 49 | case BROTHER_CUPS_COLOR: |
| 50 | case BROTHER_BRSCRIPT3_COLOR: |
skau | b793195 | 2016-07-27 18:04:51 | [diff] [blame] | 51 | case HP_COLOR_COLOR: |
| 52 | case PRINTOUTMODE_NORMAL: |
| 53 | case PROCESSCOLORMODEL_CMYK: |
| 54 | case PROCESSCOLORMODEL_RGB: |
| 55 | mode_string = CUPS_PRINT_COLOR_MODE_COLOR; |
| 56 | break; |
| 57 | case GRAY: |
| 58 | case BLACK: |
| 59 | case GRAYSCALE: |
| 60 | case COLORMODE_MONOCHROME: |
thestig | ca4af0b | 2017-03-15 18:28:48 | [diff] [blame] | 61 | case BROTHER_CUPS_MONO: |
| 62 | case BROTHER_BRSCRIPT3_BLACK: |
skau | b793195 | 2016-07-27 18:04:51 | [diff] [blame] | 63 | case HP_COLOR_BLACK: |
| 64 | case PRINTOUTMODE_NORMAL_GRAY: |
| 65 | case PROCESSCOLORMODEL_GREYSCALE: |
| 66 | mode_string = CUPS_PRINT_COLOR_MODE_MONOCHROME; |
| 67 | break; |
| 68 | default: |
| 69 | mode_string = nullptr; |
| 70 | LOG(WARNING) << "Unrecognized color mode"; |
| 71 | break; |
| 72 | } |
| 73 | |
| 74 | return mode_string; |
| 75 | } |
| 76 | |
| 77 | // Returns a new char buffer which is a null-terminated copy of |value|. The |
| 78 | // caller owns the returned string. |
| 79 | char* DuplicateString(const base::StringPiece value) { |
| 80 | char* dst = new char[value.size() + 1]; |
| 81 | value.copy(dst, value.size()); |
| 82 | dst[value.size()] = '\0'; |
| 83 | return dst; |
| 84 | } |
| 85 | |
| 86 | ScopedCupsOption ConstructOption(const base::StringPiece name, |
| 87 | const base::StringPiece value) { |
| 88 | // ScopedCupsOption frees the name and value buffers on deletion |
| 89 | ScopedCupsOption option = ScopedCupsOption(new cups_option_t); |
| 90 | option->name = DuplicateString(name); |
| 91 | option->value = DuplicateString(value); |
| 92 | return option; |
| 93 | } |
| 94 | |
| 95 | base::StringPiece GetCollateString(bool collate) { |
| 96 | return collate ? kCollated : kUncollated; |
| 97 | } |
| 98 | |
| 99 | std::vector<ScopedCupsOption> SettingsToCupsOptions( |
| 100 | const PrintSettings& settings) { |
| 101 | const char* sides = nullptr; |
| 102 | switch (settings.duplex_mode()) { |
| 103 | case SIMPLEX: |
| 104 | sides = CUPS_SIDES_ONE_SIDED; |
| 105 | break; |
| 106 | case LONG_EDGE: |
| 107 | sides = CUPS_SIDES_TWO_SIDED_PORTRAIT; |
| 108 | break; |
| 109 | case SHORT_EDGE: |
| 110 | sides = CUPS_SIDES_TWO_SIDED_LANDSCAPE; |
| 111 | break; |
| 112 | default: |
| 113 | NOTREACHED(); |
| 114 | } |
| 115 | |
| 116 | std::vector<ScopedCupsOption> options; |
| 117 | options.push_back( |
| 118 | ConstructOption(kIppColor, |
| 119 | GetColorModelForMode(settings.color()))); // color |
| 120 | options.push_back(ConstructOption(kIppDuplex, sides)); // duplexing |
| 121 | options.push_back( |
| 122 | ConstructOption(kIppMedia, |
| 123 | settings.requested_media().vendor_id)); // paper size |
| 124 | options.push_back( |
| 125 | ConstructOption(kIppCopies, |
| 126 | base::IntToString(settings.copies()))); // copies |
| 127 | options.push_back( |
| 128 | ConstructOption(kIppCollate, |
| 129 | GetCollateString(settings.collate()))); // collate |
| 130 | |
| 131 | return options; |
| 132 | } |
| 133 | |
| 134 | void SetPrintableArea(PrintSettings* settings, |
| 135 | const PrintSettings::RequestedMedia& media, |
| 136 | bool flip) { |
| 137 | if (!media.size_microns.IsEmpty()) { |
| 138 | float deviceMicronsPerDeviceUnit = |
| 139 | (kHundrethsMMPerInch * 10.0f) / settings->device_units_per_inch(); |
| 140 | gfx::Size paper_size = |
| 141 | gfx::Size(media.size_microns.width() / deviceMicronsPerDeviceUnit, |
| 142 | media.size_microns.height() / deviceMicronsPerDeviceUnit); |
| 143 | |
| 144 | gfx::Rect paper_rect(0, 0, paper_size.width(), paper_size.height()); |
| 145 | settings->SetPrinterPrintableArea(paper_size, paper_rect, flip); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | } // namespace |
| 150 | |
| 151 | // static |
| 152 | std::unique_ptr<PrintingContext> PrintingContext::Create(Delegate* delegate) { |
| 153 | if (PrintBackend::GetNativeCupsEnabled()) |
| 154 | return base::MakeUnique<PrintingContextChromeos>(delegate); |
| 155 | |
| 156 | return base::MakeUnique<PrintingContextNoSystemDialog>(delegate); |
| 157 | } |
| 158 | |
| 159 | PrintingContextChromeos::PrintingContextChromeos(Delegate* delegate) |
| 160 | : PrintingContext(delegate), |
| 161 | connection_(GURL(), HTTP_ENCRYPT_NEVER, true) {} |
| 162 | |
| 163 | PrintingContextChromeos::~PrintingContextChromeos() { |
| 164 | ReleaseContext(); |
| 165 | } |
| 166 | |
| 167 | void PrintingContextChromeos::AskUserForSettings( |
| 168 | int max_pages, |
| 169 | bool has_selection, |
| 170 | bool is_scripted, |
| 171 | const PrintSettingsCallback& callback) { |
skau | 03b6108c | 2016-10-05 01:52:29 | [diff] [blame] | 172 | // We don't want to bring up a dialog here. Ever. This should not be called. |
| 173 | NOTREACHED(); |
skau | b793195 | 2016-07-27 18:04:51 | [diff] [blame] | 174 | } |
| 175 | |
| 176 | PrintingContext::Result PrintingContextChromeos::UseDefaultSettings() { |
| 177 | DCHECK(!in_print_job_); |
| 178 | |
| 179 | ResetSettings(); |
| 180 | |
| 181 | std::string device_name = base::UTF16ToUTF8(settings_.device_name()); |
| 182 | if (device_name.empty()) |
| 183 | return OnError(); |
| 184 | |
| 185 | // TODO(skau): https://crbug.com/613779. See UpdatePrinterSettings for more |
| 186 | // info. |
| 187 | if (settings_.dpi() == 0) { |
| 188 | DVLOG(1) << "Using Default DPI"; |
| 189 | settings_.set_dpi(kDefaultPdfDpi); |
| 190 | } |
| 191 | |
| 192 | // Retrieve device information and set it |
| 193 | if (InitializeDevice(device_name) != OK) { |
| 194 | LOG(ERROR) << "Could not initialize printer"; |
| 195 | return OnError(); |
| 196 | } |
| 197 | |
| 198 | // Set printable area |
| 199 | DCHECK(printer_); |
| 200 | PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_); |
| 201 | |
| 202 | PrintSettings::RequestedMedia media; |
| 203 | media.vendor_id = paper.vendor_id; |
| 204 | media.size_microns = paper.size_um; |
| 205 | settings_.set_requested_media(media); |
| 206 | |
| 207 | SetPrintableArea(&settings_, media, true /* flip landscape */); |
| 208 | |
| 209 | return OK; |
| 210 | } |
| 211 | |
| 212 | gfx::Size PrintingContextChromeos::GetPdfPaperSizeDeviceUnits() { |
| 213 | int32_t width = 0; |
| 214 | int32_t height = 0; |
| 215 | UErrorCode error = U_ZERO_ERROR; |
| 216 | ulocdata_getPaperSize(delegate_->GetAppLocale().c_str(), &height, &width, |
| 217 | &error); |
| 218 | if (error > U_ZERO_ERROR) { |
| 219 | // If the call failed, assume a paper size of 8.5 x 11 inches. |
| 220 | LOG(WARNING) << "ulocdata_getPaperSize failed, using 8.5 x 11, error: " |
| 221 | << error; |
| 222 | width = |
| 223 | static_cast<int>(kLetterWidthInch * settings_.device_units_per_inch()); |
| 224 | height = |
| 225 | static_cast<int>(kLetterHeightInch * settings_.device_units_per_inch()); |
| 226 | } else { |
| 227 | // ulocdata_getPaperSize returns the width and height in mm. |
| 228 | // Convert this to pixels based on the dpi. |
| 229 | float multiplier = 100 * settings_.device_units_per_inch(); |
| 230 | multiplier /= kHundrethsMMPerInch; |
| 231 | width *= multiplier; |
| 232 | height *= multiplier; |
| 233 | } |
| 234 | return gfx::Size(width, height); |
| 235 | } |
| 236 | |
| 237 | PrintingContext::Result PrintingContextChromeos::UpdatePrinterSettings( |
| 238 | bool external_preview, |
| 239 | bool show_system_dialog, |
| 240 | int page_count) { |
| 241 | DCHECK(!show_system_dialog); |
| 242 | |
| 243 | if (InitializeDevice(base::UTF16ToUTF8(settings_.device_name())) != OK) |
| 244 | return OnError(); |
| 245 | |
| 246 | // TODO(skau): Convert to DCHECK when https://crbug.com/613779 is resolved |
| 247 | // Print quality suffers when this is set to the resolution reported by the |
| 248 | // printer but print quality is fine at this resolution. UseDefaultSettings |
| 249 | // exhibits the same problem. |
| 250 | if (settings_.dpi() == 0) { |
| 251 | DVLOG(1) << "Using Default DPI"; |
| 252 | settings_.set_dpi(kDefaultPdfDpi); |
| 253 | } |
| 254 | |
| 255 | // compute paper size |
| 256 | PrintSettings::RequestedMedia media = settings_.requested_media(); |
| 257 | |
| 258 | if (media.IsDefault()) { |
| 259 | DCHECK(printer_); |
| 260 | PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_); |
| 261 | |
| 262 | media.vendor_id = paper.vendor_id; |
| 263 | media.size_microns = paper.size_um; |
| 264 | settings_.set_requested_media(media); |
| 265 | } |
| 266 | |
| 267 | SetPrintableArea(&settings_, media, true); |
| 268 | |
| 269 | return OK; |
| 270 | } |
| 271 | |
| 272 | PrintingContext::Result PrintingContextChromeos::InitializeDevice( |
| 273 | const std::string& device) { |
| 274 | DCHECK(!in_print_job_); |
| 275 | |
| 276 | std::unique_ptr<CupsPrinter> printer = connection_.GetPrinter(device); |
| 277 | if (!printer) { |
| 278 | LOG(WARNING) << "Could not initialize device"; |
| 279 | return OnError(); |
| 280 | } |
| 281 | |
| 282 | printer_ = std::move(printer); |
| 283 | |
| 284 | return OK; |
| 285 | } |
| 286 | |
skau | b793195 | 2016-07-27 18:04:51 | [diff] [blame] | 287 | PrintingContext::Result PrintingContextChromeos::NewDocument( |
| 288 | const base::string16& document_name) { |
| 289 | DCHECK(!in_print_job_); |
| 290 | in_print_job_ = true; |
| 291 | |
| 292 | std::string converted_name = base::UTF16ToUTF8(document_name); |
| 293 | std::string title = base::UTF16ToUTF8(settings_.title()); |
| 294 | std::vector<ScopedCupsOption> cups_options = SettingsToCupsOptions(settings_); |
| 295 | |
| 296 | std::vector<cups_option_t> options; |
| 297 | for (const ScopedCupsOption& option : cups_options) { |
| 298 | if (printer_->CheckOptionSupported(option->name, option->value)) { |
| 299 | options.push_back(*(option.get())); |
| 300 | } else { |
| 301 | DVLOG(1) << "Unsupported option skipped " << option->name << ", " |
| 302 | << option->value; |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | ipp_status_t create_status = printer_->CreateJob(&job_id_, title, options); |
| 307 | |
| 308 | if (job_id_ == 0) { |
| 309 | DLOG(WARNING) << "Creating cups job failed" |
| 310 | << ippErrorString(create_status); |
| 311 | return OnError(); |
| 312 | } |
| 313 | |
| 314 | // we only send one document, so it's always the last one |
| 315 | if (!printer_->StartDocument(job_id_, converted_name, true, options)) { |
| 316 | LOG(ERROR) << "Starting document failed"; |
| 317 | return OnError(); |
| 318 | } |
| 319 | |
| 320 | return OK; |
| 321 | } |
| 322 | |
| 323 | PrintingContext::Result PrintingContextChromeos::NewPage() { |
| 324 | if (abort_printing_) |
| 325 | return CANCEL; |
| 326 | |
| 327 | DCHECK(in_print_job_); |
| 328 | |
| 329 | // Intentional No-op. |
| 330 | |
| 331 | return OK; |
| 332 | } |
| 333 | |
| 334 | PrintingContext::Result PrintingContextChromeos::PageDone() { |
| 335 | if (abort_printing_) |
| 336 | return CANCEL; |
| 337 | |
| 338 | DCHECK(in_print_job_); |
| 339 | |
| 340 | // Intentional No-op. |
| 341 | |
| 342 | return OK; |
| 343 | } |
| 344 | |
| 345 | PrintingContext::Result PrintingContextChromeos::DocumentDone() { |
| 346 | if (abort_printing_) |
| 347 | return CANCEL; |
| 348 | |
| 349 | DCHECK(in_print_job_); |
| 350 | |
| 351 | if (!printer_->FinishDocument()) { |
| 352 | LOG(WARNING) << "Finishing document failed"; |
| 353 | return OnError(); |
| 354 | } |
| 355 | |
| 356 | ipp_status_t job_status = printer_->CloseJob(job_id_); |
| 357 | job_id_ = 0; |
| 358 | |
| 359 | if (job_status != IPP_STATUS_OK) { |
| 360 | LOG(WARNING) << "Closing job failed"; |
| 361 | return OnError(); |
| 362 | } |
| 363 | |
| 364 | ResetSettings(); |
| 365 | return OK; |
| 366 | } |
| 367 | |
| 368 | void PrintingContextChromeos::Cancel() { |
| 369 | abort_printing_ = true; |
| 370 | in_print_job_ = false; |
| 371 | } |
| 372 | |
| 373 | void PrintingContextChromeos::ReleaseContext() { |
| 374 | printer_.reset(); |
| 375 | } |
| 376 | |
tfarina | 876d1e0 | 2016-10-11 23:12:53 | [diff] [blame] | 377 | skia::NativeDrawingContext PrintingContextChromeos::context() const { |
skau | b793195 | 2016-07-27 18:04:51 | [diff] [blame] | 378 | // Intentional No-op. |
| 379 | return nullptr; |
| 380 | } |
| 381 | |
| 382 | PrintingContext::Result PrintingContextChromeos::StreamData( |
| 383 | const std::vector<char>& buffer) { |
| 384 | if (abort_printing_) |
| 385 | return CANCEL; |
| 386 | |
| 387 | DCHECK(in_print_job_); |
| 388 | DCHECK(printer_); |
| 389 | |
| 390 | if (!printer_->StreamData(buffer)) |
| 391 | return OnError(); |
| 392 | |
| 393 | return OK; |
| 394 | } |
| 395 | |
| 396 | } // namespace printing |