import { jsPDF } from 'jspdf'; import html2canvas from 'html2canvas'; import { deepCloneBasic } from './snapdom/clone.js'; import { objType, createElement, toPx } from './utils.js'; /* ----- CONSTRUCTOR ----- */ var Worker = function Worker(opt) { // Create the root parent for the proto chain, and the starting Worker. var root = Object.assign(Worker.convert(Promise.resolve()), JSON.parse(JSON.stringify(Worker.template))); var self = Worker.convert(Promise.resolve(), root); // Set progress, optional settings, and return. self = self.setProgress(1, Worker, 1, [Worker]); self = self.set(opt); return self; }; // Boilerplate for subclassing Promise. Worker.prototype = Object.create(Promise.prototype); Worker.prototype.constructor = Worker; // Converts/casts promises into Workers. Worker.convert = function convert(promise, inherit) { // Uses prototypal inheritance to receive changes made to ancestors' properties. promise.__proto__ = inherit || Worker.prototype; return promise; }; Worker.template = { prop: { src: null, container: null, overlay: null, canvas: null, img: null, pdf: null, pageSize: null }, progress: { val: 0, state: null, n: 0, stack: [] }, opt: { filename: 'file.pdf', margin: [0,0,0,0], image: { type: 'jpeg', quality: 0.95 }, enableLinks: true, html2canvas: {}, jsPDF: {} } }; /* ----- FROM / TO ----- */ Worker.prototype.from = function from(src, type) { function getType(src) { switch (objType(src)) { case 'string': return 'string'; case 'element': return src.nodeName.toLowerCase && src.nodeName.toLowerCase() === 'canvas' ? 'canvas' : 'element'; default: return 'unknown'; } } return this.then(function from_main() { type = type || getType(src); switch (type) { case 'string': return this.set({ src: createElement('div', {innerHTML: src}) }); case 'element': return this.set({ src: src }); case 'canvas': return this.set({ canvas: src }); case 'img': return this.set({ img: src }); default: return this.error('Unknown source type.'); } }); }; Worker.prototype.to = function to(target) { // Route the 'to' request to the appropriate method. switch (target) { case 'container': return this.toContainer(); case 'canvas': return this.toCanvas(); case 'img': return this.toImg(); case 'pdf': return this.toPdf(); default: return this.error('Invalid target.'); } }; Worker.prototype.toContainer = function toContainer() { // Set up function prerequisites. var prereqs = [ function checkSrc() { return this.prop.src || this.error('Cannot duplicate - no source HTML.'); }, function checkPageSize() { return this.prop.pageSize || this.setPageSize(); } ]; return this.thenList(prereqs).then(function toContainer_main() { // Define the CSS styles for the container and its overlay parent. var overlayCSS = { position: 'fixed', overflow: 'hidden', zIndex: 1000, left: 0, right: 0, bottom: 0, top: 0, backgroundColor: 'rgba(0,0,0,0.8)' }; var containerCSS = { position: 'absolute', width: this.prop.pageSize.inner.width + this.prop.pageSize.unit, left: 0, right: 0, top: 0, height: 'auto', margin: 'auto', backgroundColor: 'white' }; // Set the overlay to hidden (could be changed in the future to provide a print preview). overlayCSS.opacity = 0; // Create and attach the elements. var source = deepCloneBasic(this.prop.src); this.prop.overlay = createElement('div', { className: 'html2pdf__overlay', style: overlayCSS }); this.prop.container = createElement('div', { className: 'html2pdf__container', style: containerCSS }); this.prop.container.appendChild(source); this.prop.overlay.appendChild(this.prop.container); document.body.appendChild(this.prop.overlay); // Delay to better ensure content is fully cloned and rendering before capturing. return new Promise(resolve => setTimeout(resolve, 10)); }); }; Worker.prototype.toCanvas = function toCanvas() { // Set up function prerequisites. var prereqs = [ function checkContainer() { return document.body.contains(this.prop.container) || this.toContainer(); } ]; // Fulfill prereqs then create the canvas. return this.thenList(prereqs).then(function toCanvas_main() { // Handle old-fashioned 'onrendered' argument. var options = Object.assign({}, this.opt.html2canvas); delete options.onrendered; return html2canvas(this.prop.container, options); }).then(function toCanvas_post(canvas) { // Handle old-fashioned 'onrendered' argument. var onRendered = this.opt.html2canvas.onrendered || function () {}; onRendered(canvas); this.prop.canvas = canvas; document.body.removeChild(this.prop.overlay); }); }; Worker.prototype.toImg = function toImg() { // Set up function prerequisites. var prereqs = [ function checkCanvas() { return this.prop.canvas || this.toCanvas(); } ]; // Fulfill prereqs then create the image. return this.thenList(prereqs).then(function toImg_main() { var imgData = this.prop.canvas.toDataURL('image/' + this.opt.image.type, this.opt.image.quality); this.prop.img = document.createElement('img'); this.prop.img.src = imgData; }); }; Worker.prototype.toPdf = function toPdf() { // Set up function prerequisites. var prereqs = [ function checkCanvas() { return this.prop.canvas || this.toCanvas(); }, function checkPageSize() { return this.prop.pageSize || this.setPageSize(); } ]; // Fulfill prereqs then create the image. return this.thenList(prereqs).then(function toPdf_main() { // Create local copies of frequently used properties. var canvas = this.prop.canvas; var opt = this.opt; // Calculate the number of pages. var pxFullHeight = canvas.height; var pxPageHeight = Math.floor(canvas.width * this.prop.pageSize.inner.ratio); var nPages = Math.ceil(pxFullHeight / pxPageHeight); // Define pageHeight separately so it can be trimmed on the final page. var pageHeight = this.prop.pageSize.inner.height; // Create a one-page canvas to split up the full image. var pageCanvas = document.createElement('canvas'); var pageCtx = pageCanvas.getContext('2d'); pageCanvas.width = canvas.width; pageCanvas.height = pxPageHeight; // Initialize the PDF. this.prop.pdf = this.prop.pdf || new jsPDF(opt.jsPDF); for (var page=0; page