Skip to content

Instantly share code, notes, and snippets.

Last active June 16, 2024 15:17
Show Gist options
  • Save pikhovkin/5642563 to your computer and use it in GitHub Desktop.
Save pikhovkin/5642563 to your computer and use it in GitHub Desktop.
Repeat on each page of complex headers (eg, tables) except the first page
# coding: utf-8
from weasyprint import HTML, CSS
def get_page_body(boxes):
for box in boxes:
if box.element_tag == 'body':
return box
return get_page_body(box.all_children())
# Main template
html = HTML('template.html')
main_doc = html.render(stylesheets=[CSS('styles.css')])
exists_links = False
# Template of header
html = HTML('header.html')
header = html.render(stylesheets=[CSS(string='div {position: fixed; top: 1cm; left: 1cm;}')])
header_page = header.pages[0]
exists_links = exists_links or header_page.links
header_body = get_page_body(header_page._page_box.all_children())
header_body = header_body.copy_with_children(header_body.all_children())
# Template of footer
html = HTML('footer.html')
footer = html.render(stylesheets=[CSS(string='div {position: fixed; bottom: 1cm; left: 1cm;}')])
footer_page = footer.pages[0]
exists_links = exists_links or footer_page.links
footer_body = get_page_body(footer_page._page_box.all_children())
footer_body = footer_body.copy_with_children(footer_body.all_children())
# Insert header and footer in main doc
for i, page in enumerate(main_doc.pages):
if not i:
page_body = get_page_body(page._page_box.all_children())
page_body.children += header_body.all_children()
page_body.children += footer_body.all_children()
if exists_links:
Copy link

Thanks for this - you saved me a tremendous amount of time :)

Copy link

Welcome! :)

Copy link

This gist is our python, pdf creating saviour. Thanks a lot for sharing, @pikhovkin.
For anyone trying it, you might want to change:
header = html.render(stylesheets=[CSS(string='div {position: fixed; top: 1cm; left: 1cm;}')])
header = html.render(stylesheets=[CSS(string='@page {size:A4; margin:0;} div {position: fixed; top: 1cm; left: 1cm;}')])
in order to get the full header space available in your PDF page so that you can add even more complex elements.

Copy link

This is brilliant, you saved me a hug amount of time, thanks 👍

Copy link

Thanks a lot man!!!
Great solution

Copy link

elecay commented Jan 10, 2022


Copy link

domjancik commented Feb 7, 2022

It's a neat solution but I'd be wary of using non-documented internal attributes like _page_box here as there is always a chance these might break in the future without anyone mentioning it in change logs.

I'd also like to point out that at least as of WeasyPrint 52.5 running() and content() values are supported, see for example.

This way you can include your as-complex-as-needed headers and footers right in the main document, select them with CSS and place them in one of the page margins. With this solution counter(page) will also work as the headers are actually part of the full document.

To skip the first page, you'd just include the header element only in the second page. You can also change headers further down the document simply by inlining another header with the same CSS selector. This is something that's rather hard to do with this solution when dynamic length data is concerned.

I have noticed that WeasyPrint will by default size these according to content and even setting width: 100% or width: 100vw will not stretch them to full width - maybe there is no proper parent element? In any case I worked around that by setting an absolute width equal to the page width, there may also be better ways.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment