910e62b5创建于 1月15日历史提交
// Copyright 2012 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_helper.h"

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/scoped_environment_variable_override.h"
#include "base/strings/stringprintf.h"
#include "printing/backend/print_backend.h"
#include "printing/mojom/print.mojom.h"
#include "printing/print_settings.h"
#include "printing/printing_utils.h"
#include "printing/units.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace printing {

namespace {

// Returns true if the papers have the same name, vendor ID, and size.
bool PapersEqual(const PrinterSemanticCapsAndDefaults::Paper& lhs,
                 const PrinterSemanticCapsAndDefaults::Paper& rhs) {
  return lhs.display_name() == rhs.display_name() &&
         lhs.vendor_id() == rhs.vendor_id() && lhs.size_um() == rhs.size_um();
}

void VerifyCapabilityColorModels(const PrinterSemanticCapsAndDefaults& caps) {
  std::optional<bool> maybe_color = IsColorModelSelected(caps.color_model);
  ASSERT_TRUE(maybe_color.has_value());
  EXPECT_TRUE(maybe_color.value());
  maybe_color = IsColorModelSelected(caps.bw_model);
  ASSERT_TRUE(maybe_color.has_value());
  EXPECT_FALSE(maybe_color.value());
}

std::string GeneratePpdResolutionTestData(const char* res_name) {
  return base::StringPrintf(R"(*PPD-Adobe: 4.3
*OpenUI *%1$s/%1$s: PickOne
*%1$s 600dpi/600 dpi: " "
*Default%1$s: 600dpi
*CloseUI: *%1$s)",
                            res_name);
}

}  // namespace

TEST(PrintBackendCupsHelperTest, PpdParsingNoColorDuplexShortEdge) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*OpenGroup: General/General
*OpenUI *ColorModel/Color Model: PickOne
*DefaultColorModel: Gray
*ColorModel Gray/Grayscale: "
  <</cupsColorSpace 0/cupsColorOrder 0>>setpagedevice"
*ColorModel Black/Inverted Grayscale: "
  <</cupsColorSpace 3/cupsColorOrder 0>>setpagedevice"
*CloseUI: *ColorModel
*OpenUI *Duplex/2-Sided Printing: PickOne
*DefaultDuplex: DuplexTumble
*Duplex None/Off: "
  <</Duplex false>>setpagedevice"
*Duplex DuplexNoTumble/LongEdge: "
  </Duplex true/Tumble false>>setpagedevice"
*Duplex DuplexTumble/ShortEdge: "
  <</Duplex true/Tumble true>>setpagedevice"
*CloseUI: *Duplex
*CloseGroup: General)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.collate_capable);
  EXPECT_TRUE(caps.collate_default);
  EXPECT_EQ(caps.copies_max, 9999);
  EXPECT_THAT(caps.duplex_modes,
              testing::UnorderedElementsAre(mojom::DuplexMode::kSimplex,
                                            mojom::DuplexMode::kLongEdge,
                                            mojom::DuplexMode::kShortEdge));
  EXPECT_EQ(mojom::DuplexMode::kShortEdge, caps.duplex_default);
  EXPECT_FALSE(caps.color_changeable);
  EXPECT_FALSE(caps.color_default);
}

// Test duplex detection code, which regressed in http://crbug.com/103999.
TEST(PrintBackendCupsHelperTest, PpdParsingNoColorDuplexSimples) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*OpenGroup: General/General
*OpenUI *Duplex/Double-Sided Printing: PickOne
*DefaultDuplex: None
*Duplex None/Off: "
  <</Duplex false>>setpagedevice"
*Duplex DuplexNoTumble/Long Edge (Standard): "
  <</Duplex true/Tumble false>>setpagedevice"
*Duplex DuplexTumble/Short Edge (Flip): "
  <</Duplex true/Tumble true>>setpagedevice"
*CloseUI: *Duplex
*CloseGroup: General)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.collate_capable);
  EXPECT_TRUE(caps.collate_default);
  EXPECT_EQ(caps.copies_max, 9999);
  EXPECT_THAT(caps.duplex_modes,
              testing::UnorderedElementsAre(mojom::DuplexMode::kSimplex,
                                            mojom::DuplexMode::kLongEdge,
                                            mojom::DuplexMode::kShortEdge));
  EXPECT_EQ(mojom::DuplexMode::kSimplex, caps.duplex_default);
  EXPECT_FALSE(caps.color_changeable);
  EXPECT_FALSE(caps.color_default);
}

TEST(PrintBackendCupsHelperTest, PpdParsingNoColorNoDuplex) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*OpenGroup: General/General
*OpenUI *ColorModel/Color Model: PickOne
*DefaultColorModel: Gray
*ColorModel Gray/Grayscale: "
  <</cupsColorSpace 0/cupsColorOrder 0>>setpagedevice"
*ColorModel Black/Inverted Grayscale: "
  <</cupsColorSpace 3/cupsColorOrder 0>>setpagedevice"
*CloseUI: *ColorModel
*CloseGroup: General)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.collate_capable);
  EXPECT_TRUE(caps.collate_default);
  EXPECT_EQ(caps.copies_max, 9999);
  EXPECT_THAT(caps.duplex_modes, testing::UnorderedElementsAre());
  EXPECT_EQ(mojom::DuplexMode::kUnknownDuplexMode, caps.duplex_default);
  EXPECT_FALSE(caps.color_changeable);
  EXPECT_FALSE(caps.color_default);
}

TEST(PrintBackendCupsHelperTest, PpdParsingColorTrueDuplexShortEdge) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*DefaultColorSpace: CMYK
*OpenGroup: General/General
*OpenUI *ColorModel/Color Model: PickOne
*DefaultColorModel: CMYK
*ColorModel CMYK/Color: "(cmyk) RCsetdevicecolor"
*ColorModel Gray/Black and White: "(gray) RCsetdevicecolor"
*CloseUI: *ColorModel
*OpenUI *Duplex/2-Sided Printing: PickOne
*DefaultDuplex: DuplexTumble
*Duplex None/Off: "
  <</Duplex false>>setpagedevice"
*Duplex DuplexNoTumble/LongEdge: "
  <</Duplex true/Tumble false>>setpagedevice"
*Duplex DuplexTumble/ShortEdge: "
  <</Duplex true/Tumble true>>setpagedevice"
*CloseUI: *Duplex
*CloseGroup: General)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.collate_capable);
  EXPECT_TRUE(caps.collate_default);
  EXPECT_EQ(caps.copies_max, 9999);
  EXPECT_THAT(caps.duplex_modes,
              testing::UnorderedElementsAre(mojom::DuplexMode::kSimplex,
                                            mojom::DuplexMode::kLongEdge,
                                            mojom::DuplexMode::kShortEdge));
  EXPECT_EQ(mojom::DuplexMode::kShortEdge, caps.duplex_default);
  EXPECT_TRUE(caps.color_changeable);
  EXPECT_TRUE(caps.color_default);
}

TEST(PrintBackendCupsHelperTest, PpdParsingColorFalseDuplexLongEdge) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*DefaultColorSpace: CMYK
*OpenGroup: General/General
*OpenUI *ColorModel/Color Model: PickOne
*DefaultColorModel: Grayscale
*ColorModel Color/Color: "
  %% FoomaticRIPOptionSetting: ColorModel=Color"
*FoomaticRIPOptionSetting ColorModel=Color: "
  JCLDatamode=Color GSCmdLine=Color"
*ColorModel Grayscale/Grayscale: "
  %% FoomaticRIPOptionSetting: ColorModel=Grayscale"
*FoomaticRIPOptionSetting ColorModel=Grayscale: "
  JCLDatamode=Grayscale GSCmdLine=Grayscale"
*CloseUI: *ColorModel
*OpenUI *Duplex/2-Sided Printing: PickOne
*DefaultDuplex: DuplexNoTumble
*Duplex None/Off: "
  <</Duplex false>>setpagedevice"
*Duplex DuplexNoTumble/LongEdge: "
  <</Duplex true/Tumble false>>setpagedevice"
*Duplex DuplexTumble/ShortEdge: "
  <</Duplex true/Tumble true>>setpagedevice"
*CloseUI: *Duplex
*CloseGroup: General)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.collate_capable);
  EXPECT_TRUE(caps.collate_default);
  EXPECT_EQ(caps.copies_max, 9999);
  EXPECT_THAT(caps.duplex_modes,
              testing::UnorderedElementsAre(mojom::DuplexMode::kSimplex,
                                            mojom::DuplexMode::kLongEdge,
                                            mojom::DuplexMode::kShortEdge));
  EXPECT_EQ(mojom::DuplexMode::kLongEdge, caps.duplex_default);
  EXPECT_TRUE(caps.color_changeable);
  EXPECT_FALSE(caps.color_default);
}

TEST(PrintBackendCupsHelperTest, PpdParsingPageSize) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*OpenUI *PageSize: PickOne
*DefaultPageSize: Legal
*PageSize Letter/US Letter: "
  <</DeferredMediaSelection true /PageSize [612 792]
  /ImagingBBox null /MediaClass null >> setpagedevice"
*End
*PageSize Legal/US Legal: "
  <</DeferredMediaSelection true /PageSize [612 1008]
  /ImagingBBox null /MediaClass null >> setpagedevice"
*End
*DefaultPaperDimension: Legal
*PaperDimension Letter/US Letter: "612   792"
*PaperDimension Legal/US Legal: "612  1008"
*CloseUI: *PageSize)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  ASSERT_EQ(2UL, caps.papers.size());
  EXPECT_EQ("Letter", caps.papers[0].vendor_id());
  EXPECT_EQ("US Letter", caps.papers[0].display_name());
  EXPECT_EQ(215900, caps.papers[0].size_um().width());
  EXPECT_EQ(279400, caps.papers[0].size_um().height());
  EXPECT_EQ("Legal", caps.papers[1].vendor_id());
  EXPECT_EQ("US Legal", caps.papers[1].display_name());
  EXPECT_EQ(215900, caps.papers[1].size_um().width());
  EXPECT_EQ(355600, caps.papers[1].size_um().height());
  EXPECT_TRUE(PapersEqual(caps.papers[1], caps.default_paper));
}

TEST(PrintBackendCupsHelperTest, PpdParsingPageSizeNoDefaultSpecified) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*OpenUI *PageSize: PickOne
*PageSize A3/ISO A3: "
  << /DeferredMediaSelection true /PageSize [842 1191]
  /ImagingBBox null >> setpagedevice"
*End
*PageSize A4/ISO A4: "
  << /DeferredMediaSelection true /PageSize [595 842]
  /ImagingBBox null >> setpagedevice"
*End
*PageSize Legal/US Legal: "
  << /DeferredMediaSelection true /PageSize [612 1008]
  /ImagingBBox null >> setpagedevice"
*End
*PageSize Letter/US Letter: "
  << /DeferredMediaSelection true /PageSize [612 792]
  /ImagingBBox null >> setpagedevice"
*End
*PaperDimension A3/ISO A3: "842 1191"
*PaperDimension A4/ISO A4: "595 842"
*PaperDimension Legal/US Legal: "612 1008"
*PaperDimension Letter/US Letter: "612 792"
*CloseUI: *PageSize)";

  {
    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"en-US",
                                     kTestPpdData, &caps));
    ASSERT_EQ(4UL, caps.papers.size());
    EXPECT_EQ("Letter", caps.papers[3].vendor_id());
    EXPECT_EQ("US Letter", caps.papers[3].display_name());
    EXPECT_EQ(215900, caps.papers[3].size_um().width());
    EXPECT_EQ(279400, caps.papers[3].size_um().height());
    EXPECT_TRUE(PapersEqual(caps.papers[3], caps.default_paper));
  }
  {
    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"en-UK",
                                     kTestPpdData, &caps));
    ASSERT_EQ(4UL, caps.papers.size());
    EXPECT_EQ("A4", caps.papers[1].vendor_id());
    EXPECT_EQ("ISO A4", caps.papers[1].display_name());
    EXPECT_EQ(209903, caps.papers[1].size_um().width());
    EXPECT_EQ(297039, caps.papers[1].size_um().height());
    EXPECT_TRUE(PapersEqual(caps.papers[1], caps.default_paper));
  }
}

TEST(PrintBackendCupsHelperTest, PpdParsingPrintableArea) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*OpenUI *PageSize: PickOne
*DefaultPageSize: Legal
*PageSize Letter/US Letter: "
  <</DeferredMediaSelection true /PageSize [612 792]
  /ImagingBBox null /MediaClass null >> setpagedevice"
*End
*PageSize Legal/US Legal: "
  <</DeferredMediaSelection true /PageSize [612 1008]
  /ImagingBBox null /MediaClass null >> setpagedevice"
*End
*DefaultPaperDimension: Legal
*PaperDimension Letter/US Letter: "612   792"
*PaperDimension Legal/US Legal: "612  1008"
*CloseUI: *PageSize
*DefaultImageableArea: Legal
*ImageableArea Letter/US Letter:  "24 30 600 710"
*ImageableArea Legal/US Legal:    "12 12 600 996")";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  ASSERT_EQ(2UL, caps.papers.size());

  {
    EXPECT_EQ("Letter", caps.papers[0].vendor_id());
    EXPECT_EQ("US Letter", caps.papers[0].display_name());
    EXPECT_EQ(215900, caps.papers[0].size_um().width());
    EXPECT_EQ(279400, caps.papers[0].size_um().height());
    const gfx::Rect& printable_area_um = caps.papers[0].printable_area_um();
    EXPECT_EQ(8467, printable_area_um.x());
    EXPECT_EQ(10583, printable_area_um.y());
    EXPECT_EQ(203200, printable_area_um.width());
    EXPECT_EQ(239889, printable_area_um.height());
  }
  {
    EXPECT_EQ("Legal", caps.papers[1].vendor_id());
    EXPECT_EQ("US Legal", caps.papers[1].display_name());
    EXPECT_EQ(215900, caps.papers[1].size_um().width());
    EXPECT_EQ(355600, caps.papers[1].size_um().height());
    const gfx::Rect& printable_area_um = caps.papers[1].printable_area_um();
    EXPECT_EQ(4233, printable_area_um.x());
    EXPECT_EQ(4233, printable_area_um.y());
    EXPECT_EQ(207434, printable_area_um.width());
    EXPECT_EQ(347134, printable_area_um.height());
    EXPECT_TRUE(PapersEqual(caps.papers[1], caps.default_paper));
  }
}

TEST(PrintBackendCupsHelperTest, PpdParsingBrotherPrinters) {
  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *BRPrintQuality/Color/Mono: PickOne
*DefaultBRPrintQuality: Auto
*BRPrintQuality Auto/Auto: ""
*BRPrintQuality Color/Color: ""
*BRPrintQuality Black/Mono: ""
*CloseUI: *BRPrintQuality)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_TRUE(caps.color_changeable);
    EXPECT_TRUE(caps.color_default);
    EXPECT_EQ(mojom::ColorModel::kBrotherBRScript3Color, caps.color_model);
    EXPECT_EQ(mojom::ColorModel::kBrotherBRScript3Black, caps.bw_model);
    VerifyCapabilityColorModels(caps);
  }
  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *BRMonoColor/Color / Mono: PickOne
*DefaultBRMonoColor: Auto
*BRMonoColor Auto/Auto: ""
*BRMonoColor FullColor/Color: ""
*BRMonoColor Mono/Mono: ""
*CloseUI: *BRMonoColor)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_TRUE(caps.color_changeable);
    EXPECT_TRUE(caps.color_default);
    EXPECT_EQ(mojom::ColorModel::kBrotherCUPSColor, caps.color_model);
    EXPECT_EQ(mojom::ColorModel::kBrotherCUPSMono, caps.bw_model);
    VerifyCapabilityColorModels(caps);
  }
  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *BRDuplex/Two-Sided Printing: PickOne
*DefaultBRDuplex: DuplexTumble
*BRDuplex DuplexTumble/Short-Edge Binding: ""
*BRDuplex DuplexNoTumble/Long-Edge Binding: ""
*BRDuplex None/Off: ""
*CloseUI: *BRDuplex)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_THAT(caps.duplex_modes,
                testing::UnorderedElementsAre(mojom::DuplexMode::kSimplex,
                                              mojom::DuplexMode::kLongEdge,
                                              mojom::DuplexMode::kShortEdge));
    EXPECT_EQ(mojom::DuplexMode::kShortEdge, caps.duplex_default);
  }
}

TEST(PrintBackendCupsHelperTest, PpdParsingHpPrinters) {
  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *HPColorMode/Mode: PickOne
*DefaultHPColorMode: ColorPrint
*HPColorMode ColorPrint/Color: "
  << /ProcessColorModel /DeviceCMYK >> setpagedevice"
*HPColorMode GrayscalePrint/Grayscale: "
  << /ProcessColorModel /DeviceGray >> setpagedevice"
*CloseUI: *HPColorMode)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_TRUE(caps.color_changeable);
    EXPECT_TRUE(caps.color_default);
    EXPECT_EQ(mojom::ColorModel::kHPColorColor, caps.color_model);
    EXPECT_EQ(mojom::ColorModel::kHPColorBlack, caps.bw_model);
    VerifyCapabilityColorModels(caps);
  }

  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *HPPJLColorAsGray/Print Color as Gray: PickOne
*DefaultHPPJLColorAsGray: no
*HPPJLColorAsGray yes/On: " "
*HPPJLColorAsGray no/Off: " "
*CloseUI: *HPPJLColorAsGray)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_TRUE(caps.color_changeable);
    EXPECT_TRUE(caps.color_default);
    EXPECT_EQ(mojom::ColorModel::kHpPjlColorAsGrayNo, caps.color_model);
    EXPECT_EQ(mojom::ColorModel::kHpPjlColorAsGrayYes, caps.bw_model);
    VerifyCapabilityColorModels(caps);
  }
}

TEST(PrintBackendCupsHelperTest, PpdParsingCanonPrinters) {
  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *CNColorMode/Color Mode: PickOne
*DefaultCNColorMode: color
*CNColorMode mono/Black and White: "<< >>setpagedevice"
*CNColorMode color/Color: "<< >>setpagedevice"
*CloseUI: *CNColorMode)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_TRUE(caps.color_changeable);
    EXPECT_TRUE(caps.color_default);
    EXPECT_EQ(mojom::ColorModel::kCanonCNColorModeColor, caps.color_model);
    EXPECT_EQ(mojom::ColorModel::kCanonCNColorModeMono, caps.bw_model);
    VerifyCapabilityColorModels(caps);
  }

  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *CNIJGrayScale/Grayscale Printing: PickOne
*OrderDependency: 0 AnySetup *CNIJGrayScale
*DefaultCNIJGrayScale: 0
*CNIJGrayScale 0/OFF: ""
*CNIJGrayScale 1/ON: ""
*CloseUI: *CNIJGrayScale)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_TRUE(caps.color_changeable);
    EXPECT_TRUE(caps.color_default);
    EXPECT_EQ(mojom::ColorModel::kCanonCNIJGrayScaleZero, caps.color_model);
    EXPECT_EQ(mojom::ColorModel::kCanonCNIJGrayScaleOne, caps.bw_model);
    VerifyCapabilityColorModels(caps);
  }
}

TEST(PrintBackendCupsHelperTest, PpdParsingEpsonPrinters) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *Ink/Ink: PickOne
*DefaultInk: COLOR
*Ink COLOR/Color: "
  <</cupsBitsPerColor 8 /cupsColorOrder 0
  /cupsColorSpace 1 /cupsCompression 1>> setpagedevice"
*Ink MONO/Monochrome: "
  <</cupsBitsPerColor 8 /cupsColorOrder 0
  /cupsColorSpace 0 /cupsCompression 1>> setpagedevice"
*CloseUI: *Ink)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.color_changeable);
  EXPECT_TRUE(caps.color_default);
  EXPECT_EQ(mojom::ColorModel::kEpsonInkColor, caps.color_model);
  EXPECT_EQ(mojom::ColorModel::kEpsonInkMono, caps.bw_model);
  VerifyCapabilityColorModels(caps);
}

TEST(PrintBackendCupsHelperTest, PpdParsingKonicaMinoltaPrinters) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI  *SelectColor/Select Color: PickOne
*OrderDependency: 10 AnySetup *SelectColor
*DefaultSelectColor: Color
*SelectColor Color/Color:  "
  <</ProcessColorModel /DeviceCMYK>> setpagedevice"
*SelectColor Grayscale/Grayscale:  "
  <</ProcessColorModel /DeviceGray>> setpagedevice"
*CloseUI: *SelectColor)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.color_changeable);
  EXPECT_TRUE(caps.color_default);
  EXPECT_EQ(mojom::ColorModel::kColor, caps.color_model);
  EXPECT_EQ(mojom::ColorModel::kGrayscale, caps.bw_model);
  VerifyCapabilityColorModels(caps);
}

TEST(PrintBackendCupsHelperTest, PpdParsingLexmarkPrinters) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *BLW/Black & White: PickOne
*OrderDependency: 13.0 AnySetup *BLW
*DefaultBLW: PrinterS
*BLW PrinterS/Printer Setting: ""
*BLW FalseM/Off: "<< /ProcessColorModel /DeviceCMYK >> setpagedevice"
*BLW TrueM/On: "<< /ProcessColorModel /DeviceGray >> setpagedevice"
*?BLW: "
  gsave
  currentpagedevice /ProcessColorModel get /DeviceGray
  (True) eq {(TrueM)}{(FalseM)} ifelse
  = flush
  grestore
*End
*CloseUI: *BLW)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.color_changeable);
  EXPECT_FALSE(caps.color_default);
  EXPECT_EQ(mojom::ColorModel::kColor, caps.color_model);
  EXPECT_EQ(mojom::ColorModel::kGray, caps.bw_model);
  VerifyCapabilityColorModels(caps);
}

TEST(PrintBackendCupsHelperTest, PpdParsingOkiPrinters) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *OKControl/Color Mode: PickOne
*OrderDependency: 105.0 DocumentSetup *OKControl
*DefaultOKControl: Auto
*OKControl Auto/Automatic: "
 globaldict /OK@ColorMono 2 copy known{pop pop}{0 put}ifelse
 /DriverOps /ProcSet 2 copy resourcestatus{
  pop pop findresource
  globaldict /OK@ColorMono get 1 eq{
   5 4 false <</SelectHalftone 1>> true 6 -1 roll }{
   dup dup
   currentpagedevice /UseCIEColor 2 copy known{get not{
   dup dup
   1 exch /setdri_cm get exec 0 exch /setcrd get exec
   }if}{pop pop}ifelse
   <</CMYKTransform 1>> exch /setdrinfo get exec
   0 exch /setdri_bk get exec
   1 0 false <</SelectColorMode 0 /SelectHalftone 1>> false 6 -1 roll
  }ifelse
  /setcolmode 2 copy known{get exec}{7{pop}repeat}ifelse
 }{pop pop}ifelse"
*End
*OKControl Gray/Gray Scale Print: "
 /DriverOps /ProcSet 2 copy resourcestatus{
  pop pop findresource
  5 4 false <</SelectHalftone 1>> true 6 -1 roll
  /setcolmode 2 copy known{get exec}{7{pop}repeat}ifelse
 }{pop pop}ifelse
 /DriverOps /ProcSet 2 copy resourcestatus{
 pop pop findresource dup 1 exch /setprtspeed get exec
 /unloadscreenobo get exec
 userdict /setcolorspace 2 copy known {undef}{pop pop}ifelse
 }{pop pop}ifelse"
*End
*CloseUI: *OKControl)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.color_changeable);
  EXPECT_TRUE(caps.color_default);
  EXPECT_EQ(mojom::ColorModel::kOkiOKControlColor, caps.color_model);
  EXPECT_EQ(mojom::ColorModel::kOkiOKControlGray, caps.bw_model);
  VerifyCapabilityColorModels(caps);
}

TEST(PrintBackendCupsHelperTest, PpdParsingSamsungPrinters) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *ColorMode/Color Mode:  Boolean
*DefaultColorMode: True
*ColorMode False/Grayscale: ""
*ColorMode True/Color: ""
*CloseUI: *ColorMode)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.color_changeable);
  EXPECT_TRUE(caps.color_default);
  EXPECT_EQ(mojom::ColorModel::kColorModeColor, caps.color_model);
  EXPECT_EQ(mojom::ColorModel::kColorModeMonochrome, caps.bw_model);
  VerifyCapabilityColorModels(caps);
}

TEST(PrintBackendCupsHelperTest, PpdParsingSharpPrinters) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *ARCMode/Color Mode: PickOne
*OrderDependency: 180 AnySetup *ARCMode
*DefaultARCMode: CMAuto
*ARCMode CMAuto/Automatic: ""
*End
*ARCMode CMColor/Color: ""
*End
*ARCMode CMBW/Black and White: ""
*End
*CloseUI: *ARCMode)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_TRUE(caps.color_changeable);
  EXPECT_TRUE(caps.color_default);
  EXPECT_EQ(mojom::ColorModel::kSharpARCModeCMColor, caps.color_model);
  EXPECT_EQ(mojom::ColorModel::kSharpARCModeCMBW, caps.bw_model);
  VerifyCapabilityColorModels(caps);
}

TEST(PrintBackendCupsHelperTest, PpdParsingXeroxPrinters) {
  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *XRXColor/Color Correction: PickOne
*OrderDependency: 48.0 AnySetup *XRXColor
*DefaultXRXColor: Automatic
*XRXColor Automatic/Automatic: "
  <</ProcessColorModel /DeviceCMYK>> setpagedevice"
*XRXColor BW/Black and White:  "
  <</ProcessColorModel /DeviceGray>> setpagedevice"
*CloseUI: *XRXColor)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_TRUE(caps.color_changeable);
    EXPECT_TRUE(caps.color_default);
    EXPECT_EQ(mojom::ColorModel::kXeroxXRXColorAutomatic, caps.color_model);
    EXPECT_EQ(mojom::ColorModel::kXeroxXRXColorBW, caps.bw_model);
    VerifyCapabilityColorModels(caps);
  }

  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*ColorDevice: True
*OpenUI *XROutputColor/Xerox Black and White: PickOne
*OrderDependency: 10 AnySetup *XROutputColor
*DefaultXROutputColor: PrintAsColor
*XROutputColor Unspecified/Printer Default: ""
*XROutputColor PrintAsColor/Off (Use Document Color): ""
*XROutputColor PrintAsGrayscale/On: "
  <</ProcessColorModel /DeviceGray >> setpagedevice "
*CloseUI: *XROutputColor)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_TRUE(caps.color_changeable);
    EXPECT_TRUE(caps.color_default);
    EXPECT_EQ(mojom::ColorModel::kXeroxXROutputColorPrintAsColor,
              caps.color_model);
    EXPECT_EQ(mojom::ColorModel::kXeroxXROutputColorPrintAsGrayscale,
              caps.bw_model);
    VerifyCapabilityColorModels(caps);
  }
}

TEST(PrintBackendCupsHelperTest, PpdParsingCupsMaxCopies) {
  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*cupsMaxCopies: 99
*OpenUI *ColorMode/Color Mode:  Boolean
*DefaultColorMode: True
*CloseUI: *ColorMode)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_EQ(99, caps.copies_max);
  }

  {
    constexpr char kTestPpdData[] =
        R"(*PPD-Adobe: "4.3"
*cupsMaxCopies: notavalidnumber
*OpenUI *ColorMode/Color Mode:  Boolean
*DefaultColorMode: True
*CloseUI: *ColorMode)";

    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kTestPpdData, &caps));
    EXPECT_EQ(9999, caps.copies_max);
  }
}

TEST(PrintBackendCupsHelperTest, PpdParsingResolutionTagNames) {
  constexpr const char* kTestResNames[] = {
      "Resolution",     "JCLResolution", "SetResolution", "CNRes_PGP",
      "HPPrintQuality", "LXResolution",  "BRResolution"};
  const std::vector<gfx::Size> kExpectedResolutions = {gfx::Size(600, 600)};
  PrinterSemanticCapsAndDefaults caps;
  for (const char* res_name : kTestResNames) {
    EXPECT_TRUE(ParsePpdCapabilities(
        /*dest=*/nullptr, /*locale=*/"",
        GeneratePpdResolutionTestData(res_name).c_str(), &caps));
    EXPECT_EQ(kExpectedResolutions, caps.dpis);
    EXPECT_EQ(kExpectedResolutions[0], caps.default_dpi);
  }
}

TEST(PrintBackendCupsHelperTest, PpdParsingResolutionInvalidDefaultResolution) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*OpenUI *Resolution/Resolution: PickOne
*DefaultResolution: 500dpi
*Resolution 600dpi/600 dpi: ""
*CloseUI: *Resolution)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_EQ(std::vector<gfx::Size>{gfx::Size(600, 600)}, caps.dpis);
  EXPECT_TRUE(caps.default_dpi.IsEmpty());
}

TEST(PrintBackendCupsHelperTest,
     PpdParsingResolutionStandaloneDefaultResolution) {
  // The PPD spec allows for standalone default keywords, which implies there is
  // only 1 resolution and it is the default.
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*OpenUI *ColorModel/Color Model: PickOne
*DefaultColorModel: CMYK
*ColorModel CMYK/Color: "(cmyk) RCsetdevicecolor"
*ColorModel Gray/Black and White: "(gray) RCsetdevicecolor"
*CloseUI: *ColorModel
*DefaultResolution: 500dpi
*OpenUI *Duplex/2-Sided Printing: PickOne
*DefaultDuplex: DuplexTumble
*Duplex None/Off: <</Duplex false>>setpagedevice"
*Duplex DuplexNoTumble/LongEdge: <</Duplex true/Tumble false>>setpagedevice"
*CloseUI: *Duplex)";
  constexpr gfx::Size kExpectedResolution(500, 500);

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_THAT(caps.dpis, testing::ElementsAre(kExpectedResolution));
  EXPECT_EQ(kExpectedResolution, caps.default_dpi);
}

TEST(PrintBackendCupsHelperTest, PpdParsingResolutionNoResolution) {
  constexpr gfx::Size kExpectedDpi(kDefaultPdfDpi, kDefaultPdfDpi);

  // If the PPD does not have a valid resolution, the DPI should still be set to
  // an OS-dependent default value.
  {
    constexpr char kPpdWithNoResolutionValue[] =
        R"(*PPD-Adobe: "4.3"
*OpenUI *Resolution/Resolution: PickOne
*CloseUI: *Resolution)";
    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kPpdWithNoResolutionValue, &caps));
    EXPECT_THAT(caps.dpis, testing::ElementsAre(kExpectedDpi));
    EXPECT_EQ(kExpectedDpi, caps.default_dpi);
  }

  // Same goes for a PPD that is missing the resolution option entirely.
  {
    constexpr char kPpdWithNoResolutionOption[] = R"(*PPD-Adobe: "4.3")";
    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(
        /*dest=*/nullptr, /*locale=*/"", kPpdWithNoResolutionOption, &caps));
    EXPECT_THAT(caps.dpis, testing::ElementsAre(kExpectedDpi));
    EXPECT_EQ(kExpectedDpi, caps.default_dpi);
  }

  // Same goes for a PPD where the resolution option only contains a
  // DefaultResolution but no actual Resolution values.
  {
    constexpr char kPpdWithOnlyDefaultResolutionValue[] =
        R"(*PPD-Adobe: "4.3"
*OpenUI *Resolution/Resolution: PickOne
*DefaultResolution: 500dpi
*CloseUI: *Resolution)";
    PrinterSemanticCapsAndDefaults caps;
    EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                     kPpdWithOnlyDefaultResolutionValue,
                                     &caps));
    EXPECT_THAT(caps.dpis, testing::ElementsAre(kExpectedDpi));
    EXPECT_EQ(kExpectedDpi, caps.default_dpi);
  }
}

TEST(PrintBackendCupsHelperTest, PpdParsingResolutionNoDefaultResolution) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*OpenUI *Resolution/Resolution: PickOne
*Resolution 600dpi/600 dpi: ""
*CloseUI: *Resolution)";

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_EQ(std::vector<gfx::Size>{gfx::Size(600, 600)}, caps.dpis);
  EXPECT_TRUE(caps.default_dpi.IsEmpty());
}

TEST(PrintBackendCupsHelperTest, PpdParsingResolutionDpiFormat) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*JCLOpenUI *Resolution/Resolution: PickOne
*OrderDependency: 100 JCLSetup *Resolution
*DefaultResolution: 600dpi
*Resolution 500x500dpi/500 dpi: " "
*Resolution 0.5dpi/0.5 dpi: " "
*Resolution 5.0dpi/5 dpi: " "
*Resolution 600dpi/600 dpi: " "
*Resolution 0dpi/0 dpi: " "
*Resolution 1e1dpi/10 dpi: " "
*Resolution -3dpi/-3 dpi: " "
*Resolution -3x300dpi/dpi: " "
*Resolution 300x0dpi/dpi: " "
*Resolution 50/50: " "
*Resolution 50dpis/50 dpis: " "
*Resolution 30x30dpis/30 dpis: " "
*Resolution 2400x600dpi/HQ1200: " "
*JCLCloseUI: *Resolution)";

  const std::vector<gfx::Size> kExpectedResolutions = {
      gfx::Size(500, 500), gfx::Size(600, 600), gfx::Size(2400, 600)};
  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(/*dest=*/nullptr, /*locale=*/"",
                                   kTestPpdData, &caps));
  EXPECT_EQ(kExpectedResolutions, caps.dpis);
  EXPECT_EQ(kExpectedResolutions[1], caps.default_dpi);
}

TEST(PrintBackendCupsHelperTest, PpdSetsDestOptions) {
  constexpr char kTestPpdData[] =
      R"(*PPD-Adobe: "4.3"
*OpenUI *Duplex/2-Sided Printing: PickOne
*DefaultDuplex: DuplexTumble
*Duplex None/Off: "
  <</Duplex false>>setpagedevice"
*Duplex DuplexNoTumble/LongEdge: "
  </Duplex true/Tumble false>>setpagedevice"
*Duplex DuplexTumble/ShortEdge: "
  <</Duplex true/Tumble true>>setpagedevice"
*CloseUI: *Duplex)";

  cups_dest_t* dest;
  int num_dests = 0;
  num_dests =
      cupsAddDest(/*name=*/"test_dest", /*instance=*/nullptr, num_dests, &dest);
  ASSERT_EQ(1, num_dests);

  // Set long edge duplex mode in the destination options even though the PPD
  // sets short edge duplex mode as the default.
  cups_option_t* options = nullptr;
  int num_options = 0;
  num_options =
      cupsAddOption("Duplex", "DuplexNoTumble", num_options, &options);
  dest->num_options = num_options;
  dest->options = options;

  PrinterSemanticCapsAndDefaults caps;
  EXPECT_TRUE(ParsePpdCapabilities(dest, /*locale=*/"", kTestPpdData, &caps));
  EXPECT_THAT(caps.duplex_modes,
              testing::UnorderedElementsAre(mojom::DuplexMode::kSimplex,
                                            mojom::DuplexMode::kLongEdge,
                                            mojom::DuplexMode::kShortEdge));
  EXPECT_EQ(mojom::DuplexMode::kLongEdge, caps.duplex_default);

  cupsFreeDests(num_dests, dest);
}

// For crbug.com/1245412
TEST(PrintBackendCupsHelperTest, NoTempFileLeftBehind) {
  // Create a temp dir and set it as the global temp dir, so
  // ParsePpdCapabilities() will put its temporary files there.
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  EXPECT_TRUE(base::IsDirectoryEmpty(temp_dir.GetPath()));

  {
    static constexpr char kTempDirEnvVar[] = "TMPDIR";
    base::ScopedEnvironmentVariableOverride env_override(
        kTempDirEnvVar, temp_dir.GetPath().value());

    // Make sure ParsePpdCapabilities() does some work and succeeds.
    PrinterSemanticCapsAndDefaults dummy_caps;
    EXPECT_TRUE(ParsePpdCapabilities(
        /*dest=*/nullptr, /*locale=*/"",
        GeneratePpdResolutionTestData("Resolution").c_str(), &dummy_caps));
  }

  EXPECT_TRUE(base::IsDirectoryEmpty(temp_dir.GetPath()));
}

}  // namespace printing