PrintHtml.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. using Microsoft.Web.WebView2.Core;
  2. using Microsoft.Web.WebView2.WinForms;
  3. using Newtonsoft.Json.Linq;
  4. using PdfiumViewer;
  5. using prBrowser;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Drawing.Printing;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Net.Http;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using System.Windows.Forms;
  16. namespace PrintHtml
  17. {
  18. public class PrintHtml
  19. {
  20. private readonly HttpClient _httpClient = new HttpClient(); // 重用 HttpClient 实例
  21. Thread thread;
  22. string inputParams = "";
  23. dynamic paperobj;
  24. PrintSettings settings;
  25. [STAThread]
  26. public string PrintUrlHtml(string input)
  27. {
  28. System.Diagnostics.Debugger.Launch();
  29. String errorCode = "0";
  30. String errorMessage = "";
  31. inputParams = input;
  32. JObject output = new JObject();
  33. JArray fileStringArray = new JArray();
  34. try
  35. {
  36. dynamic inputobj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(input);
  37. var paramsobj = inputobj["params"][0];
  38. thread = new Thread(new ThreadStart(ReportFormd));
  39. thread.SetApartmentState(ApartmentState.STA); //重点
  40. thread.Start();
  41. thread.Join();
  42. }
  43. catch (Exception ex)
  44. {
  45. errorMessage = ex.Message;
  46. }
  47. output.Add("errorCode", errorCode);
  48. output.Add("errorMessage", errorMessage);
  49. return output.ToString();
  50. }
  51. private void ReportFormd()
  52. {
  53. try
  54. {
  55. PrinFrom dForm = new PrinFrom(inputParams);
  56. dForm.ShowDialog();
  57. }
  58. catch (Exception ex2)
  59. {
  60. }
  61. thread.Abort();
  62. }
  63. public string PrintInfo(string inputString)
  64. {
  65. string output = "";
  66. output = Task.Run(async () => await PrintURLAsync(inputString)).Result;
  67. return output;
  68. }
  69. /// <summary>
  70. /// 打印指定 URL 的 PDF 文件。
  71. /// </summary>
  72. /// <param name="inParams">输入参数的 JSON 字符串</param>
  73. /// <returns>操作结果的 JSON 字符串</returns>
  74. public async Task<string> PrintURLAsync(string inParams)
  75. {
  76. //System.Diagnostics.Debugger.Launch();
  77. string errorCode = "0";
  78. string errorMessage = "打印成功";
  79. string tempPdfPath = string.Empty;
  80. PdfDocument pdfDocument = null;
  81. PrintDocument pdfPrintDoc = null;
  82. try
  83. {
  84. if (string.IsNullOrWhiteSpace(inParams))
  85. {
  86. return CreateErrorResponse("-1", "输入参数不能为空");
  87. }
  88. var inParamsObj = JObject.Parse(inParams);
  89. var inParamsResult = inParamsObj["result"];
  90. string url = inParamsResult?["url"]?.ToString();
  91. if (string.IsNullOrWhiteSpace(url))
  92. {
  93. return CreateErrorResponse("-1", "URL 不能为空");
  94. }
  95. // 确保临时目录存在
  96. // 确保临时目录存在
  97. string tempDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
  98. tempDir = tempDir + "\\tmpPDF";
  99. tempPdfPath = Path.Combine(tempDir, $"{Guid.NewGuid()}.pdf");
  100. // 1. 异步下载 PDF 文件
  101. bool downloaded = await DownloadPdfFileAsync(url, tempPdfPath);
  102. if (!downloaded)
  103. {
  104. return CreateErrorResponse("-1", "PDF 文件下载失败");
  105. }
  106. paperobj = inParamsObj["printParameters"];
  107. if (paperobj == null)
  108. {
  109. paperobj = new JObject
  110. {
  111. ["PaperName"] = "A4",
  112. ["Landscape"] = false,
  113. ["ManualDuplex"]= false
  114. };
  115. }
  116. // 2. 配置打印机设置
  117. GetPrintSettings();
  118. // 3. 加载 PDF 并打印
  119. PrintPdf pdf = new PrintPdf();
  120. pdf.Print(tempPdfPath, settings);
  121. // 成功打印后返回成功信息
  122. return CreateResponseString(errorCode, errorMessage, new JObject());
  123. }
  124. catch (Exception ex)
  125. {
  126. //Console.WriteLine($"PrintURLAsync 发生未处理异常: {ex}");
  127. errorCode = "-1";
  128. errorMessage = $"打印 PDF 时发生错误: {ex.Message}";
  129. // 异常时也返回字符串
  130. return CreateResponseString(errorCode, errorMessage, new JObject());
  131. }
  132. finally
  133. {
  134. // 5. 释放资源并清理临时文件
  135. if (!string.IsNullOrEmpty(tempPdfPath) && File.Exists(tempPdfPath))
  136. {
  137. try
  138. {
  139. File.Delete(tempPdfPath);
  140. }
  141. catch (Exception ex)
  142. {
  143. // 记录删除失败日志,但不应中断主流程
  144. //Console.WriteLine($"警告:无法删除临时文件 {tempPdfPath}: {ex.Message}");
  145. }
  146. }
  147. }
  148. // 这行代码理论上不会执行到,因为 try/catch 块都会 return
  149. }
  150. /// <summary>
  151. /// 异步下载 PDF 文件。(兼容 .NET Framework)
  152. /// </summary>
  153. /// <param name="url">PDF 文件的 URL</param>
  154. /// <param name="filePath">保存到本地的文件路径</param>
  155. /// <returns>下载是否成功</returns>
  156. private async Task<bool> DownloadPdfFileAsync(string url, string filePath)
  157. {
  158. try
  159. {
  160. // 发送 GET 请求获取内容流
  161. using (HttpResponseMessage response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
  162. {
  163. if (response.IsSuccessStatusCode)
  164. {
  165. // 使用 FileStream 异步写入
  166. using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 8192, useAsync: true))
  167. {
  168. // 获取响应内容流
  169. using (Stream contentStream = await response.Content.ReadAsStreamAsync())
  170. {
  171. // 异步复制内容流到文件流
  172. await contentStream.CopyToAsync(fileStream);
  173. }
  174. }
  175. return true;
  176. }
  177. else
  178. {
  179. //Console.WriteLine($"无法下载文件,状态码:{response.StatusCode}");
  180. return false;
  181. }
  182. }
  183. }
  184. catch (Exception ex)
  185. {
  186. //Console.WriteLine($"下载过程中发生错误:{ex.Message}");
  187. return false;
  188. }
  189. }
  190. /// <summary>
  191. /// 根据参数配置打印机设置。
  192. /// </summary>
  193. /// <param name="printParametersJson">打印机参数的 JSON 字符串</param>
  194. /// <returns>配置好的 PrinterSettings 对象</returns>
  195. private void GetPrinterSettings(PrintSettings settings)
  196. {
  197. PrinterSettings printerSettings = new PrinterSettings();
  198. if (paperobj != null)
  199. {
  200. if (paperobj["PrinterName"] != null && !string.IsNullOrEmpty(paperobj["PrinterName"].ToString()))
  201. {
  202. foreach (PrinterSettings printer in PrinterSettings.InstalledPrinters)
  203. {
  204. if (printer.PrinterName == paperobj["PrinterName"].ToString())
  205. {
  206. printerSettings = printer;
  207. break;
  208. }
  209. }
  210. }
  211. }
  212. PaperSize paperSize = new PaperSize();
  213. string paperName = "A4";
  214. if (paperobj["PaperName"] != null && !"A4".Equals(paperobj["PaperName"].ToString()))
  215. {
  216. paperName = paperobj["PaperName"].ToString();
  217. }
  218. foreach (PaperSize size in printerSettings.PaperSizes)
  219. {
  220. if (size.PaperName == paperName)
  221. {
  222. paperSize = size;
  223. break;
  224. }
  225. }
  226. settings.SelectedPaperSize = paperSize;
  227. printerSettings.DefaultPageSettings.PaperSize = paperSize;
  228. printerSettings.DefaultPageSettings.Margins.Left = 0;
  229. printerSettings.DefaultPageSettings.Margins.Right = 0;
  230. printerSettings.DefaultPageSettings.Margins.Bottom = 0;
  231. printerSettings.DefaultPageSettings.Landscape = settings.IsLandscape;
  232. printerSettings.Copies = (short)settings.Copies;
  233. if (settings.PrintedByRange)
  234. {
  235. printerSettings.PrintRange = PrintRange.SomePages;
  236. printerSettings.FromPage = settings.FromPage;
  237. printerSettings.ToPage = settings.ToPage;
  238. }
  239. printerSettings.Duplex = settings.Duplex;
  240. settings.printerSettings = printerSettings;
  241. }
  242. public PrintSettings GetPrintSettings()
  243. {
  244. settings = new PrintSettings();
  245. if (paperobj != null)
  246. {
  247. if (paperobj["Landscape"] != null && !string.IsNullOrEmpty(paperobj["Landscape"].ToString()))
  248. {
  249. settings.IsLandscape = bool.Parse(paperobj["Landscape"].ToString());
  250. }
  251. if (paperobj["Copies"] != null && !string.IsNullOrEmpty(paperobj["Copies"].ToString()))
  252. {
  253. settings.Copies = int.Parse(paperobj["Copies"].ToString()); // 打印份数,默认是1
  254. }
  255. if (paperobj["ManualDuplex"] != null && !string.IsNullOrEmpty(paperobj["ManualDuplex"].ToString()))
  256. {
  257. settings.manualDuplex = bool.Parse(paperobj["ManualDuplex"].ToString());
  258. }
  259. settings.Duplex = Duplex.Simplex;
  260. if (paperobj["Duplex"] != null)
  261. {
  262. if (paperobj["Duplex"].ToString() == "Horizontal")
  263. {
  264. settings.Duplex = Duplex.Horizontal;
  265. }
  266. if (paperobj["Duplex"].ToString() == "Vertical")
  267. {
  268. settings.Duplex = Duplex.Vertical;
  269. }
  270. }
  271. if (paperobj["PageRanges"] != null)
  272. {
  273. string pageRanges = paperobj["PageRanges"].ToString();
  274. if (!string.IsNullOrEmpty(pageRanges))
  275. {
  276. settings.PrintedByRange = true;
  277. if (pageRanges.IndexOf("-") > 0)
  278. {
  279. settings.FromPage = int.Parse(pageRanges.Split("-".ToCharArray())[0]);
  280. settings.ToPage = int.Parse(pageRanges.Split("-".ToCharArray())[0]);
  281. }
  282. else
  283. {
  284. settings.FromPage = int.Parse(pageRanges);
  285. settings.ToPage = settings.FromPage;
  286. }
  287. }
  288. }
  289. if (paperobj["DuplexTip"] != null)
  290. {
  291. string _duplexTips = paperobj["DuplexTip"].ToString();
  292. if (!string.IsNullOrEmpty(_duplexTips))
  293. {
  294. settings.duplexTips = _duplexTips;
  295. }
  296. }
  297. }
  298. GetPrinterSettings(settings);
  299. return settings;
  300. }
  301. private async Task PrintPdfWithWebView2(string pdfPath)
  302. {
  303. // 创建一个不可见的 WinForms 窗口来承载 WebView2
  304. using (var form = new Form())
  305. {
  306. // 设置窗口为不可见,不显示在任务栏
  307. form.WindowState = FormWindowState.Minimized;
  308. form.ShowInTaskbar = false;
  309. form.Opacity = 0; // 完全透明
  310. form.Visible = false; // 完全隐藏
  311. var webView2 = new WebView2
  312. {
  313. Dock = DockStyle.Fill
  314. };
  315. form.Controls.Add(webView2);
  316. // 初始化 WebView2
  317. await webView2.EnsureCoreWebView2Async(null);
  318. // 设置 PDF 文件的 URL
  319. string fileUrl = new Uri(pdfPath).AbsoluteUri;
  320. webView2.CoreWebView2.Navigate(fileUrl);
  321. // 等待页面加载完成
  322. TaskCompletionSource<bool> printTask = new TaskCompletionSource<bool>();
  323. webView2.CoreWebView2.NavigationCompleted += async (sender, e) =>
  324. {
  325. if (e.IsSuccess)
  326. {
  327. // 等待一段时间确保 PDF 完全加载
  328. await Task.Delay(2000); // 增加等待时间以确保 PDF 完全渲染
  329. try
  330. {
  331. // 调用 JavaScript 的 window.print() 方法
  332. await webView2.CoreWebView2.ExecuteScriptAsync("window.print();");
  333. }
  334. catch (Exception ex)
  335. {
  336. // 如果打印失败,记录错误但继续
  337. Console.WriteLine($"打印调用失败: {ex.Message}");
  338. }
  339. // 设置任务完成
  340. printTask.SetResult(true);
  341. }
  342. else
  343. {
  344. printTask.SetException(new Exception($"PDF 加载失败: {e.WebErrorStatus}"));
  345. }
  346. };
  347. // 启动消息循环
  348. form.Show();
  349. // 等待打印完成或超时
  350. var timeoutTask = Task.Delay(TimeSpan.FromSeconds(30)); // 30秒超时
  351. var completedTask = await Task.WhenAny(printTask.Task, timeoutTask);
  352. if (completedTask == timeoutTask)
  353. {
  354. throw new TimeoutException("PDF 打印超时");
  355. }
  356. // 确保表单关闭
  357. form.Invoke((MethodInvoker)delegate
  358. {
  359. form.Close();
  360. });
  361. }
  362. }
  363. private string CreateErrorResponse(string errorCode, string errorMessage)
  364. {
  365. return CreateResponseString(errorCode, errorMessage, new JObject()); // 确保返回 string
  366. }
  367. private string CreateResponseString(string errorCode, string errorMessage, JObject result)
  368. {
  369. return CreateResponse(errorCode, errorMessage, result).ToString(); // 调用 JObject 版本并转为 string
  370. }
  371. private JObject CreateResponse(string errorCode, string errorMessage, JObject result)
  372. {
  373. var output = new JObject
  374. {
  375. ["errorCode"] = errorCode,
  376. ["errorMessage"] = errorMessage,
  377. ["result"] = result ?? new JObject()
  378. };
  379. return output; // 返回 JObject
  380. }
  381. }
  382. }