1 /** @preserve jsPDF ( ${buildDate} ${commitID} ) 2 Copyright (c) 2010 James Hall, https://github.com/MrRio/jsPDF 3 Copyright (c) 2012 Willow Systems Corporation, willow-systems.com 4 MIT license. 5 */ 6 /** 7 * Permission is hereby granted, free of charge, to any person obtaining 8 * a copy of this software and associated documentation files (the 9 * "Software"), to deal in the Software without restriction, including 10 * without limitation the rights to use, copy, modify, merge, publish, 11 * distribute, sublicense, and/or sell copies of the Software, and to 12 * permit persons to whom the Software is furnished to do so, subject to 13 * the following conditions: 14 * 15 * The above copyright notice and this permission notice shall be 16 * included in all copies or substantial portions of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 * ==================================================================== 26 */ 27 28 var jsPDF = (function() { 29 'use strict' 30 31 // this will run on <=IE9, possibly some niche browsers 32 // new webkit-based, FireFox, IE10 already have native version of this. 33 if (typeof btoa === 'undefined') { 34 var btoa = function(data) { 35 // DO NOT ADD UTF8 ENCODING CODE HERE!!!! 36 37 // UTF8 encoding encodes bytes over char code 128 38 // and, essentially, turns an 8-bit binary streams 39 // (that base64 can deal with) into 7-bit binary streams. 40 // (by default server does not know that and does not recode the data back to 8bit) 41 // You destroy your data. 42 43 // binary streams like jpeg image data etc, while stored in JavaScript strings, 44 // (which are 16bit arrays) are in 8bit format already. 45 // You do NOT need to char-encode that before base64 encoding. 46 47 // if you, by act of fate 48 // have string which has individual characters with code 49 // above 255 (pure unicode chars), encode that BEFORE you base64 here. 50 // you can use absolutely any approch there, as long as in the end, 51 // base64 gets an 8bit (char codes 0 - 255) stream. 52 // when you get it on the server after un-base64, you must 53 // UNencode it too, to get back to 16, 32bit or whatever original bin stream. 54 55 // Note, Yes, JavaScript strings are, in most cases UCS-2 - 56 // 16-bit character arrays. This does not mean, however, 57 // that you always have to UTF8 it before base64. 58 // it means that if you have actual characters anywhere in 59 // that string that have char code above 255, you need to 60 // recode *entire* string from 16-bit (or 32bit) to 8-bit array. 61 // You can do binary split to UTF16 (BE or LE) 62 // you can do utf8, you can split the thing by hand and prepend BOM to it, 63 // but whatever you do, make sure you mirror the opposite on 64 // the server. If server does not expect to post-process un-base64 65 // 8-bit binary stream, think very very hard about messing around with encoding. 66 67 // so, long story short: 68 // DO NOT ADD UTF8 ENCODING CODE HERE!!!! 69 70 /** 71 ==================================================================== 72 base64 encoder 73 MIT, GPL 74 75 version: 1109.2015 76 discuss at: http://phpjs.org/functions/base64_encode 77 + original by: Tyler Akins (http://rumkin.com) 78 + improved by: Bayron Guevara 79 + improved by: Thunder.m 80 + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 81 + bugfixed by: Pellentesque Malesuada 82 + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 83 + improved by: Rafal Kukawski (http://kukawski.pl) 84 + Daniel Dotsenko, Willow Systems Corp, willow-systems.com 85 ==================================================================== 86 */ 87 88 var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" 89 , b64a = b64.split('') 90 , o1, o2, o3, h1, h2, h3, h4, bits, i = 0, 91 ac = 0, 92 enc = "", 93 tmp_arr = []; 94 95 do { // pack three octets into four hexets 96 o1 = data.charCodeAt(i++); 97 o2 = data.charCodeAt(i++); 98 o3 = data.charCodeAt(i++); 99 100 bits = o1 << 16 | o2 << 8 | o3; 101 102 h1 = bits >> 18 & 0x3f; 103 h2 = bits >> 12 & 0x3f; 104 h3 = bits >> 6 & 0x3f; 105 h4 = bits & 0x3f; 106 107 // use hexets to index into b64, and append result to encoded string 108 tmp_arr[ac++] = b64a[h1] + b64a[h2] + b64a[h3] + b64a[h4]; 109 } while (i < data.length); 110 111 enc = tmp_arr.join(''); 112 var r = data.length % 3; 113 return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3); 114 115 // end of base64 encoder MIT, GPL 116 } 117 } 118 119 var getObjectLength = typeof Object.keys === 'function' ? 120 function(object){ 121 return Object.keys(object).length 122 } : 123 function(object){ 124 var i = 0 125 for (var e in object){if(object.hasOwnProperty(e)){ i++ }} 126 return i 127 } 128 129 var PubSub = function(context){ 130 'use strict' 131 /* @preserve 132 ----------------------------------------------------------------------------------------------- 133 JavaScript PubSub library 134 2012 (c) ddotsenko@willowsystems.com 135 based on Peter Higgins (dante@dojotoolkit.org) 136 Loosely based on Dojo publish/subscribe API, limited in scope. Rewritten blindly. 137 Original is (c) Dojo Foundation 2004-2010. Released under either AFL or new BSD, see: 138 http://dojofoundation.org/license for more information. 139 ----------------------------------------------------------------------------------------------- 140 */ 141 this.topics = {} 142 this.context = context 143 /** 144 * Allows caller to emit an event and pass arguments to event listeners. 145 * @public 146 * @function 147 * @param topic {String} Name of the channel on which to voice this event 148 * @param **arguments Any number of arguments you want to pass to the listeners of this event. 149 */ 150 this.publish = function(topic, arg1, arg2, etc) { 151 'use strict' 152 if (this.topics[topic]) { 153 var currentTopic = this.topics[topic] 154 , args = Array.prototype.slice.call(arguments, 1) 155 , toremove = [] 156 , fn 157 , i, l 158 , pair 159 160 for (i = 0, l = currentTopic.length; i < l; i++) { 161 pair = currentTopic[i] // this is a [function, once_flag] array 162 fn = pair[0] 163 if (pair[1] /* 'run once' flag set */){ 164 pair[0] = function(){} 165 toremove.push(i) 166 } 167 fn.apply(this.context, args) 168 } 169 for (i = 0, l = toremove.length; i < l; i++) { 170 currentTopic.splice(toremove[i], 1) 171 } 172 } 173 } 174 /** 175 * Allows listener code to subscribe to channel and be called when data is available 176 * @public 177 * @function 178 * @param topic {String} Name of the channel on which to voice this event 179 * @param callback {Function} Executable (function pointer) that will be ran when event is voiced on this channel. 180 * @param once {Boolean} (optional. False by default) Flag indicating if the function is to be triggered only once. 181 * @returns {Object} A token object that cen be used for unsubscribing. 182 */ 183 this.subscribe = function(topic, callback, once) { 184 'use strict' 185 if (!this.topics[topic]) { 186 this.topics[topic] = [[callback, once]]; 187 } else { 188 this.topics[topic].push([callback,once]); 189 } 190 return { 191 "topic": topic, 192 "callback": callback 193 }; 194 }; 195 /** 196 * Allows listener code to unsubscribe from a channel 197 * @public 198 * @function 199 * @param token {Object} A token object that was returned by `subscribe` method 200 */ 201 this.unsubscribe = function(token) { 202 if (this.topics[token.topic]) { 203 var currentTopic = this.topics[token.topic] 204 205 for (var i = 0, l = currentTopic.length; i < l; i++) { 206 if (currentTopic[i][0] === token.callback) { 207 currentTopic.splice(i, 1) 208 } 209 } 210 } 211 } 212 } 213 214 215 /** 216 * Creates new jsPDF document object instance 217 * @constructor jsPDF 218 * @param orientation One of "portrait" or "landscape" (or shortcuts "p" (Default), "l") 219 * @param unit Measurement unit to be used when coordinates are specified. One of "pt" (points), "mm" (Default), "cm", "in" 220 * @param format One of 'a3', 'a4' (Default),'a5' ,'letter' ,'legal' 221 * @returns {jsPDF} 222 */ 223 function jsPDF(/** String */ orientation, /** String */ unit, /** String */ format){ 224 225 // Default parameter values 226 if (typeof orientation === 'undefined') orientation = 'p' 227 else orientation = orientation.toString().toLowerCase() 228 if (typeof unit === 'undefined') unit = 'mm' 229 if (typeof format === 'undefined') format = 'a4' 230 231 var format_as_string = format.toString().toLowerCase() 232 , version = '20120619' 233 , content = [] 234 , content_length = 0 235 236 , pdfVersion = '1.3' // PDF Version 237 , pageFormats = { // Size in pt of various paper formats 238 'a3': [841.89, 1190.55] 239 , 'a4': [595.28, 841.89] 240 , 'a5': [420.94, 595.28] 241 , 'letter': [612, 792] 242 , 'legal': [612, 1008] 243 } 244 , textColor = '0 g' 245 , drawColor = '0 G' 246 , page = 0 247 , pages = [] 248 , objectNumber = 2 // 'n' Current object number 249 , outToPages = false // switches where out() prints. outToPages true = push to pages obj. outToPages false = doc builder content 250 , offsets = [] // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes. 251 , fonts = {} // collection of font objects, where key is fontKey - a dynamically created label for a given font. 252 , fontmap = {} // mapping structure fontName > fontStyle > font key - performance layer. See addFont() 253 , activeFontSize = 16 254 , activeFontKey // will be string representing the KEY of the font as combination of fontName + fontStyle 255 , lineWidth = 0.200025 // 2mm 256 , pageHeight 257 , pageWidth 258 , k // Scale factor 259 , documentProperties = {'title':'','subject':'','author':'','keywords':'','creator':''} 260 , lineCapID = 0 261 , lineJoinID = 0 262 , API = {} 263 , events = new PubSub(API) 264 265 if (unit == 'pt') { 266 k = 1 267 } else if(unit == 'mm') { 268 k = 72/25.4 269 } else if(unit == 'cm') { 270 k = 72/2.54 271 } else if(unit == 'in') { 272 k = 72 273 } else { 274 throw('Invalid unit: ' + unit) 275 } 276 277 // Dimensions are stored as user units and converted to points on output 278 if (format_as_string in pageFormats) { 279 pageHeight = pageFormats[format_as_string][1] / k 280 pageWidth = pageFormats[format_as_string][0] / k 281 } else { 282 try { 283 pageHeight = format[1] 284 pageWidth = format[0] 285 } 286 catch(err) { 287 throw('Invalid format: ' + format) 288 } 289 } 290 291 if (orientation === 'p' || orientation === 'portrait') { 292 orientation = 'p' 293 } else if (orientation === 'l' || orientation === 'landscape') { 294 orientation = 'l' 295 var tmp = pageWidth 296 pageWidth = pageHeight 297 pageHeight = tmp 298 } else { 299 throw('Invalid orientation: ' + orientation) 300 } 301 302 ///////////////////// 303 // Private functions 304 ///////////////////// 305 // simplified (speedier) replacement for sprintf's %.2f conversion 306 var f2 = function(number){ 307 return number.toFixed(2) 308 } 309 // simplified (speedier) replacement for sprintf's %.3f conversion 310 , f3 = function(number){ 311 return number.toFixed(3) 312 } 313 // simplified (speedier) replacement for sprintf's %02d 314 , padd2 = function(number) { 315 var n = (number).toFixed(0) 316 if ( number < 10 ) return '0' + n 317 else return n 318 } 319 // simplified (speedier) replacement for sprintf's %02d 320 , padd10 = function(number) { 321 var n = (number).toFixed(0) 322 if (n.length < 10) return new Array( 11 - n.length ).join( '0' ) + n 323 else return n 324 } 325 , out = function(string) { 326 if(outToPages /* set by beginPage */) { 327 pages[page].push(string) 328 } else { 329 content.push(string) 330 content_length += string.length + 1 // +1 is for '\n' that will be used to join contents of content 331 } 332 } 333 , newObject = function() { 334 // Begin a new object 335 objectNumber ++ 336 offsets[objectNumber] = content_length 337 out(objectNumber + ' 0 obj'); 338 return objectNumber 339 } 340 , putPages = function() { 341 var wPt = pageWidth * k 342 var hPt = pageHeight * k 343 344 // outToPages = false as set in endDocument(). out() writes to content. 345 346 var n, p 347 for(n=1; n <= page; n++) { 348 newObject() 349 out('<</Type /Page') 350 out('/Parent 1 0 R'); 351 out('/Resources 2 0 R') 352 out('/Contents ' + (objectNumber + 1) + ' 0 R>>') 353 out('endobj') 354 355 // Page content 356 p = pages[n].join('\n') 357 newObject() 358 out('<</Length ' + p.length + '>>') 359 putStream(p) 360 out('endobj') 361 } 362 offsets[1] = content_length 363 out('1 0 obj') 364 out('<</Type /Pages') 365 var kids = '/Kids [' 366 for (var i = 0; i < page; i++) { 367 kids += (3 + 2 * i) + ' 0 R ' 368 } 369 out(kids + ']') 370 out('/Count ' + page) 371 out('/MediaBox [0 0 '+f2(wPt)+' '+f2(hPt)+']') 372 out('>>') 373 out('endobj'); 374 } 375 , putStream = function(str) { 376 out('stream') 377 out(str) 378 out('endstream') 379 } 380 , putResources = function() { 381 putFonts() 382 events.publish('putResources') 383 // Resource dictionary 384 offsets[2] = content_length 385 out('2 0 obj') 386 out('<<') 387 putResourceDictionary() 388 out('>>') 389 out('endobj') 390 } 391 , putFonts = function() { 392 for (var fontKey in fonts) { 393 if (fonts.hasOwnProperty(fontKey)) { 394 putFont(fonts[fontKey]) 395 } 396 } 397 } 398 , putFont = function(font) { 399 font.objectNumber = newObject() 400 out('<</BaseFont/' + font.PostScriptName + '/Type/Font') 401 if (typeof font.encoding === 'string') { 402 out('/Encoding/'+font.encoding) 403 } 404 out('/Subtype/Type1>>') 405 out('endobj') 406 } 407 , addToFontDictionary = function(fontKey, fontName, fontStyle) { 408 // this is mapping structure for quick font key lookup. 409 // returns the KEY of the font (ex: "F1") for a given pair of font name and type (ex: "Arial". "Italic") 410 var undef 411 if (fontmap[fontName] === undef){ 412 fontmap[fontName] = {} // fontStyle is a var interpreted and converted to appropriate string. don't wrap in quotes. 413 } 414 fontmap[fontName][fontStyle] = fontKey 415 } 416 /** 417 FontObject describes a particular font as member of an instnace of jsPDF 418 419 It's a collection of properties like 'id' (to be used in PDF stream), 420 'fontName' (font's family name), 'fontStyle' (font's style variant label) 421 422 @public 423 @memberOf jsPDF 424 @name FontObject 425 @property id {String} PDF-document-instance-specific label assinged to the font. 426 @property PostScriptName {String} 427 @property 428 */ 429 , FontObject = {} 430 , addFont = function(PostScriptName, fontName, fontStyle, encoding) { 431 var fontKey = 'F' + (getObjectLength(fonts) + 1).toString(10) 432 433 // This is FontObject 434 var font = fonts[fontKey] = { 435 'id': fontKey 436 // , 'objectNumber': will be set by putFont() 437 , 'PostScriptName': PostScriptName 438 , 'fontName': fontName 439 , 'fontStyle': fontStyle 440 , 'encoding': encoding 441 , 'metadata': {} 442 } 443 444 addToFontDictionary(fontKey, fontName, fontStyle) 445 446 events.publish('addFont', font) 447 448 return fontKey 449 } 450 , addFonts = function() { 451 452 var HELVETICA = "helvetica" 453 , TIMES = "times" 454 , COURIER = "courier" 455 , NORMAL = "normal" 456 , BOLD = "bold" 457 , ITALIC = "italic" 458 , BOLD_ITALIC = "bolditalic" 459 , encoding = 'StandardEncoding' 460 , standardFonts = [ 461 ['Helvetica', HELVETICA, NORMAL] 462 , ['Helvetica-Bold', HELVETICA, BOLD] 463 , ['Helvetica-Oblique', HELVETICA, ITALIC] 464 , ['Helvetica-BoldOblique', HELVETICA, BOLD_ITALIC] 465 , ['Courier', COURIER, NORMAL] 466 , ['Courier-Bold', COURIER, BOLD] 467 , ['Courier-Oblique', COURIER, ITALIC] 468 , ['Courier-BoldOblique', COURIER, BOLD_ITALIC] 469 , ['Times-Roman', TIMES, NORMAL] 470 , ['Times-Bold', TIMES, BOLD] 471 , ['Times-Italic', TIMES, ITALIC] 472 , ['Times-BoldItalic', TIMES, BOLD_ITALIC] 473 ] 474 475 var i, l, fontKey, parts 476 for (i = 0, l = standardFonts.length; i < l; i++) { 477 fontKey = addFont( 478 standardFonts[i][0] 479 , standardFonts[i][1] 480 , standardFonts[i][2] 481 , encoding 482 ) 483 484 // adding aliases for standard fonts, this time matching the capitalization 485 parts = standardFonts[i][0].split('-') 486 addToFontDictionary(fontKey, parts[0], parts[1] || '') 487 } 488 489 events.publish('addFonts', {'fonts':fonts, 'dictionary':fontmap}) 490 } 491 , putResourceDictionary = function() { 492 out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]') 493 out('/Font <<') 494 // Do this for each font, the '1' bit is the index of the font 495 for (var fontKey in fonts) { 496 if (fonts.hasOwnProperty(fontKey)) { 497 out('/' + fontKey + ' ' + fonts[fontKey].objectNumber + ' 0 R') 498 } 499 } 500 out('>>') 501 out('/XObject <<') 502 putXobjectDict() 503 out('>>') 504 } 505 , putXobjectDict = function() { 506 // Loop through images, or other data objects 507 events.publish('putXobjectDict') 508 } 509 , putInfo = function() { 510 out('/Producer (jsPDF ' + version + ')') 511 if(documentProperties.title) { 512 out('/Title (' + pdfEscape(documentProperties.title) + ')') 513 } 514 if(documentProperties.subject) { 515 out('/Subject (' + pdfEscape(documentProperties.subject) + ')') 516 } 517 if(documentProperties.author) { 518 out('/Author (' + pdfEscape(documentProperties.author) + ')') 519 } 520 if(documentProperties.keywords) { 521 out('/Keywords (' + pdfEscape(documentProperties.keywords) + ')') 522 } 523 if(documentProperties.creator) { 524 out('/Creator (' + pdfEscape(documentProperties.creator) + ')') 525 } 526 var created = new Date() 527 out('/CreationDate (D:' + 528 [ 529 created.getFullYear() 530 , padd2(created.getMonth() + 1) 531 , padd2(created.getDate()) 532 , padd2(created.getHours()) 533 , padd2(created.getMinutes()) 534 , padd2(created.getSeconds()) 535 ].join('')+ 536 ')' 537 ) 538 } 539 , putCatalog = function () { 540 out('/Type /Catalog') 541 out('/Pages 1 0 R') 542 // @TODO: Add zoom and layout modes 543 out('/OpenAction [3 0 R /FitH null]') 544 out('/PageLayout /OneColumn') 545 } 546 , putTrailer = function () { 547 out('/Size ' + (objectNumber + 1)) 548 out('/Root ' + objectNumber + ' 0 R') 549 out('/Info ' + (objectNumber - 1) + ' 0 R') 550 } 551 , beginPage = function() { 552 page ++ 553 // Do dimension stuff 554 outToPages = true 555 pages[page] = [] 556 } 557 , _addPage = function() { 558 beginPage() 559 // Set line width 560 out(f2(lineWidth * k) + ' w') 561 // Set draw color 562 out(drawColor) 563 // resurrecting non-default line caps, joins 564 if (lineCapID !== 0) out(lineCapID.toString(10)+' J') 565 if (lineJoinID !== 0) out(lineJoinID.toString(10)+' j') 566 567 events.publish('addPage', {'pageNumber':page}) 568 } 569 /** 570 Returns a document-specific font key - a label assigned to a 571 font name + font type combination at the time the font was added 572 to the font inventory. 573 574 Font key is used as label for the desired font for a block of text 575 to be added to the PDF document stream. 576 @public 577 @function 578 @param fontName {String} can be undefined on "falthy" to indicate "use current" 579 @param fontStyle {String} can be undefined on "falthy" to indicate "use current" 580 @returns {String} Font key. 581 */ 582 , getFont = function(fontName, fontStyle) { 583 var key, undef 584 585 if (fontName === undef) { 586 fontName = fonts[activeFontKey]['fontName'] 587 } 588 if (fontStyle === undef) { 589 fontStyle = fonts[activeFontKey]['fontStyle'] 590 } 591 592 try { 593 key = fontmap[fontName][fontStyle] // returns a string like 'F3' - the KEY corresponding tot he font + type combination. 594 } catch (e) { 595 key = undef 596 } 597 if (!key){ 598 throw new Error("Unable to look up font label for font '"+fontName+"', '"+fontStyle+"'. Refer to getFontList() for available fonts.") 599 } 600 601 return key 602 } 603 , buildDocument = function() { 604 605 outToPages = false // switches out() to content 606 content = [] 607 offsets = [] 608 609 // putHeader() 610 out('%PDF-' + pdfVersion) 611 612 putPages() 613 614 putResources() 615 616 // Info 617 newObject() 618 out('<<') 619 putInfo() 620 out('>>') 621 out('endobj') 622 623 // Catalog 624 newObject() 625 out('<<') 626 putCatalog() 627 out('>>') 628 out('endobj') 629 630 // Cross-ref 631 var o = content_length 632 out('xref') 633 out('0 ' + (objectNumber + 1)) 634 out('0000000000 65535 f ') 635 for (var i=1; i <= objectNumber; i++) { 636 out(padd10(offsets[i]) + ' 00000 n ') 637 } 638 // Trailer 639 out('trailer') 640 out('<<') 641 putTrailer() 642 out('>>') 643 out('startxref') 644 out(o) 645 out('%%EOF') 646 647 outToPages = true 648 649 return content.join('\n') 650 } 651 /** 652 653 @public 654 @function 655 @param text {String} 656 @param flags {Object} Encoding flags. 657 @returns {String} Encoded string 658 */ 659 , to8bitStream = function(text, flags){ 660 /* PDF 1.3 spec: 661 "For text strings encoded in Unicode, the first two bytes must be 254 followed by 662 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts 663 with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely 664 to be a meaningful beginning of a word or phrase.) The remainder of the 665 string consists of Unicode character codes, according to the UTF-16 encoding 666 specified in the Unicode standard, version 2.0. Commonly used Unicode values 667 are represented as 2 bytes per character, with the high-order byte appearing first 668 in the string." 669 670 In other words, if there are chars in a string with char code above 255, we 671 recode the string to UCS2 BE - string doubles in length and BOM is prepended. 672 673 HOWEVER! 674 Actual *content* (body) text (as opposed to strings used in document properties etc) 675 does NOT expect BOM. There, it is treated as a literal GID (Glyph ID) 676 677 Because of Adobe's focus on "you subset your fonts!" you are not supposed to have 678 a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could 679 fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode 680 code page. There, however, all characters in the stream are treated as GIDs, 681 including BOM, which is the reason we need to skip BOM in content text (i.e. that 682 that is tied to a font). 683 684 To signal this "special" PDFEscape / to8bitStream handling mode, 685 API.text() function sets (unless you overwrite it with manual values 686 given to API.text(.., flags) ) 687 flags.autoencode = true 688 flags.noBOM = true 689 690 */ 691 692 /* 693 `flags` properties relied upon: 694 .sourceEncoding = string with encoding label. 695 "Unicode" by default. = encoding of the incoming text. 696 pass some non-existing encoding name 697 (ex: 'Do not touch my strings! I know what I am doing.') 698 to make encoding code skip the encoding step. 699 .outputEncoding = Either valid PDF encoding name 700 (must be supported by jsPDF font metrics, otherwise no encoding) 701 or a JS object, where key = sourceCharCode, value = outputCharCode 702 missing keys will be treated as: sourceCharCode === outputCharCode 703 .noBOM 704 See comment higher above for explanation for why this is important 705 .autoencode 706 See comment higher above for explanation for why this is important 707 */ 708 709 var i, l, undef 710 711 if (flags === undef) { 712 flags = {} 713 } 714 715 var sourceEncoding = flags.sourceEncoding ? sourceEncoding : 'Unicode' 716 , encodingBlock 717 , outputEncoding = flags.outputEncoding 718 , newtext 719 , isUnicode, ch, bch 720 // This 'encoding' section relies on font metrics format 721 // attached to font objects by, among others, 722 // "Willow Systems' standard_font_metrics plugin" 723 // see jspdf.plugin.standard_font_metrics.js for format 724 // of the font.metadata.encoding Object. 725 // It should be something like 726 // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}} 727 // .widths = {0:width, code:width, ..., 'fof':divisor} 728 // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...} 729 if ((flags.autoencode || outputEncoding ) && 730 fonts[activeFontKey].metadata && 731 fonts[activeFontKey].metadata[sourceEncoding] && 732 fonts[activeFontKey].metadata[sourceEncoding].encoding 733 ) { 734 encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding 735 736 // each font has default encoding. Some have it clearly defined. 737 if (!outputEncoding && fonts[activeFontKey].encoding) { 738 outputEncoding = fonts[activeFontKey].encoding 739 } 740 741 // Hmmm, the above did not work? Let's try again, in different place. 742 if (!outputEncoding && encodingBlock.codePages) { 743 outputEncoding = encodingBlock.codePages[0] // let's say, first one is the default 744 } 745 746 if (typeof outputEncoding === 'string') { 747 outputEncoding = encodingBlock[outputEncoding] 748 } 749 // we want output encoding to be a JS Object, where 750 // key = sourceEncoding's character code and 751 // value = outputEncoding's character code. 752 if (outputEncoding) { 753 isUnicode = false 754 newtext = [] 755 for (i = 0, l = text.length; i < l; i++) { 756 ch = outputEncoding[text.charCodeAt(i)] 757 if (ch) { 758 newtext.push( 759 String.fromCharCode(ch) 760 ) 761 } else { 762 newtext.push( 763 text[i] 764 ) 765 } 766 767 // since we are looping over chars anyway, might as well 768 // check for residual unicodeness 769 if (newtext[i].charCodeAt(0) >> 8 /* more than 255 */ ) { 770 isUnicode = true 771 } 772 } 773 text = newtext.join('') 774 } 775 } 776 777 i = text.length 778 // isUnicode may be set to false above. Hence the triple-equal to undefined 779 while (isUnicode === undef && i !== 0){ 780 if ( text.charCodeAt(i - 1) >> 8 /* more than 255 */ ) { 781 isUnicode = true 782 } 783 ;i--; 784 } 785 if (!isUnicode) { 786 return text 787 } else { 788 newtext = flags.noBOM ? [] : [254, 255] 789 for (i = 0, l = text.length; i < l; i++) { 790 ch = text.charCodeAt(i) 791 bch = ch >> 8 // divide by 256 792 if (bch >> 8 /* something left after dividing by 256 second time */ ) { 793 throw new Error("Character at position "+i.toString(10)+" of string '"+text+"' exceeds 16bits. Cannot be encoded into UCS-2 BE") 794 } 795 newtext.push(bch) 796 newtext.push(ch - ( bch << 8)) 797 } 798 return String.fromCharCode.apply(undef, newtext) 799 } 800 } 801 // Replace '/', '(', and ')' with pdf-safe versions 802 , pdfEscape = function(text, flags) { 803 // doing to8bitStream does NOT make this PDF display unicode text. For that 804 // we also need to reference a unicode font and embed it - royal pain in the rear. 805 806 // There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars, 807 // which JavaScript Strings are happy to provide. So, while we still cannot display 808 // 2-byte characters property, at least CONDITIONALLY converting (entire string containing) 809 // 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF 810 // is still parseable. 811 // This will allow immediate support for unicode in document properties strings. 812 return to8bitStream(text, flags).replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)') 813 } 814 , getStyle = function(style){ 815 // see Path-Painting Operators of PDF spec 816 var op = 'S'; // stroke 817 if (style === 'F') { 818 op = 'f'; // fill 819 } else if (style === 'FD' || style === 'DF') { 820 op = 'B'; // both 821 } 822 return op; 823 } 824 825 826 //--------------------------------------- 827 // Public API 828 829 /** 830 Object exposing internal API to plugins 831 @public 832 */ 833 API.internal = { 834 'pdfEscape': pdfEscape 835 , 'getStyle': getStyle 836 /** 837 Returns {FontObject} describing a particular font. 838 @public 839 @function 840 @param fontName {String} (Optional) Font's family name 841 @param fontStyle {String} (Optional) Font's style variation name (Example:"Italic") 842 @returns {FontObject} 843 */ 844 , 'getFont': function(){ return fonts[getFont.apply(API, arguments)] } 845 , 'getFontSize': function() { return activeFontSize } 846 , 'btoa': btoa 847 , 'write': function(string1, string2, string3, etc){ 848 out( 849 arguments.length === 1? 850 arguments[0] : 851 Array.prototype.join.call(arguments, ' ') 852 ) 853 } 854 , 'getCoordinateString': function(value){ 855 return f2(value * k) 856 } 857 , 'getVerticalCoordinateString': function(value){ 858 return f2((pageHeight - value) * k) 859 } 860 , 'collections': {} 861 , 'newObject': newObject 862 , 'putStream': putStream 863 , 'events': events 864 // ratio that you use in multiplication of a given "size" number to arrive to 'point' 865 // units of measurement. 866 // scaleFactor is set at initialization of the document and calculated against the stated 867 // default measurement units for the document. 868 // If default is "mm", k is the number that will turn number in 'mm' into 'points' number. 869 // through multiplication. 870 , 'scaleFactor': k 871 , 'pageSize': {'width':pageWidth, 'height':pageHeight} 872 } 873 874 /** 875 Adds (and transfers the focus to) new page to the PDF document. 876 @function 877 @returns {jsPDF} 878 @name jsPDF.addPage 879 */ 880 API.addPage = function() { 881 _addPage() 882 return this 883 } 884 885 /** 886 Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings. 887 @function 888 @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. 889 @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page 890 @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page 891 @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. 892 @returns {jsPDF} 893 @name jsPDF.text 894 */ 895 API.text = function(text, x, y, flags) { 896 /** 897 * Inserts something like this into PDF 898 BT 899 /F1 16 Tf % Font name + size 900 16 TL % How many units down for next line in multiline text 901 0 g % color 902 28.35 813.54 Td % position 903 (line one) Tj 904 T* (line two) Tj 905 T* (line three) Tj 906 ET 907 */ 908 909 var undef 910 // , args = Array.prototype.slice.apply(arguments) 911 912 // Pre-August-2012 the order of arguments was function(x, y, text, flags) 913 // in effort to make all calls have similar signature like 914 // function(data, coordinates... , miscellaneous) 915 // this method had its args flipped. 916 // code below allows backward compatibility with old arg order. 917 if (typeof text === 'number') { 918 // yep, old order 919 text = arguments[2] 920 x = arguments[0] 921 y = arguments[1] 922 } 923 924 // If there are any newlines in text, we assume 925 // the user wanted to print multiple lines, so break the 926 // text up into an array. If the text is already an array, 927 // we assume the user knows what they are doing. 928 if (typeof text === 'string' && text.match(/[\n\r]/)) { 929 text = text.split(/\r\n|\r|\n/g) 930 } 931 932 if (typeof flags === 'undefined') { 933 flags = {'noBOM':true,'autoencode':true} 934 } else { 935 936 if (flags.noBOM === undef) { 937 flags.noBOM = true 938 } 939 940 if (flags.autoencode === undef) { 941 flags.autoencode = true 942 } 943 944 } 945 946 var newtext, str 947 948 if (typeof text === 'string') { 949 str = pdfEscape(text, flags) 950 } else if (text instanceof Array) /* Array */{ 951 // we don't want to destroy original text array, so cloning it 952 newtext = text.concat() 953 // we do array.join('text that must not be PDFescaped") 954 // thus, pdfEscape each component separately 955 for ( var i = newtext.length - 1; i !== -1 ; i--) { 956 newtext[i] = pdfEscape( newtext[i], flags) 957 } 958 str = newtext.join( ") Tj\nT* (" ) 959 } else { 960 throw new Error('Type of text must be string or Array. "'+text+'" is not recognized.') 961 } 962 // Using "'" ("go next line and render text" mark) would save space but would complicate our rendering code, templates 963 964 // BT .. ET does NOT have default settings for Tf. You must state that explicitely every time for BT .. ET 965 // if you want text transformation matrix (+ multiline) to work reliably (which reads sizes of things from font declarations) 966 // Thus, there is NO useful, *reliable* concept of "default" font for a page. 967 // The fact that "default" (reuse font used before) font worked before in basic cases is an accident 968 // - readers dealing smartly with brokenness of jsPDF's markup. 969 out( 970 'BT\n/' + 971 activeFontKey + ' ' + activeFontSize + ' Tf\n' + // font face, style, size 972 activeFontSize + ' TL\n' + // line spacing 973 textColor + 974 '\n' + f2(x * k) + ' ' + f2((pageHeight - y) * k) + ' Td\n(' + 975 str + 976 ') Tj\nET' 977 ) 978 return this 979 } 980 981 API.line = function(x1, y1, x2, y2) { 982 out( 983 f2(x1 * k) + ' ' + f2((pageHeight - y1) * k) + ' m ' + 984 f2(x2 * k) + ' ' + f2((pageHeight - y2) * k) + ' l S' 985 ) 986 return this 987 } 988 989 /** 990 Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates. 991 All data points in `lines` are relative to last line origin. 992 `x`, `y` become x1,y1 for first line / curve in the set. 993 For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point. 994 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. 995 996 @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, 10) // line, line, bezier curve, line 997 @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves). 998 @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page 999 @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page 1000 @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. 1001 @function 1002 @returns {jsPDF} 1003 @name jsPDF.text 1004 */ 1005 API.lines = function(lines, x, y, scale, style) { 1006 var undef 1007 1008 // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style) 1009 // in effort to make all calls have similar signature like 1010 // function(content, coordinateX, coordinateY , miscellaneous) 1011 // this method had its args flipped. 1012 // code below allows backward compatibility with old arg order. 1013 if (typeof lines === 'number') { 1014 // yep, old order 1015 lines = arguments[2] 1016 x = arguments[0] 1017 y = arguments[1] 1018 } 1019 1020 style = getStyle(style) 1021 scale = scale === undef ? [1,1] : scale 1022 1023 // starting point 1024 out(f3(x * k) + ' ' + f3((pageHeight - y) * k) + ' m ') 1025 1026 var scalex = scale[0] 1027 , scaley = scale[1] 1028 , i = 0 1029 , l = lines.length 1030 , leg 1031 , x2, y2 // bezier only. In page default measurement "units", *after* scaling 1032 , x3, y3 // bezier only. In page default measurement "units", *after* scaling 1033 // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling 1034 , x4 = x // last / ending point = starting point for first item. 1035 , y4 = y // last / ending point = starting point for first item. 1036 1037 for (; i < l; i++) { 1038 leg = lines[i] 1039 if (leg.length === 2){ 1040 // simple line 1041 x4 = leg[0] * scalex + x4 // here last x4 was prior ending point 1042 y4 = leg[1] * scaley + y4 // here last y4 was prior ending point 1043 out(f3(x4 * k) + ' ' + f3((pageHeight - y4) * k) + ' l') 1044 } else { 1045 // bezier curve 1046 x2 = leg[0] * scalex + x4 // here last x4 is prior ending point 1047 y2 = leg[1] * scaley + y4 // here last y4 is prior ending point 1048 x3 = leg[2] * scalex + x4 // here last x4 is prior ending point 1049 y3 = leg[3] * scaley + y4 // here last y4 is prior ending point 1050 x4 = leg[4] * scalex + x4 // here last x4 was prior ending point 1051 y4 = leg[5] * scaley + y4 // here last y4 was prior ending point 1052 out( 1053 f3(x2 * k) + ' ' + 1054 f3((pageHeight - y2) * k) + ' ' + 1055 f3(x3 * k) + ' ' + 1056 f3((pageHeight - y3) * k) + ' ' + 1057 f3(x4 * k) + ' ' + 1058 f3((pageHeight - y4) * k) + ' c' 1059 ) 1060 } 1061 } 1062 // stroking / filling / both the path 1063 out(style) 1064 return this 1065 } 1066 1067 /** 1068 Adds a rectangle to PDF 1069 1070 @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page 1071 @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page 1072 @param {Number} w Width (in units declared at inception of PDF document) 1073 @param {Number} h Height (in units declared at inception of PDF document) 1074 @param {String} style (Defaults to active fill/stroke style) A string signalling if stroke, fill or both are to be applied. 1075 @function 1076 @returns {jsPDF} 1077 @name jsPDF.rect 1078 */ 1079 API.rect = function(x, y, w, h, style) { 1080 var op = getStyle(style) 1081 out([ 1082 f2(x * k) 1083 , f2((pageHeight - y) * k) 1084 , f2(w * k) 1085 , f2(-h * k) 1086 , 're' 1087 , op 1088 ].join(' ')) 1089 return this 1090 } 1091 1092 /** 1093 Adds a triangle to PDF 1094 1095 @param {Number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page 1096 @param {Number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page 1097 @param {Number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page 1098 @param {Number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page 1099 @param {Number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page 1100 @param {Number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page 1101 @param {String} style (Defaults to active fill/stroke style) A string signalling if stroke, fill or both are to be applied. 1102 @function 1103 @returns {jsPDF} 1104 @name jsPDF.triangle 1105 */ 1106 API.triangle = function(x1, y1, x2, y2, x3, y3, style) { 1107 this.lines( 1108 x1, x2 // start of path 1109 , [ 1110 [ x2 - x1 , y2 - y1 ] // vector to point 2 1111 , [ x3 - x2 , y3 - y2 ] // vector to point 3 1112 , [ x1 - x3 , y1 - y3 ] // closing vector back to point 1 1113 ] 1114 , [1,1] 1115 , style 1116 ) 1117 return this; 1118 } 1119 1120 /** 1121 Adds an ellipse to PDF 1122 1123 @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page 1124 @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page 1125 @param {Number} rx Radius along x axis (in units declared at inception of PDF document) 1126 @param {Number} rx Radius along y axis (in units declared at inception of PDF document) 1127 @param {String} style (Defaults to active fill/stroke style) A string signalling if stroke, fill or both are to be applied. 1128 @function 1129 @returns {jsPDF} 1130 @name jsPDF.ellipse 1131 */ 1132 API.ellipse = function(x, y, rx, ry, style) { 1133 var op = getStyle(style) 1134 , lx = 4/3*(Math.SQRT2-1)*rx 1135 , ly = 4/3*(Math.SQRT2-1)*ry 1136 1137 out([ 1138 f2((x+rx)*k) 1139 , f2((pageHeight-y)*k) 1140 , 'm' 1141 , f2((x+rx)*k) 1142 , f2((pageHeight-(y-ly))*k) 1143 , f2((x+lx)*k) 1144 , f2((pageHeight-(y-ry))*k) 1145 , f2(x*k) 1146 , f2((pageHeight-(y-ry))*k) 1147 , 'c' 1148 ].join(' ')) 1149 out([ 1150 f2((x-lx)*k) 1151 , f2((pageHeight-(y-ry))*k) 1152 , f2((x-rx)*k) 1153 , f2((pageHeight-(y-ly))*k) 1154 , f2((x-rx)*k) 1155 , f2((pageHeight-y)*k) 1156 , 'c' 1157 ].join(' ')) 1158 out([ 1159 f2((x-rx)*k) 1160 , f2((pageHeight-(y+ly))*k) 1161 , f2((x-lx)*k) 1162 , f2((pageHeight-(y+ry))*k) 1163 , f2(x*k) 1164 , f2((pageHeight-(y+ry))*k) 1165 , 'c' 1166 ].join(' ')) 1167 out([ 1168 f2((x+lx)*k) 1169 , f2((pageHeight-(y+ry))*k) 1170 , f2((x+rx)*k) 1171 , f2((pageHeight-(y+ly))*k) 1172 , f2((x+rx)*k) 1173 , f2((pageHeight-y)*k) 1174 ,'c' 1175 , op 1176 ].join(' ')) 1177 return this 1178 } 1179 1180 /** 1181 Adds an circle to PDF 1182 1183 @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page 1184 @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page 1185 @param {Number} r Radius (in units declared at inception of PDF document) 1186 @param {String} style (Defaults to active fill/stroke style) A string signalling if stroke, fill or both are to be applied. 1187 @function 1188 @returns {jsPDF} 1189 @name jsPDF.circle 1190 */ 1191 API.circle = function(x, y, r, style) { 1192 return this.ellipse(x, y, r, r, style) 1193 } 1194 1195 /** 1196 Adds a properties to the PDF document 1197 1198 @param {Object} A property_name-to-property_value object structure. 1199 @function 1200 @returns {jsPDF} 1201 @name jsPDF.setProperties 1202 */ 1203 API.setProperties = function(properties) { 1204 // copying only those properties we can render. 1205 for (var property in documentProperties){ 1206 if (documentProperties.hasOwnProperty(property) && properties[property]) { 1207 documentProperties[property] = properties[property] 1208 } 1209 } 1210 return this 1211 } 1212 1213 API.addImage = function(imageData, format, x, y, w, h) { 1214 return this 1215 } 1216 1217 /** 1218 Sets font size for upcoming text elements. 1219 1220 @param {Number} size Font size in points. 1221 @function 1222 @returns {jsPDF} 1223 @name jsPDF.setFontSize 1224 */ 1225 API.setFontSize = function(size) { 1226 activeFontSize = size 1227 return this 1228 } 1229 1230 /** 1231 Sets text font face, variant for upcoming text elements. 1232 See output of jsPDF.getFontList() for possible font names, styles. 1233 1234 @param {String} fontName Font name or family. Example: "times" 1235 @param {String} fontStyle Font style or variant. Example: "italic" 1236 @function 1237 @returns {jsPDF} 1238 @name jsPDF.setFont 1239 */ 1240 API.setFont = function(fontName, fontStyle) { 1241 activeFontKey = getFont(fontName, fontStyle) 1242 // if font is not found, the above line blows up and we never go further 1243 return this 1244 } 1245 1246 /** 1247 Switches font style or variant for upcoming text elements, 1248 while keeping the font face or family same. 1249 See output of jsPDF.getFontList() for possible font names, styles. 1250 1251 @param {String} style Font style or variant. Example: "italic" 1252 @function 1253 @returns {jsPDF} 1254 @name jsPDF.setFontStyle 1255 */ 1256 API.setFontStyle = API.setFontType = function(style) { 1257 var undef 1258 activeFontKey = getFont(undef, style) 1259 // if font is not found, the above line blows up and we never go further 1260 return this 1261 } 1262 1263 /** 1264 Returns an object - a tree of fontName to fontStyle relationships available to 1265 active PDF document. 1266 1267 @public 1268 @function 1269 @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... } 1270 @name jsPDF.getFontList 1271 */ 1272 API.getFontList = function(){ 1273 // TODO: iterate over fonts array or return copy of fontmap instead in case more are ever added. 1274 var list = {} 1275 , fontName 1276 , fontStyle 1277 , tmp 1278 1279 for (fontName in fontmap) { 1280 if (fontmap.hasOwnProperty(fontName)) { 1281 list[fontName] = tmp = [] 1282 for (fontStyle in fontmap[fontName]){ 1283 if (fontmap[fontName].hasOwnProperty(fontStyle)) { 1284 tmp.push(fontStyle) 1285 } 1286 } 1287 } 1288 } 1289 1290 return list 1291 } 1292 1293 /** 1294 Sets line width for upcoming lines. 1295 1296 @param {Number} width Line width (in units declared at inception of PDF document) 1297 @function 1298 @returns {jsPDF} 1299 @name jsPDF.setLineWidth 1300 */ 1301 API.setLineWidth = function(width) { 1302 out((width * k).toFixed(2) + ' w') 1303 return this 1304 } 1305 1306 /** 1307 Sets the stroke color for upcoming elements. 1308 If only one, first argument is given, 1309 treats the value as gray-scale color value. 1310 1311 @param {Number} r Red channel color value in range 0-255 1312 @param {Number} g Green channel color value in range 0-255 1313 @param {Number} b Blue channel color value in range 0-255 1314 @function 1315 @returns {jsPDF} 1316 @name jsPDF.setDrawColor 1317 */ 1318 API.setDrawColor = function(r,g,b) { 1319 var color 1320 if ((r===0 && g===0 && b===0) || (typeof g === 'undefined')) { 1321 color = f3(r/255) + ' G' 1322 } else { 1323 color = [f3(r/255), f3(g/255), f3(b/255), 'RG'].join(' ') 1324 } 1325 out(color) 1326 return this 1327 } 1328 1329 /** 1330 Sets the fill color for upcoming elements. 1331 If only one, first argument is given, 1332 treats the value as gray-scale color value. 1333 1334 @param {Number} r Red channel color value in range 0-255 1335 @param {Number} g Green channel color value in range 0-255 1336 @param {Number} b Blue channel color value in range 0-255 1337 @function 1338 @returns {jsPDF} 1339 @name jsPDF.setFillColor 1340 */ 1341 API.setFillColor = function(r,g,b) { 1342 var color 1343 if ((r===0 && g===0 && b===0) || (typeof g === 'undefined')) { 1344 color = f3(r/255) + ' g' 1345 } else { 1346 color = [f3(r/255), f3(g/255), f3(b/255), 'rg'].join(' ') 1347 } 1348 out(color) 1349 return this 1350 } 1351 1352 /** 1353 Sets the text color for upcoming elements. 1354 If only one, first argument is given, 1355 treats the value as gray-scale color value. 1356 1357 @param {Number} r Red channel color value in range 0-255 1358 @param {Number} g Green channel color value in range 0-255 1359 @param {Number} b Blue channel color value in range 0-255 1360 @function 1361 @returns {jsPDF} 1362 @name jsPDF.setTextColor 1363 */ 1364 API.setTextColor = function(r,g,b) { 1365 if ((r===0 && g===0 && b===0) || (typeof g === 'undefined')) { 1366 textColor = f3(r/255) + ' g' 1367 } else { 1368 textColor = [f3(r/255), f3(g/255), f3(b/255), 'rg'].join(' ') 1369 } 1370 return this 1371 } 1372 1373 /** 1374 Is an Object providing a mapping from human-readable to 1375 integer flag values designating the varieties of line cap 1376 and join styles. 1377 1378 @returns {Object} 1379 @name jsPDF.CapJoinStyles 1380 */ 1381 API.CapJoinStyles = { 1382 0:0, 'butt':0, 'but':0, 'bevel':0 1383 , 1:1, 'round': 1, 'rounded':1, 'circle':1 1384 , 2:2, 'projecting':2, 'project':2, 'square':2, 'milter':2 1385 } 1386 1387 /** 1388 Sets the line cap styles 1389 See jsPDF.CapJoinStyles for variants 1390 1391 @param {String|Number} style A string or number identifying the type of line cap 1392 @function 1393 @returns {jsPDF} 1394 @name jsPDF.setLineCap 1395 */ 1396 API.setLineCap = function(style) { 1397 var undefined 1398 , id = this.CapJoinStyles[style] 1399 if (id === undefined) { 1400 throw new Error("Line cap style of '"+style+"' is not recognized. See or extend .CapJoinStyles property for valid styles") 1401 } 1402 lineCapID = id 1403 out(id.toString(10) + ' J') 1404 1405 return this 1406 } 1407 1408 /** 1409 Sets the line join styles 1410 See jsPDF.CapJoinStyles for variants 1411 1412 @param {String|Number} style A string or number identifying the type of line join 1413 @function 1414 @returns {jsPDF} 1415 @name jsPDF.setLineJoin 1416 */ 1417 API.setLineJoin = function(style) { 1418 var undefined 1419 , id = this.CapJoinStyles[style] 1420 if (id === undefined) { 1421 throw new Error("Line join style of '"+style+"' is not recognized. See or extend .CapJoinStyles property for valid styles") 1422 } 1423 lineJoinID = id 1424 out(id.toString(10) + ' j') 1425 1426 return this 1427 } 1428 1429 /** 1430 Generates the PDF document. 1431 Possible values: 1432 datauristring (alias dataurlstring) - Data-Url-formatted data returned as string. 1433 datauri (alias datauri) - Data-Url-formatted data pushed into current window's location (effectively reloading the window with contents of the PDF). 1434 1435 If `type` argument is undefined, output is raw body of resulting PDF returned as a string. 1436 1437 @param {String} type A string identifying one of the possible output types. 1438 @param {Object} options An object providing some additional signalling to PDF generator. 1439 @function 1440 @returns {jsPDF} 1441 @name jsPDF.output 1442 */ 1443 API.output = function(type, options) { 1444 var undef 1445 switch (type){ 1446 case undef: return buildDocument() 1447 case 'datauristring': 1448 case 'dataurlstring': 1449 return 'data:application/pdf;base64,' + btoa(buildDocument()) 1450 case 'datauri': 1451 case 'dataurl': 1452 document.location.href = 'data:application/pdf;base64,' + btoa(buildDocument()); break; 1453 default: throw new Error('Output type "'+type+'" is not supported.') 1454 } 1455 // @TODO: Add different output options 1456 } 1457 1458 // applying plugins (more methods) ON TOP of built-in API. 1459 // this is intentional as we allow plugins to override 1460 // built-ins 1461 for (var plugin in jsPDF.API){ 1462 if (jsPDF.API.hasOwnProperty(plugin)){ 1463 if (plugin === 'events' && jsPDF.API.events.length) { 1464 (function(events, newEvents){ 1465 1466 // jsPDF.API.events is a JS Array of Arrays 1467 // where each Array is a pair of event name, handler 1468 // Events were added by plugins to the jsPDF instantiator. 1469 // These are always added to the new instance and some ran 1470 // during instantiation. 1471 1472 var eventname, handler_and_args 1473 1474 for (var i = newEvents.length - 1; i !== -1; i--){ 1475 // subscribe takes 3 args: 'topic', function, runonce_flag 1476 // if undefined, runonce is false. 1477 // users can attach callback directly, 1478 // or they can attach an array with [callback, runonce_flag] 1479 // that's what the "apply" magic is for below. 1480 eventname = newEvents[i][0] 1481 handler_and_args = newEvents[i][1] 1482 events.subscribe.apply( 1483 events 1484 , [eventname].concat( 1485 typeof handler_and_args === 'function' ? 1486 [ handler_and_args ] : 1487 handler_and_args 1488 ) 1489 ) 1490 } 1491 })(events, jsPDF.API.events) 1492 } else { 1493 API[plugin] = jsPDF.API[plugin] 1494 } 1495 } 1496 } 1497 1498 ///////////////////////////////////////// 1499 // continuing initilisation of jsPDF Document object 1500 ///////////////////////////////////////// 1501 1502 1503 // Add the first page automatically 1504 addFonts() 1505 activeFontKey = 'F1' 1506 _addPage() 1507 1508 events.publish('initialized') 1509 1510 return API 1511 } 1512 1513 /** 1514 jsPDF.API is an object you can add methods and properties to. 1515 The methods / properties you add will show up in new jsPDF objects. 1516 1517 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. 1518 Examples: 1519 jsPDF.API.events['initialized'] = function(){ 'this' is API object } 1520 jsPDF.API.events['addFont'] = function(added_font_object){ 'this' is API object } 1521 1522 @static 1523 @public 1524 @memberOf jsPDF 1525 @name API 1526 1527 @example 1528 jsPDF.API.mymethod = function(){ 1529 // 'this' will be ref to internal API object. see jsPDF source 1530 // , so you can refer to built-in methods like so: 1531 // this.line(....) 1532 // this.text(....) 1533 } 1534 var pdfdoc = new jsPDF() 1535 pdfdoc.mymethod() // <- !!!!!! 1536 */ 1537 jsPDF.API = {'events':[]} 1538 1539 return jsPDF 1540 })() 1541