highcharts_rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +106 -0
  9. data/Rakefile +6 -0
  10. data/highcharts_rails.gemspec +27 -0
  11. data/lib/highcharts_rails/version.rb +3 -0
  12. data/lib/highcharts_rails.rb +8 -0
  13. data/vendor/assets/javascripts/highcharts-3d.src.js +2139 -0
  14. data/vendor/assets/javascripts/highcharts-more.src.js +2982 -0
  15. data/vendor/assets/javascripts/highcharts.src.js +22947 -0
  16. data/vendor/assets/javascripts/js/highcharts-3d.src.js +2085 -0
  17. data/vendor/assets/javascripts/js/highcharts-more.src.js +2820 -0
  18. data/vendor/assets/javascripts/js/highcharts.src.js +20917 -0
  19. data/vendor/assets/javascripts/js/modules/accessibility.src.js +1072 -0
  20. data/vendor/assets/javascripts/js/modules/annotations.src.js +408 -0
  21. data/vendor/assets/javascripts/js/modules/boost.src.js +652 -0
  22. data/vendor/assets/javascripts/js/modules/broken-axis.src.js +338 -0
  23. data/vendor/assets/javascripts/js/modules/data.src.js +981 -0
  24. data/vendor/assets/javascripts/js/modules/drilldown.src.js +756 -0
  25. data/vendor/assets/javascripts/js/modules/exporting.src.js +953 -0
  26. data/vendor/assets/javascripts/js/modules/funnel.src.js +290 -0
  27. data/vendor/assets/javascripts/js/modules/gantt.src.js +791 -0
  28. data/vendor/assets/javascripts/js/modules/grid-axis.src.js +545 -0
  29. data/vendor/assets/javascripts/js/modules/heatmap.src.js +798 -0
  30. data/vendor/assets/javascripts/js/modules/no-data-to-display.src.js +150 -0
  31. data/vendor/assets/javascripts/js/modules/offline-exporting.src.js +492 -0
  32. data/vendor/assets/javascripts/js/modules/overlapping-datalabels.src.js +164 -0
  33. data/vendor/assets/javascripts/js/modules/series-label.src.js +606 -0
  34. data/vendor/assets/javascripts/js/modules/solid-gauge.src.js +305 -0
  35. data/vendor/assets/javascripts/js/modules/treemap.src.js +881 -0
  36. data/vendor/assets/javascripts/js/modules/xrange-series.src.js +254 -0
  37. data/vendor/assets/javascripts/js/themes/dark-blue.js +317 -0
  38. data/vendor/assets/javascripts/js/themes/dark-green.js +314 -0
  39. data/vendor/assets/javascripts/js/themes/dark-unica.js +243 -0
  40. data/vendor/assets/javascripts/js/themes/gray.js +326 -0
  41. data/vendor/assets/javascripts/js/themes/grid-light.js +99 -0
  42. data/vendor/assets/javascripts/js/themes/grid.js +131 -0
  43. data/vendor/assets/javascripts/js/themes/sand-signika.js +129 -0
  44. data/vendor/assets/javascripts/js/themes/skies.js +112 -0
  45. data/vendor/assets/javascripts/lib/canvg.src.js +3073 -0
  46. data/vendor/assets/javascripts/lib/jspdf.src.js +3031 -0
  47. data/vendor/assets/javascripts/lib/rgbcolor.src.js +299 -0
  48. data/vendor/assets/javascripts/lib/svg2pdf.src.js +1451 -0
  49. data/vendor/assets/javascripts/modules/accessibility.src.js +1072 -0
  50. data/vendor/assets/javascripts/modules/annotations.src.js +408 -0
  51. data/vendor/assets/javascripts/modules/boost.src.js +652 -0
  52. data/vendor/assets/javascripts/modules/broken-axis.src.js +338 -0
  53. data/vendor/assets/javascripts/modules/data.src.js +981 -0
  54. data/vendor/assets/javascripts/modules/drilldown.src.js +797 -0
  55. data/vendor/assets/javascripts/modules/exporting.src.js +882 -0
  56. data/vendor/assets/javascripts/modules/funnel.src.js +304 -0
  57. data/vendor/assets/javascripts/modules/gantt.src.js +815 -0
  58. data/vendor/assets/javascripts/modules/grid-axis.src.js +547 -0
  59. data/vendor/assets/javascripts/modules/heatmap.src.js +810 -0
  60. data/vendor/assets/javascripts/modules/no-data-to-display.src.js +161 -0
  61. data/vendor/assets/javascripts/modules/offline-exporting.src.js +492 -0
  62. data/vendor/assets/javascripts/modules/overlapping-datalabels.src.js +164 -0
  63. data/vendor/assets/javascripts/modules/series-label.src.js +606 -0
  64. data/vendor/assets/javascripts/modules/solid-gauge.src.js +316 -0
  65. data/vendor/assets/javascripts/modules/treemap.src.js +935 -0
  66. data/vendor/assets/javascripts/modules/xrange-series.src.js +276 -0
  67. data/vendor/assets/javascripts/themes/dark-blue.js +317 -0
  68. data/vendor/assets/javascripts/themes/dark-green.js +314 -0
  69. data/vendor/assets/javascripts/themes/dark-unica.js +243 -0
  70. data/vendor/assets/javascripts/themes/gray.js +326 -0
  71. data/vendor/assets/javascripts/themes/grid-light.js +99 -0
  72. data/vendor/assets/javascripts/themes/grid.js +131 -0
  73. data/vendor/assets/javascripts/themes/sand-signika.js +129 -0
  74. data/vendor/assets/javascripts/themes/skies.js +112 -0
  75. data/vendor/assets/stylesheets/highcharts.scss +610 -0
  76. metadata +161 -0
@@ -0,0 +1,3031 @@
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
+ */
58
+ var 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));