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