/** @preserve * jsPDF - PDF Document creation from JavaScript * Version ${versionID} * CommitID ${commitID} * * Copyright (c) 2010-2014 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 * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Contributor(s): * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, * kim3er, mfo, alnorth, Flamenco */ /** * Creates new jsPDF document object instance. * * @class * @param orientation One of "portrait" or "landscape" (or shortcuts "p" (Default), "l") * @param unit Measurement unit to be used when coordinates are specified. * One of "pt" (points), "mm" (Default), "cm", "in" * @param format One of 'pageFormats' as shown below, default: a4 * @returns {jsPDF} * @name jsPDF */ var jsPDF = (function(global) { 'use strict'; 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 */ 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 === '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, lineHeightProportion = options.lineHeight || 1.15, lineWidth = options.lineWidth || 0.200025, // 2mm 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 fontStateStack = [], // patterns = {}, // collection of pattern objects patternMap = {}, // see fonts gStates = {}, // collection of graphic state objects gStatesMap = {}, // see fonts activeGState = null, 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, renderTargets = {}, renderTargetMap = {}, renderTargetStack = [], pageX, pageY, pageMatrix, // only used for FormObjects pageWidth, pageHeight, pageMode, zoomMode, layoutMode, documentProperties = { 'title' : '', 'subject' : '', 'author' : '', 'keywords' : '', 'creator' : '' }, API = {}, events = new PubSub(API), ///////////////////// // Private functions ///////////////////// f2 = function(number) { return number.toFixed(2); // Ie, %.2f }, f3 = function(number) { return number.toFixed(3); // Ie, %.3f }, padd2 = function(number) { return ('0' + parseInt(number)).slice(-2); }, padd2Hex = function (hexString) { var s = "00" + hexString; return s.substr(s.length - 2); }, out = function(string) { 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() { // 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() { 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() { objectNumber++; offsets[objectNumber] = function(){ return content_length; }; return objectNumber; }, newObjectDeferredBegin = function(oid) { offsets[oid] = content_length; }, putStream = function(str) { out('stream'); out(str); out('endstream'); }, putPages = function() { var n,p,arr,i,deflater,adler32,adler32cs,wPt,hPt; adler32cs = global.adler32cs || jsPDF.adler32cs; if (compress && typeof adler32cs === 'undefined') { compress = false; } // outToPages = false as set in endDocument(). out() writes to content. for (n = 1; n <= page; n++) { newObject(); wPt = (pageWidth = pagedim[n].width) * k; hPt = (pageHeight = pagedim[n].height) * k; out('<>'); out('endobj'); // Page content p = pages[n].join('\n'); // prepend global change of basis matrix // (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix // that does this job for us (however, texts, images and similar objects must be drawn bottom up)) p = new Matrix(k, 0, 0, -k, 0, pageHeight).toString() + " cm\n" + p; 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(font) { font.objectNumber = newObject(); out('<>'); out('endobj'); }, putFonts = function() { for (var fontKey in fonts) { if (fonts.hasOwnProperty(fontKey)) { putFont(fonts[fontKey]); } } }, putXObject = function (xObject) { xObject.objectNumber = newObject(); out("<<"); out("/Type /XObject"); out("/Subtype /Form"); out("/BBox [" + [ f2(xObject.x), f2(xObject.y), f2(xObject.x + xObject.width), f2(xObject.y + xObject.height) ].join(" ") + "]"); out("/Matrix [" + xObject.matrix.toString() + "]"); // TODO: /Resources var p = xObject.pages[1].join("\n"); out("/Length " + p.length); out(">>"); putStream(p); out("endobj"); }, putXObjects = function () { for (var xObjectKey in renderTargets) { if (renderTargets.hasOwnProperty(xObjectKey)) { putXObject(renderTargets[xObjectKey]); } } }, interpolateAndEncodeRGBStream = function (colors, numberSamples) { var tValues = []; var t; var dT = 1.0 / (numberSamples - 1); for (t = 0.0; t < 1.0; t += dT) { tValues.push(t); } tValues.push(1.0); // add first and last control point if not present if (colors[0].offset != 0.0) { var c0 = { offset: 0.0, color: colors[0].color }; colors.unshift(c0) } if (colors[colors.length - 1].offset != 1.0) { var c1 = { offset: 1.0, color: colors[colors.length - 1].color }; colors.push(c1); } var out = ""; var index = 0; for (var i = 0; i < tValues.length; i++) { t = tValues[i]; while (t > colors[index + 1].offset) index++; var a = colors[index].offset; var b = colors[index + 1].offset; var d = (t - a) / (b - a); var aColor = colors[index].color; var bColor = colors[index + 1].color; out += padd2Hex((Math.round((1 - d) * aColor[0] + d * bColor[0])).toString(16)) + padd2Hex((Math.round((1 - d) * aColor[1] + d * bColor[1])).toString(16)) + padd2Hex((Math.round((1 - d) * aColor[2] + d * bColor[2])).toString(16)); } return out.trim(); }, putShadingPattern = function (pattern, numberSamples) { /* Axial patterns shade between the two points specified in coords, radial patterns between the inner and outer circle. The user can specify an array (colors) that maps t-Values in [0, 1] to RGB colors. These are now interpolated to equidistant samples and written to pdf as a sample (type 0) function. */ // The number of color samples that should be used to describe the shading. // The higher, the more accurate the gradient will be. numberSamples || (numberSamples = 21); var funcObjectNumber = newObject(); var stream = interpolateAndEncodeRGBStream(pattern.colors, numberSamples); out("<< /FunctionType 0"); out("/Domain [0.0 1.0]"); out("/Size [" + numberSamples + "]"); out("/BitsPerSample 8"); out("/Range [0.0 1.0 0.0 1.0 0.0 1.0]"); out("/Decode [0.0 1.0 0.0 1.0 0.0 1.0]"); out("/Length " + stream.length); // The stream is Hex encoded out("/Filter /ASCIIHexDecode"); out(">>"); putStream(stream); out("endobj"); pattern.objectNumber = newObject(); out("<< /ShadingType " + pattern.type); out("/ColorSpace /DeviceRGB"); var coords = "/Coords [" + f3(parseFloat(pattern.coords[0])) + " "// x1 + f3(parseFloat(pattern.coords[1])) + " "; // y1 if (pattern.type === 2) { // axial coords += f3(parseFloat(pattern.coords[2])) + " " // x2 + f3(parseFloat(pattern.coords[3])); // y2 } else { // radial coords += f3(parseFloat(pattern.coords[2])) + " "// r1 + f3(parseFloat(pattern.coords[3])) + " " // x2 + f3(parseFloat(pattern.coords[4])) + " " // y2 + f3(parseFloat(pattern.coords[5])); // r2 } coords += "]"; out(coords); if (pattern.matrix) { out("/Matrix [" + pattern.matrix.toString() + "]"); } out("/Function " + funcObjectNumber + " 0 R"); out("/Extend [true true]"); out(">>"); out("endobj"); }, putTilingPattern = function (pattern) { var resourcesObjectNumber = newObject(); out("<<"); putResourceDictionary(); out(">>"); out("endobj"); pattern.objectNumber = newObject(); out("<< /Type /Pattern"); out("/PatternType 1"); // tiling pattern out("/PaintType 1"); // colored tiling pattern out("/TilingType 1"); // constant spacing out("/BBox [" + pattern.boundingBox.map(f3).join(" ") + "]"); out("/XStep " + f3(pattern.xStep)); out("/YStep " + f3(pattern.yStep)); out("/Length " + pattern.stream.length); out("/Resources " + resourcesObjectNumber + " 0 R"); // TODO: resources pattern.matrix && out("/Matrix [" + pattern.matrix.toString() + "]"); out(">>"); putStream(pattern.stream); out("endobj"); }, putPatterns = function () { var patternKey; for (patternKey in patterns) { if (patterns.hasOwnProperty(patternKey)) { if (patterns[patternKey] instanceof API.ShadingPattern) { putShadingPattern(patterns[patternKey]); } else if (patterns[patternKey] instanceof API.TilingPattern) { putTilingPattern(patterns[patternKey]); } } } }, putGState = function (gState) { gState.objectNumber = newObject(); out("<<"); for (var p in gState) { switch (p) { case "opacity": out("/ca " + f2(gState[p])); break; } } out(">>"); out("endobj"); }, putGStates = function () { var gStateKey; for (gStateKey in gStates) { if (gStates.hasOwnProperty(gStateKey)) { putGState(gStates[gStateKey]); } } }, putXobjectDict = function () { for (var xObjectKey in renderTargets) { if (renderTargets.hasOwnProperty(xObjectKey) && renderTargets[xObjectKey].objectNumber >= 0) { out("/" + xObjectKey + " " + renderTargets[xObjectKey].objectNumber + " 0 R"); } } events.publish('putXobjectDict'); }, putShadingPatternDict = function () { for (var patternKey in patterns) { if (patterns.hasOwnProperty(patternKey) && patterns[patternKey] instanceof API.ShadingPattern && patterns[patternKey].objectNumber >= 0) { out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R"); } } events.publish("putShadingPatternDict"); }, putTilingPatternDict = function () { for (var patternKey in patterns) { if (patterns.hasOwnProperty(patternKey) && patterns[patternKey] instanceof API.TilingPattern && patterns[patternKey].objectNumber >= 0) { out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R"); } } events.publish("putTilingPatternDict"); }, putGStatesDict = function () { var gStateKey; for (gStateKey in gStates) { if (gStates.hasOwnProperty(gStateKey) && gStates[gStateKey].objectNumber >= 0) { out("/" + gStateKey + " " + gStates[gStateKey].objectNumber + " 0 R"); } } events.publish("putGStateDict"); }, putResourceDictionary = function() { 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("/Shading <<"); putShadingPatternDict(); out(">>"); out("/Pattern <<"); putTilingPatternDict(); out(">>"); out("/ExtGState <<"); putGStatesDict(); out('>>'); out('/XObject <<'); putXobjectDict(); out('>>'); }, putResources = function() { putFonts(); putGStates(); putXObjects(); putPatterns(); events.publish('putResources'); // Resource dictionary offsets[2] = content_length; out('2 0 obj'); out('<<'); putResourceDictionary(); out('>>'); out('endobj'); events.publish('postPutResources'); }, putAdditionalObjects = function() { events.publish('putAdditionalObjects'); for (var i=0; i> 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(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() { 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]) + ')'); } } var created = new Date(), tzoffset = created.getTimezoneOffset(), tzsign = tzoffset < 0 ? '+' : '-', tzhour = Math.floor(Math.abs(tzoffset / 60)), tzmin = Math.abs(tzoffset % 60), tzstr = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join(''); out(['/CreationDate (D:', created.getFullYear(), padd2(created.getMonth() + 1), padd2(created.getDate()), padd2(created.getHours()), padd2(created.getMinutes()), padd2(created.getSeconds()), tzstr, ')'].join('')); }, putCatalog = function() { 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() { out('/Size ' + (objectNumber + 1)); out('/Root ' + objectNumber + ' 0 R'); out('/Info ' + (objectNumber - 1) + ' 0 R'); }, beginPage = function(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; // } // TODO: What is the reason for this (for me it only seems to raise bugs)? // 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() { beginPage.apply(this, arguments); // Set line width out(f2(lineWidth) + ' 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( 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(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 {String} fontName can be undefined on "falthy" to indicate "use current" * @param {String} fontStyle can be undefined on "falthy" to indicate "use current" * @returns {String} Font key. */ getFont = function(fontName, fontStyle) { var key; fontName = fontName !== undefined ? fontName : fonts[activeFontKey].fontName; fontStyle = fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle; if (fontName !== undefined){ fontName = fontName.toLowerCase(); } switch(fontName){ case 'sans-serif': case 'verdana': case 'arial': case 'helvetica': fontName = 'helvetica'; break; case 'fixed': case 'monospace': case 'terminal': case 'courier': fontName = 'courier'; break; case 'serif': case 'cursive': case 'fantasy': default: fontName = 'times'; break; } try { // get a string like 'F3' - the KEY corresponding tot he font + type combination. key = fontmap[fontName][fontStyle]; } catch (e) {} if (!key) { //throw new Error("Unable to look up font label for font '" + fontName + "', '" //+ fontStyle + "'. Refer to getFontList() for available fonts."); key = fontmap['times'][fontStyle]; if (key == null){ key = fontmap['times']['normal']; } } return key; }, buildDocument = function() { outToPages = false; // switches out() to content objectNumber = 2; content = []; offsets = []; additionalObjects = []; // putHeader() out('%PDF-' + pdfVersion); 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(style) { // see path-painting operators in PDF spec var op = 'n'; // none if (style === "D") { op = 'S'; // stroke } else 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; }, // puts the style for the previously drawn path. If a patternKey is provided, the pattern is used to fill // the path. Use patternMatrix to transform the pattern to rhe right location. putStyle = function (style, patternKey, patternData) { style = getStyle(style); // stroking / filling / both the path if (!patternKey) { out(style); return; } patternData || (patternData = unitMatrix); var patternId = patternMap[patternKey]; var pattern = patterns[patternId]; if (pattern instanceof API.ShadingPattern) { out("q"); out("W " + style); if (pattern.gState) { API.setGState(pattern.gState); } out(patternData.toString() + " cm"); out("/" + patternId + " sh"); out("Q"); } else if (pattern instanceof API.TilingPattern) { // pdf draws patterns starting at the bottom left corner and they are not affected by the global transformation, // so we must flip them var matrix = new Matrix(1, 0, 0, -1, 0, pageHeight); if (patternData.matrix) { matrix = matrixMult(patternData.matrix || unitMatrix, matrix); // we cannot apply a matrix to the pattern on use so we must abuse the pattern matrix and create new instances // for each use patternId = pattern.createClone(patternKey, patternData.boundingBox, patternData.xStep, patternData.yStep, matrix).id; } out("q"); out("/Pattern cs"); out("/" + patternId + " scn"); if (pattern.gState) { API.setGState(pattern.gState); } out(style); out("Q"); } }, getArrayBuffer = function() { 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() { 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 (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 }); switch (unit) { case 'pt': k = 1; break; case 'mm': k = 72 / 25.4000508; break; case 'cm': k = 72 / 2.54000508; break; case 'in': k = 72; break; case 'px': k = 96 / 72; break; case 'pc': k = 12; break; case 'em': k = 12; break; case 'ex': k = 6; break; default: throw ('Invalid unit: ' + unit); } //--------------------------------------- // Public API /** * Object exposing internal API to plugins * @public */ API.internal = { 'pdfEscape' : pdfEscape, 'getStyle' : getStyle, /** * Returns {FontObject} describing a particular font. * @public * @function * @param {String} fontName (Optional) Font's family name * @param {String} fontStyle (Optional) Font's style variation name (Example:"Italic") * @returns {FontObject} */ 'getFont' : function() { return fonts[getFont.apply(API, arguments)]; }, 'getFontSize' : function() { return activeFontSize; }, 'getLineHeight' : function() { return activeFontSize * lineHeightProportion; }, 'write' : function(string1 /*, string2, string3, etc */) { out(arguments.length === 1 ? string1 : Array.prototype.join.call(arguments, ' ')); }, 'getCoordinateString' : function(value) { return f2(value); }, 'getVerticalCoordinateString' : function(value) { return f2(value); }, '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' : { get width() { return pageWidth }, get height() { return pageHeight } }, 'output' : function(type, options) { return output(type, options); }, 'getNumberOfPages' : function() { return pages.length - 1; }, 'pages' : pages, 'out' : out, 'f2' : f2, 'getPageInfo' : function(pageNumberOneBased){ var objId = (pageNumberOneBased - 1) * 2 + 3; return {objId:objId, pageNumber:pageNumberOneBased, pageContext:pagesContext[pageNumberOneBased]}; }, 'getCurrentPageInfo' : function(){ var objId = (currentPage - 1) * 2 + 3; return {objId:objId, pageNumber:currentPage, pageContext:pagesContext[currentPage]}; }, 'getPDFVersion': function () { return pdfVersion; } }; /** * An object representing a pdf graphics state. * @param parameters A parameter object that contains all properties this graphics state wants to set. * Supported are: opacity * @constructor */ API.GState = function (parameters) { var supported = "opacity"; for (var p in parameters) { if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) { this[p] = parameters[p]; } } this.id = ""; // set by addGState() this.objectNumber = -1; // will be set by putGState() this.equals = function (other) { var ignore = "id,objectNumber,equals"; if (!other || typeof other !== typeof this) return false; var count = 0; for (var p in this) { if (ignore.indexOf(p) >= 0) continue; if (this.hasOwnProperty(p) && !other.hasOwnProperty(p)) return false; if (this[p] !== other[p]) return false; count++; } for (var p in other) { if (other.hasOwnProperty(p) && ignore.indexOf(p) < 0) count--; } return count === 0; } }; /** * Adds a new {@link GState} for later use {@see setGState}. * @param {String} key * @param {GState} gState * @function * @returns {jsPDF} * @methodOf jsPDF# * @name addGState */ API.addGState = function (key, gState) { addGState(key, gState); return this; }; /** * Adds (and transfers the focus to) new page to the PDF document. * @function * @returns {jsPDF} * * @methodOf jsPDF# * @name addPage */ API.addPage = function() { _addPage.apply(this, arguments); return this; }; 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) { var tmpPagesContext, tmpPagedim, tmpPages, i; if (targetPage > beforePage){ tmpPages = pages[targetPage]; tmpPagedim = pagedim[targetPage]; tmpPagesContext = pagesContext[targetPage]; for (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){ tmpPages = pages[targetPage]; tmpPagedim = pagedim[targetPage]; tmpPagesContext = pagesContext[targetPage]; for (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.setDisplayMode = function(zoom, layout, pmode) { zoomMode = zoom; layoutMode = layout; pageMode = pmode; return this; }; /** * Saves the current graphics state ("pushes it on the stack"). It can be restored by {@link restoreGraphicsState} * later. Here, the general pdf graphics state is meant, also including the current transformation matrix, * fill and stroke colors etc. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name saveGraphicsState */ API.saveGraphicsState = function () { out("q"); // as we cannot set font key and size independently we must keep track of both fontStateStack.push({ key: activeFontKey, size: activeFontSize }); return this; }; /** * Restores a previously saved graphics state saved by {@link saveGraphicsState} ("pops the stack"). * @function * @returns {jsPDF} * @methodOf jsPDF# * @name restoreGraphicsState */ API.restoreGraphicsState = function () { out("Q"); // restore previous font state var fontState = fontStateStack.pop(); activeFontKey = fontState.key; activeFontSize = fontState.size; return this; }; /** * Appends this matrix to the left of all previously applied matrices. * @param {Matrix} matrix * @function * @returns {jsPDF} * @methodOf jsPDF# * @name setCurrentTransformationMatrix */ API.setCurrentTransformationMatrix = function (matrix) { out(matrix.toString() + " cm"); return this; }; /** * Starts a new pdf form object, which means that all conseequent draw calls target a new independent object * until {@link endFormObject} is called. The created object can be referenced and drawn later using * {@link doFormObject}. Nested form objects are possible. * x, y, width, height set the bounding box that is used to clip the content. * @param {number} x * @param {number} y * @param {number} width * @param {number} height * @param {Matrix} matrix The matrix that will be applied to convert the form objects coordinate system to * the parent's. * @function * @returns {jsPDF} * @methodOf jsPDF# */ API.beginFormObject = function (x, y, width, height, matrix) { // The user can set the output target to a new form object. Nested form objects are possible. // Currently, they use the resource dictionary of the surrounding stream. This should be changed, as // the PDF-Spec states: // "In PDF 1.2 and later versions, form XObjects may be independent of the content streams in which // they appear, and this is strongly recommended although not requiredIn PDF 1.2 and later versions, // form XObjects may be independent of the content streams in which they appear, and this is strongly // recommended although not required" beginNewRenderTarget(x, y, width, height, matrix); return this; }; /** * Completes and saves the form object. * @param {String} key The key by which this form object can be referenced. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name endFormObject */ API.endFormObject = function (key) { endFormObject(key); return this; }; /** * Draws the specified form object by referencing to the respective pdf XObject created with * {@link API.beginFormObject} and {@link endFormObject}. * The location is determined by matrix. * @param {String} key The key to the form object. * @param {Matrix} matrix The matrix applied before drawing the form object. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name doFormObject */ API.doFormObject = function (key, matrix) { var xObject = renderTargets[renderTargetMap[key]]; out("q"); out(matrix.toString() + " cm"); out("/" + xObject.id + " Do"); out("Q"); return this; }; /** * Returns the form object specified by key. * @param key {String} * @returns {{x: number, y: number, width: number, height: number, matrix: Matrix}} * @function * @returns {jsPDF} * @methodOf jsPDF# * @name getFormObject */ API.getFormObject = function (key) { var xObject = renderTargets[renderTargetMap[key]]; return { x: xObject.x, y: xObject.y, width: xObject.width, height: xObject.height, matrix: xObject.matrix }; }; /** * A matrix object for 2D homogenous transformations: * | a b 0 | * | c d 0 | * | e f 1 | * pdf multiplies matrices righthand: v' = v x m1 x m2 x ... * @param {number} a * @param {number} b * @param {number} c * @param {number} d * @param {number} e * @param {number} f * @constructor */ API.Matrix = Matrix; /** * Multiplies two matrices. (see {@link Matrix}) * @param {Matrix} m1 * @param {Matrix} m2 */ API.matrixMult = matrixMult; /** * The unit matrix (equal to new Matrix(1, 0, 0, 1, 0, 0). * @type {Matrix} */ API.unitMatrix = unitMatrix; var Pattern = function (gState, matrix) { this.gState = gState; this.matrix = matrix; this.id = ""; // set by addPattern() this.objectNumber = -1; // will be set by putPattern() }; /** * A pattern describing a shading pattern. * @param {String} type One of "axial" or "radial" * @param {Array} coords Either [x1, y1, x2, y2] for "axial" type describing the two interpolation points * or [x1, y1, r, x2, y2, r2] for "radial" describing inner and the outer circle. * @param {Array} colors An array of objects with the fields "offset" and "color". "offset" describes * the offset in parameter space [0, 1]. "color" is an array of length 3 describing RGB values in [0, 255]. * @param {GState=} gState An additional graphics state that gets applied to the pattern (optional). * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system * and the use coordinate system (optional). * @constructor * @extends API.Pattern */ API.ShadingPattern = function (type, coords, colors, gState, matrix) { // see putPattern() for information how they are realized this.type = type === "axial" ? 2 : 3; this.coords = coords; this.colors = colors; Pattern.call(this, gState, matrix); }; /** * A PDF Tiling pattern. * @param {Array.} boundingBox The bounding box at which one pattern cell gets clipped. * @param {Number} xStep Horizontal spacing between pattern cells. * @param {Number} yStep Vertical spacing between pattern cells. * @param {API.GState=} gState An additional graphics state that gets applied to the pattern (optional). * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system * and the use coordinate system (optional). * @constructor * @extends API.Pattern */ API.TilingPattern = function (boundingBox, xStep, yStep, gState, matrix) { this.boundingBox = boundingBox; this.xStep = xStep; this.yStep = yStep; this.stream = ""; // set by endTilingPattern(); this.cloneIndex = 0; Pattern.call(this, gState, matrix); }; API.TilingPattern.prototype = { createClone: function (patternKey, boundingBox, xStep, yStep, matrix) { var clone = new API.TilingPattern(boundingBox || this.boundingBox, xStep || this.xStep, yStep || this.yStep, this.gState, matrix || this.matrix); clone.stream = this.stream; var key = patternKey + "$$" + this.cloneIndex++ + "$$"; addPattern(key, clone); return clone; } }; /** * Adds a new {@link API.ShadingPattern} for later use. * @param {String} key * @param {Pattern} pattern * @function * @returns {jsPDF} * @methodOf jsPDF# * @name addPattern */ API.addShadingPattern = function (key, pattern) { addPattern(key, pattern); return this; }; /** * Begins a new tiling pattern. All subsequent render calls are drawn to this pattern until {@link API.endTilingPattern} * gets called. * @param {API.Pattern} pattern */ API.beginTilingPattern = function (pattern) { beginNewRenderTarget(pattern.boundingBox[0], pattern.boundingBox[1], pattern.boundingBox[2] - pattern.boundingBox[0], pattern.boundingBox[3] - pattern.boundingBox[1], pattern.matrix); }; /** * Ends a tiling pattern and sets the render target to the one active before {@link API.beginTilingPattern} has been called. * @param {string} key A unique key that is used to reference this pattern at later use. * @param {API.Pattern} pattern The pattern to end. */ API.endTilingPattern = function (key, pattern) { // retrieve the stream pattern.stream = pages[currentPage].join("\n"); addPattern(key, pattern); events.publish("endTilingPattern", pattern); // restore state from stack renderTargetStack.pop().restore(); }; /** * 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} flags 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. * @param {number|Matrix} transform If transform is a number the text will be rotated by this value. If it is a Matrix, * this matrix gets directly applied to the text, which allows shearing effects etc. * @param align {string} * @returns {jsPDF} * @methodOf jsPDF# */ API.text = function(text, x, y, flags, transform, align) { /** * 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 */ function ESC(s) { s = s.split("\t").join(Array(options.TabLen||9).join(" ")); return pdfEscape(s, flags); } // 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') { var tmp = y; y = x; x = text; text = tmp; } // 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(/[\n\r]/)) { text = text.split( /\r\n|\r|\n/g); } else { text = [text]; } } if (typeof transform === 'string') { align = transform; transform = null; } if (typeof flags === 'string') { align = flags; flags = null; } if (typeof flags === 'number') { transform = flags; flags = null; } var todo; if (transform && typeof transform === "number") { transform *= (Math.PI / 180); var c = Math.cos(transform), s = Math.sin(transform); transform = new Matrix(c, s , -s, c, 0, 0); } else if (!transform) { transform = unitMatrix; } flags = flags || {}; if (!('noBOM' in flags)) flags.noBOM = true; if (!('autoencode' in flags)) flags.autoencode = true; var strokeOption = ''; var pageContext = this.internal.getCurrentPageInfo().pageContext; if (true === flags.stroke){ if (pageContext.lastTextWasStroke !== true){ strokeOption = '1 Tr\n'; pageContext.lastTextWasStroke = true; } } else{ if (pageContext.lastTextWasStroke){ strokeOption = '0 Tr\n'; } pageContext.lastTextWasStroke = false; } if (typeof this._runningPageHeight === 'undefined'){ this._runningPageHeight = 0; } if (typeof text === 'string') { text = ESC(text); } 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(), da = [], len = sa.length; // we do array.join('text that must not be PDFescaped") // thus, pdfEscape each component separately while (len--) { da.push(ESC(sa.shift())); } var linesLeft = Math.ceil((y - this._runningPageHeight) / (activeFontSize * lineHeightProportion)); if (0 <= linesLeft && linesLeft < da.length + 1) { //todo = da.splice(linesLeft-1); } if( align ) { var left, prevX, maxLineLength, leading = activeFontSize * lineHeightProportion, lineWidths = text.map( function( v ) { return this.getStringUnitWidth( v ) * activeFontSize; }, this ); 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. if( align === "center" ) { // The passed in x coordinate defines // the center point. left = x - maxLineLength / 2; x -= lineWidths[0] / 2; } else if ( align === "right" ) { // The passed in x coordinate defines the // rightmost point of the text. left = x - maxLineLength; x -= lineWidths[0]; } else { throw new Error('Unrecognized alignment option, use "center" or "right".'); } prevX = x; text = da[0] + ") Tj\n"; for ( i = 1, len = da.length ; i < len; i++ ) { var delta = maxLineLength - lineWidths[i]; if( align === "center" ) delta /= 2; // T* = x-offset leading Td ( text ) text += ( ( left - prevX ) + delta ) + " -" + leading + " Td (" + da[i]; prevX = left + delta; if( i < len - 1 ) { text += ") Tj\n"; } } } else { text = da.join(") Tj\nT* ("); } } else { throw new Error('Type of text must be string or Array. "' + text + '" is not recognized.'); } // Using "'" ("go next line and render text" mark) would save space but would complicate our rendering code, templates // BT .. ET does NOT have default settings for Tf. You must state that explicitely every time for BT .. ET // if you want text transformation matrix (+ multiline) to work reliably (which reads sizes of things from font declarations) // Thus, there is NO useful, *reliable* concept of "default" font for a page. // The fact that "default" (reuse font used before) font worked before in basic cases is an accident // - readers dealing smartly with brokenness of jsPDF's markup. var curY; if (todo){ //this.addPage(); //this._runningPageHeight += y - (activeFontSize * 1.7); //curY = f2(activeFontSize * 1.7); } else { curY = f2(y); } //curY = f2(((y - this._runningPageHeight)); // if (curY < 0){ // console.log('auto page break'); // this.addPage(); // this._runningPageHeight = y - (activeFontSize * 1.7); // curY = f2(activeFontSize * 1.7); // } var translate = new Matrix(1, 0, 0, -1, x, curY); transform = matrixMult(translate, transform); var position = transform.toString() + " Tm"; out( 'BT\n' + (activeFontSize * lineHeightProportion) + ' TL\n' + // line spacing strokeOption +// stroke option position + '\n(' + text + ') Tj\nET'); if (todo) { //this.text( todo, x, activeFontSize * 1.7); //this.text( todo, x, this._runningPageHeight + (activeFontSize * 1.7)); this.text( todo, x, y);// + (activeFontSize * 1.7)); } return this; }; API.lstext = function(text, x, y, spacing) { for (var i = 0, len = text.length ; i < len; i++, x += spacing) this.text(text[i], x, y); }; /** * Draw a line * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 * @function * @returns {jsPDF} * @methodOf jsPDF# * @name line */ API.line = function(x1, y1, x2, y2) { return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1], "D"); }; 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 }; /** * @typedef {Object} PatternData * {Matrix|undefined} matrix * {Number|undefined} xStep * {Number|undefined} yStep * {Array.|undefined} boundingBox */ /** * 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. * @param {String} patternKey The pattern key for the pattern that should be used to fill the path. * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that * will modify the pattern on use. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name lines */ API.lines = function(lines, x, y, scale, style, closed, patternKey, patternData) { 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') { var tmp = y; y = x; x = lines; lines = tmp; } scale = scale || [1, 1]; // starting point out(f3(x) + ' ' + f3(y) + ' 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) + ' ' + f3(y4) + ' 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) + ' ' + f3(y2) + ' ' + f3(x3) + ' ' + f3(y3) + ' ' + f3(x4) + ' ' + f3(y4) + ' c'); } } if (closed) { out('h'); } putStyle(style, patternKey, patternData); return this; }; /** * Similar to {@link API.lines} but all coordinates are interpreted as absolute coordinates instead of relative. * @param {Array} lines An array of {op: operator, c: coordinates} object, where op is one of "m" (move to), "l" (line to) * "c" (cubic bezier curve) and "h" (close (sub)path)). c is an array of coordinates. "m" and "l" expect two, "c" * six and "h" an empty array (or undefined). * @param {String} style The style * @param {String} patternKey The pattern key for the pattern that should be used to fill the path. * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that * will modify the pattern on use. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name path */ API.path = function (lines, style, patternKey, patternData) { for (var i = 0; i < lines.length; i++) { var leg = lines[i]; var coords = leg.c; switch (leg.op) { case "m": // move out(f3(coords[0]) + ' ' + f3(coords[1]) + ' m'); break; case "l": // simple line out(f3(coords[0]) + ' ' + f3(coords[1]) + ' l'); break; case "c": // bezier curve out([ f3(coords[0]), f3(coords[1]), f3(coords[2]), f3(coords[3]), f3(coords[4]), f3(coords[5]), "c" ].join(" ")); break; case "h": // close path out("h"); } } putStyle(style, patternKey, patternData); 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. * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive. * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that * will modify the pattern on use. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name rect */ API.rect = function(x, y, w, h, style, patternKey, patternData) { out([ f2(x), f2(y), f2(w), f2(-h), 're' ].join(' ')); putStyle(style, patternKey, patternData); 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. * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive. * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that * will modify the pattern on use. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name triangle */ API.triangle = function(x1, y1, x2, y2, x3, y3, style, patternKey, patternData) { 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, patternKey, patternData ); 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} ry 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. * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive. * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that * will modify the pattern on use. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name roundedRect */ API.roundedRect = function(x, y, w, h, rx, ry, style, patternKey, patternData) { var MyArc = 4 / 3 * (Math.SQRT2 - 1); rx = Math.min(rx, w * 0.5); ry = Math.min(ry, h * 0.5); 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, true, patternKey, patternData ); 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} ry 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. * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive. * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that * will modify the pattern on use. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name ellipse */ API.ellipse = function(x, y, rx, ry, style, patternKey, patternData) { var lx = 4 / 3 * (Math.SQRT2 - 1) * rx, ly = 4 / 3 * (Math.SQRT2 - 1) * ry; out([ f2(x + rx), f2(y), 'm', f2(x + rx), f2(y - ly), f2(x + lx), f2(y - ry), f2(x), f2(y - ry), 'c' ].join(' ')); out([ f2(x - lx), f2(y - ry), f2(x - rx), f2(y - ly), f2(x - rx), f2(y), 'c' ].join(' ')); out([ f2(x - rx), f2(y + ly), f2(x - lx), f2(y + ry), f2(x), f2(y + ry), 'c' ].join(' ')); out([ f2(x + lx), f2(y + ry), f2(x + rx), f2(y + ly), f2(x + rx), f2(y), 'c' ].join(' ')); putStyle(style, patternKey, patternData); 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. * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive. * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that * will modify the pattern on use. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name circle */ API.circle = function(x, y, r, style, patternKey, patternData) { return this.ellipse(x, y, r, r, style, patternKey, patternData); }; /** * Adds a properties to the PDF document * * @param {Object} properties 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; out("/" + activeFontKey + " " + activeFontSize + " Tf"); return this; }; API.getFontSize = function () { return activeFontSize; }; /** * 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 out("/" + activeFontKey + " " + activeFontSize + " Tf"); 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} postScriptName name of the Font. Example: "Menlo-Regular" * @param {String} fontName of font-family from @font-face definition. Example: "Menlo Regular" * @param {String} fontStyle style. Example: "normal" * @function * @returns the {fontKey} (same as the internal method) * @methodOf jsPDF# * @name addFont */ API.addFont = function(postScriptName, fontName, fontStyle) { addFont(postScriptName, fontName, fontStyle, 'StandardEncoding'); }; /** * 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.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 * @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 color; if (ch2 === undefined || (ch4 === undefined && ch1 === ch2 === ch3)) { // Gray color space. if (typeof ch1 === 'string') { color = ch1 + ' G'; } else { color = f2(ch1 / 255) + ' G'; } } else if (ch4 === undefined) { // RGB if (typeof ch1 === 'string') { color = [ch1, ch2, ch3, 'RG'].join(' '); } else { color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'RG'].join(' '); } } else { // CMYK if (typeof ch1 === 'string') { color = [ch1, ch2, ch3, ch4, 'K'].join(' '); } else { color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'K'].join(' '); } } out(color); 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 * @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 color; if (ch2 === undefined || (ch4 === undefined && ch1 === ch2 === ch3)) { // Gray color space. if (typeof ch1 === 'string') { color = ch1 + ' g'; } else { color = f2(ch1 / 255) + ' g'; } } else if (ch4 === undefined || typeof ch4 === 'object') { // RGB if (typeof ch1 === 'string') { color = [ch1, ch2, ch3, 'rg'].join(' '); } else { color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'rg'].join(' '); } if (ch4 && ch4.a === 0){ //TODO Implement transparency. //WORKAROUND use white for now color = ['255', '255', '255', 'rg'].join(' '); } } else { // CMYK if (typeof ch1 === 'string') { color = [ch1, ch2, ch3, ch4, 'k'].join(' '); } else { color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'k'].join(' '); } } out(color); return this; }; /** * Sets the text color for upcoming elements. * If only one, first argument is given, * treats the value as gray-scale color value. * * @param {Number} r Red channel color value in range 0-255 or {String} r color value in hexadecimal, example: '#FFFFFF' * @param {Number} g Green channel color value in range 0-255 * @param {Number} b Blue channel color value in range 0-255 * @function * @returns {jsPDF} * @methodOf jsPDF# * @name setTextColor */ API.setTextColor = function(r, g, b) { if ((typeof r === 'string') && /^#[0-9A-Fa-f]{6}$/.test(r)) { var hex = parseInt(r.substr(1), 16); r = (hex >> 16) & 255; g = (hex >> 8) & 255; b = (hex & 255); } if ((r === 0 && g === 0 && b === 0) || (typeof g === 'undefined')) { textColor = f3(r / 255) + ' g'; } else { textColor = [f3(r / 255), f3(g / 255), f3(b / 255), 'rg'].join(' '); } out(textColor); return this; }; /** * Sets a either previously added {@link GState} (via {@link addGState}) or a new {@link GState}. * @param {String|GState} gState If type is string, a previously added GState is used, if type is GState * it will be added before use. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name setGState */ API.setGState = function (gState) { if (typeof gState === "string") { gState = gStates[gStatesMap[gState]]; } else { gState = addGState(null, gState); } if (!gState.equals(activeGState)) { out("/" + gState.id + " gs"); activeGState = gState; } }; /** * 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; }; /** * Sets the miter limit. * @param {number} miterLimit * @function * @returns {jsPDF} * @methodOf jsPDF# * @name setMiterLimit */ API.setLineMiterLimit = function (miterLimit) { out(f2(miterLimit) + " M"); return this; }; /** * Sets the line dash pattern. * @param {Array} array An array containing 0-2 numbers. The first number sets the length of the * dashes, the second number the length of the gaps. If the second number is missing, the gaps are considered * to be as long as the dashes. An empty array means solid, unbroken lines. * @param phase The phase lines start with. * @function * @returns {jsPDF} * @methodOf jsPDF# * @name setLineDashPattern */ API.setLineDashPattern = function (array, phase) { out([ "[" + (array[0] !== undefined ? array[0] : ""), (array[1] !== undefined ? array[1] : "" ) + "]", phase, "d" ].join(" ")); 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 = "1.0.0-trunk"; if (typeof define === 'function' && define.amd) { define('jsPDF', function() { return jsPDF; }); } else if (typeof module !== 'undefined' && module.exports) { module.exports = jsPDF; } else { global.jsPDF = jsPDF; } return jsPDF; }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this));