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 there
background: '#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