blob: 32851bf0769f91e7ccc1b22a97f7f5f3b85d00e4 [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:
thestigca4af0b2017-03-15 18:28:4849 case BROTHER_CUPS_COLOR:
50 case BROTHER_BRSCRIPT3_COLOR:
skaub7931952016-07-27 18:04:5151 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:
thestigca4af0b2017-03-15 18:28:4861 case BROTHER_CUPS_MONO:
62 case BROTHER_BRSCRIPT3_BLACK:
skaub7931952016-07-27 18:04:5163 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.
79char* 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
86ScopedCupsOption 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
95base::StringPiece GetCollateString(bool collate) {
96 return collate ? kCollated : kUncollated;
97}
98
99std::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
134void 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
152std::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
159PrintingContextChromeos::PrintingContextChromeos(Delegate* delegate)
160 : PrintingContext(delegate),
161 connection_(GURL(), HTTP_ENCRYPT_NEVER, true) {}
162
163PrintingContextChromeos::~PrintingContextChromeos() {
164 ReleaseContext();
165}
166
167void PrintingContextChromeos::AskUserForSettings(
168 int max_pages,
169 bool has_selection,
170 bool is_scripted,
171 const PrintSettingsCallback& callback) {
skau03b6108c2016-10-05 01:52:29172 // We don't want to bring up a dialog here. Ever. This should not be called.
173 NOTREACHED();
skaub7931952016-07-27 18:04:51174}
175
176PrintingContext::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
212gfx::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
237PrintingContext::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
272PrintingContext::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
skaub7931952016-07-27 18:04:51287PrintingContext::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
323PrintingContext::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
334PrintingContext::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
345PrintingContext::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
368void PrintingContextChromeos::Cancel() {
369 abort_printing_ = true;
370 in_print_job_ = false;
371}
372
373void PrintingContextChromeos::ReleaseContext() {
374 printer_.reset();
375}
376
tfarina876d1e02016-10-11 23:12:53377skia::NativeDrawingContext PrintingContextChromeos::context() const {
skaub7931952016-07-27 18:04:51378 // Intentional No-op.
379 return nullptr;
380}
381
382PrintingContext::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