Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6d00799
Merge pull request #73 from drbeat/patch-1
eKoopmans Feb 3, 2018
ad7abb8
Merge branch 'release/v0.9.0'
eKoopmans May 19, 2018
f1b0449
Merge remote-tracking branch 'origin/master'
eKoopmans May 19, 2018
36041a9
Improve page-break commenting
eKoopmans Sep 11, 2018
bb5f0f9
Add margin comment
eKoopmans Sep 11, 2018
8ab0604
Move toPx into utils and update unitConvert
eKoopmans Sep 11, 2018
0f1e320
Add proper function names to util functions
eKoopmans Sep 11, 2018
6fe9755
Add pageBreak features: css and avoidAll
eKoopmans Sep 15, 2018
a1eee56
Remove goal of avoiding the loop through all els
eKoopmans Sep 15, 2018
02232e9
Merge branch 'feature/pagebreaks' of https://github.com/dave-deep/htm…
Sep 30, 2018
b1f5bf4
Fixed issues with references and checks
Sep 30, 2018
97e269e
Merge pull request #164 from dave-deep/master
eKoopmans Oct 7, 2018
d476407
Minor refactor when handling undefined keys
eKoopmans Oct 4, 2018
e26fdf4
Fix insertAfter usage
eKoopmans Oct 4, 2018
a8d1354
Avoid breaks on elements that are exactly 1 page
eKoopmans Oct 4, 2018
81b75a0
Fix to support modern CSS options
eKoopmans Oct 7, 2018
804becc
Support multiple CSS break-inside options
eKoopmans Oct 7, 2018
7886ac6
Remove whiteline pagebreak option
eKoopmans Oct 7, 2018
9ee2f69
Change case of pagebreak option (no camel-case)
eKoopmans Oct 7, 2018
950c355
Add manual tests (template and pagebreaks)
eKoopmans Oct 7, 2018
25f4d2f
Add advanced tests of avoid-all
eKoopmans Oct 7, 2018
79b4549
Add avoid-all to default pagebreak behaviour
eKoopmans Oct 7, 2018
13f1011
Add doc and remove avoid-all from default
eKoopmans Oct 20, 2018
2de9985
Change order of set/from in example
eKoopmans Oct 20, 2018
6028322
Add pagebreak updates to the readme
eKoopmans Oct 20, 2018
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
63 changes: 47 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,33 +98,64 @@ var opt = {
};

// New Promise-based usage:
html2pdf().from(element).set(opt).save();
html2pdf().set(opt).from(element).save();

// Old monolithic-style usage:
html2pdf(element, opt);
```

The `opt` parameter has the following optional fields:

|Name |Type |Default |Description |
|------------|----------------|------------------------------|------------------------------------------------------------------------------------------------------------|
|margin |number or array |0 |PDF margin (in jsPDF units). Can be a single number, `[vMargin, hMargin]`, or `[top, left, bottom, right]`. |
|filename |string |'file.pdf' |The default filename of the exported PDF. |
|image |object |{type: 'jpeg', quality: 0.95} |The image type and quality used to generate the PDF. See the Extra Features section below. |
|enableLinks |boolean |true |If enabled, PDF hyperlinks are automatically added ontop of all anchor tags. |
|html2canvas |object |{ } |Configuration options sent directly to `html2canvas` ([see here](https://html2canvas.hertzen.com/configuration) for usage).|
|jsPDF |object |{ } |Configuration options sent directly to `jsPDF` ([see here](http://rawgit.com/MrRio/jsPDF/master/docs/jsPDF.html) for usage).|
|Name |Type |Default |Description |
|------------|----------------|--------------------------------|------------------------------------------------------------------------------------------------------------|
|margin |number or array |`0` |PDF margin (in jsPDF units). Can be a single number, `[vMargin, hMargin]`, or `[top, left, bottom, right]`. |
|filename |string |`'file.pdf'` |The default filename of the exported PDF. |
|pagebreak |object |`{mode: ['css', 'legacy']}` |Controls the pagebreak behaviour on the page. See [Page-breaks](#page-breaks) below. |
|image |object |`{type: 'jpeg', quality: 0.95}` |The image type and quality used to generate the PDF. See [Image type and quality](#image-type-and-quality) below.|
|enableLinks |boolean |`true` |If enabled, PDF hyperlinks are automatically added ontop of all anchor tags. |
|html2canvas |object |`{ }` |Configuration options sent directly to `html2canvas` ([see here](https://html2canvas.hertzen.com/configuration) for usage).|
|jsPDF |object |`{ }` |Configuration options sent directly to `jsPDF` ([see here](http://rawgit.com/MrRio/jsPDF/master/docs/jsPDF.html) for usage).|

### Page-breaks

You may add `html2pdf`-specific page-breaks to your document by adding the CSS class `html2pdf__page-break` to any element (normally an empty `div`). For React elements, use `className=html2pdf__page-break`. During PDF creation, these elements will be given a height calculated to fill the remainder of the PDF page that they are on. Example usage:
html2pdf has the ability to automatically add page-breaks to clean up your document. Page-breaks can be added by CSS styles, set on individual elements using selectors, or avoided from breaking inside all elements (`avoid-all` mode).

```html
<div id="element-to-print">
<span>I'm on page 1!</span>
<div class="html2pdf__page-break"></div>
<span>I'm on page 2!</span>
</div>
By default, html2pdf will respect most CSS [`break-before`](https://developer.mozilla.org/en-US/docs/Web/CSS/break-before), [`break-after`](https://developer.mozilla.org/en-US/docs/Web/CSS/break-after), and [`break-inside`](https://developer.mozilla.org/en-US/docs/Web/CSS/break-inside) rules, and also add page-breaks after any element with class `html2pdf__page-break` (for legacy purposes).

#### Page-break settings

|Setting |Type |Default |Description |
|----------|----------------|--------------------|------------|
|mode |string or array |`['css', 'legacy']` |The mode(s) on which to automatically add page-breaks. One or more of `'avoid-all'`, `'css'`, and `'legacy'`. |
|before |string or array |`[]` |CSS selectors for which to add page-breaks before each element. Can be a specific element with an ID (`'#myID'`), all elements of a type (e.g. `'img'`), all of a class (`'.myClass'`), or even `'*'` to match every element. |
|after |string or array |`[]` |Like 'before', but adds a page-break immediately after the element. |
|avoid |string or array |`[]` |Like 'before', but avoids page-breaks on these elements. You can enable this feature on every element using the 'avoid-all' mode. |

#### Page-break modes

| Mode | Description |
|-----------|-------------|
| avoid-all | Automatically adds page-breaks to avoid splitting any elements across pages. |
| css | Adds page-breaks according to the CSS `break-before`, `break-after`, and `break-inside` properties. Only recognizes `always/left/right` for before/after, and `avoid` for inside. |
| legacy | Adds page-breaks after elements with class `html2pdf__page-break`. This feature may be removed in the future. |

#### Example usage

```js
// Avoid page-breaks on all elements, and add one before #page2el.
html2pdf().set({
pagebreak: { mode: 'avoid-all', before: '#page2el' }
});

// Enable all 'modes', with no explicit elements.
html2pdf().set({
pagebreak: { mode: ['avoid-all', 'css', 'legacy'] }
});

// No modes, only explicit elements.
html2pdf().set({
pagebreak: { before: '.beforeClass', after: ['#after1', '#after2'], avoid: 'img' }
});
```

### Image type and quality
Expand Down
128 changes: 122 additions & 6 deletions src/plugin/pagebreaks.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,134 @@
import Worker from '../worker.js';
import { objType, createElement } from '../utils.js';

/* Pagebreak plugin:

Adds page-break functionality to the html2pdf library. Page-breaks can be
enabled by CSS styles, set on individual elements using selectors, or
avoided from breaking inside all elements.

Options on the `opt.pagebreak` object:

mode: String or array of strings: 'avoid-all', 'css', and/or 'legacy'
Default: ['css', 'legacy']

before: String or array of CSS selectors for which to add page-breaks
before each element. Can be a specific element with an ID
('#myID'), all elements of a type (e.g. 'img'), all of a class
('.myClass'), or even '*' to match every element.

after: Like 'before', but adds a page-break immediately after the element.

avoid: Like 'before', but avoids page-breaks on these elements. You can
enable this feature on every element using the 'avoid-all' mode.
*/

// 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'],
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
};

// Get arrays of all explicitly requested elements.
var select = {};
var self = this;
['before', 'after', 'avoid'].forEach(function(key) {
var all = mode.avoidAll && key === 'avoid';
select[key] = all ? [] : [].concat(self.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 breakOpt = ['always', 'page', 'left', 'right'];
var avoidOpt = ['avoid', 'avoid-page'];
rules = {
before: rules.before || breakOpt.indexOf(style.breakBefore || style.pageBreakBefore) !== -1,
after: rules.after || breakOpt.indexOf(style.breakAfter || style.pageBreakAfter) !== -1,
avoid: rules.avoid || avoidOpt.indexOf(style.breakInside || style.pageBreakInside) !== -1
};
}

// 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 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) {
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.insertBefore(pad, el.nextSibling);
}
});
});
};
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
Loading