A4 page height is not considered when generating dynamic page number using javascript during print event

96 Views Asked by At

I'm generating A4 invoice using HTML, CSS and JS. The invoice is getting generated as expected during print preview. However page number is not aligned and extra empty pages are generated automatically.

Here is my code snippet:

<html>

<head>
    <script>function setPrintStyles(pagesize, standardSize) {
            var documentHeight = standardSize ? '' : (document.getElementsByTagName('html')[0].offsetHeight / 2) + 100 + 'px';
            var bodySize = standardSize ? '' : 'body { width: ' + pagesize + '; }';
            var css = `@media print { @page { size: ${pagesize} ${documentHeight}; } ${bodySize} }`,
                head = document.head || document.getElementsByTagName('head')[0],
                style = document.createElement('style');
            head.appendChild(style);
            style.type = 'text/css';
            style.appendChild(document.createTextNode(css));
        }</script>
    <style type="text/css">
        @media print {
            #section-to-print {
                position: absolute;
                left: 0;
                top: 0;
            }

            body {
                font-family: Calibri, monospace;
                font-size: 0.8rem;
                margin: 1rem;
                border: 1px solid;
            }

            body ol {
                list-style: none;
                padding-left: 0;
                margin: 0.5rem 0;
            }

            table {
                width: 100%;
                height: 100%;
                border-collapse: collapse;
                page-break-inside: auto;
            }

            td section div {
                page-break-inside: avoid;
            }

            thead {
                display: table-header-group;
            }

            tfoot {
                display: table-footer-group;
            }

            header {
                display: flex;
                flex-direction: column;
                font-weight: normal;
            }

            #brand {
                border-bottom: 1px solid;
                padding: 0.25rem 0.5rem;
                text-transform: uppercase;
                font-weight: bold;
            }

            #invoicedetails {
                display: grid;
                grid: "shop customer invoice";
                grid-template-columns: repeat(3, 1fr);
                padding: 0 0.5rem;
                border-bottom: 1px solid;
            }

            #shop {
                text-align: left;
            }

            #customer {
                border: 1px solid;
                border-top: 0;
                border-bottom: 0;
                padding-left: 0.5rem;
                text-align: center;
            }

            #invoice {
                text-align: right;
            }

            table tbody td {
                height: 100%;
                display: flex;
                flex-direction: column;
                justify-content: space-between;
            }

            main {
                display: block;
                height: 100%;
            }

            #invoiceitems {
                display: block;
                height: 100%;
                text-transform: uppercase;
            }

            #itemscontent {
                height: 100%;
                display: grid;
                grid: "sno isbnno title price quantity amount";
                /*Below line is added as inline style in A4 invoice component*/
                /*grid-template-rows: repeat(2, minmax(1rem, max-content)) auto repeat(2, minmax(1rem, max-content));*/
                /*grid-template-columns: minmax(min-content, max-content) minmax(min-content, auto) max-content max-content max-content max-content max-content max-content;*/
                grid-auto-rows: minmax(1rem, max-content);
                grid-column-gap: 1px;
                align-items: center;
            }

            #itemscontent>div {
                display: flex;
                align-items: center;
                border: 1px solid;
                height: 100%;
                border-top: 0;
                border-right: 0;
                padding: 0 0.25rem;
            }

            .border-left-0 {
                border-left: 0 !important;
            }

            .border-bottom-0 {
                border-bottom: 0 !important;
            }

            .span-4 {
                grid-column-start: span 4;
            }

            .flex-end {
                justify-content: flex-end;
            }

            footer {
                display: flex;
                flex-direction: column;
            }

            #itemsfooter {
                display: flex;
                justify-content: space-between;
                align-items: center;
                text-transform: uppercase;
                border-top: 1px solid;
            }

            #itemsfooter>div {
                align-self: stretch;
                display: flex;
                align-items: center;
                padding: 0 0.5rem;
            }

            #amountinwords {
                flex: 75%;
            }

            #totalamount {
                flex: 25%;
                text-align: right;
                border-left: 1px solid black;
            }

            #totalamount ol {
                width: 100%;
            }

            #totalamount ol li:last-child {
                font-size: x-large;
            }

            #invoiceinformation {
                display: flex;
                justify-content: end;
                padding: 0 0.5rem;
                border-top: 1px solid;
                border-bottom: 1px solid;
            }

            #signature {
                padding-left: 0.5rem;
            }

            #message {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 0 0.5rem;
            }
        }
    </style>
    <style type="text/css">
        @media print {
            @page {
                size: A4;
            }
        }
    </style>
    <style>
        html {
            height: 297mm !important;
            overflow-y: scroll;
        }
    </style>
</head>

<body><!--!-->
    <table>
        <thead>
            <tr>
                <th>
                    <header>
                        <section id="brand"></section><!--!-->
                        <section id="invoicedetails">
                            <section id="shop">
                                <ol>
                                    <li>From</li><!--!-->
                                    <li><b>ABC</b></li><!--!-->
                                    <li>Address:</li><!--!-->
                                    <li>Phone:</li><!--!-->
                                    <li>Mobile:</li>
                                </ol>
                            </section><!--!-->
                            <section id="customer">
                                <ol>
                                    <li>To</li><!--!-->
                                    <li><b>DEF </b></li><!--!-->
                                    <li>Mobile: </li>
                                </ol>
                            </section><!--!-->
                            <section id="invoice">
                                <ol id="invoice-details"><!--!-->
                                    <li>Invoice Details</li>
                                    <li>Invoice No: <b>18/2023-24</b></li><!--!-->
                                    <li>Invoice Date: <b>17 Nov 2023</b></li><!--!-->
                                    <li>Payment Mode: <b>Cash</b></li><!--!-->
                                    <li>Payment Status: Paid</li><!--!-->
                                    <li id="page-number"><br/></li>
                                </ol>
                            </section>
                        </section>
                    </header>
                </th>
            </tr>
        </thead><!--!-->
        <tbody>
            <tr>
                <td>
                    <main>
                        <section id="invoiceitems">
                            <section id="itemscontent"
                                style="grid-template-rows: repeat(5, minmax(1rem, max-content)) auto repeat(2, minmax(1rem, max-content)); grid-template-columns: minmax(min-content, max-content) minmax(min-content, max-content) minmax(min-content, auto) max-content max-content max-content">
                                <!--!-->
                                <div class="border-left-0">S.No</div>
                                <!--!-->
                                <div>ISBN No</div>
                                <!--!-->
                                <div>Title</div>
                                <!--!-->
                                <div>Price</div>
                                <!--!-->
                                <div>Quantity</div>
                                <!--!-->
                                <div>Amount</div>
                                <div class="border-left-0 border-bottom-0">1</div><!--!-->
                                <div class="border-bottom-0"></div><!--!-->
                                <div class="border-bottom-0">Another Test</div><!--!-->
                                <div class="flex-end border-bottom-0">10.00</div><!--!-->
                                <div class="flex-end border-bottom-0">1</div><!--!-->
                                <div class="flex-end border-bottom-0">10.00</div>
                                <div class="border-left-0 border-bottom-0">2</div><!--!-->
                                <div class="border-bottom-0"></div><!--!-->
                                <div class="border-bottom-0">Another Test</div><!--!-->
                                <div class="flex-end border-bottom-0">10.00</div><!--!-->
                                <div class="flex-end border-bottom-0">1</div><!--!-->
                                <div class="flex-end border-bottom-0">10.00</div>
                                <div class="border-left-0 border-bottom-0">3</div><!--!-->
                                <div class="border-bottom-0"></div><!--!-->
                                <div class="border-bottom-0">Another Test</div><!--!-->
                                <div class="flex-end border-bottom-0">10.00</div><!--!-->
                                <div class="flex-end border-bottom-0">1</div><!--!-->
                                <div class="flex-end border-bottom-0">10.00</div>
                                <div class="border-left-0 border-bottom-0">4</div><!--!-->
                                <div class="border-bottom-0"></div><!--!-->
                                <div class="border-bottom-0">Another Test</div><!--!-->
                                <div class="flex-end border-bottom-0">10.00</div><!--!-->
                                <div class="flex-end border-bottom-0">1</div><!--!-->
                                <div class="flex-end border-bottom-0">10.00</div><!--!-->
                                <div class="border-left-0"></div>
                                <div></div>
                                <div></div>
                                <div></div>
                                <div></div>
                                <div></div>

                                <!--!-->
                                <div class="border-left-0 flex-end span-4">Sub Total</div>
                                <div class="flex-end">4</div><!--!-->
                                <div class="flex-end">40.00</div>
                                <div class="border-left-0 flex-end span-4">Discount (10.00%)</div><!--!-->
                                <div></div>
                                <div class="flex-end">-3.00</div>
                            </section>
                        </section>
                    </main><!--!-->
                    <footer>
                        <section id="itemsfooter">
                            <div id="amountinwords">
                                <ol>
                                    <li><small>Total Amount in Words (INR)</small></li><!--!-->
                                    <li><b>thirty seven ONLY</b></li>
                                </ol>
                            </div><!--!-->
                            <div id="totalamount">
                                <ol><!--!-->
                                    <li><small>Total Amount</small></li>
                                    <li><b>₹37.00</b></li>
                                </ol>
                            </div>
                        </section><!--!-->
                        <section id="invoiceinformation">
                            <div id="signature"><text>for <!--!--><br><br><br>Authorized
                                    Signatory</text></div>
                        </section><!--!-->
                        <!--!-->
                        <section id="message">
                            <div><small>THANK YOU FOR THE BUSINESS</small></div>
                            <div><small>THIS IS A COMPUTER GENERATED INVOICE</small></div>
                        </section>
                    </footer>
                </td>
            </tr>
        </tbody>
    </table>
    <div id="dpi" style="height: 1in; left: -100%; position: absolute; top: -100%; width: 1in;"></div>
    <script>window.print();</script>
    <script>window.onbeforeprint = addPageNumbers;
        window.onafterprint = removePageNumbers;
        function removePageNumbers() {
            document.querySelectorAll('.dynamic-page-number').forEach(function (li) {
                li.remove();
            });
        }
        function addPageNumbers() {
            var dpi = document.getElementById("dpi").offsetHeight;
            console.log(dpi);
            var sheetHeightInMM = 297;
            var mmPerInch = 25.4;
            var pixels = (sheetHeightInMM * dpi) / mmPerInch;
            var bodyHeight = document.getElementsByTagName('body')[0].scrollHeight;
            console.log(bodyHeight);
            var totalPages = Math.ceil(bodyHeight / pixels);
            console.log(totalPages);
            for (var i = 1; i <= totalPages; i++) {
                var pageNumberLi = document.createElement("li");
                var pageNumber = document.createTextNode("Page " + i + " of " + totalPages);
                pageNumberLi.classList.add('dynamic-page-number');
                pageNumberLi.style.paddingTop = '5px'; // added to adjust spacing between previous li element in dom
                pageNumberLi.style.position = "absolute";
                var offsetTop = document.getElementById("page-number").offsetTop + "px";
                console.log(offsetTop);
                pageNumberLi.style.top = `calc((${i - 1} * ${sheetHeightInMM}mm) + ${offsetTop} - ${i * 1}rem)`;
                pageNumberLi.style.right = '1.5rem'; // 1rem body right margin + 0.5rem header right padding
                pageNumberLi.appendChild(pageNumber);
                document.getElementById("invoice-details").appendChild(pageNumberLi);
            }
        }</script>
</body>

</html>

Issue I'm facing:

the above code will generate single page A4 invoice. But this generates two pages in print preview. The page number can also be seen in invoice.

Page 1:

enter image description here

Page 2:

enter image description here

Script used for page number generation:

function addPageNumbers() {
    var dpi = document.getElementById("dpi").offsetHeight;
    console.log(dpi);
    var sheetHeightInMM = 297;
    var mmPerInch = 25.4;
    var pixels = (sheetHeightInMM * dpi) / mmPerInch;
    var bodyHeight = document.getElementsByTagName('body')[0].scrollHeight;
    console.log(bodyHeight);
    var totalPages = Math.ceil(bodyHeight / pixels);
    console.log(totalPages);
    for (var i = 1; i <= totalPages; i++) {
        var pageNumberLi = document.createElement("li");
        var pageNumber = document.createTextNode("Page " + i + " of " + totalPages);
        pageNumberLi.classList.add('dynamic-page-number');
        pageNumberLi.style.paddingTop = '5px'; // added to adjust spacing between previous li element in dom
        pageNumberLi.style.position = "absolute";
        var offsetTop = document.getElementById("page-number").offsetTop + "px";
        console.log(offsetTop);
        pageNumberLi.style.top = `calc((${i - 1} * ${sheetHeightInMM}mm) + ${offsetTop} - ${i * 1}rem)`;
        pageNumberLi.style.right = '1.5rem'; // 1rem body right margin + 0.5rem header right padding
        pageNumberLi.appendChild(pageNumber);
        document.getElementById("invoice-details").appendChild(pageNumberLi);
    }
}

Expected Behavior: Page should be generated properly considering the height set in html which is 297mm (height of A4 page).

Please try saving the code snippet as html file and open in browser. The above code snippet execution gives different output.

2

There are 2 best solutions below

0
On BEST ANSWER

I fixed this my simply removing the @media print from CSS. The reason I can see is,

When @media print is present, then in normal screen media, there are no styles and it increases the height of the page as shown below. You can see the scroll bars because of incorrect height.

Image with print media present

This produces the incorrect page numbers as shown below. You can see two pages.

enter image description here

When @media print is not present, then in normal screen media, there are styles applied and it prodices the correct height of the page as shown below. You cannot see the scroll bars because of correct height.

Image with print media not present

This produces the correct page numbers as shown below. You can see one page.

enter image description here

0
On

To take full control of the page's dimensions; as in the case of accommodating an invoice, we need to specify some more CSS as per below...

body {

 position:absolute; left:0; top:0; width:95%; height:95%; /* I added this line of CSS */

 font-family: Calibri, monospace;
 font-size: 0.8rem;
 margin: 1rem;
 border: 1px solid;

}

This yields a near perfect A4 invoice nicely centered on a single page for me.

I'd post an image of my output here if I knew how, but alas...

I haven't looked at correcting your custom page numbering being in the wrong place, but I see no need for page numbering given that the printing options provide for that and they go in the right place so what's the point of re-doing what's already there?

I can’t remember how right now, but I’m pretty sure you can send a page numbering request programmatically.