PrintOnWindows.cs
  1. //
  2. // This code is part of Document Solutions for PDF demos.
  3. // Copyright (c) MESCIUS inc. All rights reserved.
  4. //
  5. // NOTE: #define DsPdfBefore_8_0_3 if using versions of DsPdf prior to v8.0.3:
  6. // #define DsPdfBefore_8_0_3
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Text;
  10. using System.Runtime.InteropServices;
  11. using System.Drawing;
  12. using System.Drawing.Printing;
  13. using System.ComponentModel;
  14. using System.Numerics;
  15. using System.IO;
  16.  
  17. using GrapeCity.Documents.Common;
  18. using GrapeCity.Documents.Pdf;
  19. using GrapeCity.Documents.Pdf.Renderer;
  20. using GrapeCity.Documents.Drawing;
  21. using GrapeCity.Documents.Text;
  22. using GrapeCity.Documents.Imaging.Windows;
  23.  
  24. using STG = GrapeCity.Documents.DX.Storage;
  25. using D2D = GrapeCity.Documents.DX.Direct2D;
  26. using D3D = GrapeCity.Documents.DX.Direct3D11;
  27. using DW = GrapeCity.Documents.DX.DirectWrite;
  28. using DXGI = GrapeCity.Documents.DX.DXGI;
  29. using WIC = GrapeCity.Documents.DX.WIC;
  30. using DX = GrapeCity.Documents.DX;
  31.  
  32.  
  33. namespace DsPdfWeb.Demos
  34. {
  35. // This sample shows how to print a GcPdfDocument on a Windows system using Direct2D.
  36. // The GcD2DBitmap and GcD2DGraphics classes that make this possible live in the
  37. // GrapeCity.Documents.Imaging.Windows package.
  38. //
  39. // This sample includes some ready to use utility types that implement printing
  40. // and can be used as is in your applications:
  41. // - Static class GcPdfDocumentPrintExt provides convenient extension methods
  42. // to print a GcPdfDocument.
  43. // - Class GcPdfPrintManager implements printing services used by GcPdfDocumentPrintExt.
  44. // - PageScaling enum specifies how pages are scaled when printed.
  45. //
  46. // When run inside the online DsPdf demo browser, the sample just creates a simple
  47. // one-page PDF document and returns it. The actual code that prints the generated PDF
  48. // on a local system printer is part of the sample but resides inside a false condition
  49. // for obvious reasons.
  50. //
  51. // To actually print a PDF, download the sample, edit the sample source so that it does call
  52. // the GcPdfPrintManager.Print() method, and run the sample. It should print the sample PDF
  53. // on your default printer. Set and adjust the GcPdfPrintManager and PrinterSettings
  54. // properties as needed.
  55. public class PrintOnWindows
  56. {
  57. public int CreatePDF(Stream stream)
  58. {
  59. var doc = new GcPdfDocument();
  60.  
  61. Common.Util.AddNote(
  62. "PDF printing is system-dependent. On Linux and macOS systems, a PDF can be printed with a standard system command. " +
  63. "\n" +
  64. "To print a PDF on Windows, you can use the DS.Documents.Imaging.Windows package " +
  65. "and GcPdfPrintManager and GcPdfDocumentPrintExt classes which are included in this sample. " +
  66. "\n" +
  67. "The GcPdfPrintManager class implements PDF printing on Windows using Direct2D, " +
  68. "while GcPdfDocumentPrintExt extends GcPdfDocument by adding convenient Print() methods to it. " +
  69. "The included full C# source code can be copied to your application and used as is to print PDFs on Windows systems. " +
  70. "To try it, download this sample, turn on the block of code that calls the Print() method " +
  71. "(it is excluded by the 'if (false)' condition), and build and run the sample project on your Windows machine. " +
  72. "It will print this PDF on your default printer. For more details, check the sample source code.",
  73. doc.NewPage());
  74.  
  75. // Include this block to print the PDF on Windows:
  76. #pragma warning disable CS0162
  77. if (false)
  78. {
  79. var pm = new GcPdfPrintManager();
  80. pm.Doc = doc;
  81. pm.PrinterSettings = new PrinterSettings();
  82. // Set the printer name and other settings if not using the defaults:
  83. // pm.PrinterSettings.PrinterName = "my printer";
  84. pm.PageScaling = PageScaling.FitToPrintableArea;
  85. pm.Print();
  86. }
  87. #pragma warning restore CS0162
  88.  
  89. // Save the PDF:
  90. doc.Save(stream);
  91. return doc.Pages.Count;
  92. }
  93. }
  94.  
  95. /// <summary>
  96. /// Specifies how pages are scaled when printed.
  97. /// </summary>
  98. public enum PageScaling
  99. {
  100. /// <summary>
  101. /// Pages are enlarged or made smaller if needed to fit paper.
  102. /// </summary>
  103. FitToPaper,
  104. /// <summary>
  105. /// Pages are enlarged or made smaller if needed to fit printable page bounds.
  106. /// </summary>
  107. FitToPrintableArea,
  108. }
  109.  
  110. /// <summary>
  111. /// Provides extension methods for printing a <see cref="GcPdfDocument"/>
  112. /// on Windows systems using Direct2D.
  113. /// </summary>
  114. public static class GcPdfDocumentPrintExt
  115. {
  116. /// <summary>
  117. /// Prints the PDF document on the default printer.
  118. /// </summary>
  119. /// <param name="doc">The <see cref="GcPdfDocument"/> to print.</param>
  120. /// <param name="outputRange">The range of pages to print (<see langword="null"/> includes all pages).</param>
  121. public static void Print(this GcPdfDocument doc, OutputRange outputRange = null)
  122. {
  123. Print(doc, null, outputRange);
  124. }
  125.  
  126. /// <summary>
  127. /// Prints the PDF document using the specified printer settings.
  128. /// </summary>
  129. /// <param name="doc">The <see cref="GcPdfDocument"/> to print.</param>
  130. /// <param name="printerSettings"><see cref="PrinterSettings"/> that specify the target printer and other settings. If <see langword="null"/>, the default printer will be used.</param>
  131. /// <param name="outputRange">The range of pages to print. If <see langword="null"/>, the range specified in <paramref name="printerSettings"/> will be used.</param>
  132. public static void Print(this GcPdfDocument doc, PrinterSettings printerSettings, OutputRange outputRange = null)
  133. {
  134. var pm = new GcPdfPrintManager();
  135. pm.Doc = doc;
  136. pm.PrinterSettings = printerSettings;
  137. pm.OutputRange = outputRange;
  138. pm.Print();
  139. }
  140. }
  141.  
  142. /// <summary>
  143. /// Provides properties and methods that enable printing
  144. /// <see cref="GcPdfDocument"/> objects on Windows systems using Direct2D.
  145. /// </summary>
  146. public class GcPdfPrintManager
  147. {
  148. #region Data members
  149. private const PageScaling c_DefPageScaling = PageScaling.FitToPaper;
  150.  
  151. private PrinterSettings _printerSettings;
  152. private PageSettings _pageSettings;
  153. private OutputRange _outputRange;
  154. private string _printJobName;
  155. private PageScaling _pageScaling = PageScaling.FitToPaper;
  156. private bool _autoRotate = true;
  157. private RenderingCache _renderingCache;
  158. private bool? _autoCollate;
  159. private GcPdfDocument _doc;
  160. #endregion
  161.  
  162. #region Protected
  163. protected bool OnLongOperation(double complete, bool canCancel)
  164. {
  165. if (LongOperation != null)
  166. return LongOperation.Invoke(complete, canCancel);
  167. return true;
  168. }
  169. #endregion
  170.  
  171. #region Public properties
  172. /// <summary>
  173. /// Gets or sets a value indicating whether the PrinterSettings.Collate property
  174. /// should be processed by the <see cref="GcPdfPrintManager"/> or by the printer driver.
  175. /// By default this property is null, the GcPdfPrintManager will try to determine
  176. /// does printer driver supports collate or not.
  177. /// </summary>
  178. public bool? AutoCollate
  179. {
  180. get { return _autoCollate; }
  181. set { _autoCollate = value; }
  182. }
  183.  
  184. /// <summary>
  185. /// Gets or sets the <see cref="GcPdfDocument"/> object to print.
  186. /// </summary>
  187. public GcPdfDocument Doc
  188. {
  189. get { return _doc; }
  190. set { _doc = value; }
  191. }
  192.  
  193. /// <summary>
  194. /// Gets or sets a value indicating whether pages should be auto-rotated to better fit the paper during printing.
  195. /// <para>The default is <see langword="true"/>.</para>
  196. /// </summary>
  197. public bool AutoRotate
  198. {
  199. get { return _autoRotate; }
  200. set { _autoRotate = value; }
  201. }
  202.  
  203. /// <summary>
  204. /// Gets or sets the <see cref="System.Drawing.Printing.PrinterSettings"/> object defining the print parameters.
  205. /// If <see langword="null"/>, default printer settings will be used.
  206. /// </summary>
  207. public PrinterSettings PrinterSettings
  208. {
  209. get { return _printerSettings; }
  210. set { _printerSettings = value; }
  211. }
  212.  
  213. /// <summary>
  214. /// Gets or sets the <see cref="System.Drawing.Printing.PageSettings"/> object defining the page settings (page size, orientation and so on).
  215. /// if <see langword="null"/>, default page settings will be used.
  216. /// </summary>
  217. public PageSettings PageSettings
  218. {
  219. get { return _pageSettings; }
  220. set { _pageSettings = value; }
  221. }
  222.  
  223. /// <summary>
  224. /// Gets or sets a value indicating how pages are scaled during printing.
  225. /// <para>
  226. /// The default value is <see cref="PageScaling.FitToPaper"/>.
  227. /// </para>
  228. /// </summary>
  229. [DefaultValue(c_DefPageScaling)]
  230. public PageScaling PageScaling
  231. {
  232. get { return _pageScaling; }
  233. set { _pageScaling = value; }
  234. }
  235.  
  236. /// <summary>
  237. /// Gets or sets the <see cref="Common.OutputRange"/> object specifying the range of pages to print.
  238. /// If <see langword="null"/>, the range specified in <see cref="PrinterSettings"/> will be used.
  239. /// </summary>
  240. public OutputRange OutputRange
  241. {
  242. get { return _outputRange; }
  243. set { _outputRange = value; }
  244. }
  245.  
  246. /// <summary>
  247. /// Gets or sets the name for the print job.
  248. /// </summary>
  249. public string PrintJobName
  250. {
  251. get { return _printJobName; }
  252. set { _printJobName = value; }
  253. }
  254.  
  255. /// <summary>
  256. /// Gets or sets the <see cref="Pdf.RenderingCache"/> object to use while rendering.
  257. /// This can be <see langword="null"/>.
  258. /// </summary>
  259. public RenderingCache RenderingCache
  260. {
  261. get { return _renderingCache; }
  262. set { _renderingCache = value; }
  263. }
  264. #endregion
  265.  
  266. #region Public static
  267. /// <summary>
  268. /// Some printer models are not handled correctly by the printing subsystem.
  269. /// Trying to use <see cref="GcPdfPrintManager"/> with such printers will fail
  270. /// due to reasons outside of GcPdfPrintManager's control.
  271. /// Use this method to check whether a particular printer can be used,
  272. /// note that if it fails that is not GcPdfPrintManager's fault.
  273. /// </summary>
  274. /// <param name="printerName">The printer to check.</param>
  275. /// <returns><see langword="true"/> if the printer is OK, <see langword="false"/> otherwise.</returns>
  276. public static bool TestPrinter(string printerName)
  277. {
  278. try
  279. {
  280. var ps = new PrinterSettings() { PrinterName = printerName };
  281. IntPtr hDevMode = ps.GetHdevmode();
  282. return true;
  283. }
  284. catch
  285. {
  286. return false;
  287. }
  288. }
  289.  
  290. /// <summary>
  291. /// Swaps two values.
  292. /// </summary>
  293. /// <typeparam name="T">The type of values.</typeparam>
  294. public static void Swap<T>(ref T x, ref T y)
  295. {
  296. T temp = x;
  297. x = y;
  298. y = temp;
  299. }
  300.  
  301. /// <summary>
  302. /// Returns the paper rotation angle.
  303. /// </summary>
  304. /// <param name="pageRotationAngle">The page rotation angle, in degrees.</param>
  305. /// <returns>The paper rotation angle, in degrees.</returns>
  306. public static int PaperRotationAngle(int pageRotationAngle)
  307. {
  308. if (pageRotationAngle == 90)
  309. return 270;
  310. else if (pageRotationAngle == 270)
  311. return 90;
  312. else if (pageRotationAngle == 0)
  313. return 0;
  314. else
  315. throw new ArgumentOutOfRangeException("Valid values are 90 & 270.");
  316. }
  317.  
  318. /// <summary>
  319. /// Tests whether a page should be rotated to better fit paper.
  320. /// </summary>
  321. /// <param name="paperSize">The paper size.</param>
  322. /// <param name="pageSize">The page size.</param>
  323. /// <returns><b>true</b> if the page should be rotated, <b>false</b> otherwise.</returns>
  324. public static bool ShouldRotate(SizeF paperSize, SizeF pageSize)
  325. {
  326. return (paperSize.Width > paperSize.Height) != (pageSize.Width > pageSize.Height);
  327. }
  328.  
  329. /// <summary>
  330. /// Swaps the width and height of a <see cref="Size"/> structure (rotates 90 degrees).
  331. /// </summary>
  332. /// <param name="s">The <see cref="Size"/> to rotate.</param>
  333. /// <returns>The newly created <see cref="Size"/> with width and height swapped.</returns>
  334. public static SizeF RotateSize(SizeF s)
  335. {
  336. float t = s.Width;
  337. s.Width = s.Height;
  338. s.Height = t;
  339. return s;
  340. }
  341.  
  342. /// <summary>
  343. /// Rotates a paper size and the printable area within it by the specified angle.
  344. /// </summary>
  345. /// <param name="angle">The rotation angle, counterclockwise (valid values are <b>90</b> and <b>270</b>).</param>
  346. /// <param name="paperSize">The paper size.</param>
  347. /// <param name="printableArea">The printable area.</param>
  348. public static void RotatePaper(int angle,
  349. ref SizeF paperSize, ref RectangleF printableArea)
  350. {
  351. if (angle == 90)
  352. {
  353. printableArea = new RectangleF(
  354. printableArea.Top,
  355. paperSize.Width - printableArea.Right,
  356. printableArea.Height, printableArea.Width);
  357. paperSize = RotateSize(paperSize);
  358. }
  359. else if (angle == 270)
  360. {
  361. printableArea = new RectangleF(
  362. paperSize.Height - printableArea.Bottom,
  363. printableArea.Left,
  364. printableArea.Height, printableArea.Width);
  365. paperSize = RotateSize(paperSize);
  366. }
  367. else if (angle != 0)
  368. throw new ArgumentOutOfRangeException("Valid values are 90 & 270.");
  369. }
  370. #endregion
  371.  
  372. #region Public
  373. /// <summary>
  374. /// Prints the document.
  375. /// </summary>
  376. public void Print()
  377. {
  378. PrinterSettings printerSettings = _printerSettings;
  379. if (printerSettings == null)
  380. printerSettings = new PrinterSettings();
  381. bool autoCollate;
  382. if (_autoCollate.HasValue)
  383. autoCollate = _autoCollate.Value;
  384. else
  385. {
  386. // check does printer support DM_COLLATE and DM_COPIES properties or not
  387. IntPtr hdm = printerSettings.GetHdevmode();
  388. IntPtr lhdm = GlobalLock(hdm);
  389. DEVMODE dm = (DEVMODE)Marshal.PtrToStructure(lhdm, typeof(DEVMODE));
  390. if ((dm.dmFields & DM.DM_COLLATE) != 0 && (dm.dmFields & DM.DM_COPIES) != 0)
  391. autoCollate = false;
  392. else
  393. autoCollate = true;
  394. GlobalUnlock(hdm);
  395. GlobalFree(hdm);
  396. }
  397.  
  398. int printCopies = printerSettings.Copies;
  399. if (autoCollate)
  400. {
  401. printCopies = printerSettings.Copies;
  402. printerSettings.Copies = 1;
  403. }
  404. else
  405. {
  406. printCopies = 1;
  407. }
  408. PageSettings pageSettings;
  409. IntPtr hDevMode = printerSettings.GetHdevmode();
  410. if (_pageSettings != null)
  411. {
  412. pageSettings = _pageSettings;
  413. pageSettings.CopyToHdevmode(hDevMode);
  414. printerSettings.SetHdevmode(hDevMode);
  415. }
  416. else
  417. {
  418. pageSettings = printerSettings.DefaultPageSettings;
  419. }
  420. // * 0.96 - used to convert to DIPs
  421. var printableArea = new RectangleF(
  422. pageSettings.PrintableArea.X * 0.96f,
  423. pageSettings.PrintableArea.Y * 0.96f,
  424. pageSettings.PrintableArea.Width * 0.96f,
  425. pageSettings.PrintableArea.Height * 0.96f);
  426. PaperSize paperSize = pageSettings.PaperSize;
  427. float printerPageWidthPx = paperSize.Width * 0.96f;
  428. float printerPageHeightPx = paperSize.Height * 0.96f;
  429. if (pageSettings.Landscape)
  430. {
  431. Swap(ref printerPageWidthPx, ref printerPageHeightPx);
  432. printableArea = new RectangleF(printableArea.Y, printableArea.X, printableArea.Height, printableArea.Width);
  433. }
  434. //
  435. OutputRange outputRange = _outputRange;
  436. if (outputRange == null)
  437. {
  438. int pMin, pMax;
  439. switch (printerSettings.PrintRange)
  440. {
  441. case PrintRange.SomePages:
  442. pMin = Math.Min(Math.Max(printerSettings.FromPage, 1), _doc.Pages.Count);
  443. pMax = Math.Min(Math.Max(printerSettings.ToPage, 1), _doc.Pages.Count);
  444. break;
  445. default:
  446. pMin = 1;
  447. pMax = _doc.Pages.Count;
  448. break;
  449. }
  450. outputRange = new OutputRange(pMin, pMax);
  451. }
  452. //
  453. IntPtr lockedDevMode = GlobalLock(hDevMode);
  454. DEVMODE devMode = (DEVMODE)Marshal.PtrToStructure(lockedDevMode, typeof(DEVMODE));
  455. int dpi = 150;
  456. if ((devMode.dmFields & DM.DM_PRINTQUALITY) != 0 && devMode.dmPrintQuality > 0)
  457. {
  458. dpi = devMode.dmPrintQuality;
  459. }
  460. else if ((devMode.dmFields & DM.DM_YRESOLUTION) != 0)
  461. {
  462. dpi = devMode.dmYResolution;
  463. }
  464.  
  465. STG.ComStream jobPrintTicketStream = null;
  466. try
  467. {
  468. jobPrintTicketStream = CreatePrintTicketFromDevMode(printerSettings.PrinterName, lockedDevMode, devMode.dmSize + devMode.dmDriverExtra);
  469.  
  470. Print(printerSettings.PrinterName,
  471. jobPrintTicketStream,
  472. printerPageWidthPx,
  473. printerPageHeightPx,
  474. printableArea,
  475. dpi,
  476. autoCollate,
  477. printCopies,
  478. printerSettings.Collate,
  479. outputRange);
  480. }
  481. finally
  482. {
  483. if (jobPrintTicketStream != null)
  484. jobPrintTicketStream.Dispose();
  485. GlobalUnlock(hDevMode);
  486. GlobalFree(hDevMode);
  487. }
  488. }
  489. #endregion
  490.  
  491. #region Private
  492. private STG.ComStream CreatePrintTicketFromDevMode(string printerName, IntPtr lockedDevMode, int devModeSize)
  493. {
  494. IntPtr istream = IntPtr.Zero;
  495. DX.HResult hr = CreateStreamOnHGlobal(IntPtr.Zero, true, ref istream);
  496. hr.CheckError();
  497. if (istream == IntPtr.Zero)
  498. {
  499. // Should not happen:
  500. throw new InvalidOperationException();
  501. }
  502.  
  503. STG.ComStream result = new STG.ComStream(istream);
  504. IntPtr hProvider = IntPtr.Zero;
  505. try
  506. {
  507. hr = PTOpenProvider(printerName, 1, ref hProvider);
  508. hr.CheckError();
  509.  
  510. hr = PTConvertDevModeToPrintTicket(hProvider, devModeSize, lockedDevMode, 2 /* kPTJobScope */, istream);
  511. hr.CheckError();
  512.  
  513. return result;
  514. }
  515. catch
  516. {
  517. result.Dispose();
  518. throw;
  519. }
  520. finally
  521. {
  522. if (hProvider != IntPtr.Zero)
  523. PTCloseProvider(hProvider);
  524. }
  525. }
  526.  
  527. private void AlignInRect(RectangleF rect, float width, float height,
  528. out float offsX, out float offsY, out float scaleX, out float scaleY)
  529. {
  530. float k = width / height;
  531. var offset = new PointF();
  532. if (rect.Width / rect.Height > k)
  533. {
  534. float contentWidth = k * rect.Height;
  535. k = rect.Height / height;
  536. offset.X = (rect.Width - contentWidth) / 2;
  537. }
  538. else
  539. {
  540. float contentHeight = rect.Width / k;
  541. k = rect.Width / width;
  542. offset.Y = (rect.Height - contentHeight) / 2;
  543. }
  544.  
  545. scaleX = k;
  546. scaleY = k;
  547. offsX = offset.X + rect.X;
  548. offsY = offset.Y + rect.Y;
  549. }
  550.  
  551. private void DrawContent(
  552. GcDXGraphics graphics,
  553. D2D.Device device,
  554. ref GcD2DBitmap bitmap,
  555. int pageIndex,
  556. int landscapeAngle,
  557. float printerDpi,
  558. SizeF paperSize,
  559. RectangleF printableArea,
  560. RenderingCache renderingCache,
  561. FontCache fontCache)
  562. {
  563. Page page = _doc.Pages[pageIndex];
  564.  
  565. SizeF pageSize = page.GetRenderSize(graphics.Resolution, graphics.Resolution);
  566.  
  567. if (PageScaling == PageScaling.FitToPaper)
  568. printableArea = new RectangleF(0, 0, paperSize.Width, paperSize.Height);
  569.  
  570. RectangleF alignRect;
  571. int rotationAngle;
  572. if (AutoRotate && ShouldRotate(printableArea.Size, pageSize))
  573. {
  574. rotationAngle = landscapeAngle;
  575. alignRect = new RectangleF(0, 0, printableArea.Height, printableArea.Width);
  576. }
  577. else
  578. {
  579. rotationAngle = 0;
  580. alignRect = new RectangleF(0, 0, printableArea.Width, printableArea.Height);
  581. }
  582. AlignInRect(alignRect,
  583. pageSize.Width,
  584. pageSize.Height,
  585. out float offsX,
  586. out float offsY,
  587. out float scaleX,
  588. out float scaleY);
  589. Matrix3x2 m;
  590. switch (rotationAngle)
  591. {
  592. case 0:
  593. m = new Matrix3x2(scaleX, 0, 0, scaleY, printableArea.X + offsX, printableArea.Y + offsY);
  594. break;
  595. case 90:
  596. m = Matrix3x2.Multiply(Matrix3x2.CreateScale(scaleX, scaleY),
  597. Matrix3x2.CreateRotation((float)(90 * Math.PI / 180)));
  598. m.M31 = offsY + pageSize.Height * scaleY + printableArea.X;
  599. m.M32 = printableArea.Y;
  600. break;
  601. case 270:
  602. m = Matrix3x2.Multiply(Matrix3x2.CreateScale(scaleX, scaleY),
  603. Matrix3x2.CreateRotation((float)(270 * Math.PI / 180)));
  604. m.M31 = printableArea.X;
  605. m.M32 = offsX + pageSize.Width * scaleX + printableArea.Y;
  606. break;
  607. default:
  608. throw new ArgumentOutOfRangeException("Valid values are 90 & 270.");
  609. }
  610.  
  611. TransparencyFeatures tf = page.GetTransparencyFeatures();
  612. if (tf == TransparencyFeatures.None)
  613. {
  614. // the page's content stream does not use transparency features, render directly on the graphics:
  615. graphics.Transform = m;
  616. page.Draw(graphics, new RectangleF(0, 0, pageSize.Width, pageSize.Height), true, true, renderingCache, true);
  617. }
  618. else
  619. {
  620. // use rendering via GcD2DGraphics:
  621. if (bitmap == null)
  622. {
  623. bitmap = new GcD2DBitmap(device, graphics.Factory);
  624. bitmap.SetFontCache(fontCache);
  625. }
  626. var pageSizeScaled = new SizeF(
  627. pageSize.Width * scaleX * printerDpi / 96f,
  628. pageSize.Height * scaleY * printerDpi / 96f);
  629. var bitmapSize = new Size(
  630. (int)(pageSizeScaled.Width + 0.5f),
  631. (int)(pageSizeScaled.Height + 0.5f));
  632. if (bitmap.PixelWidth != bitmapSize.Width || bitmap.PixelHeight != bitmapSize.Height)
  633. bitmap.CreateImage(bitmapSize.Width, bitmapSize.Height, printerDpi, printerDpi);
  634. using (GcD2DBitmapGraphics bg = bitmap.CreateGraphics(Color.FromArgb(0)))
  635. {
  636. page.Draw(bg, new RectangleF(0, 0, pageSizeScaled.Width, pageSizeScaled.Height), true, true, renderingCache, true);
  637. }
  638. // draw bitmap on graphics with offset:
  639. graphics.Transform = m;
  640. graphics.RenderTarget.DrawBitmap(bitmap.Bitmap, new DX.RectF(offsX, offsY, pageSize.Width, pageSize.Height));
  641. }
  642. }
  643.  
  644. private bool PrintPage(
  645. string printerName,
  646. D2D.DeviceContext rt,
  647. D2D.PrintControl printControl,
  648. D2D.Device device,
  649. GcDXGraphics graphics,
  650. int pageIndex,
  651. int landscapeAngle,
  652. float printerDpi,
  653. float printerPageWidthPx,
  654. float printerPageHeightPx,
  655. RectangleF printableArea,
  656. int pageCount,
  657. RenderingCache renderingCache,
  658. FontCache fontCache,
  659. ref int pageNo,
  660. ref GcD2DBitmap bitmap)
  661. {
  662. D2D.CommandList printCommandList = D2D.CommandList.Create(rt);
  663. rt.SetTarget(printCommandList);
  664. rt.BeginDraw();
  665.  
  666. DrawContent(
  667. graphics,
  668. device,
  669. ref bitmap,
  670. pageIndex,
  671. landscapeAngle,
  672. printerDpi,
  673. new SizeF(printerPageWidthPx, printerPageHeightPx),
  674. printableArea,
  675. renderingCache,
  676. fontCache);
  677.  
  678. bool res = rt.EndDraw(true);
  679. if (res)
  680. {
  681. printCommandList.Close();
  682. printControl.AddPage(printCommandList, new DX.Size2F(printerPageWidthPx, printerPageHeightPx));
  683. }
  684. printCommandList.Dispose();
  685.  
  686. //
  687. pageNo++;
  688. if (!OnLongOperation(0.2 + ((double)pageNo / (double)pageCount) * 0.8, true))
  689. return false;
  690.  
  691. if (!res)
  692. throw new Exception($"Error while printing on printer [{printerName}].");
  693.  
  694. return true;
  695. }
  696.  
  697. private unsafe void Print(
  698. string printerName,
  699. STG.ComStream jobPrintTicketStream,
  700. float printerPageWidthPx,
  701. float printerPageHeightPx,
  702. RectangleF printableArea,
  703. int dpi,
  704. bool autoCollate,
  705. int copies,
  706. bool collate,
  707. OutputRange outputRange)
  708. {
  709. DXGI.PrintDocumentPackageTargetFactory documentTargetFactory = null;
  710. DXGI.PrintDocumentPackageTarget documentTarget = null;
  711. D2D.Factory1 d2dFactory = null;
  712. D3D.DeviceContext d3dContext = null;
  713. D3D.Device d3dDevice = null;
  714. DXGI.Device1 dxgiDevice = null;
  715. D2D.Device d2dDevice = null;
  716. D2D.PrintControl printControl = null;
  717. WIC.ImagingFactory2 wicFactory = null;
  718. D2D.DeviceContext rt = null;
  719. DW.Factory1 dwFactory = null;
  720. GcD2DBitmap bitmap = null;
  721. RenderingCache renderingCache = _renderingCache;
  722. if (renderingCache == null)
  723. renderingCache = new RenderingCache();
  724. try
  725. {
  726. #if DEBUG && false
  727. jobPrintTicketStream.Seek(0, System.IO.SeekOrigin.Begin);
  728. var tfile = Path.GetTempFileName();
  729. using (var fs = new FileStream(tfile, System.IO.FileMode.Create))
  730. {
  731. byte[] data = new byte[1024 * 16];
  732. fixed (byte* d = data)
  733. {
  734. while (true)
  735. {
  736. var read = jobPrintTicketStream.Read((IntPtr)d, data.Length);
  737. if (read <= 0)
  738. break;
  739. fs.Write(data, 0, data.Length);
  740. }
  741. }
  742. }
  743. #endif
  744. //
  745. string printJobName = _printJobName;
  746. if (string.IsNullOrEmpty(printJobName))
  747. printJobName = "DsPdf Print Job";
  748.  
  749. //
  750. documentTargetFactory = DXGI.PrintDocumentPackageTargetFactory.Create();
  751. try
  752. {
  753. documentTarget = documentTargetFactory.CreateDocumentPackageTargetForPrintJob(
  754. printerName,
  755. printJobName,
  756. jobPrintTicketStream);
  757. }
  758. catch (Exception ex)
  759. {
  760. throw new Exception(string.Format("Cannot create print job for printer [{0}].\r\nException:\r\n{1}", printerName, ex.Message));
  761. }
  762. finally
  763. {
  764. documentTargetFactory.Dispose();
  765. documentTargetFactory = null;
  766. }
  767. if (documentTarget == null)
  768. // null is returned only if user cancels printing
  769. throw new OperationCanceledException();
  770.  
  771. // initialize device resources
  772. d2dFactory = D2D.Factory1.Create(D2D.FactoryType.SingleThreaded);
  773. wicFactory = WIC.ImagingFactory2.Create();
  774. dwFactory = DW.Factory1.Create(DW.FactoryType.Shared);
  775. D3D.FeatureLevel[] featureLevels = new D3D.FeatureLevel[]
  776. {
  777. D3D.FeatureLevel.Level_11_1,
  778. D3D.FeatureLevel.Level_11_0,
  779. D3D.FeatureLevel.Level_10_1,
  780. D3D.FeatureLevel.Level_10_0
  781. };
  782. D3D.FeatureLevel actualLevel;
  783. d3dContext = null;
  784. d3dDevice = new D3D.Device(IntPtr.Zero);
  785.  
  786. DX.HResult result = DX.HResult.Ok;
  787. for (int i = 0; i <= 1; i++)
  788. {
  789. // use WARP if hardware is not available
  790. D3D.DriverType driverType = i == 0 ? D3D.DriverType.Hardware : D3D.DriverType.Warp;
  791. result = D3D.D3D11.CreateDevice(null, driverType, IntPtr.Zero, D3D.DeviceCreationFlags.BgraSupport | D3D.DeviceCreationFlags.SingleThreaded,
  792. featureLevels, featureLevels.Length, D3D.D3D11.SdkVersion, d3dDevice, out actualLevel, out d3dContext);
  793. if (result.Code != unchecked((int)0x887A0004)) // DXGI_ERROR_UNSUPPORTED
  794. {
  795. break;
  796. }
  797. }
  798. result.CheckError();
  799. //
  800. dxgiDevice = d3dDevice.QueryInterface<DXGI.Device1>();
  801. d3dContext.Dispose();
  802. d3dContext = null;
  803. //
  804. d2dDevice = d2dFactory.CreateDevice(dxgiDevice);
  805. //
  806. D2D.PrintControlProperties printControlProperties = new D2D.PrintControlProperties
  807. {
  808. FontSubset = D2D.PrintFontSubsetMode.Default,
  809. RasterDPI = (float)dpi,
  810. ColorSpace = D2D.ColorSpace.SRgb
  811. };
  812. //
  813. if (!OnLongOperation(0.2, true))
  814. throw new OperationCanceledException();
  815. //
  816. printControl = D2D.PrintControl.Create(d2dDevice, wicFactory, documentTarget.NativePointer, printControlProperties);
  817. if (printControl == null)
  818. throw new OperationCanceledException();
  819. rt = D2D.DeviceContext.Create(d2dDevice, D2D.DeviceContextOptions.None);
  820. //
  821. int totalPageCount = _doc.Pages.Count;
  822. int pageNo = 0;
  823. int pageCount = outputRange.GetPageCount(1, totalPageCount) * copies;
  824. int landscapeAngle = PrinterSettings.LandscapeAngle;
  825. using (var fontCache = new FontCache(dwFactory))
  826. using (var glyphPathCache = new GlyphPathCache())
  827. using (D2D.SolidColorBrush brush = rt.CreateSolidColorBrush(DX.ColorF.Black, null))
  828. #if DsPdfBefore_8_0_3
  829. using (var graphics = new GcDXGraphics(rt, d2dFactory, null, fontCache, brush, glyphPathCache, false))
  830. #else
  831. using (var graphics = new GcDXGraphics(rt, d2dFactory, null, fontCache, brush, glyphPathCache, false, dpi))
  832. #endif
  833. {
  834. IEnumerator<int> pages = outputRange.GetEnumerator(1, totalPageCount);
  835. if (autoCollate)
  836. {
  837. if (collate)
  838. {
  839. for (int i = 0; i < copies; i++)
  840. {
  841. pages.Reset();
  842. while (pages.MoveNext())
  843. {
  844. PrintPage(
  845. printerName,
  846. rt,
  847. printControl,
  848. d2dDevice,
  849. graphics,
  850. pages.Current - 1,
  851. landscapeAngle,
  852. dpi,
  853. printerPageWidthPx,
  854. printerPageHeightPx,
  855. printableArea,
  856. pageCount,
  857. renderingCache,
  858. fontCache,
  859. ref pageNo,
  860. ref bitmap);
  861. }
  862. }
  863. }
  864. else
  865. {
  866. while (pages.MoveNext())
  867. {
  868. for (int i = 0; i < copies; i++)
  869. {
  870. PrintPage(
  871. printerName,
  872. rt,
  873. printControl,
  874. d2dDevice,
  875. graphics,
  876. pages.Current - 1,
  877. landscapeAngle,
  878. dpi,
  879. printerPageWidthPx,
  880. printerPageHeightPx,
  881. printableArea,
  882. pageCount,
  883. renderingCache,
  884. fontCache,
  885. ref pageNo,
  886. ref bitmap);
  887. }
  888. }
  889. }
  890. }
  891. else
  892. {
  893. while (pages.MoveNext())
  894. {
  895. PrintPage(
  896. printerName,
  897. rt,
  898. printControl,
  899. d2dDevice,
  900. graphics,
  901. pages.Current - 1,
  902. landscapeAngle,
  903. dpi,
  904. printerPageWidthPx,
  905. printerPageHeightPx,
  906. printableArea,
  907. pageCount,
  908. renderingCache,
  909. fontCache,
  910. ref pageNo,
  911. ref bitmap);
  912. }
  913. }
  914. }
  915. rt.Dispose();
  916. rt = null;
  917. printControl.Close();
  918. }
  919. finally
  920. {
  921. if (bitmap != null)
  922. bitmap.Dispose();
  923. if (rt != null)
  924. rt.Dispose();
  925. if (printControl != null)
  926. printControl.Dispose();
  927. if (d2dDevice != null)
  928. d2dDevice.Dispose();
  929. if (dxgiDevice != null)
  930. dxgiDevice.Dispose();
  931. if (d3dContext != null)
  932. d3dContext.Dispose();
  933. if (d3dDevice != null)
  934. d3dDevice.Dispose();
  935. if (dwFactory != null)
  936. dwFactory.Dispose();
  937. if (wicFactory != null)
  938. wicFactory.Dispose();
  939. if (d2dFactory != null)
  940. d2dFactory.Dispose();
  941. if (documentTarget != null)
  942. documentTarget.Dispose();
  943. if (documentTargetFactory != null)
  944. documentTargetFactory.Dispose();
  945. if (_renderingCache == null)
  946. renderingCache.Dispose();
  947. }
  948. }
  949. #endregion
  950.  
  951. #region Events
  952. public event Func<double, bool, bool> LongOperation;
  953. #endregion
  954.  
  955. #region pinvoke
  956. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
  957. private class DEVMODE
  958. {
  959. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
  960. public string dmDeviceName;
  961. public short dmSpecVersion;
  962. public short dmDriverVersion;
  963. public short dmSize;
  964. public short dmDriverExtra;
  965. public int dmFields;
  966. public short dmOrientation;
  967. public short dmPaperSize;
  968. public short dmPaperLength;
  969. public short dmPaperWidth;
  970. public short dmScale;
  971. public short dmCopies;
  972. public short dmDefaultSource;
  973. public short dmPrintQuality;
  974. public short dmColor;
  975. public short dmDuplex;
  976. public short dmYResolution;
  977. public short dmTTOption;
  978. public short dmCollate;
  979. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
  980. public string dmFormName;
  981. public short dmLogPixels;
  982. public int dmBitsPerPel;
  983. public int dmPelsWidth;
  984. public int dmPelsHeight;
  985. public int dmDisplayFlags;
  986. public int dmDisplayFrequency;
  987. public int dmICMMethod;
  988. public int dmICMIntent;
  989. public int dmMediaType;
  990. public int dmDitherType;
  991. public int dmICCManufacturer;
  992. public int dmICCModel;
  993. public int dmPanningWidth;
  994. public int dmPanningHeight;
  995. }
  996.  
  997. /// <summary>
  998. /// Fields of DEVMODE structure.
  999. /// </summary>
  1000. private class DM
  1001. {
  1002. public const int
  1003. DM_ORIENTATION = 0x00001,
  1004. DM_PAPERSIZE = 2,
  1005. DM_PAPERLENGTH = 4,
  1006. DM_PAPERWIDTH = 8,
  1007. DM_COPIES = 0x100,
  1008. DM_DEFAULTSOURCE = 0x200,
  1009. DM_PRINTQUALITY = 0x400,
  1010. DM_COLOR = 0x800,
  1011. DM_YRESOLUTION = 0x2000,
  1012. DM_COLLATE = 0x00008000,
  1013. DM_BITSPERPEL = 0x40000,
  1014. DM_PELSWIDTH = 0x80000,
  1015. DM_PELSHEIGHT = 0x100000,
  1016. DM_DISPLAYFREQUENCY = 0x400000;
  1017. }
  1018.  
  1019. [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
  1020. private static extern IntPtr GlobalLock(IntPtr handle);
  1021.  
  1022. [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
  1023. private static extern bool GlobalUnlock(IntPtr handle);
  1024.  
  1025. [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
  1026. private static extern IntPtr GlobalFree(IntPtr hMem);
  1027.  
  1028. [DllImport("ole32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
  1029. private static extern int CreateStreamOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease, ref IntPtr istream);
  1030.  
  1031. [DllImport("prntvpt.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
  1032. private static extern int PTConvertDevModeToPrintTicket(IntPtr hProvider, int cbDevmode, IntPtr devMode, int scope, IntPtr printTicket);
  1033.  
  1034. [DllImport("prntvpt.dll", ExactSpelling = true, CharSet = CharSet.Unicode)]
  1035. private static extern int PTOpenProvider(string pszPrinterName, int dwVersion, ref IntPtr phProvider);
  1036.  
  1037. [DllImport("prntvpt.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
  1038. private static extern int PTCloseProvider(IntPtr hProvider);
  1039. #endregion
  1040. }
  1041. }
  1042.