Pure client and pure server implementation of HTML to PDF

Time:2020-10-4

demand

Users fill in the form and click Save to download the PDF document directly.

Solutions

Server generation

thinking

In 17 years, Google browser developed the chrome headless feature, and at the same time launched the puppeter. It can be understood as a browser without interface but can complete the function characteristics of the server.

So we can start the puppeter browser on the server side, open the target website, and use the conversion function of Chrome browser to convert HTML to PDF.

Server generated core code

First, install puppeter, NPM installation may be wrong, it is best to use cnpm Taobao image installation.

inputcnpm i puppeteer -SInstallation dependencies.

To create a JS file, you just need to open the URL with the puppeter browser and save the PDF.

// html2pdf.js

const puppeteer = require('puppeteer');
(async function(){
    //Start service
    const browser = await puppeteer.launch();
    //Open tab
    const page = await browser.newPage();
    //Go to this address
    await page.goto('https://koa.bootcss.com/#context');
    //HTML page to PDF and save to path
    await page.pdf({path:"test.pdf",format:'A4'})
    //Close browser
    await browser.close();
})();

Then the console inputsnode html2pdf.jsStart the service.

Of course module.export Export the module method according to the business logic.

shortcoming

Unable to save form dynamic data

Since the page is requested from the server, if the user input is not saved on the request address, the truncated PDF will be the initial state of the page not filled in.

In other words, it can only convert static pages, because we need a lot of user input, so pass.

The client generates the core code

thinking

  • Using html2canvas, input the DOM node to be converted, traverse and convert it into canvas
  • Convert canvas to Base64 image, create PDF file with jspdf, and insert the picture into PDF.

shortcoming

Distortion.

We can clearly find that since it is similar to the screenshot of the page and then insert the screenshot into PDF, the resolution and configuration of the page may affect the quality of the output image.

At the same time, because it is a screenshot, it may lose page links and other functions.

Text truncation

When the canvas is larger than one page of PDF, there will be an error in the output. At this time, we need to determine whether the canvas exceeds the A4 size. If the canvas exceeds the A4 size, the canvas is segmented and inserted into different pages.

At this time, the problem comes again. Since the image is segmented, it is likely that the image or text will be truncated from half, because we can’t analyze the structure of item in canvas.

Core code

There are no pictures and links in our requirements, so the problem of distortion has little impact on us. At the same time, our form is composed of multiple repeated items of equal length, and these items are very short and will not exceed a piece of A4 paper (although this is not rigorous, if necessary, you can obtain the width and height of DOM elements and cut them according to the height of DOM elements).

So I’m going to cut the canvas directly according to the item, and each item will be saved in A4 paper.

Before you start, you need to understand several core approaches:

html2canvas

//DOM is the DOM node to convert
    html2canvas(DOM,{
        backgroundColor:"#ffffff",
        width:width,
        height:height,
        scale:2,
        allowTaint:true,
    }).then((canvas)=>{
        //Canvas is the canvas after successful conversion
    })

jsPDF

//Create instance
    let pdf = new jsPDF('','pt','a4');
    //Add the picture to the PDF file
    //The first parameter is the format of the file to be inserted (Base64), and the second is the file format
    //The third and fourth are the coordinates of the upper left corner of the image, and the last two are the width and height of the image after insertion
    pdf.addImage(image,'JPEG',10,10,height,width);
    //Add a new page
    pdf.addPage()
    //Save PDF file
    pdf.save()

canvas

//Canvas is the image to be cut
    //SX, sy are the coordinates to start clipping
    //Swidth and sheight are the width and height of the cut
    //DX and Dy are the coordinates of the cropped image inserted into the canvas
    //Swidth, sheight is the width and height of the cropped image in canvas
    cxt.drawImage(canvas,sx,sy,sWidth,sHeight,dx,dy,sWidth,sHeight);
/**
 *@ Description: form to PDF file
 * @return: pdf
 */
onSubmit(){
    //This is the form I want to convert. There are many forms in it
    let form = this.$refs.form;
    //Gets the width and height of the element
    let width = form.getBoundingClientRect().width;
    let height = form.getBoundingClientRect().height;
    html2canvas(form,{
        backgroundColor:"#ffffff",
        width:width,
        height:height,
        scale:2,
        allowTaint:true,
    }).then((canvas)=>{
        let pdf = new jsPDF('','pt','a4');
        //Cut the picture
        let canvasList = this.splitCanvas(canvas,this.forms.length);

        //Traverse the canvas list and add an image to each page
        canvasList.forEach((item,index)=>{
            //Convert image format to Base64
            let itemImage = item.toDataURL('image/jpeg',1.0);
            //With a margin of 10px, the width of A4 paper is 595px on a 72 resolution display
            pdf.addImage(itemImage,'JPEG',10,10,575.28,575.28/item.width*item.height);
            //If it is not the last page, paginate
            index == this.forms.length-1 ? '' : pdf.addPage();
        })
        //File preservation
        let blob = pdf.output('blob');
        
        pdf.save('test.pdf');
    })
},
/**
 *@ Description: cut canvas
 *@ param {number} num slice number 
 * @param {canvas} canvas 
 *@ return {array} canvas list
 */
splitCanvas(canvas,num){
    let height = canvas.height,width = canvas.width;
    Let chunkheight = height / num; // height of each slice
    Let chunklist = []; // store the result canvas
    for(let i=0; i<height ; i+=chunkHeight){
        //Initialize clipping rectangle position
        let sx = 0,sy = i,sWidth = width,sHeight = chunkHeight,dx = 0, dy = 0;
        //Create a canvas node
        let canvasItem =document.createElement("canvas");
        //Initialize canvas size
        canvasItem.height = chunkHeight;
        canvasItem.width = width;
        let cxt = canvasItem.getContext("2d");
        //Put the cropped image on the new canvas node
        cxt.drawImage(canvas,sx,sy,sWidth,sHeight,dx,dy,sWidth,sHeight);
        chunkList.push(canvasItem); 
    }
    return chunkList;
},

Final effect

Page after the form is saved

Effect of converting to PDF

This article on the HTML to PDF pure client and pure server-side implementation of the article introduced here, more related HTML to PDF content please search the previous articles or continue to browse the related articles below, I hope you can support developeppaer more in the future!