jspdf.js

/** @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
  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,
  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);
  },
  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('/Parent 1 0 R');
  out('/Resources 2 0 R');
  out('/MediaBox [0 0 ' + f2(wPt) + ' ' + f2(hPt) + ']');
  out('/Contents ' + (objectNumber + 1) + ' 0 R');
  // Added for annotation plugin
  events.publish('putPage', {pageNumber:n,page:pages[n]});
  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('<' + p.length + ' /Filter [/FlateDecode]>>');
  } else {
  out('<' + p.length + '>>');
  }
  putStream(p);
  out('endobj');
  }
  offsets[1] = content_length;
  out('1 0 obj');
  out('<');
  var kids = '/Kids [';
  for (i = 0; i < page; i++) {
  kids += (3 + 2 * i) + ' 0 R ';
  }
  out(kids + ']');
  out('/Count ' + page);
  out('>>');
  out('endobj');
  },
  putFont = function(font) {
  font.objectNumber = newObject();
  out('<' + font.PostScriptName + '/Type/Font');
  if (typeof font.encoding === 'string') {
  out('/Encoding/' + font.encoding);
  }
  out('/Subtype/Type1>>');
  out('endobj');
  },
  putFonts = function() {
  for (var fontKey in fonts) {
  if (fonts.hasOwnProperty(fontKey)) {
  putFont(fonts[fontKey]);
  }
  }
  },
  putXobjectDict = function() {
  // Loop through images, or other data objects
  events.publish('putXobjectDict');
  },
  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('/XObject <<');
  putXobjectDict();
  out('>>');
  },
  putResources = function() {
  putFonts();
  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<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(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
  */
  addFont = function(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() {
   
  var HELVETICA = "helvetica",
  TIMES = "times",
  COURIER = "courier",
  NORMAL = "normal",
  BOLD = "bold",
  ITALIC = "italic",
  BOLD_ITALIC = "bolditalic",
  encoding = 'StandardEncoding',
  standardFonts = [
  ['Helvetica', HELVETICA, NORMAL],
  ['Helvetica-Bold', HELVETICA, BOLD],
  ['Helvetica-Oblique', HELVETICA, ITALIC],
  ['Helvetica-BoldOblique', HELVETICA, BOLD_ITALIC],
  ['Courier', COURIER, NORMAL],
  ['Courier-Bold', COURIER, BOLD],
  ['Courier-Oblique', COURIER, ITALIC],
  ['Courier-BoldOblique', COURIER, BOLD_ITALIC],
  ['Times-Roman', TIMES, NORMAL],
  ['Times-Bold', TIMES, BOLD],
  ['Times-Italic', TIMES, ITALIC],
  ['Times-BoldItalic', TIMES, BOLD_ITALIC]
  ];
   
  for (var i = 0, l = standardFonts.length; i < l; i++) {
  var fontKey = addFont(
  standardFonts[i][0],
  standardFonts[i][1],
  standardFonts[i][2],
  encoding);
   
  // 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(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(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;
  }
  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 * 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( 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 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(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':
  fontName = 'helvetica';
  break;
  case 'fixed':
  case 'monospace':
  case 'terminal':
  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 = '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() {
  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 fontName {String} (Optional) Font's family name
  * @param fontStyle {String} (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 * k);
  },
  'getVerticalCoordinateString' : function(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' : {
  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]};
  }
  };
   
  /**
  * 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) {
  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.setDisplayMode = function(zoom, layout, pmode) {
  zoomMode = zoom;
  layoutMode = layout;
  pageMode = pmode;
  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} 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.
  * @returns {jsPDF}
  * @methodOf jsPDF#
  * @name text
  */
  API.text = function(text, x, y, flags, angle, 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') {
  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 angle === 'string') {
  align = angle;
  angle = null;
  }
  if (typeof flags === 'string') {
  align = flags;
  flags = null;
  }
  if (typeof flags === 'number') {
  angle = flags;
  flags = null;
  }
  var xtra = '',mode = 'Td', todo;
  if (angle) {
  angle *= (Math.PI / 180);
  var c = Math.cos(angle),
  s = Math.sin(angle);
  xtra = [f2(c), f2(s), f2(s * -1), f2(c), ''].join(" ");
  mode = 'Tm';
  }
  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 (text instanceof 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((pageHeight - y - this._runningPageHeight) * k / (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 / k;
  }, 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 / k);
  //curY = f2(pageHeight - activeFontSize * 1.7 /k);
  }else{
  curY = f2((pageHeight - y) * k);
  }
  //curY = f2((pageHeight - (y - this._runningPageHeight)) * k);
   
  // if (curY < 0){
  // console.log('auto page break');
  // this.addPage();
  // this._runningPageHeight = y - (activeFontSize * 1.7 / k);
  // curY = f2(pageHeight - activeFontSize * 1.7 /k);
  // }
   
  out(
  'BT\n/' +
  activeFontKey + ' ' + activeFontSize + ' Tf\n' + // font face, style, size
  (activeFontSize * lineHeightProportion) + ' TL\n' + // line spacing
  strokeOption +// stroke option
  textColor +
  '\n' + xtra + f2(x * k) + ' ' + curY + ' ' + mode + '\n(' +
  text +
  ') Tj\nET');
   
  if (todo) {
  //this.text( todo, x, activeFontSize * 1.7 / k);
  //this.text( todo, x, this._runningPageHeight + (activeFontSize * 1.7 / k));
  this.text( todo, x, y);// + (activeFontSize * 1.7 / k));
  }
   
  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);
  };
   
  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
  };
   
  /**
  * 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) {
  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 * 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
  * @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(' ');
  }
  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");
 

你可能感兴趣的:(web)