printing_context_chromeos.cc 15 KB

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