printing_context_win.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. // Copyright (c) 2012 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_win.h"
  5. #include <winspool.h>
  6. #include <algorithm>
  7. #include <utility>
  8. #include <vector>
  9. #include "base/bind.h"
  10. #include "base/check.h"
  11. #include "base/check_op.h"
  12. #include "base/memory/free_deleter.h"
  13. #include "base/strings/string_number_conversions.h"
  14. #include "base/strings/utf_string_conversions.h"
  15. #include "base/task/current_thread.h"
  16. #include "printing/backend/print_backend.h"
  17. #include "printing/backend/win_helper.h"
  18. #include "printing/buildflags/buildflags.h"
  19. #include "printing/metafile.h"
  20. #include "printing/metafile_skia.h"
  21. #include "printing/mojom/print.mojom.h"
  22. #include "printing/page_setup.h"
  23. #include "printing/print_settings_initializer_win.h"
  24. #include "printing/printed_document.h"
  25. #include "printing/printed_page_win.h"
  26. #include "printing/printing_context_system_dialog_win.h"
  27. #include "printing/printing_features.h"
  28. #include "printing/printing_utils.h"
  29. #include "printing/units.h"
  30. #include "skia/ext/skia_utils_win.h"
  31. #include "ui/aura/window.h"
  32. #include "ui/aura/window_tree_host.h"
  33. namespace printing {
  34. namespace {
  35. // Helper class to ensure that a saved device context state gets restored at end
  36. // of scope.
  37. class ScopedSavedState {
  38. public:
  39. ScopedSavedState(HDC context)
  40. : context_(context), saved_state_(SaveDC(context)) {
  41. DCHECK_NE(saved_state_, 0);
  42. }
  43. ~ScopedSavedState() {
  44. BOOL res = RestoreDC(context_, saved_state_);
  45. DCHECK_NE(res, 0);
  46. }
  47. private:
  48. HDC context_;
  49. int saved_state_;
  50. };
  51. void AssignResult(mojom::ResultCode* out, mojom::ResultCode in) {
  52. *out = in;
  53. }
  54. void SimpleModifyWorldTransform(HDC context,
  55. int offset_x,
  56. int offset_y,
  57. float shrink_factor) {
  58. XFORM xform = {0};
  59. xform.eDx = static_cast<float>(offset_x);
  60. xform.eDy = static_cast<float>(offset_y);
  61. xform.eM11 = xform.eM22 = 1.f / shrink_factor;
  62. BOOL res = ModifyWorldTransform(context, &xform, MWT_LEFTMULTIPLY);
  63. DCHECK_NE(res, 0);
  64. }
  65. } // namespace
  66. // static
  67. std::unique_ptr<PrintingContext> PrintingContext::CreateImpl(
  68. Delegate* delegate,
  69. bool skip_system_calls) {
  70. std::unique_ptr<PrintingContext> context;
  71. context = std::make_unique<PrintingContextSystemDialogWin>(delegate);
  72. #if BUILDFLAG(ENABLE_OOP_PRINTING)
  73. if (skip_system_calls)
  74. context->set_skip_system_calls();
  75. #endif
  76. return context;
  77. }
  78. PrintingContextWin::PrintingContextWin(Delegate* delegate)
  79. : PrintingContext(delegate), context_(nullptr) {}
  80. PrintingContextWin::~PrintingContextWin() {
  81. ReleaseContext();
  82. }
  83. void PrintingContextWin::AskUserForSettings(int max_pages,
  84. bool has_selection,
  85. bool is_scripted,
  86. PrintSettingsCallback callback) {
  87. NOTIMPLEMENTED();
  88. }
  89. mojom::ResultCode PrintingContextWin::UseDefaultSettings() {
  90. DCHECK(!in_print_job_);
  91. scoped_refptr<PrintBackend> backend =
  92. PrintBackend::CreateInstance(delegate_->GetAppLocale());
  93. std::string default_printer_name;
  94. mojom::ResultCode result =
  95. backend->GetDefaultPrinterName(default_printer_name);
  96. if (result != mojom::ResultCode::kSuccess)
  97. return result;
  98. std::wstring default_printer = base::UTF8ToWide(default_printer_name);
  99. if (!default_printer.empty()) {
  100. ScopedPrinterHandle printer;
  101. if (printer.OpenPrinterWithName(default_printer.c_str())) {
  102. std::unique_ptr<DEVMODE, base::FreeDeleter> dev_mode =
  103. CreateDevMode(printer.Get(), nullptr);
  104. if (InitializeSettings(default_printer, dev_mode.get()) ==
  105. mojom::ResultCode::kSuccess) {
  106. return mojom::ResultCode::kSuccess;
  107. }
  108. }
  109. }
  110. ReleaseContext();
  111. // No default printer configured, do we have any printers at all?
  112. DWORD bytes_needed = 0;
  113. DWORD count_returned = 0;
  114. (void)::EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, nullptr,
  115. 2, nullptr, 0, &bytes_needed, &count_returned);
  116. if (bytes_needed) {
  117. DCHECK_GE(bytes_needed, count_returned * sizeof(PRINTER_INFO_2));
  118. std::vector<BYTE> printer_info_buffer(bytes_needed);
  119. BOOL ret = ::EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
  120. nullptr, 2, printer_info_buffer.data(),
  121. bytes_needed, &bytes_needed, &count_returned);
  122. if (ret && count_returned) { // have printers
  123. // Open the first successfully found printer.
  124. const PRINTER_INFO_2* info_2 =
  125. reinterpret_cast<PRINTER_INFO_2*>(printer_info_buffer.data());
  126. const PRINTER_INFO_2* info_2_end = info_2 + count_returned;
  127. for (; info_2 < info_2_end; ++info_2) {
  128. ScopedPrinterHandle printer;
  129. if (!printer.OpenPrinterWithName(info_2->pPrinterName))
  130. continue;
  131. std::unique_ptr<DEVMODE, base::FreeDeleter> dev_mode =
  132. CreateDevMode(printer.Get(), nullptr);
  133. if (InitializeSettings(info_2->pPrinterName, dev_mode.get()) ==
  134. mojom::ResultCode::kSuccess) {
  135. return mojom::ResultCode::kSuccess;
  136. }
  137. }
  138. if (context_)
  139. return mojom::ResultCode::kSuccess;
  140. }
  141. }
  142. return OnError();
  143. }
  144. gfx::Size PrintingContextWin::GetPdfPaperSizeDeviceUnits() {
  145. // Default fallback to Letter size.
  146. gfx::SizeF paper_size(kLetterWidthInch, kLetterHeightInch);
  147. // Get settings from locale. Paper type buffer length is at most 4.
  148. const int paper_type_buffer_len = 4;
  149. wchar_t paper_type_buffer[paper_type_buffer_len] = {0};
  150. GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IPAPERSIZE, paper_type_buffer,
  151. paper_type_buffer_len);
  152. if (wcslen(paper_type_buffer)) { // The call succeeded.
  153. int paper_code = _wtoi(paper_type_buffer);
  154. switch (paper_code) {
  155. case DMPAPER_LEGAL:
  156. paper_size.SetSize(kLegalWidthInch, kLegalHeightInch);
  157. break;
  158. case DMPAPER_A4:
  159. paper_size.SetSize(kA4WidthInch, kA4HeightInch);
  160. break;
  161. case DMPAPER_A3:
  162. paper_size.SetSize(kA3WidthInch, kA3HeightInch);
  163. break;
  164. default: // DMPAPER_LETTER is used for default fallback.
  165. break;
  166. }
  167. }
  168. return gfx::Size(paper_size.width() * settings_->device_units_per_inch(),
  169. paper_size.height() * settings_->device_units_per_inch());
  170. }
  171. mojom::ResultCode PrintingContextWin::UpdatePrinterSettings(
  172. const PrinterSettings& printer_settings) {
  173. DCHECK(!in_print_job_);
  174. ScopedPrinterHandle printer;
  175. if (!printer.OpenPrinterWithName(base::as_wcstr(settings_->device_name())))
  176. return OnError();
  177. // Make printer changes local to Chrome.
  178. // See MSDN documentation regarding DocumentProperties.
  179. std::unique_ptr<DEVMODE, base::FreeDeleter> scoped_dev_mode =
  180. CreateDevModeWithColor(printer.Get(),
  181. base::UTF16ToWide(settings_->device_name()),
  182. settings_->color() != mojom::ColorModel::kGray);
  183. if (!scoped_dev_mode)
  184. return OnError();
  185. {
  186. DEVMODE* dev_mode = scoped_dev_mode.get();
  187. dev_mode->dmCopies = std::max(settings_->copies(), 1);
  188. if (dev_mode->dmCopies > 1) { // do not change unless multiple copies
  189. dev_mode->dmFields |= DM_COPIES;
  190. dev_mode->dmCollate =
  191. settings_->collate() ? DMCOLLATE_TRUE : DMCOLLATE_FALSE;
  192. }
  193. switch (settings_->duplex_mode()) {
  194. case mojom::DuplexMode::kLongEdge:
  195. dev_mode->dmFields |= DM_DUPLEX;
  196. dev_mode->dmDuplex = DMDUP_VERTICAL;
  197. break;
  198. case mojom::DuplexMode::kShortEdge:
  199. dev_mode->dmFields |= DM_DUPLEX;
  200. dev_mode->dmDuplex = DMDUP_HORIZONTAL;
  201. break;
  202. case mojom::DuplexMode::kSimplex:
  203. dev_mode->dmFields |= DM_DUPLEX;
  204. dev_mode->dmDuplex = DMDUP_SIMPLEX;
  205. break;
  206. default: // kUnknownDuplexMode
  207. break;
  208. }
  209. dev_mode->dmFields |= DM_ORIENTATION;
  210. dev_mode->dmOrientation =
  211. settings_->landscape() ? DMORIENT_LANDSCAPE : DMORIENT_PORTRAIT;
  212. if (settings_->dpi_horizontal() > 0) {
  213. dev_mode->dmPrintQuality = settings_->dpi_horizontal();
  214. dev_mode->dmFields |= DM_PRINTQUALITY;
  215. }
  216. if (settings_->dpi_vertical() > 0) {
  217. dev_mode->dmYResolution = settings_->dpi_vertical();
  218. dev_mode->dmFields |= DM_YRESOLUTION;
  219. }
  220. const PrintSettings::RequestedMedia& requested_media =
  221. settings_->requested_media();
  222. unsigned id = 0;
  223. // If the paper size is a custom user size, setting by ID may not work.
  224. if (base::StringToUint(requested_media.vendor_id, &id) && id &&
  225. id < DMPAPER_USER) {
  226. dev_mode->dmFields |= DM_PAPERSIZE;
  227. dev_mode->dmPaperSize = static_cast<short>(id);
  228. } else if (!requested_media.size_microns.IsEmpty()) {
  229. static constexpr int kFromUm = 100; // Windows uses 0.1mm.
  230. dev_mode->dmFields |= DM_PAPERWIDTH | DM_PAPERLENGTH;
  231. dev_mode->dmPaperWidth = requested_media.size_microns.width() / kFromUm;
  232. dev_mode->dmPaperLength = requested_media.size_microns.height() / kFromUm;
  233. }
  234. }
  235. // Update data using DocumentProperties.
  236. if (printer_settings.show_system_dialog) {
  237. mojom::ResultCode result = mojom::ResultCode::kFailed;
  238. AskUserForSettings(printer_settings.page_count, /*has_selection=*/false,
  239. /*is_scripted=*/false,
  240. base::BindOnce(&AssignResult, &result));
  241. return result;
  242. }
  243. // Set printer then refresh printer settings.
  244. scoped_dev_mode = CreateDevMode(printer.Get(), scoped_dev_mode.get());
  245. if (!scoped_dev_mode)
  246. return OnError();
  247. // Since CreateDevMode() doesn't honor color settings through the GDI call
  248. // to DocumentProperties(), ensure the requested values persist here.
  249. scoped_dev_mode->dmFields |= DM_COLOR;
  250. scoped_dev_mode->dmColor = settings_->color() != mojom::ColorModel::kGray
  251. ? DMCOLOR_COLOR
  252. : DMCOLOR_MONOCHROME;
  253. return InitializeSettings(base::UTF16ToWide(settings_->device_name()),
  254. scoped_dev_mode.get());
  255. }
  256. mojom::ResultCode PrintingContextWin::InitWithSettingsForTest(
  257. std::unique_ptr<PrintSettings> settings) {
  258. DCHECK(!in_print_job_);
  259. settings_ = std::move(settings);
  260. // TODO(maruel): settings_.ToDEVMODE()
  261. ScopedPrinterHandle printer;
  262. if (!printer.OpenPrinterWithName(base::as_wcstr(settings_->device_name()))) {
  263. return logging::GetLastSystemErrorCode() == ERROR_ACCESS_DENIED
  264. ? mojom::ResultCode::kAccessDenied
  265. : mojom::ResultCode::kFailed;
  266. }
  267. std::unique_ptr<DEVMODE, base::FreeDeleter> dev_mode =
  268. CreateDevMode(printer.Get(), nullptr);
  269. return InitializeSettings(base::UTF16ToWide(settings_->device_name()),
  270. dev_mode.get());
  271. }
  272. mojom::ResultCode PrintingContextWin::NewDocument(
  273. const std::u16string& document_name) {
  274. DCHECK(!in_print_job_);
  275. if (!context_ && !skip_system_calls())
  276. return OnError();
  277. // Set the flag used by the AbortPrintJob dialog procedure.
  278. abort_printing_ = false;
  279. in_print_job_ = true;
  280. if (skip_system_calls())
  281. return mojom::ResultCode::kSuccess;
  282. if (base::FeatureList::IsEnabled(printing::features::kUseXpsForPrinting)) {
  283. // This is all the new document context needed when using XPS.
  284. return mojom::ResultCode::kSuccess;
  285. }
  286. // Need more context setup when using GDI.
  287. // Register the application's AbortProc function with GDI.
  288. if (SP_ERROR == SetAbortProc(context_, &AbortProc))
  289. return OnError();
  290. DCHECK(SimplifyDocumentTitle(document_name) == document_name);
  291. DOCINFO di = {sizeof(DOCINFO)};
  292. di.lpszDocName = base::as_wcstr(document_name);
  293. // Is there a debug dump directory specified? If so, force to print to a file.
  294. if (PrintedDocument::HasDebugDumpPath()) {
  295. base::FilePath debug_dump_path = PrintedDocument::CreateDebugDumpPath(
  296. document_name, FILE_PATH_LITERAL(".prn"));
  297. if (!debug_dump_path.empty())
  298. di.lpszOutput = debug_dump_path.value().c_str();
  299. }
  300. // No message loop running in unit tests.
  301. DCHECK(!base::CurrentThread::Get() ||
  302. !base::CurrentThread::Get()->NestableTasksAllowed());
  303. // Begin a print job by calling the StartDoc function.
  304. // NOTE: StartDoc() starts a message loop. That causes a lot of problems with
  305. // IPC. Make sure recursive task processing is disabled.
  306. if (StartDoc(context_, &di) <= 0)
  307. return OnError();
  308. return mojom::ResultCode::kSuccess;
  309. }
  310. mojom::ResultCode PrintingContextWin::RenderPage(const PrintedPage& page,
  311. const PageSetup& page_setup) {
  312. if (abort_printing_)
  313. return mojom::ResultCode::kCanceled;
  314. DCHECK(context_);
  315. DCHECK(in_print_job_);
  316. gfx::Rect content_area = GetCenteredPageContentRect(
  317. page_setup.physical_size(), page.page_size(), page.page_content_rect());
  318. // Save the state to make sure the context this function call does not modify
  319. // the device context.
  320. ScopedSavedState saved_state(context_);
  321. skia::InitializeDC(context_);
  322. {
  323. // Save the state (again) to apply the necessary world transformation.
  324. ScopedSavedState saved_state_inner(context_);
  325. // Setup the matrix to translate and scale to the right place. Take in
  326. // account the actual shrinking factor.
  327. // Note that the printing output is relative to printable area of the page.
  328. // That is 0,0 is offset by PHYSICALOFFSETX/Y from the page.
  329. SimpleModifyWorldTransform(
  330. context_, content_area.x() - page_setup.printable_area().x(),
  331. content_area.y() - page_setup.printable_area().y(),
  332. page.shrink_factor());
  333. if (::StartPage(context_) <= 0)
  334. return mojom::ResultCode::kFailed;
  335. bool played_back = page.metafile()->SafePlayback(context_);
  336. DCHECK(played_back);
  337. if (::EndPage(context_) <= 0)
  338. return mojom::ResultCode::kFailed;
  339. }
  340. return mojom::ResultCode::kSuccess;
  341. }
  342. mojom::ResultCode PrintingContextWin::PrintDocument(
  343. const MetafilePlayer& metafile,
  344. const PrintSettings& settings,
  345. uint32_t num_pages) {
  346. // TODO(crbug.com/1008222)
  347. NOTIMPLEMENTED();
  348. return mojom::ResultCode::kFailed;
  349. }
  350. mojom::ResultCode PrintingContextWin::DocumentDone() {
  351. if (abort_printing_)
  352. return mojom::ResultCode::kCanceled;
  353. DCHECK(in_print_job_);
  354. DCHECK(context_);
  355. // Inform the driver that document has ended.
  356. if (EndDoc(context_) <= 0)
  357. return OnError();
  358. ResetSettings();
  359. return mojom::ResultCode::kSuccess;
  360. }
  361. void PrintingContextWin::Cancel() {
  362. abort_printing_ = true;
  363. in_print_job_ = false;
  364. if (context_)
  365. CancelDC(context_);
  366. }
  367. void PrintingContextWin::ReleaseContext() {
  368. if (context_) {
  369. DeleteDC(context_);
  370. context_ = nullptr;
  371. }
  372. }
  373. printing::NativeDrawingContext PrintingContextWin::context() const {
  374. return context_;
  375. }
  376. // static
  377. BOOL PrintingContextWin::AbortProc(HDC hdc, int nCode) {
  378. if (nCode) {
  379. // TODO(maruel): Need a way to find the right instance to set. Should
  380. // leverage PrintJobManager here?
  381. // abort_printing_ = true;
  382. }
  383. return true;
  384. }
  385. mojom::ResultCode PrintingContextWin::InitializeSettings(
  386. const std::wstring& device_name,
  387. DEVMODE* dev_mode) {
  388. if (!dev_mode)
  389. return OnError();
  390. ReleaseContext();
  391. context_ = CreateDC(L"WINSPOOL", device_name.c_str(), nullptr, dev_mode);
  392. if (!context_)
  393. return OnError();
  394. skia::InitializeDC(context_);
  395. DCHECK(!in_print_job_);
  396. settings_->set_device_name(base::WideToUTF16(device_name));
  397. PrintSettingsInitializerWin::InitPrintSettings(context_, *dev_mode,
  398. settings_.get());
  399. return mojom::ResultCode::kSuccess;
  400. }
  401. mojom::ResultCode PrintingContextWin::OnError() {
  402. mojom::ResultCode result;
  403. if (abort_printing_) {
  404. result = mojom::ResultCode::kCanceled;
  405. } else {
  406. result = logging::GetLastSystemErrorCode() == ERROR_ACCESS_DENIED
  407. ? mojom::ResultCode::kAccessDenied
  408. : mojom::ResultCode::kFailed;
  409. }
  410. ResetSettings();
  411. return result;
  412. }
  413. HWND PrintingContextWin::GetRootWindow(gfx::NativeView view) {
  414. HWND window = nullptr;
  415. if (view && view->GetHost())
  416. window = view->GetHost()->GetAcceleratedWidget();
  417. if (!window) {
  418. // TODO(maruel): b/1214347 Get the right browser window instead.
  419. return GetDesktopWindow();
  420. }
  421. return window;
  422. }
  423. } // namespace printing