/** @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