[go: nahoru, domu]

blob: da63e1959bc79e6e5ff7d2fc4ce1419188770998 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "printing/backend/cups_ipp_helper.h"
#include <cups/cups.h>
#include <algorithm>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_set.h"
#include "base/containers/flat_map.h"
#include "base/logging.h"
#include "base/numerics/clamped_math.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "printing/backend/cups_connection.h"
#include "printing/backend/cups_ipp_constants.h"
#include "printing/backend/cups_printer.h"
#include "printing/backend/print_backend_consts.h"
#include "printing/backend/print_backend_utils.h"
#include "printing/mojom/print.mojom.h"
#include "printing/printing_utils.h"
#include "printing/units.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "base/functional/callback.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "printing/backend/ipp_handler_map.h"
#include "printing/backend/ipp_handlers.h"
#include "printing/printing_features.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace printing {
#if BUILDFLAG(IS_CHROMEOS)
constexpr int kPinMinimumLength = 4;
#endif // BUILDFLAG(IS_CHROMEOS)
namespace {
constexpr double kMMPerInch = 25.4;
constexpr double kCmPerInch = kMMPerInch * 0.1;
struct ColorMap {
const char* color;
mojom::ColorModel model;
};
struct DuplexMap {
const char* name;
mojom::DuplexMode mode;
};
const ColorMap kColorList[]{
{CUPS_PRINT_COLOR_MODE_COLOR, mojom::ColorModel::kColorModeColor},
{CUPS_PRINT_COLOR_MODE_MONOCHROME, mojom::ColorModel::kColorModeMonochrome},
};
const DuplexMap kDuplexList[]{
{CUPS_SIDES_ONE_SIDED, mojom::DuplexMode::kSimplex},
{CUPS_SIDES_TWO_SIDED_PORTRAIT, mojom::DuplexMode::kLongEdge},
{CUPS_SIDES_TWO_SIDED_LANDSCAPE, mojom::DuplexMode::kShortEdge},
};
mojom::ColorModel ColorModelFromIppColor(std::string_view ippColor) {
for (const ColorMap& color : kColorList) {
if (ippColor.compare(color.color) == 0) {
return color.model;
}
}
return mojom::ColorModel::kUnknownColorModel;
}
mojom::DuplexMode DuplexModeFromIpp(std::string_view ipp_duplex) {
for (const DuplexMap& entry : kDuplexList) {
if (base::EqualsCaseInsensitiveASCII(ipp_duplex, entry.name))
return entry.mode;
}
return mojom::DuplexMode::kUnknownDuplexMode;
}
mojom::ColorModel DefaultColorModel(const CupsOptionProvider& printer) {
// default color
ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppColor);
if (!attr)
return mojom::ColorModel::kUnknownColorModel;
const char* const value = ippGetString(attr, 0, nullptr);
return value ? ColorModelFromIppColor(value)
: mojom::ColorModel::kUnknownColorModel;
}
std::vector<mojom::ColorModel> SupportedColorModels(
const CupsOptionProvider& printer) {
std::vector<mojom::ColorModel> colors;
std::vector<std::string_view> color_modes =
printer.GetSupportedOptionValueStrings(kIppColor);
for (std::string_view color : color_modes) {
mojom::ColorModel color_model = ColorModelFromIppColor(color);
if (color_model != mojom::ColorModel::kUnknownColorModel) {
colors.push_back(color_model);
}
}
return colors;
}
void ExtractColor(const CupsOptionProvider& printer,
PrinterSemanticCapsAndDefaults* printer_info) {
printer_info->bw_model = mojom::ColorModel::kUnknownColorModel;
printer_info->color_model = mojom::ColorModel::kUnknownColorModel;
// color and b&w
std::vector<mojom::ColorModel> color_models = SupportedColorModels(printer);
for (mojom::ColorModel color : color_models) {
switch (color) {
case mojom::ColorModel::kColorModeColor:
printer_info->color_model = mojom::ColorModel::kColorModeColor;
break;
case mojom::ColorModel::kColorModeMonochrome:
printer_info->bw_model = mojom::ColorModel::kColorModeMonochrome;
break;
default:
// value not needed
break;
}
}
// changeable
printer_info->color_changeable =
(printer_info->color_model != mojom::ColorModel::kUnknownColorModel &&
printer_info->bw_model != mojom::ColorModel::kUnknownColorModel);
// default color
printer_info->color_default =
DefaultColorModel(printer) == mojom::ColorModel::kColorModeColor;
}
void ExtractDuplexModes(const CupsOptionProvider& printer,
PrinterSemanticCapsAndDefaults* printer_info) {
std::vector<std::string_view> duplex_modes =
printer.GetSupportedOptionValueStrings(kIppDuplex);
for (std::string_view duplex : duplex_modes) {
mojom::DuplexMode duplex_mode = DuplexModeFromIpp(duplex);
if (duplex_mode != mojom::DuplexMode::kUnknownDuplexMode)
printer_info->duplex_modes.push_back(duplex_mode);
}
ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppDuplex);
if (!attr) {
printer_info->duplex_default = mojom::DuplexMode::kUnknownDuplexMode;
return;
}
const char* const attr_str = ippGetString(attr, 0, nullptr);
printer_info->duplex_default = attr_str
? DuplexModeFromIpp(attr_str)
: mojom::DuplexMode::kUnknownDuplexMode;
}
void CopiesRange(const CupsOptionProvider& printer,
int* lower_bound,
int* upper_bound) {
ipp_attribute_t* attr = printer.GetSupportedOptionValues(kIppCopies);
if (!attr) {
*lower_bound = -1;
*upper_bound = -1;
}
*lower_bound = ippGetRange(attr, 0, upper_bound);
}
void ExtractCopies(const CupsOptionProvider& printer,
PrinterSemanticCapsAndDefaults* printer_info) {
// copies
int lower_bound;
int upper_bound;
CopiesRange(printer, &lower_bound, &upper_bound);
printer_info->copies_max =
(lower_bound != -1 && upper_bound >= 2) ? upper_bound : 1;
}
// Reads resolution from `attr` and puts into `size` in dots per inch.
std::optional<gfx::Size> GetResolution(ipp_attribute_t* attr, int i) {
ipp_res_t units;
int yres;
int xres = ippGetResolution(attr, i, &yres, &units);
if (!xres)
return {};
switch (units) {
case IPP_RES_PER_INCH:
return gfx::Size(xres, yres);
case IPP_RES_PER_CM:
return gfx::Size(xres * kCmPerInch, yres * kCmPerInch);
}
return {};
}
// Initializes `printer_info.dpis` with available resolutions and
// `printer_info.default_dpi` with default resolution provided by `printer`.
void ExtractResolutions(const CupsOptionProvider& printer,
PrinterSemanticCapsAndDefaults* printer_info) {
// Provide a default DPI if no valid DPI is found.
#if BUILDFLAG(IS_MAC)
constexpr gfx::Size kDefaultMissingDpi(kDefaultMacDpi, kDefaultMacDpi);
#elif BUILDFLAG(IS_LINUX)
constexpr gfx::Size kDefaultMissingDpi(kPixelsPerInch, kPixelsPerInch);
#else
constexpr gfx::Size kDefaultMissingDpi(kDefaultPdfDpi, kDefaultPdfDpi);
#endif
ipp_attribute_t* attr = printer.GetSupportedOptionValues(kIppResolution);
if (!attr) {
printer_info->dpis.push_back(kDefaultMissingDpi);
return;
}
int num_options = ippGetCount(attr);
for (int i = 0; i < num_options; i++) {
std::optional<gfx::Size> size = GetResolution(attr, i);
if (size)
printer_info->dpis.push_back(size.value());
}
ipp_attribute_t* def_attr = printer.GetDefaultOptionValue(kIppResolution);
std::optional<gfx::Size> size = GetResolution(def_attr, 0);
if (size) {
printer_info->default_dpi = size.value();
} else if (!printer_info->dpis.empty()) {
printer_info->default_dpi = printer_info->dpis[0];
} else {
printer_info->default_dpi = kDefaultMissingDpi;
}
if (printer_info->dpis.empty()) {
printer_info->dpis.push_back(printer_info->default_dpi);
}
}
std::optional<PrinterSemanticCapsAndDefaults::Paper>
PaperFromMediaColDatabaseEntry(ipp_t* db_entry) {
DCHECK(db_entry);
std::optional<MediaColData> size = ExtractMediaColData(db_entry);
if (!size) {
LOG(WARNING) << "Unable to create Paper from media-col-database";
return std::nullopt;
}
if (size->HasVariableWidth()) {
LOG(WARNING) << "Invalid media-col-database entry:"
<< " variable widths are not supported.";
return std::nullopt;
}
// Some PPDs (only ones with a custom height range) have a min height of 0,
// which doesn't work with the printing stack. If there is a min height of 0
// (or equal to the margins) change it to some small value. For entries that
// have a fixed height, the height will not get modified; if this fixed height
// is invalid, it will just get rejected below.
if (size->HasVariableHeight()) {
size->min_height = base::ClampMax(
size->min_height,
base::ClampedNumeric<int>(size->top_margin) + size->bottom_margin + 1);
}
if (size->min_width <= 0 || size->min_height <= 0 ||
size->bottom_margin < 0 || size->top_margin < 0 ||
size->left_margin < 0 || size->right_margin < 0 ||
size->min_width <=
base::ClampedNumeric<int>(size->left_margin) + size->right_margin ||
size->min_height <=
base::ClampedNumeric<int>(size->bottom_margin) + size->top_margin ||
(size->HasVariableHeight() && size->max_height < size->min_height)) {
LOG(WARNING) << "Invalid media-col-database entry:"
<< " width=" << size->min_width << "-" << size->max_width
<< " height=" << size->min_height << "-" << size->max_height
<< " media-bottom-margin=" << size->bottom_margin
<< " media-left-margin=" << size->left_margin
<< " media-right-margin=" << size->right_margin
<< " media-top-margin=" << size->top_margin;
return std::nullopt;
}
gfx::Size size_um(size->min_width * kMicronsPerPwgUnit,
size->min_height * kMicronsPerPwgUnit);
gfx::Rect printable_area_um = PrintableAreaFromSizeAndPwgMargins(
size_um, size->bottom_margin, size->left_margin, size->right_margin,
size->top_margin);
int max_height_um =
size->HasVariableHeight() ? size->max_height * kMicronsPerPwgUnit : 0;
return PrinterSemanticCapsAndDefaults::Paper(
/*display_name=*/"", /*vendor_id=*/"", size_um, printable_area_um,
max_height_um);
}
bool PaperIsBorderless(const PrinterSemanticCapsAndDefaults::Paper& paper) {
return paper.printable_area_um().x() == 0 &&
paper.printable_area_um().y() == 0 &&
paper.printable_area_um().width() == paper.size_um().width() &&
paper.printable_area_um().height() == paper.size_um().height();
}
PrinterSemanticCapsAndDefaults::Papers SupportedPapers(
const CupsPrinter& printer) {
auto size_comparer = [](const gfx::Size& a, const gfx::Size& b) {
auto result = a.width() - b.width();
if (result == 0) {
result = a.height() - b.height();
}
return result < 0;
};
std::map<gfx::Size, PrinterSemanticCapsAndDefaults::Paper,
decltype(size_comparer)>
paper_map;
ipp_attribute_t* attr = printer.GetMediaColDatabase();
int count = ippGetCount(attr);
for (int i = 0; i < count; i++) {
std::optional<PrinterSemanticCapsAndDefaults::Paper> paper_opt =
PaperFromMediaColDatabaseEntry(ippGetCollection(attr, i));
if (!paper_opt.has_value()) {
continue;
}
const auto& paper = paper_opt.value();
auto existing_entry = paper_map.find(paper.size_um());
if (existing_entry == paper_map.end()) {
paper_map.emplace(paper.size_um(), paper);
continue;
}
// When a paper size has both bordered and borderless variants, set the
// printable area according to the bordered variant, and set the flag
// indicating that a borderless variant exists.
if (PaperIsBorderless(existing_entry->second)) {
if (!PaperIsBorderless(paper)) {
existing_entry->second = paper;
existing_entry->second.set_has_borderless_variant(true);
}
} else if (PaperIsBorderless(paper)) {
existing_entry->second.set_has_borderless_variant(true);
}
}
PrinterSemanticCapsAndDefaults::Papers parsed_papers;
parsed_papers.reserve(paper_map.size());
for (const auto& entry : paper_map) {
parsed_papers.push_back(entry.second);
}
return parsed_papers;
}
// Overrides the given printer's default media type as needed.
void CorrectDefaultMediaType(PrinterSemanticCapsAndDefaults*& printer_info) {
// Some Canon printers give a proprietary default media type that's frequently
// unavailable to users.
if (base::StartsWith(printer_info->default_media_type.vendor_id, "com.canon",
base::CompareCase::INSENSITIVE_ASCII)) {
for (const auto& media_type : printer_info->media_types) {
if (media_type.vendor_id == "stationery") {
printer_info->default_media_type = media_type;
break;
}
}
}
}
void ExtractMediaTypes(const CupsOptionProvider& printer,
PrinterSemanticCapsAndDefaults* printer_info) {
std::vector<std::string_view> names =
printer.GetSupportedOptionValueStrings(kIppMediaType);
if (names.empty()) {
return;
}
printer_info->media_types.reserve(names.size());
for (std::string_view vendor_id : names) {
PrinterSemanticCapsAndDefaults::MediaType type;
type.vendor_id = std::string(vendor_id);
// This name will be overwritten by its Chromium localization if it's a
// standard IPP media type. But for non-standard types, the only
// human-readable name available is this one from the driver (or
// driverless printer).
const char* display_name = printer.GetLocalizedOptionValueName(
kIppMediaType, type.vendor_id.c_str());
if (display_name) {
type.display_name = display_name;
} else {
type.display_name = std::string(vendor_id);
}
printer_info->media_types.push_back(type);
}
// Set default media type, or use the first in the list if unavailable.
DCHECK(!printer_info->media_types.empty());
printer_info->default_media_type = printer_info->media_types[0];
ipp_t* media_col_default =
ippGetCollection(printer.GetDefaultOptionValue(kIppMediaCol), 0);
const char* media_type_default = ippGetString(
ippFindAttribute(media_col_default, "media-type", IPP_TAG_KEYWORD), 0,
nullptr);
if (media_type_default) {
// Don't set the "default" media type if it isn't in the list of supported
// media types for some reason.
for (const auto& media_type : printer_info->media_types) {
if (media_type.vendor_id == media_type_default) {
printer_info->default_media_type = media_type;
}
}
}
CorrectDefaultMediaType(printer_info);
}
bool CollateCapable(const CupsOptionProvider& printer) {
std::vector<std::string_view> values =
printer.GetSupportedOptionValueStrings(kIppCollate);
return base::Contains(values, kCollated) &&
base::Contains(values, kUncollated);
}
bool CollateDefault(const CupsOptionProvider& printer) {
ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppCollate);
if (!attr)
return false;
const char* const name = ippGetString(attr, 0, nullptr);
return name && !std::string_view(name).compare(kCollated);
}
#if BUILDFLAG(IS_CHROMEOS)
bool PinSupported(const CupsOptionProvider& printer) {
ipp_attribute_t* attr = printer.GetSupportedOptionValues(kIppPin);
if (!attr)
return false;
int password_maximum_length_supported = ippGetInteger(attr, 0);
if (password_maximum_length_supported < kPinMinimumLength)
return false;
std::vector<std::string_view> values =
printer.GetSupportedOptionValueStrings(kIppPinEncryption);
return base::Contains(values, kPinEncryptionNone);
}
// Returns the number of IPP attributes added to `caps` (not necessarily in
// 1-to-1 correspondence).
size_t AddAttributes(const CupsOptionProvider& printer,
const char* attr_group_name,
AdvancedCapabilities* caps) {
ipp_attribute_t* attr = printer.GetSupportedOptionValues(attr_group_name);
if (!attr)
return 0;
int num_options = ippGetCount(attr);
static const base::NoDestructor<HandlerMap> handlers(GenerateHandlers());
// The names of attributes that we know are not supported (b/266573545).
static constexpr auto kOptionsToIgnore =
base::MakeFixedFlatSet<std::string_view>(
{"finishings-col", "ipp-attribute-fidelity", "job-name",
"number-up-layout"});
std::vector<std::string> unknown_options;
size_t attr_count = 0;
for (int i = 0; i < num_options; i++) {
const char* option_name = ippGetString(attr, i, nullptr);
if (base::Contains(kOptionsToIgnore, option_name)) {
continue;
}
auto it = handlers->find(option_name);
if (it == handlers->end()) {
unknown_options.emplace_back(option_name);
continue;
}
size_t previous_size = caps->size();
// Run the handler that adds items to `caps` based on option type.
it->second.Run(printer, option_name, caps);
if (caps->size() > previous_size)
attr_count++;
}
if (!unknown_options.empty()) {
LOG(WARNING) << "Unknown IPP options: "
<< base::JoinString(unknown_options, ", ");
}
return attr_count;
}
// Adds the "Input Tray" option to Advanced Attributes.
size_t AddInputTray(const CupsOptionProvider& printer,
AdvancedCapabilities* caps) {
size_t previous_size = caps->size();
KeywordHandler(printer, "media-source", caps);
return caps->size() - previous_size;
}
void ExtractAdvancedCapabilities(const CupsOptionProvider& printer,
PrinterSemanticCapsAndDefaults* printer_info) {
AdvancedCapabilities* options = &printer_info->advanced_capabilities;
size_t attr_count = AddInputTray(printer, options);
attr_count += AddAttributes(printer, kIppJobAttributes, options);
attr_count += AddAttributes(printer, kIppDocumentAttributes, options);
base::UmaHistogramCounts1000("Printing.CUPS.IppAttributesCount", attr_count);
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace
PrinterSemanticCapsAndDefaults::Paper DefaultPaper(const CupsPrinter& printer) {
ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppMediaCol);
if (!attr)
return PrinterSemanticCapsAndDefaults::Paper();
ipp_t* media_col_default = ippGetCollection(attr, 0);
if (!media_col_default) {
return PrinterSemanticCapsAndDefaults::Paper();
}
PrinterSemanticCapsAndDefaults::Paper paper;
return PaperFromMediaColDatabaseEntry(media_col_default)
.value_or(PrinterSemanticCapsAndDefaults::Paper());
}
void CapsAndDefaultsFromPrinter(const CupsPrinter& printer,
PrinterSemanticCapsAndDefaults* printer_info) {
// collate
printer_info->collate_default = CollateDefault(printer);
printer_info->collate_capable = CollateCapable(printer);
// paper
printer_info->default_paper = DefaultPaper(printer);
printer_info->papers = SupportedPapers(printer);
#if BUILDFLAG(IS_CHROMEOS)
printer_info->pin_supported = PinSupported(printer);
ExtractAdvancedCapabilities(printer, printer_info);
#endif // BUILDFLAG(IS_CHROMEOS)
ExtractCopies(printer, printer_info);
ExtractColor(printer, printer_info);
ExtractDuplexModes(printer, printer_info);
ExtractResolutions(printer, printer_info);
ExtractMediaTypes(printer, printer_info);
}
gfx::Rect GetPrintableAreaForSize(const CupsPrinter& printer,
const gfx::Size& size_um) {
ipp_attribute_t* attr = printer.GetMediaColDatabase();
int count = ippGetCount(attr);
gfx::Rect result(0, 0, size_um.width(), size_um.height());
for (int i = 0; i < count; i++) {
ipp_t* db_entry = ippGetCollection(attr, i);
std::optional<PrinterSemanticCapsAndDefaults::Paper> paper_opt =
PaperFromMediaColDatabaseEntry(db_entry);
if (!paper_opt.has_value()) {
continue;
}
const auto& paper = paper_opt.value();
if (paper.size_um() != size_um) {
continue;
}
result = paper.printable_area_um();
// If this is a borderless size, try to find a non-borderless version.
if (!PaperIsBorderless(paper)) {
return result;
}
}
return result;
}
ScopedIppPtr WrapIpp(ipp_t* ipp) {
return ScopedIppPtr(ipp, IppDeleter());
}
void IppDeleter::operator()(ipp_t* ipp) const {
ippDelete(ipp);
}
std::optional<MediaColData> ExtractMediaColData(ipp_t* db_entry) {
if (!db_entry) {
LOG(WARNING) << "Invalid media-col-database entry: empty entry.";
return std::nullopt;
}
ipp_t* media_size = ippGetCollection(
ippFindAttribute(db_entry, kIppMediaSize, IPP_TAG_BEGIN_COLLECTION), 0);
if (!media_size) {
LOG(WARNING) << "Invalid media-col-database entry: empty media_size.";
return std::nullopt;
}
ipp_attribute_t* bottom_attr =
ippFindAttribute(db_entry, kIppMediaBottomMargin, IPP_TAG_INTEGER);
ipp_attribute_t* left_attr =
ippFindAttribute(db_entry, kIppMediaLeftMargin, IPP_TAG_INTEGER);
ipp_attribute_t* right_attr =
ippFindAttribute(db_entry, kIppMediaRightMargin, IPP_TAG_INTEGER);
ipp_attribute_t* top_attr =
ippFindAttribute(db_entry, kIppMediaTopMargin, IPP_TAG_INTEGER);
if (!bottom_attr || !left_attr || !right_attr || !top_attr) {
LOG(WARNING) << "Invalid media-col-database entry:"
<< " margins are not present.";
return std::nullopt;
}
int bottom_margin = ippGetInteger(bottom_attr, 0);
int left_margin = ippGetInteger(left_attr, 0);
int right_margin = ippGetInteger(right_attr, 0);
int top_margin = ippGetInteger(top_attr, 0);
ipp_attribute_t* width_attr =
ippFindAttribute(media_size, kIppXDimension, IPP_TAG_INTEGER);
ipp_attribute_t* height_attr =
ippFindAttribute(media_size, kIppYDimension, IPP_TAG_INTEGER);
ipp_attribute_t* width_range_attr =
ippFindAttribute(media_size, kIppXDimension, IPP_TAG_RANGE);
ipp_attribute_t* height_range_attr =
ippFindAttribute(media_size, kIppYDimension, IPP_TAG_RANGE);
// Ensure there is a width and height (or ranges).
if ((!width_attr && !width_range_attr) ||
(!height_attr && !height_range_attr)) {
LOG(WARNING) << "Invalid media-col-database entry:"
<< " media-size needs x and y (or x and y range).";
return std::nullopt;
}
int min_width = 0;
int min_height = 0;
int max_width = 0;
int max_height = 0;
if (width_attr) {
min_width = ippGetInteger(width_attr, 0);
max_width = min_width;
} else {
min_width = ippGetRange(width_range_attr, 0, &max_width);
}
if (height_attr) {
min_height = ippGetInteger(height_attr, 0);
max_height = min_height;
} else {
min_height = ippGetRange(height_range_attr, 0, &max_height);
}
if (min_width > max_width) {
LOG(WARNING) << "Invalid media-col-database entry:"
<< " min_width (" << min_width << ") > max_width ("
<< max_width << ").";
return std::nullopt;
}
if (min_height > max_height) {
LOG(WARNING) << "Invalid media-col-database entry:"
<< " min_height (" << min_height << ") > max_height ("
<< max_height << ").";
return std::nullopt;
}
#if BUILDFLAG(IS_MAC)
pwg_media_t* media = pwgMediaForSize(max_width, max_height);
if (media && (media->width != max_width || media->length != max_height)) {
// Paper size detected to be close to a standard size. While the maximum
// size will be based on this media, must also do checks to adjust the
// minimum dimensions, as they must not end up exceeding the new maximum.
if (min_width == max_width || min_width > media->width) {
min_width = media->width;
}
if (min_height == max_height || min_height > media->length) {
min_height = media->length;
}
max_width = media->width;
max_height = media->length;
}
#endif
return MediaColData{min_width, min_height, max_width, max_height,
bottom_margin, left_margin, right_margin, top_margin};
}
ScopedIppPtr NewMediaCollection(const MediaColData& data) {
// Don't create any entries with a variable width.
if (data.HasVariableWidth()) {
return nullptr;
}
ScopedIppPtr media_col = WrapIpp(ippNew());
ScopedIppPtr media_size = WrapIpp(ippNew());
ippAddInteger(media_size.get(), IPP_TAG_PRINTER, IPP_TAG_INTEGER,
kIppXDimension, data.min_width);
if (data.HasVariableHeight()) {
ippAddRange(media_size.get(), IPP_TAG_PRINTER, kIppYDimension,
data.min_height, data.max_height);
} else {
ippAddInteger(media_size.get(), IPP_TAG_PRINTER, IPP_TAG_INTEGER,
kIppYDimension, data.min_height);
}
ippAddCollection(media_col.get(), IPP_TAG_PRINTER, kIppMediaSize,
media_size.get());
ippAddInteger(media_col.get(), IPP_TAG_PRINTER, IPP_TAG_INTEGER,
kIppMediaBottomMargin, data.bottom_margin);
ippAddInteger(media_col.get(), IPP_TAG_PRINTER, IPP_TAG_INTEGER,
kIppMediaLeftMargin, data.left_margin);
ippAddInteger(media_col.get(), IPP_TAG_PRINTER, IPP_TAG_INTEGER,
kIppMediaRightMargin, data.right_margin);
ippAddInteger(media_col.get(), IPP_TAG_PRINTER, IPP_TAG_INTEGER,
kIppMediaTopMargin, data.top_margin);
return media_col;
}
void FilterMediaColSizes(ScopedIppPtr& attributes) {
if (!attributes) {
return;
}
ipp_attribute_t* media_col_db = ippFindAttribute(
attributes.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);
if (!media_col_db) {
return;
}
// `fixed_widths` is the width for any media-col-database entry that is
// non-variable. For instance, if the media-col-database has 4 fixed
// widthxheight entries and one variable widthxheight entry like so:
//
// 58mmx200mm
// 58mmx2000mm
// 80mmx200mm
// 80mmx2000mm
// 10-80mmx20-2000mm
//
// then `fixed_widths` will contain 58 and 80. After filtering, the following
// sizes will exist:
//
// 58mmx200mm
// 58mmx2000mm
// 80mmx200mm
// 80mmx2000mm
// 58mmx20-2000mm
// 80mmx20-2000mm
std::set<int> fixed_widths;
std::vector<ScopedIppPtr> new_entries;
std::vector<MediaColData> variable_height_entries;
// Loop over all the `media_col_db` entries and either add them to
// `new_entries`, skip them, or save them in `variable_height_entries` so
// they can be added later (once all the fixed widths have been discovered).
for (int i = 0; i < ippGetCount(media_col_db); i++) {
ipp_t* db_entry = ippGetCollection(media_col_db, i);
std::optional<MediaColData> size = ExtractMediaColData(db_entry);
if (!size.has_value()) {
return;
}
// TODO(nmuggli): Consider adding the boundaries of a variable-width entry
// to `fixed_widths`.
if (!size->HasVariableWidth()) {
fixed_widths.insert(size->min_width);
}
// Handle four different cases:
// 1. Variable width and fixed height. Skip this - variable widths are not
// supported.
// 2. Fixed width and fixed height. Add to the new array.
// 3. Fixed width and variable height. Add to the new array.
// 4. Variable width and variable height. Save this entry until after
// all of the entries are processed. For each fixed width that fits
// within this variable width, a new entry will get added with the
// variable height.
// Case 1: skip over this.
if (size->HasVariableWidth() && !size->HasVariableHeight()) {
continue;
}
// Case 2 and case 3 - add to `new_entries`.
if (!size->HasVariableWidth()) {
ScopedIppPtr new_entry = NewMediaCollection(size.value());
if (new_entry) {
new_entries.push_back(std::move(new_entry));
}
continue;
}
// Case 4: Save entry for later processing.
variable_height_entries.push_back(size.value());
}
for (const MediaColData& variable_height_entry : variable_height_entries) {
// For each fixed width, create a new media size entry for that width and a
// variable height.
for (int width : fixed_widths) {
MediaColData size = variable_height_entry;
// Ensure `width` fits within the variable width.
if (width < size.min_width || width > size.max_width) {
continue;
}
size.min_width = width;
size.max_width = width;
ScopedIppPtr new_entry = NewMediaCollection(size);
if (new_entry) {
new_entries.push_back(std::move(new_entry));
}
}
}
// Finally, update the attribute that was passed in with the new entries.
ippDeleteAttribute(attributes.get(), media_col_db);
std::vector<const ipp_t*> raw_entries(new_entries.size());
base::ranges::transform(new_entries, raw_entries.begin(), &ScopedIppPtr::get);
ippAddCollections(attributes.get(), IPP_TAG_PRINTER, kIppMediaColDatabase,
raw_entries.size(), raw_entries.data());
}
} // namespace printing