Introduction
If you are running on Windows Server 2019, you may download Microsoft Edge at: https://www.microsoft.com/en-us/edge/download
Here is the basic command line for doing the job:
*Note: the comand lines displayed below are separated for documentation purpose, but in runtime, all arguments must be on the same line (no line breaks).
msedge
--headless
--disable-gpu
--run-all-compositor-stages-before-draw
--print-to-pdf="{filePath}"
{url}
Example:
msedge --headless --disable-gpu --run-all-compositor-stages-before-draw
--print-to-pdf="D:\\mysite\temp\pdf\2059060194.pdf"
http://localhost:50964/temp/pdf/2059060194.html
Based on this, I have written a simple C# class library to automate this:
To begin, you can either download the source code and add the C# class file "pdf_edge.cs
" into your project, or install the Nuget Package(Html-PDF-Edge);
Then, at your project:
To generate PDF and download as attachment:
pdf_edge.GeneratePdfAttachment(html, "file.pdf");
To generate PDF and display in browser:
pdf_edge.GeneratePdfInline(html);
Background
Previously, I posted an [article about Using Chrome as PDF generator] to convert HTML to PDF.
Later, I found out that Microsoft Edge can also do the same thing. Since Microsoft Edge is also a chromium based web browser, both Chrome and Edge shared the same parameters.
I have tested this implementation (using Edge) in the following environments:
- Local IIS hosting
- Web Shared Hosting (smarterasp.net)
- VPS Web Hosting
All the above environments are able to generate PDF without issues. It runs smoothly without the need to configure the permission, Application Pool Identity and Website IIS authentication. I have a more seamless integration experience as compared to using Chrome.
The following screenshot shows that the execution of MS Edge is allowed even with default permission settings:
Chrome.exe, however is not so permissive in most environments. This is because executing an EXE over a web server is generally prohibited due to security issues.
For Chrome.exe, I failed to run it at web hosting environment (smarterasp.net).
Even in Local IIS hosting, I have to set the Application Pool Identify to "LocalSystem
" in order for Chrome.exe to run properly.
But Microsoft Edge does not have such requirements. Microsoft Edge is able to be executed with lowest/default permission and security settings.
Therefore, it is highly recommended to use Microsoft Edge than Chrome.exe.
Important CSS Properties
There are a few necessary CSS that you have to include in the HTML page in order for this to work properly.
- Set page margin to 0 (zero).
- Set paper size.
- Wrap all content within a "
div
" with fixed width and margin. - Use CSS of
page-break-always
to split between pages. - All fonts must already be installed or hosted on your website.
- URL links for images, external CSS stylesheet reference must include the root path.
1. Set Page Margin to 0 (zero)
@page {
margin: 0;
}
The purpose of doing this is to hide the header and footer:
2. Set Paper Size
Example 1
@page {
margin: 0;
size: A4 portrait;
}
Example 2
@page {
margin: 0;
size: letter landscape;
}
Example 3
custom size (inch) *width then height
@page {
margin: 0;
size: 4in 6in;
}
Example 4
custom size (cm) *width then height
@page {
margin: 0;
size: 14cm 14cm;
}
For more options/info on the CSS of @page
, you may refer to this link.
3. Wrap All Content Within a DIV with Fixed Width and Margin
Example:
<div class="page">
<h1>Page 1</h1>
<img src="/pdf.jpg" style="width: 100%; height: auto;" />
<!--
</div>
Style the "div"
with class "page
" (act as the main block/wrapper/container). Since the page has zero margin, we need to manually specified the top margin in CSS:
.page {
width: 18cm;
margin: auto;
margin-top: 10mm;
}
The width has to be specified.
The "margin: auto
" will align the div
block at center horizontally.
"margin-top: 10mm
", will provide space between the main block and the edge of the paper at top section.
4. Use CSS of "Page-Break-After" to Split Between Pages
To split pages, use a "div
" and style with CSS of "page-break-after
".
page-break-after: always
Example:
<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. All Fonts Must Already Installed or Hosted in Your Website
The font rendering might not be working properly if the fonts are hosted at 3rd party's server, for example: Google Fonts. Try installing the fonts into your server Windows OS or host the fonts within your website.
6. URL Links for Images, External CSS Stylesheet Reference must Include the Root Path
For example, the following img
tag might not be rendered properly. The image has the potential to be missing in the final rendered PDF output.
<img src="logo.png" />
<img src="images/logo.png" />
Instead, include the root path like this:
<img src="/logo.png" />
<img src="/images/logo.png" />
The Class Object of "pdf_edge.cs"
Here I'll explain a bit how the C# class works in behind.
As mentioned, the real work is done within the C# class of "pdf_edge.cs".
Start off by adding 2 using statements:
using System.Diagnostics;
using System.IO;
Here is the main method that runs Microsoft Edge for converting HTML into PDF file:
public static void GeneratePdf(string url, string filePath)
{
using (var p = new Process())
{
p.StartInfo.FileName = "msedge";
p.StartInfo.Arguments = $"--headless --disable-gpu
--run-all-compositor-stages-before-draw
--print-to-pdf=\"{filePath}\" {url}";
p.Start();
p.WaitForExit();
}
}
The enum for defining the Transmit Method:
public enum TransmitMethod
{
None,
Attachment,
Inline
}
Here is the code for preparing the URL for Microsoft Edge to generate the PDF:
static void EdgePublish(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();
}
Displaying A Loading GIF While Generating PDF
After the user clicked on the "Generate PDF" button, due to that the web server might takes some (short) time to process the PDF, the page might appears to be "freezed" or no response (but actually yes). This might makes the user become nervous and re-click on the button multiple times. Therefore, it's a good idea to display a message or GIF animated loading image to give the user a piece of mind that the server is in the process of generating the PDF.
Here is one of the example of a message box.
<div id="divLoading" class="divLoading" onclick="hideLoading();">
<img src="5348585/loading.gif" /><br />
Generating PDF...
</div>
The Image: loading.gif
Style the message box:
.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;
}
Here is how it looks like:
Example of Javascript that displays the message box:
<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>
The effect of this implementation can be viewed at the live demo site.
Cheers :)
*Bonus - Generate PDF in WinForms
Well, turns out that, Microsoft Edge can also be used in WinForms to Convert HTML to PDF.
Since this is running in WinForms, there is no web server to supply an URL. Therefore, in stead of using an URL to tell MS Edge the source of HTML, we can tell Microsoft Edge to load a local file.
So, just replace the URL:
http://localhost:59403/temp/82348723.html
to local file path:
C:\web\temp\82348723.html
and execute the command line like this:
msedge --headless --disable-gpu
--run-all-compositor-stages-before-draw
--print-to-pdf="C:\file.pdf"
"C:\web\temp\82348723.html"
*Note: during real execution, all arguments must be on the same line.
In 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();
}
and for all the media and resources, do not use root path.
This will be rendered:
<img src="images/logo.png" />
<img src="../images/logo.png" />
This will not be rendered:
<img src="/images/logo.png" />
<img src="/upfolder/images/logo.png" />
The images can also be rendered without saving as a physical file by encoding it as base64 images, for example:
<img src="data:image/png;base64,iVBORw0KGgoAAA.................." />
History
- 2nd December, 2022: Initial version