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