How to convert HTML to PDF using iTextSharp

254,083

Solution 1

First, HTML and PDF are not related although they were created around the same time. HTML is intended to convey higher level information such as paragraphs and tables. Although there are methods to control it, it is ultimately up to the browser to draw these higher level concepts. PDF is intended to convey documents and the documents must "look" the same wherever they are rendered.

In an HTML document you might have a paragraph that's 100% wide and depending on the width of your monitor it might take 2 lines or 10 lines and when you print it it might be 7 lines and when you look at it on your phone it might take 20 lines. A PDF file, however, must be independent of the rendering device, so regardless of your screen size it must always render exactly the same.

Because of the musts above, PDF doesn't support abstract things like "tables" or "paragraphs". There are three basic things that PDF supports: text, lines/shapes and images. (There are other things like annotations and movies but I'm trying to keep it simple here.) In a PDF you don't say "here's a paragraph, browser do your thing!". Instead you say, "draw this text at this exact X,Y location using this exact font and don't worry, I've previously calculated the width of the text so I know it will all fit on this line". You also don't say "here's a table" but instead you say "draw this text at this exact location and then draw a rectangle at this other exact location that I've previously calculated so I know it will appear to be around the text".

Second, iText and iTextSharp parse HTML and CSS. That's it. ASP.Net, MVC, Razor, Struts, Spring, etc, are all HTML frameworks but iText/iTextSharp is 100% unaware of them. Same with DataGridViews, Repeaters, Templates, Views, etc. which are all framework-specific abstractions. It is your responsibility to get the HTML from your choice of framework, iText won't help you. If you get an exception saying The document has no pages or you think that "iText isn't parsing my HTML" it is almost definite that you don't actually have HTML, you only think you do.

Third, the built-in class that's been around for years is the HTMLWorker however this has been replaced with XMLWorker (Java / .Net). Zero work is being done on HTMLWorker which doesn't support CSS files and has only limited support for the most basic CSS properties and actually breaks on certain tags. If you do not see the HTML attribute or CSS property and value in this file then it probably isn't supported by HTMLWorker. XMLWorker can be more complicated sometimes but those complications also make it more extensible.

Below is C# code that shows how to parse HTML tags into iText abstractions that get automatically added to the document that you are working on. C# and Java are very similar so it should be relatively easy to convert this. Example #1 uses the built-in HTMLWorker to parse the HTML string. Since only inline styles are supported the class="headline" gets ignored but everything else should actually work. Example #2 is the same as the first except it uses XMLWorker instead. Example #3 also parses the simple CSS example.

//Create a byte array that will eventually hold our final PDF
Byte[] bytes;

//Boilerplate iTextSharp setup here
//Create a stream that we can write to, in this case a MemoryStream
using (var ms = new MemoryStream()) {

    //Create an iTextSharp Document which is an abstraction of a PDF but **NOT** a PDF
    using (var doc = new Document()) {

        //Create a writer that's bound to our PDF abstraction and our stream
        using (var writer = PdfWriter.GetInstance(doc, ms)) {

            //Open the document for writing
            doc.Open();

            //Our sample HTML and CSS
            var example_html = @"<p>This <em>is </em><span class=""headline"" style=""text-decoration: underline;"">some</span> <strong>sample <em> text</em></strong><span style=""color: red;"">!!!</span></p>";
            var example_css = @".headline{font-size:200%}";

            /**************************************************
             * Example #1                                     *
             *                                                *
             * Use the built-in HTMLWorker to parse the HTML. *
             * Only inline CSS is supported.                  *
             * ************************************************/

            //Create a new HTMLWorker bound to our document
            using (var htmlWorker = new iTextSharp.text.html.simpleparser.HTMLWorker(doc)) {

                //HTMLWorker doesn't read a string directly but instead needs a TextReader (which StringReader subclasses)
                using (var sr = new StringReader(example_html)) {

                    //Parse the HTML
                    htmlWorker.Parse(sr);
                }
            }

            /**************************************************
             * Example #2                                     *
             *                                                *
             * Use the XMLWorker to parse the HTML.           *
             * Only inline CSS and absolutely linked          *
             * CSS is supported                               *
             * ************************************************/

            //XMLWorker also reads from a TextReader and not directly from a string
            using (var srHtml = new StringReader(example_html)) {

                //Parse the HTML
                iTextSharp.tool.xml.XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, srHtml);
            }

            /**************************************************
             * Example #3                                     *
             *                                                *
             * Use the XMLWorker to parse HTML and CSS        *
             * ************************************************/

            //In order to read CSS as a string we need to switch to a different constructor
            //that takes Streams instead of TextReaders.
            //Below we convert the strings into UTF8 byte array and wrap those in MemoryStreams
            using (var msCss = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(example_css))) {
                using (var msHtml = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(example_html))) {

                    //Parse the HTML
                    iTextSharp.tool.xml.XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, msHtml, msCss);
                }
            }


            doc.Close();
        }
    }

    //After all of the PDF "stuff" above is done and closed but **before** we
    //close the MemoryStream, grab all of the active bytes from the stream
    bytes = ms.ToArray();
}

//Now we just need to do something with those bytes.
//Here I'm writing them to disk but if you were in ASP.Net you might Response.BinaryWrite() them.
//You could also write the bytes to a database in a varbinary() column (but please don't) or you
//could pass them to another function for further PDF processing.
var testFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.pdf");
System.IO.File.WriteAllBytes(testFile, bytes);

2017's update

There are good news for HTML-to-PDF demands. As this answer showed, the W3C standard css-break-3 will solve the problem... It is a Candidate Recommendation with plan to turn into definitive Recommendation this year, after tests.

As not-so-standard there are solutions, with plugins for C#, as showed by print-css.rocks.

Solution 2

As of 2018, there is also iText7 (A next iteration of old iTextSharp library) and its HTML to PDF package available: itext7.pdfhtml

Usage is straightforward:

HtmlConverter.ConvertToPdf(
    new FileInfo(@"Path\to\Html\File.html"),
    new FileInfo(@"Path\to\Pdf\File.pdf")
);

Method has many more overloads.

Update: iText* family of products has dual licensing model: free for open source, paid for commercial use.

Solution 3

@Chris Haas has explained very well how to use itextSharp to convert HTML to PDF, very helpful
my add is:
By using HtmlTextWriter I put html tags inside HTML table + inline CSS i got my PDF as I wanted without using XMLWorker .
Edit: adding sample code:
ASPX page:

<asp:Panel runat="server" ID="PendingOrdersPanel">
 <!-- to be shown on PDF-->
 <table style="border-spacing: 0;border-collapse: collapse;width:100%;display:none;" >
 <tr><td><img src="abc.com/webimages/logo1.png" style="display: none;" width="230" /></td></tr>
<tr style="line-height:10px;height:10px;"><td style="display:none;font-size:9px;color:#10466E;padding:0px;text-align:right;">blablabla.</td></tr>
 <tr style="line-height:10px;height:10px;"><td style="display:none;font-size:9px;color:#10466E;padding:0px;text-align:right;">blablabla.</td></tr>
 <tr style="line-height:10px;height:10px;"><td style="display:none;font-size:9px;color:#10466E;padding:0px;text-align:right;">blablabla</td></tr>
<tr style="line-height:10px;height:10px;"><td style="display:none;font-size:9px;color:#10466E;padding:0px;text-align:right;">blablabla</td></tr>
<tr style="line-height:10px;height:10px;"><td style="display:none;font-size:11px;color:#10466E;padding:0px;text-align:center;"><i>blablabla</i> Pending orders report<br /></td></tr>
 </table>
<asp:GridView runat="server" ID="PendingOrdersGV" RowStyle-Wrap="false" AllowPaging="true" PageSize="10" Width="100%" CssClass="Grid" AlternatingRowStyle-CssClass="alt" AutoGenerateColumns="false"
   PagerStyle-CssClass="pgr" HeaderStyle-ForeColor="White" PagerStyle-HorizontalAlign="Center" HeaderStyle-HorizontalAlign="Center" RowStyle-HorizontalAlign="Center" DataKeyNames="Document#" 
      OnPageIndexChanging="PendingOrdersGV_PageIndexChanging" OnRowDataBound="PendingOrdersGV_RowDataBound" OnRowCommand="PendingOrdersGV_RowCommand">
   <EmptyDataTemplate><div style="text-align:center;">no records found</div></EmptyDataTemplate>
    <Columns>                                           
     <asp:ButtonField CommandName="PendingOrders_Details" DataTextField="Document#" HeaderText="Document #" SortExpression="Document#" ItemStyle-ForeColor="Black" ItemStyle-Font-Underline="true"/>
      <asp:BoundField DataField="Order#" HeaderText="order #" SortExpression="Order#"/>
     <asp:BoundField DataField="Order Date" HeaderText="Order Date" SortExpression="Order Date" DataFormatString="{0:d}"></asp:BoundField> 
    <asp:BoundField DataField="Status" HeaderText="Status" SortExpression="Status"></asp:BoundField>
    <asp:BoundField DataField="Amount" HeaderText="Amount" SortExpression="Amount" DataFormatString="{0:C2}"></asp:BoundField> 
   </Columns>
    </asp:GridView>
</asp:Panel>

C# code:

protected void PendingOrdersPDF_Click(object sender, EventArgs e)
{
    if (PendingOrdersGV.Rows.Count > 0)
    {
        //to allow paging=false & change style.
        PendingOrdersGV.HeaderStyle.ForeColor = System.Drawing.Color.Black;
        PendingOrdersGV.BorderColor = Color.Gray;
        PendingOrdersGV.Font.Name = "Tahoma";
        PendingOrdersGV.DataSource = clsBP.get_PendingOrders(lbl_BP_Id.Text);
        PendingOrdersGV.AllowPaging = false;
        PendingOrdersGV.Columns[0].Visible = false; //export won't work if there's a link in the gridview
        PendingOrdersGV.DataBind();

        //to PDF code --Sam
        string attachment = "attachment; filename=report.pdf";
        Response.ClearContent();
        Response.AddHeader("content-disposition", attachment);
        Response.ContentType = "application/pdf";
        StringWriter stw = new StringWriter();
        HtmlTextWriter htextw = new HtmlTextWriter(stw);
        htextw.AddStyleAttribute("font-size", "8pt");
        htextw.AddStyleAttribute("color", "Grey");

        PendingOrdersPanel.RenderControl(htextw); //Name of the Panel
        Document document = new Document();
        document = new Document(PageSize.A4, 5, 5, 15, 5);
        FontFactory.GetFont("Tahoma", 50, iTextSharp.text.BaseColor.BLUE);
        PdfWriter.GetInstance(document, Response.OutputStream);
        document.Open();

        StringReader str = new StringReader(stw.ToString());
        HTMLWorker htmlworker = new HTMLWorker(document);
        htmlworker.Parse(str);

        document.Close();
        Response.Write(document);
    }
}

of course include iTextSharp Refrences to cs file

using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.html.simpleparser;
using iTextSharp.tool.xml;

Hope this helps!
Thank you

Solution 4

I use the following code to create PDF

protected void CreatePDF(Stream stream)
        {
            using (var document = new Document(PageSize.A4, 40, 40, 40, 30))
            {
                var writer = PdfWriter.GetInstance(document, stream);
                writer.PageEvent = new ITextEvents();
                document.Open();

                // instantiate custom tag processor and add to `HtmlPipelineContext`.
                var tagProcessorFactory = Tags.GetHtmlTagProcessorFactory();
                tagProcessorFactory.AddProcessor(
                    new TableProcessor(),
                    new string[] { HTML.Tag.TABLE }
                );

                //Register Fonts.
                XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS);
                fontProvider.Register(HttpContext.Current.Server.MapPath("~/Content/Fonts/GothamRounded-Medium.ttf"), "Gotham Rounded Medium");
                CssAppliers cssAppliers = new CssAppliersImpl(fontProvider);

                var htmlPipelineContext = new HtmlPipelineContext(cssAppliers);
                htmlPipelineContext.SetTagFactory(tagProcessorFactory);

                var pdfWriterPipeline = new PdfWriterPipeline(document, writer);
                var htmlPipeline = new HtmlPipeline(htmlPipelineContext, pdfWriterPipeline);

                // get an ICssResolver and add the custom CSS
                var cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(true);
                cssResolver.AddCss(CSSSource, "utf-8", true);
                var cssResolverPipeline = new CssResolverPipeline(
                    cssResolver, htmlPipeline
                );

                var worker = new XMLWorker(cssResolverPipeline, true);
                var parser = new XMLParser(worker);
                using (var stringReader = new StringReader(HTMLSource))
                {
                    parser.Parse(stringReader);
                    document.Close();
                    HttpContext.Current.Response.ContentType = "application /pdf";
                    if (base.View)
                        HttpContext.Current.Response.AddHeader("content-disposition", "inline;filename=\"" + OutputFileName + ".pdf\"");
                    else
                        HttpContext.Current.Response.AddHeader("content-disposition", "attachment;filename=\"" + OutputFileName + ".pdf\"");
                    HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
                    HttpContext.Current.Response.WriteFile(OutputPath);
                    HttpContext.Current.Response.End();
                }
            }
        }
Share:
254,083
Chris Haas
Author by

Chris Haas

I'm a coder. I have and have had other titles, Director of Development, Lead Developer, etc., but I think coder is what really describes me these days. I love to hate PHP, and I have to say "needle" and "haystack" several times a day outload to make sure I'm doing things correctly. (I get that right 90% of the time these days, too.) Coming from .Net, I miss generics but only for collections. JavaScript is probably my favorite language. Not jQuery, Node, React or whatever framework is cool this week, but JavaScript. No compiling, interpreting or transpiling, just raw JS (with the evolutions provided by ES). After spending over a decade with .Net (C# and VB.Net) using Visual Studio, I then spent way too long without an IDE and cannot stress enough: USE AN IDE!!! Even if you have to pay for it. Notepad, Sublime, VIM, grep, sed, etc., are all well and fine for simple things, but seriously, switching to an IDE can save so much time. I also like talking and writing about technical things, and given the stage, I can go on and on and on. Lastly, for no good reason except that they make me smile, here's some quotes: Debugging is like being the detective in a crime movie where you are also the murderer. — Filipe Fortes, 2013-11-10 A comment in a CSS file means you've probably sinned in your code. — Chris Haas, 2020-03-31 (private Slack) Anyone ever dread writing a block of code, only to find out that you already wrote it a month ago? Really makes you smile. — Chris Haas, 2020-04-22 (private Slack)

Updated on April 25, 2020

Comments

  • Chris Haas
    Chris Haas about 4 years

    I want to convert the below HTML to PDF using iTextSharp but don't know where to start:

    <style>
    .headline{font-size:200%}
    </style>
    <p>
      This <em>is </em>
      <span class="headline" style="text-decoration: underline;">some</span>
      <strong>sample<em> text</em></strong>
      <span style="color: red;">!!!</span>
    </p>
    
  • AH.
    AH. over 9 years
    Very nice example.Thanks.
  • Jay Bienvenu
    Jay Bienvenu over 9 years
    The code declares a "new Document()" and comments that this Document type is an "iTextSharp Document." This reference should be namespaced completely as "iTextSharp.text.Document()". The project where I'm using iTextSharp already had a Document class and I had to dig through the iTextSharp namespace to correct the reference.
  • Don
    Don over 9 years
    @Chris Haas I have a problem to convert all html pages to pdf using your code samples. The problem is posted in stackoverflow.com/questions/28923080/…. Could you please check out?
  • B. Clay Shannon-B. Crow Raven
    B. Clay Shannon-B. Crow Raven over 9 years
    Is there a similar example that works on XML (Sharepoint lists) instead of HTML, converting to PDF using iTextSharp (or something else, even)?
  • Chris Haas
    Chris Haas over 9 years
    @B.ClayShannon, see this and this
  • Ensar Turkoglu
    Ensar Turkoglu over 8 years
    Hi from the future, how can i specify the html path? e.g: var example_html = "~Reports/Index.html" is that possible?
  • Chris Haas
    Chris Haas over 8 years
    If you have a file path then you need to load it using a TextReader or something like System.IO.File.ReadAllText()
  • Dr R Dizzle
    Dr R Dizzle almost 8 years
    When I try your third example (to parse both HTML and CSS), I get a no accessible 'ParseXHTML' accepts this number of arguments error. Do you know how I can fix this?
  • Jamshaid K.
    Jamshaid K. almost 8 years
    This code works properly. but i cannot view the pdf file when it is downloaded. what am i doing wrong?
  • Sam
    Sam almost 8 years
    if this code works for you, you should be able to see your PDF after downloading. I would suggest that you post a question with your code to review and see where's the error. Also try to run your code from different browsers and see if you would be able to view the PDF or not.
  • mkl
    mkl almost 8 years
    Ah, ok. It looks like some TestPDF.HtmlToPdfBuilder utility class is used here to do the actual conversion. [...] I just downloaded it. It turns out to essentially be a wrapper for the iTextSharp HTMLWorker class which meanwhile has been deprecated / obsoleted.
  • Jonas T
    Jonas T over 7 years
    This code simple is giving me "Cannot access a closed Stream." error. I am using itextsharp 5.5.10 and itextsharp.xmlworker 5.5.10
  • SteveFerg
    SteveFerg over 7 years
    the line with "iTextSharp.text.html.simpleparser.HTMLWorker(doc))" says 'htmlWorker' is obsolete with 5.5.10. What should this be changed to?
  • Hakan Fıstık
    Hakan Fıstık over 7 years
    iTextSharp.tool namespace give me an error that is not exists, and I also get iTextSharp.text.html.simpleparser.HTMLWorker(doc)) is obsolete Version (5.5.8.0)
  • Ori Nachum
    Ori Nachum over 7 years
    @HakamFostok, read the whole text - Chris mentioend there HtmlWorker is obsolete, yet he put it there for comparison. It's worth reading. Trying to handle the iTextSharp.tool issue as well
  • kmasalski
    kmasalski over 7 years
    In case anyone is looking for solution to iTextSharp.tool, you have to execute NuGet command: Install-Package itextsharp.xmlworker
  • Selman
    Selman over 6 years
    @konradmas Thanks That is What I was looking for
  • George Chakhidze
    George Chakhidze almost 6 years
    @rst Updated. Other answers mention iText library too (without noting its commercial nature), — You might want to nitpick them as well...
  • rst
    rst almost 6 years
    I dont nitpick.
  • Daniel Georgiev
    Daniel Georgiev almost 6 years
    @JonasT I am stuck with the same exception. Did you solve it?
  • Jonas T
    Jonas T almost 6 years
    @DanielGeorgiev I did. I posted my answer in the answering section. It is too long to post here. Hope it helps.
  • Som
    Som over 5 years
    what is the TestPDF in the CreatePDFFromHTMLFile() method
  • Amedee Van Gasse
    Amedee Van Gasse over 5 years
    HTMLWorker and XMLWorker are indeed obsolete. The current "state of the art" is iText 7 + pdfHTML.
  • Carlos Maia de Morais
    Carlos Maia de Morais almost 4 years
    i am with error trying execute the second example. i would put css on the head tag. But i am using .Net core 2.1
  • Andrei Krasutski
    Andrei Krasutski over 2 years
    CSS page-break-after is works!