From da59adc820dc192bb51a7c9aa394f9bf2f895b58 Mon Sep 17 00:00:00 2001 From: wangxiao1427 <464339371@qq.com> Date: Tue, 24 Jul 2018 09:54:53 +0800 Subject: [PATCH 1/5] init lib --- libs/jspdf/dist/jspdf.debug.js | 20722 +++++++++++++++++++++++++++++++ 1 file changed, 20722 insertions(+) create mode 100644 libs/jspdf/dist/jspdf.debug.js diff --git a/libs/jspdf/dist/jspdf.debug.js b/libs/jspdf/dist/jspdf.debug.js new file mode 100644 index 000000000..b92f65180 --- /dev/null +++ b/libs/jspdf/dist/jspdf.debug.js @@ -0,0 +1,20722 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.jsPDF = factory()); +}(this, (function () { 'use strict'; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + + /** @preserve + * jsPDF - PDF Document creation from JavaScript + * Version 1.4.0 Built on 2018-05-21T16:49:17.000Z + * CommitID 48c1917315 + * + * Copyright (c) 2010-2016 James Hall , https://github.com/MrRio/jsPDF + * 2010 Aaron Spike, https://github.com/acspike + * 2012 Willow Systems Corporation, willow-systems.com + * 2012 Pablo Hess, https://github.com/pablohess + * 2012 Florian Jenett, https://github.com/fjenett + * 2013 Warren Weckesser, https://github.com/warrenweckesser + * 2013 Youssef Beddad, https://github.com/lifof + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2013 Stefan Slonevskiy, https://github.com/stefslon + * 2013 Jeremy Morel, https://github.com/jmorel + * 2013 Christoph Hartmann, https://github.com/chris-rock + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Makes, https://github.com/dollaruw + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 Steven Spungin, https://github.com/Flamenco + * 2014 Kenneth Glassey, https://github.com/Gavvers + * + * Licensed under the MIT License + * + * Contributor(s): + * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, + * kim3er, mfo, alnorth, Flamenco + */ + + /** + * Creates new jsPDF document object instance. + * @name jsPDF + * @class + * @param orientation {String/Object} Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" (Default), "l")
+ * Can also be an options object. + * @param unit {String} Measurement unit to be used when coordinates are specified.
+ * Possible values are "pt" (points), "mm" (Default), "cm", "in" or "px". + * @param format {String/Array} The format of the first page. Can be
+ * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array , e.g. [595.28, 841.89] + * @returns {jsPDF} + * @description + * If the first parameter (orientation) is an object, it will be interpreted as an object of named parameters + * ``` + * { + * orientation: 'p', + * unit: 'mm', + * format: 'a4', + * hotfixes: [] // an array of hotfix strings to enable + * } + * ``` + */ + var jsPDF = function (global) { + + var pdfVersion = '1.3', + pageFormats = { // Size in pt of various paper formats + 'a0': [2383.94, 3370.39], + 'a1': [1683.78, 2383.94], + 'a2': [1190.55, 1683.78], + 'a3': [841.89, 1190.55], + 'a4': [595.28, 841.89], + 'a5': [419.53, 595.28], + 'a6': [297.64, 419.53], + 'a7': [209.76, 297.64], + 'a8': [147.40, 209.76], + 'a9': [104.88, 147.40], + 'a10': [73.70, 104.88], + 'b0': [2834.65, 4008.19], + 'b1': [2004.09, 2834.65], + 'b2': [1417.32, 2004.09], + 'b3': [1000.63, 1417.32], + 'b4': [708.66, 1000.63], + 'b5': [498.90, 708.66], + 'b6': [354.33, 498.90], + 'b7': [249.45, 354.33], + 'b8': [175.75, 249.45], + 'b9': [124.72, 175.75], + 'b10': [87.87, 124.72], + 'c0': [2599.37, 3676.54], + 'c1': [1836.85, 2599.37], + 'c2': [1298.27, 1836.85], + 'c3': [918.43, 1298.27], + 'c4': [649.13, 918.43], + 'c5': [459.21, 649.13], + 'c6': [323.15, 459.21], + 'c7': [229.61, 323.15], + 'c8': [161.57, 229.61], + 'c9': [113.39, 161.57], + 'c10': [79.37, 113.39], + 'dl': [311.81, 623.62], + 'letter': [612, 792], + 'government-letter': [576, 756], + 'legal': [612, 1008], + 'junior-legal': [576, 360], + 'ledger': [1224, 792], + 'tabloid': [792, 1224], + 'credit-card': [153, 243] + }; + + /** + * jsPDF's Internal PubSub Implementation. + * See mrrio.github.io/jsPDF/doc/symbols/PubSub.html + * Backward compatible rewritten on 2014 by + * Diego Casorran, https://github.com/diegocr + * + * @class + * @name PubSub + * @ignore This should not be in the public docs. + */ + function PubSub(context) { + var topics = {}; + + this.subscribe = function (topic, callback, once) { + if (typeof callback !== 'function') { + return false; + } + + if (!topics.hasOwnProperty(topic)) { + topics[topic] = {}; + } + + var id = Math.random().toString(35); + topics[topic][id] = [callback, !!once]; + + return id; + }; + + this.unsubscribe = function (token) { + for (var topic in topics) { + if (topics[topic][token]) { + delete topics[topic][token]; + return true; + } + } + return false; + }; + + this.publish = function (topic) { + if (topics.hasOwnProperty(topic)) { + var args = Array.prototype.slice.call(arguments, 1), + idr = []; + + for (var id in topics[topic]) { + var sub = topics[topic][id]; + try { + sub[0].apply(context, args); + } catch (ex) { + if (global.console) { + console.error('jsPDF PubSub Error', ex.message, ex); + } + } + if (sub[1]) idr.push(id); + } + if (idr.length) idr.forEach(this.unsubscribe); + } + }; + } + + /** + * @constructor + * @private + */ + function jsPDF(orientation, unit, format, compressPdf) { + var options = {}; + + if ((typeof orientation === 'undefined' ? 'undefined' : _typeof(orientation)) === 'object') { + options = orientation; + + orientation = options.orientation; + unit = options.unit || unit; + format = options.format || format; + compressPdf = options.compress || options.compressPdf || compressPdf; + } + + // Default options + unit = unit || 'mm'; + format = format || 'a4'; + orientation = ('' + (orientation || 'P')).toLowerCase(); + + var format_as_string = ('' + format).toLowerCase(), + compress = !!compressPdf && typeof Uint8Array === 'function', + textColor = options.textColor || '0 g', + drawColor = options.drawColor || '0 G', + activeFontSize = options.fontSize || 16, + activeCharSpace = options.charSpace || 0, + R2L = options.R2L || false, + lineHeightProportion = options.lineHeight || 1.15, + lineWidth = options.lineWidth || 0.200025, + // 2mm + fileId = '00000000000000000000000000000000', + objectNumber = 2, + // 'n' Current object number + outToPages = !1, + // switches where out() prints. outToPages true = push to pages obj. outToPages false = doc builder content + offsets = [], + // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes. + fonts = {}, + // collection of font objects, where key is fontKey - a dynamically created label for a given font. + fontmap = {}, + // mapping structure fontName > fontStyle > font key - performance layer. See addFont() + activeFontKey, + // will be string representing the KEY of the font as combination of fontName + fontStyle + k, + // Scale factor + tmp, + page = 0, + currentPage, + pages = [], + pagesContext = [], + // same index as pages and pagedim + pagedim = [], + content = [], + additionalObjects = [], + lineCapID = 0, + lineJoinID = 0, + content_length = 0, + pageWidth, + pageHeight, + pageMode, + zoomMode, + layoutMode, + creationDate, + documentProperties = { + 'title': '', + 'subject': '', + 'author': '', + 'keywords': '', + 'creator': '' + }, + API = {}, + events = new PubSub(API), + hotfixes = options.hotfixes || [], + + + ///////////////////// + // Private functions + ///////////////////// + generateColorString = function generateColorString(options) { + var color; + + var ch1 = options.ch1; + var ch2 = options.ch2; + var ch3 = options.ch3; + var ch4 = options.ch4; + var precision = options.precision; + var letterArray = options.pdfColorType === "draw" ? ['G', 'RG', 'K'] : ['g', 'rg', 'k']; + + var cssColorNames = { "aliceblue": "#f0f8ff", "antiquewhite": "#faebd7", "aqua": "#00ffff", "aquamarine": "#7fffd4", "azure": "#f0ffff", + "beige": "#f5f5dc", "bisque": "#ffe4c4", "black": "#000000", "blanchedalmond": "#ffebcd", "blue": "#0000ff", "blueviolet": "#8a2be2", "brown": "#a52a2a", "burlywood": "#deb887", + "cadetblue": "#5f9ea0", "chartreuse": "#7fff00", "chocolate": "#d2691e", "coral": "#ff7f50", "cornflowerblue": "#6495ed", "cornsilk": "#fff8dc", "crimson": "#dc143c", "cyan": "#00ffff", + "darkblue": "#00008b", "darkcyan": "#008b8b", "darkgoldenrod": "#b8860b", "darkgray": "#a9a9a9", "darkgreen": "#006400", "darkkhaki": "#bdb76b", "darkmagenta": "#8b008b", "darkolivegreen": "#556b2f", + "darkorange": "#ff8c00", "darkorchid": "#9932cc", "darkred": "#8b0000", "darksalmon": "#e9967a", "darkseagreen": "#8fbc8f", "darkslateblue": "#483d8b", "darkslategray": "#2f4f4f", "darkturquoise": "#00ced1", + "darkviolet": "#9400d3", "deeppink": "#ff1493", "deepskyblue": "#00bfff", "dimgray": "#696969", "dodgerblue": "#1e90ff", + "firebrick": "#b22222", "floralwhite": "#fffaf0", "forestgreen": "#228b22", "fuchsia": "#ff00ff", + "gainsboro": "#dcdcdc", "ghostwhite": "#f8f8ff", "gold": "#ffd700", "goldenrod": "#daa520", "gray": "#808080", "green": "#008000", "greenyellow": "#adff2f", + "honeydew": "#f0fff0", "hotpink": "#ff69b4", + "indianred ": "#cd5c5c", "indigo": "#4b0082", "ivory": "#fffff0", "khaki": "#f0e68c", + "lavender": "#e6e6fa", "lavenderblush": "#fff0f5", "lawngreen": "#7cfc00", "lemonchiffon": "#fffacd", "lightblue": "#add8e6", "lightcoral": "#f08080", "lightcyan": "#e0ffff", "lightgoldenrodyellow": "#fafad2", + "lightgrey": "#d3d3d3", "lightgreen": "#90ee90", "lightpink": "#ffb6c1", "lightsalmon": "#ffa07a", "lightseagreen": "#20b2aa", "lightskyblue": "#87cefa", "lightslategray": "#778899", "lightsteelblue": "#b0c4de", "lightyellow": "#ffffe0", "lime": "#00ff00", "limegreen": "#32cd32", "linen": "#faf0e6", + "magenta": "#ff00ff", "maroon": "#800000", "mediumaquamarine": "#66cdaa", "mediumblue": "#0000cd", "mediumorchid": "#ba55d3", "mediumpurple": "#9370d8", "mediumseagreen": "#3cb371", "mediumslateblue": "#7b68ee", "mediumspringgreen": "#00fa9a", "mediumturquoise": "#48d1cc", "mediumvioletred": "#c71585", "midnightblue": "#191970", "mintcream": "#f5fffa", "mistyrose": "#ffe4e1", "moccasin": "#ffe4b5", + "navajowhite": "#ffdead", "navy": "#000080", + "oldlace": "#fdf5e6", "olive": "#808000", "olivedrab": "#6b8e23", "orange": "#ffa500", "orangered": "#ff4500", "orchid": "#da70d6", + "palegoldenrod": "#eee8aa", "palegreen": "#98fb98", "paleturquoise": "#afeeee", "palevioletred": "#d87093", "papayawhip": "#ffefd5", "peachpuff": "#ffdab9", "peru": "#cd853f", "pink": "#ffc0cb", "plum": "#dda0dd", "powderblue": "#b0e0e6", "purple": "#800080", + "rebeccapurple": "#663399", "red": "#ff0000", "rosybrown": "#bc8f8f", "royalblue": "#4169e1", + "saddlebrown": "#8b4513", "salmon": "#fa8072", "sandybrown": "#f4a460", "seagreen": "#2e8b57", "seashell": "#fff5ee", "sienna": "#a0522d", "silver": "#c0c0c0", "skyblue": "#87ceeb", "slateblue": "#6a5acd", "slategray": "#708090", "snow": "#fffafa", "springgreen": "#00ff7f", "steelblue": "#4682b4", + "tan": "#d2b48c", "teal": "#008080", "thistle": "#d8bfd8", "tomato": "#ff6347", "turquoise": "#40e0d0", + "violet": "#ee82ee", + "wheat": "#f5deb3", "white": "#ffffff", "whitesmoke": "#f5f5f5", + "yellow": "#ffff00", "yellowgreen": "#9acd32" }; + + if (typeof ch1 === "string" && cssColorNames.hasOwnProperty(ch1)) { + ch1 = cssColorNames[ch1]; + } + + //convert short rgb to long form + if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{3}$/.test(ch1)) { + ch1 = '#' + ch1[1] + ch1[1] + ch1[2] + ch1[2] + ch1[3] + ch1[3]; + } + + if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{6}$/.test(ch1)) { + var hex = parseInt(ch1.substr(1), 16); + ch1 = hex >> 16 & 255; + ch2 = hex >> 8 & 255; + ch3 = hex & 255; + } + + if (typeof ch2 === "undefined" || typeof ch4 === "undefined" && ch1 === ch2 && ch2 === ch3) { + // Gray color space. + if (typeof ch1 === "string") { + color = ch1 + " " + letterArray[0]; + } else { + switch (options.precision) { + case 2: + color = f2(ch1 / 255) + " " + letterArray[0]; + break; + case 3: + default: + color = f3(ch1 / 255) + " " + letterArray[0]; + } + } + } else if (typeof ch4 === "undefined" || (typeof ch4 === 'undefined' ? 'undefined' : _typeof(ch4)) === "object") { + // assume RGB + if (typeof ch1 === "string") { + color = [ch1, ch2, ch3, letterArray[1]].join(" "); + } else { + switch (options.precision) { + case 2: + color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), letterArray[1]].join(" "); + break; + default: + case 3: + color = [f3(ch1 / 255), f3(ch2 / 255), f3(ch3 / 255), letterArray[1]].join(" "); + } + } + // assume RGBA + if (ch4 && ch4.a === 0) { + //TODO Implement transparency. + //WORKAROUND use white for now + color = ['255', '255', '255', letterArray[1]].join(" "); + } + } else { + // assume CMYK + if (typeof ch1 === 'string') { + color = [ch1, ch2, ch3, ch4, letterArray[2]].join(" "); + } else { + switch (options.precision) { + case 2: + color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), letterArray[2]].join(" "); + break; + case 3: + default: + color = [f3(ch1), f3(ch2), f3(ch3), f3(ch4), letterArray[2]].join(" "); + } + } + } + return color; + }, + convertDateToPDFDate = function convertDateToPDFDate(parmDate) { + var padd2 = function padd2(number) { + return ('0' + parseInt(number)).slice(-2); + }; + var result = ''; + var tzoffset = parmDate.getTimezoneOffset(), + tzsign = tzoffset < 0 ? '+' : '-', + tzhour = Math.floor(Math.abs(tzoffset / 60)), + tzmin = Math.abs(tzoffset % 60), + timeZoneString = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join(''); + + result = ['D:', parmDate.getFullYear(), padd2(parmDate.getMonth() + 1), padd2(parmDate.getDate()), padd2(parmDate.getHours()), padd2(parmDate.getMinutes()), padd2(parmDate.getSeconds()), timeZoneString].join(''); + return result; + }, + convertPDFDateToDate = function convertPDFDateToDate(parmPDFDate) { + var year = parseInt(parmPDFDate.substr(2, 4), 10); + var month = parseInt(parmPDFDate.substr(6, 2), 10) - 1; + var date = parseInt(parmPDFDate.substr(8, 2), 10); + var hour = parseInt(parmPDFDate.substr(10, 2), 10); + var minutes = parseInt(parmPDFDate.substr(12, 2), 10); + var seconds = parseInt(parmPDFDate.substr(14, 2), 10); + var timeZoneHour = parseInt(parmPDFDate.substr(16, 2), 10); + var timeZoneMinutes = parseInt(parmPDFDate.substr(20, 2), 10); + + var resultingDate = new Date(year, month, date, hour, minutes, seconds, 0); + return resultingDate; + }, + setCreationDate = function setCreationDate(date) { + var tmpCreationDateString; + var regexPDFCreationDate = /^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|\-0[0-9]|\-1[0-1])\'(0[0-9]|[1-5][0-9])\'?$/; + if ((typeof date === 'undefined' ? 'undefined' : _typeof(date)) === undefined) { + date = new Date(); + } + + if ((typeof date === 'undefined' ? 'undefined' : _typeof(date)) === "object" && Object.prototype.toString.call(date) === "[object Date]") { + tmpCreationDateString = convertDateToPDFDate(date); + } else if (regexPDFCreationDate.test(date)) { + tmpCreationDateString = date; + } else { + tmpCreationDateString = convertDateToPDFDate(new Date()); + } + creationDate = tmpCreationDateString; + return creationDate; + }, + getCreationDate = function getCreationDate(type) { + var result = creationDate; + if (type === "jsDate") { + result = convertPDFDateToDate(creationDate); + } + return result; + }, + setFileId = function setFileId(value) { + value = value || "12345678901234567890123456789012".split('').map(function () { + return "ABCDEF0123456789".charAt(Math.floor(Math.random() * 16)); + }).join(''); + fileId = value; + return fileId; + }, + getFileId = function getFileId() { + return fileId; + }, + f2 = function f2(number) { + return number.toFixed(2); // Ie, %.2f + }, + f3 = function f3(number) { + return number.toFixed(3); // Ie, %.3f + }, + out = function out(string) { + string = typeof string === "string" ? string : string.toString(); + if (outToPages) { + /* set by beginPage */ + pages[currentPage].push(string); + } else { + // +1 for '\n' that will be used to join 'content' + content_length += string.length + 1; + content.push(string); + } + }, + newObject = function newObject() { + // Begin a new object + objectNumber++; + offsets[objectNumber] = content_length; + out(objectNumber + ' 0 obj'); + return objectNumber; + }, + + // Does not output the object until after the pages have been output. + // Returns an object containing the objectId and content. + // All pages have been added so the object ID can be estimated to start right after. + // This does not modify the current objectNumber; It must be updated after the newObjects are output. + newAdditionalObject = function newAdditionalObject() { + var objId = pages.length * 2 + 1; + objId += additionalObjects.length; + var obj = { + objId: objId, + content: '' + }; + additionalObjects.push(obj); + return obj; + }, + + // Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data + newObjectDeferred = function newObjectDeferred() { + objectNumber++; + offsets[objectNumber] = function () { + return content_length; + }; + return objectNumber; + }, + newObjectDeferredBegin = function newObjectDeferredBegin(oid) { + offsets[oid] = content_length; + }, + putStream = function putStream(str) { + out('stream'); + out(str); + out('endstream'); + }, + putPages = function putPages() { + var n, + p, + arr, + i, + deflater, + adler32, + adler32cs, + wPt, + hPt, + pageObjectNumbers = []; + + adler32cs = global.adler32cs || jsPDF.API.adler32cs; + if (compress && typeof adler32cs === 'undefined') { + compress = false; + } + + // outToPages = false as set in endDocument(). out() writes to content. + + for (n = 1; n <= page; n++) { + pageObjectNumbers.push(newObject()); + wPt = (pageWidth = pagedim[n].width) * k; + hPt = (pageHeight = pagedim[n].height) * k; + out('<>'); + out('endobj'); + + // Page content + p = pages[n].join('\n'); + newObject(); + if (compress) { + arr = []; + i = p.length; + while (i--) { + arr[i] = p.charCodeAt(i); + } + adler32 = adler32cs.from(p); + deflater = new Deflater(6); + deflater.append(new Uint8Array(arr)); + p = deflater.flush(); + arr = new Uint8Array(p.length + 6); + arr.set(new Uint8Array([120, 156])), arr.set(p, 2); + arr.set(new Uint8Array([adler32 & 0xFF, adler32 >> 8 & 0xFF, adler32 >> 16 & 0xFF, adler32 >> 24 & 0xFF]), p.length + 2); + p = String.fromCharCode.apply(null, arr); + out('<>'); + } else { + out('<>'); + } + putStream(p); + out('endobj'); + } + offsets[1] = content_length; + out('1 0 obj'); + out('<>'); + out('endobj'); + events.publish('postPutPages'); + }, + putFont = function putFont(font) { + + events.publish('putFont', { + font: font, + out: out, + newObject: newObject + }); + if (font.isAlreadyPutted !== true) { + font.objectNumber = newObject(); + out('<<'); + out('/Type /Font'); + out('/BaseFont /' + font.postScriptName); + out('/Subtype /Type1'); + if (typeof font.encoding === 'string') { + out('/Encoding /' + font.encoding); + } + out('/FirstChar 32'); + out('/LastChar 255'); + out('>>'); + out('endobj'); + } + }, + putFonts = function putFonts() { + for (var fontKey in fonts) { + if (fonts.hasOwnProperty(fontKey)) { + putFont(fonts[fontKey]); + } + } + }, + putXobjectDict = function putXobjectDict() { + // Loop through images, or other data objects + events.publish('putXobjectDict'); + }, + putResourceDictionary = function putResourceDictionary() { + out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); + out('/Font <<'); + + // Do this for each font, the '1' bit is the index of the font + for (var fontKey in fonts) { + if (fonts.hasOwnProperty(fontKey)) { + out('/' + fontKey + ' ' + fonts[fontKey].objectNumber + ' 0 R'); + } + } + out('>>'); + out('/XObject <<'); + putXobjectDict(); + out('>>'); + }, + putResources = function putResources() { + putFonts(); + events.publish('putResources'); + // Resource dictionary + offsets[2] = content_length; + out('2 0 obj'); + out('<<'); + putResourceDictionary(); + out('>>'); + out('endobj'); + events.publish('postPutResources'); + }, + putAdditionalObjects = function putAdditionalObjects() { + events.publish('putAdditionalObjects'); + for (var i = 0; i < additionalObjects.length; i++) { + var obj = additionalObjects[i]; + offsets[obj.objId] = content_length; + out(obj.objId + ' 0 obj'); + out(obj.content); out('endobj'); + } + objectNumber += additionalObjects.length; + events.publish('postPutAdditionalObjects'); + }, + addToFontDictionary = function addToFontDictionary(fontKey, fontName, fontStyle) { + // this is mapping structure for quick font key lookup. + // returns the KEY of the font (ex: "F1") for a given + // pair of font name and type (ex: "Arial". "Italic") + if (!fontmap.hasOwnProperty(fontName)) { + fontmap[fontName] = {}; + } + fontmap[fontName][fontStyle] = fontKey; + }, + + /** + * FontObject describes a particular font as member of an instnace of jsPDF + * + * It's a collection of properties like 'id' (to be used in PDF stream), + * 'fontName' (font's family name), 'fontStyle' (font's style variant label) + * + * @class + * @public + * @property id {String} PDF-document-instance-specific label assinged to the font. + * @property postScriptName {String} PDF specification full name for the font + * @property encoding {Object} Encoding_name-to-Font_metrics_object mapping. + * @name FontObject + * @ignore This should not be in the public docs. + */ + addFont = function addFont(postScriptName, fontName, fontStyle, encoding) { + var fontKey = 'F' + (Object.keys(fonts).length + 1).toString(10), + + // This is FontObject + font = fonts[fontKey] = { + 'id': fontKey, + 'postScriptName': postScriptName, + 'fontName': fontName, + 'fontStyle': fontStyle, + 'encoding': encoding, + 'metadata': {} + }; + addToFontDictionary(fontKey, fontName, fontStyle); + events.publish('addFont', font); + + return fontKey; + }, + addFonts = function addFonts() { + + var HELVETICA = "helvetica", + TIMES = "times", + COURIER = "courier", + NORMAL = "normal", + BOLD = "bold", + ITALIC = "italic", + BOLD_ITALIC = "bolditalic", + ZAPF = "zapfdingbats", + SYMBOL = "symbol", + standardFonts = [['Helvetica', HELVETICA, NORMAL, 'WinAnsiEncoding'], ['Helvetica-Bold', HELVETICA, BOLD, 'WinAnsiEncoding'], ['Helvetica-Oblique', HELVETICA, ITALIC, 'WinAnsiEncoding'], ['Helvetica-BoldOblique', HELVETICA, BOLD_ITALIC, 'WinAnsiEncoding'], ['Courier', COURIER, NORMAL, 'WinAnsiEncoding'], ['Courier-Bold', COURIER, BOLD, 'WinAnsiEncoding'], ['Courier-Oblique', COURIER, ITALIC, 'WinAnsiEncoding'], ['Courier-BoldOblique', COURIER, BOLD_ITALIC, 'WinAnsiEncoding'], ['Times-Roman', TIMES, NORMAL, 'WinAnsiEncoding'], ['Times-Bold', TIMES, BOLD, 'WinAnsiEncoding'], ['Times-Italic', TIMES, ITALIC, 'WinAnsiEncoding'], ['Times-BoldItalic', TIMES, BOLD_ITALIC, 'WinAnsiEncoding'], ['ZapfDingbats', ZAPF, NORMAL, null], ['Symbol', SYMBOL, NORMAL, null]]; + + for (var i = 0, l = standardFonts.length; i < l; i++) { + var fontKey = addFont(standardFonts[i][0], standardFonts[i][1], standardFonts[i][2], standardFonts[i][3]); + + // adding aliases for standard fonts, this time matching the capitalization + var parts = standardFonts[i][0].split('-'); + addToFontDictionary(fontKey, parts[0], parts[1] || ''); + } + events.publish('addFonts', { + fonts: fonts, + dictionary: fontmap + }); + }, + SAFE = function __safeCall(fn) { + fn.foo = function __safeCallWrapper() { + try { + return fn.apply(this, arguments); + } catch (e) { + var stack = e.stack || ''; + if (~stack.indexOf(' at ')) stack = stack.split(" at ")[1]; + var m = "Error in function " + stack.split("\n")[0].split('<')[0] + ": " + e.message; + if (global.console) { + global.console.error(m, e); + if (global.alert) alert(m); + } else { + throw new Error(m); + } + } + }; + fn.foo.bar = fn; + return fn.foo; + }, + to8bitStream = function to8bitStream(text, flags) { + /** + * PDF 1.3 spec: + * "For text strings encoded in Unicode, the first two bytes must be 254 followed by + * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts + * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely + * to be a meaningful beginning of a word or phrase.) The remainder of the + * string consists of Unicode character codes, according to the UTF-16 encoding + * specified in the Unicode standard, version 2.0. Commonly used Unicode values + * are represented as 2 bytes per character, with the high-order byte appearing first + * in the string." + * + * In other words, if there are chars in a string with char code above 255, we + * recode the string to UCS2 BE - string doubles in length and BOM is prepended. + * + * HOWEVER! + * Actual *content* (body) text (as opposed to strings used in document properties etc) + * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID) + * + * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have + * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could + * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode + * code page. There, however, all characters in the stream are treated as GIDs, + * including BOM, which is the reason we need to skip BOM in content text (i.e. that + * that is tied to a font). + * + * To signal this "special" PDFEscape / to8bitStream handling mode, + * API.text() function sets (unless you overwrite it with manual values + * given to API.text(.., flags) ) + * flags.autoencode = true + * flags.noBOM = true + * + * =================================================================================== + * `flags` properties relied upon: + * .sourceEncoding = string with encoding label. + * "Unicode" by default. = encoding of the incoming text. + * pass some non-existing encoding name + * (ex: 'Do not touch my strings! I know what I am doing.') + * to make encoding code skip the encoding step. + * .outputEncoding = Either valid PDF encoding name + * (must be supported by jsPDF font metrics, otherwise no encoding) + * or a JS object, where key = sourceCharCode, value = outputCharCode + * missing keys will be treated as: sourceCharCode === outputCharCode + * .noBOM + * See comment higher above for explanation for why this is important + * .autoencode + * See comment higher above for explanation for why this is important + */ + + var i, l, sourceEncoding, encodingBlock, outputEncoding, newtext, isUnicode, ch, bch; + + flags = flags || {}; + sourceEncoding = flags.sourceEncoding || 'Unicode'; + outputEncoding = flags.outputEncoding; + + // This 'encoding' section relies on font metrics format + // attached to font objects by, among others, + // "Willow Systems' standard_font_metrics plugin" + // see jspdf.plugin.standard_font_metrics.js for format + // of the font.metadata.encoding Object. + // It should be something like + // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}} + // .widths = {0:width, code:width, ..., 'fof':divisor} + // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...} + if ((flags.autoencode || outputEncoding) && fonts[activeFontKey].metadata && fonts[activeFontKey].metadata[sourceEncoding] && fonts[activeFontKey].metadata[sourceEncoding].encoding) { + encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding; + + // each font has default encoding. Some have it clearly defined. + if (!outputEncoding && fonts[activeFontKey].encoding) { + outputEncoding = fonts[activeFontKey].encoding; + } + + // Hmmm, the above did not work? Let's try again, in different place. + if (!outputEncoding && encodingBlock.codePages) { + outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default + } + + if (typeof outputEncoding === 'string') { + outputEncoding = encodingBlock[outputEncoding]; + } + // we want output encoding to be a JS Object, where + // key = sourceEncoding's character code and + // value = outputEncoding's character code. + if (outputEncoding) { + isUnicode = false; + newtext = []; + for (i = 0, l = text.length; i < l; i++) { + ch = outputEncoding[text.charCodeAt(i)]; + if (ch) { + newtext.push(String.fromCharCode(ch)); + } else { + newtext.push(text[i]); + } + + // since we are looping over chars anyway, might as well + // check for residual unicodeness + if (newtext[i].charCodeAt(0) >> 8) { + /* more than 255 */ + isUnicode = true; + } + } + text = newtext.join(''); + } + } + + i = text.length; + // isUnicode may be set to false above. Hence the triple-equal to undefined + while (isUnicode === undefined && i !== 0) { + if (text.charCodeAt(i - 1) >> 8) { + /* more than 255 */ + isUnicode = true; + } + i--; + } + if (!isUnicode) { + return text; + } + + newtext = flags.noBOM ? [] : [254, 255]; + for (i = 0, l = text.length; i < l; i++) { + ch = text.charCodeAt(i); + bch = ch >> 8; // divide by 256 + if (bch >> 8) { + /* something left after dividing by 256 second time */ + throw new Error("Character at position " + i + " of string '" + text + "' exceeds 16bits. Cannot be encoded into UCS-2 BE"); + } + newtext.push(bch); + newtext.push(ch - (bch << 8)); + } + return String.fromCharCode.apply(undefined, newtext); + }, + pdfEscape = function pdfEscape(text, flags) { + /** + * Replace '/', '(', and ')' with pdf-safe versions + * + * Doing to8bitStream does NOT make this PDF display unicode text. For that + * we also need to reference a unicode font and embed it - royal pain in the rear. + * + * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars, + * which JavaScript Strings are happy to provide. So, while we still cannot display + * 2-byte characters property, at least CONDITIONALLY converting (entire string containing) + * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF + * is still parseable. + * This will allow immediate support for unicode in document properties strings. + */ + return to8bitStream(text, flags).replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)'); + }, + putInfo = function putInfo() { + out('/Producer (jsPDF ' + jsPDF.version + ')'); + for (var key in documentProperties) { + if (documentProperties.hasOwnProperty(key) && documentProperties[key]) { + out('/' + key.substr(0, 1).toUpperCase() + key.substr(1) + ' (' + pdfEscape(documentProperties[key]) + ')'); + } + } + out('/CreationDate (' + creationDate + ')'); + }, + putCatalog = function putCatalog() { + out('/Type /Catalog'); + out('/Pages 1 0 R'); + // PDF13ref Section 7.2.1 + if (!zoomMode) zoomMode = 'fullwidth'; + switch (zoomMode) { + case 'fullwidth': + out('/OpenAction [3 0 R /FitH null]'); + break; + case 'fullheight': + out('/OpenAction [3 0 R /FitV null]'); + break; + case 'fullpage': + out('/OpenAction [3 0 R /Fit]'); + break; + case 'original': + out('/OpenAction [3 0 R /XYZ null null 1]'); + break; + default: + var pcn = '' + zoomMode; + if (pcn.substr(pcn.length - 1) === '%') zoomMode = parseInt(zoomMode) / 100; + if (typeof zoomMode === 'number') { + out('/OpenAction [3 0 R /XYZ null null ' + f2(zoomMode) + ']'); + } + } + if (!layoutMode) layoutMode = 'continuous'; + switch (layoutMode) { + case 'continuous': + out('/PageLayout /OneColumn'); + break; + case 'single': + out('/PageLayout /SinglePage'); + break; + case 'two': + case 'twoleft': + out('/PageLayout /TwoColumnLeft'); + break; + case 'tworight': + out('/PageLayout /TwoColumnRight'); + break; + } + if (pageMode) { + /** + * A name object specifying how the document should be displayed when opened: + * UseNone : Neither document outline nor thumbnail images visible -- DEFAULT + * UseOutlines : Document outline visible + * UseThumbs : Thumbnail images visible + * FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible + */ + out('/PageMode /' + pageMode); + } + events.publish('putCatalog'); + }, + putTrailer = function putTrailer() { + out('/Size ' + (objectNumber + 1)); + out('/Root ' + objectNumber + ' 0 R'); + out('/Info ' + (objectNumber - 1) + ' 0 R'); + out("/ID [ <" + fileId + "> <" + fileId + "> ]"); + }, + beginPage = function beginPage(width, height) { + // Dimensions are stored as user units and converted to points on output + var orientation = typeof height === 'string' && height.toLowerCase(); + if (typeof width === 'string') { + var format = width.toLowerCase(); + if (pageFormats.hasOwnProperty(format)) { + width = pageFormats[format][0] / k; + height = pageFormats[format][1] / k; + } + } + if (Array.isArray(width)) { + height = width[1]; + width = width[0]; + } + if (orientation) { + switch (orientation.substr(0, 1)) { + case 'l': + if (height > width) orientation = 's'; + break; + case 'p': + if (width > height) orientation = 's'; + break; + } + if (orientation === 's') { + tmp = width; + width = height; + height = tmp; + } + } + outToPages = true; + pages[++page] = []; + pagedim[page] = { + width: Number(width) || pageWidth, + height: Number(height) || pageHeight + }; + pagesContext[page] = {}; + _setPage(page); + }, + _addPage = function _addPage() { + beginPage.apply(this, arguments); + // Set line width + out(f2(lineWidth * k) + ' w'); + // Set draw color + out(drawColor); + // resurrecting non-default line caps, joins + if (lineCapID !== 0) { + out(lineCapID + ' J'); + } + if (lineJoinID !== 0) { + out(lineJoinID + ' j'); + } + events.publish('addPage', { + pageNumber: page + }); + }, + _deletePage = function _deletePage(n) { + if (n > 0 && n <= page) { + pages.splice(n, 1); + pagedim.splice(n, 1); + page--; + if (currentPage > page) { + currentPage = page; + } + this.setPage(currentPage); + } + }, + _setPage = function _setPage(n) { + if (n > 0 && n <= page) { + currentPage = n; + pageWidth = pagedim[n].width; + pageHeight = pagedim[n].height; + } + }, + + /** + * Returns a document-specific font key - a label assigned to a + * font name + font type combination at the time the font was added + * to the font inventory. + * + * Font key is used as label for the desired font for a block of text + * to be added to the PDF document stream. + * @private + * @function + * @param fontName {String} can be undefined on "falthy" to indicate "use current" + * @param fontStyle {String} can be undefined on "falthy" to indicate "use current" + * @returns {String} Font key. + */ + _getFont = function _getFont(fontName, fontStyle) { + var key, fontNameLowerCase; + + fontName = fontName !== undefined ? fontName : fonts[activeFontKey].fontName; + fontStyle = fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle; + fontNameLowerCase = fontName.toLowerCase(); + + if (fontmap[fontNameLowerCase] !== undefined && fontmap[fontNameLowerCase][fontStyle] !== undefined) { + key = fontmap[fontNameLowerCase][fontStyle]; + } else if (fontmap[fontName] !== undefined && fontmap[fontName][fontStyle] !== undefined) { + key = fontmap[fontName][fontStyle]; + } else { + console.warn("Unable to look up font label for font '" + fontName + "', '" + fontStyle + "'. Refer to getFontList() for available fonts."); + } + + if (!key) { + //throw new Error(); + key = fontmap['times'][fontStyle]; + if (key == null) { + key = fontmap['times']['normal']; + } + } + return key; + }, + buildDocument = function buildDocument() { + outToPages = false; // switches out() to content + + objectNumber = 2; + content_length = 0; + content = []; + offsets = []; + additionalObjects = []; + // Added for AcroForm + events.publish('buildDocument'); + + // putHeader() + out('%PDF-' + pdfVersion); + out("%\xBA\xDF\xAC\xE0"); + + putPages(); + + // Must happen after putPages + // Modifies current object Id + putAdditionalObjects(); + + putResources(); + + // Info + newObject(); + out('<<'); + putInfo(); + out('>>'); + out('endobj'); + + // Catalog + newObject(); + out('<<'); + putCatalog(); + out('>>'); + out('endobj'); + + // Cross-ref + var o = content_length, + i, + p = "0000000000"; + out('xref'); + out('0 ' + (objectNumber + 1)); + out(p + ' 65535 f '); + for (i = 1; i <= objectNumber; i++) { + var offset = offsets[i]; + if (typeof offset === 'function') { + out((p + offsets[i]()).slice(-10) + ' 00000 n '); + } else { + out((p + offsets[i]).slice(-10) + ' 00000 n '); + } + } + // Trailer + out('trailer'); + out('<<'); + putTrailer(); + out('>>'); + out('startxref'); + out('' + o); + out('%%EOF'); + + outToPages = true; + + return content.join('\n'); + }, + getStyle = function getStyle(style) { + // see path-painting operators in PDF spec + var op = 'S'; // stroke + if (style === 'F') { + op = 'f'; // fill + } else if (style === 'FD' || style === 'DF') { + op = 'B'; // both + } else if (style === 'f' || style === 'f*' || style === 'B' || style === 'B*') { + /* + Allow direct use of these PDF path-painting operators: + - f fill using nonzero winding number rule + - f* fill using even-odd rule + - B fill then stroke with fill using non-zero winding number rule + - B* fill then stroke with fill using even-odd rule + */ + op = style; + } + return op; + }, + getArrayBuffer = function getArrayBuffer() { + var data = buildDocument(), + len = data.length, + ab = new ArrayBuffer(len), + u8 = new Uint8Array(ab); + + while (len--) { + u8[len] = data.charCodeAt(len); + }return ab; + }, + getBlob = function getBlob() { + return new Blob([getArrayBuffer()], { + type: "application/pdf" + }); + }, + + /** + * Generates the PDF document. + * + * If `type` argument is undefined, output is raw body of resulting PDF returned as a string. + * + * @param {String} type A string identifying one of the possible output types. + * @param {Object} options An object providing some additional signalling to PDF generator. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name output + */ + _output = SAFE(function (type, options) { + var datauri = ('' + type).substr(0, 6) === 'dataur' ? 'data:application/pdf;base64,' + btoa(buildDocument()) : 0; + + switch (type) { + case undefined: + return buildDocument(); + case 'save': + if ((typeof navigator === 'undefined' ? 'undefined' : _typeof(navigator)) === "object" && navigator.getUserMedia) { + if (global.URL === undefined || global.URL.createObjectURL === undefined) { + return API.output('dataurlnewwindow'); + } + } + saveAs(getBlob(), options); + if (typeof saveAs.unload === 'function') { + if (global.setTimeout) { + setTimeout(saveAs.unload, 911); + } + } + break; + case 'arraybuffer': + return getArrayBuffer(); + case 'blob': + return getBlob(); + case 'bloburi': + case 'bloburl': + // User is responsible of calling revokeObjectURL + return global.URL && global.URL.createObjectURL(getBlob()) || void 0; + case 'datauristring': + case 'dataurlstring': + return datauri; + case 'dataurlnewwindow': + var nW = global.open(datauri); + if (nW || typeof safari === "undefined") return nW; + /* pass through */ + case 'datauri': + case 'dataurl': + return global.document.location.href = datauri; + default: + throw new Error('Output type "' + type + '" is not supported.'); + } + // @TODO: Add different output options + }), + + + /** + * Used to see if a supplied hotfix was requested when the pdf instance was created. + * @param {String} hotfixName - The name of the hotfix to check. + * @returns {boolean} + */ + hasHotfix = function hasHotfix(hotfixName) { + return Array.isArray(hotfixes) === true && hotfixes.indexOf(hotfixName) > -1; + }; + + switch (unit) { + case 'pt': + k = 1; + break; + case 'mm': + k = 72 / 25.4; + break; + case 'cm': + k = 72 / 2.54; + break; + case 'in': + k = 72; + break; + case 'px': + if (hasHotfix('px_scaling') == true) { + k = 72 / 96; + } else { + k = 96 / 72; + } + break; + case 'pc': + k = 12; + break; + case 'em': + k = 12; + break; + case 'ex': + k = 6; + break; + default: + throw 'Invalid unit: ' + unit; + } + + setCreationDate(); + setFileId(); + + //--------------------------------------- + // Public API + + /** + * Object exposing internal API to plugins + * @public + */ + API.internal = { + 'pdfEscape': pdfEscape, + 'getStyle': getStyle, + /** + * Returns {FontObject} describing a particular font. + * @public + * @function + * @param fontName {String} (Optional) Font's family name + * @param fontStyle {String} (Optional) Font's style variation name (Example:"Italic") + * @returns {FontObject} + */ + 'getFont': function getFont() { + return fonts[_getFont.apply(API, arguments)]; + }, + 'getFontSize': function getFontSize() { + return activeFontSize; + }, + 'getCharSpace': function getCharSpace() { + return activeCharSpace; + }, + 'getTextColor': function getTextColor() { + var colorEncoded = textColor.split(' '); + if (colorEncoded.length === 2 && colorEncoded[1] === 'g') { + // convert grayscale value to rgb so that it can be converted to hex for consistency + var floatVal = parseFloat(colorEncoded[0]); + colorEncoded = [floatVal, floatVal, floatVal, 'r']; + } + var colorAsHex = '#'; + for (var i = 0; i < 3; i++) { + colorAsHex += ('0' + Math.floor(parseFloat(colorEncoded[i]) * 255).toString(16)).slice(-2); + } + return colorAsHex; + }, + 'getLineHeight': function getLineHeight() { + return activeFontSize * lineHeightProportion; + }, + 'write': function write(string1 /*, string2, string3, etc */) { + out(arguments.length === 1 ? string1 : Array.prototype.join.call(arguments, ' ')); + }, + 'getCoordinateString': function getCoordinateString(value) { + return f2(value * k); + }, + 'getVerticalCoordinateString': function getVerticalCoordinateString(value) { + return f2((pageHeight - value) * k); + }, + 'collections': {}, + 'newObject': newObject, + 'newAdditionalObject': newAdditionalObject, + 'newObjectDeferred': newObjectDeferred, + 'newObjectDeferredBegin': newObjectDeferredBegin, + 'putStream': putStream, + 'events': events, + // ratio that you use in multiplication of a given "size" number to arrive to 'point' + // units of measurement. + // scaleFactor is set at initialization of the document and calculated against the stated + // default measurement units for the document. + // If default is "mm", k is the number that will turn number in 'mm' into 'points' number. + // through multiplication. + 'scaleFactor': k, + 'pageSize': { + getWidth: function getWidth() { + return pageWidth; + }, + getHeight: function getHeight() { + return pageHeight; + } + }, + 'output': function output(type, options) { + return _output(type, options); + }, + 'getNumberOfPages': function getNumberOfPages() { + return pages.length - 1; + }, + 'pages': pages, + 'out': out, + 'f2': f2, + 'getPageInfo': function getPageInfo(pageNumberOneBased) { + var objId = (pageNumberOneBased - 1) * 2 + 3; + return { + objId: objId, + pageNumber: pageNumberOneBased, + pageContext: pagesContext[pageNumberOneBased] + }; + }, + 'getCurrentPageInfo': function getCurrentPageInfo() { + var objId = (currentPage - 1) * 2 + 3; + return { + objId: objId, + pageNumber: currentPage, + pageContext: pagesContext[currentPage] + }; + }, + 'getPDFVersion': function getPDFVersion() { + return pdfVersion; + }, + 'hasHotfix': hasHotfix //Expose the hasHotfix check so plugins can also check them. + }; + + /** + * Adds (and transfers the focus to) new page to the PDF document. + * @param format {String/Array} The format of the new page. Can be
+ * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array , e.g. [595.28, 841.89] + * @param orientation {String} Orientation of the new page. Possible values are "portrait" or "landscape" (or shortcuts "p" (Default), "l") + * @function + * @returns {jsPDF} + * + * @methodOf jsPDF# + * @name addPage + */ + API.addPage = function () { + _addPage.apply(this, arguments); + return this; + }; + /** + * Adds (and transfers the focus to) new page to the PDF document. + * @function + * @returns {jsPDF} + * + * @methodOf jsPDF# + * @name setPage + * @param {Number} page Switch the active page to the page number specified + * @example + * doc = jsPDF() + * doc.addPage() + * doc.addPage() + * doc.text('I am on page 3', 10, 10) + * doc.setPage(1) + * doc.text('I am on page 1', 10, 10) + */ + API.setPage = function () { + _setPage.apply(this, arguments); + return this; + }; + API.insertPage = function (beforePage) { + this.addPage(); + this.movePage(currentPage, beforePage); + return this; + }; + API.movePage = function (targetPage, beforePage) { + if (targetPage > beforePage) { + var tmpPages = pages[targetPage]; + var tmpPagedim = pagedim[targetPage]; + var tmpPagesContext = pagesContext[targetPage]; + for (var i = targetPage; i > beforePage; i--) { + pages[i] = pages[i - 1]; + pagedim[i] = pagedim[i - 1]; + pagesContext[i] = pagesContext[i - 1]; + } + pages[beforePage] = tmpPages; + pagedim[beforePage] = tmpPagedim; + pagesContext[beforePage] = tmpPagesContext; + this.setPage(beforePage); + } else if (targetPage < beforePage) { + var tmpPages = pages[targetPage]; + var tmpPagedim = pagedim[targetPage]; + var tmpPagesContext = pagesContext[targetPage]; + for (var i = targetPage; i < beforePage; i++) { + pages[i] = pages[i + 1]; + pagedim[i] = pagedim[i + 1]; + pagesContext[i] = pagesContext[i + 1]; + } + pages[beforePage] = tmpPages; + pagedim[beforePage] = tmpPagedim; + pagesContext[beforePage] = tmpPagesContext; + this.setPage(beforePage); + } + return this; + }; + + API.deletePage = function () { + _deletePage.apply(this, arguments); + return this; + }; + + API.setCreationDate = function (date) { + setCreationDate(date); + return this; + }; + + API.getCreationDate = function (type) { + return getCreationDate(type); + }; + + API.setFileId = function (value) { + setFileId(value); + return this; + }; + + API.getFileId = function () { + return getFileId(); + }; + + /** + * Set the display mode options of the page like zoom and layout. + * + * @param {integer|String} zoom You can pass an integer or percentage as + * a string. 2 will scale the document up 2x, '200%' will scale up by the + * same amount. You can also set it to 'fullwidth', 'fullheight', + * 'fullpage', or 'original'. + * + * Only certain PDF readers support this, such as Adobe Acrobat + * + * @param {String} layout Layout mode can be: 'continuous' - this is the + * default continuous scroll. 'single' - the single page mode only shows one + * page at a time. 'twoleft' - two column left mode, first page starts on + * the left, and 'tworight' - pages are laid out in two columns, with the + * first page on the right. This would be used for books. + * @param {String} pmode 'UseOutlines' - it shows the + * outline of the document on the left. 'UseThumbs' - shows thumbnails along + * the left. 'FullScreen' - prompts the user to enter fullscreen mode. + * + * @function + * @returns {jsPDF} + * @name setDisplayMode + */ + API.setDisplayMode = function (zoom, layout, pmode) { + zoomMode = zoom; + layoutMode = layout; + pageMode = pmode; + + var validPageModes = [undefined, null, 'UseNone', 'UseOutlines', 'UseThumbs', 'FullScreen']; + if (validPageModes.indexOf(pmode) == -1) { + throw new Error('Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "' + pmode + '" is not recognized.'); + } + return this; + }; + + /** + * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings. + * + * @function + * @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down per font, spacing settings declared before this call. + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Object} options Collection of settings signalling how the text must be encoded. Defaults are sane. If you think you want to pass some flags, you likely can read the source. + * @returns {jsPDF} + * @methodOf jsPDF# + * @name text + */ + API.text = function (text, x, y, options) { + /** + * Inserts something like this into PDF + * BT + * /F1 16 Tf % Font name + size + * 16 TL % How many units down for next line in multiline text + * 0 g % color + * 28.35 813.54 Td % position + * (line one) Tj + * T* (line two) Tj + * T* (line three) Tj + * ET + */ + + var xtra = ''; + var isHex = false; + var lineHeight = lineHeightProportion; + + function ESC(s) { + s = s.split("\t").join(Array(options.TabLen || 9).join(" ")); + return pdfEscape(s, flags); + } + + function transformTextToSpecialArray(text) { + //we don't want to destroy original text array, so cloning it + var sa = text.concat(); + var da = []; + var len = sa.length; + var curDa; + //we do array.join('text that must not be PDFescaped") + //thus, pdfEscape each component separately + while (len--) { + curDa = sa.shift(); + if (typeof curDa === "string") { + da.push(curDa); + } else { + if (Object.prototype.toString.call(text) === '[object Array]' && curDa.length === 1) { + da.push(curDa[0]); + } else { + da.push([curDa[0], curDa[1], curDa[2]]); + } + } + } + return da; + } + + function processTextByFunction(text, processingFunction) { + var result; + if (typeof text === 'string') { + result = processingFunction(text)[0]; + } else if (Object.prototype.toString.call(text) === '[object Array]') { + //we don't want to destroy original text array, so cloning it + var sa = text.concat(); + var da = []; + var len = sa.length; + var curDa; + var tmpResult; + //we do array.join('text that must not be PDFescaped") + //thus, pdfEscape each component separately + while (len--) { + curDa = sa.shift(); + if (typeof curDa === "string") { + da.push(processingFunction(curDa)[0]); + } else if (Object.prototype.toString.call(curDa) === '[object Array]' && curDa[0] === "string") { + tmpResult = processingFunction(curDa[0], curDa[1], curDa[2]); + da.push([tmpResult[0], tmpResult[1], tmpResult[2]]); + } + } + result = da; + } + return result; + } + /** + Returns a widths of string in a given font, if the font size is set as 1 point. + In other words, this is "proportional" value. For 1 unit of font size, the length + of the string will be that much. + Multiply by font size to get actual width in *points* + Then divide by 72 to get inches or divide by (72/25.6) to get 'mm' etc. + @public + @function + @param + @returns {Type} + */ + var getStringUnitWidth = function getStringUnitWidth(text, options) { + var result = 0; + if (typeof options.font.metadata.widthOfString === "function") { + result = options.font.metadata.widthOfString(text, options.fontSize, options.charSpace); + } else { + result = getArraySum(getCharWidthsArray(text, options)) * options.fontSize; + } + return result; + }; + + /** + Returns an array of length matching length of the 'word' string, with each + cell ocupied by the width of the char in that position. + @function + @param word {String} + @param widths {Object} + @param kerning {Object} + @returns {Array} + */ + function getCharWidthsArray(text, options) { + options = options || {}; + + var widths = options.widths ? options.widths : options.font.metadata.Unicode.widths; + var widthsFractionOf = widths.fof ? widths.fof : 1; + var kerning = options.kerning ? options.kerning : options.font.metadata.Unicode.kerning; + var kerningFractionOf = kerning.fof ? kerning.fof : 1; + + var i; + var l; + var char_code; + var prior_char_code = 0; //for kerning + var default_char_width = widths[0] || widthsFractionOf; + var output = []; + + for (i = 0, l = text.length; i < l; i++) { + char_code = text.charCodeAt(i); + output.push((widths[char_code] || default_char_width) / widthsFractionOf + (kerning[char_code] && kerning[char_code][prior_char_code] || 0) / kerningFractionOf); + prior_char_code = char_code; + } + + return output; + } + + var getArraySum = function getArraySum(array) { + var i = array.length; + var output = 0; + + while (i) { + i--; + output += array[i]; + } + + return output; + }; + + //backwardsCompatibility + var tmp; + + // Pre-August-2012 the order of arguments was function(x, y, text, flags) + // in effort to make all calls have similar signature like + // function(data, coordinates... , miscellaneous) + // this method had its args flipped. + // code below allows backward compatibility with old arg order. + if (typeof text === 'number') { + tmp = y; + y = x; + x = text; + text = tmp; + } + + var flags = arguments[3]; + var angle = arguments[4]; + var align = arguments[5]; + + if ((typeof flags === 'undefined' ? 'undefined' : _typeof(flags)) !== "object" || flags === null) { + if (typeof angle === 'string') { + align = angle; + angle = null; + } + if (typeof flags === 'string') { + align = flags; + flags = null; + } + if (typeof flags === 'number') { + angle = flags; + flags = null; + } + options = { flags: flags, angle: angle, align: align }; + } + + //Check if text is of type String + var textIsOfTypeString = false; + var tmpTextIsOfTypeString = true; + + if (typeof text === 'string') { + textIsOfTypeString = true; + } else if (Object.prototype.toString.call(text) === '[object Array]') { + //we don't want to destroy original text array, so cloning it + var sa = text.concat(); + var da = []; + var len = sa.length; + var curDa; + //we do array.join('text that must not be PDFescaped") + //thus, pdfEscape each component separately + while (len--) { + curDa = sa.shift(); + if (typeof curDa !== "string" || Object.prototype.toString.call(curDa) === '[object Array]' && typeof curDa[0] !== "string") { + tmpTextIsOfTypeString = false; + } + } + textIsOfTypeString = tmpTextIsOfTypeString; + } + if (textIsOfTypeString === false) { + throw new Error('Type of text must be string or Array. "' + text + '" is not recognized.'); + } + + //Escaping + var activeFontEncoding = fonts[activeFontKey].encoding; + + if (activeFontEncoding === "WinAnsiEncoding" || activeFontEncoding === "StandardEncoding") { + text = processTextByFunction(text, function (text, posX, posY) { + return [ESC(text), posX, posY]; + }); + } + //If there are any newlines in text, we assume + //the user wanted to print multiple lines, so break the + //text up into an array. If the text is already an array, + //we assume the user knows what they are doing. + //Convert text into an array anyway to simplify + //later code. + + if (typeof text === 'string') { + if (text.match(/[\r?\n]/)) { + text = text.split(/\r\n|\r|\n/g); + } else { + text = [text]; + } + } + + //multiline + var maxWidth = options.maxWidth || 0; + var algorythm = options.maxWidthAlgorythm || "first-fit"; + + lineHeight = options.lineHeight || lineHeightProportion; + var leading = activeFontSize * lineHeight; + var activeFont = fonts[activeFontKey]; + var k = this.internal.scaleFactor; + var charSpace = options.charSpace || activeCharSpace; + + var widthOfSpace = getStringUnitWidth(" ", { font: activeFont, charSpace: charSpace, fontSize: activeFontSize }) / k; + var splitByMaxWidth = function splitByMaxWidth(value, maxWidth) { + var i = 0; + var lastBreak = 0; + var currentWidth = 0; + var resultingChunks = []; + var widthOfEachWord = []; + var currentChunk = []; + + var listOfWords = []; + var result = []; + + listOfWords = value.split(/ /g); + + for (i = 0; i < listOfWords.length; i += 1) { + widthOfEachWord.push(getStringUnitWidth(listOfWords[i], { font: activeFont, charSpace: charSpace, fontSize: activeFontSize }) / k); + } + for (i = 0; i < listOfWords.length; i += 1) { + currentChunk = widthOfEachWord.slice(lastBreak, i); + currentWidth = getArraySum(currentChunk) + widthOfSpace * (currentChunk.length - 1); + if (currentWidth >= maxWidth) { + resultingChunks.push(listOfWords.slice(lastBreak, i !== 0 ? i - 1 : 0).join(" ")); + lastBreak = i !== 0 ? i - 1 : 0; + i -= 1; + } else if (i === widthOfEachWord.length - 1) { + resultingChunks.push(listOfWords.slice(lastBreak, widthOfEachWord.length).join(" ")); + } + } + result = []; + for (i = 0; i < resultingChunks.length; i += 1) { + result = result.concat(resultingChunks[i]); + } + return result; + }; + var firstFitMethod = function firstFitMethod(value, maxWidth) { + var j = 0; + var tmpText = []; + for (j = 0; j < value.length; j += 1) { + tmpText = tmpText.concat(splitByMaxWidth(value[j], maxWidth)); + } + return tmpText; + }; + if (maxWidth > 0) { + switch (algorythm) { + case "first-fit": + default: + text = firstFitMethod(text, maxWidth); + break; + } + } + + //creating Payload-Object to make text byRef + var payload = { + text: text, + x: x, + y: y, + options: options, + mutex: { + pdfEscape: pdfEscape, + activeFontKey: activeFontKey, + fonts: fonts, + activeFontSize: activeFontSize + } + }; + events.publish('preProcessText', payload); + + text = payload.text; + options = payload.options; + //angle + + var angle = options.angle; + var k = this.internal.scaleFactor; + var curY = (this.internal.pageSize.getHeight() - y) * k; + var transformationMatrix = []; + + if (angle) { + angle *= Math.PI / 180; + var c = Math.cos(angle), + s = Math.sin(angle); + var f2 = function f2(number) { + return number.toFixed(2); + }; + transformationMatrix = [f2(c), f2(s), f2(s * -1), f2(c)]; + } + + //charSpace + + var charSpace = options.charSpace; + + if (charSpace !== undefined) { + xtra += charSpace + " Tc\n"; + } + + //lang + + var lang = options.lang; + var tmpRenderingMode = -1; + var parmRenderingMode = options.renderingMode || options.stroke; + var pageContext = this.internal.getCurrentPageInfo().pageContext; + + switch (parmRenderingMode) { + case 0: + case false: + case 'fill': + tmpRenderingMode = 0; + break; + case 1: + case true: + case 'stroke': + tmpRenderingMode = 1; + break; + case 2: + case 'fillThenStroke': + tmpRenderingMode = 2; + break; + case 3: + case 'invisible': + tmpRenderingMode = 3; + break; + case 4: + case 'fillAndAddForClipping': + tmpRenderingMode = 4; + break; + case 5: + case 'strokeAndAddPathForClipping': + tmpRenderingMode = 5; + break; + case 6: + case 'fillThenStrokeAndAddToPathForClipping': + tmpRenderingMode = 6; + break; + case 7: + case 'addToPathForClipping': + tmpRenderingMode = 7; + break; + } + + var usedRenderingMode = pageContext.usedRenderingMode || -1; + + //if the coder wrote it explicitly to use a specific + //renderingMode, then use it + if (tmpRenderingMode !== -1) { + xtra += tmpRenderingMode + " Tr\n"; + //otherwise check if we used the rendering Mode already + //if so then set the rendering Mode... + } else if (usedRenderingMode !== -1) { + xtra += "0 Tr\n"; + } + + if (tmpRenderingMode !== -1) { + pageContext.usedRenderingMode = tmpRenderingMode; + } + + //align + + var align = options.align || 'left'; + var leading = activeFontSize * lineHeight; + var pageHeight = this.internal.pageSize.getHeight(); + var pageWidth = this.internal.pageSize.getWidth(); + var k = this.internal.scaleFactor; + var activeFont = fonts[activeFontKey]; + var charSpace = options.charSpace || activeCharSpace; + var maxWidth = options.maxWidth || 0; + + var lineWidths; + var flags = {}; + var wordSpacingPerLine = []; + + if (Object.prototype.toString.call(text) === '[object Array]') { + var da = transformTextToSpecialArray(text); + var newY; + var maxLineLength; + var lineWidths; + if (align !== "left") { + lineWidths = da.map(function (v) { + return getStringUnitWidth(v, { font: activeFont, charSpace: charSpace, fontSize: activeFontSize }) / k; + }); + } + var maxLineLength = Math.max.apply(Math, lineWidths); + //The first line uses the "main" Td setting, + //and the subsequent lines are offset by the + //previous line's x coordinate. + var prevWidth = 0; + var delta; + var newX; + if (align === "right") { + x -= lineWidths[0]; + text = []; + for (var i = 0, len = da.length; i < len; i++) { + delta = maxLineLength - lineWidths[i]; + if (i === 0) { + newX = x * k; + newY = (pageHeight - y) * k; + } else { + newX = (prevWidth - lineWidths[i]) * k; + newY = -leading; + } + text.push([da[i], newX, newY]); + prevWidth = lineWidths[i]; + } + } else if (align === "center") { + x -= lineWidths[0] / 2; + text = []; + for (var i = 0, len = da.length; i < len; i++) { + delta = (maxLineLength - lineWidths[i]) / 2; + if (i === 0) { + newX = x * k; + newY = (pageHeight - y) * k; + } else { + newX = (prevWidth - lineWidths[i]) / 2 * k; + newY = -leading; + } + text.push([da[i], newX, newY]); + prevWidth = lineWidths[i]; + } + } else if (align === "left") { + text = []; + for (var i = 0, len = da.length; i < len; i++) { + newY = i === 0 ? (pageHeight - y) * k : -leading; + newX = i === 0 ? x * k : 0; + //text.push([da[i], newX, newY]); + text.push(da[i]); + } + } else if (align === "justify") { + text = []; + var maxWidth = maxWidth !== 0 ? maxWidth : pageWidth; + + for (var i = 0, len = da.length; i < len; i++) { + newY = i === 0 ? (pageHeight - y) * k : -leading; + newX = i === 0 ? x * k : 0; + if (i < len - 1) { + wordSpacingPerLine.push(((maxWidth - lineWidths[i]) / (da[i].split(" ").length - 1) * k).toFixed(2)); + } + text.push([da[i], newX, newY]); + } + } else { + throw new Error('Unrecognized alignment option, use "left", "center", "right" or "justify".'); + } + } + + //R2L + var doReversing = typeof options.R2L === "boolean" ? options.R2L : R2L; + if (doReversing === true) { + text = processTextByFunction(text, function (text, posX, posY) { + return [text.split("").reverse().join(""), posX, posY]; + }); + } + + //creating Payload-Object to make text byRef + var payload = { + text: text, + x: x, + y: y, + options: options, + mutex: { + pdfEscape: pdfEscape, + activeFontKey: activeFontKey, + fonts: fonts, + activeFontSize: activeFontSize + } + }; + events.publish('postProcessText', payload); + + text = payload.text; + isHex = payload.mutex.isHex; + + var da = transformTextToSpecialArray(text); + + text = []; + var variant = 0; + var len = da.length; + var posX; + var posY; + var content; + var wordSpacing = ''; + + for (var i = 0; i < len; i++) { + + wordSpacing = ''; + if (Object.prototype.toString.call(da[i]) !== '[object Array]') { + posX = parseFloat(x * k).toFixed(2); + posY = parseFloat((pageHeight - y) * k).toFixed(2); + content = (isHex ? "<" : "(") + da[i] + (isHex ? ">" : ")"); + } else if (Object.prototype.toString.call(da[i]) === '[object Array]') { + posX = parseFloat(da[i][1]).toFixed(2); + posY = parseFloat(da[i][2]).toFixed(2); + content = (isHex ? "<" : "(") + da[i][0] + (isHex ? ">" : ")"); + variant = 1; + } + if (wordSpacingPerLine !== undefined && wordSpacingPerLine[i] !== undefined) { + wordSpacing = wordSpacingPerLine[i] + " Tw\n"; + } + //TODO: Kind of a hack? + if (transformationMatrix.length !== 0 && i === 0) { + text.push(wordSpacing + transformationMatrix.join(" ") + " " + posX + " " + posY + " Tm\n" + content); + } else if (variant === 1 || variant === 0 && i === 0) { + text.push(wordSpacing + posX + " " + posY + " Td\n" + content); + } else { + text.push(wordSpacing + content); + } + } + if (variant === 0) { + text = text.join(" Tj\nT* "); + } else { + text = text.join(" Tj\n"); + } + + text += " Tj\n"; + + var result = 'BT\n/' + activeFontKey + ' ' + activeFontSize + ' Tf\n' + // font face, style, size + (activeFontSize * lineHeight).toFixed(2) + ' TL\n' + // line spacing + textColor + '\n'; + result += xtra; + result += text; + result += "ET"; + + out(result); + return this; + }; + + /** + * Letter spacing method to print text with gaps + * + * @function + * @param {String|Array} text String to be added to the page. + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} spacing Spacing (in units declared at inception) + * @returns {jsPDF} + * @methodOf jsPDF# + * @name lstext + * @deprecated We'll be removing this function. It doesn't take character width into account. + */ + API.lstext = function (text, x, y, spacing) { + console.warn('jsPDF.lstext is deprecated'); + for (var i = 0, len = text.length; i < len; i++, x += spacing) { + this.text(text[i], x, y); + }return this; + }; + + API.line = function (x1, y1, x2, y2) { + return this.lines([[x2 - x1, y2 - y1]], x1, y1); + }; + + API.clip = function () { + // By patrick-roberts, github.com/MrRio/jsPDF/issues/328 + // Call .clip() after calling .rect() with a style argument of null + out('W'); // clip + out('S'); // stroke path; necessary for clip to work + }; + + /** + * This fixes the previous function clip(). Perhaps the 'stroke path' hack was due to the missing 'n' instruction? + * We introduce the fixed version so as to not break API. + * @param fillRule + */ + API.clip_fixed = function (fillRule) { + // Call .clip() after calling drawing ops with a style argument of null + // W is the PDF clipping op + if ('evenodd' === fillRule) { + out('W*'); + } else { + out('W'); + } + // End the path object without filling or stroking it. + // This operator is a path-painting no-op, used primarily for the side effect of changing the current clipping path + // (see Section 4.4.3, “Clipping Path Operators”) + out('n'); + }; + + /** + * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates. + * All data points in `lines` are relative to last line origin. + * `x`, `y` become x1,y1 for first line / curve in the set. + * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point. + * For bezier curves you need to specify [x2,y2,x3,y3,x4,y4] - vectors to control points 1, 2, ending point. All vectors are against the start of the curve - x1,y1. + * + * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, 10) // line, line, bezier curve, line + * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves). + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} scale (Defaults to [1.0,1.0]) x,y Scaling factor for all vectors. Elements can be any floating number Sub-one makes drawing smaller. Over-one grows the drawing. Negative flips the direction. + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @param {Boolean} closed If true, the path is closed with a straight line from the end of the last curve to the starting point. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name lines + */ + API.lines = function (lines, x, y, scale, style, closed) { + var scalex, scaley, i, l, leg, x2, y2, x3, y3, x4, y4; + + // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style) + // in effort to make all calls have similar signature like + // function(content, coordinateX, coordinateY , miscellaneous) + // this method had its args flipped. + // code below allows backward compatibility with old arg order. + if (typeof lines === 'number') { + tmp = y; + y = x; + x = lines; + lines = tmp; + } + + scale = scale || [1, 1]; + + // starting point + out(f3(x * k) + ' ' + f3((pageHeight - y) * k) + ' m '); + + scalex = scale[0]; + scaley = scale[1]; + l = lines.length; + //, x2, y2 // bezier only. In page default measurement "units", *after* scaling + //, x3, y3 // bezier only. In page default measurement "units", *after* scaling + // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling + x4 = x; // last / ending point = starting point for first item. + y4 = y; // last / ending point = starting point for first item. + + for (i = 0; i < l; i++) { + leg = lines[i]; + if (leg.length === 2) { + // simple line + x4 = leg[0] * scalex + x4; // here last x4 was prior ending point + y4 = leg[1] * scaley + y4; // here last y4 was prior ending point + out(f3(x4 * k) + ' ' + f3((pageHeight - y4) * k) + ' l'); + } else { + // bezier curve + x2 = leg[0] * scalex + x4; // here last x4 is prior ending point + y2 = leg[1] * scaley + y4; // here last y4 is prior ending point + x3 = leg[2] * scalex + x4; // here last x4 is prior ending point + y3 = leg[3] * scaley + y4; // here last y4 is prior ending point + x4 = leg[4] * scalex + x4; // here last x4 was prior ending point + y4 = leg[5] * scaley + y4; // here last y4 was prior ending point + out(f3(x2 * k) + ' ' + f3((pageHeight - y2) * k) + ' ' + f3(x3 * k) + ' ' + f3((pageHeight - y3) * k) + ' ' + f3(x4 * k) + ' ' + f3((pageHeight - y4) * k) + ' c'); + } + } + + if (closed) { + out(' h'); + } + + // stroking / filling / both the path + if (style !== null) { + out(getStyle(style)); + } + return this; + }; + + /** + * Adds a rectangle to PDF + * + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} w Width (in units declared at inception of PDF document) + * @param {Number} h Height (in units declared at inception of PDF document) + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name rect + */ + API.rect = function (x, y, w, h, style) { + var op = getStyle(style); + out([f2(x * k), f2((pageHeight - y) * k), f2(w * k), f2(-h * k), 're'].join(' ')); + + if (style !== null) { + out(getStyle(style)); + } + + return this; + }; + + /** + * Adds a triangle to PDF + * + * @param {Number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name triangle + */ + API.triangle = function (x1, y1, x2, y2, x3, y3, style) { + this.lines([[x2 - x1, y2 - y1], // vector to point 2 + [x3 - x2, y3 - y2], // vector to point 3 + [x1 - x3, y1 - y3] // closing vector back to point 1 + ], x1, y1, // start of path + [1, 1], style, true); + return this; + }; + + /** + * Adds a rectangle with rounded corners to PDF + * + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} w Width (in units declared at inception of PDF document) + * @param {Number} h Height (in units declared at inception of PDF document) + * @param {Number} rx Radius along x axis (in units declared at inception of PDF document) + * @param {Number} rx Radius along y axis (in units declared at inception of PDF document) + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name roundedRect + */ + API.roundedRect = function (x, y, w, h, rx, ry, style) { + var MyArc = 4 / 3 * (Math.SQRT2 - 1); + this.lines([[w - 2 * rx, 0], [rx * MyArc, 0, rx, ry - ry * MyArc, rx, ry], [0, h - 2 * ry], [0, ry * MyArc, -(rx * MyArc), ry, -rx, ry], [-w + 2 * rx, 0], [-(rx * MyArc), 0, -rx, -(ry * MyArc), -rx, -ry], [0, -h + 2 * ry], [0, -(ry * MyArc), rx * MyArc, -ry, rx, -ry]], x + rx, y, // start of path + [1, 1], style); + return this; + }; + + /** + * Adds an ellipse to PDF + * + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} rx Radius along x axis (in units declared at inception of PDF document) + * @param {Number} rx Radius along y axis (in units declared at inception of PDF document) + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name ellipse + */ + API.ellipse = function (x, y, rx, ry, style) { + var lx = 4 / 3 * (Math.SQRT2 - 1) * rx, + ly = 4 / 3 * (Math.SQRT2 - 1) * ry; + + out([f2((x + rx) * k), f2((pageHeight - y) * k), 'm', f2((x + rx) * k), f2((pageHeight - (y - ly)) * k), f2((x + lx) * k), f2((pageHeight - (y - ry)) * k), f2(x * k), f2((pageHeight - (y - ry)) * k), 'c'].join(' ')); + out([f2((x - lx) * k), f2((pageHeight - (y - ry)) * k), f2((x - rx) * k), f2((pageHeight - (y - ly)) * k), f2((x - rx) * k), f2((pageHeight - y) * k), 'c'].join(' ')); + out([f2((x - rx) * k), f2((pageHeight - (y + ly)) * k), f2((x - lx) * k), f2((pageHeight - (y + ry)) * k), f2(x * k), f2((pageHeight - (y + ry)) * k), 'c'].join(' ')); + out([f2((x + lx) * k), f2((pageHeight - (y + ry)) * k), f2((x + rx) * k), f2((pageHeight - (y + ly)) * k), f2((x + rx) * k), f2((pageHeight - y) * k), 'c'].join(' ')); + + if (style !== null) { + out(getStyle(style)); + } + + return this; + }; + + /** + * Adds an circle to PDF + * + * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page + * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page + * @param {Number} r Radius (in units declared at inception of PDF document) + * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name circle + */ + API.circle = function (x, y, r, style) { + return this.ellipse(x, y, r, r, style); + }; + + /** + * Adds a properties to the PDF document + * + * @param {Object} A property_name-to-property_value object structure. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setProperties + */ + API.setProperties = function (properties) { + // copying only those properties we can render. + for (var property in documentProperties) { + if (documentProperties.hasOwnProperty(property) && properties[property]) { + documentProperties[property] = properties[property]; + } + } + return this; + }; + + /** + * Sets font size for upcoming text elements. + * + * @param {Number} size Font size in points. + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setFontSize + */ + API.setFontSize = function (size) { + activeFontSize = size; + return this; + }; + + /** + * Sets text font face, variant for upcoming text elements. + * See output of jsPDF.getFontList() for possible font names, styles. + * + * @param {String} fontName Font name or family. Example: "times" + * @param {String} fontStyle Font style or variant. Example: "italic" + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setFont + */ + API.setFont = function (fontName, fontStyle) { + activeFontKey = _getFont(fontName, fontStyle); + // if font is not found, the above line blows up and we never go further + return this; + }; + + /** + * Switches font style or variant for upcoming text elements, + * while keeping the font face or family same. + * See output of jsPDF.getFontList() for possible font names, styles. + * + * @param {String} style Font style or variant. Example: "italic" + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setFontStyle + */ + API.setFontStyle = API.setFontType = function (style) { + activeFontKey = _getFont(undefined, style); + // if font is not found, the above line blows up and we never go further + return this; + }; + + /** + * Returns an object - a tree of fontName to fontStyle relationships available to + * active PDF document. + * + * @public + * @function + * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... } + * @methodOf jsPDF# + * @name getFontList + */ + API.getFontList = function () { + // TODO: iterate over fonts array or return copy of fontmap instead in case more are ever added. + var list = {}, + fontName, + fontStyle, + tmp; + + for (fontName in fontmap) { + if (fontmap.hasOwnProperty(fontName)) { + list[fontName] = tmp = []; + for (fontStyle in fontmap[fontName]) { + if (fontmap[fontName].hasOwnProperty(fontStyle)) { + tmp.push(fontStyle); + } + } + } + } + + return list; + }; + + /** + * Add a custom font. + * + * @param {String} Postscript name of the Font. Example: "Menlo-Regular" + * @param {String} Name of font-family from @font-face definition. Example: "Menlo Regular" + * @param {String} Font style. Example: "normal" + * @function + * @returns the {fontKey} (same as the internal method) + * @methodOf jsPDF# + * @name addFont + */ + API.addFont = function (postScriptName, fontName, fontStyle, encoding) { + encoding = encoding || 'Identity-H'; + addFont(postScriptName, fontName, fontStyle, encoding); + }; + + /** + * Sets line width for upcoming lines. + * + * @param {Number} width Line width (in units declared at inception of PDF document) + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setLineWidth + */ + API.setLineWidth = function (width) { + out((width * k).toFixed(2) + ' w'); + return this; + }; + + /** + * Sets the stroke color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value or {String} ch1 color value in hexadecimal, example: '#FFFFFF' + * @param {Number|String} ch2 Color channel value + * @param {Number|String} ch3 Color channel value + * @param {Number|String} ch4 Color channel value + * + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setDrawColor + */ + API.setDrawColor = function (ch1, ch2, ch3, ch4) { + var options = { + "ch1": ch1, + "ch2": ch2, + "ch3": ch3, + "ch4": ch4, + "pdfColorType": "draw", + "precision": 2 + }; + + out(generateColorString(options)); + return this; + }; + + /** + * Sets the fill color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value or {String} ch1 color value in hexadecimal, example: '#FFFFFF' + * @param {Number|String} ch2 Color channel value + * @param {Number|String} ch3 Color channel value + * @param {Number|String} ch4 Color channel value + * + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setFillColor + */ + + API.setFillColor = function (ch1, ch2, ch3, ch4) { + var options = { + "ch1": ch1, + "ch2": ch2, + "ch3": ch3, + "ch4": ch4, + "pdfColorType": "fill", + "precision": 2 + }; + + out(generateColorString(options)); + return this; + }; + + /** + * Sets the text color for upcoming elements. + * + * Depending on the number of arguments given, Gray, RGB, or CMYK + * color space is implied. + * + * When only ch1 is given, "Gray" color space is implied and it + * must be a value in the range from 0.00 (solid black) to to 1.00 (white) + * if values are communicated as String types, or in range from 0 (black) + * to 255 (white) if communicated as Number type. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each + * value must be in the range from 0.00 (minimum intensity) to to 1.00 + * (max intensity) if values are communicated as String types, or + * from 0 (min intensity) to to 255 (max intensity) if values are communicated + * as Number types. + * The RGB-like 0-255 range is provided for backward compatibility. + * + * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each + * value must be a in the range from 0.00 (0% concentration) to to + * 1.00 (100% concentration) + * + * Because JavaScript treats fixed point numbers badly (rounds to + * floating point nearest to binary representation) it is highly advised to + * communicate the fractional numbers as String types, not JavaScript Number type. + * + * @param {Number|String} ch1 Color channel value or {String} ch1 color value in hexadecimal, example: '#FFFFFF' + * @param {Number|String} ch2 Color channel value + * @param {Number|String} ch3 Color channel value + * @param {Number|String} ch4 Color channel value + * + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setTextColor + */ + API.setTextColor = function (ch1, ch2, ch3, ch4) { + var options = { + "ch1": ch1, + "ch2": ch2, + "ch3": ch3, + "ch4": ch4, + "pdfColorType": "text", + "precision": 3 + }; + textColor = generateColorString(options); + + return this; + }; + + /** + * Initializes the default character set that the user wants to be global.. + * + * @param {Number} charSpace + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setCharSpace + */ + + API.setCharSpace = function (charSpace) { + activeCharSpace = charSpace; + return this; + }; + + /** + * Initializes the default character set that the user wants to be global.. + * + * @param {Boolean} boolean + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setR2L + */ + + API.setR2L = function (boolean) { + R2L = boolean; + return this; + }; + + /** + * Is an Object providing a mapping from human-readable to + * integer flag values designating the varieties of line cap + * and join styles. + * + * @returns {Object} + * @fieldOf jsPDF# + * @name CapJoinStyles + */ + API.CapJoinStyles = { + 0: 0, + 'butt': 0, + 'but': 0, + 'miter': 0, + 1: 1, + 'round': 1, + 'rounded': 1, + 'circle': 1, + 2: 2, + 'projecting': 2, + 'project': 2, + 'square': 2, + 'bevel': 2 + }; + + /** + * Sets the line cap styles + * See {jsPDF.CapJoinStyles} for variants + * + * @param {String|Number} style A string or number identifying the type of line cap + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setLineCap + */ + API.setLineCap = function (style) { + var id = this.CapJoinStyles[style]; + if (id === undefined) { + throw new Error("Line cap style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles"); + } + lineCapID = id; + out(id + ' J'); + + return this; + }; + + /** + * Sets the line join styles + * See {jsPDF.CapJoinStyles} for variants + * + * @param {String|Number} style A string or number identifying the type of line join + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name setLineJoin + */ + API.setLineJoin = function (style) { + var id = this.CapJoinStyles[style]; + if (id === undefined) { + throw new Error("Line join style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles"); + } + lineJoinID = id; + out(id + ' j'); + + return this; + }; + + // Output is both an internal (for plugins) and external function + API.output = _output; + + /** + * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf') + * @param {String} filename The filename including extension. + * + * @function + * @returns {jsPDF} + * @methodOf jsPDF# + * @name save + */ + API.save = function (filename) { + API.output('save', filename); + }; + + // applying plugins (more methods) ON TOP of built-in API. + // this is intentional as we allow plugins to override + // built-ins + for (var plugin in jsPDF.API) { + if (jsPDF.API.hasOwnProperty(plugin)) { + if (plugin === 'events' && jsPDF.API.events.length) { + (function (events, newEvents) { + + // jsPDF.API.events is a JS Array of Arrays + // where each Array is a pair of event name, handler + // Events were added by plugins to the jsPDF instantiator. + // These are always added to the new instance and some ran + // during instantiation. + var eventname, handler_and_args, i; + + for (i = newEvents.length - 1; i !== -1; i--) { + // subscribe takes 3 args: 'topic', function, runonce_flag + // if undefined, runonce is false. + // users can attach callback directly, + // or they can attach an array with [callback, runonce_flag] + // that's what the "apply" magic is for below. + eventname = newEvents[i][0]; + handler_and_args = newEvents[i][1]; + events.subscribe.apply(events, [eventname].concat(typeof handler_and_args === 'function' ? [handler_and_args] : handler_and_args)); + } + })(events, jsPDF.API.events); + } else { + API[plugin] = jsPDF.API[plugin]; + } + } + } + + ////////////////////////////////////////////////////// + // continuing initialization of jsPDF Document object + ////////////////////////////////////////////////////// + // Add the first page automatically + addFonts(); + activeFontKey = 'F1'; + _addPage(format, orientation); + + events.publish('initialized'); + return API; + } + + /** + * jsPDF.API is a STATIC property of jsPDF class. + * jsPDF.API is an object you can add methods and properties to. + * The methods / properties you add will show up in new jsPDF objects. + * + * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics, + * callbacks to this object. These will be reassigned to all new instances of jsPDF. + * Examples: + * jsPDF.API.events['initialized'] = function(){ 'this' is API object } + * jsPDF.API.events['addFont'] = function(added_font_object){ 'this' is API object } + * + * @static + * @public + * @memberOf jsPDF + * @name API + * + * @example + * jsPDF.API.mymethod = function(){ + * // 'this' will be ref to internal API object. see jsPDF source + * // , so you can refer to built-in methods like so: + * // this.line(....) + * // this.text(....) + * } + * var pdfdoc = new jsPDF() + * pdfdoc.mymethod() // <- !!!!!! + */ + jsPDF.API = { + events: [] + }; + jsPDF.version = "0.0.0"; + + if (typeof define === 'function' && define.amd) { + define('jsPDF', function () { + return jsPDF; + }); + } else if (typeof module !== 'undefined' && module.exports) { + module.exports = jsPDF; + module.exports.jsPDF = jsPDF; + } else { + global.jsPDF = jsPDF; + } + return jsPDF; + }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || typeof global !== "undefined" && global || Function('return typeof this === "object" && this.content')() || Function('return this')()); + // `self` is undefined in Firefox for Android content script context + // while `this` is nsIContentFrameMessageManager + // with an attribute `content` that corresponds to the window + + + /** + * jsPDF AcroForm Plugin + * Copyright (c) 2016 Alexander Weidt, https://github.com/BiggA94 + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + + (function (jsPDFAPI, globalObj) { + + var scope; + var pageHeight; + var scaleFactor = 1; + var inherit = function inherit(child, parent) { + child.prototype = Object.create(parent.prototype); + child.prototype.constructor = child; + }; + var scale = function scale(x) { + return x * (scaleFactor / 1); // 1 = (96 / 72) + }; + + /** + Returns a widths of string in a given font, if the font size is set as 1 point. + In other words, this is "proportional" value. For 1 unit of font size, the length + of the string will be that much. + Multiply by font size to get actual width in *points* + Then divide by 72 to get inches or divide by (72/25.6) to get 'mm' etc. + @public + @function + @param + @returns {Type} + */ + var getStringUnitWidth = function getStringUnitWidth(text, options) { + var result = 0; + if (typeof options.font.metadata.widthOfString === "function") { + result = options.font.metadata.widthOfString(text, options.fontSize, options.charSpace); + } else { + result = getArraySum(getCharWidthsArray(text, options)) * options.fontSize; + } + return result; + }; + + /** + Returns an array of length matching length of the 'word' string, with each + cell ocupied by the width of the char in that position. + @function + @param word {String} + @param widths {Object} + @param kerning {Object} + @returns {Array} + */ + function getCharWidthsArray(text, options) { + options = options || {}; + + var widths = options.widths ? options.widths : options.font.metadata.Unicode.widths; + var widthsFractionOf = widths.fof ? widths.fof : 1; + var kerning = options.kerning ? options.kerning : options.font.metadata.Unicode.kerning; + var kerningFractionOf = kerning.fof ? kerning.fof : 1; + + var i; + var l; + var char_code; + var prior_char_code = 0; //for kerning + var default_char_width = widths[0] || widthsFractionOf; + var output = []; + + for (i = 0, l = text.length; i < l; i++) { + char_code = text.charCodeAt(i); + output.push((widths[char_code] || default_char_width) / widthsFractionOf + (kerning[char_code] && kerning[char_code][prior_char_code] || 0) / kerningFractionOf); + prior_char_code = char_code; + } + + return output; + } + + var getArraySum = function getArraySum(array) { + var i = array.length; + var output = 0; + + while (i) { + i--; + output += array[i]; + } + + return output; + }; + + var createFormXObject = function createFormXObject(formObject) { + var xobj = new AcroFormXObject(); + var height = AcroFormAppearance.internal.getHeight(formObject) || 0; + var width = AcroFormAppearance.internal.getWidth(formObject) || 0; + xobj.BBox = [0, 0, width.toFixed(2), height.toFixed(2)]; + return xobj; + }; + + var setBitPosition = function setBitPosition(variable, position, value) { + variable = variable || 0; + value = value || 1; + + var bitMask = 1; + bitMask = bitMask << position - 1; + + if (value == 1) { + // Set the Bit to 1 + var variable = variable | bitMask; + } else { + // Set the Bit to 0 + var variable = variable & ~bitMask; + } + + return variable; + }; + + /** + * Calculating the Ff entry: + * + * The Ff entry contains flags, that have to be set bitwise + * In the Following the number in the Comment is the BitPosition + */ + var calculateFlagsOnOptions = function calculateFlagsOnOptions(flags, opts, PDFVersion) { + var PDFVersion = PDFVersion || 1.3; + var flags = flags || 0; + + // 1, readOnly + if (opts.readOnly == true) { + flags = setBitPosition(flags, 1); + } + + // 2, required + if (opts.required == true) { + flags = setBitPosition(flags, 2); + } + + // 4, noExport + if (opts.noExport == true) { + flags = setBitPosition(flags, 3); + } + + // 13, multiline + if (opts.multiline == true) { + flags = setBitPosition(flags, 13); + } + + // 14, Password + if (opts.password) { + flags = setBitPosition(flags, 14); + } + + // 15, NoToggleToOff (Radio buttons only + if (opts.noToggleToOff) { + flags = setBitPosition(flags, 15); + } + + //16, Radio + if (opts.radio) { + flags = setBitPosition(flags, 16); + } + + // 17, Pushbutton + if (opts.pushbutton) { + flags = setBitPosition(flags, 17); + } + + // 18, Combo (If not set, the choiceField is a listBox!!) + if (opts.combo) { + flags = setBitPosition(flags, 18); + } + + // 19, Edit + if (opts.edit) { + flags = setBitPosition(flags, 19); + } + + // 20, Sort + if (opts.sort) { + flags = setBitPosition(flags, 20); + } + + // 21, FileSelect, PDF 1.4... + if (opts.fileSelect && PDFVersion >= 1.4) { + flags = setBitPosition(flags, 21); + } + + // 22, MultiSelect (PDF 1.4) + if (opts.multiSelect && PDFVersion >= 1.4) { + flags = setBitPosition(flags, 22); + } + + // 23, DoNotSpellCheck (PDF 1.4) + if (opts.doNotSpellCheck && PDFVersion >= 1.4) { + flags = setBitPosition(flags, 23); + } + + // 24, DoNotScroll (PDF 1.4) + if (opts.doNotScroll == true && PDFVersion >= 1.4) { + flags = setBitPosition(flags, 24); + } + + // 25, RichText (PDF 1.4) + if (opts.richText && PDFVersion >= 1.4) { + flags = setBitPosition(flags, 25); + } + + return flags; + }; + + var calculateCoordinates = function calculateCoordinates(args) { + var x = args[0]; + var y = args[1]; + var w = args[2]; + var h = args[3]; + + var coordinates = {}; + + if (Array.isArray(x)) { + x[0] = scale(x[0]); + x[1] = scale(x[1]); + x[2] = scale(x[2]); + x[3] = scale(x[3]); + } else { + x = scale(x); + y = scale(y); + w = scale(w); + h = scale(h); + } + coordinates.lowerLeft_X = x || 0; + coordinates.lowerLeft_Y = scale(pageHeight) - y - h || 0; + coordinates.upperRight_X = x + w || 0; + coordinates.upperRight_Y = scale(pageHeight) - y || 0; + + return [coordinates.lowerLeft_X.toFixed(2), coordinates.lowerLeft_Y.toFixed(2), coordinates.upperRight_X.toFixed(2), coordinates.upperRight_Y.toFixed(2)]; + }; + + var calculateAppearanceStream = function calculateAppearanceStream(formObject) { + if (formObject.appearanceStreamContent) { + // If appearanceStream is already set, use it + return formObject.appearanceStreamContent; + } + + if (!formObject.V && !formObject.DV) { + return; + } + + // else calculate it + + var stream = []; + var text = formObject.V || formObject.DV; + var calcRes = calculateX(formObject, text); + + stream.push('/Tx BMC'); + stream.push('q'); + stream.push('/F1 ' + calcRes.fontSize.toFixed(2) + ' Tf'); + stream.push('1 0 0 1 0 0 Tm'); // Text Matrix + + stream.push('BT'); // Begin Text + stream.push(calcRes.text); + + stream.push('ET'); // End Text + stream.push('Q'); + stream.push('EMC'); + + var appearanceStreamContent = new createFormXObject(formObject); + appearanceStreamContent.stream = stream.join("\n"); + + return appearanceStreamContent; + }; + + var calculateX = function calculateX(formObject, text, font, maxFontSize) { + var maxFontSize = maxFontSize || 12; + var font = font || "helvetica"; + var returnValue = { + text: "", + fontSize: "" + }; + // Remove Brackets + text = text.substr(0, 1) == '(' ? text.substr(1) : text; + text = text.substr(text.length - 1) == ')' ? text.substr(0, text.length - 1) : text; + // split into array of words + var textSplit = text.split(' '); + var fontSize = maxFontSize; // The Starting fontSize (The Maximum) + var lineSpacing = 2; + var borderPadding = 2; + + var height = AcroFormAppearance.internal.getHeight(formObject) || 0; + height = height < 0 ? -height : height; + var width = AcroFormAppearance.internal.getWidth(formObject) || 0; + width = width < 0 ? -width : width; + + var isSmallerThanWidth = function isSmallerThanWidth(i, lastLine, fontSize) { + if (i + 1 < textSplit.length) { + var tmp = lastLine + " " + textSplit[i + 1]; + var TextWidth = calculateFontSpace(tmp, fontSize + "px", font).width; + var FieldWidth = width - 2 * borderPadding; + return TextWidth <= FieldWidth; + } else { + return false; + } + }; + + fontSize++; + FontSize: while (true) { + var text = ""; + fontSize--; + var textHeight = calculateFontSpace("3", fontSize + "px", font).height; + var startY = formObject.multiline ? height - fontSize : (height - textHeight) / 2; + startY += lineSpacing; + var startX = -borderPadding; + + var lastY = startY; + var firstWordInLine = 0, + lastWordInLine = 0; + var lastLength = 0; + if (fontSize <= 0) { + // In case, the Text doesn't fit at all + fontSize = 12; + text = "(...) Tj\n"; + text += "% Width of Text: " + calculateFontSpace(text, "1px").width + ", FieldWidth:" + width + "\n"; + break; + } + + lastLength = calculateFontSpace(textSplit[0] + " ", fontSize + "px", font).width; + + var lastLine = ""; + var lineCount = 0; + Line: for (var i in textSplit) { + lastLine += textSplit[i] + " "; + // Remove last blank + lastLine = lastLine.substr(lastLine.length - 1) == " " ? lastLine.substr(0, lastLine.length - 1) : lastLine; + var key = parseInt(i); + lastLength = calculateFontSpace(lastLine + " ", fontSize + "px", font).width; + var nextLineIsSmaller = isSmallerThanWidth(key, lastLine, fontSize); + var isLastWord = i >= textSplit.length - 1; + if (nextLineIsSmaller && !isLastWord) { + lastLine += " "; + continue; // Line + } else if (!nextLineIsSmaller && !isLastWord) { + if (!formObject.multiline) { + continue FontSize; + } else { + if ((textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > height) { + // If the Text is higher than the FieldObject + continue FontSize; + } + lastWordInLine = key; + // go on + } + } else if (isLastWord) { + lastWordInLine = key; + } else { + if (formObject.multiline && (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > height) { + // If the Text is higher than the FieldObject + continue FontSize; + } + } + + var line = ''; + + for (var x = firstWordInLine; x <= lastWordInLine; x++) { + line += textSplit[x] + ' '; + } + + // Remove last blank + line = line.substr(line.length - 1) == " " ? line.substr(0, line.length - 1) : line; + //lastLength -= blankSpace.width; + lastLength = calculateFontSpace(line, fontSize + "px", font).width; + + // Calculate startX + switch (formObject.Q) { + case 2: + // Right justified + startX = width - lastLength - borderPadding; + break; + case 1: + // Q = 1 := Text-Alignment: Center + startX = (width - lastLength) / 2; + break; + case 0: + default: + startX = borderPadding; + break; + } + text += startX.toFixed(2) + ' ' + lastY.toFixed(2) + ' Td\n'; + text += '(' + line + ') Tj\n'; + // reset X in PDF + text += -startX.toFixed(2) + ' 0 Td\n'; + + // After a Line, adjust y position + lastY = -(fontSize + lineSpacing); + + // Reset for next iteration step + lastLength = 0; + firstWordInLine = lastWordInLine + 1; + lineCount++; + + lastLine = ""; + continue Line; + } + break; + } + + returnValue.text = text; + returnValue.fontSize = fontSize; + + return returnValue; + }; + /* + * small workaround for calculating the TextMetric approximately + * @param text + * @param fontsize + * @returns {TextMetrics} (Has Height and Width) + */ + var calculateFontSpace = function calculateFontSpace(text, fontsize, fonttype) { + var fonttype = fonttype || "helvetica"; + var font = scope.internal.getFont(fonttype); + var width = getStringUnitWidth(text, { font: font, fontSize: parseFloat(fontsize), charSpace: 0 }); + var height = getStringUnitWidth("3", { font: font, fontSize: parseFloat(fontsize), charSpace: 0 }) * 1.5; + var result = { height: height, width: width }; + return result; + }; + + var acroformPluginTemplate = { + fields: [], + xForms: [], + /** + * acroFormDictionaryRoot contains information about the AcroForm Dictionary + * 0: The Event-Token, the AcroFormDictionaryCallback has + * 1: The Object ID of the Root + */ + acroFormDictionaryRoot: null, + /** + * After the PDF gets evaluated, the reference to the root has to be reset, + * this indicates, whether the root has already been printed out + */ + printedOut: false, + internal: null, + isInitialized: false + }; + + var annotReferenceCallback = function annotReferenceCallback() { + for (var i in scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields) { + var formObject = scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields[i]; + // add Annot Reference! + if (formObject.hasAnnotation) { + // If theres an Annotation Widget in the Form Object, put the Reference in the /Annot array + createAnnotationReference.call(scope, formObject); + } + } + }; + + var putForm = function putForm(formObject) { + if (scope.internal.acroformPlugin.printedOut) { + scope.internal.acroformPlugin.printedOut = false; + scope.internal.acroformPlugin.acroFormDictionaryRoot = null; + } + if (!scope.internal.acroformPlugin.acroFormDictionaryRoot) { + initializeAcroForm.call(scope); + } + scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields.push(formObject); + }; + /** + * Create the Reference to the widgetAnnotation, so that it gets referenced in the Annot[] int the+ + * (Requires the Annotation Plugin) + */ + var createAnnotationReference = function createAnnotationReference(object) { + var options = { + type: 'reference', + object: object + }; + scope.annotationPlugin.annotations[scope.internal.getPageInfo(object.page).pageNumber].push(options); + }; + + // Callbacks + + var putCatalogCallback = function putCatalogCallback() { + //Put reference to AcroForm to DocumentCatalog + if (typeof scope.internal.acroformPlugin.acroFormDictionaryRoot != 'undefined') { + // for safety, shouldn't normally be the case + scope.internal.write('/AcroForm ' + scope.internal.acroformPlugin.acroFormDictionaryRoot.objId + ' ' + 0 + ' R'); + } else { + console.log('Root missing...'); + } + }; + + /** + * Adds /Acroform X 0 R to Document Catalog, + * and creates the AcroForm Dictionary + */ + var AcroFormDictionaryCallback = function AcroFormDictionaryCallback() { + // Remove event + scope.internal.events.unsubscribe(scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID); + + delete scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID; + + scope.internal.acroformPlugin.printedOut = true; + }; + + /** + * Creates the single Fields and writes them into the Document + * + * If fieldArray is set, use the fields that are inside it instead of the fields from the AcroRoot + * (for the FormXObjects...) + */ + var createFieldCallback = function createFieldCallback(fieldArray) { + var standardFields = !fieldArray; + + if (!fieldArray) { + // in case there is no fieldArray specified, we want to print out the Fields of the AcroForm + // Print out Root + scope.internal.newObjectDeferredBegin(scope.internal.acroformPlugin.acroFormDictionaryRoot.objId); + scope.internal.out(scope.internal.acroformPlugin.acroFormDictionaryRoot.getString()); + } + + var fieldArray = fieldArray || scope.internal.acroformPlugin.acroFormDictionaryRoot.Kids; + + for (var i in fieldArray) { + var form = fieldArray[i]; + + var oldRect = form.Rect; + + if (form.Rect) { + form.Rect = calculateCoordinates.call(this, form.Rect); + } + + // Start Writing the Object + scope.internal.newObjectDeferredBegin(form.objId); + + var content = form.objId + " 0 obj\n<<\n"; + + if ((typeof form === 'undefined' ? 'undefined' : _typeof(form)) === "object" && typeof form.getContent === "function") { + content += form.getContent(); + } + + form.Rect = oldRect; + + if (form.hasAppearanceStream && !form.appearanceStreamContent) { + // Calculate Appearance + var appearance = calculateAppearanceStream.call(this, form); + content += "/AP << /N " + appearance + " >>\n"; + + scope.internal.acroformPlugin.xForms.push(appearance); + } + + // Assume AppearanceStreamContent is a Array with N,R,D (at least one of them!) + if (form.appearanceStreamContent) { + content += "/AP << "; + // Iterate over N,R and D + for (var k in form.appearanceStreamContent) { + var value = form.appearanceStreamContent[k]; + content += "/" + k + " "; + content += "<< "; + if (Object.keys(value).length >= 1 || Array.isArray(value)) { + // appearanceStream is an Array or Object! + for (var i in value) { + var obj = value[i]; + if (typeof obj === 'function') { + // if Function is referenced, call it in order to get the FormXObject + obj = obj.call(this, form); + } + content += "/" + i + " " + obj + " "; + + // In case the XForm is already used, e.g. OffState of CheckBoxes, don't add it + if (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0)) scope.internal.acroformPlugin.xForms.push(obj); + } + } else { + var obj = value; + if (typeof obj === 'function') { + // if Function is referenced, call it in order to get the FormXObject + obj = obj.call(this, form); + } + content += "/" + i + " " + obj + " \n"; + if (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0)) scope.internal.acroformPlugin.xForms.push(obj); + } + content += " >>\n"; + } + + // appearance stream is a normal Object.. + content += ">>\n"; + } + + content += ">>\nendobj\n"; + + scope.internal.out(content); + } + if (standardFields) { + createXFormObjectCallback.call(this, scope.internal.acroformPlugin.xForms); + } + }; + + var createXFormObjectCallback = function createXFormObjectCallback(fieldArray) { + for (var i in fieldArray) { + var key = i; + var form = fieldArray[i]; + // Start Writing the Object + scope.internal.newObjectDeferredBegin(form && form.objId); + + var content = ""; + if ((typeof form === 'undefined' ? 'undefined' : _typeof(form)) === "object" && typeof form.getString === "function") { + content = form.getString(); + } + scope.internal.out(content); + + delete fieldArray[key]; + } + }; + var initializeAcroForm = function initializeAcroForm() { + if (this.internal !== undefined && (this.internal.acroformPlugin === undefined || this.internal.acroformPlugin.isInitialized === false)) { + + scope = this; + + AcroFormField.FieldNum = 0; + this.internal.acroformPlugin = JSON.parse(JSON.stringify(acroformPluginTemplate)); + if (this.internal.acroformPlugin.acroFormDictionaryRoot) { + //return; + throw new Error("Exception while creating AcroformDictionary"); + } + scaleFactor = scope.internal.scaleFactor; + pageHeight = scope.internal.pageSize.getHeight(); + + // The Object Number of the AcroForm Dictionary + scope.internal.acroformPlugin.acroFormDictionaryRoot = new AcroFormDictionary(); + + // add Callback for creating the AcroForm Dictionary + scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID = scope.internal.events.subscribe('postPutResources', AcroFormDictionaryCallback); + + scope.internal.events.subscribe('buildDocument', annotReferenceCallback); //buildDocument + + // Register event, that is triggered when the DocumentCatalog is written, in order to add /AcroForm + scope.internal.events.subscribe('putCatalog', putCatalogCallback); + + // Register event, that creates all Fields + scope.internal.events.subscribe('postPutPages', createFieldCallback); + + scope.internal.acroformPlugin.isInitialized = true; + } + }; + + // ### Handy Functions: + + var arrayToPdfArray = function arrayToPdfArray(array) { + if (Array.isArray(array)) { + var content = ' ['; + for (var i in array) { + var element = array[i].toString(); + content += element; + content += i < array.length - 1 ? ' ' : ''; + } + content += ']'; + + return content; + } + }; + + var toPdfString = function toPdfString(string) { + string = string || ""; + + // put Bracket at the Beginning of the String + if (string.indexOf('(') !== 0) { + string = '(' + string; + } + + if (string.substring(string.length - 1) != ')') { + string += ')'; + } + return string; + }; + + // ########################## + // Classes + // ########################## + + var AcroFormPDFObject = function AcroFormPDFObject() { + // The Object ID in the PDF Object Model + // todo + var _objId; + Object.defineProperty(this, 'objId', { + get: function get$$1() { + if (!_objId) { + _objId = scope.internal.newObjectDeferred(); + } + if (!_objId) { + console.log("Couldn't create Object ID"); + } + return _objId; + }, + configurable: false + }); + }; + + AcroFormPDFObject.prototype.toString = function () { + return this.objId + " 0 R"; + }; + + AcroFormPDFObject.prototype.getString = function () { + var res = this.objId + " 0 obj\n<<"; + var content = this.getContent(); + + res += content + ">>\n"; + if (this.stream) { + res += "stream\n"; + res += this.stream; + res += "\nendstream\n"; + } + res += "endobj\n"; + return res; + }; + + AcroFormPDFObject.prototype.getContent = function () { + /** + * Prints out all enumerable Variables from the Object + * @param fieldObject + * @returns {string} + */ + var createContentFromFieldObject = function createContentFromFieldObject(fieldObject) { + var content = ''; + + var keys = Object.keys(fieldObject).filter(function (key) { + return key != 'content' && key != 'appearanceStreamContent' && key.substring(0, 1) != "_"; + }); + + for (var i in keys) { + var key = keys[i]; + var value = fieldObject[key]; + + /*if (key == 'Rect' && value) { + value = AcroForm.internal.calculateCoordinates.call(jsPDF.API.acroformPlugin.internal, value); + }*/ + + if (value) { + if (Array.isArray(value)) { + content += '/' + key + ' ' + arrayToPdfArray(value) + "\n"; + } else if (value instanceof AcroFormPDFObject) { + // In case it is a reference to another PDFObject, take the referennce number + content += '/' + key + ' ' + value.objId + " 0 R" + "\n"; + } else { + content += '/' + key + ' ' + value + '\n'; + } + } + } + return content; + }; + + var object = ""; + + object += createContentFromFieldObject(this); + return object; + }; + + var AcroFormXObject = function AcroFormXObject() { + AcroFormPDFObject.call(this); + this.Type = "/XObject"; + this.Subtype = "/Form"; + this.FormType = 1; + this.BBox; + this.Matrix; + this.Resources = "2 0 R"; + this.PieceInfo; + var _stream; + Object.defineProperty(this, 'Length', { + enumerable: true, + get: function get$$1() { + return _stream !== undefined ? _stream.length : 0; + } + }); + Object.defineProperty(this, 'stream', { + enumerable: false, + set: function set$$1(val) { + _stream = val.trim(); + }, + get: function get$$1() { + if (_stream) { + return _stream; + } else { + return null; + } + } + }); + }; + + inherit(AcroFormXObject, AcroFormPDFObject); + // ##### The Objects, the User can Create: + + var AcroFormDictionary = function AcroFormDictionary() { + AcroFormPDFObject.call(this); + var _Kids = []; + Object.defineProperty(this, 'Kids', { + enumerable: false, + configurable: true, + get: function get$$1() { + if (_Kids.length > 0) { + return _Kids; + } else { + return; + } + } + }); + Object.defineProperty(this, 'Fields', { + enumerable: true, + configurable: true, + get: function get$$1() { + return _Kids; + } + }); + // Default Appearance + this.DA; + }; + + inherit(AcroFormDictionary, AcroFormPDFObject); + + // The Field Object contains the Variables, that every Field needs + // Rectangle for Appearance: lower_left_X, lower_left_Y, width, height + var AcroFormField = function AcroFormField() { + + AcroFormPDFObject.call(this); + + var _Rect; + Object.defineProperty(this, 'Rect', { + enumerable: true, + configurable: false, + get: function get$$1() { + if (!_Rect) { + return; + } + var tmp = _Rect; + //var calculatedRes = AcroForm.internal.calculateCoordinates(_Rect); // do later! + return tmp; + }, + set: function set$$1(val) { + _Rect = val; + } + }); + + var _FT = ""; + Object.defineProperty(this, 'FT', { + enumerable: true, + set: function set$$1(val) { + _FT = val; + }, + get: function get$$1() { + return _FT; + } + }); + /** + * The Partial name of the Field Object. + * It has to be unique. + */ + var _T; + + Object.defineProperty(this, 'T', { + enumerable: true, + configurable: false, + set: function set$$1(val) { + _T = val; + }, + get: function get$$1() { + if (!_T || _T.length < 1) { + if (this instanceof AcroFormChildClass) { + // In case of a Child from a Radio´Group, you don't need a FieldName!!! + return; + } + return "(FieldObject" + AcroFormField.FieldNum++ + ")"; + } + if (_T.substring(0, 1) == "(" && _T.substring(_T.length - 1)) { + return _T; + } + return "(" + _T + ")"; + } + }); + + var _DA; + // Defines the default appearance (Needed for variable Text) + Object.defineProperty(this, 'DA', { + enumerable: true, + get: function get$$1() { + if (!_DA) { + return; + } + return '(' + _DA + ')'; + }, + set: function set$$1(val) { + _DA = val; + } + }); + + var _DV; + // Defines the default value + Object.defineProperty(this, 'DV', { + enumerable: true, + configurable: true, + get: function get$$1() { + if (!_DV) { + return; + } + return _DV; + }, + set: function set$$1(val) { + _DV = val; + } + }); + + var _V; + // Defines the default value + Object.defineProperty(this, 'V', { + enumerable: true, + configurable: true, + get: function get$$1() { + if (!_V) { + return; + } + return _V; + }, + set: function set$$1(val) { + _V = val; + } + }); + + //this.Type = "/Annot"; + //this.Subtype = "/Widget"; + Object.defineProperty(this, 'Type', { + enumerable: true, + get: function get$$1() { + return this.hasAnnotation ? "/Annot" : null; + } + }); + + Object.defineProperty(this, 'Subtype', { + enumerable: true, + get: function get$$1() { + return this.hasAnnotation ? "/Widget" : null; + } + }); + + /** + * + * @type {Array} + */ + this.BG; + + Object.defineProperty(this, 'hasAnnotation', { + enumerable: false, + get: function get$$1() { + if (this.Rect || this.BC || this.BG) { + return true; + } + return false; + } + }); + + Object.defineProperty(this, 'hasAppearanceStream', { + enumerable: false, + configurable: true, + writable: true + }); + + Object.defineProperty(this, 'page', { + enumerable: false, + configurable: true, + writable: true + }); + }; + + inherit(AcroFormField, AcroFormPDFObject); + + var AcroFormChoiceField = function AcroFormChoiceField() { + AcroFormField.call(this); + // Field Type = Choice Field + this.FT = "/Ch"; + // options + this.Opt = []; + this.V = '()'; + // Top Index + this.TI = 0; + /** + * Defines, whether the + * @type {boolean} + */ + + var _combo = false; + + Object.defineProperty(this, 'combo', { + enumerable: false, + get: function get$$1() { + return _combo; + }, + set: function set$$1(val) { + _combo = val; + } + }); + /** + * Defines, whether the Choice Field is an Edit Field. + * An Edit Field is automatically an Combo Field. + */ + Object.defineProperty(this, 'edit', { + enumerable: true, + set: function set$$1(val) { + if (val == true) { + this._edit = true; + // ComboBox has to be true + this.combo = true; + } else { + this._edit = false; + } + }, + get: function get$$1() { + if (!this._edit) { + return false; + } + return this._edit; + }, + configurable: false + }); + this.hasAppearanceStream = true; + }; + inherit(AcroFormChoiceField, AcroFormField); + + var AcroFormListBox = function AcroFormListBox() { + AcroFormChoiceField.call(this); + this.combo = false; + }; + inherit(AcroFormListBox, AcroFormChoiceField); + + var AcroFormComboBox = function AcroFormComboBox() { + AcroFormListBox.call(this); + this.combo = true; + }; + inherit(AcroFormComboBox, AcroFormListBox); + + var AcroFormEditBox = function AcroFormEditBox() { + AcroFormComboBox.call(this); + this.edit = true; + }; + inherit(AcroFormEditBox, AcroFormComboBox); + + var AcroFormButton = function AcroFormButton() { + AcroFormField.call(this); + this.FT = "/Btn"; + //this.hasAnnotation = true; + }; + inherit(AcroFormButton, AcroFormField); + + var AcroFormPushButton = function AcroFormPushButton() { + AcroFormButton.call(this); + + var _pushbutton = true; + Object.defineProperty(this, 'pushbutton', { + enumerable: false, + get: function get$$1() { + return _pushbutton; + }, + set: function set$$1(val) { + _pushbutton = val; + } + }); + }; + inherit(AcroFormPushButton, AcroFormButton); + + var AcroFormRadioButton = function AcroFormRadioButton() { + AcroFormButton.call(this); + + var _radio = true; + Object.defineProperty(this, 'radio', { + enumerable: false, + get: function get$$1() { + return _radio; + }, + set: function set$$1(val) { + _radio = val; + } + }); + + var _Kids = []; + Object.defineProperty(this, 'Kids', { + enumerable: true, + get: function get$$1() { + if (_Kids.length > 0) { + return _Kids; + } + } + }); + + Object.defineProperty(this, '__Kids', { + get: function get$$1() { + return _Kids; + } + }); + + var _noToggleToOff; + + Object.defineProperty(this, 'noToggleToOff', { + enumerable: false, + get: function get$$1() { + return _noToggleToOff; + }, + set: function set$$1(val) { + _noToggleToOff = val; + } + }); + + //this.hasAnnotation = false; + }; + inherit(AcroFormRadioButton, AcroFormButton); + + /* + * The Child classs of a RadioButton (the radioGroup) + * -> The single Buttons + */ + var AcroFormChildClass = function AcroFormChildClass(parent, name) { + AcroFormField.call(this); + this.Parent = parent; + + // todo: set AppearanceType as variable that can be set from the outside... + this._AppearanceType = AcroFormAppearance.RadioButton.Circle; // The Default appearanceType is the Circle + this.appearanceStreamContent = this._AppearanceType.createAppearanceStream(name); + + // Set Print in the Annot Flag + this.F = setBitPosition(this.F, 3, 1); + + // Set AppearanceCharacteristicsDictionary with default appearance if field is not interacting with user + this.MK = this._AppearanceType.createMK(); // (8) -> Cross, (1)-> Circle, ()-> nothing + + // Default Appearance is Off + this.AS = "/Off"; // + name; + + this._Name = name; + }; + inherit(AcroFormChildClass, AcroFormField); + + AcroFormRadioButton.prototype.setAppearance = function (appearance) { + if (!('createAppearanceStream' in appearance && 'createMK' in appearance)) { + console.log("Couldn't assign Appearance to RadioButton. Appearance was Invalid!"); + return; + } + for (var i in this.__Kids) { + var child = this.__Kids[i]; + + child.appearanceStreamContent = appearance.createAppearanceStream(child._Name); + child.MK = appearance.createMK(); + } + }; + + AcroFormRadioButton.prototype.createOption = function (name) { + var parent = this; + var kidCount = this.__Kids.length; + + // Create new Child for RadioGroup + var child = new AcroFormChildClass(parent, name); + // Add to Parent + this.__Kids.push(child); + + jsPDFAPI.addField(child); + + return child; + }; + + var AcroFormCheckBox = function AcroFormCheckBox() { + AcroFormButton.call(this); + this.appearanceStreamContent = AcroFormAppearance.CheckBox.createAppearanceStream(); + this.MK = AcroFormAppearance.CheckBox.createMK(); + this.AS = "/On"; + this.V = "/On"; + }; + inherit(AcroFormCheckBox, AcroFormButton); + + var AcroFormTextField = function AcroFormTextField() { + AcroFormField.call(this); + this.DA = AcroFormAppearance.createDefaultAppearanceStream(); + this.F = 4; + var _V; + Object.defineProperty(this, 'V', { + get: function get$$1() { + if (_V) { + return toPdfString(_V); + } else { + return _V; + } + }, + enumerable: true, + set: function set$$1(val) { + _V = val; + } + }); + + var _DV; + Object.defineProperty(this, 'DV', { + get: function get$$1() { + if (_DV) { + return toPdfString(_DV); + } else { + return _DV; + } + }, + enumerable: true, + set: function set$$1(val) { + _DV = val; + } + }); + + var _multiline = false; + Object.defineProperty(this, 'multiline', { + enumerable: false, + get: function get$$1() { + return _multiline; + }, + set: function set$$1(val) { + _multiline = val; + } + }); + + /** + * For PDF 1.4 + * @type {boolean} + */ + var _fileSelect = false; + Object.defineProperty(this, 'fileSelect', { + enumerable: false, + get: function get$$1() { + return _fileSelect; + }, + set: function set$$1(val) { + _fileSelect = val; + } + }); + /** + * For PDF 1.4 + * @type {boolean} + */ + var _doNotSpellCheck = false; + Object.defineProperty(this, 'doNotSpellCheck', { + enumerable: false, + get: function get$$1() { + return _doNotSpellCheck; + }, + set: function set$$1(val) { + _doNotSpellCheck = val; + } + }); + /** + * For PDF 1.4 + * @type {boolean} + */ + var _doNotScroll = false; + Object.defineProperty(this, 'doNotScroll', { + enumerable: false, + get: function get$$1() { + return _doNotScroll; + }, + set: function set$$1(val) { + _doNotScroll = val; + } + }); + + var _MaxLen = false; + Object.defineProperty(this, 'MaxLen', { + enumerable: true, + get: function get$$1() { + return _MaxLen; + }, + set: function set$$1(val) { + _MaxLen = val; + } + }); + + Object.defineProperty(this, 'hasAppearanceStream', { + enumerable: false, + get: function get$$1() { + return this.V || this.DV; + } + }); + }; + inherit(AcroFormTextField, AcroFormField); + + var AcroFormPasswordField = function AcroFormPasswordField() { + AcroFormTextField.call(this); + + var _password = true; + Object.defineProperty(this, 'password', { + enumerable: false, + get: function get$$1() { + return _password; + }, + set: function set$$1(val) { + _password = val; + } + }); + }; + inherit(AcroFormPasswordField, AcroFormTextField); + + // Contains Methods for creating standard appearances + var AcroFormAppearance = { + CheckBox: { + createAppearanceStream: function createAppearanceStream() { + var appearance = { + N: { + On: AcroFormAppearance.CheckBox.YesNormal + }, + D: { + On: AcroFormAppearance.CheckBox.YesPushDown, + Off: AcroFormAppearance.CheckBox.OffPushDown + } + }; + + return appearance; + }, + /** + * If any other icons are needed, the number between the brackets can be changed + * @returns {string} + */ + createMK: function createMK() { + return "<< /CA (3)>>"; + }, + /** + * Returns the standard On Appearance for a CheckBox + * @returns {AcroFormXObject} + */ + YesPushDown: function YesPushDown(formObject) { + var xobj = createFormXObject(formObject); + var stream = []; + var zapfDingbatsId = scope.internal.getFont("zapfdingbats", "normal").id; + formObject.Q = 1; // set text-alignment as centered + var calcRes = calculateX(formObject, "3", "ZapfDingbats", 50); + stream.push("0.749023 g"); + stream.push("0 0 " + AcroFormAppearance.internal.getWidth(formObject).toFixed(2) + " " + AcroFormAppearance.internal.getHeight(formObject).toFixed(2) + " re"); + stream.push("f"); + stream.push("BMC"); + stream.push("q"); + stream.push("0 0 1 rg"); + stream.push("/" + zapfDingbatsId + " " + calcRes.fontSize.toFixed(2) + " Tf 0 g"); + stream.push("BT"); + stream.push(calcRes.text); + stream.push("ET"); + stream.push("Q"); + stream.push("EMC"); + xobj.stream = stream.join("\n"); + return xobj; + }, + + YesNormal: function YesNormal(formObject) { + var xobj = createFormXObject(formObject); + var zapfDingbatsId = scope.internal.getFont("zapfdingbats", "normal").id; + var stream = []; + formObject.Q = 1; // set text-alignment as centered + var height = AcroFormAppearance.internal.getHeight(formObject); + var width = AcroFormAppearance.internal.getWidth(formObject); + var calcRes = calculateX(formObject, "3", "ZapfDingbats", height * 0.9); + stream.push("1 g"); + stream.push("0 0 " + width.toFixed(2) + " " + height.toFixed(2) + " re"); + stream.push("f"); + stream.push("q"); + stream.push("0 0 1 rg"); + stream.push("0 0 " + (width - 1).toFixed(2) + " " + (height - 1).toFixed(2) + " re"); + stream.push("W"); + stream.push("n"); + stream.push("0 g"); + stream.push("BT"); + stream.push("/" + zapfDingbatsId + " " + calcRes.fontSize.toFixed(2) + " Tf 0 g"); + stream.push(calcRes.text); + stream.push("ET"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + + /** + * Returns the standard Off Appearance for a CheckBox + * @returns {AcroFormXObject} + */ + OffPushDown: function OffPushDown(formObject) { + var xobj = createFormXObject(formObject); + var stream = []; + stream.push("0.749023 g"); + stream.push("0 0 " + AcroFormAppearance.internal.getWidth(formObject).toFixed(2) + " " + AcroFormAppearance.internal.getHeight(formObject).toFixed(2) + " re"); + stream.push("f"); + xobj.stream = stream.join("\n"); + return xobj; + } + }, + + RadioButton: { + Circle: { + createAppearanceStream: function createAppearanceStream(name) { + var appearanceStreamContent = { + D: { + 'Off': AcroFormAppearance.RadioButton.Circle.OffPushDown + }, + N: {} + }; + appearanceStreamContent.N[name] = AcroFormAppearance.RadioButton.Circle.YesNormal; + appearanceStreamContent.D[name] = AcroFormAppearance.RadioButton.Circle.YesPushDown; + return appearanceStreamContent; + }, + createMK: function createMK() { + return "<< /CA (l)>>"; + }, + + YesNormal: function YesNormal(formObject) { + var xobj = createFormXObject(formObject); + var stream = []; + // Make the Radius of the Circle relative to min(height, width) of formObject + var DotRadius = AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject) ? AcroFormAppearance.internal.getWidth(formObject) / 4 : AcroFormAppearance.internal.getHeight(formObject) / 4; + // The Borderpadding... + DotRadius *= 0.9; + var c = AcroFormAppearance.internal.Bezier_C; + /* + The Following is a Circle created with Bezier-Curves. + */ + stream.push("q"); + stream.push("1 0 0 1 " + AcroFormAppearance.internal.getWidth(formObject) / 2 + " " + AcroFormAppearance.internal.getHeight(formObject) / 2 + " cm"); + stream.push(DotRadius + " 0 m"); + stream.push(DotRadius + " " + DotRadius * c + " " + DotRadius * c + " " + DotRadius + " 0 " + DotRadius + " c"); + stream.push("-" + DotRadius * c + " " + DotRadius + " -" + DotRadius + " " + DotRadius * c + " -" + DotRadius + " 0 c"); + stream.push("-" + DotRadius + " -" + DotRadius * c + " -" + DotRadius * c + " -" + DotRadius + " 0 -" + DotRadius + " c"); + stream.push(DotRadius * c + " -" + DotRadius + " " + DotRadius + " -" + DotRadius * c + " " + DotRadius + " 0 c"); + stream.push("f"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + YesPushDown: function YesPushDown(formObject) { + var xobj = createFormXObject(formObject); + var stream = []; + var DotRadius = AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject) ? AcroFormAppearance.internal.getWidth(formObject) / 4 : AcroFormAppearance.internal.getHeight(formObject) / 4; + // The Borderpadding... + DotRadius *= 0.9; + // Save results for later use; no need to waste processor ticks on doing math + var k = DotRadius * 2; + // var c = AcroFormAppearance.internal.Bezier_C; + var kc = k * AcroFormAppearance.internal.Bezier_C; + var dc = DotRadius * AcroFormAppearance.internal.Bezier_C; + + stream.push("0.749023 g"); + stream.push("q"); + stream.push("1 0 0 1 " + (AcroFormAppearance.internal.getWidth(formObject) / 2).toFixed(2) + " " + (AcroFormAppearance.internal.getHeight(formObject) / 2).toFixed(2) + " cm"); + stream.push(k + " 0 m"); + stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c"); + stream.push("-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c"); + stream.push("-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c"); + stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c"); + stream.push("f"); + stream.push("Q"); + stream.push("0 g"); + stream.push("q"); + stream.push("1 0 0 1 " + (AcroFormAppearance.internal.getWidth(formObject) / 2).toFixed(2) + " " + (AcroFormAppearance.internal.getHeight(formObject) / 2).toFixed(2) + " cm"); + stream.push(DotRadius + " 0 m"); + stream.push("" + DotRadius + " " + dc + " " + dc + " " + DotRadius + " 0 " + DotRadius + " c"); + stream.push("-" + dc + " " + DotRadius + " -" + DotRadius + " " + dc + " -" + DotRadius + " 0 c"); + stream.push("-" + DotRadius + " -" + dc + " -" + dc + " -" + DotRadius + " 0 -" + DotRadius + " c"); + stream.push(dc + " -" + DotRadius + " " + DotRadius + " -" + dc + " " + DotRadius + " 0 c"); + stream.push("f"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + OffPushDown: function OffPushDown(formObject) { + var xobj = createFormXObject(formObject); + var stream = []; + var DotRadius = AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject) ? AcroFormAppearance.internal.getWidth(formObject) / 4 : AcroFormAppearance.internal.getHeight(formObject) / 4; + // The Borderpadding... + DotRadius *= 0.9; + // Save results for later use; no need to waste processor ticks on doing math + var k = DotRadius * 2; + // var c = AcroFormAppearance.internal.Bezier_C; + var kc = k * AcroFormAppearance.internal.Bezier_C; + + stream.push("0.749023 g"); + stream.push("q"); + stream.push("1 0 0 1 " + (AcroFormAppearance.internal.getWidth(formObject) / 2).toFixed(2) + " " + (AcroFormAppearance.internal.getHeight(formObject) / 2).toFixed(2) + " cm"); + stream.push(k + " 0 m"); + stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c"); + stream.push("-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c"); + stream.push("-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c"); + stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c"); + stream.push("f"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + } + }, + + Cross: { + /** + * Creates the Actual AppearanceDictionary-References + * @param name + * @returns + */ + createAppearanceStream: function createAppearanceStream(name) { + var appearanceStreamContent = { + D: { + 'Off': AcroFormAppearance.RadioButton.Cross.OffPushDown + }, + N: {} + }; + appearanceStreamContent.N[name] = AcroFormAppearance.RadioButton.Cross.YesNormal; + appearanceStreamContent.D[name] = AcroFormAppearance.RadioButton.Cross.YesPushDown; + return appearanceStreamContent; + }, + createMK: function createMK() { + return "<< /CA (8)>>"; + }, + + YesNormal: function YesNormal(formObject) { + var xobj = createFormXObject(formObject); + var stream = []; + var cross = AcroFormAppearance.internal.calculateCross(formObject); + stream.push("q"); + stream.push("1 1 " + (AcroFormAppearance.internal.getWidth(formObject) - 2).toFixed(2) + " " + (AcroFormAppearance.internal.getHeight(formObject) - 2).toFixed(2) + " re"); + stream.push("W"); + stream.push("n"); + stream.push(cross.x1.x.toFixed(2) + " " + cross.x1.y.toFixed(2) + " m"); + stream.push(cross.x2.x.toFixed(2) + " " + cross.x2.y.toFixed(2) + " l"); + stream.push(cross.x4.x.toFixed(2) + " " + cross.x4.y.toFixed(2) + " m"); + stream.push(cross.x3.x.toFixed(2) + " " + cross.x3.y.toFixed(2) + " l"); + stream.push("s"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + YesPushDown: function YesPushDown(formObject) { + var xobj = createFormXObject(formObject); + var cross = AcroFormAppearance.internal.calculateCross(formObject); + var stream = []; + stream.push("0.749023 g"); + stream.push("0 0 " + AcroFormAppearance.internal.getWidth(formObject).toFixed(2) + " " + AcroFormAppearance.internal.getHeight(formObject).toFixed(2) + " re"); + stream.push("f"); + stream.push("q"); + stream.push("1 1 " + (AcroFormAppearance.internal.getWidth(formObject) - 2).toFixed(2) + " " + (AcroFormAppearance.internal.getHeight(formObject) - 2).toFixed(2) + " re"); + stream.push("W"); + stream.push("n"); + stream.push(cross.x1.x.toFixed(2) + " " + cross.x1.y.toFixed(2) + " m"); + stream.push(cross.x2.x.toFixed(2) + " " + cross.x2.y.toFixed(2) + " l"); + stream.push(cross.x4.x.toFixed(2) + " " + cross.x4.y.toFixed(2) + " m"); + stream.push(cross.x3.x.toFixed(2) + " " + cross.x3.y.toFixed(2) + " l"); + stream.push("s"); + stream.push("Q"); + xobj.stream = stream.join("\n"); + return xobj; + }, + OffPushDown: function OffPushDown(formObject) { + var xobj = createFormXObject(formObject); + var stream = []; + stream.push("0.749023 g"); + stream.push("0 0 " + AcroFormAppearance.internal.getWidth(formObject).toFixed(2) + " " + AcroFormAppearance.internal.getHeight(formObject).toFixed(2) + " re"); + stream.push("f"); + xobj.stream = stream.join("\n"); + return xobj; + } + } + }, + + /** + * Returns the standard Appearance + * @returns {AcroFormXObject} + */ + createDefaultAppearanceStream: function createDefaultAppearanceStream(formObject) { + // Set Helvetica to Standard Font (size: auto) + // Color: Black + return "/F1 0 Tf 0 g"; + } + }; + + AcroFormAppearance.internal = { + Bezier_C: 0.551915024494, + + calculateCross: function calculateCross(formObject) { + var min = function min(x, y) { + return x > y ? y : x; + }; + + var width = AcroFormAppearance.internal.getWidth(formObject); + var height = AcroFormAppearance.internal.getHeight(formObject); + var a = min(width, height); + + + var cross = { + x1: { // upperLeft + x: (width - a) / 2, + y: (height - a) / 2 + a //height - borderPadding + }, + x2: { // lowerRight + x: (width - a) / 2 + a, + y: (height - a) / 2 //borderPadding + }, + x3: { // lowerLeft + x: (width - a) / 2, + y: (height - a) / 2 //borderPadding + }, + x4: { // upperRight + x: (width - a) / 2 + a, + y: (height - a) / 2 + a //height - borderPadding + } + }; + + return cross; + } + }; + AcroFormAppearance.internal.getWidth = function (formObject) { + var result = 0; + if ((typeof formObject === 'undefined' ? 'undefined' : _typeof(formObject)) === "object") { + result = scale(formObject.Rect[2]); //(formObject.Rect[2] - formObject.Rect[0]) || 0; + } + return result; + }; + AcroFormAppearance.internal.getHeight = function (formObject) { + var result = 0; + if ((typeof formObject === 'undefined' ? 'undefined' : _typeof(formObject)) === "object") { + result = scale(formObject.Rect[3]); //(formObject.Rect[1] - formObject.Rect[3]) || 0; + } + return result; + }; + + // Public: + + jsPDFAPI.addField = function (fieldObject) { + initializeAcroForm.call(this); + //var opt = parseOptions(fieldObject); + if (fieldObject instanceof AcroFormTextField) { + this.addTextField.call(this, fieldObject); + } else if (fieldObject instanceof AcroFormChoiceField) { + this.addChoiceField.call(this, fieldObject); + } else if (fieldObject instanceof AcroFormButton) { + this.addButton.call(this, fieldObject); + } else if (fieldObject instanceof AcroFormChildClass) { + putForm.call(this, fieldObject); + } else if (fieldObject) { + // try to put.. + putForm.call(this, fieldObject); + } + fieldObject.page = scope.internal.getCurrentPageInfo().pageNumber; + return this; + }; + + /** + * Button + * FT = Btn + */ + jsPDFAPI.addButton = function (opts) { + initializeAcroForm.call(this); + var options = opts || new AcroFormField(); + + options.FT = '/Btn'; + options.Ff = calculateFlagsOnOptions(options.Ff, opts, scope.internal.getPDFVersion()); + + putForm.call(this, options); + }; + + jsPDFAPI.addTextField = function (opts) { + initializeAcroForm.call(this); + var options = opts || new AcroFormField(); + + options.FT = '/Tx'; + + options.Ff = calculateFlagsOnOptions(options.Ff, opts, scope.internal.getPDFVersion()); + + // Add field + putForm.call(this, options); + }; + + jsPDFAPI.addChoiceField = function (opts) { + initializeAcroForm.call(this); + var options = opts || new AcroFormField(); + + options.FT = '/Ch'; + + options.Ff = calculateFlagsOnOptions(options.Ff, opts, scope.internal.getPDFVersion()); + //options.hasAnnotation = true; + + // Add field + putForm.call(this, options); + }; + + if ((typeof globalObj === 'undefined' ? 'undefined' : _typeof(globalObj)) == "object") { + globalObj["ChoiceField"] = AcroFormChoiceField; + globalObj["ListBox"] = AcroFormListBox; + globalObj["ComboBox"] = AcroFormComboBox; + globalObj["EditBox"] = AcroFormEditBox; + globalObj["Button"] = AcroFormButton; + globalObj["PushButton"] = AcroFormPushButton; + globalObj["RadioButton"] = AcroFormRadioButton; + globalObj["CheckBox"] = AcroFormCheckBox; + globalObj["TextField"] = AcroFormTextField; + globalObj["PasswordField"] = AcroFormPasswordField; + + //backwardsCompatibility + globalObj["AcroForm"] = { Appearance: AcroFormAppearance }; + } + })(jsPDF.API, typeof window !== "undefined" && window || typeof global !== "undefined" && global); + + /** + * jsPDF addHTML PlugIn + * Copyright (c) 2014 Diego Casorran + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + + (function (jsPDFAPI) { + + /** + * Renders an HTML element to canvas object which added to the PDF + * + * This feature requires [html2canvas](https://github.com/niklasvh/html2canvas) + * or [rasterizeHTML](https://github.com/cburgmer/rasterizeHTML.js) + * + * @returns {jsPDF} + * @name addHTML + * @param element {Mixed} HTML Element, or anything supported by html2canvas. + * @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 options {Object} Additional options, check the code below. + * @param callback {Function} to call when the rendering has finished. + * NOTE: Every parameter is optional except 'element' and 'callback', in such + * case the image is positioned at 0x0 covering the whole PDF document + * size. Ie, to easily take screenshots of webpages saving them to PDF. + * @deprecated This is being replace with a vector-supporting API. See + * [this link](https://cdn.rawgit.com/MrRio/jsPDF/master/examples/html2pdf/showcase_supported_html.html) + */ + + jsPDFAPI.addHTML = function (element, x, y, options, callback) { + + if (typeof html2canvas === 'undefined' && typeof rasterizeHTML === 'undefined') throw new Error('You need either ' + 'https://github.com/niklasvh/html2canvas' + ' or https://github.com/cburgmer/rasterizeHTML.js'); + + if (typeof x !== 'number') { + options = x; + callback = y; + } + + if (typeof options === 'function') { + callback = options; + options = null; + } + + if (typeof callback !== 'function') { + callback = function callback() {}; + } + + var I = this.internal, + K = I.scaleFactor, + W = I.pageSize.getWidth(), + H = I.pageSize.getHeight(); + + options = options || {}; + options.onrendered = function (obj) { + x = parseInt(x) || 0; + y = parseInt(y) || 0; + var dim = options.dim || {}; + var margin = Object.assign({ top: 0, right: 0, bottom: 0, left: 0, useFor: 'content' }, options.margin); + var h = dim.h || Math.min(H, obj.height / K); + var w = dim.w || Math.min(W, obj.width / K) - x; + + var format = options.format || 'JPEG'; + var imageCompression = options.imageCompression || 'SLOW'; + + var notFittingHeight = obj.height > H - margin.top - margin.bottom; + + if (notFittingHeight && options.pagesplit) { + var cropArea = function cropArea(parmObj, parmX, parmY, parmWidth, parmHeight) { + var canvas = document.createElement('canvas'); + canvas.height = parmHeight; + canvas.width = parmWidth; + var ctx = canvas.getContext('2d'); + ctx.mozImageSmoothingEnabled = false; + ctx.webkitImageSmoothingEnabled = false; + ctx.msImageSmoothingEnabled = false; + ctx.imageSmoothingEnabled = false; + ctx.fillStyle = options.backgroundColor || '#ffffff'; + ctx.fillRect(0, 0, parmWidth, parmHeight); + ctx.drawImage(parmObj, parmX, parmY, parmWidth, parmHeight, 0, 0, parmWidth, parmHeight); + return canvas; + }; + var crop = function () { + var cy = 0; + var cx = 0; + var position = {}; + var isOverWide = false; + var width; + var height; + while (1) { + cx = 0; + position.top = cy !== 0 ? margin.top : y; + position.left = cy !== 0 ? margin.left : x; + isOverWide = (W - margin.left - margin.right) * K < obj.width; + if (margin.useFor === "content") { + if (cy === 0) { + width = Math.min((W - margin.left) * K, obj.width); + height = Math.min((H - margin.top) * K, obj.height - cy); + } else { + width = Math.min(W * K, obj.width); + height = Math.min(H * K, obj.height - cy); + position.top = 0; + } + } else { + width = Math.min((W - margin.left - margin.right) * K, obj.width); + height = Math.min((H - margin.bottom - margin.top) * K, obj.height - cy); + } + if (isOverWide) { + while (1) { + if (margin.useFor === "content") { + if (cx === 0) { + width = Math.min((W - margin.left) * K, obj.width); + } else { + width = Math.min(W * K, obj.width - cx); + position.left = 0; + } + } + var canvas = cropArea(obj, cx, cy, width, height); + var args = [canvas, position.left, position.top, canvas.width / K, canvas.height / K, format, null, imageCompression]; + this.addImage.apply(this, args); + cx += width; + if (cx >= obj.width) { + break; + } + this.addPage(); + } + } else { + var canvas = cropArea(obj, 0, cy, width, height); + var args = [canvas, position.left, position.top, canvas.width / K, canvas.height / K, format, null, imageCompression]; + this.addImage.apply(this, args); + } + cy += height; + if (cy >= obj.height) { + break; + } + this.addPage(); + } + callback(w, cy, null, args); + }.bind(this); + if (obj.nodeName === 'CANVAS') { + var img = new Image(); + img.onload = crop; + img.src = obj.toDataURL("image/png"); + obj = img; + } else { + crop(); + } + } else { + var alias = Math.random().toString(35); + var args = [obj, x, y, w, h, format, alias, imageCompression]; + + this.addImage.apply(this, args); + + callback(w, h, alias, args); + } + }.bind(this); + + if (typeof html2canvas !== 'undefined' && !options.rstz) { + return html2canvas(element, options); + } + + if (typeof rasterizeHTML !== 'undefined') { + var meth = 'drawDocument'; + if (typeof element === 'string') { + meth = /^http/.test(element) ? 'drawURL' : 'drawHTML'; + } + options.width = options.width || W * K; + return rasterizeHTML[meth](element, void 0, options).then(function (r) { + options.onrendered(r.image); + }, function (e) { + callback(null, e); + }); + } + + return null; + }; + })(jsPDF.API); + + /** @preserve + * jsPDF addImage plugin + * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/ + * 2013 Chris Dowling, https://github.com/gingerchris + * 2013 Trinh Ho, https://github.com/ineedfat + * 2013 Edwin Alejandro Perez, https://github.com/eaparango + * 2013 Norah Smith, https://github.com/burnburnrocket + * 2014 Diego Casorran, https://github.com/diegocr + * 2014 James Robb, https://github.com/jamesbrobb + * + * + */ + (function (jsPDFAPI) { + + var namespace = 'addImage_'; + + var imageFileTypeHeaders = { + PNG: [[0x89, 0x50, 0x4e, 0x47]], + TIFF: [[0x4D, 0x4D, 0x00, 0x2A], //Motorola + [0x49, 0x49, 0x2A, 0x00] //Intel + ], + JPEG: [[0xFF, 0xD8, 0xFF, 0xE0, undefined, undefined, 0x4A, 0x46, 0x49, 0x46, 0x00], //JFIF + [0xFF, 0xD8, 0xFF, 0xE1, undefined, undefined, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00] //Exif + ], + JPEG2000: [[0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20]], + GIF87a: [[0x47, 0x49, 0x46, 0x38, 0x37, 0x61]], + GIF89a: [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61]], + BMP: [[0x42, 0x4D], //BM - Windows 3.1x, 95, NT, ... etc. + [0x42, 0x41], //BA - OS/2 struct bitmap array + [0x43, 0x49], //CI - OS/2 struct color icon + [0x43, 0x50], //CP - OS/2 const color pointer + [0x49, 0x43], //IC - OS/2 struct icon + [0x50, 0x54] //PT - OS/2 pointer + ] + }; + + jsPDFAPI.getImageFileTypeByImageData = function (imageData) { + var i; + var j; + var result = 'UNKNOWN'; + var headerSchemata; + var compareResult; + var fileType; + + for (fileType in imageFileTypeHeaders) { + headerSchemata = imageFileTypeHeaders[fileType]; + for (i = 0; i < headerSchemata.length; i += 1) { + compareResult = true; + for (j = 0; j < headerSchemata[i].length; j += 1) { + if (headerSchemata[i][j] === undefined) { + continue; + } + if (headerSchemata[i][j] !== imageData.charCodeAt(j)) { + compareResult = false; + break; + } + } + if (compareResult === true) { + result = fileType; + break; + } + } + } + return result; + }; + + // Image functionality ported from pdf.js + var putImage = function putImage(img) { + + var objectNumber = this.internal.newObject(), + out = this.internal.write, + putStream = this.internal.putStream; + + img['n'] = objectNumber; + + out('<>'); + } + if ('trns' in img && img['trns'].constructor == Array) { + var trns = '', + i = 0, + len = img['trns'].length; + for (; i < len; i++) { + trns += img['trns'][i] + ' ' + img['trns'][i] + ' '; + }out('/Mask [' + trns + ']'); + } + if ('smask' in img) { + out('/SMask ' + (objectNumber + 1) + ' 0 R'); + } + out('/Length ' + img['data'].length + '>>'); + + putStream(img['data']); + + out('endobj'); + + // Soft mask + if ('smask' in img) { + var dp = '/Predictor ' + img['p'] + ' /Colors 1 /BitsPerComponent ' + img['bpc'] + ' /Columns ' + img['w']; + var smask = { 'w': img['w'], 'h': img['h'], 'cs': 'DeviceGray', 'bpc': img['bpc'], 'dp': dp, 'data': img['smask'] }; + if ('f' in img) smask.f = img['f']; + putImage.call(this, smask); + } + + //Palette + if (img['cs'] === this.color_spaces.INDEXED) { + + this.internal.newObject(); + //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>'); + //putStream(zlib.compress(img['pal'])); + out('<< /Length ' + img['pal'].length + '>>'); + putStream(this.arrayBufferToBinaryString(new Uint8Array(img['pal']))); + out('endobj'); + } + }, + putResourcesCallback = function putResourcesCallback() { + var images = this.internal.collections[namespace + 'images']; + for (var i in images) { + putImage.call(this, images[i]); + } + }, + putXObjectsDictCallback = function putXObjectsDictCallback() { + var images = this.internal.collections[namespace + 'images'], + out = this.internal.write, + image; + for (var i in images) { + image = images[i]; + out('/I' + image['i'], image['n'], '0', 'R'); + } + }, + checkCompressValue = function checkCompressValue(value) { + if (value && typeof value === 'string') value = value.toUpperCase(); + return value in jsPDFAPI.image_compression ? value : jsPDFAPI.image_compression.NONE; + }, + getImages = function getImages() { + var images = this.internal.collections[namespace + 'images']; + //first run, so initialise stuff + if (!images) { + this.internal.collections[namespace + 'images'] = images = {}; + this.internal.events.subscribe('putResources', putResourcesCallback); + this.internal.events.subscribe('putXobjectDict', putXObjectsDictCallback); + } + + return images; + }, + getImageIndex = function getImageIndex(images) { + var imageIndex = 0; + + if (images) { + // this is NOT the first time this method is ran on this instance of jsPDF object. + imageIndex = Object.keys ? Object.keys(images).length : function (o) { + var i = 0; + for (var e in o) { + if (o.hasOwnProperty(e)) { + i++; + } + } + return i; + }(images); + } + + return imageIndex; + }, + notDefined = function notDefined(value) { + return typeof value === 'undefined' || value === null || value.length === 0; + }, + generateAliasFromData = function generateAliasFromData(data) { + return typeof data === 'string' && jsPDFAPI.sHashCode(data); + }, + isImageTypeSupported = function isImageTypeSupported(type) { + return typeof jsPDFAPI["process" + type.toUpperCase()] === "function"; + }, + isDOMElement = function isDOMElement(object) { + return (typeof object === 'undefined' ? 'undefined' : _typeof(object)) === 'object' && object.nodeType === 1; + }, + createDataURIFromElement = function createDataURIFromElement(element, format) { + + //if element is an image which uses data url definition, just return the dataurl + if (element.nodeName === 'IMG' && element.hasAttribute('src')) { + var src = '' + element.getAttribute('src'); + if (src.indexOf('data:image/') === 0) return src; + + // only if the user doesn't care about a format + if (!format && /\.png(?:[?#].*)?$/i.test(src)) format = 'png'; + } + + if (element.nodeName === 'CANVAS') { + var canvas = element; + } else { + var canvas = document.createElement('canvas'); + canvas.width = element.clientWidth || element.width; + canvas.height = element.clientHeight || element.height; + + var ctx = canvas.getContext('2d'); + if (!ctx) { + throw 'addImage requires canvas to be supported by browser.'; + } + ctx.drawImage(element, 0, 0, canvas.width, canvas.height); + } + return canvas.toDataURL(('' + format).toLowerCase() == 'png' ? 'image/png' : 'image/jpeg'); + }, + checkImagesForAlias = function checkImagesForAlias(alias, images) { + var cached_info; + if (images) { + for (var e in images) { + if (alias === images[e].alias) { + cached_info = images[e]; + break; + } + } + } + return cached_info; + }, + determineWidthAndHeight = function determineWidthAndHeight(w, h, info) { + if (!w && !h) { + w = -96; + h = -96; + } + if (w < 0) { + w = -1 * info['w'] * 72 / w / this.internal.scaleFactor; + } + if (h < 0) { + h = -1 * info['h'] * 72 / h / this.internal.scaleFactor; + } + if (w === 0) { + w = h * info['w'] / info['h']; + } + if (h === 0) { + h = w * info['h'] / info['w']; + } + + return [w, h]; + }, + writeImageToPDF = function writeImageToPDF(x, y, w, h, info, index, images, rotation) { + var dims = determineWidthAndHeight.call(this, w, h, info), + coord = this.internal.getCoordinateString, + vcoord = this.internal.getVerticalCoordinateString; + + w = dims[0]; + h = dims[1]; + + images[index] = info; + + if (rotation) { + rotation *= Math.PI / 180; + var c = Math.cos(rotation); + var s = Math.sin(rotation); + //like in pdf Reference do it 4 digits instead of 2 + var f4 = function f4(number) { + return number.toFixed(4); + }; + var rotationTransformationMatrix = [f4(c), f4(s), f4(s * -1), f4(c), 0, 0, 'cm']; + } + this.internal.write('q'); //Save graphics state + if (rotation) { + this.internal.write([1, '0', '0', 1, coord(x), vcoord(y + h), 'cm'].join(' ')); //Translate + this.internal.write(rotationTransformationMatrix.join(' ')); //Rotate + this.internal.write([coord(w), '0', '0', coord(h), '0', '0', 'cm'].join(' ')); //Scale + } else { + this.internal.write([coord(w), '0', '0', coord(h), coord(x), vcoord(y + h), 'cm'].join(' ')); //Translate and Scale + } + this.internal.write('/I' + info['i'] + ' Do'); //Paint Image + this.internal.write('Q'); //Restore graphics state + }; + + /** + * COLOR SPACES + */ + jsPDFAPI.color_spaces = { + DEVICE_RGB: 'DeviceRGB', + DEVICE_GRAY: 'DeviceGray', + DEVICE_CMYK: 'DeviceCMYK', + CAL_GREY: 'CalGray', + CAL_RGB: 'CalRGB', + LAB: 'Lab', + ICC_BASED: 'ICCBased', + INDEXED: 'Indexed', + PATTERN: 'Pattern', + SEPARATION: 'Separation', + DEVICE_N: 'DeviceN' + }; + + /** + * DECODE METHODS + */ + jsPDFAPI.decode = { + DCT_DECODE: 'DCTDecode', + FLATE_DECODE: 'FlateDecode', + LZW_DECODE: 'LZWDecode', + JPX_DECODE: 'JPXDecode', + JBIG2_DECODE: 'JBIG2Decode', + ASCII85_DECODE: 'ASCII85Decode', + ASCII_HEX_DECODE: 'ASCIIHexDecode', + RUN_LENGTH_DECODE: 'RunLengthDecode', + CCITT_FAX_DECODE: 'CCITTFaxDecode' + }; + + /** + * IMAGE COMPRESSION TYPES + */ + jsPDFAPI.image_compression = { + NONE: 'NONE', + FAST: 'FAST', + MEDIUM: 'MEDIUM', + SLOW: 'SLOW' + }; + + jsPDFAPI.sHashCode = function (str) { + str = str || ""; + return Array.prototype.reduce && str.split("").reduce(function (a, b) { + a = (a << 5) - a + b.charCodeAt(0);return a & a; + }, 0); + }; + + jsPDFAPI.isString = function (object) { + return typeof object === 'string'; + }; + + /** + * Regex taken from https://github.com/kevva/base64-regex + **/ + jsPDFAPI.validateStringAsBase64 = function (possibleBase64String) { + possibleBase64String = possibleBase64String || ''; + var base64Regex = new RegExp('(?:^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$)'); + return base64Regex.test(possibleBase64String); + }; + + /** + * Strips out and returns info from a valid base64 data URI + * @param {String[dataURI]} a valid data URI of format 'data:[][;base64],' + * @returns an Array containing the following + * [0] the complete data URI + * [1] + * [2] format - the second part of the mime-type i.e 'png' in 'image/png' + * [4] + */ + jsPDFAPI.extractInfoFromBase64DataURI = function (dataURI) { + return (/^data:([\w]+?\/([\w]+?));base64,(.+?)$/g.exec(dataURI) + ); + }; + + /** + * Check to see if ArrayBuffer is supported + */ + jsPDFAPI.supportsArrayBuffer = function () { + return typeof ArrayBuffer !== 'undefined' && typeof Uint8Array !== 'undefined'; + }; + + /** + * Tests supplied object to determine if ArrayBuffer + * @param {Object[object]} + */ + jsPDFAPI.isArrayBuffer = function (object) { + if (!this.supportsArrayBuffer()) return false; + return object instanceof ArrayBuffer; + }; + + /** + * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface + * @param {Object[object]} + */ + jsPDFAPI.isArrayBufferView = function (object) { + if (!this.supportsArrayBuffer()) return false; + if (typeof Uint32Array === 'undefined') return false; + return object instanceof Int8Array || object instanceof Uint8Array || typeof Uint8ClampedArray !== 'undefined' && object instanceof Uint8ClampedArray || object instanceof Int16Array || object instanceof Uint16Array || object instanceof Int32Array || object instanceof Uint32Array || object instanceof Float32Array || object instanceof Float64Array; + }; + + /** + * Exactly what it says on the tin + */ + jsPDFAPI.binaryStringToUint8Array = function (binary_string) { + /* + * not sure how efficient this will be will bigger files. Is there a native method? + */ + var len = binary_string.length; + var bytes = new Uint8Array(len); + for (var i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + return bytes; + }; + + /** + * Convert the Buffer to a Binary String + */ + jsPDFAPI.arrayBufferToBinaryString = function (buffer) { + + if (typeof atob === "function") { + return atob(this.arrayBufferToBase64(buffer)); + } + + if (typeof TextDecoder === "function") { + var decoder = new TextDecoder('ascii'); + // test if the encoding is supported + if (decoder.encoding === 'ascii') { + return decoder.decode(buffer); + } + } + + //Fallback-solution + var data = this.isArrayBuffer(buffer) ? buffer : new Uint8Array(buffer); + var chunkSizeForSlice = 0x5000; + var binary_string = ''; + var slicesCount = Math.ceil(data.byteLength / chunkSizeForSlice); + for (var i = 0; i < slicesCount; i++) { + binary_string += String.fromCharCode.apply(null, data.slice(i * chunkSizeForSlice, i * chunkSizeForSlice + chunkSizeForSlice)); + } + return binary_string; + }; + + /** + * Converts an ArrayBuffer directly to base64 + * + * Taken from here + * + * http://jsperf.com/encoding-xhr-image-data/31 + * + * Need to test if this is a better solution for larger files + * + */ + jsPDFAPI.arrayBufferToBase64 = function (arrayBuffer) { + var base64 = ''; + var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + + var bytes = new Uint8Array(arrayBuffer); + var byteLength = bytes.byteLength; + var byteRemainder = byteLength % 3; + var mainLength = byteLength - byteRemainder; + + var a, b, c, d; + var chunk; + + // Main loop deals with bytes in chunks of 3 + for (var i = 0; i < mainLength; i = i + 3) { + // Combine the three bytes into a single integer + chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2]; + + // Use bitmasks to extract 6-bit segments from the triplet + a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18 + b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12 + c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6 + d = chunk & 63; // 63 = 2^6 - 1 + + // Convert the raw binary segments to the appropriate ASCII encoding + base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]; + } + + // Deal with the remaining bytes and padding + if (byteRemainder == 1) { + chunk = bytes[mainLength]; + + a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2 + + // Set the 4 least significant bits to zero + b = (chunk & 3) << 4; // 3 = 2^2 - 1 + + base64 += encodings[a] + encodings[b] + '=='; + } else if (byteRemainder == 2) { + chunk = bytes[mainLength] << 8 | bytes[mainLength + 1]; + + a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10 + b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4 + + // Set the 2 least significant bits to zero + c = (chunk & 15) << 2; // 15 = 2^4 - 1 + + base64 += encodings[a] + encodings[b] + encodings[c] + '='; + } + + return base64; + }; + + jsPDFAPI.createImageInfo = function (data, wd, ht, cs, bpc, f, imageIndex, alias, dp, trns, pal, smask, p) { + var info = { + alias: alias, + w: wd, + h: ht, + cs: cs, + bpc: bpc, + i: imageIndex, + data: data + // n: objectNumber will be added by putImage code + }; + + if (f) info.f = f; + if (dp) info.dp = dp; + if (trns) info.trns = trns; + if (pal) info.pal = pal; + if (smask) info.smask = smask; + if (p) info.p = p; // predictor parameter for PNG compression + + return info; + }; + + jsPDFAPI.addImage = function (imageData, format, x, y, w, h, alias, compression, rotation) { + + var tmpImageData = ''; + + if (typeof format !== 'string') { + var tmp = h; + h = w; + w = y; + y = x; + x = format; + format = tmp; + } + + if ((typeof imageData === 'undefined' ? 'undefined' : _typeof(imageData)) === 'object' && !isDOMElement(imageData) && "imageData" in imageData) { + var options = imageData; + + imageData = options.imageData; + format = options.format || format; + x = options.x || x || 0; + y = options.y || y || 0; + w = options.w || w; + h = options.h || h; + alias = options.alias || alias; + compression = options.compression || compression; + rotation = options.rotation || options.angle || rotation; + } + + if (isNaN(x) || isNaN(y)) { + console.error('jsPDF.addImage: Invalid coordinates', arguments); + throw new Error('Invalid coordinates passed to jsPDF.addImage'); + } + + var images = getImages.call(this), + info; + + if (!(info = checkImagesForAlias(imageData, images))) { + var dataAsBinaryString; + + if (isDOMElement(imageData)) imageData = createDataURIFromElement(imageData, format); + + if (notDefined(alias)) alias = generateAliasFromData(imageData); + + if (!(info = checkImagesForAlias(alias, images))) { + if (this.isString(imageData)) { + tmpImageData = this.convertStringToImageData(imageData); + + if (tmpImageData !== '') { + imageData = tmpImageData; + } else { + tmpImageData = this.loadImageFile(imageData); + if (tmpImageData !== undefined) { + imageData = tmpImageData; + } + } + } + format = this.getImageFileTypeByImageData(imageData); + + if (!isImageTypeSupported(format)) throw new Error('addImage does not support files of type \'' + format + '\', please ensure that a plugin for \'' + format + '\' support is added.'); + + /** + * need to test if it's more efficient to convert all binary strings + * to TypedArray - or should we just leave and process as string? + */ + if (this.supportsArrayBuffer()) { + // no need to convert if imageData is already uint8array + if (!(imageData instanceof Uint8Array)) { + dataAsBinaryString = imageData; + imageData = this.binaryStringToUint8Array(imageData); + } + } + + info = this['process' + format.toUpperCase()](imageData, getImageIndex(images), alias, checkCompressValue(compression), dataAsBinaryString); + + if (!info) throw new Error('An unkwown error occurred whilst processing the image'); + } + } + writeImageToPDF.call(this, x, y, w, h, info, info.i, images, rotation); + + return this; + }; + + jsPDFAPI.convertStringToImageData = function (stringData) { + var base64Info; + var imageData = ''; + if (this.isString(stringData)) { + var base64Info = this.extractInfoFromBase64DataURI(stringData); + + if (base64Info !== null) { + if (jsPDFAPI.validateStringAsBase64(base64Info[3])) { + imageData = atob(base64Info[3]); //convert to binary string + } + } else if (jsPDFAPI.validateStringAsBase64(stringData)) { + imageData = atob(stringData); + } + } + return imageData; + }; + /** + * JPEG SUPPORT + **/ + + //takes a string imgData containing the raw bytes of + //a jpeg image and returns [width, height] + //Algorithm from: http://www.64lines.com/jpeg-width-height + var getJpegSize = function getJpegSize(imgData) { + + var width, height, numcomponents; + // Verify we have a valid jpeg header 0xff,0xd8,0xff,0xe0,?,?,'J','F','I','F',0x00 + if (!imgData.charCodeAt(0) === 0xff || !imgData.charCodeAt(1) === 0xd8 || !imgData.charCodeAt(2) === 0xff || !imgData.charCodeAt(3) === 0xe0 || !imgData.charCodeAt(6) === 'J'.charCodeAt(0) || !imgData.charCodeAt(7) === 'F'.charCodeAt(0) || !imgData.charCodeAt(8) === 'I'.charCodeAt(0) || !imgData.charCodeAt(9) === 'F'.charCodeAt(0) || !imgData.charCodeAt(10) === 0x00) { + throw new Error('getJpegSize requires a binary string jpeg file'); + } + var blockLength = imgData.charCodeAt(4) * 256 + imgData.charCodeAt(5); + var i = 4, + len = imgData.length; + while (i < len) { + i += blockLength; + if (imgData.charCodeAt(i) !== 0xff) { + throw new Error('getJpegSize could not find the size of the image'); + } + if (imgData.charCodeAt(i + 1) === 0xc0 || //(SOF) Huffman - Baseline DCT + imgData.charCodeAt(i + 1) === 0xc1 || //(SOF) Huffman - Extended sequential DCT + imgData.charCodeAt(i + 1) === 0xc2 || // Progressive DCT (SOF2) + imgData.charCodeAt(i + 1) === 0xc3 || // Spatial (sequential) lossless (SOF3) + imgData.charCodeAt(i + 1) === 0xc4 || // Differential sequential DCT (SOF5) + imgData.charCodeAt(i + 1) === 0xc5 || // Differential progressive DCT (SOF6) + imgData.charCodeAt(i + 1) === 0xc6 || // Differential spatial (SOF7) + imgData.charCodeAt(i + 1) === 0xc7) { + height = imgData.charCodeAt(i + 5) * 256 + imgData.charCodeAt(i + 6); + width = imgData.charCodeAt(i + 7) * 256 + imgData.charCodeAt(i + 8); + numcomponents = imgData.charCodeAt(i + 9); + return [width, height, numcomponents]; + } else { + i += 2; + blockLength = imgData.charCodeAt(i) * 256 + imgData.charCodeAt(i + 1); + } + } + }, + getJpegSizeFromBytes = function getJpegSizeFromBytes(data) { + + var hdr = data[0] << 8 | data[1]; + + if (hdr !== 0xFFD8) throw new Error('Supplied data is not a JPEG'); + + var len = data.length, + block = (data[4] << 8) + data[5], + pos = 4, + bytes, + width, + height, + numcomponents; + + while (pos < len) { + pos += block; + bytes = readBytes(data, pos); + block = (bytes[2] << 8) + bytes[3]; + if ((bytes[1] === 0xC0 || bytes[1] === 0xC2) && bytes[0] === 0xFF && block > 7) { + bytes = readBytes(data, pos + 5); + width = (bytes[2] << 8) + bytes[3]; + height = (bytes[0] << 8) + bytes[1]; + numcomponents = bytes[4]; + return { width: width, height: height, numcomponents: numcomponents }; + } + + pos += 2; + } + + throw new Error('getJpegSizeFromBytes could not find the size of the image'); + }, + readBytes = function readBytes(data, offset) { + return data.subarray(offset, offset + 5); + }; + + jsPDFAPI.processJPEG = function (data, index, alias, compression, dataAsBinaryString, colorSpace) { + + var filter = this.decode.DCT_DECODE, + bpc = 8, + dims; + + if (!this.isString(data) && !this.isArrayBuffer(data) && !this.isArrayBufferView(data)) { + return null; + } + + if (this.isString(data)) { + dims = getJpegSize(data); + } + + if (this.isArrayBuffer(data)) { + data = new Uint8Array(data); + } + if (this.isArrayBufferView(data)) { + + dims = getJpegSizeFromBytes(data); + + // if we already have a stored binary string rep use that + data = dataAsBinaryString || this.arrayBufferToBinaryString(data); + } + + if (colorSpace === undefined) { + switch (dims.numcomponents) { + case 1: + colorSpace = this.color_spaces.DEVICE_GRAY; + break; + case 4: + colorSpace = this.color_spaces.DEVICE_CMYK; + break; + default: + case 3: + colorSpace = this.color_spaces.DEVICE_RGB; + break; + } + } + + return this.createImageInfo(data, dims.width, dims.height, colorSpace, bpc, filter, index, alias); + }; + + jsPDFAPI.processJPG = function () /*data, index, alias, compression, dataAsBinaryString*/{ + return this.processJPEG.apply(this, arguments); + }; + + jsPDFAPI.loadImageFile = function (path, sync, callback) { + sync = sync || true; + callback = callback || function () {}; + var isNode = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]'; + + var xhrMethod = function xhrMethod(url, sync, callback) { + var req = new XMLHttpRequest(); + var byteArray = []; + var i = 0; + + var sanitizeUnicode = function sanitizeUnicode(data) { + var dataLength = data.length; + var StringFromCharCode = String.fromCharCode; + + //Transform Unicode to ASCII + for (i = 0; i < dataLength; i += 1) { + byteArray.push(StringFromCharCode(data.charCodeAt(i) & 0xff)); + } + return byteArray.join(""); + }; + + req.open('GET', url, !sync); + // XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com] + req.overrideMimeType('text\/plain; charset=x-user-defined'); + + if (sync === false) { + req.onload = function () { + return sanitizeUnicode(this.responseText); + }; + } + req.send(null); + + if (req.status !== 200) { + console.warn('Unable to load file "' + url + '"'); + return; + } + + if (sync) { + return sanitizeUnicode(req.responseText); + } + }; + + var nodeJSMethod = function nodeJSMethod(path, sync, callback) { + sync = sync || true; + var fs = require('fs'); + if (sync === true) { + var data = fs.readFileSync(path).toString(); + return data; + } else { + fs.readFile('image.jpg', function (err, data) { + callback(data); + }); + } + }; + + //we have a browser and probably no CORS-Problem + if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== undefined && (typeof location === 'undefined' ? 'undefined' : _typeof(location)) === "object" && location.protocol.substr(0, 4) === "http") { + return xhrMethod(path, sync, callback); + } else if (isNode) { + return nodeJSMethod(path, sync, callback); + } + }; + + jsPDFAPI.getImageProperties = function (imageData) { + var info; + var tmpImageData = ''; + var format; + + if (isDOMElement(imageData)) { + imageData = createDataURIFromElement(imageData); + } + + if (this.isString(imageData)) { + tmpImageData = this.convertStringToImageData(imageData); + + if (tmpImageData !== '') { + imageData = tmpImageData; + } else { + tmpImageData = this.loadImageFile(imageData); + if (tmpImageData !== undefined) { + imageData = tmpImageData; + } + } + } + format = this.getImageFileTypeByImageData(imageData); + + if (!isImageTypeSupported(format)) throw new Error('addImage does not support files of type \'' + format + '\', please ensure that a plugin for \'' + format + '\' support is added.'); + + /** + * need to test if it's more efficient to convert all binary strings + * to TypedArray - or should we just leave and process as string? + */ + if (this.supportsArrayBuffer()) { + // no need to convert if imageData is already uint8array + if (!(imageData instanceof Uint8Array)) { + imageData = this.binaryStringToUint8Array(imageData); + } + } + + info = this['process' + format.toUpperCase()](imageData); + + if (!info) { + throw new Error('An unkwown error occurred whilst processing the image'); + } + + return { + fileType: format, + width: info.w, + height: info.h, + colorSpace: info.cs, + compressionMode: info.f, + bitsPerComponent: info.bpc + }; + }; + })(jsPDF.API); + + /** + * jsPDF Annotations PlugIn + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + + /** + * There are many types of annotations in a PDF document. Annotations are placed + * on a page at a particular location. They are not 'attached' to an object. + *
+ * This plugin current supports
+ *
  • Goto Page (set pageNumber and top in options) + *
  • Goto Name (set name and top in options) + *
  • Goto URL (set url in options) + *

    + * The destination magnification factor can also be specified when goto is a page number or a named destination. (see documentation below) + * (set magFactor in options). XYZ is the default. + *

    + *

    + * Links, Text, Popup, and FreeText are supported. + *

    + *

    + * Options In PDF spec Not Implemented Yet + *

  • link border + *
  • named target + *
  • page coordinates + *
  • destination page scaling and layout + *
  • actions other than URL and GotoPage + *
  • background / hover actions + *

    + */ + + /* + Destination Magnification Factors + See PDF 1.3 Page 386 for meanings and options + + [supported] + XYZ (options; left top zoom) + Fit (no options) + FitH (options: top) + FitV (options: left) + + [not supported] + FitR + FitB + FitBH + FitBV + */ + + (function (jsPDFAPI) { + + var annotationPlugin = { + + /** + * An array of arrays, indexed by pageNumber. + */ + annotations: [], + + f2: function f2(number) { + return number.toFixed(2); + }, + + notEmpty: function notEmpty(obj) { + if (typeof obj != 'undefined') { + if (obj != '') { + return true; + } + } + } + }; + + jsPDF.API.annotationPlugin = annotationPlugin; + + jsPDF.API.events.push(['addPage', function (info) { + this.annotationPlugin.annotations[info.pageNumber] = []; + }]); + + jsPDFAPI.events.push(['putPage', function (info) { + //TODO store annotations in pageContext so reorder/remove will not affect them. + var pageAnnos = this.annotationPlugin.annotations[info.pageNumber]; + + var found = false; + for (var a = 0; a < pageAnnos.length && !found; a++) { + var anno = pageAnnos[a]; + switch (anno.type) { + case 'link': + if (annotationPlugin.notEmpty(anno.options.url) || annotationPlugin.notEmpty(anno.options.pageNumber)) { + found = true; + break; + } + case 'reference': + case 'text': + case 'freetext': + found = true; + break; + } + } + if (found == false) { + return; + } + + this.internal.write("/Annots ["); + var f2 = this.annotationPlugin.f2; + var k = this.internal.scaleFactor; + var pageHeight = this.internal.pageSize.getHeight(); + var pageInfo = this.internal.getPageInfo(info.pageNumber); + for (var a = 0; a < pageAnnos.length; a++) { + var anno = pageAnnos[a]; + + switch (anno.type) { + case 'reference': + // References to Widget Anotations (for AcroForm Fields) + this.internal.write(' ' + anno.object.objId + ' 0 R '); + break; + case 'text': + // Create a an object for both the text and the popup + var objText = this.internal.newAdditionalObject(); + var objPopup = this.internal.newAdditionalObject(); + + var title = anno.title || 'Note'; + var rect = "/Rect [" + f2(anno.bounds.x * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + " " + f2((anno.bounds.x + anno.bounds.w) * k) + " " + f2((pageHeight - anno.bounds.y) * k) + "] "; + line = '<>'; + objText.content = line; + + var parent = objText.objId + ' 0 R'; + var popoff = 30; + var rect = "/Rect [" + f2((anno.bounds.x + popoff) * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + " " + f2((anno.bounds.x + anno.bounds.w + popoff) * k) + " " + f2((pageHeight - anno.bounds.y) * k) + "] "; + //var rect2 = "/Rect [" + f2(anno.bounds.x * k) + " " + f2((pageHeight - anno.bounds.y) * k) + " " + f2(anno.bounds.x + anno.bounds.w * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + "] "; + line = '<>'; + } else if (anno.options.pageNumber) { + // first page is 0 + var info = this.internal.getPageInfo(anno.options.pageNumber); + line = '< pageNumber or url [required] + *

    If pageNumber is specified, top and zoom may also be specified

    + */ + jsPDFAPI.link = function (x, y, w, h, options) { + + this.annotationPlugin.annotations[this.internal.getCurrentPageInfo().pageNumber].push({ + x: x, + y: y, + w: w, + h: h, + options: options, + type: 'link' + }); + }; + + /** + * Currently only supports single line text. + * Returns the width of the text/link + */ + jsPDFAPI.textWithLink = function (text, x, y, options) { + + var width = this.getTextWidth(text); + var height = this.internal.getLineHeight() / this.internal.scaleFactor; + this.text(text, x, y); + //TODO We really need the text baseline height to do this correctly. + // Or ability to draw text on top, bottom, center, or baseline. + y += height * .2; + this.link(x, y - height, width, height, options); + return width; + }; + + //TODO move into external library + jsPDFAPI.getTextWidth = function (text) { + + var fontSize = this.internal.getFontSize(); + var txtWidth = this.getStringUnitWidth(text) * fontSize / this.internal.scaleFactor; + return txtWidth; + }; + + //TODO move into external library + jsPDFAPI.getLineHeight = function () { + return this.internal.getLineHeight(); + }; + + return this; + })(jsPDF.API); + + (function (jsPDFAPI) { + + var arLangCodes = { + "ar": "Arabic (Standard)", + "ar-DZ": "Arabic (Algeria)", + "ar-BH": "Arabic (Bahrain)", + "ar-EG": "Arabic (Egypt)", + "ar-IQ": "Arabic (Iraq)", + "ar-JO": "Arabic (Jordan)", + "ar-KW": "Arabic (Kuwait)", + "ar-LB": "Arabic (Lebanon)", + "ar-LY": "Arabic (Libya)", + "ar-MA": "Arabic (Morocco)", + "ar-OM": "Arabic (Oman)", + "ar-QA": "Arabic (Qatar)", + "ar-SA": "Arabic (Saudi Arabia)", + "ar-SY": "Arabic (Syria)", + "ar-TN": "Arabic (Tunisia)", + "ar-AE": "Arabic (U.A.E.)", + "ar-YE": "Arabic (Yemen)", + "fa": "Persian", + "fa-IR": "Persian/Iran", + "ur": "Urdu" + }; + + var arLangCodesKeys = Object.keys(arLangCodes); + + /** + * Arabic shape substitutions: char code => (isolated, final, initial, medial). + */ + var arabicSubst = { + 1569: [65152], + 1570: [65153, 65154, 65153, 65154], + 1571: [65155, 65156, 65155, 65156], + 1572: [65157, 65158], + 1573: [65159, 65160, 65159, 65160], + 1574: [65161, 65162, 65163, 65164], + 1575: [65165, 65166, 65165, 65166], + 1576: [65167, 65168, 65169, 65170], + 1577: [65171, 65172], + 1578: [65173, 65174, 65175, 65176], + 1579: [65177, 65178, 65179, 65180], + 1580: [65181, 65182, 65183, 65184], + 1581: [65185, 65186, 65187, 65188], + 1582: [65189, 65190, 65191, 65192], + 1583: [65193, 65194, 65193, 65194], + 1584: [65195, 65196, 65195, 65196], + 1585: [65197, 65198, 65197, 65198], + 1586: [65199, 65200, 65199, 65200], + 1587: [65201, 65202, 65203, 65204], + 1588: [65205, 65206, 65207, 65208], + 1589: [65209, 65210, 65211, 65212], + 1590: [65213, 65214, 65215, 65216], + 1591: [65217, 65218, 65219, 65220], + 1592: [65221, 65222, 65223, 65224], + 1593: [65225, 65226, 65227, 65228], + 1594: [65229, 65230, 65231, 65232], + 1601: [65233, 65234, 65235, 65236], + 1602: [65237, 65238, 65239, 65240], + 1603: [65241, 65242, 65243, 65244], + 1604: [65245, 65246, 65247, 65248], + 1605: [65249, 65250, 65251, 65252], + 1606: [65253, 65254, 65255, 65256], + 1607: [65257, 65258, 65259, 65260], + 1608: [65261, 65262, 65261, 65262], + 1609: [65263, 65264, 64488, 64489], + 1610: [65265, 65266, 65267, 65268], + 1649: [64336, 64337], + 1655: [64477], + 1657: [64358, 64359, 64360, 64361], + 1658: [64350, 64351, 64352, 64353], + 1659: [64338, 64339, 64340, 64341], + 1662: [64342, 64343, 64344, 64345], + 1663: [64354, 64355, 64356, 64357], + 1664: [64346, 64347, 64348, 64349], + 1667: [64374, 64375, 64376, 64377], + 1668: [64370, 64371, 64372, 64373], + 1670: [64378, 64379, 64380, 64381], + 1671: [64382, 64383, 64384, 64385], + 1672: [64392, 64393], + 1676: [64388, 64389], + 1677: [64386, 64387], + 1678: [64390, 64391], + 1681: [64396, 64397], + 1688: [64394, 64395, 64394, 64395], + 1700: [64362, 64363, 64364, 64365], + 1702: [64366, 64367, 64368, 64369], + 1705: [64398, 64399, 64400, 64401], + 1709: [64467, 64468, 64469, 64470], + 1711: [64402, 64403, 64404, 64405], + 1713: [64410, 64411, 64412, 64413], + 1715: [64406, 64407, 64408, 64409], + 1722: [64414, 64415], + 1723: [64416, 64417, 64418, 64419], + 1726: [64426, 64427, 64428, 64429], + 1728: [64420, 64421], + 1729: [64422, 64423, 64424, 64425], + 1733: [64480, 64481], + 1734: [64473, 64474], + 1735: [64471, 64472], + 1736: [64475, 64476], + 1737: [64482, 64483], + 1739: [64478, 64479], + 1740: [64508, 64509, 64510, 64511], + 1744: [64484, 64485, 64486, 64487], + 1746: [64430, 64431], + 1747: [64432, 64433] + }; + var arabiclaasubst = { + 1570: [65269, 65270, 65269, 65270], + 1571: [65271, 65272, 65271, 65272], + 1573: [65273, 65274, 65273, 65274], + 1575: [65275, 65276, 65275, 65276] + }; + var arabicorigsubst = { + 1570: [65153, 65154, 65153, 65154], + 1571: [65155, 65156, 65155, 65156], + 1573: [65159, 65160, 65159, 65160], + 1575: [65165, 65166, 65165, 65166] + }; + var arabic_diacritics = { + 1612: 64606, // Shadda + Dammatan + 1613: 64607, // Shadda + Kasratan + 1614: 64608, // Shadda + Fatha + 1615: 64609, // Shadda + Damma + 1616: 64610 // Shadda + Kasra + }; + + var alfletter = [1570, 1571, 1573, 1575]; + var endedletter = [1569, 1570, 1571, 1572, 1573, 1575, 1577, 1583, 1584, 1585, 1586, 1608, 1688]; + + var isolatedForm = 0; + var finalForm = 1; + var initialForm = 2; + var medialForm = 3; + + //private + function isArabicLetter(letter) { + return letter !== undefined && arabicSubst[letter.charCodeAt(0)] !== undefined; + } + + function isArabicEndLetter(letter) { + return letter !== undefined && endedletter.indexOf(letter.charCodeAt(0)) >= 0; + } + + function isArabicAlfLetter(letter) { + return letter !== undefined && alfletter.indexOf(letter.charCodeAt(0)) >= 0; + } + + function arabicLetterHasFinalForm(letter) { + return isArabicLetter(letter) && arabicSubst[letter.charCodeAt(0)].length >= 2; + } + + function arabicLetterHasMedialForm(letter) { + return isArabicLetter(letter) && arabicSubst[letter.charCodeAt(0)].length == 4; + } + + function isArabicDiacritic(letter) { + return letter !== undefined && arabic_diacritics[letter.charCodeAt(0)] !== undefined; + } + + function getCorrectForm(currentChar, beforeChar, nextChar, arabicSubstition) { + if (!isArabicLetter(currentChar)) { + return -1; + } + + arabicSubstition = arabicSubstition || {}; + arabicSubst = Object.assign(arabicSubst, arabicSubstition); + + if (!arabicLetterHasFinalForm(currentChar) || !isArabicLetter(beforeChar) && !isArabicLetter(nextChar) || !isArabicLetter(nextChar) && isArabicEndLetter(beforeChar) || isArabicEndLetter(currentChar) && !isArabicLetter(beforeChar) || isArabicEndLetter(currentChar) && isArabicAlfLetter(beforeChar)) { + arabicSubst = Object.assign(arabicSubst, arabicorigsubst); + return isolatedForm; + } + + if (arabicLetterHasMedialForm(currentChar) && isArabicLetter(beforeChar) && !isArabicEndLetter(beforeChar) && isArabicLetter(nextChar) && arabicLetterHasFinalForm(nextChar)) { + arabicSubst = Object.assign(arabicSubst, arabicorigsubst); + return medialForm; + } + + if (isArabicEndLetter(currentChar) || !isArabicLetter(nextChar)) { + arabicSubst = Object.assign(arabicSubst, arabicorigsubst); + return finalForm; + } + + arabicSubst = Object.assign(arabicSubst, arabicorigsubst); + return initialForm; + } + + function processArabic(text) { + text = text || ""; + var result = ""; + var i = 0; + var position = 0; + var currentLetter = ""; + var prevLetter = ""; + var nextLetter = ""; + var resultingLetter; + + var localPrevLetter; + var localCurrentLetter; + var localNextLetter; + + for (i = 0; i < text.length; i += 1) { + currentLetter = text[i]; + prevLetter = text[i - 1]; + nextLetter = text[i + 1]; + if (!isArabicLetter(currentLetter)) { + result += currentLetter; + } else { + if (prevLetter !== undefined && prevLetter.charCodeAt(0) === 1604 && isArabicAlfLetter(currentLetter)) { + localPrevLetter = text[i - 2]; + localCurrentLetter = currentLetter; + localNextLetter = text[i + 1]; + position = getCorrectForm(localCurrentLetter, localPrevLetter, localNextLetter, arabiclaasubst); + resultingLetter = String.fromCharCode(arabiclaasubst[currentLetter.charCodeAt(0)][position]); + result = result.substr(0, result.length - 1) + resultingLetter; + } else if (prevLetter !== undefined && prevLetter.charCodeAt(0) === 1617 && isArabicDiacritic(currentLetter)) { + localPrevLetter = text[i - 2]; + localCurrentLetter = currentLetter; + localNextLetter = text[i + 1]; + position = getCorrectForm(localCurrentLetter, localPrevLetter, localNextLetter, arabicorigsubst); + resultingLetter = String.fromCharCode(arabic_diacritics[currentLetter.charCodeAt(0)][position]); + result = result.substr(0, result.length - 1) + resultingLetter; + } else { + position = getCorrectForm(currentLetter, prevLetter, nextLetter, arabicorigsubst); + result += String.fromCharCode(arabicSubst[currentLetter.charCodeAt(0)][position]); + } + } + } + return result.split("").reverse().join(""); + } + + var arabicParserFunction = function arabicParserFunction(args) { + var text = args.text; + var x = args.x; + var y = args.y; + var options = args.options || {}; + var mutex = args.mutex || {}; + var lang = options.lang; + var tmpText = []; + + if (arLangCodesKeys.indexOf(lang) >= 0) { + if (Object.prototype.toString.call(text) === '[object Array]') { + var i = 0; + tmpText = []; + for (i = 0; i < text.length; i += 1) { + if (Object.prototype.toString.call(text[i]) === '[object Array]') { + tmpText.push([processArabic(text[i][0]), text[i][1], text[i][2]]); + } else { + tmpText.push([processArabic(text[i])]); + } + } + args.text = tmpText; + } else { + args.text = processArabic(text); + } + //force charSpace if not given. + if (options.charSpace === undefined) { + args.options.charSpace = 0; + } + //if R2L is true, set it false. + if (options.R2L === true) { + args.options.R2L = false; + } + } + }; + + jsPDFAPI.events.push(['preProcessText', arabicParserFunction]); + })(jsPDF.API); + + /** + * jsPDF Autoprint Plugin + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + + /** + * Makes the PDF automatically print. This works in Chrome, Firefox, Acrobat + * Reader. + * + * @returns {jsPDF} + * @name autoPrint + * @example + * var doc = new jsPDF() + * doc.text(10, 10, 'This is a test') + * doc.autoPrint() + * doc.save('autoprint.pdf') + */ + + (function (jsPDFAPI) { + + jsPDFAPI.autoPrint = function (options) { + + var refAutoPrintTag; + options = options || {}; + options.variant = options.variant || 'non-conform'; + + switch (options.variant) { + case 'javascript': + //https://github.com/Rob--W/pdf.js/commit/c676ecb5a0f54677b9f3340c3ef2cf42225453bb + this.addJS('print({});'); + break; + case 'non-conform': + default: + this.internal.events.subscribe('postPutResources', function () { + refAutoPrintTag = this.internal.newObject(); + this.internal.out("<<"); + this.internal.out("/S /Named"); + this.internal.out("/Type /Action"); + this.internal.out("/N /Print"); + this.internal.out(">>"); + this.internal.out("endobj"); + }); + + this.internal.events.subscribe("putCatalog", function () { + this.internal.out("/OpenAction " + refAutoPrintTag + " 0 R"); + }); + break; + } + return this; + }; + })(jsPDF.API); + + /** + * jsPDF Canvas PlugIn + * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. + * http://opensource.org/licenses/mit-license + */ + + /** + * This plugin mimics the HTML5 Canvas + * + * The goal is to provide a way for current canvas users to print directly to a PDF. + */ + + (function (jsPDFAPI) { + + jsPDFAPI.events.push(['initialized', function () { + this.canvas.pdf = this; + }]); + + jsPDFAPI.canvas = { + getContext: function getContext(name) { + this.pdf.context2d._canvas = this; + return this.pdf.context2d; + }, + style: {} + }; + + Object.defineProperty(jsPDFAPI.canvas, 'width', { + get: function get() { + return this._width; + }, + set: function set(value) { + this._width = value; + this.getContext('2d').pageWrapX = value + 1; + } + }); + + Object.defineProperty(jsPDFAPI.canvas, 'height', { + get: function get() { + return this._height; + }, + set: function set(value) { + this._height = value; + this.getContext('2d').pageWrapY = value + 1; + } + }); + + return this; + })(jsPDF.API); + + /** ==================================================================== + * jsPDF Cell plugin + * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com + * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br + * 2013 Lee Driscoll, https://github.com/lsdriscoll + * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria + * 2014 James Hall, james@parall.ax + * 2014 Diego Casorran, https://github.com/diegocr + * + * + * ==================================================================== + */ + + (function (jsPDFAPI) { + /*jslint browser:true */ + /*global document: false, jsPDF */ + + var fontName, + fontSize, + fontStyle, + padding = 3, + margin = 13, + headerFunction, + lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined }, + pages = 1, + setLastCellPosition = function setLastCellPosition(x, y, w, h, ln) { + lastCellPos = { 'x': x, 'y': y, 'w': w, 'h': h, 'ln': ln }; + }, + getLastCellPosition = function getLastCellPosition() { + return lastCellPos; + }, + NO_MARGINS = { left: 0, top: 0, bottom: 0 }; + + jsPDFAPI.setHeaderFunction = function (func) { + headerFunction = func; + }; + + jsPDFAPI.getTextDimensions = function (txt) { + fontName = this.internal.getFont().fontName; + fontSize = this.table_font_size || this.internal.getFontSize(); + fontStyle = this.internal.getFont().fontStyle; + // 1 pixel = 0.264583 mm and 1 mm = 72/25.4 point + var px2pt = 0.264583 * 72 / 25.4, + dimensions, + text; + + text = document.createElement('font'); + text.id = "jsPDFCell"; + + try { + text.style.fontStyle = fontStyle; + } catch (e) { + text.style.fontWeight = fontStyle; + } + + text.style.fontSize = fontSize + 'pt'; + text.style.fontFamily = fontName; + try { + text.textContent = txt; + } catch (e) { + text.innerText = txt; + } + + document.body.appendChild(text); + + dimensions = { w: (text.offsetWidth + 1) * px2pt, h: (text.offsetHeight + 1) * px2pt }; + + document.body.removeChild(text); + + return dimensions; + }; + + jsPDFAPI.cellAddPage = function () { + var margins = this.margins || NO_MARGINS; + + this.addPage(); + + setLastCellPosition(margins.left, margins.top, undefined, undefined); + //setLastCellPosition(undefined, undefined, undefined, undefined, undefined); + pages += 1; + }; + + jsPDFAPI.cellInitialize = function () { + lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined }; + pages = 1; + }; + + jsPDFAPI.cell = function (x, y, w, h, txt, ln, align) { + var curCell = getLastCellPosition(); + var pgAdded = false; + + // If this is not the first cell, we must change its position + if (curCell.ln !== undefined) { + if (curCell.ln === ln) { + //Same line + x = curCell.x + curCell.w; + y = curCell.y; + } else { + //New line + var margins = this.margins || NO_MARGINS; + if (curCell.y + curCell.h + h + margin >= this.internal.pageSize.getHeight() - margins.bottom) { + this.cellAddPage(); + pgAdded = true; + if (this.printHeaders && this.tableHeaderRow) { + this.printHeaderRow(ln, true); + } + } + //We ignore the passed y: the lines may have different heights + y = getLastCellPosition().y + getLastCellPosition().h; + if (pgAdded) y = margin + 10; + } + } + + if (txt[0] !== undefined) { + if (this.printingHeaderRow) { + this.rect(x, y, w, h, 'FD'); + } else { + this.rect(x, y, w, h); + } + if (align === 'right') { + if (!(txt instanceof Array)) { + txt = [txt]; + } + for (var i = 0; i < txt.length; i++) { + var currentLine = txt[i]; + var textSize = this.getStringUnitWidth(currentLine) * this.internal.getFontSize(); + this.text(currentLine, x + w - textSize - padding, y + this.internal.getLineHeight() * (i + 1)); + } + } else { + this.text(txt, x + padding, y + this.internal.getLineHeight()); + } + } + setLastCellPosition(x, y, w, h, ln); + return this; + }; + + /** + * Return the maximum value from an array + * @param array + * @param comparisonFn + * @returns {*} + */ + jsPDFAPI.arrayMax = function (array, comparisonFn) { + var max = array[0], + i, + ln, + item; + + for (i = 0, ln = array.length; i < ln; i += 1) { + item = array[i]; + + if (comparisonFn) { + if (comparisonFn(max, item) === -1) { + max = item; + } + } else { + if (item > max) { + max = item; + } + } + } + + return max; + }; + + /** + * Create a table from a set of data. + * @param {Integer} [x] : left-position for top-left corner of table + * @param {Integer} [y] top-position for top-left corner of table + * @param {Object[]} [data] As array of objects containing key-value pairs corresponding to a row of data. + * @param {String[]} [headers] Omit or null to auto-generate headers at a performance cost + * @param {Object} [config.printHeaders] True to print column headers at the top of every page + * @param {Object} [config.autoSize] True to dynamically set the column widths to match the widest cell value + * @param {Object} [config.margins] margin values for left, top, bottom, and width + * @param {Object} [config.fontSize] Integer fontSize to use (optional) + */ + + jsPDFAPI.table = function (x, y, data, headers, config) { + if (!data) { + throw 'No data for PDF table'; + } + + var headerNames = [], + headerPrompts = [], + header, + i, + ln, + cln, + columnMatrix = {}, + columnWidths = {}, + columnData, + column, + columnMinWidths = [], + j, + tableHeaderConfigs = [], + model, + jln, + func, + + + //set up defaults. If a value is provided in config, defaults will be overwritten: + autoSize = false, + printHeaders = true, + fontSize = 12, + margins = NO_MARGINS; + + margins.width = this.internal.pageSize.getWidth(); + + if (config) { + //override config defaults if the user has specified non-default behavior: + if (config.autoSize === true) { + autoSize = true; + } + if (config.printHeaders === false) { + printHeaders = false; + } + if (config.fontSize) { + fontSize = config.fontSize; + } + if (config.css && typeof config.css['font-size'] !== "undefined") { + fontSize = config.css['font-size'] * 16; + } + if (config.margins) { + margins = config.margins; + } + } + + /** + * @property {Number} lnMod + * Keep track of the current line number modifier used when creating cells + */ + this.lnMod = 0; + lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined }, pages = 1; + + this.printHeaders = printHeaders; + this.margins = margins; + this.setFontSize(fontSize); + this.table_font_size = fontSize; + + // Set header values + if (headers === undefined || headers === null) { + // No headers defined so we derive from data + headerNames = Object.keys(data[0]); + } else if (headers[0] && typeof headers[0] !== 'string') { + var px2pt = 0.264583 * 72 / 25.4; + + // Split header configs into names and prompts + for (i = 0, ln = headers.length; i < ln; i += 1) { + header = headers[i]; + headerNames.push(header.name); + headerPrompts.push(header.prompt); + columnWidths[header.name] = header.width * px2pt; + } + } else { + headerNames = headers; + } + + if (autoSize) { + // Create a matrix of columns e.g., {column_title: [row1_Record, row2_Record]} + func = function func(rec) { + return rec[header]; + }; + + for (i = 0, ln = headerNames.length; i < ln; i += 1) { + header = headerNames[i]; + + columnMatrix[header] = data.map(func); + + // get header width + columnMinWidths.push(this.getTextDimensions(headerPrompts[i] || header).w); + column = columnMatrix[header]; + + // get cell widths + for (j = 0, cln = column.length; j < cln; j += 1) { + columnData = column[j]; + columnMinWidths.push(this.getTextDimensions(columnData).w); + } + + // get final column width + columnWidths[header] = jsPDFAPI.arrayMax(columnMinWidths); + + //have to reset + columnMinWidths = []; + } + } + + // -- Construct the table + + if (printHeaders) { + var lineHeight = this.calculateLineHeight(headerNames, columnWidths, headerPrompts.length ? headerPrompts : headerNames); + + // Construct the header row + for (i = 0, ln = headerNames.length; i < ln; i += 1) { + header = headerNames[i]; + tableHeaderConfigs.push([x, y, columnWidths[header], lineHeight, String(headerPrompts.length ? headerPrompts[i] : header)]); + } + + // Store the table header config + this.setTableHeaderRow(tableHeaderConfigs); + + // Print the header for the start of the table + this.printHeaderRow(1, false); + } + + // Construct the data rows + for (i = 0, ln = data.length; i < ln; i += 1) { + var lineHeight; + model = data[i]; + lineHeight = this.calculateLineHeight(headerNames, columnWidths, model); + + for (j = 0, jln = headerNames.length; j < jln; j += 1) { + header = headerNames[j]; + this.cell(x, y, columnWidths[header], lineHeight, model[header], i + 2, header.align); + } + } + this.lastCellPos = lastCellPos; + this.table_x = x; + this.table_y = y; + return this; + }; + /** + * Calculate the height for containing the highest column + * @param {String[]} headerNames is the header, used as keys to the data + * @param {Integer[]} columnWidths is size of each column + * @param {Object[]} model is the line of data we want to calculate the height of + */ + jsPDFAPI.calculateLineHeight = function (headerNames, columnWidths, model) { + var header, + lineHeight = 0; + for (var j = 0; j < headerNames.length; j++) { + header = headerNames[j]; + model[header] = this.splitTextToSize(String(model[header]), columnWidths[header] - padding); + var h = this.internal.getLineHeight() * model[header].length + padding; + if (h > lineHeight) lineHeight = h; + } + return lineHeight; + }; + + /** + * Store the config for outputting a table header + * @param {Object[]} config + * An array of cell configs that would define a header row: Each config matches the config used by jsPDFAPI.cell + * except the ln parameter is excluded + */ + jsPDFAPI.setTableHeaderRow = function (config) { + this.tableHeaderRow = config; + }; + + /** + * Output the store header row + * @param lineNumber The line number to output the header at + */ + jsPDFAPI.printHeaderRow = function (lineNumber, new_page) { + if (!this.tableHeaderRow) { + throw 'Property tableHeaderRow does not exist.'; + } + + var tableHeaderCell, tmpArray, i, ln; + + this.printingHeaderRow = true; + if (headerFunction !== undefined) { + var position = headerFunction(this, pages); + setLastCellPosition(position[0], position[1], position[2], position[3], -1); + } + this.setFontStyle('bold'); + var tempHeaderConf = []; + for (i = 0, ln = this.tableHeaderRow.length; i < ln; i += 1) { + this.setFillColor(200, 200, 200); + + tableHeaderCell = this.tableHeaderRow[i]; + if (new_page) { + this.margins.top = margin; + tableHeaderCell[1] = this.margins && this.margins.top || 0; + tempHeaderConf.push(tableHeaderCell); + } + tmpArray = [].concat(tableHeaderCell); + this.cell.apply(this, tmpArray.concat(lineNumber)); + } + if (tempHeaderConf.length > 0) { + this.setTableHeaderRow(tempHeaderConf); + } + this.setFontStyle('normal'); + this.printingHeaderRow = false; + }; + })(jsPDF.API); + + /** + * jsPDF Context2D PlugIn Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv + * + * Licensed under the MIT License. http://opensource.org/licenses/mit-license + */ + + /** + * This plugin mimics the HTML5 Canvas's context2d. + * + * The goal is to provide a way for current canvas implementations to print directly to a PDF. + */ + + /** + * TODO implement stroke opacity (refactor from fill() method ) + * TODO transform angle and radii parameters + */ + + /** + * require('jspdf.js'); require('lib/css_colors.js'); + */ + + (function (jsPDFAPI) { + + jsPDFAPI.events.push(['initialized', function () { + this.context2d.pdf = this; + this.context2d.internal.pdf = this; + this.context2d.ctx = new context(); + this.context2d.ctxStack = []; + this.context2d.path = []; + }]); + + jsPDFAPI.context2d = { + pageWrapXEnabled: false, + pageWrapYEnabled: false, + pageWrapX: 9999999, + pageWrapY: 9999999, + ctx: new context(), + f2: function f2(number) { + return number.toFixed(2); + }, + + fillRect: function fillRect(x, y, w, h) { + if (this._isFillTransparent()) { + return; + } + x = this._wrapX(x); + y = this._wrapY(y); + + var xRect = this._matrix_map_rect(this.ctx._transform, { x: x, y: y, w: w, h: h }); + this.pdf.rect(xRect.x, xRect.y, xRect.w, xRect.h, "f"); + }, + + strokeRect: function strokeRect(x, y, w, h) { + if (this._isStrokeTransparent()) { + return; + } + x = this._wrapX(x); + y = this._wrapY(y); + + var xRect = this._matrix_map_rect(this.ctx._transform, { x: x, y: y, w: w, h: h }); + this.pdf.rect(xRect.x, xRect.y, xRect.w, xRect.h, "s"); + }, + + /** + * We cannot clear PDF commands that were already written to PDF, so we use white instead.
    + * As a special case, read a special flag (ignoreClearRect) and do nothing if it is set. + * This results in all calls to clearRect() to do nothing, and keep the canvas transparent. + * This flag is stored in the save/restore context and is managed the same way as other drawing states. + * @param x + * @param y + * @param w + * @param h + */ + clearRect: function clearRect(x, y, w, h) { + if (this.ctx.ignoreClearRect) { + return; + } + + x = this._wrapX(x); + y = this._wrapY(y); + + var xRect = this._matrix_map_rect(this.ctx._transform, { x: x, y: y, w: w, h: h }); + this.save(); + this.setFillStyle('#ffffff'); + //TODO This is hack to fill with white. + this.pdf.rect(xRect.x, xRect.y, xRect.w, xRect.h, "f"); + this.restore(); + }, + + save: function save() { + this.ctx._fontSize = this.pdf.internal.getFontSize(); + var ctx = new context(); + ctx.copy(this.ctx); + this.ctxStack.push(this.ctx); + this.ctx = ctx; + }, + + restore: function restore() { + this.ctx = this.ctxStack.pop(); + this.setFillStyle(this.ctx.fillStyle); + this.setStrokeStyle(this.ctx.strokeStyle); + this.setFont(this.ctx.font); + this.pdf.setFontSize(this.ctx._fontSize); + this.setLineCap(this.ctx.lineCap); + this.setLineWidth(this.ctx.lineWidth); + this.setLineJoin(this.ctx.lineJoin); + }, + + rect: function rect(x, y, w, h) { + this.moveTo(x, y); + this.lineTo(x + w, y); + this.lineTo(x + w, y + h); + this.lineTo(x, y + h); + this.lineTo(x, y); //TODO not needed + this.closePath(); + }, + + beginPath: function beginPath() { + this.path = []; + }, + + closePath: function closePath() { + this.path.push({ + type: 'close' + }); + }, + + _getRGBA: function _getRGBA(style) { + // get the decimal values of r, g, and b; + var r, g, b, a; + if (!style) { + return { r: 0, g: 0, b: 0, a: 0, style: style }; + } + + if (this.internal.rxTransparent.test(style)) { + r = 0; + g = 0; + b = 0; + a = 0; + } else { + var m = this.internal.rxRgb.exec(style); + if (m != null) { + r = parseInt(m[1]); + g = parseInt(m[2]); + b = parseInt(m[3]); + a = 1; + } else { + m = this.internal.rxRgba.exec(style); + if (m != null) { + r = parseInt(m[1]); + g = parseInt(m[2]); + b = parseInt(m[3]); + a = parseFloat(m[4]); + } else { + a = 1; + if (style.charAt(0) != '#') { + style = CssColors.colorNameToHex(style); + if (!style) { + style = '#000000'; + } + } + + if (style.length === 4) { + r = style.substring(1, 2); + r += r; + g = style.substring(2, 3); + g += g; + b = style.substring(3, 4); + b += b; + } else { + r = style.substring(1, 3); + g = style.substring(3, 5); + b = style.substring(5, 7); + } + r = parseInt(r, 16); + g = parseInt(g, 16); + b = parseInt(b, 16); + } + } + } + return { r: r, g: g, b: b, a: a, style: style }; + }, + + setFillStyle: function setFillStyle(style) { + var rgba = this._getRGBA(style); + + this.ctx.fillStyle = style; + this.ctx._isFillTransparent = rgba.a === 0; + this.ctx._fillOpacity = rgba.a; + + this.pdf.setFillColor(rgba.r, rgba.g, rgba.b, { + a: rgba.a + }); + this.pdf.setTextColor(rgba.r, rgba.g, rgba.b, { + a: rgba.a + }); + }, + + setStrokeStyle: function setStrokeStyle(style) { + var rgba = this._getRGBA(style); + + this.ctx.strokeStyle = rgba.style; + this.ctx._isStrokeTransparent = rgba.a === 0; + this.ctx._strokeOpacity = rgba.a; + + //TODO jsPDF to handle rgba + if (rgba.a === 0) { + this.pdf.setDrawColor(255, 255, 255); + } else if (rgba.a === 1) { + this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b); + } else { + //this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b, {a: rgba.a}); + this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b); + } + }, + + fillText: function fillText(text, x, y, maxWidth) { + if (this._isFillTransparent()) { + return; + } + x = this._wrapX(x); + y = this._wrapY(y); + + var xpt = this._matrix_map_point(this.ctx._transform, [x, y]); + x = xpt[0]; + y = xpt[1]; + var rads = this._matrix_rotation(this.ctx._transform); + var degs = rads * 57.2958; + + //TODO only push the clip if it has not been applied to the current PDF context + if (this.ctx._clip_path.length > 0) { + var lines; + if (window.outIntercept) { + lines = window.outIntercept.type === 'group' ? window.outIntercept.stream : window.outIntercept; + } else { + lines = this.internal.getCurrentPage(); + } + lines.push("q"); + var origPath = this.path; + this.path = this.ctx._clip_path; + this.ctx._clip_path = []; + this._fill(null, true); + this.ctx._clip_path = this.path; + this.path = origPath; + } + + // We only use X axis as scale hint + var scale = 1; + try { + scale = this._matrix_decompose(this._getTransform()).scale[0]; + } catch (e) { + console.warn(e); + } + + // In some cases the transform was very small (5.715760606202283e-17). Most likely a canvg rounding error. + if (scale < 0.01) { + this.pdf.text(text, x, this._getBaseline(y), null, degs); + } else { + var oldSize = this.pdf.internal.getFontSize(); + this.pdf.setFontSize(oldSize * scale); + this.pdf.text(text, x, this._getBaseline(y), null, degs); + this.pdf.setFontSize(oldSize); + } + + if (this.ctx._clip_path.length > 0) { + lines.push('Q'); + } + }, + + strokeText: function strokeText(text, x, y, maxWidth) { + if (this._isStrokeTransparent()) { + return; + } + x = this._wrapX(x); + y = this._wrapY(y); + + var xpt = this._matrix_map_point(this.ctx._transform, [x, y]); + x = xpt[0]; + y = xpt[1]; + var rads = this._matrix_rotation(this.ctx._transform); + var degs = rads * 57.2958; + + //TODO only push the clip if it has not been applied to the current PDF context + if (this.ctx._clip_path.length > 0) { + var lines; + if (window.outIntercept) { + lines = window.outIntercept.type === 'group' ? window.outIntercept.stream : window.outIntercept; + } else { + lines = this.internal.getCurrentPage(); + } + lines.push("q"); + var origPath = this.path; + this.path = this.ctx._clip_path; + this.ctx._clip_path = []; + this._fill(null, true); + this.ctx._clip_path = this.path; + this.path = origPath; + } + + var scale = 1; + // We only use the X axis as scale hint + try { + scale = this._matrix_decompose(this._getTransform()).scale[0]; + } catch (e) { + console.warn(e); + } + + if (scale === 1) { + this.pdf.text(text, x, this._getBaseline(y), { + stroke: true + }, degs); + } else { + var oldSize = this.pdf.internal.getFontSize(); + this.pdf.setFontSize(oldSize * scale); + this.pdf.text(text, x, this._getBaseline(y), { + stroke: true + }, degs); + this.pdf.setFontSize(oldSize); + } + + if (this.ctx._clip_path.length > 0) { + lines.push('Q'); + } + }, + + setFont: function setFont(font) { + this.ctx.font = font; + + //var rx = /\s*(\w+)\s+(\w+)\s+(\w+)\s+([\d\.]+)(px|pt|em)\s+["']?(\w+)['"]?/; + var rx = /\s*(\w+)\s+(\w+)\s+(\w+)\s+([\d\.]+)(px|pt|em)\s+(.*)?/; + m = rx.exec(font); + if (m != null) { + var fontStyle = m[1]; + var fontVariant = m[2]; + var fontWeight = m[3]; + var fontSize = m[4]; + var fontSizeUnit = m[5]; + var fontFamily = m[6]; + + if ('px' === fontSizeUnit) { + fontSize = Math.floor(parseFloat(fontSize)); + // fontSize = fontSize * 1.25; + } else if ('em' === fontSizeUnit) { + fontSize = Math.floor(parseFloat(fontSize) * this.pdf.getFontSize()); + } else { + fontSize = Math.floor(parseFloat(fontSize)); + } + + this.pdf.setFontSize(fontSize); + + if (fontWeight === 'bold' || fontWeight === '700') { + this.pdf.setFontStyle('bold'); + } else { + if (fontStyle === 'italic') { + this.pdf.setFontStyle('italic'); + } else { + this.pdf.setFontStyle('normal'); + } + } + + var name = fontFamily; + var parts = name.toLowerCase().split(/\s*,\s*/); + var jsPdfFontName; + + if (parts.indexOf('arial') != -1) { + jsPdfFontName = 'Arial'; + } else if (parts.indexOf('verdana') != -1) { + jsPdfFontName = 'Verdana'; + } else if (parts.indexOf('helvetica') != -1) { + jsPdfFontName = 'Helvetica'; + } else if (parts.indexOf('sans-serif') != -1) { + jsPdfFontName = 'sans-serif'; + } else if (parts.indexOf('fixed') != -1) { + jsPdfFontName = 'Fixed'; + } else if (parts.indexOf('monospace') != -1) { + jsPdfFontName = 'Monospace'; + } else if (parts.indexOf('terminal') != -1) { + jsPdfFontName = 'Terminal'; + } else if (parts.indexOf('courier') != -1) { + jsPdfFontName = 'Courier'; + } else if (parts.indexOf('times') != -1) { + jsPdfFontName = 'Times'; + } else if (parts.indexOf('cursive') != -1) { + jsPdfFontName = 'Cursive'; + } else if (parts.indexOf('fantasy') != -1) { + jsPdfFontName = 'Fantasy'; + } else if (parts.indexOf('serif') != -1) { + jsPdfFontName = 'Serif'; + } else { + jsPdfFontName = 'Serif'; + } + + //TODO check more cases + var style; + if ('bold' === fontWeight) { + style = 'bold'; + } else { + style = 'normal'; + } + + this.pdf.setFont(jsPdfFontName, style); + } else { + var rx = /\s*(\d+)(pt|px|em)\s+([\w "]+)\s*([\w "]+)?/; + var m = rx.exec(font); + if (m != null) { + var size = m[1]; + var unit = m[2]; + var name = m[3]; + var style = m[4]; + if (!style) { + style = 'normal'; + } + if ('em' === fontSizeUnit) { + size = Math.floor(parseFloat(fontSize) * this.pdf.getFontSize()); + } else { + size = Math.floor(parseFloat(size)); + } + this.pdf.setFontSize(size); + this.pdf.setFont(name, style); + } + } + }, + + setTextBaseline: function setTextBaseline(baseline) { + this.ctx.textBaseline = baseline; + }, + + getTextBaseline: function getTextBaseline() { + return this.ctx.textBaseline; + }, + + //TODO implement textAlign + setTextAlign: function setTextAlign(align) { + this.ctx.textAlign = align; + }, + + getTextAlign: function getTextAlign() { + return this.ctx.textAlign; + }, + + setLineWidth: function setLineWidth(width) { + this.ctx.lineWidth = width; + this.pdf.setLineWidth(width); + }, + + setLineCap: function setLineCap(style) { + this.ctx.lineCap = style; + this.pdf.setLineCap(style); + }, + + setLineJoin: function setLineJoin(style) { + this.ctx.lineJoin = style; + this.pdf.setLineJoin(style); + }, + + moveTo: function moveTo(x, y) { + x = this._wrapX(x); + y = this._wrapY(y); + + var xpt = this._matrix_map_point(this.ctx._transform, [x, y]); + x = xpt[0]; + y = xpt[1]; + + var obj = { + type: 'mt', + x: x, + y: y + }; + this.path.push(obj); + }, + + _wrapX: function _wrapX(x) { + if (this.pageWrapXEnabled) { + return x % this.pageWrapX; + } else { + return x; + } + }, + + _wrapY: function _wrapY(y) { + if (this.pageWrapYEnabled) { + this._gotoPage(this._page(y)); + return (y - this.lastBreak) % this.pageWrapY; + } else { + return y; + } + }, + + transform: function transform(a, b, c, d, e, f) { + //TODO apply to current transformation instead of replacing + this.ctx._transform = [a, b, c, d, e, f]; + }, + + setTransform: function setTransform(a, b, c, d, e, f) { + this.ctx._transform = [a, b, c, d, e, f]; + }, + + _getTransform: function _getTransform() { + return this.ctx._transform; + }, + + lastBreak: 0, + // Y Position of page breaks. + pageBreaks: [], + // returns: One-based Page Number + // Should only be used if pageWrapYEnabled is true + _page: function _page(y) { + if (this.pageWrapYEnabled) { + this.lastBreak = 0; + var manualBreaks = 0; + var autoBreaks = 0; + for (var i = 0; i < this.pageBreaks.length; i++) { + if (y >= this.pageBreaks[i]) { + manualBreaks++; + if (this.lastBreak === 0) { + autoBreaks++; + } + var spaceBetweenLastBreak = this.pageBreaks[i] - this.lastBreak; + this.lastBreak = this.pageBreaks[i]; + var pagesSinceLastBreak = Math.floor(spaceBetweenLastBreak / this.pageWrapY); + autoBreaks += pagesSinceLastBreak; + } + } + if (this.lastBreak === 0) { + var pagesSinceLastBreak = Math.floor(y / this.pageWrapY) + 1; + autoBreaks += pagesSinceLastBreak; + } + return autoBreaks + manualBreaks; + } else { + return this.pdf.internal.getCurrentPageInfo().pageNumber; + } + }, + + _gotoPage: function _gotoPage(pageOneBased) { + // This is a stub to be overriden if needed + }, + + lineTo: function lineTo(x, y) { + x = this._wrapX(x); + y = this._wrapY(y); + + var xpt = this._matrix_map_point(this.ctx._transform, [x, y]); + x = xpt[0]; + y = xpt[1]; + + var obj = { + type: 'lt', + x: x, + y: y + }; + this.path.push(obj); + }, + + bezierCurveTo: function bezierCurveTo(x1, y1, x2, y2, x, y) { + x1 = this._wrapX(x1); + y1 = this._wrapY(y1); + x2 = this._wrapX(x2); + y2 = this._wrapY(y2); + x = this._wrapX(x); + y = this._wrapY(y); + + var xpt; + xpt = this._matrix_map_point(this.ctx._transform, [x, y]); + x = xpt[0]; + y = xpt[1]; + xpt = this._matrix_map_point(this.ctx._transform, [x1, y1]); + x1 = xpt[0]; + y1 = xpt[1]; + xpt = this._matrix_map_point(this.ctx._transform, [x2, y2]); + x2 = xpt[0]; + y2 = xpt[1]; + + var obj = { + type: 'bct', + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y + }; + this.path.push(obj); + }, + + quadraticCurveTo: function quadraticCurveTo(x1, y1, x, y) { + x1 = this._wrapX(x1); + y1 = this._wrapY(y1); + x = this._wrapX(x); + y = this._wrapY(y); + + var xpt; + xpt = this._matrix_map_point(this.ctx._transform, [x, y]); + x = xpt[0]; + y = xpt[1]; + xpt = this._matrix_map_point(this.ctx._transform, [x1, y1]); + x1 = xpt[0]; + y1 = xpt[1]; + + var obj = { + type: 'qct', + x1: x1, + y1: y1, + x: x, + y: y + }; + this.path.push(obj); + }, + + arc: function arc(x, y, radius, startAngle, endAngle, anticlockwise) { + x = this._wrapX(x); + y = this._wrapY(y); + + if (!this._matrix_is_identity(this.ctx._transform)) { + var xpt = this._matrix_map_point(this.ctx._transform, [x, y]); + x = xpt[0]; + y = xpt[1]; + + var x_radPt0 = this._matrix_map_point(this.ctx._transform, [0, 0]); + var x_radPt = this._matrix_map_point(this.ctx._transform, [0, radius]); + radius = Math.sqrt(Math.pow(x_radPt[0] - x_radPt0[0], 2) + Math.pow(x_radPt[1] - x_radPt0[1], 2)); + + //TODO angles need to be transformed + } + + var obj = { + type: 'arc', + x: x, + y: y, + radius: radius, + startAngle: startAngle, + endAngle: endAngle, + anticlockwise: anticlockwise + }; + this.path.push(obj); + }, + + drawImage: function drawImage(img, x, y, w, h, x2, y2, w2, h2) { + if (x2 !== undefined) { + x = x2; + y = y2; + w = w2; + h = h2; + } + x = this._wrapX(x); + y = this._wrapY(y); + + var xRect = this._matrix_map_rect(this.ctx._transform, { x: x, y: y, w: w, h: h }); + var xRect2 = this._matrix_map_rect(this.ctx._transform, { x: x2, y: y2, w: w2, h: h2 }); + + // TODO implement source clipping and image scaling + var format; + var rx = /data:image\/(\w+).*/i; + var m = rx.exec(img); + if (m != null) { + format = m[1]; + } else { + // format = "jpeg"; + format = "png"; + } + + this.pdf.addImage(img, format, xRect.x, xRect.y, xRect.w, xRect.h); + }, + + /** + * Multiply the first matrix by the second + * @param m1 + * @param m2 + * @returns {*[]} + * @private + */ + _matrix_multiply: function _matrix_multiply(m2, m1) { + var sx = m1[0]; + var shy = m1[1]; + var shx = m1[2]; + var sy = m1[3]; + var tx = m1[4]; + var ty = m1[5]; + + var t0 = sx * m2[0] + shy * m2[2]; + var t2 = shx * m2[0] + sy * m2[2]; + var t4 = tx * m2[0] + ty * m2[2] + m2[4]; + shy = sx * m2[1] + shy * m2[3]; + sy = shx * m2[1] + sy * m2[3]; + ty = tx * m2[1] + ty * m2[3] + m2[5]; + sx = t0; + shx = t2; + tx = t4; + + return [sx, shy, shx, sy, tx, ty]; + }, + + _matrix_rotation: function _matrix_rotation(m) { + return Math.atan2(m[2], m[0]); + }, + + _matrix_decompose: function _matrix_decompose(matrix) { + + var a = matrix[0]; + var b = matrix[1]; + var c = matrix[2]; + var d = matrix[3]; + + var scaleX = Math.sqrt(a * a + b * b); + a /= scaleX; + b /= scaleX; + + var shear = a * c + b * d; + c -= a * shear; + d -= b * shear; + + var scaleY = Math.sqrt(c * c + d * d); + c /= scaleY; + d /= scaleY; + shear /= scaleY; + + if (a * d < b * c) { + a = -a; + b = -b; + shear = -shear; + scaleX = -scaleX; + } + + return { + scale: [scaleX, 0, 0, scaleY, 0, 0], + translate: [1, 0, 0, 1, matrix[4], matrix[5]], + rotate: [a, b, -b, a, 0, 0], + skew: [1, 0, shear, 1, 0, 0] + }; + }, + + _matrix_map_point: function _matrix_map_point(m1, pt) { + var sx = m1[0]; + var shy = m1[1]; + var shx = m1[2]; + var sy = m1[3]; + var tx = m1[4]; + var ty = m1[5]; + + var px = pt[0]; + var py = pt[1]; + + var x = px * sx + py * shx + tx; + var y = px * shy + py * sy + ty; + return [x, y]; + }, + + _matrix_map_point_obj: function _matrix_map_point_obj(m1, pt) { + var xpt = this._matrix_map_point(m1, [pt.x, pt.y]); + return { x: xpt[0], y: xpt[1] }; + }, + + _matrix_map_rect: function _matrix_map_rect(m1, rect) { + var p1 = this._matrix_map_point(m1, [rect.x, rect.y]); + var p2 = this._matrix_map_point(m1, [rect.x + rect.w, rect.y + rect.h]); + return { x: p1[0], y: p1[1], w: p2[0] - p1[0], h: p2[1] - p1[1] }; + }, + + _matrix_is_identity: function _matrix_is_identity(m1) { + if (m1[0] != 1) { + return false; + } + if (m1[1] != 0) { + return false; + } + if (m1[2] != 0) { + return false; + } + if (m1[3] != 1) { + return false; + } + if (m1[4] != 0) { + return false; + } + if (m1[5] != 0) { + return false; + } + return true; + }, + + rotate: function rotate(angle) { + var matrix = [Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), 0.0, 0.0]; + this.ctx._transform = this._matrix_multiply(this.ctx._transform, matrix); + }, + + scale: function scale(sx, sy) { + var matrix = [sx, 0.0, 0.0, sy, 0.0, 0.0]; + this.ctx._transform = this._matrix_multiply(this.ctx._transform, matrix); + }, + + translate: function translate(x, y) { + var matrix = [1.0, 0.0, 0.0, 1.0, x, y]; + this.ctx._transform = this._matrix_multiply(this.ctx._transform, matrix); + }, + + stroke: function stroke() { + if (this.ctx._clip_path.length > 0) { + + var lines; + if (window.outIntercept) { + lines = window.outIntercept.type === 'group' ? window.outIntercept.stream : window.outIntercept; + } else { + lines = this.internal.getCurrentPage(); + } + lines.push("q"); + + var origPath = this.path; + this.path = this.ctx._clip_path; + this.ctx._clip_path = []; + this._stroke(true); + + this.ctx._clip_path = this.path; + this.path = origPath; + this._stroke(false); + + lines.push("Q"); + } else { + this._stroke(false); + } + }, + + _stroke: function _stroke(isClip) { + if (!isClip && this._isStrokeTransparent()) { + return; + } + + //TODO opacity + + var moves = []; + + var xPath = this.path; + + for (var i = 0; i < xPath.length; i++) { + var pt = xPath[i]; + switch (pt.type) { + case 'mt': + moves.push({ start: pt, deltas: [], abs: [] }); + break; + case 'lt': + var delta = [pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; + moves[moves.length - 1].deltas.push(delta); + moves[moves.length - 1].abs.push(pt); + break; + case 'bct': + var delta = [pt.x1 - xPath[i - 1].x, pt.y1 - xPath[i - 1].y, pt.x2 - xPath[i - 1].x, pt.y2 - xPath[i - 1].y, pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; + moves[moves.length - 1].deltas.push(delta); + break; + case 'qct': + // convert to bezier + var x1 = xPath[i - 1].x + 2.0 / 3.0 * (pt.x1 - xPath[i - 1].x); + var y1 = xPath[i - 1].y + 2.0 / 3.0 * (pt.y1 - xPath[i - 1].y); + var x2 = pt.x + 2.0 / 3.0 * (pt.x1 - pt.x); + var y2 = pt.y + 2.0 / 3.0 * (pt.y1 - pt.y); + var x3 = pt.x; + var y3 = pt.y; + var delta = [x1 - xPath[i - 1].x, y1 - xPath[i - 1].y, x2 - xPath[i - 1].x, y2 - xPath[i - 1].y, x3 - xPath[i - 1].x, y3 - xPath[i - 1].y]; + moves[moves.length - 1].deltas.push(delta); + break; + case 'arc': + //TODO this was hack to avoid out-of-bounds issue + // No move-to before drawing the arc + if (moves.length == 0) { + moves.push({ start: { x: 0, y: 0 }, deltas: [], abs: [] }); + } + moves[moves.length - 1].arc = true; + moves[moves.length - 1].abs.push(pt); + break; + case 'close': + break; + } + } + + for (var i = 0; i < moves.length; i++) { + var style; + if (i == moves.length - 1) { + style = 's'; + } else { + style = null; + } + if (moves[i].arc) { + var arcs = moves[i].abs; + for (var ii = 0; ii < arcs.length; ii++) { + var arc = arcs[ii]; + var start = arc.startAngle * 360 / (2 * Math.PI); + var end = arc.endAngle * 360 / (2 * Math.PI); + var x = arc.x; + var y = arc.y; + this.internal.arc2(this, x, y, arc.radius, start, end, arc.anticlockwise, style, isClip); + } + } else { + var x = moves[i].start.x; + var y = moves[i].start.y; + if (!isClip) { + this.pdf.lines(moves[i].deltas, x, y, null, style); + } else { + this.pdf.lines(moves[i].deltas, x, y, null, null); + this.pdf.clip_fixed(); + } + } + } + }, + + _isFillTransparent: function _isFillTransparent() { + return this.ctx._isFillTransparent || this.globalAlpha == 0; + }, + + _isStrokeTransparent: function _isStrokeTransparent() { + return this.ctx._isStrokeTransparent || this.globalAlpha == 0; + }, + + fill: function fill(fillRule) { + //evenodd or nonzero (default) + if (this.ctx._clip_path.length > 0) { + + var lines; + if (window.outIntercept) { + lines = window.outIntercept.type === 'group' ? window.outIntercept.stream : window.outIntercept; + } else { + lines = this.internal.getCurrentPage(); + } + lines.push("q"); + + var origPath = this.path; + this.path = this.ctx._clip_path; + this.ctx._clip_path = []; + this._fill(fillRule, true); + + this.ctx._clip_path = this.path; + this.path = origPath; + this._fill(fillRule, false); + + lines.push('Q'); + } else { + this._fill(fillRule, false); + } + }, + + _fill: function _fill(fillRule, isClip) { + if (this._isFillTransparent()) { + return; + } + var v2Support = typeof this.pdf.internal.newObject2 === 'function'; + + var lines; + if (window.outIntercept) { + lines = window.outIntercept.type === 'group' ? window.outIntercept.stream : window.outIntercept; + } else { + lines = this.internal.getCurrentPage(); + } + + // if (this.ctx._clip_path.length > 0) { + // lines.push('q'); + // var oldPath = this.path; + // this.path = this.ctx._clip_path; + // this.ctx._clip_path = []; + // this._fill(fillRule, true); + // this.ctx._clip_path = this.path; + // this.path = oldPath; + // } + + var moves = []; + var outInterceptOld = window.outIntercept; + + if (v2Support) { + // Blend and Mask + switch (this.ctx.globalCompositeOperation) { + case 'normal': + case 'source-over': + break; + case 'destination-in': + case 'destination-out': + //TODO this need to be added to the current group or page + // define a mask stream + var obj = this.pdf.internal.newStreamObject(); + + // define a mask state + var obj2 = this.pdf.internal.newObject2(); + obj2.push('<>'); // /S /Luminosity will need to define color space + obj2.push('>>'); + + // add mask to page resources + var gsName = 'MASK' + obj2.objId; + this.pdf.internal.addGraphicsState(gsName, obj2.objId); + + var instruction = '/' + gsName + ' gs'; + // add mask to page, group, or stream + lines.splice(0, 0, 'q'); + lines.splice(1, 0, instruction); + lines.push('Q'); + + window.outIntercept = obj; + break; + default: + var dictionaryEntry = '/' + this.pdf.internal.blendModeMap[this.ctx.globalCompositeOperation.toUpperCase()]; + if (dictionaryEntry) { + this.pdf.internal.out(dictionaryEntry + ' gs'); + } + break; + } + } + + var alpha = this.ctx.globalAlpha; + if (this.ctx._fillOpacity < 1) { + // TODO combine this with global opacity + alpha = this.ctx._fillOpacity; + } + + //TODO check for an opacity graphics state that was already created + //TODO do not set opacity if current value is already active + if (v2Support) { + var objOpac = this.pdf.internal.newObject2(); + objOpac.push('<>'); + var gsName = 'GS_O_' + objOpac.objId; + this.pdf.internal.addGraphicsState(gsName, objOpac.objId); + this.pdf.internal.out('/' + gsName + ' gs'); + } + + var xPath = this.path; + + for (var i = 0; i < xPath.length; i++) { + var pt = xPath[i]; + switch (pt.type) { + case 'mt': + moves.push({ start: pt, deltas: [], abs: [] }); + break; + case 'lt': + var delta = [pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; + moves[moves.length - 1].deltas.push(delta); + moves[moves.length - 1].abs.push(pt); + break; + case 'bct': + var delta = [pt.x1 - xPath[i - 1].x, pt.y1 - xPath[i - 1].y, pt.x2 - xPath[i - 1].x, pt.y2 - xPath[i - 1].y, pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; + moves[moves.length - 1].deltas.push(delta); + break; + case 'qct': + // convert to bezier + var x1 = xPath[i - 1].x + 2.0 / 3.0 * (pt.x1 - xPath[i - 1].x); + var y1 = xPath[i - 1].y + 2.0 / 3.0 * (pt.y1 - xPath[i - 1].y); + var x2 = pt.x + 2.0 / 3.0 * (pt.x1 - pt.x); + var y2 = pt.y + 2.0 / 3.0 * (pt.y1 - pt.y); + var x3 = pt.x; + var y3 = pt.y; + var delta = [x1 - xPath[i - 1].x, y1 - xPath[i - 1].y, x2 - xPath[i - 1].x, y2 - xPath[i - 1].y, x3 - xPath[i - 1].x, y3 - xPath[i - 1].y]; + moves[moves.length - 1].deltas.push(delta); + break; + case 'arc': + //TODO this was hack to avoid out-of-bounds issue when drawing circle + // No move-to before drawing the arc + if (moves.length === 0) { + moves.push({ deltas: [], abs: [] }); + } + moves[moves.length - 1].arc = true; + moves[moves.length - 1].abs.push(pt); + break; + case 'close': + moves.push({ close: true }); + break; + } + } + + for (var i = 0; i < moves.length; i++) { + var style; + if (i == moves.length - 1) { + style = 'f'; + if (fillRule === 'evenodd') { + style += '*'; + } + } else { + style = null; + } + + if (moves[i].close) { + this.pdf.internal.out('h'); + if (style) { + // only fill at final path move + this.pdf.internal.out(style); + } + } else if (moves[i].arc) { + if (moves[i].start) { + this.internal.move2(this, moves[i].start.x, moves[i].start.y); + } + var arcs = moves[i].abs; + for (var ii = 0; ii < arcs.length; ii++) { + var arc = arcs[ii]; + //TODO lines deltas were getting in here + if (typeof arc.startAngle !== 'undefined') { + var start = arc.startAngle * 360 / (2 * Math.PI); + var end = arc.endAngle * 360 / (2 * Math.PI); + var x = arc.x; + var y = arc.y; + if (ii === 0) { + this.internal.move2(this, x, y); + } + this.internal.arc2(this, x, y, arc.radius, start, end, arc.anticlockwise, null, isClip); + if (ii === arcs.length - 1) { + // The original arc move did not occur because of the algorithm + if (moves[i].start) { + var x = moves[i].start.x; + var y = moves[i].start.y; + this.internal.line2(c2d, x, y); + } + } + } else { + this.internal.line2(c2d, arc.x, arc.y); + } + } + } else { + var x = moves[i].start.x; + var y = moves[i].start.y; + if (!isClip) { + this.pdf.lines(moves[i].deltas, x, y, null, style); + } else { + this.pdf.lines(moves[i].deltas, x, y, null, null); + this.pdf.clip_fixed(); + } + } + } + + window.outIntercept = outInterceptOld; + + // if (this.ctx._clip_path.length > 0) { + // lines.push('Q'); + // } + }, + + pushMask: function pushMask() { + var v2Support = typeof this.pdf.internal.newObject2 === 'function'; + + if (!v2Support) { + console.log('jsPDF v2 not enabled'); + return; + } + + // define a mask stream + var obj = this.pdf.internal.newStreamObject(); + + // define a mask state + var obj2 = this.pdf.internal.newObject2(); + obj2.push('<>'); // /S /Luminosity will need to define color space + obj2.push('>>'); + + // add mask to page resources + var gsName = 'MASK' + obj2.objId; + this.pdf.internal.addGraphicsState(gsName, obj2.objId); + + var instruction = '/' + gsName + ' gs'; + this.pdf.internal.out(instruction); + }, + + clip: function clip() { + //TODO do we reset the path, or just copy it? + if (this.ctx._clip_path.length > 0) { + for (var i = 0; i < this.path.length; i++) { + this.ctx._clip_path.push(this.path[i]); + } + } else { + this.ctx._clip_path = this.path; + } + this.path = []; + }, + + measureText: function measureText(text) { + var pdf = this.pdf; + return { + getWidth: function getWidth() { + var fontSize = pdf.internal.getFontSize(); + var txtWidth = pdf.getStringUnitWidth(text) * fontSize / pdf.internal.scaleFactor; + // Convert points to pixels + txtWidth *= 1.3333; + return txtWidth; + }, + + get width() { + return this.getWidth(text); + } + }; + }, + _getBaseline: function _getBaseline(y) { + var height = parseInt(this.pdf.internal.getFontSize()); + // TODO Get descent from font descriptor + var descent = height * 0.25; + switch (this.ctx.textBaseline) { + case 'bottom': + return y - descent; + case 'top': + return y + height; + case 'hanging': + return y + height - descent; + case 'middle': + return y + height / 2 - descent; + case 'ideographic': + // TODO not implemented + return y; + case 'alphabetic': + default: + return y; + } + } + }; + + var c2d = jsPDFAPI.context2d; + + // accessor methods + Object.defineProperty(c2d, 'fillStyle', { + set: function set(value) { + this.setFillStyle(value); + }, + get: function get() { + return this.ctx.fillStyle; + } + }); + Object.defineProperty(c2d, 'strokeStyle', { + set: function set(value) { + this.setStrokeStyle(value); + }, + get: function get() { + return this.ctx.strokeStyle; + } + }); + Object.defineProperty(c2d, 'lineWidth', { + set: function set(value) { + this.setLineWidth(value); + }, + get: function get() { + return this.ctx.lineWidth; + } + }); + Object.defineProperty(c2d, 'lineCap', { + set: function set(val) { + this.setLineCap(val); + }, + get: function get() { + return this.ctx.lineCap; + } + }); + Object.defineProperty(c2d, 'lineJoin', { + set: function set(val) { + this.setLineJoin(val); + }, + get: function get() { + return this.ctx.lineJoin; + } + }); + Object.defineProperty(c2d, 'miterLimit', { + set: function set(val) { + this.ctx.miterLimit = val; + }, + get: function get() { + return this.ctx.miterLimit; + } + }); + Object.defineProperty(c2d, 'textBaseline', { + set: function set(value) { + this.setTextBaseline(value); + }, + get: function get() { + return this.getTextBaseline(); + } + }); + Object.defineProperty(c2d, 'textAlign', { + set: function set(value) { + this.setTextAlign(value); + }, + get: function get() { + return this.getTextAlign(); + } + }); + Object.defineProperty(c2d, 'font', { + set: function set(value) { + this.setFont(value); + }, + get: function get() { + return this.ctx.font; + } + }); + Object.defineProperty(c2d, 'globalCompositeOperation', { + set: function set(value) { + this.ctx.globalCompositeOperation = value; + }, + get: function get() { + return this.ctx.globalCompositeOperation; + } + }); + Object.defineProperty(c2d, 'globalAlpha', { + set: function set(value) { + this.ctx.globalAlpha = value; + }, + get: function get() { + return this.ctx.globalAlpha; + } + }); + // Not HTML API + Object.defineProperty(c2d, 'ignoreClearRect', { + set: function set(value) { + this.ctx.ignoreClearRect = value; + }, + get: function get() { + return this.ctx.ignoreClearRect; + } + }); + // End Not HTML API + + c2d.internal = {}; + + c2d.internal.rxRgb = /rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/; + c2d.internal.rxRgba = /rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/; + c2d.internal.rxTransparent = /transparent|rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*0+\s*\)/; + + // http://hansmuller-flex.blogspot.com/2011/10/more-about-approximating-circular-arcs.html + c2d.internal.arc = function (c2d, xc, yc, r, a1, a2, anticlockwise, style) { + + var k = this.pdf.internal.scaleFactor; + var pageHeight = this.pdf.internal.pageSize.getHeight(); + var f2 = this.pdf.internal.f2; + + var a1r = a1 * (Math.PI / 180); + var a2r = a2 * (Math.PI / 180); + var curves = this.createArc(r, a1r, a2r, anticlockwise); + + for (var i = 0; i < curves.length; i++) { + var curve = curves[i]; + if (i === 0) { + this.pdf.internal.out([f2((curve.x1 + xc) * k), f2((pageHeight - (curve.y1 + yc)) * k), 'm', f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), 'c'].join(' ')); + } else { + this.pdf.internal.out([f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), 'c'].join(' ')); + } + + //c2d._lastPoint = {x: curve.x1 + xc, y: curve.y1 + yc}; + c2d._lastPoint = { x: xc, y: yc }; + // f2((curve.x1 + xc) * k), f2((pageHeight - (curve.y1 + yc)) * k), 'm', f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), 'c' + } + + if (style !== null) { + this.pdf.internal.out(this.pdf.internal.getStyle(style)); + } + }; + + /** + * + * @param x Edge point X + * @param y Edge point Y + * @param r Radius + * @param a1 start angle + * @param a2 end angle + * @param anticlockwise + * @param style + * @param isClip + */ + c2d.internal.arc2 = function (c2d, x, y, r, a1, a2, anticlockwise, style, isClip) { + // we need to convert from cartesian to polar here methinks. + var centerX = x; // + r; + var centerY = y; + + if (!isClip) { + this.arc(c2d, centerX, centerY, r, a1, a2, anticlockwise, style); + } else { + this.arc(c2d, centerX, centerY, r, a1, a2, anticlockwise, null); + this.pdf.clip_fixed(); + } + }; + + c2d.internal.move2 = function (c2d, x, y) { + var k = this.pdf.internal.scaleFactor; + var pageHeight = this.pdf.internal.pageSize.getHeight(); + var f2 = this.pdf.internal.f2; + + this.pdf.internal.out([f2(x * k), f2((pageHeight - y) * k), 'm'].join(' ')); + c2d._lastPoint = { x: x, y: y }; + }; + + c2d.internal.line2 = function (c2d, dx, dy) { + var k = this.pdf.internal.scaleFactor; + var pageHeight = this.pdf.internal.pageSize.getHeight(); + var f2 = this.pdf.internal.f2; + + //var pt = {x: c2d._lastPoint.x + dx, y: c2d._lastPoint.y + dy}; + var pt = { x: dx, y: dy }; + + this.pdf.internal.out([f2(pt.x * k), f2((pageHeight - pt.y) * k), 'l'].join(' ')); + //this.pdf.internal.out('f'); + c2d._lastPoint = pt; + }; + + /** + * Return a array of objects that represent bezier curves which approximate the circular arc centered at the origin, from startAngle to endAngle (radians) with the specified radius. + * + * Each bezier curve is an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points. + */ + + c2d.internal.createArc = function (radius, startAngle, endAngle, anticlockwise) { + var EPSILON = 0.00001; // Roughly 1/1000th of a degree, see below + var twoPI = Math.PI * 2; + var piOverTwo = Math.PI / 2.0; + + // normalize startAngle, endAngle to [0, 2PI] + var startAngleN = startAngle; + if (startAngleN < twoPI || startAngleN > twoPI) { + startAngleN = startAngleN % twoPI; + } + if (startAngleN < 0) { + startAngleN = twoPI + startAngleN; + } + + while (startAngle > endAngle) { + startAngle = startAngle - twoPI; + } + var totalAngle = Math.abs(endAngle - startAngle); + if (totalAngle < twoPI) { + if (anticlockwise) { + totalAngle = twoPI - totalAngle; + } + } + + // Compute the sequence of arc curves, up to PI/2 at a time. + var curves = []; + var sgn = anticlockwise ? -1 : +1; + + var a1 = startAngleN; + for (; totalAngle > EPSILON;) { + var remain = sgn * Math.min(totalAngle, piOverTwo); + var a2 = a1 + remain; + curves.push(this.createSmallArc(radius, a1, a2)); + totalAngle -= Math.abs(a2 - a1); + a1 = a2; + } + + return curves; + }; + + c2d.internal.getCurrentPage = function () { + return this.pdf.internal.pages[this.pdf.internal.getCurrentPageInfo().pageNumber]; + }; + + /** + * Cubic bezier approximation of a circular arc centered at the origin, from (radians) a1 to a2, where a2-a1 < pi/2. The arc's radius is r. + * + * Returns an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points. + * + * This algorithm is based on the approach described in: A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa," Information Technology and Control, 35(4), 2006 pp. 371-378. + */ + + c2d.internal.createSmallArc = function (r, a1, a2) { + // Compute all four points for an arc that subtends the same total angle + // but is centered on the X-axis + + var a = (a2 - a1) / 2.0; + + var x4 = r * Math.cos(a); + var y4 = r * Math.sin(a); + var x1 = x4; + var y1 = -y4; + + var q1 = x1 * x1 + y1 * y1; + var q2 = q1 + x1 * x4 + y1 * y4; + var k2 = 4 / 3 * (Math.sqrt(2 * q1 * q2) - q2) / (x1 * y4 - y1 * x4); + + var x2 = x1 - k2 * y1; + var y2 = y1 + k2 * x1; + var x3 = x2; + var y3 = -y2; + + // Find the arc points' actual locations by computing x1,y1 and x4,y4 + // and rotating the control points by a + a1 + + var ar = a + a1; + var cos_ar = Math.cos(ar); + var sin_ar = Math.sin(ar); + + return { + x1: r * Math.cos(a1), + y1: r * Math.sin(a1), + x2: x2 * cos_ar - y2 * sin_ar, + y2: x2 * sin_ar + y2 * cos_ar, + x3: x3 * cos_ar - y3 * sin_ar, + y3: x3 * sin_ar + y3 * cos_ar, + x4: r * Math.cos(a2), + y4: r * Math.sin(a2) + }; + }; + + function context() { + this._isStrokeTransparent = false; + this._strokeOpacity = 1; + this.strokeStyle = '#000000'; + this.fillStyle = '#000000'; + this._isFillTransparent = false; + this._fillOpacity = 1; + this.font = "12pt times"; + this.textBaseline = 'alphabetic'; // top,bottom,middle,ideographic,alphabetic,hanging + this.textAlign = 'start'; + this.lineWidth = 1; + this.lineJoin = 'miter'; // round, bevel, miter + this.lineCap = 'butt'; // butt, round, square + this._transform = [1, 0, 0, 1, 0, 0]; // sx, shy, shx, sy, tx, ty + this.globalCompositeOperation = 'normal'; + this.globalAlpha = 1.0; + this._clip_path = []; + // TODO miter limit //default 10 + + // Not HTML API + this.ignoreClearRect = false; + + this.copy = function (ctx) { + this._isStrokeTransparent = ctx._isStrokeTransparent; + this._strokeOpacity = ctx._strokeOpacity; + this.strokeStyle = ctx.strokeStyle; + this._isFillTransparent = ctx._isFillTransparent; + this._fillOpacity = ctx._fillOpacity; + this.fillStyle = ctx.fillStyle; + this.font = ctx.font; + this.lineWidth = ctx.lineWidth; + this.lineJoin = ctx.lineJoin; + this.lineCap = ctx.lineCap; + this.textBaseline = ctx.textBaseline; + this.textAlign = ctx.textAlign; + this._fontSize = ctx._fontSize; + this._transform = ctx._transform.slice(0); + this.globalCompositeOperation = ctx.globalCompositeOperation; + this.globalAlpha = ctx.globalAlpha; + this._clip_path = ctx._clip_path.slice(0); //TODO deep copy? + + // Not HTML API + this.ignoreClearRect = ctx.ignoreClearRect; + }; + } + + return this; + })(jsPDF.API); + + /** @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 + * + * + * ==================================================================== + */ + + (function (jsPDFAPI) { + var clone, _DrillForContent, FontNameDB, FontStyleMap, TextAlignMap, 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 PurgeWhiteSpace(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 Renderer(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 ResolveFont(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 ResolveUnitedNumber(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 GetCSS(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 elementHandledElsewhere(element, renderer, elementHandlers) { + var handlers, i, isHandledElsewhere, l, classNames; + isHandledElsewhere = false; + i = void 0; + l = 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++; + } + } + } + + // Try class names + classNames = typeof element.className === 'string' ? element.className.split(' ') : []; + for (i = 0; i < classNames.length; i++) { + handlers = elementHandlers['.' + classNames[i]]; + 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 tableToJson(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.getWidth() + }; + 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 DrillForContent(element, renderer, elementHandlers) { + var cn, cns, fragmentCSS, i, isBlock, l, table2json, cb; + cns = element.childNodes; + cn = void 0; + fragmentCSS = GetCSS(element); + isBlock = fragmentCSS.display === "block"; + if (isBlock) { + renderer.setBlockBoundary(); + renderer.setBlockStyle(fragmentCSS); + } + i = 0; + l = cns.length; + while (i < l) { + cn = cns[i]; + if ((typeof cn === "undefined" ? "undefined" : _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.getHeight() - 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: elementHandlers.printHeaders, + margins: renderer.pdf.margins_doc, + css: GetCSS(cn) + }); + 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"]; + var offsetX = (3 - fontSize * 0.75) * renderer.pdf.internal.scaleFactor; + var offsetY = fontSize * 0.75 * renderer.pdf.internal.scaleFactor; + var radius = fontSize * 1.74 / renderer.pdf.internal.scaleFactor; + cb = function cb(x, y) { + this.pdf.circle(x + offsetX, y + offsetY, radius, 'FD'); + }; + } + } + // Only add the text if the text node is in the body element + // Add compatibility with IE11 + if (!!(cn.ownerDocument.body.compareDocumentPosition(cn) & 16)) { + renderer.addText(value, fragmentCSS); + } + } else if (typeof cn === "string") { + renderer.addText(cn, fragmentCSS); + } + } + i++; + } + elementHandlers.outY = renderer.y; + + if (isBlock) { + return renderer.setBlockBoundary(cb); + } + }; + images = {}; + loadImgs = function loadImgs(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 checkForFooter(elem, renderer, elementHandlers) { + //check if we can found a