parent
badf2ff705
commit
180cbd3b4c
@ -0,0 +1,286 @@ |
||||
import domToImage from 'dom-to-image'; |
||||
import { jsPDF } from 'jspdf'; |
||||
|
||||
const _cloneNode = (node, javascriptEnabled) => { |
||||
let child = node.firstChild |
||||
const clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false) |
||||
while (child) { |
||||
if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') { |
||||
clone.appendChild(_cloneNode(child, javascriptEnabled)) |
||||
} |
||||
child = child.nextSibling |
||||
} |
||||
if (node.nodeType === 1) { |
||||
if (node.nodeName === 'CANVAS') { |
||||
clone.width = node.width |
||||
clone.height = node.height |
||||
clone.getContext('2d').drawImage(node, 0, 0) |
||||
} else if (node.nodeName === 'TEXTAREA' || node.nodeName === 'SELECT') { |
||||
clone.value = node.value |
||||
} |
||||
clone.addEventListener('load', (() => { |
||||
clone.scrollTop = node.scrollTop |
||||
clone.scrollLeft = node.scrollLeft |
||||
}), true) |
||||
} |
||||
return clone |
||||
} |
||||
|
||||
const _createElement = (tagName, {className, innerHTML, style}) => { |
||||
let i |
||||
let scripts |
||||
const el = document.createElement(tagName) |
||||
if (className) { |
||||
el.className = className |
||||
} |
||||
if (innerHTML) { |
||||
el.innerHTML = innerHTML |
||||
scripts = el.getElementsByTagName('script') |
||||
i = scripts.length |
||||
while (i-- > 0) { |
||||
scripts[i].parentNode.removeChild(scripts[i]) |
||||
} |
||||
} |
||||
for (const key in style) { |
||||
el.style[key] = style[key]; |
||||
} |
||||
return el; |
||||
}; |
||||
|
||||
const _isCanvasBlank = canvas => { |
||||
const blank = document.createElement('canvas'); |
||||
blank.width = canvas.width; |
||||
blank.height = canvas.height; |
||||
const ctx = blank.getContext('2d'); |
||||
ctx.fillStyle = '#FFFFFF'; |
||||
ctx.fillRect(0, 0, blank.width, blank.height); |
||||
return canvas.toDataURL() === blank.toDataURL(); |
||||
}; |
||||
|
||||
const downloadPdf = (dom, options, cb) => { |
||||
const a4Height = 841.89; |
||||
const a4Width = 595.28; |
||||
let overrideWidth; |
||||
let container; |
||||
let containerCSS; |
||||
let containerWidth; |
||||
let elements; |
||||
let excludeClassNames; |
||||
let excludeTagNames; |
||||
let filename; |
||||
let filterFn; |
||||
let innerRatio; |
||||
let overlay; |
||||
let overlayCSS; |
||||
let pageHeightPx; |
||||
let proxyUrl; |
||||
let compression = 'NONE'; |
||||
let scale; |
||||
let opts; |
||||
let offsetHeight; |
||||
let offsetWidth; |
||||
let scaleObj; |
||||
let style; |
||||
const transformOrigin = 'top left'; |
||||
const pdfOptions = { |
||||
orientation: 'l', |
||||
unit: 'pt', |
||||
format: 'a4' |
||||
}; |
||||
|
||||
({filename, excludeClassNames = [], excludeTagNames = ['button', 'input', 'select'], overrideWidth, proxyUrl, compression, scale} = options); |
||||
|
||||
overlayCSS = { |
||||
position: 'fixed', |
||||
zIndex: 1000, |
||||
opacity: 0, |
||||
left: 0, |
||||
right: 0, |
||||
bottom: 0, |
||||
top: 0, |
||||
backgroundColor: 'rgba(0,0,0,0.8)' |
||||
}; |
||||
if (overrideWidth) { |
||||
overlayCSS.width = `${overrideWidth}px`; |
||||
} |
||||
containerCSS = { |
||||
position: 'absolute', |
||||
left: 0, |
||||
right: 0, |
||||
top: 0, |
||||
height: 'auto', |
||||
margin: 'auto', |
||||
overflow: 'auto', |
||||
backgroundColor: 'white' |
||||
}; |
||||
overlay = _createElement('div', { |
||||
style: overlayCSS |
||||
}); |
||||
container = _createElement('div', { |
||||
style: containerCSS |
||||
}); |
||||
container.appendChild(_cloneNode(dom)); |
||||
overlay.appendChild(container); |
||||
document.body.appendChild(overlay); |
||||
innerRatio = a4Height / a4Width; |
||||
containerWidth = overrideWidth || container.getBoundingClientRect().width; |
||||
pageHeightPx = Math.floor(containerWidth * innerRatio); |
||||
elements = container.querySelectorAll('*'); |
||||
|
||||
for (let i = 0, len = excludeClassNames.length; i < len; i++) { |
||||
const clName = excludeClassNames[i]; |
||||
container.querySelectorAll(`.${clName}`).forEach(function(a) { |
||||
return a.remove(); |
||||
}); |
||||
} |
||||
|
||||
for (let j = 0, len1 = excludeTagNames.length; j < len1; j++) { |
||||
const tName = excludeTagNames[j]; |
||||
let els = container.getElementsByTagName(tName); |
||||
|
||||
for (let k = els.length - 1; k >= 0; k--) { |
||||
if (!els[k]) { |
||||
continue; |
||||
} |
||||
els[k].parentNode.removeChild(els[k]); |
||||
} |
||||
} |
||||
|
||||
Array.prototype.forEach.call(elements, el => { |
||||
let clientRect; |
||||
let endPage; |
||||
let nPages; |
||||
let pad; |
||||
let rules; |
||||
let startPage; |
||||
rules = { |
||||
before: false, |
||||
after: false, |
||||
avoid: true |
||||
}; |
||||
clientRect = el.getBoundingClientRect(); |
||||
if (rules.avoid && !rules.before) { |
||||
startPage = Math.floor(clientRect.top / pageHeightPx); |
||||
endPage = Math.floor(clientRect.bottom / pageHeightPx); |
||||
nPages = Math.abs(clientRect.bottom - clientRect.top) / pageHeightPx; |
||||
// Turn on rules.before if the el is broken and is at most one page long.
|
||||
if (endPage !== startPage && nPages <= 1) { |
||||
rules.before = true; |
||||
} |
||||
// Before: Create a padding div to push the element to the next page.
|
||||
if (rules.before) { |
||||
pad = _createElement('div', { |
||||
style: { |
||||
display: 'block', |
||||
height: `${pageHeightPx - clientRect.top % pageHeightPx}px` |
||||
} |
||||
}); |
||||
return el.parentNode.insertBefore(pad, el); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
// Remove unnecessary elements from result pdf
|
||||
filterFn = ({classList, tagName}) => { |
||||
let cName; |
||||
let j; |
||||
let len; |
||||
let ref; |
||||
if (classList) { |
||||
for (j = 0, len = excludeClassNames.length; j < len; j++) { |
||||
cName = excludeClassNames[j]; |
||||
if (Array.prototype.indexOf.call(classList, cName) >= 0) { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
ref = tagName != null ? tagName.toLowerCase() : undefined; |
||||
return excludeTagNames.indexOf(ref) < 0; |
||||
}; |
||||
|
||||
opts = { |
||||
filter: filterFn, |
||||
proxy: proxyUrl |
||||
}; |
||||
|
||||
if (scale) { |
||||
offsetWidth = container.offsetWidth; |
||||
offsetHeight = container.offsetHeight; |
||||
style = { |
||||
transform: 'scale(' + scale + ')', |
||||
transformOrigin: transformOrigin, |
||||
width: offsetWidth + 'px', |
||||
height: offsetHeight + 'px' |
||||
}; |
||||
scaleObj = { |
||||
width: offsetWidth * scale, |
||||
height: offsetHeight * scale, |
||||
quality: 1, |
||||
style: style |
||||
}; |
||||
opts = Object.assign(opts, scaleObj); |
||||
} |
||||
|
||||
return domToImage.toCanvas(container, opts).then(canvas => { |
||||
let h; |
||||
let imgData; |
||||
let nPages; |
||||
let page; |
||||
let pageCanvas; |
||||
let pageCtx; |
||||
let pageHeight; |
||||
let pdf; |
||||
let pxFullHeight; |
||||
let w; |
||||
// Remove overlay
|
||||
document.body.removeChild(overlay); |
||||
// Initialize the PDF.
|
||||
pdf = new jsPDF(pdfOptions); |
||||
// Calculate the number of pages.
|
||||
pxFullHeight = canvas.height; |
||||
nPages = Math.ceil(pxFullHeight / pageHeightPx); |
||||
// Define pageHeight separately so it can be trimmed on the final page.
|
||||
pageHeight = a4Height; |
||||
pageCanvas = document.createElement('canvas'); |
||||
pageCtx = pageCanvas.getContext('2d'); |
||||
pageCanvas.width = canvas.width; |
||||
pageCanvas.height = pageHeightPx; |
||||
page = 0; |
||||
while (page < nPages) { |
||||
if (page === nPages - 1 && pxFullHeight % pageHeightPx !== 0) { |
||||
pageCanvas.height = pxFullHeight % pageHeightPx; |
||||
pageHeight = pageCanvas.height * a4Width / pageCanvas.width; |
||||
} |
||||
w = pageCanvas.width; |
||||
h = pageCanvas.height; |
||||
pageCtx.fillStyle = 'white'; |
||||
pageCtx.fillRect(0, 0, w, h); |
||||
pageCtx.drawImage(canvas, 0, page * pageHeightPx, w, h, 0, 0, w, h); |
||||
// Don't create blank pages
|
||||
if (_isCanvasBlank(pageCanvas)) { |
||||
++page; |
||||
continue; |
||||
} |
||||
// Add the page to the PDF.
|
||||
if (page) { |
||||
pdf.addPage(); |
||||
} |
||||
imgData = pageCanvas.toDataURL('image/PNG'); |
||||
pdf.addImage(imgData, 'PNG', 0, 0, a4Width, pageHeight, undefined, compression); |
||||
++page; |
||||
} |
||||
if (typeof cb === "function") { |
||||
cb(pdf); |
||||
} |
||||
return pdf.save(filename); |
||||
}).catch(error => { |
||||
// Remove overlay
|
||||
document.body.removeChild(overlay); |
||||
if (typeof cb === "function") { |
||||
cb(null); |
||||
} |
||||
return console.error(error); |
||||
}); |
||||
}; |
||||
|
||||
module.exports = downloadPdf; |
Loading…
Reference in new issue