diff --git a/src/plugin/pagebreaks.js b/src/plugin/pagebreaks.js index b620688..05de8c5 100644 --- a/src/plugin/pagebreaks.js +++ b/src/plugin/pagebreaks.js @@ -1,18 +1,113 @@ import Worker from '../worker.js'; +import { objType, createElement } from './utils.js'; +// Add page-break functionality. + +// Refs to original functions. var orig = { toContainer: Worker.prototype.toContainer }; +// Add pageBreak default options to the Worker template. +Worker.template.opt.pageBreak = { + mode: ['css', 'legacy'], // 'avoid-all', 'css', 'legacy', 'whiteline' + before: [], + after: [], + avoid: [] +}; + Worker.prototype.toContainer = function toContainer() { return orig.toContainer.call(this).then(function toContainer_pagebreak() { - // Enable page-breaks. - var pageBreaks = this.prop.container.querySelectorAll('.html2pdf__page-break'); + // Setup root element and inner page height. + var root = this.prop.container; var pxPageHeight = this.prop.pageSize.inner.px.height; - Array.prototype.forEach.call(pageBreaks, function pageBreak_loop(el) { - el.style.display = 'block'; + + // Check all requested modes. + var modeSrc = [].concat(this.opt.pageBreak.mode); + var mode = { + avoidAll: modeSrc.indexOf('avoid-all') !== -1, + css: modeSrc.indexOf('css') !== -1, + legacy: modeSrc.indexOf('legacy') !== -1, + whiteline: modeSrc.indexOf('whiteline') !== -1 + }; + + // Get arrays of all explicitly requested elements. + var select = {}; + ['before', 'after', 'avoid'].forEach(function(key) { + var all = mode.avoidAll && key === 'avoid'; + select[key] = all ? [] : [].concat(this.opt.pageBreak[key]); + if (select[key].length > 0) { + select[key] = Array.prototype.slice.call( + root.querySelectorAll(select[key].join(', '))); + } + }); + + // Get all legacy page-break elements. + var legacyEls = root.querySelectorAll('.html2pdf__page-break'); + legacyEls = Array.prototype.slice.call(legacyEls); + + // Loop through all elements. + var els = root.querySelectorAll('*'); + Array.prototype.forEach.call(els, function pageBreak_loop(el) { + // Setup pagebreak rules based on legacy and avoidAll modes. + var rules = { + before: false, + after: mode.legacy && legacyEls.indexOf(el) !== -1, + avoid: mode.avoidAll + }; + + // Add rules for css mode. + if (mode.css) { + // TODO: Check if this is valid with iFrames. + var style = window.getComputedStyle(el); + // TODO: Handle 'left' and 'right' correctly. + // TODO: Add support for 'avoid' on breakBefore/After. + var cssOpt = ['always', 'left', 'right']; + rules = { + before: rules.before || cssOpt.indexOf(style.breakBefore || style.pageBreakBefore) !== -1, + after: rules.after || cssOpt.indexOf(style.breakAfter || style.pageBreakAfter) !== -1, + avoid: rules.avoid || (style.breakInside || style.pageBreakInside) === 'avoid' + }; + } + + // Add rules for explicit requests. + Object.keys(rules).forEach(function(key) { + rules[key] = rules[key] || select[key].indexOf(el) !== -1; + }); + + // Get element position on the screen. + // TODO: Subtract the top of the container from clientRect.top/bottom? var clientRect = el.getBoundingClientRect(); - el.style.height = pxPageHeight - (clientRect.top % pxPageHeight) + 'px'; - }, this); + + // Avoid: Check if a break happens mid-element. + if (rules.avoid && !rules.before) { + var startPage = Math.floor(clientRect.top / pxPageHeight); + var endPage = Math.floor(clientRect.bottom / pxPageHeight); + var nPages = Math.abs(clientRect.bottom - clientRect.top) / pxPageHeight; + + // Turn on rules.before if the el is broken and is less than a 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) { + var pad = createElement('div', {style: { + display: 'block', + height: pxPageHeight - (clientRect.top % pxPageHeight) + 'px' + }}); + el.parentNode.insertBefore(pad, el); + } + + // After: Create a padding div to fill the remaining page. + if (rules.after) { + var pad = createElement('div', {style: { + display: 'block', + height: pxPageHeight - (clientRect.bottom % pxPageHeight) + 'px' + }}); + el.parentNode.insertAfter(pad, el); + } + }); }); }; diff --git a/src/utils.js b/src/utils.js index a0e9486..ce0c413 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,5 @@ // Determine the type of a variable/object. -export const objType = function(obj) { +export const objType = function objType(obj) { var type = typeof obj; if (type === 'undefined') return 'undefined'; else if (type === 'string' || obj instanceof String) return 'string'; @@ -12,7 +12,7 @@ export const objType = function(obj) { }; // Create an HTML element with optional className, innerHTML, and style. -export const createElement = function(tagName, opt) { +export const createElement = function createElement(tagName, opt) { var el = document.createElement(tagName); if (opt.className) el.className = opt.className; if (opt.innerHTML) { @@ -29,7 +29,7 @@ export const createElement = function(tagName, opt) { }; // Deep-clone a node and preserve contents/properties. -export const cloneNode = function(node, javascriptEnabled) { +export const cloneNode = function cloneNode(node, javascriptEnabled) { // Recursively clone the node. var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false); for (var child = node.firstChild; child; child = child.nextSibling) { @@ -59,11 +59,20 @@ export const cloneNode = function(node, javascriptEnabled) { return clone; } -// Convert units using the conversion value 'k' from jsPDF. -export const unitConvert = function(obj, k) { - var newObj = {}; - for (var key in obj) { - newObj[key] = obj[key] * 72 / 96 / k; +// Convert units from px using the conversion value 'k' from jsPDF. +export const unitConvert = function unitConvert(obj, k) { + if (objType(obj) === 'number') { + return obj * 72 / 96 / k; + } else { + var newObj = {}; + for (var key in obj) { + newObj[key] = obj[key] * 72 / 96 / k; + } + return newObj; } - return newObj; }; + +// Convert units to px using the conversion value 'k' from jsPDF. +export const toPx = function toPx(val, k) { + return Math.floor(val * k / 72 * 96); +} diff --git a/src/worker.js b/src/worker.js index 657fd77..cbcb6fb 100644 --- a/src/worker.js +++ b/src/worker.js @@ -1,6 +1,6 @@ import jsPDF from 'jspdf'; import html2canvas from 'html2canvas'; -import { objType, createElement, cloneNode, unitConvert } from './utils.js'; +import { objType, createElement, cloneNode, toPx } from './utils.js'; /* ----- CONSTRUCTOR ----- */ @@ -330,7 +330,7 @@ Worker.prototype.get = function get(key, cbk) { Worker.prototype.setMargin = function setMargin(margin) { return this.then(function setMargin_main() { - // Parse the margin property. + // Parse the margin property: [top, left, bottom, right]. switch (objType(margin)) { case 'number': margin = [margin, margin, margin, margin]; @@ -351,10 +351,6 @@ Worker.prototype.setMargin = function setMargin(margin) { } Worker.prototype.setPageSize = function setPageSize(pageSize) { - function toPx(val, k) { - return Math.floor(val * k / 72 * 96); - } - return this.then(function setPageSize_main() { // Retrieve page-size based on jsPDF settings, if not explicitly provided. pageSize = pageSize || jsPDF.getPageSize(this.opt.jsPDF);