preface

In general, electronic signature means to load electronic signature on electronic document by technical means. Its function is similar to handwritten signature or official seal on paper contract. Although the legality of electronic signatures has been questioned for many years, they are widely used in enterprise workflow approval, invitations, document security, and other scenarios, including a recent project requiring such a handwritten signature and the generation of PDF files.

Implementation approach

  1. Use canvas to realize the function of handwritten signature, and then convert canvas into a picture and paste it in the position of signature;
  2. Use html2Canvas plug-in to convert the whole DOM area that needs to generate a document into a large image;
  3. Use the JsPDF plug-in to generate PDF documents from the above images.
  4. For the case of more file content, it is necessary to choose a reasonable paging location;

To generate the signature

1. Define canvas canvas in TSX

 <canvas className={styles.canvas} ref={canvasDom} width="350" height="150" />
Copy the code

Note: Canvas width and height must be defined inline because the Canvas tag has its own default width and height of 300px×150px. Its inline style defines width and height as the actual width and height of the painting area (canvas) on which the graphics are drawn. If the width and height are defined in the style outer chain file, the width and height are the height and width of the Canvas rendered in the browser. {width:300px, height:150px} if the Canvas does not define width and height directly or the values are incorrect, the default values are set to {width:300px, height:150px}. So, if you set canvas {width: 200px; height: 200px; }, but does not define the canvas width and height directly on the canvas, then you output canvas.height and canvas.width as 150 and 300 respectively. This means that a 150×300 canvas is rendered in a 200×200 area, so the image will stretch and distort.

2. Define the signature function

const writing = ( beginX: number, beginY: number, stopX: number, stopY: number, ctx: any, ) => { ctx.beginPath(); // Open a new path ctx.globalalpha = 1; // Set the opacity of the image ctx.lineWidth = 3; // Set line width ctx.strokeStyle = 'red'; // Set the path color ctx.moveto (beginX, beginY); Ctx. lineTo(stopX, stopY); // Start with (beginX, beginY); // Define lines from (beginX, beginY) to (stopX, stopY) (this method does not create lines) ctx.closepath (); // Create the path ctx.stroke(); // Actually draw the path defined by the moveTo() and lineTo() methods. The default color is black. };Copy the code

3. Register listening events

let beginX: number, beginY: number; const canvas: HTMLCanvasElement = canvasDom.current; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, canvas.width, canvas.height); canvas.addEventListener('touchstart', function(event: any) { event.preventDefault(); // Prevents the page from scrolling when signing on canvas beginX = event.touches[0]. Clientx-this.offsetleft; beginY = event.touches[0].pageY - this.offsetTop; }); canvas.addEventListener('touchmove', (event: any) => { event.preventDefault(); // Prevents the page from scrolling Event = event.touches[0] when signing on canvas; let stopX = event.clientX - canvas.offsetLeft; let stopY = event.pageY - canvas.offsetTop; writing(beginX, beginY, stopX, stopY, ctx); beginX = stopX; BeginY = stopY; beginY = stopY; beginY = stopY; });Copy the code

Note:

  1. When registering ‘TouchStart’ and ‘TouchMove’ events, you need to block the default events, otherwise the page will slide up and down following the gesture.

  2. ‘Touches [0]’ Every touch object on mobile devices includes the property Touches, which represents a list of all fingers on the screen. To retrieve the current event object, we use event = event.Touches [0], which isn’t necessary on PCS.

  3. The offsetLeft and offsetTop values are not related to the parent element, but to the position elements above it (fixed,relative,absolute). The offset is relative to the body if none of the upper positioned elements has a position other than position:staice.

  4. A few properties of the mobile event object need to be sorted out, ⏬

ClientX /clientY: Touch position x, Y coordinates from the current body viewable area; PageX /pageY: For the entire page, the touch position is x and Y coordinates away from the upper left corner of the body, including the values scrollTop and scrollLeft; ScreenX /screenY: Touch position x, Y distance from the left and top of the display. Therefore, when obtaining the coordinate of the end point, if there is no scroll bar on the current page, there is no difference between clientY and pageY calculation. If the page is long and the scroll bar appears, then pageY must be used to calculate. ClientX is the same, but there are not many horizontal scrolling scenarios on mobile, so use clientX to calculate.

  1. In the process of signing the touchmove, we need to constantly update the starting position, otherwise the drawing will look like this

In fact, this principle is very similar to calculus, a line segment is essentially composed of an infinite number of small line segments, from a macro point of view, line segments can be regarded as a small length of small line segments end to end. So I always think that programming to the end is a test of one’s mathematical ability, intersection set, logical thinking, algorithm and so on can see the figure of mathematics. Finally, the signature is as follows:

Generating PDF documents

Html2canvas is a plug-in that converts HTML codes into Canvas. Therefore, it is necessary to wrap the content area to be printed with a div to obtain this DOM node.

Html2Canvas (dom, {allowTaint: true, width: dom.offsetwidth, // Set the canvas width height: Dom.offsetheight, // Set the canvas height x: 0, // the horizontal scrolling distance y: 0, // the vertical scrolling distance})Copy the code

Note: You need to set width, height and x,y here, otherwise the page content is only one page is fine, but if the page content is many pages, the generated image will only have a small part of the content phenomenon. The problem with this configuration parameter is that if the width and height are not set, only the content of the current viewport is taken by default and anything beyond the current viewport is discarded. Set print parameters:

const print = () => { let dom: HTMLElement = pdfDom.current; Html2Canvas (dom, {allowTaint: true, width: dom.offsetwidth, // Set the canvas width height: Dom.offsetheight, // Set the acquired canvas height x: 0, // the horizontal scrolling distance y: 0, // the vertical scrolling distance}). Then ((canvas: HTMLCanvasElement) => { let canvasWidth = canvas.width; let canvasHeight = canvas.height; Let pageHeight = (canvasWidth / 592.28) * 841.89; ImgWidth = 595.28; // let imgHeight = (592.28 / canvasWidth) * canvasHeight; // let totalHeight = imgHeight; // Let pageData = canvas.toDataURL('image/ PNG ', 1.0); let PDF = new JsPDF('p', 'pt', 'a4', true); if (totalHeight < pageHeight) { // PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight); } else {let top = 0; While (totalHeight > 0) {PDF. AddImage (pageData, 'JPEG', 0, top, imgWidth, imgHeight); // Print totalHeight -= pageHeight; Top - = 841.89; if (totalHeight > 0) { PDF.addPage(); } } } PDF.save('test.pdf'); }); };Copy the code

Select paging location

A PDF document was generated by following the steps above, but there are problems when the PDF pages are many ⏬

As you can see, when I’m paginating, I cut it off from this text here. This is obviously not what we want to see. How can we solve this problem?

  • Fewer pages in a PDF document

When developing tests, you can pre-insert a padding where pages are to be paged

  • Large number of pages in PDF document

In this case, the author tries to traverse the child nodes of the DOM node to be printed, adding the dom node heights that can be printed on each page. If the height exceeds the maximum height that the page can carry, the padding is added to the last node, and the style is restored after printing. Because this method needs to calculate the height of each DOM node, it is very performance consuming, and also requires the granularity of the DOM element of the page to be fine, otherwise there will be a large blank page, completely unable to simulate the effect of Word to generate PDF, so IT will not be discussed.

If the reader has a better liberation plan, welcome to give advice, thanks ~❤️