UNPKG

102 kBJavaScriptView Raw
1/** @preserve
2 * jsPDF - PDF Document creation from JavaScript
3 * Version ${versionID}
4 * CommitID ${commitID}
5 *
6 * Copyright (c) 2010-2014 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF
7 * 2010 Aaron Spike, https://github.com/acspike
8 * 2012 Willow Systems Corporation, willow-systems.com
9 * 2012 Pablo Hess, https://github.com/pablohess
10 * 2012 Florian Jenett, https://github.com/fjenett
11 * 2013 Warren Weckesser, https://github.com/warrenweckesser
12 * 2013 Youssef Beddad, https://github.com/lifof
13 * 2013 Lee Driscoll, https://github.com/lsdriscoll
14 * 2013 Stefan Slonevskiy, https://github.com/stefslon
15 * 2013 Jeremy Morel, https://github.com/jmorel
16 * 2013 Christoph Hartmann, https://github.com/chris-rock
17 * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
18 * 2014 James Makes, https://github.com/dollaruw
19 * 2014 Diego Casorran, https://github.com/diegocr
20 * 2014 Steven Spungin, https://github.com/Flamenco
21 * 2014 Kenneth Glassey, https://github.com/Gavvers
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining
24 * a copy of this software and associated documentation files (the
25 * "Software"), to deal in the Software without restriction, including
26 * without limitation the rights to use, copy, modify, merge, publish,
27 * distribute, sublicense, and/or sell copies of the Software, and to
28 * permit persons to whom the Software is furnished to do so, subject to
29 * the following conditions:
30 *
31 * The above copyright notice and this permission notice shall be
32 * included in all copies or substantial portions of the Software.
33 *
34 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
35 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
36 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
37 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
38 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
39 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
40 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 *
42 * Contributor(s):
43 * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango,
44 * kim3er, mfo, alnorth, Flamenco
45 */
46
47/**
48 * Creates new jsPDF document object instance.
49 *
50 * @class
51 * @param orientation One of "portrait" or "landscape" (or shortcuts "p" (Default), "l")
52 * @param unit Measurement unit to be used when coordinates are specified.
53 * One of "pt" (points), "mm" (Default), "cm", "in"
54 * @param format One of 'pageFormats' as shown below, default: a4
55 * @returns {jsPDF}
56 * @name jsPDF
57 */
58var jsPDF = (function(global) {
59 'use strict';
60 var pdfVersion = '1.3',
61 pageFormats = { // Size in pt of various paper formats
62 'a0' : [2383.94, 3370.39], 'a1' : [1683.78, 2383.94],
63 'a2' : [1190.55, 1683.78], 'a3' : [ 841.89, 1190.55],
64 'a4' : [ 595.28, 841.89], 'a5' : [ 419.53, 595.28],
65 'a6' : [ 297.64, 419.53], 'a7' : [ 209.76, 297.64],
66 'a8' : [ 147.40, 209.76], 'a9' : [ 104.88, 147.40],
67 'a10' : [ 73.70, 104.88], 'b0' : [2834.65, 4008.19],
68 'b1' : [2004.09, 2834.65], 'b2' : [1417.32, 2004.09],
69 'b3' : [1000.63, 1417.32], 'b4' : [ 708.66, 1000.63],
70 'b5' : [ 498.90, 708.66], 'b6' : [ 354.33, 498.90],
71 'b7' : [ 249.45, 354.33], 'b8' : [ 175.75, 249.45],
72 'b9' : [ 124.72, 175.75], 'b10' : [ 87.87, 124.72],
73 'c0' : [2599.37, 3676.54], 'c1' : [1836.85, 2599.37],
74 'c2' : [1298.27, 1836.85], 'c3' : [ 918.43, 1298.27],
75 'c4' : [ 649.13, 918.43], 'c5' : [ 459.21, 649.13],
76 'c6' : [ 323.15, 459.21], 'c7' : [ 229.61, 323.15],
77 'c8' : [ 161.57, 229.61], 'c9' : [ 113.39, 161.57],
78 'c10' : [ 79.37, 113.39], 'dl' : [ 311.81, 623.62],
79 'letter' : [612, 792],
80 'government-letter' : [576, 756],
81 'legal' : [612, 1008],
82 'junior-legal' : [576, 360],
83 'ledger' : [1224, 792],
84 'tabloid' : [792, 1224],
85 'credit-card' : [153, 243]
86 };
87
88 /**
89 * jsPDF's Internal PubSub Implementation.
90 * See mrrio.github.io/jsPDF/doc/symbols/PubSub.html
91 * Backward compatible rewritten on 2014 by
92 * Diego Casorran, https://github.com/diegocr
93 *
94 * @class
95 * @name PubSub
96 */
97 function PubSub(context) {
98 var topics = {};
99
100 this.subscribe = function(topic, callback, once) {
101 if(typeof callback !== 'function') {
102 return false;
103 }
104
105 if(!topics.hasOwnProperty(topic)) {
106 topics[topic] = {};
107 }
108
109 var id = Math.random().toString(35);
110 topics[topic][id] = [callback,!!once];
111
112 return id;
113 };
114
115 this.unsubscribe = function(token) {
116 for(var topic in topics) {
117 if(topics[topic][token]) {
118 delete topics[topic][token];
119 return true;
120 }
121 }
122 return false;
123 };
124
125 this.publish = function(topic) {
126 if(topics.hasOwnProperty(topic)) {
127 var args = Array.prototype.slice.call(arguments, 1), idr = [];
128
129 for(var id in topics[topic]) {
130 var sub = topics[topic][id];
131 try {
132 sub[0].apply(context, args);
133 } catch(ex) {
134 if(global.console) {
135 console.error('jsPDF PubSub Error', ex.message, ex);
136 }
137 }
138 if(sub[1]) idr.push(id);
139 }
140 if(idr.length) idr.forEach(this.unsubscribe);
141 }
142 };
143 }
144
145 /**
146 * @constructor
147 * @private
148 */
149 function jsPDF(orientation, unit, format, compressPdf) {
150 var options = {};
151
152 if (typeof orientation === 'object') {
153 options = orientation;
154
155 orientation = options.orientation;
156 unit = options.unit || unit;
157 format = options.format || format;
158 compressPdf = options.compress || options.compressPdf || compressPdf;
159 }
160
161 // Default options
162 unit = unit || 'mm';
163 format = format || 'a4';
164 orientation = ('' + (orientation || 'P')).toLowerCase();
165
166 var format_as_string = ('' + format).toLowerCase(),
167 compress = !!compressPdf && typeof Uint8Array === 'function',
168 textColor = options.textColor || '0 g',
169 drawColor = options.drawColor || '0 G',
170 activeFontSize = options.fontSize || 16,
171 lineHeightProportion = options.lineHeight || 1.15,
172 lineWidth = options.lineWidth || 0.200025, // 2mm
173 objectNumber = 2, // 'n' Current object number
174 outToPages = !1, // switches where out() prints. outToPages true = push to pages obj. outToPages false = doc builder content
175 offsets = [], // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes.
176 fonts = {}, // collection of font objects, where key is fontKey - a dynamically created label for a given font.
177 fontmap = {}, // mapping structure fontName > fontStyle > font key - performance layer. See addFont()
178 activeFontKey, // will be string representing the KEY of the font as combination of fontName + fontStyle
179
180 fontStateStack = [], //
181
182 patterns = {}, // collection of pattern objects
183 patternMap = {}, // see fonts
184
185 gStates = {}, // collection of graphic state objects
186 gStatesMap = {}, // see fonts
187 activeGState = null,
188
189 k, // Scale factor
190 tmp,
191 page = 0,
192 currentPage,
193 pages = [],
194 pagesContext = [], // same index as pages and pagedim
195 pagedim = [],
196 content = [],
197 additionalObjects = [],
198 lineCapID = 0,
199 lineJoinID = 0,
200 content_length = 0,
201
202 renderTargets = {},
203 renderTargetMap = {},
204 renderTargetStack = [],
205
206 pageX, pageY, pageMatrix, // only used for FormObjects
207 pageWidth,
208 pageHeight,
209 pageMode,
210 zoomMode,
211 layoutMode,
212 documentProperties = {
213 'title' : '',
214 'subject' : '',
215 'author' : '',
216 'keywords' : '',
217 'creator' : ''
218 },
219 API = {},
220 events = new PubSub(API),
221
222 /////////////////////
223 // Private functions
224 /////////////////////
225 f2 = function(number) {
226 return number.toFixed(2); // Ie, %.2f
227 },
228 f3 = function(number) {
229 return number.toFixed(3); // Ie, %.3f
230 },
231 padd2 = function(number) {
232 return ('0' + parseInt(number)).slice(-2);
233 },
234 padd2Hex = function (hexString) {
235 var s = "00" + hexString;
236 return s.substr(s.length - 2);
237 },
238 out = function(string) {
239 if (outToPages) {
240 /* set by beginPage */
241 pages[currentPage].push(string);
242 } else {
243 // +1 for '\n' that will be used to join 'content'
244 content_length += string.length + 1;
245 content.push(string);
246 }
247 },
248 newObject = function() {
249 // Begin a new object
250 objectNumber++;
251 offsets[objectNumber] = content_length;
252 out(objectNumber + ' 0 obj');
253 return objectNumber;
254 },
255 // Does not output the object until after the pages have been output.
256 // Returns an object containing the objectId and content.
257 // All pages have been added so the object ID can be estimated to start right after.
258 // This does not modify the current objectNumber; It must be updated after the newObjects are output.
259 newAdditionalObject = function() {
260 var objId = pages.length * 2 + 1;
261 objId += additionalObjects.length;
262 var obj = {objId:objId, content:''};
263 additionalObjects.push(obj);
264 return obj;
265 },
266 // Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data
267 newObjectDeferred = function() {
268 objectNumber++;
269 offsets[objectNumber] = function(){
270 return content_length;
271 };
272 return objectNumber;
273 },
274 newObjectDeferredBegin = function(oid) {
275 offsets[oid] = content_length;
276 },
277 putStream = function(str) {
278 out('stream');
279 out(str);
280 out('endstream');
281 },
282 putPages = function() {
283 var n,p,arr,i,deflater,adler32,adler32cs,wPt,hPt;
284
285 adler32cs = global.adler32cs || jsPDF.adler32cs;
286 if (compress && typeof adler32cs === 'undefined') {
287 compress = false;
288 }
289
290 // outToPages = false as set in endDocument(). out() writes to content.
291
292 for (n = 1; n <= page; n++) {
293 newObject();
294 wPt = (pageWidth = pagedim[n].width) * k;
295 hPt = (pageHeight = pagedim[n].height) * k;
296 out('<</Type /Page');
297 out('/Parent 1 0 R');
298 out('/Resources 2 0 R');
299 out('/MediaBox [0 0 ' + f2(wPt) + ' ' + f2(hPt) + ']');
300 // Added for annotation plugin
301 events.publish('putPage', {pageNumber: n, page: pages[n]});
302 out('/Contents ' + (objectNumber + 1) + ' 0 R');
303 out('>>');
304 out('endobj');
305
306 // Page content
307 p = pages[n].join('\n');
308
309 // prepend global change of basis matrix
310 // (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix
311 // that does this job for us (however, texts, images and similar objects must be drawn bottom up))
312 p = new Matrix(k, 0, 0, -k, 0, pageHeight).toString() + " cm\n" + p;
313
314 newObject();
315 if (compress) {
316 arr = [];
317 i = p.length;
318 while(i--) {
319 arr[i] = p.charCodeAt(i);
320 }
321 adler32 = adler32cs.from(p);
322 deflater = new Deflater(6);
323 deflater.append(new Uint8Array(arr));
324 p = deflater.flush();
325 arr = new Uint8Array(p.length + 6);
326 arr.set(new Uint8Array([120, 156]));
327 arr.set(p, 2);
328 arr.set(new Uint8Array([adler32 & 0xFF, (adler32 >> 8) & 0xFF, (adler32 >> 16) & 0xFF, (adler32 >> 24) & 0xFF]), p.length+2);
329 p = String.fromCharCode.apply(null, arr);
330 out('<</Length ' + p.length + ' /Filter [/FlateDecode]>>');
331 } else {
332 out('<</Length ' + p.length + '>>');
333 }
334 putStream(p);
335 out('endobj');
336 }
337 offsets[1] = content_length;
338 out('1 0 obj');
339 out('<</Type /Pages');
340 var kids = '/Kids [';
341 for (i = 0; i < page; i++) {
342 kids += (3 + 2 * i) + ' 0 R ';
343 }
344 out(kids + ']');
345 out('/Count ' + page);
346 out('>>');
347 out('endobj');
348 events.publish('postPutPages');
349 },
350 putFont = function(font) {
351 font.objectNumber = newObject();
352 out('<</BaseFont/' + font.PostScriptName + '/Type/Font');
353 if (typeof font.encoding === 'string') {
354 out('/Encoding/' + font.encoding);
355 }
356 out('/Subtype/Type1>>');
357 out('endobj');
358 },
359 putFonts = function() {
360 for (var fontKey in fonts) {
361 if (fonts.hasOwnProperty(fontKey)) {
362 putFont(fonts[fontKey]);
363 }
364 }
365 },
366 putXObject = function (xObject) {
367 xObject.objectNumber = newObject();
368 out("<<");
369 out("/Type /XObject");
370 out("/Subtype /Form");
371 out("/BBox [" + [
372 f2(xObject.x),
373 f2(xObject.y),
374 f2(xObject.x + xObject.width),
375 f2(xObject.y + xObject.height)
376 ].join(" ") + "]");
377 out("/Matrix [" + xObject.matrix.toString() + "]");
378 // TODO: /Resources
379
380 var p = xObject.pages[1].join("\n");
381 out("/Length " + p.length);
382
383 out(">>");
384 putStream(p);
385 out("endobj");
386 },
387 putXObjects = function () {
388 for (var xObjectKey in renderTargets) {
389 if (renderTargets.hasOwnProperty(xObjectKey)) {
390 putXObject(renderTargets[xObjectKey]);
391 }
392 }
393 },
394
395 interpolateAndEncodeRGBStream = function (colors, numberSamples) {
396 var tValues = [];
397 var t;
398 var dT = 1.0 / (numberSamples - 1);
399 for (t = 0.0; t < 1.0; t += dT) {
400 tValues.push(t);
401 }
402 tValues.push(1.0);
403
404 // add first and last control point if not present
405 if (colors[0].offset != 0.0) {
406 var c0 = {
407 offset: 0.0,
408 color: colors[0].color
409 };
410 colors.unshift(c0)
411 }
412 if (colors[colors.length - 1].offset != 1.0) {
413 var c1 = {
414 offset: 1.0,
415 color: colors[colors.length - 1].color
416 };
417 colors.push(c1);
418 }
419
420 var out = "";
421 var index = 0;
422
423 for (var i = 0; i < tValues.length; i++) {
424 t = tValues[i];
425
426 while (t > colors[index + 1].offset)
427 index++;
428
429 var a = colors[index].offset;
430 var b = colors[index + 1].offset;
431 var d = (t - a) / (b - a);
432
433 var aColor = colors[index].color;
434 var bColor = colors[index + 1].color;
435
436 out += padd2Hex((Math.round((1 - d) * aColor[0] + d * bColor[0])).toString(16))
437 + padd2Hex((Math.round((1 - d) * aColor[1] + d * bColor[1])).toString(16))
438 + padd2Hex((Math.round((1 - d) * aColor[2] + d * bColor[2])).toString(16));
439 }
440 return out.trim();
441 },
442 putShadingPattern = function (pattern, numberSamples) {
443 /*
444 Axial patterns shade between the two points specified in coords, radial patterns between the inner
445 and outer circle.
446
447 The user can specify an array (colors) that maps t-Values in [0, 1] to RGB colors. These are now
448 interpolated to equidistant samples and written to pdf as a sample (type 0) function.
449 */
450
451 // The number of color samples that should be used to describe the shading.
452 // The higher, the more accurate the gradient will be.
453 numberSamples || (numberSamples = 21);
454
455 var funcObjectNumber = newObject();
456 var stream = interpolateAndEncodeRGBStream(pattern.colors, numberSamples);
457 out("<< /FunctionType 0");
458 out("/Domain [0.0 1.0]");
459 out("/Size [" + numberSamples + "]");
460 out("/BitsPerSample 8");
461 out("/Range [0.0 1.0 0.0 1.0 0.0 1.0]");
462 out("/Decode [0.0 1.0 0.0 1.0 0.0 1.0]");
463 out("/Length " + stream.length);
464 // The stream is Hex encoded
465 out("/Filter /ASCIIHexDecode");
466 out(">>");
467 putStream(stream);
468 out("endobj");
469
470 pattern.objectNumber = newObject();
471 out("<< /ShadingType " + pattern.type);
472 out("/ColorSpace /DeviceRGB");
473
474 var coords = "/Coords ["
475 + f3(parseFloat(pattern.coords[0])) + " "// x1
476 + f3(parseFloat(pattern.coords[1])) + " "; // y1
477 if (pattern.type === 2) {
478 // axial
479 coords += f3(parseFloat(pattern.coords[2])) + " " // x2
480 + f3(parseFloat(pattern.coords[3])); // y2
481 } else {
482 // radial
483 coords += f3(parseFloat(pattern.coords[2])) + " "// r1
484 + f3(parseFloat(pattern.coords[3])) + " " // x2
485 + f3(parseFloat(pattern.coords[4])) + " " // y2
486 + f3(parseFloat(pattern.coords[5])); // r2
487 }
488 coords += "]";
489 out(coords);
490
491 if (pattern.matrix) {
492 out("/Matrix [" + pattern.matrix.toString() + "]");
493 }
494
495 out("/Function " + funcObjectNumber + " 0 R");
496 out("/Extend [true true]");
497 out(">>");
498 out("endobj");
499 },
500 putTilingPattern = function (pattern) {
501 var resourcesObjectNumber = newObject();
502 out("<<");
503 putResourceDictionary();
504 out(">>");
505 out("endobj");
506
507 pattern.objectNumber = newObject();
508 out("<< /Type /Pattern");
509 out("/PatternType 1"); // tiling pattern
510 out("/PaintType 1"); // colored tiling pattern
511 out("/TilingType 1"); // constant spacing
512 out("/BBox [" + pattern.boundingBox.map(f3).join(" ") + "]");
513 out("/XStep " + f3(pattern.xStep));
514 out("/YStep " + f3(pattern.yStep));
515 out("/Length " + pattern.stream.length);
516 out("/Resources " + resourcesObjectNumber + " 0 R"); // TODO: resources
517 pattern.matrix && out("/Matrix [" + pattern.matrix.toString() + "]");
518
519 out(">>");
520
521 putStream(pattern.stream);
522
523 out("endobj");
524 },
525 putPatterns = function () {
526 var patternKey;
527 for (patternKey in patterns) {
528 if (patterns.hasOwnProperty(patternKey)) {
529 if (patterns[patternKey] instanceof API.ShadingPattern) {
530 putShadingPattern(patterns[patternKey]);
531 } else if (patterns[patternKey] instanceof API.TilingPattern) {
532 putTilingPattern(patterns[patternKey]);
533 }
534 }
535 }
536 },
537
538 putGState = function (gState) {
539 gState.objectNumber = newObject();
540 out("<<");
541 for (var p in gState) {
542 switch (p) {
543 case "opacity":
544 out("/ca " + f2(gState[p]));
545 break;
546 }
547 }
548 out(">>");
549 out("endobj");
550 },
551 putGStates = function () {
552 var gStateKey;
553 for (gStateKey in gStates) {
554 if (gStates.hasOwnProperty(gStateKey)) {
555 putGState(gStates[gStateKey]);
556 }
557 }
558 },
559
560 putXobjectDict = function () {
561 for (var xObjectKey in renderTargets) {
562 if (renderTargets.hasOwnProperty(xObjectKey) && renderTargets[xObjectKey].objectNumber >= 0) {
563 out("/" + xObjectKey + " " + renderTargets[xObjectKey].objectNumber + " 0 R");
564 }
565 }
566
567 events.publish('putXobjectDict');
568 },
569
570 putShadingPatternDict = function () {
571 for (var patternKey in patterns) {
572 if (patterns.hasOwnProperty(patternKey) && patterns[patternKey] instanceof API.ShadingPattern && patterns[patternKey].objectNumber >= 0) {
573 out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R");
574 }
575 }
576
577 events.publish("putShadingPatternDict");
578 },
579
580 putTilingPatternDict = function () {
581 for (var patternKey in patterns) {
582 if (patterns.hasOwnProperty(patternKey) && patterns[patternKey] instanceof API.TilingPattern && patterns[patternKey].objectNumber >= 0) {
583 out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R");
584 }
585 }
586
587 events.publish("putTilingPatternDict");
588 },
589
590 putGStatesDict = function () {
591 var gStateKey;
592 for (gStateKey in gStates) {
593 if (gStates.hasOwnProperty(gStateKey) && gStates[gStateKey].objectNumber >= 0) {
594 out("/" + gStateKey + " " + gStates[gStateKey].objectNumber + " 0 R");
595 }
596 }
597
598 events.publish("putGStateDict");
599 },
600 putResourceDictionary = function() {
601 out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
602 out('/Font <<');
603 // Do this for each font, the '1' bit is the index of the font
604 for (var fontKey in fonts) {
605 if (fonts.hasOwnProperty(fontKey)) {
606 out('/' + fontKey + ' ' + fonts[fontKey].objectNumber + ' 0 R');
607 }
608 }
609 out('>>');
610
611 out("/Shading <<");
612 putShadingPatternDict();
613 out(">>");
614
615 out("/Pattern <<");
616 putTilingPatternDict();
617 out(">>");
618
619 out("/ExtGState <<");
620 putGStatesDict();
621 out('>>');
622
623 out('/XObject <<');
624 putXobjectDict();
625 out('>>');
626 },
627 putResources = function() {
628 putFonts();
629 putGStates();
630 putXObjects();
631 putPatterns();
632 events.publish('putResources');
633 // Resource dictionary
634 offsets[2] = content_length;
635 out('2 0 obj');
636 out('<<');
637 putResourceDictionary();
638 out('>>');
639 out('endobj');
640 events.publish('postPutResources');
641 },
642 putAdditionalObjects = function() {
643 events.publish('putAdditionalObjects');
644 for (var i=0; i<additionalObjects.length; i++){
645 var obj = additionalObjects[i];
646 offsets[obj.objId] = content_length;
647 out( obj.objId + ' 0 obj');
648 out(obj.content);
649 out('endobj');
650 }
651 objectNumber += additionalObjects.length;
652 events.publish('postPutAdditionalObjects');
653 },
654 addToFontDictionary = function(fontKey, fontName, fontStyle) {
655 // this is mapping structure for quick font key lookup.
656 // returns the KEY of the font (ex: "F1") for a given
657 // pair of font name and type (ex: "Arial". "Italic")
658 if (!fontmap.hasOwnProperty(fontName)) {
659 fontmap[fontName] = {};
660 }
661 fontmap[fontName][fontStyle] = fontKey;
662 },
663 /**
664 * FontObject describes a particular font as member of an instnace of jsPDF
665 *
666 * It's a collection of properties like 'id' (to be used in PDF stream),
667 * 'fontName' (font's family name), 'fontStyle' (font's style variant label)
668 *
669 * @public
670 * @property id {String} PDF-document-instance-specific label assinged to the font.
671 * @property PostScriptName {String} PDF specification full name for the font
672 * @property encoding {Object} Encoding_name-to-Font_metrics_object mapping.
673 * @name FontObject
674 */
675 addFont = function(PostScriptName, fontName, fontStyle, encoding) {
676 var fontKey = 'F' + (Object.keys(fonts).length + 1).toString(10),
677 // This is FontObject
678 font = fonts[fontKey] = {
679 'id' : fontKey,
680 'PostScriptName' : PostScriptName,
681 'fontName' : fontName,
682 'fontStyle' : fontStyle,
683 'encoding' : encoding,
684 'metadata' : {}
685 };
686 addToFontDictionary(fontKey, fontName, fontStyle);
687 events.publish('addFont', font);
688
689 return fontKey;
690 },
691 addFonts = function() {
692
693 var HELVETICA = "helvetica",
694 TIMES = "times",
695 COURIER = "courier",
696 NORMAL = "normal",
697 BOLD = "bold",
698 ITALIC = "italic",
699 BOLD_ITALIC = "bolditalic",
700 encoding = 'StandardEncoding',
701 ZAPF = "zapfdingbats",
702 standardFonts = [
703 ['Helvetica', HELVETICA, NORMAL],
704 ['Helvetica-Bold', HELVETICA, BOLD],
705 ['Helvetica-Oblique', HELVETICA, ITALIC],
706 ['Helvetica-BoldOblique', HELVETICA, BOLD_ITALIC],
707 ['Courier', COURIER, NORMAL],
708 ['Courier-Bold', COURIER, BOLD],
709 ['Courier-Oblique', COURIER, ITALIC],
710 ['Courier-BoldOblique', COURIER, BOLD_ITALIC],
711 ['Times-Roman', TIMES, NORMAL],
712 ['Times-Bold', TIMES, BOLD],
713 ['Times-Italic', TIMES, ITALIC],
714 ['Times-BoldItalic', TIMES, BOLD_ITALIC],
715 ['ZapfDingbats',ZAPF ]
716 ];
717
718 for (var i = 0, l = standardFonts.length; i < l; i++) {
719 var fontKey = addFont(
720 standardFonts[i][0],
721 standardFonts[i][1],
722 standardFonts[i][2],
723 encoding);
724
725 // adding aliases for standard fonts, this time matching the capitalization
726 var parts = standardFonts[i][0].split('-');
727 addToFontDictionary(fontKey, parts[0], parts[1] || '');
728 }
729 events.publish('addFonts', { fonts : fonts, dictionary : fontmap });
730 },
731 matrixMult = function (m1, m2) {
732 return new Matrix(
733 m1.a * m2.a + m1.b * m2.c,
734 m1.a * m2.b + m1.b * m2.d,
735 m1.c * m2.a + m1.d * m2.c,
736 m1.c * m2.b + m1.d * m2.d,
737 m1.e * m2.a + m1.f * m2.c + m2.e,
738 m1.e * m2.b + m1.f * m2.d + m2.f
739 );
740 },
741 Matrix = function (a, b, c, d, e, f) {
742 this.a = a;
743 this.b = b;
744 this.c = c;
745 this.d = d;
746 this.e = e;
747 this.f = f;
748 };
749
750 Matrix.prototype = {
751 toString: function () {
752 return [
753 f3(this.a),
754 f3(this.b),
755 f3(this.c),
756 f3(this.d),
757 f3(this.e),
758 f3(this.f)
759 ].join(" ");
760 }
761 };
762
763 var unitMatrix = new Matrix(1, 0, 0, 1, 0, 0),
764
765 // Used (1) to save the current stream state to the XObjects stack and (2) to save completed form
766 // objects in the xObjects map.
767 RenderTarget = function () {
768 this.page = page;
769 this.currentPage = currentPage;
770 this.pages = pages.slice(0);
771 this.pagedim = pagedim.slice(0);
772 this.pagesContext = pagesContext.slice(0);
773 this.x = pageX;
774 this.y = pageY;
775 this.matrix = pageMatrix;
776 this.width = pageWidth;
777 this.height = pageHeight;
778
779 this.id = ""; // set by endFormObject()
780 this.objectNumber = -1; // will be set by putXObject()
781 };
782
783 RenderTarget.prototype = {
784 restore: function () {
785 page = this.page;
786 currentPage = this.currentPage;
787 pagesContext = this.pagesContext;
788 pagedim = this.pagedim;
789 pages = this.pages;
790 pageX = this.x;
791 pageY = this.y;
792 pageMatrix = this.matrix;
793 pageWidth = this.width;
794 pageHeight = this.height;
795 }
796 };
797
798 var beginNewRenderTarget = function (x, y, width, height, matrix) {
799 // save current state
800 renderTargetStack.push(new RenderTarget());
801
802 // clear pages
803 page = currentPage = 0;
804 pages = [];
805 pageX = x;
806 pageY = y;
807
808 pageMatrix = matrix;
809
810 beginPage(width, height);
811 },
812
813 endFormObject = function (key) {
814 // only add it if it is not already present (the keys provided by the user must be unique!)
815 if (renderTargetMap[key])
816 return;
817
818 // save the created xObject
819 var newXObject = new RenderTarget();
820
821 var xObjectId = 'Xo' + (Object.keys(renderTargets).length + 1).toString(10);
822 newXObject.id = xObjectId;
823
824 renderTargetMap[key] = xObjectId;
825 renderTargets[xObjectId] = newXObject;
826
827 events.publish('addFormObject', newXObject);
828
829 // restore state from stack
830 renderTargetStack.pop().restore();
831 },
832
833 /**
834 * Adds a new pattern for later use.
835 * @param {String} key The key by it can be referenced later. The keys must be unique!
836 * @param {API.Pattern} pattern The pattern
837 */
838 addPattern = function (key, pattern) {
839 // only add it if it is not already present (the keys provided by the user must be unique!)
840 if (patternMap[key])
841 return;
842
843 var prefix = pattern instanceof API.ShadingPattern ? "Sh" : "P";
844 var patternKey = prefix + (Object.keys(patterns).length + 1).toString(10);
845 pattern.id = patternKey;
846
847 patternMap[key] = patternKey;
848 patterns[patternKey] = pattern;
849
850 events.publish('addPattern', pattern);
851 },
852
853 /**
854 * Adds a new Graphics State. Duplicates are automatically eliminated.
855 * @param {String} key Might also be null, if no later reference to this gState is needed
856 * @param {Object} gState The gState object
857 */
858 addGState = function (key, gState) {
859 // only add it if it is not already present (the keys provided by the user must be unique!)
860 if (key && gStatesMap[key])
861 return;
862
863 var duplicate = false;
864 for (var s in gStates) {
865 if (gStates.hasOwnProperty(s)) {
866 if (gStates[s].equals(gState)) {
867 duplicate = true;
868 break;
869 }
870 }
871 }
872
873 if (duplicate) {
874 gState = gStates[s];
875 } else {
876 var gStateKey = 'GS' + (Object.keys(gStates).length + 1).toString(10);
877 gStates[gStateKey] = gState;
878 gState.id = gStateKey;
879 }
880
881 // several user keys may point to the same GState object
882 key && (gStatesMap[key] = gState.id);
883
884 events.publish('addGState', gState);
885
886 return gState;
887 },
888 SAFE = function __safeCall(fn) {
889 fn.foo = function __safeCallWrapper() {
890 try {
891 return fn.apply(this, arguments);
892 } catch (e) {
893 var stack = e.stack || '';
894 if(~stack.indexOf(' at ')) stack = stack.split(" at ")[1];
895 var m = "Error in function " + stack.split("\n")[0].split('<')[0] + ": " + e.message;
896 if(global.console) {
897 global.console.error(m, e);
898 if(global.alert) alert(m);
899 } else {
900 throw new Error(m);
901 }
902 }
903 };
904 fn.foo.bar = fn;
905 return fn.foo;
906 },
907 to8bitStream = function(text, flags) {
908 /**
909 * PDF 1.3 spec:
910 * "For text strings encoded in Unicode, the first two bytes must be 254 followed by
911 * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts
912 * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely
913 * to be a meaningful beginning of a word or phrase.) The remainder of the
914 * string consists of Unicode character codes, according to the UTF-16 encoding
915 * specified in the Unicode standard, version 2.0. Commonly used Unicode values
916 * are represented as 2 bytes per character, with the high-order byte appearing first
917 * in the string."
918 *
919 * In other words, if there are chars in a string with char code above 255, we
920 * recode the string to UCS2 BE - string doubles in length and BOM is prepended.
921 *
922 * HOWEVER!
923 * Actual *content* (body) text (as opposed to strings used in document properties etc)
924 * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID)
925 *
926 * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have
927 * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could
928 * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode
929 * code page. There, however, all characters in the stream are treated as GIDs,
930 * including BOM, which is the reason we need to skip BOM in content text (i.e. that
931 * that is tied to a font).
932 *
933 * To signal this "special" PDFEscape / to8bitStream handling mode,
934 * API.text() function sets (unless you overwrite it with manual values
935 * given to API.text(.., flags) )
936 * flags.autoencode = true
937 * flags.noBOM = true
938 *
939 * ===================================================================================
940 * `flags` properties relied upon:
941 * .sourceEncoding = string with encoding label.
942 * "Unicode" by default. = encoding of the incoming text.
943 * pass some non-existing encoding name
944 * (ex: 'Do not touch my strings! I know what I am doing.')
945 * to make encoding code skip the encoding step.
946 * .outputEncoding = Either valid PDF encoding name
947 * (must be supported by jsPDF font metrics, otherwise no encoding)
948 * or a JS object, where key = sourceCharCode, value = outputCharCode
949 * missing keys will be treated as: sourceCharCode === outputCharCode
950 * .noBOM
951 * See comment higher above for explanation for why this is important
952 * .autoencode
953 * See comment higher above for explanation for why this is important
954 */
955
956 var i,l,sourceEncoding,encodingBlock,outputEncoding,newtext,isUnicode,ch,bch;
957
958 flags = flags || {};
959 sourceEncoding = flags.sourceEncoding || 'Unicode';
960 outputEncoding = flags.outputEncoding;
961
962 // This 'encoding' section relies on font metrics format
963 // attached to font objects by, among others,
964 // "Willow Systems' standard_font_metrics plugin"
965 // see jspdf.plugin.standard_font_metrics.js for format
966 // of the font.metadata.encoding Object.
967 // It should be something like
968 // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}}
969 // .widths = {0:width, code:width, ..., 'fof':divisor}
970 // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...}
971 if ((flags.autoencode || outputEncoding) &&
972 fonts[activeFontKey].metadata &&
973 fonts[activeFontKey].metadata[sourceEncoding] &&
974 fonts[activeFontKey].metadata[sourceEncoding].encoding) {
975 encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding;
976
977 // each font has default encoding. Some have it clearly defined.
978 if (!outputEncoding && fonts[activeFontKey].encoding) {
979 outputEncoding = fonts[activeFontKey].encoding;
980 }
981
982 // Hmmm, the above did not work? Let's try again, in different place.
983 if (!outputEncoding && encodingBlock.codePages) {
984 outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default
985 }
986
987 if (typeof outputEncoding === 'string') {
988 outputEncoding = encodingBlock[outputEncoding];
989 }
990 // we want output encoding to be a JS Object, where
991 // key = sourceEncoding's character code and
992 // value = outputEncoding's character code.
993 if (outputEncoding) {
994 isUnicode = false;
995 newtext = [];
996 for (i = 0, l = text.length; i < l; i++) {
997 ch = outputEncoding[text.charCodeAt(i)];
998 if (ch) {
999 newtext.push(
1000 String.fromCharCode(ch));
1001 } else {
1002 newtext.push(
1003 text[i]);
1004 }
1005
1006 // since we are looping over chars anyway, might as well
1007 // check for residual unicodeness
1008 if (newtext[i].charCodeAt(0) >> 8) {
1009 /* more than 255 */
1010 isUnicode = true;
1011 }
1012 }
1013 text = newtext.join('');
1014 }
1015 }
1016
1017 i = text.length;
1018 // isUnicode may be set to false above. Hence the triple-equal to undefined
1019 while (isUnicode === undefined && i !== 0) {
1020 if (text.charCodeAt(i - 1) >> 8) {
1021 /* more than 255 */
1022 isUnicode = true;
1023 }
1024 i--;
1025 }
1026 if (!isUnicode) {
1027 return text;
1028 }
1029
1030 newtext = flags.noBOM ? [] : [254, 255];
1031 for (i = 0, l = text.length; i < l; i++) {
1032 ch = text.charCodeAt(i);
1033 bch = ch >> 8; // divide by 256
1034 if (bch >> 8) {
1035 /* something left after dividing by 256 second time */
1036 throw new Error("Character at position " + i + " of string '"
1037 + text + "' exceeds 16bits. Cannot be encoded into UCS-2 BE");
1038 }
1039 newtext.push(bch);
1040 newtext.push(ch - (bch << 8));
1041 }
1042 return String.fromCharCode.apply(undefined, newtext);
1043 },
1044 pdfEscape = function(text, flags) {
1045 /**
1046 * Replace '/', '(', and ')' with pdf-safe versions
1047 *
1048 * Doing to8bitStream does NOT make this PDF display unicode text. For that
1049 * we also need to reference a unicode font and embed it - royal pain in the rear.
1050 *
1051 * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars,
1052 * which JavaScript Strings are happy to provide. So, while we still cannot display
1053 * 2-byte characters property, at least CONDITIONALLY converting (entire string containing)
1054 * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF
1055 * is still parseable.
1056 * This will allow immediate support for unicode in document properties strings.
1057 */
1058 return to8bitStream(text, flags).replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
1059 },
1060 putInfo = function() {
1061 out('/Producer (jsPDF ' + jsPDF.version + ')');
1062 for(var key in documentProperties) {
1063 if(documentProperties.hasOwnProperty(key) && documentProperties[key]) {
1064 out('/'+key.substr(0,1).toUpperCase() + key.substr(1)
1065 +' (' + pdfEscape(documentProperties[key]) + ')');
1066 }
1067 }
1068 var created = new Date(),
1069 tzoffset = created.getTimezoneOffset(),
1070 tzsign = tzoffset < 0 ? '+' : '-',
1071 tzhour = Math.floor(Math.abs(tzoffset / 60)),
1072 tzmin = Math.abs(tzoffset % 60),
1073 tzstr = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join('');
1074 out(['/CreationDate (D:',
1075 created.getFullYear(),
1076 padd2(created.getMonth() + 1),
1077 padd2(created.getDate()),
1078 padd2(created.getHours()),
1079 padd2(created.getMinutes()),
1080 padd2(created.getSeconds()), tzstr, ')'].join(''));
1081 },
1082 putCatalog = function() {
1083 out('/Type /Catalog');
1084 out('/Pages 1 0 R');
1085 // PDF13ref Section 7.2.1
1086 if (!zoomMode) zoomMode = 'fullwidth';
1087 switch(zoomMode) {
1088 case 'fullwidth' : out('/OpenAction [3 0 R /FitH null]'); break;
1089 case 'fullheight' : out('/OpenAction [3 0 R /FitV null]'); break;
1090 case 'fullpage' : out('/OpenAction [3 0 R /Fit]'); break;
1091 case 'original' : out('/OpenAction [3 0 R /XYZ null null 1]'); break;
1092 default:
1093 var pcn = '' + zoomMode;
1094 if (pcn.substr(pcn.length-1) === '%')
1095 zoomMode = parseInt(zoomMode) / 100;
1096 if (typeof zoomMode === 'number') {
1097 out('/OpenAction [3 0 R /XYZ null null '+f2(zoomMode)+']');
1098 }
1099 }
1100 if (!layoutMode) layoutMode = 'continuous';
1101 switch(layoutMode) {
1102 case 'continuous' : out('/PageLayout /OneColumn'); break;
1103 case 'single' : out('/PageLayout /SinglePage'); break;
1104 case 'two':
1105 case 'twoleft' : out('/PageLayout /TwoColumnLeft'); break;
1106 case 'tworight' : out('/PageLayout /TwoColumnRight'); break;
1107 }
1108 if (pageMode) {
1109 /**
1110 * A name object specifying how the document should be displayed when opened:
1111 * UseNone : Neither document outline nor thumbnail images visible -- DEFAULT
1112 * UseOutlines : Document outline visible
1113 * UseThumbs : Thumbnail images visible
1114 * FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible
1115 */
1116 out('/PageMode /' + pageMode);
1117 }
1118 events.publish('putCatalog');
1119 },
1120 putTrailer = function() {
1121 out('/Size ' + (objectNumber + 1));
1122 out('/Root ' + objectNumber + ' 0 R');
1123 out('/Info ' + (objectNumber - 1) + ' 0 R');
1124 },
1125 beginPage = function(width,height) {
1126 // Dimensions are stored as user units and converted to points on output
1127 var orientation = typeof height === 'string' && height.toLowerCase();
1128 if (typeof width === 'string') {
1129 var format = width.toLowerCase();
1130 if (pageFormats.hasOwnProperty(format)) {
1131 width = pageFormats[format][0] / k;
1132 height = pageFormats[format][1] / k;
1133 }
1134 }
1135 if (Array.isArray(width)) {
1136 height = width[1];
1137 width = width[0];
1138 }
1139 //if (orientation) {
1140 // switch(orientation.substr(0,1)) {
1141 // case 'l': if (height > width ) orientation = 's'; break;
1142 // case 'p': if (width > height ) orientation = 's'; break;
1143 // }
1144 // TODO: What is the reason for this (for me it only seems to raise bugs)?
1145 // if (orientation === 's') { tmp = width; width = height; height = tmp; }
1146 //}
1147 outToPages = true;
1148 pages[++page] = [];
1149 pagedim[page] = {
1150 width : Number(width) || pageWidth,
1151 height : Number(height) || pageHeight
1152 };
1153 pagesContext[page] = {};
1154 _setPage(page);
1155 },
1156 _addPage = function() {
1157 beginPage.apply(this, arguments);
1158 // Set line width
1159 out(f2(lineWidth) + ' w');
1160 // Set draw color
1161 out(drawColor);
1162 // resurrecting non-default line caps, joins
1163 if (lineCapID !== 0) {
1164 out(lineCapID + ' J');
1165 }
1166 if (lineJoinID !== 0) {
1167 out(lineJoinID + ' j');
1168 }
1169 events.publish('addPage', { pageNumber : page });
1170 },
1171 _deletePage = function( n ) {
1172 if (n > 0 && n <= page) {
1173 pages.splice(n, 1);
1174 pagedim.splice(n, 1);
1175 page--;
1176 if (currentPage > page){
1177 currentPage = page;
1178 }
1179 this.setPage(currentPage);
1180 }
1181 },
1182 _setPage = function(n) {
1183 if (n > 0 && n <= page) {
1184 currentPage = n;
1185 pageWidth = pagedim[n].width;
1186 pageHeight = pagedim[n].height;
1187 }
1188 },
1189 /**
1190 * Returns a document-specific font key - a label assigned to a
1191 * font name + font type combination at the time the font was added
1192 * to the font inventory.
1193 *
1194 * Font key is used as label for the desired font for a block of text
1195 * to be added to the PDF document stream.
1196 * @private
1197 * @function
1198 * @param {String} fontName can be undefined on "falthy" to indicate "use current"
1199 * @param {String} fontStyle can be undefined on "falthy" to indicate "use current"
1200 * @returns {String} Font key.
1201 */
1202 getFont = function(fontName, fontStyle) {
1203 var key;
1204
1205 fontName = fontName !== undefined ? fontName : fonts[activeFontKey].fontName;
1206 fontStyle = fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle;
1207
1208 if (fontName !== undefined){
1209 fontName = fontName.toLowerCase();
1210 }
1211 switch(fontName){
1212 case 'sans-serif':
1213 case 'verdana':
1214 case 'arial':
1215 case 'helvetica':
1216 fontName = 'helvetica';
1217 break;
1218 case 'fixed':
1219 case 'monospace':
1220 case 'terminal':
1221 case 'courier':
1222 fontName = 'courier';
1223 break;
1224 case 'serif':
1225 case 'cursive':
1226 case 'fantasy':
1227 default:
1228 fontName = 'times';
1229 break;
1230 }
1231
1232 try {
1233 // get a string like 'F3' - the KEY corresponding tot he font + type combination.
1234 key = fontmap[fontName][fontStyle];
1235 } catch (e) {}
1236
1237 if (!key) {
1238 //throw new Error("Unable to look up font label for font '" + fontName + "', '"
1239 //+ fontStyle + "'. Refer to getFontList() for available fonts.");
1240 key = fontmap['times'][fontStyle];
1241 if (key == null){
1242 key = fontmap['times']['normal'];
1243 }
1244 }
1245 return key;
1246 },
1247 buildDocument = function() {
1248
1249 outToPages = false; // switches out() to content
1250 objectNumber = 2;
1251 content = [];
1252 offsets = [];
1253 additionalObjects = [];
1254
1255 // putHeader()
1256 out('%PDF-' + pdfVersion);
1257
1258 putPages();
1259
1260 // Must happen after putPages
1261 // Modifies current object Id
1262 putAdditionalObjects();
1263
1264 putResources();
1265
1266 // Info
1267 newObject();
1268 out('<<');
1269 putInfo();
1270 out('>>');
1271 out('endobj');
1272
1273 // Catalog
1274 newObject();
1275 out('<<');
1276 putCatalog();
1277 out('>>');
1278 out('endobj');
1279
1280 // Cross-ref
1281 var o = content_length, i, p = "0000000000";
1282 out('xref');
1283 out('0 ' + (objectNumber + 1));
1284 out(p+' 65535 f ');
1285 for (i = 1; i <= objectNumber; i++) {
1286 var offset = offsets[i];
1287 if (typeof offset === 'function'){
1288 out((p + offsets[i]()).slice(-10) + ' 00000 n ');
1289 }else{
1290 out((p + offsets[i]).slice(-10) + ' 00000 n ');
1291 }
1292 }
1293 // Trailer
1294 out('trailer');
1295 out('<<');
1296 putTrailer();
1297 out('>>');
1298 out('startxref');
1299 out(o);
1300 out('%%EOF');
1301
1302 outToPages = true;
1303
1304 return content.join('\n');
1305 },
1306
1307 getStyle = function(style) {
1308 // see path-painting operators in PDF spec
1309 var op = 'n'; // none
1310 if (style === "D") {
1311 op = 'S'; // stroke
1312 } else if (style === 'F') {
1313 op = 'f'; // fill
1314 } else if (style === 'FD' || style === 'DF') {
1315 op = 'B'; // both
1316 } else if (style === 'f' || style === 'f*' || style === 'B' || style === 'B*') {
1317 /*
1318 Allow direct use of these PDF path-painting operators:
1319 - f fill using nonzero winding number rule
1320 - f* fill using even-odd rule
1321 - B fill then stroke with fill using non-zero winding number rule
1322 - B* fill then stroke with fill using even-odd rule
1323 */
1324 op = style;
1325 }
1326 return op;
1327 },
1328 // puts the style for the previously drawn path. If a patternKey is provided, the pattern is used to fill
1329 // the path. Use patternMatrix to transform the pattern to rhe right location.
1330 putStyle = function (style, patternKey, patternData) {
1331 style = getStyle(style);
1332
1333 // stroking / filling / both the path
1334 if (!patternKey) {
1335 out(style);
1336 return;
1337 }
1338
1339 patternData || (patternData = unitMatrix);
1340
1341 var patternId = patternMap[patternKey];
1342 var pattern = patterns[patternId];
1343
1344 if (pattern instanceof API.ShadingPattern) {
1345 out("q");
1346 out("W " + style);
1347
1348 if (pattern.gState) {
1349 API.setGState(pattern.gState);
1350 }
1351
1352 out(patternData.toString() + " cm");
1353 out("/" + patternId + " sh");
1354 out("Q");
1355 } else if (pattern instanceof API.TilingPattern) {
1356 // pdf draws patterns starting at the bottom left corner and they are not affected by the global transformation,
1357 // so we must flip them
1358 var matrix = new Matrix(1, 0, 0, -1, 0, pageHeight);
1359
1360 if (patternData.matrix) {
1361 matrix = matrixMult(patternData.matrix || unitMatrix, matrix);
1362
1363 // we cannot apply a matrix to the pattern on use so we must abuse the pattern matrix and create new instances
1364 // for each use
1365 patternId = pattern.createClone(patternKey, patternData.boundingBox, patternData.xStep, patternData.yStep, matrix).id;
1366 }
1367
1368 out("q");
1369 out("/Pattern cs");
1370 out("/" + patternId + " scn");
1371
1372 if (pattern.gState) {
1373 API.setGState(pattern.gState);
1374 }
1375
1376 out(style);
1377 out("Q");
1378 }
1379 },
1380
1381 getArrayBuffer = function() {
1382 var data = buildDocument(), len = data.length,
1383 ab = new ArrayBuffer(len), u8 = new Uint8Array(ab);
1384
1385 while(len--) u8[len] = data.charCodeAt(len);
1386 return ab;
1387 },
1388 getBlob = function() {
1389 return new Blob([getArrayBuffer()], { type : "application/pdf" });
1390 },
1391 /**
1392 * Generates the PDF document.
1393 *
1394 * If `type` argument is undefined, output is raw body of resulting PDF returned as a string.
1395 *
1396 * @param {String} type A string identifying one of the possible output types.
1397 * @param {Object} options An object providing some additional signalling to PDF generator.
1398 * @function
1399 * @returns {jsPDF}
1400 * @methodOf jsPDF#
1401 * @name output
1402 */
1403 output = SAFE(function(type, options) {
1404 var datauri = ('' + type).substr(0,6) === 'dataur'
1405 ? 'data:application/pdf;base64,'+btoa(buildDocument()):0;
1406
1407 switch (type) {
1408 case undefined:
1409 return buildDocument();
1410 case 'save':
1411 if (navigator.getUserMedia) {
1412 if (global.URL === undefined
1413 || global.URL.createObjectURL === undefined) {
1414 return API.output('dataurlnewwindow');
1415 }
1416 }
1417 saveAs(getBlob(), options);
1418 if(typeof saveAs.unload === 'function') {
1419 if(global.setTimeout) {
1420 setTimeout(saveAs.unload,911);
1421 }
1422 }
1423 break;
1424 case 'arraybuffer':
1425 return getArrayBuffer();
1426 case 'blob':
1427 return getBlob();
1428 case 'bloburi':
1429 case 'bloburl':
1430 // User is responsible of calling revokeObjectURL
1431 return global.URL && global.URL.createObjectURL(getBlob()) || void 0;
1432 case 'datauristring':
1433 case 'dataurlstring':
1434 return datauri;
1435 case 'dataurlnewwindow':
1436 var nW = global.open(datauri);
1437 if (nW || typeof safari === "undefined") return nW;
1438 /* pass through */
1439 case 'datauri':
1440 case 'dataurl':
1441 return global.document.location.href = datauri;
1442 default:
1443 throw new Error('Output type "' + type + '" is not supported.');
1444 }
1445 // @TODO: Add different output options
1446 });
1447
1448 switch (unit) {
1449 case 'pt': k = 1; break;
1450 case 'mm': k = 72 / 25.4000508; break;
1451 case 'cm': k = 72 / 2.54000508; break;
1452 case 'in': k = 72; break;
1453 case 'px': k = 96 / 72; break;
1454 case 'pc': k = 12; break;
1455 case 'em': k = 12; break;
1456 case 'ex': k = 6; break;
1457 default:
1458 throw ('Invalid unit: ' + unit);
1459 }
1460
1461 //---------------------------------------
1462 // Public API
1463
1464 /**
1465 * Object exposing internal API to plugins
1466 * @public
1467 */
1468 API.internal = {
1469 'pdfEscape' : pdfEscape,
1470 'getStyle' : getStyle,
1471 /**
1472 * Returns {FontObject} describing a particular font.
1473 * @public
1474 * @function
1475 * @param {String} fontName (Optional) Font's family name
1476 * @param {String} fontStyle (Optional) Font's style variation name (Example:"Italic")
1477 * @returns {FontObject}
1478 */
1479 'getFont' : function() {
1480 return fonts[getFont.apply(API, arguments)];
1481 },
1482 'getFontSize' : function() {
1483 return activeFontSize;
1484 },
1485 'getLineHeight' : function() {
1486 return activeFontSize * lineHeightProportion;
1487 },
1488 'write' : function(string1 /*, string2, string3, etc */) {
1489 out(arguments.length === 1 ? string1 : Array.prototype.join.call(arguments, ' '));
1490 },
1491 'getCoordinateString' : function(value) {
1492 return f2(value);
1493 },
1494 'getVerticalCoordinateString' : function(value) {
1495 return f2(value);
1496 },
1497 'collections' : {},
1498 'newObject' : newObject,
1499 'newAdditionalObject' : newAdditionalObject,
1500 'newObjectDeferred' : newObjectDeferred,
1501 'newObjectDeferredBegin' : newObjectDeferredBegin,
1502 'putStream' : putStream,
1503 'events' : events,
1504 // ratio that you use in multiplication of a given "size" number to arrive to 'point'
1505 // units of measurement.
1506 // scaleFactor is set at initialization of the document and calculated against the stated
1507 // default measurement units for the document.
1508 // If default is "mm", k is the number that will turn number in 'mm' into 'points' number.
1509 // through multiplication.
1510 'scaleFactor' : k,
1511 'pageSize' : {
1512 get width() {
1513 return pageWidth
1514 },
1515 get height() {
1516 return pageHeight
1517 }
1518 },
1519 'output' : function(type, options) {
1520 return output(type, options);
1521 },
1522 'getNumberOfPages' : function() {
1523 return pages.length - 1;
1524 },
1525 'pages' : pages,
1526 'out' : out,
1527 'f2' : f2,
1528 'getPageInfo' : function(pageNumberOneBased){
1529 var objId = (pageNumberOneBased - 1) * 2 + 3;
1530 return {objId:objId, pageNumber:pageNumberOneBased, pageContext:pagesContext[pageNumberOneBased]};
1531 },
1532 'getCurrentPageInfo' : function(){
1533 var objId = (currentPage - 1) * 2 + 3;
1534 return {objId:objId, pageNumber:currentPage, pageContext:pagesContext[currentPage]};
1535 },
1536 'getPDFVersion': function () {
1537 return pdfVersion;
1538 }
1539 };
1540
1541 /**
1542 * An object representing a pdf graphics state.
1543 * @param parameters A parameter object that contains all properties this graphics state wants to set.
1544 * Supported are: opacity
1545 * @constructor
1546 */
1547 API.GState = function (parameters) {
1548 var supported = "opacity";
1549 for (var p in parameters) {
1550 if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) {
1551 this[p] = parameters[p];
1552 }
1553 }
1554 this.id = ""; // set by addGState()
1555 this.objectNumber = -1; // will be set by putGState()
1556
1557 this.equals = function (other) {
1558 var ignore = "id,objectNumber,equals";
1559 if (!other || typeof other !== typeof this)
1560 return false;
1561 var count = 0;
1562 for (var p in this) {
1563 if (ignore.indexOf(p) >= 0)
1564 continue;
1565 if (this.hasOwnProperty(p) && !other.hasOwnProperty(p))
1566 return false;
1567 if (this[p] !== other[p])
1568 return false;
1569 count++;
1570 }
1571 for (var p in other) {
1572 if (other.hasOwnProperty(p) && ignore.indexOf(p) < 0)
1573 count--;
1574 }
1575 return count === 0;
1576 }
1577 };
1578
1579 /**
1580 * Adds a new {@link GState} for later use {@see setGState}.
1581 * @param {String} key
1582 * @param {GState} gState
1583 * @function
1584 * @returns {jsPDF}
1585 * @methodOf jsPDF#
1586 * @name addGState
1587 */
1588 API.addGState = function (key, gState) {
1589 addGState(key, gState);
1590 return this;
1591 };
1592
1593 /**
1594 * Adds (and transfers the focus to) new page to the PDF document.
1595 * @function
1596 * @returns {jsPDF}
1597 *
1598 * @methodOf jsPDF#
1599 * @name addPage
1600 */
1601 API.addPage = function() {
1602 _addPage.apply(this, arguments);
1603 return this;
1604 };
1605 API.setPage = function() {
1606 _setPage.apply(this, arguments);
1607 return this;
1608 };
1609 API.insertPage = function(beforePage) {
1610 this.addPage();
1611 this.movePage(currentPage, beforePage);
1612 return this;
1613 };
1614 API.movePage = function(targetPage, beforePage) {
1615 var tmpPagesContext, tmpPagedim, tmpPages, i;
1616 if (targetPage > beforePage){
1617 tmpPages = pages[targetPage];
1618 tmpPagedim = pagedim[targetPage];
1619 tmpPagesContext = pagesContext[targetPage];
1620 for (i = targetPage; i > beforePage; i--){
1621 pages[i] = pages[i-1];
1622 pagedim[i] = pagedim[i-1];
1623 pagesContext[i] = pagesContext[i-1];
1624 }
1625 pages[beforePage] = tmpPages;
1626 pagedim[beforePage] = tmpPagedim;
1627 pagesContext[beforePage] = tmpPagesContext;
1628 this.setPage(beforePage);
1629 } else if (targetPage < beforePage){
1630 tmpPages = pages[targetPage];
1631 tmpPagedim = pagedim[targetPage];
1632 tmpPagesContext = pagesContext[targetPage];
1633 for (i = targetPage; i < beforePage; i++){
1634 pages[i] = pages[i+1];
1635 pagedim[i] = pagedim[i+1];
1636 pagesContext[i] = pagesContext[i+1];
1637 }
1638 pages[beforePage] = tmpPages;
1639 pagedim[beforePage] = tmpPagedim;
1640 pagesContext[beforePage] = tmpPagesContext;
1641 this.setPage(beforePage);
1642 }
1643 return this;
1644 };
1645
1646 API.deletePage = function() {
1647 _deletePage.apply( this, arguments );
1648 return this;
1649 };
1650 API.setDisplayMode = function(zoom, layout, pmode) {
1651 zoomMode = zoom;
1652 layoutMode = layout;
1653 pageMode = pmode;
1654 return this;
1655 };
1656
1657 /**
1658 * Saves the current graphics state ("pushes it on the stack"). It can be restored by {@link restoreGraphicsState}
1659 * later. Here, the general pdf graphics state is meant, also including the current transformation matrix,
1660 * fill and stroke colors etc.
1661 * @function
1662 * @returns {jsPDF}
1663 * @methodOf jsPDF#
1664 * @name saveGraphicsState
1665 */
1666 API.saveGraphicsState = function () {
1667 out("q");
1668 // as we cannot set font key and size independently we must keep track of both
1669 fontStateStack.push({
1670 key: activeFontKey,
1671 size: activeFontSize
1672 });
1673 return this;
1674 };
1675
1676 /**
1677 * Restores a previously saved graphics state saved by {@link saveGraphicsState} ("pops the stack").
1678 * @function
1679 * @returns {jsPDF}
1680 * @methodOf jsPDF#
1681 * @name restoreGraphicsState
1682 */
1683 API.restoreGraphicsState = function () {
1684 out("Q");
1685
1686 // restore previous font state
1687 var fontState = fontStateStack.pop();
1688 activeFontKey = fontState.key;
1689 activeFontSize = fontState.size;
1690
1691 return this;
1692 };
1693
1694 /**
1695 * Appends this matrix to the left of all previously applied matrices.
1696 * @param {Matrix} matrix
1697 * @function
1698 * @returns {jsPDF}
1699 * @methodOf jsPDF#
1700 * @name setCurrentTransformationMatrix
1701 */
1702 API.setCurrentTransformationMatrix = function (matrix) {
1703 out(matrix.toString() + " cm");
1704 return this;
1705 };
1706
1707 /**
1708 * Starts a new pdf form object, which means that all conseequent draw calls target a new independent object
1709 * until {@link endFormObject} is called. The created object can be referenced and drawn later using
1710 * {@link doFormObject}. Nested form objects are possible.
1711 * x, y, width, height set the bounding box that is used to clip the content.
1712 * @param {number} x
1713 * @param {number} y
1714 * @param {number} width
1715 * @param {number} height
1716 * @param {Matrix} matrix The matrix that will be applied to convert the form objects coordinate system to
1717 * the parent's.
1718 * @function
1719 * @returns {jsPDF}
1720 * @methodOf jsPDF#
1721 */
1722 API.beginFormObject = function (x, y, width, height, matrix) {
1723 // The user can set the output target to a new form object. Nested form objects are possible.
1724 // Currently, they use the resource dictionary of the surrounding stream. This should be changed, as
1725 // the PDF-Spec states:
1726 // "In PDF 1.2 and later versions, form XObjects may be independent of the content streams in which
1727 // they appear, and this is strongly recommended although not requiredIn PDF 1.2 and later versions,
1728 // form XObjects may be independent of the content streams in which they appear, and this is strongly
1729 // recommended although not required"
1730 beginNewRenderTarget(x, y, width, height, matrix);
1731 return this;
1732 };
1733
1734 /**
1735 * Completes and saves the form object.
1736 * @param {String} key The key by which this form object can be referenced.
1737 * @function
1738 * @returns {jsPDF}
1739 * @methodOf jsPDF#
1740 * @name endFormObject
1741 */
1742 API.endFormObject = function (key) {
1743 endFormObject(key);
1744 return this;
1745 };
1746
1747 /**
1748 * Draws the specified form object by referencing to the respective pdf XObject created with
1749 * {@link API.beginFormObject} and {@link endFormObject}.
1750 * The location is determined by matrix.
1751 * @param {String} key The key to the form object.
1752 * @param {Matrix} matrix The matrix applied before drawing the form object.
1753 * @function
1754 * @returns {jsPDF}
1755 * @methodOf jsPDF#
1756 * @name doFormObject
1757 */
1758 API.doFormObject = function (key, matrix) {
1759 var xObject = renderTargets[renderTargetMap[key]];
1760 out("q");
1761 out(matrix.toString() + " cm");
1762 out("/" + xObject.id + " Do");
1763 out("Q");
1764 return this;
1765 };
1766
1767 /**
1768 * Returns the form object specified by key.
1769 * @param key {String}
1770 * @returns {{x: number, y: number, width: number, height: number, matrix: Matrix}}
1771 * @function
1772 * @returns {jsPDF}
1773 * @methodOf jsPDF#
1774 * @name getFormObject
1775 */
1776 API.getFormObject = function (key) {
1777 var xObject = renderTargets[renderTargetMap[key]];
1778 return {
1779 x: xObject.x,
1780 y: xObject.y,
1781 width: xObject.width,
1782 height: xObject.height,
1783 matrix: xObject.matrix
1784 };
1785 };
1786
1787 /**
1788 * A matrix object for 2D homogenous transformations:
1789 * | a b 0 |
1790 * | c d 0 |
1791 * | e f 1 |
1792 * pdf multiplies matrices righthand: v' = v x m1 x m2 x ...
1793 * @param {number} a
1794 * @param {number} b
1795 * @param {number} c
1796 * @param {number} d
1797 * @param {number} e
1798 * @param {number} f
1799 * @constructor
1800 */
1801 API.Matrix = Matrix;
1802
1803 /**
1804 * Multiplies two matrices. (see {@link Matrix})
1805 * @param {Matrix} m1
1806 * @param {Matrix} m2
1807 */
1808 API.matrixMult = matrixMult;
1809
1810 /**
1811 * The unit matrix (equal to new Matrix(1, 0, 0, 1, 0, 0).
1812 * @type {Matrix}
1813 */
1814 API.unitMatrix = unitMatrix;
1815
1816 var Pattern = function (gState, matrix) {
1817 this.gState = gState;
1818 this.matrix = matrix;
1819
1820 this.id = ""; // set by addPattern()
1821 this.objectNumber = -1; // will be set by putPattern()
1822 };
1823
1824 /**
1825 * A pattern describing a shading pattern.
1826 * @param {String} type One of "axial" or "radial"
1827 * @param {Array<Number>} coords Either [x1, y1, x2, y2] for "axial" type describing the two interpolation points
1828 * or [x1, y1, r, x2, y2, r2] for "radial" describing inner and the outer circle.
1829 * @param {Array<Object>} colors An array of objects with the fields "offset" and "color". "offset" describes
1830 * the offset in parameter space [0, 1]. "color" is an array of length 3 describing RGB values in [0, 255].
1831 * @param {GState=} gState An additional graphics state that gets applied to the pattern (optional).
1832 * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
1833 * and the use coordinate system (optional).
1834 * @constructor
1835 * @extends API.Pattern
1836 */
1837 API.ShadingPattern = function (type, coords, colors, gState, matrix) {
1838 // see putPattern() for information how they are realized
1839 this.type = type === "axial" ? 2 : 3;
1840 this.coords = coords;
1841 this.colors = colors;
1842
1843 Pattern.call(this, gState, matrix);
1844 };
1845
1846 /**
1847 * A PDF Tiling pattern.
1848 * @param {Array.<Number>} boundingBox The bounding box at which one pattern cell gets clipped.
1849 * @param {Number} xStep Horizontal spacing between pattern cells.
1850 * @param {Number} yStep Vertical spacing between pattern cells.
1851 * @param {API.GState=} gState An additional graphics state that gets applied to the pattern (optional).
1852 * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
1853 * and the use coordinate system (optional).
1854 * @constructor
1855 * @extends API.Pattern
1856 */
1857 API.TilingPattern = function (boundingBox, xStep, yStep, gState, matrix) {
1858 this.boundingBox = boundingBox;
1859 this.xStep = xStep;
1860 this.yStep = yStep;
1861
1862 this.stream = ""; // set by endTilingPattern();
1863
1864 this.cloneIndex = 0;
1865
1866 Pattern.call(this, gState, matrix);
1867 };
1868
1869 API.TilingPattern.prototype = {
1870 createClone: function (patternKey, boundingBox, xStep, yStep, matrix) {
1871 var clone = new API.TilingPattern(boundingBox || this.boundingBox, xStep || this.xStep, yStep || this.yStep,
1872 this.gState, matrix || this.matrix);
1873 clone.stream = this.stream;
1874 var key = patternKey + "$$" + this.cloneIndex++ + "$$";
1875 addPattern(key, clone);
1876 return clone;
1877 }
1878 };
1879
1880 /**
1881 * Adds a new {@link API.ShadingPattern} for later use.
1882 * @param {String} key
1883 * @param {Pattern} pattern
1884 * @function
1885 * @returns {jsPDF}
1886 * @methodOf jsPDF#
1887 * @name addPattern
1888 */
1889 API.addShadingPattern = function (key, pattern) {
1890 addPattern(key, pattern);
1891 return this;
1892 };
1893
1894 /**
1895 * Begins a new tiling pattern. All subsequent render calls are drawn to this pattern until {@link API.endTilingPattern}
1896 * gets called.
1897 * @param {API.Pattern} pattern
1898 */
1899 API.beginTilingPattern = function (pattern) {
1900 beginNewRenderTarget(pattern.boundingBox[0], pattern.boundingBox[1],
1901 pattern.boundingBox[2] - pattern.boundingBox[0], pattern.boundingBox[3] - pattern.boundingBox[1], pattern.matrix);
1902 };
1903
1904 /**
1905 * Ends a tiling pattern and sets the render target to the one active before {@link API.beginTilingPattern} has been called.
1906 * @param {string} key A unique key that is used to reference this pattern at later use.
1907 * @param {API.Pattern} pattern The pattern to end.
1908 */
1909 API.endTilingPattern = function (key, pattern) {
1910 // retrieve the stream
1911 pattern.stream = pages[currentPage].join("\n");
1912
1913 addPattern(key, pattern);
1914
1915 events.publish("endTilingPattern", pattern);
1916
1917 // restore state from stack
1918 renderTargetStack.pop().restore();
1919 };
1920
1921 /**
1922 * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings.
1923 *
1924 * @function
1925 * @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down
1926 * per font, spacing settings declared before this call.
1927 * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
1928 * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
1929 * @param {Object} flags Collection of settings signalling how the text must be encoded. Defaults are sane. If you
1930 * think you want to pass some flags, you likely can read the source.
1931 * @param {number|Matrix} transform If transform is a number the text will be rotated by this value. If it is a Matrix,
1932 * this matrix gets directly applied to the text, which allows shearing effects etc.
1933 * @param align {string}
1934 * @returns {jsPDF}
1935 * @methodOf jsPDF#
1936 */
1937 API.text = function(text, x, y, flags, transform, align) {
1938 /**
1939 * Inserts something like this into PDF
1940 * BT
1941 * /F1 16 Tf % Font name + size
1942 * 16 TL % How many units down for next line in multiline text
1943 * 0 g % color
1944 * 28.35 813.54 Td % position
1945 * (line one) Tj
1946 * T* (line two) Tj
1947 * T* (line three) Tj
1948 * ET
1949 */
1950 function ESC(s) {
1951 s = s.split("\t").join(Array(options.TabLen||9).join(" "));
1952 return pdfEscape(s, flags);
1953 }
1954
1955 // Pre-August-2012 the order of arguments was function(x, y, text, flags)
1956 // in effort to make all calls have similar signature like
1957 // function(data, coordinates... , miscellaneous)
1958 // this method had its args flipped.
1959 // code below allows backward compatibility with old arg order.
1960 if (typeof text === 'number') {
1961 var tmp = y;
1962 y = x;
1963 x = text;
1964 text = tmp;
1965 }
1966
1967 // If there are any newlines in text, we assume
1968 // the user wanted to print multiple lines, so break the
1969 // text up into an array. If the text is already an array,
1970 // we assume the user knows what they are doing.
1971 // Convert text into an array anyway to simplify
1972 // later code.
1973 if (typeof text === 'string') {
1974 if(text.match(/[\n\r]/)) {
1975 text = text.split( /\r\n|\r|\n/g);
1976 } else {
1977 text = [text];
1978 }
1979 }
1980 if (typeof transform === 'string') {
1981 align = transform;
1982 transform = null;
1983 }
1984 if (typeof flags === 'string') {
1985 align = flags;
1986 flags = null;
1987 }
1988 if (typeof flags === 'number') {
1989 transform = flags;
1990 flags = null;
1991 }
1992
1993 var todo;
1994 if (transform && typeof transform === "number") {
1995 transform *= (Math.PI / 180);
1996 var c = Math.cos(transform),
1997 s = Math.sin(transform);
1998 transform = new Matrix(c, s , -s, c, 0, 0);
1999 } else if (!transform) {
2000 transform = unitMatrix;
2001 }
2002
2003 flags = flags || {};
2004 if (!('noBOM' in flags))
2005 flags.noBOM = true;
2006 if (!('autoencode' in flags))
2007 flags.autoencode = true;
2008
2009 var strokeOption = '';
2010 var pageContext = this.internal.getCurrentPageInfo().pageContext;
2011 if (true === flags.stroke){
2012 if (pageContext.lastTextWasStroke !== true){
2013 strokeOption = '1 Tr\n';
2014 pageContext.lastTextWasStroke = true;
2015 }
2016 }
2017 else{
2018 if (pageContext.lastTextWasStroke){
2019 strokeOption = '0 Tr\n';
2020 }
2021 pageContext.lastTextWasStroke = false;
2022 }
2023
2024 if (typeof this._runningPageHeight === 'undefined'){
2025 this._runningPageHeight = 0;
2026 }
2027
2028 if (typeof text === 'string') {
2029 text = ESC(text);
2030 } else if (Object.prototype.toString.call(text) === '[object Array]') {
2031 // we don't want to destroy original text array, so cloning it
2032 var sa = text.concat(), da = [], len = sa.length;
2033 // we do array.join('text that must not be PDFescaped")
2034 // thus, pdfEscape each component separately
2035 while (len--) {
2036 da.push(ESC(sa.shift()));
2037 }
2038 var linesLeft = Math.ceil((y - this._runningPageHeight) / (activeFontSize * lineHeightProportion));
2039 if (0 <= linesLeft && linesLeft < da.length + 1) {
2040 //todo = da.splice(linesLeft-1);
2041 }
2042
2043 if( align ) {
2044 var left,
2045 prevX,
2046 maxLineLength,
2047 leading = activeFontSize * lineHeightProportion,
2048 lineWidths = text.map( function( v ) {
2049 return this.getStringUnitWidth( v ) * activeFontSize;
2050 }, this );
2051 maxLineLength = Math.max.apply( Math, lineWidths );
2052 // The first line uses the "main" Td setting,
2053 // and the subsequent lines are offset by the
2054 // previous line's x coordinate.
2055 if( align === "center" ) {
2056 // The passed in x coordinate defines
2057 // the center point.
2058 left = x - maxLineLength / 2;
2059 x -= lineWidths[0] / 2;
2060 } else if ( align === "right" ) {
2061 // The passed in x coordinate defines the
2062 // rightmost point of the text.
2063 left = x - maxLineLength;
2064 x -= lineWidths[0];
2065 } else {
2066 throw new Error('Unrecognized alignment option, use "center" or "right".');
2067 }
2068 prevX = x;
2069 text = da[0] + ") Tj\n";
2070 for ( i = 1, len = da.length ; i < len; i++ ) {
2071 var delta = maxLineLength - lineWidths[i];
2072 if( align === "center" ) delta /= 2;
2073 // T* = x-offset leading Td ( text )
2074 text += ( ( left - prevX ) + delta ) + " -" + leading + " Td (" + da[i];
2075 prevX = left + delta;
2076 if( i < len - 1 ) {
2077 text += ") Tj\n";
2078 }
2079 }
2080 } else {
2081 text = da.join(") Tj\nT* (");
2082 }
2083 } else {
2084 throw new Error('Type of text must be string or Array. "' + text + '" is not recognized.');
2085 }
2086 // Using "'" ("go next line and render text" mark) would save space but would complicate our rendering code, templates
2087
2088 // BT .. ET does NOT have default settings for Tf. You must state that explicitely every time for BT .. ET
2089 // if you want text transformation matrix (+ multiline) to work reliably (which reads sizes of things from font declarations)
2090 // Thus, there is NO useful, *reliable* concept of "default" font for a page.
2091 // The fact that "default" (reuse font used before) font worked before in basic cases is an accident
2092 // - readers dealing smartly with brokenness of jsPDF's markup.
2093
2094 var curY;
2095
2096 if (todo){
2097 //this.addPage();
2098 //this._runningPageHeight += y - (activeFontSize * 1.7);
2099 //curY = f2(activeFontSize * 1.7);
2100 } else {
2101 curY = f2(y);
2102 }
2103 //curY = f2(((y - this._runningPageHeight));
2104
2105// if (curY < 0){
2106// console.log('auto page break');
2107// this.addPage();
2108// this._runningPageHeight = y - (activeFontSize * 1.7);
2109// curY = f2(activeFontSize * 1.7);
2110// }
2111
2112 var translate = new Matrix(1, 0, 0, -1, x, curY);
2113 transform = matrixMult(translate, transform);
2114 var position = transform.toString() + " Tm";
2115
2116 out(
2117 'BT\n' +
2118 (activeFontSize * lineHeightProportion) + ' TL\n' + // line spacing
2119 strokeOption +// stroke option
2120 position + '\n(' +
2121 text +
2122 ') Tj\nET');
2123
2124 if (todo) {
2125 //this.text( todo, x, activeFontSize * 1.7);
2126 //this.text( todo, x, this._runningPageHeight + (activeFontSize * 1.7));
2127 this.text( todo, x, y);// + (activeFontSize * 1.7));
2128 }
2129
2130 return this;
2131 };
2132
2133
2134 API.lstext = function(text, x, y, spacing) {
2135 for (var i = 0, len = text.length ; i < len; i++, x += spacing) this.text(text[i], x, y);
2136 };
2137
2138 /**
2139 * Draw a line
2140 * @param {number} x1
2141 * @param {number} y1
2142 * @param {number} x2
2143 * @param {number} y2
2144 * @function
2145 * @returns {jsPDF}
2146 * @methodOf jsPDF#
2147 * @name line
2148 */
2149 API.line = function(x1, y1, x2, y2) {
2150 return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1], "D");
2151 };
2152
2153 API.clip = function() {
2154 // By patrick-roberts, github.com/MrRio/jsPDF/issues/328
2155 // Call .clip() after calling .rect() with a style argument of null
2156 out('W'); // clip
2157 out('S'); // stroke path; necessary for clip to work
2158 };
2159
2160
2161 /**
2162 * @typedef {Object} PatternData
2163 * {Matrix|undefined} matrix
2164 * {Number|undefined} xStep
2165 * {Number|undefined} yStep
2166 * {Array.<Number>|undefined} boundingBox
2167 */
2168
2169 /**
2170 * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates.
2171 * All data points in `lines` are relative to last line origin.
2172 * `x`, `y` become x1,y1 for first line / curve in the set.
2173 * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point.
2174 * 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.
2175 *
2176 * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, 10) // line, line, bezier curve, line
2177 * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves).
2178 * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
2179 * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
2180 * @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.
2181 * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
2182 * @param {Boolean} closed If true, the path is closed with a straight line from the end of the last curve to the starting point.
2183 * @param {String} patternKey The pattern key for the pattern that should be used to fill the path.
2184 * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
2185 * will modify the pattern on use.
2186 * @function
2187 * @returns {jsPDF}
2188 * @methodOf jsPDF#
2189 * @name lines
2190 */
2191 API.lines = function(lines, x, y, scale, style, closed, patternKey, patternData) {
2192 var scalex,scaley,i,l,leg,x2,y2,x3,y3,x4,y4;
2193
2194 // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style)
2195 // in effort to make all calls have similar signature like
2196 // function(content, coordinateX, coordinateY , miscellaneous)
2197 // this method had its args flipped.
2198 // code below allows backward compatibility with old arg order.
2199 if (typeof lines === 'number') {
2200 var tmp = y;
2201 y = x;
2202 x = lines;
2203 lines = tmp;
2204 }
2205
2206 scale = scale || [1, 1];
2207
2208 // starting point
2209 out(f3(x) + ' ' + f3(y) + ' m ');
2210
2211 scalex = scale[0];
2212 scaley = scale[1];
2213 l = lines.length;
2214 //, x2, y2 // bezier only. In page default measurement "units", *after* scaling
2215 //, x3, y3 // bezier only. In page default measurement "units", *after* scaling
2216 // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling
2217 x4 = x; // last / ending point = starting point for first item.
2218 y4 = y; // last / ending point = starting point for first item.
2219
2220 for (i = 0; i < l; i++) {
2221 leg = lines[i];
2222 if (leg.length === 2) {
2223 // simple line
2224 x4 = leg[0] * scalex + x4; // here last x4 was prior ending point
2225 y4 = leg[1] * scaley + y4; // here last y4 was prior ending point
2226 out(f3(x4) + ' ' + f3(y4) + ' l');
2227 } else {
2228 // bezier curve
2229 x2 = leg[0] * scalex + x4; // here last x4 is prior ending point
2230 y2 = leg[1] * scaley + y4; // here last y4 is prior ending point
2231 x3 = leg[2] * scalex + x4; // here last x4 is prior ending point
2232 y3 = leg[3] * scaley + y4; // here last y4 is prior ending point
2233 x4 = leg[4] * scalex + x4; // here last x4 was prior ending point
2234 y4 = leg[5] * scaley + y4; // here last y4 was prior ending point
2235 out(
2236 f3(x2) + ' ' +
2237 f3(y2) + ' ' +
2238 f3(x3) + ' ' +
2239 f3(y3) + ' ' +
2240 f3(x4) + ' ' +
2241 f3(y4) + ' c');
2242 }
2243 }
2244
2245 if (closed) {
2246 out('h');
2247 }
2248
2249 putStyle(style, patternKey, patternData);
2250
2251 return this;
2252 };
2253
2254 /**
2255 * Similar to {@link API.lines} but all coordinates are interpreted as absolute coordinates instead of relative.
2256 * @param {Array<Object>} lines An array of {op: operator, c: coordinates} object, where op is one of "m" (move to), "l" (line to)
2257 * "c" (cubic bezier curve) and "h" (close (sub)path)). c is an array of coordinates. "m" and "l" expect two, "c"
2258 * six and "h" an empty array (or undefined).
2259 * @param {String} style The style
2260 * @param {String} patternKey The pattern key for the pattern that should be used to fill the path.
2261 * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
2262 * will modify the pattern on use.
2263 * @function
2264 * @returns {jsPDF}
2265 * @methodOf jsPDF#
2266 * @name path
2267 */
2268 API.path = function (lines, style, patternKey, patternData) {
2269
2270 for (var i = 0; i < lines.length; i++) {
2271 var leg = lines[i];
2272 var coords = leg.c;
2273 switch (leg.op) {
2274 case "m":
2275 // move
2276 out(f3(coords[0]) + ' ' + f3(coords[1]) + ' m');
2277 break;
2278 case "l":
2279 // simple line
2280 out(f3(coords[0]) + ' ' + f3(coords[1]) + ' l');
2281 break;
2282 case "c":
2283 // bezier curve
2284 out([
2285 f3(coords[0]),
2286 f3(coords[1]),
2287 f3(coords[2]),
2288 f3(coords[3]),
2289 f3(coords[4]),
2290 f3(coords[5]),
2291 "c"
2292 ].join(" "));
2293 break;
2294 case "h":
2295 // close path
2296 out("h");
2297 }
2298
2299
2300 }
2301
2302 putStyle(style, patternKey, patternData);
2303
2304 return this;
2305 };
2306
2307 /**
2308 * Adds a rectangle to PDF
2309 *
2310 * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
2311 * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
2312 * @param {Number} w Width (in units declared at inception of PDF document)
2313 * @param {Number} h Height (in units declared at inception of PDF document)
2314 * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
2315 * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive.
2316 * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
2317 * will modify the pattern on use.
2318 * @function
2319 * @returns {jsPDF}
2320 * @methodOf jsPDF#
2321 * @name rect
2322 */
2323 API.rect = function(x, y, w, h, style, patternKey, patternData) {
2324 out([
2325 f2(x),
2326 f2(y),
2327 f2(w),
2328 f2(-h),
2329 're'
2330 ].join(' '));
2331
2332 putStyle(style, patternKey, patternData);
2333
2334 return this;
2335 };
2336
2337 /**
2338 * Adds a triangle to PDF
2339 *
2340 * @param {Number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page
2341 * @param {Number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page
2342 * @param {Number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page
2343 * @param {Number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page
2344 * @param {Number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page
2345 * @param {Number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page
2346 * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
2347 * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive.
2348 * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
2349 * will modify the pattern on use.
2350 * @function
2351 * @returns {jsPDF}
2352 * @methodOf jsPDF#
2353 * @name triangle
2354 */
2355 API.triangle = function(x1, y1, x2, y2, x3, y3, style, patternKey, patternData) {
2356 this.lines(
2357 [
2358 [x2 - x1, y2 - y1], // vector to point 2
2359 [x3 - x2, y3 - y2], // vector to point 3
2360 [x1 - x3, y1 - y3]// closing vector back to point 1
2361 ],
2362 x1,
2363 y1, // start of path
2364 [1, 1],
2365 style,
2366 true,
2367 patternKey,
2368 patternData
2369 );
2370 return this;
2371 };
2372
2373 /**
2374 * Adds a rectangle with rounded corners to PDF
2375 *
2376 * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
2377 * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
2378 * @param {Number} w Width (in units declared at inception of PDF document)
2379 * @param {Number} h Height (in units declared at inception of PDF document)
2380 * @param {Number} rx Radius along x axis (in units declared at inception of PDF document)
2381 * @param {Number} ry Radius along y axis (in units declared at inception of PDF document)
2382 * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
2383 * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive.
2384 * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
2385 * will modify the pattern on use.
2386 * @function
2387 * @returns {jsPDF}
2388 * @methodOf jsPDF#
2389 * @name roundedRect
2390 */
2391 API.roundedRect = function(x, y, w, h, rx, ry, style, patternKey, patternData) {
2392 var MyArc = 4 / 3 * (Math.SQRT2 - 1);
2393
2394 rx = Math.min(rx, w * 0.5);
2395 ry = Math.min(ry, h * 0.5);
2396
2397 this.lines(
2398 [
2399 [(w - 2 * rx), 0],
2400 [(rx * MyArc), 0, rx, ry - (ry * MyArc), rx, ry],
2401 [0, (h - 2 * ry)],
2402 [0, (ry * MyArc), - (rx * MyArc), ry, -rx, ry],
2403 [(-w + 2 * rx), 0],
2404 [ - (rx * MyArc), 0, -rx, - (ry * MyArc), -rx, -ry],
2405 [0, (-h + 2 * ry)],
2406 [0, - (ry * MyArc), (rx * MyArc), -ry, rx, -ry]
2407 ],
2408 x + rx,
2409 y, // start of path
2410 [1, 1],
2411 style,
2412 true,
2413 patternKey,
2414 patternData
2415 );
2416 return this;
2417 };
2418
2419 /**
2420 * Adds an ellipse to PDF
2421 *
2422 * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
2423 * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
2424 * @param {Number} rx Radius along x axis (in units declared at inception of PDF document)
2425 * @param {Number} ry Radius along y axis (in units declared at inception of PDF document)
2426 * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
2427 * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive.
2428 * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
2429 * will modify the pattern on use.
2430 * @function
2431 * @returns {jsPDF}
2432 * @methodOf jsPDF#
2433 * @name ellipse
2434 */
2435 API.ellipse = function(x, y, rx, ry, style, patternKey, patternData) {
2436 var lx = 4 / 3 * (Math.SQRT2 - 1) * rx,
2437 ly = 4 / 3 * (Math.SQRT2 - 1) * ry;
2438
2439 out([
2440 f2(x + rx),
2441 f2(y),
2442 'm',
2443 f2(x + rx),
2444 f2(y - ly),
2445 f2(x + lx),
2446 f2(y - ry),
2447 f2(x),
2448 f2(y - ry),
2449 'c'
2450 ].join(' '));
2451 out([
2452 f2(x - lx),
2453 f2(y - ry),
2454 f2(x - rx),
2455 f2(y - ly),
2456 f2(x - rx),
2457 f2(y),
2458 'c'
2459 ].join(' '));
2460 out([
2461 f2(x - rx),
2462 f2(y + ly),
2463 f2(x - lx),
2464 f2(y + ry),
2465 f2(x),
2466 f2(y + ry),
2467 'c'
2468 ].join(' '));
2469 out([
2470 f2(x + lx),
2471 f2(y + ry),
2472 f2(x + rx),
2473 f2(y + ly),
2474 f2(x + rx),
2475 f2(y),
2476 'c'
2477 ].join(' '));
2478
2479 putStyle(style, patternKey, patternData);
2480
2481 return this;
2482 };
2483
2484 /**
2485 * Adds an circle to PDF
2486 *
2487 * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
2488 * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
2489 * @param {Number} r Radius (in units declared at inception of PDF document)
2490 * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
2491 * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive.
2492 * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
2493 * will modify the pattern on use.
2494 * @function
2495 * @returns {jsPDF}
2496 * @methodOf jsPDF#
2497 * @name circle
2498 */
2499 API.circle = function(x, y, r, style, patternKey, patternData) {
2500 return this.ellipse(x, y, r, r, style, patternKey, patternData);
2501 };
2502
2503 /**
2504 * Adds a properties to the PDF document
2505 *
2506 * @param {Object} properties A property_name-to-property_value object structure.
2507 * @function
2508 * @returns {jsPDF}
2509 * @methodOf jsPDF#
2510 * @name setProperties
2511 */
2512 API.setProperties = function(properties) {
2513 // copying only those properties we can render.
2514 for (var property in documentProperties) {
2515 if (documentProperties.hasOwnProperty(property) && properties[property]) {
2516 documentProperties[property] = properties[property];
2517 }
2518 }
2519 return this;
2520 };
2521
2522 /**
2523 * Sets font size for upcoming text elements.
2524 *
2525 * @param {Number} size Font size in points.
2526 * @function
2527 * @returns {jsPDF}
2528 * @methodOf jsPDF#
2529 * @name setFontSize
2530 */
2531 API.setFontSize = function(size) {
2532 activeFontSize = size;
2533 out("/" + activeFontKey + " " + activeFontSize + " Tf");
2534 return this;
2535 };
2536
2537 API.getFontSize = function () {
2538 return activeFontSize;
2539 };
2540
2541 /**
2542 * Sets text font face, variant for upcoming text elements.
2543 * See output of jsPDF.getFontList() for possible font names, styles.
2544 *
2545 * @param {String} fontName Font name or family. Example: "times"
2546 * @param {String} fontStyle Font style or variant. Example: "italic"
2547 * @function
2548 * @returns {jsPDF}
2549 * @methodOf jsPDF#
2550 * @name setFont
2551 */
2552 API.setFont = function(fontName, fontStyle) {
2553 activeFontKey = getFont(fontName, fontStyle);
2554 // if font is not found, the above line blows up and we never go further
2555 out("/" + activeFontKey + " " + activeFontSize + " Tf");
2556 return this;
2557 };
2558
2559 /**
2560 * Switches font style or variant for upcoming text elements,
2561 * while keeping the font face or family same.
2562 * See output of jsPDF.getFontList() for possible font names, styles.
2563 *
2564 * @param {String} style Font style or variant. Example: "italic"
2565 * @function
2566 * @returns {jsPDF}
2567 * @methodOf jsPDF#
2568 * @name setFontStyle
2569 */
2570 API.setFontStyle = API.setFontType = function(style) {
2571 activeFontKey = getFont(undefined, style);
2572 // if font is not found, the above line blows up and we never go further
2573 return this;
2574 };
2575
2576 /**
2577 * Returns an object - a tree of fontName to fontStyle relationships available to
2578 * active PDF document.
2579 *
2580 * @public
2581 * @function
2582 * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... }
2583 * @methodOf jsPDF#
2584 * @name getFontList
2585 */
2586 API.getFontList = function() {
2587 // TODO: iterate over fonts array or return copy of fontmap instead in case more are ever added.
2588 var list = {},fontName,fontStyle,tmp;
2589
2590 for (fontName in fontmap) {
2591 if (fontmap.hasOwnProperty(fontName)) {
2592 list[fontName] = tmp = [];
2593 for (fontStyle in fontmap[fontName]) {
2594 if (fontmap[fontName].hasOwnProperty(fontStyle)) {
2595 tmp.push(fontStyle);
2596 }
2597 }
2598 }
2599 }
2600
2601 return list;
2602 };
2603
2604 /**
2605 * Add a custom font.
2606 *
2607 * @param {String} postScriptName name of the Font. Example: "Menlo-Regular"
2608 * @param {String} fontName of font-family from @font-face definition. Example: "Menlo Regular"
2609 * @param {String} fontStyle style. Example: "normal"
2610 * @function
2611 * @returns the {fontKey} (same as the internal method)
2612 * @methodOf jsPDF#
2613 * @name addFont
2614 */
2615 API.addFont = function(postScriptName, fontName, fontStyle) {
2616 addFont(postScriptName, fontName, fontStyle, 'StandardEncoding');
2617 };
2618
2619 /**
2620 * Sets line width for upcoming lines.
2621 *
2622 * @param {Number} width Line width (in units declared at inception of PDF document)
2623 * @function
2624 * @returns {jsPDF}
2625 * @methodOf jsPDF#
2626 * @name setLineWidth
2627 */
2628 API.setLineWidth = function(width) {
2629 out(width.toFixed(2) + ' w');
2630 return this;
2631 };
2632
2633 /**
2634 * Sets the stroke color for upcoming elements.
2635 *
2636 * Depending on the number of arguments given, Gray, RGB, or CMYK
2637 * color space is implied.
2638 *
2639 * When only ch1 is given, "Gray" color space is implied and it
2640 * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
2641 * if values are communicated as String types, or in range from 0 (black)
2642 * to 255 (white) if communicated as Number type.
2643 * The RGB-like 0-255 range is provided for backward compatibility.
2644 *
2645 * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
2646 * value must be in the range from 0.00 (minimum intensity) to to 1.00
2647 * (max intensity) if values are communicated as String types, or
2648 * from 0 (min intensity) to to 255 (max intensity) if values are communicated
2649 * as Number types.
2650 * The RGB-like 0-255 range is provided for backward compatibility.
2651 *
2652 * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
2653 * value must be a in the range from 0.00 (0% concentration) to to
2654 * 1.00 (100% concentration)
2655 *
2656 * Because JavaScript treats fixed point numbers badly (rounds to
2657 * floating point nearest to binary representation) it is highly advised to
2658 * communicate the fractional numbers as String types, not JavaScript Number type.
2659 *
2660 * @param {Number|String} ch1 Color channel value
2661 * @param {Number|String} ch2 Color channel value
2662 * @param {Number|String} ch3 Color channel value
2663 * @param {Number|String} ch4 Color channel value
2664 *
2665 * @function
2666 * @returns {jsPDF}
2667 * @methodOf jsPDF#
2668 * @name setDrawColor
2669 */
2670 API.setDrawColor = function(ch1, ch2, ch3, ch4) {
2671 var color;
2672 if (ch2 === undefined || (ch4 === undefined && ch1 === ch2 === ch3)) {
2673 // Gray color space.
2674 if (typeof ch1 === 'string') {
2675 color = ch1 + ' G';
2676 } else {
2677 color = f2(ch1 / 255) + ' G';
2678 }
2679 } else if (ch4 === undefined) {
2680 // RGB
2681 if (typeof ch1 === 'string') {
2682 color = [ch1, ch2, ch3, 'RG'].join(' ');
2683 } else {
2684 color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'RG'].join(' ');
2685 }
2686 } else {
2687 // CMYK
2688 if (typeof ch1 === 'string') {
2689 color = [ch1, ch2, ch3, ch4, 'K'].join(' ');
2690 } else {
2691 color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'K'].join(' ');
2692 }
2693 }
2694
2695 out(color);
2696 return this;
2697 };
2698
2699 /**
2700 * Sets the fill color for upcoming elements.
2701 *
2702 * Depending on the number of arguments given, Gray, RGB, or CMYK
2703 * color space is implied.
2704 *
2705 * When only ch1 is given, "Gray" color space is implied and it
2706 * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
2707 * if values are communicated as String types, or in range from 0 (black)
2708 * to 255 (white) if communicated as Number type.
2709 * The RGB-like 0-255 range is provided for backward compatibility.
2710 *
2711 * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
2712 * value must be in the range from 0.00 (minimum intensity) to to 1.00
2713 * (max intensity) if values are communicated as String types, or
2714 * from 0 (min intensity) to to 255 (max intensity) if values are communicated
2715 * as Number types.
2716 * The RGB-like 0-255 range is provided for backward compatibility.
2717 *
2718 * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
2719 * value must be a in the range from 0.00 (0% concentration) to to
2720 * 1.00 (100% concentration)
2721 *
2722 * Because JavaScript treats fixed point numbers badly (rounds to
2723 * floating point nearest to binary representation) it is highly advised to
2724 * communicate the fractional numbers as String types, not JavaScript Number type.
2725 *
2726 * @param {Number|String} ch1 Color channel value
2727 * @param {Number|String} ch2 Color channel value
2728 * @param {Number|String} ch3 Color channel value
2729 * @param {Number|String} ch4 Color channel value
2730 *
2731 * @function
2732 * @returns {jsPDF}
2733 * @methodOf jsPDF#
2734 * @name setFillColor
2735 */
2736 API.setFillColor = function(ch1, ch2, ch3, ch4) {
2737 var color;
2738
2739 if (ch2 === undefined || (ch4 === undefined && ch1 === ch2 === ch3)) {
2740 // Gray color space.
2741 if (typeof ch1 === 'string') {
2742 color = ch1 + ' g';
2743 } else {
2744 color = f2(ch1 / 255) + ' g';
2745 }
2746 } else if (ch4 === undefined || typeof ch4 === 'object') {
2747 // RGB
2748 if (typeof ch1 === 'string') {
2749 color = [ch1, ch2, ch3, 'rg'].join(' ');
2750 } else {
2751 color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'rg'].join(' ');
2752 }
2753 if (ch4 && ch4.a === 0){
2754 //TODO Implement transparency.
2755 //WORKAROUND use white for now
2756 color = ['255', '255', '255', 'rg'].join(' ');
2757 }
2758 } else {
2759 // CMYK
2760 if (typeof ch1 === 'string') {
2761 color = [ch1, ch2, ch3, ch4, 'k'].join(' ');
2762 } else {
2763 color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'k'].join(' ');
2764 }
2765 }
2766
2767 out(color);
2768 return this;
2769 };
2770
2771 /**
2772 * Sets the text color for upcoming elements.
2773 * If only one, first argument is given,
2774 * treats the value as gray-scale color value.
2775 *
2776 * @param {Number} r Red channel color value in range 0-255 or {String} r color value in hexadecimal, example: '#FFFFFF'
2777 * @param {Number} g Green channel color value in range 0-255
2778 * @param {Number} b Blue channel color value in range 0-255
2779 * @function
2780 * @returns {jsPDF}
2781 * @methodOf jsPDF#
2782 * @name setTextColor
2783 */
2784 API.setTextColor = function(r, g, b) {
2785 if ((typeof r === 'string') && /^#[0-9A-Fa-f]{6}$/.test(r)) {
2786 var hex = parseInt(r.substr(1), 16);
2787 r = (hex >> 16) & 255;
2788 g = (hex >> 8) & 255;
2789 b = (hex & 255);
2790 }
2791
2792 if ((r === 0 && g === 0 && b === 0) || (typeof g === 'undefined')) {
2793 textColor = f3(r / 255) + ' g';
2794 } else {
2795 textColor = [f3(r / 255), f3(g / 255), f3(b / 255), 'rg'].join(' ');
2796 }
2797
2798 out(textColor);
2799
2800 return this;
2801 };
2802
2803 /**
2804 * Sets a either previously added {@link GState} (via {@link addGState}) or a new {@link GState}.
2805 * @param {String|GState} gState If type is string, a previously added GState is used, if type is GState
2806 * it will be added before use.
2807 * @function
2808 * @returns {jsPDF}
2809 * @methodOf jsPDF#
2810 * @name setGState
2811 */
2812 API.setGState = function (gState) {
2813 if (typeof gState === "string") {
2814 gState = gStates[gStatesMap[gState]];
2815 } else {
2816 gState = addGState(null, gState);
2817 }
2818
2819 if (!gState.equals(activeGState)) {
2820 out("/" + gState.id + " gs");
2821 activeGState = gState;
2822 }
2823 };
2824
2825 /**
2826 * Is an Object providing a mapping from human-readable to
2827 * integer flag values designating the varieties of line cap
2828 * and join styles.
2829 *
2830 * @returns {Object}
2831 * @fieldOf jsPDF#
2832 * @name CapJoinStyles
2833 */
2834 API.CapJoinStyles = {
2835 0 : 0,
2836 'butt' : 0,
2837 'but' : 0,
2838 'miter' : 0,
2839 1 : 1,
2840 'round' : 1,
2841 'rounded' : 1,
2842 'circle' : 1,
2843 2 : 2,
2844 'projecting' : 2,
2845 'project' : 2,
2846 'square' : 2,
2847 'bevel' : 2
2848 };
2849
2850 /**
2851 * Sets the line cap styles
2852 * See {jsPDF.CapJoinStyles} for variants
2853 *
2854 * @param {String|Number} style A string or number identifying the type of line cap
2855 * @function
2856 * @returns {jsPDF}
2857 * @methodOf jsPDF#
2858 * @name setLineCap
2859 */
2860 API.setLineCap = function(style) {
2861 var id = this.CapJoinStyles[style];
2862 if (id === undefined) {
2863 throw new Error("Line cap style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles");
2864 }
2865 lineCapID = id;
2866 out(id + ' J');
2867
2868 return this;
2869 };
2870
2871 /**
2872 * Sets the line join styles
2873 * See {jsPDF.CapJoinStyles} for variants
2874 *
2875 * @param {String|Number} style A string or number identifying the type of line join
2876 * @function
2877 * @returns {jsPDF}
2878 * @methodOf jsPDF#
2879 * @name setLineJoin
2880 */
2881 API.setLineJoin = function(style) {
2882 var id = this.CapJoinStyles[style];
2883 if (id === undefined) {
2884 throw new Error("Line join style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles");
2885 }
2886 lineJoinID = id;
2887 out(id + ' j');
2888
2889 return this;
2890 };
2891
2892 /**
2893 * Sets the miter limit.
2894 * @param {number} miterLimit
2895 * @function
2896 * @returns {jsPDF}
2897 * @methodOf jsPDF#
2898 * @name setMiterLimit
2899 */
2900 API.setLineMiterLimit = function (miterLimit) {
2901 out(f2(miterLimit) + " M");
2902
2903 return this;
2904 };
2905
2906 /**
2907 * Sets the line dash pattern.
2908 * @param {Array<number>} array An array containing 0-2 numbers. The first number sets the length of the
2909 * dashes, the second number the length of the gaps. If the second number is missing, the gaps are considered
2910 * to be as long as the dashes. An empty array means solid, unbroken lines.
2911 * @param phase The phase lines start with.
2912 * @function
2913 * @returns {jsPDF}
2914 * @methodOf jsPDF#
2915 * @name setLineDashPattern
2916 */
2917 API.setLineDashPattern = function (array, phase) {
2918 out([
2919 "[" + (array[0] !== undefined ? array[0] : ""),
2920 (array[1] !== undefined ? array[1] : "" ) + "]",
2921 phase,
2922 "d"
2923 ].join(" "));
2924
2925 return this;
2926 };
2927
2928 // Output is both an internal (for plugins) and external function
2929 API.output = output;
2930
2931 /**
2932 * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf')
2933 * @param {String} filename The filename including extension.
2934 *
2935 * @function
2936 * @returns {jsPDF}
2937 * @methodOf jsPDF#
2938 * @name save
2939 */
2940 API.save = function(filename) {
2941 API.output('save', filename);
2942 };
2943
2944 // applying plugins (more methods) ON TOP of built-in API.
2945 // this is intentional as we allow plugins to override
2946 // built-ins
2947 for (var plugin in jsPDF.API) {
2948 if (jsPDF.API.hasOwnProperty(plugin)) {
2949 if (plugin === 'events' && jsPDF.API.events.length) {
2950 (function(events, newEvents) {
2951
2952 // jsPDF.API.events is a JS Array of Arrays
2953 // where each Array is a pair of event name, handler
2954 // Events were added by plugins to the jsPDF instantiator.
2955 // These are always added to the new instance and some ran
2956 // during instantiation.
2957 var eventname,handler_and_args,i;
2958
2959 for (i = newEvents.length - 1; i !== -1; i--) {
2960 // subscribe takes 3 args: 'topic', function, runonce_flag
2961 // if undefined, runonce is false.
2962 // users can attach callback directly,
2963 // or they can attach an array with [callback, runonce_flag]
2964 // that's what the "apply" magic is for below.
2965 eventname = newEvents[i][0];
2966 handler_and_args = newEvents[i][1];
2967 events.subscribe.apply(
2968 events,
2969 [eventname].concat(
2970 typeof handler_and_args === 'function' ?
2971 [handler_and_args] : handler_and_args));
2972 }
2973 }(events, jsPDF.API.events));
2974 } else {
2975 API[plugin] = jsPDF.API[plugin];
2976 }
2977 }
2978 }
2979
2980 //////////////////////////////////////////////////////
2981 // continuing initialization of jsPDF Document object
2982 //////////////////////////////////////////////////////
2983 // Add the first page automatically
2984 addFonts();
2985 activeFontKey = 'F1';
2986 _addPage(format, orientation);
2987
2988 events.publish('initialized');
2989 return API;
2990 }
2991
2992 /**
2993 * jsPDF.API is a STATIC property of jsPDF class.
2994 * jsPDF.API is an object you can add methods and properties to.
2995 * The methods / properties you add will show up in new jsPDF objects.
2996 *
2997 * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics,
2998 * callbacks to this object. These will be reassigned to all new instances of jsPDF.
2999 * Examples:
3000 * jsPDF.API.events['initialized'] = function(){ 'this' is API object }
3001 * jsPDF.API.events['addFont'] = function(added_font_object){ 'this' is API object }
3002 *
3003 * @static
3004 * @public
3005 * @memberOf jsPDF
3006 * @name API
3007 *
3008 * @example
3009 * jsPDF.API.mymethod = function(){
3010 * // 'this' will be ref to internal API object. see jsPDF source
3011 * // , so you can refer to built-in methods like so:
3012 * // this.line(....)
3013 * // this.text(....)
3014 * }
3015 * var pdfdoc = new jsPDF()
3016 * pdfdoc.mymethod() // <- !!!!!!
3017 */
3018 jsPDF.API = {events:[]};
3019 jsPDF.version = "1.0.0-trunk";
3020
3021 if (typeof define === 'function' && define.amd) {
3022 define('jsPDF', function() {
3023 return jsPDF;
3024 });
3025 } else if (typeof module !== 'undefined' && module.exports) {
3026 module.exports = jsPDF;
3027 } else {
3028 global.jsPDF = jsPDF;
3029 }
3030 return jsPDF;
3031}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this));