printing_context_chromeos.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. // Copyright 2016 The Chromium Authors
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "printing/printing_context_chromeos.h"
  5. #include <cups/cups.h>
  6. #include <stdint.h>
  7. #include <unicode/ulocdata.h>
  8. #include <map>
  9. #include <memory>
  10. #include <utility>
  11. #include <vector>
  12. #include "base/logging.h"
  13. #include "base/memory/ptr_util.h"
  14. #include "base/strings/string_number_conversions.h"
  15. #include "base/strings/string_util.h"
  16. #include "base/strings/utf_string_conversions.h"
  17. #include "printing/backend/cups_connection.h"
  18. #include "printing/backend/cups_ipp_constants.h"
  19. #include "printing/backend/cups_ipp_helper.h"
  20. #include "printing/backend/cups_printer.h"
  21. #include "printing/buildflags/buildflags.h"
  22. #include "printing/metafile.h"
  23. #include "printing/mojom/print.mojom.h"
  24. #include "printing/print_job_constants.h"
  25. #include "printing/print_settings.h"
  26. #include "printing/printing_utils.h"
  27. #include "printing/units.h"
  28. namespace printing {
  29. namespace {
  30. // We only support sending username for secure printers.
  31. const char kUsernamePlaceholder[] = "chronos";
  32. // We only support sending document name for secure printers.
  33. const char kDocumentNamePlaceholder[] = "-";
  34. bool IsUriSecure(base::StringPiece uri) {
  35. return base::StartsWith(uri, "ipps:") || base::StartsWith(uri, "https:") ||
  36. base::StartsWith(uri, "usb:") || base::StartsWith(uri, "ippusb:");
  37. }
  38. ScopedCupsOption ConstructOption(std::string name, std::string value) {
  39. // ScopedCupsOption frees the name and value buffers on deletion
  40. cups_option_t* cups_option = nullptr;
  41. int num_options = 0;
  42. // Use cupsAddOption so that the pair of malloc and free are used.
  43. num_options =
  44. cupsAddOption(name.c_str(), value.c_str(), num_options, &cups_option);
  45. DCHECK(cups_option);
  46. DCHECK_EQ(num_options, 1);
  47. return ScopedCupsOption(cups_option);
  48. }
  49. std::string GetCollateString(bool collate) {
  50. return collate ? kCollated : kUncollated;
  51. }
  52. // Given an integral `value` expressed in PWG units (1/100 mm), returns
  53. // the same value expressed in device units.
  54. int PwgUnitsToDeviceUnits(int value, float micrometers_per_device_unit) {
  55. return ConvertUnitFloat(value, micrometers_per_device_unit, 10);
  56. }
  57. // Given a `media_size`, the specification of the media's `margins`, and
  58. // the number of micrometers per device unit, returns the rectangle
  59. // bounding the apparent printable area of said media.
  60. gfx::Rect RepresentPrintableArea(const gfx::Size& media_size,
  61. const CupsPrinter::CupsMediaMargins& margins,
  62. float micrometers_per_device_unit) {
  63. // These values express inward encroachment by margins, away from the
  64. // edges of the `media_size`.
  65. int left_bound =
  66. PwgUnitsToDeviceUnits(margins.left, micrometers_per_device_unit);
  67. int bottom_bound =
  68. PwgUnitsToDeviceUnits(margins.bottom, micrometers_per_device_unit);
  69. int right_bound =
  70. PwgUnitsToDeviceUnits(margins.right, micrometers_per_device_unit);
  71. int top_bound =
  72. PwgUnitsToDeviceUnits(margins.top, micrometers_per_device_unit);
  73. // These values express the bounding box of the printable area on the
  74. // page.
  75. int printable_width = media_size.width() - (left_bound + right_bound);
  76. int printable_height = media_size.height() - (top_bound + bottom_bound);
  77. if (printable_width > 0 && printable_height > 0) {
  78. return {left_bound, bottom_bound, printable_width, printable_height};
  79. }
  80. return {0, 0, media_size.width(), media_size.height()};
  81. }
  82. void SetPrintableArea(PrintSettings* settings,
  83. const PrintSettings::RequestedMedia& media,
  84. const CupsPrinter::CupsMediaMargins& margins) {
  85. if (!media.size_microns.IsEmpty()) {
  86. float device_microns_per_device_unit =
  87. static_cast<float>(kMicronsPerInch) / settings->device_units_per_inch();
  88. gfx::Size paper_size =
  89. gfx::Size(media.size_microns.width() / device_microns_per_device_unit,
  90. media.size_microns.height() / device_microns_per_device_unit);
  91. gfx::Rect paper_rect = RepresentPrintableArea(
  92. paper_size, margins, device_microns_per_device_unit);
  93. settings->SetPrinterPrintableArea(paper_size, paper_rect,
  94. /*landscape_needs_flip=*/true);
  95. }
  96. }
  97. } // namespace
  98. std::vector<ScopedCupsOption> SettingsToCupsOptions(
  99. const PrintSettings& settings) {
  100. const char* sides = nullptr;
  101. switch (settings.duplex_mode()) {
  102. case mojom::DuplexMode::kSimplex:
  103. sides = CUPS_SIDES_ONE_SIDED;
  104. break;
  105. case mojom::DuplexMode::kLongEdge:
  106. sides = CUPS_SIDES_TWO_SIDED_PORTRAIT;
  107. break;
  108. case mojom::DuplexMode::kShortEdge:
  109. sides = CUPS_SIDES_TWO_SIDED_LANDSCAPE;
  110. break;
  111. default:
  112. NOTREACHED();
  113. }
  114. std::vector<ScopedCupsOption> options;
  115. options.push_back(
  116. ConstructOption(kIppColor,
  117. GetIppColorModelForModel(settings.color()))); // color
  118. options.push_back(ConstructOption(kIppDuplex, sides)); // duplexing
  119. options.push_back(
  120. ConstructOption(kIppMedia,
  121. settings.requested_media().vendor_id)); // paper size
  122. options.push_back(
  123. ConstructOption(kIppCopies,
  124. base::NumberToString(settings.copies()))); // copies
  125. options.push_back(
  126. ConstructOption(kIppCollate,
  127. GetCollateString(settings.collate()))); // collate
  128. if (!settings.pin_value().empty()) {
  129. options.push_back(ConstructOption(kIppPin, settings.pin_value()));
  130. options.push_back(ConstructOption(kIppPinEncryption, kPinEncryptionNone));
  131. }
  132. if (settings.dpi_horizontal() > 0 && settings.dpi_vertical() > 0) {
  133. std::string dpi = base::NumberToString(settings.dpi_horizontal());
  134. if (settings.dpi_horizontal() != settings.dpi_vertical())
  135. dpi += "x" + base::NumberToString(settings.dpi_vertical());
  136. options.push_back(ConstructOption(kIppResolution, dpi + "dpi"));
  137. }
  138. std::map<std::string, std::vector<std::string>> multival;
  139. for (const auto& setting : settings.advanced_settings()) {
  140. const std::string& key = setting.first;
  141. const std::string& value = setting.second.GetString();
  142. if (value.empty())
  143. continue;
  144. // Check for multivalue enum ("attribute/value").
  145. size_t pos = key.find('/');
  146. if (pos == std::string::npos) {
  147. // Regular value.
  148. options.push_back(ConstructOption(key, value));
  149. continue;
  150. }
  151. // Store selected enum values.
  152. if (value == kOptionTrue)
  153. multival[key.substr(0, pos)].push_back(key.substr(pos + 1));
  154. }
  155. // Pass multivalue enums as comma-separated lists.
  156. for (const auto& it : multival) {
  157. options.push_back(
  158. ConstructOption(it.first, base::JoinString(it.second, ",")));
  159. }
  160. return options;
  161. }
  162. // static
  163. std::unique_ptr<PrintingContext> PrintingContext::CreateImpl(
  164. Delegate* delegate,
  165. bool skip_system_calls) {
  166. auto context = std::make_unique<PrintingContextChromeos>(delegate);
  167. #if BUILDFLAG(ENABLE_OOP_PRINTING)
  168. if (skip_system_calls)
  169. context->set_skip_system_calls();
  170. #endif
  171. return context;
  172. }
  173. // static
  174. std::unique_ptr<PrintingContextChromeos>
  175. PrintingContextChromeos::CreateForTesting(
  176. Delegate* delegate,
  177. std::unique_ptr<CupsConnection> connection) {
  178. // Private ctor.
  179. return base::WrapUnique(
  180. new PrintingContextChromeos(delegate, std::move(connection)));
  181. }
  182. PrintingContextChromeos::PrintingContextChromeos(Delegate* delegate)
  183. : PrintingContext(delegate),
  184. connection_(CupsConnection::Create(GURL(), HTTP_ENCRYPT_NEVER, true)) {}
  185. PrintingContextChromeos::PrintingContextChromeos(
  186. Delegate* delegate,
  187. std::unique_ptr<CupsConnection> connection)
  188. : PrintingContext(delegate), connection_(std::move(connection)) {}
  189. PrintingContextChromeos::~PrintingContextChromeos() {
  190. ReleaseContext();
  191. }
  192. void PrintingContextChromeos::AskUserForSettings(
  193. int max_pages,
  194. bool has_selection,
  195. bool is_scripted,
  196. PrintSettingsCallback callback) {
  197. // We don't want to bring up a dialog here. Ever. This should not be called.
  198. NOTREACHED();
  199. }
  200. mojom::ResultCode PrintingContextChromeos::UseDefaultSettings() {
  201. DCHECK(!in_print_job_);
  202. ResetSettings();
  203. std::string device_name = base::UTF16ToUTF8(settings_->device_name());
  204. if (device_name.empty())
  205. return OnError();
  206. // TODO(skau): https://crbug.com/613779. See UpdatePrinterSettings for more
  207. // info.
  208. if (settings_->dpi() == 0) {
  209. DVLOG(1) << "Using Default DPI";
  210. settings_->set_dpi(kDefaultPdfDpi);
  211. }
  212. // Retrieve device information and set it
  213. if (InitializeDevice(device_name) != mojom::ResultCode::kSuccess) {
  214. LOG(ERROR) << "Could not initialize printer";
  215. return OnError();
  216. }
  217. // Set printable area
  218. DCHECK(printer_);
  219. PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_);
  220. PrintSettings::RequestedMedia media;
  221. media.vendor_id = paper.vendor_id;
  222. media.size_microns = paper.size_um;
  223. settings_->set_requested_media(media);
  224. CupsPrinter::CupsMediaMargins margins =
  225. printer_->GetMediaMarginsByName(paper.vendor_id);
  226. SetPrintableArea(settings_.get(), media, margins);
  227. return mojom::ResultCode::kSuccess;
  228. }
  229. gfx::Size PrintingContextChromeos::GetPdfPaperSizeDeviceUnits() {
  230. int32_t width = 0;
  231. int32_t height = 0;
  232. UErrorCode error = U_ZERO_ERROR;
  233. ulocdata_getPaperSize(delegate_->GetAppLocale().c_str(), &height, &width,
  234. &error);
  235. if (error > U_ZERO_ERROR) {
  236. // If the call failed, assume a paper size of 8.5 x 11 inches.
  237. LOG(WARNING) << "ulocdata_getPaperSize failed, using 8.5 x 11, error: "
  238. << error;
  239. width =
  240. static_cast<int>(kLetterWidthInch * settings_->device_units_per_inch());
  241. height = static_cast<int>(kLetterHeightInch *
  242. settings_->device_units_per_inch());
  243. } else {
  244. // ulocdata_getPaperSize returns the width and height in mm.
  245. // Convert this to pixels based on the dpi.
  246. float multiplier = settings_->device_units_per_inch() / kMicronsPerMil;
  247. width *= multiplier;
  248. height *= multiplier;
  249. }
  250. return gfx::Size(width, height);
  251. }
  252. mojom::ResultCode PrintingContextChromeos::UpdatePrinterSettings(
  253. const PrinterSettings& printer_settings) {
  254. DCHECK(!printer_settings.show_system_dialog);
  255. if (InitializeDevice(base::UTF16ToUTF8(settings_->device_name())) !=
  256. mojom::ResultCode::kSuccess) {
  257. return OnError();
  258. }
  259. // TODO(skau): Convert to DCHECK when https://crbug.com/613779 is resolved
  260. // Print quality suffers when this is set to the resolution reported by the
  261. // printer but print quality is fine at this resolution. UseDefaultSettings
  262. // exhibits the same problem.
  263. if (settings_->dpi() == 0) {
  264. DVLOG(1) << "Using Default DPI";
  265. settings_->set_dpi(kDefaultPdfDpi);
  266. }
  267. // compute paper size
  268. PrintSettings::RequestedMedia media = settings_->requested_media();
  269. DCHECK(printer_);
  270. if (media.IsDefault()) {
  271. PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_);
  272. media.vendor_id = paper.vendor_id;
  273. media.size_microns = paper.size_um;
  274. settings_->set_requested_media(media);
  275. }
  276. CupsPrinter::CupsMediaMargins margins =
  277. printer_->GetMediaMarginsByName(media.vendor_id);
  278. SetPrintableArea(settings_.get(), media, margins);
  279. cups_options_ = SettingsToCupsOptions(*settings_);
  280. send_user_info_ = settings_->send_user_info();
  281. if (send_user_info_) {
  282. DCHECK(printer_);
  283. username_ = IsUriSecure(printer_->GetUri()) ? settings_->username()
  284. : kUsernamePlaceholder;
  285. }
  286. return mojom::ResultCode::kSuccess;
  287. }
  288. mojom::ResultCode PrintingContextChromeos::InitializeDevice(
  289. const std::string& device) {
  290. DCHECK(!in_print_job_);
  291. std::unique_ptr<CupsPrinter> printer = connection_->GetPrinter(device);
  292. if (!printer) {
  293. LOG(WARNING) << "Could not initialize device";
  294. return OnError();
  295. }
  296. printer_ = std::move(printer);
  297. return mojom::ResultCode::kSuccess;
  298. }
  299. mojom::ResultCode PrintingContextChromeos::NewDocument(
  300. const std::u16string& document_name) {
  301. DCHECK(!in_print_job_);
  302. in_print_job_ = true;
  303. if (skip_system_calls())
  304. return mojom::ResultCode::kSuccess;
  305. std::string converted_name;
  306. if (send_user_info_) {
  307. DCHECK(printer_);
  308. converted_name = IsUriSecure(printer_->GetUri())
  309. ? base::UTF16ToUTF8(document_name)
  310. : kDocumentNamePlaceholder;
  311. }
  312. std::vector<cups_option_t> options;
  313. for (const ScopedCupsOption& option : cups_options_) {
  314. if (printer_->CheckOptionSupported(option->name, option->value)) {
  315. options.push_back(*(option.get()));
  316. } else {
  317. DVLOG(1) << "Unsupported option skipped " << option->name << ", "
  318. << option->value;
  319. }
  320. }
  321. ipp_status_t create_status =
  322. printer_->CreateJob(&job_id_, converted_name, username_, options);
  323. if (job_id_ == 0) {
  324. DLOG(WARNING) << "Creating cups job failed"
  325. << ippErrorString(create_status);
  326. return OnError();
  327. }
  328. // we only send one document, so it's always the last one
  329. if (!printer_->StartDocument(job_id_, converted_name, true, username_,
  330. options)) {
  331. LOG(ERROR) << "Starting document failed";
  332. return OnError();
  333. }
  334. return mojom::ResultCode::kSuccess;
  335. }
  336. mojom::ResultCode PrintingContextChromeos::PrintDocument(
  337. const MetafilePlayer& metafile,
  338. const PrintSettings& settings,
  339. uint32_t num_pages) {
  340. if (abort_printing_)
  341. return mojom::ResultCode::kCanceled;
  342. DCHECK(in_print_job_);
  343. #if defined(USE_CUPS)
  344. std::vector<char> buffer;
  345. if (!metafile.GetDataAsVector(&buffer))
  346. return mojom::ResultCode::kFailed;
  347. return StreamData(buffer);
  348. #else
  349. NOTREACHED();
  350. return mojom::ResultCode::kFailed;
  351. #endif // defined(USE_CUPS)
  352. }
  353. mojom::ResultCode PrintingContextChromeos::DocumentDone() {
  354. if (abort_printing_)
  355. return mojom::ResultCode::kCanceled;
  356. DCHECK(in_print_job_);
  357. if (!printer_->FinishDocument()) {
  358. LOG(WARNING) << "Finishing document failed";
  359. return OnError();
  360. }
  361. ipp_status_t job_status = printer_->CloseJob(job_id_, username_);
  362. job_id_ = 0;
  363. if (job_status != IPP_STATUS_OK) {
  364. LOG(WARNING) << "Closing job failed";
  365. return OnError();
  366. }
  367. ResetSettings();
  368. return mojom::ResultCode::kSuccess;
  369. }
  370. void PrintingContextChromeos::Cancel() {
  371. abort_printing_ = true;
  372. in_print_job_ = false;
  373. }
  374. void PrintingContextChromeos::ReleaseContext() {
  375. printer_.reset();
  376. }
  377. printing::NativeDrawingContext PrintingContextChromeos::context() const {
  378. // Intentional No-op.
  379. return nullptr;
  380. }
  381. mojom::ResultCode PrintingContextChromeos::StreamData(
  382. const std::vector<char>& buffer) {
  383. if (abort_printing_)
  384. return mojom::ResultCode::kCanceled;
  385. DCHECK(in_print_job_);
  386. DCHECK(printer_);
  387. if (!printer_->StreamData(buffer))
  388. return OnError();
  389. return mojom::ResultCode::kSuccess;
  390. }
  391. } // namespace printing