weasyprint: Printing documents using WeasyPrint

The lino.modlib.weasyprint plugin installs two build methods for generating printable documents using weasyprint.

Applications that use this plugin will automatically install the 'weasyprint' Python package when the server administrator runs install.

The two build methods defined by this plugin both use the same input template, whose ending must be .weasy.html. Both methods then render the input template through Jinja with the standard context variables (defined by get_printable_context. The base build method WeasyBuildMethod then returns this HTML output "as is", the other method runs weasyprint over the HTML file to convert it to a .pdf file.

Examples in this document use the lino_book.projects.lydia demo project.

>>> from lino import startup
>>> startup('lino_book.projects.lydia.settings.doctests')
>>> from lino.api.shell import *
>>> from lino.api.doctest import *

See also printing : Basic printing functionality.

Build methods

This plugin defines no models, it just adds two build methods to your the global list of build methods (lino.modlib.printing.BuildMethods).

class lino.modlib.weasyprint.WeasyBuildMethod

The base class for both build methods.

class lino.modlib.weasyprint.WeasyHtmlBuildMethod

Renders the input template and returns the unmodified output as plain HTML.

class lino.modlib.weasyprint.WeasyPdfBuildMethod

Like WeasyBuildMethod, but the rendered HTML is then passed through weasyprint which converts from HTML to PDF.

Templates

weasyprint/base.weasy.html

The base template. Defines the general HTML and CSS and block definitions to be used by every weasyprint template. See the source code.

Actual templates use the base template by starting adding the following line:

{%- extends "weasyprint/base.weasy.html" -%}

Examples of weasyprint templates

Here is a list of weasyprint templates that use the base template. You can use them as examples for your own work. We also use this list for manual end-user testing.

Here is a list of the weasy templates included with the Lino Extensions Library:

>>> import lino_xl
>>> from os.path import dirname
>>> from atelier.sheller import Sheller
>>> shell = Sheller(dirname(lino_xl.__file__))
>>> shell("find -name '*.weasy.html' | sort")
./lib/bevats/config/bevats/Declaration/default.weasy.html
./lib/courses/config/courses/Course/presence_sheet.weasy.html
./lib/excerpts/config/excerpts/base.weasy.html
./lib/ledger/config/contacts/Partner/payment_reminder.weasy.html
./lib/lists/config/lists/List/list_members.weasy.html
./lib/orders/config/orders/Order/base.weasy.html
./lib/orders/config/orders/Order/default.weasy.html
./lib/sales/config/sales/VatProductInvoice/base.weasy.html
./lib/sales/config/sales/VatProductInvoice/default.weasy.html
./lib/sheets/config/sheets/Report/default.weasy.html
./lib/subscriptions/config/subscriptions/Subscription/base.weasy.html
./lib/subscriptions/config/subscriptions/Subscription/default.weasy.html
./lib/working/config/working/ServiceReport/default.weasy.html

Note that excerpts, orders and sales have their own FOO/base.weasy.html templates, which inherit from the main base weasyprint/base.weasy.html.

For other usage examples see the specs of Lino Welfare.

Warnings about Cairo and Pango

This plugin installs a warnings filter for the cffi.model module in order to get rid of a disturbing warning There are known rendering problems with Cairo <= 1.14.0 and @font-face support needs Pango >= 1.38 issued by weasyprint.

How it all works

What happens when I print an invoice?

When Lino starts up, it finds the excerpt type for sales invoices (more precisely the sales.VatProductInvoice model) and therefore installs the print action on that model. This is why you a have a print button per invoice.

The excerpt type tells Lino which build method you want to use for building your printable document. The default build method is weasypdf.

When we know the build method, we can compute the name of the template to use. This name is a combination of sales/VatProductInvoice (the plugin and model name) and default.weasy.html (the default template filename for weasypdf when lino_xl.lib.excerpts.ExcerptType.template is empty).

Lino now searches for a file named sales/VatProductInvoice/default.weasy.html. This file can exist under any config directory. If you have a local config directory, this is searched first. Otherwise Lino uses a default file from the source code directory. More about config directories in Introduction to config directories.

Now look at the default sales/VatProductInvoice/default.weasy.html template file. The first line is:

{%- extends "weasyprint/base.weasy.html" -%}

Which means that Lino first loads yet another template, called weasyprint/base.weasy.html.

How weasyprint templates work

The weasyprint template uses the CSS @-rules @page and @bottom-right, which define styles to apply to individual pages when printing the document. That is, they are used to apply styles for paged media only, not for continuous media like a browser window.

List of all page-margin properties: https://www.quackit.com/css/at-rules/css_page-margin_properties_list.cfm

Setting the height attribute in HTML is called a "presentational hint" and it's now recommended not to use them and use CSS instead. Presentational hints are ignored by WeasyPrint by default, but you can handle them using the --presentational-hints CLI parameter. https://github.com/Kozea/WeasyPrint/issues/872

Lino currently doesn't use arbitrary "complex" HTML in headers and footers (as documented here). The standard system with at-rules works well for us.

It contains pseudo-elements for styling the first page as well as the left and right margins of the page.

It can contain something like this:

<style type="text/css">
@page {
    @top-right {
      height: 20mm;
      padding: 0px;
      text-align: right;
      content: url(file://{{logo_file}});
    }
</style>

More readings:

Related work: