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