blob: 8b261a6140716a567dfb1d10418489cbcd560e69 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "printing/backend/cups_helper.h"
#include <stddef.h>
#include <stdio.h>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "printing/backend/print_backend.h"
#include "printing/backend/print_backend_consts.h"
#include "printing/mojom/print.mojom.h"
#include "printing/printing_utils.h"
#include "printing/units.h"
#include "url/gurl.h"
using base::EqualsCaseInsensitiveASCII;
namespace printing {
// This section contains helper code for PPD parsing for semantic capabilities.
namespace {
// Function availability can be tested by checking whether its address is not
// nullptr. Weak symbols remove the need for platform specific build flags and
// allow for appropriate CUPS usage on platforms with non-uniform version
// support, namely Linux.
#define WEAK_CUPS_FN(x) extern "C" __attribute__((weak)) decltype(x) x
WEAK_CUPS_FN(httpConnect2);
// Timeout for establishing a CUPS connection. It is expected that cupsd is
// able to start and respond on all systems within this duration.
constexpr base::TimeDelta kCupsTimeout = base::TimeDelta::FromSeconds(5);
// CUPS default max copies value (parsed from kCupsMaxCopies PPD attribute).
constexpr int32_t kDefaultMaxCopies = 9999;
constexpr char kCupsMaxCopies[] = "cupsMaxCopies";
constexpr char kColorDevice[] = "ColorDevice";
constexpr char kColorModel[] = "ColorModel";
constexpr char kColorMode[] = "ColorMode";
constexpr char kProcessColorModel[] = "ProcessColorModel";
constexpr char kPrintoutMode[] = "PrintoutMode";
constexpr char kDraftGray[] = "Draft.Gray";
constexpr char kHighGray[] = "High.Gray";
constexpr char kDuplex[] = "Duplex";
constexpr char kDuplexNone[] = "None";
constexpr char kDuplexNoTumble[] = "DuplexNoTumble";
constexpr char kDuplexTumble[] = "DuplexTumble";
constexpr char kPageSize[] = "PageSize";
// Brother printer specific options.
constexpr char kBrotherDuplex[] = "BRDuplex";
constexpr char kBrotherMonoColor[] = "BRMonoColor";
constexpr char kBrotherPrintQuality[] = "BRPrintQuality";
// Epson printer specific options.
constexpr char kEpsonInk[] = "Ink";
constexpr char kEpsonColor[] = "COLOR";
constexpr char kEpsonMono[] = "MONO";
// HP printer specific options.
constexpr char kHpColorMode[] = "HPColorMode";
constexpr char kHpColorPrint[] = "ColorPrint";
constexpr char kHpGrayscalePrint[] = "GrayscalePrint";
// Samsung printer specific options.
constexpr char kSamsungColorTrue[] = "True";
constexpr char kSamsungColorFalse[] = "False";
// Sharp printer specific options.
constexpr char kSharpARCMode[] = "ARCMode";
constexpr char kSharpCMColor[] = "CMColor";
constexpr char kSharpCMBW[] = "CMBW";
// Xerox printer specific options.
constexpr char kXeroxXRXColor[] = "XRXColor";
constexpr char kXeroxAutomatic[] = "Automatic";
constexpr char kXeroxBW[] = "BW";
int32_t GetCopiesMax(ppd_file_t* ppd) {
ppd_attr_t* attr = ppdFindAttr(ppd, kCupsMaxCopies, nullptr);
if (!attr || !attr->value) {
return kDefaultMaxCopies;
}
int32_t ret;
return base::StringToInt(attr->value, &ret) ? ret : kDefaultMaxCopies;
}
void GetDuplexSettings(ppd_file_t* ppd,
std::vector<mojom::DuplexMode>* duplex_modes,
mojom::DuplexMode* duplex_default) {
ppd_choice_t* duplex_choice = ppdFindMarkedChoice(ppd, kDuplex);
ppd_option_t* option = ppdFindOption(ppd, kDuplex);
if (!option)
option = ppdFindOption(ppd, kBrotherDuplex);
if (!option)
return;
if (!duplex_choice)
duplex_choice = ppdFindChoice(option, option->defchoice);
if (ppdFindChoice(option, kDuplexNone))
duplex_modes->push_back(mojom::DuplexMode::kSimplex);
if (ppdFindChoice(option, kDuplexNoTumble))
duplex_modes->push_back(mojom::DuplexMode::kLongEdge);
if (ppdFindChoice(option, kDuplexTumble))
duplex_modes->push_back(mojom::DuplexMode::kShortEdge);
if (!duplex_choice)
return;
const char* choice = duplex_choice->choice;
if (EqualsCaseInsensitiveASCII(choice, kDuplexNone)) {
*duplex_default = mojom::DuplexMode::kSimplex;
} else if (EqualsCaseInsensitiveASCII(choice, kDuplexTumble)) {
*duplex_default = mojom::DuplexMode::kShortEdge;
} else {
*duplex_default = mojom::DuplexMode::kLongEdge;
}
}
void GetResolutionSettings(ppd_file_t* ppd,
std::vector<gfx::Size>* dpis,
gfx::Size* default_dpi) {
static constexpr const char* kResolutions[] = {
"Resolution", "JCLResolution", "SetResolution",
"CNRes_PGP", "HPPrintQuality", "LXResolution"};
ppd_option_t* res;
for (const char* res_name : kResolutions) {
res = ppdFindOption(ppd, res_name);
if (res)
break;
}
if (!res)
return;
for (int i = 0; i < res->num_choices; i++) {
char* choice = res->choices[i].choice;
DCHECK(choice);
int len = strlen(choice);
if (len == 0) {
VLOG(1) << "Bad PPD resolution choice: null string";
continue;
}
int n = 0; // number of chars successfully parsed by sscanf()
int dpi_x;
int dpi_y;
sscanf(choice, "%ddpi%n", &dpi_x, &n);
if (n == len) {
dpi_y = dpi_x;
} else {
sscanf(choice, "%dx%ddpi%n", &dpi_x, &dpi_y, &n);
if (n != len) {
VLOG(1) << "Bad PPD resolution choice: " << choice;
continue;
}
}
if (dpi_x <= 0 || dpi_y <= 0) {
VLOG(1) << "Invalid PPD resolution dimensions: " << dpi_x << " " << dpi_y;
continue;
}
dpis->push_back({dpi_x, dpi_y});
if (!strcmp(choice, res->defchoice))
*default_dpi = dpis->back();
}
}
bool GetBasicColorModelSettings(ppd_file_t* ppd,
mojom::ColorModel* color_model_for_black,
mojom::ColorModel* color_model_for_color,
bool* color_is_default) {
ppd_option_t* color_model = ppdFindOption(ppd, kColorModel);
if (!color_model)
return false;
if (ppdFindChoice(color_model, kBlack))
*color_model_for_black = mojom::ColorModel::kBlack;
else if (ppdFindChoice(color_model, kGray))
*color_model_for_black = mojom::ColorModel::kGray;
else if (ppdFindChoice(color_model, kGrayscale))
*color_model_for_black = mojom::ColorModel::kGrayscale;
if (ppdFindChoice(color_model, kColor))
*color_model_for_color = mojom::ColorModel::kColor;
else if (ppdFindChoice(color_model, kCMYK))
*color_model_for_color = mojom::ColorModel::kCMYK;
else if (ppdFindChoice(color_model, kRGB))
*color_model_for_color = mojom::ColorModel::kRGB;
else if (ppdFindChoice(color_model, kRGBA))
*color_model_for_color = mojom::ColorModel::kRGBA;
else if (ppdFindChoice(color_model, kRGB16))
*color_model_for_color = mojom::ColorModel::kRGB16;
else if (ppdFindChoice(color_model, kCMY))
*color_model_for_color = mojom::ColorModel::kCMY;
else if (ppdFindChoice(color_model, kKCMY))
*color_model_for_color = mojom::ColorModel::kKCMY;
else if (ppdFindChoice(color_model, kCMY_K))
*color_model_for_color = mojom::ColorModel::kCMYPlusK;
ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kColorModel);
if (!marked_choice)
marked_choice = ppdFindChoice(color_model, color_model->defchoice);
if (marked_choice) {
*color_is_default =
!EqualsCaseInsensitiveASCII(marked_choice->choice, kBlack) &&
!EqualsCaseInsensitiveASCII(marked_choice->choice, kGray) &&
!EqualsCaseInsensitiveASCII(marked_choice->choice, kGrayscale);
}
return true;
}
bool GetPrintOutModeColorSettings(ppd_file_t* ppd,
mojom::ColorModel* color_model_for_black,
mojom::ColorModel* color_model_for_color,
bool* color_is_default) {
ppd_option_t* printout_mode = ppdFindOption(ppd, kPrintoutMode);
if (!printout_mode)
return false;
*color_model_for_color = mojom::ColorModel::kPrintoutModeNormal;
*color_model_for_black = mojom::ColorModel::kPrintoutModeNormal;
// Check to see if NORMAL_GRAY value is supported by PrintoutMode.
// If NORMAL_GRAY is not supported, NORMAL value is used to
// represent grayscale. If NORMAL_GRAY is supported, NORMAL is used to
// represent color.
if (ppdFindChoice(printout_mode, kNormalGray))
*color_model_for_black = mojom::ColorModel::kPrintoutModeNormalGray;
// Get the default marked choice to identify the default color setting
// value.
ppd_choice_t* printout_mode_choice = ppdFindMarkedChoice(ppd, kPrintoutMode);
if (!printout_mode_choice) {
printout_mode_choice =
ppdFindChoice(printout_mode, printout_mode->defchoice);
}
if (printout_mode_choice) {
if (EqualsCaseInsensitiveASCII(printout_mode_choice->choice, kNormalGray) ||
EqualsCaseInsensitiveASCII(printout_mode_choice->choice, kHighGray) ||
EqualsCaseInsensitiveASCII(printout_mode_choice->choice, kDraftGray)) {
*color_model_for_black = mojom::ColorModel::kPrintoutModeNormalGray;
*color_is_default = false;
}
}
return true;
}
bool GetColorModeSettings(ppd_file_t* ppd,
mojom::ColorModel* color_model_for_black,
mojom::ColorModel* color_model_for_color,
bool* color_is_default) {
// Samsung printers use "ColorMode" attribute in their PPDs.
ppd_option_t* color_mode_option = ppdFindOption(ppd, kColorMode);
if (!color_mode_option)
return false;
if (ppdFindChoice(color_mode_option, kColor) ||
ppdFindChoice(color_mode_option, kSamsungColorTrue)) {
*color_model_for_color = mojom::ColorModel::kColorModeColor;
}
if (ppdFindChoice(color_mode_option, kMonochrome) ||
ppdFindChoice(color_mode_option, kSamsungColorFalse)) {
*color_model_for_black = mojom::ColorModel::kColorModeMonochrome;
}
ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode);
if (!mode_choice) {
mode_choice =
ppdFindChoice(color_mode_option, color_mode_option->defchoice);
}
if (mode_choice) {
*color_is_default =
EqualsCaseInsensitiveASCII(mode_choice->choice, kColor) ||
EqualsCaseInsensitiveASCII(mode_choice->choice, kSamsungColorTrue);
}
return true;
}
bool GetBrotherColorSettings(ppd_file_t* ppd,
mojom::ColorModel* color_model_for_black,
mojom::ColorModel* color_model_for_color,
bool* color_is_default) {
// Some Brother printers use "BRMonoColor" attribute in their PPDs.
// Some Brother printers use "BRPrintQuality" attribute in their PPDs.
ppd_option_t* color_mode_option = ppdFindOption(ppd, kBrotherMonoColor);
if (!color_mode_option)
color_mode_option = ppdFindOption(ppd, kBrotherPrintQuality);
if (!color_mode_option)
return false;
if (ppdFindChoice(color_mode_option, kFullColor))
*color_model_for_color = mojom::ColorModel::kBrotherCUPSColor;
else if (ppdFindChoice(color_mode_option, kColor))
*color_model_for_color = mojom::ColorModel::kBrotherBRScript3Color;
if (ppdFindChoice(color_mode_option, kMono))
*color_model_for_black = mojom::ColorModel::kBrotherCUPSMono;
else if (ppdFindChoice(color_mode_option, kBlack))
*color_model_for_black = mojom::ColorModel::kBrotherBRScript3Black;
ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kColorMode);
if (!marked_choice) {
marked_choice =
ppdFindChoice(color_mode_option, color_mode_option->defchoice);
}
if (marked_choice) {
*color_is_default =
!EqualsCaseInsensitiveASCII(marked_choice->choice, kBlack) &&
!EqualsCaseInsensitiveASCII(marked_choice->choice, kMono);
}
return true;
}
bool GetHPColorSettings(ppd_file_t* ppd,
mojom::ColorModel* color_model_for_black,
mojom::ColorModel* color_model_for_color,
bool* color_is_default) {
// Some HP printers use "Color/Color Model" attribute in their PPDs.
ppd_option_t* color_mode_option = ppdFindOption(ppd, kColor);
if (!color_mode_option)
return false;
if (ppdFindChoice(color_mode_option, kColor))
*color_model_for_color = mojom::ColorModel::kHPColorColor;
if (ppdFindChoice(color_mode_option, kBlack))
*color_model_for_black = mojom::ColorModel::kHPColorBlack;
ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode);
if (!mode_choice) {
mode_choice =
ppdFindChoice(color_mode_option, color_mode_option->defchoice);
}
if (mode_choice) {
*color_is_default = EqualsCaseInsensitiveASCII(mode_choice->choice, kColor);
}
return true;
}
bool GetHPColorModeSettings(ppd_file_t* ppd,
mojom::ColorModel* color_model_for_black,
mojom::ColorModel* color_model_for_color,
bool* color_is_default) {
// Some HP printers use "HPColorMode/Mode" attribute in their PPDs.
ppd_option_t* color_mode_option = ppdFindOption(ppd, kHpColorMode);
if (!color_mode_option)
return false;
if (ppdFindChoice(color_mode_option, kHpColorPrint))
*color_model_for_color = mojom::ColorModel::kHPColorColor;
if (ppdFindChoice(color_mode_option, kHpGrayscalePrint))
*color_model_for_black = mojom::ColorModel::kHPColorBlack;
ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kHpColorMode);
if (!mode_choice) {
mode_choice =
ppdFindChoice(color_mode_option, color_mode_option->defchoice);
}
if (mode_choice) {
*color_is_default =
EqualsCaseInsensitiveASCII(mode_choice->choice, kHpColorPrint);
}
return true;
}
bool GetEpsonInkSettings(ppd_file_t* ppd,
mojom::ColorModel* color_model_for_black,
mojom::ColorModel* color_model_for_color,
bool* color_is_default) {
// Epson printers use "Ink" attribute in their PPDs.
ppd_option_t* color_mode_option = ppdFindOption(ppd, kEpsonInk);
if (!color_mode_option)
return false;
if (ppdFindChoice(color_mode_option, kEpsonColor))
*color_model_for_color = mojom::ColorModel::kEpsonInkColor;
if (ppdFindChoice(color_mode_option, kEpsonMono))
*color_model_for_black = mojom::ColorModel::kEpsonInkMono;
ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kEpsonInk);
if (!mode_choice) {
mode_choice =
ppdFindChoice(color_mode_option, color_mode_option->defchoice);
}
if (mode_choice) {
*color_is_default = EqualsCaseInsensitiveASCII(mode_choice->choice, kColor);
}
return true;
}
bool GetSharpARCModeSettings(ppd_file_t* ppd,
mojom::ColorModel* color_model_for_black,
mojom::ColorModel* color_model_for_color,
bool* color_is_default) {
// Sharp printers use "ARCMode" attribute in their PPDs.
ppd_option_t* color_mode_option = ppdFindOption(ppd, kSharpARCMode);
if (!color_mode_option)
return false;
if (ppdFindChoice(color_mode_option, kSharpCMColor))
*color_model_for_color = mojom::ColorModel::kSharpARCModeCMColor;
if (ppdFindChoice(color_mode_option, kSharpCMBW))
*color_model_for_black = mojom::ColorModel::kSharpARCModeCMBW;
ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kSharpARCMode);
if (!mode_choice) {
mode_choice =
ppdFindChoice(color_mode_option, color_mode_option->defchoice);
}
if (mode_choice) {
// Many Sharp printers use "CMAuto" as the default color mode.
*color_is_default =
!EqualsCaseInsensitiveASCII(mode_choice->choice, kSharpCMBW);
}
return true;
}
bool GetXeroxColorSettings(ppd_file_t* ppd,
mojom::ColorModel* color_model_for_black,
mojom::ColorModel* color_model_for_color,
bool* color_is_default) {
// Some Xerox printers use "XRXColor" attribute in their PPDs.
ppd_option_t* color_mode_option = ppdFindOption(ppd, kXeroxXRXColor);
if (!color_mode_option)
return false;
if (ppdFindChoice(color_mode_option, kXeroxAutomatic))
*color_model_for_color = mojom::ColorModel::kXeroxXRXColorAutomatic;
if (ppdFindChoice(color_mode_option, kXeroxBW))
*color_model_for_black = mojom::ColorModel::kXeroxXRXColorBW;
ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kXeroxXRXColor);
if (!mode_choice) {
mode_choice =
ppdFindChoice(color_mode_option, color_mode_option->defchoice);
}
if (mode_choice) {
// Many Xerox printers use "Automatic" as the default color mode.
*color_is_default =
!EqualsCaseInsensitiveASCII(mode_choice->choice, kXeroxBW);
}
return true;
}
bool GetProcessColorModelSettings(ppd_file_t* ppd,
mojom::ColorModel* color_model_for_black,
mojom::ColorModel* color_model_for_color,
bool* color_is_default) {
// Canon printers use "ProcessColorModel" attribute in their PPDs.
ppd_option_t* color_mode_option = ppdFindOption(ppd, kProcessColorModel);
if (!color_mode_option)
return false;
if (ppdFindChoice(color_mode_option, kRGB))
*color_model_for_color = mojom::ColorModel::kProcessColorModelRGB;
else if (ppdFindChoice(color_mode_option, kCMYK))
*color_model_for_color = mojom::ColorModel::kProcessColorModelCMYK;
if (ppdFindChoice(color_mode_option, kGreyscale))
*color_model_for_black = mojom::ColorModel::kProcessColorModelGreyscale;
ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kProcessColorModel);
if (!mode_choice) {
mode_choice =
ppdFindChoice(color_mode_option, color_mode_option->defchoice);
}
if (mode_choice) {
*color_is_default =
!EqualsCaseInsensitiveASCII(mode_choice->choice, kGreyscale);
}
return true;
}
bool GetColorModelSettings(ppd_file_t* ppd,
mojom::ColorModel* cm_black,
mojom::ColorModel* cm_color,
bool* is_color) {
bool is_color_device = false;
ppd_attr_t* attr = ppdFindAttr(ppd, kColorDevice, nullptr);
if (attr && attr->value)
is_color_device = ppd->color_device;
*is_color = is_color_device;
return (is_color_device &&
GetBasicColorModelSettings(ppd, cm_black, cm_color, is_color)) ||
GetPrintOutModeColorSettings(ppd, cm_black, cm_color, is_color) ||
GetColorModeSettings(ppd, cm_black, cm_color, is_color) ||
GetHPColorSettings(ppd, cm_black, cm_color, is_color) ||
GetHPColorModeSettings(ppd, cm_black, cm_color, is_color) ||
GetBrotherColorSettings(ppd, cm_black, cm_color, is_color) ||
GetEpsonInkSettings(ppd, cm_black, cm_color, is_color) ||
GetSharpARCModeSettings(ppd, cm_black, cm_color, is_color) ||
GetXeroxColorSettings(ppd, cm_black, cm_color, is_color) ||
GetProcessColorModelSettings(ppd, cm_black, cm_color, is_color);
}
// Default port for IPP print servers.
const int kDefaultIPPServerPort = 631;
// Provide scoping of a temporary file to be used by a FILE stream. This is
// akin to base::ScopedFILE but provides access to an open FILE handle, which
// is needed when performing multiple operations from within a sandbox.
class ScopedTempFile {
public:
ScopedTempFile() { file_ = base::CreateAndOpenTemporaryStream(&path_); }
ScopedTempFile(const ScopedTempFile&) = delete;
ScopedTempFile& operator=(const ScopedTempFile&) = delete;
~ScopedTempFile() {
file_.reset(); // Closes the file if it is open.
if (base::PathExists(path_))
base::DeleteFile(path_);
}
FILE* handle() { return file_.get(); }
private:
base::FilePath path_;
base::ScopedFILE file_;
};
} // namespace
// Helper wrapper around http_t structure, with connection and cleanup
// functionality.
HttpConnectionCUPS::HttpConnectionCUPS(const GURL& print_server_url,
http_encryption_t encryption,
bool blocking)
: http_(nullptr) {
// If we have an empty url, use default print server.
if (print_server_url.is_empty())
return;
int port = print_server_url.IntPort();
if (port == url::PORT_UNSPECIFIED)
port = kDefaultIPPServerPort;
if (httpConnect2) {
http_ = httpConnect2(print_server_url.host().c_str(), port,
/*addrlist=*/nullptr, AF_UNSPEC, encryption,
blocking ? 1 : 0, kCupsTimeout.InMilliseconds(),
/*cancel=*/nullptr);
} else {
// Continue to use deprecated CUPS calls because because older Linux
// distribution such as RHEL/CentOS 7 are shipped with CUPS 1.6.
http_ =
httpConnectEncrypt(print_server_url.host().c_str(), port, encryption);
}
if (!http_) {
LOG(ERROR) << "CP_CUPS: Failed connecting to print server: "
<< print_server_url;
return;
}
if (!httpConnect2)
httpBlocking(http_, blocking ? 1 : 0);
}
HttpConnectionCUPS::~HttpConnectionCUPS() {
if (http_)
httpClose(http_);
}
http_t* HttpConnectionCUPS::http() {
return http_;
}
bool ParsePpdCapabilities(cups_dest_t* dest,
base::StringPiece locale,
base::StringPiece printer_capabilities,
PrinterSemanticCapsAndDefaults* printer_info) {
ScopedTempFile ppd_file;
if (!ppd_file.handle())
return false;
const size_t caps_size = printer_capabilities.size();
if (fwrite(printer_capabilities.data(), 1, caps_size, ppd_file.handle()) !=
caps_size) {
return false;
}
if (fseek(ppd_file.handle(), 0, SEEK_SET) != 0)
return false;
ppd_file_t* ppd = ppdOpen(ppd_file.handle());
if (!ppd)
return false;
ppdMarkDefaults(ppd);
if (dest)
cupsMarkOptions(ppd, dest->num_options, dest->options);
PrinterSemanticCapsAndDefaults caps;
caps.collate_capable = true;
caps.collate_default = true;
caps.copies_max = GetCopiesMax(ppd);
GetDuplexSettings(ppd, &caps.duplex_modes, &caps.duplex_default);
GetResolutionSettings(ppd, &caps.dpis, &caps.default_dpi);
mojom::ColorModel cm_black = mojom::ColorModel::kUnknownColorModel;
mojom::ColorModel cm_color = mojom::ColorModel::kUnknownColorModel;
bool is_color = false;
if (!GetColorModelSettings(ppd, &cm_black, &cm_color, &is_color)) {
VLOG(1) << "Unknown printer color model";
}
caps.color_changeable =
((cm_color != mojom::ColorModel::kUnknownColorModel) &&
(cm_black != mojom::ColorModel::kUnknownColorModel) &&
(cm_color != cm_black));
caps.color_default = is_color;
caps.color_model = cm_color;
caps.bw_model = cm_black;
if (ppd->num_sizes > 0 && ppd->sizes) {
VLOG(1) << "Paper list size - " << ppd->num_sizes;
ppd_option_t* paper_option = ppdFindOption(ppd, kPageSize);
bool is_default_found = false;
for (int i = 0; i < ppd->num_sizes; ++i) {
gfx::Size paper_size_microns(
ConvertUnit(ppd->sizes[i].width, kPointsPerInch, kMicronsPerInch),
ConvertUnit(ppd->sizes[i].length, kPointsPerInch, kMicronsPerInch));
if (!paper_size_microns.IsEmpty()) {
PrinterSemanticCapsAndDefaults::Paper paper;
paper.size_um = paper_size_microns;
paper.vendor_id = ppd->sizes[i].name;
if (paper_option) {
ppd_choice_t* paper_choice =
ppdFindChoice(paper_option, ppd->sizes[i].name);
// Human readable paper name should be UTF-8 encoded, but some PPDs
// do not follow this standard.
if (paper_choice && base::IsStringUTF8(paper_choice->text)) {
paper.display_name = paper_choice->text;
}
}
caps.papers.push_back(paper);
if (ppd->sizes[i].marked) {
caps.default_paper = paper;
is_default_found = true;
}
}
}
if (!is_default_found) {
gfx::Size locale_paper_microns =
GetDefaultPaperSizeFromLocaleMicrons(locale);
for (const PrinterSemanticCapsAndDefaults::Paper& paper : caps.papers) {
// Set epsilon to 500 microns to allow tolerance of rounded paper sizes.
// While the above utility function returns paper sizes in microns, they
// are still rounded to the nearest millimeter (1000 microns).
constexpr int kSizeEpsilon = 500;
if (SizesEqualWithinEpsilon(paper.size_um, locale_paper_microns,
kSizeEpsilon)) {
caps.default_paper = paper;
is_default_found = true;
break;
}
}
// If no default was set in the PPD or if the locale default is not within
// the printer's capabilities, select the first on the list.
if (!is_default_found)
caps.default_paper = caps.papers[0];
}
}
ppdClose(ppd);
*printer_info = caps;
return true;
}
} // namespace printing