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 | ;
|
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));
|