Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 101 additions & 6 deletions src/plugin/pagebreaks.js
Original file line number Diff line number Diff line change
@@ -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);
}
});
});
};
27 changes: 18 additions & 9 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
8 changes: 2 additions & 6 deletions src/worker.js
Original file line number Diff line number Diff line change
@@ -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 ----- */

Expand Down Expand Up @@ -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];
Expand All @@ -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);
Expand Down