Generate html to pdf using xhtml2pdf in django project

I have an HTML page containing multiple tables. When converting it to a PDF using xhtml2pdf, it works fine if only a few tables are expanded. However, if I expand more tables, I run into a size limitation where the PDF turns out blank if the content exceeds a certain size(30Mb).

Views.py

def generate_pdf(request):
    template_path = 'app.html'
    context = {
        'data': 'This is dynamic data passed to the template'
    }
    
    pdf_stylesheets = """
    <style>
        @page {
            size: A4 landscape;
            margin: 1.5cm;
        }
        
        .table-wrapper {
            width: 100%;
        }
        
        table {
            width: 100%;
            border-collapse: collapse;
            table-layout: fixed !important;
        }
        
        thead {
            display: table-header-group;
        }
        
        tbody {
            page-break-inside: avoid !important;
            break-inside: avoid !important;
        }
        
        tr {
            page-break-inside: avoid !important;
            break-inside: avoid !important;
        }
        
        td {
            padding: 8px;
            border: 1px solid #ddd;
            font-size: 14px;
            vertical-align: top;
        }
        
        /* Column widths */
        td:nth-child(1) { width: 10%; }
        td:nth-child(2) { width: 15%; }
        td:nth-child(3) { width: 10%; }
        td:nth-child(4) { 
            width: 45%;
            max-width: 45%;
            word-wrap: break-word !important;
            word-break: break-word !important;
            overflow-wrap: break-word !important;
            white-space: pre-wrap !important;
        }
        td:nth-child(5) { width: 20%; }
        
        .message-cell {
            position: relative;
            max-width: 45%;
            word-wrap: break-word !important;
            word-break: break-word !important;
            overflow-wrap: break-word !important;
            white-space: pre-wrap !important;
        }
        
        .message-content {
            display: inline-block;
            width: 100%;
            word-wrap: break-word !important;
            word-break: break-word !important;
            overflow-wrap: break-word !important;
            white-space: pre-wrap !important;
            line-height: 1.4;
        }
        
        th {
            padding: 8px;
            background-color: #355b7a;
            color: white;
            border: 1px solid #ddd;
            font-size: 14px;
        }
        
        @media print {
            table { 
                table-layout: fixed !important;
            }
            
            thead {
                display: table-header-group !important;
            }
            
            tbody {
                page-break-inside: avoid !important;
                break-inside: avoid !important;
                display: block !important;
                min-height: fit-content !important;
            }
            
            tr { 
                page-break-inside: avoid !important;
                break-inside: avoid !important;
            }
            
            td {
                border: 0.5pt solid #ddd !important;
                font-size: 14px !important;
            }
            
            .message-cell, .message-content {
                page-break-inside: avoid !important;
                break-inside: avoid !important;
            }
            
            * {
                -webkit-print-color-adjust: exact !important;
                print-color-adjust: exact !important;
            }
        }
    </style>
    """
    
    html = render_to_string(template_path, context)
    html = pdf_stylesheets + html

    response = HttpResponse(content_type='application/pdf')
    response['Content-Disposition'] = 'attachment; filename="app.pdf"'
    
    pdf_options = {
        'page-size': 'A4',
        'orientation': 'Landscape',
        'margin-top': '1.5cm',
        'margin-right': '1.5cm',
        'margin-bottom': '1.5cm',
        'margin-left': '1.5cm',
        'encoding': 'UTF-8',
        'keep-with-next': 'true',
        'no-split-tables': 'true',
        'enable-smart-shrinking': 'true',
        'print-media-type': 'true'
    }
    
    pisa_status = pisa.CreatePDF(
        html.encode('UTF-8'),
        dest=response,
        encoding='UTF-8',
        show_error_as_pdf=True,
        pdf_options=pdf_options
    )

    if pisa_status.err:
        return HttpResponse('We had some errors <pre>' + html + '</pre>')
 
    return response

html template

{% load static %}
<html>
    <head>
        <meta charset="UTF-8">
        <title>Test Report</title>
    </head>
<style>

    body {
        font-family: Arial, sans-serif;
        margin: 0;
        padding: 0;
        background-color: #f5f5f5;
    }
 
    .container {
        display: flex;
        flex-direction: column;
        align-items: left;
        margin: 0;
        padding: 0;
    }
 
    .header-content {
        width: 100%;
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        margin: 0;
        padding: 0;
    }

    h1 {
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
        font-size: 1.5em;
    }

    .environment-details {
        align-self: flex-start;
        color: black;
        font-size: 0.9em;
    }

    header {
        background: #5ea3db;
        color: white;
        padding-top: 10px;
        min-height: 60px;
        border-bottom: #e8491d 3px solid;
    }
 
    .header-buttons {
        position: absolute;
        top: 2%;
        right: 0;
        transform: translateY(-50%);
        display: flex;
        gap: 3px;
    }

    .header-buttons button {
        border: none;
        border-radius: 7px;
        padding: 5px 10px;
        font-size: 14px;
        transition-duration: 0.4s;
        cursor: pointer;
    }

    .table-container {
        width: 95%;
        margin: 20px auto;
        background: white;
        padding: 15px;
        border-radius: 5px;
        box-shadow: 0 0 10px rgba(0,0,0,0.4);
    }

    .table-wrapper {
        width: auto;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        margin-bottom: 15px;
        page-break-inside: avoid;
        overflow: visible;
    }

    .main_table {
        min-width: 100%;
        margin-bottom: 15px;
    }
 
    .main_table th {
        background-color: #355b7a;
        color: white;
        font-size: 0.9em;
        white-space: nowrap;
        padding: 8px 15px;
        position: sticky;
        top: 0;
    }

    .main_table td {
        padding: 8px 15px;
        text-align: justify;
        font-size: 0.8em;
        white-space: nowrap;
    }
    .main_table td:nth-child(1) {
        width: 40%;
        word-break: break-word;
        white-space: normal;
        overflow-wrap: break-word;
    }
    .main_table td:nth-child(2) { width: 10%; }
    .main_table td:nth-child(3) { width: 15%; }
    .main_table td:nth-child(4) { width: 15%; }
    .main_table td:nth-child(5) { 
        width: 20%;
        word-break: break-word;
        white-space: normal;
        overflow-wrap: break-word;
    }

    .summary_table td {
        padding: 15px;
        font-size: 0.8em;
    }
 
    table {
        border-collapse: collapse;
        background-color: white;
    }

    table, th, td {
        border: 1px solid #ddd;
    }

    tr:nth-child(even) {
        background-color: #f2f2f2;
    }

    .table-wrapper::-webkit-scrollbar {
        height: 8px;
    }
 
    .table-wrapper::-webkit-scrollbar-track {
        background: #f1f1f1;
        border-radius: 4px;
    }
 
    .table-wrapper::-webkit-scrollbar-thumb {
        background: #888;
        border-radius: 4px;
    }
 
    .table-wrapper::-webkit-scrollbar-thumb:hover {
        background: #555;
    }
    /* @media print {
        body { zoom: 0.75; }
    } */
    @media print {
        body {
            margin: 0;
            padding: 0;
        }

        .page-break {
            page-break-before: always;
        }

        table, img {
            page-break-inside: avoid;
        }
    }

</style>
</head>
    <body>
        <header>
            <div class="container">
                <div class="header-content">
                    <h1>TEST REPORT</h1>
                    <div class="environment-details">
                        <b>Project Name:</b> {{ project_name }}<br>
                        <b>Environment:</b> {{environment}}<br>
                        <b>Verification Type:</b> {{VERIFICATION_TYPE}}<br>
                    </div>
                </div>
                <div class="header-buttons">
                <button>
                    {% if log_file_name %}
                    <a href="{{ log_file_name }}" style="text-decoration: none; color: black;" target="_blank">Robot Log</a>
                    {% else %}
                    <span>Robot Log Not Available</span>
                    {% endif %}
                </button>
                <button id="downloadBtn">Download PDF</button>
            </div>
        </div>
    </header>
    <div class="table-container">
        <p style="text-align:center;">This report includes the test case details and individual summary tables for each test case.</p>
        <div class="table-wrapper">
            {{ main_table_html|safe }}
        </div>
        <div>
            {% for table in summary_tables_html %}
            <div class="table-wrapper">
                {{ table|safe }}
            </div>
            {% endfor %}
        </div>
    </div>
</body>

<script>
    const script = document.createElement('script');
    script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js";
    script.onload = function () {
        document.getElementById('downloadBtn').addEventListener('click', function() {
            // Temporarily remove collapsed tables
            document.querySelectorAll('details:not([open])').forEach(detail => {
                detail.style.display = 'none';
            });

            const element = document.body;
            const opt = {
                margin:       [0.2, 0.2, 0.2, 0.2],
                filename:     'test_report.pdf',
                image:        { type: 'jpeg', quality: 0.98 },
                html2canvas:  { scale: 4, scrollX: 0, scrollY: 0, useCORS: true },
                jsPDF:        { unit: 'in', format: 'a4', orientation: 'landscape' }
            };

            // Generate and estimate PDF size before downloading
            html2pdf().from(element).set(opt).toPdf().output('datauristring').then(dataUri => {
                let estimatedSizeKB = (dataUri.length * (3 / 4)) / 1024; // Convert Base64 size to KB
                console.log("PDF Size (KB):", estimatedSizeKB);

                // If the estimated size is too large, show an alert and stop the process
                if (estimatedSizeKB >= 100 && estimatedSizeKB <= 300) {  
                    alert("The generated PDF is too large. Try again with fewer expanded test cases.");
                    
                    // Restore hidden elements after canceling the process
                    document.querySelectorAll('details:not([open])').forEach(detail => {
                        detail.style.display = '';
                    });

                    return; // Stop execution, do NOT generate the PDF
                }

                // If the size is acceptable, proceed with the download
                html2pdf().from(element).set(opt).save().then(() => {
                    // Restore hidden elements after generating the PDF
                    document.querySelectorAll('details').forEach(detail => {
                        detail.style.display = '';
                    });
                });
            });
        });
    };
    document.head.appendChild(script);
</script>

</html>

How can I ensure that large PDFs download correctly?

Вернуться на верх