UNPKG

blocktrail-sdk

Version:

BlockTrail's Developer Friendly API binding for NodeJS

528 lines (523 loc) 19.1 kB
/** @preserve * jsPDF fromHTML plugin. BETA stage. API subject to change. Needs browser, jQuery * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria * 2014 Diego Casorran, https://github.com/diegocr * * 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 DrillForContent, FontNameDB, FontStyleMap, FontWeightMap, GetCSS, PurgeWhiteSpace, Renderer, ResolveFont, ResolveUnitedNumber, UnitedNumberMap, elementHandledElsewhere, images, loadImgs, process, tableToJson; 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) { 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; 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) { 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 $e, css, tmp; $e = $(element); css = {}; tmp = void 0; css["font-family"] = ResolveFont($e.css("font-family")) || "times"; css["font-style"] = FontStyleMap[$e.css("font-style")] || "normal"; tmp = FontWeightMap[$e.css("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($e.css("font-size")) || 1; css["line-height"] = ResolveUnitedNumber($e.css("line-height")) || 1; css["display"] = ($e.css("display") === "inline" ? "inline" : "block"); if (css["display"] === "block") { css["margin-top"] = ResolveUnitedNumber($e.css("margin-top")) || 0; css["margin-bottom"] = ResolveUnitedNumber($e.css("margin-bottom")) || 0; css["padding-top"] = ResolveUnitedNumber($e.css("padding-top")) || 0; css["padding-bottom"] = ResolveUnitedNumber($e.css("padding-bottom")) || 0; css["margin-left"] = ResolveUnitedNumber($e.css("margin-left")) || 0; css["margin-right"] = ResolveUnitedNumber($e.css("margin-right")) || 0; css["padding-left"] = ResolveUnitedNumber($e.css("padding-left")) || 0; css["padding-right"] = ResolveUnitedNumber($e.css("padding-right")) || 0; } 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; data = []; headers = []; i = 0; table_with = table.clientWidth; while (i < table.rows[0].cells.length) { headers[i] = { name: table.rows[0].cells[i].innerHTML.toLowerCase().replace(RegExp("(\r\n|\n|\r)", "g"), "").replace(RegExp(" ", "g"), ""), prompt: table.rows[0].cells[i].innerHTML.toLowerCase().replace(RegExp("(\r\n|\n|\r)", "g"), ""), width: (table.rows[0].cells[i].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].innerHTML.replace(RegExp("(\r\n|\n|\r)", "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 }; DrillForContent = function(element, renderer, elementHandlers) { var cn, cns, fragmentCSS, i, isBlock, l, px2pt, table2json; 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") { if (cn.nodeType === 8 && cn.nodeName === "#comment") { if (cn.textContent.match("ADD_PAGE")) { renderer.pdf.addPage(); renderer.y = renderer.pdf.margins_doc.top; } } else if (cn.nodeType === 1 && !SkipNode[cn.nodeName]) { if (cn.nodeName === "IMG" && images[cn.getAttribute("src")]) { 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; } renderer.pdf.addImage(images[cn.getAttribute("src")], renderer.x, renderer.y, cn.width, cn.height); renderer.y += cn.height; } 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 (!elementHandledElsewhere(cn, renderer, elementHandlers)) { DrillForContent(cn, renderer, elementHandlers); } } } else if (cn.nodeType === 3) { renderer.addText(cn.nodeValue, fragmentCSS); } else if (typeof cn === "string") { renderer.addText(cn, fragmentCSS); } } i++; } if (isBlock) { return renderer.setBlockBoundary(); } }; images = {}; loadImgs = function(element, renderer, elementHandlers, cb) { var imgs = element.getElementsByTagName('img'), l = imgs.length, x = 0; function done() { DrillForContent(element, renderer, elementHandlers); cb(renderer.dispose()); } function loadImage(url) { if(!url) return; var img = new Image(); ++x;img.crossOrigin=''; img.onerror = img.onload = function() { if(img.complete && img.width + img.height) images[url] = images[url] || img; if(!--x) done(); }; img.src = url; } while(l--) loadImage(imgs[l].getAttribute("src")); cb = cb || function() {}; return x || done(); }; process = function(pdf, element, x, y, settings, callback) { var imgs, r; if (typeof element === "string") { element = (function(element) { var $frame, $hiddendiv, framename, visuallyhidden; framename = "jsPDFhtmlText" + Date.now().toString() + (Math.random() * 1000).toFixed(0); visuallyhidden = "position: absolute !important;" + "clip: rect(1px 1px 1px 1px); /* IE6, IE7 */" + "clip: rect(1px, 1px, 1px, 1px);" + "padding:0 !important;" + "border:0 !important;" + "height: 1px !important;" + "width: 1px !important; " + "top:auto;" + "left:-100px;" + "overflow: hidden;"; $hiddendiv = $("<div style=\"" + visuallyhidden + "\">" + "<iframe style=\"height:1px;width:1px\" name=\"" + framename + "\" />" + "</div>").appendTo(document.body); $frame = window.frames[framename]; return $($frame.document.body).html(element)[0]; })(element); } r = new Renderer(pdf, x, y, settings); imgs = loadImgs.call(this, element, r, settings.elementHandlers, callback); return r.dispose(); }; "use strict"; if (!String.prototype.trim) { String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); }; } if (!String.prototype.trimLeft) { String.prototype.trimLeft = function() { return this.replace(/^\s+/g, ""); }; } if (!String.prototype.trimRight) { String.prototype.trimRight = function() { return this.replace(/\s+$/g, ""); }; } Renderer.prototype.init = function() { this.paragraph = { text: [], style: [] }; return this.pdf.internal.write("q"); }; Renderer.prototype.dispose = function() { this.pdf.internal.write("Q"); return { x: this.x, y: this.y }; }; Renderer.prototype.splitFragmentsIntoLines = function(fragments, styles) { var currentLineLength, defaultFontSize, ff, fontMetrics, fontMetricsCache, fragment, fragmentChopped, fragmentLength, fragmentSpecificMetrics, fs, k, line, lines, maxLineLength, style; defaultFontSize = 12; k = this.pdf.internal.scaleFactor; fontMetricsCache = {}; ff = void 0; fs = void 0; fontMetrics = void 0; fragment = void 0; style = void 0; fragmentSpecificMetrics = void 0; fragmentLength = void 0; fragmentChopped = void 0; line = []; lines = [line]; currentLineLength = 0; maxLineLength = this.settings.width; while (fragments.length) { fragment = fragments.shift(); style = styles.shift(); if (fragment) { ff = style["font-family"]; fs = style["font-style"]; fontMetrics = fontMetricsCache[ff + fs]; if (!fontMetrics) { fontMetrics = this.pdf.internal.getFont(ff, fs).metadata.Unicode; fontMetricsCache[ff + fs] = fontMetrics; } fragmentSpecificMetrics = { widths: fontMetrics.widths, kerning: fontMetrics.kerning, fontSize: style["font-size"] * defaultFontSize, textIndent: currentLineLength }; fragmentLength = this.pdf.getStringUnitWidth(fragment, fragmentSpecificMetrics) * fragmentSpecificMetrics.fontSize / k; if (currentLineLength + fragmentLength > maxLineLength) { fragmentChopped = this.pdf.splitTextToSize(fragment, maxLineLength, fragmentSpecificMetrics); line.push([fragmentChopped.shift(), style]); while (fragmentChopped.length) { line = [[fragmentChopped.shift(), style]]; lines.push(line); } currentLineLength = this.pdf.getStringUnitWidth(line[0][0], fragmentSpecificMetrics) * fragmentSpecificMetrics.fontSize / k; } else { line.push([fragment, style]); currentLineLength += fragmentLength; } } } return lines; }; Renderer.prototype.RenderTextFragment = function(text, style) { var defaultFontSize, font; if (this.pdf.internal.pageSize.height - this.pdf.margins_doc.bottom < this.y + this.pdf.internal.getFontSize()) { this.pdf.internal.write("ET", "Q"); this.pdf.addPage(); this.y = this.pdf.margins_doc.top; this.pdf.internal.write("q", "BT", this.pdf.internal.getCoordinateString(this.x), this.pdf.internal.getVerticalCoordinateString(this.y), "Td"); } defaultFontSize = 12; font = this.pdf.internal.getFont(style["font-family"], style["font-style"]); return this.pdf.internal.write("/" + font.id, (defaultFontSize * style["font-size"]).toFixed(2), "Tf", "(" + this.pdf.internal.pdfEscape(text) + ") Tj"); }; Renderer.prototype.renderParagraph = function() { var blockstyle, defaultFontSize, fontToUnitRatio, fragments, i, l, line, lines, maxLineHeight, out, paragraphspacing_after, paragraphspacing_before, priorblockstype, styles; fragments = PurgeWhiteSpace(this.paragraph.text); styles = this.paragraph.style; blockstyle = this.paragraph.blockstyle; priorblockstype = this.paragraph.blockstyle || {}; this.paragraph = { text: [], style: [], blockstyle: {}, priorblockstyle: blockstyle }; if (!fragments.join("").trim()) { return; } lines = this.splitFragmentsIntoLines(fragments, styles); line = void 0; maxLineHeight = void 0; defaultFontSize = 12; fontToUnitRatio = defaultFontSize / this.pdf.internal.scaleFactor; paragraphspacing_before = (Math.max((blockstyle["margin-top"] || 0) - (priorblockstype["margin-bottom"] || 0), 0) + (blockstyle["padding-top"] || 0)) * fontToUnitRatio; paragraphspacing_after = ((blockstyle["margin-bottom"] || 0) + (blockstyle["padding-bottom"] || 0)) * fontToUnitRatio; out = this.pdf.internal.write; i = void 0; l = void 0; this.y += paragraphspacing_before; out("q", "BT", this.pdf.internal.getCoordinateString(this.x), this.pdf.internal.getVerticalCoordinateString(this.y), "Td"); while (lines.length) { line = lines.shift(); maxLineHeight = 0; i = 0; l = line.length; while (i !== l) { if (line[i][0].trim()) { maxLineHeight = Math.max(maxLineHeight, line[i][1]["line-height"], line[i][1]["font-size"]); } i++; } out(0, (-1 * defaultFontSize * maxLineHeight).toFixed(2), "Td"); i = 0; l = line.length; while (i !== l) { if (line[i][0]) { this.RenderTextFragment(line[i][0], line[i][1]); } i++; } this.y += maxLineHeight * fontToUnitRatio; } out("ET", "Q"); return this.y += paragraphspacing_after; }; Renderer.prototype.setBlockBoundary = function() { return this.renderParagraph(); }; Renderer.prototype.setBlockStyle = function(css) { return this.paragraph.blockstyle = css; }; Renderer.prototype.addText = function(text, css) { this.paragraph.text.push(text); return this.paragraph.style.push(css); }; FontNameDB = { helvetica: "helvetica", "sans-serif": "helvetica", serif: "times", times: "times", "times new roman": "times", monospace: "courier", courier: "courier" }; FontWeightMap = { 100: "normal", 200: "normal", 300: "normal", 400: "normal", 500: "bold", 600: "bold", 700: "bold", 800: "bold", 900: "bold", normal: "normal", bold: "bold", bolder: "bold", lighter: "normal" }; FontStyleMap = { normal: "normal", italic: "italic", oblique: "italic" }; UnitedNumberMap = { normal: 1 /* Converts HTML-formatted text into formatted PDF text. Notes: 2012-07-18 Plugin relies on having browser, DOM around. The HTML is pushed into dom and traversed. Plugin relies on jQuery for CSS extraction. Targeting HTML output from Markdown templating, which is a very simple markup - div, span, em, strong, p. No br-based paragraph separation supported explicitly (but still may work.) Images, tables are NOT supported. @public @function @param HTML {String or DOM Element} HTML-formatted text, or pointer to DOM element that is to be rendered into PDF. @param x {Number} starting X coordinate in jsPDF instance's declared units. @param y {Number} starting Y coordinate in jsPDF instance's declared units. @param settings {Object} Additional / optional variables controlling parsing, rendering. @returns {Object} jsPDF instance */ }; return jsPDFAPI.fromHTML = function(HTML, x, y, settings, callback, margins) { "use strict"; this.margins_doc = margins || {top:0,bottom:0}; return process(this, HTML, x, y, settings, callback); }; })(jsPDF.API);