| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/printing/common/cloud_print_cdd_conversion.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "components/cloud_devices/common/cloud_device_description.h" |
| #include "components/cloud_devices/common/printer_description.h" |
| #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 { |
| |
| namespace { |
| |
| #if BUILDFLAG(IS_WIN) |
| constexpr char kIdPageOutputQuality[] = "page_output_quality"; |
| constexpr char kDisplayNamePageOutputQuality[] = "Page output quality"; |
| #endif // BUILDFLAG(IS_WIN) |
| |
| printer::DuplexType ToCloudDuplexType(printing::mojom::DuplexMode mode) { |
| switch (mode) { |
| case printing::mojom::DuplexMode::kSimplex: |
| return printer::DuplexType::NO_DUPLEX; |
| case printing::mojom::DuplexMode::kLongEdge: |
| return printer::DuplexType::LONG_EDGE; |
| case printing::mojom::DuplexMode::kShortEdge: |
| return printer::DuplexType::SHORT_EDGE; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| printer::TypedValueVendorCapability::ValueType ToCloudValueType( |
| printing::AdvancedCapability::Type type) { |
| switch (type) { |
| case printing::AdvancedCapability::Type::kBoolean: |
| return printer::TypedValueVendorCapability::ValueType::BOOLEAN; |
| case printing::AdvancedCapability::Type::kFloat: |
| return printer::TypedValueVendorCapability::ValueType::FLOAT; |
| case printing::AdvancedCapability::Type::kInteger: |
| return printer::TypedValueVendorCapability::ValueType::INTEGER; |
| case printing::AdvancedCapability::Type::kString: |
| return printer::TypedValueVendorCapability::ValueType::STRING; |
| default: |
| NOTREACHED(); |
| } |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| printer::Media ConvertPaperToMedia( |
| const printing::PrinterSemanticCapsAndDefaults::Paper& paper) { |
| if (paper.SupportsCustomSize()) { |
| return printer::MediaBuilder() |
| .WithCustomName(paper.display_name(), paper.vendor_id()) |
| .WithSizeAndDefaultPrintableArea(paper.size_um()) |
| .WithMaxHeight(paper.max_height_um()) |
| .Build(); |
| } |
| |
| gfx::Size paper_size = paper.size_um(); |
| gfx::Rect paper_printable_area = paper.printable_area_um(); |
| // When converting to Media, the size and printable area should have a larger |
| // height than width. |
| if (paper_size.width() > paper_size.height()) { |
| // When swapping the printable_area, we can't simply transpose the rect |
| // since the margins may not be the same on all sides. A visualization may |
| // help. Suppose we have a page with width of 127000 and height of 76200. |
| // Additionally, the left margin is 1000, right margin is 700, bottom margin |
| // is 500, and top margin is 200. |
| // |
| // +---------- 127000 ---------+ |
| // | 200 | |
| // 76200 +-----------+ | |
| // | | | | |
| // | 1000 | | 700 | |
| // | | | | |
| // | +-----------+ | |
| // | 500 | |
| // +---------------------------+ |
| // |
| // After swapping the page size and printable area (rotating 90 degrees |
| // clockwise), this should be the resulting sizes: |
| // |
| // +---- 76200 ---+ |
| // | | |
| // | 1000 | |
| // | 127000 |
| // | +-----+ | |
| // | | | | |
| // | | | | |
| // |500 | |200| |
| // | | | | |
| // | | | | |
| // | +-----+ | |
| // | | |
| // | 700 | |
| // +--------------+ |
| // |
| // Namely, the new x value for the printable area is: the old printable area |
| // y value. The new y value for the printable area is: (the old width) - |
| // (the old printable area width) - (the old printable areay x value). Note |
| // that if the top/bottom margins are equal and the left/right margins are |
| // equal, then a simple transpose does indeed work. |
| |
| // Rotate clockwise by 90 degrees. |
| int new_x = paper_printable_area.y(); |
| int new_y = paper_size.width() - paper_printable_area.width() - |
| paper_printable_area.x(); |
| paper_printable_area.SetRect(new_x, new_y, paper_printable_area.height(), |
| paper_printable_area.width()); |
| paper_size.SetSize(paper_size.height(), paper_size.width()); |
| } |
| return printer::MediaBuilder() |
| .WithSizeAndPrintableArea(paper_size, paper_printable_area) |
| .WithNameMaybeBasedOnSize(paper.display_name(), paper.vendor_id()) |
| .WithBorderlessVariant(paper.has_borderless_variant()) |
| .Build(); |
| } |
| |
| printer::MediaCapability GetMediaCapabilities( |
| const printing::PrinterSemanticCapsAndDefaults& semantic_info) { |
| printer::MediaCapability media_capabilities; |
| bool is_default_set = false; |
| |
| const printing::PrinterSemanticCapsAndDefaults::Paper& default_paper = |
| semantic_info.default_paper; |
| printer::Media default_media = |
| printer::MediaBuilder() |
| .WithSizeAndPrintableArea(default_paper.size_um(), |
| default_paper.printable_area_um()) |
| .WithNameMaybeBasedOnSize(default_paper.display_name(), |
| default_paper.vendor_id()) |
| .WithBorderlessVariant(default_paper.has_borderless_variant()) |
| .Build(); |
| |
| for (const auto& paper : semantic_info.papers) { |
| printer::Media new_media = ConvertPaperToMedia(paper); |
| if (!new_media.IsValid()) |
| continue; |
| |
| if (media_capabilities.Contains(new_media)) |
| continue; |
| |
| if (!default_media.IsValid()) |
| default_media = new_media; |
| media_capabilities.AddDefaultOption(new_media, new_media == default_media); |
| is_default_set = is_default_set || (new_media == default_media); |
| } |
| if (!is_default_set && default_media.IsValid()) |
| media_capabilities.AddDefaultOption(default_media, true); |
| |
| // Allow user defined paper sizes to be repeats of existing paper sizes. |
| // Do not allow user defined paper sizes to be the default, for now. |
| // TODO(thestig): Figure out the default paper policy here. |
| for (const auto& paper : semantic_info.user_defined_papers) { |
| printer::Media new_media = ConvertPaperToMedia(paper); |
| if (!new_media.IsValid()) |
| continue; |
| |
| media_capabilities.AddOption(new_media); |
| } |
| return media_capabilities; |
| } |
| |
| printer::MediaTypeCapability GetMediaTypeCapabilities( |
| const printing::PrinterSemanticCapsAndDefaults& semantic_info) { |
| printer::MediaTypeCapability media_type_capabilities; |
| |
| for (const auto& media_type : semantic_info.media_types) { |
| printer::MediaType new_media_type(media_type.vendor_id, |
| media_type.display_name); |
| if (!new_media_type.IsValid()) { |
| continue; |
| } |
| |
| if (media_type_capabilities.Contains(new_media_type)) { |
| continue; |
| } |
| |
| media_type_capabilities.AddDefaultOption( |
| new_media_type, |
| media_type.vendor_id == semantic_info.default_media_type.vendor_id); |
| } |
| |
| return media_type_capabilities; |
| } |
| |
| printer::DpiCapability GetDpiCapabilities( |
| const printing::PrinterSemanticCapsAndDefaults& semantic_info) { |
| printer::DpiCapability dpi_capabilities; |
| bool is_default_set = false; |
| |
| printer::Dpi default_dpi(semantic_info.default_dpi.width(), |
| semantic_info.default_dpi.height()); |
| for (const auto& dpi : semantic_info.dpis) { |
| printer::Dpi new_dpi(dpi.width(), dpi.height()); |
| if (!new_dpi.IsValid()) |
| continue; |
| |
| if (dpi_capabilities.Contains(new_dpi)) |
| continue; |
| |
| if (!default_dpi.IsValid()) |
| default_dpi = new_dpi; |
| dpi_capabilities.AddDefaultOption(new_dpi, new_dpi == default_dpi); |
| is_default_set = is_default_set || (new_dpi == default_dpi); |
| } |
| if (!is_default_set && default_dpi.IsValid()) |
| dpi_capabilities.AddDefaultOption(default_dpi, true); |
| |
| return dpi_capabilities; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| printer::VendorCapabilities GetVendorCapabilities( |
| const printing::PrinterSemanticCapsAndDefaults& semantic_info) { |
| printer::VendorCapabilities vendor_capabilities; |
| for (const auto& capability : semantic_info.advanced_capabilities) { |
| std::string capability_name = capability.display_name.empty() |
| ? capability.name |
| : capability.display_name; |
| if (capability.values.empty()) { |
| vendor_capabilities.AddOption( |
| printer::VendorCapability(capability.name, capability_name, |
| printer::TypedValueVendorCapability( |
| ToCloudValueType(capability.type)))); |
| continue; |
| } |
| |
| printer::SelectVendorCapability select_capability; |
| for (const auto& value : capability.values) { |
| std::string localized_value = |
| value.display_name.empty() ? value.name : value.display_name; |
| select_capability.AddDefaultOption( |
| printer::SelectVendorCapabilityOption(value.name, localized_value), |
| value.name == capability.default_value); |
| } |
| vendor_capabilities.AddOption(printer::VendorCapability( |
| capability.name, capability_name, std::move(select_capability))); |
| } |
| |
| 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; |
| } |
| |
| // Helper that verifies if the margins are unique and stores them to the |
| // supplied vector. |
| void StoreMarginsUnique(const printing::PaperMargins& margins_um, |
| std::vector<printer::Margins>& margins_storage) { |
| printer::Margins printer_margins( |
| margins_um.top_margin_um, margins_um.right_margin_um, |
| margins_um.bottom_margin_um, margins_um.left_margin_um); |
| if (!base::Contains(margins_storage, printer_margins)) { |
| margins_storage.emplace_back(std::move(printer_margins)); |
| } |
| } |
| |
| printer::MarginsCapability GetMarginsCapabilities( |
| const printing::PrinterSemanticCapsAndDefaults& semantic_info) { |
| std::optional<printer::Margins> default_margins = std::nullopt; |
| if (semantic_info.default_paper.supported_margins_um().has_value()) { |
| const auto& margins = |
| semantic_info.default_paper.supported_margins_um().value(); |
| default_margins = |
| printer::Margins(margins.top_margin_um, margins.right_margin_um, |
| margins.bottom_margin_um, margins.left_margin_um); |
| } |
| |
| // Populate the `all_papers` with all the available papers. |
| printing::PrinterSemanticCapsAndDefaults::Papers all_papers; |
| all_papers.reserve(semantic_info.papers.size() + |
| semantic_info.user_defined_papers.size()); |
| all_papers.insert(all_papers.end(), semantic_info.papers.begin(), |
| semantic_info.papers.end()); |
| all_papers.insert(all_papers.end(), semantic_info.user_defined_papers.begin(), |
| semantic_info.user_defined_papers.end()); |
| |
| // Stores all the available sets of margins from papers. This temporary |
| // container is used to avoid duplicates, which is then populated to the |
| // `margins_capabilites`. |
| std::vector<printer::Margins> available_margins_um; |
| for (const auto& paper : all_papers) { |
| if (!paper.supported_margins_um().has_value()) { |
| continue; |
| } |
| StoreMarginsUnique(paper.supported_margins_um().value(), |
| available_margins_um); |
| if (paper.has_borderless_variant()) { |
| StoreMarginsUnique(printing::PaperMargins(), available_margins_um); |
| } |
| } |
| |
| // Populate the `margins_capabilities` with the unique margins. |
| printer::MarginsCapability margins_capabilities; |
| bool default_margins_set = false; |
| for (const auto& margins : available_margins_um) { |
| if (!default_margins.has_value()) { |
| default_margins = margins; |
| } |
| margins_capabilities.AddDefaultOption(margins, default_margins == margins); |
| default_margins_set = default_margins_set || (default_margins == margins); |
| } |
| if (!default_margins_set && default_margins.has_value()) { |
| margins_capabilities.AddDefaultOption(default_margins.value(), true); |
| } |
| return margins_capabilities; |
| } |
| |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_WIN) |
| printer::SelectVendorCapability GetPageOutputQualityCapabilities( |
| const printing::PrinterSemanticCapsAndDefaults& semantic_info) { |
| printer::SelectVendorCapability page_output_quality_capabilities; |
| const std::optional<printing::PageOutputQuality>& page_output_quality = |
| semantic_info.page_output_quality; |
| for (const auto& attribute : page_output_quality->qualities) { |
| page_output_quality_capabilities.AddDefaultOption( |
| printer::SelectVendorCapabilityOption(attribute.name, |
| attribute.display_name), |
| attribute.name == page_output_quality->default_quality); |
| } |
| return page_output_quality_capabilities; |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| } // namespace |
| |
| base::Value PrinterSemanticCapsAndDefaultsToCdd( |
| const printing::PrinterSemanticCapsAndDefaults& semantic_info) { |
| cloud_devices::CloudDeviceDescription description; |
| |
| printer::ContentTypesCapability content_types; |
| content_types.AddOption("application/pdf"); |
| content_types.SaveTo(&description); |
| |
| if (semantic_info.collate_capable) { |
| printer::CollateCapability collate; |
| collate.set_default_value(semantic_info.collate_default); |
| collate.SaveTo(&description); |
| } |
| |
| printer::Copies copies_val; |
| copies_val.max_value = semantic_info.copies_max; |
| |
| printer::CopiesCapability copies_cap; |
| copies_cap.set_value(copies_val); |
| copies_cap.SaveTo(&description); |
| |
| if (semantic_info.duplex_modes.size() > 1) { |
| printer::DuplexCapability duplex; |
| for (printing::mojom::DuplexMode mode : semantic_info.duplex_modes) { |
| duplex.AddDefaultOption(ToCloudDuplexType(mode), |
| semantic_info.duplex_default == mode); |
| } |
| duplex.SaveTo(&description); |
| } |
| |
| printer::ColorCapability color; |
| if (semantic_info.color_default || semantic_info.color_changeable) { |
| printer::Color standard_color(printer::ColorType::STANDARD_COLOR); |
| standard_color.vendor_id = |
| base::NumberToString(static_cast<int>(semantic_info.color_model)); |
| color.AddDefaultOption(standard_color, semantic_info.color_default); |
| } |
| if (!semantic_info.color_default || semantic_info.color_changeable) { |
| printer::Color standard_monochrome(printer::ColorType::STANDARD_MONOCHROME); |
| standard_monochrome.vendor_id = |
| base::NumberToString(static_cast<int>(semantic_info.bw_model)); |
| color.AddDefaultOption(standard_monochrome, !semantic_info.color_default); |
| } |
| color.SaveTo(&description); |
| |
| if (!semantic_info.papers.empty()) { |
| printer::MediaCapability media = GetMediaCapabilities(semantic_info); |
| DCHECK(media.IsValid()); |
| media.SaveTo(&description); |
| } |
| |
| // Only create this capability if more than one media type is supported. |
| if (semantic_info.media_types.size() > 1) { |
| printer::MediaTypeCapability media_type = |
| GetMediaTypeCapabilities(semantic_info); |
| DCHECK(media_type.IsValid()); |
| media_type.SaveTo(&description); |
| } |
| |
| if (!semantic_info.dpis.empty()) { |
| printer::DpiCapability dpi = GetDpiCapabilities(semantic_info); |
| DCHECK(dpi.IsValid()); |
| dpi.SaveTo(&description); |
| } |
| |
| printer::OrientationCapability orientation; |
| orientation.AddDefaultOption(printer::OrientationType::PORTRAIT, true); |
| orientation.AddOption(printer::OrientationType::LANDSCAPE); |
| orientation.AddOption(printer::OrientationType::AUTO_ORIENTATION); |
| orientation.SaveTo(&description); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| printer::PinCapability pin; |
| pin.set_value(semantic_info.pin_supported); |
| pin.SaveTo(&description); |
| |
| if (!semantic_info.advanced_capabilities.empty()) { |
| printer::VendorCapabilities vendor_capabilities = |
| 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); |
| } |
| } |
| |
| if (!semantic_info.papers.empty()) { |
| printer::MarginsCapability margins = |
| GetMarginsCapabilities(semantic_info); |
| if (margins.IsValid()) { |
| margins.SaveTo(&description); |
| } |
| } |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_WIN) |
| if (semantic_info.page_output_quality) { |
| printer::VendorCapabilities vendor_capabilities; |
| vendor_capabilities.AddOption(printer::VendorCapability( |
| kIdPageOutputQuality, kDisplayNamePageOutputQuality, |
| GetPageOutputQualityCapabilities(semantic_info))); |
| vendor_capabilities.SaveTo(&description); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| return std::move(description).ToValue(); |
| } |
| |
| } // namespace cloud_print |