0

extension: printing: provide FitToPage values 1/*

FitToPageType represents values supported by printers, which are
extracted via CUPS by checking "print-scaling" options.

This patch adds necessary types and containers to allow clients of
chrome.printing access these supported values.

Design doc:
https://docs.google.com/document/d/1EgbaHpFDl8sEMYDaeL0oAta3lSa_x4Nv9KmSfScyKRk

Bug: 316999874, 308709702
Change-Id: I23a6e7720e0860c44bad4048f9a1a56db5d23018
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6355189
Reviewed-by: Antonio Sartori <antoniosartori@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Maksim Sisov <msisov@igalia.com>
Reviewed-by: Nathan Muggli <nmuggli@google.com>
Cr-Commit-Position: refs/heads/main@{#1439414}
This commit is contained in:
Maksim Sisov 2025-03-28 08:02:06 -07:00 committed by Chromium LUCI CQ
parent 128fa3a913
commit c5d2479072
15 changed files with 659 additions and 24 deletions

@ -53,4 +53,8 @@ source_set("unit_tests") {
"//testing/gtest",
"//ui/gfx/geometry:geometry",
]
if (is_chromeos) {
deps += [ "//printing" ]
}
}

@ -20,6 +20,10 @@
#include "printing/backend/print_backend.h"
#include "printing/mojom/print.mojom.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "printing/printing_features.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace printer = cloud_devices::printer;
namespace cloud_print {
@ -256,6 +260,49 @@ printer::VendorCapabilities GetVendorCapabilities(
return vendor_capabilities;
}
printer::FitToPageCapability GetFitToPageCapabilities(
const printing::PrinterSemanticCapsAndDefaults& semantic_info) {
auto ToFitToPageType = [](const printing::mojom::PrintScalingType& type) {
switch (type) {
case printing::mojom::PrintScalingType::kAuto:
return printer::FitToPageType::AUTO;
case printing::mojom::PrintScalingType::kAutoFit:
return printer::FitToPageType::AUTO_FIT;
case printing::mojom::PrintScalingType::kFit:
return printer::FitToPageType::FIT;
case printing::mojom::PrintScalingType::kFill:
return printer::FitToPageType::FILL;
case printing::mojom::PrintScalingType::kNone:
return printer::FitToPageType::NONE;
case printing::mojom::PrintScalingType::kUnknownPrintScalingType:
NOTREACHED();
}
};
printer::FitToPageCapability fit_to_page;
for (const auto& value : semantic_info.print_scaling_types) {
if (value == printing::mojom::PrintScalingType::kUnknownPrintScalingType) {
continue;
}
fit_to_page.AddOption(ToFitToPageType(value));
}
if (semantic_info.print_scaling_type_default !=
printing::mojom::PrintScalingType::kUnknownPrintScalingType) {
auto default_type =
ToFitToPageType(semantic_info.print_scaling_type_default);
// If default value is not among supported options, return empty options.
if (!fit_to_page.Contains(default_type)) {
return {};
}
fit_to_page.AddDefaultOption(default_type, true);
} else if (!fit_to_page.empty()) {
fit_to_page.AddDefaultOption(fit_to_page[0], true);
}
return fit_to_page;
}
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)
@ -357,6 +404,17 @@ base::Value PrinterSemanticCapsAndDefaultsToCdd(
GetVendorCapabilities(semantic_info);
vendor_capabilities.SaveTo(&description);
}
if (base::FeatureList::IsEnabled(
printing::features::kApiPrintingMarginsAndScale)) {
if (!semantic_info.print_scaling_types.empty()) {
printer::FitToPageCapability fit_to_page =
GetFitToPageCapabilities(semantic_info);
if (fit_to_page.IsValid()) {
fit_to_page.SaveTo(&description);
}
}
}
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)

@ -12,6 +12,11 @@
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "base/test/scoped_feature_list.h"
#include "printing/printing_features.h" // nogncheck
#endif // BUILDFLAG(IS_CHROMEOS)
namespace cloud_print {
namespace {
@ -247,6 +252,49 @@ constexpr char kExpectedAdvancedCapabilities[] = R"json([
"type": "SELECT"
}
])json";
constexpr char kExpectedFitToPageValues[] = R"json({
"option": [ {
"type": "AUTO"
}, {
"type": "AUTO_FIT"
}, {
"type": "FILL"
}, {
"type": "FIT"
}, {
"type": "NONE"
}, {
"is_default": true,
"type": "FIT"
}
]})json";
constexpr char kExpectedFitToPageValues2[] = R"json({
"option": [ {
"type": "FILL"
}, {
"type": "NONE"
}, {
"type": "AUTO"
}, {
"type": "FIT"
}, {
"type": "AUTO_FIT"
}, {
"is_default": true,
"type": "FILL"
}
]})json";
constexpr char kExpectedFitToPageSingleValue[] = R"json({
"option": [ {
"type": "AUTO_FIT"
}, {
"is_default": true,
"type": "AUTO_FIT"
}
]})json";
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)
@ -306,17 +354,21 @@ const base::Value::Dict* GetPrinterDict(const base::Value& caps_value) {
} // namespace
TEST(CloudPrintCddConversionTest, ValidCloudPrintCddConversion) {
#if BUILDFLAG(IS_CHROMEOS)
base::test::ScopedFeatureList feature_list(
printing::features::kApiPrintingMarginsAndScale);
#endif // BUILDFLAG(IS_CHROMEOS)
const printing::PrinterSemanticCapsAndDefaults input =
printing::GenerateSamplePrinterSemanticCapsAndDefaults({});
const base::Value output = PrinterSemanticCapsAndDefaultsToCdd(input);
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
size_t expected_dict_size = 9;
#if BUILDFLAG(IS_CHROMEOS)
ASSERT_EQ(10u, printer_dict->size());
#else
ASSERT_EQ(9u, printer_dict->size());
++expected_dict_size;
#endif // BUILDFLAG(IS_CHROMEOS)
ASSERT_EQ(expected_dict_size, printer_dict->size());
EXPECT_THAT(
*printer_dict,
base::test::IsSupersetOfValue(
@ -337,6 +389,7 @@ TEST(CloudPrintCddConversionTest, ValidCloudPrintCddConversion) {
#if BUILDFLAG(IS_CHROMEOS)
EXPECT_THAT(printer_dict->Find("pin"),
Pointee(base::test::IsJson(kExpectedPinSupportedFalse)));
ASSERT_FALSE(printer_dict->contains("fit_to_page"));
#endif // BUILDFLAG(IS_CHROMEOS)
}
@ -349,11 +402,11 @@ TEST(CloudPrintCddConversionTest, MissingEntry) {
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
size_t expected_dict_size = 8;
#if BUILDFLAG(IS_CHROMEOS)
ASSERT_EQ(9u, printer_dict->size());
#else
ASSERT_EQ(8u, printer_dict->size());
++expected_dict_size;
#endif // BUILDFLAG(IS_CHROMEOS)
ASSERT_EQ(expected_dict_size, printer_dict->size());
ASSERT_FALSE(printer_dict->contains("collate"));
}
@ -366,11 +419,11 @@ TEST(CloudPrintCddConversionTest, CollateDefaultIsFalse) {
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
size_t expected_dict_size = 9;
#if BUILDFLAG(IS_CHROMEOS)
ASSERT_EQ(10u, printer_dict->size());
#else
ASSERT_EQ(9u, printer_dict->size());
++expected_dict_size;
#endif // BUILDFLAG(IS_CHROMEOS)
ASSERT_EQ(expected_dict_size, printer_dict->size());
EXPECT_THAT(printer_dict->Find("collate"),
Pointee(base::test::IsJson(kExpectedCollateDefaultFalse)));
}
@ -389,11 +442,11 @@ TEST(CloudPrintCddConversionTest, WiderPaper) {
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
size_t expected_dict_size = 9;
#if BUILDFLAG(IS_CHROMEOS)
ASSERT_EQ(10u, printer_dict->size());
#else
ASSERT_EQ(9u, printer_dict->size());
++expected_dict_size;
#endif // BUILDFLAG(IS_CHROMEOS)
ASSERT_EQ(expected_dict_size, printer_dict->size());
EXPECT_THAT(printer_dict->Find("media_size"),
Pointee(base::test::IsJson(kExpectedMediaSizeWithWiderPaper)));
}
@ -412,15 +465,16 @@ TEST(CloudPrintCddConversionTest, MediaTypeOnlyOne) {
EXPECT_FALSE(printer_dict->contains("media_type"));
}
TEST(CloudPrintCddConversionTest, PinAndAdvancedCapabilities) {
TEST(CloudPrintCddConversionTest, FitToPageAndPinAndAdvancedCapabilities) {
printing::PrinterSemanticCapsAndDefaults input =
printing::GenerateSamplePrinterSemanticCapsAndDefaults(
printing::SampleWithPinAndAdvancedCapabilities());
const base::Value output = PrinterSemanticCapsAndDefaultsToCdd(input);
printing::SampleWithScaleAndPinAndAdvancedCapabilities());
base::Value output = PrinterSemanticCapsAndDefaultsToCdd(input);
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
ASSERT_EQ(11u, printer_dict->size());
size_t expected_dict_size = 11;
ASSERT_EQ(expected_dict_size, printer_dict->size());
EXPECT_THAT(
*printer_dict,
base::test::IsSupersetOfValue(
@ -428,6 +482,188 @@ TEST(CloudPrintCddConversionTest, PinAndAdvancedCapabilities) {
.Set("pin", base::test::ParseJson(kExpectedPinSupportedTrue))
.Set("vendor_capability",
base::test::ParseJson(kExpectedAdvancedCapabilities))));
EXPECT_FALSE(printer_dict->contains("fit_to_page"));
base::test::ScopedFeatureList feature_list(
printing::features::kApiPrintingMarginsAndScale);
output = PrinterSemanticCapsAndDefaultsToCdd(input);
printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
ASSERT_EQ(++expected_dict_size, printer_dict->size());
EXPECT_THAT(
*printer_dict,
base::test::IsSupersetOfValue(base::Value::Dict().Set(
"fit_to_page", base::test::ParseJson(kExpectedFitToPageValues))));
}
TEST(CloudPrintCddConversionTest, FitToPageNoCapability) {
base::test::ScopedFeatureList feature_list(
printing::features::kApiPrintingMarginsAndScale);
printing::PrinterSemanticCapsAndDefaults printer_info;
base::Value output =
cloud_print::PrinterSemanticCapsAndDefaultsToCdd(printer_info);
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
ASSERT_EQ(5u, printer_dict->size());
EXPECT_FALSE(printer_dict->contains("fit_to_page"));
}
TEST(CloudPrintCddConversionTest, FitToPageSingleValue) {
base::test::ScopedFeatureList feature_list(
printing::features::kApiPrintingMarginsAndScale);
printing::PrinterSemanticCapsAndDefaults printer_info;
printer_info.print_scaling_types = {
printing::mojom::PrintScalingType::kAutoFit};
base::Value output =
cloud_print::PrinterSemanticCapsAndDefaultsToCdd(printer_info);
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
ASSERT_EQ(6u, printer_dict->size());
EXPECT_TRUE(printer_dict->contains("fit_to_page"));
EXPECT_THAT(*printer_dict,
base::test::IsSupersetOfValue(base::Value::Dict().Set(
"fit_to_page",
base::test::ParseJson(kExpectedFitToPageSingleValue))));
}
TEST(CloudPrintCddConversionTest, FitToPageDefaultValueOnly) {
base::test::ScopedFeatureList feature_list(
printing::features::kApiPrintingMarginsAndScale);
printing::PrinterSemanticCapsAndDefaults printer_info;
printer_info.print_scaling_type_default =
printing::mojom::PrintScalingType::kFit;
base::Value output =
cloud_print::PrinterSemanticCapsAndDefaultsToCdd(printer_info);
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
ASSERT_EQ(5u, printer_dict->size());
EXPECT_FALSE(printer_dict->contains("fit_to_page"));
}
TEST(CloudPrintCddConversionTest, FitToPageNoDefaultInSupported) {
base::test::ScopedFeatureList feature_list(
printing::features::kApiPrintingMarginsAndScale);
printing::PrinterSemanticCapsAndDefaults printer_info;
printer_info.print_scaling_types = {
printing::mojom::PrintScalingType::kAutoFit};
printer_info.print_scaling_type_default =
printing::mojom::PrintScalingType::kFit;
base::Value output =
cloud_print::PrinterSemanticCapsAndDefaultsToCdd(printer_info);
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
ASSERT_EQ(5u, printer_dict->size());
EXPECT_FALSE(printer_dict->contains("fit_to_page"));
}
TEST(CloudPrintCddConversionTest, FitToPageUnknownDefault) {
base::test::ScopedFeatureList feature_list(
printing::features::kApiPrintingMarginsAndScale);
printing::PrinterSemanticCapsAndDefaults printer_info;
printer_info.print_scaling_type_default =
printing::mojom::PrintScalingType::kUnknownPrintScalingType;
printer_info.print_scaling_types = {
printing::mojom::PrintScalingType::kFill,
printing::mojom::PrintScalingType::kNone,
printing::mojom::PrintScalingType::kAuto,
printing::mojom::PrintScalingType::kFit,
printing::mojom::PrintScalingType::kAutoFit,
printing::mojom::PrintScalingType::kUnknownPrintScalingType};
base::Value output =
cloud_print::PrinterSemanticCapsAndDefaultsToCdd(printer_info);
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
ASSERT_EQ(6u, printer_dict->size());
EXPECT_THAT(
*printer_dict,
base::test::IsSupersetOfValue(base::Value::Dict().Set(
"fit_to_page", base::test::ParseJson(kExpectedFitToPageValues2))));
}
TEST(CloudPrintCddConversionTest, FitToPageUnknownsOnly) {
base::test::ScopedFeatureList feature_list(
printing::features::kApiPrintingMarginsAndScale);
printing::PrinterSemanticCapsAndDefaults printer_info;
printer_info.print_scaling_type_default =
printing::mojom::PrintScalingType::kUnknownPrintScalingType;
printer_info.print_scaling_types = {
printing::mojom::PrintScalingType::kUnknownPrintScalingType};
base::Value output =
cloud_print::PrinterSemanticCapsAndDefaultsToCdd(printer_info);
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
ASSERT_EQ(5u, printer_dict->size());
EXPECT_FALSE(printer_dict->contains("fit_to_page"));
}
TEST(CloudPrintCddConversionTest, FitToPageCorrectMapping) {
base::test::ScopedFeatureList feature_list(
printing::features::kApiPrintingMarginsAndScale);
struct ScalingTypeToString {
printing::mojom::PrintScalingType type;
std::string str;
};
constexpr std::array<ScalingTypeToString, 6> kScalingTypes{
ScalingTypeToString{printing::mojom::PrintScalingType::kAuto, "AUTO"},
ScalingTypeToString{printing::mojom::PrintScalingType::kAutoFit,
"AUTO_FIT"},
ScalingTypeToString{printing::mojom::PrintScalingType::kFill, "FILL"},
ScalingTypeToString{printing::mojom::PrintScalingType::kFit, "FIT"},
ScalingTypeToString{printing::mojom::PrintScalingType::kNone, "NONE"},
ScalingTypeToString{
printing::mojom::PrintScalingType::kUnknownPrintScalingType, ""}};
for (const auto& value : kScalingTypes) {
printing::PrinterSemanticCapsAndDefaults printer_info;
printer_info.print_scaling_types = {value.type};
base::Value output =
cloud_print::PrinterSemanticCapsAndDefaultsToCdd(printer_info);
const base::Value::Dict* printer_dict = GetPrinterDict(output);
ASSERT_TRUE(printer_dict);
if (value.type ==
printing::mojom::PrintScalingType::kUnknownPrintScalingType) {
ASSERT_EQ(5u, printer_dict->size());
EXPECT_FALSE(printer_dict->contains("fit_to_page"));
} else {
std::string formatted_json = base::StringPrintf(
R"json({
"option": [ {
"type": "%s"
}, {
"is_default": true,
"type": "%s"
}]
})json",
value.str.c_str(), value.str.c_str());
ASSERT_EQ(6u, printer_dict->size());
EXPECT_THAT(*printer_dict,
base::test::IsSupersetOfValue(base::Value::Dict().Set(
"fit_to_page", base::test::ParseJson(formatted_json))));
}
}
}
#endif // BUILDFLAG(IS_CHROMEOS)

@ -68,6 +68,9 @@ constexpr char kIppClientStringVersion[] = "client-string-version";
constexpr char kIppClientType[] = "client-type";
constexpr char kIppClientVersion[] = "client-version";
// printer attributes
constexpr char kIppPrintScaling[] = "print-scaling"; // PWG 5100.16
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace printing

@ -67,6 +67,9 @@ COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppClientStringVersion[];
COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppClientType[];
COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppClientVersion[];
// printer attributes
COMPONENT_EXPORT(PRINT_BACKEND) extern const char kIppPrintScaling[];
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace printing

@ -503,6 +503,63 @@ void ExtractAdvancedCapabilities(const CupsOptionProvider& printer,
attr_count += AddAttributes(printer, kIppDocumentAttributes, options);
base::UmaHistogramCounts1000("Printing.CUPS.IppAttributesCount", attr_count);
}
// Convert string value to mojom::PrintScalingType
mojom::PrintScalingType PrintScalingTypeFromString(
const std::string_view value) {
if (value == "auto") {
return mojom::PrintScalingType::kAuto;
}
if (value == "auto-fit") {
return mojom::PrintScalingType::kAutoFit;
}
if (value == "fill") {
return mojom::PrintScalingType::kFill;
}
if (value == "fit") {
return mojom::PrintScalingType::kFit;
}
if (value == "none") {
return mojom::PrintScalingType::kNone;
}
// Default to unknown for any unrecognized values.
return mojom::PrintScalingType::kUnknownPrintScalingType;
}
void ExtractPrintScaling(const CupsOptionProvider& printer,
PrinterSemanticCapsAndDefaults* printer_info) {
printer_info->print_scaling_types.clear();
printer_info->print_scaling_type_default =
mojom::PrintScalingType::kUnknownPrintScalingType;
std::vector<std::string_view> values =
printer.GetSupportedOptionValueStrings(kIppPrintScaling);
for (const auto& value : values) {
auto type = PrintScalingTypeFromString(value);
if (type != mojom::PrintScalingType::kUnknownPrintScalingType) {
printer_info->print_scaling_types.emplace_back(type);
}
}
if (printer_info->print_scaling_types.empty()) {
return;
}
// Get default value
ipp_attribute_t* attr = printer.GetDefaultOptionValue(kIppPrintScaling);
if (const char* const name = ippGetString(attr, 0, nullptr)) {
printer_info->print_scaling_type_default = PrintScalingTypeFromString(name);
}
if (printer_info->print_scaling_type_default ==
mojom::PrintScalingType::kUnknownPrintScalingType &&
!printer_info->print_scaling_types.empty()) {
// If no default is provided, use the first value.
printer_info->print_scaling_type_default =
printer_info->print_scaling_types[0];
}
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace
@ -534,6 +591,7 @@ void CapsAndDefaultsFromPrinter(const CupsPrinter& printer,
#if BUILDFLAG(IS_CHROMEOS)
printer_info->pin_supported = PinSupported(printer);
ExtractAdvancedCapabilities(printer, printer_info);
ExtractPrintScaling(printer, printer_info);
#endif // BUILDFLAG(IS_CHROMEOS)
ExtractCopies(printer, printer_info);

@ -9,6 +9,7 @@
#include <map>
#include <memory>
#include <string_view>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
@ -24,6 +25,8 @@
namespace printing {
using ::testing::Pointwise;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
using ::testing::UnorderedPointwise;
// Matches the name field to a string.
@ -400,8 +403,8 @@ TEST_F(PrintBackendCupsIppHelperTest, DuplexSupported) {
CapsAndDefaultsFromPrinter(*printer_, &caps);
EXPECT_THAT(caps.duplex_modes,
testing::UnorderedElementsAre(mojom::DuplexMode::kSimplex,
mojom::DuplexMode::kLongEdge));
UnorderedElementsAre(mojom::DuplexMode::kSimplex,
mojom::DuplexMode::kLongEdge));
EXPECT_EQ(mojom::DuplexMode::kSimplex, caps.duplex_default);
}
@ -414,7 +417,7 @@ TEST_F(PrintBackendCupsIppHelperTest, DuplexNotSupported) {
CapsAndDefaultsFromPrinter(*printer_, &caps);
EXPECT_THAT(caps.duplex_modes,
testing::UnorderedElementsAre(mojom::DuplexMode::kSimplex));
UnorderedElementsAre(mojom::DuplexMode::kSimplex));
EXPECT_EQ(mojom::DuplexMode::kSimplex, caps.duplex_default);
}
@ -1170,6 +1173,137 @@ TEST_F(PrintBackendCupsIppHelperTest, MediaSource) {
Pointwise(AdvancedCapabilityName(),
{"top", "main", "auto", "tray-3", "tray-4"}));
}
// Test print-scaling values are correctly stored.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes) {
printer_->SetSupportedOptions(
"print-scaling",
MakeStringCollection(ipp_, {"auto", "auto-fit", "fill", "fit", "none"}));
printer_->SetOptionDefault("print-scaling", MakeString(ipp_, "fit"));
PrinterSemanticCapsAndDefaults caps;
CapsAndDefaultsFromPrinter(*printer_, &caps);
EXPECT_EQ(caps.print_scaling_type_default, mojom::PrintScalingType::kFit);
EXPECT_THAT(
caps.print_scaling_types,
UnorderedElementsAreArray(
{mojom::PrintScalingType::kAuto, mojom::PrintScalingType::kAutoFit,
mojom::PrintScalingType::kFill, mojom::PrintScalingType::kFit,
mojom::PrintScalingType::kNone}));
}
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_CorrectMapping) {
struct ScalingTypeToString {
std::string str;
mojom::PrintScalingType type;
};
constexpr std::array<ScalingTypeToString, 5> kScalingTypes{
ScalingTypeToString{"auto", mojom::PrintScalingType::kAuto},
ScalingTypeToString{"auto-fit", mojom::PrintScalingType::kAutoFit},
ScalingTypeToString{"fill", mojom::PrintScalingType::kFill},
ScalingTypeToString{"fit", mojom::PrintScalingType::kFit},
ScalingTypeToString{"none", mojom::PrintScalingType::kNone}};
for (const auto& value : kScalingTypes) {
printer_->SetSupportedOptions(
"print-scaling", MakeStringCollection(ipp_, {value.str.c_str()}));
PrinterSemanticCapsAndDefaults caps;
CapsAndDefaultsFromPrinter(*printer_, &caps);
EXPECT_EQ(caps.print_scaling_type_default, value.type);
EXPECT_THAT(caps.print_scaling_types,
UnorderedElementsAreArray({value.type}));
}
}
// Test first value from supported values is used as default if default is
// missing.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_NoDefault) {
printer_->SetSupportedOptions(
"print-scaling",
MakeStringCollection(ipp_, {"auto-fit", "fill", "none"}));
PrinterSemanticCapsAndDefaults caps;
CapsAndDefaultsFromPrinter(*printer_, &caps);
EXPECT_EQ(caps.print_scaling_type_default, mojom::PrintScalingType::kAutoFit);
EXPECT_THAT(caps.print_scaling_types,
UnorderedElementsAreArray({mojom::PrintScalingType::kAutoFit,
mojom::PrintScalingType::kFill,
mojom::PrintScalingType::kNone}));
}
// Test first value from supported values is used as default if default is
// unknown.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_DefaultUnknown) {
printer_->SetSupportedOptions(
"print-scaling", MakeStringCollection(ipp_, {"fit", "fill", "none"}));
printer_->SetOptionDefault("print-scaling", MakeString(ipp_, "value-1"));
PrinterSemanticCapsAndDefaults caps;
CapsAndDefaultsFromPrinter(*printer_, &caps);
EXPECT_EQ(caps.print_scaling_type_default, mojom::PrintScalingType::kFit);
EXPECT_THAT(caps.print_scaling_types,
UnorderedElementsAreArray({mojom::PrintScalingType::kFit,
mojom::PrintScalingType::kFill,
mojom::PrintScalingType::kNone}));
}
// Test no values are stored when there are no supported values.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_NoValues) {
PrinterSemanticCapsAndDefaults caps;
CapsAndDefaultsFromPrinter(*printer_, &caps);
EXPECT_EQ(caps.print_scaling_type_default,
mojom::PrintScalingType::kUnknownPrintScalingType);
EXPECT_TRUE(caps.print_scaling_types.empty());
}
// Test no values are stored despite printer saying it has one default value
// while supported values are empty.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_NoValues2) {
printer_->SetOptionDefault("print-scaling", MakeString(ipp_, "auto"));
PrinterSemanticCapsAndDefaults caps;
CapsAndDefaultsFromPrinter(*printer_, &caps);
EXPECT_EQ(caps.print_scaling_type_default,
mojom::PrintScalingType::kUnknownPrintScalingType);
EXPECT_TRUE(caps.print_scaling_types.empty());
}
// Test unknown values are not stored and default is correctly set as first
// value of known print-scaling types.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_UnknownValues) {
printer_->SetSupportedOptions(
"print-scaling",
MakeStringCollection(ipp_, {"value-1", "value-2", "auto", "none"}));
PrinterSemanticCapsAndDefaults caps;
CapsAndDefaultsFromPrinter(*printer_, &caps);
EXPECT_EQ(caps.print_scaling_type_default, mojom::PrintScalingType::kAuto);
EXPECT_THAT(caps.print_scaling_types,
UnorderedElementsAreArray({mojom::PrintScalingType::kAuto,
mojom::PrintScalingType::kNone}));
}
// Test default is not set if only unknown values are supported.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_UnknownValues2) {
printer_->SetSupportedOptions(
"print-scaling",
MakeStringCollection(ipp_, {"value-1", "value-2", "value-3", "value-4"}));
PrinterSemanticCapsAndDefaults caps;
CapsAndDefaultsFromPrinter(*printer_, &caps);
EXPECT_EQ(caps.print_scaling_type_default,
mojom::PrintScalingType::kUnknownPrintScalingType);
EXPECT_TRUE(caps.print_scaling_types.empty());
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace printing

@ -119,4 +119,10 @@ struct PrinterSemanticCapsAndDefaults {
array<MediaType>? media_types; // Allowed to be empty.
MediaType? default_media_type;
// Print scaling capability
[EnableIf=is_chromeos]
array<PrintScalingType> print_scaling_types; // Allowed to be empty.
[EnableIf=is_chromeos]
PrintScalingType print_scaling_type_default = kUnknownPrintScalingType;
};

@ -408,6 +408,16 @@ bool StructTraits<printing::mojom::PrinterSemanticCapsAndDefaultsDataView,
base::debug::DumpWithoutCrashing();
return false;
}
if (!data.ReadPrintScalingTypes(&out->print_scaling_types)) {
base::debug::Alias(&data);
base::debug::DumpWithoutCrashing();
return false;
}
if (!data.ReadPrintScalingTypeDefault(&out->print_scaling_type_default)) {
base::debug::Alias(&data);
base::debug::DumpWithoutCrashing();
return false;
}
#endif // BUILDFLAG(IS_CHROMEOS)
// Extra validity checks.
@ -448,6 +458,14 @@ bool StructTraits<printing::mojom::PrinterSemanticCapsAndDefaultsDataView,
base::debug::DumpWithoutCrashing();
return false;
}
DuplicateChecker<printing::mojom::PrintScalingType>
print_scaling_types_dup_checker;
if (print_scaling_types_dup_checker.HasDuplicates(out->print_scaling_types)) {
DLOG(ERROR) << "Duplicate print_scaling_types detected.";
base::debug::Alias(&data);
base::debug::DumpWithoutCrashing();
return false;
}
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)

@ -250,6 +250,14 @@ struct StructTraits<printing::mojom::PrinterSemanticCapsAndDefaultsDataView,
const printing::PrinterSemanticCapsAndDefaults& p) {
return p.advanced_capabilities;
}
static const std::vector<printing::mojom::PrintScalingType>&
print_scaling_types(const printing::PrinterSemanticCapsAndDefaults& p) {
return p.print_scaling_types;
}
static printing::mojom::PrintScalingType print_scaling_type_default(
const printing::PrinterSemanticCapsAndDefaults& p) {
return p.print_scaling_type_default;
}
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)

@ -15,8 +15,16 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "testing/gmock/include/gmock/gmock.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace printing {
#if BUILDFLAG(IS_CHROMEOS)
using ::testing::UnorderedElementsAreArray;
#endif // BUILDFLAG(IS_CHROMEOS)
TEST(PrintBackendMojomTraitsTest, TestSerializeAndDeserializePrinterBasicInfo) {
static const PrinterBasicInfo kPrinterBasicInfo1(
/*printer_name=*/"test printer name 1",
@ -208,13 +216,76 @@ TEST(PrintBackendMojomTraitsTest,
EXPECT_EQ(advanced_capability, output);
}
}
TEST(
PrintBackendMojomTraitsTest,
TestSerializeAndDeserializePrinterSemanticCapsAndDefaultsPrintScalingTypes) {
// Normal scenario: valid types and default value
{
PrinterSemanticCapsAndDefaults input =
GenerateSamplePrinterSemanticCapsAndDefaults(
SampleWithScaleAndPinAndAdvancedCapabilities());
PrinterSemanticCapsAndDefaults output;
EXPECT_TRUE(mojo::test::SerializeAndDeserialize<
mojom::PrinterSemanticCapsAndDefaults>(input, output));
EXPECT_THAT(output.print_scaling_types,
UnorderedElementsAreArray(kPrintScalingTypes));
EXPECT_EQ(kPrintScalingTypeDefault, output.print_scaling_type_default);
}
// Empty print_scaling_types (should be valid)
{
PrinterSemanticCapsAndDefaults input =
GenerateSamplePrinterSemanticCapsAndDefaults(
SampleWithScaleAndPinAndAdvancedCapabilities());
input.print_scaling_types.clear();
PrinterSemanticCapsAndDefaults output;
EXPECT_TRUE(mojo::test::SerializeAndDeserialize<
mojom::PrinterSemanticCapsAndDefaults>(input, output));
EXPECT_TRUE(output.print_scaling_types.empty());
EXPECT_EQ(kPrintScalingTypeDefault, output.print_scaling_type_default);
}
// Unknown default type (should be valid)
{
PrinterSemanticCapsAndDefaults input =
GenerateSamplePrinterSemanticCapsAndDefaults(
SampleWithScaleAndPinAndAdvancedCapabilities());
input.print_scaling_type_default =
mojom::PrintScalingType::kUnknownPrintScalingType;
PrinterSemanticCapsAndDefaults output;
EXPECT_TRUE(mojo::test::SerializeAndDeserialize<
mojom::PrinterSemanticCapsAndDefaults>(input, output));
EXPECT_THAT(output.print_scaling_types,
UnorderedElementsAreArray(kPrintScalingTypes));
EXPECT_EQ(mojom::PrintScalingType::kUnknownPrintScalingType,
output.print_scaling_type_default);
}
}
TEST(
PrintBackendMojomTraitsTest,
TestSerializeAndDeserializePrinterSemanticCapsAndDefaultsPrintScalingTypesDuplicate) {
// Duplicates in print_scaling_types (should be invalid)
PrinterSemanticCapsAndDefaults input =
GenerateSamplePrinterSemanticCapsAndDefaults(
SampleWithScaleAndPinAndAdvancedCapabilities());
input.print_scaling_types = {
mojom::PrintScalingType::kAuto,
mojom::PrintScalingType::kFit,
mojom::PrintScalingType::kAuto, // Duplicate
mojom::PrintScalingType::kNone,
};
PrinterSemanticCapsAndDefaults output;
EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
mojom::PrinterSemanticCapsAndDefaults>(input, output));
}
#endif // BUILDFLAG(IS_CHROMEOS)
TEST(PrintBackendMojomTraitsTest,
TestSerializeAndDeserializePrinterSemanticCapsAndDefaults) {
OptionalSampleCapabilities caps;
#if BUILDFLAG(IS_CHROMEOS)
caps = SampleWithPinAndAdvancedCapabilities();
caps = SampleWithScaleAndPinAndAdvancedCapabilities();
#endif // BUILDFLAG(IS_CHROMEOS)
PrinterSemanticCapsAndDefaults input =
GenerateSamplePrinterSemanticCapsAndDefaults(std::move(caps));
@ -242,6 +313,9 @@ TEST(PrintBackendMojomTraitsTest,
#if BUILDFLAG(IS_CHROMEOS)
EXPECT_EQ(kPinSupported, output.pin_supported);
EXPECT_EQ(kAdvancedCapabilities, output.advanced_capabilities);
EXPECT_THAT(output.print_scaling_types,
UnorderedElementsAreArray(kPrintScalingTypes));
EXPECT_EQ(kPrintScalingTypeDefault, output.print_scaling_type_default);
#endif // BUILDFLAG(IS_CHROMEOS)
}

@ -282,6 +282,11 @@ struct COMPONENT_EXPORT(PRINT_BACKEND) PrinterSemanticCapsAndDefaults {
#if BUILDFLAG(IS_CHROMEOS)
bool pin_supported = false;
AdvancedCapabilities advanced_capabilities;
// Print scaling capability
std::vector<mojom::PrintScalingType> print_scaling_types;
mojom::PrintScalingType print_scaling_type_default =
mojom::PrintScalingType::kUnknownPrintScalingType;
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)

@ -21,10 +21,13 @@ OptionalSampleCapabilities& OptionalSampleCapabilities::operator=(
OptionalSampleCapabilities::~OptionalSampleCapabilities() = default;
#if BUILDFLAG(IS_CHROMEOS)
OptionalSampleCapabilities SampleWithPinAndAdvancedCapabilities() {
OptionalSampleCapabilities SampleWithScaleAndPinAndAdvancedCapabilities() {
OptionalSampleCapabilities caps;
caps.pin_supported = kPinSupported;
caps.advanced_capabilities = kAdvancedCapabilities;
caps.print_scaling_types = {kPrintScalingTypes.begin(),
kPrintScalingTypes.end()};
caps.print_scaling_type_default = kPrintScalingTypeDefault;
return caps;
}
#endif // BUILDFLAG(IS_CHROMEOS)
@ -60,11 +63,13 @@ PrinterSemanticCapsAndDefaults GenerateSamplePrinterSemanticCapsAndDefaults(
#if BUILDFLAG(IS_CHROMEOS)
caps.pin_supported = sample_capabilities.pin_supported;
caps.advanced_capabilities = sample_capabilities.advanced_capabilities;
caps.print_scaling_type_default =
sample_capabilities.print_scaling_type_default;
caps.print_scaling_types = sample_capabilities.print_scaling_types;
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)
caps.page_output_quality = sample_capabilities.page_output_quality;
#endif // BUILDFLAG(IS_WIN)
return caps;
}

@ -32,6 +32,9 @@ struct OptionalSampleCapabilities {
#if BUILDFLAG(IS_CHROMEOS)
bool pin_supported = false;
AdvancedCapabilities advanced_capabilities;
std::vector<mojom::PrintScalingType> print_scaling_types;
mojom::PrintScalingType print_scaling_type_default =
mojom::PrintScalingType::kUnknownPrintScalingType;
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)
std::optional<PageOutputQuality> page_output_quality;
@ -147,10 +150,19 @@ inline const PrinterSemanticCapsAndDefaults::MediaType kDefaultMediaType =
kMediaTypePlain;
#if BUILDFLAG(IS_CHROMEOS)
inline constexpr bool kPinSupported = true;
inline constexpr std::array<mojom::PrintScalingType, 6> kPrintScalingTypes{
mojom::PrintScalingType::kAuto,
mojom::PrintScalingType::kAutoFit,
mojom::PrintScalingType::kFill,
mojom::PrintScalingType::kFit,
mojom::PrintScalingType::kNone,
mojom::PrintScalingType::kUnknownPrintScalingType};
inline constexpr mojom::PrintScalingType kPrintScalingTypeDefault =
mojom::PrintScalingType::kFit;
#endif
#if BUILDFLAG(IS_CHROMEOS)
OptionalSampleCapabilities SampleWithPinAndAdvancedCapabilities();
OptionalSampleCapabilities SampleWithScaleAndPinAndAdvancedCapabilities();
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_WIN)

@ -61,6 +61,17 @@ enum DuplexMode {
kShortEdge,
};
// Print job print scaling values.
[EnableIf=is_chromeos]
enum PrintScalingType {
kUnknownPrintScalingType = -1,
kAuto,
kAutoFit,
kFit,
kFill,
kNone,
};
// Struct that holds margin and content area sizes of a page. Units are
// arbitrary and can be chosen by the programmer.
struct PageSizeMargins {