Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Insert header / footer on every pdf pages #36

Open
bastien34 opened this issue May 7, 2020 · 8 comments
Open

Insert header / footer on every pdf pages #36

bastien34 opened this issue May 7, 2020 · 8 comments

Comments

@bastien34
Copy link

Is there a way to generate pages with arbitrary complex footer or header (ie.rendered template)?

@Gagaro
Copy link

Gagaro commented May 13, 2020

Here is the snippet I use to do that:

def build_pdf(html_content, header_html=None, footer_html=None):
    """
    Build a pdf and returns its binary content

    For header and footer, CURRENT_PAGE and PAGES_COUNT are replaced by the actual numbers.

    """

    def get_page_body(boxes):
        for box in boxes:
            if box.element_tag == "body":
                return box

            return get_page_body(box.all_children())

    def get_page_and_body(html, css):
        if html is None:
            return None
        html = weasyprint.HTML(
            string=html,
            base_url=getattr(settings, "WEASYPRINT_BASEURL", None),
            url_fetcher=django_url_fetcher,
        )
        css += "@page { margin 0 !important; }"
        document = html.render(stylesheets=[weasyprint.CSS(string=css)])

        document_page = document.pages[0]
        document_body = get_page_body(document_page._page_box.all_children())
        return (
            document_page,
            document_body.copy_with_children(document_body.all_children()),
        )

    def preprocess_html(html, context):
        for key, value in context.items():
            html = html.replace(key, str(value))
        return html

    document = weasyprint.HTML(
        string=html_content,
        base_url=getattr(settings, "WEASYPRINT_BASEURL", None),
        url_fetcher=django_url_fetcher,
    ).render()

    if header_html is not None or footer_html is not None:

        pages_count = len(document.pages)
        for current_page_number, page in enumerate(document.pages, start=1):
            context = {"CURRENT_PAGE": current_page_number, "PAGES_COUNT": pages_count}
            header_page, header_body = get_page_and_body(
                preprocess_html(header_html, context),
                css="body {position: fixed; top: -1.5cm;}",
            )
            footer_page, footer_body = get_page_and_body(
                preprocess_html(footer_html, context),
                css="body {position: fixed; bottom: -1.5cm;}",
            )

            page_body = get_page_body(page._page_box.all_children())

            if header_body is not None:
                page_body.children += header_body.all_children()

            if footer_body is not None:
                page_body.children += footer_body.all_children()

            page.links.extend(header_page.links)
            page.links.extend(footer_page.links)

    return document.write_pdf()

@barslmn
Copy link

barslmn commented Dec 25, 2020

Using css for this might be an easier approach.

Change margins according to your needs.

@charset "UTF-8";
@page {
  margin: 1cm;
  margin-top: 6cm;
  margin-bottom: 1cm;
  size: landscape;
}

.footer {
  position: fixed;
  bottom: 0;
  width: 100%;
  margin-bottom: -1cm;
}
.header {
  position: fixed;
  top: 0;
  margin-top: -5.5cm;
  width: 100%;
}

Here is the minimal html.

{% load static %}
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" media="print" href="{% static 'stylesheet.css' %}">
    <title>My title</title>
    <meta name="description" content="pdf template">
    <meta name="author" content="bars">
  </head>
  <body>
    <div class="header">
    Header content
    </div>
    
    ....
    Other Content
    ....

    <div class="footer">
    Footer content
    </div>
  </body>
</html>

@kachmul2004
Copy link

kachmul2004 commented Jun 1, 2022

Hi @barslmn thanks for this trick. It works fine with text header, but I would like to use an image instead. Do you have an idea on how I can accomplish this?

I am trying to position the image to the right (it's a small rectangular logo about 250 * 90px). My problem is that when I "position: fixed; right: 0" it, it goes into the same line with the body of text on each page. However, when I "float: right", I am able to have it where I want it to be, but then it only appears on the first page.

If I try to move the image above the text by reducing margin-top, part of the image gets cropped, and the further I keep decreasing the margin-top, the image eventually ends up moving to the previous page (meaning it starts showing on the bottom of of each previous page)

@barslmn
Copy link

barslmn commented Jun 1, 2022

Hi @kachmul2004,
Looking back at it I can see that I used columns and rows to position the image in the header. So aligning images can be done like this:

CSS:

@charset "UTF-8";
@page {
  margin: 1cm;
  margin-top: 6cm;
  margin-bottom: 1cm;
  size: landscape;
}

.footer {
  position: fixed;
  bottom: 0;
  width: 100%;
  margin-bottom: -1cm;
}
.header {
  position: fixed;
  top: 0;
  margin-top: -5.5cm;
  width: 100%;
}
.row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  width: 100%;
}
.column {
  flex-direction: column;
  flex-basis: 100%;
  flex: 1;
}

and HTML:

{% load static %}
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" media="print" href="{% static 'stylesheet.css' %}">
    <title>My title</title>
    <meta name="description" content="pdf template">
    <meta name="author" content="bars">
  </head>
  <body>
    <div class="header">
      <div class="row">
        <div class="column">
        </div>
        <div class="column">
        </div>
        <div class="column">
          <img id="myrightalignedlogo" src="{% static 'path/to/my/myrightalignedlogo.png' %}">
        </div>
      </div>
    </div>
    
    ....
    Other Content
    ....

    <div class="footer">
    Footer content
    </div>
  </body>
</html>

@kachmul2004
Copy link

kachmul2004 commented Jun 1, 2022

@barslmn unfortunately, I am still getting the same results as before. I have even tried using pure html (without Jinja templates) and the result is still the same. Here is a jsfiddle code that will show you what I am getting.

https://jsfiddle.net/o5svhd7f/

@barslmn
Copy link

barslmn commented Jun 3, 2022

Sorry I got some attribute in the css wrong. .column doesnt have width it has flex:1. I edited the above comment. And your jsfiddle: https://jsfiddle.net/5kphbo7x/

@kachmul2004
Copy link

I ended up using a table. Hacky, but easier to manipulate. Thanks

@edaagapu
Copy link

edaagapu commented Aug 30, 2022

Hi! I found a solution than not require change the position.

That's is my CSS stylesheet:
_.header {
position: running(header);
}

.footer {
position: running(footer);
text-align: right;
}

@page {
@top-right {
content: element(header, first-except);
height: -4cm;
width: 100%;
}
@bottom-center {
color: #014e04;
content: counter(page);
height: 1cm;
text-align: center;
width: 1cm;
}
@bottom-left {
content: element(footer, first-except);
height: 1cm;
width: 100%;
}
}_

My Template (HTML) is:

_<!doctype html>

<title>{% block title %}{% endblock %}</title> {% block styles %} {% endblock %}

Custom header

{{ url_your_image }}

Custom footer

{% block content %} {% endblock %} _

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

No branches or pull requests

6 participants