使用 Microsoft Edge 在 ASP.NET WebForms 中将 HTML 转换为 PDF
使用 MS Edge 从 HTML 生成 PDF
- 下载源代码 - 271.2 KB
- 在线演示
- Github
- 可在 Nuget 找到
- PM> NuGet\Install-Package Html-PDF-Edge
引言
如果您使用的是 Windows Server 2019,则可以在以下位置下载 Microsoft Edge: https://www.microsoft.com/en-us/edge/download
这是完成这项工作需要使用的基本命令行
*注意:下面显示的命令行是为了文档目的而分开的,但在运行时,所有参数必须在同一行(没有换行符)。
msedge
--headless
--disable-gpu
--run-all-compositor-stages-before-draw
--print-to-pdf="{filePath}"
{url}
示例
msedge --headless --disable-gpu --run-all-compositor-stages-before-draw
--print-to-pdf="D:\\mysite\temp\pdf\2059060194.pdf"
https://:50964/temp/pdf/2059060194.html
在此基础上,我编写了一个简单的 C# 类库来自动化此过程
要开始,您可以下载源代码并将 C# 类文件“pdf_edge.cs
”添加到您的项目中,或者安装 Nuget 包 (Html-PDF-Edge);
然后,在您的项目中
生成 PDF 并作为附件下载
pdf_edge.GeneratePdfAttachment(html, "file.pdf");
生成 PDF 并在浏览器中显示
pdf_edge.GeneratePdfInline(html);
背景
之前,我曾发布过一篇关于[使用 Chrome 作为 PDF 生成器] 的文章,用于将 HTML 转换为 PDF。
后来,我发现 Microsoft Edge 也可以做同样的事情。由于 Microsoft Edge 也是基于 Chromium 的 Web 浏览器,Chrome 和 Edge 共享相同的参数。
我在以下环境中测试了此实现(使用 Edge)
- 本地 IIS 托管
- Web 共享托管 (smarterasp.net)
- VPS Web 托管
以上所有环境都能成功生成 PDF。它运行顺畅,无需配置权限、应用程序池身份和网站 IIS 身份验证。与使用 Chrome 相比,我获得了更无缝的集成体验。
以下屏幕截图显示,即使在默认权限设置下,也允许执行 MS Edge
然而,Chrome.exe 在大多数环境中并不那么宽松。这是因为出于安全原因,通常禁止在 Web 服务器上执行 EXE。
对于Chrome.exe,我在 Web 托管环境 (smarterasp.net) 中未能成功运行它。
即使在本地 IIS 托管中,我也必须将应用程序池身份设置为“LocalSystem
”,以便Chrome.exe 能够正常运行。
但是 Microsoft Edge 没有这些要求。Microsoft Edge 能够以最低/默认权限和安全设置执行。
因此,强烈建议使用 Microsoft Edge 而非Chrome.exe。
重要的 CSS 属性
为了使此功能正常工作,您需要在 HTML 页面中包含一些必要的 CSS。
- 将页面边距设置为 0(零)。
- 设置纸张大小。
- 将所有内容包装在具有固定宽度和边距的“
div
”中。 - 使用 `page-break-always` 的 CSS 来分割页面。
- 所有字体必须已安装或托管在您的网站上。
- 图片、外部 CSS 样式表引用的 URL 链接必须包含根路径。
1. 将页面边距设置为 0(零)
@page {
margin: 0;
}
这样做的目的是隐藏页眉和页脚
2. 设置纸张大小
示例 1
@page {
margin: 0;
size: A4 portrait;
}
示例 2
@page {
margin: 0;
size: letter landscape;
}
示例 3
自定义尺寸(英寸)*先宽度后高度
@page {
margin: 0;
size: 4in 6in;
}
示例 4
自定义尺寸(厘米)*先宽度后高度
@page {
margin: 0;
size: 14cm 14cm;
}
有关 `@page` CSS 的更多选项/信息,您可以参考此链接。
3. 将所有内容包装在具有固定宽度和边距的 DIV 中
示例
<div class="page">
<h1>Page 1</h1>
<img src="/pdf.jpg" style="width: 100%; height: auto;" />
<!-- The rest of the body content -->
</div>
为类名为“page
”的“div
”设置样式(充当主块/包装器/容器)。由于页面边距为零,我们需要在 CSS 中手动指定上边距
.page {
width: 18cm;
margin: auto;
margin-top: 10mm;
}
必须指定宽度。
“margin: auto
”会将 `div` 块水平居中对齐。
“margin-top: 10mm
”将在主块和顶部区域的纸张边缘之间提供空间。
4. 使用“Page-Break-After”CSS 来分割页面
要分割页面,请使用“div
”并使用“page-break-after
”CSS 设置样式。
page-break-after: always
示例
<div class="page">
<h1>Page 1</h1>
</div>
<div style="page-break-after: always"></div>
<div class="page">
<h1>Page 2</h1>
</div>
<div style="page-break-after: always"></div>
<div class="page">
<h1>Page 3</h1>
</div>
5. 所有字体必须已安装或托管在您的网站上
如果字体托管在第三方服务器上,例如 Google Fonts,字体渲染可能无法正常工作。尝试将字体安装到您的服务器 Windows OS 或将字体托管在您的网站内。
6. 图片、外部 CSS 样式表引用的 URL 链接必须包含根路径
例如,以下 `img` 标签可能无法正确渲染。在最终渲染的 PDF 输出中,图片可能会缺失。
<img src="logo.png" />
<img src="images/logo.png" />
相反,包含根路径,如下所示
<img src="/logo.png" />
<img src="/images/logo.png" />
“pdf_edge.cs”的类对象
这里我将解释一下 C# 类是如何在后台工作的。
如前所述,实际工作是在“pdf_edge.cs”的 C# 类中完成的。
首先添加两个 using 语句
using System.Diagnostics;
using System.IO;
这是运行 Microsoft Edge 将 HTML 转换为 PDF 文件的主要方法
public static void GeneratePdf(string url, string filePath)
{
using (var p = new Process())
{
p.StartInfo.FileName = "msedge";
// arguments have to be in single line, no line breaks
p.StartInfo.Arguments = $"--headless --disable-gpu
--run-all-compositor-stages-before-draw
--print-to-pdf=\"{filePath}\" {url}";
p.Start();
p.WaitForExit();
}
}
用于定义传输方法的枚举
public enum TransmitMethod
{
None,
Attachment,
Inline
}
这是准备 URL 以供 Microsoft Edge 生成 PDF 的代码
static void EdgePublish(string html, TransmitMethod transmitMethod, string filename)
{
// Create a temporary folder for storing the PDF
string folderTemp = HttpContext.Current.Server.MapPath("~/temp/pdf");
if (!Directory.Exists(folderTemp))
{
Directory.CreateDirectory(folderTemp);
}
// Create 2 temporary filename
Random rd = new Random();
string randomstr = rd.Next(100000000, int.MaxValue).ToString();
string fileHtml = HttpContext.Current.Server.MapPath($"~/temp/pdf/{randomstr}.html");
string filePdf = HttpContext.Current.Server.MapPath($"~/temp/pdf/{randomstr}.pdf");
// Create the HTML file
File.WriteAllText(fileHtml, html);
// Obtain the URL of the HTML file
var r = HttpContext.Current.Request.Url;
string url = $"{r.Scheme}://{r.Host}:{r.Port}/temp/pdf/{randomstr}.html";
// Create the PDF file
GeneratePdf(url, filePdf);
// Obtain the file size
FileInfo fi = new FileInfo(filePdf);
string filelength = fi.Length.ToString();
// Load the file into memory (byte array)
byte[] ba = File.ReadAllBytes(filePdf);
// Delete the 2 temp files from server
try
{
File.Delete(filePdf);
}
catch { }
try
{
File.Delete(fileHtml);
}
catch { }
// set the transmission type
HttpContext.Current.Response.Clear();
if (transmitMethod == TransmitMethod.Inline)
{
// display in browser
HttpContext.Current.Response.AddHeader("Content-Disposition", "inline");
}
else if (transmitMethod == TransmitMethod.Attachment)
{
// as downloadable document
HttpContext.Current.Response.AddHeader("Content-Disposition",
$"attachment; filename=\"{filename}\"");
}
// finally, transmit the pdf for download
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AddHeader("Content-Length", filelength);
HttpContext.Current.Response.BinaryWrite(ba);
HttpContext.Current.Response.End();
}
生成 PDF 时显示加载 GIF
在用户点击“生成 PDF”按钮后,由于 Web 服务器可能需要一些(短暂的)时间来处理 PDF,页面可能会显得“冻结”或无响应(但实际上有)。这可能会让用户感到紧张并多次重新点击按钮。因此,最好显示一条消息或 GIF 动画加载图像,让用户放心服务器正在生成 PDF。
这是消息框的一个示例。
<div id="divLoading" class="divLoading" onclick="hideLoading();">
<img src="5348585/loading.gif" /><br />
Generating PDF...
</div>
图片:loading.gif
样式消息框
.divLoading {
width: 360px;
font-size: 20pt;
font-style: italic;
font-family: Arial;
z-index: 9;
position: fixed;
top: calc(50vh - 150px);
left: calc(50vw - 130px);
border: 10px solid #7591ef;
border-radius: 25px;
padding: 10px;
text-align: center;
background: #dce5ff;
display: none;
font-weight: bold;
}
效果如下
显示消息框的 JavaScript 示例
<script type="text/javascript">
function showLoading() {
let d = document.getElementById("divLoading");
d.style.display = "block";
setTimeout(hideLoading, 2000);
}
function hideLoading() {
let d = document.getElementById("divLoading");
d.style.display = "none";
}
</script>
可以在实时演示站点上查看此实现的效果。
致敬 :)
*奖励 - 在 WinForms 中生成 PDF
事实证明,Microsoft Edge 也可以在 WinForms 中用于将 HTML 转换为 PDF。
由于这是在 WinForms 中运行的,因此没有 Web 服务器来提供 URL。因此,我们不需要使用 URL 来告诉 MS Edge HTML 的来源,而是可以告诉 Microsoft Edge 加载本地文件。
所以,只需替换 URL
https://:59403/temp/82348723.html
为本地文件路径
C:\web\temp\82348723.html
并像这样执行命令行
msedge --headless --disable-gpu --run-all-compositor-stages-before-draw --print-to-pdf="C:\file.pdf" "C:\web\temp\82348723.html"
*注意:在实际执行中,所有参数必须在同一行。
在 C# 中,
using (var p = new Process())
{
p.StartInfo.FileName = "msedge";
p.StartInfo.Arguments = $@"--headless --disable-gpu --run-all-compositor-stages-before-draw --print-to-pdf=""C:\file.pdf"" ""C:\web\temp\82348723.html""";
p.Start();
p.WaitForExit();
}
对于所有媒体和资源,请勿使用根路径。
这将成功渲染
<img src="images/logo.png" /> <img src="../images/logo.png" />
这将不会渲染
<img src="/images/logo.png" /> <img src="/upfolder/images/logo.png" />
还可以通过将其编码为 base64 图像来渲染图像,而无需将其保存为物理文件,例如
<img src="data:image/png;base64,iVBORw0KGgoAAA.................." />
历史
- 2022 年 12 月 2 日:初始版本