/** @preserve
* jsPDF fromHTML plugin. BETA stage. API subject to change. Needs browser
* Copyright (c) 2012 Willow Systems Corporation, willow-systems.com
* 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
* 2014 Diego Casorran, https://github.com/diegocr
* 2014 Daniel Husar, https://github.com/danielhusar
* 2014 Wolfgang Gassler, https://github.com/woolfg
* 2014 Steven Spungin, https://github.com/flamenco
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/
(function (jsPDFAPI) {
var clone,
DrillForContent,
FontNameDB,
FontStyleMap,
FontWeightMap,
FloatMap,
ClearMap,
GetCSS,
PurgeWhiteSpace,
Renderer,
ResolveFont,
ResolveUnitedNumber,
UnitedNumberMap,
elementHandledElsewhere,
images,
loadImgs,
checkForFooter,
process,
tableToJson;
clone = (function () {
return function (obj) {
Clone.prototype = obj;
return new Clone()
};
function Clone() {}
})();
PurgeWhiteSpace = function (array) {
var fragment,
i,
l,
lTrimmed,
r,
rTrimmed,
trailingSpace;
i = 0;
l = array.length;
fragment = void 0;
lTrimmed = false;
rTrimmed = false;
while (!lTrimmed && i !== l) {
fragment = array[i] = array[i].trimLeft();
if (fragment) {
lTrimmed = true;
}
i++;
}
i = l - 1;
while (l && !rTrimmed && i !== -1) {
fragment = array[i] = array[i].trimRight();
if (fragment) {
rTrimmed = true;
}
i--;
}
r = /\s+$/g;
trailingSpace = true;
i = 0;
while (i !== l) {
// Leave the line breaks intact
if (array[i] != "\u2028") {
fragment = array[i].replace(/\s+/g, " ");
if (trailingSpace) {
fragment = fragment.trimLeft();
}
if (fragment) {
trailingSpace = r.test(fragment);
}
array[i] = fragment;
}
i++;
}
return array;
};
Renderer = function (pdf, x, y, settings) {
this.pdf = pdf;
this.x = x;
this.y = y;
this.settings = settings;
//list of functions which are called after each element-rendering process
this.watchFunctions = [];
this.init();
return this;
};
ResolveFont = function (css_font_family_string) {
var name,
part,
parts;
name = void 0;
parts = css_font_family_string.split(",");
part = parts.shift();
while (!name && part) {
name = FontNameDB[part.trim().toLowerCase()];
part = parts.shift();
}
return name;
};
ResolveUnitedNumber = function (css_line_height_string) {
//IE8 issues
css_line_height_string = css_line_height_string === "auto" ? "0px" : css_line_height_string;
if (css_line_height_string.indexOf("em") > -1 && !isNaN(Number(css_line_height_string.replace("em", "")))) {
css_line_height_string = Number(css_line_height_string.replace("em", "")) * 18.719 + "px";
}
if (css_line_height_string.indexOf("pt") > -1 && !isNaN(Number(css_line_height_string.replace("pt", "")))) {
css_line_height_string = Number(css_line_height_string.replace("pt", "")) * 1.333 + "px";
}
var normal,
undef,
value;
undef = void 0;
normal = 16.00;
value = UnitedNumberMap[css_line_height_string];
if (value) {
return value;
}
value = {
"xx-small" : 9,
"x-small" : 11,
small : 13,
medium : 16,
large : 19,
"x-large" : 23,
"xx-large" : 28,
auto : 0
}[{ css_line_height_string : css_line_height_string }];
if (value !== undef) {
return UnitedNumberMap[css_line_height_string] = value / normal;
}
if (value = parseFloat(css_line_height_string)) {
return UnitedNumberMap[css_line_height_string] = value / normal;
}
value = css_line_height_string.match(/([\d\.]+)(px)/);
if (value.length === 3) {
return UnitedNumberMap[css_line_height_string] = parseFloat(value[1]) / normal;
}
return UnitedNumberMap[css_line_height_string] = 1;
};
GetCSS = function (element) {
var css,tmp,computedCSSElement;
computedCSSElement = (function (el) {
var compCSS;
compCSS = (function (el) {
if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView.getComputedStyle(el, null);
} else if (el.currentStyle) {
return el.currentStyle;
} else {
return el.style;
}
})(el);
return function (prop) {
prop = prop.replace(/-\D/g, function (match) {
return match.charAt(1).toUpperCase();
});
return compCSS[prop];
};
})(element);
css = {};
tmp = void 0;
css["font-family"] = ResolveFont(computedCSSElement("font-family")) || "times";
css["font-style"] = FontStyleMap[computedCSSElement("font-style")] || "normal";
css["text-align"] = TextAlignMap[computedCSSElement("text-align")] || "left";
tmp = FontWeightMap[computedCSSElement("font-weight")] || "normal";
if (tmp === "bold") {
if (css["font-style"] === "normal") {
css["font-style"] = tmp;
} else {
css["font-style"] = tmp + css["font-style"];
}
}
css["font-size"] = ResolveUnitedNumber(computedCSSElement("font-size")) || 1;
css["line-height"] = ResolveUnitedNumber(computedCSSElement("line-height")) || 1;
css["display"] = (computedCSSElement("display") === "inline" ? "inline" : "block");
tmp = (css["display"] === "block");
css["margin-top"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-top")) || 0;
css["margin-bottom"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-bottom")) || 0;
css["padding-top"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-top")) || 0;
css["padding-bottom"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-bottom")) || 0;
css["margin-left"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-left")) || 0;
css["margin-right"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-right")) || 0;
css["padding-left"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-left")) || 0;
css["padding-right"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-right")) || 0;
css["page-break-before"] = computedCSSElement("page-break-before") || "auto";
//float and clearing of floats
css["float"] = FloatMap[computedCSSElement("cssFloat")] || "none";
css["clear"] = ClearMap[computedCSSElement("clear")] || "none";
css["color"] = computedCSSElement("color");
return css;
};
elementHandledElsewhere = function (element, renderer, elementHandlers) {
var handlers,
i,
isHandledElsewhere,
l,
t;
isHandledElsewhere = false;
i = void 0;
l = void 0;
t = void 0;
handlers = elementHandlers["#" + element.id];
if (handlers) {
if (typeof handlers === "function") {
isHandledElsewhere = handlers(element, renderer);
} else {
i = 0;
l = handlers.length;
while (!isHandledElsewhere && i !== l) {
isHandledElsewhere = handlers[i](element, renderer);
i++;
}
}
}
handlers = elementHandlers[element.nodeName];
if (!isHandledElsewhere && handlers) {
if (typeof handlers === "function") {
isHandledElsewhere = handlers(element, renderer);
} else {
i = 0;
l = handlers.length;
while (!isHandledElsewhere && i !== l) {
isHandledElsewhere = handlers[i](element, renderer);
i++;
}
}
}
return isHandledElsewhere;
};
tableToJson = function (table, renderer) {
var data,
headers,
i,
j,
rowData,
tableRow,
table_obj,
table_with,
cell,
l;
data = [];
headers = [];
i = 0;
l = table.rows[0].cells.length;
table_with = table.clientWidth;
while (i < l) {
cell = table.rows[0].cells[i];
headers[i] = {
name : cell.textContent.toLowerCase().replace(/\s+/g, ''),
prompt : cell.textContent.replace(/\r?\n/g, ''),
width : (cell.clientWidth / table_with) * renderer.pdf.internal.pageSize.width
};
i++;
}
i = 1;
while (i < table.rows.length) {
tableRow = table.rows[i];
rowData = {};
j = 0;
while (j < tableRow.cells.length) {
rowData[headers[j].name] = tableRow.cells[j].textContent.replace(/\r?\n/g, '');
j++;
}
data.push(rowData);
i++;
}
return table_obj = {
rows : data,
headers : headers
};
};
var SkipNode = {
SCRIPT : 1,
STYLE : 1,
NOSCRIPT : 1,
OBJECT : 1,
EMBED : 1,
SELECT : 1
};
var listCount = 1;
DrillForContent = function (element, renderer, elementHandlers) {
var cn,
cns,
fragmentCSS,
i,
isBlock,
l,
px2pt,
table2json,
cb;
cns = element.childNodes;
cn = void 0;
fragmentCSS = GetCSS(element);
isBlock = fragmentCSS.display === "block";
if (isBlock) {
renderer.setBlockBoundary();
renderer.setBlockStyle(fragmentCSS);
}
px2pt = 0.264583 * 72 / 25.4;
i = 0;
l = cns.length;
while (i < l) {
cn = cns[i];
if (typeof cn === "object") {
//execute all watcher functions to e.g. reset floating
renderer.executeWatchFunctions(cn);
/*** HEADER rendering **/
if (cn.nodeType === 1 && cn.nodeName === 'HEADER') {
var header = cn;
//store old top margin
var oldMarginTop = renderer.pdf.margins_doc.top;
//subscribe for new page event and render header first on every page
renderer.pdf.internal.events.subscribe('addPage', function (pageInfo) {
//set current y position to old margin
renderer.y = oldMarginTop;
//render all child nodes of the header element
DrillForContent(header, renderer, elementHandlers);
//set margin to old margin + rendered header + 10 space to prevent overlapping
//important for other plugins (e.g. table) to start rendering at correct position after header
renderer.pdf.margins_doc.top = renderer.y + 10;
renderer.y += 10;
}, false);
}
if (cn.nodeType === 8 && cn.nodeName === "#comment") {
if (~cn.textContent.indexOf("ADD_PAGE")) {
renderer.pdf.addPage();
renderer.y = renderer.pdf.margins_doc.top;
}
} else if (cn.nodeType === 1 && !SkipNode[cn.nodeName]) {
/*** IMAGE RENDERING ***/
var cached_image;
if (cn.nodeName === "IMG") {
var url = cn.getAttribute("src");
cached_image = images[renderer.pdf.sHashCode(url) || url];
}
if (cached_image) {
if ((renderer.pdf.internal.pageSize.height - renderer.pdf.margins_doc.bottom < renderer.y + cn.height) && (renderer.y > renderer.pdf.margins_doc.top)) {
renderer.pdf.addPage();
renderer.y = renderer.pdf.margins_doc.top;
//check if we have to set back some values due to e.g. header rendering for new page
renderer.executeWatchFunctions(cn);
}
var imagesCSS = GetCSS(cn);
var imageX = renderer.x;
var fontToUnitRatio = 12 / renderer.pdf.internal.scaleFactor;
//define additional paddings, margins which have to be taken into account for margin calculations
var additionalSpaceLeft = (imagesCSS["margin-left"] + imagesCSS["padding-left"])*fontToUnitRatio;
var additionalSpaceRight = (imagesCSS["margin-right"] + imagesCSS["padding-right"])*fontToUnitRatio;
var additionalSpaceTop = (imagesCSS["margin-top"] + imagesCSS["padding-top"])*fontToUnitRatio;
var additionalSpaceBottom = (imagesCSS["margin-bottom"] + imagesCSS["padding-bottom"])*fontToUnitRatio;
//if float is set to right, move the image to the right border
//add space if margin is set
if (imagesCSS['float'] !== undefined && imagesCSS['float'] === 'right') {
imageX += renderer.settings.width - cn.width - additionalSpaceRight;
} else {
imageX += additionalSpaceLeft;
}
renderer.pdf.addImage(cached_image, imageX, renderer.y + additionalSpaceTop, cn.width, cn.height);
cached_image = undefined;
//if the float prop is specified we have to float the text around the image
if (imagesCSS['float'] === 'right' || imagesCSS['float'] === 'left') {
//add functiont to set back coordinates after image rendering
renderer.watchFunctions.push((function(diffX , thresholdY, diffWidth, el) {
//undo drawing box adaptions which were set by floating
if (renderer.y >= thresholdY) {
renderer.x += diffX;
renderer.settings.width += diffWidth;
return true;
} else if(el && el.nodeType === 1 && !SkipNode[el.nodeName] && renderer.x+el.width > (renderer.pdf.margins_doc.left + renderer.pdf.margins_doc.width)) {
renderer.x += diffX;
renderer.y = thresholdY;
renderer.settings.width += diffWidth;
return true;
} else {
return false;
}
}).bind(this, (imagesCSS['float'] === 'left') ? -cn.width-additionalSpaceLeft-additionalSpaceRight : 0, renderer.y+cn.height+additionalSpaceTop+additionalSpaceBottom, cn.width));
//reset floating by clear:both divs
//just set cursorY after the floating element
renderer.watchFunctions.push((function(yPositionAfterFloating, pages, el) {
if (renderer.y < yPositionAfterFloating && pages === renderer.pdf.internal.getNumberOfPages()) {
if (el.nodeType === 1 && GetCSS(el).clear === 'both') {
renderer.y = yPositionAfterFloating;
return true;
} else {
return false;
}
} else {
return true;
}
}).bind(this, renderer.y+cn.height, renderer.pdf.internal.getNumberOfPages()));
//if floating is set we decrease the available width by the image width
renderer.settings.width -= cn.width+additionalSpaceLeft+additionalSpaceRight;
//if left just add the image width to the X coordinate
if (imagesCSS['float'] === 'left') {
renderer.x += cn.width+additionalSpaceLeft+additionalSpaceRight;
}
} else {
//if no floating is set, move the rendering cursor after the image height
renderer.y += cn.height + additionalSpaceTop + additionalSpaceBottom;
}
/*** TABLE RENDERING ***/
} else if (cn.nodeName === "TABLE") {
table2json = tableToJson(cn, renderer);
renderer.y += 10;
renderer.pdf.table(renderer.x, renderer.y, table2json.rows, table2json.headers, {
autoSize : false,
printHeaders : true,
margins : renderer.pdf.margins_doc
});
renderer.y = renderer.pdf.lastCellPos.y + renderer.pdf.lastCellPos.h + 20;
} else if (cn.nodeName === "OL" || cn.nodeName === "UL") {
listCount = 1;
if (!elementHandledElsewhere(cn, renderer, elementHandlers)) {
DrillForContent(cn, renderer, elementHandlers);
}
renderer.y += 10;
} else if (cn.nodeName === "LI") {
var temp = renderer.x;
renderer.x += 20 / renderer.pdf.internal.scaleFactor;
renderer.y += 3;
if (!elementHandledElsewhere(cn, renderer, elementHandlers)) {
DrillForContent(cn, renderer, elementHandlers);
}
renderer.x = temp;
} else if (cn.nodeName === "BR") {
renderer.y += fragmentCSS["font-size"] * renderer.pdf.internal.scaleFactor;
renderer.addText("\u2028", clone(fragmentCSS));
} else {
if (!elementHandledElsewhere(cn, renderer, elementHandlers)) {
DrillForContent(cn, renderer, elementHandlers);
}
}
} else if (cn.nodeType === 3) {
var value = cn.nodeValue;
if (cn.nodeValue && cn.parentNode.nodeName === "LI") {
if (cn.parentNode.parentNode.nodeName === "OL") {
value = listCount++ + '. ' + value;
} else {
var fontSize = fragmentCSS["font-size"];
offsetX = (3 - fontSize * 0.75) * renderer.pdf.internal.scaleFactor;
offsetY = fontSize * 0.75 * renderer.pdf.internal.scaleFactor;
radius = fontSize * 1.74 / renderer.pdf.internal.scaleFactor;
cb = function (x, y) {
this.pdf.circle(x + offsetX, y + offsetY, radius, 'FD');
};
}
}
// Only add the text if the text node is in the body element
if (cn.ownerDocument.body.contains(cn)){
renderer.addText(value, fragmentCSS);
}
} else if (typeof cn === "string") {
renderer.addText(cn, fragmentCSS);
}
}
i++;
}
if (isBlock) {
return renderer.setBlockBoundary(cb);
}
};
images = {};
loadImgs = function (element, renderer, elementHandlers, cb) {
var imgs = element.getElementsByTagName('img'),
l = imgs.length, found_images,
x = 0;
function done() {
renderer.pdf.internal.events.publish('imagesLoaded');
cb(found_images);
}
function loadImage(url, width, height) {
if (!url)
return;
var img = new Image();
found_images = ++x;
img.crossOrigin = '';
img.onerror = img.onload = function () {
if(img.complete) {
//to support data urls in images, set width and height
//as those values are not recognized automatically
if (img.src.indexOf('data:image/') === 0) {
img.width = width || img.width || 0;
img.height = height || img.height || 0;
}
//if valid image add to known images array
if (img.width + img.height) {
var hash = renderer.pdf.sHashCode(url) || url;
images[hash] = images[hash] || img;
}
}
if(!--x) {
done();
}
};
img.src = url;
}
while (l--)
loadImage(imgs[l].getAttribute("src"),imgs[l].width,imgs[l].height);
return x || done();
};
checkForFooter = function (elem, renderer, elementHandlers) {
//check if we can found a