目录
6. 图片的URL链接,外部css样式表引用必须包含根路径。
*注意:请使用Microsoft Edge作为更好的选择。阅读更多: https://blog.csdn.net/mzl87/article/details/130726582
使用Chrome.exe作为HTML到PDF转换器有一些缺点。
主要缺点是执行EXE的权限。由于安全问题,网络托管环境将禁止直接执行任何EXE,包括“Chrome.exe”。
如果尝试在本地IIS上运行此程序,则必须将应用程序池的标识设置为“本地系统”,以允许池运行外部 EXE。
尽管仍然可以通过网络服务器运行 Chrome.exe,但强烈建议不要这样做。这就是为什么与“Chrome.exe”相比,使用Microsoft Edge是一个非常好的选择。
阅读更多关于使用Microsoft Edge将HTML转换为PDF
基本理念
Chrome有一个内置功能,用于为HTML页面生成PDF。
根据我在进行这项研究时收集的信息,所有基于chromium的网络浏览器的工作方式都相同,但我还没有在其他基于chromium的网络浏览器上测试过这一点。
以下是运行Chrome.exe的基本命令行以生成带有参数/开关的PDF:
chrome.exe
// arguments:
--headless
--disable-gpu
--run-all-compositor-stages-before-draw
--print-to-pdf="{filePath}"
{url}
完整的命令行示例:
C:\Program Files\Google\Chrome\Application\chrome.exe --headless
--disable-gpu --run-all-compositor-stages-before-draw
--print-to-pdf="D:\test\web_pdf\pdf_chrome\temp\pdf\345555635.pdf"
http://localhost:55977/temp/pdf/345555635.html
基于此,我编写了一个简单的C#类库来自动执行此过程。
您现在可以在一行简单的行中生成PDF。
这会将PDF作为附件传输以供下载:
pdf.GeneratePdfAttachment(html, "file.pdf");
这将在浏览器中打开PDF:
pdf.GeneratePdfInline(html);
是的,完成了。就这样。
好的,让我们深入了解一些重要的细节。
重要的CSS属性
您必须在HTML页面中包含一些必要的CSS才能正常工作。
- 将页边距设置为0(zero)
- 设置纸张大小
- 将所有内容换行在具有固定宽度和边距的“div”内
- 使用page-break-always的CSS在页面之间拆分。
- 所有字体必须已安装或托管在您的网站上
- 图像的URL链接,外部css样式表引用必须包含根路径。
1. 将页边距设置为0(zero)
@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. 使用“分页后”的CSS在页面之间拆分
要拆分页面,请使用“div”和样式,CSS为“page-break-after”。
page-break-after: always
例子:
<div class="page">
<h1>Page 1</h1>
<img src="/pdf.jpg" style="width: 100%; height: auto;" />
</div>
<div style="page-break-after: always"></div>
<div class="page">
<h1>Page 2</h1>
<img src="/pdf.jpg" style="width: 100%; height: auto;" />
</div>
<div style="page-break-after: always"></div>
<div class="page">
<h1>Page 3</h1>
<img src="/pdf.jpg" style="width: 100%; height: auto;" />
</div>
5. 所有字体必须已安装或托管在您的网站中
如果字体托管在第三方的服务器上,则字体呈现可能无法正常工作,例如:Google 字体。尝试将字体安装到服务器Windows操作系统中或在您的网站中托管字体。
6. 图片的URL链接,外部css样式表引用必须包含根路径。
例如,以下img标记可能无法正确呈现。图像可能会在最终渲染的PDF输出中丢失。
<img src="logo.png" />
相反,请包含如下所示的根路径:
<img src="/logo.png" />
或
<img src="/images/logo.png" />
完整网页示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
h1 {
margin: 0;
padding: 0;
}
.page {
margin: auto;
margin-top: 10mm;
border: 1px solid black;
width: 18cm;
height: 27cm;
}
@page {
margin: 0;
size: A4 portrait;
}
</style>
</head>
<body>
<div class="page">
<h1>Page 1</h1>
<img src="/pdf.jpg" style="width: 100%; height: auto;" />
</div>
<div style="page-break-after: always"></div>
<div class="page">
<h1>Page 2</h1>
<img src="/pdf.jpg" style="width: 100%; height: auto;" />
</div>
<div style="page-break-after: always"></div>
<div class="page">
<h1>Page 3</h1>
<img src="/pdf.jpg" style="width: 100%; height: auto;" />
</div>
</body>
</html>
背后的C#代码
在这里,我将解释代码是如何工作的。
准备主要方法:
public static void GeneratePdf(string url, string filePath)
{
}
确定 Chrome .exe的文件路径。将其定位在两个常见位置:
public static void GeneratePdf(string url, string filePath)
{
var chromePath = @"C:\Program Files\Google\Chrome\Application\chrome.exe";
if (!File.Exists(chromePath))
{
string userfolder = Environment.GetFolderPath
(Environment.SpecialFolder.UserProfile);
chromePath =
$@"{userfolder}\AppData\Local\Google\Chrome\Application\chrome.exe";
}
if (!File.Exists(chromePath))
{
throw new Exception("Unable to locate Chrome.exe");
}
}
添加以下“using”语句:
using System.Diagnostics;
using System.IO;
启动一个“process”来运行Chrome.exe并带有参数:
public static void GeneratePdf(string url, string pdfFilePath)
{
var chromePath = @"C:\Program Files\Google\Chrome\Application\chrome.exe";
if (!File.Exists(chromePath))
{
string userfolder = Environment.GetFolderPath
(Environment.SpecialFolder.UserProfile);
chromePath =
$@"{userfolder}\AppData\Local\Google\Chrome\Application\chrome.exe";
}
if (!File.Exists(chromePath))
{
throw new Exception("Unable to locate Chrome.exe");
}
using (var p = new Process())
{
p.StartInfo.FileName = chromePath;
p.StartInfo.Arguments = $"--headless --disable-gpu
--run-all-compositor-stages-before-draw --print-to-pdf=\"{pdfFilePath}\" {url}";
p.Start();
p.WaitForExit();
}
}
以上将生成PDF文件。
Chrome.exe 需要一个网址来生成PDF文件。
因此,以下代码将生成所需的URL:
准备一个“enum”变量:
public enum TransmitMethod
{
None,
Attachment,
Inline
}
准备为 Chrome.exe生成“URL”的方法。
我们将该方法称为“ChromePublish”:
static void ChromePublish(string html, TransmitMethod transmitMethod, string filename)
{
}
在方法“ChromePublish”中,首先创建一个临时目录来保存临时文件:
string folderTemp = HttpContext.Current.Server.MapPath("~/temp/pdf");
if (!Directory.Exists(folderTemp))
{
Directory.CreateDirectory(folderTemp);
}
然后,为两个临时文件(HTML和PDF)创建文件名和路径
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");
生成HTML文件并将其保存在本地:
File.WriteAllText(fileHtml, html);
获取HTML文件的URL:
var r = HttpContext.Current.Request.Url;
string url = $"{r.Scheme}://{r.Host}:{r.Port}/temp/pdf/{randomstr}.html";
执行我们之前创建的方法在本地服务器上生成PDF文件:
GeneratePdf(url, filePdf);
获取文件大小:
FileInfo fi = new FileInfo(filePdf);
string filelength = fi.Length.ToString();
将PDF文件加载到字节数组中:
byte[] ba = File.ReadAllBytes(filePdf);
从服务器中删除(清理)临时文件,不再需要它们:
try
{
File.Delete(filePdf);
}
catch { }
try
{
File.Delete(fileHtml);
}
catch { }
接下来,准备要传输的PDF。
清除“响应”的所有内容:
HttpContext.Current.Response.Clear();
在响应标头中指定“Content-Disposition”的类型。
在此处阅读更多内容。
if (transmitMethod == TransmitMethod.Inline)
{
HttpContext.Current.Response.AddHeader("Content-Disposition", "inline");
}
else if (transmitMethod == TransmitMethod.Attachment)
{
HttpContext.Current.Response.AddHeader("Content-Disposition",
$"attachment; filename=\"{filename}\"");
}
最后,传输数据(PDF):
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AddHeader("Content-Length", filelength);
HttpContext.Current.Response.BinaryWrite(ba);
HttpContext.Current.Response.End();
以下是完整代码:
static void ChromePublish(string html, TransmitMethod transmitMethod, string filename)
{
string folderTemp = HttpContext.Current.Server.MapPath("~/temp/pdf");
if (!Directory.Exists(folderTemp))
{
Directory.CreateDirectory(folderTemp);
}
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");
File.WriteAllText(fileHtml, html);
var r = HttpContext.Current.Request.Url;
string url = $"{r.Scheme}://{r.Host}:{r.Port}/temp/pdf/{randomstr}.html";
GeneratePdf(url, filePdf);
FileInfo fi = new FileInfo(filePdf);
string filelength = fi.Length.ToString();
byte[] ba = File.ReadAllBytes(filePdf);
try
{
File.Delete(filePdf);
}
catch { }
try
{
File.Delete(fileHtml);
}
catch { }
HttpContext.Current.Response.Clear();
if (transmitMethod == TransmitMethod.Inline)
HttpContext.Current.Response.AddHeader("Content-Disposition", "inline");
else if (transmitMethod == TransmitMethod.Attachment)
HttpContext.Current.Response.AddHeader
("Content-Disposition", $"attachment; filename=\"{filename}\"");
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AddHeader("Content-Length", filelength);
HttpContext.Current.Response.BinaryWrite(ba);
HttpContext.Current.Response.End();
}
最后,创建两个简单的方法来环绕方法“ChromePublish()”:
public static void GeneratePdfInline(string html)
{
ChromePublish(html, TransmitMethod.Inline, null);
}
public static void GeneratePdfAttachment(string html, string filenameWithPdf)
{
ChromePublish(html, TransmitMethod.Attachment, filenameWithPdf);
}
由于生成PDF可能需要几秒钟,因此您还可以考虑在生成PDF时向用户显示“Loading”消息。
例如:
<div id="divLoading" class="divLoading" onclick="hideLoading();">
<img src="loading.gif" /><br />
Generating PDF...
</div>
设置“div”消息框的样式:
.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>
在用户单击按钮以生成PDF时显示消息。将以下属性添加到按钮以执行javascript:
OnClientClick="showLoading();"
例子:
<asp:Button ID="btGeneratePdfAttachment" runat="server"
Text="Generate PDF (download as attachment)"
OnClick="btGeneratePdfAttachment_Click"
OnClientClick="showLoading();" />
您可以下载本文的源代码,以更好地了解整个事情的实际工作原理。
感谢您的阅读和快乐编码!!
https://www.codeproject.com/Articles/5347275/Convert-HTML-to-PDF-with-Chrome-in-ASP-NET-WebForm