Introduction to the

Before, I had a request for front-end PDF guide. At that time, I googled a little and used JSPDF + html2Canvas to export PDF. I encountered a lot of problems and wrote them here

Problems encountered

  • Export picture error, Baidu said it was a cross – domain problem
  • The PDF background of the guide is black
  • PDF fuzzy
  • The exported PDF has no header footer
  • The exported PDF table is truncated in the paginated area

1. The exported image has a cross-domain problem

Baidu has a lot of solutions, but I tried none of them worked for me… As there were not many images to export, I finally chose to download the images locally and transfer them to the front-end by base64 encoding, although it was a bit troublesome and no other solutions worked and I was desperate. The PHP code looks like this

PHP code: image link base64

    function downImgToBase64($images_root_dir) {
        // Set the image storage directory
    	$image_append_dir = 'templates/tempImg/' . rand(1.999999). '/'; // The first half of the image store directory
    	defined("ROOT_PATH") || define("ROOT_PATH", preg_replace('/webapp(.*)/'.' ', str_replace('\ \'.'/'.__FILE__)));
    	$images_root_dir = ROOT_PATH . $image_append_dir; // The absolute path to store the image may not be the same for everyone
    
    	if(! file_exists($images_root_dir)) { mkdir($images_root_dir,0777.true);
            chmod($images_root_dir, 0777);
    	}
    	
    	$imgUrl = 'https://www.baidu.com/img/bd_logo1.png'; // Test diagram
    	$imgPath = substr($imgUrl,  - 13.8); // Download the image from the file name
    	$path = curl_file_get_contents($imgUrl, $images_root_dir . $imgPath); // The image is downloaded locally to return to the storage path
    	$base64Img = $gg->base64EncodeImage($path); // Convert the image to base64 encoding
    	unlink($path);
    	return $base64Img;
	}
	
	// Download image guide local $url: image link $path: image storage path
	function curl_file_get_contents($url,$path)
	{
		$ch = curl_init();
		curl_setopt($ch,CURLOPT_URL,$url);
		curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
		curl_setopt($ch, CURLOPT_TIMEOUT, 60); // The maximum number of seconds allowed to execute
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); // The time to wait before initiating a connection. If set to 0, wait indefinitely
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);// The information obtained by curl_exec() is returned as a string rather than printed directly.
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // This is the key, circumvent SSL certificate check.
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // Skip host validation
		curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla / 4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident / 4.0; The.net CLR 2.0.50727)");
		$res  = curl_exec($ch);

		$error = curl_error($ch);
		$info = curl_getinfo($ch);
		curl_close($ch);


		if ($info['http_code'] != 200) {
			throw new \Exception($url . ' http code: ' . $info['http_code'] . $error);
		}
		$fp = fopen($path,'wb');
		fwrite($fp, $res);
		fclose($fp);
		return $path;
	}
	
	// Base64 $file is the local path for storing images
	function base64EncodeImage ($file) {
		$type = getimagesize( $file ); // Get the image size, type, etc
		if ($type === false) {
			$error = error_get_last();
			throw new \Exception($error['message']);
		}
		$img_type = $type['mime'];
		$file_content = base64_encode( file_get_contents( $file ) );
		$img = 'data:image/' . $img_type . '; base64,' . $file_content; // Base64 encoding of composite images
		return $img;
	}
Copy the code

The PDF background of the guide is black

  • Html2Canvas you can set parameters therebackground: '#FFFFFF'; backgroundColor: null;See my last htmltopdf.js function
  • Set the exported DOM background to white my vue code:<tbody id="pdfDomId" style="background: #FFFFFF"></tbody>

PDF fuzzy

This is another problem encountered by my friend. My requirement for clarity is not high. The solution is to set the scale in htmltopdf.js to 2. The larger the scale, the clearer the PDF will be, but at the same time, the PDF file size will be larger

The exported PDF has no header footer

Main reference document is zhuanlan.zhihu.com/p/35753622 basic logic is calculated in the paging add a div element, style custom content. $nextTick(() => {getPdf(id, title)}} {this.$nextTick(() => {getPdf(id, title)}}}} Finally, put the export PDF function into window.onload. window.onload = () => { getPdf(id, title) }

The exported PDF table is truncated in the paginated area

Also reference zhuanlan.zhihu.com/p/35753622 node objects of every level of child elements are treated as a whole cannot be divided, traverse these child elements, if an element from the height of the body is pageH multiples of x, When adding the height of the element itself, it is a multiple of pageH x+1, which means that the element will not fit on page X +1, so it needs to be placed on page x+1, using marginTop to set a white area. My page layout vue code is:

<tbody id="goodsCraftPdfDom" style="background: #FFFFFF">
    <el-card :body-style="{ padding: '0px' }">
      {{ childNodes1... }}
    </el-card>
    
    <el-card :body-style="{ padding: '0px' }">
      {{ childNodes2... }}
    </el-card>
</tbody>
Copy the code

Code htmlToPdf. Js

// The following two packages need to be installed separately
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'

/** * This function can export the PDF can be basically perfect pagination, and solve the text/table truncation problem ** 1: * Note2: The function to download the PDF with images should be performed in windot.onload (). * @param id: id of the dom to be downloaded * @param title: Download the PDF file name */
export default {
  install(Vue, options) {
    Vue.prototype.getPdf = function (id, title) {
      const WGUTTER = 15  // Horizontal margins
      let deleteNullPage = 0 // This is a special logic I added to the canvas. I don't know why the height of the generated canvas will be higher than the current height, so there will be an extra blank page.
      const SIZE = [595.28.841.89]  / / a4 width is high
      let node = document.querySelector(` #${id}`)
      let nodeH = node.clientHeight
      let nodeW = node.clientWidth
      const pageH = nodeW / (SIZE[0] - 2 * WGUTTER) * SIZE[1]
      let modules = node.childNodes
      let pageFooterH = 10  // 10 is the height at the end of the page
      this.addPageHeader(node, node.childNodes[0]);  // Add the header
      // console.log(node.clientHeight, node.clientWidth, pageH)
      for (let i = 0, len = modules.length; i < len; i++) {
        len = modules.length // Modules.length changes when the header is added to the footer
        let item = modules[i]
        if (typeof item.clientHeight === "undefined") { // Filter empty elements
          continue
        }
        // The height of the div from the body is a multiple of pageH x, but adding its own height is a multiple of pageH x+1
        let beforeH = item.offsetTop + pageFooterH
        let afterH = item.offsetTop + item.clientHeight + pageFooterH
        let currentPage = parseInt(beforeH / pageH)
        // console.log(pageH, item.offsetTop, item.clientHeight, currentPage, parseInt(afterH / pageH), item)
        if(currentPage ! = =parseInt(afterH / pageH)) {
          let diff = pageH - item.offsetTop % pageH - pageFooterH
          // console.log(pageH, item.offsetTop, item.clientHeight, lastItemAftarH, diff)
          // console.log(modules[j - 1].offsetTop, modules[j - 1].clientHeight)
          / / footer
          this.addPageFooter(node, item, currentPage + 1, diff)
          / / add the header
          this.addPageHeader(node, item)
        }
        if (i === modules.length - 1) { // Modules.length changes when headers and footers are added
          let diff = pageH - afterH % pageH
          deleteNullPage = diff + pageFooterH // This is a special logic that I added completely. I don't know why the height of the generated canvas will be higher than the current height, so there will be an extra blank page
          // console.log(pageH, afterH, diff)
          / / footer
          this.addPageFooter(node, item, currentPage + 1, diff, true)}}let obj = document.querySelector(` #${id}`)
      let width = obj.clientWidth
      let height = obj.clientHeight
      let canvasBox = document.createElement('canvas')
      let scale = window.devicePixelRatio
      let rect = obj.getBoundingClientRect()
      canvasBox.width = width * scale
      canvasBox.height = height * scale

      canvasBox.style.width = width + 'px'
      canvasBox.style.height = height + 'px'
      canvasBox.getContext('2d').scale(scale, scale)
      canvasBox.getContext('2d').translate(-rect.left, -rect.top)

      // const WGUTTER = 10 // Horizontal margin
      html2Canvas(document.querySelector(` #${id}`), {
        backgroundColor: null.background: '#FFFFFF'.useCORS: true.// Depending on the situation,
        scale: scale,
        canvas: canvasBox,
        crossOrigin: 'Anonymous'
      }).then(function (canvas) {
        let pdf = new JsPDF(' '.'pt'.'a4'.true)    // A4 paper, longitudinal
        let ctx = canvas.getContext('2d')
        let a4w = SIZE[0] - 2 * WGUTTER
        let a4h = SIZE[1]    // Size A4, 210mm x 297mm, 10mm margin on each side, display area 190x297
        let imgHeight = pageH    // Convert the pixel height of a page to A4 display scale
        let renderedHeight = 0

        let page
        while (renderedHeight < canvas.height + 1) { // This is -1 because sometimes
          page = document.createElement('canvas')
          page.width = canvas.width
          page.height = Math.min(imgHeight, canvas.height - renderedHeight - deleteNullPage) // It may be less than one page

          // Use getImageData to crop the specified area and draw it into the canvas object created earlier
          page.getContext('2d').putImageData(ctx.getImageData(0, renderedHeight, canvas.width, Math.min(imgHeight, canvas.height - renderedHeight)), 0.0)
          pdf.addImage(page.toDataURL('image/jpeg'.1.0), 'JPEG', WGUTTER, 0, a4w, a4w * page.height / page.width)    // Add images to the page
          // console.log(page.height, page.width, Math.min(a4h, a4w * page.height / page.width))

          renderedHeight += imgHeight
          console.log(renderedHeight, imgHeight, canvas.height, deleteNullPage)
          if (renderedHeight < canvas.height - deleteNullPage) {
            pdf.addPage()// Add a blank page if there is more content to follow
          } else {
            break
          }
        }
        pdf.save(title + '.pdf')
        window.close()
      )
    },

    Vue.prototype.addPageHeader = (node, item) = > {
      let pageHeader = document.createElement("div")
      pageHeader.className = "c-page-head"
      pageHeader.innerHTML = "Header content"
      node.insertBefore(pageHeader,item)
    },
    Vue.prototype.addPageFooter = (node, item, currentPage, diff, isLastest) = > {
      console.log(item.offsetTop, diff)
      let pageFooter = document.createElement("div")
      pageFooter.className = "c-page-foot"
      pageFooter.innerHTML = "The first" + currentPage + "Page"isLastest? node.insertBefore(pageFooter,null):node.insertBefore(pageFooter,item)
      pageFooter.style.marginTop = diff+"px"
      pageFooter.style.marginBottom = "10px"}}}Copy the code