diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/printing | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/printing')
91 files changed, 10792 insertions, 0 deletions
diff --git a/chromium/printing/DEPS b/chromium/printing/DEPS new file mode 100644 index 00000000000..9c706cd6ef0 --- /dev/null +++ b/chromium/printing/DEPS @@ -0,0 +1,10 @@ +include_rules = [ + "+skia/ext", + "+third_party/icu/source/common/unicode", + "+third_party/icu/source/i18n/unicode", + "+third_party/skia", + "+ui/aura", + "+ui/base/text", + "+ui/gfx", + "+win8/util", +] diff --git a/chromium/printing/OWNERS b/chromium/printing/OWNERS new file mode 100644 index 00000000000..f8be0e6feea --- /dev/null +++ b/chromium/printing/OWNERS @@ -0,0 +1,8 @@ +abodenha@chromium.org +dpapad@chromium.org +gene@chromium.org +kmadhusu@chromium.org +scottbyer@chromium.org +thestig@chromium.org +vandebo@chromium.org +vitalybuka@chromium.org diff --git a/chromium/printing/backend/cups_helper.cc b/chromium/printing/backend/cups_helper.cc new file mode 100644 index 00000000000..1c143a5dd51 --- /dev/null +++ b/chromium/printing/backend/cups_helper.cc @@ -0,0 +1,396 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/backend/cups_helper.h" + +#include <cups/ppd.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "printing/backend/print_backend.h" +#include "printing/backend/print_backend_consts.h" +#include "url/gurl.h" + +// This section contains helper code for PPD parsing for semantic capabilities. +namespace { + +const char kColorDevice[] = "ColorDevice"; +const char kColorModel[] = "ColorModel"; +const char kColorMode[] = "ColorMode"; +const char kProcessColorModel[] = "ProcessColorModel"; +const char kPrintoutMode[] = "PrintoutMode"; +const char kDraftGray[] = "Draft.Gray"; +const char kHighGray[] = "High.Gray"; + +const char kDuplex[] = "Duplex"; +const char kDuplexNone[] = "None"; + +#if !defined(OS_MACOSX) +void ParseLpOptions(const base::FilePath& filepath, + const std::string& printer_name, + int* num_options, cups_option_t** options) { + std::string content; + if (!file_util::ReadFileToString(filepath, &content)) + return; + + const char kDest[] = "dest"; + const char kDefault[] = "default"; + const size_t kDestLen = sizeof(kDest) - 1; + const size_t kDefaultLen = sizeof(kDefault) - 1; + std::vector<std::string> lines; + base::SplitString(content, '\n', &lines); + + for (size_t i = 0; i < lines.size(); ++i) { + std::string line = lines[i]; + if (line.empty()) + continue; + + if (base::strncasecmp (line.c_str(), kDefault, kDefaultLen) == 0 && + isspace(line[kDefaultLen])) { + line = line.substr(kDefaultLen); + } else if (base::strncasecmp (line.c_str(), kDest, kDestLen) == 0 && + isspace(line[kDestLen])) { + line = line.substr(kDestLen); + } else { + continue; + } + + TrimWhitespaceASCII(line, TRIM_ALL, &line); + if (line.empty()) + continue; + + size_t space_found = line.find(' '); + if (space_found == std::string::npos) + continue; + + std::string name = line.substr(0, space_found); + if (name.empty()) + continue; + + if (base::strncasecmp(printer_name.c_str(), name.c_str(), + name.length()) != 0) { + continue; // This is not the required printer. + } + + line = line.substr(space_found + 1); + TrimWhitespaceASCII(line, TRIM_ALL, &line); // Remove extra spaces. + if (line.empty()) + continue; + // Parse the selected printer custom options. + *num_options = cupsParseOptions(line.c_str(), 0, options); + } +} + +void MarkLpOptions(const std::string& printer_name, ppd_file_t** ppd) { + cups_option_t* options = NULL; + int num_options = 0; + ppdMarkDefaults(*ppd); + + const char kSystemLpOptionPath[] = "/etc/cups/lpoptions"; + const char kUserLpOptionPath[] = ".cups/lpoptions"; + + std::vector<base::FilePath> file_locations; + file_locations.push_back(base::FilePath(kSystemLpOptionPath)); + file_locations.push_back(base::FilePath( + file_util::GetHomeDir().Append(kUserLpOptionPath))); + + for (std::vector<base::FilePath>::const_iterator it = file_locations.begin(); + it != file_locations.end(); ++it) { + num_options = 0; + options = NULL; + ParseLpOptions(*it, printer_name, &num_options, &options); + if (num_options > 0 && options) { + cupsMarkOptions(*ppd, num_options, options); + cupsFreeOptions(num_options, options); + } + } +} +#endif // !defined(OS_MACOSX) + +bool GetBasicColorModelSettings(ppd_file_t* ppd, + int* color_model_for_black, + int* color_model_for_color, + bool* color_is_default) { + ppd_option_t* color_model = ppdFindOption(ppd, kColorModel); + if (!color_model) + return false; + + if (ppdFindChoice(color_model, printing::kBlack)) + *color_model_for_black = printing::BLACK; + else if (ppdFindChoice(color_model, printing::kGray)) + *color_model_for_black = printing::GRAY; + else if (ppdFindChoice(color_model, printing::kGrayscale)) + *color_model_for_black = printing::GRAYSCALE; + + if (ppdFindChoice(color_model, printing::kColor)) + *color_model_for_color = printing::COLOR; + else if (ppdFindChoice(color_model, printing::kCMYK)) + *color_model_for_color = printing::CMYK; + else if (ppdFindChoice(color_model, printing::kRGB)) + *color_model_for_color = printing::RGB; + else if (ppdFindChoice(color_model, printing::kRGBA)) + *color_model_for_color = printing::RGBA; + else if (ppdFindChoice(color_model, printing::kRGB16)) + *color_model_for_color = printing::RGB16; + else if (ppdFindChoice(color_model, printing::kCMY)) + *color_model_for_color = printing::CMY; + else if (ppdFindChoice(color_model, printing::kKCMY)) + *color_model_for_color = printing::KCMY; + else if (ppdFindChoice(color_model, printing::kCMY_K)) + *color_model_for_color = printing::CMY_K; + + ppd_choice_t* marked_choice = ppdFindMarkedChoice(ppd, kColorModel); + if (!marked_choice) + marked_choice = ppdFindChoice(color_model, color_model->defchoice); + + if (marked_choice) { + *color_is_default = + (base::strcasecmp(marked_choice->choice, printing::kBlack) != 0) && + (base::strcasecmp(marked_choice->choice, printing::kGray) != 0) && + (base::strcasecmp(marked_choice->choice, printing::kGrayscale) != 0); + } + return true; +} + +bool GetPrintOutModeColorSettings(ppd_file_t* ppd, + int* color_model_for_black, + int* color_model_for_color, + bool* color_is_default) { + ppd_option_t* printout_mode = ppdFindOption(ppd, kPrintoutMode); + if (!printout_mode) + return false; + + *color_model_for_color = printing::PRINTOUTMODE_NORMAL; + *color_model_for_black = printing::PRINTOUTMODE_NORMAL; + + // Check to see if NORMAL_GRAY value is supported by PrintoutMode. + // If NORMAL_GRAY is not supported, NORMAL value is used to + // represent grayscale. If NORMAL_GRAY is supported, NORMAL is used to + // represent color. + if (ppdFindChoice(printout_mode, printing::kNormalGray)) + *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY; + + // Get the default marked choice to identify the default color setting + // value. + ppd_choice_t* printout_mode_choice = ppdFindMarkedChoice(ppd, kPrintoutMode); + if (!printout_mode_choice) { + printout_mode_choice = ppdFindChoice(printout_mode, + printout_mode->defchoice); + } + if (printout_mode_choice) { + if ((base::strcasecmp(printout_mode_choice->choice, + printing::kNormalGray) == 0) || + (base::strcasecmp(printout_mode_choice->choice, kHighGray) == 0) || + (base::strcasecmp(printout_mode_choice->choice, kDraftGray) == 0)) { + *color_model_for_black = printing::PRINTOUTMODE_NORMAL_GRAY; + *color_is_default = false; + } + } + return true; +} + +bool GetColorModeSettings(ppd_file_t* ppd, + int* color_model_for_black, + int* color_model_for_color, + bool* color_is_default) { + // Samsung printers use "ColorMode" attribute in their ppds. + ppd_option_t* color_mode_option = ppdFindOption(ppd, kColorMode); + if (!color_mode_option) + return false; + + if (ppdFindChoice(color_mode_option, printing::kColor)) + *color_model_for_color = printing::COLORMODE_COLOR; + + if (ppdFindChoice(color_mode_option, printing::kMonochrome)) + *color_model_for_black = printing::COLORMODE_MONOCHROME; + + ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode); + if (!mode_choice) { + mode_choice = ppdFindChoice(color_mode_option, + color_mode_option->defchoice); + } + + if (mode_choice) { + *color_is_default = + (base::strcasecmp(mode_choice->choice, printing::kColor) == 0); + } + return true; +} + +bool GetHPColorSettings(ppd_file_t* ppd, + int* color_model_for_black, + int* color_model_for_color, + bool* color_is_default) { + // HP printers use "Color/Color Model" attribute in their ppds. + ppd_option_t* color_mode_option = ppdFindOption(ppd, printing::kColor); + if (!color_mode_option) + return false; + + if (ppdFindChoice(color_mode_option, printing::kColor)) + *color_model_for_color = printing::HP_COLOR_COLOR; + if (ppdFindChoice(color_mode_option, printing::kBlack)) + *color_model_for_black = printing::HP_COLOR_BLACK; + + ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kColorMode); + if (!mode_choice) { + mode_choice = ppdFindChoice(color_mode_option, + color_mode_option->defchoice); + } + if (mode_choice) { + *color_is_default = + (base::strcasecmp(mode_choice->choice, printing::kColor) == 0); + } + return true; +} + +bool GetProcessColorModelSettings(ppd_file_t* ppd, + int* color_model_for_black, + int* color_model_for_color, + bool* color_is_default) { + // Canon printers use "ProcessColorModel" attribute in their ppds. + ppd_option_t* color_mode_option = ppdFindOption(ppd, kProcessColorModel); + if (!color_mode_option) + return false; + + if (ppdFindChoice(color_mode_option, printing::kRGB)) + *color_model_for_color = printing::PROCESSCOLORMODEL_RGB; + else if (ppdFindChoice(color_mode_option, printing::kCMYK)) + *color_model_for_color = printing::PROCESSCOLORMODEL_CMYK; + + if (ppdFindChoice(color_mode_option, printing::kGreyscale)) + *color_model_for_black = printing::PROCESSCOLORMODEL_GREYSCALE; + + ppd_choice_t* mode_choice = ppdFindMarkedChoice(ppd, kProcessColorModel); + if (!mode_choice) { + mode_choice = ppdFindChoice(color_mode_option, + color_mode_option->defchoice); + } + + if (mode_choice) { + *color_is_default = + (base::strcasecmp(mode_choice->choice, printing::kGreyscale) != 0); + } + return true; +} + +bool GetColorModelSettings(ppd_file_t* ppd, + int* cm_black, + int* cm_color, + bool* is_color) { + bool is_color_device = false; + ppd_attr_t* attr = ppdFindAttr(ppd, kColorDevice, NULL); + if (attr && attr->value) + is_color_device = ppd->color_device; + + *is_color = is_color_device; + return (is_color_device && + GetBasicColorModelSettings(ppd, cm_black, cm_color, is_color)) || + GetPrintOutModeColorSettings(ppd, cm_black, cm_color, is_color) || + GetColorModeSettings(ppd, cm_black, cm_color, is_color) || + GetHPColorSettings(ppd, cm_black, cm_color, is_color) || + GetProcessColorModelSettings(ppd, cm_black, cm_color, is_color); +} + +} // namespace + +namespace printing { + +// Default port for IPP print servers. +static const int kDefaultIPPServerPort = 631; + +// Helper wrapper around http_t structure, with connection and cleanup +// functionality. +HttpConnectionCUPS::HttpConnectionCUPS(const GURL& print_server_url, + http_encryption_t encryption) + : http_(NULL) { + // If we have an empty url, use default print server. + if (print_server_url.is_empty()) + return; + + int port = print_server_url.IntPort(); + if (port == url_parse::PORT_UNSPECIFIED) + port = kDefaultIPPServerPort; + + http_ = httpConnectEncrypt(print_server_url.host().c_str(), port, + encryption); + if (http_ == NULL) { + LOG(ERROR) << "CP_CUPS: Failed connecting to print server: " << + print_server_url; + } +} + +HttpConnectionCUPS::~HttpConnectionCUPS() { + if (http_ != NULL) + httpClose(http_); +} + +void HttpConnectionCUPS::SetBlocking(bool blocking) { + httpBlocking(http_, blocking ? 1 : 0); +} + +http_t* HttpConnectionCUPS::http() { + return http_; +} + +bool parsePpdCapabilities( + const std::string& printer_name, + const std::string& printer_capabilities, + PrinterSemanticCapsAndDefaults* printer_info) { + base::FilePath ppd_file_path; + if (!file_util::CreateTemporaryFile(&ppd_file_path)) + return false; + + int data_size = printer_capabilities.length(); + if (data_size != file_util::WriteFile( + ppd_file_path, + printer_capabilities.data(), + data_size)) { + base::DeleteFile(ppd_file_path, false); + return false; + } + + ppd_file_t* ppd = ppdOpenFile(ppd_file_path.value().c_str()); + if (!ppd) + return false; + + printing::PrinterSemanticCapsAndDefaults caps; +#if !defined(OS_MACOSX) + MarkLpOptions(printer_name, &ppd); +#endif + ppd_choice_t* duplex_choice = ppdFindMarkedChoice(ppd, kDuplex); + if (!duplex_choice) { + ppd_option_t* option = ppdFindOption(ppd, kDuplex); + if (option) + duplex_choice = ppdFindChoice(option, option->defchoice); + } + + if (duplex_choice) { + caps.duplex_capable = true; + if (base::strcasecmp(duplex_choice->choice, kDuplexNone) != 0) + caps.duplex_default = printing::LONG_EDGE; + else + caps.duplex_default = printing::SIMPLEX; + } + + bool is_color = false; + int cm_color = 0, cm_black = 0; + if (!GetColorModelSettings(ppd, &cm_black, &cm_color, &is_color)) { + VLOG(1) << "Unknown printer color model"; + } + + caps.color_changeable = (cm_color && cm_black && (cm_color != cm_black)); + caps.color_default = is_color; + + ppdClose(ppd); + base::DeleteFile(ppd_file_path, false); + + *printer_info = caps; + return true; +} + +} // namespace printing diff --git a/chromium/printing/backend/cups_helper.h b/chromium/printing/backend/cups_helper.h new file mode 100644 index 00000000000..04ff08ad6cb --- /dev/null +++ b/chromium/printing/backend/cups_helper.h @@ -0,0 +1,46 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_BACKEND_CUPS_HELPER_H_ +#define PRINTING_BACKEND_CUPS_HELPER_H_ + +#include <cups/cups.h> + +#include <string> + +#include "printing/printing_export.h" + +class GURL; + +// These are helper functions for dealing with CUPS. +namespace printing { + +struct PrinterSemanticCapsAndDefaults; + +// Helper wrapper around http_t structure, with connection and cleanup +// functionality. +class PRINTING_EXPORT HttpConnectionCUPS { + public: + HttpConnectionCUPS(const GURL& print_server_url, + http_encryption_t encryption); + ~HttpConnectionCUPS(); + + void SetBlocking(bool blocking); + + http_t* http(); + + private: + http_t* http_; +}; + +// Helper function to parse and convert PPD capabilitites to +// semantic options. +PRINTING_EXPORT bool parsePpdCapabilities( + const std::string& printer_name, + const std::string& printer_capabilities, + PrinterSemanticCapsAndDefaults* printer_info); + +} // namespace printing + +#endif // PRINTING_BACKEND_CUPS_HELPER_H_ diff --git a/chromium/printing/backend/cups_helper_unittest.cc b/chromium/printing/backend/cups_helper_unittest.cc new file mode 100644 index 00000000000..7a111e3616c --- /dev/null +++ b/chromium/printing/backend/cups_helper_unittest.cc @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/backend/cups_helper.h" +#include "printing/backend/print_backend.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PrintBackendCupsHelperTest, TestPpdParsingNoColorDuplexLongEdge) { + std::string test_ppd_data; + test_ppd_data.append( + "*PPD-Adobe: \"4.3\"\n\n" + "*OpenGroup: General/General\n\n" + "*OpenUI *ColorModel/Color Model: PickOne\n" + "*DefaultColorModel: Gray\n" + "*ColorModel Gray/Grayscale: \"" + "<</cupsColorSpace 0/cupsColorOrder 0>>" + "setpagedevice\"\n" + "*ColorModel Black/Inverted Grayscale: \"" + "<</cupsColorSpace 3/cupsColorOrder 0>>" + "setpagedevice\"\n" + "*CloseUI: *ColorModel\n" + "*OpenUI *Duplex/2-Sided Printing: PickOne\n" + "*DefaultDuplex: DuplexTumble\n" + "*Duplex None/Off: \"<</Duplex false>>" + "setpagedevice\"\n" + "*Duplex DuplexNoTumble/LongEdge: \"" + "<</Duplex true/Tumble false>>setpagedevice\"\n" + "*Duplex DuplexTumble/ShortEdge: \"" + "<</Duplex true/Tumble true>>setpagedevice\"\n" + "*CloseUI: *Duplex\n\n" + "*CloseGroup: General\n"); + + printing::PrinterSemanticCapsAndDefaults caps; + EXPECT_TRUE(printing::parsePpdCapabilities("test", test_ppd_data, &caps)); + EXPECT_FALSE(caps.color_changeable); + EXPECT_FALSE(caps.color_default); + EXPECT_TRUE(caps.duplex_capable); + EXPECT_EQ(caps.duplex_default, printing::LONG_EDGE); +} + +// Test duplex detection code, which regressed in http://crbug.com/103999. +TEST(PrintBackendCupsHelperTest, TestPpdParsingNoColorDuplexSimples) { + std::string test_ppd_data; + test_ppd_data.append( + "*PPD-Adobe: \"4.3\"\n\n" + "*OpenGroup: General/General\n\n" + "*OpenUI *Duplex/Double-Sided Printing: PickOne\n" + "*DefaultDuplex: None\n" + "*Duplex None/Off: " + "\"<</Duplex false>>setpagedevice\"\n" + "*Duplex DuplexNoTumble/Long Edge (Standard): " + "\"<</Duplex true/Tumble false>>setpagedevice\"\n" + "*Duplex DuplexTumble/Short Edge (Flip): " + "\"<</Duplex true/Tumble true>>setpagedevice\"\n" + "*CloseUI: *Duplex\n\n" + "*CloseGroup: General\n"); + + printing::PrinterSemanticCapsAndDefaults caps; + EXPECT_TRUE(printing::parsePpdCapabilities("test", test_ppd_data, &caps)); + EXPECT_FALSE(caps.color_changeable); + EXPECT_FALSE(caps.color_default); + EXPECT_TRUE(caps.duplex_capable); + EXPECT_EQ(caps.duplex_default, printing::SIMPLEX); +} + +TEST(PrintBackendCupsHelperTest, TestPpdParsingNoColorNoDuplex) { + std::string test_ppd_data; + test_ppd_data.append( + "*PPD-Adobe: \"4.3\"\n\n" + "*OpenGroup: General/General\n\n" + "*OpenUI *ColorModel/Color Model: PickOne\n" + "*DefaultColorModel: Gray\n" + "*ColorModel Gray/Grayscale: \"" + "<</cupsColorSpace 0/cupsColorOrder 0>>" + "setpagedevice\"\n" + "*ColorModel Black/Inverted Grayscale: \"" + "<</cupsColorSpace 3/cupsColorOrder 0>>" + "setpagedevice\"\n" + "*CloseUI: *ColorModel\n" + "*CloseGroup: General\n"); + + printing::PrinterSemanticCapsAndDefaults caps; + EXPECT_TRUE(printing::parsePpdCapabilities("test", test_ppd_data, &caps)); + EXPECT_FALSE(caps.color_changeable); + EXPECT_FALSE(caps.color_default); + EXPECT_FALSE(caps.duplex_capable); + EXPECT_EQ(caps.duplex_default, printing::UNKNOWN_DUPLEX_MODE); +} + +TEST(PrintBackendCupsHelperTest, TestPpdParsingColorTrueDuplexLongEdge) { + std::string test_ppd_data; + test_ppd_data.append( + "*PPD-Adobe: \"4.3\"\n\n" + "*ColorDevice: True\n" + "*DefaultColorSpace: CMYK\n\n" + "*OpenGroup: General/General\n\n" + "*OpenUI *ColorModel/Color Model: PickOne\n" + "*DefaultColorModel: CMYK\n" + "*ColorModel CMYK/Color: " + "\"(cmyk) RCsetdevicecolor\"\n" + "*ColorModel Gray/Black and White: " + "\"(gray) RCsetdevicecolor\"\n" + "*CloseUI: *ColorModel\n" + "*OpenUI *Duplex/2-Sided Printing: PickOne\n" + "*DefaultDuplex: DuplexTumble\n" + "*Duplex None/Off: \"<</Duplex false>>" + "setpagedevice\"\n" + "*Duplex DuplexNoTumble/LongEdge: \"" + "<</Duplex true/Tumble false>>setpagedevice\"\n" + "*Duplex DuplexTumble/ShortEdge: \"" + "<</Duplex true/Tumble true>>setpagedevice\"\n" + "*CloseUI: *Duplex\n\n" + "*CloseGroup: General\n"); + + printing::PrinterSemanticCapsAndDefaults caps; + EXPECT_TRUE(printing::parsePpdCapabilities("test", test_ppd_data, &caps)); + EXPECT_TRUE(caps.color_changeable); + EXPECT_TRUE(caps.color_default); + EXPECT_TRUE(caps.duplex_capable); + EXPECT_EQ(caps.duplex_default, printing::LONG_EDGE); +} + +TEST(PrintBackendCupsHelperTest, TestPpdParsingColorFalseDuplexLongEdge) { + std::string test_ppd_data; + test_ppd_data.append( + "*PPD-Adobe: \"4.3\"\n\n" + "*ColorDevice: True\n" + "*DefaultColorSpace: CMYK\n\n" + "*OpenGroup: General/General\n\n" + "*OpenUI *ColorModel/Color Model: PickOne\n" + "*DefaultColorModel: Grayscale\n" + "*ColorModel Color/Color: " + "\"%% FoomaticRIPOptionSetting: ColorModel=Color\"\n" + "*FoomaticRIPOptionSetting ColorModel=Color: " + "\"JCLDatamode=Color GSCmdLine=Color\"\n" + "*ColorModel Grayscale/Grayscale: " + "\"%% FoomaticRIPOptionSetting: ColorModel=Grayscale\"\n" + "*FoomaticRIPOptionSetting ColorModel=Grayscale: " + "\"JCLDatamode=Grayscale GSCmdLine=Grayscale\"\n" + "*CloseUI: *ColorModel\n" + "*OpenUI *Duplex/2-Sided Printing: PickOne\n" + "*DefaultDuplex: DuplexTumble\n" + "*Duplex None/Off: \"<</Duplex false>>" + "setpagedevice\"\n" + "*Duplex DuplexNoTumble/LongEdge: \"" + "<</Duplex true/Tumble false>>setpagedevice\"\n" + "*Duplex DuplexTumble/ShortEdge: \"" + "<</Duplex true/Tumble true>>setpagedevice\"\n" + "*CloseUI: *Duplex\n\n" + "*CloseGroup: General\n"); + + printing::PrinterSemanticCapsAndDefaults caps; + EXPECT_TRUE(printing::parsePpdCapabilities("test", test_ppd_data, &caps)); + EXPECT_TRUE(caps.color_changeable); + EXPECT_FALSE(caps.color_default); + EXPECT_TRUE(caps.duplex_capable); + EXPECT_EQ(caps.duplex_default, printing::LONG_EDGE); +} diff --git a/chromium/printing/backend/print_backend.cc b/chromium/printing/backend/print_backend.cc new file mode 100644 index 00000000000..958343f4ac8 --- /dev/null +++ b/chromium/printing/backend/print_backend.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/backend/print_backend.h" + +#include <algorithm> + +#include "third_party/icu/source/common/unicode/uchar.h" +#include "ui/base/text/text_elider.h" + +namespace { + +const wchar_t kDefaultDocumentTitle[] = L"Untitled Document"; +const int kMaxDocumentTitleLength = 50; + +} // namespace + +namespace printing { + +PrinterBasicInfo::PrinterBasicInfo() + : printer_status(0), + is_default(false) {} + +PrinterBasicInfo::~PrinterBasicInfo() {} + +PrinterSemanticCapsAndDefaults::PrinterSemanticCapsAndDefaults() + : color_changeable(false), + duplex_capable(false), + color_default(false), + duplex_default(UNKNOWN_DUPLEX_MODE) {} + +PrinterSemanticCapsAndDefaults::~PrinterSemanticCapsAndDefaults() {} + +PrinterCapsAndDefaults::PrinterCapsAndDefaults() {} + +PrinterCapsAndDefaults::~PrinterCapsAndDefaults() {} + +PrintBackend::~PrintBackend() {} + +string16 PrintBackend::SimplifyDocumentTitle(const string16& title) { + string16 no_controls(title); + no_controls.erase( + std::remove_if(no_controls.begin(), no_controls.end(), &u_iscntrl), + no_controls.end()); + string16 result; + ui::ElideString(no_controls, kMaxDocumentTitleLength, &result); + return result; +} + +} // namespace printing diff --git a/chromium/printing/backend/print_backend.h b/chromium/printing/backend/print_backend.h new file mode 100644 index 00000000000..6a50e74aeb3 --- /dev/null +++ b/chromium/printing/backend/print_backend.h @@ -0,0 +1,112 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_BACKEND_PRINT_BACKEND_H_ +#define PRINTING_BACKEND_PRINT_BACKEND_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "printing/print_job_constants.h" +#include "printing/printing_export.h" + +namespace base { +class DictionaryValue; +} + +// This is the interface for platform-specific code for a print backend +namespace printing { + +struct PRINTING_EXPORT PrinterBasicInfo { + PrinterBasicInfo(); + ~PrinterBasicInfo(); + + std::string printer_name; + std::string printer_description; + int printer_status; + int is_default; + std::map<std::string, std::string> options; +}; + +typedef std::vector<PrinterBasicInfo> PrinterList; + +struct PRINTING_EXPORT PrinterSemanticCapsAndDefaults { + PrinterSemanticCapsAndDefaults(); + ~PrinterSemanticCapsAndDefaults(); + + // Capabilities. + bool color_changeable; + bool duplex_capable; + + // Current defaults. + bool color_default; + DuplexMode duplex_default; +}; + +struct PRINTING_EXPORT PrinterCapsAndDefaults { + PrinterCapsAndDefaults(); + ~PrinterCapsAndDefaults(); + + std::string printer_capabilities; + std::string caps_mime_type; + std::string printer_defaults; + std::string defaults_mime_type; +}; + +// PrintBackend class will provide interface for different print backends +// (Windows, CUPS) to implement. User will call CreateInstance() to +// obtain available print backend. +// Please note, that PrintBackend is not platform specific, but rather +// print system specific. For example, CUPS is available on both Linux and Mac, +// but not available on ChromeOS, etc. This design allows us to add more +// functionality on some platforms, while reusing core (CUPS) functions. +class PRINTING_EXPORT PrintBackend + : public base::RefCountedThreadSafe<PrintBackend> { + public: + // Enumerates the list of installed local and network printers. + virtual bool EnumeratePrinters(PrinterList* printer_list) = 0; + + // Get the default printer name. Empty string if no default printer. + virtual std::string GetDefaultPrinterName() = 0; + + // Gets the semantic capabilities and defaults for a specific printer. + // This is usually a lighter implementation than GetPrinterCapsAndDefaults(). + // NOTE: on some old platforms (WinXP without XPS pack) + // GetPrinterCapsAndDefaults() will fail, while this function will succeed. + virtual bool GetPrinterSemanticCapsAndDefaults( + const std::string& printer_name, + PrinterSemanticCapsAndDefaults* printer_info) = 0; + + // Gets the capabilities and defaults for a specific printer. + virtual bool GetPrinterCapsAndDefaults( + const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) = 0; + + // Gets the information about driver for a specific printer. + virtual std::string GetPrinterDriverInfo( + const std::string& printer_name) = 0; + + // Returns true if printer_name points to a valid printer. + virtual bool IsValidPrinter(const std::string& printer_name) = 0; + + // Simplify title to resolve issue with some drivers. + static string16 SimplifyDocumentTitle(const string16& title); + + // Allocate a print backend. If |print_backend_settings| is NULL, default + // settings will be used. + // Return NULL if no print backend available. + static scoped_refptr<PrintBackend> CreateInstance( + const base::DictionaryValue* print_backend_settings); + + protected: + friend class base::RefCountedThreadSafe<PrintBackend>; + virtual ~PrintBackend(); +}; + +} // namespace printing + +#endif // PRINTING_BACKEND_PRINT_BACKEND_H_ diff --git a/chromium/printing/backend/print_backend_chromeos.cc b/chromium/printing/backend/print_backend_chromeos.cc new file mode 100644 index 00000000000..c206d51b0f1 --- /dev/null +++ b/chromium/printing/backend/print_backend_chromeos.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/backend/print_backend.h" + +#include "base/logging.h" + +namespace printing { + +// Provides a stubbed out PrintBackend implementation for use on ChromeOS. +class PrintBackendChromeOS : public PrintBackend { + public: + PrintBackendChromeOS(); + + // PrintBackend implementation. + virtual bool EnumeratePrinters(PrinterList* printer_list) OVERRIDE; + virtual std::string GetDefaultPrinterName() OVERRIDE; + virtual bool GetPrinterSemanticCapsAndDefaults( + const std::string& printer_name, + PrinterSemanticCapsAndDefaults* printer_info) OVERRIDE; + virtual bool GetPrinterCapsAndDefaults( + const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) OVERRIDE; + virtual std::string GetPrinterDriverInfo( + const std::string& printer_name) OVERRIDE; + virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE; + + protected: + virtual ~PrintBackendChromeOS() {} +}; + +PrintBackendChromeOS::PrintBackendChromeOS() {} + +bool PrintBackendChromeOS::EnumeratePrinters(PrinterList* printer_list) { + return true; +} + +bool PrintBackendChromeOS::GetPrinterSemanticCapsAndDefaults( + const std::string& printer_name, + PrinterSemanticCapsAndDefaults* printer_info) { + NOTREACHED(); + return false; +} + +bool PrintBackendChromeOS::GetPrinterCapsAndDefaults( + const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) { + NOTREACHED(); + return false; +} + +std::string PrintBackendChromeOS::GetPrinterDriverInfo( + const std::string& printer_name) { + NOTREACHED(); + return std::string(); +} + +std::string PrintBackendChromeOS::GetDefaultPrinterName() { + return std::string(); +} + +bool PrintBackendChromeOS::IsValidPrinter(const std::string& printer_name) { + NOTREACHED(); + return true; +} + +scoped_refptr<PrintBackend> PrintBackend::CreateInstance( + const base::DictionaryValue* print_backend_settings) { + return new PrintBackendChromeOS(); +} + +} // namespace printing + diff --git a/chromium/printing/backend/print_backend_consts.cc b/chromium/printing/backend/print_backend_consts.cc new file mode 100644 index 00000000000..8b2f6db07ae --- /dev/null +++ b/chromium/printing/backend/print_backend_consts.cc @@ -0,0 +1,16 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Constant defines used in the print backend code + +#include "printing/backend/print_backend_consts.h" + +const char kLocationTagName[] = "location"; +const char kDriverNameTagName[] = "drivername"; +const char kDriverInfoTagName[] = "system_driverinfo"; +const char kCUPSPrintServerURL[] = "print_server_url"; +const char kCUPSBlocking[] = "cups_blocking"; +const char kCUPSEncryption[] = "cups_encryption"; +const char kValueTrue[] = "true"; +const char kValueFalse[] = "false"; diff --git a/chromium/printing/backend/print_backend_consts.h b/chromium/printing/backend/print_backend_consts.h new file mode 100644 index 00000000000..0cab35e5b20 --- /dev/null +++ b/chromium/printing/backend/print_backend_consts.h @@ -0,0 +1,19 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_BACKEND_PRINT_BACKEND_CONSTS_H_ +#define PRINTING_BACKEND_PRINT_BACKEND_CONSTS_H_ + +#include "printing/printing_export.h" + +PRINTING_EXPORT extern const char kLocationTagName[]; +PRINTING_EXPORT extern const char kDriverNameTagName[]; +PRINTING_EXPORT extern const char kDriverInfoTagName[]; +PRINTING_EXPORT extern const char kCUPSPrintServerURL[]; +PRINTING_EXPORT extern const char kCUPSBlocking[]; +PRINTING_EXPORT extern const char kCUPSEncryption[]; +PRINTING_EXPORT extern const char kValueTrue[]; +PRINTING_EXPORT extern const char kValueFalse[]; + +#endif // PRINTING_BACKEND_PRINT_BACKEND_CONSTS_H_ diff --git a/chromium/printing/backend/print_backend_cups.cc b/chromium/printing/backend/print_backend_cups.cc new file mode 100644 index 00000000000..44c38bb96c3 --- /dev/null +++ b/chromium/printing/backend/print_backend_cups.cc @@ -0,0 +1,386 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/backend/print_backend.h" + +#include "build/build_config.h" + +#include <dlfcn.h> +#include <errno.h> +#include <pthread.h> + +#if defined(OS_MACOSX) +#include <AvailabilityMacros.h> +#else +#include <gcrypt.h> +#endif + +#include "base/debug/leak_annotations.h" +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/synchronization/lock.h" +#include "base/values.h" +#include "printing/backend/cups_helper.h" +#include "printing/backend/print_backend_consts.h" +#include "url/gurl.h" + +#if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 4) +const int CUPS_PRINTER_SCANNER = 0x2000000; // Scanner-only device +#endif + +#if !defined(OS_MACOSX) +GCRY_THREAD_OPTION_PTHREAD_IMPL; + +namespace { + +// Init GCrypt library (needed for CUPS) using pthreads. +// There exists a bug in CUPS library, where it crashed with: "ath.c:184: +// _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed." +// It happened when multiple threads tried printing simultaneously. +// Google search for 'gnutls thread safety' provided a solution that +// initialized gcrypt and gnutls. + +// TODO(phajdan.jr): Remove this after https://bugs.g10code.com/gnupg/issue1197 +// gets fixed on all Linux distros we support (i.e. when they ship libgcrypt +// with the fix). + +// Initially, we linked with -lgnutls and simply called gnutls_global_init(), +// but this did not work well since we build one binary on Ubuntu Hardy and +// expect it to run on many Linux distros. (See http://crbug.com/46954) +// So instead we use dlopen() and dlsym() to dynamically load and call +// gnutls_global_init(). + +class GcryptInitializer { + public: + GcryptInitializer() { + Init(); + } + + private: + void Init() { + const char* kGnuTlsFiles[] = { + "libgnutls.so.28", + "libgnutls.so.26", + "libgnutls.so", + }; + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + for (size_t i = 0; i < arraysize(kGnuTlsFiles); ++i) { + void* gnutls_lib = dlopen(kGnuTlsFiles[i], RTLD_NOW); + if (!gnutls_lib) { + VLOG(1) << "Cannot load " << kGnuTlsFiles[i]; + continue; + } + const char* kGnuTlsInitFuncName = "gnutls_global_init"; + int (*pgnutls_global_init)(void) = reinterpret_cast<int(*)()>( + dlsym(gnutls_lib, kGnuTlsInitFuncName)); + if (!pgnutls_global_init) { + VLOG(1) << "Could not find " << kGnuTlsInitFuncName + << " in " << kGnuTlsFiles[i]; + continue; + } + { + // GnuTLS has a genuine small memory leak that is easier to annotate + // than suppress. See http://crbug.com/176888#c7 + // TODO(earthdok): remove this once the leak is fixed. + ANNOTATE_SCOPED_MEMORY_LEAK; + if ((*pgnutls_global_init)() != 0) + LOG(ERROR) << "gnutls_global_init() failed"; + } + return; + } + LOG(ERROR) << "Cannot find libgnutls"; + } +}; + +base::LazyInstance<GcryptInitializer> g_gcrypt_initializer = + LAZY_INSTANCE_INITIALIZER; + +} // namespace +#endif // !defined(OS_MACOSX) + +namespace printing { + +static const char kCUPSPrinterInfoOpt[] = "printer-info"; +static const char kCUPSPrinterStateOpt[] = "printer-state"; +static const char kCUPSPrinterTypeOpt[] = "printer-type"; +static const char kCUPSPrinterMakeModelOpt[] = "printer-make-and-model"; + +class PrintBackendCUPS : public PrintBackend { + public: + PrintBackendCUPS(const GURL& print_server_url, + http_encryption_t encryption, bool blocking); + + // PrintBackend implementation. + virtual bool EnumeratePrinters(PrinterList* printer_list) OVERRIDE; + virtual std::string GetDefaultPrinterName() OVERRIDE; + virtual bool GetPrinterSemanticCapsAndDefaults( + const std::string& printer_name, + PrinterSemanticCapsAndDefaults* printer_info) OVERRIDE; + virtual bool GetPrinterCapsAndDefaults( + const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) OVERRIDE; + virtual std::string GetPrinterDriverInfo( + const std::string& printer_name) OVERRIDE; + virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE; + + protected: + virtual ~PrintBackendCUPS() {} + + private: + // Following functions are wrappers around corresponding CUPS functions. + // <functions>2() are called when print server is specified, and plain + // version in another case. There is an issue specifing CUPS_HTTP_DEFAULT + // in the <functions>2(), it does not work in CUPS prior to 1.4. + int GetDests(cups_dest_t** dests); + base::FilePath GetPPD(const char* name); + + GURL print_server_url_; + http_encryption_t cups_encryption_; + bool blocking_; +}; + +PrintBackendCUPS::PrintBackendCUPS(const GURL& print_server_url, + http_encryption_t encryption, + bool blocking) + : print_server_url_(print_server_url), + cups_encryption_(encryption), + blocking_(blocking) { +} + +bool PrintBackendCUPS::EnumeratePrinters(PrinterList* printer_list) { + DCHECK(printer_list); + printer_list->clear(); + + cups_dest_t* destinations = NULL; + int num_dests = GetDests(&destinations); + if ((num_dests == 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE)) { + VLOG(1) << "CUPS: Error getting printers from CUPS server" + << ", server: " << print_server_url_ + << ", error: " << static_cast<int>(cupsLastError()); + return false; + } + + for (int printer_index = 0; printer_index < num_dests; printer_index++) { + const cups_dest_t& printer = destinations[printer_index]; + + // CUPS can have 'printers' that are actually scanners. (not MFC) + // At least on Mac. Check for scanners and skip them. + const char* type_str = cupsGetOption(kCUPSPrinterTypeOpt, + printer.num_options, printer.options); + if (type_str != NULL) { + int type; + if (base::StringToInt(type_str, &type) && (type & CUPS_PRINTER_SCANNER)) + continue; + } + + PrinterBasicInfo printer_info; + printer_info.printer_name = printer.name; + printer_info.is_default = printer.is_default; + + const char* info = cupsGetOption(kCUPSPrinterInfoOpt, + printer.num_options, printer.options); + if (info != NULL) + printer_info.printer_description = info; + + const char* state = cupsGetOption(kCUPSPrinterStateOpt, + printer.num_options, printer.options); + if (state != NULL) + base::StringToInt(state, &printer_info.printer_status); + + const char* drv_info = cupsGetOption(kCUPSPrinterMakeModelOpt, + printer.num_options, + printer.options); + if (drv_info) + printer_info.options[kDriverInfoTagName] = *drv_info; + + // Store printer options. + for (int opt_index = 0; opt_index < printer.num_options; opt_index++) { + printer_info.options[printer.options[opt_index].name] = + printer.options[opt_index].value; + } + + printer_list->push_back(printer_info); + } + + cupsFreeDests(num_dests, destinations); + + VLOG(1) << "CUPS: Enumerated printers" + << ", server: " << print_server_url_ + << ", # of printers: " << printer_list->size(); + return true; +} + +std::string PrintBackendCUPS::GetDefaultPrinterName() { + // Not using cupsGetDefault() because it lies about the default printer. + cups_dest_t* dests; + int num_dests = GetDests(&dests); + cups_dest_t* dest = cupsGetDest(NULL, NULL, num_dests, dests); + std::string name = dest ? std::string(dest->name) : std::string(); + cupsFreeDests(num_dests, dests); + return name; +} + +bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults( + const std::string& printer_name, + PrinterSemanticCapsAndDefaults* printer_info) { + PrinterCapsAndDefaults info; + if (!GetPrinterCapsAndDefaults(printer_name, &info) ) + return false; + + return parsePpdCapabilities( + printer_name, info.printer_capabilities, printer_info); +} + +bool PrintBackendCUPS::GetPrinterCapsAndDefaults( + const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) { + DCHECK(printer_info); + + VLOG(1) << "CUPS: Getting caps and defaults" + << ", printer name: " << printer_name; + + base::FilePath ppd_path(GetPPD(printer_name.c_str())); + // In some cases CUPS failed to get ppd file. + if (ppd_path.empty()) { + LOG(ERROR) << "CUPS: Failed to get PPD" + << ", printer name: " << printer_name; + return false; + } + + std::string content; + bool res = file_util::ReadFileToString(ppd_path, &content); + + base::DeleteFile(ppd_path, false); + + if (res) { + printer_info->printer_capabilities.swap(content); + printer_info->caps_mime_type = "application/pagemaker"; + // In CUPS, printer defaults is a part of PPD file. Nothing to upload here. + printer_info->printer_defaults.clear(); + printer_info->defaults_mime_type.clear(); + } + + return res; +} + +std::string PrintBackendCUPS::GetPrinterDriverInfo( + const std::string& printer_name) { + cups_dest_t* destinations = NULL; + int num_dests = GetDests(&destinations); + std::string result; + for (int printer_index = 0; printer_index < num_dests; printer_index++) { + const cups_dest_t& printer = destinations[printer_index]; + if (printer_name == printer.name) { + const char* info = cupsGetOption(kCUPSPrinterMakeModelOpt, + printer.num_options, + printer.options); + if (info) + result = *info; + } + } + + cupsFreeDests(num_dests, destinations); + return result; +} + +bool PrintBackendCUPS::IsValidPrinter(const std::string& printer_name) { + // This is not very efficient way to get specific printer info. CUPS 1.4 + // supports cupsGetNamedDest() function. However, CUPS 1.4 is not available + // everywhere (for example, it supported from Mac OS 10.6 only). + PrinterList printer_list; + EnumeratePrinters(&printer_list); + + PrinterList::iterator it; + for (it = printer_list.begin(); it != printer_list.end(); ++it) + if (it->printer_name == printer_name) + return true; + return false; +} + +scoped_refptr<PrintBackend> PrintBackend::CreateInstance( + const DictionaryValue* print_backend_settings) { +#if !defined(OS_MACOSX) + // Initialize gcrypt library. + g_gcrypt_initializer.Get(); +#endif + + std::string print_server_url_str, cups_blocking; + int encryption = HTTP_ENCRYPT_NEVER; + if (print_backend_settings) { + print_backend_settings->GetString(kCUPSPrintServerURL, + &print_server_url_str); + + print_backend_settings->GetString(kCUPSBlocking, + &cups_blocking); + + print_backend_settings->GetInteger(kCUPSEncryption, &encryption); + } + GURL print_server_url(print_server_url_str.c_str()); + return new PrintBackendCUPS(print_server_url, + static_cast<http_encryption_t>(encryption), + cups_blocking == kValueTrue); +} + +int PrintBackendCUPS::GetDests(cups_dest_t** dests) { + if (print_server_url_.is_empty()) { // Use default (local) print server. + return cupsGetDests(dests); + } else { + HttpConnectionCUPS http(print_server_url_, cups_encryption_); + http.SetBlocking(blocking_); + return cupsGetDests2(http.http(), dests); + } +} + +base::FilePath PrintBackendCUPS::GetPPD(const char* name) { + // cupsGetPPD returns a filename stored in a static buffer in CUPS. + // Protect this code with lock. + CR_DEFINE_STATIC_LOCAL(base::Lock, ppd_lock, ()); + base::AutoLock ppd_autolock(ppd_lock); + base::FilePath ppd_path; + const char* ppd_file_path = NULL; + if (print_server_url_.is_empty()) { // Use default (local) print server. + ppd_file_path = cupsGetPPD(name); + if (ppd_file_path) + ppd_path = base::FilePath(ppd_file_path); + } else { + // cupsGetPPD2 gets stuck sometimes in an infinite time due to network + // configuration/issues. To prevent that, use non-blocking http connection + // here. + // Note: After looking at CUPS sources, it looks like non-blocking + // connection will timeout after 10 seconds of no data period. And it will + // return the same way as if data was completely and sucessfully downloaded. + HttpConnectionCUPS http(print_server_url_, cups_encryption_); + http.SetBlocking(blocking_); + ppd_file_path = cupsGetPPD2(http.http(), name); + // Check if the get full PPD, since non-blocking call may simply return + // normally after timeout expired. + if (ppd_file_path) { + // There is no reliable way right now to detect full and complete PPD + // get downloaded. If we reach http timeout, it may simply return + // downloaded part as a full response. It might be good enough to check + // http->data_remaining or http->_data_remaining, unfortunately http_t + // is an internal structure and fields are not exposed in CUPS headers. + // httpGetLength or httpGetLength2 returning the full content size. + // Comparing file size against that content length might be unreliable + // since some http reponses are encoded and content_length > file size. + // Let's just check for the obvious CUPS and http errors here. + ppd_path = base::FilePath(ppd_file_path); + ipp_status_t error_code = cupsLastError(); + int http_error = httpError(http.http()); + if (error_code > IPP_OK_EVENTS_COMPLETE || http_error != 0) { + LOG(ERROR) << "Error downloading PPD file" + << ", name: " << name + << ", CUPS error: " << static_cast<int>(error_code) + << ", HTTP error: " << http_error; + base::DeleteFile(ppd_path, false); + ppd_path.clear(); + } + } + } + return ppd_path; +} + +} // namespace printing diff --git a/chromium/printing/backend/print_backend_dummy.cc b/chromium/printing/backend/print_backend_dummy.cc new file mode 100644 index 00000000000..fdc3b7ff363 --- /dev/null +++ b/chromium/printing/backend/print_backend_dummy.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is dummy implementation for all configurations where there is no +// print backend. +#if !defined(PRINT_BACKEND_AVAILABLE) + +#include "printing/backend/print_backend.h" + +#include "base/logging.h" +#include "base/values.h" + +namespace printing { + +scoped_refptr<PrintBackend> PrintBackend::CreateInstance( + const DictionaryValue* print_backend_settings) { + NOTREACHED(); + return NULL; +} + +} // namespace printing + +#endif // PRINT_BACKEND_AVAILABLE diff --git a/chromium/printing/backend/print_backend_unittest.cc b/chromium/printing/backend/print_backend_unittest.cc new file mode 100644 index 00000000000..356a34627bf --- /dev/null +++ b/chromium/printing/backend/print_backend_unittest.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/utf_string_conversions.h" +#include "printing/backend/print_backend.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace printing { + +std::string Simplify(const char* title) { + return UTF16ToUTF8(PrintBackend::SimplifyDocumentTitle(ASCIIToUTF16(title))); +} + +TEST(PrintBackendTest, SimplifyDocumentTitle) { + EXPECT_STREQ("", Simplify("").c_str()); + EXPECT_STREQ("Long string. Long string...ng string. Long string.", + Simplify("Long string. Long string. Long string. Long string. " + "Long string. Long string. Long string.").c_str()); + EXPECT_STREQ("Control Characters", + Simplify("C\ron\ntrol Charac\15ters").c_str()); + EXPECT_STREQ("", Simplify("\n\r\n\r\t\r").c_str()); +} + +} // namespace printing + diff --git a/chromium/printing/backend/print_backend_win.cc b/chromium/printing/backend/print_backend_win.cc new file mode 100644 index 00000000000..0d1958fd39c --- /dev/null +++ b/chromium/printing/backend/print_backend_win.cc @@ -0,0 +1,255 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/backend/print_backend.h" + +#include <objidl.h> +#include <winspool.h> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/scoped_bstr.h" +#include "base/win/scoped_comptr.h" +#include "base/win/scoped_hglobal.h" +#include "printing/backend/print_backend_consts.h" +#include "printing/backend/printing_info_win.h" +#include "printing/backend/win_helper.h" + + +namespace { + +HRESULT StreamOnHGlobalToString(IStream* stream, std::string* out) { + DCHECK(stream); + DCHECK(out); + HGLOBAL hdata = NULL; + HRESULT hr = GetHGlobalFromStream(stream, &hdata); + if (SUCCEEDED(hr)) { + DCHECK(hdata); + base::win::ScopedHGlobal<char> locked_data(hdata); + out->assign(locked_data.release(), locked_data.Size()); + } + return hr; +} + +} // namespace + +namespace printing { + +class PrintBackendWin : public PrintBackend { + public: + PrintBackendWin() {} + + // PrintBackend implementation. + virtual bool EnumeratePrinters(PrinterList* printer_list) OVERRIDE; + virtual std::string GetDefaultPrinterName() OVERRIDE; + virtual bool GetPrinterSemanticCapsAndDefaults( + const std::string& printer_name, + PrinterSemanticCapsAndDefaults* printer_info) OVERRIDE; + virtual bool GetPrinterCapsAndDefaults( + const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) OVERRIDE; + virtual std::string GetPrinterDriverInfo( + const std::string& printer_name) OVERRIDE; + virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE; + + protected: + virtual ~PrintBackendWin() {} +}; + +bool PrintBackendWin::EnumeratePrinters(PrinterList* printer_list) { + DCHECK(printer_list); + DWORD bytes_needed = 0; + DWORD count_returned = 0; + const DWORD kLevel = 4; + BOOL ret = EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, NULL, + kLevel, NULL, 0, &bytes_needed, &count_returned); + if (!bytes_needed) + return false; + scoped_ptr<BYTE[]> printer_info_buffer(new BYTE[bytes_needed]); + ret = EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, NULL, kLevel, + printer_info_buffer.get(), bytes_needed, &bytes_needed, + &count_returned); + DCHECK(ret); + if (!ret) + return false; + + std::string default_printer = GetDefaultPrinterName(); + PRINTER_INFO_4* printer_info = + reinterpret_cast<PRINTER_INFO_4*>(printer_info_buffer.get()); + for (DWORD index = 0; index < count_returned; index++) { + ScopedPrinterHandle printer; + PrinterBasicInfo info; + if (printer.OpenPrinter(printer_info[index].pPrinterName) && + InitBasicPrinterInfo(printer, &info)) { + info.is_default = (info.printer_name == default_printer); + printer_list->push_back(info); + } + } + return true; +} + +std::string PrintBackendWin::GetDefaultPrinterName() { + DWORD size = MAX_PATH; + TCHAR default_printer_name[MAX_PATH]; + if (!::GetDefaultPrinter(default_printer_name, &size)) + return std::string(); + return WideToUTF8(default_printer_name); +} + +bool PrintBackendWin::GetPrinterSemanticCapsAndDefaults( + const std::string& printer_name, + PrinterSemanticCapsAndDefaults* printer_info) { + ScopedPrinterHandle printer_handle; + if (!printer_handle.OpenPrinter(UTF8ToWide(printer_name).c_str())) { + LOG(WARNING) << "Failed to open printer, error = " << GetLastError(); + return false; + } + + PrinterInfo5 info_5; + if (!info_5.Init(printer_handle)) { + return false; + } + DCHECK_EQ(info_5.get()->pPrinterName, UTF8ToUTF16(printer_name)); + + PrinterSemanticCapsAndDefaults caps; + + // Get printer capabilities. For more info see here: + // http://msdn.microsoft.com/en-us/library/windows/desktop/dd183552(v=vs.85).aspx + caps.color_changeable = (::DeviceCapabilities(info_5.get()->pPrinterName, + info_5.get()->pPortName, + DC_COLORDEVICE, + NULL, + NULL) == 1); + + caps.duplex_capable = (::DeviceCapabilities(info_5.get()->pPrinterName, + info_5.get()->pPortName, + DC_DUPLEX, + NULL, + NULL) == 1); + + UserDefaultDevMode user_settings; + + if (user_settings.Init(printer_handle)) { + if ((user_settings.get()->dmFields & DM_COLOR) == DM_COLOR) + caps.color_default = (user_settings.get()->dmColor == DMCOLOR_COLOR); + + if ((user_settings.get()->dmFields & DM_DUPLEX) == DM_DUPLEX) { + switch (user_settings.get()->dmDuplex) { + case DMDUP_SIMPLEX: + caps.duplex_default = SIMPLEX; + break; + case DMDUP_VERTICAL: + caps.duplex_default = LONG_EDGE; + break; + case DMDUP_HORIZONTAL: + caps.duplex_default = SHORT_EDGE; + break; + default: + NOTREACHED(); + } + } + } else { + LOG(WARNING) << "Fallback to color/simplex mode."; + caps.color_default = caps.color_changeable; + caps.duplex_default = SIMPLEX; + } + + *printer_info = caps; + return true; +} + +bool PrintBackendWin::GetPrinterCapsAndDefaults( + const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) { + ScopedXPSInitializer xps_initializer; + if (!xps_initializer.initialized()) { + // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll) + return false; + } + if (!IsValidPrinter(printer_name)) { + return false; + } + DCHECK(printer_info); + HPTPROVIDER provider = NULL; + std::wstring printer_name_wide = UTF8ToWide(printer_name); + HRESULT hr = XPSModule::OpenProvider(printer_name_wide, 1, &provider); + if (provider) { + base::win::ScopedComPtr<IStream> print_capabilities_stream; + hr = CreateStreamOnHGlobal(NULL, TRUE, + print_capabilities_stream.Receive()); + DCHECK(SUCCEEDED(hr)); + if (print_capabilities_stream) { + base::win::ScopedBstr error; + hr = XPSModule::GetPrintCapabilities(provider, + NULL, + print_capabilities_stream, + error.Receive()); + DCHECK(SUCCEEDED(hr)); + if (FAILED(hr)) { + return false; + } + hr = StreamOnHGlobalToString(print_capabilities_stream.get(), + &printer_info->printer_capabilities); + DCHECK(SUCCEEDED(hr)); + printer_info->caps_mime_type = "text/xml"; + } + ScopedPrinterHandle printer_handle; + if (printer_handle.OpenPrinter(printer_name_wide.c_str())) { + LONG devmode_size = DocumentProperties( + NULL, printer_handle, const_cast<LPTSTR>(printer_name_wide.c_str()), + NULL, NULL, 0); + if (devmode_size <= 0) + return false; + scoped_ptr<BYTE[]> devmode_out_buffer(new BYTE[devmode_size]); + DEVMODE* devmode_out = + reinterpret_cast<DEVMODE*>(devmode_out_buffer.get()); + DocumentProperties( + NULL, printer_handle, const_cast<LPTSTR>(printer_name_wide.c_str()), + devmode_out, NULL, DM_OUT_BUFFER); + base::win::ScopedComPtr<IStream> printer_defaults_stream; + hr = CreateStreamOnHGlobal(NULL, TRUE, + printer_defaults_stream.Receive()); + DCHECK(SUCCEEDED(hr)); + if (printer_defaults_stream) { + hr = XPSModule::ConvertDevModeToPrintTicket(provider, + devmode_size, + devmode_out, + kPTJobScope, + printer_defaults_stream); + DCHECK(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) { + hr = StreamOnHGlobalToString(printer_defaults_stream.get(), + &printer_info->printer_defaults); + DCHECK(SUCCEEDED(hr)); + printer_info->defaults_mime_type = "text/xml"; + } + } + } + XPSModule::CloseProvider(provider); + } + return true; +} + +// Gets the information about driver for a specific printer. +std::string PrintBackendWin::GetPrinterDriverInfo( + const std::string& printer_name) { + ScopedPrinterHandle printer; + if (!printer.OpenPrinter(UTF8ToWide(printer_name).c_str())) { + return std::string(); + } + return GetDriverInfo(printer); +} + +bool PrintBackendWin::IsValidPrinter(const std::string& printer_name) { + ScopedPrinterHandle printer_handle; + return printer_handle.OpenPrinter(UTF8ToWide(printer_name).c_str()); +} + +scoped_refptr<PrintBackend> PrintBackend::CreateInstance( + const base::DictionaryValue* print_backend_settings) { + return new PrintBackendWin; +} + +} // namespace printing diff --git a/chromium/printing/backend/printing_info_win.cc b/chromium/printing/backend/printing_info_win.cc new file mode 100644 index 00000000000..0e4509430d2 --- /dev/null +++ b/chromium/printing/backend/printing_info_win.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/backend/printing_info_win.h" + +#include "base/logging.h" + +namespace printing { + +namespace internal { + +uint8* GetDriverInfo(HANDLE printer, int level) { + DWORD size = 0; + ::GetPrinterDriver(printer, NULL, level, NULL, 0, &size); + if (size == 0) { + return NULL; + } + scoped_ptr<uint8[]> buffer(new uint8[size]); + memset(buffer.get(), 0, size); + if (!::GetPrinterDriver(printer, NULL, level, buffer.get(), size, &size)) { + return NULL; + } + return buffer.release(); +} + +uint8* GetPrinterInfo(HANDLE printer, int level) { + DWORD size = 0; + ::GetPrinter(printer, level, NULL, 0, &size); + if (size == 0) { + LOG(WARNING) << "Failed to get size of PRINTER_INFO_" << level << + ", error = " << GetLastError(); + return NULL; + } + scoped_ptr<uint8[]> buffer(new uint8[size]); + memset(buffer.get(), 0, size); + if (!::GetPrinter(printer, level, buffer.get(), size, &size)) { + LOG(WARNING) << "Failed to get PRINTER_INFO_" << level << + ", error = " << GetLastError(); + return NULL; + } + return buffer.release(); +} + +} // namespace internal + +UserDefaultDevMode::UserDefaultDevMode() : dev_mode_(NULL) { +} + +bool UserDefaultDevMode::Init(HANDLE printer) { + if (info_9_.Init(printer)) + dev_mode_ = info_9_.get()->pDevMode; + + if (!dev_mode_ && info_8_.Init(printer)) + dev_mode_ = info_8_.get()->pDevMode; + + if (!dev_mode_ && info_2_.Init(printer)) + dev_mode_ = info_2_.get()->pDevMode; + + return dev_mode_ != NULL; +} + +UserDefaultDevMode::~UserDefaultDevMode() { +} + +} // namespace printing diff --git a/chromium/printing/backend/printing_info_win.h b/chromium/printing/backend/printing_info_win.h new file mode 100644 index 00000000000..931ae6df0c4 --- /dev/null +++ b/chromium/printing/backend/printing_info_win.h @@ -0,0 +1,91 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_BACKEND_PRINTING_INFO_WIN_H_ +#define PRINTING_BACKEND_PRINTING_INFO_WIN_H_ + +#include <objidl.h> +#include <winspool.h> + +#include "base/memory/scoped_ptr.h" +#include "printing/printing_export.h" + +namespace printing { + +namespace internal { + +PRINTING_EXPORT uint8* GetDriverInfo(HANDLE printer, int level); +PRINTING_EXPORT uint8* GetPrinterInfo(HANDLE printer, int level); + +// This class is designed to work with PRINTER_INFO_X structures +// and calls GetPrinter internally with correctly allocated buffer. +template <typename PrinterInfoType, int level> +class PrinterInfo { + public: + bool Init(HANDLE printer) { + buffer_.reset(GetPrinterInfo(printer, level)); + return buffer_; + } + + const PrinterInfoType* get() const { + return reinterpret_cast<const PrinterInfoType*>(buffer_.get()); + } + + private: + scoped_ptr<uint8[]> buffer_; +}; + +// This class is designed to work with DRIVER_INFO_X structures +// and calls GetDriverInfo internally with correctly allocated buffer. +template <typename DriverInfoType, int level> +class DriverInfo { + public: + bool Init(HANDLE printer) { + buffer_.reset(GetDriverInfo(printer, level)); + return buffer_; + } + + const DriverInfoType* get() const { + return reinterpret_cast<const DriverInfoType*>(buffer_.get()); + } + + private: + scoped_ptr<uint8[]> buffer_; +}; + +} // namespace internal + +typedef internal::PrinterInfo<PRINTER_INFO_2, 2> PrinterInfo2; +typedef internal::PrinterInfo<PRINTER_INFO_5, 5> PrinterInfo5; +typedef internal::PrinterInfo<PRINTER_INFO_8, 8> PrinterInfo8; +typedef internal::PrinterInfo<PRINTER_INFO_9, 9> PrinterInfo9; + +typedef internal::DriverInfo<DRIVER_INFO_6, 6> DriverInfo6; + +// Retrieves DEVMODE from PRINTER_INFO_* structures. +// Requests in following order: +// 9 (user-default), +// 8 (admin-default), +// 2 (printer-default). +class PRINTING_EXPORT UserDefaultDevMode { + public: + UserDefaultDevMode(); + ~UserDefaultDevMode(); + + bool Init(HANDLE printer); + + const DEVMODE* get() const { + return dev_mode_; + } + + private: + PrinterInfo2 info_2_; + PrinterInfo8 info_8_; + PrinterInfo9 info_9_; + const DEVMODE* dev_mode_; +}; + +} // namespace printing + +#endif // PRINTING_BACKEND_PRINTING_INFO_WIN_H_ diff --git a/chromium/printing/backend/win_helper.cc b/chromium/printing/backend/win_helper.cc new file mode 100644 index 00000000000..3832dff69a0 --- /dev/null +++ b/chromium/printing/backend/win_helper.cc @@ -0,0 +1,344 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/backend/win_helper.h" + +#include <algorithm> + +#include "base/file_version_info.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "printing/backend/print_backend.h" +#include "printing/backend/print_backend_consts.h" +#include "printing/backend/printing_info_win.h" + +namespace { + +typedef HRESULT (WINAPI* PTOpenProviderProc)(PCWSTR printer_name, + DWORD version, + HPTPROVIDER* provider); + +typedef HRESULT (WINAPI* PTGetPrintCapabilitiesProc)(HPTPROVIDER provider, + IStream* print_ticket, + IStream* capabilities, + BSTR* error_message); + +typedef HRESULT (WINAPI* PTConvertDevModeToPrintTicketProc)( + HPTPROVIDER provider, + ULONG devmode_size_in_bytes, + PDEVMODE devmode, + EPrintTicketScope scope, + IStream* print_ticket); + +typedef HRESULT (WINAPI* PTConvertPrintTicketToDevModeProc)( + HPTPROVIDER provider, + IStream* print_ticket, + EDefaultDevmodeType base_devmode_type, + EPrintTicketScope scope, + ULONG* devmode_byte_count, + PDEVMODE* devmode, + BSTR* error_message); + +typedef HRESULT (WINAPI* PTMergeAndValidatePrintTicketProc)( + HPTPROVIDER provider, + IStream* base_ticket, + IStream* delta_ticket, + EPrintTicketScope scope, + IStream* result_ticket, + BSTR* error_message); + +typedef HRESULT (WINAPI* PTReleaseMemoryProc)(PVOID buffer); + +typedef HRESULT (WINAPI* PTCloseProviderProc)(HPTPROVIDER provider); + +typedef HRESULT (WINAPI* StartXpsPrintJobProc)( + const LPCWSTR printer_name, + const LPCWSTR job_name, + const LPCWSTR output_file_name, + HANDLE progress_event, + HANDLE completion_event, + UINT8* printable_pages_on, + UINT32 printable_pages_on_count, + IXpsPrintJob** xps_print_job, + IXpsPrintJobStream** document_stream, + IXpsPrintJobStream** print_ticket_stream); + +PTOpenProviderProc g_open_provider_proc = NULL; +PTGetPrintCapabilitiesProc g_get_print_capabilities_proc = NULL; +PTConvertDevModeToPrintTicketProc g_convert_devmode_to_print_ticket_proc = NULL; +PTConvertPrintTicketToDevModeProc g_convert_print_ticket_to_devmode_proc = NULL; +PTMergeAndValidatePrintTicketProc g_merge_and_validate_print_ticket_proc = NULL; +PTReleaseMemoryProc g_release_memory_proc = NULL; +PTCloseProviderProc g_close_provider_proc = NULL; +StartXpsPrintJobProc g_start_xps_print_job_proc = NULL; + +} // namespace + + +namespace printing { + +bool XPSModule::Init() { + static bool initialized = InitImpl(); + return initialized; +} + +bool XPSModule::InitImpl() { + HMODULE prntvpt_module = LoadLibrary(L"prntvpt.dll"); + if (prntvpt_module == NULL) + return false; + g_open_provider_proc = reinterpret_cast<PTOpenProviderProc>( + GetProcAddress(prntvpt_module, "PTOpenProvider")); + if (!g_open_provider_proc) { + NOTREACHED(); + return false; + } + g_get_print_capabilities_proc = reinterpret_cast<PTGetPrintCapabilitiesProc>( + GetProcAddress(prntvpt_module, "PTGetPrintCapabilities")); + if (!g_get_print_capabilities_proc) { + NOTREACHED(); + return false; + } + g_convert_devmode_to_print_ticket_proc = + reinterpret_cast<PTConvertDevModeToPrintTicketProc>( + GetProcAddress(prntvpt_module, "PTConvertDevModeToPrintTicket")); + if (!g_convert_devmode_to_print_ticket_proc) { + NOTREACHED(); + return false; + } + g_convert_print_ticket_to_devmode_proc = + reinterpret_cast<PTConvertPrintTicketToDevModeProc>( + GetProcAddress(prntvpt_module, "PTConvertPrintTicketToDevMode")); + if (!g_convert_print_ticket_to_devmode_proc) { + NOTREACHED(); + return false; + } + g_merge_and_validate_print_ticket_proc = + reinterpret_cast<PTMergeAndValidatePrintTicketProc>( + GetProcAddress(prntvpt_module, "PTMergeAndValidatePrintTicket")); + if (!g_merge_and_validate_print_ticket_proc) { + NOTREACHED(); + return false; + } + g_release_memory_proc = + reinterpret_cast<PTReleaseMemoryProc>( + GetProcAddress(prntvpt_module, "PTReleaseMemory")); + if (!g_release_memory_proc) { + NOTREACHED(); + return false; + } + g_close_provider_proc = + reinterpret_cast<PTCloseProviderProc>( + GetProcAddress(prntvpt_module, "PTCloseProvider")); + if (!g_close_provider_proc) { + NOTREACHED(); + return false; + } + return true; +} + +HRESULT XPSModule::OpenProvider(const string16& printer_name, + DWORD version, + HPTPROVIDER* provider) { + return g_open_provider_proc(printer_name.c_str(), version, provider); +} + +HRESULT XPSModule::GetPrintCapabilities(HPTPROVIDER provider, + IStream* print_ticket, + IStream* capabilities, + BSTR* error_message) { + return g_get_print_capabilities_proc(provider, + print_ticket, + capabilities, + error_message); +} + +HRESULT XPSModule::ConvertDevModeToPrintTicket(HPTPROVIDER provider, + ULONG devmode_size_in_bytes, + PDEVMODE devmode, + EPrintTicketScope scope, + IStream* print_ticket) { + return g_convert_devmode_to_print_ticket_proc(provider, + devmode_size_in_bytes, + devmode, + scope, + print_ticket); +} + +HRESULT XPSModule::ConvertPrintTicketToDevMode( + HPTPROVIDER provider, + IStream* print_ticket, + EDefaultDevmodeType base_devmode_type, + EPrintTicketScope scope, + ULONG* devmode_byte_count, + PDEVMODE* devmode, + BSTR* error_message) { + return g_convert_print_ticket_to_devmode_proc(provider, + print_ticket, + base_devmode_type, + scope, + devmode_byte_count, + devmode, + error_message); +} + +HRESULT XPSModule::MergeAndValidatePrintTicket(HPTPROVIDER provider, + IStream* base_ticket, + IStream* delta_ticket, + EPrintTicketScope scope, + IStream* result_ticket, + BSTR* error_message) { + return g_merge_and_validate_print_ticket_proc(provider, + base_ticket, + delta_ticket, + scope, + result_ticket, + error_message); +} + +HRESULT XPSModule::ReleaseMemory(PVOID buffer) { + return g_release_memory_proc(buffer); +} + +HRESULT XPSModule::CloseProvider(HPTPROVIDER provider) { + return g_close_provider_proc(provider); +} + +ScopedXPSInitializer::ScopedXPSInitializer() : initialized_(false) { + if (!XPSModule::Init()) + return; + // Calls to XPS APIs typically require the XPS provider to be opened with + // PTOpenProvider. PTOpenProvider calls CoInitializeEx with + // COINIT_MULTITHREADED. We have seen certain buggy HP printer driver DLLs + // that call CoInitializeEx with COINIT_APARTMENTTHREADED in the context of + // PTGetPrintCapabilities. This call fails but the printer driver calls + // CoUninitialize anyway. This results in the apartment being torn down too + // early and the msxml DLL being unloaded which in turn causes code in + // unidrvui.dll to have a dangling pointer to an XML document which causes a + // crash. To protect ourselves from such drivers we make sure we always have + // an extra CoInitialize (calls to CoInitialize/CoUninitialize are + // refcounted). + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + // If this succeeded we are done because the PTOpenProvider call will provide + // the extra refcount on the apartment. If it failed because someone already + // called CoInitializeEx with COINIT_APARTMENTTHREADED, we try the other model + // to provide the additional refcount (since we don't know which model buggy + // printer drivers will use). + if (!SUCCEEDED(hr)) + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + DCHECK(SUCCEEDED(hr)); + initialized_ = true; +} + +ScopedXPSInitializer::~ScopedXPSInitializer() { + if (initialized_) + CoUninitialize(); + initialized_ = false; +} + +bool XPSPrintModule::Init() { + static bool initialized = InitImpl(); + return initialized; +} + +bool XPSPrintModule::InitImpl() { + HMODULE xpsprint_module = LoadLibrary(L"xpsprint.dll"); + if (xpsprint_module == NULL) + return false; + g_start_xps_print_job_proc = reinterpret_cast<StartXpsPrintJobProc>( + GetProcAddress(xpsprint_module, "StartXpsPrintJob")); + if (!g_start_xps_print_job_proc) { + NOTREACHED(); + return false; + } + return true; +} + +HRESULT XPSPrintModule::StartXpsPrintJob( + const LPCWSTR printer_name, + const LPCWSTR job_name, + const LPCWSTR output_file_name, + HANDLE progress_event, + HANDLE completion_event, + UINT8* printable_pages_on, + UINT32 printable_pages_on_count, + IXpsPrintJob** xps_print_job, + IXpsPrintJobStream** document_stream, + IXpsPrintJobStream** print_ticket_stream) { + return g_start_xps_print_job_proc(printer_name, + job_name, + output_file_name, + progress_event, + completion_event, + printable_pages_on, + printable_pages_on_count, + xps_print_job, + document_stream, + print_ticket_stream); +} + +bool InitBasicPrinterInfo(HANDLE printer, PrinterBasicInfo* printer_info) { + DCHECK(printer); + DCHECK(printer_info); + if (!printer) + return false; + + PrinterInfo2 info_2; + if (!info_2.Init(printer)) + return false; + + printer_info->printer_name = WideToUTF8(info_2.get()->pPrinterName); + if (info_2.get()->pComment) + printer_info->printer_description = WideToUTF8(info_2.get()->pComment); + if (info_2.get()->pLocation) + printer_info->options[kLocationTagName] = + WideToUTF8(info_2.get()->pLocation); + if (info_2.get()->pDriverName) + printer_info->options[kDriverNameTagName] = + WideToUTF8(info_2.get()->pDriverName); + printer_info->printer_status = info_2.get()->Status; + + std::string driver_info = GetDriverInfo(printer); + if (!driver_info.empty()) + printer_info->options[kDriverInfoTagName] = driver_info; + return true; +} + +std::string GetDriverInfo(HANDLE printer) { + DCHECK(printer); + std::string driver_info; + + if (!printer) + return driver_info; + + DriverInfo6 info_6; + if (!info_6.Init(printer)) + return driver_info; + + std::string info[4]; + if (info_6.get()->pName) + info[0] = WideToUTF8(info_6.get()->pName); + + if (info_6.get()->pDriverPath) { + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfo( + base::FilePath(info_6.get()->pDriverPath))); + if (version_info.get()) { + info[1] = WideToUTF8(version_info->file_version()); + info[2] = WideToUTF8(version_info->product_name()); + info[3] = WideToUTF8(version_info->product_version()); + } + } + + for (size_t i = 0; i < arraysize(info); ++i) { + std::replace(info[i].begin(), info[i].end(), ';', ','); + driver_info.append(info[i]); + if (i < arraysize(info) - 1) + driver_info.append(";"); + } + return driver_info; +} + +} // namespace printing diff --git a/chromium/printing/backend/win_helper.h b/chromium/printing/backend/win_helper.h new file mode 100644 index 00000000000..d340b67d43c --- /dev/null +++ b/chromium/printing/backend/win_helper.h @@ -0,0 +1,152 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_BACKEND_WIN_HELPER_H_ +#define PRINTING_BACKEND_WIN_HELPER_H_ + +#include <objidl.h> +#include <winspool.h> +#include <prntvpt.h> +#include <xpsprint.h> + +#include <string> + +#include "base/strings/string16.h" +#include "base/win/scoped_handle.h" +#include "printing/printing_export.h" + +// These are helper functions for dealing with Windows Printing. +namespace printing { + +struct PRINTING_EXPORT PrinterBasicInfo; + +class PrinterHandleTraits { + public: + typedef HANDLE Handle; + + static bool CloseHandle(HANDLE handle) { + return ::ClosePrinter(handle) != FALSE; + } + + static bool IsHandleValid(HANDLE handle) { + return handle != NULL; + } + + static HANDLE NullHandle() { + return NULL; + } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(PrinterHandleTraits); +}; + +class ScopedPrinterHandle + : public base::win::GenericScopedHandle<PrinterHandleTraits, + base::win::VerifierTraits> { + public: + bool OpenPrinter(const wchar_t* printer) { + // ::OpenPrinter may return error but assign some value into handle. + if (!::OpenPrinter(const_cast<LPTSTR>(printer), Receive(), NULL)) { + Take(); + } + return IsValid(); + } + + private: + typedef base::win::GenericScopedHandle<PrinterHandleTraits, + base::win::VerifierTraits> Base; + // Hide Receive to avoid assigning handle when ::OpenPrinter returned error. + Base::Receiver Receive() { + return Base::Receive(); + } +}; + +// Wrapper class to wrap the XPS APIs (PTxxx APIs). This is needed because these +// APIs are not available by default on XP. We could delayload prntvpt.dll but +// this would mean having to add that to every binary that links with +// printing.lib (which is a LOT of binaries). So choosing the GetProcAddress +// route instead). +class PRINTING_EXPORT XPSModule { + public: + // All the other methods can ONLY be called after a successful call to Init. + // Init can be called many times and by multiple threads. + static bool Init(); + static HRESULT OpenProvider(const string16& printer_name, + DWORD version, + HPTPROVIDER* provider); + static HRESULT GetPrintCapabilities(HPTPROVIDER provider, + IStream* print_ticket, + IStream* capabilities, + BSTR* error_message); + static HRESULT ConvertDevModeToPrintTicket(HPTPROVIDER provider, + ULONG devmode_size_in_bytes, + PDEVMODE devmode, + EPrintTicketScope scope, + IStream* print_ticket); + static HRESULT ConvertPrintTicketToDevMode( + HPTPROVIDER provider, + IStream* print_ticket, + EDefaultDevmodeType base_devmode_type, + EPrintTicketScope scope, + ULONG* devmode_byte_count, + PDEVMODE* devmode, + BSTR* error_message); + static HRESULT MergeAndValidatePrintTicket(HPTPROVIDER provider, + IStream* base_ticket, + IStream* delta_ticket, + EPrintTicketScope scope, + IStream* result_ticket, + BSTR* error_message); + static HRESULT ReleaseMemory(PVOID buffer); + static HRESULT CloseProvider(HPTPROVIDER provider); + + private: + XPSModule() { } + static bool InitImpl(); +}; + +// See comments in cc file explaining why we need this. +class PRINTING_EXPORT ScopedXPSInitializer { + public: + ScopedXPSInitializer(); + ~ScopedXPSInitializer(); + + bool initialized() const { return initialized_; } + + private: + bool initialized_; +}; + +// Wrapper class to wrap the XPS Print APIs (these are different from the PTxxx +// which deal with the XML Print Schema). This is needed because these +// APIs are only available on Windows 7 and higher. +class PRINTING_EXPORT XPSPrintModule { + public: + // All the other methods can ONLY be called after a successful call to Init. + // Init can be called many times and by multiple threads. + static bool Init(); + static HRESULT StartXpsPrintJob( + const LPCWSTR printer_name, + const LPCWSTR job_name, + const LPCWSTR output_file_name, + HANDLE progress_event, + HANDLE completion_event, + UINT8* printable_pages_on, + UINT32 printable_pages_on_count, + IXpsPrintJob **xps_print_job, + IXpsPrintJobStream **document_stream, + IXpsPrintJobStream **print_ticket_stream); + private: + XPSPrintModule() { } + static bool InitImpl(); +}; + +PRINTING_EXPORT bool InitBasicPrinterInfo(HANDLE printer, + PrinterBasicInfo* printer_info); + +PRINTING_EXPORT std::string GetDriverInfo(HANDLE printer); + +} // namespace printing + +#endif // PRINTING_BACKEND_WIN_HELPER_H_ diff --git a/chromium/printing/cups_config_helper.py b/chromium/printing/cups_config_helper.py new file mode 100755 index 00000000000..3fe5fe04e1a --- /dev/null +++ b/chromium/printing/cups_config_helper.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""cups-config wrapper. + +cups-config, at least on Ubuntu Lucid and Natty, dumps all +cflags/ldflags/libs when passed the --libs argument. gyp would like +to keep these separate: cflags are only needed when compiling files +that use cups directly, while libs are only needed on the final link +line. + +TODO(evan): remove me once + https://bugs.launchpad.net/ubuntu/+source/cupsys/+bug/163704 +is fixed. +""" + +import subprocess +import sys + +def usage(): + print 'usage: %s {--cflags|--ldflags|--libs}' % sys.argv[0] + + +def run_cups_config(mode): + """Run cups-config with all --cflags etc modes, parse out the mode we want, + and return those flags as a list.""" + + cups = subprocess.Popen(['cups-config', '--cflags', '--ldflags', '--libs'], + stdout=subprocess.PIPE) + flags = cups.communicate()[0].strip() + + flags_subset = [] + for flag in flags.split(): + flag_mode = None + if flag.startswith('-l'): + flag_mode = '--libs' + elif (flag.startswith('-L') or flag.startswith('-Wl,')): + flag_mode = '--ldflags' + elif (flag.startswith('-I') or flag.startswith('-D')): + flag_mode = '--cflags' + + # Be conservative: for flags where we don't know which mode they + # belong in, always include them. + if flag_mode is None or flag_mode == mode: + flags_subset.append(flag) + + return flags_subset + + +def main(): + if len(sys.argv) != 2: + usage() + return 1 + + mode = sys.argv[1] + if mode not in ('--cflags', '--libs', '--ldflags'): + usage() + return 1 + flags = run_cups_config(mode) + print ' '.join(flags) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/chromium/printing/emf_win.cc b/chromium/printing/emf_win.cc new file mode 100644 index 00000000000..98c8f8ffa6f --- /dev/null +++ b/chromium/printing/emf_win.cc @@ -0,0 +1,678 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/emf_win.h" + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/scoped_gdi_object.h" +#include "base/win/scoped_hdc.h" +#include "base/win/scoped_select_object.h" +#include "skia/ext/vector_platform_device_emf_win.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/codec/jpeg_codec.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/gdi_util.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace { + +const int kCustomGdiCommentSignature = 0xdeadbabe; +struct PageBreakRecord { + int signature; + enum PageBreakType { + START_PAGE, + END_PAGE, + } type; + explicit PageBreakRecord(PageBreakType type_in) + : signature(kCustomGdiCommentSignature), type(type_in) { + } + bool IsValid() const { + return (signature == kCustomGdiCommentSignature) && + (type >= START_PAGE) && (type <= END_PAGE); + } +}; + +int CALLBACK IsAlphaBlendUsedEnumProc(HDC, + HANDLETABLE*, + const ENHMETARECORD *record, + int, + LPARAM data) { + bool* result = reinterpret_cast<bool*>(data); + if (!result) + return 0; + switch (record->iType) { + case EMR_ALPHABLEND: { + *result = true; + return 0; + break; + } + } + return 1; +} + +int CALLBACK RasterizeAlphaBlendProc(HDC metafile_dc, + HANDLETABLE* handle_table, + const ENHMETARECORD *record, + int num_objects, + LPARAM data) { + HDC bitmap_dc = *reinterpret_cast<HDC*>(data); + // Play this command to the bitmap DC. + ::PlayEnhMetaFileRecord(bitmap_dc, handle_table, record, num_objects); + switch (record->iType) { + case EMR_ALPHABLEND: { + const EMRALPHABLEND* alpha_blend = + reinterpret_cast<const EMRALPHABLEND*>(record); + // Don't modify transformation here. + // Old implementation did reset transformations for DC to identity matrix. + // That was not correct and cause some bugs, like unexpected cropping. + // EMRALPHABLEND is rendered into bitmap and metafile contexts with + // current transformation. If we don't touch them here BitBlt will copy + // same areas. + ::BitBlt(metafile_dc, + alpha_blend->xDest, + alpha_blend->yDest, + alpha_blend->cxDest, + alpha_blend->cyDest, + bitmap_dc, + alpha_blend->xDest, + alpha_blend->yDest, + SRCCOPY); + break; + } + case EMR_CREATEBRUSHINDIRECT: + case EMR_CREATECOLORSPACE: + case EMR_CREATECOLORSPACEW: + case EMR_CREATEDIBPATTERNBRUSHPT: + case EMR_CREATEMONOBRUSH: + case EMR_CREATEPALETTE: + case EMR_CREATEPEN: + case EMR_DELETECOLORSPACE: + case EMR_DELETEOBJECT: + case EMR_EXTCREATEFONTINDIRECTW: + // Play object creation command only once. + break; + + default: + // Play this command to the metafile DC. + ::PlayEnhMetaFileRecord(metafile_dc, handle_table, record, num_objects); + break; + } + return 1; // Continue enumeration +} + +// Bitmapt for rasterization. +class RasterBitmap { + public: + explicit RasterBitmap(const gfx::Size& raster_size) + : saved_object_(NULL) { + context_.Set(::CreateCompatibleDC(NULL)); + if (!context_) { + NOTREACHED() << "Bitmap DC creation failed"; + return; + } + ::SetGraphicsMode(context_, GM_ADVANCED); + void* bits = NULL; + gfx::Rect bitmap_rect(raster_size); + gfx::CreateBitmapHeader(raster_size.width(), raster_size.height(), + &header_.bmiHeader); + bitmap_.Set(::CreateDIBSection(context_, &header_, DIB_RGB_COLORS, &bits, + NULL, 0)); + if (!bitmap_) + NOTREACHED() << "Raster bitmap creation for printing failed"; + + saved_object_ = ::SelectObject(context_, bitmap_); + RECT rect = bitmap_rect.ToRECT(); + ::FillRect(context_, &rect, + static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH))); + + } + + ~RasterBitmap() { + ::SelectObject(context_, saved_object_); + } + + HDC context() const { + return context_; + } + + base::win::ScopedCreateDC context_; + BITMAPINFO header_; + base::win::ScopedBitmap bitmap_; + HGDIOBJ saved_object_; + + private: + DISALLOW_COPY_AND_ASSIGN(RasterBitmap); +}; + + + +} // namespace + +namespace printing { + +bool DIBFormatNativelySupported(HDC dc, uint32 escape, const BYTE* bits, + int size) { + BOOL supported = FALSE; + if (ExtEscape(dc, QUERYESCSUPPORT, sizeof(escape), + reinterpret_cast<LPCSTR>(&escape), 0, 0) > 0) { + ExtEscape(dc, escape, size, reinterpret_cast<LPCSTR>(bits), + sizeof(supported), reinterpret_cast<LPSTR>(&supported)); + } + return !!supported; +} + +Emf::Emf() : emf_(NULL), hdc_(NULL), page_count_(0) { +} + +Emf::~Emf() { + DCHECK(!hdc_); + if (emf_) + DeleteEnhMetaFile(emf_); +} + +bool Emf::InitToFile(const base::FilePath& metafile_path) { + DCHECK(!emf_ && !hdc_); + hdc_ = CreateEnhMetaFile(NULL, metafile_path.value().c_str(), NULL, NULL); + DCHECK(hdc_); + return hdc_ != NULL; +} + +bool Emf::InitFromFile(const base::FilePath& metafile_path) { + DCHECK(!emf_ && !hdc_); + emf_ = GetEnhMetaFile(metafile_path.value().c_str()); + DCHECK(emf_); + return emf_ != NULL; +} + +bool Emf::Init() { + DCHECK(!emf_ && !hdc_); + hdc_ = CreateEnhMetaFile(NULL, NULL, NULL, NULL); + DCHECK(hdc_); + return hdc_ != NULL; +} + +bool Emf::InitFromData(const void* src_buffer, uint32 src_buffer_size) { + DCHECK(!emf_ && !hdc_); + emf_ = SetEnhMetaFileBits(src_buffer_size, + reinterpret_cast<const BYTE*>(src_buffer)); + return emf_ != NULL; +} + +bool Emf::FinishDocument() { + DCHECK(!emf_ && hdc_); + emf_ = CloseEnhMetaFile(hdc_); + DCHECK(emf_); + hdc_ = NULL; + return emf_ != NULL; +} + +bool Emf::Playback(HDC hdc, const RECT* rect) const { + DCHECK(emf_ && !hdc_); + RECT bounds; + if (!rect) { + // Get the natural bounds of the EMF buffer. + bounds = GetPageBounds(1).ToRECT(); + rect = &bounds; + } + return PlayEnhMetaFile(hdc, emf_, rect) != 0; +} + +bool Emf::SafePlayback(HDC context) const { + DCHECK(emf_ && !hdc_); + XFORM base_matrix; + if (!GetWorldTransform(context, &base_matrix)) { + NOTREACHED(); + return false; + } + Emf::EnumerationContext playback_context; + playback_context.base_matrix = &base_matrix; + RECT rect = GetPageBounds(1).ToRECT(); + return EnumEnhMetaFile(context, + emf_, + &Emf::SafePlaybackProc, + reinterpret_cast<void*>(&playback_context), + &rect) != 0; +} + +gfx::Rect Emf::GetPageBounds(unsigned int page_number) const { + DCHECK(emf_ && !hdc_); + DCHECK_EQ(1U, page_number); + ENHMETAHEADER header; + if (GetEnhMetaFileHeader(emf_, sizeof(header), &header) != sizeof(header)) { + NOTREACHED(); + return gfx::Rect(); + } + // Add 1 to right and bottom because it's inclusive rectangle. + // See ENHMETAHEADER. + return gfx::Rect(header.rclBounds.left, + header.rclBounds.top, + header.rclBounds.right - header.rclBounds.left + 1, + header.rclBounds.bottom - header.rclBounds.top + 1); +} + +uint32 Emf::GetDataSize() const { + DCHECK(emf_ && !hdc_); + return GetEnhMetaFileBits(emf_, 0, NULL); +} + +bool Emf::GetData(void* buffer, uint32 size) const { + DCHECK(emf_ && !hdc_); + DCHECK(buffer && size); + uint32 size2 = + GetEnhMetaFileBits(emf_, size, reinterpret_cast<BYTE*>(buffer)); + DCHECK(size2 == size); + return size2 == size && size2 != 0; +} + +bool Emf::GetDataAsVector(std::vector<uint8>* buffer) const { + uint32 size = GetDataSize(); + if (!size) + return false; + + buffer->resize(size); + if (!GetData(&buffer->front(), size)) + return false; + return true; +} + +bool Emf::SaveTo(const base::FilePath& file_path) const { + HANDLE file = CreateFile(file_path.value().c_str(), GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + CREATE_ALWAYS, 0, NULL); + if (file == INVALID_HANDLE_VALUE) + return false; + + bool success = false; + std::vector<uint8> buffer; + if (GetDataAsVector(&buffer)) { + DWORD written = 0; + if (WriteFile(file, &*buffer.begin(), static_cast<DWORD>(buffer.size()), + &written, NULL) && + written == buffer.size()) { + success = true; + } + } + CloseHandle(file); + return success; +} + +int CALLBACK Emf::SafePlaybackProc(HDC hdc, + HANDLETABLE* handle_table, + const ENHMETARECORD* record, + int objects_count, + LPARAM param) { + Emf::EnumerationContext* context = + reinterpret_cast<Emf::EnumerationContext*>(param); + context->handle_table = handle_table; + context->objects_count = objects_count; + context->hdc = hdc; + Record record_instance(record); + bool success = record_instance.SafePlayback(context); + DCHECK(success); + return 1; +} + +Emf::EnumerationContext::EnumerationContext() { + memset(this, 0, sizeof(*this)); +} + +Emf::Record::Record(const ENHMETARECORD* record) + : record_(record) { + DCHECK(record_); +} + +bool Emf::Record::Play(Emf::EnumerationContext* context) const { + return 0 != PlayEnhMetaFileRecord(context->hdc, + context->handle_table, + record_, + context->objects_count); +} + +bool Emf::Record::SafePlayback(Emf::EnumerationContext* context) const { + // For EMF field description, see [MS-EMF] Enhanced Metafile Format + // Specification. + // + // This is the second major EMF breakage I get; the first one being + // SetDCBrushColor/SetDCPenColor/DC_PEN/DC_BRUSH being silently ignored. + // + // This function is the guts of the fix for bug 1186598. Some printer drivers + // somehow choke on certain EMF records, but calling the corresponding + // function directly on the printer HDC is fine. Still, playing the EMF record + // fails. Go figure. + // + // The main issue is that SetLayout is totally unsupported on these printers + // (HP 4500/4700). I used to call SetLayout and I stopped. I found out this is + // not sufficient because GDI32!PlayEnhMetaFile internally calls SetLayout(!) + // Damn. + // + // So I resorted to manually parse the EMF records and play them one by one. + // The issue with this method compared to using PlayEnhMetaFile to play back + // an EMF buffer is that the later silently fixes the matrix to take in + // account the matrix currently loaded at the time of the call. + // The matrix magic is done transparently when using PlayEnhMetaFile but since + // I'm processing one field at a time, I need to do the fixup myself. Note + // that PlayEnhMetaFileRecord doesn't fix the matrix correctly even when + // called inside an EnumEnhMetaFile loop. Go figure (bis). + // + // So when I see a EMR_SETWORLDTRANSFORM and EMR_MODIFYWORLDTRANSFORM, I need + // to fix the matrix according to the matrix previously loaded before playing + // back the buffer. Otherwise, the previously loaded matrix would be ignored + // and the EMF buffer would always be played back at its native resolution. + // Duh. + // + // I also use this opportunity to skip over eventual EMR_SETLAYOUT record that + // could remain. + // + // Another tweak we make is for JPEGs/PNGs in calls to StretchDIBits. + // (Our Pepper plugin code uses a JPEG). If the printer does not support + // JPEGs/PNGs natively we decompress the JPEG/PNG and then set it to the + // device. + // TODO(sanjeevr): We should also add JPEG/PNG support for SetSIBitsToDevice + // + // We also process any custom EMR_GDICOMMENT records which are our + // placeholders for StartPage and EndPage. + // Note: I should probably care about view ports and clipping, eventually. + bool res = false; + const XFORM* base_matrix = context->base_matrix; + switch (record()->iType) { + case EMR_STRETCHDIBITS: { + const EMRSTRETCHDIBITS * sdib_record = + reinterpret_cast<const EMRSTRETCHDIBITS*>(record()); + const BYTE* record_start = reinterpret_cast<const BYTE *>(record()); + const BITMAPINFOHEADER *bmih = + reinterpret_cast<const BITMAPINFOHEADER *>(record_start + + sdib_record->offBmiSrc); + const BYTE* bits = record_start + sdib_record->offBitsSrc; + bool play_normally = true; + res = false; + HDC hdc = context->hdc; + scoped_ptr<SkBitmap> bitmap; + if (bmih->biCompression == BI_JPEG) { + if (!DIBFormatNativelySupported(hdc, CHECKJPEGFORMAT, bits, + bmih->biSizeImage)) { + play_normally = false; + bitmap.reset(gfx::JPEGCodec::Decode(bits, bmih->biSizeImage)); + } + } else if (bmih->biCompression == BI_PNG) { + if (!DIBFormatNativelySupported(hdc, CHECKPNGFORMAT, bits, + bmih->biSizeImage)) { + play_normally = false; + bitmap.reset(new SkBitmap()); + gfx::PNGCodec::Decode(bits, bmih->biSizeImage, bitmap.get()); + } + } + if (!play_normally) { + DCHECK(bitmap.get()); + if (bitmap.get()) { + SkAutoLockPixels lock(*bitmap.get()); + DCHECK_EQ(bitmap->config(), SkBitmap::kARGB_8888_Config); + const uint32_t* pixels = + static_cast<const uint32_t*>(bitmap->getPixels()); + if (pixels == NULL) { + NOTREACHED(); + return false; + } + BITMAPINFOHEADER bmi = {0}; + gfx::CreateBitmapHeader(bitmap->width(), bitmap->height(), &bmi); + res = (0 != StretchDIBits(hdc, sdib_record->xDest, sdib_record->yDest, + sdib_record->cxDest, + sdib_record->cyDest, sdib_record->xSrc, + sdib_record->ySrc, + sdib_record->cxSrc, sdib_record->cySrc, + pixels, + reinterpret_cast<const BITMAPINFO *>(&bmi), + sdib_record->iUsageSrc, + sdib_record->dwRop)); + } + } else { + res = Play(context); + } + break; + } + case EMR_SETWORLDTRANSFORM: { + DCHECK_EQ(record()->nSize, sizeof(DWORD) * 2 + sizeof(XFORM)); + const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm); + HDC hdc = context->hdc; + if (base_matrix) { + res = 0 != SetWorldTransform(hdc, base_matrix) && + ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY); + } else { + res = 0 != SetWorldTransform(hdc, xform); + } + break; + } + case EMR_MODIFYWORLDTRANSFORM: { + DCHECK_EQ(record()->nSize, + sizeof(DWORD) * 2 + sizeof(XFORM) + sizeof(DWORD)); + const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm); + const DWORD* option = reinterpret_cast<const DWORD*>(xform + 1); + HDC hdc = context->hdc; + switch (*option) { + case MWT_IDENTITY: + if (base_matrix) { + res = 0 != SetWorldTransform(hdc, base_matrix); + } else { + res = 0 != ModifyWorldTransform(hdc, xform, MWT_IDENTITY); + } + break; + case MWT_LEFTMULTIPLY: + case MWT_RIGHTMULTIPLY: + res = 0 != ModifyWorldTransform(hdc, xform, *option); + break; + case 4: // MWT_SET + if (base_matrix) { + res = 0 != SetWorldTransform(hdc, base_matrix) && + ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY); + } else { + res = 0 != SetWorldTransform(hdc, xform); + } + break; + default: + res = false; + break; + } + break; + } + case EMR_SETLAYOUT: + // Ignore it. + res = true; + break; + case EMR_GDICOMMENT: { + const EMRGDICOMMENT* comment_record = + reinterpret_cast<const EMRGDICOMMENT*>(record()); + if (comment_record->cbData == sizeof(PageBreakRecord)) { + const PageBreakRecord* page_break_record = + reinterpret_cast<const PageBreakRecord*>(comment_record->Data); + if (page_break_record && page_break_record->IsValid()) { + if (page_break_record->type == PageBreakRecord::START_PAGE) { + res = !!::StartPage(context->hdc); + DCHECK_EQ(0, context->dc_on_page_start); + context->dc_on_page_start = ::SaveDC(context->hdc); + } else if (page_break_record->type == PageBreakRecord::END_PAGE) { + DCHECK_NE(0, context->dc_on_page_start); + ::RestoreDC(context->hdc, context->dc_on_page_start); + context->dc_on_page_start = 0; + res = !!::EndPage(context->hdc); + } else { + res = false; + NOTREACHED(); + } + } else { + res = Play(context); + } + } else { + res = true; + } + break; + } + default: { + res = Play(context); + break; + } + } + return res; +} + +SkDevice* Emf::StartPageForVectorCanvas( + const gfx::Size& page_size, const gfx::Rect& content_area, + const float& scale_factor) { + if (!StartPage(page_size, content_area, scale_factor)) + return NULL; + + return skia::VectorPlatformDeviceEmf::CreateDevice(page_size.width(), + page_size.height(), + true, hdc_); +} + +bool Emf::StartPage(const gfx::Size& /*page_size*/, + const gfx::Rect& /*content_area*/, + const float& /*scale_factor*/) { + DCHECK(hdc_); + if (!hdc_) + return false; + page_count_++; + PageBreakRecord record(PageBreakRecord::START_PAGE); + return !!GdiComment(hdc_, sizeof(record), + reinterpret_cast<const BYTE *>(&record)); +} + +bool Emf::FinishPage() { + DCHECK(hdc_); + if (!hdc_) + return false; + PageBreakRecord record(PageBreakRecord::END_PAGE); + return !!GdiComment(hdc_, sizeof(record), + reinterpret_cast<const BYTE *>(&record)); +} + +Emf::Enumerator::Enumerator(const Emf& emf, HDC context, const RECT* rect) { + items_.clear(); + if (!EnumEnhMetaFile(context, + emf.emf(), + &Emf::Enumerator::EnhMetaFileProc, + reinterpret_cast<void*>(this), + rect)) { + NOTREACHED(); + items_.clear(); + } + DCHECK_EQ(context_.hdc, context); +} + +Emf::Enumerator::const_iterator Emf::Enumerator::begin() const { + return items_.begin(); +} + +Emf::Enumerator::const_iterator Emf::Enumerator::end() const { + return items_.end(); +} + +int CALLBACK Emf::Enumerator::EnhMetaFileProc(HDC hdc, + HANDLETABLE* handle_table, + const ENHMETARECORD* record, + int objects_count, + LPARAM param) { + Enumerator& emf = *reinterpret_cast<Enumerator*>(param); + if (!emf.context_.handle_table) { + DCHECK(!emf.context_.handle_table); + DCHECK(!emf.context_.objects_count); + emf.context_.handle_table = handle_table; + emf.context_.objects_count = objects_count; + emf.context_.hdc = hdc; + } else { + DCHECK_EQ(emf.context_.handle_table, handle_table); + DCHECK_EQ(emf.context_.objects_count, objects_count); + DCHECK_EQ(emf.context_.hdc, hdc); + } + emf.items_.push_back(Record(record)); + return 1; +} + +bool Emf::IsAlphaBlendUsed() const { + bool result = false; + ::EnumEnhMetaFile(NULL, + emf(), + &IsAlphaBlendUsedEnumProc, + &result, + NULL); + return result; +} + +Emf* Emf::RasterizeMetafile(int raster_area_in_pixels) const { + gfx::Rect page_bounds = GetPageBounds(1); + gfx::Size page_size(page_bounds.size()); + if (page_size.GetArea() <= 0) { + NOTREACHED() << "Metafile is empty"; + page_bounds = gfx::Rect(1, 1); + } + + float scale = sqrt(float(raster_area_in_pixels) / page_size.GetArea()); + page_size.set_width(std::max<int>(1, page_size.width() * scale)); + page_size.set_height(std::max<int>(1, page_size.height() * scale)); + + + RasterBitmap bitmap(page_size); + + gfx::Rect bitmap_rect(page_size); + RECT rect = bitmap_rect.ToRECT(); + Playback(bitmap.context(), &rect); + + scoped_ptr<Emf> result(new Emf); + result->Init(); + HDC hdc = result->context(); + DCHECK(hdc); + skia::InitializeDC(hdc); + + // Params are ignored. + result->StartPage(page_bounds.size(), page_bounds, 1); + + ::ModifyWorldTransform(hdc, NULL, MWT_IDENTITY); + XFORM xform = { + float(page_bounds.width()) / bitmap_rect.width(), 0, + 0, float(page_bounds.height()) / bitmap_rect.height(), + page_bounds.x(), + page_bounds.y(), + }; + ::SetWorldTransform(hdc, &xform); + ::BitBlt(hdc, 0, 0, bitmap_rect.width(), bitmap_rect.height(), + bitmap.context(), bitmap_rect.x(), bitmap_rect.y(), SRCCOPY); + + result->FinishPage(); + result->FinishDocument(); + + return result.release(); +} + +Emf* Emf::RasterizeAlphaBlend() const { + gfx::Rect page_bounds = GetPageBounds(1); + if (page_bounds.size().GetArea() <= 0) { + NOTREACHED() << "Metafile is empty"; + page_bounds = gfx::Rect(1, 1); + } + + RasterBitmap bitmap(page_bounds.size()); + + // Map metafile page_bounds.x(), page_bounds.y() to bitmap 0, 0. + XFORM xform = { 1, 0, 0, 1, -page_bounds.x(), -page_bounds.y()}; + ::SetWorldTransform(bitmap.context(), &xform); + + scoped_ptr<Emf> result(new Emf); + result->Init(); + HDC hdc = result->context(); + DCHECK(hdc); + skia::InitializeDC(hdc); + + HDC bitmap_dc = bitmap.context(); + RECT rect = page_bounds.ToRECT(); + ::EnumEnhMetaFile(hdc, emf(), &RasterizeAlphaBlendProc, &bitmap_dc, &rect); + + result->FinishDocument(); + + return result.release(); +} + + +} // namespace printing diff --git a/chromium/printing/emf_win.h b/chromium/printing/emf_win.h new file mode 100644 index 00000000000..3516f4efb7d --- /dev/null +++ b/chromium/printing/emf_win.h @@ -0,0 +1,207 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_EMF_WIN_H_ +#define PRINTING_EMF_WIN_H_ + +#include <windows.h> + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "printing/metafile.h" + +namespace base { +class FilePath; +} + +namespace gfx { +class Rect; +class Size; +} + +namespace printing { + +// http://msdn2.microsoft.com/en-us/library/ms535522.aspx +// Windows 2000/XP: When a page in a spooled file exceeds approximately 350 +// MB, it can fail to print and not send an error message. +const size_t kMetafileMaxSize = 350*1024*1024; + +// Simple wrapper class that manage an EMF data stream and its virtual HDC. +class PRINTING_EXPORT Emf : public Metafile { + public: + class Record; + class Enumerator; + struct EnumerationContext; + + // Generates a virtual HDC that will record every GDI commands and compile + // it in a EMF data stream. + Emf(); + virtual ~Emf(); + + // Generates a new metafile that will record every GDI command, and will + // be saved to |metafile_path|. + virtual bool InitToFile(const base::FilePath& metafile_path); + + // Initializes the Emf with the data in |metafile_path|. + virtual bool InitFromFile(const base::FilePath& metafile_path); + + // Metafile methods. + virtual bool Init() OVERRIDE; + virtual bool InitFromData(const void* src_buffer, + uint32 src_buffer_size) OVERRIDE; + + virtual SkDevice* StartPageForVectorCanvas( + const gfx::Size& page_size, const gfx::Rect& content_area, + const float& scale_factor) OVERRIDE; + // Inserts a custom GDICOMMENT records indicating StartPage/EndPage calls + // (since StartPage and EndPage do not work in a metafile DC). Only valid + // when hdc_ is non-NULL. |page_size|, |content_area|, and |scale_factor| are + // ignored. + virtual bool StartPage(const gfx::Size& page_size, + const gfx::Rect& content_area, + const float& scale_factor) OVERRIDE; + virtual bool FinishPage() OVERRIDE; + virtual bool FinishDocument() OVERRIDE; + + virtual uint32 GetDataSize() const OVERRIDE; + virtual bool GetData(void* buffer, uint32 size) const OVERRIDE; + + // Saves the EMF data to a file as-is. It is recommended to use the .emf file + // extension but it is not enforced. This function synchronously writes to the + // file. For testing only. + virtual bool SaveTo(const base::FilePath& file_path) const OVERRIDE; + + // Should be passed to Playback to keep the exact same size. + virtual gfx::Rect GetPageBounds(unsigned int page_number) const OVERRIDE; + + virtual unsigned int GetPageCount() const OVERRIDE { + return page_count_; + } + + virtual HDC context() const OVERRIDE { + return hdc_; + } + + virtual bool Playback(HDC hdc, const RECT* rect) const OVERRIDE; + virtual bool SafePlayback(HDC hdc) const OVERRIDE; + + virtual HENHMETAFILE emf() const OVERRIDE { + return emf_; + } + + // Returns true if metafile contains alpha blend. + bool IsAlphaBlendUsed() const; + + // Returns new metafile with only bitmap created by playback of the current + // metafile. Returns NULL if fails. + Emf* RasterizeMetafile(int raster_area_in_pixels) const; + + // Returns new metafile where AlphaBlend replaced by bitmaps. Returns NULL + // if fails. + Emf* RasterizeAlphaBlend() const; + + private: + FRIEND_TEST_ALL_PREFIXES(EmfTest, DC); + FRIEND_TEST_ALL_PREFIXES(EmfPrintingTest, PageBreak); + FRIEND_TEST_ALL_PREFIXES(EmfTest, FileBackedEmf); + + // Retrieves the underlying data stream. It is a helper function. + bool GetDataAsVector(std::vector<uint8>* buffer) const; + + // Playbacks safely one EMF record. + static int CALLBACK SafePlaybackProc(HDC hdc, + HANDLETABLE* handle_table, + const ENHMETARECORD* record, + int objects_count, + LPARAM param); + + // Compiled EMF data handle. + HENHMETAFILE emf_; + + // Valid when generating EMF data through a virtual HDC. + HDC hdc_; + + int page_count_; + + DISALLOW_COPY_AND_ASSIGN(Emf); +}; + +struct Emf::EnumerationContext { + EnumerationContext(); + + HANDLETABLE* handle_table; + int objects_count; + HDC hdc; + const XFORM* base_matrix; + int dc_on_page_start; +}; + +// One EMF record. It keeps pointers to the EMF buffer held by Emf::emf_. +// The entries become invalid once Emf::CloseEmf() is called. +class PRINTING_EXPORT Emf::Record { + public: + // Plays the record. + bool Play(EnumerationContext* context) const; + + // Plays the record working around quirks with SetLayout, + // SetWorldTransform and ModifyWorldTransform. See implementation for details. + bool SafePlayback(EnumerationContext* context) const; + + // Access the underlying EMF record. + const ENHMETARECORD* record() const { return record_; } + + protected: + explicit Record(const ENHMETARECORD* record); + + private: + friend class Emf; + friend class Enumerator; + const ENHMETARECORD* record_; +}; + +// Retrieves individual records out of a Emf buffer. The main use is to skip +// over records that are unsupported on a specific printer or to play back +// only a part of an EMF buffer. +class PRINTING_EXPORT Emf::Enumerator { + public: + // Iterator type used for iterating the records. + typedef std::vector<Record>::const_iterator const_iterator; + + // Enumerates the records at construction time. |hdc| and |rect| are + // both optional at the same time or must both be valid. + // Warning: |emf| must be kept valid for the time this object is alive. + Enumerator(const Emf& emf, HDC hdc, const RECT* rect); + + // Retrieves the first Record. + const_iterator begin() const; + + // Retrieves the end of the array. + const_iterator end() const; + + private: + FRIEND_TEST_ALL_PREFIXES(EmfPrintingTest, Enumerate); + + // Processes one EMF record and saves it in the items_ array. + static int CALLBACK EnhMetaFileProc(HDC hdc, + HANDLETABLE* handle_table, + const ENHMETARECORD* record, + int objects_count, + LPARAM param); + + // The collection of every EMF records in the currently loaded EMF buffer. + // Initialized by Enumerate(). It keeps pointers to the EMF buffer held by + // Emf::emf_. The entries become invalid once Emf::CloseEmf() is called. + std::vector<Record> items_; + + EnumerationContext context_; + + DISALLOW_COPY_AND_ASSIGN(Enumerator); +}; + +} // namespace printing + +#endif // PRINTING_EMF_WIN_H_ diff --git a/chromium/printing/emf_win_unittest.cc b/chromium/printing/emf_win_unittest.cc new file mode 100644 index 00000000000..0f5e6ca8cf9 --- /dev/null +++ b/chromium/printing/emf_win_unittest.cc @@ -0,0 +1,230 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/emf_win.h" + +// For quick access. +#include <wingdi.h> +#include <winspool.h> + +#include <string> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/win/scoped_hdc.h" +#include "printing/printing_context.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/point.h" +#include "ui/gfx/size.h" + +namespace { + +// This test is automatically disabled if no printer named "UnitTest Printer" is +// available. +class EmfPrintingTest : public testing::Test { + public: + typedef testing::Test Parent; + static bool IsTestCaseDisabled() { + // It is assumed this printer is a HP Color LaserJet 4550 PCL or 4700. + HDC hdc = CreateDC(L"WINSPOOL", L"UnitTest Printer", NULL, NULL); + if (!hdc) + return true; + DeleteDC(hdc); + return false; + } +}; + +const uint32 EMF_HEADER_SIZE = 128; + +} // namespace + +namespace printing { + +TEST(EmfTest, DC) { + // Simplest use case. + uint32 size; + std::vector<BYTE> data; + { + Emf emf; + EXPECT_TRUE(emf.Init()); + EXPECT_TRUE(emf.context() != NULL); + // An empty EMF is invalid, so we put at least a rectangle in it. + ::Rectangle(emf.context(), 10, 10, 190, 190); + EXPECT_TRUE(emf.FinishDocument()); + size = emf.GetDataSize(); + EXPECT_GT(size, EMF_HEADER_SIZE); + EXPECT_TRUE(emf.GetDataAsVector(&data)); + EXPECT_EQ(data.size(), size); + } + + // Playback the data. + Emf emf; + EXPECT_TRUE(emf.InitFromData(&data.front(), size)); + HDC hdc = CreateCompatibleDC(NULL); + EXPECT_TRUE(hdc); + RECT output_rect = {0, 0, 10, 10}; + EXPECT_TRUE(emf.Playback(hdc, &output_rect)); + EXPECT_TRUE(DeleteDC(hdc)); +} + +// Disabled if no "UnitTest printer" exist. Useful to reproduce bug 1186598. +TEST_F(EmfPrintingTest, Enumerate) { + if (IsTestCaseDisabled()) + return; + + PrintSettings settings; + + // My test case is a HP Color LaserJet 4550 PCL. + settings.set_device_name(L"UnitTest Printer"); + + // Initialize it. + scoped_ptr<PrintingContext> context(PrintingContext::Create(std::string())); + EXPECT_EQ(context->InitWithSettings(settings), PrintingContext::OK); + + base::FilePath emf_file; + EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &emf_file)); + emf_file = emf_file.Append(FILE_PATH_LITERAL("printing")) + .Append(FILE_PATH_LITERAL("test")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("test4.emf")); + // Load any EMF with an image. + Emf emf; + std::string emf_data; + file_util::ReadFileToString(emf_file, &emf_data); + ASSERT_TRUE(emf_data.size()); + EXPECT_TRUE(emf.InitFromData(&emf_data[0], emf_data.size())); + + // This will print to file. The reason is that when running inside a + // unit_test, PrintingContext automatically dumps its files to the + // current directory. + // TODO(maruel): Clean the .PRN file generated in current directory. + context->NewDocument(L"EmfTest.Enumerate"); + context->NewPage(); + // Process one at a time. + Emf::Enumerator emf_enum(emf, context->context(), + &emf.GetPageBounds(1).ToRECT()); + for (Emf::Enumerator::const_iterator itr = emf_enum.begin(); + itr != emf_enum.end(); + ++itr) { + // To help debugging. + ptrdiff_t index = itr - emf_enum.begin(); + // If you get this assert, you need to lookup iType in wingdi.h. It starts + // with EMR_HEADER. + EMR_HEADER; + EXPECT_TRUE(itr->SafePlayback(&emf_enum.context_)) << + " index: " << index << " type: " << itr->record()->iType; + } + context->PageDone(); + context->DocumentDone(); +} + +// Disabled if no "UnitTest printer" exists. +TEST_F(EmfPrintingTest, PageBreak) { + base::win::ScopedCreateDC dc( + CreateDC(L"WINSPOOL", L"UnitTest Printer", NULL, NULL)); + if (!dc.Get()) + return; + uint32 size; + std::vector<BYTE> data; + { + Emf emf; + EXPECT_TRUE(emf.Init()); + EXPECT_TRUE(emf.context() != NULL); + int pages = 3; + while (pages) { + EXPECT_TRUE(emf.StartPage(gfx::Size(), gfx::Rect(), 1)); + ::Rectangle(emf.context(), 10, 10, 190, 190); + EXPECT_TRUE(emf.FinishPage()); + --pages; + } + EXPECT_EQ(3U, emf.GetPageCount()); + EXPECT_TRUE(emf.FinishDocument()); + size = emf.GetDataSize(); + EXPECT_TRUE(emf.GetDataAsVector(&data)); + EXPECT_EQ(data.size(), size); + } + + // Playback the data. + DOCINFO di = {0}; + di.cbSize = sizeof(DOCINFO); + di.lpszDocName = L"Test Job"; + int job_id = ::StartDoc(dc.Get(), &di); + Emf emf; + EXPECT_TRUE(emf.InitFromData(&data.front(), size)); + EXPECT_TRUE(emf.SafePlayback(dc.Get())); + ::EndDoc(dc.Get()); + // Since presumably the printer is not real, let us just delete the job from + // the queue. + HANDLE printer = NULL; + if (::OpenPrinter(L"UnitTest Printer", &printer, NULL)) { + ::SetJob(printer, job_id, 0, NULL, JOB_CONTROL_DELETE); + ClosePrinter(printer); + } +} + +TEST(EmfTest, FileBackedEmf) { + // Simplest use case. + base::ScopedTempDir scratch_metafile_dir; + ASSERT_TRUE(scratch_metafile_dir.CreateUniqueTempDir()); + base::FilePath metafile_path; + EXPECT_TRUE(file_util::CreateTemporaryFileInDir(scratch_metafile_dir.path(), + &metafile_path)); + uint32 size; + std::vector<BYTE> data; + { + Emf emf; + EXPECT_TRUE(emf.InitToFile(metafile_path)); + EXPECT_TRUE(emf.context() != NULL); + // An empty EMF is invalid, so we put at least a rectangle in it. + ::Rectangle(emf.context(), 10, 10, 190, 190); + EXPECT_TRUE(emf.FinishDocument()); + size = emf.GetDataSize(); + EXPECT_GT(size, EMF_HEADER_SIZE); + EXPECT_TRUE(emf.GetDataAsVector(&data)); + EXPECT_EQ(data.size(), size); + } + int64 file_size = 0; + file_util::GetFileSize(metafile_path, &file_size); + EXPECT_EQ(size, file_size); + + // Playback the data. + HDC hdc = CreateCompatibleDC(NULL); + EXPECT_TRUE(hdc); + Emf emf; + EXPECT_TRUE(emf.InitFromFile(metafile_path)); + RECT output_rect = {0, 0, 10, 10}; + EXPECT_TRUE(emf.Playback(hdc, &output_rect)); + EXPECT_TRUE(DeleteDC(hdc)); +} + +TEST(EmfTest, RasterizeMetafile) { + Emf emf; + EXPECT_TRUE(emf.Init()); + EXPECT_TRUE(emf.context() != NULL); + HBRUSH brush = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)); + for (int i = 0; i < 4; ++i) { + RECT rect = { 5 + i, 5 + i, 5 + i + 1, 5 + i + 2}; + FillRect(emf.context(), &rect, brush); + } + EXPECT_TRUE(emf.FinishDocument()); + + scoped_ptr<Emf> raster(emf.RasterizeMetafile(1)); + // Just 1px bitmap but should be stretched to the same bounds. + EXPECT_EQ(emf.GetPageBounds(1), raster->GetPageBounds(1)); + + raster.reset(emf.RasterizeMetafile(20)); + EXPECT_EQ(emf.GetPageBounds(1), raster->GetPageBounds(1)); + + raster.reset(emf.RasterizeMetafile(16*1024*1024)); + // Expected size about 64MB. + EXPECT_LE(abs(int(raster->GetDataSize()) - 64*1024*1024), 1024*1024); + // Bounds should still be the same. + EXPECT_EQ(emf.GetPageBounds(1), raster->GetPageBounds(1)); +} + +} // namespace printing diff --git a/chromium/printing/image.cc b/chromium/printing/image.cc new file mode 100644 index 00000000000..47ce089d302 --- /dev/null +++ b/chromium/printing/image.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/image.h" + +#include <algorithm> + +#include "base/file_util.h" +#include "base/md5.h" +#include "base/safe_numerics.h" +#include "base/strings/string_number_conversions.h" +#include "printing/metafile.h" +#include "printing/metafile_impl.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/codec/png_codec.h" + +namespace printing { + +Image::Image(const base::FilePath& path) + : row_length_(0), + ignore_alpha_(true) { + std::string data; + file_util::ReadFileToString(path, &data); + bool success = false; + if (path.MatchesExtension(FILE_PATH_LITERAL(".png"))) { + success = LoadPng(data); + } else if (path.MatchesExtension(FILE_PATH_LITERAL(".emf"))) { + success = LoadMetafile(data); + } else { + DCHECK(false); + } + if (!success) { + size_.SetSize(0, 0); + row_length_ = 0; + data_.clear(); + } +} + +Image::Image(const Metafile& metafile) + : row_length_(0), + ignore_alpha_(true) { + LoadMetafile(metafile); +} + +Image::Image(const Image& image) + : size_(image.size_), + row_length_(image.row_length_), + data_(image.data_), + ignore_alpha_(image.ignore_alpha_) { +} + +Image::~Image() {} + +std::string Image::checksum() const { + base::MD5Digest digest; + base::MD5Sum(&data_[0], data_.size(), &digest); + return base::HexEncode(&digest, sizeof(digest)); +} + +bool Image::SaveToPng(const base::FilePath& filepath) const { + DCHECK(!data_.empty()); + std::vector<unsigned char> compressed; + bool success = gfx::PNGCodec::Encode(&*data_.begin(), + gfx::PNGCodec::FORMAT_BGRA, + size_, + row_length_, + true, + std::vector<gfx::PNGCodec::Comment>(), + &compressed); + DCHECK(success && compressed.size()); + if (success) { + int write_bytes = file_util::WriteFile( + filepath, + reinterpret_cast<char*>(&*compressed.begin()), + base::checked_numeric_cast<int>(compressed.size())); + success = (write_bytes == static_cast<int>(compressed.size())); + DCHECK(success); + } + return success; +} + +double Image::PercentageDifferent(const Image& rhs) const { + if (size_.width() == 0 || size_.height() == 0 || + rhs.size_.width() == 0 || rhs.size_.height() == 0) + return 100.; + + int width = std::min(size_.width(), rhs.size_.width()); + int height = std::min(size_.height(), rhs.size_.height()); + // Compute pixels different in the overlap + int pixels_different = 0; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint32 lhs_pixel = pixel_at(x, y); + uint32 rhs_pixel = rhs.pixel_at(x, y); + if (lhs_pixel != rhs_pixel) + ++pixels_different; + } + + // Look for extra right lhs pixels. They should be white. + for (int x = width; x < size_.width(); ++x) { + uint32 lhs_pixel = pixel_at(x, y); + if (lhs_pixel != Color(SK_ColorWHITE)) + ++pixels_different; + } + + // Look for extra right rhs pixels. They should be white. + for (int x = width; x < rhs.size_.width(); ++x) { + uint32 rhs_pixel = rhs.pixel_at(x, y); + if (rhs_pixel != Color(SK_ColorWHITE)) + ++pixels_different; + } + } + + // Look for extra bottom lhs pixels. They should be white. + for (int y = height; y < size_.height(); ++y) { + for (int x = 0; x < size_.width(); ++x) { + uint32 lhs_pixel = pixel_at(x, y); + if (lhs_pixel != Color(SK_ColorWHITE)) + ++pixels_different; + } + } + + // Look for extra bottom rhs pixels. They should be white. + for (int y = height; y < rhs.size_.height(); ++y) { + for (int x = 0; x < rhs.size_.width(); ++x) { + uint32 rhs_pixel = rhs.pixel_at(x, y); + if (rhs_pixel != Color(SK_ColorWHITE)) + ++pixels_different; + } + } + + // Like the WebKit ImageDiff tool, we define percentage different in terms + // of the size of the 'actual' bitmap. + double total_pixels = static_cast<double>(size_.width()) * + static_cast<double>(height); + return static_cast<double>(pixels_different) / total_pixels * 100.; +} + +bool Image::LoadPng(const std::string& compressed) { + int w; + int h; + bool success = gfx::PNGCodec::Decode( + reinterpret_cast<const unsigned char*>(compressed.c_str()), + compressed.size(), gfx::PNGCodec::FORMAT_BGRA, &data_, &w, &h); + size_.SetSize(w, h); + row_length_ = size_.width() * sizeof(uint32); + return success; +} + +bool Image::LoadMetafile(const std::string& data) { + DCHECK(!data.empty()); + NativeMetafile metafile; + if (!metafile.InitFromData(data.data(), + base::checked_numeric_cast<uint32>(data.size()))) + return false; + return LoadMetafile(metafile); +} + +} // namespace printing diff --git a/chromium/printing/image.h b/chromium/printing/image.h new file mode 100644 index 00000000000..b40657a3782 --- /dev/null +++ b/chromium/printing/image.h @@ -0,0 +1,101 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_IMAGE_H_ +#define PRINTING_IMAGE_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "printing/printing_export.h" +#include "ui/gfx/size.h" + +namespace base { +class FilePath; +} + +namespace printing { + +class Metafile; + +// Lightweight raw-bitmap management. The image, once initialized, is immutable. +// The main purpose is testing image contents. +class PRINTING_EXPORT Image { + public: + // Creates the image from the given file on disk. Uses extension to + // defer file type. PNG and EMF (on Windows) currently supported. + // If image loading fails size().IsEmpty() will be true. + explicit Image(const base::FilePath& path); + + // Creates the image from the metafile. Deduces bounds based on bounds in + // metafile. If loading fails size().IsEmpty() will be true. + explicit Image(const Metafile& metafile); + + // Copy constructor. + explicit Image(const Image& image); + + ~Image(); + + const gfx::Size& size() const { + return size_; + } + + // Return a checksum of the image (MD5 over the internal data structure). + std::string checksum() const; + + // Save image as PNG. + bool SaveToPng(const base::FilePath& filepath) const; + + // Returns % of pixels different + double PercentageDifferent(const Image& rhs) const; + + // Returns the 0x0RGB or 0xARGB value of the pixel at the given location. + uint32 Color(uint32 color) const { + if (ignore_alpha_) + return color & 0xFFFFFF; // Strip out A. + else + return color; + } + + uint32 pixel_at(int x, int y) const { + DCHECK(x >= 0 && x < size_.width()); + DCHECK(y >= 0 && y < size_.height()); + const uint32* data = reinterpret_cast<const uint32*>(&*data_.begin()); + const uint32* data_row = data + y * row_length_ / sizeof(uint32); + return Color(data_row[x]); + } + + private: + // Construct from metafile. This is kept internal since it's ambiguous what + // kind of data is used (png, bmp, metafile etc). + Image(const void* data, size_t size); + + bool LoadPng(const std::string& compressed); + + bool LoadMetafile(const std::string& data); + + bool LoadMetafile(const Metafile& metafile); + + // Pixel dimensions of the image. + gfx::Size size_; + + // Length of a line in bytes. + int row_length_; + + // Actual bitmap data in arrays of RGBAs (so when loaded as uint32, it's + // 0xABGR). + std::vector<unsigned char> data_; + + // Flag to signal if the comparison functions should ignore the alpha channel. + const bool ignore_alpha_; // Currently always true. + + // Prevent operator= (this function has no implementation) + Image& operator=(const Image& image); +}; + +} // namespace printing + +#endif // PRINTING_IMAGE_H_ diff --git a/chromium/printing/image_linux.cc b/chromium/printing/image_linux.cc new file mode 100644 index 00000000000..3968cfd993f --- /dev/null +++ b/chromium/printing/image_linux.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/image.h" + +#include "base/logging.h" + +namespace printing { + +bool Image::LoadMetafile(const Metafile& metafile) { + return false; +} + +} // namespace printing diff --git a/chromium/printing/image_mac.cc b/chromium/printing/image_mac.cc new file mode 100644 index 00000000000..2ec9ef60048 --- /dev/null +++ b/chromium/printing/image_mac.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/image.h" + +#include <ApplicationServices/ApplicationServices.h> + +#include "base/mac/scoped_cftyperef.h" +#include "printing/metafile.h" +#include "ui/gfx/rect.h" + +namespace printing { + +bool Image::LoadMetafile(const Metafile& metafile) { + // The printing system uses single-page metafiles (page indexes are 1-based). + const unsigned int page_number = 1; + gfx::Rect rect(metafile.GetPageBounds(page_number)); + if (rect.width() < 1 || rect.height() < 1) + return false; + + size_ = rect.size(); + row_length_ = size_.width() * sizeof(uint32); + size_t bytes = row_length_ * size_.height(); + DCHECK(bytes); + + data_.resize(bytes); + base::ScopedCFTypeRef<CGColorSpaceRef> color_space( + CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); + base::ScopedCFTypeRef<CGContextRef> bitmap_context( + CGBitmapContextCreate(&*data_.begin(), + size_.width(), + size_.height(), + 8, + row_length_, + color_space, + kCGImageAlphaPremultipliedLast)); + DCHECK(bitmap_context.get()); + + struct Metafile::MacRenderPageParams params; + params.shrink_to_fit = true; + metafile.RenderPage(page_number, bitmap_context, + CGRectMake(0, 0, size_.width(), size_.height()), params); + + return true; +} + +} // namespace printing diff --git a/chromium/printing/image_win.cc b/chromium/printing/image_win.cc new file mode 100644 index 00000000000..fdf99c8bf6b --- /dev/null +++ b/chromium/printing/image_win.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/image.h" + +#include "base/win/scoped_gdi_object.h" +#include "base/win/scoped_hdc.h" +#include "base/win/scoped_select_object.h" +#include "printing/metafile.h" +#include "skia/ext/platform_device.h" +#include "ui/gfx/gdi_util.h" // EMF support +#include "ui/gfx/rect.h" + + +namespace { + +// A simple class which temporarily overrides system settings. +// The bitmap image rendered via the PlayEnhMetaFile() function depends on +// some system settings. +// As a workaround for such dependency, this class saves the system settings +// and changes them. This class also restore the saved settings in its +// destructor. +class DisableFontSmoothing { + public: + explicit DisableFontSmoothing() : enable_again_(false) { + BOOL enabled; + if (SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &enabled, 0) && + enabled) { + if (SystemParametersInfo(SPI_SETFONTSMOOTHING, FALSE, NULL, 0)) + enable_again_ = true; + } + } + + ~DisableFontSmoothing() { + if (enable_again_) { + BOOL result = SystemParametersInfo(SPI_SETFONTSMOOTHING, TRUE, NULL, 0); + DCHECK(result); + } + } + + private: + bool enable_again_; + + DISALLOW_COPY_AND_ASSIGN(DisableFontSmoothing); +}; + +} // namespace + +namespace printing { + +bool Image::LoadMetafile(const Metafile& metafile) { + gfx::Rect rect(metafile.GetPageBounds(1)); + DisableFontSmoothing disable_in_this_scope; + + // Create a temporary HDC and bitmap to retrieve the rendered data. + base::win::ScopedCreateDC hdc(::CreateCompatibleDC(NULL)); + BITMAPV4HEADER hdr; + DCHECK_EQ(rect.x(), 0); + DCHECK_EQ(rect.y(), 0); + DCHECK_GE(rect.width(), 0); // Metafile could be empty. + DCHECK_GE(rect.height(), 0); + + if (rect.width() < 1 || rect.height() < 1) + return false; + + size_ = rect.size(); + gfx::CreateBitmapV4Header(rect.width(), rect.height(), &hdr); + unsigned char* bits = NULL; + base::win::ScopedBitmap bitmap( + ::CreateDIBSection(hdc, reinterpret_cast<BITMAPINFO*>(&hdr), 0, + reinterpret_cast<void**>(&bits), NULL, 0)); + DCHECK(bitmap); + base::win::ScopedSelectObject select_object(hdc, bitmap); + + skia::InitializeDC(hdc); + + bool success = metafile.Playback(hdc, NULL); + + row_length_ = size_.width() * sizeof(uint32); + size_t bytes = row_length_ * size_.height(); + DCHECK(bytes); + + data_.assign(bits, bits + bytes); + + return success; +} + +} // namespace printing diff --git a/chromium/printing/metafile.h b/chromium/printing/metafile.h new file mode 100644 index 00000000000..ca0901b2b65 --- /dev/null +++ b/chromium/printing/metafile.h @@ -0,0 +1,168 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_METAFILE_H_ +#define PRINTING_METAFILE_H_ + +#include "base/basictypes.h" +#include "build/build_config.h" +#include "printing/printing_export.h" +#include "ui/gfx/native_widget_types.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_MACOSX) +#include <ApplicationServices/ApplicationServices.h> +#include <CoreFoundation/CoreFoundation.h> +#include "base/mac/scoped_cftyperef.h" +#endif + +namespace base { +class FilePath; +} + +namespace gfx { +class Rect; +class Size; +} + +class SkDevice; + +#if defined(OS_CHROMEOS) +namespace base { +struct FileDescriptor; +} +#endif + +namespace printing { + +// This class creates a graphics context that renders into a data stream +// (usually PDF or EMF). +class PRINTING_EXPORT Metafile { + public: +#if defined(OS_MACOSX) + // |shrink_to_fit| specifies whether the output should be shrunk to fit a + // destination page if the source PDF is bigger than the destination page in + // any dimension. If this is false, parts of the source PDF page that lie + // outside the bounds will be clipped. + // |stretch_to_fit| specifies whether the output should be stretched to fit + // the destination page if the source page size is smaller in all dimensions. + // |center_horizontally| specifies whether the output (after any scaling is + // done) should be centered horizontally within the destination page. + // |center_vertically| specifies whether the output (after any scaling is + // done) should be centered vertically within the destination page. + // Note that all scaling preserves the original aspect ratio of the page. + // |autorotate| specifies whether the source PDF should be autorotated to fit + // on the destination page. + struct MacRenderPageParams { + MacRenderPageParams() + : shrink_to_fit(false), + stretch_to_fit(false), + center_horizontally(false), + center_vertically(false), + autorotate(false) { + } + + bool shrink_to_fit; + bool stretch_to_fit; + bool center_horizontally; + bool center_vertically; + bool autorotate; + }; +#endif // defined(OS_MACOSX) + + virtual ~Metafile() {} + + // Initializes a fresh new metafile for rendering. Returns false on failure. + // Note: It should only be called from within the renderer process to allocate + // rendering resources. + virtual bool Init() = 0; + + // Initializes the metafile with the data in |src_buffer|. Returns true + // on success. + // Note: It should only be called from within the browser process. + virtual bool InitFromData(const void* src_buffer, uint32 src_buffer_size) = 0; + + // This method calls StartPage and then returns an appropriate + // VectorPlatformDevice implementation bound to the context created by + // StartPage or NULL on error. + virtual SkDevice* StartPageForVectorCanvas( + const gfx::Size& page_size, + const gfx::Rect& content_area, + const float& scale_factor) = 0; + + // Prepares a context for rendering a new page with the given |page_size|, + // |content_area| and a |scale_factor| to use for the drawing. The units are + // in points (=1/72 in). Returns true on success. + virtual bool StartPage(const gfx::Size& page_size, + const gfx::Rect& content_area, + const float& scale_factor) = 0; + + // Closes the current page and destroys the context used in rendering that + // page. The results of current page will be appended into the underlying + // data stream. Returns true on success. + virtual bool FinishPage() = 0; + + // Closes the metafile. No further rendering is allowed (the current page + // is implicitly closed). + virtual bool FinishDocument() = 0; + + // Returns the size of the underlying data stream. Only valid after Close() + // has been called. + virtual uint32 GetDataSize() const = 0; + + // Copies the first |dst_buffer_size| bytes of the underlying data stream into + // |dst_buffer|. This function should ONLY be called after Close() is invoked. + // Returns true if the copy succeeds. + virtual bool GetData(void* dst_buffer, uint32 dst_buffer_size) const = 0; + + // Saves the underlying data to the given file. This function should ONLY be + // called after the metafile is closed. Returns true if writing succeeded. + virtual bool SaveTo(const base::FilePath& file_path) const = 0; + + // Returns the bounds of the given page. Pages use a 1-based index. + virtual gfx::Rect GetPageBounds(unsigned int page_number) const = 0; + virtual unsigned int GetPageCount() const = 0; + + // Get the context for rendering to the PDF. + virtual gfx::NativeDrawingContext context() const = 0; + +#if defined(OS_WIN) + // "Plays" the EMF buffer in a HDC. It is the same effect as calling the + // original GDI function that were called when recording the EMF. |rect| is in + // "logical units" and is optional. If |rect| is NULL, the natural EMF bounds + // are used. + // Note: Windows has been known to have stack buffer overflow in its GDI + // functions, whether used directly or indirectly through precompiled EMF + // data. We have to accept the risk here. Since it is used only for printing, + // it requires user intervention. + virtual bool Playback(gfx::NativeDrawingContext hdc, + const RECT* rect) const = 0; + + // The slow version of Playback(). It enumerates all the records and play them + // back in the HDC. The trick is that it skip over the records known to have + // issue with some printers. See Emf::Record::SafePlayback implementation for + // details. + virtual bool SafePlayback(gfx::NativeDrawingContext hdc) const = 0; + + virtual HENHMETAFILE emf() const = 0; +#elif defined(OS_MACOSX) + // Renders the given page into |rect| in the given context. + // Pages use a 1-based index. The rendering uses the arguments in + // |params| to determine scaling, translation, and rotation. + virtual bool RenderPage(unsigned int page_number, + gfx::NativeDrawingContext context, + const CGRect rect, + const MacRenderPageParams& params) const = 0; +#elif defined(OS_CHROMEOS) + // Saves the underlying data to the file associated with fd. This function + // should ONLY be called after the metafile is closed. + // Returns true if writing succeeded. + virtual bool SaveToFD(const base::FileDescriptor& fd) const = 0; +#endif // if defined(OS_CHROMEOS) +}; + +} // namespace printing + +#endif // PRINTING_METAFILE_H_ diff --git a/chromium/printing/metafile_impl.h b/chromium/printing/metafile_impl.h new file mode 100644 index 00000000000..840a1cede68 --- /dev/null +++ b/chromium/printing/metafile_impl.h @@ -0,0 +1,26 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_METAFILE_IMPL_H_ +#define PRINTING_METAFILE_IMPL_H_ + +#include "printing/pdf_metafile_skia.h" + +#if defined(OS_WIN) +#include "printing/emf_win.h" +#endif + +namespace printing { + +#if defined(OS_WIN) +typedef Emf NativeMetafile; +typedef PdfMetafileSkia PreviewMetafile; +#elif defined(OS_POSIX) +typedef PdfMetafileSkia NativeMetafile; +typedef PdfMetafileSkia PreviewMetafile; +#endif + +} // namespace printing + +#endif // PRINTING_METAFILE_IMPL_H_ diff --git a/chromium/printing/metafile_skia_wrapper.cc b/chromium/printing/metafile_skia_wrapper.cc new file mode 100644 index 00000000000..2fddc004e12 --- /dev/null +++ b/chromium/printing/metafile_skia_wrapper.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/metafile_skia_wrapper.h" +#include "skia/ext/platform_device.h" +#include "skia/ext/refptr.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkDevice.h" +#include "third_party/skia/include/core/SkMetaData.h" + +namespace printing { + +namespace { + +const char* kMetafileKey = "CrMetafile"; +const char* kCustomScaleKey = "CrCustomScale"; + +} // namespace + +// static +void MetafileSkiaWrapper::SetMetafileOnCanvas(const SkCanvas& canvas, + Metafile* metafile) { + skia::RefPtr<MetafileSkiaWrapper> wrapper; + if (metafile) + wrapper = skia::AdoptRef(new MetafileSkiaWrapper(metafile)); + + SkMetaData& meta = skia::getMetaData(canvas); + meta.setRefCnt(kMetafileKey, wrapper.get()); +} + +// static +Metafile* MetafileSkiaWrapper::GetMetafileFromCanvas(const SkCanvas& canvas) { + SkMetaData& meta = skia::getMetaData(canvas); + SkRefCnt* value; + if (!meta.findRefCnt(kMetafileKey, &value) || !value) + return NULL; + + return static_cast<MetafileSkiaWrapper*>(value)->metafile_; +} + +// static +void MetafileSkiaWrapper::SetCustomScaleOnCanvas(const SkCanvas& canvas, + double scale) { + SkMetaData& meta = skia::getMetaData(canvas); + meta.setScalar(kCustomScaleKey, SkFloatToScalar(scale)); +} + +// static +bool MetafileSkiaWrapper::GetCustomScaleOnCanvas(const SkCanvas& canvas, + double* scale) { + SkMetaData& meta = skia::getMetaData(canvas); + SkScalar value; + if (!meta.findScalar(kCustomScaleKey, &value)) + return false; + + *scale = SkScalarToFloat(value); + return true; +} + +MetafileSkiaWrapper::MetafileSkiaWrapper(Metafile* metafile) + : metafile_(metafile) { +} + +} // namespace printing diff --git a/chromium/printing/metafile_skia_wrapper.h b/chromium/printing/metafile_skia_wrapper.h new file mode 100644 index 00000000000..42bbc5f9f69 --- /dev/null +++ b/chromium/printing/metafile_skia_wrapper.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_METAFILE_SKIA_WRAPPER_H_ +#define PRINTING_METAFILE_SKIA_WRAPPER_H_ + +#include "printing/printing_export.h" +#include "third_party/skia/include/core/SkRefCnt.h" + +class SkCanvas; + +namespace printing { + +class Metafile; + +// A wrapper class with static methods to set and retrieve a Metafile +// on an SkCanvas. The ownership of the metafile is not affected and it +// is the caller's responsibility to ensure that the metafile remains valid +// as long as the canvas. +class PRINTING_EXPORT MetafileSkiaWrapper : public SkRefCnt { + public: + static void SetMetafileOnCanvas(const SkCanvas& canvas, Metafile* metafile); + + static Metafile* GetMetafileFromCanvas(const SkCanvas& canvas); + + // Methods to set and retrieve custom scale factor for metafile from canvas. + static void SetCustomScaleOnCanvas(const SkCanvas& canvas, double scale); + static bool GetCustomScaleOnCanvas(const SkCanvas& canvas, double* scale); + + private: + explicit MetafileSkiaWrapper(Metafile* metafile); + + Metafile* metafile_; +}; + +} // namespace printing + +#endif // PRINTING_METAFILE_SKIA_WRAPPER_H_ diff --git a/chromium/printing/page_number.cc b/chromium/printing/page_number.cc new file mode 100644 index 00000000000..8e9287027e7 --- /dev/null +++ b/chromium/printing/page_number.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/page_number.h" + +#include <limits> + +#include "base/logging.h" +#include "printing/print_settings.h" + +namespace printing { + +PageNumber::PageNumber(const PrintSettings& settings, int document_page_count) { + Init(settings, document_page_count); +} + +PageNumber::PageNumber() + : ranges_(NULL), + page_number_(-1), + page_range_index_(-1), + document_page_count_(0) { +} + +void PageNumber::operator=(const PageNumber& other) { + ranges_ = other.ranges_; + page_number_ = other.page_number_; + page_range_index_ = other.page_range_index_; + document_page_count_ = other.document_page_count_; +} + +void PageNumber::Init(const PrintSettings& settings, int document_page_count) { + DCHECK(document_page_count); + ranges_ = settings.ranges.empty() ? NULL : &settings.ranges; + document_page_count_ = document_page_count; + if (ranges_) { + page_range_index_ = 0; + page_number_ = (*ranges_)[0].from; + } else { + if (document_page_count) { + page_number_ = 0; + } else { + page_number_ = -1; + } + page_range_index_ = -1; + } +} + +int PageNumber::operator++() { + if (!ranges_) { + // Switch to next page. + if (++page_number_ == document_page_count_) { + // Finished. + *this = npos(); + } + } else { + // Switch to next page. + ++page_number_; + // Page ranges are inclusive. + if (page_number_ > (*ranges_)[page_range_index_].to) { + DCHECK(ranges_->size() <= static_cast<size_t>( + std::numeric_limits<int>::max())); + if (++page_range_index_ == static_cast<int>(ranges_->size())) { + // Finished. + *this = npos(); + } else { + page_number_ = (*ranges_)[page_range_index_].from; + } + } + } + return ToInt(); +} + +bool PageNumber::operator==(const PageNumber& other) const { + return page_number_ == other.page_number_ && + page_range_index_ == other.page_range_index_; +} +bool PageNumber::operator!=(const PageNumber& other) const { + return page_number_ != other.page_number_ || + page_range_index_ != other.page_range_index_; +} + +} // namespace printing diff --git a/chromium/printing/page_number.h b/chromium/printing/page_number.h new file mode 100644 index 00000000000..a51ad1e98ad --- /dev/null +++ b/chromium/printing/page_number.h @@ -0,0 +1,73 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PAGE_NUMBER_H_ +#define PRINTING_PAGE_NUMBER_H_ + +#include <ostream> + +#include "printing/page_range.h" + +namespace printing { + +class PrintSettings; + +// Represents a page series following the array of page ranges defined in a +// PrintSettings. +class PRINTING_EXPORT PageNumber { + public: + // Initializes the page to the first page in the settings's range or 0. + PageNumber(const PrintSettings& settings, int document_page_count); + + PageNumber(); + + void operator=(const PageNumber& other); + + // Initializes the page to the first page in the setting's range or 0. It + // initialize to npos if the range is empty and document_page_count is 0. + void Init(const PrintSettings& settings, int document_page_count); + + // Converts to a page numbers. + int ToInt() const { + return page_number_; + } + + // Calculates the next page in the serie. + int operator++(); + + // Returns an instance that represents the end of a serie. + static const PageNumber npos() { + return PageNumber(); + } + + // Equality operator. Only the current page number is verified so that + // "page != PageNumber::npos()" works. + bool operator==(const PageNumber& other) const; + bool operator!=(const PageNumber& other) const; + + private: + // The page range to follow. + const PageRanges* ranges_; + + // The next page to be printed. -1 when not printing. + int page_number_; + + // The next page to be printed. -1 when not used. Valid only if + // document()->settings().range.empty() is false. + int page_range_index_; + + // Number of expected pages in the document. Used when ranges_ is NULL. + int document_page_count_; +}; + +// Debug output support. +template<class E, class T> +inline typename std::basic_ostream<E,T>& operator<<( + typename std::basic_ostream<E,T>& ss, const PageNumber& page) { + return ss << page.ToInt(); +} + +} // namespace printing + +#endif // PRINTING_PAGE_NUMBER_H_ diff --git a/chromium/printing/page_number_unittest.cc b/chromium/printing/page_number_unittest.cc new file mode 100644 index 00000000000..ece1e0f6f0a --- /dev/null +++ b/chromium/printing/page_number_unittest.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/page_number.h" +#include "printing/print_settings.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PageNumberTest, Count) { + printing::PrintSettings settings; + printing::PageNumber page; + EXPECT_EQ(printing::PageNumber::npos(), page); + page.Init(settings, 3); + EXPECT_EQ(0, page.ToInt()); + EXPECT_NE(printing::PageNumber::npos(), page); + ++page; + EXPECT_EQ(1, page.ToInt()); + EXPECT_NE(printing::PageNumber::npos(), page); + + printing::PageNumber page_copy(page); + EXPECT_EQ(1, page_copy.ToInt()); + EXPECT_EQ(1, page.ToInt()); + ++page; + EXPECT_EQ(1, page_copy.ToInt()); + EXPECT_EQ(2, page.ToInt()); + ++page; + EXPECT_EQ(printing::PageNumber::npos(), page); + ++page; + EXPECT_EQ(printing::PageNumber::npos(), page); +} diff --git a/chromium/printing/page_range.cc b/chromium/printing/page_range.cc new file mode 100644 index 00000000000..7a8cb84c84c --- /dev/null +++ b/chromium/printing/page_range.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/page_range.h" + +#include <set> +#include <vector> + +namespace printing { + +/* static */ +std::vector<int> PageRange::GetPages(const PageRanges& ranges) { + std::set<int> pages; + for (unsigned i = 0; i < ranges.size(); ++i) { + const PageRange& range = ranges[i]; + // Ranges are inclusive. + for (int i = range.from; i <= range.to; ++i) { + pages.insert(i); + } + } + return std::vector<int>(pages.begin(), pages.end()); +} + +} // namespace printing diff --git a/chromium/printing/page_range.h b/chromium/printing/page_range.h new file mode 100644 index 00000000000..989c33c22f7 --- /dev/null +++ b/chromium/printing/page_range.h @@ -0,0 +1,33 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PAGE_RANGE_H_ +#define PRINTING_PAGE_RANGE_H_ + +#include <vector> + +#include "printing_export.h" + +namespace printing { + +struct PageRange; + +typedef std::vector<PageRange> PageRanges; + +// Print range is inclusive. To select one page, set from == to. +struct PRINTING_EXPORT PageRange { + int from; + int to; + + bool operator==(const PageRange& rhs) const { + return from == rhs.from && to == rhs.to; + } + + // Retrieves the sorted list of unique pages in the page ranges. + static std::vector<int> GetPages(const PageRanges& ranges); +}; + +} // namespace printing + +#endif // PRINTING_PAGE_RANGE_H_ diff --git a/chromium/printing/page_range_unittest.cc b/chromium/printing/page_range_unittest.cc new file mode 100644 index 00000000000..7b0f5779fc2 --- /dev/null +++ b/chromium/printing/page_range_unittest.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/page_range.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PageRangeTest, RangeMerge) { + printing::PageRanges ranges; + printing::PageRange range; + range.from = 1; + range.to = 3; + ranges.push_back(range); + range.from = 10; + range.to = 12; + ranges.push_back(range); + range.from = 2; + range.to = 5; + ranges.push_back(range); + std::vector<int> pages(printing::PageRange::GetPages(ranges)); + ASSERT_EQ(8U, pages.size()); + EXPECT_EQ(1, pages[0]); + EXPECT_EQ(2, pages[1]); + EXPECT_EQ(3, pages[2]); + EXPECT_EQ(4, pages[3]); + EXPECT_EQ(5, pages[4]); + EXPECT_EQ(10, pages[5]); + EXPECT_EQ(11, pages[6]); + EXPECT_EQ(12, pages[7]); +} + +TEST(PageRangeTest, Empty) { + printing::PageRanges ranges; + std::vector<int> pages(printing::PageRange::GetPages(ranges)); + EXPECT_EQ(0U, pages.size()); +} diff --git a/chromium/printing/page_setup.cc b/chromium/printing/page_setup.cc new file mode 100644 index 00000000000..f0ea223adb0 --- /dev/null +++ b/chromium/printing/page_setup.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/page_setup.h" + +#include <algorithm> + +#include "base/logging.h" + +namespace printing { + +PageMargins::PageMargins() + : header(0), + footer(0), + left(0), + right(0), + top(0), + bottom(0) { +} + +void PageMargins::Clear() { + header = 0; + footer = 0; + left = 0; + right = 0; + top = 0; + bottom = 0; +} + +bool PageMargins::Equals(const PageMargins& rhs) const { + return header == rhs.header && + footer == rhs.footer && + left == rhs.left && + top == rhs.top && + right == rhs.right && + bottom == rhs.bottom; +} + +PageSetup::PageSetup() { + Clear(); +} + +PageSetup::~PageSetup() {} + +void PageSetup::Clear() { + physical_size_.SetSize(0, 0); + printable_area_.SetRect(0, 0, 0, 0); + overlay_area_.SetRect(0, 0, 0, 0); + content_area_.SetRect(0, 0, 0, 0); + effective_margins_.Clear(); + text_height_ = 0; + forced_margins_ = false; +} + +bool PageSetup::Equals(const PageSetup& rhs) const { + return physical_size_ == rhs.physical_size_ && + printable_area_ == rhs.printable_area_ && + overlay_area_ == rhs.overlay_area_ && + content_area_ == rhs.content_area_ && + effective_margins_.Equals(rhs.effective_margins_) && + requested_margins_.Equals(rhs.requested_margins_) && + text_height_ == rhs.text_height_; +} + +void PageSetup::Init(const gfx::Size& physical_size, + const gfx::Rect& printable_area, + int text_height) { + DCHECK_LE(printable_area.right(), physical_size.width()); + // I've seen this assert triggers on Canon GP160PF PCL 5e and HP LaserJet 5. + // Since we don't know the dpi here, just disable the check. + // DCHECK_LE(printable_area.bottom(), physical_size.height()); + DCHECK_GE(printable_area.x(), 0); + DCHECK_GE(printable_area.y(), 0); + DCHECK_GE(text_height, 0); + physical_size_ = physical_size; + printable_area_ = printable_area; + text_height_ = text_height; + + SetRequestedMarginsAndCalculateSizes(requested_margins_); +} + +void PageSetup::SetRequestedMargins(const PageMargins& requested_margins) { + forced_margins_ = false; + SetRequestedMarginsAndCalculateSizes(requested_margins); +} + +void PageSetup::ForceRequestedMargins(const PageMargins& requested_margins) { + forced_margins_ = true; + SetRequestedMarginsAndCalculateSizes(requested_margins); +} + +void PageSetup::FlipOrientation() { + if (physical_size_.width() && physical_size_.height()) { + gfx::Size new_size(physical_size_.height(), physical_size_.width()); + int new_y = physical_size_.width() - + (printable_area_.width() + printable_area_.x()); + gfx::Rect new_printable_area(printable_area_.y(), + new_y, + printable_area_.height(), + printable_area_.width()); + Init(new_size, new_printable_area, text_height_); + } +} + +void PageSetup::SetRequestedMarginsAndCalculateSizes( + const PageMargins& requested_margins) { + requested_margins_ = requested_margins; + if (physical_size_.width() && physical_size_.height()) { + if (forced_margins_) + CalculateSizesWithinRect(gfx::Rect(physical_size_), 0); + else + CalculateSizesWithinRect(printable_area_, text_height_); + } +} + +void PageSetup::CalculateSizesWithinRect(const gfx::Rect& bounds, + int text_height) { + // Calculate the effective margins. The tricky part. + effective_margins_.header = std::max(requested_margins_.header, + bounds.y()); + effective_margins_.footer = std::max(requested_margins_.footer, + physical_size_.height() - + bounds.bottom()); + effective_margins_.left = std::max(requested_margins_.left, + bounds.x()); + effective_margins_.top = std::max(std::max(requested_margins_.top, + bounds.y()), + effective_margins_.header + text_height); + effective_margins_.right = std::max(requested_margins_.right, + physical_size_.width() - + bounds.right()); + effective_margins_.bottom = + std::max(std::max(requested_margins_.bottom, + physical_size_.height() - bounds.bottom()), + effective_margins_.footer + text_height); + + // Calculate the overlay area. If the margins are excessive, the overlay_area + // size will be (0, 0). + overlay_area_.set_x(effective_margins_.left); + overlay_area_.set_y(effective_margins_.header); + overlay_area_.set_width(std::max(0, + physical_size_.width() - + effective_margins_.right - + overlay_area_.x())); + overlay_area_.set_height(std::max(0, + physical_size_.height() - + effective_margins_.footer - + overlay_area_.y())); + + // Calculate the content area. If the margins are excessive, the content_area + // size will be (0, 0). + content_area_.set_x(effective_margins_.left); + content_area_.set_y(effective_margins_.top); + content_area_.set_width(std::max(0, + physical_size_.width() - + effective_margins_.right - + content_area_.x())); + content_area_.set_height(std::max(0, + physical_size_.height() - + effective_margins_.bottom - + content_area_.y())); +} + +} // namespace printing diff --git a/chromium/printing/page_setup.h b/chromium/printing/page_setup.h new file mode 100644 index 00000000000..585d48ab8bd --- /dev/null +++ b/chromium/printing/page_setup.h @@ -0,0 +1,103 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PAGE_SETUP_H_ +#define PRINTING_PAGE_SETUP_H_ + +#include "printing/printing_export.h" +#include "ui/gfx/rect.h" + +namespace printing { + +// Margins for a page setup. +class PRINTING_EXPORT PageMargins { + public: + PageMargins(); + + void Clear(); + + // Equality operator. + bool Equals(const PageMargins& rhs) const; + + // Vertical space for the overlay from the top of the sheet. + int header; + // Vertical space for the overlay from the bottom of the sheet. + int footer; + // Margin on each side of the sheet. + int left; + int right; + int top; + int bottom; +}; + +// Settings that define the size and printable areas of a page. Unit is +// unspecified. +class PRINTING_EXPORT PageSetup { + public: + PageSetup(); + ~PageSetup(); + + void Clear(); + + // Equality operator. + bool Equals(const PageSetup& rhs) const; + + void Init(const gfx::Size& physical_size, const gfx::Rect& printable_area, + int text_height); + + // Use |requested_margins| as long as they fall inside the printable area. + void SetRequestedMargins(const PageMargins& requested_margins); + + // Ignore the printable area, and set the margins to |requested_margins|. + void ForceRequestedMargins(const PageMargins& requested_margins); + + // Flips the orientation of the page and recalculates all page areas. + void FlipOrientation(); + + const gfx::Size& physical_size() const { return physical_size_; } + const gfx::Rect& overlay_area() const { return overlay_area_; } + const gfx::Rect& content_area() const { return content_area_; } + const gfx::Rect& printable_area() const { return printable_area_; } + const PageMargins& effective_margins() const { + return effective_margins_; + } + + private: + // Store |requested_margins_| and update page setup values. + void SetRequestedMarginsAndCalculateSizes( + const PageMargins& requested_margins); + + // Calculate overlay_area_, effective_margins_, and content_area_, based on + // a constraint of |bounds| and |text_height|. + void CalculateSizesWithinRect(const gfx::Rect& bounds, int text_height); + + // Physical size of the page, including non-printable margins. + gfx::Size physical_size_; + + // The printable area as specified by the printer driver. We can't get + // larger than this. + gfx::Rect printable_area_; + + // The printable area for headers and footers. + gfx::Rect overlay_area_; + + // The printable area as selected by the user's margins. + gfx::Rect content_area_; + + // Effective margins. + PageMargins effective_margins_; + + // Requested margins. + PageMargins requested_margins_; + + // True when |effective_margins_| respects |printable_area_| else false. + bool forced_margins_; + + // Space that must be kept free for the overlays. + int text_height_; +}; + +} // namespace printing + +#endif // PRINTING_PAGE_SETUP_H_ diff --git a/chromium/printing/page_setup_unittest.cc b/chromium/printing/page_setup_unittest.cc new file mode 100644 index 00000000000..4915a6c0ddb --- /dev/null +++ b/chromium/printing/page_setup_unittest.cc @@ -0,0 +1,274 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/page_setup.h" + +#include <stdlib.h> +#include <time.h> + +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PageSetupTest, Random) { + time_t seed = time(NULL); + int kMax = 10; + srand(static_cast<unsigned>(seed)); + + // Margins. + printing::PageMargins margins; + margins.header = rand() % kMax; + margins.footer = rand() % kMax; + margins.left = rand() % kMax; + margins.top = rand() % kMax; + margins.right = rand() % kMax; + margins.bottom = rand() % kMax; + int kTextHeight = rand() % kMax; + + // Page description. + gfx::Size page_size(100 + rand() % kMax, 200 + rand() % kMax); + gfx::Rect printable_area(rand() % kMax, rand() % kMax, 0, 0); + printable_area.set_width(page_size.width() - (rand() % kMax) - + printable_area.x()); + printable_area.set_height(page_size.height() - (rand() % kMax) - + printable_area.y()); + + // Make the calculations. + printing::PageSetup setup; + setup.SetRequestedMargins(margins); + setup.Init(page_size, printable_area, kTextHeight); + + // Calculate the effective margins. + printing::PageMargins effective_margins; + effective_margins.header = std::max(margins.header, printable_area.y()); + effective_margins.left = std::max(margins.left, printable_area.x()); + effective_margins.top = std::max(margins.top, + effective_margins.header + kTextHeight); + effective_margins.footer = std::max(margins.footer, + page_size.height() - + printable_area.bottom()); + effective_margins.right = std::max(margins.right, + page_size.width() - + printable_area.right()); + effective_margins.bottom = std::max(margins.bottom, + effective_margins.footer + kTextHeight); + + // Calculate the overlay area. + gfx::Rect overlay_area(effective_margins.left, effective_margins.header, + page_size.width() - effective_margins.right - + effective_margins.left, + page_size.height() - effective_margins.footer - + effective_margins.header); + + // Calculate the content area. + gfx::Rect content_area(overlay_area.x(), + effective_margins.top, + overlay_area.width(), + page_size.height() - effective_margins.bottom - + effective_margins.top); + + // Test values. + EXPECT_EQ(page_size, setup.physical_size()) << seed << " " << + page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(overlay_area, setup.overlay_area()) << seed << " " << + page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(content_area, setup.content_area()) << seed << " " << + page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + + EXPECT_EQ(effective_margins.header, setup.effective_margins().header) << + seed << " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(effective_margins.footer, setup.effective_margins().footer) << + seed << " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(effective_margins.left, setup.effective_margins().left) << seed << + " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(effective_margins.top, setup.effective_margins().top) << seed << + " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(effective_margins.right, setup.effective_margins().right) << seed << + " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(effective_margins.bottom, setup.effective_margins().bottom) << + seed << " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; +} + +TEST(PageSetupTest, HardCoded) { + // Margins. + printing::PageMargins margins; + margins.header = 2; + margins.footer = 2; + margins.left = 4; + margins.top = 4; + margins.right = 4; + margins.bottom = 4; + int kTextHeight = 3; + + // Page description. + gfx::Size page_size(100, 100); + gfx::Rect printable_area(3, 3, 94, 94); + + // Make the calculations. + printing::PageSetup setup; + setup.SetRequestedMargins(margins); + setup.Init(page_size, printable_area, kTextHeight); + + // Calculate the effective margins. + printing::PageMargins effective_margins; + effective_margins.header = 3; + effective_margins.left = 4; + effective_margins.top = 6; + effective_margins.footer = 3; + effective_margins.right = 4; + effective_margins.bottom = 6; + + // Calculate the overlay area. + gfx::Rect overlay_area(4, 3, 92, 94); + + // Calculate the content area. + gfx::Rect content_area(4, 6, 92, 88); + + // Test values. + EXPECT_EQ(page_size, setup.physical_size()) << " " << page_size.ToString() << + " " << printable_area.ToString() << " " << kTextHeight; + EXPECT_EQ(overlay_area, setup.overlay_area()) << " " << + page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(content_area, setup.content_area()) << " " << + page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + + EXPECT_EQ(effective_margins.header, setup.effective_margins().header) << + " " << page_size.ToString() << " " << + printable_area.ToString() << " " << kTextHeight; + EXPECT_EQ(effective_margins.footer, setup.effective_margins().footer) << + " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(effective_margins.left, setup.effective_margins().left) << + " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(effective_margins.top, setup.effective_margins().top) << + " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(effective_margins.right, setup.effective_margins().right) << + " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; + EXPECT_EQ(effective_margins.bottom, setup.effective_margins().bottom) << + " " << page_size.ToString() << " " << printable_area.ToString() << + " " << kTextHeight; +} + +TEST(PageSetupTest, OutOfRangeMargins) { + printing::PageMargins margins; + margins.header = 0; + margins.footer = 0; + margins.left = -10; + margins.top = -11; + margins.right = -12; + margins.bottom = -13; + + gfx::Size page_size(100, 100); + gfx::Rect printable_area(1, 2, 96, 94); + + // Make the calculations. + printing::PageSetup setup; + setup.SetRequestedMargins(margins); + setup.Init(page_size, printable_area, 0); + + EXPECT_EQ(setup.effective_margins().left, 1); + EXPECT_EQ(setup.effective_margins().top, 2); + EXPECT_EQ(setup.effective_margins().right, 3); + EXPECT_EQ(setup.effective_margins().bottom, 4); + + setup.ForceRequestedMargins(margins); + EXPECT_EQ(setup.effective_margins().left, 0); + EXPECT_EQ(setup.effective_margins().top, 0); + EXPECT_EQ(setup.effective_margins().right, 0); + EXPECT_EQ(setup.effective_margins().bottom, 0); +} + +TEST(PageSetupTest, FlipOrientation) { + // Margins. + printing::PageMargins margins; + margins.header = 2; + margins.footer = 3; + margins.left = 4; + margins.top = 14; + margins.right = 6; + margins.bottom = 7; + int kTextHeight = 5; + + // Page description. + gfx::Size page_size(100, 70); + gfx::Rect printable_area(8, 9, 92, 50); + + // Make the calculations. + printing::PageSetup setup; + setup.SetRequestedMargins(margins); + setup.Init(page_size, printable_area, kTextHeight); + + gfx::Rect overlay_area(8, 9, 86, 50); + gfx::Rect content_area(8, 14, 86, 40); + + EXPECT_EQ(page_size, setup.physical_size()); + EXPECT_EQ(overlay_area, setup.overlay_area()); + EXPECT_EQ(content_area, setup.content_area()); + + EXPECT_EQ(setup.effective_margins().left, 8); + EXPECT_EQ(setup.effective_margins().top, 14); + EXPECT_EQ(setup.effective_margins().right, 6); + EXPECT_EQ(setup.effective_margins().bottom, 16); + + // Flip the orientation + setup.FlipOrientation(); + + // Expected values. + gfx::Size flipped_page_size(70, 100); + gfx::Rect flipped_printable_area(9, 0, 50, 92); + gfx::Rect flipped_overlay_area(9, 2, 50, 90); + gfx::Rect flipped_content_area(9, 14, 50, 73); + + // Test values. + EXPECT_EQ(flipped_page_size, setup.physical_size()); + EXPECT_EQ(flipped_overlay_area, setup.overlay_area()); + EXPECT_EQ(flipped_content_area, setup.content_area()); + EXPECT_EQ(flipped_printable_area, setup.printable_area()); + + // Margin values are updated as per the flipped values. + EXPECT_EQ(setup.effective_margins().left, 9); + EXPECT_EQ(setup.effective_margins().top, 14); + EXPECT_EQ(setup.effective_margins().right, 11); + EXPECT_EQ(setup.effective_margins().bottom, 13); + + // Force requested margins and flip the orientation. + setup.Init(page_size, printable_area, kTextHeight); + setup.ForceRequestedMargins(margins); + EXPECT_EQ(setup.effective_margins().left, 4); + EXPECT_EQ(setup.effective_margins().top, 14); + EXPECT_EQ(setup.effective_margins().right, 6); + EXPECT_EQ(setup.effective_margins().bottom, 7); + + // Flip the orientation + setup.FlipOrientation(); + + // Expected values. + gfx::Rect new_printable_area(9, 0, 50, 92); + gfx::Rect new_overlay_area(4, 2, 60, 95); + gfx::Rect new_content_area(4, 14, 60, 79); + + // Test values. + EXPECT_EQ(flipped_page_size, setup.physical_size()); + EXPECT_EQ(new_overlay_area, setup.overlay_area()); + EXPECT_EQ(new_content_area, setup.content_area()); + EXPECT_EQ(new_printable_area, setup.printable_area()); + + // Margins values are changed respectively. + EXPECT_EQ(setup.effective_margins().left, 4); + EXPECT_EQ(setup.effective_margins().top, 14); + EXPECT_EQ(setup.effective_margins().right, 6); + EXPECT_EQ(setup.effective_margins().bottom, 7); +} diff --git a/chromium/printing/page_size_margins.cc b/chromium/printing/page_size_margins.cc new file mode 100644 index 00000000000..a28c6ada3c8 --- /dev/null +++ b/chromium/printing/page_size_margins.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/page_size_margins.h" + +#include "base/logging.h" +#include "base/values.h" +#include "printing/print_job_constants.h" + +namespace printing { + +void GetCustomMarginsFromJobSettings(const base::DictionaryValue& settings, + PageSizeMargins* page_size_margins) { + const base::DictionaryValue* custom_margins; + if (!settings.GetDictionary(kSettingMarginsCustom, &custom_margins) || + !custom_margins->GetDouble(kSettingMarginTop, + &page_size_margins->margin_top) || + !custom_margins->GetDouble(kSettingMarginBottom, + &page_size_margins->margin_bottom) || + !custom_margins->GetDouble(kSettingMarginLeft, + &page_size_margins->margin_left) || + !custom_margins->GetDouble(kSettingMarginRight, + &page_size_margins->margin_right)) { + NOTREACHED(); + } +} + +} // namespace printing diff --git a/chromium/printing/page_size_margins.h b/chromium/printing/page_size_margins.h new file mode 100644 index 00000000000..b6913724a90 --- /dev/null +++ b/chromium/printing/page_size_margins.h @@ -0,0 +1,32 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PAGE_SIZE_MARGINS_H_ +#define PRINTING_PAGE_SIZE_MARGINS_H_ + +#include "printing/printing_export.h" + +namespace base { +class DictionaryValue; +} + +namespace printing { + +// Struct that holds margin and content area sizes of a page. Units are +// arbitrary and can be chosen by the programmer. +struct PageSizeMargins { + double content_width; + double content_height; + double margin_top; + double margin_right; + double margin_bottom; + double margin_left; +}; + +PRINTING_EXPORT void GetCustomMarginsFromJobSettings( + const base::DictionaryValue& settings, PageSizeMargins* page_size_margins); + +} // namespace printing + +#endif // PRINTING_PAGE_SIZE_MARGINS_H_ diff --git a/chromium/printing/pdf_metafile_cg_mac.cc b/chromium/printing/pdf_metafile_cg_mac.cc new file mode 100644 index 00000000000..cd7c5c7e27b --- /dev/null +++ b/chromium/printing/pdf_metafile_cg_mac.cc @@ -0,0 +1,319 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/pdf_metafile_cg_mac.h" + +#include <algorithm> + +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/strings/sys_string_conversions.h" +#include "base/threading/thread_local.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +using base::ScopedCFTypeRef; + +namespace { + +// What is up with this ugly hack? <http://crbug.com/64641>, that's what. +// The bug: Printing certain PDFs crashes. The cause: When printing, the +// renderer process assembles pages one at a time, in PDF format, to send to the +// browser process. When printing a PDF, the PDF plugin returns output in PDF +// format. There is a bug in 10.5 and 10.6 (<rdar://9018916>, +// <http://www.openradar.me/9018916>) where reference counting is broken when +// drawing certain PDFs into PDF contexts. So at the high-level, a PdfMetafileCg +// is used to hold the destination context, and then about five layers down on +// the callstack, a PdfMetafileCg is used to hold the source PDF. If the source +// PDF is drawn into the destination PDF context and then released, accessing +// the destination PDF context will crash. So the outermost instantiation of +// PdfMetafileCg creates a pool for deeper instantiations to dump their used +// PDFs into rather than releasing them. When the top-level PDF is closed, then +// it's safe to clear the pool. A thread local is used to allow this to work in +// single-process mode. TODO(avi): This Apple bug appears fixed in 10.7; when +// 10.7 is the minimum required version for Chromium, remove this hack. + +base::LazyInstance<base::ThreadLocalPointer<struct __CFSet> >::Leaky + thread_pdf_docs = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace printing { + +PdfMetafileCg::PdfMetafileCg() + : page_is_open_(false), + thread_pdf_docs_owned_(false) { + if (!thread_pdf_docs.Pointer()->Get() && + base::mac::IsOSSnowLeopard()) { + thread_pdf_docs_owned_ = true; + thread_pdf_docs.Pointer()->Set( + CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks)); + } +} + +PdfMetafileCg::~PdfMetafileCg() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (pdf_doc_ && thread_pdf_docs.Pointer()->Get()) { + // Transfer ownership to the pool. + CFSetAddValue(thread_pdf_docs.Pointer()->Get(), pdf_doc_); + } + + if (thread_pdf_docs_owned_) { + CFRelease(thread_pdf_docs.Pointer()->Get()); + thread_pdf_docs.Pointer()->Set(NULL); + } +} + +bool PdfMetafileCg::Init() { + // Ensure that Init hasn't already been called. + DCHECK(!context_.get()); + DCHECK(!pdf_data_.get()); + + pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, 0)); + if (!pdf_data_.get()) { + LOG(ERROR) << "Failed to create pdf data for metafile"; + return false; + } + ScopedCFTypeRef<CGDataConsumerRef> pdf_consumer( + CGDataConsumerCreateWithCFData(pdf_data_)); + if (!pdf_consumer.get()) { + LOG(ERROR) << "Failed to create data consumer for metafile"; + pdf_data_.reset(NULL); + return false; + } + context_.reset(CGPDFContextCreate(pdf_consumer, NULL, NULL)); + if (!context_.get()) { + LOG(ERROR) << "Failed to create pdf context for metafile"; + pdf_data_.reset(NULL); + } + + return true; +} + +bool PdfMetafileCg::InitFromData(const void* src_buffer, + uint32 src_buffer_size) { + DCHECK(!context_.get()); + DCHECK(!pdf_data_.get()); + + if (!src_buffer || src_buffer_size == 0) { + return false; + } + + pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, src_buffer_size)); + CFDataAppendBytes(pdf_data_, static_cast<const UInt8*>(src_buffer), + src_buffer_size); + + return true; +} + +SkDevice* PdfMetafileCg::StartPageForVectorCanvas( + const gfx::Size& page_size, const gfx::Rect& content_area, + const float& scale_factor) { + NOTIMPLEMENTED(); + return NULL; +} + +bool PdfMetafileCg::StartPage(const gfx::Size& page_size, + const gfx::Rect& content_area, + const float& scale_factor) { + DCHECK(context_.get()); + DCHECK(!page_is_open_); + + double height = page_size.height(); + double width = page_size.width(); + + CGRect bounds = CGRectMake(0, 0, width, height); + CGContextBeginPage(context_, &bounds); + page_is_open_ = true; + CGContextSaveGState(context_); + + // Move to the context origin. + CGContextTranslateCTM(context_, content_area.x(), -content_area.y()); + + // Flip the context. + CGContextTranslateCTM(context_, 0, height); + CGContextScaleCTM(context_, scale_factor, -scale_factor); + + return context_.get() != NULL; +} + +bool PdfMetafileCg::FinishPage() { + DCHECK(context_.get()); + DCHECK(page_is_open_); + + CGContextRestoreGState(context_); + CGContextEndPage(context_); + page_is_open_ = false; + return true; +} + +bool PdfMetafileCg::FinishDocument() { + DCHECK(context_.get()); + DCHECK(!page_is_open_); + +#ifndef NDEBUG + // Check that the context will be torn down properly; if it's not, pdf_data_ + // will be incomplete and generate invalid PDF files/documents. + if (context_.get()) { + CFIndex extra_retain_count = CFGetRetainCount(context_.get()) - 1; + if (extra_retain_count > 0) { + LOG(ERROR) << "Metafile context has " << extra_retain_count + << " extra retain(s) on Close"; + } + } +#endif + CGPDFContextClose(context_.get()); + context_.reset(NULL); + return true; +} + +bool PdfMetafileCg::RenderPage(unsigned int page_number, + CGContextRef context, + const CGRect rect, + const MacRenderPageParams& params) const { + CGPDFDocumentRef pdf_doc = GetPDFDocument(); + if (!pdf_doc) { + LOG(ERROR) << "Unable to create PDF document from data"; + return false; + } + CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number); + CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox); + float scaling_factor = 1.0; + const bool source_is_landscape = + (source_rect.size.width > source_rect.size.height); + const bool dest_is_landscape = (rect.size.width > rect.size.height); + const bool rotate = + params.autorotate ? (source_is_landscape != dest_is_landscape) : false; + const float source_width = + rotate ? source_rect.size.height : source_rect.size.width; + const float source_height = + rotate ? source_rect.size.width : source_rect.size.height; + + // See if we need to scale the output. + const bool scaling_needed = + (params.shrink_to_fit && ((source_width > rect.size.width) || + (source_height > rect.size.height))) || + (params.stretch_to_fit && ((source_width < rect.size.width) && + (source_height < rect.size.height))); + if (scaling_needed) { + float x_scaling_factor = rect.size.width / source_width; + float y_scaling_factor = rect.size.height / source_height; + scaling_factor = std::min(x_scaling_factor, y_scaling_factor); + } + // Some PDFs have a non-zero origin. Need to take that into account and align + // the PDF to the origin. + const float x_origin_offset = -1 * source_rect.origin.x; + const float y_origin_offset = -1 * source_rect.origin.y; + + // If the PDF needs to be centered, calculate the offsets here. + float x_offset = params.center_horizontally ? + ((rect.size.width - (source_width * scaling_factor)) / 2) : 0; + if (rotate) + x_offset = -x_offset; + + float y_offset = params.center_vertically ? + ((rect.size.height - (source_height * scaling_factor)) / 2) : 0; + + CGContextSaveGState(context); + + // The transform operations specified here gets applied in reverse order. + // i.e. the origin offset translation happens first. + // Origin is at bottom-left. + CGContextTranslateCTM(context, x_offset, y_offset); + if (rotate) { + // After rotating by 90 degrees with the axis at the origin, the page + // content is now "off screen". Shift it right to move it back on screen. + CGContextTranslateCTM(context, rect.size.width, 0); + // Rotates counter-clockwise by 90 degrees. + CGContextRotateCTM(context, M_PI_2); + } + CGContextScaleCTM(context, scaling_factor, scaling_factor); + CGContextTranslateCTM(context, x_origin_offset, y_origin_offset); + + CGContextDrawPDFPage(context, pdf_page); + CGContextRestoreGState(context); + + return true; +} + +unsigned int PdfMetafileCg::GetPageCount() const { + CGPDFDocumentRef pdf_doc = GetPDFDocument(); + return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0; +} + +gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const { + CGPDFDocumentRef pdf_doc = GetPDFDocument(); + if (!pdf_doc) { + LOG(ERROR) << "Unable to create PDF document from data"; + return gfx::Rect(); + } + if (page_number > GetPageCount()) { + LOG(ERROR) << "Invalid page number: " << page_number; + return gfx::Rect(); + } + CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number); + CGRect page_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFMediaBox); + return gfx::Rect(page_rect); +} + +uint32 PdfMetafileCg::GetDataSize() const { + // PDF data is only valid/complete once the context is released. + DCHECK(!context_); + + if (!pdf_data_) + return 0; + return static_cast<uint32>(CFDataGetLength(pdf_data_)); +} + +bool PdfMetafileCg::GetData(void* dst_buffer, uint32 dst_buffer_size) const { + // PDF data is only valid/complete once the context is released. + DCHECK(!context_); + DCHECK(pdf_data_); + DCHECK(dst_buffer); + DCHECK_GT(dst_buffer_size, 0U); + + uint32 data_size = GetDataSize(); + if (dst_buffer_size > data_size) { + return false; + } + + CFDataGetBytes(pdf_data_, CFRangeMake(0, dst_buffer_size), + static_cast<UInt8*>(dst_buffer)); + return true; +} + +bool PdfMetafileCg::SaveTo(const base::FilePath& file_path) const { + DCHECK(pdf_data_.get()); + DCHECK(!context_.get()); + + std::string path_string = file_path.value(); + ScopedCFTypeRef<CFURLRef> path_url(CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, reinterpret_cast<const UInt8*>(path_string.c_str()), + path_string.length(), false)); + SInt32 error_code; + CFURLWriteDataAndPropertiesToResource(path_url, pdf_data_, NULL, &error_code); + return error_code == 0; +} + +CGContextRef PdfMetafileCg::context() const { + return context_.get(); +} + +CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const { + // Make sure that we have data, and that it's not being modified any more. + DCHECK(pdf_data_.get()); + DCHECK(!context_.get()); + + if (!pdf_doc_.get()) { + ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider( + CGDataProviderCreateWithCFData(pdf_data_)); + pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider)); + } + return pdf_doc_.get(); +} + +} // namespace printing diff --git a/chromium/printing/pdf_metafile_cg_mac.h b/chromium/printing/pdf_metafile_cg_mac.h new file mode 100644 index 00000000000..77f703150c6 --- /dev/null +++ b/chromium/printing/pdf_metafile_cg_mac.h @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PDF_METAFILE_CG_MAC_H_ +#define PRINTING_PDF_METAFILE_CG_MAC_H_ + +#include <ApplicationServices/ApplicationServices.h> +#include <CoreFoundation/CoreFoundation.h> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/threading/thread_checker.h" +#include "printing/metafile.h" + +namespace base { +class FilePath; +} + +namespace gfx { +class Rect; +class Size; +} + +namespace printing { + +// This class creates a graphics context that renders into a PDF data stream. +class PRINTING_EXPORT PdfMetafileCg : public Metafile { + public: + PdfMetafileCg(); + virtual ~PdfMetafileCg(); + + // Metafile methods. + virtual bool Init() OVERRIDE; + virtual bool InitFromData(const void* src_buffer, + uint32 src_buffer_size) OVERRIDE; + + // Not implemented on mac. + virtual SkDevice* StartPageForVectorCanvas( + const gfx::Size& page_size, const gfx::Rect& content_area, + const float& scale_factor) OVERRIDE; + virtual bool StartPage(const gfx::Size& page_size, + const gfx::Rect& content_area, + const float& scale_factor) OVERRIDE; + virtual bool FinishPage() OVERRIDE; + virtual bool FinishDocument() OVERRIDE; + + virtual uint32 GetDataSize() const OVERRIDE; + virtual bool GetData(void* dst_buffer, uint32 dst_buffer_size) const OVERRIDE; + + // For testing purposes only. + virtual bool SaveTo(const base::FilePath& file_path) const OVERRIDE; + + virtual gfx::Rect GetPageBounds(unsigned int page_number) const OVERRIDE; + virtual unsigned int GetPageCount() const OVERRIDE; + + // Note: The returned context *must not be retained* past Close(). If it is, + // the data returned from GetData will not be valid PDF data. + virtual CGContextRef context() const OVERRIDE; + + virtual bool RenderPage(unsigned int page_number, + gfx::NativeDrawingContext context, + const CGRect rect, + const MacRenderPageParams& params) const OVERRIDE; + + private: + // Returns a CGPDFDocumentRef version of pdf_data_. + CGPDFDocumentRef GetPDFDocument() const; + + base::ThreadChecker thread_checker_; + + // Context for rendering to the pdf. + base::ScopedCFTypeRef<CGContextRef> context_; + + // PDF backing store. + base::ScopedCFTypeRef<CFMutableDataRef> pdf_data_; + + // Lazily-created CGPDFDocument representation of pdf_data_. + mutable base::ScopedCFTypeRef<CGPDFDocumentRef> pdf_doc_; + + // Whether or not a page is currently open. + bool page_is_open_; + + // Whether this instantiation of the PdfMetafileCg owns the thread_pdf_docs. + bool thread_pdf_docs_owned_; + + DISALLOW_COPY_AND_ASSIGN(PdfMetafileCg); +}; + +} // namespace printing + +#endif // PRINTING_PDF_METAFILE_CG_MAC_H_ diff --git a/chromium/printing/pdf_metafile_cg_mac_unittest.cc b/chromium/printing/pdf_metafile_cg_mac_unittest.cc new file mode 100644 index 00000000000..1a213aea83d --- /dev/null +++ b/chromium/printing/pdf_metafile_cg_mac_unittest.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/pdf_metafile_cg_mac.h" + +#import <ApplicationServices/ApplicationServices.h> + +#include <string> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" + +namespace printing { + +TEST(PdfMetafileCgTest, Pdf) { + // Test in-renderer constructor. + PdfMetafileCg pdf; + EXPECT_TRUE(pdf.Init()); + EXPECT_TRUE(pdf.context() != NULL); + + // Render page 1. + gfx::Rect rect_1(10, 10, 520, 700); + gfx::Size size_1(540, 720); + pdf.StartPage(size_1, rect_1, 1.25); + pdf.FinishPage(); + + // Render page 2. + gfx::Rect rect_2(10, 10, 520, 700); + gfx::Size size_2(720, 540); + pdf.StartPage(size_2, rect_2, 2.0); + pdf.FinishPage(); + + pdf.FinishDocument(); + + // Check data size. + uint32 size = pdf.GetDataSize(); + EXPECT_GT(size, 0U); + + // Get resulting data. + std::vector<char> buffer(size, 0); + pdf.GetData(&buffer.front(), size); + + // Test browser-side constructor. + PdfMetafileCg pdf2; + EXPECT_TRUE(pdf2.InitFromData(&buffer.front(), size)); + + // Get the first 4 characters from pdf2. + std::vector<char> buffer2(4, 0); + pdf2.GetData(&buffer2.front(), 4); + + // Test that the header begins with "%PDF". + std::string header(&buffer2.front(), 4); + EXPECT_EQ(0U, header.find("%PDF", 0)); + + // Test that the PDF is correctly reconstructed. + EXPECT_EQ(2U, pdf2.GetPageCount()); + gfx::Size page_size = pdf2.GetPageBounds(1).size(); + EXPECT_EQ(540, page_size.width()); + EXPECT_EQ(720, page_size.height()); + page_size = pdf2.GetPageBounds(2).size(); + EXPECT_EQ(720, page_size.width()); + EXPECT_EQ(540, page_size.height()); +} + +} // namespace printing diff --git a/chromium/printing/pdf_metafile_skia.cc b/chromium/printing/pdf_metafile_skia.cc new file mode 100644 index 00000000000..3326a1d6d86 --- /dev/null +++ b/chromium/printing/pdf_metafile_skia.cc @@ -0,0 +1,248 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/pdf_metafile_skia.h" + +#include "base/containers/hash_tables.h" +#include "base/file_descriptor_posix.h" +#include "base/file_util.h" +#include "base/metrics/histogram.h" +#include "base/posix/eintr_wrapper.h" +#include "base/safe_numerics.h" +#include "skia/ext/refptr.h" +#include "skia/ext/vector_platform_device_skia.h" +#include "third_party/skia/include/core/SkData.h" +#include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/skia/include/core/SkScalar.h" +#include "third_party/skia/include/core/SkStream.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "third_party/skia/include/pdf/SkPDFDevice.h" +#include "third_party/skia/include/pdf/SkPDFDocument.h" +#include "ui/gfx/point.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +#if defined(OS_MACOSX) +#include "printing/pdf_metafile_cg_mac.h" +#endif + +namespace printing { + +struct PdfMetafileSkiaData { + skia::RefPtr<SkPDFDevice> current_page_; + SkPDFDocument pdf_doc_; + SkDynamicMemoryWStream pdf_stream_; +#if defined(OS_MACOSX) + PdfMetafileCg pdf_cg_; +#endif +}; + +PdfMetafileSkia::~PdfMetafileSkia() {} + +bool PdfMetafileSkia::Init() { + return true; +} +bool PdfMetafileSkia::InitFromData(const void* src_buffer, + uint32 src_buffer_size) { + return data_->pdf_stream_.write(src_buffer, src_buffer_size); +} + +SkDevice* PdfMetafileSkia::StartPageForVectorCanvas( + const gfx::Size& page_size, const gfx::Rect& content_area, + const float& scale_factor) { + DCHECK(!page_outstanding_); + page_outstanding_ = true; + + // Adjust for the margins and apply the scale factor. + SkMatrix transform; + transform.setTranslate(SkIntToScalar(content_area.x()), + SkIntToScalar(content_area.y())); + transform.preScale(SkFloatToScalar(scale_factor), + SkFloatToScalar(scale_factor)); + + SkISize pdf_page_size = SkISize::Make(page_size.width(), page_size.height()); + SkISize pdf_content_size = + SkISize::Make(content_area.width(), content_area.height()); + skia::RefPtr<SkPDFDevice> pdf_device = + skia::AdoptRef(new skia::VectorPlatformDeviceSkia( + pdf_page_size, pdf_content_size, transform)); + data_->current_page_ = pdf_device; + return pdf_device.get(); +} + +bool PdfMetafileSkia::StartPage(const gfx::Size& page_size, + const gfx::Rect& content_area, + const float& scale_factor) { + NOTREACHED(); + return false; +} + +bool PdfMetafileSkia::FinishPage() { + DCHECK(data_->current_page_.get()); + + data_->pdf_doc_.appendPage(data_->current_page_.get()); + page_outstanding_ = false; + return true; +} + +bool PdfMetafileSkia::FinishDocument() { + // Don't do anything if we've already set the data in InitFromData. + if (data_->pdf_stream_.getOffset()) + return true; + + if (page_outstanding_) + FinishPage(); + + data_->current_page_.clear(); + + int font_counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]; + data_->pdf_doc_.getCountOfFontTypes(font_counts); + for (int type = 0; + type <= SkAdvancedTypefaceMetrics::kNotEmbeddable_Font; + type++) { + for (int count = 0; count < font_counts[type]; count++) { + UMA_HISTOGRAM_ENUMERATION( + "PrintPreview.FontType", type, + SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1); + } + } + + return data_->pdf_doc_.emitPDF(&data_->pdf_stream_); +} + +uint32 PdfMetafileSkia::GetDataSize() const { + return base::checked_numeric_cast<uint32>(data_->pdf_stream_.getOffset()); +} + +bool PdfMetafileSkia::GetData(void* dst_buffer, + uint32 dst_buffer_size) const { + if (dst_buffer_size < GetDataSize()) + return false; + + SkAutoDataUnref data(data_->pdf_stream_.copyToData()); + memcpy(dst_buffer, data->bytes(), dst_buffer_size); + return true; +} + +bool PdfMetafileSkia::SaveTo(const base::FilePath& file_path) const { + DCHECK_GT(data_->pdf_stream_.getOffset(), 0U); + SkAutoDataUnref data(data_->pdf_stream_.copyToData()); + if (file_util::WriteFile(file_path, + reinterpret_cast<const char*>(data->data()), + GetDataSize()) != static_cast<int>(GetDataSize())) { + DLOG(ERROR) << "Failed to save file " << file_path.value().c_str(); + return false; + } + return true; +} + +gfx::Rect PdfMetafileSkia::GetPageBounds(unsigned int page_number) const { + // TODO(vandebo) add a method to get the page size for a given page to + // SkPDFDocument. + NOTIMPLEMENTED(); + return gfx::Rect(); +} + +unsigned int PdfMetafileSkia::GetPageCount() const { + // TODO(vandebo) add a method to get the number of pages to SkPDFDocument. + NOTIMPLEMENTED(); + return 0; +} + +gfx::NativeDrawingContext PdfMetafileSkia::context() const { + NOTREACHED(); + return NULL; +} + +#if defined(OS_WIN) +bool PdfMetafileSkia::Playback(gfx::NativeDrawingContext hdc, + const RECT* rect) const { + NOTREACHED(); + return false; +} + +bool PdfMetafileSkia::SafePlayback(gfx::NativeDrawingContext hdc) const { + NOTREACHED(); + return false; +} + +HENHMETAFILE PdfMetafileSkia::emf() const { + NOTREACHED(); + return NULL; +} +#elif defined(OS_MACOSX) +/* TODO(caryclark): The set up of PluginInstance::PrintPDFOutput may result in + rasterized output. Even if that flow uses PdfMetafileCg::RenderPage, + the drawing of the PDF into the canvas may result in a rasterized output. + PDFMetafileSkia::RenderPage should be not implemented as shown and instead + should do something like the following CL in PluginInstance::PrintPDFOutput: +http://codereview.chromium.org/7200040/diff/1/webkit/plugins/ppapi/ppapi_plugin_instance.cc +*/ +bool PdfMetafileSkia::RenderPage(unsigned int page_number, + CGContextRef context, + const CGRect rect, + const MacRenderPageParams& params) const { + DCHECK_GT(data_->pdf_stream_.getOffset(), 0U); + if (data_->pdf_cg_.GetDataSize() == 0) { + SkAutoDataUnref data(data_->pdf_stream_.copyToData()); + data_->pdf_cg_.InitFromData(data->bytes(), data->size()); + } + return data_->pdf_cg_.RenderPage(page_number, context, rect, params); +} +#endif + +#if defined(OS_CHROMEOS) +bool PdfMetafileSkia::SaveToFD(const base::FileDescriptor& fd) const { + DCHECK_GT(data_->pdf_stream_.getOffset(), 0U); + + if (fd.fd < 0) { + DLOG(ERROR) << "Invalid file descriptor!"; + return false; + } + + bool result = true; + SkAutoDataUnref data(data_->pdf_stream_.copyToData()); + if (file_util::WriteFileDescriptor(fd.fd, + reinterpret_cast<const char*>(data->data()), + GetDataSize()) != + static_cast<int>(GetDataSize())) { + DLOG(ERROR) << "Failed to save file with fd " << fd.fd; + result = false; + } + + if (fd.auto_close) { + if (HANDLE_EINTR(close(fd.fd)) < 0) { + DPLOG(WARNING) << "close"; + result = false; + } + } + return result; +} +#endif + +PdfMetafileSkia::PdfMetafileSkia() + : data_(new PdfMetafileSkiaData), + page_outstanding_(false) { +} + +PdfMetafileSkia* PdfMetafileSkia::GetMetafileForCurrentPage() { + SkPDFDocument pdf_doc(SkPDFDocument::kDraftMode_Flags); + SkDynamicMemoryWStream pdf_stream; + if (!pdf_doc.appendPage(data_->current_page_.get())) + return NULL; + + if (!pdf_doc.emitPDF(&pdf_stream)) + return NULL; + + SkAutoDataUnref data(pdf_stream.copyToData()); + if (data->size() == 0) + return NULL; + + PdfMetafileSkia* metafile = new PdfMetafileSkia; + metafile->InitFromData(data->bytes(), + base::checked_numeric_cast<uint32>(data->size())); + return metafile; +} + +} // namespace printing diff --git a/chromium/printing/pdf_metafile_skia.h b/chromium/printing/pdf_metafile_skia.h new file mode 100644 index 00000000000..33be6d2e0f4 --- /dev/null +++ b/chromium/printing/pdf_metafile_skia.h @@ -0,0 +1,84 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PDF_METAFILE_SKIA_H_ +#define PRINTING_PDF_METAFILE_SKIA_H_ + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "build/build_config.h" +#include "printing/metafile.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +namespace printing { + +struct PdfMetafileSkiaData; + +// This class uses Skia graphics library to generate a PDF document. +class PRINTING_EXPORT PdfMetafileSkia : public Metafile { + public: + PdfMetafileSkia(); + virtual ~PdfMetafileSkia(); + + // Metafile methods. + virtual bool Init() OVERRIDE; + virtual bool InitFromData(const void* src_buffer, + uint32 src_buffer_size) OVERRIDE; + + virtual SkDevice* StartPageForVectorCanvas( + const gfx::Size& page_size, + const gfx::Rect& content_area, + const float& scale_factor) OVERRIDE; + + virtual bool StartPage(const gfx::Size& page_size, + const gfx::Rect& content_area, + const float& scale_factor) OVERRIDE; + virtual bool FinishPage() OVERRIDE; + virtual bool FinishDocument() OVERRIDE; + + virtual uint32 GetDataSize() const OVERRIDE; + virtual bool GetData(void* dst_buffer, uint32 dst_buffer_size) const OVERRIDE; + + virtual bool SaveTo(const base::FilePath& file_path) const OVERRIDE; + + virtual gfx::Rect GetPageBounds(unsigned int page_number) const OVERRIDE; + virtual unsigned int GetPageCount() const OVERRIDE; + + virtual gfx::NativeDrawingContext context() const OVERRIDE; + +#if defined(OS_WIN) + virtual bool Playback(gfx::NativeDrawingContext hdc, + const RECT* rect) const OVERRIDE; + virtual bool SafePlayback(gfx::NativeDrawingContext hdc) const OVERRIDE; + virtual HENHMETAFILE emf() const OVERRIDE; +#elif defined(OS_MACOSX) + virtual bool RenderPage(unsigned int page_number, + gfx::NativeDrawingContext context, + const CGRect rect, + const MacRenderPageParams& params) const OVERRIDE; +#endif + +#if defined(OS_CHROMEOS) + virtual bool SaveToFD(const base::FileDescriptor& fd) const OVERRIDE; +#endif // if defined(OS_CHROMEOS) + + // Return a new metafile containing just the current page in draft mode. + PdfMetafileSkia* GetMetafileForCurrentPage(); + + private: + scoped_ptr<PdfMetafileSkiaData> data_; + + // True when finish page is outstanding for current page. + bool page_outstanding_; + + DISALLOW_COPY_AND_ASSIGN(PdfMetafileSkia); +}; + +} // namespace printing + +#endif // PRINTING_PDF_METAFILE_SKIA_H_ diff --git a/chromium/printing/pdf_render_settings.h b/chromium/printing/pdf_render_settings.h new file mode 100644 index 00000000000..22784b79ed5 --- /dev/null +++ b/chromium/printing/pdf_render_settings.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PDF_RENDER_SETTINGS_H_ +#define PRINTING_PDF_RENDER_SETTINGS_H_ + +#include "base/tuple.h" +#include "ipc/ipc_param_traits.h" +#include "printing/printing_export.h" +#include "ui/gfx/rect.h" + +namespace printing { + +// Defining PDF rendering settings here as a Tuple as following: +// gfx::Rect - render area +// int - render dpi +// bool - autorotate pages to fit paper +typedef Tuple3<gfx::Rect, int, bool> PdfRenderSettingsBase; + +struct PdfRenderSettings : public PdfRenderSettingsBase { + public: + PdfRenderSettings() : PdfRenderSettingsBase() {} + PdfRenderSettings(gfx::Rect area, int dpi, bool autorotate) + : PdfRenderSettingsBase(area, dpi, autorotate) {} + ~PdfRenderSettings() {} + + const gfx::Rect& area() const { return a; } + int dpi() const { return b; } + bool autorotate() const { return c; } +}; + +} // namespace printing + +namespace IPC { +template <> +struct SimilarTypeTraits<printing::PdfRenderSettings> { + typedef printing::PdfRenderSettingsBase Type; +}; + +} // namespace IPC + +#endif // PRINTING_PDF_RENDER_SETTINGS_H_ + diff --git a/chromium/printing/print_destination_interface.h b/chromium/printing/print_destination_interface.h new file mode 100644 index 00000000000..715f529d026 --- /dev/null +++ b/chromium/printing/print_destination_interface.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINT_DESTINATION_INTERFACE_H_ +#define PRINTING_PRINT_DESTINATION_INTERFACE_H_ + +#include "base/memory/ref_counted.h" +#include "printing/printing_export.h" + +namespace printing { + +class PrintDestinationInterface + : public base::RefCountedThreadSafe<PrintDestinationInterface> { + public: + // Sets the number of pages to print as soon as it is known. + virtual void SetPageCount(int page_count) = 0; + + // Sets the metafile bits for a given page as soon as it is ready. + virtual void SetPageContent(int page_number, + void* content, + size_t content_size) = 0; + protected: + friend class base::RefCountedThreadSafe<PrintDestinationInterface>; + virtual ~PrintDestinationInterface() {} +}; + +PRINTING_EXPORT PrintDestinationInterface* CreatePrintDestination(); + +} // namespace printing + +#endif // PRINTING_PRINT_DESTINATION_INTERFACE_H_ diff --git a/chromium/printing/print_destination_none.cc b/chromium/printing/print_destination_none.cc new file mode 100644 index 00000000000..66b838ef257 --- /dev/null +++ b/chromium/printing/print_destination_none.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/print_destination_interface.h" + +namespace printing { + +PrintDestinationInterface* CreatePrintDestination() { + return NULL; +} + +} // namespace printing diff --git a/chromium/printing/print_destination_win.cc b/chromium/printing/print_destination_win.cc new file mode 100644 index 00000000000..6b98f4c5319 --- /dev/null +++ b/chromium/printing/print_destination_win.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/print_destination_interface.h" + +#include "base/safe_numerics.h" +#include "base/win/metro.h" +#include "win8/util/win8_util.h" + +namespace printing { + +class PrintDestinationWin : public PrintDestinationInterface { + public: + PrintDestinationWin() + : metro_set_print_page_count_(NULL), + metro_set_print_page_content_(NULL) { + HMODULE metro_module = base::win::GetMetroModule(); + if (metro_module != NULL) { + metro_set_print_page_count_ = + reinterpret_cast<MetroSetPrintPageCount>( + ::GetProcAddress(metro_module, "MetroSetPrintPageCount")); + metro_set_print_page_content_ = + reinterpret_cast<MetroSetPrintPageContent>( + ::GetProcAddress(metro_module, "MetroSetPrintPageContent")); + } + } + virtual void SetPageCount(int page_count) { + if (metro_set_print_page_count_) + metro_set_print_page_count_(page_count); + } + + virtual void SetPageContent(int page_number, + void* content, + size_t content_size) { + if (metro_set_print_page_content_) + metro_set_print_page_content_(page_number - 1, content, + base::checked_numeric_cast<UINT32>(content_size)); + } + private: + typedef void (*MetroSetPrintPageCount)(INT); + typedef void (*MetroSetPrintPageContent)(INT, VOID*, UINT32); + MetroSetPrintPageCount metro_set_print_page_count_; + MetroSetPrintPageContent metro_set_print_page_content_; +}; + +PrintDestinationInterface* CreatePrintDestination() { + // We currently only support the Metro print destination. + if (win8::IsSingleWindowMetroMode()) + return new PrintDestinationWin; + else + return NULL; +} + +} // namespace printing diff --git a/chromium/printing/print_dialog_gtk_interface.h b/chromium/printing/print_dialog_gtk_interface.h new file mode 100644 index 00000000000..fc238c76217 --- /dev/null +++ b/chromium/printing/print_dialog_gtk_interface.h @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINT_DIALOG_GTK_INTERFACE_H_ +#define PRINTING_PRINT_DIALOG_GTK_INTERFACE_H_ + +#include "base/strings/string16.h" +#include "printing/printing_context_gtk.h" +#include "ui/gfx/native_widget_types.h" + +namespace printing { + +class Metafile; +class PrintSettings; + +// An interface for GTK printing dialogs. Classes that live outside of +// printing/ can implement this interface and get threading requirements +// correct without exposing those requirements to printing/. +class PrintDialogGtkInterface { + public: + // Tell the dialog to use the default print setting. + virtual void UseDefaultSettings() = 0; + + // Update the dialog to use |job_settings| and |ranges|, where |job_settings| + // is a dictionary of settings with possible keys from + // printing/print_job_constants.h. Only used when printing without the system + // print dialog. E.g. for Print Preview. Returns false on error. + virtual bool UpdateSettings(const base::DictionaryValue& job_settings, + const PageRanges& ranges, + PrintSettings* settings) = 0; + + // Shows the dialog and handles the response with |callback|. Only used when + // printing with the native print dialog. + virtual void ShowDialog( + gfx::NativeView parent_view, + bool has_selection, + const PrintingContextGtk::PrintSettingsCallback& callback) = 0; + + // Prints the document named |document_name| contained in |metafile|. + // Called from the print worker thread. Once called, the + // PrintDialogGtkInterface instance should not be reused. + virtual void PrintDocument(const Metafile* metafile, + const string16& document_name) = 0; + + // Same as AddRef/Release, but with different names since + // PrintDialogGtkInterface does not inherit from RefCounted. + virtual void AddRefToDialog() = 0; + virtual void ReleaseDialog() = 0; + + protected: + virtual ~PrintDialogGtkInterface() {} +}; + +} // namespace printing + +#endif // PRINTING_PRINT_DIALOG_GTK_INTERFACE_H_ diff --git a/chromium/printing/print_job_constants.cc b/chromium/printing/print_job_constants.cc new file mode 100644 index 00000000000..f659aeded3b --- /dev/null +++ b/chromium/printing/print_job_constants.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/print_job_constants.h" + +namespace printing { + +// True if this is the first preview request. +const char kIsFirstRequest[] = "isFirstRequest"; + +// Unique ID sent along every preview request. +const char kPreviewRequestID[] = "requestID"; + +// Unique ID to identify a print preview UI. +const char kPreviewUIID[] = "previewUIID"; + +// Print using cloud print: true if selected, false if not. +const char kSettingCloudPrintId[] = "cloudPrintID"; + +// Print using cloud print dialog: true if selected, false if not. +const char kSettingCloudPrintDialog[] = "printWithCloudPrint"; + +// Print job setting 'collate'. +const char kSettingCollate[] = "collate"; + +// Print out color: true for color, false for grayscale. +const char kSettingColor[] = "color"; + +// Default to color on or not. +const char kSettingSetColorAsDefault[] = "setColorAsDefault"; + +// Key that specifies the height of the content area of the page. +const char kSettingContentHeight[] = "contentHeight"; + +// Key that specifies the width of the content area of the page. +const char kSettingContentWidth[] = "contentWidth"; + +// Number of copies. +const char kSettingCopies[] = "copies"; + +// Device name: Unique printer identifier. +const char kSettingDeviceName[] = "deviceName"; + +// Print job duplex mode. +const char kSettingDuplexMode[] = "duplex"; + +// Option to fit source page contents to printer paper size: true if +// selected else false. +const char kSettingFitToPageEnabled[] = "fitToPageEnabled"; + +// True, when a new set of draft preview data is required. +const char kSettingGenerateDraftData[] = "generateDraftData"; + +// Option to print headers and Footers: true if selected, false if not. +const char kSettingHeaderFooterEnabled[] = "headerFooterEnabled"; + +// Interstice or gap between different header footer components. Hardcoded to +// about 0.5cm, match the value in PrintSettings::SetPrinterPrintableArea. +const float kSettingHeaderFooterInterstice = 14.2f; + +// Key that specifies the date of the page that will be printed in the headers +// and footers. +const char kSettingHeaderFooterDate[] = "date"; + +// Key that specifies the title of the page that will be printed in the headers +// and footers. +const char kSettingHeaderFooterTitle[] = "title"; + +// Key that specifies the URL of the page that will be printed in the headers +// and footers. +const char kSettingHeaderFooterURL[] = "url"; + +// Page orientation: true for landscape, false for portrait. +const char kSettingLandscape[] = "landscape"; + +// Key that specifies the bottom margin of the page. +const char kSettingMarginBottom[] = "marginBottom"; + +// Key that specifies the left margin of the page. +const char kSettingMarginLeft[] = "marginLeft"; + +// Key that specifies the right margin of the page. +const char kSettingMarginRight[] = "marginRight"; + +// Key that specifies the top margin of the page. +const char kSettingMarginTop[] = "marginTop"; + +// Key that specifies the dictionary of custom margins as set by the user. +const char kSettingMarginsCustom[] = "marginsCustom"; + +// Key that specifies the type of margins to use. Value is an int from the +// MarginType enum. +const char kSettingMarginsType[] = "marginsType"; + +// Number of pages to print. +const char kSettingPreviewPageCount[] = "pageCount"; + +// A page range. +const char kSettingPageRange[] = "pageRange"; + +// The first page of a page range. (1-based) +const char kSettingPageRangeFrom[] = "from"; + +// The last page of a page range. (1-based) +const char kSettingPageRangeTo[] = "to"; + +const char kSettingPreviewModifiable[] = "previewModifiable"; + +// Keys that specifies the printable area details. +const char kSettingPrintableAreaX[] = "printableAreaX"; +const char kSettingPrintableAreaY[] = "printableAreaY"; +const char kSettingPrintableAreaWidth[] = "printableAreaWidth"; +const char kSettingPrintableAreaHeight[] = "printableAreaHeight"; + +// Printer name. +const char kSettingPrinterName[] = "printerName"; + +// Print to PDF option: true if selected, false if not. +const char kSettingPrintToPDF[] = "printToPDF"; + +// Whether to print CSS backgrounds. +const char kSettingShouldPrintBackgrounds[] = "shouldPrintBackgrounds"; + +// Whether to print selection only. +const char kSettingShouldPrintSelectionOnly[] = "shouldPrintSelectionOnly"; + +// Indices used to represent first preview page and complete preview document. +const int FIRST_PAGE_INDEX = 0; +const int COMPLETE_PREVIEW_DOCUMENT_INDEX = -1; + +#if defined(OS_MACOSX) +const char kSettingOpenPDFInPreview[] = "OpenPDFInPreview"; +#endif + +#if defined (USE_CUPS) +const char kBlack[] = "Black"; +const char kCMYK[] = "CMYK"; +const char kKCMY[] = "KCMY"; +const char kCMY_K[] = "CMY+K"; +const char kCMY[] = "CMY"; +const char kColor[] = "Color"; +const char kGray[] = "Gray"; +const char kGrayscale[] = "Grayscale"; +const char kGreyscale[] = "Greyscale"; +const char kMonochrome[] = "Monochrome"; +const char kNormal[] = "Normal"; +const char kNormalGray[] = "Normal.Gray"; +const char kRGB[] = "RGB"; +const char kRGBA[] = "RGBA"; +const char kRGB16[] = "RGB16"; +#endif + +} // namespace printing diff --git a/chromium/printing/print_job_constants.h b/chromium/printing/print_job_constants.h new file mode 100644 index 00000000000..b5b31306b03 --- /dev/null +++ b/chromium/printing/print_job_constants.h @@ -0,0 +1,136 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINT_JOB_CONSTANTS_H_ +#define PRINTING_PRINT_JOB_CONSTANTS_H_ + +#include "build/build_config.h" +#include "printing/printing_export.h" + +namespace printing { + +PRINTING_EXPORT extern const char kIsFirstRequest[]; +PRINTING_EXPORT extern const char kPreviewRequestID[]; +PRINTING_EXPORT extern const char kPreviewUIID[]; +PRINTING_EXPORT extern const char kSettingCloudPrintId[]; +PRINTING_EXPORT extern const char kSettingCloudPrintDialog[]; +PRINTING_EXPORT extern const char kSettingCollate[]; +PRINTING_EXPORT extern const char kSettingColor[]; +PRINTING_EXPORT extern const char kSettingSetColorAsDefault[]; +PRINTING_EXPORT extern const char kSettingContentHeight[]; +PRINTING_EXPORT extern const char kSettingContentWidth[]; +PRINTING_EXPORT extern const char kSettingCopies[]; +PRINTING_EXPORT extern const char kSettingDeviceName[]; +PRINTING_EXPORT extern const char kSettingDuplexMode[]; +PRINTING_EXPORT extern const char kSettingFitToPageEnabled[]; +PRINTING_EXPORT extern const char kSettingGenerateDraftData[]; +PRINTING_EXPORT extern const char kSettingHeaderFooterEnabled[]; +PRINTING_EXPORT extern const float kSettingHeaderFooterInterstice; +PRINTING_EXPORT extern const char kSettingHeaderFooterDate[]; +PRINTING_EXPORT extern const char kSettingHeaderFooterTitle[]; +PRINTING_EXPORT extern const char kSettingHeaderFooterURL[]; +PRINTING_EXPORT extern const char kSettingLandscape[]; +PRINTING_EXPORT extern const char kSettingMarginBottom[]; +PRINTING_EXPORT extern const char kSettingMarginLeft[]; +PRINTING_EXPORT extern const char kSettingMarginRight[]; +PRINTING_EXPORT extern const char kSettingMarginTop[]; +PRINTING_EXPORT extern const char kSettingMarginsCustom[]; +PRINTING_EXPORT extern const char kSettingMarginsType[]; +PRINTING_EXPORT extern const char kSettingPreviewPageCount[]; +PRINTING_EXPORT extern const char kSettingPageRange[]; +PRINTING_EXPORT extern const char kSettingPageRangeFrom[]; +PRINTING_EXPORT extern const char kSettingPageRangeTo[]; +PRINTING_EXPORT extern const char kSettingPreviewModifiable[]; +PRINTING_EXPORT extern const char kSettingPrintableAreaX[]; +PRINTING_EXPORT extern const char kSettingPrintableAreaY[]; +PRINTING_EXPORT extern const char kSettingPrintableAreaWidth[]; +PRINTING_EXPORT extern const char kSettingPrintableAreaHeight[]; +PRINTING_EXPORT extern const char kSettingPrinterName[]; +PRINTING_EXPORT extern const char kSettingPrintToPDF[]; +PRINTING_EXPORT extern const char kSettingShouldPrintBackgrounds[]; +PRINTING_EXPORT extern const char kSettingShouldPrintSelectionOnly[]; + +PRINTING_EXPORT extern const int FIRST_PAGE_INDEX; +PRINTING_EXPORT extern const int COMPLETE_PREVIEW_DOCUMENT_INDEX; + +#if defined(OS_MACOSX) +PRINTING_EXPORT extern const char kSettingOpenPDFInPreview[]; +#endif // defined(OS_MACOSX) + +#if defined (USE_CUPS) +// Printer color models +PRINTING_EXPORT extern const char kBlack[]; +PRINTING_EXPORT extern const char kCMYK[]; +PRINTING_EXPORT extern const char kKCMY[]; +PRINTING_EXPORT extern const char kCMY_K[]; +PRINTING_EXPORT extern const char kCMY[]; +PRINTING_EXPORT extern const char kColor[]; +PRINTING_EXPORT extern const char kGray[]; +PRINTING_EXPORT extern const char kGrayscale[]; +PRINTING_EXPORT extern const char kGreyscale[]; +PRINTING_EXPORT extern const char kMonochrome[]; +PRINTING_EXPORT extern const char kNormal[]; +PRINTING_EXPORT extern const char kNormalGray[]; +PRINTING_EXPORT extern const char kRGB[]; +PRINTING_EXPORT extern const char kRGBA[]; +PRINTING_EXPORT extern const char kRGB16[]; +#endif + +// Print job duplex mode values. +enum DuplexMode { + UNKNOWN_DUPLEX_MODE = -1, + SIMPLEX, + LONG_EDGE, + SHORT_EDGE, +}; + +// Specifies the horizontal alignment of the headers and footers. +enum HorizontalHeaderFooterPosition { + LEFT, + CENTER, + RIGHT +}; + +// Specifies the vertical alignment of the Headers and Footers. +enum VerticalHeaderFooterPosition { + TOP, + BOTTOM +}; + +// Print job color mode values. +enum ColorModels { + UNKNOWN_COLOR_MODEL, + GRAY, + COLOR, + CMYK, + CMY, + KCMY, + CMY_K, // CMY_K represents CMY+K. + BLACK, + GRAYSCALE, + RGB, + RGB16, + RGBA, + COLORMODE_COLOR, // Used in samsung printer ppds. + COLORMODE_MONOCHROME, // Used in samsung printer ppds. + HP_COLOR_COLOR, // Used in HP color printer ppds. + HP_COLOR_BLACK, // Used in HP color printer ppds. + PRINTOUTMODE_NORMAL, // Used in foomatic ppds. + PRINTOUTMODE_NORMAL_GRAY, // Used in foomatic ppds. + PROCESSCOLORMODEL_CMYK, // Used in canon printer ppds. + PROCESSCOLORMODEL_GREYSCALE, // Used in canon printer ppds. + PROCESSCOLORMODEL_RGB, // Used in canon printer ppds +}; + +// What kind of margins to use. +enum MarginType { + DEFAULT_MARGINS, // Default varies depending on headers being enabled or not + NO_MARGINS, + PRINTABLE_AREA_MARGINS, + CUSTOM_MARGINS, +}; + +} // namespace printing + +#endif // PRINTING_PRINT_JOB_CONSTANTS_H_ diff --git a/chromium/printing/print_settings.cc b/chromium/printing/print_settings.cc new file mode 100644 index 00000000000..e665c170221 --- /dev/null +++ b/chromium/printing/print_settings.cc @@ -0,0 +1,252 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/print_settings.h" + +#include "base/atomic_sequence_num.h" +#include "base/logging.h" +#include "printing/print_job_constants.h" +#include "printing/units.h" + +namespace printing { + +#if defined(USE_CUPS) +void GetColorModelForMode( + int color_mode, std::string* color_setting_name, std::string* color_value) { +#if defined(OS_MACOSX) + const char kCUPSColorMode[] = "ColorMode"; + const char kCUPSColorModel[] = "ColorModel"; + const char kCUPSPrintoutMode[] = "PrintoutMode"; + const char kCUPSProcessColorModel[] = "ProcessColorModel"; +#else + const char kCUPSColorMode[] = "cups-ColorMode"; + const char kCUPSColorModel[] = "cups-ColorModel"; + const char kCUPSPrintoutMode[] = "cups-PrintoutMode"; + const char kCUPSProcessColorModel[] = "cups-ProcessColorModel"; +#endif // defined(OS_MACOSX) + + color_setting_name->assign(kCUPSColorModel); + switch (color_mode) { + case COLOR: + color_value->assign(kColor); + break; + case CMYK: + color_value->assign(kCMYK); + break; + case PRINTOUTMODE_NORMAL: + color_value->assign(kNormal); + color_setting_name->assign(kCUPSPrintoutMode); + break; + case PRINTOUTMODE_NORMAL_GRAY: + color_value->assign(kNormalGray); + color_setting_name->assign(kCUPSPrintoutMode); + break; + case RGB16: + color_value->assign(kRGB16); + break; + case RGBA: + color_value->assign(kRGBA); + break; + case RGB: + color_value->assign(kRGB); + break; + case CMY: + color_value->assign(kCMY); + break; + case CMY_K: + color_value->assign(kCMY_K); + break; + case BLACK: + color_value->assign(kBlack); + break; + case GRAY: + color_value->assign(kGray); + break; + case COLORMODE_COLOR: + color_setting_name->assign(kCUPSColorMode); + color_value->assign(kColor); + break; + case COLORMODE_MONOCHROME: + color_setting_name->assign(kCUPSColorMode); + color_value->assign(kMonochrome); + break; + case HP_COLOR_COLOR: + color_setting_name->assign(kColor); + color_value->assign(kColor); + break; + case HP_COLOR_BLACK: + color_setting_name->assign(kColor); + color_value->assign(kBlack); + break; + case PROCESSCOLORMODEL_CMYK: + color_setting_name->assign(kCUPSProcessColorModel); + color_value->assign(kCMYK); + break; + case PROCESSCOLORMODEL_GREYSCALE: + color_setting_name->assign(kCUPSProcessColorModel); + color_value->assign(kGreyscale); + break; + case PROCESSCOLORMODEL_RGB: + color_setting_name->assign(kCUPSProcessColorModel); + color_value->assign(kRGB); + break; + default: + color_value->assign(kGrayscale); + break; + } +} +#endif // defined(USE_CUPS) + +bool isColorModelSelected(int model) { + return (model != GRAY && + model != BLACK && + model != PRINTOUTMODE_NORMAL_GRAY && + model != COLORMODE_MONOCHROME && + model != PROCESSCOLORMODEL_GREYSCALE && + model != HP_COLOR_BLACK); +} + +// Global SequenceNumber used for generating unique cookie values. +static base::StaticAtomicSequenceNumber cookie_seq; + +PrintSettings::PrintSettings() + : min_shrink(1.25), + max_shrink(2.0), + desired_dpi(72), + selection_only(false), + margin_type(DEFAULT_MARGINS), + display_header_footer(false), + should_print_backgrounds(false), + dpi_(0), + landscape_(false), + supports_alpha_blend_(true) { +} + +PrintSettings::~PrintSettings() { +} + +void PrintSettings::Clear() { + ranges.clear(); + min_shrink = 1.25; + max_shrink = 2.; + desired_dpi = 72; + selection_only = false; + date = string16(); + title = string16(); + url = string16(); + display_header_footer = false; + printer_name_.clear(); + device_name_.clear(); + page_setup_device_units_.Clear(); + dpi_ = 0; + landscape_ = false; + supports_alpha_blend_ = true; + should_print_backgrounds = false; +} + +void PrintSettings::SetPrinterPrintableArea( + gfx::Size const& physical_size_device_units, + gfx::Rect const& printable_area_device_units, + int units_per_inch) { + int header_footer_text_height = 0; + if (display_header_footer) { + // Hard-code text_height = 0.5cm = ~1/5 of inch. + header_footer_text_height = ConvertUnit(kSettingHeaderFooterInterstice, + kPointsPerInch, units_per_inch); + } + + PageMargins margins; + switch (margin_type) { + case DEFAULT_MARGINS: { + // Default margins 1.0cm = ~2/5 of an inch. + int margin_printer_units = ConvertUnit(1000, kHundrethsMMPerInch, + units_per_inch); + margins.header = header_footer_text_height; + margins.footer = header_footer_text_height; + margins.top = margin_printer_units; + margins.bottom = margin_printer_units; + margins.left = margin_printer_units; + margins.right = margin_printer_units; + break; + } + case NO_MARGINS: + case PRINTABLE_AREA_MARGINS: { + margins.header = 0; + margins.footer = 0; + margins.top = 0; + margins.bottom = 0; + margins.left = 0; + margins.right = 0; + break; + } + case CUSTOM_MARGINS: { + margins.header = 0; + margins.footer = 0; + margins.top = ConvertUnitDouble( + requested_custom_margins_in_points_.top, + kPointsPerInch, + units_per_inch); + margins.bottom = ConvertUnitDouble( + requested_custom_margins_in_points_.bottom, + kPointsPerInch, + units_per_inch); + margins.left = ConvertUnitDouble( + requested_custom_margins_in_points_.left, + kPointsPerInch, + units_per_inch); + margins.right = ConvertUnitDouble( + requested_custom_margins_in_points_.right, + kPointsPerInch, + units_per_inch); + break; + } + default: { + NOTREACHED(); + } + } + + if (margin_type == DEFAULT_MARGINS || margin_type == PRINTABLE_AREA_MARGINS) + page_setup_device_units_.SetRequestedMargins(margins); + else + page_setup_device_units_.ForceRequestedMargins(margins); + + page_setup_device_units_.Init(physical_size_device_units, + printable_area_device_units, + header_footer_text_height); +} + +void PrintSettings::SetCustomMargins( + const PageMargins& requested_margins_in_points) { + requested_custom_margins_in_points_ = requested_margins_in_points; + margin_type = CUSTOM_MARGINS; +} + +bool PrintSettings::Equals(const PrintSettings& rhs) const { + // Do not test the display device name (printer_name_) for equality since it + // may sometimes be chopped off at 30 chars. As long as device_name is the + // same, that's fine. + return ranges == rhs.ranges && + min_shrink == rhs.min_shrink && + max_shrink == rhs.max_shrink && + desired_dpi == rhs.desired_dpi && + device_name_ == rhs.device_name_ && + page_setup_device_units_.Equals(rhs.page_setup_device_units_) && + dpi_ == rhs.dpi_ && + landscape_ == rhs.landscape_ && + should_print_backgrounds == rhs.should_print_backgrounds; +} + +int PrintSettings::NewCookie() { + // A cookie of 0 is used to mark a document as unassigned, count from 1. + return cookie_seq.GetNext() + 1; +} + +void PrintSettings::SetOrientation(bool landscape) { + if (landscape_ != landscape) { + landscape_ = landscape; + page_setup_device_units_.FlipOrientation(); + } +} + +} // namespace printing diff --git a/chromium/printing/print_settings.h b/chromium/printing/print_settings.h new file mode 100644 index 00000000000..5728bf7128f --- /dev/null +++ b/chromium/printing/print_settings.h @@ -0,0 +1,150 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINT_SETTINGS_H_ +#define PRINTING_PRINT_SETTINGS_H_ + +#include <string> + +#include "base/strings/string16.h" +#include "printing/page_range.h" +#include "printing/page_setup.h" +#include "printing/print_job_constants.h" +#include "printing/printing_export.h" +#include "ui/gfx/rect.h" + +namespace printing { + +// Returns true if color model is selected. +PRINTING_EXPORT bool isColorModelSelected(int model); + +#if defined(USE_CUPS) +// Get the color model setting name and value for the |color_mode|. +PRINTING_EXPORT void GetColorModelForMode(int color_mode, + std::string* color_setting_name, + std::string* color_value); +#endif + +// OS-independent print settings. +class PRINTING_EXPORT PrintSettings { + public: + PrintSettings(); + ~PrintSettings(); + + // Reinitialize the settings to the default values. + void Clear(); + + // Set printer printable area in in device units. + void SetPrinterPrintableArea(gfx::Size const& physical_size_device_units, + gfx::Rect const& printable_area_device_units, + int units_per_inch); + + void SetCustomMargins(const PageMargins& requested_margins_in_points); + + // Equality operator. + // NOTE: printer_name is NOT tested for equality since it doesn't affect the + // output. + bool Equals(const PrintSettings& rhs) const; + + void set_landscape(bool landscape) { landscape_ = landscape; } + void set_printer_name(const string16& printer_name) { + printer_name_ = printer_name; + } + const string16& printer_name() const { return printer_name_; } + void set_device_name(const string16& device_name) { + device_name_ = device_name; + } + const string16& device_name() const { return device_name_; } + void set_dpi(int dpi) { dpi_ = dpi; } + int dpi() const { return dpi_; } + void set_supports_alpha_blend(bool supports_alpha_blend) { + supports_alpha_blend_ = supports_alpha_blend; + } + bool supports_alpha_blend() const { return supports_alpha_blend_; } + const PageSetup& page_setup_device_units() const { + return page_setup_device_units_; + } + int device_units_per_inch() const { +#if defined(OS_MACOSX) + return 72; +#else // defined(OS_MACOSX) + return dpi(); +#endif // defined(OS_MACOSX) + } + + // Multi-page printing. Each PageRange describes a from-to page combination. + // This permits printing selected pages only. + PageRanges ranges; + + // By imaging to a width a little wider than the available pixels, thin pages + // will be scaled down a little, matching the way they print in IE and Camino. + // This lets them use fewer sheets than they would otherwise, which is + // presumably why other browsers do this. Wide pages will be scaled down more + // than this. + double min_shrink; + + // This number determines how small we are willing to reduce the page content + // in order to accommodate the widest line. If the page would have to be + // reduced smaller to make the widest line fit, we just clip instead (this + // behavior matches MacIE and Mozilla, at least) + double max_shrink; + + // Desired visible dots per inch rendering for output. Printing should be + // scaled to ScreenDpi/dpix*desired_dpi. + int desired_dpi; + + // Indicates if the user only wants to print the current selection. + bool selection_only; + + // Indicates what kind of margins should be applied to the printable area. + MarginType margin_type; + + // Cookie generator. It is used to initialize PrintedDocument with its + // associated PrintSettings, to be sure that each generated PrintedPage is + // correctly associated with its corresponding PrintedDocument. + static int NewCookie(); + + // Updates the orientation and flip the page if needed. + void SetOrientation(bool landscape); + + // Strings to be printed as headers and footers if requested by the user. + string16 date; + string16 title; + string16 url; + + // True if the user wants headers and footers to be displayed. + bool display_header_footer; + + // True if the user wants to print CSS backgrounds. + bool should_print_backgrounds; + + private: + ////////////////////////////////////////////////////////////////////////////// + // Settings that can't be changed without side-effects. + + // Printer name as shown to the user. + string16 printer_name_; + + // Printer device name as opened by the OS. + string16 device_name_; + + // Page setup in device units. + PageSetup page_setup_device_units_; + + // Printer's device effective dots per inch in both axis. + int dpi_; + + // Is the orientation landscape or portrait. + bool landscape_; + + // True if this printer supports AlphaBlend. + bool supports_alpha_blend_; + + // If margin type is custom, this is what was requested. + PageMargins requested_custom_margins_in_points_; +}; + +} // namespace printing + +#endif // PRINTING_PRINT_SETTINGS_H_ diff --git a/chromium/printing/print_settings_initializer.cc b/chromium/printing/print_settings_initializer.cc new file mode 100644 index 00000000000..80fa6366d57 --- /dev/null +++ b/chromium/printing/print_settings_initializer.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/print_settings_initializer.h" + +#include <algorithm> +#include <cmath> +#include <string> + +#include "base/i18n/time_formatting.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/values.h" +#include "printing/print_job_constants.h" +#include "printing/print_settings.h" +#include "printing/units.h" +#include "ui/base/text/text_elider.h" +#include "url/gurl.h" + +using base::DictionaryValue; + +namespace printing { + +void PrintSettingsInitializer::InitHeaderFooterStrings( + const DictionaryValue& job_settings, + PrintSettings* print_settings) { + if (!job_settings.GetBoolean(kSettingHeaderFooterEnabled, + &print_settings->display_header_footer)) { + NOTREACHED(); + } + if (!print_settings->display_header_footer) + return; + + string16 date = base::TimeFormatShortDateNumeric(base::Time::Now()); + string16 title; + string16 url; + if (!job_settings.GetString(kSettingHeaderFooterTitle, &title) || + !job_settings.GetString(kSettingHeaderFooterURL, &url)) { + NOTREACHED(); + } + + print_settings->date = date; + print_settings->title = title; + print_settings->url = ui::ElideUrl(GURL(url), gfx::Font(), 0, std::string()); +} + +} // namespace printing diff --git a/chromium/printing/print_settings_initializer.h b/chromium/printing/print_settings_initializer.h new file mode 100644 index 00000000000..4897be87e18 --- /dev/null +++ b/chromium/printing/print_settings_initializer.h @@ -0,0 +1,35 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINT_SETTINGS_INITIALIZER_H_ +#define PRINTING_PRINT_SETTINGS_INITIALIZER_H_ + +#include "base/basictypes.h" +#include "base/logging.h" +#include "printing/printing_export.h" + +namespace base { +class DictionaryValue; +} + +namespace printing { + +class PrintSettings; + +// Initializes the header footer strings in the PrintSettings object from the +// provided |job_settings|. +class PRINTING_EXPORT PrintSettingsInitializer { + public: + static void InitHeaderFooterStrings( + const base::DictionaryValue& job_settings, + PrintSettings* print_settings); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(PrintSettingsInitializer); +}; + +} // namespace printing + +#endif // PRINTING_PRINT_SETTINGS_INITIALIZER_H_ + diff --git a/chromium/printing/print_settings_initializer_gtk.cc b/chromium/printing/print_settings_initializer_gtk.cc new file mode 100644 index 00000000000..d4f21ed1067 --- /dev/null +++ b/chromium/printing/print_settings_initializer_gtk.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/print_settings_initializer_gtk.h" + +#include <gtk/gtk.h> +#include <gtk/gtkunixprint.h> + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "printing/print_settings.h" +#include "printing/units.h" + +namespace printing { + +// static +void PrintSettingsInitializerGtk::InitPrintSettings( + GtkPrintSettings* settings, + GtkPageSetup* page_setup, + const PageRanges& new_ranges, + bool print_selection_only, + PrintSettings* print_settings) { + DCHECK(settings); + DCHECK(page_setup); + DCHECK(print_settings); + + string16 name(UTF8ToUTF16(static_cast<const char*>( + gtk_print_settings_get_printer(settings)))); + print_settings->set_printer_name(name); + print_settings->set_device_name(print_settings->printer_name()); + print_settings->ranges = new_ranges; + print_settings->selection_only = print_selection_only; + + gfx::Size physical_size_device_units; + gfx::Rect printable_area_device_units; + int dpi = gtk_print_settings_get_resolution(settings); + if (dpi) { + // Initialize page_setup_device_units_. + physical_size_device_units.SetSize( + gtk_page_setup_get_paper_width(page_setup, GTK_UNIT_INCH) * dpi, + gtk_page_setup_get_paper_height(page_setup, GTK_UNIT_INCH) * dpi); + printable_area_device_units.SetRect( + gtk_page_setup_get_left_margin(page_setup, GTK_UNIT_INCH) * dpi, + gtk_page_setup_get_top_margin(page_setup, GTK_UNIT_INCH) * dpi, + gtk_page_setup_get_page_width(page_setup, GTK_UNIT_INCH) * dpi, + gtk_page_setup_get_page_height(page_setup, GTK_UNIT_INCH) * dpi); + } else { + // Use default values if we cannot get valid values from the print dialog. + dpi = kPixelsPerInch; + double page_width_in_pixel = 8.5 * dpi; + double page_height_in_pixel = 11.0 * dpi; + physical_size_device_units.SetSize( + static_cast<int>(page_width_in_pixel), + static_cast<int>(page_height_in_pixel)); + printable_area_device_units.SetRect( + static_cast<int>(kLeftMarginInInch * dpi), + static_cast<int>(kTopMarginInInch * dpi), + page_width_in_pixel - (kLeftMarginInInch + kRightMarginInInch) * dpi, + page_height_in_pixel - (kTopMarginInInch + kBottomMarginInInch) * dpi); + } + + print_settings->set_dpi(dpi); + print_settings->SetPrinterPrintableArea(physical_size_device_units, + printable_area_device_units, + dpi); + + // Note: With the normal GTK print dialog, when the user selects the landscape + // orientation, all that does is change the paper size. Which seems to be + // enough to render the right output and send it to the printer. + // The orientation value stays as portrait and does not actually affect + // printing. + // Thus this is only useful in print preview mode, where we manually set the + // orientation and change the paper size ourselves. + GtkPageOrientation orientation = gtk_print_settings_get_orientation(settings); + print_settings->SetOrientation(orientation == GTK_PAGE_ORIENTATION_LANDSCAPE); +} + +const double PrintSettingsInitializerGtk::kTopMarginInInch = 0.25; +const double PrintSettingsInitializerGtk::kBottomMarginInInch = 0.56; +const double PrintSettingsInitializerGtk::kLeftMarginInInch = 0.25; +const double PrintSettingsInitializerGtk::kRightMarginInInch = 0.25; + +} // namespace printing diff --git a/chromium/printing/print_settings_initializer_gtk.h b/chromium/printing/print_settings_initializer_gtk.h new file mode 100644 index 00000000000..5807500d9a0 --- /dev/null +++ b/chromium/printing/print_settings_initializer_gtk.h @@ -0,0 +1,40 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINT_SETTINGS_INITIALIZER_GTK_H_ +#define PRINTING_PRINT_SETTINGS_INITIALIZER_GTK_H_ + +#include "base/logging.h" +#include "printing/page_range.h" + +typedef struct _GtkPrintSettings GtkPrintSettings; +typedef struct _GtkPageSetup GtkPageSetup; + +namespace printing { + +class PrintSettings; + +// Initializes a PrintSettings object from the provided Gtk printer objects. +class PRINTING_EXPORT PrintSettingsInitializerGtk { + public: + static void InitPrintSettings(GtkPrintSettings* settings, + GtkPageSetup* page_setup, + const PageRanges& new_ranges, + bool print_selection_only, + PrintSettings* print_settings); + + // The default margins, in points. These values are based on 72 dpi, + // with 0.25 margins on top, left, and right, and 0.56 on bottom. + static const double kTopMarginInInch; + static const double kRightMarginInInch; + static const double kBottomMarginInInch; + static const double kLeftMarginInInch; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(PrintSettingsInitializerGtk); +}; + +} // namespace printing + +#endif // PRINTING_PRINT_SETTINGS_INITIALIZER_GTK_H_ diff --git a/chromium/printing/print_settings_initializer_mac.cc b/chromium/printing/print_settings_initializer_mac.cc new file mode 100644 index 00000000000..fd665989a59 --- /dev/null +++ b/chromium/printing/print_settings_initializer_mac.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/print_settings_initializer_mac.h" + +#include "base/strings/sys_string_conversions.h" +#include "printing/print_settings.h" + +namespace printing { + +// static +void PrintSettingsInitializerMac::InitPrintSettings( + PMPrinter printer, + PMPageFormat page_format, + const PageRanges& new_ranges, + bool print_selection_only, + PrintSettings* print_settings) { + DCHECK(print_settings); + + print_settings->set_printer_name( + base::SysCFStringRefToUTF16(PMPrinterGetName(printer))); + print_settings->set_device_name( + base::SysCFStringRefToUTF16(PMPrinterGetID(printer))); + print_settings->ranges = new_ranges; + + PMOrientation orientation = kPMPortrait; + PMGetOrientation(page_format, &orientation); + print_settings->set_landscape(orientation == kPMLandscape); + print_settings->selection_only = print_selection_only; + + UInt32 resolution_count = 0; + PMResolution best_resolution = { 72.0, 72.0 }; + OSStatus status = PMPrinterGetPrinterResolutionCount(printer, + &resolution_count); + if (status == noErr) { + // Resolution indexes are 1-based. + for (uint32 i = 1; i <= resolution_count; ++i) { + PMResolution resolution; + PMPrinterGetIndexedPrinterResolution(printer, i, &resolution); + if (resolution.hRes > best_resolution.hRes) + best_resolution = resolution; + } + } + int dpi = best_resolution.hRes; + print_settings->set_dpi(dpi); + + DCHECK_EQ(dpi, best_resolution.vRes); + + // Get printable area and paper rects (in points) + PMRect page_rect, paper_rect; + PMGetAdjustedPageRect(page_format, &page_rect); + PMGetAdjustedPaperRect(page_format, &paper_rect); + + // Device units are in points. Units per inch is 72. + gfx::Size physical_size_device_units( + (paper_rect.right - paper_rect.left), + (paper_rect.bottom - paper_rect.top)); + gfx::Rect printable_area_device_units( + (page_rect.left - paper_rect.left), + (page_rect.top - paper_rect.top), + (page_rect.right - page_rect.left), + (page_rect.bottom - page_rect.top)); + + print_settings->SetPrinterPrintableArea(physical_size_device_units, + printable_area_device_units, + 72); +} + +} // namespace printing diff --git a/chromium/printing/print_settings_initializer_mac.h b/chromium/printing/print_settings_initializer_mac.h new file mode 100644 index 00000000000..2b7925189f2 --- /dev/null +++ b/chromium/printing/print_settings_initializer_mac.h @@ -0,0 +1,32 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTING_SETTINGS_INITIALIZER_MAC_H_ +#define PRINTING_PRINTING_SETTINGS_INITIALIZER_MAC_H_ + +#import <ApplicationServices/ApplicationServices.h> + +#include "base/logging.h" +#include "printing/page_range.h" + +namespace printing { + +class PrintSettings; + +// Initializes a PrintSettings object from the provided device context. +class PRINTING_EXPORT PrintSettingsInitializerMac { + public: + static void InitPrintSettings(PMPrinter printer, + PMPageFormat page_format, + const PageRanges& new_ranges, + bool print_selection_only, + PrintSettings* print_settings); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(PrintSettingsInitializerMac); +}; + +} // namespace printing + +#endif // PRINTING_PRINTING_SETTINGS_INITIALIZER_MAC_H_ diff --git a/chromium/printing/print_settings_initializer_win.cc b/chromium/printing/print_settings_initializer_win.cc new file mode 100644 index 00000000000..0ec96d0ab62 --- /dev/null +++ b/chromium/printing/print_settings_initializer_win.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/print_settings_initializer_win.h" + +#include <windows.h> + +#include "printing/print_settings.h" + +namespace printing { + +// static +void PrintSettingsInitializerWin::InitPrintSettings( + HDC hdc, + const DEVMODE& dev_mode, + const PageRanges& new_ranges, + const std::wstring& new_device_name, + bool print_selection_only, + PrintSettings* print_settings) { + DCHECK(hdc); + DCHECK(print_settings); + + print_settings->set_printer_name(dev_mode.dmDeviceName); + print_settings->set_device_name(new_device_name); + print_settings->ranges = const_cast<PageRanges&>(new_ranges); + print_settings->set_landscape(dev_mode.dmOrientation == DMORIENT_LANDSCAPE); + print_settings->selection_only = print_selection_only; + + int dpi = GetDeviceCaps(hdc, LOGPIXELSX); + print_settings->set_dpi(dpi); + const int kAlphaCaps = SB_CONST_ALPHA | SB_PIXEL_ALPHA; + print_settings->set_supports_alpha_blend( + (GetDeviceCaps(hdc, SHADEBLENDCAPS) & kAlphaCaps) == kAlphaCaps); + // No printer device is known to advertise different dpi in X and Y axis; even + // the fax device using the 200x100 dpi setting. It's ought to break so many + // applications that it's not even needed to care about. WebKit doesn't + // support different dpi settings in X and Y axis. + DCHECK_EQ(dpi, GetDeviceCaps(hdc, LOGPIXELSY)); + + DCHECK_EQ(GetDeviceCaps(hdc, SCALINGFACTORX), 0); + DCHECK_EQ(GetDeviceCaps(hdc, SCALINGFACTORY), 0); + + // Initialize page_setup_device_units_. + gfx::Size physical_size_device_units(GetDeviceCaps(hdc, PHYSICALWIDTH), + GetDeviceCaps(hdc, PHYSICALHEIGHT)); + gfx::Rect printable_area_device_units(GetDeviceCaps(hdc, PHYSICALOFFSETX), + GetDeviceCaps(hdc, PHYSICALOFFSETY), + GetDeviceCaps(hdc, HORZRES), + GetDeviceCaps(hdc, VERTRES)); + + // Sanity check the printable_area: we've seen crashes caused by a printable + // area rect of 0, 0, 0, 0, so it seems some drivers don't set it. + if (printable_area_device_units.IsEmpty() || + !gfx::Rect(physical_size_device_units).Contains( + printable_area_device_units)) { + printable_area_device_units = gfx::Rect(physical_size_device_units); + } + + print_settings->SetPrinterPrintableArea(physical_size_device_units, + printable_area_device_units, + dpi); +} + +} // namespace printing diff --git a/chromium/printing/print_settings_initializer_win.h b/chromium/printing/print_settings_initializer_win.h new file mode 100644 index 00000000000..720abae7f79 --- /dev/null +++ b/chromium/printing/print_settings_initializer_win.h @@ -0,0 +1,36 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTING_SETTINGS_INITIALIZER_WIN_H_ +#define PRINTING_PRINTING_SETTINGS_INITIALIZER_WIN_H_ + +#include <string> + +#include "base/logging.h" +#include "printing/page_range.h" + +typedef struct HDC__* HDC; +typedef struct _devicemodeW DEVMODE; + +namespace printing { + +class PrintSettings; + +// Initializes a PrintSettings object from the provided device context. +class PRINTING_EXPORT PrintSettingsInitializerWin { + public: + static void InitPrintSettings(HDC hdc, + const DEVMODE& dev_mode, + const PageRanges& new_ranges, + const std::wstring& new_device_name, + bool print_selection_only, + PrintSettings* print_settings); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(PrintSettingsInitializerWin); +}; + +} // namespace printing + +#endif // PRINTING_PRINTING_SETTINGS_INITIALIZER_WIN_H_ diff --git a/chromium/printing/printed_document.cc b/chromium/printing/printed_document.cc new file mode 100644 index 00000000000..6e912a11be9 --- /dev/null +++ b/chromium/printing/printed_document.cc @@ -0,0 +1,235 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printed_document.h" + +#include <algorithm> +#include <set> +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/i18n/file_util_icu.h" +#include "base/i18n/time_formatting.h" +#include "base/lazy_instance.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "printing/page_number.h" +#include "printing/printed_page.h" +#include "printing/printed_pages_source.h" +#include "printing/units.h" +#include "skia/ext/platform_device.h" +#include "ui/base/text/text_elider.h" +#include "ui/gfx/font.h" + +namespace { + +struct PrintDebugDumpPath { + PrintDebugDumpPath() + : enabled(false) { + } + + bool enabled; + base::FilePath debug_dump_path; +}; + +base::LazyInstance<PrintDebugDumpPath> g_debug_dump_info = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace printing { + +PrintedDocument::PrintedDocument(const PrintSettings& settings, + PrintedPagesSource* source, + int cookie) + : mutable_(source), + immutable_(settings, source, cookie) { + + // Records the expected page count if a range is setup. + if (!settings.ranges.empty()) { + // If there is a range, set the number of page + for (unsigned i = 0; i < settings.ranges.size(); ++i) { + const PageRange& range = settings.ranges[i]; + mutable_.expected_page_count_ += range.to - range.from + 1; + } + } +} + +PrintedDocument::~PrintedDocument() { +} + +void PrintedDocument::SetPage(int page_number, + Metafile* metafile, + double shrink, + const gfx::Size& paper_size, + const gfx::Rect& page_rect) { + // Notice the page_number + 1, the reason is that this is the value that will + // be shown. Users dislike 0-based counting. + scoped_refptr<PrintedPage> page( + new PrintedPage(page_number + 1, + metafile, + paper_size, + page_rect, + shrink)); + { + base::AutoLock lock(lock_); + mutable_.pages_[page_number] = page; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + if (page_number < mutable_.first_page) + mutable_.first_page = page_number; +#endif + } + DebugDump(*page.get()); +} + +bool PrintedDocument::GetPage(int page_number, + scoped_refptr<PrintedPage>* page) { + base::AutoLock lock(lock_); + PrintedPages::const_iterator itr = mutable_.pages_.find(page_number); + if (itr != mutable_.pages_.end()) { + if (itr->second.get()) { + *page = itr->second; + return true; + } + } + return false; +} + +bool PrintedDocument::IsComplete() const { + base::AutoLock lock(lock_); + if (!mutable_.page_count_) + return false; + PageNumber page(immutable_.settings_, mutable_.page_count_); + if (page == PageNumber::npos()) + return false; + + for (; page != PageNumber::npos(); ++page) { +#if defined(OS_WIN) || defined(OS_MACOSX) + const bool metafile_must_be_valid = true; +#elif defined(OS_POSIX) + const bool metafile_must_be_valid = (page.ToInt() == mutable_.first_page); +#endif + PrintedPages::const_iterator itr = mutable_.pages_.find(page.ToInt()); + if (itr == mutable_.pages_.end() || !itr->second.get()) + return false; + if (metafile_must_be_valid && !itr->second->metafile()) + return false; + } + return true; +} + +void PrintedDocument::DisconnectSource() { + base::AutoLock lock(lock_); + mutable_.source_ = NULL; +} + +uint32 PrintedDocument::MemoryUsage() const { + std::vector< scoped_refptr<PrintedPage> > pages_copy; + { + base::AutoLock lock(lock_); + pages_copy.reserve(mutable_.pages_.size()); + PrintedPages::const_iterator end = mutable_.pages_.end(); + for (PrintedPages::const_iterator itr = mutable_.pages_.begin(); + itr != end; ++itr) { + if (itr->second.get()) { + pages_copy.push_back(itr->second); + } + } + } + uint32 total = 0; + for (size_t i = 0; i < pages_copy.size(); ++i) { + total += pages_copy[i]->metafile()->GetDataSize(); + } + return total; +} + +void PrintedDocument::set_page_count(int max_page) { + base::AutoLock lock(lock_); + DCHECK_EQ(0, mutable_.page_count_); + mutable_.page_count_ = max_page; + if (immutable_.settings_.ranges.empty()) { + mutable_.expected_page_count_ = max_page; + } else { + // If there is a range, don't bother since expected_page_count_ is already + // initialized. + DCHECK_NE(mutable_.expected_page_count_, 0); + } +} + +int PrintedDocument::page_count() const { + base::AutoLock lock(lock_); + return mutable_.page_count_; +} + +int PrintedDocument::expected_page_count() const { + base::AutoLock lock(lock_); + return mutable_.expected_page_count_; +} + +void PrintedDocument::DebugDump(const PrintedPage& page) { + if (!g_debug_dump_info.Get().enabled) + return; + + string16 filename; + filename += name(); + filename += ASCIIToUTF16("_"); + filename += ASCIIToUTF16(base::StringPrintf("%02d", page.page_number())); +#if defined(OS_WIN) + filename += ASCIIToUTF16("_.emf"); + page.metafile()->SaveTo( + g_debug_dump_info.Get().debug_dump_path.Append(filename)); +#else // OS_WIN + filename += ASCIIToUTF16("_.pdf"); + page.metafile()->SaveTo( + g_debug_dump_info.Get().debug_dump_path.Append(UTF16ToUTF8(filename))); +#endif // OS_WIN +} + +void PrintedDocument::set_debug_dump_path( + const base::FilePath& debug_dump_path) { + g_debug_dump_info.Get().enabled = !debug_dump_path.empty(); + g_debug_dump_info.Get().debug_dump_path = debug_dump_path; +} + +const base::FilePath& PrintedDocument::debug_dump_path() { + return g_debug_dump_info.Get().debug_dump_path; +} + +PrintedDocument::Mutable::Mutable(PrintedPagesSource* source) + : source_(source), + expected_page_count_(0), + page_count_(0) { +#if defined(OS_POSIX) && !defined(OS_MACOSX) + first_page = INT_MAX; +#endif +} + +PrintedDocument::Mutable::~Mutable() { +} + +PrintedDocument::Immutable::Immutable(const PrintSettings& settings, + PrintedPagesSource* source, + int cookie) + : settings_(settings), + source_message_loop_(base::MessageLoop::current()), + name_(source->RenderSourceName()), + cookie_(cookie) { +} + +PrintedDocument::Immutable::~Immutable() { +} + +#if defined(OS_POSIX) && defined(USE_AURA) +// This function is not used on aura linux/chromeos. +void PrintedDocument::RenderPrintedPage(const PrintedPage& page, + PrintingContext* context) const { +} +#endif + +} // namespace printing diff --git a/chromium/printing/printed_document.h b/chromium/printing/printed_document.h new file mode 100644 index 00000000000..4ebcfbf5bc6 --- /dev/null +++ b/chromium/printing/printed_document.h @@ -0,0 +1,180 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTED_DOCUMENT_H_ +#define PRINTING_PRINTED_DOCUMENT_H_ + +#include <map> + +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "base/synchronization/lock.h" +#include "printing/print_settings.h" +#include "ui/gfx/native_widget_types.h" + +namespace base { +class FilePath; +class MessageLoop; +} + +namespace printing { + +class Metafile; +class PrintedPage; +class PrintedPagesSource; +class PrintingContext; + +// A collection of rendered pages. The settings are immutable. If the print +// settings are changed, a new PrintedDocument must be created. +// Warning: May be accessed from many threads at the same time. Only one thread +// will have write access. Sensible functions are protected by a lock. +// Warning: Once a page is loaded, it cannot be replaced. Pages may be discarded +// under low memory conditions. +class PRINTING_EXPORT PrintedDocument + : public base::RefCountedThreadSafe<PrintedDocument> { + public: + // The cookie shall be unique and has a specific relationship with its + // originating source and settings. + PrintedDocument(const PrintSettings& settings, + PrintedPagesSource* source, + int cookie); + + // Sets a page's data. 0-based. Takes metafile ownership. + // Note: locks for a short amount of time. + void SetPage(int page_number, Metafile* metafile, double shrink, + const gfx::Size& paper_size, const gfx::Rect& page_rect); + + // Retrieves a page. If the page is not available right now, it + // requests to have this page be rendered and returns false. + // Note: locks for a short amount of time. + bool GetPage(int page_number, scoped_refptr<PrintedPage>* page); + + // Draws the page in the context. + // Note: locks for a short amount of time in debug only. +#if defined(OS_WIN) || defined(OS_MACOSX) && !defined(USE_AURA) + void RenderPrintedPage(const PrintedPage& page, + gfx::NativeDrawingContext context) const; +#elif defined(OS_POSIX) + void RenderPrintedPage(const PrintedPage& page, + PrintingContext* context) const; +#endif + + // Returns true if all the necessary pages for the settings are already + // rendered. + // Note: locks while parsing the whole tree. + bool IsComplete() const; + + // Disconnects the PrintedPage source (PrintedPagesSource). It is done when + // the source is being destroyed. + void DisconnectSource(); + + // Retrieves the current memory usage of the renderer pages. + // Note: locks for a short amount of time. + uint32 MemoryUsage() const; + + // Sets the number of pages in the document to be rendered. Can only be set + // once. + // Note: locks for a short amount of time. + void set_page_count(int max_page); + + // Number of pages in the document. Used for headers/footers. + // Note: locks for a short amount of time. + int page_count() const; + + // Returns the number of expected pages to be rendered. It is a non-linear + // series if settings().ranges is not empty. It is the same value as + // document_page_count() otherwise. + // Note: locks for a short amount of time. + int expected_page_count() const; + + // Getters. All these items are immutable hence thread-safe. + const PrintSettings& settings() const { return immutable_.settings_; } + const string16& name() const { return immutable_.name_; } + int cookie() const { return immutable_.cookie_; } + + // Sets a path where to dump printing output files for debugging. If never set + // no files are generated. + static void set_debug_dump_path(const base::FilePath& debug_dump_path); + + static const base::FilePath& debug_dump_path(); + + private: + friend class base::RefCountedThreadSafe<PrintedDocument>; + + virtual ~PrintedDocument(); + + // Array of data for each print previewed page. + typedef std::map<int, scoped_refptr<PrintedPage> > PrintedPages; + + // Contains all the mutable stuff. All this stuff MUST be accessed with the + // lock held. + struct Mutable { + explicit Mutable(PrintedPagesSource* source); + ~Mutable(); + + // Source that generates the PrintedPage's (i.e. a TabContents). It will be + // set back to NULL if the source is deleted before this object. + PrintedPagesSource* source_; + + // Contains the pages' representation. This is a collection of PrintedPage. + // Warning: Lock must be held when accessing this member. + PrintedPages pages_; + + // Number of expected pages to be rendered. + // Warning: Lock must be held when accessing this member. + int expected_page_count_; + + // The total number of pages in the document. + int page_count_; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + // Page number of the first page. + int first_page; +#endif + }; + + // Contains all the immutable stuff. All this stuff can be accessed without + // any lock held. This is because it can't be changed after the object's + // construction. + struct Immutable { + Immutable(const PrintSettings& settings, PrintedPagesSource* source, + int cookie); + ~Immutable(); + + // Print settings used to generate this document. Immutable. + PrintSettings settings_; + + // Native thread for the render source. + base::MessageLoop* source_message_loop_; + + // Document name. Immutable. + string16 name_; + + // Cookie to uniquely identify this document. It is used to make sure that a + // PrintedPage is correctly belonging to the PrintedDocument. Since + // PrintedPage generation is completely asynchronous, it could be easy to + // mess up and send the page to the wrong document. It can be viewed as a + // simpler hash of PrintSettings since a new document is made each time the + // print settings change. + int cookie_; + }; + + void DebugDump(const PrintedPage& page); + + // All writable data member access must be guarded by this lock. Needs to be + // mutable since it can be acquired from const member functions. + mutable base::Lock lock_; + + // All the mutable members. + Mutable mutable_; + + // All the immutable members. + const Immutable immutable_; + + DISALLOW_COPY_AND_ASSIGN(PrintedDocument); +}; + +} // namespace printing + +#endif // PRINTING_PRINTED_DOCUMENT_H_ diff --git a/chromium/printing/printed_document_gtk.cc b/chromium/printing/printed_document_gtk.cc new file mode 100644 index 00000000000..0970b251e2d --- /dev/null +++ b/chromium/printing/printed_document_gtk.cc @@ -0,0 +1,35 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printed_document.h" + +#include "base/logging.h" +#include "printing/page_number.h" +#include "printing/printed_page.h" +#include "printing/printing_context_gtk.h" + +namespace printing { + +void PrintedDocument::RenderPrintedPage( + const PrintedPage& page, PrintingContext* context) const { +#ifndef NDEBUG + { + // Make sure the page is from our list. + base::AutoLock lock(lock_); + DCHECK(&page == mutable_.pages_.find(page.page_number() - 1)->second.get()); + } +#endif + + DCHECK(context); + + { + base::AutoLock lock(lock_); + if (page.page_number() - 1 == mutable_.first_page) { + reinterpret_cast<PrintingContextGtk*>(context)->PrintDocument( + page.metafile()); + } + } +} + +} // namespace printing diff --git a/chromium/printing/printed_document_mac.cc b/chromium/printing/printed_document_mac.cc new file mode 100644 index 00000000000..cc671e13d2d --- /dev/null +++ b/chromium/printing/printed_document_mac.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printed_document.h" + +#import <ApplicationServices/ApplicationServices.h> +#import <CoreFoundation/CoreFoundation.h> + +#include "base/logging.h" +#include "printing/page_number.h" +#include "printing/printed_page.h" + +namespace printing { + +void PrintedDocument::RenderPrintedPage( + const PrintedPage& page, gfx::NativeDrawingContext context) const { +#ifndef NDEBUG + { + // Make sure the page is from our list. + base::AutoLock lock(lock_); + DCHECK(&page == mutable_.pages_.find(page.page_number() - 1)->second.get()); + } +#endif + + DCHECK(context); + + const PageSetup& page_setup(immutable_.settings_.page_setup_device_units()); + gfx::Rect content_area; + page.GetCenteredPageContentRect(page_setup.physical_size(), &content_area); + + const Metafile* metafile = page.metafile(); + // Each Metafile is a one-page PDF, and pages use 1-based indexing. + const int page_number = 1; + struct Metafile::MacRenderPageParams params; + params.autorotate = true; + metafile->RenderPage(page_number, context, content_area.ToCGRect(), params); +} + +} // namespace printing diff --git a/chromium/printing/printed_document_win.cc b/chromium/printing/printed_document_win.cc new file mode 100644 index 00000000000..392679384c5 --- /dev/null +++ b/chromium/printing/printed_document_win.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printed_document.h" + +#include "base/logging.h" +#include "printing/page_number.h" +#include "printing/printed_pages_source.h" +#include "printing/printed_page.h" +#include "printing/units.h" +#include "skia/ext/platform_device.h" + +namespace { + +void SimpleModifyWorldTransform(HDC context, + int offset_x, + int offset_y, + double shrink_factor) { + XFORM xform = { 0 }; + xform.eDx = static_cast<float>(offset_x); + xform.eDy = static_cast<float>(offset_y); + xform.eM11 = xform.eM22 = static_cast<float>(1. / shrink_factor); + BOOL res = ModifyWorldTransform(context, &xform, MWT_LEFTMULTIPLY); + DCHECK_NE(res, 0); +} + +void DrawRect(HDC context, gfx::Rect rect) { + Rectangle(context, rect.x(), rect.y(), rect.right(), rect.bottom()); +} + +} // namespace + +namespace printing { + +void PrintedDocument::RenderPrintedPage( + const PrintedPage& page, gfx::NativeDrawingContext context) const { +#ifndef NDEBUG + { + // Make sure the page is from our list. + base::AutoLock lock(lock_); + DCHECK(&page == mutable_.pages_.find(page.page_number() - 1)->second.get()); + } +#endif + + DCHECK(context); + + const PageSetup& page_setup(immutable_.settings_.page_setup_device_units()); + gfx::Rect content_area; + page.GetCenteredPageContentRect(page_setup.physical_size(), &content_area); + + // Save the state to make sure the context this function call does not modify + // the device context. + int saved_state = SaveDC(context); + DCHECK_NE(saved_state, 0); + skia::InitializeDC(context); + { + // Save the state (again) to apply the necessary world transformation. + int saved_state = SaveDC(context); + DCHECK_NE(saved_state, 0); + + // Setup the matrix to translate and scale to the right place. Take in + // account the actual shrinking factor. + // Note that the printing output is relative to printable area of the page. + // That is 0,0 is offset by PHYSICALOFFSETX/Y from the page. + SimpleModifyWorldTransform( + context, + content_area.x() - page_setup.printable_area().x(), + content_area.y() - page_setup.printable_area().y(), + page.shrink_factor()); + + if (!page.metafile()->SafePlayback(context)) { + NOTREACHED(); + } + + BOOL res = RestoreDC(context, saved_state); + DCHECK_NE(res, 0); + } + + int res = RestoreDC(context, saved_state); + DCHECK_NE(res, 0); +} + +} // namespace printing diff --git a/chromium/printing/printed_page.cc b/chromium/printing/printed_page.cc new file mode 100644 index 00000000000..af7b7388f86 --- /dev/null +++ b/chromium/printing/printed_page.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printed_page.h" + +namespace printing { + +PrintedPage::PrintedPage(int page_number, + Metafile* metafile, + const gfx::Size& page_size, + const gfx::Rect& page_content_rect, + double shrink_factor) + : page_number_(page_number), + metafile_(metafile), + page_size_(page_size), + page_content_rect_(page_content_rect), + shrink_factor_(shrink_factor) { +} + +PrintedPage::~PrintedPage() { +} + +const Metafile* PrintedPage::metafile() const { + return metafile_.get(); +} + +void PrintedPage::GetCenteredPageContentRect( + const gfx::Size& paper_size, gfx::Rect* content_rect) const { + *content_rect = page_content_rect(); + if (paper_size.width() > page_size().width()) { + int diff = paper_size.width() - page_size().width(); + content_rect->set_x(content_rect->x() + diff / 2); + } + if (paper_size.height() > page_size().height()) { + int diff = paper_size.height() - page_size().height(); + content_rect->set_y(content_rect->y() + diff / 2); + } +} + +} // namespace printing diff --git a/chromium/printing/printed_page.h b/chromium/printing/printed_page.h new file mode 100644 index 00000000000..40e96e041d3 --- /dev/null +++ b/chromium/printing/printed_page.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTED_PAGE_H_ +#define PRINTING_PRINTED_PAGE_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "printing/metafile.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace printing { + +// Contains the data to reproduce a printed page, either on screen or on +// paper. Once created, this object is immutable. It has no reference to the +// PrintedDocument containing this page. +// Note: May be accessed from many threads at the same time. This is an non +// issue since this object is immutable. The reason is that a page may be +// printed and be displayed at the same time. +class PRINTING_EXPORT PrintedPage + : public base::RefCountedThreadSafe<PrintedPage> { + public: + PrintedPage(int page_number, + Metafile* metafile, + const gfx::Size& page_size, + const gfx::Rect& page_content_rect, + double shrink_factor); + + // Getters + int page_number() const { return page_number_; } + const Metafile* metafile() const; + const gfx::Size& page_size() const { return page_size_; } + const gfx::Rect& page_content_rect() const { return page_content_rect_; } + double shrink_factor() const { return shrink_factor_; } + + // Get page content rect adjusted based on + // http://dev.w3.org/csswg/css3-page/#positioning-page-box + void GetCenteredPageContentRect(const gfx::Size& paper_size, + gfx::Rect* content_rect) const; + + private: + friend class base::RefCountedThreadSafe<PrintedPage>; + + ~PrintedPage(); + + // Page number inside the printed document. + const int page_number_; + + // Actual paint data. + const scoped_ptr<Metafile> metafile_; + + // The physical page size. To support multiple page formats inside on print + // job. + const gfx::Size page_size_; + + // The printable area of the page. + const gfx::Rect page_content_rect_; + + // Shrink done in comparison to desired_dpi. + double shrink_factor_; + + DISALLOW_COPY_AND_ASSIGN(PrintedPage); +}; + +} // namespace printing + +#endif // PRINTING_PRINTED_PAGE_H_ diff --git a/chromium/printing/printed_page_unittest.cc b/chromium/printing/printed_page_unittest.cc new file mode 100644 index 00000000000..9cce52a1cb8 --- /dev/null +++ b/chromium/printing/printed_page_unittest.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printed_page.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace printing { + +TEST(PrintedPageTest, GetCenteredPageContentRect) { + scoped_refptr<PrintedPage> page; + gfx::Rect page_content; + + // No centering. + page = new PrintedPage(1, + NULL, + gfx::Size(1200, 1200), + gfx::Rect(0, 0, 400, 1100), + 0.2f); + page->GetCenteredPageContentRect(gfx::Size(1000, 1000), &page_content); + EXPECT_EQ(0, page_content.x()); + EXPECT_EQ(0, page_content.y()); + EXPECT_EQ(400, page_content.width()); + EXPECT_EQ(1100, page_content.height()); + EXPECT_EQ(0.2f, page->shrink_factor()); + + // X centered. + page = new PrintedPage(1, + NULL, + gfx::Size(500, 1200), + gfx::Rect(0, 0, 400, 1100), + 0.8f); + page->GetCenteredPageContentRect(gfx::Size(1000, 1000), &page_content); + EXPECT_EQ(250, page_content.x()); + EXPECT_EQ(0, page_content.y()); + EXPECT_EQ(400, page_content.width()); + EXPECT_EQ(1100, page_content.height()); + EXPECT_EQ(0.8f, page->shrink_factor()); + + // Y centered. + page = new PrintedPage(1, + NULL, + gfx::Size(1200, 500), + gfx::Rect(0, 0, 400, 1100), + 1.0f); + page->GetCenteredPageContentRect(gfx::Size(1000, 1000), &page_content); + EXPECT_EQ(0, page_content.x()); + EXPECT_EQ(250, page_content.y()); + EXPECT_EQ(400, page_content.width()); + EXPECT_EQ(1100, page_content.height()); + EXPECT_EQ(1.0f, page->shrink_factor()); + + // Both X and Y centered. + page = new PrintedPage(1, + NULL, + gfx::Size(500, 500), + gfx::Rect(0, 0, 400, 1100), + 0.3f); + page->GetCenteredPageContentRect(gfx::Size(1000, 1000), &page_content); + EXPECT_EQ(250, page_content.x()); + EXPECT_EQ(250, page_content.y()); + EXPECT_EQ(400, page_content.width()); + EXPECT_EQ(1100, page_content.height()); + EXPECT_EQ(0.3f, page->shrink_factor()); +} + +} // namespace printing diff --git a/chromium/printing/printed_pages_source.h b/chromium/printing/printed_pages_source.h new file mode 100644 index 00000000000..6da9ddb5835 --- /dev/null +++ b/chromium/printing/printed_pages_source.h @@ -0,0 +1,24 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTED_PAGES_SOURCE_H_ +#define PRINTING_PRINTED_PAGES_SOURCE_H_ + +#include "base/strings/string16.h" + +namespace printing { + +// Source of printed pages. +class PrintedPagesSource { + public: + // Returns the document title. + virtual string16 RenderSourceName() = 0; + + protected: + virtual ~PrintedPagesSource() {} +}; + +} // namespace printing + +#endif // PRINTING_PRINTED_PAGES_SOURCE_H_ diff --git a/chromium/printing/printing.gyp b/chromium/printing/printing.gyp new file mode 100644 index 00000000000..b3bbf2b04bb --- /dev/null +++ b/chromium/printing/printing.gyp @@ -0,0 +1,329 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [ + { + 'target_name': 'printing', + 'type': '<(component)', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:base_i18n', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../skia/skia.gyp:skia', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', + '../ui/ui.gyp:ui', + '../url/url.gyp:url_lib', + ], + 'defines': [ + 'PRINTING_IMPLEMENTATION', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'backend/print_backend.cc', + 'backend/print_backend.h', + 'backend/print_backend_consts.cc', + 'backend/print_backend_consts.h', + 'backend/print_backend_dummy.cc', + 'backend/printing_info_win.cc', + 'backend/printing_info_win.h', + 'emf_win.cc', + 'emf_win.h', + 'image.cc', + 'image_linux.cc', + 'image_mac.cc', + 'image_win.cc', + 'image.h', + 'metafile.h', + 'metafile_impl.h', + 'metafile_skia_wrapper.h', + 'metafile_skia_wrapper.cc', + 'page_number.cc', + 'page_number.h', + 'page_range.cc', + 'page_range.h', + 'page_setup.cc', + 'page_setup.h', + 'page_size_margins.cc', + 'page_size_margins.h', + 'pdf_metafile_cg_mac.cc', + 'pdf_metafile_cg_mac.h', + 'pdf_metafile_skia.h', + 'pdf_metafile_skia.cc', + 'print_destination_interface.h', + 'print_destination_none.cc', + 'print_destination_win.cc', + 'printed_document_gtk.cc', + 'printed_document.cc', + 'printed_document.h', + 'printed_document_mac.cc', + 'printed_document_win.cc', + 'printed_page.cc', + 'printed_page.h', + 'printed_pages_source.h', + 'printing_context.cc', + 'printing_context.h', + 'print_dialog_gtk_interface.h', + 'print_job_constants.cc', + 'print_job_constants.h', + 'print_settings.cc', + 'print_settings.h', + 'print_settings_initializer.cc', + 'print_settings_initializer.h', + 'print_settings_initializer_gtk.cc', + 'print_settings_initializer_gtk.h', + 'print_settings_initializer_mac.cc', + 'print_settings_initializer_mac.h', + 'print_settings_initializer_win.cc', + 'print_settings_initializer_win.h', + 'units.cc', + 'units.h', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, + 'conditions': [ + ['enable_printing==0', { + 'sources/': [ + ['exclude', '.'], + ], + }], + ['use_aura==1', { + 'dependencies': [ + '<(DEPTH)/ui/aura/aura.gyp:aura', + ], + }], + ['toolkit_uses_gtk == 0',{ + 'sources/': [['exclude', '_cairo\\.cc$']] + }], + ['OS!="mac"', {'sources/': [['exclude', '_mac\\.(cc|mm?)$']]}], + ['OS!="win"', {'sources/': [['exclude', '_win\\.cc$']] + }, { # else: OS=="win" + 'sources/': [['exclude', '_posix\\.cc$']] + }], + ['toolkit_uses_gtk == 1', { + 'dependencies': [ + # For FT_Init_FreeType and friends. + '../build/linux/system.gyp:freetype2', + '../build/linux/system.gyp:gtk', + '../build/linux/system.gyp:gtkprint', + ], + }], + # Mac-Aura does not support printing. + ['OS=="mac" and use_aura==1',{ + 'sources!': [ + 'printed_document_mac.cc', + 'printing_context_mac.mm', + 'printing_context_mac.h', + ], + }], + ['OS=="mac" and use_aura==0',{ + 'sources': [ + 'printing_context_mac.mm', + 'printing_context_mac.h', + ], + }], + ['OS=="win"', { + 'dependencies': [ + '../win8/win8.gyp:win8_util', + ], + 'conditions': [ + ['use_aura==1', { + 'dependencies': [ + '<(DEPTH)/ui/aura/aura.gyp:aura', + ], + }]], + 'defines': [ + # PRINT_BACKEND_AVAILABLE disables the default dummy implementation + # of the print backend and enables a custom implementation instead. + 'PRINT_BACKEND_AVAILABLE', + ], + 'sources': [ + 'backend/win_helper.cc', + 'backend/win_helper.h', + 'backend/print_backend_win.cc', + 'printing_context_win.cc', + 'printing_context_win.h', + ], + 'sources!': [ + 'print_destination_none.cc', + ], + }], + ['chromeos==1 or (use_aura==1 and OS!="win")',{ + 'sources': [ + 'printing_context_no_system_dialog.cc', + 'printing_context_no_system_dialog.h', + ], + }], + ['use_cups==1', { + 'dependencies': [ + 'cups', + ], + 'variables': { + 'cups_version': '<!(cups-config --api-version)', + }, + 'conditions': [ + ['OS!="mac"', { + 'dependencies': [ + '../build/linux/system.gyp:libgcrypt', + ], + }], + ['cups_version in ["1.6", "1.7"]', { + 'cflags': [ + # CUPS 1.6 deprecated the PPD APIs, but we will stay with this + # API for now as supported Linux and Mac OS'es are still using + # older versions of CUPS. More info: crbug.com/226176 + '-Wno-deprecated-declarations', + # CUPS 1.7 deprecates httpConnectEncrypt(), see the mac section + # below. + ], + }], + ['OS=="mac" and mac_sdk=="10.9"', { + # The 10.9 SDK includes cups 1.7, which deprecates + # httpConnectEncrypt() in favor of httpConnect2(). hhttpConnect2() + # is new in 1.7, so it doesn't exist on OS X 10.6-10.8 and we + # can't use it until 10.9 is our minimum system version. + # (cups_version isn't reliable on OS X, so key the check off of + # mac_sdk). + 'xcode_settings': { + 'WARNING_CFLAGS': [ + '-Wno-deprecated-declarations', + ], + }, + }], + ], + 'defines': [ + # PRINT_BACKEND_AVAILABLE disables the default dummy implementation + # of the print backend and enables a custom implementation instead. + 'PRINT_BACKEND_AVAILABLE', + ], + 'sources': [ + 'backend/cups_helper.cc', + 'backend/cups_helper.h', + 'backend/print_backend_cups.cc', + ], + }], + ['OS=="linux" and chromeos==1', { + 'defines': [ + # PRINT_BACKEND_AVAILABLE disables the default dummy implementation + # of the print backend and enables a custom implementation instead. + 'PRINT_BACKEND_AVAILABLE', + ], + 'sources': [ + 'backend/print_backend_chromeos.cc', + ], + }], + ['toolkit_uses_gtk==1 and chromeos==0', { + 'sources': [ + 'printing_context_gtk.cc', + 'printing_context_gtk.h', + ], + }], + ], + }, + { + 'target_name': 'printing_unittests', + 'type': 'executable', + 'dependencies': [ + 'printing', + '../testing/gtest.gyp:gtest', + '../base/base.gyp:test_support_base', + '../ui/ui.gyp:ui', + ], + 'sources': [ + 'backend/print_backend_unittest.cc', + 'emf_win_unittest.cc', + 'printing_test.h', + 'page_number_unittest.cc', + 'page_range_unittest.cc', + 'page_setup_unittest.cc', + 'pdf_metafile_cg_mac_unittest.cc', + 'printed_page_unittest.cc', + 'run_all_unittests.cc', + 'units_unittest.cc', + ], + 'conditions': [ + ['enable_printing==0', { + 'sources/': [ + ['exclude', '.'], + ['include', 'run_all_unittests.cc'], + ], + }], + ['toolkit_uses_gtk == 0', {'sources/': [['exclude', '_gtk_unittest\\.cc$']]}], + ['OS!="mac"', {'sources/': [['exclude', '_mac_unittest\\.(cc|mm?)$']]}], + ['OS!="win"', {'sources/': [['exclude', '_win_unittest\\.cc$']]}], + ['OS=="win" and use_aura == 0', { + 'sources': [ + 'printing_context_win_unittest.cc', + ] + }], + ['use_cups==1', { + 'defines': [ + 'USE_CUPS', + ], + 'sources': [ + 'backend/cups_helper_unittest.cc', + ], + }], + ['toolkit_uses_gtk == 1', { + 'dependencies': [ + '../build/linux/system.gyp:gtk', + ], + }], + [ 'os_posix == 1 and OS != "mac" and OS != "android" and OS != "ios"', { + 'conditions': [ + ['linux_use_tcmalloc == 1', { + 'dependencies': [ + '../base/allocator/allocator.gyp:allocator', + ], + }], + ], + }], + ], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [ 4267, ], + }, + { + 'target_name': 'cups', + 'type': 'none', + 'conditions': [ + ['use_cups==1', { + 'direct_dependent_settings': { + 'defines': [ + 'USE_CUPS', + ], + 'conditions': [ + ['OS=="mac"', { + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/usr/lib/libcups.dylib', + ] + }, + }, { + 'link_settings': { + 'libraries': [ + '<!@(python cups_config_helper.py --libs)', + ], + }, + }], + [ 'os_bsd==1', { + 'cflags': [ + '<!@(python cups_config_helper.py --cflags)', + ], + }], + ], + }, + }], + ], + }, + ], +} diff --git a/chromium/printing/printing_context.cc b/chromium/printing/printing_context.cc new file mode 100644 index 00000000000..98ea11cba79 --- /dev/null +++ b/chromium/printing/printing_context.cc @@ -0,0 +1,87 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printing_context.h" + +#include "base/logging.h" +#include "base/values.h" +#include "printing/page_setup.h" +#include "printing/page_size_margins.h" +#include "printing/print_settings_initializer.h" + +namespace printing { + +PrintingContext::PrintingContext(const std::string& app_locale) + : dialog_box_dismissed_(false), + in_print_job_(false), + abort_printing_(false), + app_locale_(app_locale) { +} + +PrintingContext::~PrintingContext() { +} + +void PrintingContext::set_margin_type(MarginType type) { + DCHECK(type != CUSTOM_MARGINS); + settings_.margin_type = type; +} + +void PrintingContext::ResetSettings() { + ReleaseContext(); + + settings_.Clear(); + + in_print_job_ = false; + dialog_box_dismissed_ = false; + abort_printing_ = false; +} + +PrintingContext::Result PrintingContext::OnError() { + ResetSettings(); + return abort_printing_ ? CANCEL : FAILED; +} + +PrintingContext::Result PrintingContext::UpdatePrintSettings( + const base::DictionaryValue& job_settings, + const PageRanges& ranges) { + ResetSettings(); + + job_settings.GetBoolean(kSettingHeaderFooterEnabled, + &settings_.display_header_footer); + + int margin_type = DEFAULT_MARGINS; + if (!job_settings.GetInteger(kSettingMarginsType, &margin_type) || + (margin_type != DEFAULT_MARGINS && + margin_type != NO_MARGINS && + margin_type != CUSTOM_MARGINS && + margin_type != PRINTABLE_AREA_MARGINS)) { + margin_type = DEFAULT_MARGINS; + } + settings_.margin_type = static_cast<MarginType>(margin_type); + + if (margin_type == CUSTOM_MARGINS) { + printing::PageSizeMargins page_size_margins; + GetCustomMarginsFromJobSettings(job_settings, &page_size_margins); + + PageMargins margins_in_points; + margins_in_points.Clear(); + margins_in_points.top = page_size_margins.margin_top; + margins_in_points.bottom = page_size_margins.margin_bottom; + margins_in_points.left = page_size_margins.margin_left; + margins_in_points.right = page_size_margins.margin_right; + + settings_.SetCustomMargins(margins_in_points); + } + + PrintingContext::Result result = UpdatePrinterSettings(job_settings, ranges); + PrintSettingsInitializer::InitHeaderFooterStrings(job_settings, &settings_); + + job_settings.GetBoolean(kSettingShouldPrintBackgrounds, + &settings_.should_print_backgrounds); + job_settings.GetBoolean(kSettingShouldPrintSelectionOnly, + &settings_.selection_only); + return result; +} + +} // namespace printing diff --git a/chromium/printing/printing_context.h b/chromium/printing/printing_context.h new file mode 100644 index 00000000000..6ad1e8d3f63 --- /dev/null +++ b/chromium/printing/printing_context.h @@ -0,0 +1,136 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTING_CONTEXT_H_ +#define PRINTING_PRINTING_CONTEXT_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/strings/string16.h" +#include "printing/print_settings.h" +#include "ui/gfx/native_widget_types.h" + +namespace base { +class DictionaryValue; +} + +namespace printing { + +// An abstraction of a printer context, implemented by objects that describe the +// user selected printing context. This includes the OS-dependent UI to ask the +// user about the print settings. Concrete implementations directly talk to the +// printer and manage the document and page breaks. +class PRINTING_EXPORT PrintingContext { + public: + // Tri-state result for user behavior-dependent functions. + enum Result { + OK, + CANCEL, + FAILED, + }; + + virtual ~PrintingContext(); + + // Callback of AskUserForSettings, used to notify the PrintJobWorker when + // print settings are available. + typedef base::Callback<void(Result)> PrintSettingsCallback; + + // Asks the user what printer and format should be used to print. Updates the + // context with the select device settings. The result of the call is returned + // in the callback. This is necessary for Linux, which only has an + // asynchronous printing API. + virtual void AskUserForSettings(gfx::NativeView parent_view, + int max_pages, + bool has_selection, + const PrintSettingsCallback& callback) = 0; + + // Selects the user's default printer and format. Updates the context with the + // default device settings. + virtual Result UseDefaultSettings() = 0; + + // Updates printer related settings. |job_settings| contains all print job + // settings information. |ranges| has the new page range settings. + virtual Result UpdatePrinterSettings( + const base::DictionaryValue& job_settings, + const PageRanges& ranges) = 0; + + // Updates Print Settings. |job_settings| contains all print job + // settings information. |ranges| has the new page range settings. + Result UpdatePrintSettings(const base::DictionaryValue& job_settings, + const PageRanges& ranges); + + // Initializes with predefined settings. + virtual Result InitWithSettings(const PrintSettings& settings) = 0; + + // Does platform specific setup of the printer before the printing. Signal the + // printer that a document is about to be spooled. + // Warning: This function enters a message loop. That may cause side effects + // like IPC message processing! Some printers have side-effects on this call + // like virtual printers that ask the user for the path of the saved document; + // for example a PDF printer. + virtual Result NewDocument(const string16& document_name) = 0; + + // Starts a new page. + virtual Result NewPage() = 0; + + // Closes the printed page. + virtual Result PageDone() = 0; + + // Closes the printing job. After this call the object is ready to start a new + // document. + virtual Result DocumentDone() = 0; + + // Cancels printing. Can be used in a multi-threaded context. Takes effect + // immediately. + virtual void Cancel() = 0; + + // Releases the native printing context. + virtual void ReleaseContext() = 0; + + // Returns the native context used to print. + virtual gfx::NativeDrawingContext context() const = 0; + + // Creates an instance of this object. Implementers of this interface should + // implement this method to create an object of their implementation. The + // caller owns the returned object. + static PrintingContext* Create(const std::string& app_locale); + + void set_margin_type(MarginType type); + + const PrintSettings& settings() const { + return settings_; + } + + protected: + explicit PrintingContext(const std::string& app_locale); + + // Reinitializes the settings for object reuse. + void ResetSettings(); + + // Does bookkeeping when an error occurs. + PrintingContext::Result OnError(); + + // Complete print context settings. + PrintSettings settings_; + + // The dialog box has been dismissed. + volatile bool dialog_box_dismissed_; + + // Is a print job being done. + volatile bool in_print_job_; + + // Did the user cancel the print job. + volatile bool abort_printing_; + + // The application locale. + std::string app_locale_; + + DISALLOW_COPY_AND_ASSIGN(PrintingContext); +}; + +} // namespace printing + +#endif // PRINTING_PRINTING_CONTEXT_H_ diff --git a/chromium/printing/printing_context_gtk.cc b/chromium/printing/printing_context_gtk.cc new file mode 100644 index 00000000000..4bfa666b236 --- /dev/null +++ b/chromium/printing/printing_context_gtk.cc @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printing_context_gtk.h" + +#include <gtk/gtk.h> +#include <gtk/gtkunixprint.h> + +#include "base/logging.h" +#include "base/values.h" +#include "printing/metafile.h" +#include "printing/print_dialog_gtk_interface.h" +#include "printing/print_job_constants.h" +#include "printing/units.h" + +namespace { + +// Function pointer for creating print dialogs. |callback| is only used when +// |show_dialog| is true. +printing::PrintDialogGtkInterface* (*create_dialog_func_)( + printing::PrintingContextGtk* context) = NULL; + +} // namespace + +namespace printing { + +// static +PrintingContext* PrintingContext::Create(const std::string& app_locale) { + return static_cast<PrintingContext*>(new PrintingContextGtk(app_locale)); +} + +PrintingContextGtk::PrintingContextGtk(const std::string& app_locale) + : PrintingContext(app_locale), + print_dialog_(NULL) { +} + +PrintingContextGtk::~PrintingContextGtk() { + ReleaseContext(); + + if (print_dialog_) + print_dialog_->ReleaseDialog(); +} + +// static +void PrintingContextGtk::SetCreatePrintDialogFunction( + PrintDialogGtkInterface* (*create_dialog_func)( + PrintingContextGtk* context)) { + DCHECK(create_dialog_func); + DCHECK(!create_dialog_func_); + create_dialog_func_ = create_dialog_func; +} + +void PrintingContextGtk::PrintDocument(const Metafile* metafile) { + DCHECK(print_dialog_); + DCHECK(metafile); + print_dialog_->PrintDocument(metafile, document_name_); +} + +void PrintingContextGtk::AskUserForSettings( + gfx::NativeView parent_view, + int max_pages, + bool has_selection, + const PrintSettingsCallback& callback) { + print_dialog_->ShowDialog(parent_view, has_selection, callback); +} + +PrintingContext::Result PrintingContextGtk::UseDefaultSettings() { + DCHECK(!in_print_job_); + + ResetSettings(); + if (!print_dialog_) { + print_dialog_ = create_dialog_func_(this); + print_dialog_->AddRefToDialog(); + } + print_dialog_->UseDefaultSettings(); + + return OK; +} + +PrintingContext::Result PrintingContextGtk::UpdatePrinterSettings( + const DictionaryValue& job_settings, const PageRanges& ranges) { + DCHECK(!in_print_job_); + + if (!print_dialog_) { + print_dialog_ = create_dialog_func_(this); + print_dialog_->AddRefToDialog(); + } + + if (!print_dialog_->UpdateSettings(job_settings, ranges, &settings_)) + return OnError(); + + return OK; +} + +PrintingContext::Result PrintingContextGtk::InitWithSettings( + const PrintSettings& settings) { + DCHECK(!in_print_job_); + + settings_ = settings; + + return OK; +} + +PrintingContext::Result PrintingContextGtk::NewDocument( + const string16& document_name) { + DCHECK(!in_print_job_); + in_print_job_ = true; + + document_name_ = document_name; + + return OK; +} + +PrintingContext::Result PrintingContextGtk::NewPage() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + // Intentional No-op. + + return OK; +} + +PrintingContext::Result PrintingContextGtk::PageDone() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + // Intentional No-op. + + return OK; +} + +PrintingContext::Result PrintingContextGtk::DocumentDone() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + ResetSettings(); + return OK; +} + +void PrintingContextGtk::Cancel() { + abort_printing_ = true; + in_print_job_ = false; +} + +void PrintingContextGtk::ReleaseContext() { + // Intentional No-op. +} + +gfx::NativeDrawingContext PrintingContextGtk::context() const { + // Intentional No-op. + return NULL; +} + +} // namespace printing + diff --git a/chromium/printing/printing_context_gtk.h b/chromium/printing/printing_context_gtk.h new file mode 100644 index 00000000000..8d671aec482 --- /dev/null +++ b/chromium/printing/printing_context_gtk.h @@ -0,0 +1,63 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTING_CONTEXT_GTK_H_ +#define PRINTING_PRINTING_CONTEXT_GTK_H_ + +#include <string> + +#include "printing/printing_context.h" + +namespace base { +class DictionaryValue; +} + +namespace printing { + +class Metafile; +class PrintDialogGtkInterface; + +class PRINTING_EXPORT PrintingContextGtk : public PrintingContext { + public: + explicit PrintingContextGtk(const std::string& app_locale); + virtual ~PrintingContextGtk(); + + // Sets the function that creates the print dialog. + static void SetCreatePrintDialogFunction( + PrintDialogGtkInterface* (*create_dialog_func)( + PrintingContextGtk* context)); + + // Prints the document contained in |metafile|. + void PrintDocument(const Metafile* metafile); + + // PrintingContext implementation. + virtual void AskUserForSettings( + gfx::NativeView parent_view, + int max_pages, + bool has_selection, + const PrintSettingsCallback& callback) OVERRIDE; + virtual Result UseDefaultSettings() OVERRIDE; + virtual Result UpdatePrinterSettings( + const base::DictionaryValue& job_settings, + const PageRanges& ranges) OVERRIDE; + virtual Result InitWithSettings(const PrintSettings& settings) OVERRIDE; + virtual Result NewDocument(const string16& document_name) OVERRIDE; + virtual Result NewPage() OVERRIDE; + virtual Result PageDone() OVERRIDE; + virtual Result DocumentDone() OVERRIDE; + virtual void Cancel() OVERRIDE; + virtual void ReleaseContext() OVERRIDE; + virtual gfx::NativeDrawingContext context() const OVERRIDE; + + private: + string16 document_name_; + PrintDialogGtkInterface* print_dialog_; + + DISALLOW_COPY_AND_ASSIGN(PrintingContextGtk); +}; + +} // namespace printing + +#endif // PRINTING_PRINTING_CONTEXT_GTK_H_ + diff --git a/chromium/printing/printing_context_mac.h b/chromium/printing/printing_context_mac.h new file mode 100644 index 00000000000..c0edc323db8 --- /dev/null +++ b/chromium/printing/printing_context_mac.h @@ -0,0 +1,101 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTING_CONTEXT_MAC_H_ +#define PRINTING_PRINTING_CONTEXT_MAC_H_ + +#include <string> + +#include "base/mac/scoped_nsobject.h" +#include "printing/print_job_constants.h" +#include "printing/printing_context.h" + +#ifdef __OBJC__ +@class NSPrintInfo; +#else +class NSPrintInfo; +#endif // __OBJC__ + +namespace printing { + +class PRINTING_EXPORT PrintingContextMac : public PrintingContext { + public: + explicit PrintingContextMac(const std::string& app_locale); + virtual ~PrintingContextMac(); + + // PrintingContext implementation. + virtual void AskUserForSettings( + gfx::NativeView parent_view, + int max_pages, + bool has_selection, + const PrintSettingsCallback& callback) OVERRIDE; + virtual Result UseDefaultSettings() OVERRIDE; + virtual Result UpdatePrinterSettings( + const base::DictionaryValue& job_settings, + const PageRanges& ranges) OVERRIDE; + virtual Result InitWithSettings(const PrintSettings& settings) OVERRIDE; + virtual Result NewDocument(const string16& document_name) OVERRIDE; + virtual Result NewPage() OVERRIDE; + virtual Result PageDone() OVERRIDE; + virtual Result DocumentDone() OVERRIDE; + virtual void Cancel() OVERRIDE; + virtual void ReleaseContext() OVERRIDE; + virtual gfx::NativeDrawingContext context() const OVERRIDE; + + private: + // Initializes PrintSettings from |print_info_|. This must be called + // after changes to |print_info_| in order for the changes to take effect in + // printing. + // This function ignores the page range information specified in the print + // info object and use |ranges| instead. + void InitPrintSettingsFromPrintInfo(const PageRanges& ranges); + + // Returns the set of page ranges constructed from |print_info_|. + PageRanges GetPageRangesFromPrintInfo(); + + // Updates |print_info_| to use the given printer. + // Returns true if the printer was set else returns false. + bool SetPrinter(const std::string& device_name); + + // Updates |print_info_| page format with user default paper information. + // Returns true if the paper was set else returns false. + bool UpdatePageFormatWithPaperInfo(); + + // Sets the print job destination type as preview job. + // Returns true if the print job destination type is set. + bool SetPrintPreviewJob(); + + // Sets |copies| in PMPrintSettings. + // Returns true if the number of copies is set. + bool SetCopiesInPrintSettings(int copies); + + // Sets |collate| in PMPrintSettings. + // Returns true if |collate| is set. + bool SetCollateInPrintSettings(bool collate); + + // Sets orientation in native print info object. + // Returns true if the orientation was set. + bool SetOrientationIsLandscape(bool landscape); + + // Sets duplex mode in PMPrintSettings. + // Returns true if duplex mode is set. + bool SetDuplexModeInPrintSettings(DuplexMode mode); + + // Sets output color mode in PMPrintSettings. + // Returns true if color mode is set. + bool SetOutputColor(int color_mode); + + // The native print info object. + base::scoped_nsobject<NSPrintInfo> print_info_; + + // The current page's context; only valid between NewPage and PageDone call + // pairs. + CGContext* context_; + + DISALLOW_COPY_AND_ASSIGN(PrintingContextMac); +}; + +} // namespace printing + +#endif // PRINTING_PRINTING_CONTEXT_MAC_H_ diff --git a/chromium/printing/printing_context_mac.mm b/chromium/printing/printing_context_mac.mm new file mode 100644 index 00000000000..4d83c130718 --- /dev/null +++ b/chromium/printing/printing_context_mac.mm @@ -0,0 +1,519 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printing_context_mac.h" + +#import <ApplicationServices/ApplicationServices.h> +#import <AppKit/AppKit.h> + +#import <iomanip> +#import <numeric> + +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/mac/scoped_nsexception_enabler.h" +#include "base/strings/sys_string_conversions.h" +#include "base/values.h" +#include "printing/print_settings_initializer_mac.h" + +namespace printing { + +namespace { + +// Return true if PPD name of paper is equal. +bool IsPaperNameEqual(const PMPaper& paper1, const PMPaper& paper2) { + CFStringRef name1 = NULL; + CFStringRef name2 = NULL; + return (PMPaperGetPPDPaperName(paper1, &name1) == noErr) && + (PMPaperGetPPDPaperName(paper2, &name2) == noErr) && + (CFStringCompare(name1, name2, + kCFCompareCaseInsensitive) == kCFCompareEqualTo); +} + +} // namespace + +// static +PrintingContext* PrintingContext::Create(const std::string& app_locale) { + return static_cast<PrintingContext*>(new PrintingContextMac(app_locale)); +} + +PrintingContextMac::PrintingContextMac(const std::string& app_locale) + : PrintingContext(app_locale), + print_info_([[NSPrintInfo sharedPrintInfo] copy]), + context_(NULL) { +} + +PrintingContextMac::~PrintingContextMac() { + ReleaseContext(); +} + +void PrintingContextMac::AskUserForSettings( + gfx::NativeView parent_view, + int max_pages, + bool has_selection, + const PrintSettingsCallback& callback) { + // Third-party print drivers seem to be an area prone to raising exceptions. + // This will allow exceptions to be raised, but does not handle them. The + // NSPrintPanel appears to have appropriate NSException handlers. + base::mac::ScopedNSExceptionEnabler enabler; + + // Exceptions can also happen when the NSPrintPanel is being + // deallocated, so it must be autoreleased within this scope. + base::mac::ScopedNSAutoreleasePool pool; + + DCHECK([NSThread isMainThread]); + + // We deliberately don't feed max_pages into the dialog, because setting + // NSPrintLastPage makes the print dialog pre-select the option to only print + // a range. + + // TODO(stuartmorgan): implement 'print selection only' (probably requires + // adding a new custom view to the panel on 10.5; 10.6 has + // NSPrintPanelShowsPrintSelection). + NSPrintPanel* panel = [NSPrintPanel printPanel]; + NSPrintInfo* printInfo = print_info_.get(); + + NSPrintPanelOptions options = [panel options]; + options |= NSPrintPanelShowsPaperSize; + options |= NSPrintPanelShowsOrientation; + options |= NSPrintPanelShowsScaling; + [panel setOptions:options]; + + // Set the print job title text. + if (parent_view) { + NSString* job_title = [[parent_view window] title]; + if (job_title) { + PMPrintSettings printSettings = + (PMPrintSettings)[printInfo PMPrintSettings]; + PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title); + [printInfo updateFromPMPrintSettings]; + } + } + + // TODO(stuartmorgan): We really want a tab sheet here, not a modal window. + // Will require restructuring the PrintingContext API to use a callback. + NSInteger selection = [panel runModalWithPrintInfo:printInfo]; + if (selection == NSOKButton) { + print_info_.reset([[panel printInfo] retain]); + InitPrintSettingsFromPrintInfo(GetPageRangesFromPrintInfo()); + callback.Run(OK); + } else { + callback.Run(CANCEL); + } +} + +PrintingContext::Result PrintingContextMac::UseDefaultSettings() { + DCHECK(!in_print_job_); + + print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]); + InitPrintSettingsFromPrintInfo(GetPageRangesFromPrintInfo()); + + return OK; +} + +PrintingContext::Result PrintingContextMac::UpdatePrinterSettings( + const DictionaryValue& job_settings, const PageRanges& ranges) { + DCHECK(!in_print_job_); + + // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start + // with a clean slate. + print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]); + + bool collate; + int color; + bool landscape; + bool print_to_pdf; + bool is_cloud_dialog; + int copies; + int duplex_mode; + std::string device_name; + + if (!job_settings.GetBoolean(kSettingLandscape, &landscape) || + !job_settings.GetBoolean(kSettingCollate, &collate) || + !job_settings.GetInteger(kSettingColor, &color) || + !job_settings.GetBoolean(kSettingPrintToPDF, &print_to_pdf) || + !job_settings.GetInteger(kSettingDuplexMode, &duplex_mode) || + !job_settings.GetInteger(kSettingCopies, &copies) || + !job_settings.GetString(kSettingDeviceName, &device_name) || + !job_settings.GetBoolean(kSettingCloudPrintDialog, &is_cloud_dialog)) { + return OnError(); + } + + bool print_to_cloud = job_settings.HasKey(kSettingCloudPrintId); + bool open_pdf_in_preview = job_settings.HasKey(kSettingOpenPDFInPreview); + + if (!print_to_pdf && !print_to_cloud && !is_cloud_dialog) { + if (!SetPrinter(device_name)) + return OnError(); + + if (!SetCopiesInPrintSettings(copies)) + return OnError(); + + if (!SetCollateInPrintSettings(collate)) + return OnError(); + + if (!SetDuplexModeInPrintSettings( + static_cast<DuplexMode>(duplex_mode))) { + return OnError(); + } + + if (!SetOutputColor(color)) + return OnError(); + } + if (open_pdf_in_preview) { + if (!SetPrintPreviewJob()) + return OnError(); + } + + if (!UpdatePageFormatWithPaperInfo()) + return OnError(); + + if (!SetOrientationIsLandscape(landscape)) + return OnError(); + + [print_info_.get() updateFromPMPrintSettings]; + + InitPrintSettingsFromPrintInfo(ranges); + return OK; +} + +bool PrintingContextMac::SetPrintPreviewJob() { + PMPrintSession print_session = + static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); + PMPrintSettings print_settings = + static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); + return PMSessionSetDestination( + print_session, print_settings, kPMDestinationPreview, + NULL, NULL) == noErr; +} + +void PrintingContextMac::InitPrintSettingsFromPrintInfo( + const PageRanges& ranges) { + PMPrintSession print_session = + static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); + PMPageFormat page_format = + static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); + PMPrinter printer; + PMSessionGetCurrentPrinter(print_session, &printer); + PrintSettingsInitializerMac::InitPrintSettings( + printer, page_format, ranges, false, &settings_); +} + +bool PrintingContextMac::SetPrinter(const std::string& device_name) { + DCHECK(print_info_.get()); + PMPrintSession print_session = + static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); + + PMPrinter current_printer; + if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr) + return false; + + CFStringRef current_printer_id = PMPrinterGetID(current_printer); + if (!current_printer_id) + return false; + + base::ScopedCFTypeRef<CFStringRef> new_printer_id( + base::SysUTF8ToCFStringRef(device_name)); + if (!new_printer_id.get()) + return false; + + if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) == + kCFCompareEqualTo) { + return true; + } + + PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get()); + if (new_printer == NULL) + return false; + + OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer); + PMRelease(new_printer); + return status == noErr; +} + +bool PrintingContextMac::UpdatePageFormatWithPaperInfo() { + PMPrintSession print_session = + static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); + + PMPageFormat default_page_format = + static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); + + PMPaper default_paper; + if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr) + return false; + + double default_page_width = 0.0; + double default_page_height = 0.0; + if (PMPaperGetWidth(default_paper, &default_page_width) != noErr) + return false; + + if (PMPaperGetHeight(default_paper, &default_page_height) != noErr) + return false; + + PMPrinter current_printer = NULL; + if (PMSessionGetCurrentPrinter(print_session, ¤t_printer) != noErr) + return false; + + if (current_printer == nil) + return false; + + CFArrayRef paper_list = NULL; + if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr) + return false; + + double best_match = std::numeric_limits<double>::max(); + PMPaper best_matching_paper = kPMNoData; + int num_papers = CFArrayGetCount(paper_list); + for (int i = 0; i < num_papers; ++i) { + PMPaper paper = (PMPaper)[(NSArray*)paper_list objectAtIndex: i]; + double paper_width = 0.0; + double paper_height = 0.0; + PMPaperGetWidth(paper, &paper_width); + PMPaperGetHeight(paper, &paper_height); + double current_match = std::max(fabs(default_page_width - paper_width), + fabs(default_page_height - paper_height)); + // Ignore paper sizes that are very different. + if (current_match > 2) + continue; + current_match += IsPaperNameEqual(paper, default_paper) ? 0 : 1; + if (current_match < best_match) { + best_matching_paper = paper; + best_match = current_match; + } + } + + if (best_matching_paper == kPMNoData) { + PMPaper paper = kPMNoData; + // Create a custom paper for the specified default page size. + PMPaperMargins default_margins; + if (PMPaperGetMargins(default_paper, &default_margins) != noErr) + return false; + + const PMPaperMargins margins = + {default_margins.top, default_margins.left, default_margins.bottom, + default_margins.right}; + CFStringRef paper_id = CFSTR("Custom paper ID"); + CFStringRef paper_name = CFSTR("Custom paper"); + if (PMPaperCreateCustom(current_printer, paper_id, paper_name, + default_page_width, default_page_height, &margins, &paper) != + noErr) { + return false; + } + [print_info_.get() updateFromPMPageFormat]; + PMRelease(paper); + } else { + PMPageFormat chosen_page_format = NULL; + if (PMCreatePageFormat((PMPageFormat*) &chosen_page_format) != noErr) + return false; + + // Create page format from that paper. + if (PMCreatePageFormatWithPMPaper(&chosen_page_format, + best_matching_paper) != noErr) { + PMRelease(chosen_page_format); + return false; + } + // Copy over the original format with the new page format. + if (PMCopyPageFormat(chosen_page_format, default_page_format) != noErr) { + PMRelease(chosen_page_format); + return false; + } + [print_info_.get() updateFromPMPageFormat]; + PMRelease(chosen_page_format); + } + return true; +} + +bool PrintingContextMac::SetCopiesInPrintSettings(int copies) { + if (copies < 1) + return false; + + PMPrintSettings pmPrintSettings = + static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); + return PMSetCopies(pmPrintSettings, copies, false) == noErr; +} + +bool PrintingContextMac::SetCollateInPrintSettings(bool collate) { + PMPrintSettings pmPrintSettings = + static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); + return PMSetCollate(pmPrintSettings, collate) == noErr; +} + +bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) { + PMPageFormat page_format = + static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); + + PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait; + + if (PMSetOrientation(page_format, orientation, false) != noErr) + return false; + + PMPrintSession print_session = + static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); + + PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean); + + [print_info_.get() updateFromPMPageFormat]; + return true; +} + +bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) { + PMDuplexMode duplexSetting; + switch (mode) { + case LONG_EDGE: + duplexSetting = kPMDuplexNoTumble; + break; + case SHORT_EDGE: + duplexSetting = kPMDuplexTumble; + break; + case SIMPLEX: + duplexSetting = kPMDuplexNone; + break; + default: // UNKNOWN_DUPLEX_MODE + return true; + } + + PMPrintSettings pmPrintSettings = + static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); + return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr; +} + +bool PrintingContextMac::SetOutputColor(int color_mode) { + PMPrintSettings pmPrintSettings = + static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); + std::string color_setting_name; + std::string color_value; + GetColorModelForMode(color_mode, &color_setting_name, &color_value); + base::ScopedCFTypeRef<CFStringRef> color_setting( + base::SysUTF8ToCFStringRef(color_setting_name)); + base::ScopedCFTypeRef<CFStringRef> output_color( + base::SysUTF8ToCFStringRef(color_value)); + + return PMPrintSettingsSetValue(pmPrintSettings, + color_setting.get(), + output_color.get(), + false) == noErr; +} + +PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() { + PageRanges page_ranges; + NSDictionary* print_info_dict = [print_info_.get() dictionary]; + if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) { + PageRange range; + range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1; + range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1; + page_ranges.push_back(range); + } + return page_ranges; +} + +PrintingContext::Result PrintingContextMac::InitWithSettings( + const PrintSettings& settings) { + DCHECK(!in_print_job_); + + settings_ = settings; + + NOTIMPLEMENTED(); + + return FAILED; +} + +PrintingContext::Result PrintingContextMac::NewDocument( + const string16& document_name) { + DCHECK(!in_print_job_); + + in_print_job_ = true; + + PMPrintSession print_session = + static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); + PMPrintSettings print_settings = + static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]); + PMPageFormat page_format = + static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); + + base::ScopedCFTypeRef<CFStringRef> job_title( + base::SysUTF16ToCFStringRef(document_name)); + PMPrintSettingsSetJobName(print_settings, job_title.get()); + + OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session, + print_settings, + page_format); + if (status != noErr) + return OnError(); + + return OK; +} + +PrintingContext::Result PrintingContextMac::NewPage() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + DCHECK(!context_); + + PMPrintSession print_session = + static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); + PMPageFormat page_format = + static_cast<PMPageFormat>([print_info_.get() PMPageFormat]); + OSStatus status; + status = PMSessionBeginPageNoDialog(print_session, page_format, NULL); + if (status != noErr) + return OnError(); + status = PMSessionGetCGGraphicsContext(print_session, &context_); + if (status != noErr) + return OnError(); + + return OK; +} + +PrintingContext::Result PrintingContextMac::PageDone() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + DCHECK(context_); + + PMPrintSession print_session = + static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); + OSStatus status = PMSessionEndPageNoDialog(print_session); + if (status != noErr) + OnError(); + context_ = NULL; + + return OK; +} + +PrintingContext::Result PrintingContextMac::DocumentDone() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + PMPrintSession print_session = + static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); + OSStatus status = PMSessionEndDocumentNoDialog(print_session); + if (status != noErr) + OnError(); + + ResetSettings(); + return OK; +} + +void PrintingContextMac::Cancel() { + abort_printing_ = true; + in_print_job_ = false; + context_ = NULL; + + PMPrintSession print_session = + static_cast<PMPrintSession>([print_info_.get() PMPrintSession]); + PMSessionEndPageNoDialog(print_session); +} + +void PrintingContextMac::ReleaseContext() { + print_info_.reset(); + context_ = NULL; +} + +gfx::NativeDrawingContext PrintingContextMac::context() const { + return context_; +} + +} // namespace printing diff --git a/chromium/printing/printing_context_no_system_dialog.cc b/chromium/printing/printing_context_no_system_dialog.cc new file mode 100644 index 00000000000..958d901700e --- /dev/null +++ b/chromium/printing/printing_context_no_system_dialog.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printing_context_no_system_dialog.h" + +#include <unicode/ulocdata.h> + +#include "base/logging.h" +#include "base/values.h" +#include "printing/metafile.h" +#include "printing/print_job_constants.h" +#include "printing/units.h" + +namespace printing { + +// static +PrintingContext* PrintingContext::Create(const std::string& app_locale) { + return static_cast<PrintingContext*>( + new PrintingContextNoSystemDialog(app_locale)); +} + +PrintingContextNoSystemDialog::PrintingContextNoSystemDialog( + const std::string& app_locale) : PrintingContext(app_locale) { +} + +PrintingContextNoSystemDialog::~PrintingContextNoSystemDialog() { + ReleaseContext(); +} + +void PrintingContextNoSystemDialog::AskUserForSettings( + gfx::NativeView parent_view, + int max_pages, + bool has_selection, + const PrintSettingsCallback& callback) { + // We don't want to bring up a dialog here. Ever. Just signal the callback. + callback.Run(OK); +} + +PrintingContext::Result PrintingContextNoSystemDialog::UseDefaultSettings() { + DCHECK(!in_print_job_); + + ResetSettings(); + // TODO(abodenha): Fetch these settings from the OS where possible. See + // bug 102583. + // TODO(sanjeevr): We need a better feedback loop between the cloud print + // dialog and this code. + int dpi = 300; + gfx::Size physical_size_device_units; + gfx::Rect printable_area_device_units; + int32_t width = 0; + int32_t height = 0; + UErrorCode error = U_ZERO_ERROR; + ulocdata_getPaperSize(app_locale_.c_str(), &height, &width, &error); + if (error > U_ZERO_ERROR) { + // If the call failed, assume a paper size of 8.5 x 11 inches. + LOG(WARNING) << "ulocdata_getPaperSize failed, using 8.5 x 11, error: " + << error; + width = static_cast<int>(8.5 * dpi); + height = static_cast<int>(11 * dpi); + } else { + // ulocdata_getPaperSize returns the width and height in mm. + // Convert this to pixels based on the dpi. + width = static_cast<int>(ConvertUnitDouble(width, 25.4, 1.0) * dpi); + height = static_cast<int>(ConvertUnitDouble(height, 25.4, 1.0) * dpi); + } + + physical_size_device_units.SetSize(width, height); + + // Assume full page is printable for now. + printable_area_device_units.SetRect(0, 0, width, height); + + settings_.set_dpi(dpi); + settings_.SetPrinterPrintableArea(physical_size_device_units, + printable_area_device_units, + dpi); + + return OK; +} + +PrintingContext::Result PrintingContextNoSystemDialog::UpdatePrinterSettings( + const base::DictionaryValue& job_settings, const PageRanges& ranges) { + bool landscape = false; + + if (!job_settings.GetBoolean(kSettingLandscape, &landscape)) + return OnError(); + + if (settings_.dpi() == 0) + UseDefaultSettings(); + + settings_.SetOrientation(landscape); + settings_.ranges = ranges; + + return OK; +} + +PrintingContext::Result PrintingContextNoSystemDialog::InitWithSettings( + const PrintSettings& settings) { + DCHECK(!in_print_job_); + + settings_ = settings; + + return OK; +} + +PrintingContext::Result PrintingContextNoSystemDialog::NewDocument( + const string16& document_name) { + DCHECK(!in_print_job_); + in_print_job_ = true; + + return OK; +} + +PrintingContext::Result PrintingContextNoSystemDialog::NewPage() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + // Intentional No-op. + + return OK; +} + +PrintingContext::Result PrintingContextNoSystemDialog::PageDone() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + // Intentional No-op. + + return OK; +} + +PrintingContext::Result PrintingContextNoSystemDialog::DocumentDone() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + ResetSettings(); + return OK; +} + +void PrintingContextNoSystemDialog::Cancel() { + abort_printing_ = true; + in_print_job_ = false; +} + +void PrintingContextNoSystemDialog::ReleaseContext() { + // Intentional No-op. +} + +gfx::NativeDrawingContext PrintingContextNoSystemDialog::context() const { + // Intentional No-op. + return NULL; +} + +} // namespace printing + diff --git a/chromium/printing/printing_context_no_system_dialog.h b/chromium/printing/printing_context_no_system_dialog.h new file mode 100644 index 00000000000..b88578ba526 --- /dev/null +++ b/chromium/printing/printing_context_no_system_dialog.h @@ -0,0 +1,48 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTING_CONTEXT_NO_SYSTEM_DIALOG_H_ +#define PRINTING_PRINTING_CONTEXT_NO_SYSTEM_DIALOG_H_ + +#include <string> + +#include "printing/printing_context.h" + +namespace base { +class DictionaryValue; +} + +namespace printing { + +class PRINTING_EXPORT PrintingContextNoSystemDialog : public PrintingContext { + public: + explicit PrintingContextNoSystemDialog(const std::string& app_locale); + virtual ~PrintingContextNoSystemDialog(); + + // PrintingContext implementation. + virtual void AskUserForSettings( + gfx::NativeView parent_view, + int max_pages, + bool has_selection, + const PrintSettingsCallback& callback) OVERRIDE; + virtual Result UseDefaultSettings() OVERRIDE; + virtual Result UpdatePrinterSettings( + const base::DictionaryValue& job_settings, + const PageRanges& ranges) OVERRIDE; + virtual Result InitWithSettings(const PrintSettings& settings) OVERRIDE; + virtual Result NewDocument(const string16& document_name) OVERRIDE; + virtual Result NewPage() OVERRIDE; + virtual Result PageDone() OVERRIDE; + virtual Result DocumentDone() OVERRIDE; + virtual void Cancel() OVERRIDE; + virtual void ReleaseContext() OVERRIDE; + virtual gfx::NativeDrawingContext context() const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(PrintingContextNoSystemDialog); +}; + +} // namespace printing + +#endif // PRINTING_PRINTING_CONTEXT_NO_SYSTEM_DIALOG_H_ diff --git a/chromium/printing/printing_context_win.cc b/chromium/printing/printing_context_win.cc new file mode 100644 index 00000000000..d4f0b029190 --- /dev/null +++ b/chromium/printing/printing_context_win.cc @@ -0,0 +1,778 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/printing_context_win.h" + +#include <winspool.h> + +#include <algorithm> + +#include "base/i18n/file_util_icu.h" +#include "base/i18n/time_formatting.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/values.h" +#include "base/win/metro.h" +#include "printing/backend/print_backend.h" +#include "printing/backend/printing_info_win.h" +#include "printing/backend/win_helper.h" +#include "printing/print_job_constants.h" +#include "printing/print_settings_initializer_win.h" +#include "printing/printed_document.h" +#include "printing/units.h" +#include "skia/ext/platform_device.h" +#include "win8/util/win8_util.h" + +#if defined(USE_AURA) +#include "ui/aura/remote_root_window_host_win.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" +#endif + +using base::Time; + +namespace { + +// Constants for setting default PDF settings. +const int kPDFDpi = 300; // 300 dpi +// LETTER: 8.5 x 11 inches +const int kPDFLetterWidth = 8.5 * kPDFDpi; +const int kPDFLetterHeight = 11 * kPDFDpi; +// LEGAL: 8.5 x 14 inches +const int kPDFLegalWidth = 8.5 * kPDFDpi; +const int kPDFLegalHeight = 14 * kPDFDpi; +// A4: 8.27 x 11.69 inches +const int kPDFA4Width = 8.27 * kPDFDpi; +const int kPDFA4Height = 11.69 * kPDFDpi; +// A3: 11.69 x 16.54 inches +const int kPDFA3Width = 11.69 * kPDFDpi; +const int kPDFA3Height = 16.54 * kPDFDpi; + +HWND GetRootWindow(gfx::NativeView view) { + HWND window = NULL; +#if defined(USE_AURA) + if (view) + window = view->GetRootWindow()->GetAcceleratedWidget(); +#else + if (view && IsWindow(view)) { + window = GetAncestor(view, GA_ROOTOWNER); + } +#endif + if (!window) { + // TODO(maruel): bug 1214347 Get the right browser window instead. + return GetDesktopWindow(); + } + return window; +} + +} // anonymous namespace + +namespace printing { + +class PrintingContextWin::CallbackHandler : public IPrintDialogCallback, + public IObjectWithSite { + public: + CallbackHandler(PrintingContextWin& owner, HWND owner_hwnd) + : owner_(owner), + owner_hwnd_(owner_hwnd), + services_(NULL) { + } + + ~CallbackHandler() { + if (services_) + services_->Release(); + } + + IUnknown* ToIUnknown() { + return static_cast<IUnknown*>(static_cast<IPrintDialogCallback*>(this)); + } + + // IUnknown + virtual HRESULT WINAPI QueryInterface(REFIID riid, void**object) { + if (riid == IID_IUnknown) { + *object = ToIUnknown(); + } else if (riid == IID_IPrintDialogCallback) { + *object = static_cast<IPrintDialogCallback*>(this); + } else if (riid == IID_IObjectWithSite) { + *object = static_cast<IObjectWithSite*>(this); + } else { + return E_NOINTERFACE; + } + return S_OK; + } + + // No real ref counting. + virtual ULONG WINAPI AddRef() { + return 1; + } + virtual ULONG WINAPI Release() { + return 1; + } + + // IPrintDialogCallback methods + virtual HRESULT WINAPI InitDone() { + return S_OK; + } + + virtual HRESULT WINAPI SelectionChange() { + if (services_) { + // TODO(maruel): Get the devmode for the new printer with + // services_->GetCurrentDevMode(&devmode, &size), send that information + // back to our client and continue. The client needs to recalculate the + // number of rendered pages and send back this information here. + } + return S_OK; + } + + virtual HRESULT WINAPI HandleMessage(HWND dialog, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + // Cheap way to retrieve the window handle. + if (!owner_.dialog_box_) { + // The handle we receive is the one of the groupbox in the General tab. We + // need to get the grand-father to get the dialog box handle. + owner_.dialog_box_ = GetAncestor(dialog, GA_ROOT); + // Trick to enable the owner window. This can cause issues with navigation + // events so it may have to be disabled if we don't fix the side-effects. + EnableWindow(owner_hwnd_, TRUE); + } + return S_FALSE; + } + + virtual HRESULT WINAPI SetSite(IUnknown* site) { + if (!site) { + DCHECK(services_); + services_->Release(); + services_ = NULL; + // The dialog box is destroying, PrintJob::Worker don't need the handle + // anymore. + owner_.dialog_box_ = NULL; + } else { + DCHECK(services_ == NULL); + HRESULT hr = site->QueryInterface(IID_IPrintDialogServices, + reinterpret_cast<void**>(&services_)); + DCHECK(SUCCEEDED(hr)); + } + return S_OK; + } + + virtual HRESULT WINAPI GetSite(REFIID riid, void** site) { + return E_NOTIMPL; + } + + private: + PrintingContextWin& owner_; + HWND owner_hwnd_; + IPrintDialogServices* services_; + + DISALLOW_COPY_AND_ASSIGN(CallbackHandler); +}; + +// static +PrintingContext* PrintingContext::Create(const std::string& app_locale) { + return static_cast<PrintingContext*>(new PrintingContextWin(app_locale)); +} + +PrintingContextWin::PrintingContextWin(const std::string& app_locale) + : PrintingContext(app_locale), + context_(NULL), + dialog_box_(NULL), + print_dialog_func_(&PrintDlgEx) { +} + +PrintingContextWin::~PrintingContextWin() { + ReleaseContext(); +} + +// TODO(vitalybuka): Implement as ui::BaseShellDialog crbug.com/180997. +void PrintingContextWin::AskUserForSettings( + gfx::NativeView view, int max_pages, bool has_selection, + const PrintSettingsCallback& callback) { + DCHECK(!in_print_job_); + if (win8::IsSingleWindowMetroMode()) { + // The system dialog can not be opened while running in Metro. + // But we can programatically launch the Metro print device charm though. + HMODULE metro_module = base::win::GetMetroModule(); + if (metro_module != NULL) { + typedef void (*MetroShowPrintUI)(); + MetroShowPrintUI metro_show_print_ui = + reinterpret_cast<MetroShowPrintUI>( + ::GetProcAddress(metro_module, "MetroShowPrintUI")); + if (metro_show_print_ui) { + // TODO(mad): Remove this once we can send user metrics from the metro + // driver. crbug.com/142330 + UMA_HISTOGRAM_ENUMERATION("Metro.Print", 1, 2); + metro_show_print_ui(); + } + } + return callback.Run(CANCEL); + } + dialog_box_dismissed_ = false; + + HWND window = GetRootWindow(view); + DCHECK(window); + + // Show the OS-dependent dialog box. + // If the user press + // - OK, the settings are reset and reinitialized with the new settings. OK is + // returned. + // - Apply then Cancel, the settings are reset and reinitialized with the new + // settings. CANCEL is returned. + // - Cancel, the settings are not changed, the previous setting, if it was + // initialized before, are kept. CANCEL is returned. + // On failure, the settings are reset and FAILED is returned. + PRINTDLGEX dialog_options = { sizeof(PRINTDLGEX) }; + dialog_options.hwndOwner = window; + // Disable options we don't support currently. + // TODO(maruel): Reuse the previously loaded settings! + dialog_options.Flags = PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE | + PD_NOCURRENTPAGE | PD_HIDEPRINTTOFILE; + if (!has_selection) + dialog_options.Flags |= PD_NOSELECTION; + + PRINTPAGERANGE ranges[32]; + dialog_options.nStartPage = START_PAGE_GENERAL; + if (max_pages) { + // Default initialize to print all the pages. + memset(ranges, 0, sizeof(ranges)); + ranges[0].nFromPage = 1; + ranges[0].nToPage = max_pages; + dialog_options.nPageRanges = 1; + dialog_options.nMaxPageRanges = arraysize(ranges); + dialog_options.nMinPage = 1; + dialog_options.nMaxPage = max_pages; + dialog_options.lpPageRanges = ranges; + } else { + // No need to bother, we don't know how many pages are available. + dialog_options.Flags |= PD_NOPAGENUMS; + } + + HRESULT hr = (*print_dialog_func_)(&dialog_options); + if (hr != S_OK) { + ResetSettings(); + callback.Run(FAILED); + } + + // TODO(maruel): Support PD_PRINTTOFILE. + callback.Run(ParseDialogResultEx(dialog_options)); +} + +PrintingContext::Result PrintingContextWin::UseDefaultSettings() { + DCHECK(!in_print_job_); + + PRINTDLG dialog_options = { sizeof(PRINTDLG) }; + dialog_options.Flags = PD_RETURNDC | PD_RETURNDEFAULT; + if (PrintDlg(&dialog_options)) + return ParseDialogResult(dialog_options); + + // No default printer configured, do we have any printers at all? + DWORD bytes_needed = 0; + DWORD count_returned = 0; + (void)::EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, + NULL, 2, NULL, 0, &bytes_needed, &count_returned); + if (bytes_needed) { + DCHECK(bytes_needed >= count_returned * sizeof(PRINTER_INFO_2)); + scoped_ptr<BYTE[]> printer_info_buffer(new BYTE[bytes_needed]); + BOOL ret = ::EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, + NULL, 2, printer_info_buffer.get(), + bytes_needed, &bytes_needed, + &count_returned); + if (ret && count_returned) { // have printers + // Open the first successfully found printer. + for (DWORD count = 0; count < count_returned; ++count) { + PRINTER_INFO_2* info_2 = reinterpret_cast<PRINTER_INFO_2*>( + printer_info_buffer.get() + count * sizeof(PRINTER_INFO_2)); + std::wstring printer_name = info_2->pPrinterName; + if (info_2->pDevMode == NULL || printer_name.length() == 0) + continue; + if (!AllocateContext(printer_name, info_2->pDevMode, &context_)) + break; + if (InitializeSettings(*info_2->pDevMode, printer_name, + NULL, 0, false)) { + break; + } + ReleaseContext(); + } + if (context_) + return OK; + } + } + + ResetSettings(); + return FAILED; +} + +PrintingContext::Result PrintingContextWin::UpdatePrinterSettings( + const DictionaryValue& job_settings, + const PageRanges& ranges) { + DCHECK(!in_print_job_); + + bool collate; + int color; + bool landscape; + bool print_to_pdf; + bool is_cloud_dialog; + int copies; + int duplex_mode; + string16 device_name; + + if (!job_settings.GetBoolean(kSettingLandscape, &landscape) || + !job_settings.GetBoolean(kSettingCollate, &collate) || + !job_settings.GetInteger(kSettingColor, &color) || + !job_settings.GetBoolean(kSettingPrintToPDF, &print_to_pdf) || + !job_settings.GetInteger(kSettingDuplexMode, &duplex_mode) || + !job_settings.GetInteger(kSettingCopies, &copies) || + !job_settings.GetString(kSettingDeviceName, &device_name) || + !job_settings.GetBoolean(kSettingCloudPrintDialog, &is_cloud_dialog)) { + return OnError(); + } + + bool print_to_cloud = job_settings.HasKey(kSettingCloudPrintId); + + if (print_to_pdf || print_to_cloud || is_cloud_dialog) { + // Default fallback to Letter size. + gfx::Size paper_size; + gfx::Rect paper_rect; + paper_size.SetSize(kPDFLetterWidth, kPDFLetterHeight); + + // Get settings from locale. Paper type buffer length is at most 4. + const int paper_type_buffer_len = 4; + wchar_t paper_type_buffer[paper_type_buffer_len] = {0}; + GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IPAPERSIZE, paper_type_buffer, + paper_type_buffer_len); + if (wcslen(paper_type_buffer)) { // The call succeeded. + int paper_code = _wtoi(paper_type_buffer); + switch (paper_code) { + case DMPAPER_LEGAL: + paper_size.SetSize(kPDFLegalWidth, kPDFLegalHeight); + break; + case DMPAPER_A4: + paper_size.SetSize(kPDFA4Width, kPDFA4Height); + break; + case DMPAPER_A3: + paper_size.SetSize(kPDFA3Width, kPDFA3Height); + break; + default: // DMPAPER_LETTER is used for default fallback. + break; + } + } + paper_rect.SetRect(0, 0, paper_size.width(), paper_size.height()); + settings_.SetPrinterPrintableArea(paper_size, paper_rect, kPDFDpi); + settings_.set_dpi(kPDFDpi); + settings_.SetOrientation(landscape); + settings_.ranges = ranges; + return OK; + } + + ScopedPrinterHandle printer; + LPWSTR device_name_wide = const_cast<wchar_t*>(device_name.c_str()); + if (!printer.OpenPrinter(device_name_wide)) + return OnError(); + + // Make printer changes local to Chrome. + // See MSDN documentation regarding DocumentProperties. + scoped_ptr<uint8[]> buffer; + DEVMODE* dev_mode = NULL; + LONG buffer_size = DocumentProperties(NULL, printer, device_name_wide, + NULL, NULL, 0); + if (buffer_size > 0) { + buffer.reset(new uint8[buffer_size]); + memset(buffer.get(), 0, buffer_size); + if (DocumentProperties(NULL, printer, device_name_wide, + reinterpret_cast<PDEVMODE>(buffer.get()), NULL, + DM_OUT_BUFFER) == IDOK) { + dev_mode = reinterpret_cast<PDEVMODE>(buffer.get()); + } + } + if (dev_mode == NULL) { + buffer.reset(); + return OnError(); + } + + if (color == GRAY) + dev_mode->dmColor = DMCOLOR_MONOCHROME; + else + dev_mode->dmColor = DMCOLOR_COLOR; + + dev_mode->dmCopies = std::max(copies, 1); + if (dev_mode->dmCopies > 1) // do not change collate unless multiple copies + dev_mode->dmCollate = collate ? DMCOLLATE_TRUE : DMCOLLATE_FALSE; + switch (duplex_mode) { + case LONG_EDGE: + dev_mode->dmDuplex = DMDUP_VERTICAL; + break; + case SHORT_EDGE: + dev_mode->dmDuplex = DMDUP_HORIZONTAL; + break; + case SIMPLEX: + dev_mode->dmDuplex = DMDUP_SIMPLEX; + break; + default: // UNKNOWN_DUPLEX_MODE + break; + } + dev_mode->dmOrientation = landscape ? DMORIENT_LANDSCAPE : DMORIENT_PORTRAIT; + + // Update data using DocumentProperties. + if (DocumentProperties(NULL, printer, device_name_wide, dev_mode, dev_mode, + DM_IN_BUFFER | DM_OUT_BUFFER) != IDOK) { + return OnError(); + } + + // Set printer then refresh printer settings. + if (!AllocateContext(device_name, dev_mode, &context_)) { + return OnError(); + } + PrintSettingsInitializerWin::InitPrintSettings(context_, *dev_mode, + ranges, device_name, + false, &settings_); + return OK; +} + +PrintingContext::Result PrintingContextWin::InitWithSettings( + const PrintSettings& settings) { + DCHECK(!in_print_job_); + + settings_ = settings; + + // TODO(maruel): settings_.ToDEVMODE() + ScopedPrinterHandle printer; + if (!printer.OpenPrinter(settings_.device_name().c_str())) { + return FAILED; + } + + Result status = OK; + + if (!GetPrinterSettings(printer, settings_.device_name())) + status = FAILED; + + if (status != OK) + ResetSettings(); + return status; +} + +PrintingContext::Result PrintingContextWin::NewDocument( + const string16& document_name) { + DCHECK(!in_print_job_); + if (!context_) + return OnError(); + + // Set the flag used by the AbortPrintJob dialog procedure. + abort_printing_ = false; + + in_print_job_ = true; + + // Register the application's AbortProc function with GDI. + if (SP_ERROR == SetAbortProc(context_, &AbortProc)) + return OnError(); + + DCHECK(PrintBackend::SimplifyDocumentTitle(document_name) == document_name); + DOCINFO di = { sizeof(DOCINFO) }; + const std::wstring& document_name_wide = UTF16ToWide(document_name); + di.lpszDocName = document_name_wide.c_str(); + + // Is there a debug dump directory specified? If so, force to print to a file. + base::FilePath debug_dump_path = PrintedDocument::debug_dump_path(); + if (!debug_dump_path.empty()) { + // Create a filename. + std::wstring filename; + Time now(Time::Now()); + filename = base::TimeFormatShortDateNumeric(now); + filename += L"_"; + filename += base::TimeFormatTimeOfDay(now); + filename += L"_"; + filename += UTF16ToWide(document_name); + filename += L"_"; + filename += L"buffer.prn"; + file_util::ReplaceIllegalCharactersInPath(&filename, '_'); + debug_dump_path.Append(filename); + di.lpszOutput = debug_dump_path.value().c_str(); + } + + // No message loop running in unit tests. + DCHECK(!base::MessageLoop::current() || + !base::MessageLoop::current()->NestableTasksAllowed()); + + // Begin a print job by calling the StartDoc function. + // NOTE: StartDoc() starts a message loop. That causes a lot of problems with + // IPC. Make sure recursive task processing is disabled. + if (StartDoc(context_, &di) <= 0) + return OnError(); + + return OK; +} + +PrintingContext::Result PrintingContextWin::NewPage() { + if (abort_printing_) + return CANCEL; + DCHECK(context_); + DCHECK(in_print_job_); + + // Intentional No-op. NativeMetafile::SafePlayback takes care of calling + // ::StartPage(). + + return OK; +} + +PrintingContext::Result PrintingContextWin::PageDone() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + + // Intentional No-op. NativeMetafile::SafePlayback takes care of calling + // ::EndPage(). + + return OK; +} + +PrintingContext::Result PrintingContextWin::DocumentDone() { + if (abort_printing_) + return CANCEL; + DCHECK(in_print_job_); + DCHECK(context_); + + // Inform the driver that document has ended. + if (EndDoc(context_) <= 0) + return OnError(); + + ResetSettings(); + return OK; +} + +void PrintingContextWin::Cancel() { + abort_printing_ = true; + in_print_job_ = false; + if (context_) + CancelDC(context_); + if (dialog_box_) { + DestroyWindow(dialog_box_); + dialog_box_dismissed_ = true; + } +} + +void PrintingContextWin::ReleaseContext() { + if (context_) { + DeleteDC(context_); + context_ = NULL; + } +} + +gfx::NativeDrawingContext PrintingContextWin::context() const { + return context_; +} + +// static +BOOL PrintingContextWin::AbortProc(HDC hdc, int nCode) { + if (nCode) { + // TODO(maruel): Need a way to find the right instance to set. Should + // leverage PrintJobManager here? + // abort_printing_ = true; + } + return true; +} + +bool PrintingContextWin::InitializeSettings(const DEVMODE& dev_mode, + const std::wstring& new_device_name, + const PRINTPAGERANGE* ranges, + int number_ranges, + bool selection_only) { + skia::InitializeDC(context_); + DCHECK(GetDeviceCaps(context_, CLIPCAPS)); + DCHECK(GetDeviceCaps(context_, RASTERCAPS) & RC_STRETCHDIB); + DCHECK(GetDeviceCaps(context_, RASTERCAPS) & RC_BITMAP64); + // Some printers don't advertise these. + // DCHECK(GetDeviceCaps(context_, RASTERCAPS) & RC_SCALING); + // DCHECK(GetDeviceCaps(context_, SHADEBLENDCAPS) & SB_CONST_ALPHA); + // DCHECK(GetDeviceCaps(context_, SHADEBLENDCAPS) & SB_PIXEL_ALPHA); + + // StretchDIBits() support is needed for printing. + if (!(GetDeviceCaps(context_, RASTERCAPS) & RC_STRETCHDIB) || + !(GetDeviceCaps(context_, RASTERCAPS) & RC_BITMAP64)) { + NOTREACHED(); + ResetSettings(); + return false; + } + + DCHECK(!in_print_job_); + DCHECK(context_); + PageRanges ranges_vector; + if (!selection_only) { + // Convert the PRINTPAGERANGE array to a PrintSettings::PageRanges vector. + ranges_vector.reserve(number_ranges); + for (int i = 0; i < number_ranges; ++i) { + PageRange range; + // Transfer from 1-based to 0-based. + range.from = ranges[i].nFromPage - 1; + range.to = ranges[i].nToPage - 1; + ranges_vector.push_back(range); + } + } + + PrintSettingsInitializerWin::InitPrintSettings(context_, + dev_mode, + ranges_vector, + new_device_name, + selection_only, + &settings_); + + return true; +} + +bool PrintingContextWin::GetPrinterSettings(HANDLE printer, + const std::wstring& device_name) { + DCHECK(!in_print_job_); + + UserDefaultDevMode user_settings; + + if (!user_settings.Init(printer) || + !AllocateContext(device_name, user_settings.get(), &context_)) { + ResetSettings(); + return false; + } + + return InitializeSettings(*user_settings.get(), device_name, NULL, 0, false); +} + +// static +bool PrintingContextWin::AllocateContext(const std::wstring& device_name, + const DEVMODE* dev_mode, + gfx::NativeDrawingContext* context) { + *context = CreateDC(L"WINSPOOL", device_name.c_str(), NULL, dev_mode); + DCHECK(*context); + return *context != NULL; +} + +PrintingContext::Result PrintingContextWin::ParseDialogResultEx( + const PRINTDLGEX& dialog_options) { + // If the user clicked OK or Apply then Cancel, but not only Cancel. + if (dialog_options.dwResultAction != PD_RESULT_CANCEL) { + // Start fresh. + ResetSettings(); + + DEVMODE* dev_mode = NULL; + if (dialog_options.hDevMode) { + dev_mode = + reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode)); + DCHECK(dev_mode); + } + + std::wstring device_name; + if (dialog_options.hDevNames) { + DEVNAMES* dev_names = + reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames)); + DCHECK(dev_names); + if (dev_names) { + device_name = + reinterpret_cast<const wchar_t*>( + reinterpret_cast<const wchar_t*>(dev_names) + + dev_names->wDeviceOffset); + GlobalUnlock(dialog_options.hDevNames); + } + } + + bool success = false; + if (dev_mode && !device_name.empty()) { + context_ = dialog_options.hDC; + PRINTPAGERANGE* page_ranges = NULL; + DWORD num_page_ranges = 0; + bool print_selection_only = false; + if (dialog_options.Flags & PD_PAGENUMS) { + page_ranges = dialog_options.lpPageRanges; + num_page_ranges = dialog_options.nPageRanges; + } + if (dialog_options.Flags & PD_SELECTION) { + print_selection_only = true; + } + success = InitializeSettings(*dev_mode, + device_name, + page_ranges, + num_page_ranges, + print_selection_only); + } + + if (!success && dialog_options.hDC) { + DeleteDC(dialog_options.hDC); + context_ = NULL; + } + + if (dev_mode) { + GlobalUnlock(dialog_options.hDevMode); + } + } else { + if (dialog_options.hDC) { + DeleteDC(dialog_options.hDC); + } + } + + if (dialog_options.hDevMode != NULL) + GlobalFree(dialog_options.hDevMode); + if (dialog_options.hDevNames != NULL) + GlobalFree(dialog_options.hDevNames); + + switch (dialog_options.dwResultAction) { + case PD_RESULT_PRINT: + return context_ ? OK : FAILED; + case PD_RESULT_APPLY: + return context_ ? CANCEL : FAILED; + case PD_RESULT_CANCEL: + return CANCEL; + default: + return FAILED; + } +} + +PrintingContext::Result PrintingContextWin::ParseDialogResult( + const PRINTDLG& dialog_options) { + // If the user clicked OK or Apply then Cancel, but not only Cancel. + // Start fresh. + ResetSettings(); + + DEVMODE* dev_mode = NULL; + if (dialog_options.hDevMode) { + dev_mode = + reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode)); + DCHECK(dev_mode); + } + + std::wstring device_name; + if (dialog_options.hDevNames) { + DEVNAMES* dev_names = + reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames)); + DCHECK(dev_names); + if (dev_names) { + device_name = + reinterpret_cast<const wchar_t*>( + reinterpret_cast<const wchar_t*>(dev_names) + + dev_names->wDeviceOffset); + GlobalUnlock(dialog_options.hDevNames); + } + } + + bool success = false; + if (dev_mode && !device_name.empty()) { + context_ = dialog_options.hDC; + success = InitializeSettings(*dev_mode, device_name, NULL, 0, false); + } + + if (!success && dialog_options.hDC) { + DeleteDC(dialog_options.hDC); + context_ = NULL; + } + + if (dev_mode) { + GlobalUnlock(dialog_options.hDevMode); + } + + if (dialog_options.hDevMode != NULL) + GlobalFree(dialog_options.hDevMode); + if (dialog_options.hDevNames != NULL) + GlobalFree(dialog_options.hDevNames); + + return context_ ? OK : FAILED; +} + +} // namespace printing diff --git a/chromium/printing/printing_context_win.h b/chromium/printing/printing_context_win.h new file mode 100644 index 00000000000..a4773d04f79 --- /dev/null +++ b/chromium/printing/printing_context_win.h @@ -0,0 +1,96 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTING_CONTEXT_WIN_H_ +#define PRINTING_PRINTING_CONTEXT_WIN_H_ + +#include <ocidl.h> +#include <commdlg.h> + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "build/build_config.h" +#include "printing/printing_context.h" +#include "ui/gfx/native_widget_types.h" + +namespace printing { + +class PRINTING_EXPORT PrintingContextWin : public PrintingContext { + public: + explicit PrintingContextWin(const std::string& app_locale); + ~PrintingContextWin(); + + // PrintingContext implementation. + virtual void AskUserForSettings( + gfx::NativeView parent_view, + int max_pages, + bool has_selection, + const PrintSettingsCallback& callback) OVERRIDE; + virtual Result UseDefaultSettings() OVERRIDE; + virtual Result UpdatePrinterSettings( + const base::DictionaryValue& job_settings, + const PageRanges& ranges) OVERRIDE; + virtual Result InitWithSettings(const PrintSettings& settings) OVERRIDE; + virtual Result NewDocument(const string16& document_name) OVERRIDE; + virtual Result NewPage() OVERRIDE; + virtual Result PageDone() OVERRIDE; + virtual Result DocumentDone() OVERRIDE; + virtual void Cancel() OVERRIDE; + virtual void ReleaseContext() OVERRIDE; + virtual gfx::NativeDrawingContext context() const OVERRIDE; + +#if defined(UNIT_TEST) || defined(PRINTING_IMPLEMENTATION) + // Sets a fake PrintDlgEx function pointer in tests. + void SetPrintDialog(HRESULT (__stdcall *print_dialog_func)(LPPRINTDLGEX)) { + print_dialog_func_ = print_dialog_func; + } +#endif // defined(UNIT_TEST) + + // Allocates the HDC for a specific DEVMODE. + static bool AllocateContext(const std::wstring& printer_name, + const DEVMODE* dev_mode, + gfx::NativeDrawingContext* context); + + private: + // Class that manages the PrintDlgEx() callbacks. This is meant to be a + // temporary object used during the Print... dialog display. + class CallbackHandler; + + // Used in response to the user canceling the printing. + static BOOL CALLBACK AbortProc(HDC hdc, int nCode); + + // Reads the settings from the selected device context. Updates settings_ and + // its margins. + bool InitializeSettings(const DEVMODE& dev_mode, + const std::wstring& new_device_name, + const PRINTPAGERANGE* ranges, + int number_ranges, + bool selection_only); + + // Retrieves the printer's default low-level settings. On Windows, context_ is + // allocated with this call. + bool GetPrinterSettings(HANDLE printer, + const std::wstring& device_name); + + // Parses the result of a PRINTDLGEX result. + Result ParseDialogResultEx(const PRINTDLGEX& dialog_options); + Result ParseDialogResult(const PRINTDLG& dialog_options); + + // The selected printer context. + HDC context_; + + // The dialog box for the time it is shown. + volatile HWND dialog_box_; + + // Function pointer that defaults to PrintDlgEx. It can be changed using + // SetPrintDialog() in tests. + HRESULT (__stdcall *print_dialog_func_)(LPPRINTDLGEX); + + DISALLOW_COPY_AND_ASSIGN(PrintingContextWin); +}; + +} // namespace printing + +#endif // PRINTING_PRINTING_CONTEXT_WIN_H_ diff --git a/chromium/printing/printing_context_win_unittest.cc b/chromium/printing/printing_context_win_unittest.cc new file mode 100644 index 00000000000..5c6d1665549 --- /dev/null +++ b/chromium/printing/printing_context_win_unittest.cc @@ -0,0 +1,174 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <ocidl.h> +#include <commdlg.h> + +#include <string> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/scoped_ptr.h" +#include "printing/backend/printing_info_win.h" +#include "printing/printing_test.h" +#include "printing/printing_context.h" +#include "printing/printing_context_win.h" +#include "printing/print_settings.h" +#include "testing/gtest/include/gtest/gtest.h" + +// This test is automatically disabled if no printer is available. +class PrintingContextTest : public PrintingTest<testing::Test> { + public: + void PrintSettingsCallback(printing::PrintingContext::Result result) { + result_ = result; + } + + protected: + printing::PrintingContext::Result result() const { return result_; } + + private: + printing::PrintingContext::Result result_; +}; + +// This is a fake PrintDlgEx implementation that sets the right fields in +// |lppd| to trigger a bug in older revisions of PrintingContext. +HRESULT WINAPI PrintDlgExMock(LPPRINTDLGEX lppd) { + // The interesting bits: + // Pretend the user hit print + lppd->dwResultAction = PD_RESULT_PRINT; + + // Pretend the page range is 1-5, but since lppd->Flags does not have + // PD_SELECTION set, this really shouldn't matter. + lppd->nPageRanges = 1; + lppd->lpPageRanges[0].nFromPage = 1; + lppd->lpPageRanges[0].nToPage = 5; + + // Painful paperwork. + std::wstring printer_name = PrintingContextTest::GetDefaultPrinter(); + HANDLE printer; + if (!OpenPrinter(const_cast<wchar_t*>(printer_name.c_str()), &printer, NULL)) + return E_FAIL; + + scoped_ptr<uint8[]> buffer; + const DEVMODE* dev_mode = NULL; + HRESULT result = S_OK; + lppd->hDC = NULL; + lppd->hDevMode = NULL; + lppd->hDevNames = NULL; + + printing::PrinterInfo2 info_2; + if (info_2.Init(printer)) { + dev_mode = info_2.get()->pDevMode; + } + if (!dev_mode) { + result = E_FAIL; + goto Cleanup; + } + + if (!printing::PrintingContextWin::AllocateContext(printer_name, dev_mode, + &lppd->hDC)) { + result = E_FAIL; + goto Cleanup; + } + + size_t dev_mode_size = dev_mode->dmSize + dev_mode->dmDriverExtra; + lppd->hDevMode = GlobalAlloc(GMEM_MOVEABLE, dev_mode_size); + if (!lppd->hDevMode) { + result = E_FAIL; + goto Cleanup; + } + void* dev_mode_ptr = GlobalLock(lppd->hDevMode); + if (!dev_mode_ptr) { + result = E_FAIL; + goto Cleanup; + } + memcpy(dev_mode_ptr, dev_mode, dev_mode_size); + GlobalUnlock(lppd->hDevMode); + dev_mode_ptr = NULL; + + size_t driver_size = 2 + sizeof(wchar_t) * lstrlen(info_2.get()->pDriverName); + size_t printer_size = 2 + sizeof(wchar_t) * + lstrlen(info_2.get()->pPrinterName); + size_t port_size = 2 + sizeof(wchar_t) * lstrlen(info_2.get()->pPortName); + size_t dev_names_size = sizeof(DEVNAMES) + driver_size + printer_size + + port_size; + lppd->hDevNames = GlobalAlloc(GHND, dev_names_size); + if (!lppd->hDevNames) { + result = E_FAIL; + goto Cleanup; + } + void* dev_names_ptr = GlobalLock(lppd->hDevNames); + if (!dev_names_ptr) { + result = E_FAIL; + goto Cleanup; + } + DEVNAMES* dev_names = reinterpret_cast<DEVNAMES*>(dev_names_ptr); + dev_names->wDefault = 1; + dev_names->wDriverOffset = sizeof(DEVNAMES); + memcpy(reinterpret_cast<uint8*>(dev_names_ptr) + dev_names->wDriverOffset, + info_2.get()->pDriverName, driver_size); + dev_names->wDeviceOffset = dev_names->wDriverOffset + driver_size; + memcpy(reinterpret_cast<uint8*>(dev_names_ptr) + dev_names->wDeviceOffset, + info_2.get()->pPrinterName, printer_size); + dev_names->wOutputOffset = dev_names->wDeviceOffset + printer_size; + memcpy(reinterpret_cast<uint8*>(dev_names_ptr) + dev_names->wOutputOffset, + info_2.get()->pPortName, port_size); + GlobalUnlock(lppd->hDevNames); + dev_names_ptr = NULL; + +Cleanup: + // Note: This section does proper deallocation/free of DC/global handles. We + // did not use ScopedHGlobal or ScopedHandle because they did not + // perform what we need. Goto's are used based on Windows programming + // idiom, to avoid deeply nested if's, and try-catch-finally is not + // allowed in Chromium. + if (FAILED(result)) { + if (lppd->hDC) { + DeleteDC(lppd->hDC); + } + if (lppd->hDevMode) { + GlobalFree(lppd->hDevMode); + } + if (lppd->hDevNames) { + GlobalFree(lppd->hDevNames); + } + } + ClosePrinter(printer); + return result; +} + +TEST_F(PrintingContextTest, Base) { + // Sometimes ::GetDefaultPrinter() fails? bug 61509. + if (IsTestCaseDisabled()) + return; + + printing::PrintSettings settings; + settings.set_device_name(GetDefaultPrinter()); + // Initialize it. + scoped_ptr<printing::PrintingContext> context( + printing::PrintingContext::Create(std::string())); + EXPECT_EQ(printing::PrintingContext::OK, context->InitWithSettings(settings)); + + // The print may lie to use and may not support world transformation. + // Verify right now. + XFORM random_matrix = { 1, 0.1f, 0, 1.5f, 0, 1 }; + EXPECT_TRUE(SetWorldTransform(context->context(), &random_matrix)); + EXPECT_TRUE(ModifyWorldTransform(context->context(), NULL, MWT_IDENTITY)); +} + +TEST_F(PrintingContextTest, PrintAll) { + // Sometimes ::GetDefaultPrinter() fails? bug 61509. + if (IsTestCaseDisabled()) + return; + + std::string dummy_locale; + printing::PrintingContextWin context(dummy_locale); + context.SetPrintDialog(&PrintDlgExMock); + context.AskUserForSettings( + NULL, 123, false, base::Bind(&PrintingContextTest::PrintSettingsCallback, + base::Unretained(this))); + EXPECT_EQ(printing::PrintingContext::OK, result()); + printing::PrintSettings settings = context.settings(); + EXPECT_EQ(settings.ranges.size(), 0); +} diff --git a/chromium/printing/printing_export.h b/chromium/printing/printing_export.h new file mode 100644 index 00000000000..a8371910d47 --- /dev/null +++ b/chromium/printing/printing_export.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_PRINTING_EXPORT_H_ +#define PRINTING_PRINTING_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(PRINTING_IMPLEMENTATION) +#define PRINTING_EXPORT __declspec(dllexport) +#else +#define PRINTING_EXPORT __declspec(dllimport) +#endif // defined(PRINTING_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(PRINTING_IMPLEMENTATION) +#define PRINTING_EXPORT __attribute__((visibility("default"))) +#else +#define PRINTING_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define PRINTING_EXPORT +#endif + +#endif // PRINTING_PRINTING_EXPORT_H_ diff --git a/chromium/printing/printing_test.h b/chromium/printing/printing_test.h new file mode 100644 index 00000000000..35b91697a0a --- /dev/null +++ b/chromium/printing/printing_test.h @@ -0,0 +1,40 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_PRINTING_PRINTING_TEST_H__ +#define CHROME_BROWSER_PRINTING_PRINTING_TEST_H__ + +#include <windows.h> +#include <winspool.h> + +#include <string> + +#include "base/basictypes.h" + +// Disable the whole test case when executing on a computer that has no printer +// installed. +// Note: Parent should be testing::Test or InProcessBrowserTest. +template<typename Parent> +class PrintingTest : public Parent { + public: + static bool IsTestCaseDisabled() { + return GetDefaultPrinter().empty(); + } + static std::wstring GetDefaultPrinter() { + wchar_t printer_name[MAX_PATH]; + DWORD size = arraysize(printer_name); + BOOL result = ::GetDefaultPrinter(printer_name, &size); + if (result == 0) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + printf("There is no printer installed, printing can't be tested!\n"); + return std::wstring(); + } + printf("INTERNAL PRINTER ERROR!\n"); + return std::wstring(); + } + return printer_name; + } +}; + +#endif // CHROME_BROWSER_PRINTING_PRINTING_TEST_H__ diff --git a/chromium/printing/run_all_unittests.cc b/chromium/printing/run_all_unittests.cc new file mode 100644 index 00000000000..b715a328b99 --- /dev/null +++ b/chromium/printing/run_all_unittests.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_suite.h" + +int main(int argc, char** argv) { + return base::TestSuite(argc, argv).Run(); +} diff --git a/chromium/printing/units.cc b/chromium/printing/units.cc new file mode 100644 index 00000000000..dcee9ea56af --- /dev/null +++ b/chromium/printing/units.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/units.h" + +#include "base/logging.h" +#include "printing/print_job_constants.h" + +namespace printing { + +int ConvertUnit(int value, int old_unit, int new_unit) { + DCHECK_GT(new_unit, 0); + DCHECK_GT(old_unit, 0); + // With integer arithmetic, to divide a value with correct rounding, you need + // to add half of the divisor value to the dividend value. You need to do the + // reverse with negative number. + if (value >= 0) { + return ((value * new_unit) + (old_unit / 2)) / old_unit; + } else { + return ((value * new_unit) - (old_unit / 2)) / old_unit; + } +} + +double ConvertUnitDouble(double value, double old_unit, double new_unit) { + DCHECK_GT(new_unit, 0); + DCHECK_GT(old_unit, 0); + return value * new_unit / old_unit; +} + +int ConvertMilliInchToHundredThousanthMeter(int milli_inch) { + // 1" == 25.4 mm + // 1" == 25400 um + // 0.001" == 25.4 um + // 0.001" == 2.54 cmm + return ConvertUnit(milli_inch, 100, 254); +} + +int ConvertHundredThousanthMeterToMilliInch(int cmm) { + return ConvertUnit(cmm, 254, 100); +} + +int ConvertPixelsToPoint(int pixels) { + return ConvertUnit(pixels, kPixelsPerInch, kPointsPerInch); +} + +double ConvertPixelsToPointDouble(double pixels) { + return ConvertUnitDouble(pixels, kPixelsPerInch, kPointsPerInch); +} + +double ConvertPointsToPixelDouble(double points) { + return ConvertUnitDouble(points, kPointsPerInch, kPixelsPerInch); +} + +} // namespace printing diff --git a/chromium/printing/units.h b/chromium/printing/units.h new file mode 100644 index 00000000000..8f028324301 --- /dev/null +++ b/chromium/printing/units.h @@ -0,0 +1,47 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PRINTING_UNITS_H_ +#define PRINTING_UNITS_H_ + +#include "printing/printing_export.h" + +namespace printing { + +// Length of a thousanth of inches in 0.01mm unit. +const int kHundrethsMMPerInch = 2540; + +// Length of an inch in CSS's 1pt unit. +// http://dev.w3.org/csswg/css3-values/#absolute-length-units-cm-mm.-in-pt-pc +const int kPointsPerInch = 72; + +// Length of an inch in CSS's 1px unit. +// http://dev.w3.org/csswg/css3-values/#the-px-unit +const int kPixelsPerInch = 96; + +// Converts from one unit system to another using integer arithmetics. +PRINTING_EXPORT int ConvertUnit(int value, int old_unit, int new_unit); + +// Converts from one unit system to another using doubles. +PRINTING_EXPORT double ConvertUnitDouble(double value, double old_unit, + double new_unit); + +// Converts from 0.001 inch unit to 0.00001 meter. +PRINTING_EXPORT int ConvertMilliInchToHundredThousanthMeter(int milli_inch); + +// Converts from 0.00001 meter unit to 0.001 inch. +PRINTING_EXPORT int ConvertHundredThousanthMeterToMilliInch(int cmm); + +// Converts from 1 pixel to 1 point using integers. +PRINTING_EXPORT int ConvertPixelsToPoint(int pixels); + +// Converts from 1 pixel to 1 point using doubles. +PRINTING_EXPORT double ConvertPixelsToPointDouble(double pixels); + +// Converts from 1 point to 1 pixel using doubles. +double ConvertPointsToPixelDouble(double points); + +} // namespace printing + +#endif // PRINTING_UNITS_H_ diff --git a/chromium/printing/units_unittest.cc b/chromium/printing/units_unittest.cc new file mode 100644 index 00000000000..fe160b3f405 --- /dev/null +++ b/chromium/printing/units_unittest.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "printing/units.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gtest/include/gtest/gtest-spi.h" + +namespace printing { + +TEST(UnitsTest, Convertions) { + EXPECT_EQ(100, ConvertUnit(100, 100, 100)); + EXPECT_EQ(-100, ConvertUnit(-100, 100, 100)); + EXPECT_EQ(0, ConvertUnit(0, 100, 100)); + EXPECT_EQ(99, ConvertUnit(99, 100, 100)); + EXPECT_EQ(101, ConvertUnit(101, 100, 100)); + EXPECT_EQ(99900, ConvertUnit(999, 10, 1000)); + EXPECT_EQ(100100, ConvertUnit(1001, 10, 1000)); + + // Rounding. + EXPECT_EQ(10, ConvertUnit(999, 1000, 10)); + EXPECT_EQ(10, ConvertUnit(950, 1000, 10)); + EXPECT_EQ(9, ConvertUnit(949, 1000, 10)); + EXPECT_EQ(10, ConvertUnit(1001, 1000, 10)); + EXPECT_EQ(10, ConvertUnit(1049, 1000, 10)); + EXPECT_EQ(11, ConvertUnit(1050, 1000, 10)); + EXPECT_EQ(-10, ConvertUnit(-999, 1000, 10)); + EXPECT_EQ(-10, ConvertUnit(-950, 1000, 10)); + EXPECT_EQ(-9, ConvertUnit(-949, 1000, 10)); + EXPECT_EQ(-10, ConvertUnit(-1001, 1000, 10)); + EXPECT_EQ(-10, ConvertUnit(-1049, 1000, 10)); + EXPECT_EQ(-11, ConvertUnit(-1050, 1000, 10)); + + EXPECT_EQ(0, ConvertUnit(2, 1000000000, 1)); + EXPECT_EQ(2000000000, ConvertUnit(2, 1, 1000000000)); + EXPECT_EQ(4000000000U, + static_cast<unsigned int>(ConvertUnit(2, 1, 2000000000))); + + EXPECT_EQ(100, ConvertUnitDouble(100, 100, 100)); + EXPECT_EQ(-100, ConvertUnitDouble(-100, 100, 100)); + EXPECT_EQ(0, ConvertUnitDouble(0, 100, 100)); + EXPECT_DOUBLE_EQ(0.000002, ConvertUnitDouble(2, 1000, 0.001)); + EXPECT_EQ(2000000, ConvertUnitDouble(2, 0.001, 1000)); + + EXPECT_EQ(kHundrethsMMPerInch, ConvertMilliInchToHundredThousanthMeter(1000)); + EXPECT_EQ(-kHundrethsMMPerInch, + ConvertMilliInchToHundredThousanthMeter(-1000)); + EXPECT_EQ(0, ConvertMilliInchToHundredThousanthMeter(0)); + EXPECT_EQ(1000, ConvertHundredThousanthMeterToMilliInch(kHundrethsMMPerInch)); + EXPECT_EQ(-1000, + ConvertHundredThousanthMeterToMilliInch(-kHundrethsMMPerInch)); + EXPECT_EQ(0, ConvertHundredThousanthMeterToMilliInch(0)); + + EXPECT_EQ(8.25, ConvertPixelsToPointDouble(11.0)); + // Round down. + EXPECT_EQ(8, ConvertPixelsToPoint(11)); + EXPECT_EQ(7.5, ConvertPixelsToPointDouble(10.0)); + // Round up. + EXPECT_EQ(8, ConvertPixelsToPoint(10)); + EXPECT_EQ(0, ConvertPixelsToPoint(0)); +} + +} // namespace printing |