Tanvir Hossain Bhuiyan
Tanvir Hossain Bhuiyan

Reputation: 787

Generate PDF with WeasyPrint having common header/footer and pagination

I am using WeasyPrint to generate PDF in Django. I can generate pdf from a static html file like below -

from django.template import Context, Template
import weasyprint

with open('static_file.html', 'r') as myfile:
    html_str = myfile.read()

template = Template(html_message)
    context = Context({'some_key': 'some_value'})
    rendered_str = template.render(context)

weasyprint.HTML(string=rendered_str).write_pdf('generated.pdf')

But I want to generate a PDF in which, I can include a common header/footer in each page and add pagination.

Also it will be very helpful if anyone can tell how to include a custom font to generate the PDF. I have installed the font in the OS (Ubuntu 14.04) but it is not working.

I have searched a lot on the web about these. But could not find a proper solution.

Upvotes: 15

Views: 27698

Answers (5)

Ashutosh uniyal
Ashutosh uniyal

Reputation: 1

try this solution

@media print {
            /* Adjust font size to make table fit within the page */

            .header-container {
                position: running(header);
                top: 0;
                left: 0;
                right: 0;
                width: 795px;
                display: flex;
                margin-top: 0px;
                margin-bottom: 20px;
            }

try with different width - width: 795px;

<style>
html, body {
            font-family: 'Georgia', 'Times New Roman', serif;
            width: 100%;
            display: block;
            margin: 0px;
        }

        .header-container {
           
            background-color: #194E3B; /* Light grey background */
            margin-top: 0px;
            margin-bottom: 20px;
            display: flex; /* Use flexbox to align elements in a row */
            align-items: center; /* Align items vertically in the center */
            width: 900px; /* Full width */
            position: fixed;
            top: 0;
            left: 0;
            z-index: 1000;
        }
        
        .header-title {
            font-size: 1.2em; /* Smaller font size for "equi" */
            display: flex;
            font-weight: 500; /* Semi-bold for emphasis */
            color: #f9f9f9; /* Dark teal color for the text */
            font-family: Arial, sans-serif; /* Consistent font family */
            border-right: 2px solid #ddd;
            padding-right: 20px; /* Space between logo and text */
            flex-shrink: 0; 
        }
        
        .header-desc {
            background-image: url('{{ static_base_url }}images/header_bg.png');
            background-color: #194E3B; /* Light grey background */
            display: flex;
            justify-content: space-between; /* Space between fund info and page number */
            align-items: center; /* Align items vertically in the center */
            flex-grow: 1; /* Ensure it takes the remaining space */
            height: 100%; /* Full height of the container */
        }
        
        .header {
            display: flex;
            flex-direction: column; /* Stack fund name and date vertically */
            align-items: flex-end; /* Right-align the text */
            justify-content: center;
            text-align: right;
            border-right: 2px solid #ddd;
            flex-grow: 1; /* Allow this section to take up remaining space */
        }
        
        .page-number {
            font-size: 0.9em; /* Smaller font size for the page number */
            color: #f9f9f9; /* Light color for the text */
            font-family: Arial, sans-serif;
            font-weight: 300; /* Light weight for page number */
            padding-left: 10px;
            padding-right: 10px;
        }
        
        .fund-name {
            font-size: 1.8em; /* Larger font size for the fund name */
            font-weight: 300; /* Light weight for the fund name */
            color: #f9f9f9; /* Dark teal color for the fund name */
            line-height: 1.2; /* Line height for better spacing */
            padding-right: 10px;
        }
        
        .date {
            font-size: 1em; /* Smaller font size for the date */
            font-weight: 400; /* Regular weight for the date */
            color: #f9f9f9; /* Lighter gray color for the date */
            line-height: 1.4; /* Line height for better readability */
            padding-right: 10px;
            font-family: Arial, sans-serif; /* Consistent font family */
        }
        
        

@page {
            size: A4; /* Change from the default size of A4 */
            margin: 0mm; 
            margin-top: 120px;
            padding: 0mm;
            
            
          }
        /* Reduce font size dynamically if the table is too large */
        @media print {
            /* Adjust font size to make table fit within the page */

            .header-container {
                position: running(header);
                top: 0;
                left: 0;
                right: 0;
                width: 795px;
                display: flex;
                margin-top: 0px;
                margin-bottom: 20px;
            }

            .page-number::after {
                content: counter(page);
            }

            .table {
                font-size: 12px; /* Smaller font size for PDF */
            }

            .main-container {
                padding-left: 10px;
                padding-right: 10px;
                
            }

            .table th, .table td {
                padding: 3px; /* Reduce padding */
            }

            /* Reduce image size for PDF generation */
            .graph {
                max-width: 100%; /* Set image width to fit page */
                height: auto;
            }
        }
        
        @page {
            @top-left {
                content: element(header); /* Insert header on top of every page */
            }
        }
        
    </style>

<div class="header-container" id="headerid">
        <!-- Logo image -->
        <div class="header-title">
            <img src="{{ static_base_url }}images/logo.png" alt="Equi Logo" style="height: 100px;">
        </div>
        <!-- Fund name, date, and page number -->
        <div class="header-desc" style="height: 100px;">
            <div class="header" style="height: 100px;">
                <div class="title">
                    <div class="fund-name">{{ meta_data.fund_name }}</div>
                    <div class="date">{{ meta_data.date }}</div>
                </div>
            </div>
            <!-- Page number -->
            <div class="page-number">{{ page_number }}</div>
        </div>
    </div>

Upvotes: 0

Galawa
Galawa

Reputation: 3

Reference : https://www.quackit.com/css/at-rules/css_bottom-center_at-rule.cfm

There are in all 16 positions where u can add content. ( @top-left-corner @top-left @top-center @top-right @top-right-corner@left-top @left-middle @left-bottom @right-top @right-middle @right-bottom @bottom-left-corner @bottom-left @bottom-center @bottom-right @bottom-right-corner )

You can achieve your goal by doing using @top-center and @bottom-center as follows :

@page {{
    @top-center {{
        content: "This is my header";
        font: 10px Arial, sans-serif;
    }}
    @bottom-center {{
        content: "This is my footer";
    }}
}}

If you want to have mutiple lines in your header or footer you will have to add a \A for each line return. THIS WILL NOT WORK WITHOUT "white-space: pre;"

so for example :

@page {{
    @top-center {{
        content: "This is my header 1st line\A This is my header 2nd line";
        font: 10px Arial, sans-serif;
        white-space: pre;
    }}
    @bottom-center {{
        content: "This is my footer 1st line\A This is my footer 2nd line";
    font: 10px Arial, sans-serif;
    white-space: pre;   
    }}
}}

If you want the text to be dynamic you can also include it directly in your Django view as follows :

header_text = "This is my header 1st line\nThis is my header 2nd line"
footer_text = "This is my footer 1st line\nThis is my footer 2nd line"

header_text = header_text.replace("\n","\\A ")
footer_text = footer_text.replace("\n", "\\A ")
dynamic_css = f"""
@page {{
        @top-center {{
            content: "{header_text}";
            font: 10px Arial, sans-serif;
            white-space: pre;
        }}
        @bottom-center {{
            content: "{footer_text}";
        font: 10px Arial, sans-serif;
        white-space: pre;   
        }}
    }}
"""
pdf = HTML(string=html_content).write_pdf(stylesheets=[CSS(string=dynamic_css)])

I hope this will help anyone that was trying to add multiple lines (doing line returns) in the header or footer as well as doing it dynamically.

Upvotes: 0

Didier L
Didier L

Reputation: 20579

I could get this to work simply by using position: fixed for the header and footer.

First, as an element in fixed position does not occupy space on the page, you have to take into account for it by giving appropriate margins on the page, e.g.:

@page {
    margin: 5cm 0 3cm 0;
    size: A4;
}

then simply position the header and footer with respect to this margin:

header, footer {
    position: fixed;
    left: 0;
    right: 0;
}
header {
    /* subtract @page margin */
    top: -5cm;
    height: 5cm
}
footer {
    /* subtract @page margin */
    bottom: -3cm;
    height: 3cm;
}

With this you can just put arbitrary HTML in <header> and <footer> elements and they will be repeated on every page.

Page counters do not seem to work there though, so you will need to implement them based @page rules as described in the other answers.

Upvotes: 6

adonig
adonig

Reputation: 220

Currently running elements are not supported by WeasyPrint. Nevertheless I found a way to achieve the same result using named strings:

 @page {
   @top-center {
     content:  string(title);
   }
 }

 header {
   width: 0;
   height: 0;
   visibility: hidden;
   string-set: title content();
}

Now you can add your content to an invisible HTML header element.

<header>Content of the header goes here</header>

Upvotes: 13

Keith
Keith

Reputation: 699

Since Weasyprint supports CSS Paged Media Module Level 3, simple headers and footers (e.g. pagination, like you mentioned) can be accomplished using CSS:

@page {
    @top-right{
        content: "Page " counter(page) " of " counter(pages);
    }
}

Make sure you include your stylesheets when rendering:

HTML(string=rendered_html,
     base_url=settings.SITE_URL).write_pdf(stylesheets=[CSS(settings.STATIC_ROOT + '/css/pdf_render.css')])

However, getting more complex headers/footers to render can be more.. complex. Some people have suggested the method of including a div element in the header that renders only for print (but I must admit I have only been able to get simple elements to render properly with this method):

@page {
    @top-left {
        content: element(pageHeader);
    }
}
@media print {
    #divHeader{
        position: running(pageHeader);
    }
}

There is also another method using fixed positions, as demonstrated in this gist: https://gist.github.com/pikhovkin/5642563

Upvotes: 32

Related Questions