15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "printing/backend/cups_helper.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <cups/ppd.h>
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/file_util.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h"
117d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)#include "base/strings/string_number_conversions.h"
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/strings/string_split.h"
137dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "base/strings/string_util.h"
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/values.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "printing/backend/print_backend.h"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "printing/backend/print_backend_consts.h"
177dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "url/gurl.h"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)namespace printing {
204e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// This section contains helper code for PPD parsing for semantic capabilities.
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kColorDevice[] = "ColorDevice";
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kColorModel[] = "ColorModel";
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kColorMode[] = "ColorMode";
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kProcessColorModel[] = "ProcessColorModel";
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kPrintoutMode[] = "PrintoutMode";
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kDraftGray[] = "Draft.Gray";
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kHighGray[] = "High.Gray";
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kDuplex[] = "Duplex";
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kDuplexNone[] = "None";
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if !defined(OS_MACOSX)
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void ParseLpOptions(const base::FilePath& filepath,
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    const std::string& printer_name,
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    int* num_options, cups_option_t** options) {
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string content;
4058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  if (!base::ReadFileToString(filepath, &content))
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const char kDest[] = "dest";
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const char kDefault[] = "default";
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const size_t kDestLen = sizeof(kDest) - 1;
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const size_t kDefaultLen = sizeof(kDefault) - 1;
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::vector<std::string> lines;
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::SplitString(content, '\n', &lines);
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (size_t i = 0; i < lines.size(); ++i) {
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    std::string line = lines[i];
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (line.empty())
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (base::strncasecmp (line.c_str(), kDefault, kDefaultLen) == 0 &&
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        isspace(line[kDefaultLen])) {
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      line = line.substr(kDefaultLen);
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else if (base::strncasecmp (line.c_str(), kDest, kDestLen) == 0 &&
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               isspace(line[kDestLen])) {
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      line = line.substr(kDestLen);
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    TrimWhitespaceASCII(line, TRIM_ALL, &line);
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (line.empty())
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    size_t space_found = line.find(' ');
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (space_found == std::string::npos)
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    std::string name = line.substr(0, space_found);
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (name.empty())
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (base::strncasecmp(printer_name.c_str(), name.c_str(),
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          name.length()) != 0) {
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;  // This is not the required printer.
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    line = line.substr(space_found + 1);
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    TrimWhitespaceASCII(line, TRIM_ALL, &line);  // Remove extra spaces.
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (line.empty())
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Parse the selected printer custom options.
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *num_options = cupsParseOptions(line.c_str(), 0, options);
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void MarkLpOptions(const std::string& printer_name, ppd_file_t** ppd) {
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cups_option_t* options = NULL;
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int num_options = 0;
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppdMarkDefaults(*ppd);
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const char kSystemLpOptionPath[] = "/etc/cups/lpoptions";
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const char kUserLpOptionPath[] = ".cups/lpoptions";
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  std::vector<base::FilePath> file_locations;
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  file_locations.push_back(base::FilePath(kSystemLpOptionPath));
1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  file_locations.push_back(base::FilePath(
102a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      base::GetHomeDir().Append(kUserLpOptionPath)));
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  for (std::vector<base::FilePath>::const_iterator it = file_locations.begin();
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       it != file_locations.end(); ++it) {
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    num_options = 0;
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    options = NULL;
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ParseLpOptions(*it, printer_name, &num_options, &options);
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (num_options > 0 && options) {
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cupsMarkOptions(*ppd, num_options, options);
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cupsFreeOptions(num_options, options);
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif  // !defined(OS_MACOSX)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool GetBasicColorModelSettings(ppd_file_t* ppd,
1184e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                ColorModel* color_model_for_black,
1194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                ColorModel* color_model_for_color,
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                bool* color_is_default) {
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_option_t* color_model = ppdFindOption(ppd, kColorModel);
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!color_model)
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ppdFindChoice(color_model, printing::kBlack))
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_black = printing::BLACK;
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (ppdFindChoice(color_model, printing::kGray))
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_black = printing::GRAY;
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (ppdFindChoice(color_model, printing::kGrayscale))
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_black = printing::GRAYSCALE;
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ppdFindChoice(color_model, printing::kColor))
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::COLOR;
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (ppdFindChoice(color_model, printing::kCMYK))
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::CMYK;
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (ppdFindChoice(color_model, printing::kRGB))
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::RGB;
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (ppdFindChoice(color_model, printing::kRGBA))
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::RGBA;
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (ppdFindChoice(color_model, printing::kRGB16))
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::RGB16;
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (ppdFindChoice(color_model, printing::kCMY))
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::CMY;
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (ppdFindChoice(color_model, printing::kKCMY))
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::KCMY;
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (ppdFindChoice(color_model, printing::kCMY_K))
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::CMY_K;
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kColorModel);
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!marked_choice)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    marked_choice = ppdFindChoice(color_model, color_model->defchoice);
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (marked_choice) {
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_is_default =
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (base::strcasecmp(marked_choice->choice, printing::kBlack) != 0) &&
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (base::strcasecmp(marked_choice->choice, printing::kGray) != 0) &&
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (base::strcasecmp(marked_choice->choice, printing::kGrayscale) != 0);
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool GetPrintOutModeColorSettings(ppd_file_t* ppd,
1634e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                  ColorModel* color_model_for_black,
1644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                  ColorModel* color_model_for_color,
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                  bool* color_is_default) {
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_option_t* printout_mode = ppdFindOption(ppd, kPrintoutMode);
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!printout_mode)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  *color_model_for_color = printing::PRINTOUTMODE_NORMAL;
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  *color_model_for_black = printing::PRINTOUTMODE_NORMAL;
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Check to see if NORMAL_GRAY value is supported by PrintoutMode.
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If NORMAL_GRAY is not supported, NORMAL value is used to
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // represent grayscale. If NORMAL_GRAY is supported, NORMAL is used to
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // represent color.
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ppdFindChoice(printout_mode, printing::kNormalGray))
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY;
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Get the default marked choice to identify the default color setting
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // value.
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_choice_t* printout_mode_choice = ppdFindMarkedChoice(ppd, kPrintoutMode);
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!printout_mode_choice) {
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      printout_mode_choice = ppdFindChoice(printout_mode,
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                           printout_mode->defchoice);
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (printout_mode_choice) {
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ((base::strcasecmp(printout_mode_choice->choice,
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          printing::kNormalGray) == 0) ||
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (base::strcasecmp(printout_mode_choice->choice, kHighGray) == 0) ||
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (base::strcasecmp(printout_mode_choice->choice, kDraftGray) == 0)) {
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY;
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      *color_is_default = false;
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool GetColorModeSettings(ppd_file_t* ppd,
2004e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                          ColorModel* color_model_for_black,
2014e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                          ColorModel* color_model_for_color,
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          bool* color_is_default) {
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Samsung printers use "ColorMode" attribute in their ppds.
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_option_t* color_mode_option = ppdFindOption(ppd, kColorMode);
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!color_mode_option)
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ppdFindChoice(color_mode_option, printing::kColor))
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::COLORMODE_COLOR;
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ppdFindChoice(color_mode_option, printing::kMonochrome))
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_black = printing::COLORMODE_MONOCHROME;
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode);
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!mode_choice) {
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    mode_choice = ppdFindChoice(color_mode_option,
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                color_mode_option->defchoice);
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (mode_choice) {
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_is_default =
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (base::strcasecmp(mode_choice->choice, printing::kColor) == 0);
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool GetHPColorSettings(ppd_file_t* ppd,
2284e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                        ColorModel* color_model_for_black,
2294e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                        ColorModel* color_model_for_color,
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        bool* color_is_default) {
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // HP printers use "Color/Color Model" attribute in their ppds.
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_option_t* color_mode_option = ppdFindOption(ppd, printing::kColor);
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!color_mode_option)
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ppdFindChoice(color_mode_option, printing::kColor))
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::HP_COLOR_COLOR;
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ppdFindChoice(color_mode_option, printing::kBlack))
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_black = printing::HP_COLOR_BLACK;
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode);
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!mode_choice) {
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    mode_choice = ppdFindChoice(color_mode_option,
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                color_mode_option->defchoice);
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (mode_choice) {
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_is_default =
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (base::strcasecmp(mode_choice->choice, printing::kColor) == 0);
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool GetProcessColorModelSettings(ppd_file_t* ppd,
2544e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                  ColorModel* color_model_for_black,
2554e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                  ColorModel* color_model_for_color,
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                  bool* color_is_default) {
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Canon printers use "ProcessColorModel" attribute in their ppds.
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_option_t* color_mode_option =  ppdFindOption(ppd, kProcessColorModel);
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!color_mode_option)
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ppdFindChoice(color_mode_option, printing::kRGB))
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::PROCESSCOLORMODEL_RGB;
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else if (ppdFindChoice(color_mode_option, printing::kCMYK))
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_color = printing::PROCESSCOLORMODEL_CMYK;
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ppdFindChoice(color_mode_option, printing::kGreyscale))
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_model_for_black = printing::PROCESSCOLORMODEL_GREYSCALE;
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kProcessColorModel);
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!mode_choice) {
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    mode_choice = ppdFindChoice(color_mode_option,
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                color_mode_option->defchoice);
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (mode_choice) {
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *color_is_default =
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (base::strcasecmp(mode_choice->choice, printing::kGreyscale) != 0);
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool GetColorModelSettings(ppd_file_t* ppd,
2844e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                           ColorModel* cm_black,
2854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                           ColorModel* cm_color,
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                           bool* is_color) {
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool is_color_device = false;
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_attr_t* attr = ppdFindAttr(ppd, kColorDevice, NULL);
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (attr && attr->value)
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    is_color_device = ppd->color_device;
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  *is_color = is_color_device;
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return (is_color_device &&
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          GetBasicColorModelSettings(ppd, cm_black, cm_color, is_color)) ||
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      GetPrintOutModeColorSettings(ppd, cm_black, cm_color, is_color) ||
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      GetColorModeSettings(ppd, cm_black, cm_color, is_color) ||
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      GetHPColorSettings(ppd, cm_black, cm_color, is_color) ||
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      GetProcessColorModelSettings(ppd, cm_black, cm_color, is_color);
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Default port for IPP print servers.
3024e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)const int kDefaultIPPServerPort = 631;
3034e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
3044e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}  // namespace
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Helper wrapper around http_t structure, with connection and cleanup
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// functionality.
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HttpConnectionCUPS::HttpConnectionCUPS(const GURL& print_server_url,
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                       http_encryption_t encryption)
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : http_(NULL) {
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If we have an empty url, use default print server.
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (print_server_url.is_empty())
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int port = print_server_url.IntPort();
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (port == url_parse::PORT_UNSPECIFIED)
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    port = kDefaultIPPServerPort;
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  http_ = httpConnectEncrypt(print_server_url.host().c_str(), port, encryption);
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (http_ == NULL) {
3214e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    LOG(ERROR) << "CP_CUPS: Failed connecting to print server: "
3224e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)               << print_server_url;
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HttpConnectionCUPS::~HttpConnectionCUPS() {
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (http_ != NULL)
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    httpClose(http_);
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void HttpConnectionCUPS::SetBlocking(bool blocking) {
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  httpBlocking(http_, blocking ?  1 : 0);
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)http_t* HttpConnectionCUPS::http() {
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return http_;
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3394e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)bool ParsePpdCapabilities(
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const std::string& printer_name,
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const std::string& printer_capabilities,
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    PrinterSemanticCapsAndDefaults* printer_info) {
3432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  base::FilePath ppd_file_path;
344a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  if (!base::CreateTemporaryFile(&ppd_file_path))
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int data_size = printer_capabilities.length();
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (data_size != file_util::WriteFile(
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       ppd_file_path,
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       printer_capabilities.data(),
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       data_size)) {
3527dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch    base::DeleteFile(ppd_file_path, false);
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_file_t* ppd = ppdOpenFile(ppd_file_path.value().c_str());
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!ppd)
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  printing::PrinterSemanticCapsAndDefaults caps;
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#if !defined(OS_MACOSX)
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  MarkLpOptions(printer_name, &ppd);
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#endif
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppd_choice_t* duplex_choice = ppdFindMarkedChoice(ppd, kDuplex);
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!duplex_choice) {
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ppd_option_t* option = ppdFindOption(ppd, kDuplex);
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (option)
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      duplex_choice = ppdFindChoice(option, option->defchoice);
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (duplex_choice) {
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    caps.duplex_capable = true;
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (base::strcasecmp(duplex_choice->choice, kDuplexNone) != 0)
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      caps.duplex_default = printing::LONG_EDGE;
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      caps.duplex_default = printing::SIMPLEX;
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool is_color = false;
3804e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  ColorModel cm_color = UNKNOWN_COLOR_MODEL, cm_black = UNKNOWN_COLOR_MODEL;
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!GetColorModelSettings(ppd, &cm_black, &cm_color, &is_color)) {
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    VLOG(1) << "Unknown printer color model";
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  caps.color_changeable = ((cm_color != UNKNOWN_COLOR_MODEL) &&
3864e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                           (cm_black != UNKNOWN_COLOR_MODEL) &&
3874e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                           (cm_color != cm_black));
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  caps.color_default = is_color;
3898bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  caps.color_model = cm_color;
3908bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  caps.bw_model = cm_black;
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ppdClose(ppd);
3937dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  base::DeleteFile(ppd_file_path, false);
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  *printer_info = caps;
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace printing
400