Python 3 weasyprint Library Example to Convert HTML5 Template to PDF Document in Command Line Full Project For Beginners

 

 

pip install weasyprint

 

 

 

app.py

 

 

"""Generate PDF from HTML."""

from pathlib import Path
import sys

from weasyprint import HTML


def makepdf(html):
    """Generate a PDF file from a string of HTML."""
    htmldoc = HTML(string=html, base_url="")
    return htmldoc.write_pdf()


def run():
    """Command runner."""
    infile = sys.argv[1]
    outfile = sys.argv[2]
    html = Path(infile).read_text()
    pdf = makepdf(html)
    Path(outfile).write_bytes(pdf)


if __name__ == "__main__":
    run()

 

 

sample.html

 

 

<!DOCTYPE html>
<html>
  <head>
    <title>PDF Generation with Python and WeasyPrint</title>
    <link href="sample.css" rel="stylesheet" />
  </head>
  <body>
    <img
      src="https://dev-to-uploads.s3.amazonaws.com/i/03go0ipro79sbt8ir7oq.png"
      alt="Python and PDF"
    />
    <h1>Python PDF Generation from HTML with WeasyPrint</h1>
    <p>
      While there are numerous ways to handle PDF documents with
      <a href="https://python.org">Python</a>, I find generating or editing HTML
      far easier and more reliable than trying to figure out the intricacies of
      the PDF format. Sure, there is the venerable
      <a href="https://pypi.org/project/reportlab/">ReportLab</a>, and if HTML
      is not your cup of tea, I encourage you to look into that option. There is
      also <a href="https://mstamy2.github.io/PyPDF2/">PyPDF2</a>. Or maybe
      <a href="https://github.com/sfneal/PyPDF3">PyPDF3</a>? No, perhaps
      <a href="https://github.com/claird/PyPDF4">PyPDF4</a>! Hmmm... see the
      problem? My best guess is PyPDF3, for what that is worth.
    </p>
    <p>So many choices...</p>
    <p>
      <img
        src="https://dev-to-uploads.s3.amazonaws.com/i/omcprzuh7n6u0nyzshqv.png"
        alt="So many choices in the cereal aisle"
      />
    </p>
    <p>But there is an easy choice if you are comfortable with HTML.</p>
    <p>
      Enter <a href="https://weasyprint.org/">WeasyPrint</a>. It takes HTML and
      CSS, and converts it to a usable and potentially beautiful PDF document.
    </p>
    <blockquote>
      <p>
        The code samples in this article can be accessed in
        <a href="https://github.com/bowmanjd/pyweasyprintdemo"
          >the associated Github repo</a
        >. Feel free to clone and adapt.
      </p>
    </blockquote>
    <h2>Installation</h2>
    <p>
      To install <a href="https://weasyprint.org/">WeasyPrint</a>, I recommend
      you first
      <a
        href="https://dev.to/bowmanjd/python-tools-for-managing-virtual-environments-3bko"
        >set up a virtual environment with the tool of your choice</a
      >.
    </p>
    <p>
      Then, installation is as simple as performing something like the following
      in an activated virtual environment:
    </p>
    <pre><code class="language-console">pip install weasyprint
</code></pre>
    <p>Alternatives to the above, depending on your tooling:</p>
    <ul>
      <li><code>poetry add weasyprint</code></li>
      <li><code>conda install -c conda-forge weasyprint</code></li>
      <li><code>pipenv install weasyprint</code></li>
    </ul>
    <p>You get the idea.</p>
    <p>
      If you only want the <code>weasyprint</code> command-line tool, you could
      even
      <a
        href="https://dev.to/bowmanjd/how-do-i-install-a-python-command-line-tool-or-script-hint-pipx-3i2"
        >use pipx</a
      >
      and install with <code>pipx install weasyprint</code>. While that would
      not make it very convenient to access as a Python library, if you just
      want to convert web pages to PDFs, that may be all you need.
    </p>
    <h2>A command line tool (Python usage optional)</h2>
    <p>
      Once installed, the <code>weasyprint</code> command line tool is
      available. You can convert an HTML file or a web page to PDF. For
      instance, you could try the following:
    </p>
    <pre><code class="language-console">weasyprint \
"https://en.wikipedia.org/wiki/Python_(programming_language)" \
python.pdf
</code></pre>
    <p>
      The above command will save a file <code>python.pdf</code> in the current
      working directory, converted from the HTML from the
      <a href="https://en.wikipedia.org/wiki/Python_(programming_language)"
        >Python programming language article in English on Wikipedia</a
      >. It ain't perfect, but it gives you an idea, hopefully.
    </p>
    <p>
      You don't have to specify a web address, of course. Local HTML files work
      fine, and they provide necessary control over content and styling.
    </p>
    <pre><code class="language-console">weasyprint sample.html out/sample.pdf
</code></pre>
    <p>
      Feel free to
      <a
        href="https://raw.githubusercontent.com/bowmanjd/pyweasyprintdemo/main/sample.html"
        >download a <code>sample.html</code></a
      >
      and an associated
      <a
        href="https://raw.githubusercontent.com/bowmanjd/pyweasyprintdemo/main/sample.css"
        ><code>sample.css</code> stylesheet</a
      >
      with the contents of this article.
    </p>
    <p>
      See
      <a
        href="https://weasyprint.readthedocs.io/en/latest/tutorial.html#as-a-standalone-program"
        >the WeasyPrint docs</a
      >
      for further examples and instructions regarding the standalone
      <code>weasyprint</code> command line tool.
    </p>
    <h2>Utilizing WeasyPrint as a Python library</h2>
    <p>
      The
      <a href="https://weasyprint.readthedocs.io/">Python API for WeasyPrint</a>
      is quite versatile. It can be used to load HTML when passed appropriate
      file pointers, file names, or the text of the HTML itself.
    </p>
    <p>
      Here is an example of a simple <code>makepdf()</code> function that
      accepts an HTML string, and returns the binary PDF data.
    </p>
    <pre><code class="language-python">from weasyprint import HTML


def makepdf(html):
    """Generate a PDF file from a string of HTML."""
    htmldoc = HTML(string=html, base_url="")
    return htmldoc.write_pdf()
</code></pre>
    <p>
      The main workhorse here is the <code>HTML</code> class. When instantiating
      it, I found I needed to pass a <code>base_url</code> parameter in order
      for it to load images and other assets from relative urls, as in
      <code><img src="somefile.png"></code>.
    </p>
    <p>
      Using <code>HTML</code> and <code>write_pdf()</code>, not only will the
      HTML be parsed, but associated CSS, whether it is embedded in the head of
      the HTML (in a <code><style></code> tag), or included in a
      stylesheet (with a
      <code
        ><link href="sample.css"
        rel="stylesheet"\></code
      >
      tag).
    </p>
    <p>
      I should note that <code>HTML</code> can load straight from files, and
      <code>write_pdf()</code> can write to a file, by specifying filenames or
      file pointers. See
      <a href="https://weasyprint.readthedocs.io/">the docs</a> for more detail.
    </p>
    <p>
      Here is a more full-fledged example of the above, with primitive command
      line handling capability added:
    </p>
    <pre><code class="language-python">from pathlib import Path
import sys

from weasyprint import HTML


def makepdf(html):
    """Generate a PDF file from a string of HTML."""
    htmldoc = HTML(string=html, base_url="")
    return htmldoc.write_pdf()


def run():
    """Command runner."""
    infile = sys.argv[1]
    outfile = sys.argv[2]
    html = Path(infile).read_text()
    pdf = makepdf(html)
    Path(outfile).write_bytes(pdf)


if __name__ == "__main__":
    run()
</code></pre>
    <p>
      You may
      <a
        href="https://raw.githubusercontent.com/bowmanjd/pyweasyprintdemo/main/weasyprintdemo.py"
        >download the above file</a
      >
      directly, or
      <a href="https://github.com/bowmanjd/pyweasyprintdemo"
        >browse the Github repo</a
      >.
    </p>
    <blockquote>
      <p>
        A note about Python types: the <code>string</code> parameter when
        instantiating <code>HTML</code> is a normal (Unicode) <code>str</code>,
        but <code>makepdf()</code> outputs <code>bytes</code>.
      </p>
    </blockquote>
    <p>
      Assuming the above file is in your working directory as
      <code>weasyprintdemo.py</code> and that a <code>sample.html</code> and an
      <code>out</code> directory are also there, the following should work well:
    </p>
    <pre><code class="language-console">python weasyprintdemo.py sample.html out/sample.pdf
</code></pre>
    <p>
      Try it out, then open <code>out/sample.pdf</code> with your PDF reader.
      Are we close?
    </p>
    <h2>Styling HTML for print</h2>
    <p>
      As is probably apparent, using WeasyPrint is easy. The real work with HTML
      to PDF conversion, however, is in the styling. Thankfully, CSS has pretty
      good support for printing.
    </p>
    <p>Some useful CSS print resources:</p>
    <ul>
      <li>
        <a href="https://css-tricks.com/tag/print-stylesheet/"
          >Various articles on CSS-Tricks</a
        >
      </li>
      <li>
        <a href="https://flaviocopes.com/css-printing/#print-css"
          >A nice summary on flaviocopes</a
        >
      </li>
      <li>
        <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/Printing"
          >The MDN web docs</a
        >
      </li>
    </ul>
    <p>This simple stylesheet demonstrates a few basic tricks:</p>
    <pre><code class="language-css">body {
  font-family: sans-serif;
}
@media print {
  a::after {
    content: " (" attr(href) ") ";
  }
  pre {
    white-space: pre-wrap;
  }
  @page {
    margin: 0.75in;
    size: Letter;
    @top-right {
      content: counter(page);
    }
  }
  @page :first {
    @top-right {
      content: "";
    }
  }
}
</code></pre>
    <p>
      First, use
      <a
        href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries"
        >media queries</a
      >. This allows you to use the same stylesheet for both print and screen,
      using <code>@media print</code> and
      <code>@media screen</code> respectively. In the example stylesheet, I
      assume that the defaults (such as seen in the
      <code>body</code> declaration) apply to all formats, and that
      <code>@media print</code> provides overrides. Alternatively, you could
      include separate stylesheets for print and screen, using the
      <code>media</code> attribute of the <code><link></code> tag, as in
      <code
        ><link rel="stylesheet" src="print.css"
        media="print" /></code
      >.
    </p>
    <p>
      Second,
      <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@page"
        >use <code>@page</code> CSS rules</a
      >. While
      <a href="https://caniuse.com/mdn-css_at-rules_page_size"
        >browser support</a
      >
      is pretty abysmal in 2020, WeasyPrint does a pretty good job of supporting
      what you need. Note the margin and size adjustments above, and the page
      numbering, in which we first define a counter in the top-right, then
      override with <code>:first</code> to make it blank on the first page only.
      In other words, page numbers only show from page 2 onward.
    </p>
    <p>
      Also note the <code>a::after</code> trick to explicitly display the
      <code>href</code> attribute when printing. This is either clever or
      annoying, depending on your goals.
    </p>
    <p>
      Another hint, not demonstrated above: within the
      <code>@media print</code> block, set <code>display: none</code> on any
      elements that don't need to be printed, and set
      <code>background: none</code> where you don't want backgrounds printed.
    </p>
    <h2>Django and Flask support</h2>
    <p>
      If you write <a href="https://www.djangoproject.com/">Django</a> or
      <a href="https://flask.palletsprojects.com/">Flask</a> apps, you may
      benefit from the convenience of the respective libraries for generating
      PDFs within these frameworks:
    </p>
    <ul>
      <li>
        <a href="https://github.com/fdemmer/django-weasyprint"
          >django-weasyprint</a
        >
        provides a <code>WeasyTemplateView</code> view base class or a
        <code>WeasyTemplateResponseMixin</code> mixin on a TemplateView
      </li>
      <li>
        <a href="https://pythonhosted.org/Flask-WeasyPrint/"
          >Flask-WeasyPrint</a
        >
        provides a special <code>HTML</code> class that works just like
        WeasyPrint's, but respects Flask routes and WSGI. Also provided is a
        <code>render_pdf</code> function that can be called on a template or on
        the <code>url_for()</code> of another view, setting the correct
        mimetype.
      </li>
    </ul>
    <h2>Generate HTML the way you like</h2>
    <p>
      WeasyPrint encourages the developer to make HTML and CSS, and the PDF just
      happens. If that fits your skill set, then you may enjoy experimenting
      with and utilizing this library.
    </p>
    <p><em>How</em> you generate HTML is entirely up to you. You might:</p>
    <ul>
      <li>
        Write HTML from scratch, and use
        <a href="https://jinja.palletsprojects.com/">Jinja templates</a> for
        variables and logic.
      </li>
      <li>
        Write Markdown and convert it to HTML with
        <a href="https://github.com/theacodes/cmarkgfm">cmarkgfm</a> or
        <a
          href="https://dev.to/bowmanjd/processing-markdown-in-python-using-available-commonmark-implementations-cmarkgfm-paka-cmark-and-mistletoe-350a"
          >other Commonmark implementation</a
        >.
      </li>
      <li>
        Generate HTML Pythonically, with
        <a href="https://github.com/Knio/dominate/">Dominate</a> or
        <a href="https://lxml.de/tutorial.html#the-e-factory"
          >lxml's E factory</a
        >
      </li>
      <li>
        Parse, modify, and prettify your HTML (or HTML written by others) with
        <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/"
          >BeautifulSoup</a
        >
      </li>
    </ul>
    <p>Then generate the PDF using WeasyPrint.</p>
    <p>Anything I missed? Feel free to leave comments!</p>
  </body>
</html>

 

See also  Python 3 Tkinter Cafe Management System Using MySQL Database GUI Desktop App Full Project For Beginners

 

sample.css

 

 

body {
  font-family: sans-serif;
}
code {
  font-family: monospace;
  background: #ccc;
  padding: 2px;
}
pre code {
  display: block;
}
img {
  display: block;
  margin-left: auto;
  margin-right: auto;
  width: 90%;
}
@media print {
  a::after {
    content: " (" attr(href) ") ";
  }
  pre {
    white-space: pre-wrap;
  }
  @page {
    margin: 0.75in;
    size: Letter;
    @top-right {
      content: counter(page);
    }
  }
  @page :first {
    @top-right {
      content: "";
    }
  }
}

 

See also  Python 3 Script to Find Second Largest Element in an Array or List Using Max() Method Full Tutorial For Beginners

 

python app.py sample.html output.pdf

 

 

Leave a Reply