Add ICU message format support
[chromium-blink-merge.git] / printing / backend / cups_helper.cc
blob79e33fcf705b82788a3b073e6acb8d8b860edadb
1 // Copyright (c) 2012 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.
5 #include "printing/backend/cups_helper.h"
7 #include <cups/ppd.h>
9 #include "base/base_paths.h"
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/path_service.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/values.h"
17 #include "printing/backend/print_backend.h"
18 #include "printing/backend/print_backend_consts.h"
19 #include "printing/units.h"
20 #include "url/gurl.h"
22 namespace printing {
24 // This section contains helper code for PPD parsing for semantic capabilities.
25 namespace {
27 const char kColorDevice[] = "ColorDevice";
28 const char kColorModel[] = "ColorModel";
29 const char kColorMode[] = "ColorMode";
30 const char kProcessColorModel[] = "ProcessColorModel";
31 const char kPrintoutMode[] = "PrintoutMode";
32 const char kDraftGray[] = "Draft.Gray";
33 const char kHighGray[] = "High.Gray";
35 const char kDuplex[] = "Duplex";
36 const char kDuplexNone[] = "None";
37 const char kPageSize[] = "PageSize";
39 const double kMicronsPerPoint = 10.0f * kHundrethsMMPerInch / kPointsPerInch;
41 void ParseLpOptions(const base::FilePath& filepath,
42 const std::string& printer_name,
43 int* num_options, cups_option_t** options) {
44 std::string content;
45 if (!base::ReadFileToString(filepath, &content))
46 return;
48 const char kDest[] = "dest";
49 const char kDefault[] = "default";
50 const size_t kDestLen = sizeof(kDest) - 1;
51 const size_t kDefaultLen = sizeof(kDefault) - 1;
53 for (base::StringPiece line :
54 base::SplitStringPiece(content, "\n", base::KEEP_WHITESPACE,
55 base::SPLIT_WANT_NONEMPTY)) {
56 if (base::StartsWith(line, base::StringPiece(kDefault, kDefaultLen),
57 base::CompareCase::INSENSITIVE_ASCII) &&
58 isspace(line[kDefaultLen])) {
59 line = line.substr(kDefaultLen);
60 } else if (base::StartsWith(line, base::StringPiece(kDest, kDestLen),
61 base::CompareCase::INSENSITIVE_ASCII) &&
62 isspace(line[kDestLen])) {
63 line = line.substr(kDestLen);
64 } else {
65 continue;
68 line = base::TrimWhitespaceASCII(line, base::TRIM_ALL);
69 if (line.empty())
70 continue;
72 size_t space_found = line.find(' ');
73 if (space_found == base::StringPiece::npos)
74 continue;
76 base::StringPiece name = line.substr(0, space_found);
77 if (name.empty())
78 continue;
80 if (!base::EqualsCaseInsensitiveASCII(printer_name, name))
81 continue; // This is not the required printer.
83 line = line.substr(space_found + 1);
84 // Remove extra spaces.
85 line = base::TrimWhitespaceASCII(line, base::TRIM_ALL);
86 if (line.empty())
87 continue;
88 // Parse the selected printer custom options. Need to pass a
89 // null-terminated string.
90 *num_options = cupsParseOptions(line.as_string().c_str(), 0, options);
94 void MarkLpOptions(const std::string& printer_name, ppd_file_t** ppd) {
95 cups_option_t* options = NULL;
96 int num_options = 0;
98 const char kSystemLpOptionPath[] = "/etc/cups/lpoptions";
99 const char kUserLpOptionPath[] = ".cups/lpoptions";
101 std::vector<base::FilePath> file_locations;
102 file_locations.push_back(base::FilePath(kSystemLpOptionPath));
103 base::FilePath homedir;
104 PathService::Get(base::DIR_HOME, &homedir);
105 file_locations.push_back(base::FilePath(homedir.Append(kUserLpOptionPath)));
107 for (std::vector<base::FilePath>::const_iterator it = file_locations.begin();
108 it != file_locations.end(); ++it) {
109 num_options = 0;
110 options = NULL;
111 ParseLpOptions(*it, printer_name, &num_options, &options);
112 if (num_options > 0 && options) {
113 cupsMarkOptions(*ppd, num_options, options);
114 cupsFreeOptions(num_options, options);
119 bool GetBasicColorModelSettings(ppd_file_t* ppd,
120 ColorModel* color_model_for_black,
121 ColorModel* color_model_for_color,
122 bool* color_is_default) {
123 ppd_option_t* color_model = ppdFindOption(ppd, kColorModel);
124 if (!color_model)
125 return false;
127 if (ppdFindChoice(color_model, printing::kBlack))
128 *color_model_for_black = printing::BLACK;
129 else if (ppdFindChoice(color_model, printing::kGray))
130 *color_model_for_black = printing::GRAY;
131 else if (ppdFindChoice(color_model, printing::kGrayscale))
132 *color_model_for_black = printing::GRAYSCALE;
134 if (ppdFindChoice(color_model, printing::kColor))
135 *color_model_for_color = printing::COLOR;
136 else if (ppdFindChoice(color_model, printing::kCMYK))
137 *color_model_for_color = printing::CMYK;
138 else if (ppdFindChoice(color_model, printing::kRGB))
139 *color_model_for_color = printing::RGB;
140 else if (ppdFindChoice(color_model, printing::kRGBA))
141 *color_model_for_color = printing::RGBA;
142 else if (ppdFindChoice(color_model, printing::kRGB16))
143 *color_model_for_color = printing::RGB16;
144 else if (ppdFindChoice(color_model, printing::kCMY))
145 *color_model_for_color = printing::CMY;
146 else if (ppdFindChoice(color_model, printing::kKCMY))
147 *color_model_for_color = printing::KCMY;
148 else if (ppdFindChoice(color_model, printing::kCMY_K))
149 *color_model_for_color = printing::CMY_K;
151 ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kColorModel);
152 if (!marked_choice)
153 marked_choice = ppdFindChoice(color_model, color_model->defchoice);
155 if (marked_choice) {
156 *color_is_default =
157 !base::EqualsCaseInsensitiveASCII(marked_choice->choice,
158 printing::kBlack) &&
159 !base::EqualsCaseInsensitiveASCII(marked_choice->choice,
160 printing::kGray) &&
161 !base::EqualsCaseInsensitiveASCII(marked_choice->choice,
162 printing::kGrayscale);
164 return true;
167 bool GetPrintOutModeColorSettings(ppd_file_t* ppd,
168 ColorModel* color_model_for_black,
169 ColorModel* color_model_for_color,
170 bool* color_is_default) {
171 ppd_option_t* printout_mode = ppdFindOption(ppd, kPrintoutMode);
172 if (!printout_mode)
173 return false;
175 *color_model_for_color = printing::PRINTOUTMODE_NORMAL;
176 *color_model_for_black = printing::PRINTOUTMODE_NORMAL;
178 // Check to see if NORMAL_GRAY value is supported by PrintoutMode.
179 // If NORMAL_GRAY is not supported, NORMAL value is used to
180 // represent grayscale. If NORMAL_GRAY is supported, NORMAL is used to
181 // represent color.
182 if (ppdFindChoice(printout_mode, printing::kNormalGray))
183 *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY;
185 // Get the default marked choice to identify the default color setting
186 // value.
187 ppd_choice_t* printout_mode_choice = ppdFindMarkedChoice(ppd, kPrintoutMode);
188 if (!printout_mode_choice) {
189 printout_mode_choice = ppdFindChoice(printout_mode,
190 printout_mode->defchoice);
192 if (printout_mode_choice) {
193 if (base::EqualsCaseInsensitiveASCII(printout_mode_choice->choice,
194 printing::kNormalGray) ||
195 base::EqualsCaseInsensitiveASCII(printout_mode_choice->choice,
196 kHighGray) ||
197 base::EqualsCaseInsensitiveASCII(printout_mode_choice->choice,
198 kDraftGray)) {
199 *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY;
200 *color_is_default = false;
203 return true;
206 bool GetColorModeSettings(ppd_file_t* ppd,
207 ColorModel* color_model_for_black,
208 ColorModel* color_model_for_color,
209 bool* color_is_default) {
210 // Samsung printers use "ColorMode" attribute in their ppds.
211 ppd_option_t* color_mode_option = ppdFindOption(ppd, kColorMode);
212 if (!color_mode_option)
213 return false;
215 if (ppdFindChoice(color_mode_option, printing::kColor))
216 *color_model_for_color = printing::COLORMODE_COLOR;
218 if (ppdFindChoice(color_mode_option, printing::kMonochrome))
219 *color_model_for_black = printing::COLORMODE_MONOCHROME;
221 ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode);
222 if (!mode_choice) {
223 mode_choice = ppdFindChoice(color_mode_option,
224 color_mode_option->defchoice);
227 if (mode_choice) {
228 *color_is_default = base::EqualsCaseInsensitiveASCII(
229 mode_choice->choice, printing::kColor);
231 return true;
234 bool GetHPColorSettings(ppd_file_t* ppd,
235 ColorModel* color_model_for_black,
236 ColorModel* color_model_for_color,
237 bool* color_is_default) {
238 // HP printers use "Color/Color Model" attribute in their ppds.
239 ppd_option_t* color_mode_option = ppdFindOption(ppd, printing::kColor);
240 if (!color_mode_option)
241 return false;
243 if (ppdFindChoice(color_mode_option, printing::kColor))
244 *color_model_for_color = printing::HP_COLOR_COLOR;
245 if (ppdFindChoice(color_mode_option, printing::kBlack))
246 *color_model_for_black = printing::HP_COLOR_BLACK;
248 ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode);
249 if (!mode_choice) {
250 mode_choice = ppdFindChoice(color_mode_option,
251 color_mode_option->defchoice);
253 if (mode_choice) {
254 *color_is_default = base::EqualsCaseInsensitiveASCII(
255 mode_choice->choice, printing::kColor);
257 return true;
260 bool GetProcessColorModelSettings(ppd_file_t* ppd,
261 ColorModel* color_model_for_black,
262 ColorModel* color_model_for_color,
263 bool* color_is_default) {
264 // Canon printers use "ProcessColorModel" attribute in their ppds.
265 ppd_option_t* color_mode_option = ppdFindOption(ppd, kProcessColorModel);
266 if (!color_mode_option)
267 return false;
269 if (ppdFindChoice(color_mode_option, printing::kRGB))
270 *color_model_for_color = printing::PROCESSCOLORMODEL_RGB;
271 else if (ppdFindChoice(color_mode_option, printing::kCMYK))
272 *color_model_for_color = printing::PROCESSCOLORMODEL_CMYK;
274 if (ppdFindChoice(color_mode_option, printing::kGreyscale))
275 *color_model_for_black = printing::PROCESSCOLORMODEL_GREYSCALE;
277 ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kProcessColorModel);
278 if (!mode_choice) {
279 mode_choice = ppdFindChoice(color_mode_option,
280 color_mode_option->defchoice);
283 if (mode_choice) {
284 *color_is_default = !base::EqualsCaseInsensitiveASCII(
285 mode_choice->choice, printing::kGreyscale);
287 return true;
290 bool GetColorModelSettings(ppd_file_t* ppd,
291 ColorModel* cm_black,
292 ColorModel* cm_color,
293 bool* is_color) {
294 bool is_color_device = false;
295 ppd_attr_t* attr = ppdFindAttr(ppd, kColorDevice, NULL);
296 if (attr && attr->value)
297 is_color_device = ppd->color_device;
299 *is_color = is_color_device;
300 return (is_color_device &&
301 GetBasicColorModelSettings(ppd, cm_black, cm_color, is_color)) ||
302 GetPrintOutModeColorSettings(ppd, cm_black, cm_color, is_color) ||
303 GetColorModeSettings(ppd, cm_black, cm_color, is_color) ||
304 GetHPColorSettings(ppd, cm_black, cm_color, is_color) ||
305 GetProcessColorModelSettings(ppd, cm_black, cm_color, is_color);
308 // Default port for IPP print servers.
309 const int kDefaultIPPServerPort = 631;
311 } // namespace
313 // Helper wrapper around http_t structure, with connection and cleanup
314 // functionality.
315 HttpConnectionCUPS::HttpConnectionCUPS(const GURL& print_server_url,
316 http_encryption_t encryption)
317 : http_(NULL) {
318 // If we have an empty url, use default print server.
319 if (print_server_url.is_empty())
320 return;
322 int port = print_server_url.IntPort();
323 if (port == url::PORT_UNSPECIFIED)
324 port = kDefaultIPPServerPort;
326 http_ = httpConnectEncrypt(print_server_url.host().c_str(), port, encryption);
327 if (http_ == NULL) {
328 LOG(ERROR) << "CP_CUPS: Failed connecting to print server: "
329 << print_server_url;
333 HttpConnectionCUPS::~HttpConnectionCUPS() {
334 if (http_ != NULL)
335 httpClose(http_);
338 void HttpConnectionCUPS::SetBlocking(bool blocking) {
339 httpBlocking(http_, blocking ? 1 : 0);
342 http_t* HttpConnectionCUPS::http() {
343 return http_;
346 bool ParsePpdCapabilities(
347 const std::string& printer_name,
348 const std::string& printer_capabilities,
349 PrinterSemanticCapsAndDefaults* printer_info) {
350 base::FilePath ppd_file_path;
351 if (!base::CreateTemporaryFile(&ppd_file_path))
352 return false;
354 int data_size = printer_capabilities.length();
355 if (data_size != base::WriteFile(
356 ppd_file_path,
357 printer_capabilities.data(),
358 data_size)) {
359 base::DeleteFile(ppd_file_path, false);
360 return false;
363 ppd_file_t* ppd = ppdOpenFile(ppd_file_path.value().c_str());
364 if (!ppd) {
365 int line = 0;
366 ppd_status_t ppd_status = ppdLastError(&line);
367 LOG(ERROR) << "Failed to open PDD file: error " << ppd_status << " at line "
368 << line << ", " << ppdErrorString(ppd_status);
369 return false;
371 ppdMarkDefaults(ppd);
372 MarkLpOptions(printer_name, &ppd);
374 printing::PrinterSemanticCapsAndDefaults caps;
375 caps.collate_capable = true;
376 caps.collate_default = true;
377 caps.copies_capable = true;
379 ppd_choice_t* duplex_choice = ppdFindMarkedChoice(ppd, kDuplex);
380 if (!duplex_choice) {
381 ppd_option_t* option = ppdFindOption(ppd, kDuplex);
382 if (option)
383 duplex_choice = ppdFindChoice(option, option->defchoice);
386 if (duplex_choice) {
387 caps.duplex_capable = true;
388 if (!base::EqualsCaseInsensitiveASCII(duplex_choice->choice, kDuplexNone))
389 caps.duplex_default = printing::LONG_EDGE;
390 else
391 caps.duplex_default = printing::SIMPLEX;
394 bool is_color = false;
395 ColorModel cm_color = UNKNOWN_COLOR_MODEL, cm_black = UNKNOWN_COLOR_MODEL;
396 if (!GetColorModelSettings(ppd, &cm_black, &cm_color, &is_color)) {
397 VLOG(1) << "Unknown printer color model";
400 caps.color_changeable = ((cm_color != UNKNOWN_COLOR_MODEL) &&
401 (cm_black != UNKNOWN_COLOR_MODEL) &&
402 (cm_color != cm_black));
403 caps.color_default = is_color;
404 caps.color_model = cm_color;
405 caps.bw_model = cm_black;
407 if (ppd->num_sizes > 0 && ppd->sizes) {
408 VLOG(1) << "Paper list size - " << ppd->num_sizes;
409 ppd_option_t* paper_option = ppdFindOption(ppd, kPageSize);
410 for (int i = 0; i < ppd->num_sizes; ++i) {
411 gfx::Size paper_size_microns(
412 static_cast<int>(ppd->sizes[i].width * kMicronsPerPoint + 0.5),
413 static_cast<int>(ppd->sizes[i].length * kMicronsPerPoint + 0.5));
414 if (paper_size_microns.width() > 0 && paper_size_microns.height() > 0) {
415 PrinterSemanticCapsAndDefaults::Paper paper;
416 paper.size_um = paper_size_microns;
417 paper.vendor_id = ppd->sizes[i].name;
418 if (paper_option) {
419 ppd_choice_t* paper_choice =
420 ppdFindChoice(paper_option, ppd->sizes[i].name);
421 // Human readable paper name should be UTF-8 encoded, but some PPDs
422 // do not follow this standard.
423 if (paper_choice && base::IsStringUTF8(paper_choice->text)) {
424 paper.display_name = paper_choice->text;
427 caps.papers.push_back(paper);
428 if (i == 0 || ppd->sizes[i].marked) {
429 caps.default_paper = paper;
435 ppdClose(ppd);
436 base::DeleteFile(ppd_file_path, false);
438 *printer_info = caps;
439 return true;
442 } // namespace printing