forked from eKoopmans/html2pdf.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request eKoopmans#153 from eKoopmans/feature/pagebreaks
Page-breaks: Add modes and ability to specify elements
- Loading branch information
Showing
6 changed files
with
356 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
}); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.