Front-end partial printing (pdf pagination)

Technology: htmlcanvas and jspdf

Achievable effect: no cutting of pdf

Rendering:

<template>
    <div style="background: #f5f5f5;">
        <div class="flex-end button-wrap">
            <el-button
                :disabled="loading"
                @click="handlePrint">
                Print
            </el-button>
        </div>
        <div
            v-loading.body.fullscreen="loading"
            class="printForm"
        >
            <div id="form-main">
                <div style="margin-top:32px" />
                <template v-for="item in 25">
                    <div
                        :key="item"
                        class="item">
                        <div
                            class="line"
                            :style="{'min-height':item %2 ?'160px' : '40px'}">
                            <span style="color:red">{<!-- -->{ item }}</span>
                            <span> {<!-- -->{ deptLeaderSignTime }}{<!-- -->{ deptLeaderSignTime }}</span>
                            <div
                                v-if="item==11"
                                style="min-height:800px">
                                {<!-- -->{ deptLeaderSignTime }}{<!-- -->{ deptLeaderSignTime }}
                            </div>
                        </div>
                    </div>
                </template>
            </div>
        </div>
    </div>
</template>

<script>
import zhengli from "./zhengli.js";
export default {
    mixins: [zhengli],
    data() {
        return {
            isPrint: false,
            loading: false,
            deptLeaderSignTime:'The Guangdong Provincial Department of Industry and Information Technology issued the document form The Guangdong Provincial Department of Industry and Information Technology issued the document form The Guangdong Provincial Department of Industry and Information Technology issued the document form The Guangdong Provincial Department of Industry and Information Technology issued the document form'
        };
    },
    computed: {},
    async created() {
        this.loading = true
        this.$nextTick(async()=>{
            this.convertPdf("Print form", document.querySelector("#form-main"), false);
        });
    },
    async mounted() {
        window.addEventListener("afterprint", e => {
            location.reload();
        });
        window.addEventListener("message", (e) => {
            this.isPrint = false
            this.$forceUpdate()
        });

    },
    beforeDestroy() {
        window.removeEventListener("afterprint",'');
    },
    methods: {
    }
};
</script>
<style scoped>

.flex-end {
  display: flex;
  justify-content: flex-end;
}
.button-wrap {
    height: 60px;
    box-sizing: border-box;
    padding: 8px 24px;
    background: #fff;
}
#form-main {
    width: 1200px;
    margin: 0 auto;
    padding: 24px;
    box-sizing: border-box;
    position: relative;
    font-size: 16px;
    line-height: 24px;
}
.printForm {
    background: #ffff;
    margin: 16px 24px;
    height:calc(100vh - 100px);
    overflow:auto
}
.line {
    padding: 10px;
    border: 1px solid #000;
}

.item + .item .line{
    border-top: 0px solid transparent;
}
</style>
// zhengli.js

import html2Canvas from 'html2canvas';
import JsPDF from 'jspdf';
const zhengli = {
    data() {
        return {
            pdfFile: null,
            canvasHeight: 0,
            imgHeight:0,
            setPadingIndex: [], //Due to paging, the serial number of the padding node needs to be increased. In order to change the padding back later,

        };
    },
    methods: {
        async convertPdf(title, html, isBlob) {
            // 2400 is half the width of the html form that needs to be printed (I set 1200px here, which is worth modifying according to the specific situation), because html2Canvas doubles the resolution
            this.imgHeight = Math.floor((280 * 2400) / 180) - 30
            // res: The distance between the node of the last page and the top is recorded here. You only need to change the length of the array to determine whether you need to add a new page of pdf.
            let res = []
            if( this.isPrint ) {
                res =this.setNodeStyle( )
            }
            return new Promise((resolve) => {
                html2Canvas(html, {
                    //allowTaint: whether to allow images to cross domains
                    allowTaint: false,
                    tainTest: false,
                    logging: false,
                    //useCORS: Whether to try to use cors from the server to load images
                    useCORS: true,
                    windowWidth: '820px',
                    // dpi: Increase the resolution to a specific DPI
                    dpi: window.devicePixelRatio * 2,
                    scale: 2 // Increase resolution proportionally
                }).then((canvas) => {
                    this.pdfFile = new JsPDF('p', 'mm', 'a4'); // A4 paper, portrait
                    const ctx = canvas.getContext('2d');
                    const a4w = 180;
                    const a4h = 280;
                    // imgHeight: Convert the pixel height of a page image according to the A4 display ratio
                    const imgHeight = this.imgHeight;
                    let top = canvas.height
                    let renderedHeight = 0;
                    res.forEach((item,index) =>{
                        const page = document.createElement('canvas');
                        page.width = canvas.width;
                        page.height = Math.min(imgHeight, top - renderedHeight);
                        //Use getImageData to crop the specified area and draw it into the canvas object created previously
                        let imageData = ctx.getImageData(
                            0,
                            renderedHeight,
                            canvas.width,
                            Math.min(imgHeight, top - renderedHeight)
                        )
                        for(var i = 0; i < imageData.data.length; i + = 4) {
                            // When the pixel is transparent, set it to white
                            if(imageData.data[i + 3] == 0) {
                                imageData.data[i] = 255;
                                imageData.data[i + 1] = 255;
                                imageData.data[i + 2] = 255;
                                imageData.data[i + 3] = 255;
                            }
                        }
                        page.getContext('2d').putImageData(
                            imageData,
                            0,
                            0,
                        );
                        /* addImage:
                            1. Image: Indicates the image resource to be inserted, which can be the path of an image file or a base64 encoded string.
                            2. format: Indicates the image format to be inserted, including: JPEG’, PNG’, GIF’, BMP’, TIFF’, RAW’, JPEG2000’.
                            3. x: The x-axis coordinate of the image in PDF, in pt (point).
                            4. y: The y-axis coordinate of the image in PDF, in pt (point).
                            5. Width: The width of the image in PDF, in pt (points).
                            6. Height: The height of the image in PDF, in pt (points).
                            7. alias (optional): Specify the alias of the image resource.
                            8. Compression (optional): Specify the compression quality of the image, the value is a floating point number between 0-1.
                            9. Rotation (optional): Specify the rotation angle of the image, the value range is an integer between 0-360.
                        */
                        this.pdfFile.addImage(
                            page.toDataURL('image/jpeg', 1),
                            'JPEG',
                            15,
                            10,
                            a4w,
                            Math.min(a4h, (a4w * page.height) / page.width)
                        );
                        renderedHeight + = imgHeight
                        if(res[index + 1]) {
                            this.pdfFile.addPage(); // If there is content later, add an empty page
                        }
                    })

                    // save document
                    if (isBlob) {
                        this.pdfFile.save(`${title}.pdf`);
                    }
                    // Get base64
                    const pdfData = this.pdfFile.output('datauristring');
                    this.loading = false
                    resolve(pdfData);
                });
            });
        },
        //Set the border and padding of the paging place
        setNodeStyle() {
            let height = 0
            let data = []
            let count = 1
            // element: items is the style name of each row, as a non-cuttable part.
            let element = document.getElementsByClassName("item")
            this.setPadingIndex = []
            for(let i = 0; i<element.length;i + + ) {
                height = (element[i].offsetTop + element[i].offsetHeight)*2
                if( height > this.imgHeight*count) {
                    let bott = (this.imgHeight*count -(element[i-1].offsetTop + element[i-1].offsetHeight)*2)/2
                    element[i-1].style.paddingBottom = bott + 15 + 'px'
                    //Here you need to set the places where you need to add borders according to your own style.
                    element[i].style.borderTop= '1px solid #000'
                    // setPadingIndex: This is recorded so that the form style can be restored in time after the print pop-up box appears.
                    this.setPadingIndex.push(i-1)
                    data.push((element[i-1].offsetTop + element[i-1].offsetHeight)*2)
                    count + =1
                    if( i == element.length-1 ) {
                        data.push((element[i].offsetTop + element[i].offsetHeight)*2)
                    }
                } else if( i == element.length-1 ) {
                    data.push((element[i].offsetTop + element[i].offsetHeight)*2)
                }
            }
            return data

        },
        async handlePrint() {
            this.isPrint = true
            this.loading = true
            this.$nextTick( async()=>{
                this.pdfFile = null
                // #form-main is the html area that needs to be printed
                await this.convertPdf("Print form", document.querySelector("#form-main"), false)
                let pdfFlie = this.pdfFile.output('datauristring');
                let printFile = this.dataURLtoFile(pdfFlie, 'pdf file.pdf');
                this.pdfUrl = window.URL.createObjectURL(new Blob([printFile], { type: 'application/pdf' }));
                var date = new Date().getTime();
                var ifr = document.createElement('iframe');
                ifr.style.frameborder = 'no';
                ifr.style.display = 'none';
                ifr.style.pageBreakBefore = 'always';
                ifr.setAttribute('id', 'printPdf' + date);
                ifr.setAttribute('name', 'printPdf' + date);
                ifr.src = this.pdfUrl;
                document.body.appendChild(ifr);
                this.loading = false
                //Print pop-up window
                this.doPrint('printPdf' + date);
                // Release the URL object
                window.URL.revokeObjectURL(ifr.src);
                setTimeout(()=>{
                    //Restore the paging position back
                    let element = document.getElementsByClassName("item")
                    for(let i = 0; i<element.length;i + + ) {
                        if(this.setPadingIndex.includes(i)) {
                            element[i].style.paddingBottom = 0
                            element[i + 1].style.borderTop= '0px solid #000'
                        }
                    }
                    this.isPrint = false
                    this.$forceUpdate()
                },1000)
            })
        },
        doPrint(val) {
            var ordonnance = document.getElementById(val).contentWindow;
            setTimeout(() => {
                this.isPrint = true
                ordonnance.print();
                this.pdfLoading = false;
                while(true) {
                    // Determine and find the parent window you are looking for. This can be achieved by binding attributes on the parent window.
                    if (ordonnance.isParent = true) {
                        ordonnance.postMessage('isPrint', "*")
                    }
                    if(ordonnance == window.top) {
                        break; // Prevent infinite loop
                    } else {
                        ordonnance = ordonnance.parent;
                    }
                }

            }, 100);
        },
        // Convert to file stream
        dataURLtoFile(dataurl, filename) {
            var arr = dataurl.split(','),
                mime = arr[0].match(/:(.*?);/)[1],
                bstr = atob(arr[1]),
                n = bstr.length,
                u8arr = new Uint8Array(n);
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n);
            }

            return new File([u8arr], filename, { type: mime });
        },

    }
};
export default zhengli;